Advertisement
AlexCatze2005

stem.lua

Feb 24th, 2021
985
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 6.38 KB | None | 0 0
  1. local event = require("event")
  2. local computer = require("computer")
  3. local com = require("component")
  4.  
  5. local internet = com.internet
  6. local stem = {}
  7.  
  8. -- Constants
  9. --------------------------------------------------------------------
  10.  
  11. local ADDRESS = "stem.fomalhaut.me"
  12. local PORT = 5733
  13. local Package = {
  14.   MESSAGE = 0,
  15.   SUBSCRIBE = 1,
  16.   UNSUBSCRIBE = 2,
  17.   PING = 3,
  18.   PONG = 4
  19. }
  20. local PING_TIMEOUT = 5
  21.  
  22. -- Server level API
  23. --------------------------------------------------------------------
  24.  
  25. local server_api = {
  26.   __address = nil,
  27.   __port = nil,
  28.   __socket = nil,
  29.   __channels = {}, -- list of channels this server is subscribed to
  30.   __stream = "", -- the string which plays the role of bytearray for incoming data
  31.  
  32.   __build_package = function(type, id, message)
  33.     local package = string.char(type)
  34.     if type == Package.PING or type == Package.PONG then
  35.       -- ping/pong content takes place of the `id` argument here
  36.       package = package .. id
  37.     else
  38.       package = package .. string.char(#id) .. id
  39.       if message ~= nil then
  40.         package = package .. message
  41.       end
  42.     end
  43.     local len = #package
  44.     package = string.char(math.floor(len / 256), len % 256) .. package
  45.     return package
  46.   end,
  47.  
  48.   isSubscribed = function(self, id)
  49.     return self.__channels[id]
  50.   end,
  51.  
  52.   send = function(self, id, message)
  53.     if self:isConnected() then
  54.       local data = self.__build_package(Package.MESSAGE, id, message)
  55.       local sent = 0
  56.       repeat
  57.         local result, message = self.__socket.write(data:sub(sent + 1))
  58.         if not result then return nil, message end
  59.         sent = sent + result
  60.       until sent == #data
  61.       return true
  62.     else
  63.       return nil, "not connected"
  64.     end
  65.   end,
  66.  
  67.   subscribe = function(self, id)
  68.     if self:isConnected() then
  69.       self.__socket.write(self.__build_package(Package.SUBSCRIBE, id))
  70.       self.__channels[id] = true
  71.       return true
  72.     else
  73.       return nil, "not connected"
  74.     end
  75.   end,
  76.  
  77.   unsubscribe = function(self, id)
  78.     if self:isConnected() then
  79.       self.__socket.write(self.__build_package(Package.UNSUBSCRIBE, id))
  80.       self.__channels[id] = false
  81.       return true
  82.     else
  83.       return nil, "not connected"
  84.     end
  85.   end,
  86.  
  87.   ping = function(self, content, timeout)
  88.     -- send ping request
  89.     self.__socket.write(self.__build_package(Package.PING, content))
  90.     -- wait for response
  91.     local time = os.time()
  92.     local duration = timeout or PING_TIMEOUT
  93.     while true do
  94.       local name, data = event.pull(duration, "stem_pong")
  95.       if name == "stem_pong" then
  96.         return data == content
  97.       else
  98.         local passed = os.time() - time
  99.         if passed >= duration * 20 then
  100.           return false
  101.         else
  102.           duration = (timeout or PING_TIMEOUT) - (passed / 20)
  103.         end
  104.       end
  105.     end
  106.   end,
  107.  
  108.   isConnected = function(self)
  109.     if self.__socket == nil then
  110.       return nil, "there were no connection attempts"
  111.     else
  112.       return self.__socket.finishConnect()
  113.     end
  114.   end,
  115.  
  116.   reconnect = function(self)
  117.     if self:isConnected() then
  118.       self:disconnect()
  119.     end
  120.     self.__socket = internet.connect(self.__address or ADDRESS, self.__port or PORT)
  121.     event.listen('internet_ready', self.__listener)
  122.     -- check connection until there will be some useful information
  123.     -- also this serves to kick off internet_ready events generation
  124.     while true do
  125.       local result, error = self.__socket.finishConnect()
  126.       if result then
  127.         return self
  128.       elseif result == nil then
  129.         self:disconnect()
  130.         return nil, error
  131.       end
  132.     end
  133.   end,
  134.  
  135.   disconnect = function(self)
  136.     self.__socket.close()
  137.     self.__channels = {}
  138.     self.__stream = ""
  139.     event.ignore('internet_ready', self.__listener)
  140.   end,
  141.  
  142.   __incoming = function(self, socket_id)
  143.     -- check if the message belongs to the current connection
  144.     if self.__socket.id() == socket_id then
  145.       -- read all contents of the socket
  146.       while true do
  147.         local chunk = self.__socket.read()
  148.         if chunk ~= nil and #chunk > 0 then
  149.           self.__stream = self.__stream .. chunk
  150.         else
  151.           break
  152.         end
  153.       end
  154.       -- read all packages that may be already downloaded
  155.       while true do
  156.         -- calculate the next package size, if necessary
  157.         if self.len == nil and #self.__stream >= 2 then
  158.           local a, b = self.__stream:byte(1, 2)
  159.           self.len = a * 256 + b
  160.         end
  161.         -- check if the stream contains enough bytes for the package to be retrieved
  162.         if self.len ~= nil and #self.__stream >= self.len + 2 then
  163.           -- determine the package type
  164.           local type = self.__stream:byte(3)
  165.           local package = { type = type }
  166.           if type == Package.PING or type == Package.PONG then
  167.             -- read content
  168.             package.content = self.__stream:sub(4, self.len + 2)
  169.           else
  170.             -- read channel ID
  171.             local id_len = self.__stream:byte(4)
  172.             local id = self.__stream:sub(5, id_len + 4)
  173.             package.id = id
  174.             -- read a message
  175.             if type == Package.MESSAGE then
  176.               package.message = self.__stream:sub(id_len + 5, self.len + 2)
  177.             end
  178.           end
  179.           -- handle the package to processor
  180.           self:__process(package)
  181.           -- trim the stream
  182.           self.__stream = self.__stream:sub(self.len + 3)
  183.           self.len = nil
  184.         else
  185.           break
  186.         end
  187.       end
  188.     end
  189.   end,
  190.  
  191.   __process = function(self, package)
  192.     if package.type == Package.MESSAGE then
  193.       computer.pushSignal("stem_message", package.id, package.message)
  194.     elseif package.type == Package.PING then
  195.       self.__socket.write(self.__build_package(Package.PONG, package.content))
  196.     elseif package.type == Package.PONG then
  197.       computer.pushSignal("stem_pong", package.content)
  198.     end
  199.   end
  200. }
  201. server_api.__index = server_api
  202.  
  203. -- Library level functions
  204. --------------------------------------------------------------------
  205.  
  206. function stem.connect(address, port)
  207.   local server = {
  208.     __address = address,
  209.     __port = port,
  210.     __socket = socket
  211.   }
  212.   setmetatable(server, server_api)
  213.   server.__listener = function(_, _, id)
  214.     server:__incoming(id)
  215.   end
  216.   return server:reconnect()
  217. end
  218.  
  219. return stem
  220.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement