· 6 years ago · Aug 04, 2019, 07:26 PM
1local component = require("component")
2local event = require("event")
3local modem = component.modem
4local ser = require("serialization")
5
6local bPort = 20 -- Broadcast port
7local ePort = 21 -- Command emission port
8local tPort = 22 -- Other command emission port
9
10local aPort = 32000 -- First direct connexion port
11local rPort = 33000 -- Relay message port
12
13local servName = "Serv1" -- Server name (alias) to configure before first boot
14local localhost = ""
15
16modem.open(bPort)
17modem.open(ePort)
18modem.open(tPort)
19modem.open(rPort)
20
21local clientList = {} -- List of connected clients
22local serverAliases = {"Serv2"} -- List of servers to connect to
23local serverList = {} -- List of connected servers
24local relayCache = {} -- Cache of found servers for relaying
25
26local activeConnexions = {}
27local relayConnexions = {}
28
29
30
31-- LEN FUNCTION -- (Find length of a table)
32
33function len(table)
34 local count = 0
35 for _, _ in pairs(table) do
36 count = count + 1
37 end
38 return count
39end
40
41
42
43-- WHO FUNCTION -- (Answer to a "WHO" command, sending alias)
44
45function who(addr)
46 modem.send(addr, bPort, servName)
47 print("Answered who from "..addr)
48end
49
50
51
52-- DISCOVER FUNCTON -- (First connexion, tries to connect to configured servers)
53
54function discover()
55 modem.broadcast(bPort, "WHO")
56
57 local doSearch = true
58 while doSearch do
59 _, _, addr, _, d, mess = event.pull(2, "modem_message")
60
61 if mess == nil then
62 doSearch = false
63 else
64 for _, alias in pairs(serverAliases) do
65 if mess == alias then
66 modem.send(addr, bPort, "SCONNECT", servName)
67
68 serverList[mess] = addr
69 end
70 end
71 end
72 end
73end
74
75
76
77-- CHECK FUNCTION -- (Check if address or alias exists in the table of connected clients)
78
79function check(client)
80 for alias, addr in pairs(clientList) do
81 if client == alias then
82 return true
83 elseif client == addr then
84 return true
85 end
86 end
87 return false
88end
89
90
91
92-- HANDSHAKE FUNCTION -- (Answer to a "HANDSHAKE" command, try to add client in the table and issue "ACK" if successful)
93
94function handshake(addr, alias, serv)
95 if alias == nil then
96 alias = addr
97 end
98
99 if clientList[alias] then -- If alias is already in clientList
100 if clientList[alias] == addr then
101 modem.send(addr, bPort, "ACK")
102 else
103 modem.send(addr, bPort, "DPL")
104 print("Tried to handshake with: "..alias.." @ "..addr)
105 print("But duplicate: "..alias.." @ "..clientList[alias])
106 end
107 elseif serv == "SERV" then
108 serverList[alias] = addr
109
110 print("Server "..alias.." @ "..addr.." connected.")
111 else
112 clientList[alias] = addr
113
114 modem.send(addr, bPort, "ACK")
115 print("Handshaked with: "..alias.." @ "..addr)
116 end
117end
118
119
120
121-- DISCONNECT FUNCTION -- (Answer to a "DISCONNECT" command, remove client from the table and issue "ACK" if successful)
122
123function disconnect(addr, alias)
124 if alias == nil then
125 alias = addr
126 end
127
128 if clientList[alias] then
129 for port, connexion in pairs(activeConnexions) do
130 if (addr == connexion[1]) or (addr == connexion[2]) then
131 close(addr, port)
132 end
133 end
134 modem.send(addr, ePort, "ACK")
135 clientList[alias] = nil
136 print("Client "..alias.." successfully disconnected.")
137 end
138end
139
140
141
142-- OPEN FUNCTION -- (Try to open a session between two clients, issue "ACK" if direct and "RELAY" with ID then "RELACK" if relayed by servers)
143
144function open(addr, dest)
145 if clientList[dest] and check(addr) then -- If client and receiver are connected
146 local port = aPort + len(activeConnexions)
147 modem.open(port) -- On ouvre le bon port
148 activeConnexions[port] = {addr, clientList[dest]} -- Adding connexion to the table
149 modem.send(addr, ePort, "ACK", port)
150 modem.send(clientList[dest], ePort, "OPEN", addr, port)
151 print("Connexion opened on port "..port.." between "..addr.." and "..dest)
152
153 elseif check(addr) then -- Else, if only client is connected and not receiver
154 if relayCache[dest] then -- If a cache entry exists
155 local rID = addr:sub(1,4)..relayCache[dest].addr:sub(1,4) -- Creating an ID for the relay connexion
156 local data = ser.serialize({addr=addr, dest=relayCache[dest].addr, rID=rID})
157 modem.send(relayCache[dest].sAddr, ePort, "RELAY", data)
158
159 local recPort = _
160 while recPort ~= tPort do
161 local _, _, rAddr, recPort, _, mess, opt = event.pull(5, "modem_message")
162
163 if recPort == tPort then
164 if (mess == "RELACK") and (opt == rID) then
165 modem.send(addr, ePort, "RELAY", rID)
166 relayConnexions[rID] = {addr, relayCache[dest].sAddr}
167 relack(rAddr, rID)
168 print("Connexion opened with ID "..rID.." between "..addr.." and "..dest.." -> RELAYED")
169 return
170 else
171 print("Relay failed.")
172 end
173 elseif mess == nil then
174 print("Relay failed.")
175 break
176 end
177 end
178 else -- If there isn't a cache entry
179 for alias, sAddr in pairs(serverList) do -- Asking servers to search for the receiver
180 modem.send(sAddr, ePort, "SEARCH", dest)
181
182 local recPort = _
183 while recPort ~= tPort do
184 local _, _, sAddr, recPort, _, mess, opt = event.pull(5, "modem_message") -- MESS: "FOUND" or "PNF"; OPT: UUID of dest
185
186 if mess == "FOUND" then -- If client is found
187 print(dest.." found @ "..sAddr)
188 relayCache[dest] = {sAddr=sAddr, addr=opt}
189 local rID = addr:sub(1,4)..opt:sub(1,4) -- Creating an ID for the relay connexion
190 relayConnexions[rID] = {addr, sAddr}
191 modem.send(addr, ePort, "RELAY", rID)
192 local data = ser.serialize({addr=addr, dest=opt, rID=rID})
193 modem.send(sAddr, ePort, "RELAY", data)
194 print("Connexion opened with ID "..rID.." between "..addr.." and "..dest.." -> RELAYED")
195 return
196 elseif mess == nil then
197 break
198 end
199 end
200 end
201 end
202 modem.send(addr, ePort, "PNF")
203 elseif not clientList[dest] then
204 modem.send(addr, ePort, "PNF")
205 end
206end
207
208
209
210-- SEARCH FUNCTION -- (Search for client in the list, or relay the search to nearby servers)
211
212function search(addr, dest)
213 print("Searching for "..dest)
214
215 if check(dest) then
216 if clientList[dest] then
217 modem.send(addr, tPort, "FOUND", clientList[dest])
218 relayCache[clientList[dest]] = {sAddr=clientList[dest], addr=clientList[dest]}
219 print(dest.." found !")
220 else
221 for alias, dAddr in pairs(clientList) do
222 if dest == dAddr then
223 modem.send(addr, tPort, "FOUND", clientList[alias])
224 relayCache[clientList[alias]] = {sAddr=clientList[alias], addr=clientList[alias]}
225 print(alias.." found !")
226 end
227 end
228 end
229 elseif len(serverList) > 1 then
230 for alias, sAddr in pairs(serverList) do -- On demande une recherche aux serveurs
231 if sAddr ~= addr then -- On ignore le serveur précédent
232 modem.send(sAddr, ePort, "SEARCH", dest)
233 local recPort = _
234 while recPort ~= tPort do
235 local _, _, sAddr, _, _, mess, opt = event.pull(5, "modem_message")
236
237 if mess == "FOUND" then
238 modem.send(addr, tPort, "FOUND", opt)
239 relayCache[dest] = {sAddr=sAddr, addr=opt}
240 print(dest.." found @ "..sAddr)
241 return
242 elseif mess == nil then
243 break
244 end
245 end
246 end
247 end
248 else
249 modem.send(addr, tPort, "PNF")
250 end
251end
252
253
254
255-- RELAY FUNCTION -- (Create a relay connexion between two pairs. Answer to a "RELAY" command and may issue another "RELAY" command to a server, or "RELACK")
256
257function relay(addr, raw)
258 local data = ser.unserialize(raw)
259 if not relayCache[data.dest] then -- If a cache entry doesn't exist
260 search(localhost, data.dest)
261 end
262 if relayCache[data.dest] then -- If a cache entry exists (and it should)
263 if not check(data.dest) then -- If the receiver isn't connected to this server
264 modem.send(relayCache[data.dest].sAddr, ePort, "RELAY", raw)
265
266 local recPort = _
267 while recPort ~= tPort do
268 local _, _, sAddr, _, _, mess, opt = event.pull(5, "modem_message")
269
270 if (mess == "RELACK") and (opt == data.rID) then
271 relayConnexions[data.rID] = {addr, relayCache[data.dest].sAddr}
272 relack(relayCache[data.dest].sAddr, data.rID)
273 print("Relay "..data.rID.." created between "..relayConnexions[data.rID][1].." and "..relayConnexions[data.rID][2])
274 break
275 elseif mess == nil then
276 break
277 end
278 end
279 else
280 relayConnexions[data.rID] = {addr, data.dest}
281 modem.send(addr, tPort, "RELACK", data.rID)
282 print("Relay "..data.rID.." created between "..relayConnexions[data.rID][1].." and "..relayConnexions[data.rID][2])
283 modem.send(relayCache[data.dest].addr, ePort, "RELAY", data.rID)
284 end
285 else
286 modem.send(addr, ePort, "RELERR", data.rID)
287 end
288end
289
290
291
292-- RELAYACK FUNCTION -- (Answer to a "RELACK" command, transmit the "RELACK" in the proper direction)
293
294function relack(addr, rID)
295 if relayConnexions[rID] then
296 if addr == relayConnexions[rID][1] then
297 modem.send(relayConnexions[rID][2], tPort, "RELACK", rID)
298 elseif addr == relayConnexions[rID][2] then
299 modem.send(relayConnexions[rID][1], tPort, "RELACK", rID)
300 end
301 end
302end
303
304
305
306-- CLOSE FUNCTION -- (Answer to a "CLOSE" command. Close a direct connexion or a relay if it exists)
307
308function close(addr, port)
309 if activeConnexions[port] then
310 if (addr == activeConnexions[port][1]) or (addr == activeConnexions[port][2]) then
311 modem.close(port)
312 modem.send(activeConnexions[port][1], ePort, "CLOSE", port)
313 modem.send(activeConnexions[port][2], ePort, "CLOSE", port)
314 activeConnexions[port] = nil
315 print("Connexion closed on port "..port)
316 end
317 elseif relayConnexions[port] then
318 modem.send(relayConnexions[port][1], ePort, "CLOSE", port)
319 modem.send(relayConnexions[port][2], ePort, "CLOSE", port)
320 relayConnexions[port] = nil
321 print("Relay closed on rID "..port)
322 end
323end
324
325
326
327-- TRANSMIT FUNCTION -- (Answer to a message to transmit. Transmit the message on a direct or relayed connexion. Issue "ACK" or "RELACK" if successful)
328
329function transmit(port, addr, mess, opt)
330 if port == ePort then -- If the port is the command emission port, then it is a message to send to a relay
331 local list = relayConnexions[mess]
332 local direction = _
333 if addr == list[1] then
334 direction = 2
335 elseif addr == list[2] then
336 direction = 1
337 end
338 if not check(relayConnexions[mess][direction]) then -- If the client isn't connected on this server
339 modem.send(relayConnexions[mess][direction], ePort, mess, opt)
340 print("Relay on rID "..mess)
341 else
342 modem.send(relayConnexions[mess][direction], rPort, opt)
343 relack(relayConnexions[mess][direction], mess)
344 print("Transmit relayed message on port "..rPort.." for "..relayConnexions[mess][direction])
345 end
346 else -- Sinon, c'est un message direct
347 local list = activeConnexions[port]
348 if addr == list[1] then
349 modem.send(list[2], port, mess, opt)
350 modem.send(list[1], ePort, "ACK")
351 elseif addr == list[2] then
352 modem.send(list[1], port, mess, opt)
353 modem.send(list[2], ePort, "ACK")
354 end
355 print("Transmit on port "..port)
356 end
357end
358
359
360
361-- PING FUNCTION -- (Answer to a "PING" command, issue "PONG")
362
363function ping(addr)
364 modem.send(addr, ePort, "PONG")
365 print("Pong to "..addr)
366end
367
368
369
370-- MAIN LOOP --
371discover() -- Tries to connect to nearby servers in config.
372
373while true do
374 local _, localhost, addr, port, d, mess, opt = event.pull("modem_message")
375
376 if port == bPort then
377 if mess == "WHO" then
378 who(addr)
379 elseif mess == "HANDSHAKE" then
380 handshake(addr, opt)
381 elseif mess == "SCONNECT" then
382 handshake(addr, opt, "SERV")
383 end
384 elseif port == ePort then
385 if mess == "OPEN" then
386 open(addr, opt)
387 elseif mess == "CLOSE" then
388 close(addr, opt)
389 elseif mess == "DISCONNECT" then
390 disconnect(addr, opt)
391 elseif mess == "PING" then
392 ping(addr)
393 elseif mess == "SEARCH" then
394 search(addr, opt)
395 elseif mess == "RELAY" then
396 relay(addr, opt)
397 elseif mess == "RELERR" then
398 close(addr, opt)
399 else
400 for rID, list in pairs(relayConnexions) do
401 if mess == rID then
402 transmit(port, addr, mess, opt)
403 end
404 end
405 end
406 elseif port == tPort then
407 if mess == "RELACK" then
408 relack(addr, opt)
409 end
410 elseif activeConnexions[port] then
411 transmit(port, addr, mess, opt)
412 end
413end