local serial = require "serialization" local minitel = require "minitel" local event = require "event" local rpc = {} _G.rpcf = {} rpc.port = 111 local function setacl(self, fname, host) self[fname] = self[fname] or {} self[fname][host] = true end -- function rpc.allow(fn, host) -- string string -- -- Enable the allow list for function *fname* and add *host* to it. -- function rpc.deny(fn, host) -- string string -- -- Enable the deny list for function *fname* and add *host* to it. rpc.allow = setmetatable({},{__call=setacl}) rpc.deny = setmetatable({},{__call=setacl}) local function isPermitted(host,fn) if rpc.allow[fn] then return rpc.allow[fn][host] or false end if rpc.deny[fn] and rpc.deny[fn][host] then return false end return true end local function rpcexec(_, from, port, data) if port ~= rpc.port then return false end os.spawn(function() local rpcrq = serial.unserialize(data) or {} if rpcf[rpcrq[1]] and isPermitted(from,rpcrq[1]) then os.setenv("RPC_CLIENT",from) minitel.send(from,port,serial.serialize({rpcrq[2],pcall(rpcf[rpcrq[1]],table.unpack(rpcrq,3))})) elseif type(rpcrq[2]) == "string" then minitel.send(from,port,serial.serialize({rpcrq[2],false,"function unavailable"})) end end,"rpc worker for "..tostring(from)) end function rpcf.list() local rt = {} for k,v in pairs(rpcf) do rt[#rt+1] = k end return rt end function rpc.call(hostname,fn,...) -- string string -- boolean -- Calls exported function *fn* on host *hostname*, with parameters *...*, returning whatever the function returns, or false. if hostname == "localhost" then return rpcf[fn](...) end local rv = minitel.genPacketID() minitel.rsend(hostname,rpc.port,serial.serialize({fn,rv,...}),true) local st = computer.uptime() local rt = {} repeat local _, from, port, data = event.pull(30, "net_msg", hostname, rpc.port) rt = serial.unserialize(tostring(data)) or {} until (type(rt) == "table" and rt[1] == rv) or computer.uptime() > st + 30 if rt[1] == rv then if rt[2] then return table.unpack(rt,3) end error(rt[3]) end error("timed out") end function rpc.proxy(hostname,filter) -- string string -- table -- Returns a component.proxy()-like table from the functions on *hostname* with names matching *filter*. filter=(filter or "").."(.+)" local fnames = rpc.call(hostname,"list") if not fnames then return false end local rt = {} for k,v in pairs(fnames) do fv = v:match(filter) if fv then rt[fv] = function(...) return rpc.call(hostname,v,...) end end end return rt end function rpc.register(name,fn) -- string function -- -- Registers a function to be exported by the RPC library. local rpcrunning = false for k,v in pairs(os.tasks()) do if os.taskInfo(v).name == "rpc daemon" then rpcrunning = true end end if not rpcrunning then os.spawn(function() while true do pcall(rpcexec,event.pull("net_msg")) end end,"rpc daemon") end rpcf[name] = fn end function rpc.unregister(name) -- string -- -- Removes a function from the RPC function registry, clearing any ACL rules. rpcf[name] = nil rpc.allow[name] = nil rpc.deny[name] = nil end return rpc