diff options
author | Steven Barth <steven@midlink.org> | 2009-06-13 08:56:43 +0000 |
---|---|---|
committer | Steven Barth <steven@midlink.org> | 2009-06-13 08:56:43 +0000 |
commit | 120a7f558e27f2ca11b3dae251528d7100ca259d (patch) | |
tree | cbf5421d6bcd30da4e094a95ab8c7a3a3e2e15a7 /libs/lucid-rpc | |
parent | 90ce9746c230510f690407b7ccc1330bdca48bbd (diff) |
GSoC: Add LuCId RPC-Slave
Diffstat (limited to 'libs/lucid-rpc')
-rw-r--r-- | libs/lucid-rpc/Makefile | 2 | ||||
-rw-r--r-- | libs/lucid-rpc/luasrc/lucid/rpc.lua | 49 | ||||
-rw-r--r-- | libs/lucid-rpc/luasrc/lucid/rpc/ruci.lua | 66 | ||||
-rw-r--r-- | libs/lucid-rpc/luasrc/lucid/rpc/server.lua | 279 | ||||
-rw-r--r-- | libs/lucid-rpc/luasrc/lucid/rpc/system.lua | 86 |
5 files changed, 482 insertions, 0 deletions
diff --git a/libs/lucid-rpc/Makefile b/libs/lucid-rpc/Makefile new file mode 100644 index 000000000..f7fac7740 --- /dev/null +++ b/libs/lucid-rpc/Makefile @@ -0,0 +1,2 @@ +include ../../build/config.mk +include ../../build/module.mk diff --git a/libs/lucid-rpc/luasrc/lucid/rpc.lua b/libs/lucid-rpc/luasrc/lucid/rpc.lua new file mode 100644 index 000000000..c89fc2444 --- /dev/null +++ b/libs/lucid-rpc/luasrc/lucid/rpc.lua @@ -0,0 +1,49 @@ +--[[ +LuCI - Lua Development Framework + +Copyright 2009 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]] + +local require, ipairs, pcall = require, ipairs, pcall +local srv = require "luci.lucid.rpc.server" + +module "luci.lucid.rpc" + + +function factory(publisher) + local root = srv.Module() + local server = srv.Server(root) + + for _, r in ipairs(publisher) do + for _, m in ipairs(r.export) do + local s, mod = pcall(require, r.namespace .. "." .. m) + if s and mod then + local module = mod._factory() + + if r.exec then + for _, x in ipairs(r.exec) do + if x:sub(1,1) == ":" then + module:restrict({interface = x:sub(2)}) + else + module:restrict({user = x}) + end + end + end + + root:add(m, module) + else + return nil, mod + end + end + end + + return function(...) return server:process(...) end +end
\ No newline at end of file diff --git a/libs/lucid-rpc/luasrc/lucid/rpc/ruci.lua b/libs/lucid-rpc/luasrc/lucid/rpc/ruci.lua new file mode 100644 index 000000000..38e57f366 --- /dev/null +++ b/libs/lucid-rpc/luasrc/lucid/rpc/ruci.lua @@ -0,0 +1,66 @@ +--[[ +LuCIRPCd +(c) 2009 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]]-- +local uci = require "luci.model.uci" +local tostring, getmetatable, pairs = tostring, getmetatable, pairs +local error, type = error, type +local nixio = require "nixio" +local srv = require "luci.lucid.rpc.server" + +module "luci.lucid.rpc.ruci" + +function _factory() + local m = srv.Module("Remote UCI API") + + for k, v in pairs(_M) do + if type(v) == "function" and v ~= _factory then + m:add(k, srv.Method.extended(v)) + end + end + + return m +end + +local function getinst(session, name) + return session.ruci and session.ruci[name] +end + +local function setinst(session, obj) + session.ruci = session.ruci or {} + local name = tostring(obj):match("0x([a-z0-9]+)") + session.ruci[name] = obj + return name +end + +local Cursor = getmetatable(uci.cursor()) + +for name, func in pairs(Cursor) do + _M[name] = function(session, inst, ...) + inst = getinst(session, inst) + return inst[name](inst, ...) + end +end + +function cursor(session, ...) + return setinst(session, uci.cursor(...)) +end + +function cursor_state(session, ...) + return setinst(session, uci.cursor_state(...)) +end + +function foreach(session, inst, config, sectiontype) + local inst = getinst(session, inst) + local secs = {} + inst:foreach(config, sectiontype, function(s) secs[#secs+1] = s end) + return secs +end
\ No newline at end of file diff --git a/libs/lucid-rpc/luasrc/lucid/rpc/server.lua b/libs/lucid-rpc/luasrc/lucid/rpc/server.lua new file mode 100644 index 000000000..af25280d9 --- /dev/null +++ b/libs/lucid-rpc/luasrc/lucid/rpc/server.lua @@ -0,0 +1,279 @@ +--[[ +LuCI - Lua Development Framework + +Copyright 2009 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]] + +local ipairs, pairs = ipairs, pairs +local tostring, tonumber = tostring, tonumber +local pcall, assert, type, unpack = pcall, assert, type, unpack + +local nixio = require "nixio" +local json = require "luci.json" +local util = require "luci.util" +local table = require "table" +local ltn12 = require "luci.ltn12" + +module "luci.lucid.rpc.server" + +RQLIMIT = 32 * nixio.const.buffersize +VERSION = "1.0" + +ERRNO_PARSE = -32700 +ERRNO_INVALID = -32600 +ERRNO_UNKNOWN = -32001 +ERRNO_TIMEOUT = -32000 +ERRNO_NOTFOUND = -32601 +ERRNO_NOACCESS = -32002 +ERRNO_INTERNAL = -32603 +ERRNO_NOSUPPORT = -32003 + +ERRMSG = { + [ERRNO_PARSE] = "Parse error.", + [ERRNO_INVALID] = "Invalid request.", + [ERRNO_TIMEOUT] = "Connection timeout.", + [ERRNO_UNKNOWN] = "Unknown error.", + [ERRNO_NOTFOUND] = "Method not found.", + [ERRNO_NOACCESS] = "Access denied.", + [ERRNO_INTERNAL] = "Internal error.", + [ERRNO_NOSUPPORT] = "Operation not supported." +} + + + +Method = util.class() + +function Method.extended(...) + local m = Method(...) + m.call = m.xcall + return m +end + +function Method.__init__(self, method, description) + self.description = description + self.method = method +end + +function Method.xcall(self, session, argv) + return self.method(session, unpack(argv)) +end + +function Method.call(self, session, argv) + return self.method(unpack(argv)) +end + +function Method.process(self, session, request, argv) + local stat, result = pcall(self.call, self, session, argv) + + if stat then + return { result=result } + else + return { error={ + code=ERRNO_UNKNOWN, + message=ERRMSG[ERRNO_UNKNOWN], + data=result + } } + end +end + + +local function remapipv6(adr) + local map = "::ffff:" + if adr:sub(1, #map) == map then + return adr:sub(#map+1) + else + return adr + end +end + +Module = util.class() + +function Module.__init__(self, description) + self.description = description + self.handler = {} +end + +function Module.add(self, k, v) + self.handler[k] = v +end + +-- Access Restrictions +function Module.restrict(self, restriction) + if not self.restrictions then + self.restrictions = {restriction} + else + self.restrictions[#self.restrictions+1] = restriction + end +end + +-- Check restrictions +function Module.checkrestricted(self, session, request, argv) + if not self.restrictions then + return + end + + for _, r in ipairs(self.restrictions) do + local stat = true + if stat and r.interface then -- Interface restriction + if not session.localif then + for _, v in ipairs(session.env.interfaces) do + if v.addr == session.localaddr then + session.localif = v.name + break + end + end + end + + if r.interface ~= session.localif then + stat = false + end + end + + if stat and r.user and session.user ~= r.user then -- User restriction + stat = false + end + + if stat then + return + end + end + + return {error={code=ERRNO_NOACCESS, message=ERRMSG[ERRNO_NOACCESS]}} +end + +function Module.register(self, m, descr) + descr = descr or {} + for k, v in pairs(m) do + if util.instanceof(v, Method) then + self.handler[k] = v + elseif type(v) == "table" then + self.handler[k] = Module() + self.handler[k]:register(v, descr[k]) + elseif type(v) == "function" then + self.handler[k] = Method(v, descr[k]) + end + end + return self +end + +function Module.process(self, session, request, argv) + local first, last = request:match("^([^.]+).?(.*)$") + + local stat = self:checkrestricted(session, request, argv) + if stat then -- Access Denied + return stat + end + + local hndl = first and self.handler[first] + if not hndl then + return {error={code=ERRNO_NOTFOUND, message=ERRMSG[ERRNO_NOTFOUND]}} + end + + session.chain[#session.chain+1] = self + return hndl:process(session, last, argv) +end + + + +Server = util.class() + +function Server.__init__(self, root) + self.root = root +end + +function Server.get_root(self) + return self.root +end + +function Server.set_root(self, root) + self.root = root +end + +function Server.reply(self, jsonrpc, id, res, err) + id = id or json.null + + -- 1.0 compatibility + if jsonrpc ~= "2.0" then + jsonrpc = nil + res = res or json.null + err = err or json.null + end + + return json.Encoder( + {id=id, result=res, error=err, jsonrpc=jsonrpc}, BUFSIZE + ):source() +end + +function Server.process(self, client, env) + local decoder + local sinkout = client:sink() + client:setopt("socket", "sndtimeo", 90) + client:setopt("socket", "rcvtimeo", 90) + + local close = false + local session = {server = self, chain = {}, client = client, env = env, + localaddr = remapipv6(client:getsockname())} + local req, stat, response, result, cb + + repeat + local oldchunk = decoder and decoder.chunk + decoder = json.ActiveDecoder(client:blocksource(nil, RQLIMIT)) + decoder.chunk = oldchunk + + result, response, cb = nil, nil, nil + + -- Read one request + stat, req = pcall(decoder.get, decoder) + + if stat then + if type(req) == "table" and type(req.method) == "string" + and (not req.params or type(req.params) == "table") then + req.params = req.params or {} + result, cb = self.root:process(session, req.method, req.params) + if type(result) == "table" then + if req.id ~= nil then + response = self:reply(req.jsonrpc, req.id, + result.result, result.error) + end + close = result.close + else + if req.id ~= nil then + response = self:reply(req.jsonrpc, req.id, nil, + {code=ERRNO_INTERNAL, message=ERRMSG[ERRNO_INTERNAL]}) + end + end + else + response = self:reply(req.jsonrpc, req.id, + nil, {code=ERRNO_INVALID, message=ERRMSG[ERRNO_INVALID]}) + end + else + if nixio.errno() ~= nixio.const.EAGAIN then + response = self:reply("2.0", nil, + nil, {code=ERRNO_PARSE, message=ERRMSG[ERRNO_PARSE]}) + --[[else + response = self:reply("2.0", nil, + nil, {code=ERRNO_TIMEOUT, message=ERRMSG_TIMEOUT})]] + end + close = true + end + + if response then + ltn12.pump.all(response, sinkout) + end + + if cb then + close = cb(client, session, self) or close + end + until close + + client:shutdown() + client:close() +end diff --git a/libs/lucid-rpc/luasrc/lucid/rpc/system.lua b/libs/lucid-rpc/luasrc/lucid/rpc/system.lua new file mode 100644 index 000000000..4f7f0b5c2 --- /dev/null +++ b/libs/lucid-rpc/luasrc/lucid/rpc/system.lua @@ -0,0 +1,86 @@ +--[[ +LuCI - Lua Development Framework + +Copyright 2009 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]] + +local type, ipairs = type, ipairs +local srv = require "luci.lucid.rpc.server" +local nixio = require "nixio" +local lucid = require "luci.lucid" + +module "luci.lucid.rpc.system" + +function _factory() + local mod = srv.Module("System functions"):register({ + echo = echo, + void = void, + multicall = srv.Method.extended(multicall), + authenticate = srv.Method.extended(authenticate) + }) + mod.checkrestricted = function(self, session, request, ...) + if request ~= "authenticate" then + return srv.Module.checkrestricted(self, session, request, ...) + end + end + return mod +end + + +function echo(object) + return object +end + +function void() + +end + +function multicall(session, ...) + local server, responses, response = session.server, {}, nil + for k, req in ipairs({...}) do + response = nil + if type(req) == "table" and type(req.method) == "string" + and (not req.params or type(req.params) == "table") then + req.params = req.params or {} + result = server.root:process(session, req.method, req.params) + if type(result) == "table" then + if req.id ~= nil then + response = {jsonrpc=req.jsonrpc, id=req.id, + result=result.result, error=result.error} + end + else + if req.id ~= nil then + response = {jsonrpc=req.jsonrpc, id=req.id, + result=nil, error={code=srv.ERRNO_INTERNAL, + message=srv.ERRMSG[ERRNO_INTERNAL]}} + end + end + end + responses[k] = response + end + return responses +end + +function authenticate(session, type, entity, key) + if not type then + session.user = nil + return true + elseif type == "plain" then + local pwe = nixio.getsp and nixio.getsp(entity) or nixio.getpw(entity) + local pwh = pwe and (pwe.pwdp or pwe.passwd) + if not pwh or #pwh < 1 or nixio.crypt(key, pwh) ~= pwh then + return nil + else + session.user = entity + return true + end + end +end
\ No newline at end of file |