Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
Reticulum in Haskell
A Reticulum node: wire format, identity and link cryptography,
destinations, transport, resources, interfaces.
Compatible means identical to python-rns 1.4.2 at commit b48b96e6,
openssl backend. The corpus in CORPUS is what says so, and the count
below is the whole claim.
Where it stands
104 passed, 0 failed, corpus 72746484
Every kind the corpus holds, in both directions.
Build
Nix is the only thing that has to be installed.
nix develop
cabal build
./check
The shell carries the compiler, cabal, and the C compiler and POSIX
tools the corpus builds itself with. flake.nix names the compiler and
flake.lock pins the package set.
check
./check clone the pin and measure
CORPUS_DIR=../reticulum-vectors ./check use that checkout as it is
CORPUS holds two lines and is the only place they are written: the
corpus commit to measure against, and the number printed on its result
line. A count is a count against one corpus, so check refuses a result
that carries another.
exit 0 every vector that was not skipped passed
exit 1 a vector failed
exit 2 nothing was measured, or the fingerprint is not the one
on file
Using it
A node that dials one peer, holds a destination, announces it, and
answers one request path:
main :: IO ()
main = do
private <- Node.keypair
started <- Node.start Node.Settings {Node.transport = False} private (\_ _ _ -> pure ())
case started of
Left reason -> putStrLn reason
Right node -> do
holder <- newEmptyMVar
tcp <- Tcp.start (Tcp.Peer "127.0.0.1" "4242") $ \raw -> do
through <- readMVar holder
Node.inbound node through raw
through <- Node.interface "peer" (Tcp.transmit tcp)
putMVar holder through
Node.attach node through
held <- Node.serve node (Destination.name (C.pack "example.echo")) B.empty answers
Node.announce node held
where
answers =
Node.silent
{ Node.delivered = print
, Node.requested = Map.singleton (Request.named (C.pack "echo")) (pure . Just)
}
Node.silent hears everything and says nothing, so a caller writes down
only what it answers. A packet is filed under the interface it arrived
on, which is why the reader waits on an MVar for the interface it is
reading for: the socket exists before the interface does.
The other end of that is Node.open, which gives back a link to speak
on: Node.speak sends a packet, Node.ask sends a request, Node.hand
hands over a resource of any length, and Node.close ends it. cmd/talk
is that side, whole, in a hundred lines.
The node speaks no interface access codes: a frame that carries one is
dropped. Reticulum.Interface holds the codec for them, and the corpus
is what exercises it.
Layout
src/Reticulum/ the library: the codec is pure, the node is IO
cmd/harness/ the corpus harness, linked to ./harness by check
cmd/node/ a node on TCP interfaces, dialled and answered
cmd/talk/ opens a link to a destination and speaks on it
test/ what the corpus cannot hold
reference/ this node with python-rns nodes around it
.github/workflows/ one job, and it runs ./check
CLAUDE.md the rules this repository is written under
CORPUS the pin
DEPENDS every dependency past base and the crypto library,
and what needs it
The harness
One program taking the arguments the corpus's cmd/dump takes:
./harness <kind> <rawfile> fields out
./harness -e <kind> <expectfile> raw back
It prints one field per line from this implementation's own code, and
reads those fields back into the library's types to write the bytes
again. The contract is the corpus's doc/harness; the three rules it
binds this repository to are in CLAUDE.md.
The commands
node [-t] [-l <port>] [-n <name>] [-k <file>] [<host>:<port> ...]
-t be a transport node
-l <port> listen, and take every connection as an interface
-n <name> hold this destination, and announce it once
-k <file> the identity, written if the file is not there
talk <hex> <host>:<port> ...
Dials every peer given, opens a link to that destination, sends a
packet, asks two requests, hands over two resources -- one of them
longer than a segment -- and closes the link. Every step prints a line.
Both need at least one peer. Log lines go to standard error.
Tests
cabal test
There are none here that a vector already proves. What belongs here is
what the corpus cannot hold: properties, and the layers above link --
the transport node, both ends of a link, and the tables behind them.
./reference/check
is the same node between two python-rns nodes that cannot reach each
other any other way, a third that talks to the node itself, and a
fourth that talk opens a link to. It needs python-rns importable, takes
about three minutes, and is not run in CI. Thirty-one lines are
checked, and every one of them is a node saying what it got.
License
LICENSE, and its two restrictions. Copying upstream code or
documentation text requires attribution.