Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- lphone_server.lua
- -- Lemonphone server node: registration, routing, relay
- -- Usage: put on server computer and run. On first run you'll be asked to provide peer IDs (optional).
- local SERVER_PROTOCOL = "lemonphone_server"
- local CLIENT_PROTOCOL = "lemonphone_client" -- (optional) used if needed
- local STORE_PATH = "/lphone_store.dat"
- -- Data stores
- local phoneToComp = {} -- phone -> compID
- local compToPhone = {} -- compID -> phone
- local queued = {} -- phone -> { messages... } for offline
- local peers = {} -- other server computer IDs
- -- config load/save
- local function saveStore()
- local f = fs.open(STORE_PATH, "w")
- if not f then print("Failed to save store"); return end
- f.write(textutils.serialize({p2c=phoneToComp, c2p=compToPhone, q=queued, peers=peers}))
- f.close()
- end
- local function loadStore()
- if not fs.exists(STORE_PATH) then return end
- local f = fs.open(STORE_PATH,"r")
- if not f then return end
- local ok, tbl = pcall(textutils.unserialize, f.readAll())
- f.close()
- if ok and type(tbl)=="table" then
- phoneToComp = tbl.p2c or phoneToComp
- compToPhone = tbl.c2p or compToPhone
- queued = tbl.q or queued
- peers = tbl.peers or peers
- end
- end
- loadStore()
- -- helpers: generate phone numbers "s-xxxx-xxxx"
- local function randHex(n)
- local s = ""
- for i=1,n do s = s .. string.format("%x", math.random(0,15)) end
- return s
- end
- local function assignPhone(serviceType, compID)
- local attempts = 0
- while attempts < 100 do
- local hex = randHex(8)
- local phone = string.format("%s-%s-%s", serviceType, hex:sub(1,4), hex:sub(5,8))
- if not phoneToComp[phone] then
- phoneToComp[phone] = compID
- compToPhone[compID] = phone
- saveStore()
- return phone
- end
- attempts = attempts + 1
- end
- return nil
- end
- -- broadcast to peers (best-effort)
- local function announceToPeers()
- for _, pid in ipairs(peers) do
- pcall(rednet.send, pid, {type="server_announce", from=os.getComputerID()}, SERVER_PROTOCOL)
- end
- end
- -- handle incoming messages from clients or other servers
- local function handleMessage(sender, msg)
- if type(msg) ~= "table" or not msg.type then return end
- if msg.type == "register_request" then
- local compid = msg.compid
- local want = tostring(msg.want_service or "2")
- local phone = compToPhone[compid] or assignPhone(want, compid)
- if phone then
- rednet.send(sender, {type="register_response", phone=phone}, SERVER_PROTOCOL)
- -- announce to peers so they can route
- announceToPeers()
- else
- rednet.send(sender, {type="error", msg="assign_failed"}, SERVER_PROTOCOL)
- end
- elseif msg.type == "chat_message" then
- if msg.mode == "global" then
- -- deliver to local clients and relay to peers
- for compid, phone in pairs(compToPhone) do
- -- deliver to everyone except sender comp maybe
- if compid ~= msg.compid then
- pcall(rednet.send, compid, {type="chat_delivery", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts})
- end
- end
- -- relay to peers
- for _, pid in ipairs(peers) do
- pcall(rednet.send, pid, {type="chat_relay", mode="global", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts}, SERVER_PROTOCOL)
- end
- elseif msg.mode == "dm" and msg.to then
- local dest = msg.to
- local localComp = phoneToComp[dest]
- if localComp then
- -- deliver local
- pcall(rednet.send, localComp, {type="chat_delivery", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts})
- rednet.send(sender, {type="server_msg", text="delivered locally"}, SERVER_PROTOCOL)
- else
- -- not local: try peers (forward)
- for _, pid in ipairs(peers) do
- pcall(rednet.send, pid, {type="chat_relay", mode="dm", to=dest, fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts}, SERVER_PROTOCOL)
- end
- -- queue locally (best-effort) until some server says otherwise
- queued[dest] = queued[dest] or {}
- table.insert(queued[dest], {from=msg.fromPhone, text=msg.text, ts=msg.ts})
- saveStore()
- rednet.send(sender, {type="server_msg", text="queued"}, SERVER_PROTOCOL)
- end
- end
- elseif msg.type == "chat_relay" then
- -- message forwarded by another server (global or dm)
- if msg.mode == "global" then
- -- deliver to local comps
- for compid,_ in pairs(compToPhone) do
- pcall(rednet.send, compid, {type="chat_delivery", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts})
- end
- -- optionally re-relay? to avoid loops we can avoid re-relaying
- elseif msg.mode == "dm" and msg.to then
- local localComp = phoneToComp[msg.to]
- if localComp then
- pcall(rednet.send, localComp, {type="chat_delivery", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts})
- else
- -- not local: queue
- queued[msg.to] = queued[msg.to] or {}
- table.insert(queued[msg.to], {from=msg.fromPhone, text=msg.text, ts=msg.ts})
- saveStore()
- end
- end
- elseif msg.type == "server_announce" then
- -- other server telling us it exists: optionally store peer
- if not table.contains then
- table.contains = function(t,v) for _,x in ipairs(t) do if x==v then return true end end return false end
- end
- if not table.contains(peers, sender) and sender ~= os.getComputerID() then
- table.insert(peers, sender)
- saveStore()
- end
- end
- end
- -- small CLI for admin
- local function adminLoop()
- while true do
- term.write("> ")
- local cmd = read()
- if not cmd then break end
- local args = {}
- for s in cmd:gmatch("%S+") do table.insert(args,s) end
- if args[1] == "list" then
- print("Registered phones:")
- for p,c in pairs(phoneToComp) do print(p.." -> "..tostring(c)) end
- elseif args[1] == "peers" then
- print("Peers:"); for _,p in ipairs(peers) do print(p) end
- elseif args[1] == "addpeer" and args[2] then
- local id = tonumber(args[2])
- if id then table.insert(peers, id); saveStore(); print("added") end
- elseif args[1] == "delpeer" and args[2] then
- local id = tonumber(args[2])
- for i,v in ipairs(peers) do if v==id then table.remove(peers,i); saveStore(); print("removed") break end end
- elseif args[1] == "queue" then
- print(textutils.serialize(queued))
- elseif args[1] == "save" then saveStore(); print("saved")
- elseif args[1] == "help" then print("list, peers, addpeer <id>, delpeer <id>, queue, save, exit")
- elseif args[1] == "exit" then os.reboot() break
- else print("unknown, help for commands") end
- end
- end
- -- start server: host on protocol and listen
- if peripheral.find("modem") then
- pcall(rednet.open, "back")
- end
- rednet.host(SERVER_PROTOCOL, os.getComputerID())
- -- announce to peers if provided as program args
- local args = { ... }
- if #args > 0 then
- for _, a in ipairs(args) do
- local id = tonumber(a)
- if id then table.insert(peers, id) end
- end
- saveStore()
- end
- -- main loop: receive messages and also respond to TCP-like lookups
- local function serverLoop()
- while true do
- local sender, msg, proto = rednet.receive(SERVER_PROTOCOL)
- local ok, err = pcall(handleMessage, sender, msg)
- if not ok then print("Handle error: "..tostring(err)) end
- end
- end
- -- small background: periodically announce
- local function announcer()
- while true do
- for _, pid in ipairs(peers) do
- pcall(rednet.send, pid, {type="server_announce", from=os.getComputerID()}, SERVER_PROTOCOL)
- end
- sleep(10)
- end
- end
- parallel.waitForAny(serverLoop, adminLoop, announcer)
Advertisement
Add Comment
Please, Sign In to add comment