OC-PsychOS2/lib/rpc.lua

106 lines
3.1 KiB
Lua

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