Holdings said who holds what; nothing fetched from them. extensions/peer-delivery.md is the exchange, over records WAI already had: a neighbour's holdings record says where to ask, the publisher's receipts say what the answer must be, and the asker's settlement acknowledgement says what was served. It registers no capability and no record, and is in the delivery profile. Joining is the sink's choice: asking a peer and serving one are each off unless the operator's policy and the viewer's setting both allow it, and the setting is off until the person using the sink turns it on. The exchange is eight frames over a link that keeps message boundaries (a data channel among them) and three HTTP requests for a neighbour reached that way. Peers are not trusted. An asker asks only for an object whose object receipt it holds, never names to a holder an object that holder has not listed, and takes nothing before the bytes verify. A holder that served other bytes is asked nothing more (peer_bytes_refused); a record that listed what its holder then said it lacks is set aside (holdings_overstated); a record that misstates a group's size was already refused whole. The asker signs one acknowledgement a holder a window for exactly what it verified, of class 2 with no grant, and the holder checks it against what it served. That settles what cache-authority §9 left open. §9 of the new text says what a peer learns of a viewer: an address, which of the objects it listed were asked for and when, and a session key where the asker acknowledges; and nothing else. wai-rs: feature peer_delivery (the gate, the frames, the asker, a holder's answer and its check of an acknowledgement), the corpus live-conformance/peer-delivery/ (92 cases) and its second implementation peer_delivery_verify.py, which rebuilds every acknowledgement byte for byte: 92 cases agree, 18 of 18 mutated copies and 22 of 22 weakened verifiers caught. wai-web: the peer bundle (src/peer.rs) and peer/: the client over data channels and fetch, objects kept in the Cache API, and two runs with faults planted on the holders' side. Under node, 33 checks (wai-web-wasm.yml runs it and has the Python verifier recompute the session from its signed acknowledgements). In a browser, 23 checks over seven pages: altered bytes refused and never acknowledged, an overstating record never asked, a peer that lists what it lacks set aside, and pages whose setting is off opening no connection. Co-Authored-By: Claude <[email protected]>
3.7 KiB
Peer delivery in a page
A page fetches objects from the neighbours that hold them before it asks the origin, holds every byte a peer sent to the object's receipt before it uses it, and signs for what it verified (peer-delivery). Asking and serving are each off until the person using the page turns them on, and the operator's policy must allow each as well.
| file | what it is |
|---|---|
peer.js |
the links (a data channel, HTTP) and the client and holder over them |
index.html, page.js |
the reference page: an asker or a holder, by its query |
harness.mjs |
what the two runs share: the bundle in node, the corpus's world, an HTTP holder |
verify_peer.mjs |
the run with no browser: links in memory and a local HTTP link, faults planted |
run.mjs |
the run in a browser: pages over data channels and a local HTTP link, faults planted |
Everything that decides is the peer bundle, src/peer.rs over wai::peer_delivery: the Rust a
native sink runs. peer.js carries frames and makes requests; it never hands on a byte the
bundle has not answered taken for.
Build
# from wai/wai-web
cargo build --locked --release --target wasm32-unknown-unknown --features peer
wasm-bindgen --target web --out-dir peer/pkg --out-name wai_peer \
"${CARGO_TARGET_DIR:-target}/wasm32-unknown-unknown/release/wai_web.wasm"
The runs
Both use the world of the conformance corpus
(live-conformance/peer-delivery/): group A of six
objects and group B of three, the second of B past one data frame. Every service they start
binds port 0 and prints its port, and is closed before the run exits.
node peer/verify_peer.mjs .. peer/pkg [session.json]
PLAYWRIGHT_HOST=<dir with node_modules/playwright>/ node peer/run.mjs .. [session.json]
python3 -I ../live-conformance/peer_delivery_verify.py --session session.json
verify_peer.mjs (33 checks) needs node alone. run.mjs (23 checks) opens seven pages in the
installed browser, each a profile of its own: a holder and an asker whose settings are left
off, four holders that serve over data channels, and an asker with asking on, which also
reaches a holder over HTTP on another origin. The faults are planted on the holders' side, and
the checks are on what the asker did:
| planted | held to |
|---|---|
| a peer serves an object with a byte changed | the bytes are refused at their receipt, the next holder serves, the peer is asked nothing more, and no acknowledgement names it |
| a record states a group one object larger than its receipt | the record is refused whole and its holder is never asked |
| a peer lists an object it does not hold | its miss sets the record aside, the origin serves, and the next object it listed is not asked of it |
| a setting left off | the page constructs no peer connection, sends no request to a holder and signs nothing |
Each run can write the session's acknowledgements to a file, and
peer_delivery_verify.py --session recomputes from those signed records alone what each holder
was acknowledged, with no code of the client's.
A browser gives a page its local addresses as names that resolve on the local network. Where
they do not resolve between two profiles of one machine (a session started over SSH, with no
multicast), no data channel opens and run.mjs fails at "four data channels and the HTTP link
are up", naming the kinds of candidate each connection offered. There,
WAI_PEER_BROWSER_ARGS="--disable-features=WebRtcHideLocalIpsWithMdns" has the browser offer
the addresses themselves, and the run is otherwise the same.
run.mjs is run by hand on a machine with a browser installed; verify_peer.mjs and the session
recomputation run in wai-web-wasm.yml.