mirror of
https://github.com/urbit/shrub.git
synced 2024-12-14 20:02:51 +03:00
more %ames doc
This commit is contained in:
parent
2f24750980
commit
1eee97237f
@ -1,7 +1,574 @@
|
|||||||
Ames
|
Ames
|
||||||
====
|
====
|
||||||
|
|
||||||
Ames is our networking protocol.
|
Ames is our networking protocol. First we give an informal, high-level
|
||||||
|
description of the protocol, then we get into the nitty-gritty of the
|
||||||
|
implementation.
|
||||||
|
|
||||||
|
The Lifecycle of a Packet (or, How a Packet Becomes Law)
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
|
Here, we will trace a packet as it makes its way through ames. There are
|
||||||
|
actually two pathways through ames: the legacy path through `%want`, and the
|
||||||
|
modern way, entered through `%wont`, with full end-to-end acknowledgments.
|
||||||
|
Here we will only trace the modern way.
|
||||||
|
|
||||||
|
When an app (or a vane) wishes to send a packet to another ship, it must send a
|
||||||
|
`%wont` card:
|
||||||
|
|
||||||
|
```
|
||||||
|
[%wont p=sock q=path r=*] :: e2e send message
|
||||||
|
```
|
||||||
|
|
||||||
|
This card takes three arguments. The `p` is a `sock`, that is, a pair of two
|
||||||
|
ships, the first of which is the sender and the second is the receiver. But
|
||||||
|
wait, you ask, why do I get to decide who is the sender? Can I fake like I'm
|
||||||
|
someone else? The reason is that there are potentially multiple ships on the
|
||||||
|
same pier, and the kernel can send a message from any of them. If you attempt
|
||||||
|
to send a message from a ship not on your pier, then ames will refuse to send
|
||||||
|
it. If you hack around in your own copy of ames to go ahead and send it
|
||||||
|
anyway, then the other ship will reject it because your key is bad. Basically,
|
||||||
|
only send messages from yourself.
|
||||||
|
|
||||||
|
The `q` is a path, representing the place on the other side that you want to
|
||||||
|
receive your message. It is approximately equivalent to a port number.
|
||||||
|
Messages on the same path are guaranteed to arrive in the same order as they
|
||||||
|
were sent. No such guarantees are made across paths.
|
||||||
|
|
||||||
|
The `r` is the actual data that you are sending. As the type implies, this can
|
||||||
|
be an arbitrary noun, and it will be transferred to the receiver exactly as-is,
|
||||||
|
in a well-typed way. Of course, this is data that is sent over the wire, so be
|
||||||
|
careful not to send anything too massive unless you're willing to wait.
|
||||||
|
|
||||||
|
But enough about the interface. Grepping in ames.hoon for `%wont`, we find
|
||||||
|
that it appears in exactly two places: at its definition in `++kiss`, and in
|
||||||
|
`++knob`, where it is handled. We see that we go directly into `++wise:am`.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ wise :: wise:am
|
||||||
|
|= [soq=sock hen=duct cha=path val=* ete=?] :: send a statement
|
||||||
|
^- [p=(list boon) q=fort]
|
||||||
|
zork:zank:(wool:(ho:(um p.soq) q.soq) hen cha val ete)
|
||||||
|
```
|
||||||
|
|
||||||
|
The inputs to this gate are exactly the sort of thing you'd expect. In
|
||||||
|
particular, everything in the `%wont` gate is here plus the calling `duct` so
|
||||||
|
that we know where to send the acknowledgment and `ete` to determine if we're
|
||||||
|
going to do the modern end-to-end acknowledgments.
|
||||||
|
|
||||||
|
The actual line of code looks intimidating, but it's really not all that bad.
|
||||||
|
Working from the inside out, the call to `++um` sets up our domestic server,
|
||||||
|
and the call to `++ho` sets up our knowledge about the neighbor we're sending
|
||||||
|
to. From the outside, `++zork` and `++zank` just apply the changes made to our
|
||||||
|
`++um` and `++am` cores, respectively. If you're familiar with the common
|
||||||
|
idiom of `++abet`, that's all this is. The code predates the widespread usage
|
||||||
|
of that name.
|
||||||
|
|
||||||
|
The interesting part, then, is in `++wool:ho:um:am`. Let's look at the code.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ wool :: wool:ho:um:am
|
||||||
|
|= [hen=duct cha=path val=* ete=?] :: send a statement
|
||||||
|
^+ +>
|
||||||
|
=+ ^= rol ^- rill
|
||||||
|
=+ rol=(~(get by ryl.bah) cha)
|
||||||
|
?~(rol *rill u.rol)
|
||||||
|
=+ sex=sed.rol
|
||||||
|
:: ~& [%tx [our her] cha sex]
|
||||||
|
=. ryl.bah
|
||||||
|
%+ ~(put by ryl.bah) cha
|
||||||
|
rol(sed +(sed.rol), san (~(put by san.rol) sex hen))
|
||||||
|
=+ cov=[p=p:sen:gus q=clon:diz]
|
||||||
|
%+ wind [cha sex]
|
||||||
|
?: ete
|
||||||
|
[%bund q.cov cha sex val]
|
||||||
|
[%bond q.cov cha sex val]
|
||||||
|
```
|
||||||
|
|
||||||
|
This is slightly more complicated, but it's still not all that bad. Our
|
||||||
|
inputs, at least, are fairly obvious.
|
||||||
|
|
||||||
|
If you glance at the code for a second, you'll see that `++wind:ho:um:am` seems
|
||||||
|
to be able to send a message, or `++meal`, given a `++soup`. This gate, then,
|
||||||
|
just sets up the things we need to for `++wind` to do its job.
|
||||||
|
|
||||||
|
We first get `rol`, which is a `++rill`, that is, a particular outbound stream.
|
||||||
|
This stream is specific to the path on which we're sending. If the path hasn't
|
||||||
|
been used before, then we create it. We let `sex` be the number of messages
|
||||||
|
we've already sent on this path.
|
||||||
|
|
||||||
|
Then, we update the outbound stream by incrementing the number of messages sent
|
||||||
|
and placing an entry in `san.rol` that associates the message number with the
|
||||||
|
`duct` that sent the message. This allows us to give the acknowledgment to the
|
||||||
|
one who sent the message.
|
||||||
|
|
||||||
|
We let `cov` be the current life of our crypto and our neighbor's crypto. At
|
||||||
|
the moment, we only need our neighbor's life, which we put into the meal.
|
||||||
|
|
||||||
|
Finally, we call `++wind:ho:um:am` with the `++soup` of the path and message
|
||||||
|
number and the `++meal` of the payload itself. For end-to-end acknowledged
|
||||||
|
messages, we use `%bund`.
|
||||||
|
|
||||||
|
```
|
||||||
|
[%bund p=life q=path r=@ud s=*] :: e2e message
|
||||||
|
```
|
||||||
|
|
||||||
|
Looking at how we create the `%bund`, we can easily see what each field is for.
|
||||||
|
|
||||||
|
Following the trail a little further, we go to `++wind:ho:um:am`.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ wind :: wind:ho:um:am
|
||||||
|
|= [gom=soup ham=meal]
|
||||||
|
:: ~& [%wind her gom]
|
||||||
|
^+ +>
|
||||||
|
=^ wyv diz (zuul:diz now ham)
|
||||||
|
=^ feh puz (whap:puz now gom wyv)
|
||||||
|
(busk xong:diz feh)
|
||||||
|
```
|
||||||
|
|
||||||
|
`++wind` does three things: it (1) encodes the message into a list of
|
||||||
|
possibly-encrypted packets, (2) puts the message into the packet pump, and (3)
|
||||||
|
sends any packets that are ready to be sent. Yes, our nice little linear run
|
||||||
|
of each gate calling exactly one other interesting gate is over. We'll go in
|
||||||
|
order here.
|
||||||
|
|
||||||
|
`++zuul:lax:as:go` is the what converts a `++meal` into a list of actual, 1KB
|
||||||
|
packets.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ zuul :: zuul:lax:as:go
|
||||||
|
|= [now=@da ham=meal] :: encode message
|
||||||
|
^- [p=(list rock) q=_+>]
|
||||||
|
=< weft
|
||||||
|
++ wasp :: null security
|
||||||
|
++ weft :: fragment message
|
||||||
|
++ wisp :: generate message
|
||||||
|
```
|
||||||
|
|
||||||
|
For organizational purposes, `++zuul` constructs an internal core with three
|
||||||
|
arms. `++wasp` encodes the meal into an atom with no encryption. `++wisp`
|
||||||
|
encodes a meal with possible encryption (else it simply calls `++wasp`).
|
||||||
|
`++weft` takes the result of `++wisp` and splits it into actual packets.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ wasp :: null security
|
||||||
|
^-([p=skin q=@] [%none (jam ham)])
|
||||||
|
```
|
||||||
|
|
||||||
|
This simply jams the meal, wrapping it with the `skin` of `%none`, meaning no
|
||||||
|
encryption.
|
||||||
|
|
||||||
|
Since `++wisp` is a little long, we'll go through it line-by-line.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ wisp :: generate message
|
||||||
|
^- [[p=skin q=@] q=_..wisp]
|
||||||
|
```
|
||||||
|
|
||||||
|
`++wisp` produces a pair of a `skin` and an atom, which is the meal encoded as
|
||||||
|
a single atom and possibly encrypted.
|
||||||
|
|
||||||
|
```
|
||||||
|
?: =(%carp -.ham)
|
||||||
|
[wasp ..wisp]
|
||||||
|
```
|
||||||
|
|
||||||
|
If the meal that we're encoding is a `%carp`, then we don't encrypt it. A
|
||||||
|
`%carp` meal is a partial meal, used when a message is more than 1KB. Since
|
||||||
|
the entire message is already encrypted, we don't need to encrypt each packet
|
||||||
|
individually again.
|
||||||
|
|
||||||
|
```
|
||||||
|
?: !=(~ yed.caq.dur)
|
||||||
|
?> ?=(^ yed.caq.dur)
|
||||||
|
:_ ..wisp
|
||||||
|
:- %fast
|
||||||
|
%^ cat 7
|
||||||
|
p.u.yed.caq.dur
|
||||||
|
(en:r:cluy q.u.yed.caq.dur (jam ham))
|
||||||
|
```
|
||||||
|
|
||||||
|
If we have a symmetric key set up with this neighbor, then we simply use it.
|
||||||
|
The skin `%fast` is used to indicate a symmetric key.
|
||||||
|
|
||||||
|
```
|
||||||
|
?: &(=(~ lew.wod.dur) |(=(%back -.ham) =(%buck -.ham)))
|
||||||
|
[wasp ..wisp]
|
||||||
|
```
|
||||||
|
|
||||||
|
If we do not yet have our neighbor's will, then there is no way that we can
|
||||||
|
seal the message so that only they may read it. If what we're sending is an
|
||||||
|
acknowledgment, then we go ahead and just send it in the clear.
|
||||||
|
|
||||||
|
```
|
||||||
|
=^ tuy +>.$
|
||||||
|
?:(=(~ lew.wod.dur) [*code +>.$] (griz now))
|
||||||
|
```
|
||||||
|
|
||||||
|
If we don't have our neighbor's will, then we "encrypt" with a key of 0. If we
|
||||||
|
do have their will, then we generate a new symmetric key that we will propose.
|
||||||
|
|
||||||
|
```
|
||||||
|
:_ ..wisp
|
||||||
|
=+ yig=sen
|
||||||
|
=+ bil=law.saf :: XX send whole will
|
||||||
|
=+ hom=(jam ham)
|
||||||
|
```
|
||||||
|
|
||||||
|
`yig` will be the life and engine for our current crypto. `bil` is our will.
|
||||||
|
`hom` is the meal encoded as a single atom.
|
||||||
|
|
||||||
|
```
|
||||||
|
?: =(~ lew.wod.dur)
|
||||||
|
:- %open
|
||||||
|
%^ jam
|
||||||
|
[~ `life`p.yig]
|
||||||
|
bil
|
||||||
|
(sign:as:q.yig tuy hom)
|
||||||
|
```
|
||||||
|
|
||||||
|
If we do not have our neighbor's will, then we send our current life along with
|
||||||
|
our will and the message. The message itself is "signed" with a key of 0.
|
||||||
|
|
||||||
|
```
|
||||||
|
:- %full
|
||||||
|
=+ cay=cluy
|
||||||
|
%^ jam
|
||||||
|
[`life`p.cay `life`p.yig]
|
||||||
|
bil
|
||||||
|
(seal:as:q.yig pub:ex:r.cay tuy hom)
|
||||||
|
-- :: --zuul:lax:as:go
|
||||||
|
```
|
||||||
|
|
||||||
|
If we do have our neighbor's will, then we send our perception of their current
|
||||||
|
life, our current life, our will, and the message. The message is sealed with
|
||||||
|
their public key so that only they can read our message.
|
||||||
|
|
||||||
|
Once we have the message encoded as an atom, `++weft` goes to work.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ weft :: fragment message
|
||||||
|
^- [p=(list rock) q=_+>.$]
|
||||||
|
=^ gim ..weft wisp
|
||||||
|
:_ +>.$
|
||||||
|
^- (list rock)
|
||||||
|
```
|
||||||
|
|
||||||
|
We're going to produce a list of the packets to send. First, we use the
|
||||||
|
aforementioned `++wisp` to get the message as an atom.
|
||||||
|
|
||||||
|
```
|
||||||
|
=+ wit=(met 13 q.gim)
|
||||||
|
?< =(0 wit)
|
||||||
|
```
|
||||||
|
|
||||||
|
`wit` is the number of 1KB (2^13 bit) blocks in the message. We assert that
|
||||||
|
there is at least one block.
|
||||||
|
|
||||||
|
```
|
||||||
|
?: =(1 wit)
|
||||||
|
=+ yup=(spit [our her] p.gim q.gim)
|
||||||
|
[yup ~]
|
||||||
|
```
|
||||||
|
|
||||||
|
If there is exactly one block, then we just call `++spit` to turn the message
|
||||||
|
into a packet. We'll explain what `++spit` does momentarily.
|
||||||
|
|
||||||
|
```
|
||||||
|
=+ ruv=(rip 13 q.gim)
|
||||||
|
=+ gom=(shaf %thug q.gim)
|
||||||
|
=+ inx=0
|
||||||
|
```
|
||||||
|
|
||||||
|
If there is more than one block, then we rip it into blocks in `ruv`. `gom` is
|
||||||
|
a hash of the message, used as an id. `inx` is the number of packets we've
|
||||||
|
already made.
|
||||||
|
|
||||||
|
```
|
||||||
|
|- ^- (list rock)
|
||||||
|
?~ ruv ~
|
||||||
|
=+ ^= vie
|
||||||
|
%+ spit
|
||||||
|
[our her]
|
||||||
|
wasp(ham [%carp (ksin p.gim) inx wit gom i.ruv])
|
||||||
|
:- vie
|
||||||
|
$(ruv t.ruv, inx +(inx))
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we package each block into a packet with `++spit` and produce the list of
|
||||||
|
packets.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ spit :: cake to packet
|
||||||
|
|= kec=cake ^- @
|
||||||
|
=+ wim=(met 3 p.p.kec)
|
||||||
|
=+ dum=(met 3 q.p.kec)
|
||||||
|
=+ yax=?:((lte wim 2) 0 ?:((lte wim 4) 1 ?:((lte wim 8) 2 3)))
|
||||||
|
=+ qax=?:((lte dum 2) 0 ?:((lte dum 4) 1 ?:((lte dum 8) 2 3)))
|
||||||
|
=+ wix=(bex +(yax))
|
||||||
|
=+ vix=(bex +(qax))
|
||||||
|
=+ bod=:(mix p.p.kec (lsh 3 wix q.p.kec) (lsh 3 (add wix vix) r.kec))
|
||||||
|
=+ tay=(ksin q.kec)
|
||||||
|
%+ mix
|
||||||
|
%+ can 0
|
||||||
|
:~ [3 1]
|
||||||
|
[20 (mug bod)]
|
||||||
|
[2 yax]
|
||||||
|
[2 qax]
|
||||||
|
[5 tay]
|
||||||
|
==
|
||||||
|
(lsh 5 1 bod)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is how we turn a message into a real packet. This has the definition of
|
||||||
|
the packet format.
|
||||||
|
|
||||||
|
`wim` is the length of the sending ship, and `dum` is the length of the
|
||||||
|
receiving ship. There are only five possibilities for each of those,
|
||||||
|
corresponding to carriers, cruisers, destroyers, yachts, and submarines. These
|
||||||
|
are encoded in `yax` and `qax` as 0, 0, 1, 2, and 3, respectively. Thus, `wix`
|
||||||
|
and `vix` are the number of bytes that must be reserved for the ship names in a
|
||||||
|
packet.
|
||||||
|
|
||||||
|
Next, we construct `bod` by simply concatenating the sending ship, the
|
||||||
|
receiving ship, and the body of the message. Then, we get the encryption
|
||||||
|
mechanism from `++skin`, which may be a 0, 1, 2, or 3, and put it in `tay`.
|
||||||
|
|
||||||
|
Next, we concatenate together, bit by bit, some final metadata. We use three
|
||||||
|
bits for our protocol number, which is incremented modulo eight when there is a
|
||||||
|
continuity breach or the protocol changes. We use the final twenty bits of a
|
||||||
|
hash of the body for error-checking. We use two bits to tell how much room is
|
||||||
|
used in the body for the sending ship, and another two bits for the receiving
|
||||||
|
ship. Finally, we use five bits to store the encryption type. Note that since
|
||||||
|
there are only two bits worth of encryption types, there are three unused bits
|
||||||
|
here. This adds up to 32 bits of header data. Finally, we concatenate this
|
||||||
|
onto the front of the packet. Thus, we can summarize the packet header format
|
||||||
|
as follows.
|
||||||
|
|
||||||
|
```
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|Proto| Hash of Body |yax|qax| Crypto |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, there are `yax` bits of the sender name, `qax` bits of the receiver
|
||||||
|
name, and up to 8192 bits of data. Thus, the maximum size of a packet is
|
||||||
|
achieved in a message between two submarines with 8192 bits of data. This will
|
||||||
|
require 32+128+128+8192 = 8480 bits, or 1060 bytes.
|
||||||
|
|
||||||
|
This concludes our discussion of `++zuul:lax:as:go`. If you recall from
|
||||||
|
`++wind:ho:um:am`, the list of packets from `++zuul` is passed into `++whap:pu`
|
||||||
|
to update the packet pump and get any packets that can be sent immediately.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ whap :: whap:pu
|
||||||
|
|= [now=@da gom=soup wyv=(list rock)] :: send a message
|
||||||
|
^- [(list rock) _+>]
|
||||||
|
=. pyz (~(put by pyz) gom (lent wyv))
|
||||||
|
=. +>
|
||||||
|
|- ^+ +>.^$
|
||||||
|
?~ wyv +>.^$
|
||||||
|
%= $
|
||||||
|
wyv t.wyv
|
||||||
|
nus +(nus)
|
||||||
|
diq (~(put by diq) (shaf %flap i.wyv) nus)
|
||||||
|
puq (~(put to puq) [nus `soul`[gom 0 | ~2000.1.1 i.wyv]])
|
||||||
|
==
|
||||||
|
(harv now)
|
||||||
|
```
|
||||||
|
|
||||||
|
First, we put into `pyz` the id for this message and the number of its packets
|
||||||
|
that have not yet been acknowledged, which is of course the total number of
|
||||||
|
packets since we haven't even sent the packets.
|
||||||
|
|
||||||
|
For every packet, we change three things in the state (`++shed`) of our packet
|
||||||
|
pump: (1) we increment `nus`, the number of packets sent; (2) we put the
|
||||||
|
packet number into `diq` keyed by a hash of the packet; and (3) we put the
|
||||||
|
packet into the packet queue, with the basic metadata of its id `gom`, 0
|
||||||
|
transmissions, not live yet, last sent in the year 2000, and the packet itself.
|
||||||
|
|
||||||
|
Finally, we harvest the packet pump.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ harv :: harv:pu
|
||||||
|
|= now=@da :: harvest queue
|
||||||
|
^- [(list rock) _+>]
|
||||||
|
?: =(~ puq) [~ +>(rtn ~)]
|
||||||
|
?. (gth caw nif) [~ +>]
|
||||||
|
=+ wid=(sub caw nif)
|
||||||
|
=| rub=(list rock)
|
||||||
|
=< abet =< apse
|
||||||
|
|%
|
||||||
|
```
|
||||||
|
|
||||||
|
`++harv` contains a core for most of its work. The meat is in `++apse`.
|
||||||
|
First, though, it sets itself up. If there aren't any packets in the queue,
|
||||||
|
then we simply do nothing except set `rtn`, our next timeout, to nil because we
|
||||||
|
don't have any packets that may need to be retransmitted. If we have more live
|
||||||
|
(that is, sent and unacknowledged) packets than our window size, then we don't
|
||||||
|
do anything.
|
||||||
|
|
||||||
|
Otherwise, we let `wid` be the width of our remaining packet window, and we
|
||||||
|
initialize `rub` to nil. `rub` will be the list of packets that are ready to
|
||||||
|
be sent. We then call `++apse` and pass the result to `++abet`. `++apse`
|
||||||
|
decides which packets are ready to be sent.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ apse
|
||||||
|
^+ .
|
||||||
|
?~ puq .
|
||||||
|
?: =(0 wid) .
|
||||||
|
=> rigt =< left
|
||||||
|
?> ?=(^ puq)
|
||||||
|
?: =(0 wid) .
|
||||||
|
?. =(| liv.q.n.puq) .
|
||||||
|
:: ~& [%harv nux.q.n.puq p.n.puq]
|
||||||
|
%_ .
|
||||||
|
wid (dec wid)
|
||||||
|
rub [pac.q.n.puq rub]
|
||||||
|
nif +(nif)
|
||||||
|
liv.q.n.puq &
|
||||||
|
nux.q.n.puq +(nux.q.n.puq)
|
||||||
|
lys.q.n.puq now
|
||||||
|
==
|
||||||
|
```
|
||||||
|
|
||||||
|
If there are no remaining packets to send, or if we've filled the packet
|
||||||
|
window, do nothing. We call `++rigt` and `++left` to process the left and
|
||||||
|
right branches of the packet queue.
|
||||||
|
|
||||||
|
Now we assert that the queue is not empty, and we again check that we haven't
|
||||||
|
filled the packet window. We will operate on the head of the queue. If the
|
||||||
|
packet is live, then do nothing. Otherwise, we go ahead and send it.
|
||||||
|
|
||||||
|
To send, we (1) decrement `wid`, our packet window width; (2) cons the packet
|
||||||
|
onto the `rub`, which will be returned as the list of packets to send; (3)
|
||||||
|
increment `nif`, the number of live packets; (4) set the packet to be live;
|
||||||
|
(5) increment the number of transmissions of the packet; and (6) set the last
|
||||||
|
sent time of the packet to now.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ left
|
||||||
|
?> ?=(^ puq)
|
||||||
|
^+(. =+(lef=apse(puq l.puq) lef(puq [n.puq puq.lef r.puq])))
|
||||||
|
++ rigt
|
||||||
|
?> ?=(^ puq)
|
||||||
|
^+(. =+(rig=apse(puq r.puq) rig(puq [n.puq l.puq puq.rig])))
|
||||||
|
```
|
||||||
|
|
||||||
|
These do exactly what you would expect: they traverse the packet queue so that
|
||||||
|
`++apse` gets called recursively through it.
|
||||||
|
|
||||||
|
Finally, `++abet` gets called, which resolves the changes.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ abet
|
||||||
|
?~ rub [~ +>.$]
|
||||||
|
[(flop rub) +>.$(rtn [~ (add rto now)])]
|
||||||
|
```
|
||||||
|
|
||||||
|
This returns the packets that we wish to send, and it updates the timeout so
|
||||||
|
that we know when to try resending unacknowledged packets.
|
||||||
|
|
||||||
|
This concludes our discussion of `++whap:pu`. To finish `++wind:ho:um:am`, we
|
||||||
|
just need to delve into `++busk:ho:um:am`. But wait, in the call to `++busk`,
|
||||||
|
the first argument is `xong:diz`. What is this? This, my dear reader, is one
|
||||||
|
more detour, this time into `++xong:lax:as:go`.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ xong :: xong:lax:as:go
|
||||||
|
^- (list ship) :: route unto
|
||||||
|
=+ [fro=xen too=xeno]
|
||||||
|
=+ ^= oot ^- (list ship)
|
||||||
|
=| oot=(list ship)
|
||||||
|
|- ^+ oot
|
||||||
|
?~ too ~
|
||||||
|
?: (lien fro |=(a=ship =(a i.too))) ~
|
||||||
|
[i.too $(too t.too)]
|
||||||
|
:: ~& [%xong-to [our her] (weld oot ?>(?=(^ fro) t.fro))]
|
||||||
|
(weld oot ?>(?=(^ fro) t.fro))
|
||||||
|
```
|
||||||
|
|
||||||
|
This gets the list of intermediate ships needed to get a packet from us to our
|
||||||
|
neighbor. First, we get `fro` and `too`, the "canons" of ourself and our
|
||||||
|
neighbor, respectively.
|
||||||
|
|
||||||
|
What is this "canon", you ask? A canon is simply a ship plus its "ancestors",
|
||||||
|
as defined by `++sein`. For example, the canon of `~hoclur-bicrel` is:
|
||||||
|
|
||||||
|
```
|
||||||
|
~hoclur-bicrel/try=> (saxo ~hoclur-bicrel)
|
||||||
|
~[~hoclur-bicrel ~tasruc ~tug]
|
||||||
|
```
|
||||||
|
|
||||||
|
If we follow the algorithm in `++xong`, we see that we are simply creating a
|
||||||
|
list of ships that form a path from our neighbor to ourself. Essentially, we
|
||||||
|
look through the canon of our neighbor until we find something in our own
|
||||||
|
cannon -- a common ancestor. Or, if we are from different carriers, then there
|
||||||
|
is no common ancestor. We then weld this onto the tail of our own canon. In
|
||||||
|
the end, this is simply a list of possible ships to try to route via to get to
|
||||||
|
our neighbor, ordered by preferability (that is, closeness to our neighbor).
|
||||||
|
We will end up trying, in order, to find a lane to these.
|
||||||
|
|
||||||
|
Now, we can finally get to `++busk:ho:um:am`.
|
||||||
|
|
||||||
|
```
|
||||||
|
++ busk :: busk:ho:um:am
|
||||||
|
|= [waz=(list ship) pax=(list rock)] :: send packets
|
||||||
|
%_ +>
|
||||||
|
bin
|
||||||
|
|- ^+ bin
|
||||||
|
?~ pax bin
|
||||||
|
$(pax t.pax, bin (weld (flop (wist:diz now waz ~ i.pax)) bin))
|
||||||
|
==
|
||||||
|
```
|
||||||
|
|
||||||
|
Thankfully, `++busk` is fairly simple. We go through the list of packets and
|
||||||
|
convert them to `++boon`s with `++wist:lax:as:go`. These boons are placed into
|
||||||
|
`bin`, and they end up getting processed by `++clop` (this happens in
|
||||||
|
`++knob`).
|
||||||
|
|
||||||
|
```
|
||||||
|
++ wist :: wist:lax:as:go
|
||||||
|
|= $: now=@da :: route via
|
||||||
|
waz=(list ,@p)
|
||||||
|
ryn=(unit lane)
|
||||||
|
pac=rock
|
||||||
|
==
|
||||||
|
^- (list boon)
|
||||||
|
?: =(our her) [[%ouzo *lane pac] ~]
|
||||||
|
?~ waz ~
|
||||||
|
=+ dyr=?:(=(her i.waz) dur (gur i.waz))
|
||||||
|
?. ?& !=(our i.waz)
|
||||||
|
?=(^ lun.wod.dyr)
|
||||||
|
==
|
||||||
|
$(waz t.waz)
|
||||||
|
:_ ?: ?=(%ix -.u.lun.wod.dyr)
|
||||||
|
$(waz t.waz)
|
||||||
|
~
|
||||||
|
:+ %ouzo u.lun.wod.dyr
|
||||||
|
?: &(=(i.waz her) =(~ ryn)) pac
|
||||||
|
=+ mal=(jam `meal`[%fore her ryn pac])
|
||||||
|
%- spit
|
||||||
|
^- cake
|
||||||
|
:* [our i.waz]
|
||||||
|
?~ yed.caq.dyr [%none mal]
|
||||||
|
:- %fast
|
||||||
|
%^ cat 7
|
||||||
|
p.u.yed.caq.dyr
|
||||||
|
(en:crua q.u.yed.caq.dyr mal)
|
||||||
|
==
|
||||||
|
```
|
||||||
|
|
||||||
|
This takes a sample of the current time, the list of ships that we just
|
||||||
|
generated, a lane if we already know it, and the packet itself.
|
||||||
|
|
||||||
|
First, if we are sending a message to ourself, then we simply create a `%ouzo`
|
||||||
|
boon with a bunted lane. Otherwise, if there are no routing candidates, there
|
||||||
|
is nothing we can do, so we return nil.
|
||||||
|
|
||||||
data models
|
data models
|
||||||
-----------
|
-----------
|
||||||
|
Loading…
Reference in New Issue
Block a user