Add incomplete workspace, for now.

This commit is contained in:
20kdc 2017-03-18 09:34:13 +00:00
commit 2b458b5df8
9 changed files with 484 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
netref.dot
netref.png

1
LICENSE Normal file
View File

@ -0,0 +1 @@
I, 20kdc, release this into the public domain.

120
culib.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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