minetest-scuttlebutt/ssb.lua

244 lines
6.3 KiB
Lua

local json = require("json")
local socket = require("socket")
local unix = require("socket.unix")
local bit32 = require("bit32")
local ssb = { VERSION = '0.1' }
local function get_ssb_dir()
local home = os.getenv("HOME")
return string.format("%s/.ssb", home)
end
local function read_manifest()
local manifest = get_ssb_dir() .. "/manifest.json"
local f = io.open(manifest, "rb")
if not f then
return nil, "cannot find manifest: " .. manifest
end
local buf = f:read("*all")
f:close()
local js = json.decode(buf)
return js, nil
end
function method_to_json(meth)
local res = {}
for tok in string.gmatch(meth, "[^.]+") do
table.insert(res, tok)
end
return res
end
function do_read(conn, id, final)
local buf, err = conn:receive(9)
if string.len(buf) < 9 then
minetest.log("do_read: header len should be 9 but is: " .. string.len(buf))
return nil, string.len(buf)
end
local first = string.byte(buf, 1)
local len = bit32.bor(bit32.lshift(string.byte(buf, 2), 24),
bit32.lshift(string.byte(buf, 3), 16),
bit32.lshift(string.byte(buf, 4), 8),
string.byte(buf, 5))
-- remove minus sign
local id = bit32.bxor(bit32.bor(bit32.lshift(string.byte(buf, 6), 24),
bit32.lshift(string.byte(buf, 7), 16),
bit32.lshift(string.byte(buf, 8), 8),
string.byte(buf, 9)), 0xFFFFFFFF) + 1
-- minetest.log("do_read: len: " .. len)
-- minetest.log("do_read: id : " .. id)
if id ~= 1 then
minetest.log("do_read: id should be 1 but is: " .. id)
end
return conn:receive(len)
end
function do_write(conn, req, mtype, ptype, id, final)
-- first byte construction
local pkt = {
buffer = 0,
string = 1,
json = 2,
}
local pktype = pkt[ptype]
local stream
if mtype == "async" then stream = 0 else stream = 1 end
local fin
if final then fin = 1 else fin = 0 end
local first = string.char(bit32.bor(bit32.lshift(stream, 3), bit32.lshift(fin, 2), bit32.band(pktype, 3)))
-- big-endian header construction
local b0 = bit32.rshift(bit32.band(string.len(req), 0xFF000000), 24)
local b1 = bit32.rshift(bit32.band(string.len(req), 0x00FF0000), 16)
local b2 = bit32.rshift(bit32.band(string.len(req), 0x0000FF00), 8)
local b3 = bit32.band(string.len(req), 0x000000FF)
-- limitation to 255 ids
local head = string.char(b0, b1, b2, b3, 0, 0, 0, id)
-- concat
local buf = first .. head .. req
return conn:send(buf)
end
function do_goodbye(conn)
return do_write(conn, "", "async", "buffer", 0, true)
end
function rpc_source(conn, meth, mtype, param)
local req = json.encode({
name = method_to_json(meth),
args = {param},
type = mtype,
})
local ret, err = do_write(conn, req, mtype, "json", 1, false)
if err then
minetest.log("do_write: " .. err)
return ret, err
end
ret, err = do_read(conn, 1, false)
if err then
minetest.log("do_read :" .. err)
return ret, err
end
do_goodbye(conn)
return ret, err
end
function rpc_sync(conn, meth, mtype, param)
return rpc_source(conn, meth, "async", param)
end
function rpc_async(conn, meth, mtype, param)
-- wrap
local req = json.encode({
name = method_to_json(meth),
args = {param},
})
local ret, err = do_write(conn, req, mtype, "json", 1, false)
-- minetest.log("do_write: len: " .. ret)
if err then
minetest.log("do_write: " .. err)
return ret, err
end
ret, err = do_read(conn, 1, false)
-- minetest.log("do_read: len: " .. string.len(ret))
if err then
minetest.log("do_read: " .. err)
return ret, err
end
do_goodbye(conn)
return ret, err
end
function muxrpc(conn, meth, mtype, param)
local switch = {
async = rpc_async,
source = rpc_source,
sync = rpc_sync,
-- TODO implement all types
}
local rpc = switch[mtype]
if not rpc then
return nil, "method type unimplemented yet."
end
return rpc(conn, meth, mtype, param)
end
local function parse_msgs(arr)
local r = {}
for i, msg in ipairs(arr) do
r[i] = json.decode(msg)
end
return r
end
local mt = {
__index = function(self, k)
local manifest, err = read_manifest()
if err then
return function(s, ...)
return nil, "cannot initialize: " .. err
end
end
local mux = function (k, mtype, ...)
local params = {...}
local js
-- for key, val in ipairs(params) do
-- minetest.log(key .. "=" .. json.encode(val))
-- end
-- take only first param
js = params[1]
local c = unix()
local ret, err = c:connect(get_ssb_dir() .. "/socket")
if err then
return c, err
end
local ret, err = muxrpc(c, k, mtype, js)
if err then
c:close()
return ret, err
end
-- minetest.log(ret)
if not err then
if (type(ret) ~= "table") then
ret = json.decode(ret)
else
ret = parse_msgs(ret)
end
else
err = ret
ret = nil
end
return ret, err
end
return function(s, ...)
if not manifest[k] then
return nil, "method not found: " .. k
end
local mtype = manifest[k]
if type(mtype) ~= "table" then
return mux(k, mtype, ...)
end
local submt = {
__index = function (self, subk)
return function (s, ...)
if mtype[subk] == nil then
return nil, "method not found: " .. k .. "." .. subk
end
return mux(k .. "." .. subk, mtype[subk], ...)
end
end
}
local sub = {}
setmetatable(sub, submt)
return sub
end
end
}
setmetatable(ssb, mt)
return ssb