All checks were successful
wai-app and Create / wai-app · does this change touch it (push) Successful in 21s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 22s
wai-audit collector and player integration / does this change touch it (push) Successful in 21s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 12s
WAI operation catalogue / does this change touch it (push) Successful in 20s
wai-fleet service / does this change touch it (push) Successful in 26s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 36s
wai-key-proxy / does this change touch it (push) Successful in 32s
wai-ledger service / does this change touch it (push) Successful in 36s
wai-sink-android native sink library / does this change touch it (push) Successful in 41s
wai-quantum for JavaScript / does this change touch it (push) Successful in 43s
wai-quantum studio and desktop / does this change touch it (push) Successful in 42s
wai-sink-swift native sink package / does this change touch it (push) Successful in 32s
WAI Studio / does this change touch it (push) Successful in 1m26s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 3m9s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 4m33s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 4m25s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 7m26s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 9s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 8s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 55s
wai-key-proxy / proxy · linux (push) Successful in 1m39s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 58s
wai-fleet service / service · linux (push) Successful in 2m23s
wai-audit collector and player integration / collector · linux (push) Successful in 4m16s
wai-ledger service / service · linux (push) Successful in 4m44s
wai-quantum for JavaScript / javascript · linux (push) Successful in 6m3s
wai-quantum studio and desktop / studio · browser (push) Successful in 6m52s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 11m4s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 12m34s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 13m8s
WAI Studio / Studio in WebKit · linux (push) Successful in 21m51s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 3m17s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 4m57s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 5m41s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 8m57s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 20m35s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 9m58s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 1m17s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m13s
wai-fleet service / service · macos (push) Successful in 50s
wai-ledger service / service · macos (push) Successful in 1m12s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 7m48s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m7s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 6m40s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 14m55s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 10m22s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 15m8s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 7m7s
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]>
436 lines
26 KiB
Markdown
436 lines
26 KiB
Markdown
# WAI Extension: Peer Delivery
|
|
|
|
> Status: Draft. Registers no capability and no record. It is the exchange by which a sink
|
|
> fetches objects from the neighbours that hold them before it spends the backhaul, over the
|
|
> records other texts define: a neighbour's holdings record says where to ask
|
|
> ([holdings](holdings.md) §5.4), the publisher's receipts say what the answer must be
|
|
> ([jwp-receipts](jwp-receipts.md) §3), and the asker's settlement acknowledgement says what was
|
|
> served ([settlement](settlement.md) §3). A sink joins by its own choice: asking a peer and
|
|
> serving one are each off unless the operator's policy and the viewer's setting both allow it
|
|
> (§3). The exchange is eight frames over a link that keeps message boundaries, a WebRTC data
|
|
> channel among them, and three requests over HTTP for a neighbour reached that way (§4). An
|
|
> asker asks only for an object whose object receipt it already holds, takes nothing a peer
|
|
> sent before the bytes verify under that receipt, sets aside a holder that served other bytes
|
|
> and a record that stated what its holder did not hold, and goes to the origin for what is
|
|
> left (§5). It signs an acknowledgement for what it verified, by holder and by window, so what
|
|
> each peer served recomputes from signed records (§6). §9 says what a peer learns of a viewer.
|
|
> WAI defines no transport. The reference implementation is the `peer_delivery` module of
|
|
> `wai-rs` (feature `peer_delivery`) and, in a page, the `peer` bundle of `wai-web`; the corpus
|
|
> is [`live-conformance/peer-delivery/`](../live-conformance/peer-delivery/), and its second
|
|
> implementation is `live-conformance/peer_delivery_verify.py`.
|
|
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
|
>
|
|
> Texts read for this revision: the WAI texts this one cites; RFC 8831 (§6.1, §6.6: a data
|
|
> channel's reliability and its message size) and RFC 8832 (§5.1: the channel's type, label and
|
|
> protocol); RFC 8828 (§3, §5.1, §5.2: which addresses a WebRTC endpoint gathers, and its four
|
|
> modes); RFC 8826 §4.2.4; `draft-ietf-mmusic-mdns-ice-candidates-03` §1 and §3.1.1; RFC 7574
|
|
> §3.2 and §5; RFC 9110 §15.5.5, §15.5.21 and §15.6.4; the W3C *WebRTC: Real-Time Communication
|
|
> in Browsers* Recommendation (a data channel's label, protocol, order and largest message, and
|
|
> the transport policy `relay` of a connection's configuration); the W3C *Service Workers*
|
|
> text, §5.4 (*Cache*); and the Web Incubator Community Group's draft *Local Network Access*
|
|
> (§1, §2.1 to §2.3). A clause number of one of them in this text is that text's.
|
|
|
|
## 1. Scope
|
|
|
|
In scope: who may ask and who may serve (§3); the exchange between an asker and a holder, over a
|
|
link that keeps message boundaries and over HTTP (§4); what an asker does with its neighbours'
|
|
records and with each answer (§5); the acknowledgement of what a peer served, its object class
|
|
and what a holder checks of it (§6); what a page uses of its browser (§7); and what each party
|
|
learns (§9).
|
|
|
|
Out of scope: how two sinks are introduced to each other and how a link between them is set up,
|
|
which is the deployment's; how holdings records reach an asker before it has a link (holdings
|
|
§1); how a node chooses what to keep; a price; and a record of service beside settlement's
|
|
acknowledgement, which this text does not add.
|
|
|
|
Relationship to the core spec: this text defines no capability, no payload format and no record
|
|
([SPEC](../SPEC.md) §5). Every signed byte it relies on is another text's.
|
|
|
|
## 2. Terms
|
|
|
|
- **Asker**, **holder**, **group**, **link hash**, **position**: as [holdings](holdings.md) §2.
|
|
A **peer** is a holder that is a sink too; what this text says of a holder holds for a
|
|
neighbourhood cache as it does for a peer.
|
|
- **Origin**: where the asker fetches an object when it asks no holder for it, or none serves
|
|
it: the service it takes delivery from.
|
|
- **Link**: a path between an asker and one holder that carries messages whole and in order,
|
|
each way, with none lost. An **HTTP link** is a base URL at which a holder answers the
|
|
requests of §4.5.
|
|
- **Session**: one asker's exchanges from when it starts until it stops. What an asker sets
|
|
aside (§5.4) it sets aside for its session.
|
|
- **Instant**: milliseconds, as holdings §2. A **window** is settlement's, in microseconds
|
|
([settlement](settlement.md) §2).
|
|
- Integers in frames are unsigned and big-endian, as a holdings record's are.
|
|
|
|
## 3. Joining
|
|
|
|
### 3.1 The operator's policy
|
|
|
|
The operator of the service a sink takes delivery from states, in the configuration it gives
|
|
its sinks, four values:
|
|
|
|
| value | meaning | where the operator states nothing |
|
|
|---|---|---|
|
|
| `ask` | its sinks may fetch from peers | no |
|
|
| `serve` | its sinks may serve peers from what they hold | no |
|
|
| `max_object_bytes` | the most bytes of one object a sink takes from a peer | 4,194,304 |
|
|
| `relay_only` | its sinks reach peers only through a relay (§9) | no |
|
|
|
|
How the configuration reaches a sink is the deployment's. A sink with no configuration has an
|
|
operator that states nothing.
|
|
|
|
### 3.2 The viewer's setting
|
|
|
|
A sink keeps two settings of its own, `ask` and `serve`, each **off** until the person using the
|
|
sink turns it on, and each theirs to turn off again. A sink **MUST NOT** turn either on by
|
|
itself, and **MUST** show, where it offers them, what §9 says a peer learns.
|
|
|
|
### 3.3 The rule
|
|
|
|
A sink **asks** only while the policy's `ask` and its own `ask` are both on, and **serves** only
|
|
while the policy's `serve` and its own `serve` are both on. Asking is not serving: either may be
|
|
on without the other.
|
|
|
|
- A sink that may not ask opens no link to a holder, sends no frame and no request of §4.5,
|
|
takes no object from a peer and signs no acknowledgement to one. Every object it wants is the
|
|
origin's (`peer_not_permitted`).
|
|
- A sink that may not serve signs no holdings record for its neighbours, accepts no link, and
|
|
answers a `want` it is sent, on a link it already has, with `miss` and code 3.
|
|
- A setting turned off takes effect at the next object: an exchange under way may finish or be
|
|
dropped, and nothing new starts.
|
|
|
|
## 4. The exchange
|
|
|
|
### 4.1 The link and the version
|
|
|
|
The exchange is version 1. An asker opens it with `record-want`, which states the version; a
|
|
holder that does not speak that version closes the link. Each frame is one message of the link.
|
|
On a link, an asker has at most one request outstanding: it sends `record-want`, `want` or `ack`
|
|
and waits for the whole reply before it sends another.
|
|
|
|
### 4.2 Frames
|
|
|
|
A frame is one byte of type, then its body:
|
|
|
|
| type | name | from | body |
|
|
|---|---|---|---|
|
|
| 1 | `record-want` | asker | 1 byte: the version, 1 |
|
|
| 2 | `record-head` | holder | `len`, 8 bytes: the bytes of the holder's record, 1 to 1,048,576, which follow in `data` frames |
|
|
| 3 | `want` | asker | `group`, 32 bytes, and `index`, 8 bytes: object `index` of the group with that link hash |
|
|
| 4 | `head` | holder | `group`, `index`, and `len`, 8 bytes, at least 1: the object's bytes, which follow in `data` frames |
|
|
| 5 | `data` | holder | 1 to 16,384 bytes |
|
|
| 6 | `miss` | holder | `group`, `index`, and `code`, 1 byte: 1 the holder does not hold the object now, 2 its record does not list it, 3 it will not serve now |
|
|
| 7 | `ack` | asker | `record_len`, 4 bytes, at least 1; an acknowledgement of that many bytes (§6.1); then its item list, 1 to 256 items of 48 bytes |
|
|
| 8 | `ack-result` | holder | `digest`, 32 bytes, the acknowledgement's (settlement §3.5), and 1 byte: 0 taken, 1 not taken |
|
|
|
|
A reader reads exactly the bytes of one message as one frame and refuses it
|
|
`peer_frame_malformed` when it is empty, its type is not 1 to 8, its body is not of its type's
|
|
length, or a field is outside the range the table gives. A `record-want` of another version is
|
|
`peer_frame_malformed` in this revision. An object's bytes travel in frames of at most 16,384
|
|
bytes because RFC 8831 §6.6 asks a sender that cannot interleave messages to keep each within
|
|
16 KB.
|
|
|
|
### 4.3 The holder
|
|
|
|
A holder answers `record-want` with `record-head` and the bytes of the one record of its own
|
|
that stands (holdings §5.3), and `want` with the first of these that holds:
|
|
|
|
1. `miss`, code 3, when it may not serve (§3.3) or will not serve now;
|
|
2. `miss`, code 2, when that record does not hold the object (holdings §5.4): a holder serves
|
|
over this exchange only what it has told its neighbours it holds;
|
|
3. `miss`, code 1, when it does not hold the object's bytes now;
|
|
4. `head` with the object's length, then its bytes.
|
|
|
|
It answers `ack` as §6.3 says. A holder **MUST NOT** send bytes it has not itself verified under
|
|
the object's receipt, and learns nothing from doing so: its asker verifies them again (§5.3).
|
|
A frame a holder is sent that is not `record-want`, `want` or `ack`, or that arrives before its
|
|
reply to the one before is whole, is `peer_frame_unexpected`, and it closes the link.
|
|
|
|
### 4.4 A reply
|
|
|
|
The bytes a `record-head` or a `head` announces follow in `data` frames, each of 16,384 bytes
|
|
but the last, which holds what remains. An asker holds a reply to what it asked:
|
|
|
|
- a `head` whose `group` or `index` is not what it asked for, a `data` frame that is not the
|
|
next 16,384 bytes or the last bytes where fewer remain, a frame after the reply is whole, and
|
|
any frame of another type than its request admits: `peer_frame_unexpected`;
|
|
- a `head` whose `len` is above `max_object_bytes`: `peer_object_too_large`. The asker takes
|
|
none of its bytes;
|
|
- a link that closes, or gives no frame within the time the asker allows, before the reply is
|
|
whole: `peer_link_failed`.
|
|
|
|
After any of the three the asker closes the link and asks that holder nothing more over it. A
|
|
reply is whole when its bytes are exactly `len`; an asker never holds more of one than its head
|
|
stated, and it stores nothing of a reply that is not whole.
|
|
|
|
### 4.5 Over HTTP
|
|
|
|
A holder reached by HTTP answers, under its base URL:
|
|
|
|
| request | answer |
|
|
|---|---|
|
|
| `GET holdings` | 200 with the record's bytes |
|
|
| `GET object/<group>/<index>` | 200 with the object's bytes, whose length the response states; 404 for `miss` code 1 or 2; 503 for code 3 |
|
|
| `POST ack`, its body an `ack` frame's body | 200 with the acknowledgement's digest when it is taken; 422 when it is not |
|
|
|
|
`<group>` is the link hash as 64 lowercase hexadecimal digits and `<index>` the position in
|
|
decimal with no leading zero. The rules of §4.3 and §4.4 hold with a response for a reply: a
|
|
stated length above `max_object_bytes`, or a body that reaches it with no length stated, is
|
|
`peer_object_too_large`, and the asker reads no further; any other status, a body shorter or
|
|
longer than its stated length, and a request that fails are `peer_link_failed`. A 404 is read
|
|
as code 1. An asker's requests carry no credentials, no cookie and no referrer (§7).
|
|
|
|
## 5. The asker
|
|
|
|
### 5.1 Its records
|
|
|
|
An asker holds its neighbours' records in the order it prefers them (holdings §5.4) and judges
|
|
them at an instant as holdings §5.1 to §5.3 do, against the group receipts it holds: a record
|
|
that is refused, superseded or set aside there takes no part. Of those that stand, it sets
|
|
aside the ones §5.4 names. Its own record, where it has one, is in no route: an asker does not
|
|
ask itself.
|
|
|
|
### 5.2 The route
|
|
|
|
For a wanted object, the asker's **route** is, at an instant:
|
|
|
|
1. the origin alone, `peer_not_permitted`, when it may not ask (§3.3);
|
|
2. the origin alone, `peer_receipt_not_held`, when it does not hold the object's object receipt
|
|
and the group receipt it is included under, both verified (jwp-receipts §3 steps 1, 2 and
|
|
5): it could hold nothing a peer sent for it to a signed digest, so it asks no peer for it;
|
|
3. otherwise the holders whose record stands (§5.1) and holds the object, in the asker's order
|
|
(holdings §5.4), then the origin.
|
|
|
|
An asker **MUST NOT** send a holder a `want` for an object that holder's standing record does
|
|
not hold. So a holder learns of an asker only that it wants objects the holder has itself told
|
|
its neighbours it holds (§9).
|
|
|
|
### 5.3 The answer
|
|
|
|
The asker asks the holders of the route in order, one at a time, and stops at the first that
|
|
serves bytes it takes:
|
|
|
|
- **bytes that verify** under the object receipt and the group receipt it holds (jwp-receipts
|
|
§3 steps 1 to 5) are **taken**: `delivered-verified`, whoever sent them (holdings §6). Only
|
|
then does the asker use them, store them, serve them on or acknowledge them;
|
|
- **bytes that fail** are refused with jwp-receipts §3's reason and step, `content_hash_mismatch`
|
|
at step 4 for bytes that are not the object's. The asker discards them, and §5.4 applies;
|
|
- `miss`, a refusal of §4.4, and a failure of the link move it to the next holder.
|
|
|
|
When no holder is left it fetches the object from the origin. What comes from the origin is
|
|
verified as every object is; this text adds nothing for it.
|
|
|
|
### 5.4 What an asker sets aside
|
|
|
|
- **A holder that served bytes that failed** is asked nothing more for the session, whatever
|
|
its records say (`peer_bytes_refused`): every record of its that stands is set aside.
|
|
- **A record that overstated**: a holder that answers `miss` with code 1 or 2 for an object its
|
|
standing record holds has stated what it does not hold, and each of its records that holds
|
|
that object is set aside for the session (`holdings_overstated`). A later record of that
|
|
holder is another statement, and is judged as §5.1 judges any record.
|
|
- **A record that states a group as another size than its receipt** is already refused whole
|
|
(holdings §5.2, `holdings_leaf_count_mismatch`); its holder is in no route.
|
|
- `miss` with code 3 (`peer_declined`), an object past the bound and a link that failed set
|
|
nothing aside.
|
|
|
|
Nothing here scores a holder beyond the session, and nothing is told to anyone: what an asker
|
|
sets aside is its own state (holdings §9).
|
|
|
|
## 6. The acknowledgement
|
|
|
|
### 6.1 What the asker signs
|
|
|
|
For each holder it took objects from, an asker signs, at the end of a window, one
|
|
acknowledgement (settlement §3) of exactly the objects that holder served and the asker
|
|
verified in the window:
|
|
|
|
- `mode` 1 and `role` 2: an acknowledgement, by a sink;
|
|
- `upstream` the key of the record the asker found the object under
|
|
([cache-authority](cache-authority.md) §4.5), and `downstream` the asker's key;
|
|
- the window, and `prev` the digest of its acknowledgement to that holder before this one
|
|
(settlement §3.7), zero for the first;
|
|
- one entry, of class 2 (§6.2), `grant` and `proof` zero, its items the objects taken from that
|
|
holder in the order they were taken, each whole: `start` 0 and `end` the object's length.
|
|
|
|
An acknowledgement sent over a link names at most 256 objects; an asker with more closes the
|
|
window sooner. Where it took objects from several holders it signs one acknowledgement for
|
|
each. It sends each to its holder in an `ack` frame with the entry's item list, or as §4.5's
|
|
`POST`, and keeps it for the party it settles with.
|
|
|
|
An object is acknowledged only after it was taken (§5.3). Bytes that failed, a reply that was
|
|
not whole and an object fetched from the origin are in no acknowledgement to a holder.
|
|
|
|
### 6.2 The class, and whose word
|
|
|
|
An object a holder serves in answer to a `want` is served from what it holds in answer to a
|
|
request, so its entry is of class 2, `fetch` (settlement §3.3), whatever the object is: a
|
|
retrieval class. This revision's exchange carries no proof of possession of a grant
|
|
([live-rooms](live-rooms.md) §10.5), so its entries name no grant: each is a retrieval no grant
|
|
was presented for, acknowledged, counted in a statement's lines and classes, and free under a
|
|
statement's tariff (settlement §6.2, §7.2). A deployment that pays its peers pays them on the
|
|
acknowledged bytes, outside the statement.
|
|
|
|
A payer honours an acknowledgement only from a key it can place (settlement §13). Outside a
|
|
room no grant names the asker, so a payer takes an asker's word when its key is in a population
|
|
the payer stated (cache-authority §8.1), and reports any other acknowledgement apart, paid as
|
|
no one's. The holder's key is placed as cache-authority §4.4 places any server key.
|
|
|
|
### 6.3 What a holder checks
|
|
|
|
A holder takes an acknowledgement sent to it when, in this order:
|
|
|
|
1. the record reads and verifies (settlement §3.6), with that section's reasons;
|
|
2. it is of version 1, `mode` 1 and `role` 2, with one entry, of class 2, whose `grant` and
|
|
`proof` are zero: otherwise `peer_ack_invalid`;
|
|
3. its `upstream` is the holder's key: otherwise `ack_wrong_party`;
|
|
4. the item list reads and recomputes against the entry (settlement §3.6): `items_invalid`,
|
|
`items_mismatch`;
|
|
5. each item is an object the holder served over that link and holds no acknowledgement of,
|
|
whole: otherwise `ack_not_served`.
|
|
|
|
It answers `ack-result` with the record's digest and 0 when it takes it, 1 when it does not. An
|
|
asker may acknowledge fewer objects than it was served: what it did not verify is not its to
|
|
sign for, and what a holder served that nobody acknowledged it may state in a declaration
|
|
(settlement §4).
|
|
|
|
## 7. In a page
|
|
|
|
A sink that is a page of a browser uses what the browser gives it:
|
|
|
|
- **A data channel** (RFC 8831) is a link. The channel is reliable and ordered (RFC 8832 §5.1,
|
|
`DATA_CHANNEL_RELIABLE`), its label `wai-peer` and its protocol `wai-peer-1`; each frame is
|
|
one binary message.
|
|
- **Addresses.** A page gathers the candidates its browser offers under the browser's own
|
|
policy (RFC 8828 §5.2). With the policy's `relay_only` on, it sets its connection to use
|
|
relayed candidates alone (the W3C text's transport policy `relay`), and opens no link where
|
|
it has no relay.
|
|
- **An HTTP link** is `fetch`, with credentials omitted, no referrer, and the browser's HTTP
|
|
cache bypassed. A page reaches a holder on a local network only where its browser lets it:
|
|
the *Local Network Access* draft has a browser ask the person before a page of a public
|
|
address makes such a request (its §2.3).
|
|
- **A store.** A page that serves keeps objects it has verified, and only those, in a cache of
|
|
the Cache API under its own origin, and serves from it. A cache's entries do not expire by
|
|
themselves; a page removes an object when it stops listing it.
|
|
- **Keys.** A page signs with a key it makes for the session and keeps in memory. A page that
|
|
serves and is to be placed signs with the key its operator lists (cache-authority §4.5).
|
|
|
|
## 8. Reasons
|
|
|
|
The codes this text adds, each with where it is raised:
|
|
|
|
| reason | raised at |
|
|
|---|---|
|
|
| `peer_not_permitted` | §3.3, §5.2 |
|
|
| `peer_frame_malformed` | §4.2 |
|
|
| `peer_frame_unexpected` | §4.3, §4.4 |
|
|
| `peer_object_too_large` | §4.4, §4.5 |
|
|
| `peer_link_failed` | §4.4, §4.5 |
|
|
| `peer_declined` | §5.4 |
|
|
| `peer_receipt_not_held` | §5.2 |
|
|
| `holdings_overstated` | §5.4 |
|
|
| `peer_bytes_refused` | §5.4 |
|
|
| `peer_ack_invalid` | §6.3 |
|
|
| `ack_not_served` | §6.3 |
|
|
|
|
An implementation also raises every code holdings §7 lists, for a record; jwp-receipts §3's
|
|
reasons, for a receipt and for bytes that fail one; and, for an acknowledgement, settlement
|
|
§11's `ack_invalid`, `self_acknowledged`, `items_invalid`, `items_mismatch` and
|
|
`ack_wrong_party`, with `truncated`, `malformed`, `unknown_version`, `bad_key` and
|
|
`bad_signature`.
|
|
|
|
## 9. Security and privacy considerations
|
|
|
|
**Peers are not trusted.** Nothing a peer says is evidence of an object's bytes. An asker asks
|
|
only for objects whose receipts it holds, takes nothing before it verifies, and a holder that
|
|
sends other bytes gave it one wasted exchange and is asked nothing more. A holder's record that
|
|
overstates costs one exchange and is set aside. A holder cannot make an asker hold more than
|
|
`max_object_bytes` of one reply, nor a byte more than a head stated.
|
|
|
|
**What a holder learns of an asker**, and what it does not:
|
|
|
|
- **An address.** Over a data channel each end learns the addresses the other's browser offers
|
|
as candidates: under RFC 8828's mode 2, which it recommends where the person has consented
|
|
to no more, those of the route its HTTP traffic takes and its local addresses on that
|
|
interface, which a browser may give as names that resolve only on the local network
|
|
(`draft-ietf-mmusic-mdns-ice-candidates`). With `relay_only` each end learns the relay's
|
|
address alone, and the relay learns both. Over an HTTP link the holder learns the address the
|
|
request came from, as any server does, and, from a page, the origin of the page that made
|
|
it. An address places a viewer roughly and may identify a household.
|
|
- **What was asked for.** Each `want` names a group and a position: a holder learns that this
|
|
asker wants that object now, and so what it is watching and where in it, for the objects the
|
|
holder itself listed and for no other (§5.2).
|
|
- **A key.** An acknowledgement names the asker's key, its window and the objects it took. A
|
|
key made for the session links nothing to another session; an asker **MAY** acknowledge
|
|
nothing, and its holder then states the delivery itself (settlement §4).
|
|
- **When.** The times of its requests.
|
|
- **Nothing else.** The exchange carries no name, account, grant, cookie or credential of the
|
|
viewer, no list of what else it wants or holds, and nothing of what it fetched from the
|
|
origin or from another holder.
|
|
|
|
**What an asker learns of a holder**: its address, as above; its record, which says what it
|
|
holds (holdings §9); and whether it served.
|
|
|
|
**What the service learns**: whatever introduces two sinks learns that it did; the origin sees
|
|
no request for an object a peer served, and learns of it from the acknowledgements, where the
|
|
asker hands them on.
|
|
|
|
**Whose choice.** Joining is the sink's (§3): a viewer who has not turned `ask` on has its
|
|
address shown to no peer and names no object to one, and a viewer who has not turned `serve` on
|
|
tells no neighbour what it holds. Turning a setting off stops the next exchange; what a peer
|
|
already learned it keeps. Where a regime asks the operator to state the lawful basis on which
|
|
it processes what an acknowledgement reveals, the operator states it as settlement §13 says.
|
|
|
|
**A holder's key on the link.** A link is not authenticated by this exchange. A party that
|
|
answers on a link in a holder's place can serve only bytes that verify, and the acknowledgement
|
|
names the record's key, so what it gains is to have served for another's credit. One that
|
|
sends other bytes in a holder's place has that holder set aside by that asker for its session:
|
|
it costs the asker a route and the holder that asker's requests, and nothing else.
|
|
|
|
**Bounded work.** One request outstanding per link; a reply of at most `max_object_bytes`; a
|
|
record of at most 1,048,576 bytes; an acknowledgement of at most 256 items; a verification of
|
|
one hash and one inclusion proof per object.
|
|
|
|
## 10. Conformance
|
|
|
|
[`live-conformance/peer-delivery/`](../live-conformance/peer-delivery/) holds the corpus,
|
|
written from fixed seeds by `wai_peer_delivery_vectors gen` as `vectors.json`. It has:
|
|
|
|
- **A world**: two groups with their receipts, their objects' receipts and envelopes, one object
|
|
past one `data` frame.
|
|
- **Frames**: each frame of §4.2 with what it reads to, and a control for each bound of its
|
|
table.
|
|
- **Transfers**: a head held to what was asked and to the bound, and replies in one frame and
|
|
two, a byte long, a frame short and a frame past the end.
|
|
- **Gates**: every setting of the four switches of §3.
|
|
- **Sessions**: an asker's rounds, each with the records offered, what each holder's link
|
|
answers, each wanted object's route, attempts and source, and the acknowledgements signed. A
|
|
group taken whole from one peer; a peer that serves altered bytes, refused, set aside and
|
|
named by no acknowledgement; a record that overstates a group's size, never asked; a holder
|
|
that lists an object it does not hold, set aside, and its later record judged anew; two
|
|
windows chained; the viewer's setting off and the operator's policy off; a holder that
|
|
declines and a link that fails; an object with no receipt held; an object past the bound; the
|
|
asker's own record; an expired record; and two holders acknowledged apart.
|
|
- **Answers** and **acknowledgements at a holder**: each answer of §4.3, and each check of §6.3
|
|
with a control.
|
|
|
|
A conforming implementation gives every case the outcome the corpus states.
|
|
`wai_peer_delivery_vectors verify` and the test `tests/peer_delivery.rs` check that the file
|
|
regenerates byte for byte, that `wai-rs` gives every case its outcome, that every reason of §8
|
|
appears, and that §8 lists exactly the reference's codes.
|
|
`live-conformance/peer_delivery_verify.py` is the second implementation, written from this
|
|
text: it shares no code with `wai-rs`; it takes records, receipts and acknowledgements from the
|
|
second implementations of the texts that define them (`holdings_verify.py`,
|
|
`jwp-conformance/jwp_verify.py`, `settlement_verify.py`), writes the frames, the gate, the
|
|
route, the session and the holder's checks itself, rebuilds each acknowledgement's bytes before
|
|
its signature and verifies the signature, mutates copies of the corpus and requires every copy
|
|
to fail, and drops each of its own checks in turn and requires the corpus to catch the weakened
|
|
verifier.
|
|
|
|
In a page, `wai-web/peer/` holds the client and two runs of it over the corpus's world.
|
|
`verify_peer.mjs` runs an asker and its holders in one process over links in memory and over a
|
|
local HTTP link, with the faults above planted. `run.mjs` opens pages in a browser: holders
|
|
that serve over data channels, one of them altering bytes and one listing what it does not
|
|
hold, a local HTTP holder, an asker with both switches on and one with its setting off; it
|
|
requires every object the asker shows to have verified, the altering peer to be named by no
|
|
acknowledgement, every acknowledgement to be taken by its holder, the origin to have served
|
|
only what no peer did, and the page with its setting off to have opened no connection.
|