From 1b83c83d6dee5450d3707eba0c6aea64025d9047 Mon Sep 17 00:00:00 2001 From: payonel Date: Mon, 31 Aug 2015 23:57:14 -0700 Subject: [PATCH 1/5] initial modem implementation. this does not support disconnecting clients --- src/component/modem.lua | 298 +++++++++++++++++++++++++++++++++++++--- src/main.lua | 2 + 2 files changed, 282 insertions(+), 18 deletions(-) diff --git a/src/component/modem.lua b/src/component/modem.lua index c2a3cf5..cd9b55b 100644 --- a/src/component/modem.lua +++ b/src/component/modem.lua @@ -1,6 +1,254 @@ local address, _, wireless = ... +print(address, _, wireless); compCheckArg(1,wireless,"boolean") +local socket = require("socket") +local ser = require("loot.OpenOS.lib.serialization") + +-- yes, global +modem_host = {} + +-- modem component +local obj = {} + +-- Modem cards communicate on a real backend port +modem_host.comms_port = 61234 +modem_host.comms_ip = "127.0.0.10" +modem_host.connected = false +modem_host.messages = {} +modem_host.socket = nil + +modem_host.hosting = false +modem_host.clients = {} + +-- [port_number] = true when open +modem_host.open_ports = {} + +local function createPacket(type, address, port, ...) + -- args are not checked here (unlike the modem api methods) + -- address can be nil, which means broadcast + local packed = + { + type or "unknown_type", + address or "no address", + modem_host.id or "no sender", + port or "no port", + 0, -- distance + table.unpack(table.pack(...)) + } + + local datagram = ser.serialize(packed) + return datagram .. '\n' +end + +local function parsePacket(raw) + assert(raw) + local packed = ser.unserialize(raw) + assert(packed ~= nil) + assert(#packed >= 5) + + local packet = {} + packet.type = packed[1] + packet.target = packed[2] + packet.source = packed[3] + packet.port = packed[4] + packet.distance = packed[5] + packet.payload = {} + + for i=6,#packed do + table.insert(packet.payload, packed[i]) + end + + return packet +end + +local function packetToArray(packet) + return + { + packet.type, + packet.target, + packet.source, + packet.port, + packet.distance, + table.unpack(packet.payload) + } +end + +function modem_host.broadcast(packet) + -- only host broadcasts + -- this method will be hit for all broadcasted messages + -- but nonhosting clients will simply not repeat the broadcast + if modem_host.hosting then + local plainArray = packetToArray(packet) + local datagram = ser.serialize(plainArray) + for addr,client in pairs(modem_host.clients) do + client:send(datagram) + end + end +end + +function modem_host.validTarget(target) + if target == modem_host.id then + return true + end + + if not modem_host.hosting then + return false + end + + for address,client in pairs(modem_host.clients) do + if address == target then + return true + end + end + + return false +end + +-- backend private methods, these are not pushed to user machine environments +function modem_host.pushMessage(target, datagram) + + if not modem_host.validTarget(target) then + return false, "invalid target, no such client listening" --ignored + end + + local packet = parsePacket(datagram) + table.insert(modem_host.messages, packet) + + return true +end + +function modem_host.processPendingMessages() + modem_host.recvPendingMessages() + + -- computer address seems to be applied late + if not modem_host.id then + modem_host.id = component.list("computer",true)() + assert(modem_host.id) + end + + local i = 1; + while i <= #modem_host.messages do + local packet = modem_host.messages[i] + local move = true + + if packet.type == 'modem_message' then + + -- broadcast if no target + if packet.target == 0 then + modem_host.broadcast(packet) + -- clean up for broadcasting to self + packet.target = modem_host.id + end + + if packet.target == modem_host.id then + if obj.isOpen(packet.port) then + table.insert(machine.signals, packetToArray(packet)) + end + move = false + end + end + + if move then + i = i + 1 + else + table.remove(modem_host.messages, i) + end + + end +end + +function modem_host.recvPendingMessages() + if modem_host.hosting then + while 1 do + local client = modem_host.socket:accept() + if not client then + break; + end + + local handshakeDatagram, err = client:receive() + if err then + client:close() + else + client:settimeout(0, 't') + + local handshake = parsePacket(handshakeDatagram) + modem_host.clients[handshake.source] = client + end + end + + -- recv all pending packets + for source, client in pairs(modem_host.clients) do + local line, err = client:receive() + if not err then + modem_host.pushMessage(source, line) + end + end + elseif modem_host.socket then + while 1 do + local line, err = modem_host.socket:receive() + if not err then + modem_host.pushMessage(modem_host.id, line) + else + break + end + end + end +end + +function modem_host.createNewMessageBoard() + local why + modem_host.socket, why = socket.bind(modem_host.comms_ip, modem_host.comms_port) + if modem_host.socket then + modem_host.hosting = true + end + return modem_host.socket, why +end + +function modem_host.joinExistingMessageBoard() + local why + modem_host.socket, why = socket.connect(modem_host.comms_ip, modem_host.comms_port) + if modem_host.socket then + modem_host.hosting = nil + + -- send handshake data + local datagram = createPacket("client_handshake") + modem_host.send(datagram) + end + return modem_host.socket, why +end + +function modem_host.connectMessageBoard() + + if modem_host.connected then + return true + end + + local ok, reason = + modem_host.joinExistingMessageBoard() or + modem_host.createNewMessageBoard() + + if not ok then + return nil, reason + end + + modem_host.socket:settimeout(0, 't') -- accept calls must be already pending + modem_host.connected = true + modem_host.clients = {} + modem_host.messages = {} + + return true +end + +function modem_host.send(datagram) + -- if we are the host, we simply call pushMessage directly + if modem_host.hosting then + return modem_host.pushMessage(modem_host.id, datagram) + else + return not not modem_host.socket:send(datagram) + end +end + local wakeMessage local strength if wireless then @@ -14,36 +262,38 @@ local function checkPort(port) return math.floor(port) end --- modem component -local obj = {} - function obj.send(address, port, ...) -- Sends the specified data to the specified target. - --STUB cprint("modem.send",address, port, ...) compCheckArg(1,address,"string") compCheckArg(2,port,"number") port=checkPort(port) - return true + + local datagram = createPacket("modem_message", address, port, ...) + return modem_host.send(datagram) end + function obj.getWakeMessage() -- Get the current wake-up message. - --STUB cprint("modem.getWakeMessage") return wakeMessage end function obj.setWakeMessage(message) -- Set the wake-up message. - --STUB cprint("modem.setWakeMessage",message) compCheckArg(1,message,"string","nil") wakeMessage = message end function obj.close(port) -- Closes the specified port (default: all ports). Returns true if ports were closed. - --STUB cprint("modem.close",port) compCheckArg(1,port,"number","nil") if port ~= nil then port=checkPort(port) end - return false + + if not obj.isOpen(port) then + return false; + end + + modem_host.open_ports[port] = nil + return true end function obj.maxPacketSize() -- Gets the maximum packet size (config setting). cprint("modem.maxPacketSize") @@ -51,41 +301,53 @@ function obj.maxPacketSize() -- Gets the maximum packet size (config setting). end if wireless then function obj.getStrength() -- Get the signal strength (range) used when sending messages. - --STUB cprint("modem.getStrength") return strength end function obj.setStrength(newstrength) -- Set the signal strength (range) used when sending messages. - --STUB cprint("modem.setStrength",newstrength) compCheckArg(1,newstrength,"number") strength = newstrength end end function obj.isOpen(port) -- Whether the specified port is open. - --STUB cprint("modem.isOpen",port) compCheckArg(1,port,"number") - return false + return modem_host.open_ports[port] ~= nil end function obj.open(port) -- Opens the specified port. Returns true if the port was opened. - --STUB cprint("modem.open",port) compCheckArg(1,port,"number") port=checkPort(port) - return false + + if obj.isOpen(port) then + return false + end + + -- make sure we are connected to the message board + if not modem_host.connectMessageBoard() then + return false + end + + modem_host.open_ports[port] = true + return true end function obj.isWireless() -- Whether this is a wireless network card. - --STUB cprint("modem.isWireless") return wireless end function obj.broadcast(port, ...) -- Broadcasts the specified data on the specified port. - --STUB cprint("modem.broadcast",port, ...) compCheckArg(1,port,"number") port=checkPort(port) - return true + + -- we cannot broadcast unless we are connected to the message board + if not modem_host.connectMessageBoard() then + return false + end + + local datagram = createPacket("modem_message", 0, port, ...) + return modem_host.send(datagram) end local cec = {} diff --git a/src/main.lua b/src/main.lua index 64ac711..b897116 100644 --- a/src/main.lua +++ b/src/main.lua @@ -53,6 +53,7 @@ if settings.components == nil then {"filesystem",nil,7,"loot/OpenOS",true}, {"filesystem",nil,nil,"tmpfs",false}, {"filesystem",nil,5,nil,false}, + {"modem",nil,nil,false}, {"internet"}, {"computer"}, {"ocemu"}, @@ -313,6 +314,7 @@ function elsa.update(dt) table.remove(kbdcodes,1) table.insert(machine.signals,{kbdcode.type,kbdcode.addr,kbdcode.char or 0,kbdcode.code}) end + modem_host.processPendingMessages() if #machine.signals > 0 then signal = machine.signals[1] table.remove(machine.signals, 1) From 7e7851f0dea1cf98a7e1dc082f47a63110e59533 Mon Sep 17 00:00:00 2001 From: payonel Date: Tue, 1 Sep 2015 00:17:46 -0700 Subject: [PATCH 2/5] change white space to tabs instead of spaces --- src/component/modem.lua | 397 ++++++++++++++++++++-------------------- src/main.lua | 4 +- 2 files changed, 203 insertions(+), 198 deletions(-) diff --git a/src/component/modem.lua b/src/component/modem.lua index cd9b55b..9e1e9c3 100644 --- a/src/component/modem.lua +++ b/src/component/modem.lua @@ -25,228 +25,225 @@ modem_host.clients = {} modem_host.open_ports = {} local function createPacket(type, address, port, ...) - -- args are not checked here (unlike the modem api methods) - -- address can be nil, which means broadcast - local packed = - { - type or "unknown_type", - address or "no address", - modem_host.id or "no sender", - port or "no port", - 0, -- distance - table.unpack(table.pack(...)) - } + -- args are not checked here (unlike the modem api methods) + -- address can be nil, which means broadcast + local packed = + { + type or "unknown_type", + address or "no address", + modem_host.id or "no sender", + port or "no port", + 0, -- distance + table.unpack(table.pack(...)) + } - local datagram = ser.serialize(packed) - return datagram .. '\n' + local datagram = ser.serialize(packed) + return datagram .. '\n' end local function parsePacket(raw) - assert(raw) - local packed = ser.unserialize(raw) - assert(packed ~= nil) - assert(#packed >= 5) + assert(raw) + local packed = ser.unserialize(raw) + assert(packed ~= nil) + assert(#packed >= 5) - local packet = {} - packet.type = packed[1] - packet.target = packed[2] - packet.source = packed[3] - packet.port = packed[4] - packet.distance = packed[5] - packet.payload = {} + local packet = {} + packet.type = packed[1] + packet.target = packed[2] + packet.source = packed[3] + packet.port = packed[4] + packet.distance = packed[5] + packet.payload = {} - for i=6,#packed do - table.insert(packet.payload, packed[i]) - end + for i=6,#packed do + table.insert(packet.payload, packed[i]) + end - return packet + return packet end local function packetToArray(packet) - return - { - packet.type, - packet.target, - packet.source, - packet.port, - packet.distance, - table.unpack(packet.payload) - } + return + { + packet.type, + packet.target, + packet.source, + packet.port, + packet.distance, + table.unpack(packet.payload) + } end function modem_host.broadcast(packet) - -- only host broadcasts - -- this method will be hit for all broadcasted messages - -- but nonhosting clients will simply not repeat the broadcast - if modem_host.hosting then - local plainArray = packetToArray(packet) - local datagram = ser.serialize(plainArray) - for addr,client in pairs(modem_host.clients) do - client:send(datagram) - end - end + -- only host broadcasts + -- this method will be hit for all broadcasted messages + -- but nonhosting clients will simply not repeat the broadcast + if modem_host.hosting then + local plainArray = packetToArray(packet) + local datagram = ser.serialize(plainArray) + for addr,client in pairs(modem_host.clients) do + client:send(datagram) + end + end end function modem_host.validTarget(target) - if target == modem_host.id then - return true - end + if target == modem_host.id then + return true + end - if not modem_host.hosting then - return false - end + if not modem_host.hosting then + return false + end - for address,client in pairs(modem_host.clients) do - if address == target then - return true - end - end + for address,client in pairs(modem_host.clients) do + if address == target then + return true + end + end - return false + return false end -- backend private methods, these are not pushed to user machine environments function modem_host.pushMessage(target, datagram) + if not modem_host.validTarget(target) then + return false, "invalid target, no such client listening" --ignored + end - if not modem_host.validTarget(target) then - return false, "invalid target, no such client listening" --ignored - end + local packet = parsePacket(datagram) + table.insert(modem_host.messages, packet) - local packet = parsePacket(datagram) - table.insert(modem_host.messages, packet) - - return true + return true end function modem_host.processPendingMessages() - modem_host.recvPendingMessages() + modem_host.recvPendingMessages() - -- computer address seems to be applied late - if not modem_host.id then - modem_host.id = component.list("computer",true)() - assert(modem_host.id) - end + -- computer address seems to be applied late + if not modem_host.id then + modem_host.id = component.list("computer",true)() + assert(modem_host.id) + end - local i = 1; - while i <= #modem_host.messages do - local packet = modem_host.messages[i] - local move = true + local i = 1; + while i <= #modem_host.messages do + local packet = modem_host.messages[i] + local move = true - if packet.type == 'modem_message' then + if packet.type == 'modem_message' then - -- broadcast if no target - if packet.target == 0 then - modem_host.broadcast(packet) - -- clean up for broadcasting to self - packet.target = modem_host.id - end + -- broadcast if no target + if packet.target == 0 then + modem_host.broadcast(packet) + -- clean up for broadcasting to self + packet.target = modem_host.id + end - if packet.target == modem_host.id then - if obj.isOpen(packet.port) then - table.insert(machine.signals, packetToArray(packet)) - end - move = false - end - end + if packet.target == modem_host.id then + if obj.isOpen(packet.port) then + table.insert(machine.signals, packetToArray(packet)) + end + move = false + end + end - if move then - i = i + 1 - else - table.remove(modem_host.messages, i) - end - - end + if move then + i = i + 1 + else + table.remove(modem_host.messages, i) + end + end end function modem_host.recvPendingMessages() - if modem_host.hosting then - while 1 do - local client = modem_host.socket:accept() - if not client then - break; - end + if modem_host.hosting then + while 1 do + local client = modem_host.socket:accept() + if not client then + break; + end - local handshakeDatagram, err = client:receive() - if err then - client:close() - else - client:settimeout(0, 't') + local handshakeDatagram, err = client:receive() + if err then + client:close() + else + client:settimeout(0, 't') - local handshake = parsePacket(handshakeDatagram) - modem_host.clients[handshake.source] = client - end - end + local handshake = parsePacket(handshakeDatagram) + modem_host.clients[handshake.source] = client + end + end - -- recv all pending packets - for source, client in pairs(modem_host.clients) do - local line, err = client:receive() - if not err then - modem_host.pushMessage(source, line) - end - end - elseif modem_host.socket then - while 1 do - local line, err = modem_host.socket:receive() - if not err then - modem_host.pushMessage(modem_host.id, line) - else - break - end - end - end + -- recv all pending packets + for source, client in pairs(modem_host.clients) do + local line, err = client:receive() + if not err then + modem_host.pushMessage(source, line) + end + end + elseif modem_host.socket then + while 1 do + local line, err = modem_host.socket:receive() + if not err then + modem_host.pushMessage(modem_host.id, line) + else + break + end + end + end end function modem_host.createNewMessageBoard() - local why - modem_host.socket, why = socket.bind(modem_host.comms_ip, modem_host.comms_port) - if modem_host.socket then - modem_host.hosting = true - end - return modem_host.socket, why + local why + modem_host.socket, why = socket.bind(modem_host.comms_ip, modem_host.comms_port) + if modem_host.socket then + modem_host.hosting = true + end + return modem_host.socket, why end function modem_host.joinExistingMessageBoard() - local why - modem_host.socket, why = socket.connect(modem_host.comms_ip, modem_host.comms_port) - if modem_host.socket then - modem_host.hosting = nil + local why + modem_host.socket, why = socket.connect(modem_host.comms_ip, modem_host.comms_port) + if modem_host.socket then + modem_host.hosting = nil - -- send handshake data - local datagram = createPacket("client_handshake") - modem_host.send(datagram) - end - return modem_host.socket, why + -- send handshake data + local datagram = createPacket("client_handshake") + modem_host.send(datagram) + end + return modem_host.socket, why end function modem_host.connectMessageBoard() + if modem_host.connected then + return true + end - if modem_host.connected then - return true - end + local ok, reason = + modem_host.joinExistingMessageBoard() or + modem_host.createNewMessageBoard() - local ok, reason = - modem_host.joinExistingMessageBoard() or - modem_host.createNewMessageBoard() + if not ok then + return nil, reason + end - if not ok then - return nil, reason - end + modem_host.socket:settimeout(0, 't') -- accept calls must be already pending + modem_host.connected = true + modem_host.clients = {} + modem_host.messages = {} - modem_host.socket:settimeout(0, 't') -- accept calls must be already pending - modem_host.connected = true - modem_host.clients = {} - modem_host.messages = {} - - return true + return true end function modem_host.send(datagram) - -- if we are the host, we simply call pushMessage directly - if modem_host.hosting then - return modem_host.pushMessage(modem_host.id, datagram) - else - return not not modem_host.socket:send(datagram) - end + -- if we are the host, we simply call pushMessage directly + if modem_host.hosting then + return modem_host.pushMessage(modem_host.id, datagram) + else + return not not modem_host.socket:send(datagram) + end end local wakeMessage @@ -268,19 +265,21 @@ function obj.send(address, port, ...) -- Sends the specified data to the specifi compCheckArg(2,port,"number") port=checkPort(port) - local datagram = createPacket("modem_message", address, port, ...) - return modem_host.send(datagram) + local datagram = createPacket("modem_message", address, port, ...) + return modem_host.send(datagram) end function obj.getWakeMessage() -- Get the current wake-up message. cprint("modem.getWakeMessage") return wakeMessage end + function obj.setWakeMessage(message) -- Set the wake-up message. cprint("modem.setWakeMessage",message) compCheckArg(1,message,"string","nil") wakeMessage = message end + function obj.close(port) -- Closes the specified port (default: all ports). Returns true if ports were closed. cprint("modem.close",port) compCheckArg(1,port,"number","nil") @@ -288,17 +287,19 @@ function obj.close(port) -- Closes the specified port (default: all ports). Retu port=checkPort(port) end - if not obj.isOpen(port) then - return false; - end + if not obj.isOpen(port) then + return false; + end - modem_host.open_ports[port] = nil + modem_host.open_ports[port] = nil return true end + function obj.maxPacketSize() -- Gets the maximum packet size (config setting). cprint("modem.maxPacketSize") return settings.maxNetworkPacketSize end + if wireless then function obj.getStrength() -- Get the signal strength (range) used when sending messages. cprint("modem.getStrength") @@ -310,60 +311,64 @@ if wireless then strength = newstrength end end + function obj.isOpen(port) -- Whether the specified port is open. cprint("modem.isOpen",port) compCheckArg(1,port,"number") return modem_host.open_ports[port] ~= nil end + function obj.open(port) -- Opens the specified port. Returns true if the port was opened. cprint("modem.open",port) compCheckArg(1,port,"number") port=checkPort(port) - if obj.isOpen(port) then - return false - end + if obj.isOpen(port) then + return false + end - -- make sure we are connected to the message board - if not modem_host.connectMessageBoard() then - return false - end + -- make sure we are connected to the message board + if not modem_host.connectMessageBoard() then + return false + end - modem_host.open_ports[port] = true + modem_host.open_ports[port] = true return true end + function obj.isWireless() -- Whether this is a wireless network card. cprint("modem.isWireless") return wireless end + function obj.broadcast(port, ...) -- Broadcasts the specified data on the specified port. cprint("modem.broadcast",port, ...) compCheckArg(1,port,"number") port=checkPort(port) - -- we cannot broadcast unless we are connected to the message board - if not modem_host.connectMessageBoard() then - return false - end + -- we cannot broadcast unless we are connected to the message board + if not modem_host.connectMessageBoard() then + return false + end - local datagram = createPacket("modem_message", 0, port, ...) - return modem_host.send(datagram) + local datagram = createPacket("modem_message", 0, port, ...) + return modem_host.send(datagram) end local cec = {} local doc = { - ["send"]="function(address:string, port:number, data...) -- Sends the specified data to the specified target.", - ["getWakeMessage"]="function():string -- Get the current wake-up message.", - ["setWakeMessage"]="function(message:string):string -- Set the wake-up message.", - ["close"]="function([port:number]):boolean -- Closes the specified port (default: all ports). Returns true if ports were closed.", - ["maxPacketSize"]="function():number -- Gets the maximum packet size (config setting).", - ["getStrength"]="function():number -- Get the signal strength (range) used when sending messages.", - ["setStrength"]="function(strength:number):number -- Set the signal strength (range) used when sending messages.", - ["isOpen"]="function(port:number):boolean -- Whether the specified port is open.", - ["open"]="function(port:number):boolean -- Opens the specified port. Returns true if the port was opened.", - ["isWireless"]="function():boolean -- Whether this is a wireless network card.", - ["broadcast"]="function(port:number, data...) -- Broadcasts the specified data on the specified port.", +["send"]="function(address:string, port:number, data...) -- Sends the specified data to the specified target.", +["getWakeMessage"]="function():string -- Get the current wake-up message.", +["setWakeMessage"]="function(message:string):string -- Set the wake-up message.", +["close"]="function([port:number]):boolean -- Closes the specified port (default: all ports). Returns true if ports were closed.", +["maxPacketSize"]="function():number -- Gets the maximum packet size (config setting).", +["getStrength"]="function():number -- Get the signal strength (range) used when sending messages.", +["setStrength"]="function(strength:number):number -- Set the signal strength (range) used when sending messages.", +["isOpen"]="function(port:number):boolean -- Whether the specified port is open.", +["open"]="function(port:number):boolean -- Opens the specified port. Returns true if the port was opened.", +["isWireless"]="function():boolean -- Whether this is a wireless network card.", +["broadcast"]="function(port:number, data...) -- Broadcasts the specified data on the specified port.", } return obj,cec,doc diff --git a/src/main.lua b/src/main.lua index b897116..a933f09 100644 --- a/src/main.lua +++ b/src/main.lua @@ -53,7 +53,7 @@ if settings.components == nil then {"filesystem",nil,7,"loot/OpenOS",true}, {"filesystem",nil,nil,"tmpfs",false}, {"filesystem",nil,5,nil,false}, - {"modem",nil,nil,false}, + {"modem",nil,nil,false}, {"internet"}, {"computer"}, {"ocemu"}, @@ -314,7 +314,7 @@ function elsa.update(dt) table.remove(kbdcodes,1) table.insert(machine.signals,{kbdcode.type,kbdcode.addr,kbdcode.char or 0,kbdcode.code}) end - modem_host.processPendingMessages() + modem_host.processPendingMessages() if #machine.signals > 0 then signal = machine.signals[1] table.remove(machine.signals, 1) From 101d4feed15c3b96e02046b4fd42a289d1761b4a Mon Sep 17 00:00:00 2001 From: payonel Date: Tue, 1 Sep 2015 09:14:59 -0700 Subject: [PATCH 3/5] check for modem not nil in main.lua event handling loop, preexisting machines won't have one --- src/main.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.lua b/src/main.lua index a933f09..7a164be 100644 --- a/src/main.lua +++ b/src/main.lua @@ -314,7 +314,9 @@ function elsa.update(dt) table.remove(kbdcodes,1) table.insert(machine.signals,{kbdcode.type,kbdcode.addr,kbdcode.char or 0,kbdcode.code}) end - modem_host.processPendingMessages() + if modem_host then + modem_host.processPendingMessages() + end if #machine.signals > 0 then signal = machine.signals[1] table.remove(machine.signals, 1) From 771ae94ad5789c7dd5c3b443e22440a4d810964d Mon Sep 17 00:00:00 2001 From: payonel Date: Wed, 2 Sep 2015 05:38:50 -0700 Subject: [PATCH 4/5] refactor modem code for robustness --- src/component/modem.lua | 250 +++++++++++++++++++++++++++------------- src/main.lua | 2 +- 2 files changed, 174 insertions(+), 78 deletions(-) diff --git a/src/component/modem.lua b/src/component/modem.lua index 9e1e9c3..7a879ee 100644 --- a/src/component/modem.lua +++ b/src/component/modem.lua @@ -24,27 +24,26 @@ modem_host.clients = {} -- [port_number] = true when open modem_host.open_ports = {} -local function createPacket(type, address, port, ...) - -- args are not checked here (unlike the modem api methods) - -- address can be nil, which means broadcast +function modem_host.createPacketArray(t, address, port, ...) + compCheckArg(1,t,type("")) + compCheckArg(2,address,type(""),type(0)) + compCheckArg(3,port,type(0)) + local packed = { - type or "unknown_type", - address or "no address", - modem_host.id or "no sender", - port or "no port", + t, + address, + modem_host.id, + port, 0, -- distance - table.unpack(table.pack(...)) + ... } - local datagram = ser.serialize(packed) - return datagram .. '\n' + return packed end -local function parsePacket(raw) - assert(raw) - local packed = ser.unserialize(raw) - assert(packed ~= nil) +function modem_host.packetArrayToPacket(packed) + compCheckArg(1,packed,type({})) assert(#packed >= 5) local packet = {} @@ -62,7 +61,14 @@ local function parsePacket(raw) return packet end -local function packetToArray(packet) +function modem_host.packetArrayToDatagram(packed) + compCheckArg(1,packed,type({})) + + local datagram = ser.serialize(packed) + return datagram .. '\n' +end + +function modem_host.packetToPacketArray(packet) return { packet.type, @@ -70,24 +76,78 @@ local function packetToArray(packet) packet.source, packet.port, packet.distance, - table.unpack(packet.payload) + table.unpack(packet.payload or {}) } end +function modem_host.datagramToPacketArray(datagram) + compCheckArg(1,datagram,type("")) + local packed = ser.unserialize(datagram) + return packed +end + +function modem_host.datagramToPacket(datagram) + local packed = modem_host.datagramToPacketArray(datagram) + local packet = modem_host.packedToPacket(packed) + return packet +end + +function modem_host.packetToDatagram(packet) + local packed = modem_host.packetToPacketArray(packet) + local datagram = modem_host.packetArrayToDatagram(packed) + return datagram +end + +function modem_host.readDatagram(client) -- client:receive() + local raw, err = client:receive() + if raw then cprint("received: " .. raw) end + return raw, err +end + +function modem_host.readPacketArray(client) -- client:receive() + local datagram, err = modem_host.readDatagram(client) + if not datagram then return nil, err end + return modem_host.datagramToPacketArray(datagram) +end + +function modem_host.readPacket(client) -- client:receive() + local packed, err = modem_host.readPacketArray(client) + if not packed then return nil, err end + return modem_host.packetArrayToPacket(packed) +end + +function modem_host.sendDatagram(client, datagram) + cprint("sending: " .. datagram) + return client:send(datagram) +end + +function modem_host.sendPacketArray(client, packed) + local datagram = modem_host.packetArrayToDatagram(packed) + return modem_host.sendDatagram(client, datagram) +end + +function modem_host.sendPacket(client, packet) + local datagram = modem_host.packetToDatagram(packet) + return modem_host.sendDatagram(client, datagram) +end + function modem_host.broadcast(packet) -- only host broadcasts -- this method will be hit for all broadcasted messages -- but nonhosting clients will simply not repeat the broadcast if modem_host.hosting then - local plainArray = packetToArray(packet) - local datagram = ser.serialize(plainArray) + local datagram = modem_host.packetToDatagram(packet) for addr,client in pairs(modem_host.clients) do - client:send(datagram) + modem_host.sendDatagram(client, datagram) end end end function modem_host.validTarget(target) + if target == 0 then + return true + end + if target == modem_host.id then return true end @@ -106,87 +166,112 @@ function modem_host.validTarget(target) end -- backend private methods, these are not pushed to user machine environments -function modem_host.pushMessage(target, datagram) - if not modem_host.validTarget(target) then +function modem_host.pushMessage(packet) + if not modem_host.validTarget(packet.target) then return false, "invalid target, no such client listening" --ignored end - local packet = parsePacket(datagram) table.insert(modem_host.messages, packet) return true end -function modem_host.processPendingMessages() - modem_host.recvPendingMessages() +function modem_host.dispatchPacket(packet) + if packet.target == modem_host.id then + if obj.isOpen(packet.port) then + table.insert(machine.signals, modem_host.packetToPacketArray(packet)) + end + elseif modem_host.hosting then -- if hosting we will route + for source,client in pairs(modem_host.clients) do + if source == packet.target then + modem_host.sendPacket(client, packet) + break + end + end + else -- not hosting, send to host + modem_host.sendPacket(modem_host.socket, packet) + end +end +function modem_host.processPendingMessages() -- computer address seems to be applied late if not modem_host.id then modem_host.id = component.list("computer",true)() assert(modem_host.id) end - local i = 1; - while i <= #modem_host.messages do - local packet = modem_host.messages[i] - local move = true + modem_host.recvPendingMessages() + + for _,packet in pairs(modem_host.messages) do if packet.type == 'modem_message' then - -- broadcast if no target if packet.target == 0 then - modem_host.broadcast(packet) + modem_host.broadcast(packet) -- ignored by clients + -- clean up for broadcasting to self packet.target = modem_host.id end - if packet.target == modem_host.id then - if obj.isOpen(packet.port) then - table.insert(machine.signals, packetToArray(packet)) - end - move = false - end - end - - if move then - i = i + 1 - else - table.remove(modem_host.messages, i) + modem_host.dispatchPacket(packet) end end + + modem_host.messages = {} end function modem_host.recvPendingMessages() if modem_host.hosting then - while 1 do + while true do local client = modem_host.socket:accept() if not client then break; end - local handshakeDatagram, err = client:receive() - if err then + local handshake, err = modem_host.readPacket(client) -- client:receive() + if not handshake then client:close() else - client:settimeout(0, 't') - local handshake = parsePacket(handshakeDatagram) - modem_host.clients[handshake.source] = client + local connectionResponse + local accepted = false + if handshake.type ~= "handshake" then + connectionResponse = modem_host.createPacketArray("handshake", modem_host.id, -1, + false, "unsupported message type"); + elseif modem_host.validTarget(handshake.source) then -- repeated client + connectionResponse = modem_host.createPacketArray("handshake", modem_host.id, -1, + false, "computer address conflict detected, ignoring connection"); + else + client:settimeout(0, 't') + modem_host.clients[handshake.source] = client + accepted = true + + connectionResponse = modem_host.createPacketArray("handshake", modem_host.id, -1, true); + end + + modem_host.sendPacketArray(client, connectionResponse) + + if not accepted then + client:close() + end end end -- recv all pending packets for source, client in pairs(modem_host.clients) do - local line, err = client:receive() - if not err then - modem_host.pushMessage(source, line) + local packet, err = modem_host.readPacket(client) + if packet then + modem_host.pushMessage(packet) + elseif err ~= "timeout" then + client:close() + modem_host.clients[source] = nil end end elseif modem_host.socket then - while 1 do - local line, err = modem_host.socket:receive() - if not err then - modem_host.pushMessage(modem_host.id, line) + while true do + local packet, err = modem_host.readPacket(modem_host.socket) + if packet then + modem_host.pushMessage(packet) else break end @@ -210,8 +295,18 @@ function modem_host.joinExistingMessageBoard() modem_host.hosting = nil -- send handshake data - local datagram = createPacket("client_handshake") - modem_host.send(datagram) + local packed = modem_host.createPacketArray("handshake", 0, -1) + local sendResult = modem_host.sendPacketArray(modem_host.socket, packed) + + local response, why = modem_host.readPacket(modem_host.socket) + assert(response) + assert(response.payload) + + if not response.payload[1] then + modem_host.socket:close() + modem_host.socket = nil + return false, response.payload[2], true + end end return modem_host.socket, why end @@ -221,12 +316,18 @@ function modem_host.connectMessageBoard() return true end - local ok, reason = - modem_host.joinExistingMessageBoard() or - modem_host.createNewMessageBoard() + local ok, info, critical = modem_host.joinExistingMessageBoard() + + if not ok and critical then + return nil, info + end if not ok then - return nil, reason + ok, info = modem_host.createNewMessageBoard() + end + + if not ok then + return nil, info end modem_host.socket:settimeout(0, 't') -- accept calls must be already pending @@ -237,15 +338,6 @@ function modem_host.connectMessageBoard() return true end -function modem_host.send(datagram) - -- if we are the host, we simply call pushMessage directly - if modem_host.hosting then - return modem_host.pushMessage(modem_host.id, datagram) - else - return not not modem_host.socket:send(datagram) - end -end - local wakeMessage local strength if wireless then @@ -265,8 +357,9 @@ function obj.send(address, port, ...) -- Sends the specified data to the specifi compCheckArg(2,port,"number") port=checkPort(port) - local datagram = createPacket("modem_message", address, port, ...) - return modem_host.send(datagram) + local packed = modem_host.createPacketArray("modem_message", address, port, ...) + local packet = modem_host.packetArrayToPacket(packed) + return modem_host.dispatchPacket(packet) end function obj.getWakeMessage() -- Get the current wake-up message. @@ -324,12 +417,14 @@ function obj.open(port) -- Opens the specified port. Returns true if the port wa port=checkPort(port) if obj.isOpen(port) then - return false + return false, "port already open" end -- make sure we are connected to the message board - if not modem_host.connectMessageBoard() then - return false + local ok, why = modem_host.connectMessageBoard() + + if not ok then + return false, why end modem_host.open_ports[port] = true @@ -351,8 +446,9 @@ function obj.broadcast(port, ...) -- Broadcasts the specified data on the specif return false end - local datagram = createPacket("modem_message", 0, port, ...) - return modem_host.send(datagram) + local packed = modem_host.createPacketArray("modem_message", 0, port, ...) + local packet = modem_host.packetArrayToPacket(packed) + return modem_host.dispatchPacket(packet) end local cec = {} diff --git a/src/main.lua b/src/main.lua index 7a164be..ca8beb6 100644 --- a/src/main.lua +++ b/src/main.lua @@ -49,11 +49,11 @@ if settings.components == nil then -- Read component files for parameter documentation settings.components = { {"gpu",nil,0,160,50,3}, + {"modem",nil,1,false}, {"eeprom",nil,9,"lua/bios.lua"}, {"filesystem",nil,7,"loot/OpenOS",true}, {"filesystem",nil,nil,"tmpfs",false}, {"filesystem",nil,5,nil,false}, - {"modem",nil,nil,false}, {"internet"}, {"computer"}, {"ocemu"}, From f367469171c353c7e43ec0274d64fb96cf431424 Mon Sep 17 00:00:00 2001 From: payonel Date: Wed, 2 Sep 2015 05:47:39 -0700 Subject: [PATCH 5/5] indent table initializer --- src/component/modem.lua | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/component/modem.lua b/src/component/modem.lua index 7a879ee..ab088c4 100644 --- a/src/component/modem.lua +++ b/src/component/modem.lua @@ -454,17 +454,17 @@ end local cec = {} local doc = { -["send"]="function(address:string, port:number, data...) -- Sends the specified data to the specified target.", -["getWakeMessage"]="function():string -- Get the current wake-up message.", -["setWakeMessage"]="function(message:string):string -- Set the wake-up message.", -["close"]="function([port:number]):boolean -- Closes the specified port (default: all ports). Returns true if ports were closed.", -["maxPacketSize"]="function():number -- Gets the maximum packet size (config setting).", -["getStrength"]="function():number -- Get the signal strength (range) used when sending messages.", -["setStrength"]="function(strength:number):number -- Set the signal strength (range) used when sending messages.", -["isOpen"]="function(port:number):boolean -- Whether the specified port is open.", -["open"]="function(port:number):boolean -- Opens the specified port. Returns true if the port was opened.", -["isWireless"]="function():boolean -- Whether this is a wireless network card.", -["broadcast"]="function(port:number, data...) -- Broadcasts the specified data on the specified port.", + ["send"]="function(address:string, port:number, data...) -- Sends the specified data to the specified target.", + ["getWakeMessage"]="function():string -- Get the current wake-up message.", + ["setWakeMessage"]="function(message:string):string -- Set the wake-up message.", + ["close"]="function([port:number]):boolean -- Closes the specified port (default: all ports). Returns true if ports were closed.", + ["maxPacketSize"]="function():number -- Gets the maximum packet size (config setting).", + ["getStrength"]="function():number -- Get the signal strength (range) used when sending messages.", + ["setStrength"]="function(strength:number):number -- Set the signal strength (range) used when sending messages.", + ["isOpen"]="function(port:number):boolean -- Whether the specified port is open.", + ["open"]="function(port:number):boolean -- Opens the specified port. Returns true if the port was opened.", + ["isWireless"]="function():boolean -- Whether this is a wireless network card.", + ["broadcast"]="function(port:number, data...) -- Broadcasts the specified data on the specified port.", } return obj,cec,doc