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