mirror of
https://github.com/XeonSquared/OC-Copper.git
synced 2024-11-23 09:58:06 +11:00
Add incomplete workspace, for now.
This commit is contained in:
commit
2b458b5df8
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
netref.dot
|
||||||
|
netref.png
|
120
culib.lua
Normal file
120
culib.lua
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
-- 'Copper' networking test implementation.
|
||||||
|
-- This is meant as a portable (even into OC) library for networking.
|
||||||
|
-- This 'outer function' is the instantiator.
|
||||||
|
|
||||||
|
-- Note that it is probably possible to cause this code to run out of
|
||||||
|
-- memory in several hilarious ways.
|
||||||
|
|
||||||
|
-- Interfaces have no meaning, since addresses are names.
|
||||||
|
-- Which "side" a system is on is irrelevant.
|
||||||
|
-- For sending, the following function is used:
|
||||||
|
-- transmit(nodeId, message)
|
||||||
|
-- The nodeId is a string or number that has been given via culib.input,
|
||||||
|
-- or nil for broadcast.
|
||||||
|
-- It's more of a suggestion than a requirement to check nodeId.
|
||||||
|
|
||||||
|
-- The message is always a string.
|
||||||
|
-- This mirrors the message format usable by sendPacket and onReceive.
|
||||||
|
|
||||||
|
-- "onReceive" is a function which is called when a packet is decoded.
|
||||||
|
-- onReceive(namefrom, nameto, data)
|
||||||
|
|
||||||
|
-- "time" is a function which returns the real time, in seconds.
|
||||||
|
-- It need not be precise.
|
||||||
|
-- (This is used for caches.)
|
||||||
|
return function (hostname, transmit, onReceive, time)
|
||||||
|
|
||||||
|
-- How many packets need to be stored in seenBefore's keyspace
|
||||||
|
-- before 'panic' is the best response?
|
||||||
|
local tuningMaxSeenBeforeCountBeforeEmergencyFlush = 0x100
|
||||||
|
|
||||||
|
-- Expect a response by this many seconds,
|
||||||
|
-- or else clear the known receivers cache and resend.
|
||||||
|
local tuningExpectResponse = 20
|
||||||
|
|
||||||
|
-- Flush the loop detector every so often.
|
||||||
|
-- This is not a complete clear.
|
||||||
|
local tuningFlushLoopDetector = 120
|
||||||
|
|
||||||
|
-- Do not change this value. I mean it. Don't. Just. Don't.
|
||||||
|
local tuningAutorejectLen = 4000
|
||||||
|
|
||||||
|
local loopDetectorNext = time() + tuningFlushLoopDetector
|
||||||
|
|
||||||
|
-- Packets that have been seen before.
|
||||||
|
-- The values are the amount of times a packet has been seen.
|
||||||
|
-- This is flushed every tuningFlushLoopDetector seconds -
|
||||||
|
-- the flushing decrements the value until it reaches 0,
|
||||||
|
-- so packets which have looped before get a longer timeout.
|
||||||
|
local seenBefore = {}
|
||||||
|
local seenBeforeCount = 0
|
||||||
|
|
||||||
|
-- Unacknowledged packets.
|
||||||
|
-- Can cause an earlier cache flush.
|
||||||
|
-- [address] = giveupTime
|
||||||
|
-- (These are just forgotten after a while)
|
||||||
|
--local sentBNR = {}--NYI
|
||||||
|
|
||||||
|
-- [address] = {
|
||||||
|
-- node,
|
||||||
|
-- expiry
|
||||||
|
-- }
|
||||||
|
--local lastKnownReceiver = {}--NYI
|
||||||
|
|
||||||
|
local function refresh()
|
||||||
|
local t = time()
|
||||||
|
if t >= loopDetectorNext then
|
||||||
|
for k, v in pairs(seenBefore) do
|
||||||
|
local n = v - 1
|
||||||
|
if n > 0 then
|
||||||
|
seenBefore[k] = n
|
||||||
|
else
|
||||||
|
seenBefore[k] = nil
|
||||||
|
seenBeforeCount = seenBeforeCount - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
loopDetectorNext = time() + tuningFlushLoopDetector
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local culib = {}
|
||||||
|
|
||||||
|
-- Can be changed.
|
||||||
|
culib.hostname = hostname
|
||||||
|
culib.input = function (node, message)
|
||||||
|
if message:len() > tuningAutorejectLen then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if seenBefore[message] then
|
||||||
|
seenBefore[message] = seenBefore[message] + 1
|
||||||
|
return
|
||||||
|
else
|
||||||
|
seenBefore[message] = 0
|
||||||
|
seenBeforeCount = seenBeforeCount + 1
|
||||||
|
if seenBeforeCount > tuningMaxSeenBeforeCountBeforeEmergencyFlush then
|
||||||
|
-- Panic
|
||||||
|
seenBeforeCount = 0
|
||||||
|
seenBefore = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if message:len() < 2 then return end
|
||||||
|
local nlen = message:byte(1) + 1
|
||||||
|
local fnam = message:sub(1, nlen)
|
||||||
|
message = message:sub(nlen + 1)
|
||||||
|
if message:len() < 2 then return end
|
||||||
|
local nlen = message:byte(1) + 1
|
||||||
|
local tnam = message:sub(1, nlen)
|
||||||
|
message = message:sub(nlen + 1)
|
||||||
|
if message:len() < 1 then return end
|
||||||
|
onReceive(fnam, tnam, message)
|
||||||
|
if culib.hostname == tnam then return end
|
||||||
|
-- Redistribution of messages not aimed here
|
||||||
|
transmit(nil, message)
|
||||||
|
end
|
||||||
|
culib.output = function (fnam, tnam, message)
|
||||||
|
onReceive(fnam, tnam, message)
|
||||||
|
if tnam == culib.hostname then return end
|
||||||
|
transmit(nil, encodeName(fnam) .. encodeName(tnam) .. message)
|
||||||
|
end
|
||||||
|
return culib
|
||||||
|
end
|
81
gennet.lua
Normal file
81
gennet.lua
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
-- Generate connected network where all nodes are connected.
|
||||||
|
-- Saves graph to "netref.dot", outputs lua tables to stdout.
|
||||||
|
|
||||||
|
local nodes = {}
|
||||||
|
local wordsA = {
|
||||||
|
"changing",
|
||||||
|
"ponderous",
|
||||||
|
"intriguing",
|
||||||
|
"bright",
|
||||||
|
"solitudial",
|
||||||
|
"nuanced"
|
||||||
|
}
|
||||||
|
local wordsB = {
|
||||||
|
"fontaine",
|
||||||
|
"marple",
|
||||||
|
"poirot",
|
||||||
|
"pinkie",
|
||||||
|
"sparks",
|
||||||
|
"twi"
|
||||||
|
}
|
||||||
|
for i = 1, #wordsA do
|
||||||
|
for j = 1, #wordsB do
|
||||||
|
table.insert(nodes, wordsA[i] .. "_" .. wordsB[j])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local connections = {}
|
||||||
|
for i = 1, #nodes do
|
||||||
|
connections[i] = {}
|
||||||
|
connections[i][i] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Recursive algorithm.
|
||||||
|
-- It will always come to the right answer, though.
|
||||||
|
-- (But definitely wouldn't ever return the fastest route.)
|
||||||
|
local function canRoute(i, j, avoid)
|
||||||
|
if i == j then return true end
|
||||||
|
if avoid[i] then return false end
|
||||||
|
avoid[i] = true
|
||||||
|
for p = 1, #nodes do
|
||||||
|
if connections[i][p] then
|
||||||
|
if canRoute(p, j, avoid) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ensureRoute(i, j)
|
||||||
|
while not canRoute(i, j, {}) do
|
||||||
|
local a, b = math.random(#nodes), math.random(#nodes)
|
||||||
|
connections[a][b] = true
|
||||||
|
connections[b][a] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("return function (declare, connect)")
|
||||||
|
for i = 1, #nodes do
|
||||||
|
-- Perform declaration here so next pass can do connections
|
||||||
|
print(" declare(\"" .. nodes[i] .. "\")")
|
||||||
|
for j = i + 1, #nodes do
|
||||||
|
ensureRoute(i, j)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local dot = io.open("netref.dot", "w")
|
||||||
|
dot:write("graph \"Test Network Reference Graph\" {\n")
|
||||||
|
-- Notably this is a destructive process (to prevent backwards links)
|
||||||
|
for i = 1, #nodes do
|
||||||
|
for p = 1, #nodes do
|
||||||
|
if (p ~= i) and connections[i][p] then
|
||||||
|
print(" connect(" .. i .. ", " .. p .. ")")
|
||||||
|
dot:write(" " .. nodes[i] .. " -- " .. nodes[p] .. ";\n")
|
||||||
|
connections[p][i] = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
dot:write("}")
|
||||||
|
dot:close()
|
||||||
|
print("end")
|
39
protocol.0
Normal file
39
protocol.0
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
Copper Protocol
|
||||||
|
20kdc, 2017
|
||||||
|
|
||||||
|
Copper is a simple to implement networking protocol based on names.
|
||||||
|
This is it's sole purpose.
|
||||||
|
It can be used in various contexts, though it is not suitable as a
|
||||||
|
secure peer-to-peer networking protocol where all actors are untrusted.
|
||||||
|
|
||||||
|
Rather, Copper is better for the situation of the current internet -
|
||||||
|
hierarchial structures (operated by semi-trusted parties, with
|
||||||
|
encryption used to hide information from them as appropriate),
|
||||||
|
with arbitary network structure at the fully-trusted-network level.
|
||||||
|
|
||||||
|
Copper addresses are names.
|
||||||
|
In the context of a system not implementing a hierarchial gateway,
|
||||||
|
this is as much about Copper addressing as matters.
|
||||||
|
|
||||||
|
Copper base packets contain 3 fields.
|
||||||
|
A name (as a length-minus-1-byte-prefixed-string),
|
||||||
|
another name (in the same format),
|
||||||
|
and the rest is data.
|
||||||
|
|
||||||
|
Copper packets may be up to 4000 bytes long, including base.
|
||||||
|
(It's assumed the additional 96 bytes will be useful for any additional
|
||||||
|
framing, assuming a 4K packet limit.)
|
||||||
|
|
||||||
|
Loop detection should performed by checking if a packet exactly the same has
|
||||||
|
been seen recently - other rejection, alteration and routing measures
|
||||||
|
are up to the implementer.
|
||||||
|
|
||||||
|
Signalling is inadvisable - Copper is primarily meant to allow creating
|
||||||
|
internally "partyline" OpenComputers in-game networks with named nodes
|
||||||
|
and some semblance of routing or structure.
|
||||||
|
|
||||||
|
Should a situation be dire enough,
|
||||||
|
hierarchial networks (described in file 2, 'protocol.1'),
|
||||||
|
and custom routing software in general,
|
||||||
|
can be used to split networks however the system requires.
|
||||||
|
Copper isn't very picky.
|
42
protocol.1
Normal file
42
protocol.1
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
Copper Protocol :: Hierarchial Gateways
|
||||||
|
20kdc, 2017
|
||||||
|
|
||||||
|
"Hierarchial Gateways" are a system for ISP-like bodies to prevent their
|
||||||
|
users from causing havoc.
|
||||||
|
They are simply base low-level Copper nodes with two interfaces and the
|
||||||
|
following rules:
|
||||||
|
|
||||||
|
For the FROM address:
|
||||||
|
|
||||||
|
If it's on the parent side, reject if it's prefixed with hostname ..
|
||||||
|
"/",
|
||||||
|
otherwise prefix it with "<" and forward to child side.
|
||||||
|
If it's on the child side, reject if it's prefixed with "<",
|
||||||
|
otherwise prefix it with hostname .. "/" and forward to parent side.
|
||||||
|
|
||||||
|
For the TO address:
|
||||||
|
|
||||||
|
If it's on the parent side, reject unless prefixed with hostname .. "/",
|
||||||
|
otherwise remove that and forward to child side.
|
||||||
|
If it's on the child side, reject unless prefixed with "<",
|
||||||
|
otherwise remove that and forward to parent side.
|
||||||
|
|
||||||
|
This introduces a simple hierarchial structure that does not require any
|
||||||
|
support code apart from in the nodes supporting the hierarchy.
|
||||||
|
There are only two downsides:
|
||||||
|
1. Nodes connected to two gateways
|
||||||
|
could have multiple addresses but believe they only have one
|
||||||
|
2. Nodes behind two nested gateways can address themselves in two ways
|
||||||
|
|
||||||
|
However, this should all work out as long as people make a deliberate
|
||||||
|
effort not to notice the hierarchial structure system in their code.
|
||||||
|
|
||||||
|
Specifically, problem 2 only causes an issue should some nodes not
|
||||||
|
directly involved in gateway activities be
|
||||||
|
attempting to parse hierarchial addresses.
|
||||||
|
|
||||||
|
Possible uses of hierarchial gateways:
|
||||||
|
1. A safe (untrusting of servers in it) inter-server networking hub,
|
||||||
|
following the same protocol as everything else in Copper,
|
||||||
|
unlike certain competitors
|
||||||
|
2. ISPs within servers, perhaps those using the hubs
|
93
protocol.2
Normal file
93
protocol.2
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
Copper Protocol :: Reliability/Fragmentation Layer
|
||||||
|
20kdc, 2017
|
||||||
|
|
||||||
|
The Copper Protocol as described in files 1 and 2 does not have any
|
||||||
|
semblance of application multiplexing or failure recovery.
|
||||||
|
|
||||||
|
This is intentional.
|
||||||
|
Assuming that nobody is trying to make the fatal mistake of constructing
|
||||||
|
a NAT, files 1 and 2 are enough for all routing-related purposes.
|
||||||
|
|
||||||
|
For applications, however, a protocol must be layered on top.
|
||||||
|
|
||||||
|
This document on the Reliability Layer describes how that should work.
|
||||||
|
|
||||||
|
|
||||||
|
All implementations of Copper that synthesize their own packets SHOULD
|
||||||
|
follow this protocol when doing so, unless they are a custom system
|
||||||
|
that will not be connected to any global network.
|
||||||
|
|
||||||
|
|
||||||
|
Firstly, note that, to the application, a Reliability Layer packet can
|
||||||
|
be up to 59,895 bytes in size, though a fragment can only be up to 3993 bytes.
|
||||||
|
|
||||||
|
Secondly, note that an application should be able to ask to be notified
|
||||||
|
when a packet is received successfully or when the implementation gives up,
|
||||||
|
with a flag indicating which is which.
|
||||||
|
|
||||||
|
Reliability Layer packets have a simple format.
|
||||||
|
The first two bytes are the port number, in big-endian format.
|
||||||
|
The next three bytes are a number to this application-side packet.
|
||||||
|
They should be as random as possible.
|
||||||
|
The next byte is the 'attempt number' - the amount of attempts by this
|
||||||
|
side of the Reliability Layer "connection" to send a packet with this
|
||||||
|
meaning.
|
||||||
|
|
||||||
|
This can be achieved serially or otherwise, but should have a random base.
|
||||||
|
Combined with correctly-forgetting packet caches, this should prevent
|
||||||
|
any packets lost by data collision.
|
||||||
|
The final header byte is the actual indicator of what is in the packet.
|
||||||
|
|
||||||
|
The upper nibble indicates the amount of fragments in the packet - 0
|
||||||
|
indicates an acknowledgement.
|
||||||
|
The lower nibble indicates which fragment this is, or if this is an
|
||||||
|
acknowledgement, which fragment was acknowledged.
|
||||||
|
|
||||||
|
0x0F indicates that this is a *deliberately* unreliable packet.
|
||||||
|
(These packets cannot be fragmented or acknowledged, and thus have the
|
||||||
|
per-fragment limit of 3993 bytes.
|
||||||
|
The attempt number and primary packet number still have meaning.)
|
||||||
|
|
||||||
|
Two example scenarioes will now be presented:
|
||||||
|
|
||||||
|
1.
|
||||||
|
|
||||||
|
ARCHWAYS sends a 0x10 'First fragment of a one fragment packet' to
|
||||||
|
IWAKURA on port 8080, twice (the first attempt being dropped).
|
||||||
|
1F 90 | F4 21 B9 | 00/01 | 10 | (...)
|
||||||
|
port packetID Attempt CC Data
|
||||||
|
|
||||||
|
IWAKURA receives it successfully on the second time, and sends back a
|
||||||
|
response, three times.
|
||||||
|
1F 90 | F4 21 B9 | 00/01/02 | 00
|
||||||
|
port packetID Attempt CC
|
||||||
|
|
||||||
|
ARCHWAYS receives the response and does not send a third packet.
|
||||||
|
|
||||||
|
2.
|
||||||
|
|
||||||
|
IWAKURA, having parsed the packet, sends back a long response on the same port.
|
||||||
|
The response is two packets long.
|
||||||
|
1F 90 | 91 19 28 | 00 | 20 | (...)
|
||||||
|
1F 90 | 91 19 28 | 00 | 21 | (...)
|
||||||
|
|
||||||
|
ARCHWAYS receives both packets, in the wrong order
|
||||||
|
(but it reassembles it anyway), and ACKs three times...
|
||||||
|
...but the packets are dropped due to a crow getting in the way of the
|
||||||
|
satellite dish at the wrong point. Blasted crow.
|
||||||
|
|
||||||
|
1F 90 | 91 19 28 | 00/01/02 | 21
|
||||||
|
1F 90 | 91 19 28 | 00/01/02 | 20
|
||||||
|
|
||||||
|
IWAKURA, waiting, say, 6 seconds
|
||||||
|
(assuming ACKs are sent a second and a half apart) sends a retransmission.
|
||||||
|
|
||||||
|
1F 90 | 91 19 28 | 01 | 20 | (...)
|
||||||
|
1F 90 | 91 19 28 | 01 | 21 | (...)
|
||||||
|
|
||||||
|
ARCHWAYS ACKs the retransmission, just in case - this works.
|
||||||
|
|
||||||
|
1F 90 | 91 19 28 | 00/01/02 | 21
|
||||||
|
1F 90 | 91 19 28 | 00/01/02 | 20
|
||||||
|
|
||||||
|
IWAKURA's application knows the message got through.
|
13
runtest.lua
Normal file
13
runtest.lua
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
-- Load testnet
|
||||||
|
local nodes = {}
|
||||||
|
local nodenames = {}
|
||||||
|
loadfile("testnet.lua")(function (n)
|
||||||
|
table.insert(nodes, {})
|
||||||
|
table.insert(nodenames, n)
|
||||||
|
end, function (a, b)
|
||||||
|
table.insert(nodes[a], b)
|
||||||
|
table.insert(nodes[b], a)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Start testing
|
||||||
|
require("culib")
|
93
testnet.lua
Normal file
93
testnet.lua
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
return function (declare, connect)
|
||||||
|
declare("changing_fontaine")
|
||||||
|
declare("changing_marple")
|
||||||
|
declare("changing_poirot")
|
||||||
|
declare("changing_pinkie")
|
||||||
|
declare("changing_sparks")
|
||||||
|
declare("changing_twi")
|
||||||
|
declare("ponderous_fontaine")
|
||||||
|
declare("ponderous_marple")
|
||||||
|
declare("ponderous_poirot")
|
||||||
|
declare("ponderous_pinkie")
|
||||||
|
declare("ponderous_sparks")
|
||||||
|
declare("ponderous_twi")
|
||||||
|
declare("intriguing_fontaine")
|
||||||
|
declare("intriguing_marple")
|
||||||
|
declare("intriguing_poirot")
|
||||||
|
declare("intriguing_pinkie")
|
||||||
|
declare("intriguing_sparks")
|
||||||
|
declare("intriguing_twi")
|
||||||
|
declare("bright_fontaine")
|
||||||
|
declare("bright_marple")
|
||||||
|
declare("bright_poirot")
|
||||||
|
declare("bright_pinkie")
|
||||||
|
declare("bright_sparks")
|
||||||
|
declare("bright_twi")
|
||||||
|
declare("solitudial_fontaine")
|
||||||
|
declare("solitudial_marple")
|
||||||
|
declare("solitudial_poirot")
|
||||||
|
declare("solitudial_pinkie")
|
||||||
|
declare("solitudial_sparks")
|
||||||
|
declare("solitudial_twi")
|
||||||
|
declare("nuanced_fontaine")
|
||||||
|
declare("nuanced_marple")
|
||||||
|
declare("nuanced_poirot")
|
||||||
|
declare("nuanced_pinkie")
|
||||||
|
declare("nuanced_sparks")
|
||||||
|
declare("nuanced_twi")
|
||||||
|
connect(1, 3)
|
||||||
|
connect(1, 9)
|
||||||
|
connect(2, 20)
|
||||||
|
connect(3, 17)
|
||||||
|
connect(3, 35)
|
||||||
|
connect(4, 5)
|
||||||
|
connect(4, 19)
|
||||||
|
connect(5, 29)
|
||||||
|
connect(6, 15)
|
||||||
|
connect(6, 22)
|
||||||
|
connect(6, 25)
|
||||||
|
connect(6, 32)
|
||||||
|
connect(7, 10)
|
||||||
|
connect(7, 24)
|
||||||
|
connect(8, 18)
|
||||||
|
connect(8, 33)
|
||||||
|
connect(8, 36)
|
||||||
|
connect(9, 17)
|
||||||
|
connect(9, 33)
|
||||||
|
connect(9, 35)
|
||||||
|
connect(10, 20)
|
||||||
|
connect(11, 13)
|
||||||
|
connect(11, 23)
|
||||||
|
connect(11, 27)
|
||||||
|
connect(11, 28)
|
||||||
|
connect(12, 29)
|
||||||
|
connect(12, 30)
|
||||||
|
connect(13, 24)
|
||||||
|
connect(13, 25)
|
||||||
|
connect(13, 28)
|
||||||
|
connect(13, 33)
|
||||||
|
connect(14, 19)
|
||||||
|
connect(14, 28)
|
||||||
|
connect(15, 21)
|
||||||
|
connect(15, 30)
|
||||||
|
connect(15, 31)
|
||||||
|
connect(15, 33)
|
||||||
|
connect(16, 24)
|
||||||
|
connect(16, 32)
|
||||||
|
connect(16, 34)
|
||||||
|
connect(18, 23)
|
||||||
|
connect(18, 36)
|
||||||
|
connect(19, 23)
|
||||||
|
connect(19, 25)
|
||||||
|
connect(19, 28)
|
||||||
|
connect(19, 31)
|
||||||
|
connect(22, 35)
|
||||||
|
connect(23, 26)
|
||||||
|
connect(24, 31)
|
||||||
|
connect(25, 33)
|
||||||
|
connect(26, 34)
|
||||||
|
connect(30, 34)
|
||||||
|
connect(31, 33)
|
||||||
|
connect(33, 35)
|
||||||
|
connect(34, 35)
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user