urbit/sys/vane/alef.hoon

489 lines
13 KiB
Plaintext
Raw Normal View History

2019-05-25 08:53:29 +03:00
:: protocol-version: current version of the ames wire protocol
::
2019-05-25 05:03:33 +03:00
=/ protocol-version=?(%0 %1 %2 %3 %4 %5 %6 %7) %0
::
|%
2019-05-27 02:54:23 +03:00
+| %generics
::
:: +ordered-set: treap with user-specified horizontal order
::
:: Conceptually smaller items go on the left, so the smallest item
:: can be popped off the head. If $item is `@` and .compare is +lte,
:: then the numerically smallest item is the head.
::
++ ordered-set
|* item=mold
:: +compare: item comparator for horizontal order
::
|= compare=$-([item item] ?)
|%
:: +check-balance: verify horizontal and vertical orderings
::
++ check-balance
=| [l=(unit item) r=(unit item)]
|= a=(tree item)
^- ?
:: empty tree is valid
::
?~ a %.y
:: nonempty trees must maintain several criteria
::
?& :: if .n.a is left of .u.l, assert horizontal comparator
::
?~(l %.y (compare n.a u.l))
:: if .n.a is right of .u.r, assert horizontal comparator
::
?~(r %.y (compare u.r n.a))
:: if .a is not leftmost element, assert vertical order between
:: .l.a and .n.a and recurse to the left with .n.a as right
:: neighbor
::
?~(l.a %.y &((mor n.a n.l.a) $(a l.a, l `n.a)))
:: if .a is not rightmost element, assert vertical order
:: between .r.a and .n.a and recurse to the right with .n.a as
:: left neighbor
::
?~(r.a %.y &((mor n.a n.r.a) $(a r.a, r `n.a)))
==
:: +put: ordered item insert
::
++ put
|= [a=(tree item) =item]
^- (tree ^item)
:: base case: replace null with single-item tree
::
?~ a [n=item l=~ r=~]
:: base case: ignore duplicate
::
?: =(n.a item) a
:: if item goes on left, recurse left then rebalance vertical order
::
?: (compare item n.a)
=/ l $(a l.a)
?> ?=(^ l)
?: (mor n.a n.l)
a(l l)
l(r a(l r.l))
:: item goes on right; recurse right then rebalance vertical order
::
=/ r $(a r.a)
?> ?=(^ r)
?: (mor n.a n.r)
a(r r)
r(l a(r l.r))
:: +peek: produce head (smallest item) or null
::
++ peek
|= a=(tree item)
^- (unit item)
::
?~ a ~
?~ l.a `n.a
$(a l.a)
:: +pop: produce .head (smallest item) and .rest or crash if empty
::
++ pop
|= a=(tree item)
^- [head=item rest=(tree item)]
::
?~ a !!
?~ l.a [n.a r.a]
::
=/ l $(a l.a)
:- head.l
:: load .rest.l back into .a and rebalance
::
?: |(?=(~ rest.l) (mor n.a n.rest.l))
a(l rest.l)
rest.l(r a(r r.rest.l))
:: +sift: remove and produce all items matching .reject predicate
::
:: Unrolls to a list, extracts items, then rolls back into a tree.
:: Removed items are produced smallest to largest.
::
++ sift
|= [a=(tree item) reject=$-(item ?)]
^- [lost=(list item) kept=(tree item)]
::
=+ [l k]=(skid (tap a) reject) [l (gas ~ k)]
:: +tap: convert to list, smallest to largest
::
++ tap
|= a=(tree item)
^- (list item)
::
=| b=(list item)
|- ^+ b
?~ a b
::
$(a l.a, b [n.a $(a r.a)])
:: +gas: put a list of items
::
++ gas
|= [a=(tree item) b=(list item)]
^- (tree item)
::
?~ b a
$(b t.b, a (put a i.b))
--
::
2019-05-25 08:53:29 +03:00
+| %atomics
2019-05-25 05:03:33 +03:00
::
2019-05-25 08:53:29 +03:00
+$ blob @uxblob
+$ bone @udbone
2019-05-27 02:54:23 +03:00
+$ fragment @uwfragment
2019-05-27 04:48:41 +03:00
+$ fragment-num @udfragmentnum
2019-05-25 08:53:29 +03:00
+$ lane @uxlane
2019-05-27 02:54:23 +03:00
+$ message-num @udmessagenum
2019-05-25 08:53:29 +03:00
+$ public-key @uwpublickey
2019-05-27 02:54:23 +03:00
+$ signature @uwsignature
2019-05-25 08:53:29 +03:00
+$ symmetric-key @uwsymmetrickey
:: $rank: which kind of ship address, by length
::
:: 0: galaxy or star -- 2 bytes
:: 1: planet -- 4 bytes
:: 2: moon -- 8 bytes
:: 3: comet -- 16 bytes
2019-05-25 05:03:33 +03:00
::
+$ rank ?(%0 %1 %2 %3)
2019-05-25 08:53:29 +03:00
::
2019-05-27 06:22:38 +03:00
+| %kinetics
2019-05-27 04:48:41 +03:00
::
:: $channel: combined sender and receiver identifying data
2019-05-25 08:53:29 +03:00
::
2019-05-27 04:48:41 +03:00
+$ channel
2019-05-27 04:52:31 +03:00
$: [our=ship her=ship]
2019-05-27 04:48:41 +03:00
:: our data, common to all dyads
::
$: =our=life
crypto-core=acru:ames
==
:: her data, specific to this dyad
::
$: =symmetric-key
=her=life
=her=public-key
her-sponsors=(list ship)
== ==
2019-05-25 08:53:29 +03:00
:: $dyad: pair of sender and receiver ships
::
+$ dyad [sndr=ship rcvr=ship]
2019-05-27 06:22:38 +03:00
::
+$ error [tag=@tas =tang]
2019-05-25 08:53:29 +03:00
:: $message: application-level message
::
:: path: internal route on the receiving ship
:: payload: semantic message contents
::
+$ message [=path payload=*]
:: $packet: noun representation of an ames datagram packet
::
:: Roundtrips losslessly through atom encoding and decoding.
::
:: .origin is ~ unless the packet is being forwarded. If present,
:: it's an atom that encodes a route to another ship, such as an IPv4
:: address. Routes are opaque to Arvo and only have meaning in the
:: interpreter. This enforces that Ames is transport-agnostic.
::
+$ packet [=dyad encrypted=? origin=(unit lane) content=*]
2019-05-27 02:54:23 +03:00
:: $open-packet: unencrypted packet payload, for comet self-attestation
::
+$ open-packet
$: =signature
=sndr=life
=rcvr=life
rcvr=ship
==
:: $shut-packet: encrypted packet payload
::
+$ shut-packet
$: =sndr=life
=rcvr=life
=bone
=message-num
meat=(each fragment-meat ack-meat)
==
:: $fragment-meat: contents of a message-fragment packet
::
+$ fragment-meat
$: num-fragments=fragment-num
=fragment-num
=fragment
==
:: $ack-meat: contents of an acknowledgment packet; fragment or message
::
:: Fragment acks reference the $fragment-num of the target packet.
::
:: Message acks contain a success flag .ok, which is %.n in case of
:: negative acknowledgment (nack), along with .lag that describes the
:: time it took to process the message. .lag is zero if the message
:: was processed during a single Arvo event. At the moment, .lag is
:: always zero.
::
+$ ack-meat (each fragment-num [ok=? lag=@dr])
2019-05-25 08:53:29 +03:00
::
2019-05-27 04:52:31 +03:00
+| %statics
2019-05-25 08:53:29 +03:00
::
:: $ames-state: state for entire vane
::
+$ ames-state
$: peers=(map ship ship-state)
=life
crypto-core=acru:ames
==
:: $ship-state: all we know about a peer
::
:: %known: we know their life and public keys, so we have a channel
:: %alien: no PKI data, so enqueue actions to perform once we learn it
::
+$ ship-state
$% [%known peer-state]
[%alien pending-actions]
==
:: $peer-state: state for a peer with known life and keys
::
+$ peer-state
$: $: =symmetric-key
=life
=public-key
sponsors=(list ship)
==
route=(unit [direct=? =lane])
=ossuary
2019-05-27 02:54:23 +03:00
snd=(map bone message-pump-state)
2019-05-25 08:53:29 +03:00
rcv=(map bone rcv-state)
2019-05-27 04:38:19 +03:00
nax=(set [=bone =message-num])
2019-05-25 08:53:29 +03:00
==
2019-05-27 04:38:19 +03:00
:: $ossuary: bone<->duct bijection and .next-bone to map to a duct
2019-05-25 08:53:29 +03:00
::
+$ ossuary
2019-05-27 04:38:19 +03:00
$: =next=bone
2019-05-25 08:53:29 +03:00
by-duct=(map duct bone)
by-bone=(map bone duct)
==
:: $pending-actions: what to do when we learn a peer's life and keys
::
+$ pending-actions
$: rcv-packets=(list [=lane =packet])
snd-messages=(list [=duct =message])
==
2019-05-27 06:22:38 +03:00
:: $message-pump-state: persistent state for |message-pump
::
:: next-message-num: sequence number of next message to send
:: unsent-messages: messages to be sent after current message
:: unsent-fragments: fragments of current message waiting for sending
:: unacked-fragments: number of fragments waiting on ack
:: packet-pump-state: state of corresponding |packet-pump
::
2019-05-27 02:54:23 +03:00
+$ message-pump-state
2019-05-27 04:38:19 +03:00
$: =next=message-num
2019-05-27 02:54:23 +03:00
unsent-messages=(qeu message)
2019-05-27 04:38:19 +03:00
unsent-fragments=(list static-fragment)
2019-05-27 06:22:38 +03:00
unacked-fragments=(map message-num fragment-num)
2019-05-27 02:54:23 +03:00
=packet-pump-state
==
2019-05-27 06:22:38 +03:00
:: $packet-pump-state: persistent state for |packet-pump
::
:: next-wake: last timer we've set, or null
:: live: packets in flight
:: lost: packets to retry
:: pump-metrics: congestion control information
::
2019-05-27 02:54:23 +03:00
+$ packet-pump-state
$: next-wake=(unit @da)
2019-05-27 04:38:19 +03:00
live=(tree live-fragment)
lost=(tree static-fragment)
=pump-metrics
2019-05-27 02:54:23 +03:00
==
2019-05-27 04:38:19 +03:00
+$ pump-metrics
2019-05-27 04:48:41 +03:00
$: num-live=@ud
num-lost=@ud
last-sent-at=@da
last-dead-at=@da
rtt=@dr
max-live=@ud
==
2019-05-27 04:38:19 +03:00
+$ live-fragment
$: sent-at=@da
dead-at=@da
retried=?
static-fragment
==
+$ static-fragment
$: =message-num
num-fragments=fragment-num
2019-05-27 02:54:23 +03:00
=fragment-num
=fragment
2019-05-25 08:53:29 +03:00
==
+$ rcv-state
2019-05-27 04:38:19 +03:00
$: last-acked=message-num
last-heard=message-num
pending-vane-ack=(qeu [=message-num =message])
live-messages=(map message-num partial-rcv-message)
nax=(set message-num)
==
+$ partial-rcv-message
$: num-fragments=fragment-num
num-received=fragment-num
fragments=(map fragment-num fragment)
2019-05-25 08:53:29 +03:00
==
2019-05-27 06:22:38 +03:00
::
+| %dialectics
::
:: $message-pump-task: job for |message-pump
::
:: %send: packetize and send application-level message
:: %hear-ack: deal with a packet acknowledgment
:: %hear-nack: deal with message negative acknowledgment
:: %wake: handle timer firing
::
+$ message-pump-task
$% [%send =message-num =message]
[%hear-ack =message-num =fragment-num]
[%hear-nack =message-num]
[%wake ~]
==
:: $message-pump-gift: effect from |message-pump
::
:: %send: emit message fragment
:: %ack-message: report message acknowledgment
:: %set-timer: set a new timer at .date
:: %unset-timer: cancel timer at .date
::
+$ message-pump-gift
$% [%send =static-fragment]
[%ack-message =message-num error=(unit error)]
[%set-timer date=@da]
[%unset-timer date=@da]
==
:: $packet-pump-task: job for |packet-pump
::
:: %hear-ack: deal with a packet acknowledgment
:: %hear-nack: deal with message negative acknowledgment
:: %flush: finalize this event (i.e. maybe set timer)
:: %send: enqueue or emit message fragments
:: %wake: handle timer firing
::
+$ packet-pump-task
$% [%hear-ack =message-num =fragment-num]
[%hear-nack =message-num]
[%flush ~]
[%send fragments=(list static-fragment)]
[%wake ~]
==
:: $packet-pump-gift: effect from |packet-pump
::
:: %ack-fragment: report fresh ack on a message fragment
:: %send: emit message fragment
:: %set-timer: set a new timer at .date
:: %unset-timer: cancel timer at .date
::
+$ packet-pump-gift
$% [%ack-fragment =message-num =fragment-num]
[%send =static-fragment]
[%set-timer date=@da]
[%unset-timer date=@da]
==
2019-05-25 05:03:33 +03:00
--
|%
2019-05-25 08:53:29 +03:00
:: +encode-packet: serialize a packet into a bytestream
2019-05-25 05:03:33 +03:00
::
++ encode-packet
|= packet
2019-05-25 08:53:29 +03:00
^- blob
2019-05-25 05:03:33 +03:00
::
=/ sndr-meta (encode-ship-metadata sndr.dyad)
=/ rcvr-meta (encode-ship-metadata rcvr.dyad)
:: body: <<sndr rcvr (jam [origin content])>>
::
2019-05-25 08:53:29 +03:00
:: The .sndr and .rcvr ship addresses are encoded with fixed
:: lengths specified by the packet header. They live outside
:: the jammed-data section to simplify packet filtering in the
:: interpreter.
::
2019-05-25 05:03:33 +03:00
=/ body=@
;: mix
sndr.dyad
(lsh 3 size.sndr-meta rcvr.dyad)
(lsh 3 (add size.sndr-meta size.rcvr-meta) (jam [origin content]))
==
:: header: 32-bit header assembled from bitstreams of fields
::
:: <<version checksum sndr-rank rcvr-rank encryption-type unused>>
:: 4 bits at the end of the header are unused.
::
2019-05-25 08:53:29 +03:00
=/ header=@
2019-05-25 05:03:33 +03:00
%+ can 0
:~ [3 protocol-version]
[20 (mug body)]
[2 rank.sndr-meta]
[2 rank.rcvr-meta]
[5 ?:(encrypted %0 %1)]
==
:: result is <<header body>>
::
(mix header (lsh 5 1 body))
2019-05-25 08:53:29 +03:00
:: +decode-packet: deserialize packet from bytestream or crash
2019-05-25 05:03:33 +03:00
::
++ decode-packet
2019-05-25 08:53:29 +03:00
|= =blob
2019-05-25 05:03:33 +03:00
^- packet
:: first 32 (2^5) bits are header; the rest is body
::
=/ header (end 5 1 blob)
=/ body (rsh 5 1 blob)
::
=/ version (end 0 3 header)
=/ checksum (cut 0 [3 20] header)
=/ sndr-size (decode-ship-size (cut 0 [23 2] header))
=/ rcvr-size (decode-ship-size (cut 0 [25 2] header))
=/ encrypted ?+((cut 0 [27 5] header) !! %0 %.y, %1 %.n)
::
?> =(protocol-version version)
?> =(checksum (end 0 20 (mug body)))
::
=/ =dyad
:- sndr=(end 3 sndr-size body)
rcvr=(cut 3 [sndr-size rcvr-size] body)
::
=+ ;; [origin=(unit @uxlane) content=*]
%- cue
(rsh 3 (add rcvr-size sndr-size) body)
::
[dyad encrypted origin content]
:: +decode-ship-size: decode a 2-bit ship type specifier into a byte width
::
:: Type 0: galaxy or star -- 2 bytes
:: Type 1: planet -- 4 bytes
:: Type 2: moon -- 8 bytes
:: Type 3: comet -- 16 bytes
::
++ decode-ship-size
|= rank=@
^- @
::
?+ rank !!
%0 2
%1 4
%2 8
%3 16
==
2019-05-25 08:53:29 +03:00
:: +encode-ship-metadata: produce size (in bytes) and address rank for .ship
2019-05-25 05:03:33 +03:00
::
:: 0: galaxy or star
:: 1: planet
:: 2: moon
:: 3: comet
::
++ encode-ship-metadata
|= =ship
^- [size=@ =rank]
::
=/ size=@ (met 3 ship)
::
?: (lte size 2) [2 %0]
?: (lte size 4) [4 %1]
?: (lte size 8) [8 %2]
[16 %3]
--