[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/Phoenix-ComputerCraft/kernel/master/kernel.lua.d/60-network.lua [Back]  [Original]

-- TODO: fix user:password@domain URIs (uncommon but part of spec)
local function parseURI(uri)
    local info = {scheme = ""}
    for c in uri:gmatch "." do
        if info.fragment then
            if c:match "[%w%-%._~%%@:/!%$&'%(%)%*%+,;=/?]" then info.fragment = info.fragment .. c
            else error("Invalid URI", 3) end
        elseif info.query then
            if c == "#" then info.fragment = ""
            elseif c:match "[%w%-%._~%%@:/!%$&'%(%)%*%+,;=/?]" then info.query = info.query .. c
            else error("Invalid URI", 3) end
        elseif info.path then
            if c == "/" and info.path == "/" and not info.host then info.path, info.host = nil, ""
            elseif c == "?" then info.query = ""
            elseif c == "#" then info.fragment = ""
            elseif c:match "[%w%-%._~%%@:/!%$&'%(%)%*%+,;=/]" then info.path = info.path .. c
            else error("Invalid URI", 3) end
        elseif info.port then
            if tonumber(c) then info.port = info.port .. c
            elseif c == "/" then info.path = "/"
            else error("Invalid URI", 3) end
        elseif info.host then
            if c == "@" and not info.user then info.user, info.host = info.host, ""
            elseif c == ":" then info.port = ""
            elseif c == "/" then info.path = "/"
            elseif c:match "[%w%-%._~%%/!%$&'%(%)%*%+,;=]" then info.host = info.host .. c
            else error("Invalid URI", 3) end
        else
            if c == ":" then info.path = ""
            elseif c:match(info.scheme == "" and "[%a%+%-%.]" or "[%w%+%-%.]") then info.scheme = info.scheme .. c
            else error("Invalid URI", 3) end
        end
    end
    if info.port then info.port = tonumber(info.port) end
    return info
end

local function ipToNumber(ip)
    if ip:match "^%d+$" then return tonumber(ip)
    elseif ip:match "^%d+%.%d+$" then return tonumber(ip:match "^%d+") * 0x1000000 + tonumber(ip:match "^%d+%.(%d+)")
    elseif ip:match "^%d+%.%d+%.%d+$" then return tonumber(ip:match "^(%d+)") * 0x1000000 + tonumber(ip:match "^%d+%.(%d+)") * 0x10000 + tonumber(ip:match "^%d+%.%d+%.(%d+)")
    elseif ip:match "^%d+%.%d+%.%d+%.%d+$" then return tonumber(ip:match "^(%d+)") * 0x1000000 + tonumber(ip:match "^%d+%.(%d+)") * 0x10000 + tonumber(ip:match "^%d+%.%d+%.(%d+)") * 0x100 + tonumber(ip:match "^%d+%.%d+%.%d+%.(%d+)")
    else error("Invalid IP address", 2) end
end

local function numberToIP(num)
    if not num then return nil end
    return ("%d.%d.%d.%d"):format(bit32.band(bit32.rshift(num, 24), 0xFF), bit32.band(bit32.rshift(num, 16), 0xFF), bit32.band(bit32.rshift(num, 8), 0xFF), bit32.band(num, 0xFF))
end

local function randomString(len)
    local s = ""
    for i = 1, len do s = s .. string.char(math.random(0, 255)) end
    return s
end

local function prefixToMask(num) return bit32.bnot(2^(32-num)-1) end

local function maskToPrefix(mask)
    local n = 0
    while bit32.btest(mask, 0x80000000) do mask, n = bit32.lshift(mask, 1), n + 1 end
    return n
end

local function checkModem(node)
    if not node then error("No such device") end
    for _, v in pairs(node.drivers) do if v.type == "modem" then return node end end
    error("Not a modem")
end

local nextHandleID = 0

--#region Network stack implementation

local ipconfig = {}
local routes = {maxn = 0, [0] = {}}
local arptable = {}
local protocols = {send = {}, recv = {}}
local openSockets = {}
local openSocketIDs = {}
local pdpListeners = {}
local networkListeners = setmetatable({}, {__mode = "k"})
local waiting = {arp = {}, socket = {}}
local usedMessageIDs = {}

--[[
    socket object:
    * ip: IP address of the remote host
    * port: Port of the remote host
    * localPort: Receive port on the local host
    * id: ID of the socket/handle
    * sendSeq: last sequence acknowledged by peer [SND.UNA]
    * sendSeqNext: next sender sequence to peer [SND.NXT]
    * sendSeqMax: maximum sequence to peer (for window) [SND.WND]
      - on close, this is set to the sequence for the FIN message
    * recvSeq: next expected received sequence from peer [RCV.NXT]
    * recvSeqMax: maximum sequence from peer (for window) [RCV.WND]
    * status: status of socket (listening, syn-sent, syn-received, connected, fin-wait, closing, close-wait [LAST-ACK], time-wait, closed)
    * nextUpdate: time that the next update can be triggered
    * process?: process that opened the socket
    * retryCount: number of retries in the current state
    * error?: an error status if something went wrong
    * outQueue: list of messages that were sent but not ACKed
    * nextAck?: whether the next timer should be for sending an ACK
    * buffer: data queue for the socket (string)
]]

local phoenixNetworkingChannel = 20568 -- "PX" converted to decimal

function protocols.send.link(info, destination, message)
    expect(2, destination, "number", "nil")
    expect.field(info, "device", "table")
    local obj = {
        PhoenixNetworking = true,
        type = "link",
        source = os.computerID(),
        destination = destination,
        inPort = info.inPort,
        outPort = info.outPort,
        payload = message
    }
    if destination == os.computerID() then os.queueEvent("modem_message", info.device.id, phoenixNetworkingChannel, phoenixNetworkingChannel, obj, 0)
    else hardware.call(info.process or KERNEL, info.device, "transmit", phoenixNetworkingChannel, phoenixNetworkingChannel, obj) end
end

function protocols.send.arp_request(info, ip)
    expect.field(info, "device", "table")
    expect(2, ip, "string")
    hardware.call(info.process or KERNEL, info.device, "transmit", phoenixNetworkingChannel, phoenixNetworkingChannel, {
        PhoenixNetworking = true,
        type = "arp",
        reply = false,
        source = os.computerID(),
        sourceIP = ipconfig[info.device.uuid] and numberToIP(ipconfig[info.device.uuid].ip),
        destinationIP = ip
    })
end

function protocols.send.arp_reply(info, destination, destIP)
    expect.field(info, "device", "table")
    expect(2, destination, "number")
    expect(3, destIP, "string", "nil")
    hardware.call(info.process or KERNEL, info.device, "transmit", phoenixNetworkingChannel, phoenixNetworkingChannel, {
        PhoenixNetworking = true,
        type = "arp",
        reply = true,
        source = os.computerID(),
        sourceIP = numberToIP(ipconfig[info.device.uuid].ip),
        destination = destination,
        destinationIP = destIP
    })
end

function protocols.send.internet(info, destination, message, ttl, callback)
    expect(2, destination, "number")
    local msg = {PhoenixNetworking = true, type = "internet", hopsLeft = ttl or 15, payload = message, destination = numberToIP(destination)}
    local id = randomString(32)
    msg.messageID = id
    --usedMessageIDs[id] = os.epoch "utc"
    local v
    for i = routes.maxn, 0, -1 do if routes[i] then
        for _, rt in ipairs(routes[i]) do
            if bit32.band(rt.source, rt.sourceNetmask) == bit32.band(destination, rt.sourceNetmask) and
               (not v or maskToPrefix(rt.sourceNetmask) > maskToPrefix(v.sourceNetmask)) then v = rt end
        end
    end end
    if not v then
        local res = protocols.recv.control({}, {
            PhoenixNetworking = true,
            messageType = "unreachable",
            error = "No route to host",
            payload = msg
        })
        if callback then callback() end
        return res
    end
    if v.action == "unicast" and ipconfig[v.device.uuid] and ipconfig[v.device.uuid].up then
        info.device = v.device
        msg.source = numberToIP(ipconfig[v.device.uuid].ip)
        if arptable[v.device.uuid] and arptable[v.device.uuid][v.destination] then
            local res = protocols.send.link(info, arptable[v.device.uuid][v.destination], msg)
            if callback then callback() end
            return res
        end
        local sent = false
        local timer
        local function arp_reply(_, ip, dest)
            if not sent and ipToNumber(ip) == v.destination then
                sent = true
                protocols.send.link(info, dest, msg)
                if callback then callback() end
            end
            if sent then for i, f in ipairs(waiting.arp) do if f == arp_reply then table.remove(waiting.arp, i) break end end end
        end
        local function timer_func(ev)
            if ev[2] == timer then
                if not sent then
                    protocols.recv.control({}, {
                        PhoenixNetworking = true,
                        messageType = "unreachable",
                        error = "No route to host",
                        payload = msg
                    })
                    if callback then callback() end
                end
                sent = true
                for i, w in ipairs(eventHooks.timer) do if w == timer_func then table.remove(eventHooks.timer, i) break end end
                for i, f in ipairs(waiting.arp) do if f == arp_reply then table.remove(waiting.arp, i) break end end
            end
        end
        waiting.arp[#waiting.arp+1] = arp_reply
        protocols.send.arp_request(info, numberToIP(v.destination))
        eventHooks.timer = eventHooks.timer or {}
        eventHooks.timer[#eventHooks.timer+1] = timer_func
        timer = os.startTimer(2)
        return
    elseif v.action == "broadcast" and ipconfig[v.device.uuid] and ipconfig[v.device.uuid].up then
        info.device = v.device
        msg.source = numberToIP(ipconfig[v.device.uuid].ip)
        local res = protocols.send.link(info, nil, msg)
        if callback then callback() end
        return res
    elseif v.action == "local" and ipconfig[v.device.uuid] and ipconfig[v.device.uuid].up then
        info.device = v.device
        msg.source = numberToIP(ipconfig[v.device.uuid].ip)
        if arptable[v.device.uuid] and arptable[v.device.uuid][destination] then
            local res = protocols.send.link(info, arptable[v.device.uuid][destination], msg)
            if callback then callback() end
            return res
        end
        local sent = false
        local timer
        local function arp_reply(_, ip, dest)
            if not sent and ipToNumber(ip) == destination then
                sent = true
                protocols.send.link(info, dest, msg)
                if callback then callback() end
            end
            if sent then for i, f in ipairs(waiting.arp) do if f == arp_reply then table.remove(waiting.arp, i) break end end end
        end
        local function timer_func(ev)
            if ev[2] == timer then
                if not sent then
                    protocols.recv.control({}, {
                        PhoenixNetworking = true,
                        messageType = "unreachable",
                        error = "No route to host",
                        payload = msg
                    })
                    if callback then callback() end
                end
                sent = true
                for i, w in ipairs(eventHooks.timer) do if w == timer_func then table.remove(eventHooks.timer, i) break end end
            end
        end
        waiting.arp[#waiting.arp+1] = arp_reply
        protocols.send.arp_request(info, numberToIP(destination))
        eventHooks.timer = eventHooks.timer or {}
        eventHooks.timer[#eventHooks.timer+1] = timer_func
        timer = os.startTimer(2)
        return
    elseif v.action == "unreachable" then
        local res = protocols.recv.control({}, {
            PhoenixNetworking = true,
            messageType = "unreachable",
            error = "Destination unreachable",
            payload = msg
        })
        if callback then callback() end
        return res
    elseif v.action == "prohibit" then
        local res = protocols.recv.control({}, {
            PhoenixNetworking = true,
            messageType = "unreachable",
            error = "Prohibited",
            payload = msg
        })
        if callback then callback() end
        return res
    elseif v.action == "blackhole" then
        if callback then callback() end
        return
    end
end

function protocols.send.control(info, destination, type, err, packet, ttl)
    expect(3, type, "string")
    expect(4, err, "string", "nil")
    return protocols.send.internet(info, destination, {
        PhoenixNetworking = true,
        type = "control",
        messageType = type,
        error = err,
        payload = packet
    }, ttl)
end

protocols.send.socket = {}

local socketUpdate

function protocols.send.socket.connect(info, ip, port, socket)
    for i = 1, 16384 do
        local p = math.random(49152, 65535)
        if not openSockets[p] or not openSockets[p][port] then socket.localPort = p break end
    end
    if not socket.localPort then error("Too many open sockets") end
    socket.id = nextHandleID
    nextHandleID = nextHandleID + 1
    socket.ip = ip
    socket.port = port
    socket.sendSeq = math.floor(math.random()*0x10000000000)
    socket.sendSeqNext = socket.sendSeq + 2
    socket.sendSeqMax = socket.sendSeq + 256
    socket.status = "syn-sent"
    info.outPort = port
    info.inPort = socket.localPort
    networkListeners[socket] = function(p)
        if p.type == "control" and p.payload.destination == numberToIP(ip) then
            socket.status = "error"
            socket.error = p.error
            return true
        end
        return false
    end
    protocols.send.internet(info, ip, {
        PhoenixNetworking = true,
        type = "socket",
        sequence = socket.sendSeqNext - 1,
        windowSize = 256,
        synchronize = true
    }, nil, function()
        socket.devices = {[info.device.uuid] = true}
        socket.nextUpdate = os.epoch "utc" + 5000
        socket.process = info.process
        socket.retryCount = 0
        openSockets[socket.localPort] = openSockets[socket.localPort] or {}
        openSockets[socket.localPort][port] = socket
        openSocketIDs[socket.id] = socket
    end)
end

function protocols.send.socket.data(info, message, socket)
    info.outPort = socket.port
    info.inPort = socket.localPort
    message.PhoenixNetworking = true
    message.type = "socket"
    if not message.sequence then
        message.sequence = socket.sendSeqNext
        socket.sendSeqNext = socket.sendSeqNext + 1
    end
    message.acknowledgement = message.acknowledgement or (socket.recvSeq - 1)
    socket.nextAck = nil
    if not message.final then message.windowSize = 256 end -- TODO: adjustable?
    return protocols.send.internet(info, socket.ip, message)
end

function protocols.send.socket.ack(info, num, socket)
    --if socket.lastAck and os.epoch "utc" - socket.lastAck < 50 then return end
    socket.lastAck = os.epoch "utc"
    return protocols.send.socket.data(info, {acknowledgement = num}, socket)
end

function protocols.send.socket.reset(info, ip, port, seq, ack, inPort)
    info.outPort = port
    info.inPort = inPort or port
    return protocols.send.internet(info, ip, {
        PhoenixNetworking = true,
        type = "socket",
        sequence = seq,
        acknowledgement = ack,
        reset = true
    })
end

function protocols.send.datagram(info, ip, sourcePort, port, message)
    info.outPort = port
    info.inPort = sourcePort or math.random(49152, 65535)
    return protocols.send.internet(info, ip, {
        PhoenixNetworking = true,
        type = "datagram",
        message = message
    })
end

local function socket_read(socket, mode, ...)
    mode = mode or "*l"
    if type(mode) ~= "string" and type(mode) ~= "number" then error("bad argument (expected string or number, got " .. type(mode) .. ")", 2) end
    if socket.buffer == "" then return nil end
    mode = mode:gsub("^%*", "")
    if mode == "a" then
        local str = socket.buffer
        socket.buffer = ""
        return str
    elseif mode == "l" then
        local str, pos = socket.buffer:match "^([^\n]*)\n?()"
        if str then
            socket.buffer = socket.buffer:sub(pos)
            if select("#", ...) > 0 then return str, socket_read(socket, ...)
            else return str end
        else return nil end
    elseif mode == "L" then
        local str, pos = socket.buffer:match "^([^\n]*\n?)()"
        if str then
            socket.buffer = socket.buffer:sub(pos)
            if select("#", ...) > 0 then return str, socket_read(socket, ...)
            else return str end
        else return nil end
    elseif mode == "n" then
        local str, pos = socket.buffer:match "(%d+)()"
        if str then
            socket.buffer = socket.buffer:sub(pos)
            if select("#", ...) > 0 then return tonumber(str), socket_read(socket, ...)
            else return tonumber(str) end
        else return nil end
    elseif type(mode) == "number" then
        local str = socket.buffer:sub(1, mode)
        socket.buffer = socket.buffer:sub(mode + 1)
        if select("#", ...) > 0 then return str, socket_read(socket, ...)
        else return str end
    else error("bad argument (invalid mode '" .. mode .. "')", 2) end
end

local function socket_write(socket, data, ...)
    data = tostring(data)
    socket.outQueue[socket.sendSeqNext] = data
    protocols.send.socket.data({}, {payload = data}, socket)
    if select("#", ...) > 0 then return socket_write(socket, ...) end
end

function syscalls.__socketcall(process, thread, id, method, ...)
    local socket = openSocketIDs[id]
    if not socket then error("No such socket") end
    local realProcess = process
    while process ~= socket.process do
        if process == nil then error("No such socket") end
        process = processes[process.parent or -1]
    end
    if method == "close" then
        socket.sendSeqMax = socket.sendSeqNext
        protocols.send.socket.data({}, {final = true}, socket)
        socket.status = "fin-wait"
    elseif method == "read" then return socket_read(socket, ...)
    elseif method == "write" then return socket_write(socket, ...)
    elseif method == "transfer" then socket.process = realProcess
    else error("No such method") end
end

function syscalls.__datagramcall(process, thread, port, id, method, ...)
    local listener = pdpListeners[port]
    local realProcess = process
    while process ~= listener.process do
        if process == nil then error("No such socket") end
        process = processes[process.parent or -1]
    end
    local socket
    if listener.listen then
        for _, v in pairs(listener.open) do if v.id == id then socket = v break end end
        if not socket then error("No socket found for ID") end
    else socket = listener end
    if method == "close" then
        -- TODO: close channels properly
        if listener.listen then
            for k, v in pairs(listener.open) do if v.id == id then listener.open[k] = nil break end end
        else
            pdpListeners[port] = nil
        end
    elseif method == "read" then
        local v = socket[1]
        socket.n = socket.n - 1
        for i = 1, socket.n do socket[i] = socket[i+1] end
        return v
    elseif method == "write" then return protocols.send.datagram({process = process}, socket.sourceIP, port, socket.port, ...)
    elseif method == "transfer" then listener.process = realProcess
    else error("No such method") end
end

local do_syscall = do_syscall

local function makePSPHandle(socket)
    local obj = setmetatable({id = socket.id}, {__name = "socket"})
    function obj:localIP()
        return socket.localIP
    end
    function obj:status()
        if socket.status == "listening" or socket.status == "syn-sent" or socket.status == "syn-received" then return "connecting"
        elseif socket.status == "connected" or socket.buffer ~= "" then return "open"
        elseif socket.status == "error" then return "error", socket.error
        else return "closed" end
    end
    function obj:read(mode, ...)
        if socket.status ~= "connected" and socket.status ~= "close-wait" and socket.status ~= "fin-wait" and socket.status ~= "closed" then error("attempt to read from a " .. socket.status .. " handle", 2) end
        return do_syscall("__socketcall", socket.id, "read", mode, ...)
    end
    function obj:write(data, ...)
        if socket.status ~= "connected" then error("attempt to write to a " .. socket.status .. " handle", 2) end
        return do_syscall("__socketcall", socket.id, "write", data, ...)
    end
    function obj:close()
        if socket.status == "closing" or socket.status == "fin-wait" or socket.status == "closed" then return end
        if not (socket.status == "listening" or socket.status == "syn-sent" or socket.status == "syn-received" or socket.status == "connected") then error("attempt to close a " .. socket.status .. " handle", 2) end
        return do_syscall("__socketcall", socket.id, "close")
    end
    function obj:transfer()
        return do_syscall("__socketcall", socket.id, "transfer")
    end
    return obj
end

local function makePDPHandle(socket, port)
    local obj = setmetatable({id = socket.id}, {__name = "socket"})
    function obj:localIP()
        return numberToIP(socket.localIP)
    end
    function obj:status()
        return "open"
    end
    function obj:read(mode, ...)
        return do_syscall("__datagramcall", port, socket.id, "read", mode, ...)
    end
    function obj:write(data, ...)
        return do_syscall("__datagramcall", port, socket.id, "write", data, ...)
    end
    function obj:close()
        return do_syscall("__datagramcall", port, socket.id, "close")
    end
    function obj:transfer()
        return do_syscall("__datagramcall", port, socket.id, "transfer")
    end
    return obj
end

-- prefilled entries in info: device

function protocols.recv.link(info, message)
    expect.field(message, "source", "number")
    expect.field(message, "destination", "number")
    expect.field(message, "payload", "table")
    expect.field(message, "outPort", "number")
    expect.field(message, "inPort", "number")
    syslog.debug("Received link message from", message.source, "to", message.destination)
    if message.destination ~= os.computerID() then return end
    info.sourceID = message.source
    info.channel = message.outPort
    info.replyChannel = message.inPort
    assert(message.payload.PhoenixNetworking)
    expect.field(message.payload, "type", "string")
    if not protocols.recv[message.payload.type] then error("Unknown protocol '" .. message.payload.type .. "'") end
    return protocols.recv[message.payload.type](info, message.payload)
end

function protocols.recv.arp(info, message)
    expect.field(message, "source", "number")
    expect.field(message, "reply", "boolean")
    syslog.debug("Received arp message from", message.source)
    if not message.reply and message.destinationIP and message.sourceIP ~= message.destinationIP then
        local ip = ipToNumber(expect.field(message, "destinationIP", "string"))
        if ipconfig[info.device.uuid] and ipconfig[info.device.uuid].ip == ip then
            protocols.send.arp_reply(info, message.source, message.sourceIP)
        end
    end
    if message.sourceIP then
        local ip = ipToNumber(expect.field(message, "sourceIP", "string"))
        arptable[info.device.uuid] = arptable[info.device.uuid] or {}
        arptable[info.device.uuid][ip] = message.source
        -- copy the table so we don't skip any if the function modifies the table
        local tmp = {}
        for i, v in ipairs(waiting.arp) do tmp[i] = v end
        for _, v in ipairs(tmp) do v(v, message.sourceIP, message.source) end
    end
end

function protocols.recv.internet(info, message)
    info.sourceIP = ipToNumber(expect.field(message, "source", "string"))
    local dest = ipToNumber(expect.field(message, "destination", "string"))
    info.localIP = dest
    syslog.debug("Received internet message from", message.source, "to", message.destination)
    expect.field(message, "payload", "table")
    if usedMessageIDs[expect.field(message, "messageID", "number", "string")] then return end
    usedMessageIDs[message.messageID] = os.epoch "utc"
    if not ipconfig[info.device.uuid] or ipconfig[info.device.uuid].ip ~= dest then
        local retval = false
        for _, v in pairs(networkListeners) do retval = v(message, info) or retval end
        return retval
    end
    if not arptable[info.device.uuid] or not arptable[info.device.uuid][info.sourceIP] then
        arptable[info.device.uuid] = arptable[info.device.uuid] or {}
        arptable[info.device.uuid][info.sourceIP] = info.sourceID
        -- copy the table so we don't skip any if the function modifies the table
        local tmp = {}
        for i, v in ipairs(waiting.arp) do tmp[i] = v end
        for _, v in ipairs(tmp) do v(v, info.sourceIP, info.sourceID) end
    end
    info.ipPacket = message
    assert(message.payload.PhoenixNetworking)
    expect.field(message.payload, "type", "string")
    if not protocols.recv[message.payload.type] then error("Unknown protocol '" .. message.payload.type .. "'") end
    return protocols.recv[message.payload.type](info, message.payload)
end

function protocols.recv.control(info, message)
    expect.field(message, "messageType", "string")
    syslog.debug("Received control message", message.messageType)
    local retval = false
    if message.messageType == "ping" then protocols.send.control({device = info.device}, info.sourceIP, "pong", nil, info.ipPacket)
    else for _, v in pairs(networkListeners) do retval = v{type = "control", messageType = message.messageType, error = message.error, payload = message.payload, sender = numberToIP(info.sourceIP)} or retval end end
    return retval
end

local function safe_serialize(v)
    local ok, res = pcall(serialize, v)
    if ok then return res else return tostring(v) end
end

function protocols.recv.socket(info, message)
    expect.field(message, "sequence", "number")
    expect.field(message, "acknowledgement", "number", "nil")
    expect.field(message, "windowSize", "number", "nil")
    expect.field(message, "payload", "string", "nil")
    if info.channel == 0 or info.replyChannel == 0 then syslog.debug("Received socket event on channel 0; discarding.") return end
    local socket = (openSockets[info.channel] or {})[info.replyChannel] or (openSockets[info.channel] or {}).listen
    if not socket then
        if message.acknowledgement then protocols.send.socket.reset(info, info.sourceIP, info.replyChannel, message.acknowledgement, nil, info.channel)
        else protocols.send.socket.reset(info, info.sourceIP, info.replyChannel, 0, message.sequence + (message.windowSize or 0), info.channel) end
        return
    end
    do
        local s = {}
        for k, v in pairs(socket) do if k ~= "process" then s[k] = v end end
        syslog.debug("Received socket message:", safe_serialize(message), "\nSocket info:", safe_serialize(s))
    end
    if socket.status == "listening" then
        if message.reset then return end
        if message.acknowledgement then
            protocols.send.socket.reset(info, info.sourceIP, info.replyChannel, message.acknowledgement, nil, info.channel)
            return
        end
        if not message.synchronize then return end
        socket.ip = info.sourceIP
        socket.localIP = numberToIP(info.localIP)
        socket.port = info.replyChannel
        socket.recvSeq = message.sequence + 1
        socket.recvSeqMax = socket.recvSeq + (message.windowSize or 0)
        socket.sendSeq = math.floor(math.random()*0x10000000000)
        socket.sendSeqNext = socket.sendSeq + 2
        socket.sendSeqMax = socket.sendSeq + (message.windowSize or 0)
        socket.status = "syn-received"
        socket.nextUpdate = os.epoch "utc" + 5000
        socket.retryCount = 0
        openSockets[info.channel][info.replyChannel] = socket
        openSockets[info.channel].listen = nil
        protocols.send.internet({inPort = info.channel, outPort = info.replyChannel}, socket.ip, {
            PhoenixNetworking = true,
            type = "socket",
            sequence = socket.sendSeqNext - 1,
            acknowledgement = socket.recvSeq,
            windowSize = 256,
            synchronize = true
        })
    elseif socket.status == "syn-sent" then
        if message.reset then
            socket.status = "error"
            socket.error = "Connection refused"
            openSockets[info.channel][info.replyChannel] = nil
            if socket.process then socket.process.eventQueue[#socket.process.eventQueue+1] = {"handle_status_change", {id = socket.id, status = "error"}} end
            return true
        end
        if not message.synchronize or not message.acknowledgement or message.acknowledgement < socket.sendSeq then
            protocols.send.socket.reset(info, info.sourceIP, info.replyChannel, message.acknowledgement, nil, info.channel)
            socket.status = "error"
            socket.error = "Connection refused"
            openSockets[info.channel][info.replyChannel] = nil
            if socket.process then socket.process.eventQueue[#socket.process.eventQueue+1] = {"handle_status_change", {id = socket.id, status = "error"}} end
            return true
        end
        socket.localIP = numberToIP(info.localIP)
        socket.status = "connected"
        socket.sendSeq = message.acknowledgement
        socket.sendSeqMax = socket.sendSeq + 256
        socket.recvSeq = message.sequence + 1
        socket.recvSeqMax = socket.recvSeq + (message.windowSize or 0)
        socket.outQueue = {}
        socket.nextUpdate = os.epoch "utc" + 2000
        protocols.send.socket.ack({}, socket.recvSeq, socket)
        if socket.process then socket.process.eventQueue[#socket.process.eventQueue+1] = {"handle_status_change", {id = socket.id, status = "connected"}} end
        return true
    else
        if message.sequence < socket.recvSeq --[[or message.sequence > socket.recvSeqMax]] then
            syslog.debug("Sequence out of range")
            if message.reset then
                socket.status = "error"
                socket.error = "Connection reset by peer"
                openSockets[info.channel][info.replyChannel] = nil
                if socket.process then socket.process.eventQueue[#socket.process.eventQueue+1] = {"handle_status_change", {id = socket.id, status = "error"}} end
                return true
            else
                protocols.send.socket.ack({}, socket.recvSeq, socket)
                return
            end
        end
        if message.reset then
            syslog.debug("Received reset")
            if socket.status == "syn-received" then
                socket.status = "listening"
                return
            elseif socket.status == "connected" or socket.status == "fin-wait" then
                socket.status = "error"
                socket.error = "Connection reset by peer"
                openSockets[info.channel][info.replyChannel] = nil
                if socket.process then socket.process.eventQueue[#socket.process.eventQueue+1] = {"handle_status_change", {id = socket.id, status = "error"}} end
                return true
            else
                socket.status = "closed"
                openSockets[info.channel][info.replyChannel] = nil
                if socket.process then socket.process.eventQueue[#socket.process.eventQueue+1] = {"handle_status_change", {id = socket.id, status = "closed"}} end
                return true
            end
        end
        if message.synchronize then
            protocols.send.socket.reset(info, info.sourceIP, info.replyChannel, message.acknowledgement, nil, info.channel)
            socket.status = "error"
            socket.error = "Connection reset by host"
            openSockets[info.channel][info.replyChannel] = nil
            if socket.process then socket.process.eventQueue[#socket.process.eventQueue+1] = {"handle_status_change", {id = socket.id, status = "error"}} end
            return true
        end
        local retval
        if not message.acknowledgement then syslog.debug("No acknowledgement") return end
        if socket.status == "syn-received" then
            if message.acknowledgement >= socket.sendSeq and message.acknowledgement  socket.sendSeq and message.acknowledgement = socket.nextUpdate then
            if socket.status == "syn-sent" then
                socket.status = "error"
                socket.error = "Connection timed out (syn-sent)"
                openSockets[port][replyPort] = nil
                if socket.process then socket.process.eventQueue[#socket.process.eventQueue+1] = {"handle_status_change", {id = socket.id, status = "error"}} event = true end
            elseif socket.status == "syn-received" then
                socket.retryCount = socket.retryCount + 1
                if socket.retryCount > 3 then
                    socket.status = "error"
                    socket.error = "Connection timed out (syn-received)"
                    openSockets[port][replyPort] = nil
                    if socket.process then socket.process.eventQueue[#socket.process.eventQueue+1] = {"handle_status_change", {id = socket.id, status = "error"}} event = true end
                else
                    -- TODO: resend syn+ack packet
                    socket.nextUpdate = os.epoch "utc" + 2000
                end
            elseif socket.status == "connected" then
                for i = socket.sendSeq + 1, socket.sendSeqNext - 1 do
                    if socket.outQueue[i] then
                        protocols.send.socket.data({}, {
                            sequence = i,
                            payload = socket.outQueue[i]
                        }, socket)
                    end
                end
                if socket.nextAck then
                    protocols.send.socket.ack({}, socket.recvSeq - 1, socket)
                    socket.nextAck = nil
                end
                socket.nextUpdate = os.epoch "utc" + 2000
            elseif socket.status == "fin-wait" then

            elseif socket.status == "close-wait" then

            end
        elseif socket.status == "time-wait" and (time >= socket.nextUpdate or force) then
            syslog.debug("Time wait finished on port " .. port)
            socket.status = "closed"
            openSockets[port][replyPort] = nil
        end
    end end
    return event
end

function protocols.recv.datagram(info, message)
    if not pdpListeners[info.channel] then
        syslog.debug("Received spurious PDP message on port " .. info.channel .. ", discarding.")
        return
    end
    local listener = pdpListeners[info.channel]
    if listener.listen then
        local socket = listener.open[info.sourceIP]
        if socket then
            socket.n = socket.n + 1
            socket[socket.n] = message.message
            listener.process.eventQueue[#listener.process.eventQueue+1] = {"handle_data_ready", {id = socket.id}}
        else
            listener.open[info.sourceIP] = {
                id = nextHandleID,
                sourceIP = info.sourceIP,
                localIP = info.localIP,
                port = info.replyChannel,
                n = 1,
                message.message
            }
            nextHandleID = nextHandleID + 1
            listener.process.eventQueue[#listener.process.eventQueue+1] = {"network_request", {uri = listener.uri, ip = numberToIP(info.sourceIP), handle = makePDPHandle(listener.open[info.sourceIP], info.channel)}}
        end
    else
        listener.n = listener.n + 1
        listener[listener.n] = message.message
        listener.process.eventQueue[#listener.process.eventQueue+1] = {"handle_data_ready", {id = listener.id}}
    end
    return true
end

eventHooks.modem_message = eventHooks.modem_message or {}
eventHooks.modem_message[#eventHooks.modem_message+1] = function(ev)
    if type(ev[5]) == "table" and ev[5].PhoenixNetworking and type(ev[5].type) == "string" and protocols.recv[ev[5].type] then
        local node = getNodeById(ev[2]) or hardware.get(ev[2])
        if not node then
            syslog.log({level = "notice", module = "Network"}, "Received network event for device ID " .. ev[2] .. ", but no device node was found; ignoring")
            return
        end
        if not ipconfig[node.uuid] or not ipconfig[node.uuid].up then return end
        syslog.debug(ev[2], safe_serialize(ev[5]))
        local ok, err = pcall(protocols.recv[ev[5].type], {device = node}, ev[5])
        if not ok then syslog.log({level = "debug", module = "Network"}, "Network event errored while processing:", err)
        else return err end
    end
end

local socketTimer = os.startTimer(1)
eventHooks.timer = eventHooks.timer or {}
eventHooks.timer[#eventHooks.timer+1] = function(ev)
    if ev[2] == socketTimer then
        socketTimer = os.startTimer(1)
        --syslog.debug("Triggering socket timer")
        return socketUpdate()
    end
end

local function pspHandler(process, options)
    -- TODO: implement device filtering
    local uri = parseURI(options.url)
    if not uri.port then error("No port specified") end
    local ip = ipToNumber(uri.host)
    local port = uri.port
    local socket = {process = process, buffer = ""}
    protocols.send.socket.connect({process = process}, ip, port, socket)
    return makePSPHandle(socket)
end

local function pdpHandler(process, options)
    local uri = parseURI(options.url)
    if not uri.port then error("No port specified") end
    local localPort
    for i = 1, 16384 do
        local p = math.random(49152, 65535)
        if not pdpListeners[p] then localPort = p break end
    end
    if not localPort then error("Too many open sockets") end
    local ip = ipToNumber(uri.host)
    local port = uri.port
    local socket = {
        process = process,
        id = nextHandleID,
        sourceIP = ip,
        port = port,
        n = 0
    }
    nextHandleID = nextHandleID + 1
    pdpListeners[localPort] = socket
    return makePDPHandle(socket, localPort)
end

--#endregion

--#region HTTP/Rednet handlers

local httpRequests = {}
local rednetOpenCount = {}
local rednetHandles = {}
local rednetChannel = os.computerID() % 65500
local receivedRednetMessages = {}

local httpHandler, wsHandler
if http then
    eventHooks.http_success = eventHooks.http_success or {}
    eventHooks.http_success[#eventHooks.http_success+1] = function(ev)
        local info = httpRequests[ev[2]]
        if info then
            info.handle, info.status = ev[3], "open"
            info.process.eventQueue[#info.process.eventQueue+1] = {"handle_status_change", {id = info.id, status = "open"}}
            httpRequests[ev[2]] = nil
            return true
        else syslog.log({level = "notice"}, "Received HTTP response for " .. ev[2] .. " but nobody requested it; ignoring.") end
    end

    eventHooks.http_failure = eventHooks.http_failure or {}
    eventHooks.http_failure[#eventHooks.http_failure+1] = function(ev)
        local info = httpRequests[ev[2]]
        if info then
            if ev[4] then info.handle, info.status = ev[4], "open"
            else info.status, info.error = "error", ev[3] end
            info.process.eventQueue[#info.process.eventQueue+1] = {"handle_status_change", {id = info.id, status = info.status}}
            httpRequests[ev[2]] = nil
            return true
        else syslog.log({level = "notice"}, "Received HTTP response for " .. ev[2] .. " but nobody requested it; ignoring.") end
    end

    eventHooks.websocket_success = eventHooks.websocket_success or {}
    eventHooks.websocket_success[#eventHooks.websocket_success+1] = function(ev)
        local info = httpRequests[ev[2]]
        if info then
            info.handle, info.status = ev[3], "open"
            info.process.eventQueue[#info.process.eventQueue+1] = {"handle_status_change", {id = info.id, status = "open"}}
            return true
        else syslog.log({level = "notice"}, "Received WebSocket response for " .. ev[2] .. " but nobody requested it; ignoring.") end
    end

    eventHooks.websocket_failure = eventHooks.websocket_failure or {}
    eventHooks.websocket_failure[#eventHooks.websocket_failure+1] = function(ev)
        local info = httpRequests[ev[2]]
        if info then
            info.status, info.error = "error", ev[3]
            info.process.eventQueue[#info.process.eventQueue+1] = {"handle_status_change", {id = info.id, status = info.status}}
            return true
        else syslog.log({level = "notice"}, "Received WebSocket response for " .. ev[2] .. " but nobody requested it; ignoring.") end
    end

    eventHooks.websocket_message = eventHooks.websocket_message or {}
    eventHooks.websocket_message[#eventHooks.websocket_message+1] = function(ev)
        local info = httpRequests[ev[2]]
        if info then
            -- TODO: decide whether to transcode if the requested encoding doesn't match the message
            info.buffer = info.buffer .. ev[3]
            info.process.eventQueue[#info.process.eventQueue+1] = {"handle_data_ready", {id = info.id}}
            return true
        else syslog.log({level = "notice"}, "Received WebSocket message for " .. ev[2] .. " but nobody requested it; ignoring.") end
    end

    eventHooks.websocket_closed = eventHooks.websocket_closed or {}
    eventHooks.websocket_closed[#eventHooks.websocket_closed+1] = function(ev)
        local info = httpRequests[ev[2]]
        if info then
            info.status = "closed"
            info.process.eventQueue[#info.process.eventQueue+1] = {"handle_status_change", {id = info.id, status = info.status}}
            httpRequests[ev[2]] = nil
            return true
        else syslog.log({level = "notice"}, "Received WebSocket message for " .. ev[2] .. " but it's not open; ignoring.") end
    end

    eventHooks.modem_message = eventHooks.modem_message or {}
    eventHooks.modem_message[#eventHooks.modem_message+1] = function(ev)
        local retval = false
        if rednetOpenCount[ev[2]] and (ev[3] == rednetChannel or ev[3] == 65535) and
        type(ev[5]) == "table" and type(ev[5].nMessageID) == "number" and
        ev[5].nMessageID == ev[5].nMessageID and not receivedRednetMessages[ev[5].nMessageID] and
        ((ev[5].nRecipient and ev[5].nRecipient == os.computerID()) or ev[3] == 65535) then
            if rednetHandles[ev[5].nSender] then
                for _, v in ipairs(rednetHandles[ev[5].nSender]) do
                    if not v.protocol or v.protocol == ev[5].sProtocol then
                        v.buffer[#v.buffer+1] = deepcopy(ev[5].message)
                        receivedRednetMessages[ev[5].nMessageID] = os.clock() + 9.5
                        v.process.eventQueue[#v.process.eventQueue+1] = {"handle_data_ready", {id = v.id}}
                        retval = true
                    end
                end
            end
            if rednetHandles[0xFFFFFFFF] then
                for _, v in ipairs(rednetHandles[0xFFFFFFFF]) do
                    if not v.protocol or v.protocol == ev[5].sProtocol then
                        v.buffer[#v.buffer+1] = deepcopy(ev[5].message)
                        receivedRednetMessages[ev[5].nMessageID] = os.clock() + 9.5
                        v.process.eventQueue[#v.process.eventQueue+1] = {"handle_data_ready", {id = v.id}}
                        retval = true
                    end
                end
            end
            for k, v in pairs(receivedRednetMessages) do if v < os.clock() then receivedRednetMessages[k] = nil end end
        end
        return retval
    end

    -- TODO: Fix handle:read() not being equivalent to handle:read("*l")

    local request = http.request
    function httpHandler(process, options)
        expect.field(options, "encoding", "string", "nil")
        expect.field(options, "headers", "table", "nil")
        expect.field(options, "method", "string", "nil")
        expect.field(options, "redirect", "boolean", "nil")
        local info = {status = "ready", process = process, id = nextHandleID}
        local obj = setmetatable({id = nextHandleID}, {__name = "socket"})
        nextHandleID = nextHandleID + 1
        function obj:status()
            return info.status, info.error
        end
        function obj:read(mode, ...)
            if info.status ~= "open" then error("attempt to read from a " .. info.status .. " handle", 2) end
            mode = mode or "*l"
            if type(mode) ~= "string" and type(mode) ~= "number" then error("bad argument (expected string or number, got " .. type(mode) .. ")", 2) end
            mode = mode:gsub("^%*", "")
            if mode == "a" then
                if select("#", ...) > 0 then return info.handle.readAll(), self:read(...)
                else return info.handle.readAll() end
            elseif mode == "l" then
                if select("#", ...) > 0 then return info.handle.readLine(false), self:read(...)
                else return info.handle.readLine(false) end
            elseif mode == "L" then
                if select("#", ...) > 0 then return info.handle.readLine(true), self:read(...)
                else return info.handle.readLine(true) end
            elseif mode == "n" then
                local str
                repeat
                    str = info.handle.read(1)
                    if not str then return nil end
                until tonumber(str)
                while true do
                    local c = info.handle.read(1)
                    if not c or not c:match "%d" then break end
                    str = str .. c
                end
                if select("#", ...) > 0 then return tonumber(str), self:read(...)
                else return tonumber(str) end
            elseif type(mode) == "number" then
                if select("#", ...) > 0 then return info.handle.read(mode), self:read(...)
                else return info.handle.read(mode) end
            else error("bad argument (invalid mode '" .. mode .. "')", 2) end
        end
        function obj:write(...)
            if info.status ~= "ready" then error("attempt to write to a " .. info.status .. " handle", 2) end
            local data
            if select("#", ...) > 0 then
                data = ""
                for _, v in ipairs{...} do data = data .. tostring(v) end
            end
            local url = options.url .. "#" .. info.id
            local ok, err = request{url = url, body = data, headers = options.headers, binary = options.encoding == "binary" or options.encoding == nil, method = options.method, redirect = options.redirect}
            if ok then
                httpRequests[url] = info
                info.status = "connecting"
            else info.status, info.error = "error", err end
        end
        function obj:close()
            if info.status ~= "open" then error("attempt to close a " .. info.status .. " handle", 2) end
            info.handle.close()
            info.status = "closed"
        end
        function obj:responseHeaders()
            if info.status ~= "open" then error("attempt to read from a " .. info.status .. " handle", 2) end
            return info.handle.getResponseHeaders()
        end
        function obj:responseCode()
            if info.status ~= "open" then error("attempt to read from a " .. info.status .. " handle", 2) end
            return info.handle.getResponseCode()
        end
        return obj
    end

    -- TODO: decide whether messages should be split, binary, etc.
    function wsHandler(process, options)
        expect.field(options, "encoding", "string", "nil")
        expect.field(options, "headers", "table", "nil")
        local info = {process = process, id = nextHandleID, buffer = ""}
        local obj = setmetatable({id = nextHandleID}, {__name = "socket"})
        nextHandleID = nextHandleID + 1
        function obj:status()
            return info.status, info.error
        end
        function obj:read(mode, ...)
            if info.status ~= "open" then error("attempt to read from a " .. info.status .. " handle", 2) end
            mode = mode or "*l"
            if type(mode) ~= "string" and type(mode) ~= "number" then error("bad argument (expected string or number, got " .. type(mode) .. ")", 2) end
            if info.buffer == "" then return nil end
            mode = mode:gsub("^%*", "")
            if mode == "a" then
                local str = info.buffer
                info.buffer = ""
                return str
            elseif mode == "l" then
                local str, pos = info.buffer:match "^([^\n]*)\n?()"
                if str then
                    info.buffer = info.buffer:sub(pos)
                    if select("#", ...) > 0 then return str, self:read(...)
                    else return str end
                else return nil end
            elseif mode == "L" then
                local str, pos = info.buffer:match "^([^\n]*\n?)()"
                if str then
                    info.buffer = info.buffer:sub(pos)
                    if select("#", ...) > 0 then return str, self:read(...)
                    else return str end
                else return nil end
            elseif mode == "n" then
                local str, pos = info.buffer:match "(%d+)()"
                if str then
                    info.buffer = info.buffer:sub(pos)
                    if select("#", ...) > 0 then return tonumber(str), self:read(...)
                    else return tonumber(str) end
                else return nil end
            elseif type(mode) == "number" then
                local str = info.buffer:sub(1, mode)
                info.buffer = info.buffer:sub(mode + 1)
                if select("#", ...) > 0 then return str, self:read(...)
                else return str end
            else error("bad argument (invalid mode '" .. mode .. "')", 2) end
        end
        function obj:write(data, ...)
            if info.status ~= "open" then error("attempt to write to a " .. info.status .. " handle", 2) end
            info.handle.send(tostring(data), options.encoding == "binary")
            if select("#", ...) > 0 then return self:write(...) end
        end
        function obj:close()
            if info.status ~= "open" then error("attempt to close a " .. info.status .. " handle", 2) end
            info.handle.close()
            info.status = "closed"
        end
        local url = options.url .. "#" .. info.id
        local ok, err = http.websocket(url, options.headers)
        if ok then
            httpRequests[url] = info
            info.status = "connecting"
        else return nil, err end
        return obj
    end
end

-- TODO: consider adding hostname lookup
local function rednetHandler(process, options)
    expect.field(options, "device", "string", "nil")
    local modems
    if options.device then modems = {hardware.get(options.device)}
    else modems = {hardware.find("modem")} end
    if #modems == 0 then error("Could not find a modem", 2) end
    for _, v in ipairs(modems) do
        checkModem(v)
        if not rednetOpenCount[v] then
            hardware.call(process, v, "open", rednetChannel)
            hardware.call(process, v, "open", 65535)
            rednetOpenCount[v] = 1
        else rednetOpenCount[v] = rednetOpenCount[v] + 1 end
    end
    local uri = parseURI(options.url)
    if not uri.host then error("Missing host", 2) end
    local id = ipToNumber(uri.host)
    local info = {process = process, id = nextHandleID, buffer = {}, protocol = uri.scheme:match "rednet%+(.+)"}
    local obj = setmetatable({id = nextHandleID}, {__name = "socket"})
    nextHandleID = nextHandleID + 1
    function obj:status()
        return info.closed and "closed" or "open"
    end
    function obj:read(mode, ...)
        if info.closed then error("attempt to read from a " .. info.status .. " handle", 2) end
        mode = mode or "*l"
        if type(mode) ~= "string" and type(mode) ~= "number" then error("bad argument (expected string or number, got " .. type(mode) .. ")", 2) end
        if #info.buffer == 0 then return nil end
        mode = mode:gsub("^%*", "")
        if mode == "a" then
            return table.remove(info.buffer, 1)
        elseif mode == "l" then
            info.buffer[1] = tostring(info.buffer[1])
            local str, pos = info.buffer[1]:match "^([^\n]*)\n?()"
            if str then
                info.buffer[1] = info.buffer[1]:sub(pos)
                if select("#", ...) > 0 then return str, self:read(...)
                else return str end
            else
                table.remove(info.buffer, 1)
                return self:read(mode, ...)
            end
        elseif mode == "L" then
            info.buffer[1] = tostring(info.buffer[1])
            local str, pos = info.buffer[1]:match "^([^\n]*\n?)()"
            if str then
                info.buffer[1] = info.buffer[1]:sub(pos)
                if select("#", ...) > 0 then return str, self:read(...)
                else return str end
            else
                table.remove(info.buffer, 1)
                return self:read(mode, ...)
            end
        elseif mode == "n" then
            info.buffer[1] = tostring(info.buffer[1])
            local str, pos = info.buffer[1]:match "(%d+)()"
            if str then
                info.buffer[1] = info.buffer[1]:sub(pos)
                if select("#", ...) > 0 then return tonumber(str), self:read(...)
                else return tonumber(str) end
            else
                table.remove(info.buffer, 1)
                return self:read(mode, ...)
            end
        elseif type(mode) == "number" then
            local str = ""
            while #str < mode do
                info.buffer[1] = tostring(info.buffer[1])
                str = str .. info.buffer[1]:sub(1, mode - #str)
                info.buffer[1] = info.buffer[1]:sub(mode - #str + 1)
                if info.buffer[1] == "" then table.remove(info.buffer, 1) end
                if #info.buffer == 0 then break end
            end
            if select("#", ...) > 0 then return str, self:read(...)
            else return str end
        else error("bad argument (invalid mode '" .. mode .. "')", 2) end
    end
    function obj:write(data, ...)
        if info.closed then error("attempt to write to a " .. info.status .. " handle", 2) end
        local msgid = math.random(1, 0x7FFFFFFF)
        local msg = {
            nMessageID = msgid,
            nRecipient = id,
            nSender = os.computerID(),
            message = data,
            sProtocol = info.protocol
        }
        if id == os.computerID() then
            for _, v in ipairs(modems) do
                os.queueEvent("modem_message", v.id, rednetChannel, rednetChannel, msg, 0)
            end
        else
            receivedRednetMessages[msgid] = os.clock() + 9.5
            for _, v in ipairs(modems) do
                hardware.call(process, v, "transmit", id == 0xFFFFFFFF and 65535 or id % 65500, rednetChannel, msg)
                hardware.call(process, v, "transmit", 65533, rednetChannel, msg)
            end
        end
        if select("#", ...) > 0 then return self:write(...) end
    end
    function obj:close()
        if info.closed then error("attempt to close a " .. info.status .. " handle", 2) end
        for _, v in ipairs(modems) do
            rednetOpenCount[v] = rednetOpenCount[v] - 1
            if rednetOpenCount[v] == 0 then
                hardware.call(process, v, "close", rednetChannel)
                hardware.call(process, v, "close", 65535)
                rednetOpenCount[v] = nil
            end
        end
        info.status = "closed"
    end
    return obj
end

--#endregion

-- TODO: add event handling for HTTP/WS servers

--- Stores all URI scheme handlers using Lua patterns as keys.
uriSchemes = {
    ["https?"] = httpHandler,
    ["wss?"] = wsHandler,
    ["rednet"] = rednetHandler,
    ["rednet%+%a+"] = rednetHandler,
    ["psp"] = pspHandler,
    ["pdp"] = pdpHandler,
}

function syscalls.connect(process, thread, options)
    if type(options) == "string" then options = {url = options} end
    expect(1, options, "table")
    expect.field(options, "url", "string")
    local uri = parseURI(options.url)
    local obj, err
    for k, v in pairs(uriSchemes) do
        if uri.scheme:match(k) then
            obj, err = v(process, options)
            break
        end
    end
    if not obj and not err then error("Invalid protocol " .. uri.scheme) end
    if obj then for _, v in pairs(obj) do if type(v) == "function" then setfenv(v, process.env) debug.protect(v) end end end
    return obj, err
end

function syscalls.listen(process, thread, uri)
    expect(1, uri, "string")
    local URI = parseURI(uri)
    if http and http.addListener then
        if URI.scheme == "http" then
            http.addListener(URI.port or 80)
            return
        elseif URI.scheme == "ws" then
            http.websocket(URI.port or 80)
            return
        end
    end
    if URI.scheme == "psp" then
        if not URI.port then error("Missing port") end
        if openSockets[URI.port] and openSockets[URI.port].listen then error("Port already open") end
        local ip = ipToNumber(URI.host)
        local devices = {}
        for k, v in pairs(ipconfig) do
            if v.up and (ip == 0 or v.ip == ip) then
                local dev = hardware.get(k)
                devices[dev.uuid] = true
            end
        end
        local socket = {
            localPort = URI.port,
            id = nextHandleID,
            devices = devices,
            status = "listening",
            process = process,
            nextUpdate = math.huge,
            retryCount = 0,
            uri = uri,
            buffer = ""
        }
        nextHandleID = nextHandleID + 1
        openSockets[URI.port] = openSockets[URI.port] or {}
        openSockets[URI.port].listen = socket
        openSocketIDs[socket.id] = socket
        return
    elseif URI.scheme == "pdp" then
        if not URI.port then error("Missing port") end
        if pdpListeners[URI.port] then error("Port already open") end
        pdpListeners[URI.port] = {
            process = process,
            listen = true,
            open = {}
        }
        return
    end
    error("Invalid protocol " .. URI.scheme)
end

function syscalls.unlisten(process, thread, uri)
    expect(1, uri, "string")
    local URI = parseURI(uri)
    if http and http.addListener then
        if URI.scheme == "http" then
            http.removeListener(URI.port or 80)
            return
        elseif URI.scheme == "ws" then
            -- TODO
            return
        end
    end
    if URI.scheme == "psp" then
        if not URI.port then error("Missing port") end
        if not openSockets[URI.port] or not openSockets[URI.port].listen then return end
        if openSockets[URI.port].listen.process ~= process then error("Port open in another process") end
        openSockets[URI.port].listen = nil
        -- TODO: harden this?
        return
    elseif URI.scheme == "pdp" then
        if not URI.port then error("Missing port") end
        if not pdpListeners[URI.port] then return end
        pdpListeners[URI.port] = nil
        return
    end
    error("Invalid protocol " .. URI.scheme)
end

function syscalls.ipconfig(process, thread, device, info)
    if info and process.user ~= "root" then error("Permission denied") end
    expect(1, device, "string")
    expect(2, info, "table", "nil")
    local node = checkModem(hardware.get(device))
    local t = ipconfig[node.uuid]
    if not t then
        if info then
            expect.field(info, "ip", "string", "number")
            expect.field(info, "netmask", "string", "number")
            t = {up = true}
            ipconfig[node.uuid] = t
            hardware.call(KERNEL, node, "open", phoenixNetworkingChannel)
        else return nil end
    end
    if info then
        expect.field(info, "ip", "string", "number", "nil")
        expect.field(info, "netmask", "string", "number", "nil")
        expect.field(info, "up", "boolean", "nil")
        local localRoute, broadcastRoute
        if t.ip then
            for _, v in ipairs(routes[0]) do
                if v.source == bit32.band(t.ip, t.netmask) and v.netmask == t.netmask then localRoute = v
                elseif v.source == bit32.bor(bit32.band(t.ip, t.netmask), bit32.bnot(t.netmask)) and v.netmask == 0xFFFFFFFF then broadcastRoute = v end
            end
        end
        if info.ip then
            if arptable[node.uuid] then arptable[node.uuid][t.ip] = nil end
            if type(info.ip) == "number" then t.ip = bit32.band(info.ip, 0xFFFFFFFF)
            else t.ip = ipToNumber(info.ip) end
            if localRoute then localRoute.source = bit32.band(t.ip, t.netmask) end
            if broadcastRoute then broadcastRoute.source = bit32.bor(bit32.band(t.ip, t.netmask), bit32.bnot(t.netmask)) end
            arptable[node.uuid] = arptable[node.uuid] or {}
            arptable[node.uuid][t.ip] = os.computerID()
        end
        if info.netmask then
            if type(info.netmask) == "number" then t.netmask = prefixToMask(info.netmask)
            else t.netmask = ipToNumber(info.netmask) end
            if localRoute then localRoute.source = bit32.band(t.ip, t.netmask) end
            if broadcastRoute then broadcastRoute.source = bit32.bor(bit32.band(t.ip, t.netmask), bit32.bnot(t.netmask)) end
        end
        if info.up ~= nil then
            t.up = info.up
            if t.up then hardware.call(KERNEL, node, "open", phoenixNetworkingChannel)
            else hardware.call(KERNEL, node, "close", phoenixNetworkingChannel) end
        end
        if not localRoute then routes[0][#routes[0]+1] = {
            source = bit32.band(t.ip, t.netmask),
            sourceNetmask = t.netmask,
            action = "local",
            device = node
        } end
        if not broadcastRoute then routes[0][#routes[0]+1] = {
            source = bit32.bor(bit32.band(t.ip, t.netmask), bit32.bnot(t.netmask)),
            sourceNetmask = 0xFFFFFFFF,
            action = "broadcast",
            device = node
        } end
    end
    return {
        ip = numberToIP(t.ip),
        netmask = maskToPrefix(t.netmask),
        up = t.up
    }
end

function syscalls.routelist(process, thread, num)
    num = expect(1, num, "number", "nil") or 1
    expect.range(num, 0)
    if not routes[num] then return nil end
    local retval = {}
    for i, t in ipairs(routes[num]) do
        retval[i] = {
            source = numberToIP(t.source),
            sourceNetmask = maskToPrefix(t.sourceNetmask),
            action = t.action,
            device = t.device and hardware.path(t.device),
            destination = t.destination and numberToIP(t.destination)
        }
    end
    return retval
end

local actionNames = {unicast = true, broadcast = true, ["local"] = true, unreachable = true, prohibit = true, blackhole = true}

function syscalls.routeadd(process, thread, options)
    if process.user ~= "root" then error("Permission denied") end
    expect(1, options, "table")
    expect.field(options, "source", "string", "number")
    expect.field(options, "sourceNetmask", "string", "number")
    expect.field(options, "action", "string")
    expect.field(options, "device", "string", (options.action ~= "unicast" and options.action ~= "broadcast" and options.action ~= "local") and "nil" or nil)
    expect.field(options, "destination", "string", options.action ~= "unicast" and "nil" or nil)
    expect.range(expect.field(options, "table", "number", "nil") or 1, 1)
    options.table = options.table or 1
    if not actionNames[options.action] then error("bad field 'action' (invalid option '" .. options.action .. "')") end
    local t = {}
    if type(options.source) == "number" then t.source = bit32.band(options.source, 0xFFFFFFFF)
    else t.source = ipToNumber(options.source) end
    if type(options.sourceNetmask) == "number" then t.sourceNetmask = prefixToMask(options.sourceNetmask)
    else t.sourceNetmask = ipToNumber(options.sourceNetmask) end
    t.source = bit32.band(t.source, t.sourceNetmask)
    t.action = options.action
    t.device = options.device and checkModem(hardware.get(options.device))
    t.destination = options.destination and ipToNumber(options.destination)
    routes[options.table] = routes[options.table] or {}
    for _, v in ipairs(routes[options.table]) do if v.source == t.source and v.sourceNetmask == t.sourceNetmask then error("Route already exists") end end
    routes[options.table][#routes[options.table]+1] = t
    routes.maxn = math.max(routes.maxn, options.table)
end

function syscalls.routedel(process, thread, source, mask, num)
    if process.user ~= "root" then error("Permission denied") end
    expect(1, source, "string", "number")
    expect(2, mask, "string", "number")
    num = expect(3, num, "number", "nil") or 1
    expect.range(num, 1)
    if type(mask) == "number" then mask = prefixToMask(mask)
    else mask = ipToNumber(mask) end
    if type(source) == "number" then source = bit32.band(source, mask)
    else source = bit32.band(ipToNumber(source), mask) end
    if not routes[num] then error("Route table does not exist") end
    for i, v in ipairs(routes[num]) do if v.source == source and v.sourceNetmask == mask then table.remove(routes[num], i) return end end
end

function syscalls.arplist(process, thread, device)
    expect(1, device, "string")
    local node = checkModem(hardware.get(device))
    local retval = {}
    for k, v in pairs(arptable[node.uuid] or {}) do retval[numberToIP(k)] = v end
    return retval
end

function syscalls.arpset(process, thread, device, ip, id)
    if process.user ~= "root" then error("Permission denied") end
    expect(1, device, "string")
    expect(2, ip, "string", "number")
    expect(3, id, "number")
    local node = checkModem(hardware.get(device))
    if type(ip) == "string" then ip = ipToNumber(ip)
    else ip = bit32.band(ip, 0xFFFFFFFF) end
    arptable[node.uuid] = arptable[node.uuid] or {}
    arptable[node.uuid][ip] = id
end

local controlNames = {ping = true, pong = true, unreachable = true, timeout = true}

function syscalls.netcontrol(process, thread, ip, typ, err, ttl)
    if process.user ~= "root" then error("Permission denied") end
    expect(1, ip, "string", "number")
    expect(2, typ, "string")
    expect(3, err, "string", "nil")
    expect(4, ttl, "number", "nil")
    if not controlNames[typ] then error("bad argument #2 (invalid option '" .. typ .. "')") end
    if type(ip) == "string" then ip = ipToNumber(ip)
    else ip = bit32.band(ip, 0xFFFFFFFF) end
    protocols.send.control({process = process}, ip, typ, err, ttl)
end

function syscalls.netevent(process, thread, state)
    if process.user ~= "root" then error("Permission denied") end
    expect(1, state, "boolean", "nil")
    if state == true then networkListeners[process] = function(message, info)
        process.eventQueue[#process.eventQueue+1] = {"network_event", {message = deepcopy(message), senderID = info.sourceID, device = info.device.uuid}}
        return true
    end elseif state == false then networkListeners[process] = nil end
    return networkListeners[process] ~= nil
end

function syscalls.checkuri(process, thread, uri)
    -- TODO
    return true
end

function registerLoopback()
    local node = hardware.get("/lo")
    if node then
        ipconfig[node.uuid] = {ip = 0x7F000001, netmask = 0xFF000000, up = true}
        routes[0][#routes[0]+1] = {
            source = 0x7F000000,
            sourceNetmask = 0xFF000000,
            action = "local",
            device = node
        }
        routes[0][#routes[0]+1] = {
            source = 0x7FFFFFFF,
            sourceNetmask = 0xFFFFFFFF,
            action = "broadcast",
            device = node
        }
        arptable[node.uuid] = setmetatable({}, {__index = function() return os.computerID() end})
        syslog.log("Configured IP for loopback device")
    end
end

Web Proxy Viewer  |  New URL  |  Original Page