--[[
LuCI - Network model

Copyright 2009-2010 Jo-Philipp Wich <xm@subsignal.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

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

]]--

local type, next, pairs, ipairs, loadfile, table, tonumber, math, i18n
	= type, next, pairs, ipairs, loadfile, table, tonumber, math, luci.i18n

local nxo = require "nixio"
local ipc = require "luci.ip"
local sys = require "luci.sys"
local utl = require "luci.util"
local dsp = require "luci.dispatcher"
local uci = require "luci.model.uci"

module "luci.model.network"


local ifs, brs, sws, uci_r, uci_s

function _list_del(c, s, o, r)
	local val = uci_r:get(c, s, o)
	if val then
		local l = { }
		if type(val) == "string" then
			for val in val:gmatch("%S+") do
				if val ~= r then
					l[#l+1] = val
				end
			end
			if #l > 0 then
				uci_r:set(c, s, o, table.concat(l, " "))
			else
				uci_r:delete(c, s, o)
			end
		elseif type(val) == "table" then
			for _, val in ipairs(val) do
				if val ~= r then
					l[#l+1] = val
				end
			end
			if #l > 0 then
				uci_r:set(c, s, o, l)
			else
				uci_r:delete(c, s, o)
			end
		end
	end
end

function _list_add(c, s, o, a)
	local val = uci_r:get(c, s, o) or ""
	if type(val) == "string" then
		local l = { }
		for val in val:gmatch("%S+") do
			if val ~= a then
				l[#l+1] = val
			end
		end
		l[#l+1] = a
		uci_r:set(c, s, o, table.concat(l, " "))
	elseif type(val) == "table" then
		local l = { }
		for _, val in ipairs(val) do
			if val ~= a then
				l[#l+1] = val
			end
		end
		l[#l+1] = a
		uci_r:set(c, s, o, l)
	end
end

function _stror(s1, s2)
	if not s1 or #s1 == 0 then
		return s2 and #s2 > 0 and s2
	else
		return s1
	end
end

function _get(c, s, o)
	return uci_r:get(c, s, o)
end

function _set(c, s, o, v)
	if v ~= nil then
		if type(v) == "boolean" then v = v and "1" or "0" end
		return uci_r:set(c, s, o, v)
	else
		return uci_r:delete(c, s, o)
	end
end

function _wifi_iface(x)
	return (
		x:match("^wlan%d") or x:match("^wl%d") or x:match("^ath%d") or
		x:match("^%w+%.network%d")
	)
end

function _wifi_lookup(ifn)
	-- got a radio#.network# pseudo iface, locate the corresponding section
	local radio, ifnidx = ifn:match("^(%w+)%.network(%d+)$")
	if radio and ifnidx then
		local sid = nil
		local num = 0

		ifnidx = tonumber(ifnidx)
		uci_r:foreach("wireless", "wifi-iface",
			function(s)
				if s.device == radio then
					num = num + 1
					if num == ifnidx then
						sid = s['.name']
						return false
					end
				end
			end)

		return sid

	-- looks like wifi, try to locate the section via state vars
	elseif _wifi_iface(ifn) then
		local sid = nil

		uci_s:foreach("wireless", "wifi-iface",
			function(s)
				if s.ifname == ifn then
					sid = s['.name']
					return false
				end
			end)

		return sid
	end
end

function _iface_virtual(x)
	return (
		x:match("^6in4-%w") or x:match("^6to4-%w") or x:match("^3g-%w") or
		x:match("^ppp-%w") or x:match("^pppoe-%w") or x:match("^pppoa-%w") or
		x:match("^relay-%w")
	)
end

function _iface_ignore(x)
	return (
		x:match("^wmaster%d") or x:match("^wifi%d") or x:match("^hwsim%d") or
		x:match("^imq%d") or x:match("^mon.wlan%d") or
		x == "sit0" or x == "lo" or _iface_virtual(x)
	)
end


function init(cursor)
	uci_r = cursor or uci_r or uci.cursor()
	uci_s = uci_r:substate()

	ifs = { }
	brs = { }
	sws = { }

	-- read interface information
	local n, i
	for n, i in ipairs(nxo.getifaddrs()) do
		local name = i.name:match("[^:]+")
		local prnt = name:match("^([^%.]+)%.")

		if _iface_virtual(name) or not _iface_ignore(name) then
			ifs[name] = ifs[name] or {
				idx      = i.ifindex or n,
				name     = name,
				rawname  = i.name,
				flags    = { },
				ipaddrs  = { },
				ip6addrs = { }
			}

			if prnt then
				sws[name] = true
				sws[prnt] = true
			end

			if i.family == "packet" then
				ifs[name].flags   = i.flags
				ifs[name].stats   = i.data
				ifs[name].macaddr = i.addr
			elseif i.family == "inet" then
				ifs[name].ipaddrs[#ifs[name].ipaddrs+1] = ipc.IPv4(i.addr, i.netmask)
			elseif i.family == "inet6" then
				ifs[name].ip6addrs[#ifs[name].ip6addrs+1] = ipc.IPv6(i.addr, i.netmask)
			end
		end
	end

	-- read bridge informaton
	local b, l
	for l in utl.execi("brctl show") do
		if not l:match("STP") then
			local r = utl.split(l, "%s+", nil, true)
			if #r == 4 then
				b = {
					name    = r[1],
					id      = r[2],
					stp     = r[3] == "yes",
					ifnames = { ifs[r[4]] }
				}
				if b.ifnames[1] then
					b.ifnames[1].bridge = b
				end
				brs[r[1]] = b
			elseif b then
				b.ifnames[#b.ifnames+1] = ifs[r[2]]
				b.ifnames[#b.ifnames].bridge = b
			end
		end
	end

	return _M
end

function save(self, ...)
	uci_r:save(...)
	uci_r:load(...)
end

function commit(self, ...)
	uci_r:commit(...)
	uci_r:load(...)
end

function has_ipv6(self)
	return nfs.access("/proc/net/ipv6_route")
end

function add_network(self, n, options)
	local oldnet = self:get_network(n)
	if n and #n > 0 and n:match("^[a-zA-Z0-9_]+$") and not oldnet then
		if uci_r:section("network", "interface", n, options) then
			return network(n)
		end
	elseif oldnet and oldnet:is_empty() then
		if options then
			local k, v
			for k, v in pairs(options) do
				oldnet:set(k, v)
			end
		end
		return oldnet
	end
end

function get_network(self, n)
	if n and uci_r:get("network", n) == "interface" then
		return network(n)
	end
end

function get_networks(self)
	local nets = { }
	local nls = { }

	uci_r:foreach("network", "interface",
		function(s)
			nls[s['.name']] = network(s['.name'])
		end)

	local n
	for n in utl.kspairs(nls) do
		nets[#nets+1] = nls[n]
	end

	return nets
end

function del_network(self, n)
	local r = uci_r:delete("network", n)
	if r then
		uci_r:delete_all("network", "alias",
			function(s) return (s.interface == n) end)

		uci_r:delete_all("network", "route",
			function(s) return (s.interface == n) end)

		uci_r:delete_all("network", "route6",
			function(s) return (s.interface == n) end)

		uci_r:foreach("wireless", "wifi-iface",
			function(s)
				if s.network == n then
					uci_r:delete("wireless", s['.name'], "network")
				end
			end)
	end
	return r
end

function rename_network(self, old, new)
	local r
	if new and #new > 0 and new:match("^[a-zA-Z0-9_]+$") and not self:get_network(new) then
		r = uci_r:section("network", "interface", new, uci_r:get_all("network", old))

		if r then
			uci_r:foreach("network", "alias",
				function(s)
					if s.interface == old then
						uci_r:set("network", s['.name'], "interface", new)
					end
				end)

			uci_r:foreach("network", "route",
				function(s)
					if s.interface == old then
						uci_r:set("network", s['.name'], "interface", new)
					end
				end)

			uci_r:foreach("network", "route6",
				function(s)
					if s.interface == old then
						uci_r:set("network", s['.name'], "interface", new)
					end
				end)

			uci_r:foreach("wireless", "wifi-iface",
				function(s)
					if s.network == old then
						uci_r:set("wireless", s['.name'], "network", new)
					end
				end)

			uci_r:delete("network", old)
		end
	end
	return r or false
end

function get_interface(self, i)
	if ifs[i] or _wifi_iface(i) then
		return interface(i)
	else
		local ifc
		local num = { }
		uci_r:foreach("wireless", "wifi-iface",
			function(s)
				if s.device then
					num[s.device] = num[s.device] and num[s.device] + 1 or 1
					if s['.name'] == i then
						ifc = interface(
							"%s.network%d" %{s.device, num[s.device] })
						return false
					end
				end
			end)
		return ifc
	end
end

function get_interfaces(self)
	local iface
	local ifaces = { }
	local seen = { }
	local nfs = { }

	-- find normal interfaces
	uci_r:foreach("network", "interface",
		function(s)
			for iface in utl.imatch(s.ifname) do
				if not _iface_ignore(iface) and not _wifi_iface(iface) then
					seen[iface] = true
					nfs[iface] = interface(iface)
				end
			end
		end)

	for iface in utl.kspairs(ifs) do
		if not (seen[iface] or _iface_ignore(iface) or _wifi_iface(iface)) then
			nfs[iface] = interface(iface)
		end
	end

	for iface in utl.kspairs(nfs) do
		ifaces[#ifaces+1] = nfs[iface]
	end

	-- find wifi interfaces
	local num = { }
	local wfs = { }
	uci_r:foreach("wireless", "wifi-iface",
		function(s)
			if s.device then
				num[s.device] = num[s.device] and num[s.device] + 1 or 1
				local i = "%s.network%d" %{ s.device, num[s.device] }
				wfs[i] = interface(i)
			end
		end)

	for iface in utl.kspairs(wfs) do
		ifaces[#ifaces+1] = wfs[iface]
	end

	return ifaces
end

function ignore_interface(self, x)
	return _iface_ignore(x)
end

function get_wifidev(self, dev)
	if uci_r:get("wireless", dev) == "wifi-device" then
		return wifidev(dev)
	end
end

function get_wifidevs(self)
	local devs = { }
	local wfd  = { }

	uci_r:foreach("wireless", "wifi-device",
		function(s) wfd[#wfd+1] = s['.name'] end)

	local dev
	for _, dev in utl.vspairs(wfd) do
		devs[#devs+1] = wifidev(dev)
	end

	return devs
end

function get_wifinet(self, net)
	local wnet = _wifi_lookup(net)
	if wnet then
		return wifinet(wnet)
	end
end

function add_wifinet(self, net, options)
	if type(options) == "table" and options.device and
		uci_r:get("wireless", options.device) == "wifi-device"
	then
		local wnet = uci_r:section("wireless", "wifi-iface", nil, options)
		return wifinet(wnet)
	end
end

function del_wifinet(self, net)
	local wnet = _wifi_lookup(net)
	if wnet then
		uci_r:delete("wireless", wnet)
		return true
	end
	return false
end


network = utl.class()

function network.__init__(self, name)
	self.sid = name
end

function network._get(self, opt)
	local v = uci_r:get("network", self.sid, opt)
	if type(v) == "table" then
		return table.concat(v, " ")
	end
	return v or ""
end

function network._ip(self, opt, family, list)
	local ip = uci_s:get("network", self.sid, opt)
	local fc = (family == 6) and ipc.IPv6 or ipc.IPv4
	if ip or list then
		if list then
			local l = { }
			for ip in utl.imatch(ip) do
				ip = fc(ip)
				if ip then l[#l+1] = ip:string() end
			end
			return l
		else
			ip = fc(ip)
			return ip and ip:string()
		end
	end
end

function network.get(self, opt)
	return _get("network", self.sid, opt)
end

function network.set(self, opt, val)
	return _set("network", self.sid, opt, val)
end

function network.ifname(self)
	local p = self:proto()
	if self:is_bridge() then
		return "br-" .. self.sid
	elseif self:proto() == "relay" then
		return uci_s:get("network", self.sid, "up") == "1" and "lo" or nil
	elseif self:is_virtual() then
		return p .. "-" .. self.sid
	else
		local num = { }
		local dev = uci_r:get("network", self.sid, "ifname") or
			uci_s:get("network", self.sid, "ifname")

		dev = (type(dev) == "table") and dev[1] or dev
		dev = (dev ~= nil) and dev:match("%S+")

		if not dev then
			uci_r:foreach("wireless", "wifi-iface",
				function(s)
					if s.device then
						num[s.device] = num[s.device]
							and num[s.device] + 1 or 1

						if s.network == self.sid then
							dev = "%s.network%d" %{ s.device, num[s.device] }
							return false
						end
					end
				end)
		end

		return dev
	end
end

function network.device(self)
	local dev = uci_r:get("network", self.sid, "device") or
		uci_s:get("network", self.sid, "device")

	dev = (type(dev) == "table") and dev[1] or dev

	if not dev or dev:match("[^%w%-%.%s]") then
		dev = uci_r:get("network", self.sid, "ifname") or
			uci_s:get("network", self.sid, "ifname")

		dev = (type(dev) == "table") and dev[1] or dev
	end

	for dev in utl.imatch(dev) do
		return dev
	end
end

function network.proto(self)
	return self:_get("proto") or "none"
end

function network.type(self)
	return self:_get("type")
end

function network.name(self)
	return self.sid
end

function network.uptime(self)
	local cnt = tonumber(uci_s:get("network", self.sid, "connect_time"))
	if cnt ~= nil then
		return nxo.sysinfo().uptime - cnt
	else
		return 0
	end
end

function network.expires(self)
	local a = tonumber(uci_s:get("network", self.sid, "lease_acquired"))
	local l = tonumber(uci_s:get("network", self.sid, "lease_lifetime"))
	if a and l then
		l = l - (nxo.sysinfo().uptime - a)
		return l > 0 and l or 0
	end
	return -1
end

function network.metric(self)
	return tonumber(uci_s:get("network", self.sid, "metric")) or 0
end

function network.ipaddr(self)
	return self:_ip("ipaddr", 4)
end

function network.netmask(self)
	return self:_ip("netmask", 4)
end

function network.gwaddr(self)
	return self:_ip("gateway", 4)
end

function network.dnsaddrs(self)
	return self:_ip("dns", 4, true)
end

function network.ip6addr(self)
	local ip6 = self:_ip("ip6addr", 6)
	if not ip6 then
		local ifc = ifs[self:ifname()]
		if ifc and ifc.ip6addrs then
			local a
			for _, a in ipairs(ifc.ip6addrs) do
				if not a:is6linklocal() then
					ip6 = a:string()
					break
				end
			end
		end
	end
	return ip6
end

function network.gw6addr(self)
	local ip6 = self:_ip("ip6gw", 6)
	if not ip6 then
		local dr6 = sys.net.defaultroute6()
		if dr6 and dr6.device == self:ifname() then
			return dr6.nexthop:string()
		end
	end
	return ip6
end

function network.dns6addrs(self)
	return self:_ip("dns", 6, true)
end

function network.is_bridge(self)
	return (self:type() == "bridge")
end

function network.is_virtual(self)
	local p = self:proto()
	return (
		p == "3g" or p == "6in4" or p == "6to4" or p == "ppp" or
		p == "pppoe" or p == "pppoa" or p == "relay"
	)
end

function network.is_empty(self)
	if self:is_virtual() then
		return false
	else
		local rv = true

		if (self:_get("ifname") or ""):match("%S+") then
			rv = false
		end

		uci_r:foreach("wireless", "wifi-iface",
			function(s)
				if s.network == self.sid then
					rv = false
					return false
				end
			end)

		return rv
	end
end

function network.add_interface(self, ifname)
	if not self:is_virtual() then
		if type(ifname) ~= "string" then
			ifname = ifname:name()
		else
			ifname = ifname:match("[^%s:]+")
		end

		-- remove the interface from all ifaces
		uci_r:foreach("network", "interface",
			function(s)
				_list_del("network", s['.name'], "ifname", ifname)
			end)

		-- if its a wifi interface, change its network option
		local wif = _wifi_lookup(ifname)
		if wif then
			uci_r:set("wireless", wif, "network", self.sid)

		-- add iface to our iface list
		else
			_list_add("network", self.sid, "ifname", ifname)
		end
	end
end

function network.del_interface(self, ifname)
	if not self:is_virtual() then
		if utl.instanceof(ifname, interface) then
			ifname = ifname:name()
		else
			ifname = ifname:match("[^%s:]+")
		end

		-- if its a wireless interface, clear its network option
		local wif = _wifi_lookup(ifname)
		if wif then	uci_r:delete("wireless", wif, "network") end

		-- remove the interface
		_list_del("network", self.sid, "ifname", ifname)
	end
end

function network.get_interfaces(self)
	local ifaces = { }

	local ifn
	if self:is_virtual() then
		ifn = self:proto() .. "-" .. self.sid
		ifaces = { interface(ifn) }
	else
		local nfs = { }
		for ifn in utl.imatch(self:get("ifname")) do
			ifn = ifn:match("[^:]+")
			nfs[ifn] = interface(ifn)
		end

		for ifn in utl.kspairs(nfs) do
			ifaces[#ifaces+1] = nfs[ifn]
		end

		local num = { }
		local wfs = { }
		uci_r:foreach("wireless", "wifi-iface",
			function(s)
				if s.device then
					num[s.device] = num[s.device] and num[s.device] + 1 or 1
					if s.network == self.sid then
						ifn = "%s.network%d" %{ s.device, num[s.device] }
						wfs[ifn] = interface(ifn)
					end
				end
			end)

		for ifn in utl.kspairs(wfs) do
			ifaces[#ifaces+1] = wfs[ifn]
		end
	end

	return ifaces
end

function network.contains_interface(self, ifname)
	if type(ifname) ~= "string" then
		ifname = ifname:name()
	else
		ifname = ifname:match("[^%s:]+")
	end

	local ifn
	if self:is_virtual() then
		ifn = self:proto() .. "-" .. self.sid
		return ifname == ifn
	else
		for ifn in utl.imatch(self:get("ifname")) do
			ifn = ifn:match("[^:]+")
			if ifn == ifname then
				return true
			end
		end

		local wif = _wifi_lookup(ifname)
		if wif then
			return (uci_r:get("wireless", wif, "network") == self.sid)
		end
	end

	return false
end

function network.adminlink(self)
	return dsp.build_url("admin", "network", "network", self.sid)
end


interface = utl.class()
function interface.__init__(self, ifname)
	local wif = _wifi_lookup(ifname)
	if wif then self.wif = wifinet(wif) end

	self.ifname = self.ifname or ifname
	self.dev    = ifs[self.ifname]
end

function interface.name(self)
	return self.wif and self.wif:ifname() or self.ifname
end

function interface.mac(self)
	return self.dev and self.dev.macaddr or "00:00:00:00:00:00"
end

function interface.ipaddrs(self)
	return self.dev and self.dev.ipaddrs or { }
end

function interface.ip6addrs(self)
	return self.dev and self.dev.ip6addrs or { }
end

function interface.type(self)
	if self.wif or _wifi_iface(self.ifname) then
		return "wifi"
	elseif brs[self.ifname] then
		return "bridge"
	elseif sws[self.ifname] or self.ifname:match("%.") then
		return "switch"
	else
		return "ethernet"
	end
end

function interface.shortname(self)
	if self.wif then
		return "%s %q" %{
			self.wif:active_mode(),
			self.wif:active_ssid() or self.wif:active_bssid()
		}
	else
		return self.ifname
	end
end

function interface.get_i18n(self)
	if self.wif then
		return "%s: %s %q" %{
			i18n.translate("Wireless Network"),
			self.wif:active_mode(),
			self.wif:active_ssid() or self.wif:active_bssid()
		}
	else
		return "%s: %q" %{ self:get_type_i18n(), self:name() }
	end
end

function interface.get_type_i18n(self)
	local x = self:type()
	if x == "wifi" then
		return i18n.translate("Wireless Adapter")
	elseif x == "bridge" then
		return i18n.translate("Bridge")
	elseif x == "switch" then
		return i18n.translate("Ethernet Switch")
	else
		return i18n.translate("Ethernet Adapter")
	end
end

function interface.adminlink(self)
	if self.wif then
		return self.wif:adminlink()
	end
end

function interface.ports(self)
	if self.br then
		local iface
		local ifaces = { }
		for _, iface in ipairs(self.br.ifnames) do
			ifaces[#ifaces+1] = interface(iface.name)
		end
		return ifaces
	end
end

function interface.bridge_id(self)
	if self.br then
		return self.br.id
	else
		return nil
	end
end

function interface.bridge_stp(self)
	if self.br then
		return self.br.stp
	else
		return false
	end
end

function interface.is_up(self)
	if self.wif then
		return self.wif:is_up()
	else
		return self.dev and self.dev.flags and self.dev.flags.up or false
	end
end

function interface.is_bridge(self)
	return (self:type() == "bridge")
end

function interface.is_bridgeport(self)
	return self.dev and self.dev.bridge and true or false
end

function interface.tx_bytes(self)
	return self.dev and self.dev.stats
		and self.dev.stats.tx_bytes or 0
end

function interface.rx_bytes(self)
	return self.dev and self.dev.stats
		and self.dev.stats.rx_bytes or 0
end

function interface.tx_packets(self)
	return self.dev and self.dev.stats
		and self.dev.stats.tx_packets or 0
end

function interface.rx_packets(self)
	return self.dev and self.dev.stats
		and self.dev.stats.rx_packets or 0
end

function interface.get_network(self)
	if self.dev and self.dev.network then
		self.network = _M:get_network(self.dev.network)
	end

	if not self.network then
		local net
		for _, net in ipairs(_M:get_networks()) do
			if net:contains_interface(self.ifname) or
			   net:ifname() == self.ifname
			then
				self.network = net
				return net
			end
		end
	else
		return self.network
	end
end

function interface.get_wifinet(self)
	return self.wif
end


wifidev = utl.class()
function wifidev.__init__(self, dev)
	self.sid    = dev
	self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { }
end

function wifidev.get(self, opt)
	return _get("wireless", self.sid, opt)
end

function wifidev.set(self, opt, val)
	return _set("wireless", self.sid, opt, val)
end

function wifidev.name(self)
	return self.sid
end

function wifidev.hwmodes(self)
	local l = self.iwinfo.hwmodelist
	if l and next(l) then
		return l
	else
		return { b = true, g = true }
	end
end

function wifidev.get_i18n(self)
	local t = "Generic"
	if self.iwinfo.type == "wl" then
		t = "Broadcom"
	elseif self.iwinfo.type == "madwifi" then
		t = "Atheros"
	end

	local m = ""
	local l = self:hwmodes()
	if l.a then m = m .. "a" end
	if l.b then m = m .. "b" end
	if l.g then m = m .. "g" end
	if l.n then m = m .. "n" end

	return "%s 802.11%s Wireless Controller (%s)" %{ t, m, self:name() }
end

function wifidev.is_up(self)
	local up = false

	uci_s:foreach("wireless", "wifi-iface",
		function(s)
			if s.device == self.sid then
				if s.up == "1" then
					up = true
					return false
				end
			end
		end)

	return up
end

function wifidev.get_wifinet(self, net)
	if uci_r:get("wireless", net) == "wifi-iface" then
		return wifinet(net)
	else
		local wnet = _wifi_lookup(net)
		if wnet then
			return wifinet(wnet)
		end
	end
end

function wifidev.get_wifinets(self)
	local nets = { }

	uci_r:foreach("wireless", "wifi-iface",
		function(s)
			if s.device == self.sid then
				nets[#nets+1] = wifinet(s['.name'])
			end
		end)

	return nets
end

function wifidev.add_wifinet(self, options)
	options = options or { }
	options.device = self.sid

	local wnet = uci_r:section("wireless", "wifi-iface", nil, options)
	if wnet then
		return wifinet(wnet, options)
	end
end

function wifidev.del_wifinet(self, net)
	if utl.instanceof(net, wifinet) then
		net = net.sid
	elseif uci_r:get("wireless", net) ~= "wifi-iface" then
		net = _wifi_lookup(net)
	end

	if net and uci_r:get("wireless", net, "device") == self.sid then
		uci_r:delete("wireless", net)
		return true
	end

	return false
end


wifinet = utl.class()
function wifinet.__init__(self, net, data)
	self.sid = net

	local num = { }
	local netid
	uci_r:foreach("wireless", "wifi-iface",
		function(s)
			if s.device then
				num[s.device] = num[s.device] and num[s.device] + 1 or 1
				if s['.name'] == self.sid then
					netid = "%s.network%d" %{ s.device, num[s.device] }
					return false
				end
			end
		end)

	local dev = uci_s:get("wireless", self.sid, "ifname") or netid

	self.netid  = netid
	self.wdev   = dev
	self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { }
	self.iwdata = data or uci_s:get_all("wireless", self.sid) or
		uci_r:get_all("wireless", self.sid) or { }
end

function wifinet.get(self, opt)
	return _get("wireless", self.sid, opt)
end

function wifinet.set(self, opt, val)
	return _set("wireless", self.sid, opt, val)
end

function wifinet.mode(self)
	return uci_s:get("wireless", self.sid, "mode") or "ap"
end

function wifinet.ssid(self)
	return uci_s:get("wireless", self.sid, "ssid")
end

function wifinet.bssid(self)
	return uci_s:get("wireless", self.sid, "bssid")
end

function wifinet.network(self)
	return uci_s:get("wifinet", self.sid, "network")
end

function wifinet.id(self)
	return self.netid
end

function wifinet.name(self)
	return self.sid
end

function wifinet.ifname(self)
	local ifname = self.iwinfo.ifname
	if not ifname or ifname:match("^wifi%d") or ifname:match("^radio%d") then
		ifname = self.wdev
	end
	return ifname
end

function wifinet.get_device(self)
	if self.iwdata.device then
		return wifidev(self.iwdata.device)
	end
end

function wifinet.is_up(self)
	return (self.iwdata.up == "1")
end

function wifinet.active_mode(self)
	local m = _stror(self.iwinfo.mode, self.iwdata.mode) or "ap"

	if     m == "ap"      then m = "Master"
	elseif m == "sta"     then m = "Client"
	elseif m == "adhoc"   then m = "Ad-Hoc"
	elseif m == "mesh"    then m = "Mesh"
	elseif m == "monitor" then m = "Monitor"
	end

	return m
end

function wifinet.active_mode_i18n(self)
	return i18n.translate(self:active_mode())
end

function wifinet.active_ssid(self)
	return _stror(self.iwinfo.ssid, self.iwdata.ssid)
end

function wifinet.active_bssid(self)
	return _stror(self.iwinfo.bssid, self.iwdata.bssid) or "00:00:00:00:00:00"
end

function wifinet.active_encryption(self)
	local enc = self.iwinfo and self.iwinfo.encryption
	return enc and enc.description or "-"
end

function wifinet.assoclist(self)
	return self.iwinfo.assoclist or { }
end

function wifinet.frequency(self)
	local freq = self.iwinfo.frequency
	if freq and freq > 0 then
		return "%.03f" % (freq / 1000)
	end
end

function wifinet.bitrate(self)
	local rate = self.iwinfo.bitrate
	if rate and rate > 0 then
		return (rate / 1000)
	end
end

function wifinet.channel(self)
	return self.iwinfo.channel or
		tonumber(uci_s:get("wireless", self.iwdata.device, "channel"))
end

function wifinet.signal(self)
	return self.iwinfo.signal or 0
end

function wifinet.noise(self)
	return self.iwinfo.noise or 0
end

function wifinet.country(self)
	return self.iwinfo.country or "00"
end

function wifinet.txpower(self)
	return self.iwinfo.txpower or 0
end

function wifinet.signal_level(self, s, n)
	if self:active_bssid() ~= "00:00:00:00:00:00" then
		local signal = s or self:signal()
		local noise  = n or self:noise()

		if signal < 0 and noise < 0 then
			local snr = -1 * (noise - signal)
			return math.floor(snr / 5)
		else
			return 0
		end
	else
		return -1
	end
end

function wifinet.signal_percent(self)
	local qc = self.iwinfo.quality or 0
	local qm = self.iwinfo.quality_max or 0

	if qc > 0 and qm > 0 then
		return math.floor((100 / qm) * qc)
	else
		return 0
	end
end

function wifinet.shortname(self)
	return "%s %q" %{
		i18n.translate(self:active_mode()),
		self:active_ssid() or self:active_bssid()
	}
end

function wifinet.get_i18n(self)
	return "%s: %s %q (%s)" %{
		i18n.translate("Wireless Network"),
		i18n.translate(self:active_mode()),
		self:active_ssid() or self:active_bssid(),
		self:ifname()
	}
end

function wifinet.adminlink(self)
	return dsp.build_url("admin", "network", "wireless", self.netid)
end

function wifinet.get_network(self)
	if uci_r:get("network", self.iwdata.network) == "interface" then
		return network(self.iwdata.network)
	end
end

function wifinet.get_interface(self)
	return interface(self:ifname())
end
