|
- --[[
- packet format:
- packetID: random string to differentiate
- packetType:
- - 0: unreliable
- - 1: reliable, requires ack
- - 2: ack packet
- destination: end destination hostname
- sender: original sender of packet
- data: the actual packet data, duh.
- ]]--
-
- local listeners = {}
- local timers = {}
-
- local cfg = {}
-
- local event = require "event"
- local component = require "component"
- local computer = require "computer"
- local serial = require "serialization"
-
- local hostname = computer.address():sub(1,8)
- local modems = {}
-
- local pid = nil
-
- cfg.debug = false
- cfg.port = 4096
- cfg.retry = 10
- cfg.retrycount = 3
- cfg.route = true
-
- --[[
- LKR format:
- address {
- local hardware address
- remote hardware address
- time last received
- }
- ]]--
- cfg.sroutes = {}
- local rcache = setmetatable({},{__index=cfg.sroutes})
- cfg.rctime = 15
-
- --[[
- packet queue format:
- {
- packetID,
- packetType
- destination,
- data,
- timestamp,
- attempts
- }
- ]]--
- local pqueue = {}
-
- -- packet cache: [packet ID]=uptime
- local pcache = {}
- cfg.pctime = 30
-
- local function dprint(...)
- if cfg.debug then
- print(...)
- end
- end
-
- local function saveconfig()
- local f = io.open("/boot/cfg/minitel.cfg","wb")
- if f then
- f:write(serial.serialize(cfg))
- f:close()
- end
- end
- local function loadconfig()
- local f = io.open("/boot/cfg/minitel.cfg","rb")
- if f then
- local newcfg = serial.unserialize(f:read("*a"))
- f:close()
- for k,v in pairs(newcfg) do
- cfg[k] = v
- end
- else
- saveconfig()
- end
- end
-
- function start()
- loadconfig()
- hostname = os.getenv("HOSTNAME") or computer.address():sub(1,8)
- print("Hostname: "..hostname)
-
- if pid then return false end
-
- modems={}
- for a,t in component.list("modem") do
- modems[#modems+1] = component.proxy(a)
- end
- for k,v in ipairs(modems) do
- v.open(cfg.port)
- print("Opened port "..cfg.port.." on "..v.address)
- end
- for a,t in component.list("tunnel") do
- modems[#modems+1] = component.proxy(a)
- end
-
- local function genPacketID()
- local npID = ""
- for i = 1, 16 do
- npID = npID .. string.char(math.random(32,126))
- end
- return npID
- end
-
- local function sendPacket(packetID,packetType,dest,sender,vPort,data,repeatingFrom)
- if rcache[dest] then
- dprint("Cached", rcache[dest][1],"send",rcache[dest][2],cfg.port,packetID,packetType,dest,sender,vPort,data)
- if component.type(rcache[dest][1]) == "modem" then
- component.invoke(rcache[dest][1],"send",rcache[dest][2],cfg.port,packetID,packetType,dest,sender,vPort,data)
- elseif component.type(rcache[dest][1]) == "tunnel" then
- component.invoke(rcache[dest][1],"send",packetID,packetType,dest,sender,vPort,data)
- end
- else
- dprint("Not cached", cfg.port,packetID,packetType,dest,sender,vPort,data)
- for k,v in pairs(modems) do
- -- do not send message back to the wired or linked modem it came from
- -- the check for tunnels is for short circuiting `v.isWireless()`, which does not exist for tunnels
- if v.address ~= repeatingFrom or (v.type ~= "tunnel" and v.isWireless()) then
- if v.type == "modem" then
- v.broadcast(cfg.port,packetID,packetType,dest,sender,vPort,data)
- v.send(packetID,packetType,dest,sender,vPort,data)
- end
- end
- end
- end
- end
-
- local function pruneCache()
- for k,v in pairs(rcache) do
- dprint(k,v[3],computer.uptime())
- if v[3] < computer.uptime() then
- rcache[k] = nil
- dprint("pruned "..k.." from routing cache")
- end
- end
- for k,v in pairs(pcache) do
- if v < computer.uptime() then
- pcache[k] = nil
- dprint("pruned "..k.." from packet cache")
- end
- end
- end
-
- local function checkPCache(packetID)
- dprint(packetID)
- for k,v in pairs(pcache) do
- dprint(k)
- if k == packetID then return true end
- end
- return false
- end
-
- local function processPacket(_,localModem,from,pport,_,packetID,packetType,dest,sender,vPort,data)
- pruneCache()
- if pport == cfg.port or pport == 0 then -- for linked cards
- dprint(cfg.port,vPort,packetType,dest)
- if checkPCache(packetID) then return end
- if dest == hostname then
- if packetType == 1 then
- sendPacket(genPacketID(),2,sender,hostname,vPort,packetID)
- end
- if packetType == 2 then
- dprint("Dropping "..data.." from queue")
- pqueue[data] = nil
- computer.pushSignal("net_ack",data)
- end
- if packetType ~= 2 then
- computer.pushSignal("net_msg",sender,vPort,data)
- end
- elseif dest:sub(1,1) == "~" then -- broadcasts start with ~
- computer.pushSignal("net_broadcast",sender,vPort,data)
- elseif cfg.route then -- repeat packets if route is enabled
- sendPacket(packetID,packetType,dest,sender,vPort,data,localModem)
- end
- if not rcache[sender] then -- add the sender to the rcache
- dprint("rcache: "..sender..":", localModem,from,computer.uptime())
- rcache[sender] = {localModem,from,computer.uptime()+cfg.rctime}
- end
- if not pcache[packetID] then -- add the packet ID to the pcache
- pcache[packetID] = computer.uptime()+cfg.pctime
- end
- end
- end
-
- local function queuePacket(_,ptype,to,vPort,data,npID)
- npID = npID or genPacketID()
- if to == hostname or to == "localhost" then
- computer.pushSignal("net_msg",to,vPort,data)
- computer.pushSignal("net_ack",npID)
- return
- end
- pqueue[npID] = {ptype,to,vPort,data,0,0}
- dprint(npID,table.unpack(pqueue[npID]))
- end
-
- local function packetPusher()
- for k,v in pairs(pqueue) do
- if v[5] < computer.uptime() then
- dprint(k,v[1],v[2],hostname,v[3],v[4])
- sendPacket(k,v[1],v[2],hostname,v[3],v[4])
- if v[1] ~= 1 or v[6] == cfg.retrycount then
- pqueue[k] = nil
- else
- pqueue[k][5]=computer.uptime()+cfg.retry
- pqueue[k][6]=pqueue[k][6]+1
- end
- end
- end
- end
-
- listeners["modem_message"]=processPacket
- listeners["net_send"]=queuePacket
- listeners["net_ack"]=dprint
-
- pid=os.spawn(function()
- while true do
- local ev = {coroutine.yield()}
- packetPusher()
- pruneCache()
- if listeners[ev[1]] then
- pcall(listeners[ev[1]],table.unpack(ev))
- end
- end
- end,"minitel")
- print("Started Minitel daemon: "..tostring(pid))
- return pid
- end
-
- function stop()
- if pid then
- os.kill(pid)
- pid = nil
- return true
- else
- return false
- end
- end
-
- function set(k,v)
- if type(cfg[k]) == "string" then
- cfg[k] = v
- elseif type(cfg[k]) == "number" then
- cfg[k] = tonumber(v)
- elseif type(cfg[k]) == "boolean" then
- if v:lower():sub(1,1) == "t" then
- cfg[k] = true
- else
- cfg[k] = false
- end
- end
- print("cfg."..k.." = "..tostring(cfg[k]))
- saveconfig()
- end
-
- function set_route(to,laddr,raddr)
- cfg.sroutes[to] = {laddr,raddr,0}
- saveconfig()
- end
- function del_route(to)
- cfg.sroutes[to] = nil
- saveconfig()
- end
- return {start=start,stop=stop,set=set,set_route=set_route,del_route=del_route}
|