1
0
mirror of https://github.com/XeonSquared/OC-Copper.git synced 2024-09-21 18:58:47 +10:00
OC-Copper/relib.lua
20kdc 880a4dff08 Adjust the tuning parameters for real-world usage.
The tests assume a mesh network with known connections, the node doesn't,
 so unneeded packet leakage isn't really as controllable as I'd hoped.

What's definitely important is that the system doesn't cache entries for
 long enough that it starts continuously sending packets the wrong way
 no matter what.
2017-03-19 21:54:57 +00:00

153 lines
4.2 KiB
Lua

-- I, 20kdc, release this into the public domain.
-- No warranty is provided, implied or otherwise.
-- Copper Reliability Layer
-- Notably, this should be instantiated rather than the normal Copper instance.
local culib = require("culib")
-- onRReceive is now: (from, to, port, data, unreliablePacket)
-- where to can be anything for unreliable packets, but otherwise is the current hostname.
return function (hostname, transmit, onRReceive, time)
-- node.hostname should be used for hostname generally.
local node
-- The maximum amount of timers (used to cap memory usage)
local tuningMaxTimers = 0x200
local tuningClearAntiduplicate = 60
local tuningAttempts = 12
local tuningAttemptTime = 2.5
-- Just an array, no special index.
-- Contents : {
-- trigger function,
-- expiry time
-- }
local timers = {}
-- Indexes are globalIds, values are timers for deleting entries out of this table.
local weAcked = {}
-- Indexes are globalIds, values are { successFunc, deathTimer }
local needsAck = {}
local function addTimer(trig, expi)
if #timers < tuningMaxTimers then
local t = {trig, time() + expi}
table.insert(timers, t)
return t
end
return nil
end
local function killTimer(t)
for i = 1, #timers do
if timers[i] == t then
table.remove(timers, i)
return
end
end
end
local function gen3Random()
return string.char(math.random(256) - 1) .. string.char(math.random(256) - 1) .. string.char(math.random(256) - 1)
end
local function genGlobalId(port)
local low = math.abs(math.floor(port)) % 256
local high = math.abs(math.floor(port / 256)) % 256
local portD = string.char(high) .. string.char(low)
return portD .. gen3Random()
end
local onReceive = function (nfrom, nto, data)
if data:len() < 7 then return end
local port = data:byte(2) + (data:byte(1) * 256)
local tp = data:byte(7)
local globalId = data:sub(1, 5)
if (tp == 0x01) or (tp == 0x00) then
-- Only send one acknowledgement per packet,
-- but only receive the packet once.
-- (This is why timers are counted - to prevent the weAcked pool from getting too big.)
if not weAcked[nto .. globalId] then
onRReceive(nfrom, nto, port, data:sub(8), tp == 0x00)
else
killTimer(weAcked[nto .. globalId])
end
weAcked[nto .. globalId] = addTimer(function ()
weAcked[nto .. globalId] = nil
end, tuningClearAntiduplicate)
-- Check if this should actually be ACKed
if tp ~= 0x01 then return end
if nto ~= node.hostname then return end
node.output(nto, nfrom, data:sub(1, 6) .. "\x02")
end
if (tp == 0x02) and needsAck[nfrom .. globalId] then
needsAck[nfrom .. globalId][1](nfrom)
killTimer(needsAck[nfrom .. globalId][2])
needsAck[nfrom .. globalId] = nil
end
if nto ~= node.hostname then
return
end
end
node = culib(hostname, transmit, onReceive, time)
local relib = {}
relib.setHostname = function (h)
node.hostname = h
end
relib.getHostname = function ()
return node.hostname
end
relib.refresh = function ()
node.refresh()
local i = 1
local t = time()
while i <= #timers do
if timers[i][2] <= t then
timers[i][1]()
table.remove(timers, i)
else
i = i + 1
end
end
end
relib.input = node.input
-- can be reduced to output(nto, port, data) safely
relib.output = function (nto, port, data, unreliable, onSucceed, onFailure)
onSucceed = onSucceed or (function () end)
onFailure = onFailure or (function () end)
local gid = genGlobalId(port)
local tp = "\x01"
-- Unreliable packets:
-- 1. Can't be ACKed (not in the needsAck table)
-- 2. Are otherwise subject to the same rules as regular packets
if unreliable then
tp = "\x00"
end
local na = {onSucceed}
local attempt = -1
local doAttempt
doAttempt = function ()
attempt = attempt + 1
if attempt == tuningAttempts then
if not unreliable then
needsAck[nto .. gid] = nil
onFailure()
end
return
end
node.output(node.hostname, nto, gid .. string.char(attempt) .. tp .. data)
na[2] = addTimer(doAttempt, tuningAttemptTime)
if not na[2] then
needsAck[nto .. gid] = nil
if not unreliable then
onFailure()
end
end
end
if not unreliable then
needsAck[nto .. gid] = na
end
doAttempt()
end
return relib
end