Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- ThunderNet main library; needs th_interfaces and th_inform.lua files.
- -- (c) ProgramCrafter
- -- Last patch: security of thunderlib.run_server (06.04.2021)
- local evt = require('event')
- local com = require('component')
- local cmp = require('computer')
- local ser = require('serialization')
- package.loaded['th_interfaces'] = nil
- local interfaces = require('th_interfaces')
- os.execute('th_inform')
- local uid = _G.digest
- local PORT = 777
- _G.digest = nil
- local connections = {}
- local global = interfaces.combine(connections)
- local NULLADDR = interfaces.null(nil).addr
- connections[NULLADDR] = interfaces.null(nil)
- for addr in com.list() do
- local proxy = com.proxy(addr)
- if interfaces[proxy.type] then
- connections[addr] = interfaces[proxy.type](proxy, uid)
- end
- if proxy.type == 'modem' then proxy.open(PORT) end
- end
- local function reverseTable(tableToReverse)
- local reversedTable, i = {}, 0
- for i = #tableToReverse, 1, -1 do
- table.insert(reversedTable, tableToReverse[i])
- end
- return reversedTable
- end
- --[[
- Network info: it's a tree of infinite depth with root server on ocelot.fomalhaut.me
- Connecting:
- WTC [uid]
- CAC [uid] [lev]
- AC [uid]
- UCA [uid] [connected uid]
- Disconnecting:
- DC [uid]
- Sending message:
- RM [msg] [addr]
- -- the node stores the first segment of route to underlying nodes
- -- if the route is not stored, message is retranslated up
- -- and so on, until the root of the tree is reached
- ]]
- local server_runs = false
- local parent_node = nil
- local self_level = nil
- local connts = {[uid] = {connections[NULLADDR], NULLADDR}} -- groups {connection, real recv addr}
- local thunderlib = {['IMPORTANT'] = 'All methods are synchronous!'}
- thunderlib.hook = function() end
- function thunderlib.connect(is_timeout)
- -- This method is SYNCHRONOUS == it won't return until the is_timeout function returns 1
- if parent_node then return false, 'already connected' end
- thunderlib.hook('Integrating into ThunderNet with connections', connections)
- is_timeout = is_timeout or function() os.sleep(0) end
- global.broadcast(PORT, ser.serialize {'WTC', uid})
- while not is_timeout() do
- local connection, data = global.recv(is_timeout)
- if connection then
- local success, pack = pcall(ser.unserialize, data)
- if success and pack[1] == 'CAC' and pack[2] and pack[3] then
- connts[pack[2]] = connection
- parent_node = pack[2]
- self_level = (tonumber(pack[3]) or 0) + 1
- return
- end
- end
- end
- end
- function thunderlib.disconnect()
- -- This method is SYNCHRONOUS
- if not parent_node then return false, 'not connected' end
- thunderlib.hook('Disconnecting from ThunderNet')
- global.broadcast(PORT, ser.serialize {'DC', uid})
- parent_node = nil
- self_level = nil
- for k in pairs(connts) do
- if k ~= uid then connts[k] = nil end
- end
- end
- function thunderlib.stop()
- -- This method is SYNCHRONOUS
- thunderlib.disconnect()
- for k, conn in next, connections do
- if conn.stop then
- thunderlib.hook('Stopping connection', conn)
- conn.stop()
- end
- end
- end
- function thunderlib.send(addr, data)
- -- This method is SYNCHRONOUS == it won't return until the data is sent
- os.sleep(0)
- thunderlib.hook('Sending message', addr, data)
- if connts[addr] then
- thunderlib.hook('Route to target is known')
- return global.send(connts, addr, PORT, ser.serialize {'RM', data, addr})
- elseif parent_node then
- thunderlib.hook('Target is unknown, sending message to parent', parent_node)
- return global.send(connts, parent_node, PORT, ser.serialize {'RM', data, addr})
- else
- thunderlib.hook('Target is unknown, message lost')
- return false
- end
- end
- function thunderlib.run_server(is_timeout, allow_connections)
- -- This method is SYNCHRONOUS == it blocks the executor thread forever
- thunderlib.run_server = function() error('Server cannot be run twice') end
- -- That's not enough secure, as runServer method can be saved and then run twice.
- -- If that becomes a problem, pls add check on local variable server_runs (l33).
- is_timeout = is_timeout or function() os.sleep(0) end
- local hook = thunderlib.hook
- hook('Server started')
- while not is_timeout() do
- local connection, data = global.recv(is_timeout)
- hook('Got incoming data:', data)
- if not connection then goto continue_rs end
- local success, pack = pcall(ser.unserialize, data)
- if not success or not pack then goto continue_rs end
- if pack[1] == 'WTC' and pack[2] and allow_connections then
- hook('Got a connection attempt', pack)
- connts[pack[2]] = connection
- global.send(connts, pack[2], PORT, ser.serialize {'CAC', uid, self_level or 0})
- elseif pack[1] == 'AC' and pack[2] and allow_connections then
- hook('Connection established', pack)
- if parent_node then
- global.send(connts, parent_node, PORT, ser.serialize {'UCA', uid, pack[2]})
- end
- elseif pack[1] == 'UCA' and pack[2] and pack[3] then
- hook('Underlying connection accepted', pack)
- connts[pack[3]] = connection
- if parent_node then
- global.send(connts, parent_node, PORT, ser.serialize {'UCA', uid, pack[2]})
- end
- elseif pack[1] == 'DC' and pack[2] then
- hook('Node nearby is disconnected', pack)
- local test_conn = connts[pack[2]]
- if not test_conn then
- hook('That node was unknown till now')
- else
- for k, cur_conn in pairs(connts) do
- if cur_conn[1] == test_conn[1] and cur_conn[2] == test_conn[2] then
- connts[k] = nil
- end
- end
- end
- elseif pack[1] == 'RM' and pack[2] and pack[3] then
- if pack[3] == uid then
- hook('Incoming message:', pack[2])
- cmp.pushSignal('thunderlib_incoming', pack[2])
- else
- local d = connts[pack[3]] and pack[3] or parent_node
- if d then
- hook('Passing message:', pack[2])
- global.send(connts, d, PORT, data)
- else
- hook('Message lost', pack)
- end
- end
- end
- ::continue_rs::
- end
- server_runs = false
- end
- function thunderlib.uid()
- return uid
- end
- function thunderlib.parent()
- return parent_node
- end
- _G.t = thunderlib
- return thunderlib
Add Comment
Please, Sign In to add comment