summaryrefslogtreecommitdiffhomepage
path: root/libs/lucid-rpc
diff options
context:
space:
mode:
authorSteven Barth <steven@midlink.org>2009-06-13 08:56:43 +0000
committerSteven Barth <steven@midlink.org>2009-06-13 08:56:43 +0000
commit120a7f558e27f2ca11b3dae251528d7100ca259d (patch)
treecbf5421d6bcd30da4e094a95ab8c7a3a3e2e15a7 /libs/lucid-rpc
parent90ce9746c230510f690407b7ccc1330bdca48bbd (diff)
GSoC: Add LuCId RPC-Slave
Diffstat (limited to 'libs/lucid-rpc')
-rw-r--r--libs/lucid-rpc/Makefile2
-rw-r--r--libs/lucid-rpc/luasrc/lucid/rpc.lua49
-rw-r--r--libs/lucid-rpc/luasrc/lucid/rpc/ruci.lua66
-rw-r--r--libs/lucid-rpc/luasrc/lucid/rpc/server.lua279
-rw-r--r--libs/lucid-rpc/luasrc/lucid/rpc/system.lua86
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