Refactor all code to new API; several minor changes/bug fixes.

This commit is contained in:
Wouter R 2013-07-08 16:53:45 +02:00
parent 915d1cd900
commit 115e6a7eff
7 changed files with 236 additions and 263 deletions

View File

@ -1,13 +1,36 @@
--[[
TODO:
- network/state returns awfully little information (only station mode)
- document REST API (mention rq IDs and endpoint information, list endpoints+args+CRUD type, unknown values are empty fields)
- use a slightly more descriptive success/error definition (e.g. errortype=system/missing-arg/generic)
- how to handle requests which need a restart of uhttpd? (e.g. network/openap)
- a plain GET request (no ajax/script) runs the risk of timing out on lengthy operations: implement polling in API to get progress updates?
(this would require those operations to run in a separate daemon process which can be monitored by the CGI handler)
- protect dump function against reference loops (see: http://lua-users.org/wiki/TableSerialization, json also handles this well)
- (this is an old todo item from network:available(), might still be relevant at some point)
extend reconf interface to support function arguments (as tables) so wifihelper functionality can be integrated
but how? idea: pass x_args={arg1="a",arg2="2342"} for component 'x'
or: allow alternative for x="y" --> x={action="y", arg1="a", arg2="2342"}
in any case, arguments should be put in a new table to pass to the function (since order is undefined it must be an assoc array)
NOTES:
- The endpoint function info in response objects is incorrect when the global function is called with a blank argument,
to cleanly solve this, module/function resolution should be moved from main() to the request object
]]--
local l = require("logger")
local RequestClass = require("rest.request")
local ResponseClass = require("rest.response")
local wifi = require("network.wlanconfig")
local reconf = require("network.netconfig")
local DEBUG_PCALLS = false
--NOTE: pcall protects from invocation exceptions, which is what we need except
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
local DEBUG_PCALLS = true
local postData = nil
local resp = ResponseClass.new()
local function setupAutoWifiMode()
@ -26,6 +49,8 @@ local function init()
local n = tonumber(os.getenv("CONTENT_LENGTH"))
postData = io.read(n)
end
return wifi.init() and reconf.init(wifi, true)
end
--usually returns function+nil, function+number in case of number in place of function name; or
@ -96,12 +121,14 @@ end
if ok == true then
print(r:serializeAsJson())
else
local resp = ResponseClass.new(rq)
resp:setError("call to function '" .. mod .. "/" .. sr .. "' failed")
print(resp:serializeAsJson())
l:error("calling function '" .. func .. "' in API module '" .. mod .. "' somehow failed ('" .. r .. "')")
end
else
resp:setError("function unknown '" .. mod .. "/" .. func .. "'")
local resp = ResponseClass.new(rq)
resp:setError("function unknown '" .. (mod or "<empty>") .. "/" .. (func or "<global>") .. "'")
print(resp:serializeAsJson())
l:error("could not resolve requested API function ('" .. sr .. "')")
end
@ -109,5 +136,13 @@ end
end
init()
main()
if init() == false then
local resp = ResponseClass.new()
resp:setError("initialization failed")
print(resp:serializeAsJson()) --FIXME: this message does not seem to be sent
l:error("initialization failed") --NOTE: this assumes the logger has been inited properly, despite init() having failed
os.exit(1)
else
main()
os.exit(0)
end

View File

@ -1,173 +0,0 @@
--[[
Response format:
["OK" | "WARN" | "ERR"]<,{message}>
{comma-separated line 1}
...
{comma-separated line n}
- general info on wireless config: http://wiki.openwrt.org/doc/uci/wireless
- uci docs: http://wiki.openwrt.org/doc/techref/uci
- parse/generate urls: https://github.com/keplerproject/cgilua/blob/master/src/cgilua/urlcode.lua
- utility functions: http://luci.subsignal.org/trac/browser/luci/trunk/libs/sys/luasrc/sys.lua
- iwinfo tool source: http://luci.subsignal.org/trac/browser/luci/trunk/contrib/package/iwinfo/src/iwinfo.lua?rev=7919
- captive portal -> redirect all web traffic to one page for auth (or network selection)
http://wiki.openwrt.org/doc/howto/wireless.hotspot
]]
--print ("HTTP/1.0 200 OK")
io.write ("Content-type: text/plain\r\n\r\n")
local u = require("util")
local l = require("logger")
local wifi = require("network.wlanconfig")
local reconf = require("network.netconfig")
local urlcode = require("util.urlcode")
local uci = require("uci").cursor()
local iwinfo = require("iwinfo")
local argOperation, argDevice, argSsid, argPhrase, argRecreate
local errortext = nil
function init()
l:init(l.LEVEL.debug, true, io.stderr)
local qs = os.getenv("QUERY_STRING")
local urlargs = {}
urlcode.parsequery(qs, urlargs)
--supplement urlargs with arguments from the command-line
for _, v in ipairs(arg) do
local split = v:find("=")
if split ~= nil then
urlargs[v:sub(1, split - 1)] = v:sub(split + 1)
end
end
argOperation = urlargs["op"]
argDevice = urlargs["dev"] or DFL_DEVICE
argSsid = urlargs["ssid"]
argPhrase = urlargs["phrase"]
argRecreate = urlargs["recreate"]
if urlargs["echo"] ~= nil then
print("[[echo: '"..qs.."']]");
end
if argOperation == nil then
errortext = "Missing operation specifier"
return false
end
return wifi.init() and reconf.init(wifi, true)
end
function main()
if argOperation == "getavl" then
local sr = wifi.getScanInfo()
local si, se
--TODO:
-- - extend reconf interface to support function arguments (as tables) so wifihelper functionality can be integrated
-- but how? idea: pass x_args={arg1="a",arg2="2342"} for component 'x'
-- or: allow alternative for x="y" --> x={action="y", arg1="a", arg2="2342"}
-- in any case, arguments should be put in a new table to pass to the function (since order is undefined it must be an assoc array)
if sr and #sr > 0 then
u.printWithSuccess(#sr .. " network(s) found");
for _, se in ipairs(sr) do
--print("[[ " .. u.dump(se) .. " ]]") --TEMP
if se.mode ~= "ap" and se.ssid ~= wifi.AP_SSID then
print(se.ssid .. "," .. se.bssid .. "," .. se.channel .. "," .. wifi.mapDeviceMode(se.mode) .. "," .. wifi.mapEncryptionType(se.encryption))
end
end
else
u.exitWithError("No scan results or scanning not possible")
end
elseif argOperation == "getknown" then
u.printWithSuccess("")
for _, net in ipairs(wifi.getConfigs()) do
if net.mode == "sta" then
local bssid = net.bssid or "<unknown BSSID>"
local channel = net.channel or "<unknown channel>"
print(net.ssid .. "," .. bssid .. "," .. channel)
end
end
elseif argOperation == "getstate" then
local ds = wifi.getDeviceState()
local ssid = ds.ssid or "<unknown SSID>"
local bssid = ds.bssid or "<unknown BSSID>"
local channel = ds.channel or "<unknown channel>"
u.printWithSuccess("");
print(ssid .. "," .. bssid .. "," .. channel .. "," .. ds.mode)
elseif argOperation == "assoc" then
if argSsid == nil or argSsid == "" then u.exitWithError("Please supply an SSID to associate with") end
local cfg = nil
for _, net in ipairs(wifi.getConfigs()) do
if net.mode ~= "ap" and net.ssid == argSsid then
cfg = net
break
end
end
if cfg == nil or argRecreate ~= nil then
local scanResult = wifi.getScanInfo(argSsid)
if scanResult ~= nil then
wifi.createConfigFromScanInfo(scanResult, argPhrase)
else
--check for error
u.exitWithError("No wireless network with SSID '" .. argSsid .. "' is available")
end
end
wifi.activateConfig(argSsid)
reconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
u.exitWithSuccess("Wlan associated with network " .. argSsid .. "!")
elseif argOperation == "disassoc" then
wifi.activateConfig()
local rv = wifi.restart()
u.exitWithSuccess("Deactivated all wireless networks [$?=" .. rv .. "]")
elseif argOperation == "openap" then
--add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed
reconf.switchConfiguration{apnet="add_noreload"}
wifi.activateConfig(wifi.AP_SSID)
reconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
u.exitWithSuccess("Switched to AP mode (SSID: '" .. wifi.AP_SSID .. "')")
elseif argOperation == "rm" then
if argSsid == nil or argSsid == "" then u.exitWithError("Please supply an SSID to remove") end
if wifi.removeConfig(argSsid) then
u.exitWithSuccess("Removed wireless network with SSID " .. argSsid)
else
u.exitWithWarning("No wireless network with SSID " .. argSsid)
end
elseif argOperation == "test" then
--invert actions performed by openap operation
reconf.switchConfiguration{ apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm" }
-- reconf.switchConfiguration{dnsredir="add"}
u.exitWithSuccess("nop")
elseif argOperation == "auto" then
u.exitWithWarning("Not implemented");
--scan nets
--take union of scan and known
--connect to first if not empty; setup ap otherwise
else
u.exitWithError("Unknown operation: '" .. argOperation .. "'")
end
os.exit(0)
end
--[[ START OF CODE ]]--
if init() == false then
u.exitWithError(errortext)
end
main()

View File

@ -1,95 +1,179 @@
local l = require("logger")
local u = require("util.utils")
local netconf = require("network.netconfig")
local wifi = require("network.wlanconfig")
local ResponseClass = require("rest.response")
local M = {}
M.isApi = true
function M._global(d)
return "not implemented..."
local r = ResponseClass.new(d)
r:setError("not implemented")
return r
end
--[[
if argOperation == "getavl" then
local sr = wifi.getScanInfo()
local si, se
--TODO:
-- - extend reconf interface to support function arguments (as tables) so wifihelper functionality can be integrated
-- but how? idea: pass x_args={arg1="a",arg2="2342"} for component 'x'
-- or: allow alternative for x="y" --> x={action="y", arg1="a", arg2="2342"}
-- in any case, arguments should be put in a new table to pass to the function (since order is undefined it must be an assoc array)
if sr and #sr > 0 then
u.printWithSuccess(#sr .. " network(s) found");
for _, se in ipairs(sr) do
--print("[[ " .. u.dump(se) .. " ]]") --TEMP
if se.mode ~= "ap" and se.ssid ~= wifi.AP_SSID then
print(se.ssid .. "," .. se.bssid .. "," .. se.channel .. "," .. wifi.mapDeviceMode(se.mode) .. "," .. wifi.mapEncryptionType(se.encryption))
end
--accepts API argument 'nofilter'(bool) to disable filtering of APs and 'self'
function M.available(d)
local r = ResponseClass.new(d)
local noFilter = u.toboolean(d:get("nofilter"))
local sr = wifi.getScanInfo()
local si, se
if sr and #sr > 0 then
r:setSuccess("")
local netInfoList = {}
for _, se in ipairs(sr) do
if noFilter or se.mode ~= "ap" and se.ssid ~= wifi.AP_SSID then
local netInfo = {}
netInfo["ssid"] = se.ssid
netInfo["bssid"] = se.bssid
netInfo["channel"] = se.channel
netInfo["mode"] = wifi.mapDeviceMode(se.mode)
netInfo["encryption"] = wifi.mapEncryptionType(se.encryption)
netInfo["signal"] = se.signal
netInfo["quality"] = se.quality
netInfo["quality_max"] = se.quality_max
--netInfo["raw"] = l:dump(se) --TEMP for debugging only
table.insert(netInfoList, netInfo)
end
end
r:addData("count", #netInfoList)
r:addData("networks", netInfoList)
else
r:setError("No scan results or scanning not possible")
end
return r
end
--accepts API argument 'nofilter'(bool) to disable filtering of APs and 'self'
function M.known(d)
local r = ResponseClass.new(d)
local noFilter = u.toboolean(d:get("nofilter"))
r:setSuccess()
local netInfoList = {}
for _, net in ipairs(wifi.getConfigs()) do
if noFilter or net.mode == "sta" then
local netInfo = {}
netInfo["ssid"] = net.ssid
netInfo["bssid"] = net.bssid or ""
netInfo["channel"] = net.channel or ""
netInfo["encryption"] = net.encryption
netInfo["raw"] = l:dump(net) --TEMP for debugging only
table.insert(netInfoList, netInfo)
end
end
r:addData("count", #netInfoList)
r:addData("networks", netInfoList)
return r
end
function M.state(d)
local r = ResponseClass.new(d)
local ds = wifi.getDeviceState()
r:setSuccess()
r:addData("ssid", ds.ssid or "")
r:addData("bssid", ds.bssid or "")
r:addData("channel", ds.channel or "")
r:addData("mode", ds.mode)
r:addData("raw", l:dump(ds)) --TEMP for debugging only
return r
end
--UNTESTED
--requires ssid(string), accepts phrase(string), recreate(bool)
function M.assoc(d)
local r = ResponseClass.new(d)
local argSsid = d:get("ssid")
local argPhrase = d:get("phrase")
local argRecreate = d:get("recreate")
if argSsid == nil or argSsid == "" then
r:setError("missing ssid argument")
return r
end
local cfg = nil
for _, net in ipairs(wifi.getConfigs()) do
if net.mode ~= "ap" and net.ssid == argSsid then
cfg = net
break
end
end
if cfg == nil or argRecreate ~= nil then
local scanResult = wifi.getScanInfo(argSsid)
if scanResult ~= nil then
wifi.createConfigFromScanInfo(scanResult, argPhrase)
else
u.exitWithError("No scan results or scanning not possible")
--check for error
r:setError("no wireless network with requested SSID is available")
r:addData("ssid", argSsid)
end
end
elseif argOperation == "getknown" then
u.printWithSuccess("")
for _, net in ipairs(wifi.getConfigs()) do
if net.mode == "sta" then
local bssid = net.bssid or "<unknown BSSID>"
local channel = net.channel or "<unknown channel>"
print(net.ssid .. "," .. bssid .. "," .. channel)
end
end
wifi.activateConfig(argSsid)
netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
r:setSuccess("wlan associated")
r:addData("ssid", argSsid)
elseif argOperation == "getstate" then
local ds = wifi.getDeviceState()
local ssid = ds.ssid or "<unknown SSID>"
local bssid = ds.bssid or "<unknown BSSID>"
local channel = ds.channel or "<unknown channel>"
u.printWithSuccess("");
print(ssid .. "," .. bssid .. "," .. channel .. "," .. ds.mode)
return r
end
--UNTESTED
function M.disassoc(d)
local r = ResponseClass.new(d)
elseif argOperation == "assoc" then
if argSsid == nil or argSsid == "" then u.exitWithError("Please supply an SSID to associate with") end
local cfg = nil
for _, net in ipairs(wifi.getConfigs()) do
if net.mode ~= "ap" and net.ssid == argSsid then
cfg = net
break
end
end
if cfg == nil or argRecreate ~= nil then
local scanResult = wifi.getScanInfo(argSsid)
if scanResult ~= nil then
wifi.createConfigFromScanInfo(scanResult, argPhrase)
else
--check for error
u.exitWithError("No wireless network with SSID '" .. argSsid .. "' is available")
end
end
wifi.activateConfig(argSsid)
reconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" }
u.exitWithSuccess("Wlan associated with network " .. argSsid .. "!")
wifi.activateConfig()
local rv = wifi.restart()
r:setSuccess("all wireless networks deactivated")
r:addData("wifi_restart_result", rv)
elseif argOperation == "disassoc" then
wifi.activateConfig()
local rv = wifi.restart()
u.exitWithSuccess("Deactivated all wireless networks [$?=" .. rv .. "]")
return r
end
--UNTESTED
function M.openap(d)
local r = ResponseClass.new(d)
--add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed
netconf.switchConfiguration{apnet="add_noreload"}
wifi.activateConfig(wifi.AP_SSID)
netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
r:setSuccess("switched to Access Point mode")
r:addData("ssid", wifi.AP_SSID)
elseif argOperation == "openap" then
--add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed
reconf.switchConfiguration{apnet="add_noreload"}
wifi.activateConfig(wifi.AP_SSID)
reconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" }
u.exitWithSuccess("Switched to AP mode (SSID: '" .. wifi.AP_SSID .. "')")
return r
end
--UNTESTED
--requires ssid(string)
function M.rm(d)
local r = ResponseClass.new(d)
local argSsid = d:get("ssid")
elseif argOperation == "rm" then
if argSsid == nil or argSsid == "" then u.exitWithError("Please supply an SSID to remove") end
if wifi.removeConfig(argSsid) then
u.exitWithSuccess("Removed wireless network with SSID " .. argSsid)
else
u.exitWithWarning("No wireless network with SSID " .. argSsid)
end
]--
if argSsid == nil or argSsid == "" then
r:setError("missing ssid argument")
return r
end
if wifi.removeConfig(argSsid) then
r:setSuccess("removed wireless network with requested SSID")
r:addData("ssid", argSsid)
else
r:setError("no wireless network with requested SSID") --this used to be a warning instead of an error...
r:addData("ssid", argSsid)
end
return r
end
return M

View File

@ -6,26 +6,29 @@ local M = {}
M.isApi = true
function M._global(d)
local r = ResponseClass.new()
local ba = d:getBlankArgument() or "<nil>"
r:setSuccess("REST test API - default function called with blank argument: '" .. ba .. "'")
local r = ResponseClass.new(d)
local ba = d:getBlankArgument()
r:setSuccess("REST test API - default function called with blank argument: '" .. (ba or "<nil>") .. "'")
if ba ~= nil then r:addData("blank_argument", ba) end
return r
end
function M.success(d)
local r = ResponseClass.new()
r:setSuccess("yay!")
local r = ResponseClass.new(d)
r:setSuccess()
return r
end
function M.error(d)
local r = ResponseClass.new()
local r = ResponseClass.new(d)
r:setError("this error has been generated on purpose")
return r
end
function M.echo(d)
local r = ResponseClass.new()
local r = ResponseClass.new(d)
r:setSuccess("request echo")
r:addData("request_data", d)
return r

View File

@ -59,6 +59,9 @@ function M.new(postData, debug)
self.apiFunction = self.cmdLineArgs["f"] or self.apiFunction
end
if self.apiModule == "" then self.apiModule = nil end
if self.apiFunction == "" then self.apiFunction = nil end
return self
end

View File

@ -3,17 +3,31 @@ local JSON = (loadfile "util/JSON.lua")()
local M = {}
M.__index = M
local REQUEST_ID_ARGUMENT = "rq_id"
local INCLUDE_ENDPOINT_INFO = false
setmetatable(M, {
__call = function(cls, ...)
return cls.new(...)
end
})
function M.new()
--requestObject should always be passed (except on init failure, when it is not yet available)
function M.new(requestObject)
local self = setmetatable({}, M)
self.body = {status = nil, data = {}}
if requestObject ~= nil then
local rqId = requestObject:get(REQUEST_ID_ARGUMENT)
if rqId ~= nil then self.body[REQUEST_ID_ARGUMENT] = rqId end
if INCLUDE_ENDPOINT_INFO == true then
self.body["module"] = requestObject:getApiModule()
self.body["function"] = requestObject:getApiFunction() or ""
end
end
return self
end
@ -23,12 +37,12 @@ end
function M:setSuccess(msg)
self.body.status = "success"
self.body.msg = msg
if msg ~= "" then self.body.msg = msg end
end
function M:setError(msg)
self.body.status = "error"
self.body.msg = msg
if msg ~= "" then self.body.msg = msg end
end
--NOTE: with this method, to add nested data, it is necessary to precreate the table and add it with its root key

View File

@ -2,6 +2,13 @@ local uci = require("uci").cursor()
local M = {}
function M.toboolean(s)
if not s then return false end
local b = s:lower()
return (b == "1" or b == "t" or b == "true") and true or false
end
function M.getUciSectionName(config, type)
local sname = nil
uci:foreach(config, type, function(s) sname = s[".name"] end)