GSoC: Add LuCId RPC-Slave
authorSteven Barth <steven@midlink.org>
Sat, 13 Jun 2009 08:56:43 +0000 (08:56 +0000)
committerSteven Barth <steven@midlink.org>
Sat, 13 Jun 2009 08:56:43 +0000 (08:56 +0000)
libs/lucid-rpc/Makefile [new file with mode: 0644]
libs/lucid-rpc/luasrc/lucid/rpc.lua [new file with mode: 0644]
libs/lucid-rpc/luasrc/lucid/rpc/ruci.lua [new file with mode: 0644]
libs/lucid-rpc/luasrc/lucid/rpc/server.lua [new file with mode: 0644]
libs/lucid-rpc/luasrc/lucid/rpc/system.lua [new file with mode: 0644]

diff --git a/libs/lucid-rpc/Makefile b/libs/lucid-rpc/Makefile
new file mode 100644 (file)
index 0000000..f7fac77
--- /dev/null
@@ -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 (file)
index 0000000..c89fc24
--- /dev/null
@@ -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 (file)
index 0000000..38e57f3
--- /dev/null
@@ -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 (file)
index 0000000..af25280
--- /dev/null
@@ -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 (file)
index 0000000..4f7f0b5
--- /dev/null
@@ -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