OC-PsychOS2/service/minitel.lua

342 lines
8.5 KiB
Lua

--[[
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,timers,processes,modems = {},{},{},{}
local hostname = os.getenv("HOSTNAME")
local cfg = {}
cfg.debug = false
cfg.port = 4096
cfg.retry = 10
cfg.retrycount = 64
cfg.route = true
local event, component, computer, serial = event, component, computer, serial
local hnpath, cfgpath = "", ""
OPENOS, PSYCHOS, KITTENOS = false, false, false
if _OSVERSION:sub(1,6) == "OpenOS" then
OPENOS = true
hnpath = "/etc/hostname"
cfgpath = "/etc/minitel.cfg"
elseif _OSVERSION:sub(1,7) == "PsychOS" then
PSYCHOS = true
hnpath = "/boot/cfg/hostname"
cfgpath = "/boot/cfg/minitel.cfg"
elseif _OSVERSION:sub(1,8) == "KittenOS" then
KITTENOS = true
end
-- packet cache: [packet ID]=uptime
local pcache = {}
cfg.pctime = 30
--[[
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 = {}
local function saveconfig()
if OPENOS or PSYCHOS then
local f = io.open(cfgpath,"wb")
if f then
f:write(serial.serialize(cfg))
f:close()
end
end
end
local function loadconfig()
hostname = os.getenv("HOSTNAME") or computer.address():sub(1,8)
if OPENOS or PSYCHOS then
local f,g=io.open(hnpath,"rb")
if f then
hostname = f:read("*a"):match("(.-)\n")
f:close()
end
local f = io.open(cfgpath,"rb")
if f then
local newcfg = serial.unserialize(f:read("*a")) or {}
f:close()
for k,v in pairs(newcfg) do
cfg[k] = v
end
else
saveconfig()
end
elseif KITTENOS then
local globals = neo.requestAccess("x.neo.pub.globals") -- KittenOS standard hostname stuff
if globals then
hostname = globals.getSetting("hostname") or hostname
globals.setSetting("hostname",hostname)
end
end
end
-- specific OS support here
if PSYCHOS then -- PsychOS specific code
serial = require "serialization"
elseif OPENOS then -- OpenOS specific code
event = require "event"
component = require "component"
computer = require "computer"
serial = require "serialization"
listener = false
elseif KITTENOS then
neo.requireAccess("s.h.modem_message","pulling packets")
computer = {["uptime"]=os.uptime,["address"]=os.address} -- wrap computer so the OpenOS code more or less works
function computer.pushSignal(...)
for k,v in pairs(processes) do
v(...)
end
end
end
local function dprint(...)
if cfg.debug then
print(...)
end
end
function start()
loadconfig()
print("Hostname: "..hostname)
if listener then return end
if OPENOS or PSYCHOS then
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:sub(1,8))
end
for a,t in component.list("tunnel") do
modems[#modems+1] = component.proxy(a)
end
elseif KITTENOS then
for p in neo.requireAccess("c.modem","networking").list() do -- fun stuff for KittenOS
dprint(p.address)
modems[p.address] = p
end
for k,v in pairs(modems) do
v.open(port)
print("Opened port "..port.." on "..v.address)
end
for p in neo.requireAccess("c.tunnel","networking").list() do
dprint(p.address)
modems[p.address] = p
end
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
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)
elseif v.type == "tunnel" then
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
if OPENOS then
event.listen("modem_message",processPacket)
print("Started packet listening daemon: "..tostring(processPacket))
event.listen("net_send",queuePacket)
print("Started packet queueing daemon: "..tostring(queuePacket))
timers[#timers+1]=event.timer(0,packetPusher,math.huge)
print("Started packet pusher: "..tostring(timers[#timers]))
elseif KITTENOS then
neo.requireAccess("r.svc.minitel","minitel daemon")(function(pkg,pid,sendSig)
processes[pid] = sendSig
return {["sendPacket"]=queuePacket}
end)
end
if KITTENOS or PSYCHOS then
while true do
local ev = {coroutine.yield()}
packetPusher()
pruneCache()
if ev[1] == "k.procdie" then
processes[ev[3]] = nil
end
if listeners[ev[1]] then
pcall(listeners[ev[1]],table.unpack(ev))
end
end
end
end
function stop()
for k,v in pairs(listeners) do
event.ignore(k,v)
print("Stopped listener: "..tostring(v))
end
for k,v in pairs(timers) do
event.cancel(v)
print("Stopped timer: "..tostring(v))
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}
end
function del_route(to)
cfg.sroutes[to] = nil
end
if not OPENOS then
start()
end