DorianH935

Untitled

Nov 15th, 2025
31
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.70 KB | None | 0 0
  1. -- lphone_server.lua
  2. -- Lemonphone server node: registration, routing, relay
  3. -- Usage: put on server computer and run. On first run you'll be asked to provide peer IDs (optional).
  4.  
  5. local SERVER_PROTOCOL = "lemonphone_server"
  6. local CLIENT_PROTOCOL = "lemonphone_client" -- (optional) used if needed
  7. local STORE_PATH = "/lphone_store.dat"
  8.  
  9. -- Data stores
  10. local phoneToComp = {} -- phone -> compID
  11. local compToPhone = {} -- compID -> phone
  12. local queued = {} -- phone -> { messages... } for offline
  13. local peers = {} -- other server computer IDs
  14.  
  15. -- config load/save
  16. local function saveStore()
  17. local f = fs.open(STORE_PATH, "w")
  18. if not f then print("Failed to save store"); return end
  19. f.write(textutils.serialize({p2c=phoneToComp, c2p=compToPhone, q=queued, peers=peers}))
  20. f.close()
  21. end
  22. local function loadStore()
  23. if not fs.exists(STORE_PATH) then return end
  24. local f = fs.open(STORE_PATH,"r")
  25. if not f then return end
  26. local ok, tbl = pcall(textutils.unserialize, f.readAll())
  27. f.close()
  28. if ok and type(tbl)=="table" then
  29. phoneToComp = tbl.p2c or phoneToComp
  30. compToPhone = tbl.c2p or compToPhone
  31. queued = tbl.q or queued
  32. peers = tbl.peers or peers
  33. end
  34. end
  35. loadStore()
  36.  
  37. -- helpers: generate phone numbers "s-xxxx-xxxx"
  38. local function randHex(n)
  39. local s = ""
  40. for i=1,n do s = s .. string.format("%x", math.random(0,15)) end
  41. return s
  42. end
  43.  
  44. local function assignPhone(serviceType, compID)
  45. local attempts = 0
  46. while attempts < 100 do
  47. local hex = randHex(8)
  48. local phone = string.format("%s-%s-%s", serviceType, hex:sub(1,4), hex:sub(5,8))
  49. if not phoneToComp[phone] then
  50. phoneToComp[phone] = compID
  51. compToPhone[compID] = phone
  52. saveStore()
  53. return phone
  54. end
  55. attempts = attempts + 1
  56. end
  57. return nil
  58. end
  59.  
  60. -- broadcast to peers (best-effort)
  61. local function announceToPeers()
  62. for _, pid in ipairs(peers) do
  63. pcall(rednet.send, pid, {type="server_announce", from=os.getComputerID()}, SERVER_PROTOCOL)
  64. end
  65. end
  66.  
  67. -- handle incoming messages from clients or other servers
  68. local function handleMessage(sender, msg)
  69. if type(msg) ~= "table" or not msg.type then return end
  70. if msg.type == "register_request" then
  71. local compid = msg.compid
  72. local want = tostring(msg.want_service or "2")
  73. local phone = compToPhone[compid] or assignPhone(want, compid)
  74. if phone then
  75. rednet.send(sender, {type="register_response", phone=phone}, SERVER_PROTOCOL)
  76. -- announce to peers so they can route
  77. announceToPeers()
  78. else
  79. rednet.send(sender, {type="error", msg="assign_failed"}, SERVER_PROTOCOL)
  80. end
  81. elseif msg.type == "chat_message" then
  82. if msg.mode == "global" then
  83. -- deliver to local clients and relay to peers
  84. for compid, phone in pairs(compToPhone) do
  85. -- deliver to everyone except sender comp maybe
  86. if compid ~= msg.compid then
  87. pcall(rednet.send, compid, {type="chat_delivery", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts})
  88. end
  89. end
  90. -- relay to peers
  91. for _, pid in ipairs(peers) do
  92. pcall(rednet.send, pid, {type="chat_relay", mode="global", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts}, SERVER_PROTOCOL)
  93. end
  94. elseif msg.mode == "dm" and msg.to then
  95. local dest = msg.to
  96. local localComp = phoneToComp[dest]
  97. if localComp then
  98. -- deliver local
  99. pcall(rednet.send, localComp, {type="chat_delivery", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts})
  100. rednet.send(sender, {type="server_msg", text="delivered locally"}, SERVER_PROTOCOL)
  101. else
  102. -- not local: try peers (forward)
  103. for _, pid in ipairs(peers) do
  104. 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)
  105. end
  106. -- queue locally (best-effort) until some server says otherwise
  107. queued[dest] = queued[dest] or {}
  108. table.insert(queued[dest], {from=msg.fromPhone, text=msg.text, ts=msg.ts})
  109. saveStore()
  110. rednet.send(sender, {type="server_msg", text="queued"}, SERVER_PROTOCOL)
  111. end
  112. end
  113. elseif msg.type == "chat_relay" then
  114. -- message forwarded by another server (global or dm)
  115. if msg.mode == "global" then
  116. -- deliver to local comps
  117. for compid,_ in pairs(compToPhone) do
  118. pcall(rednet.send, compid, {type="chat_delivery", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts})
  119. end
  120. -- optionally re-relay? to avoid loops we can avoid re-relaying
  121. elseif msg.mode == "dm" and msg.to then
  122. local localComp = phoneToComp[msg.to]
  123. if localComp then
  124. pcall(rednet.send, localComp, {type="chat_delivery", fromPhone=msg.fromPhone, fromName=msg.fromName, text=msg.text, ts=msg.ts})
  125. else
  126. -- not local: queue
  127. queued[msg.to] = queued[msg.to] or {}
  128. table.insert(queued[msg.to], {from=msg.fromPhone, text=msg.text, ts=msg.ts})
  129. saveStore()
  130. end
  131. end
  132. elseif msg.type == "server_announce" then
  133. -- other server telling us it exists: optionally store peer
  134. if not table.contains then
  135. table.contains = function(t,v) for _,x in ipairs(t) do if x==v then return true end end return false end
  136. end
  137. if not table.contains(peers, sender) and sender ~= os.getComputerID() then
  138. table.insert(peers, sender)
  139. saveStore()
  140. end
  141. end
  142. end
  143.  
  144. -- small CLI for admin
  145. local function adminLoop()
  146. while true do
  147. term.write("> ")
  148. local cmd = read()
  149. if not cmd then break end
  150. local args = {}
  151. for s in cmd:gmatch("%S+") do table.insert(args,s) end
  152. if args[1] == "list" then
  153. print("Registered phones:")
  154. for p,c in pairs(phoneToComp) do print(p.." -> "..tostring(c)) end
  155. elseif args[1] == "peers" then
  156. print("Peers:"); for _,p in ipairs(peers) do print(p) end
  157. elseif args[1] == "addpeer" and args[2] then
  158. local id = tonumber(args[2])
  159. if id then table.insert(peers, id); saveStore(); print("added") end
  160. elseif args[1] == "delpeer" and args[2] then
  161. local id = tonumber(args[2])
  162. for i,v in ipairs(peers) do if v==id then table.remove(peers,i); saveStore(); print("removed") break end end
  163. elseif args[1] == "queue" then
  164. print(textutils.serialize(queued))
  165. elseif args[1] == "save" then saveStore(); print("saved")
  166. elseif args[1] == "help" then print("list, peers, addpeer <id>, delpeer <id>, queue, save, exit")
  167. elseif args[1] == "exit" then os.reboot() break
  168. else print("unknown, help for commands") end
  169. end
  170. end
  171.  
  172. -- start server: host on protocol and listen
  173. if peripheral.find("modem") then
  174. pcall(rednet.open, "back")
  175. end
  176. rednet.host(SERVER_PROTOCOL, os.getComputerID())
  177.  
  178. -- announce to peers if provided as program args
  179. local args = { ... }
  180. if #args > 0 then
  181. for _, a in ipairs(args) do
  182. local id = tonumber(a)
  183. if id then table.insert(peers, id) end
  184. end
  185. saveStore()
  186. end
  187.  
  188. -- main loop: receive messages and also respond to TCP-like lookups
  189. local function serverLoop()
  190. while true do
  191. local sender, msg, proto = rednet.receive(SERVER_PROTOCOL)
  192. local ok, err = pcall(handleMessage, sender, msg)
  193. if not ok then print("Handle error: "..tostring(err)) end
  194. end
  195. end
  196.  
  197. -- small background: periodically announce
  198. local function announcer()
  199. while true do
  200. for _, pid in ipairs(peers) do
  201. pcall(rednet.send, pid, {type="server_announce", from=os.getComputerID()}, SERVER_PROTOCOL)
  202. end
  203. sleep(10)
  204. end
  205. end
  206.  
  207. parallel.waitForAny(serverLoop, adminLoop, announcer)
  208.  
Advertisement
Add Comment
Please, Sign In to add comment