From f988c13dfa15d306f4578672ab382dd4867896b6 Mon Sep 17 00:00:00 2001 From: Wouter R Date: Wed, 17 Jul 2013 22:55:27 +0200 Subject: [PATCH] Many changes, mostly a new configuration interface: * change API access control to use function name suffixes * implement settings interface with baseline configuration * make AP properties configurable * AP SSID can now contain partial MAC address * update more quotation style and documentation * remove captive directory change from network mode switcher. --- src/conf_defaults.lua | 3 - src/main.lua | 8 +-- src/network/netconfig.lua | 18 ++--- src/network/wlanconfig.lua | 14 ++++ src/rest/api/api_config.lua | 33 +++++++++ src/rest/api/api_network.lua | 20 +++--- src/rest/api/api_test.lua | 18 ++--- src/rest/request.lua | 129 ++++++++++++++++++++--------------- src/rest/response.lua | 4 +- src/test/test_settings.lua | 12 +++- src/test/test_utils.lua | 1 + src/test/test_wlanconfig.lua | 39 +++++++++++ src/util/settings.lua | 17 +++-- 13 files changed, 213 insertions(+), 103 deletions(-) create mode 100644 src/rest/api/api_config.lua create mode 100644 src/test/test_wlanconfig.lua diff --git a/src/conf_defaults.lua b/src/conf_defaults.lua index 84c62c7..2f3162e 100644 --- a/src/conf_defaults.lua +++ b/src/conf_defaults.lua @@ -11,7 +11,6 @@ M.DEBUG_PCALLS = true M.API_INCLUDE_ENDPOINT_INFO = false --- was: M.DEFAULT_AP_SSID = "d3d-ap-%MAC_ADDR_TAIL%" M.apSsid = { default = 'd3d-ap-%%MAC_ADDR_TAIL%%', type = 'string', @@ -20,7 +19,6 @@ M.apSsid = { max = 32 } --- was: M.DEFAULT_AP_ADDRESS = "192.168.10.1" M.apAddress = { default = '192.168.10.1', type = 'string', @@ -28,7 +26,6 @@ M.apAddress = { regex = '%d+\.%d+\.%d+\.%d+' } --- was: M.DEFAULT_AP_NETMASK = "255.255.255.0" M.apNetmask = { default = '255.255.255.0', type = 'string', diff --git a/src/main.lua b/src/main.lua index d5ea59f..ce48df8 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,6 +1,6 @@ package.path = package.path .. ';/usr/share/lua/wifibox/?.lua' -local config = require('config') +local confDefaults = require('conf_defaults') local u = require('util.utils') local l = require('util.logger') local wifi = require('network.wlanconfig') @@ -19,7 +19,7 @@ local function init() l:init(l.LEVEL.debug) l:setStream(io.stderr) - if config.DEBUG_PCALLS then l:info("Wifibox CGI handler started (pcall debugging enabled)") + if confDefaults.DEBUG_PCALLS then l:info("Wifibox CGI handler started (pcall debugging enabled)") else l:info("Wifibox CGI handler started") end @@ -39,7 +39,7 @@ local function init() end local function main() - local rq = RequestClass.new(postData, config.DEBUG_PCALLS) + local rq = RequestClass.new(postData, confDefaults.DEBUG_PCALLS) l:info("received request of type " .. rq:getRequestMethod() .. " for " .. (rq:getRequestedApiModule() or "") .. "/" .. (rq:getRealApiFunctionName() or "") .. " with arguments: " .. u.dump(rq:getAll())) @@ -48,7 +48,7 @@ end l:debug("user agent: " .. rq:getUserAgent()) end - if (not config.DEBUG_PCALLS and rq:getRequestMethod() == 'CMDLINE') then + if (not confDefaults.DEBUG_PCALLS and rq:getRequestMethod() == 'CMDLINE') then if rq:get('autowifi') ~= nil then setupAutoWifiMode() else diff --git a/src/network/netconfig.lua b/src/network/netconfig.lua index 8e7c9de..a69d531 100644 --- a/src/network/netconfig.lua +++ b/src/network/netconfig.lua @@ -1,6 +1,7 @@ -local config = require("config") local u = require("util.utils") local l = require("util.logger") +local s = require("util.settings") +local wifi = require("network.wlanconfig") local uci = require("uci").cursor() local M = {} @@ -95,15 +96,16 @@ end --[[ Add/remove access point network ]] function reconf.apnet_add_noreload(dirtyList) reconf.apnet_add(dirtyList, true) end function reconf.apnet_add(dirtyList, noReload) + local ourSsid = wifi.getSubstitutedSsid(s.get('apSsid')) local sname = nil uci:foreach("wireless", "wifi-iface", function(s) - if s.ssid == config.DEFAULT_AP_SSID then sname = s[".name"]; return false end + if s.ssid == ourSsid then sname = s[".name"]; return false end end) if sname == nil then sname = uci:add("wireless", "wifi-iface") end M.uciTableSet("wireless", sname, { network = wifi.NET, - ssid = config.DEFAULT_AP_SSID, + ssid = ourSsid, encryption = "none", device = "radio0", mode = "ap", @@ -115,7 +117,7 @@ end function reconf.apnet_rm(dirtyList) local sname = nil uci:foreach("wireless", "wifi-iface", function(s) - if s.ssid == config.DEFAULT_AP_SSID then sname = s[".name"]; return false end + if s.ssid == wifi.getSubstitutedSsid(s.get(apSsid)) then sname = s[".name"]; return false end end) if sname == nil then return l:info("AP network configuration does not exist, nothing to remove") end uci:delete("wireless", sname) @@ -130,8 +132,8 @@ function reconf.staticaddr_add(dirtyList) --NOTE: 'type = "bridge"' should -not- be added as this prevents defining a separate dhcp pool (http://wiki.openwrt.org/doc/recipes/routedap) M.uciTableSet("network", wifi.NET, { proto = "static", - ipaddr = config.DEFAULT_AP_ADDRESS, - netmask = config.DEFAULT_AP_NETMASK + ipaddr = s.get('apAddress'), + netmask = s.get('apNetmask') }) bothBits(dirtyList, "network") end @@ -178,7 +180,7 @@ end --[[ Add/remove redirecton of all DNS requests to self ]] function reconf.dnsredir_add(dirtyList) - local redirText = "/#/" .. config.DEFAULT_AP_ADDRESS + local redirText = "/#/" .. s.get('apAddress') local sname = u.getUciSectionName("dhcp", "dnsmasq") if sname == nil then return l:error("dhcp config does not contain a dnsmasq section") end if uci:get("dhcp", sname, "address") ~= nil then return l:debug("DNS address redirection already in place, not re-adding", false) end @@ -226,7 +228,7 @@ function reconf.natreflect_add(dirtyList) proto = "tcp", src_dport = "80", dest_port = "80", - dest_ip = config.DEFAULT_AP_ADDRESS, + dest_ip = s.get('apAddress'), target = "DNAT" }) bothBits(dirtyList, "firewall") diff --git a/src/network/wlanconfig.lua b/src/network/wlanconfig.lua index 2256417..96b05e7 100644 --- a/src/network/wlanconfig.lua +++ b/src/network/wlanconfig.lua @@ -12,6 +12,20 @@ M.NET = "wlan" local dev, dev_api +-- if a substitution of baseApSsid is requested, cachedApSsid is returned if not nil +local cachedApSsid, baseApSsid = nil, nil + +function M.getSubstitutedSsid(unformattedSsid) + if unformattedSsid == baseApSsid and cachedApSsid ~= nil then return cachedApSsid end + + local macTail = M.getMacAddress():sub(7) + + baseApSsid = unformattedSsid + cachedApSsid = unformattedSsid:gsub('%%%%MAC_ADDR_TAIL%%%%', macTail) + + return cachedApSsid +end + --- Map device mode as reported by iwinfo to device mode as required by UCI -- Note that this function is quite naive. diff --git a/src/rest/api/api_config.lua b/src/rest/api/api_config.lua new file mode 100644 index 0000000..c9f0229 --- /dev/null +++ b/src/rest/api/api_config.lua @@ -0,0 +1,33 @@ +local log = require('util.logger') +local settings = require('util.settings') + +local M = { + isApi = true +} + + +function M._global_GET(request, response) + response:setSuccess() + --TODO: we need a function to list all configuration keys + for k,v in pairs(request:getAll()) do + local r,m = settings.get(k) + + if r then response:addData(k, r) + else response:addData(k, "could not read key ('" .. m .. "')") + end + end +end + +function M._global_POST(request, response) + response:setSuccess() + + for k,v in pairs(request:getAll()) do + local r,m = settings.set(k, v) + + if r then response:addData(k, "ok") + else response:addData(k, "could not set key ('" .. m .. "')") + end + end +end + +return M diff --git a/src/rest/api/api_network.lua b/src/rest/api/api_network.lua index 9351e09..0a246df 100644 --- a/src/rest/api/api_network.lua +++ b/src/rest/api/api_network.lua @@ -1,13 +1,14 @@ -local config = require("config") +local s = require("util.settings") local u = require("util.utils") local l = require("util.logger") local netconf = require("network.netconfig") local wifi = require("network.wlanconfig") local ResponseClass = require("rest.response") -local M = {} +local M = { + isApi = true +} -M.isApi = true function M._global(request, response) response:setError("not implemented") @@ -25,7 +26,7 @@ function M.available(request, response) response:setSuccess("") local netInfoList = {} for _, se in ipairs(sr) do - if noFilter or se.mode ~= "ap" and se.ssid ~= config.DEFAULT_AP_SSID then + if noFilter or se.mode ~= "ap" and se.ssid ~= wifi.getSubstitutedSsid(s.get('apSsid')) then local netInfo = {} netInfo["ssid"] = se.ssid @@ -123,7 +124,8 @@ function M.assoc(request, response) end wifi.activateConfig(argSsid) - netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" } + --netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wwwcaptive="rm", wireless="reload" } + netconf.switchConfiguration{ wifiiface="add", apnet="rm", staticaddr="rm", dhcppool="rm", wwwredir="rm", dnsredir="rm", wireless="reload" } response:setSuccess("wlan associated") response:addData("ssid", argSsid) end @@ -139,11 +141,13 @@ end --UNTESTED function M.openap(request, response) --add AP net, activate it, deactivate all others, reload network/wireless config, add all dhcp and captive settings and reload as needed + local ssid = wifi.getSubstitutedSsid(s.get('apSsid')) netconf.switchConfiguration{apnet="add_noreload"} - wifi.activateConfig(config.DEFAULT_AP_SSID) - netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" } + wifi.activateConfig(ssid) + --netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add", wwwcaptive="add" } + netconf.switchConfiguration{ wifiiface="add", network="reload", staticaddr="add", dhcppool="add", wwwredir="add", dnsredir="add" } response:setSuccess("switched to Access Point mode") - response:addData("ssid", config.DEFAULT_AP_SSID) + response:addData("ssid", ssid) end --UNTESTED diff --git a/src/rest/api/api_test.lua b/src/rest/api/api_test.lua index c01f454..75c829c 100644 --- a/src/rest/api/api_test.lua +++ b/src/rest/api/api_test.lua @@ -1,16 +1,8 @@ local l = require("util.logger") local ResponseClass = require("rest.response") -local M = {} - -M.isApi = true - ---empty or nil is equivalent to 'ANY', otherwise restrict to specified letters (command-line is always allowed) -M._access = { - _global = "GET", - success = "GET", fail = "GET", error = "GET", - read = "GET", write = "POST", readwrite = "ANY", readwrite2 = "", - echo = "GET" +local M = { + isApi = true } @@ -38,9 +30,9 @@ end function M.read(request, response) response:setSuccess("this endpoint can only be accessed through GET request") end -function M.write(request, response) response:setSuccess("this endpoint can only be accessed through POST request") end -function M.readwrite(request, response) response:setSuccess("this endpoint can only be accessed through POST request") end -function M.readwrite2(request, response) response:setSuccess("this endpoint can only be accessed through POST request") end +function M.write_POST(request, response) response:setSuccess("this endpoint can only be accessed through POST request") end +function M.readwrite(request, response) response:setSuccess("this endpoint can be accessed both through GET and POST request") end +function M.readwrite2(request, response) response:setSuccess("this endpoint can be accessed both through GET and POST request") end function M.echo(request, response) diff --git a/src/rest/request.lua b/src/rest/request.lua index 8b64768..c852c82 100644 --- a/src/rest/request.lua +++ b/src/rest/request.lua @@ -1,12 +1,13 @@ -local util = require("util.utils") --required for string:split() -local urlcode = require("util.urlcode") -local config = require("config") -local ResponseClass = require("rest.response") +local util = require('util.utils') -- required for string:split() +local urlcode = require('util.urlcode') +local confDefaults = require('conf_defaults') +local s = require('util.settings') +local ResponseClass = require('rest.response') local M = {} M.__index = M -local GLOBAL_API_FUNCTION_NAME = "_global" +local GLOBAL_API_FUNCTION_NAME = '_global' --NOTE: requestedApi* contain what was extracted from the request data @@ -17,7 +18,6 @@ M.requestedApiFunction = nil M.resolvedApiFunction = nil --will contain function address, or nil M.realApiFunctionName = nil --will contain requested name, or global name, or nil M.resolutionError = nil --non-nil means function could not be resolved -M.moduleAccessTable = nil local function kvTableFromUrlEncodedString(encodedText) @@ -46,26 +46,30 @@ local function kvTableFromArray(argArray) return args end ---NOTE: this function ignores empty tokens (e.g. '/a//b/' yields { [1] = a, [2] = b }) +--- Create an array from the given '/'-separated path. +-- Empty path elements are not ignored (e.g. '/a//b' yields { [1] = '', [2] = 'a', [3] = '', [4] = 'b' }). +-- @param pathText The path to split. +-- @return An array with the path elements. local function arrayFromPath(pathText) - return pathText and pathText:split("/") or {} + return pathText and pathText:split('/') or {} end ---returns true if acceptable is nil or empty or 'ANY' or if it contains requested -local function matchRequestMethod(acceptable, requested) - return acceptable == nil or acceptable == '' or acceptable == 'ANY' or string.find(acceptable, requested) -end - - ---returns either a module object, or nil+errmsg +--- Resolve the given module name. +-- Modules are searched for in the 'rest.api' path, with their name prefixed by 'api_'. +-- e.g. if modname is 'test', then the generated 'require()' path will be 'rest.api.api_test'. +-- Furthermore, the module must have the table key 'isApi' set to true. +-- @param modname The basename of the module to resolve. +-- @return Either a module object, or nil on error +-- @return An message on error, or nil otherwise +-- @see resolveApiFunction local function resolveApiModule(modname) if modname == nil then return nil, "missing module name" end - if string.find(modname, "_") == 1 then return nil, "module names starting with '_' are preserved for internal use" end + if string.find(modname, '_') == 1 then return nil, "module names starting with '_' are preserved for internal use" end - local reqModName = "rest.api.api_" .. modname + local reqModName = 'rest.api.api_' .. modname local ok, modObj - if config.DEBUG_PCALLS then ok, modObj = true, require(reqModName) + if confDefaults.DEBUG_PCALLS then ok, modObj = true, require(reqModName) else ok, modObj = pcall(require, reqModName) end @@ -76,9 +80,17 @@ local function resolveApiModule(modname) return modObj end ---returns resultData+nil (usual), or nil+errmsg (unresolvable or inaccessible) ---resultData contains 'func', 'accessTable' and if found, also 'blankArg' -local function resolveApiFunction(modname, funcname) +--- Resolves a module/function name pair with appropiate access for the given request method. +-- First, the function name suffixed with the request method, if not found the plain +-- function name is looked up. +-- Returned result data contains a 'func' key and if found, also 'blankArg'. +-- @param modname Basename of the module to resolve funcname in. +-- @param funcname Basename of the function to resolve. +-- @param requestMethod Method by which the request was received. +-- @return A table with resultData or nil on error. +-- @return A message on error (unresolvable or inaccessible). +-- @see resolveApiModule +local function resolveApiFunction(modname, funcname, requestMethod) local resultData = {} if funcname and string.find(funcname, "_") == 1 then return nil, "function names starting with '_' are preserved for internal use" end @@ -90,15 +102,18 @@ local function resolveApiFunction(modname, funcname) end if (funcname == nil or funcname == '') then funcname = GLOBAL_API_FUNCTION_NAME end --treat empty function name as nil - local f = mod[funcname] + local rqType = requestMethod == 'POST' and 'POST' or 'GET' + local fGeneric = mod[funcname] + local fWithMethod = mod[funcname .. '_' .. rqType] local funcNumber = tonumber(funcname) - if (type(f) == "function") then - resultData.func = f - resultData.accessTable = mod._access + if (type(fWithMethod) == 'function') then + resultData.func = fWithMethod + elseif (type(fGeneric) == 'function') then + resultData.func = fGeneric elseif funcNumber ~= nil then - resultData.func = mod[GLOBAL_API_FUNCTION_NAME] - resultData.accessTable = mod._access + resultData.func = mod[GLOBAL_API_FUNCTION_NAME .. '_' .. rqType] + if not resultData.func then resultData.func = mod[GLOBAL_API_FUNCTION_NAME] end resultData.blankArg = funcNumber else return nil, ("function '" .. funcname .. "' does not exist in API module '" .. modname .. "'") @@ -116,27 +131,37 @@ setmetatable(M, { --This function initializes itself using various environment variables, the arg array and the given postData --NOTE: if debugging is enabled, commandline arguments 'm' and 'f' override requested module and function -function M.new(postData, debug) +function M.new(postData, debugEnabled) local self = setmetatable({}, M) --NOTE: is it correct to assume that absence of REQUEST_METHOD indicates command line invocation? - self.requestMethod = os.getenv("REQUEST_METHOD") + self.requestMethod = os.getenv('REQUEST_METHOD') if self.requestMethod ~= nil then - self.remoteHost = os.getenv("REMOTE_HOST") - self.remotePort = os.getenv("REMOTE_PORT") - self.userAgent = os.getenv("HTTP_USER_AGENT") + self.remoteHost = os.getenv('REMOTE_HOST') + self.remotePort = os.getenv('REMOTE_PORT') + self.userAgent = os.getenv('HTTP_USER_AGENT') else - self.requestMethod = "CMDLINE" + self.requestMethod = 'CMDLINE' end self.cmdLineArgs = kvTableFromArray(arg) - self.getArgs = kvTableFromUrlEncodedString(os.getenv("QUERY_STRING")) + self.getArgs = kvTableFromUrlEncodedString(os.getenv('QUERY_STRING')) self.postArgs = kvTableFromUrlEncodedString(postData) - self.pathArgs = arrayFromPath(os.getenv("PATH_INFO")) + self.pathArgs = arrayFromPath(os.getenv('PATH_INFO')) - --override path arguments with command line parameter if debugging is enabled - if debug and self.requestMethod == "CMDLINE" then - self.pathArgs = arrayFromPath(self.cmdLineArgs["p"]) + -- override path arguments with command line parameter and allow to emulate GET/POST if debugging is enabled + if debugEnabled and self.requestMethod == 'CMDLINE' then + self.pathArgs = arrayFromPath(self.cmdLineArgs['p']) + + if self.cmdLineArgs['r'] == 'GET' then + self.requestMethod = 'GET' + self.getArgs = self.cmdLineArgs + self.getArgs.p, self.getArgs.r = nil, nil + elseif self.cmdLineArgs['r'] == 'POST' then + self.requestMethod = 'POST' + self.postArgs = self.cmdLineArgs + self.postArgs.p, self.postArgs.r = nil, nil + end end table.remove(self.pathArgs, 1) --drop the first 'empty' field caused by the opening slash of the query string @@ -144,16 +169,15 @@ function M.new(postData, debug) if #self.pathArgs >= 1 then self.requestedApiModule = self.pathArgs[1] end if #self.pathArgs >= 2 then self.requestedApiFunction = self.pathArgs[2] end - if self.requestedApiModule == "" then self.requestedApiModule = nil end - if self.requestedApiFunction == "" then self.requestedApiFunction = nil end + if self.requestedApiModule == '' then self.requestedApiModule = nil end + if self.requestedApiFunction == '' then self.requestedApiFunction = nil end -- Perform module/function resolution - local rData, errMsg = resolveApiFunction(self:getRequestedApiModule(), self:getRequestedApiFunction()) + local rData, errMsg = resolveApiFunction(self:getRequestedApiModule(), self:getRequestedApiFunction(), self.requestMethod) if rData ~= nil and rData.func ~= nil then --function (possibly the global one) could be resolved self.resolvedApiFunction = rData.func - self.moduleAccessTable = rData.accessTable if rData.blankArg ~= nil then --apparently it was the global one, and we received a 'blank argument' self:setBlankArgument(rData.blankArg) self.realApiFunctionName = GLOBAL_API_FUNCTION_NAME @@ -185,11 +209,11 @@ function M:getRemotePort() return self.remotePort or 0 end function M:getUserAgent() return self.userAgent or "" end function M:get(key) - if self.requestMethod == "GET" then + if self.requestMethod == 'GET' then return self.getArgs[key] - elseif self.requestMethod == "POST" then + elseif self.requestMethod == 'POST' then return self.postArgs[key] - elseif self.requestMethod == "CMDLINE" then + elseif self.requestMethod == 'CMDLINE' then return self.cmdLineArgs[key] else return nil @@ -197,11 +221,11 @@ function M:get(key) end function M:getAll() - if self.requestMethod == "GET" then + if self.requestMethod == 'GET' then return self.getArgs - elseif self.requestMethod == "POST" then + elseif self.requestMethod == 'POST' then return self.postArgs - elseif self.requestMethod == "CMDLINE" then + elseif self.requestMethod == 'CMDLINE' then return self.cmdLineArgs else return nil @@ -218,16 +242,9 @@ function M:handle() local resp = ResponseClass.new(self) if (self.resolvedApiFunction ~= nil) then --we found a function (possible the global function) - --check access type - local accessText = self.moduleAccessTable[self.realApiFunctionName] - if not matchRequestMethod(accessText, self.requestMethod) then - resp:setError("function '" .. modname .. "/" .. self.realApiFunctionName .. "' requires different request method ('" .. accessText .. "')") - return resp, "incorrect access method (" .. accessText .. " != " .. self.requestMethod .. ")" - end - --invoke the function local ok, r - if config.DEBUG_PCALLS then ok, r = true, self.resolvedApiFunction(self, resp) + if confDefaults.DEBUG_PCALLS then ok, r = true, self.resolvedApiFunction(self, resp) else ok, r = pcall(self.resolvedApiFunction, self, resp) end diff --git a/src/rest/response.lua b/src/rest/response.lua index 7824462..6c17012 100644 --- a/src/rest/response.lua +++ b/src/rest/response.lua @@ -1,5 +1,5 @@ local JSON = require("util/JSON") -local config = require("config") +local s = require("util.settings") local M = {} M.__index = M @@ -26,7 +26,7 @@ function M.new(requestObject) local rqId = requestObject:get(REQUEST_ID_ARGUMENT) if rqId ~= nil then self.body[REQUEST_ID_ARGUMENT] = rqId end - if config.API_INCLUDE_ENDPOINT_INFO == true then + if s.API_INCLUDE_ENDPOINT_INFO == true then self.body["module"] = requestObject:getRequestedApiModule() self.body["function"] = requestObject:getRealApiFunctionName() or "" end diff --git a/src/test/test_settings.lua b/src/test/test_settings.lua index 7f1d75f..39d84e1 100644 --- a/src/test/test_settings.lua +++ b/src/test/test_settings.lua @@ -35,8 +35,8 @@ function M:test_get() end function M:test_set() - local key = 'apAddress' - local goodValue, badValue1, badValue2 = '10.0.0.1', '10.00.1', '10.0.0d.1' + local key, intKey = 'apAddress', 'temperature' + local intValue, goodValue, badValue1, badValue2 = 340, '10.0.0.1', '10.00.1', '10.0.0d.1' assert(s.get(key) == defaults.apAddress.default) assert(s.isDefault(key)) @@ -53,6 +53,14 @@ function M:test_set() assert(s.set(key, nil)) assert(s.isDefault(key)) + + -- test with value of int type + assert(s.get(intKey) == defaults.temperature.default) + assert(s.isDefault(intKey)) + + assert(s.set(intKey, intValue)) + assert(s.get(intKey) == intValue) + assert(not s.isDefault(intKey)) end function M:test_setNonExistent() diff --git a/src/test/test_utils.lua b/src/test/test_utils.lua index 7faaac8..ada6205 100644 --- a/src/test/test_utils.lua +++ b/src/test/test_utils.lua @@ -98,6 +98,7 @@ function M:test_symlink() assert(false, 'not implemented') end +-- this should test for the following condition: 'Could not rename /www to /www-regular(/www: Invalid cross-device link)' function M:test_symlinkInRoot() assert(false, 'not implemented') end diff --git a/src/test/test_wlanconfig.lua b/src/test/test_wlanconfig.lua new file mode 100644 index 0000000..461bc64 --- /dev/null +++ b/src/test/test_wlanconfig.lua @@ -0,0 +1,39 @@ +local wlanconfig = require("network.wlanconfig") + +local M = { + _is_test = true, + _skip = {}, + _wifibox_only = {} +} + +local function captureCommandOutput(cmd) + local f = assert(io.popen(cmd, 'r')) + return assert(f:read('*all')) +end + +function M._setup() + wlanconfig.init() +end + +function M.test_getMacAddress() + local reportedMac = wlanconfig.getMacAddress() + local output = captureCommandOutput('ifconfig wlan0') + local actualMac = output:match('HWaddr (%w%w:%w%w:%w%w:%w%w:%w%w:%w%w)'):gsub(':', ''):upper() + + assert(reportedMac == actualMac) +end + +function M.test_getSubstitutedSsid() + local mac = wlanconfig.getMacAddress() + local macTail = mac:sub(7) + + local expected1 = 'pre' .. macTail .. 'post' + local expected2 = 'pre' .. macTail .. 'post-cache-test' + + assert(wlanconfig.getSubstitutedSsid('pre%%MAC_ADDR_TAIL%%post') == expected1) + assert(wlanconfig.getSubstitutedSsid('pre%%MAC_ADDR_TAIL%%post-cache-test') == expected2) + assert(wlanconfig.getSubstitutedSsid('pre%%MAC_ADDR_TAIL%%post') == expected1) + assert(wlanconfig.getSubstitutedSsid('pre%%MAC_ADDR_TAIL%%post') == expected1) +end + +return M diff --git a/src/util/settings.lua b/src/util/settings.lua index 685345f..ca13188 100644 --- a/src/util/settings.lua +++ b/src/util/settings.lua @@ -40,12 +40,15 @@ local function isValid(value, baseTable) if type == 'bool' then return isboolean(value) or nil,"invalid bool value" + elseif type == 'int' or type == 'float' then - local ok = isnumber(value) - ok = ok and (type == 'float' or math.floor(value) == value) - if min then ok = ok and value >= min end - if max then ok = ok and value <= max end - return ok or nil,"invalid int/float value" + local numValue = tonumber(value) + local ok = numValue and true or false + ok = ok and (type == 'float' or math.floor(numValue) == numValue) + if min then ok = ok and numValue >= min end + if max then ok = ok and numValue <= max end + return ok or nil,"invalid int/float value or out of range" + elseif type == 'string' then local ok = true if min then ok = ok and value:len() >= min end @@ -69,7 +72,7 @@ function M.get(key) if not base then return nil,ERR_NO_SUCH_KEY end local v = base.default - local uciV = fromUciValue(uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key)) + local uciV = fromUciValue(uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key), base.type) return uciV or v end @@ -95,7 +98,7 @@ function M.set(key, value) local current = uci:get(UCI_CONFIG_NAME, UCI_CONFIG_SECTION, key) - if fromUciValue(current) == value then return true end + if fromUciValue(current, base.type) == value then return true end if value ~= nil then local valid,m = isValid(value, base)