diff --git a/main/pub/src/doc/ref/volumes/4/4a.md b/main/pub/src/doc/ref/volumes/4/4a.md index 8dc25825a0..d3a5b56760 100644 --- a/main/pub/src/doc/ref/volumes/4/4a.md +++ b/main/pub/src/doc/ref/volumes/4/4a.md @@ -1,7 +1,574 @@ 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 -----------