-- TrapOS networking: service-name bus on a single channel. -- -- Servers register handlers on the boot eventloop: -- net.serve('ping', function(msg, reply) reply('pong') end) -- net.listen('events.foo', function(msg, packet) ... end) -- -- Clients (CLI programs) call or send without an eventloop: -- local ok, res = net.call('ping', 'ping', { destId = 5, timeout = 0.5 }) -- net.send('events.foo', payload, { destId = 'alice' }) -- -- A router service (servers/router-server.lua) must run on exactly one machine. -- It resolves label-addressed packets, stamps routerId, and rebroadcasts. local createEventLoop = require('/apis/eventloop'); local BUS_CHANNEL = 10; local DEFAULT_TIMEOUT = 0.5; local nextRequestSeq = 1; local function newRequestId() local id = tostring(os.getComputerID()) .. ':' .. tostring(nextRequestSeq) .. ':' .. tostring(os.clock()); nextRequestSeq = nextRequestSeq + 1; return id; end local function isPacketOk(packet) if type(packet) ~= 'table' then return false end if not packet.routerId or not packet.sourceId then return false end if packet.destId == nil then return true end if type(packet.destId) == 'number' and packet.destId == os.getComputerID() then return true end if type(packet.destId) == 'string' and packet.destId == os.getComputerLabel() then return true end return false end local function createNetwork(el, modem, modemSide) el = el or createEventLoop(); modem = modem or peripheral.find('modem') or error('modem not found'); modem.open(BUS_CHANNEL); local isRouter = false; modemSide = modemSide or peripheral.getName(modem); local function buildPacket(service, kind, payload, destId, requestId) return { sourceId = os.getComputerID(), sourceLabel = os.getComputerLabel(), destId = tonumber(destId) or destId, service = service, kind = kind, requestId = requestId, payload = payload, routerId = isRouter and os.getComputerID() or nil, }; end local function transmit(packet) local selfId = os.getComputerID(); local selfLabel = os.getComputerLabel(); local destIsSelfId = packet.destId == selfId; local destIsSelfLabel = selfLabel ~= nil and packet.destId == selfLabel; local destIsSelf = destIsSelfId or destIsSelfLabel; local matchesSelf = packet.destId == nil or destIsSelf; if matchesSelf then local localPacket = {}; for k, v in pairs(packet) do localPacket[k] = v end localPacket.routerId = localPacket.routerId or selfId; os.queueEvent('modem_message', modemSide, BUS_CHANNEL, BUS_CHANNEL, localPacket, 0); end if not destIsSelf then modem.transmit(BUS_CHANNEL, BUS_CHANNEL, packet); end end local function serve(serviceName, handler) return el.register('modem_message', function(_, _, replyChannel, packet) if replyChannel ~= BUS_CHANNEL then return end if not isPacketOk(packet) then return end if packet.service ~= serviceName or packet.kind ~= 'req' then return end local function reply(responsePayload) local response = buildPacket(serviceName, 'res', responsePayload, packet.sourceId, packet.requestId); transmit(response); end handler(packet.payload, reply, packet); end); end local function listen(serviceName, handler) return el.register('modem_message', function(_, _, replyChannel, packet) if replyChannel ~= BUS_CHANNEL then return end if not isPacketOk(packet) then return end if packet.service ~= serviceName or packet.kind ~= 'evt' then return end handler(packet.payload, packet); end); end local function send(serviceName, payload, opts) opts = opts or {}; transmit(buildPacket(serviceName, 'evt', payload, opts.destId, nil)); end local function awaitResponse(serviceName, requestId, timeout, collectMultiple) local timerId = os.startTimer(timeout); local results = {}; local packets = {}; while true do local event, p1, _, p3, p4 = os.pullEvent(); if event == 'timer' and p1 == timerId then if collectMultiple then if #results == 0 then return false, 'net.call timeout', {} end return true, results, packets; end return false, 'net.call timeout', nil; elseif event == 'modem_message' then local replyChannel = p3; local recvPacket = p4; if replyChannel == BUS_CHANNEL and isPacketOk(recvPacket) and recvPacket.service == serviceName and recvPacket.kind == 'res' and recvPacket.requestId == requestId then if collectMultiple then table.insert(results, recvPacket.payload); table.insert(packets, recvPacket); else os.cancelTimer(timerId); return true, recvPacket.payload, recvPacket; end end end end end local function call(serviceName, payload, opts) opts = opts or {}; local timeout = opts.timeout or DEFAULT_TIMEOUT; local requestId = newRequestId(); transmit(buildPacket(serviceName, 'req', payload, opts.destId, requestId)); return awaitResponse(serviceName, requestId, timeout, false); end local function callMultiple(serviceName, payload, opts) opts = opts or {}; local timeout = opts.timeout or DEFAULT_TIMEOUT; local requestId = newRequestId(); transmit(buildPacket(serviceName, 'req', payload, opts.destId, requestId)); return awaitResponse(serviceName, requestId, timeout, true); end local function setRouter(enabled) isRouter = enabled and true or false; end local function onUnrouted(handler) return el.register('modem_message', function(_, _, replyChannel, packet) if replyChannel ~= BUS_CHANNEL then return end if type(packet) ~= 'table' then return end if packet.routerId then return end if not packet.sourceId then return end handler(packet); end); end local function rebroadcast(packet) packet.routerId = packet.routerId or os.getComputerID(); local selfId = os.getComputerID(); local selfLabel = os.getComputerLabel(); local destIsSelfId = packet.destId == selfId; local destIsSelfLabel = selfLabel ~= nil and packet.destId == selfLabel; local destIsSelf = destIsSelfId or destIsSelfLabel; local matchesSelf = packet.destId == nil or destIsSelf; if matchesSelf then os.queueEvent('modem_message', modemSide, BUS_CHANNEL, BUS_CHANNEL, packet, 0); end if not destIsSelf then modem.transmit(BUS_CHANNEL, BUS_CHANNEL, packet); end end return { BUS_CHANNEL = BUS_CHANNEL, DEFAULT_TIMEOUT = DEFAULT_TIMEOUT, eventloop = el, isPacketOk = isPacketOk, serve = serve, listen = listen, send = send, call = call, callMultiple = callMultiple, setRouter = setRouter, onUnrouted = onUnrouted, rebroadcast = rebroadcast, }; end local singleton = nil; return function(el, modem, modemSide) if el == nil and modem == nil and _G.bootEventLoop then if not singleton then singleton = createNetwork(_G.bootEventLoop, nil, nil); end return singleton; end return createNetwork(el, modem, modemSide); end