p2party 0.14.3 → 0.14.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -31
- package/THIRD_PARTY_NOTICES.md +33 -6
- package/docs/getting-started.md +118 -4
- package/docs/protocol-v4-security.md +96 -6
- package/docs/references.md +28 -8
- package/docs/session-api.md +19 -2
- package/docs/wire-format.md +102 -14
- package/lib/api/signalingServerApi.d.ts +51 -2
- package/lib/api/webrtc/interfaces.d.ts +70 -1
- package/lib/api/webrtc/peerSocketGeneration.d.ts +68 -0
- package/lib/cryptography/coverCell.d.ts +7 -0
- package/lib/cryptography/emscripten.d.ts +101 -0
- package/lib/cryptography/interfaces.d.ts +3 -0
- package/lib/cryptography/libcrypto.d.ts +181 -0
- package/lib/cryptography/mlkem.d.ts +27 -1
- package/lib/cryptography/mnemonic.d.ts +65 -1
- package/lib/db/outboxTypes.d.ts +135 -0
- package/lib/db/types.d.ts +81 -7
- package/lib/db.worker.js +1 -1
- package/lib/handlers/coverRuntime.d.ts +14 -1
- package/lib/handlers/coverScheduler.d.ts +59 -1
- package/lib/handlers/coverTransfer.d.ts +169 -3
- package/lib/handlers/handleSendMessage.d.ts +138 -9
- package/lib/handlers/handshakeCore.d.ts +2 -7
- package/lib/handlers/handshakeFrame.d.ts +25 -0
- package/lib/handlers/outbox.d.ts +47 -0
- package/lib/handlers/ratchetGate.d.ts +9 -0
- package/lib/handlers/reconcile.d.ts +5 -0
- package/lib/index.d.ts +102 -12
- package/lib/index.js +1 -1
- package/lib/index.min.js +1 -1
- package/lib/index.mjs +1 -1
- package/lib/libcrypto.provenance.json +5 -5
- package/lib/libcrypto.wasm +0 -0
- package/lib/reducers/commonSlice.d.ts +3 -2
- package/lib/reducers/keyPairSlice.d.ts +3 -6
- package/lib/reducers/roomSlice.d.ts +89 -6
- package/lib/reducers/signalingServerSlice.d.ts +3 -2
- package/lib/session.d.ts +6 -3
- package/lib/session.js +1 -1
- package/lib/session.mjs +1 -1
- package/lib/store.d.ts +4 -2
- package/lib/utils/constants.d.ts +19 -0
- package/lib/utils/identityRestore.d.ts +108 -0
- package/lib/utils/interfaces.d.ts +36 -6
- package/lib/utils/roomLeaveCoordinator.d.ts +38 -0
- package/lib/utils/signalingReconnectController.d.ts +44 -0
- package/lib/utils/terminalSettlement.d.ts +53 -0
- package/package.json +13 -15
- package/lib/api/webrtc/disconnectFromAllRoomsQuery.d.ts +0 -8
- package/lib/api/webrtc/disconnectFromChannelLabelQuery.d.ts +0 -7
- package/lib/api/webrtc/disconnectFromPeerChannelLabelQuery.d.ts +0 -8
- package/lib/api/webrtc/disconnectFromPeerQuery.d.ts +0 -9
- package/lib/api/webrtc/disconnectFromRoomQuery.d.ts +0 -8
- package/lib/api/webrtc/disconnectQuery.d.ts +0 -8
- package/lib/api/webrtc/iceGeneration.d.ts +0 -17
- package/lib/api/webrtc/iceRepair.d.ts +0 -10
- package/lib/api/webrtc/index.d.ts +0 -16
- package/lib/api/webrtc/negotiationLock.d.ts +0 -12
- package/lib/api/webrtc/openChannelQuery.d.ts +0 -8
- package/lib/api/webrtc/pendingIceCandidates.d.ts +0 -14
- package/lib/api/webrtc/roomPeer.d.ts +0 -11
- package/lib/api/webrtc/sendMessageQuery.d.ts +0 -14
- package/lib/api/webrtc/setCandidateQuery.d.ts +0 -8
- package/lib/api/webrtc/setDescriptionQuery.d.ts +0 -9
- package/lib/cryptography/cpace.d.ts +0 -41
- package/lib/cryptography/ed25519.d.ts +0 -11
- package/lib/cryptography/hashStream.d.ts +0 -15
- package/lib/cryptography/hkdf.d.ts +0 -3
- package/lib/cryptography/identityCrossSig.d.ts +0 -7
- package/lib/cryptography/memory.d.ts +0 -14
- package/lib/cryptography/merkle.d.ts +0 -42
- package/lib/cryptography/pqHealingFrame.d.ts +0 -39
- package/lib/cryptography/random.d.ts +0 -22
- package/lib/cryptography/testModule.d.ts +0 -7
- package/lib/cryptography/utils.d.ts +0 -13
- package/lib/cryptography/x25519.d.ts +0 -15
- package/lib/cryptography/x3dh.d.ts +0 -9
- package/lib/db/api.d.ts +0 -67
- package/lib/db/db.worker.d.ts +0 -1
- package/lib/db/ratchetWrap.d.ts +0 -36
- package/lib/db/src/getDB.d.ts +0 -81
- package/lib/handlers/chunkFrame.d.ts +0 -13
- package/lib/handlers/connectionSignal.d.ts +0 -26
- package/lib/handlers/coverEdge.d.ts +0 -42
- package/lib/handlers/frameType.d.ts +0 -10
- package/lib/handlers/handleChallenge.d.ts +0 -3
- package/lib/handlers/handleConnectToPeer.d.ts +0 -6
- package/lib/handlers/handleHandshake.d.ts +0 -74
- package/lib/handlers/handleMessageQueueing.d.ts +0 -54
- package/lib/handlers/handleOpenChannel.d.ts +0 -12
- package/lib/handlers/handleQueuedIceCandidates.d.ts +0 -2
- package/lib/handlers/handleReadReceipt.d.ts +0 -17
- package/lib/handlers/handleReceiveMessage.d.ts +0 -32
- package/lib/handlers/handleWebSocketMessage.d.ts +0 -3
- package/lib/handlers/messageChunkCrypto.d.ts +0 -70
- package/lib/handlers/peerRosterDelta.d.ts +0 -12
- package/lib/handlers/pqHealingOrchestrator.d.ts +0 -46
- package/lib/handlers/ratchetPersist.d.ts +0 -53
- package/lib/handlers/receiptFrame.d.ts +0 -18
- package/lib/handlers/receiveMessageKeyLifetime.d.ts +0 -24
- package/lib/handlers/transferAbort.d.ts +0 -42
- package/lib/middleware/keyPairListenerMiddleware.d.ts +0 -2
- package/lib/middleware/roomListenerMiddleware.d.ts +0 -2
- package/lib/roomInvite.d.ts +0 -18
- package/lib/roomPinAttempts.d.ts +0 -12
- package/lib/roomPinVault.d.ts +0 -26
- package/lib/utils/channelLabel.d.ts +0 -9
- package/lib/utils/chunkBounds.d.ts +0 -9
- package/lib/utils/drainAndClose.d.ts +0 -5
- package/lib/utils/identityRole.d.ts +0 -19
- package/lib/utils/leafHash.d.ts +0 -13
- package/lib/utils/metadata.d.ts +0 -22
- package/lib/utils/mutex.d.ts +0 -24
- package/lib/utils/protocolVersion.d.ts +0 -5
- package/lib/utils/receiptToken.d.ts +0 -7
- package/lib/utils/sendQueueKey.d.ts +0 -2
- package/lib/utils/signalingAuth.d.ts +0 -8
- package/lib/utils/signalingBounds.d.ts +0 -9
- package/lib/utils/splitToChunks.d.ts +0 -30
- package/lib/utils/uint8array.d.ts +0 -16
- package/lib/utils/waitForOpen.d.ts +0 -3
- package/lib/utils/zeroFree.d.ts +0 -10
|
@@ -17,9 +17,9 @@ There are three different acknowledgements in the system:
|
|
|
17
17
|
2. The protocol-v4 handshake runs over the open main channel. After HELLO,
|
|
18
18
|
responder CONFIRM, initiator CONFIRM, and responder FINISH, the peers have
|
|
19
19
|
authenticated the same hybrid root and initial ratchet keys.
|
|
20
|
-
3.
|
|
21
|
-
|
|
22
|
-
establishment.
|
|
20
|
+
3. Immediate 65-byte receipts on authenticated message channels, or encrypted
|
|
21
|
+
scheduled control cells, acknowledge chunks and transfer completion. They
|
|
22
|
+
are delivery/reconciliation signals, not handshake establishment.
|
|
23
23
|
|
|
24
24
|
An open data channel therefore is necessary transport readiness, not an
|
|
25
25
|
established p2party crypto session.
|
|
@@ -66,15 +66,65 @@ byte so the sparse post-quantum healing epoch cannot wrap.
|
|
|
66
66
|
|
|
67
67
|
The fixed frame geometry absorbs the ratchet and AEAD overhead into the cell
|
|
68
68
|
budget. Random padding and decoy slots can hide the exact payload length within
|
|
69
|
-
one transfer's chosen number of frames.
|
|
70
|
-
geometry
|
|
71
|
-
65,490-byte
|
|
69
|
+
one transfer's chosen number of frames. Immediate receipts have a distinct
|
|
70
|
+
65-byte geometry and immediate pause/cancel controls are 66 bytes. Scheduled
|
|
71
|
+
receipt batches, terminal receipts and CANCELs occupy existing 65,490-byte
|
|
72
|
+
cover slots. Handshake flights are suite- and step-dependent.
|
|
72
73
|
|
|
73
74
|
Per-message channels give each transfer an independent lifecycle for
|
|
74
75
|
cancellation, bounded channel accounting, receipts, selective retransmission,
|
|
75
76
|
and reconnect resume. Channel isolation is a UX and reliability property; it
|
|
76
77
|
does not make timing or channel count invisible.
|
|
77
78
|
|
|
79
|
+
### Durable selective resume and cancellation
|
|
80
|
+
|
|
81
|
+
A chunk acknowledgement becomes live only after its per-recipient outbox
|
|
82
|
+
update commits. Receipt acceptance binds room, peer ID, peer public key,
|
|
83
|
+
random transfer ID, Merkle root and original chunk index. Scheduled cells
|
|
84
|
+
add direction, PQ epoch, AEAD and counter replay checks; their terminal token
|
|
85
|
+
must equal the transfer root. Stale asynchronous storage responses cannot
|
|
86
|
+
advance a replacement owner's live state. Recipient refusal is persisted
|
|
87
|
+
separately from completion and stops only that recipient's retry.
|
|
88
|
+
|
|
89
|
+
Scheduled chunk receipts use a separate encrypted subtype with up to 64 tokens
|
|
90
|
+
per root. Existing subtype 3 remains terminal-only: older clients interpreted
|
|
91
|
+
any token in that subtype as completion. Older protocol-v4 clients drop the
|
|
92
|
+
new subtype and retain terminal-only scheduled delivery, including full replay
|
|
93
|
+
when necessary. Selective scheduled resume therefore requires supporting peers.
|
|
94
|
+
|
|
95
|
+
Have-set membership is O(1) in memory and does not allocate a set copy per
|
|
96
|
+
scheduled slot. A resume scans indices once, sends missing real chunks and
|
|
97
|
+
may send one completion probe. Storage lookup costs, initial file preparation
|
|
98
|
+
and scanning remain; this is not a claim of sublinear total file processing.
|
|
99
|
+
Batching and selective resume reduce real work within the existing schedule,
|
|
100
|
+
whose observable cell count remains fixed.
|
|
101
|
+
|
|
102
|
+
Immediate resume uses the channel protocol `p2party-resume-v1` and a capability
|
|
103
|
+
echo to distinguish pause from explicit cancellation. Type-6 controls are
|
|
104
|
+
authenticated by the concrete DTLS/SCTP channel and current identity gate,
|
|
105
|
+
with an exact-root check; they do not have a separate application AEAD.
|
|
106
|
+
Legacy peers retain close-as-cancel behavior. A fresh immediate attempt
|
|
107
|
+
rebuilds its have-set from paced receiver receipts instead of assuming a
|
|
108
|
+
legacy receiver retained its partial. Remote cancellation drains pending
|
|
109
|
+
writes, then atomically checks room, sender and incomplete status before
|
|
110
|
+
deleting receiver data; completed files and sender self-copies are preserved.
|
|
111
|
+
|
|
112
|
+
### Sparse-PQ healing during scheduled sends
|
|
113
|
+
|
|
114
|
+
Local healing waits for queued or admitted real jobs, message channels,
|
|
115
|
+
derive-to-enqueue reservations and queued receive work to drain. An atomic
|
|
116
|
+
application reservation closes the race between deriving a message key and
|
|
117
|
+
admitting its scheduled producer. Dummy cover does not block healing.
|
|
118
|
+
|
|
119
|
+
An incoming exchange blocks new admission and drains reservations, then
|
|
120
|
+
persists its new epoch before adopting it. Epoch adoption invalidates old-key
|
|
121
|
+
real producers on the same scheduler and wakes bounded sender recovery.
|
|
122
|
+
Recovery requires an actual epoch advance when reusing that runtime and
|
|
123
|
+
re-derives the missing chunks. If sealing was already in flight, its retired
|
|
124
|
+
cell is wiped and the slot remains dummy; pending explicit CANCEL is resealed
|
|
125
|
+
under the new epoch. Lane timing continues throughout this transition.
|
|
126
|
+
Persistence failure leaves the prior live epoch unchanged for exact retry.
|
|
127
|
+
|
|
78
128
|
## Guarantees, assuming authenticated peer keys
|
|
79
129
|
|
|
80
130
|
With a correctly pinned peer Ed25519 identity, uncompromised endpoints, matching
|
|
@@ -126,6 +176,46 @@ Putting the capability in a URL fragment keeps it out of ordinary HTTP
|
|
|
126
176
|
requests and common access logs, but the shipped legacy signaling path still
|
|
127
177
|
receives its normalized value. A fragment is not a server-blind meeting point.
|
|
128
178
|
|
|
179
|
+
## Signaling and TURN correctness boundary
|
|
180
|
+
|
|
181
|
+
SDK 0.14.10 makes legacy signaling state transitions correlated and bounded; it
|
|
182
|
+
does not make that signaling path blind.
|
|
183
|
+
|
|
184
|
+
- A room response is accepted only for the exact current socket, room
|
|
185
|
+
capability, and UUIDv4 request. A delayed response cannot configure another
|
|
186
|
+
room.
|
|
187
|
+
- Caller ICE configuration and signaling-managed TURN credentials are separate
|
|
188
|
+
inputs. Managed credentials carry an absolute expiry, are applied to every
|
|
189
|
+
live room edge before an ICE restart, refresh proactively, and are removed or
|
|
190
|
+
failed closed when they expire.
|
|
191
|
+
- `purgeRoom()` tears down local transports and durable state before waiting
|
|
192
|
+
for a correlated server acknowledgement. The leave is keyed by the stable
|
|
193
|
+
room capability rather than the server-assigned room UUID, so a purge racing
|
|
194
|
+
an in-flight join still revokes that membership. Matching `p2party-server`
|
|
195
|
+
0.1.1 unlinks the authenticated peer from the room before acknowledging. An
|
|
196
|
+
offline or non-acknowledging server produces
|
|
197
|
+
`RemoteRoomRevocationUnconfirmedError`; it is never reported as confirmed.
|
|
198
|
+
- Signaling ingress, room rosters, live mesh edges, pending ICE, and channel
|
|
199
|
+
registries have explicit client-side bounds. Those bounds limit one client's
|
|
200
|
+
exposure; they are not server-side Sybil resistance or a global availability
|
|
201
|
+
proof.
|
|
202
|
+
|
|
203
|
+
TURN is a transport relay, not a privacy proxy. The legacy REST username is
|
|
204
|
+
expiry-bound but contains the client's Ed25519 public key, and the TURN
|
|
205
|
+
operator can observe source addresses, allocations, timing, and byte volume.
|
|
206
|
+
Neither relay use nor TLS makes the signaling operator unable to read SDP, ICE,
|
|
207
|
+
room capabilities, or membership.
|
|
208
|
+
|
|
209
|
+
The current challenge proves possession of the presented Ed25519 key to the
|
|
210
|
+
server by signing a fresh nonce. Its transcript is not bound to a server
|
|
211
|
+
identity or origin, so it does not resist a malicious server relaying another
|
|
212
|
+
server's nonce as a signing oracle. The legacy service also has no
|
|
213
|
+
cross-instance session-generation fence or Byzantine quorum. Consequently none
|
|
214
|
+
of malicious-rendezvous blindness, two-server BFT, social-graph hiding,
|
|
215
|
+
server-bound authentication, shutdown survival, or post-quantum signaling
|
|
216
|
+
authentication is a property of this path. Those remain requirements of the
|
|
217
|
+
versioned blind-rendezvous design.
|
|
218
|
+
|
|
129
219
|
## Shipped, implemented core, and research
|
|
130
220
|
|
|
131
221
|
| Status | Exact boundary in 0.14 |
|
package/docs/references.md
CHANGED
|
@@ -13,13 +13,33 @@ These are compiled into the shipped `libcrypto.wasm` or bundled into the
|
|
|
13
13
|
package. Licences and notices are reproduced in
|
|
14
14
|
[THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md).
|
|
15
15
|
|
|
16
|
-
| Project | Used for
|
|
17
|
-
| ----------------------- |
|
|
18
|
-
| libsodium | X25519, Ed25519, ChaCha20-Poly1305, BLAKE2b, HKDF, Argon2, SHA-512
|
|
19
|
-
| mlkem-native | ML-KEM-512/768/1024 (FIPS 203)
|
|
20
|
-
| Emscripten | Compiles the C cryptography to the pinned WASM module
|
|
21
|
-
| BIP-39 English wordlist | The
|
|
22
|
-
| Redux Toolkit | Browser-root state store
|
|
16
|
+
| Project | Used for | Link |
|
|
17
|
+
| ----------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
18
|
+
| libsodium | X25519, Ed25519, ChaCha20-Poly1305, BLAKE2b, HKDF, Argon2, SHA-512 | [github.com/jedisct1/libsodium](https://github.com/jedisct1/libsodium) |
|
|
19
|
+
| mlkem-native | ML-KEM-512/768/1024 (FIPS 203) | [github.com/pq-code-package/mlkem-native](https://github.com/pq-code-package/mlkem-native) |
|
|
20
|
+
| Emscripten | Compiles the C cryptography to the pinned WASM module | [emscripten.org](https://emscripten.org/) |
|
|
21
|
+
| BIP-39 English wordlist | The 2,048 words only, shared by room invites and identity recovery phrases — see the note below | [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md) |
|
|
22
|
+
| Redux Toolkit | Browser-root state store | [redux-toolkit.js.org](https://redux-toolkit.js.org/) |
|
|
23
|
+
|
|
24
|
+
`src/utils/wordlist.json` is the BIP-39 English list, and that is the whole
|
|
25
|
+
extent of the relationship. Two unrelated encodings share it, and **neither
|
|
26
|
+
uses BIP-39 checksum semantics**:
|
|
27
|
+
|
|
28
|
+
- the 24-word room invite (`src/roomInvite.ts`), which encodes a 32-byte room
|
|
29
|
+
capability and checksums it with SHA-256 over the domain separator
|
|
30
|
+
`p2party/room-invite/checksum/v1\0`, under its own wordlist id
|
|
31
|
+
`p2party-invite-en-v1`; and
|
|
32
|
+
- the identity recovery phrase (`src/cryptography/mnemonic.ts`), which takes
|
|
33
|
+
128–512 bits of entropy (12–48 words), checksums with the leading bits of
|
|
34
|
+
SHA-512, and derives its seed with argon2id rather than PBKDF2-HMAC-SHA512.
|
|
35
|
+
This is the phrase
|
|
36
|
+
[identity backup and restore](../README.md#back-up-and-restore-your-identity)
|
|
37
|
+
derives an Ed25519 identity from; the invite encodes a room capability and
|
|
38
|
+
restores no identity at all.
|
|
39
|
+
|
|
40
|
+
Both reject the canonical BIP-39 English test vectors — the invite decoder with
|
|
41
|
+
`Room invite checksum is invalid`, `validateMnemonic()` by returning `false` —
|
|
42
|
+
and no BIP-39 tool can restore either. Do not describe them as BIP-39.
|
|
23
43
|
|
|
24
44
|
## 2. Standards the wire format implements
|
|
25
45
|
|
|
@@ -152,6 +172,6 @@ third-party security audit. Cite the implementation:
|
|
|
152
172
|
@software{p2party,
|
|
153
173
|
title = {p2party: protocol-v4 end-to-end encryption over a WebRTC room mesh},
|
|
154
174
|
url = {https://github.com/p2party/p2party-js},
|
|
155
|
-
note = {Version 0.14.
|
|
175
|
+
note = {Version 0.14.10}
|
|
156
176
|
}
|
|
157
177
|
```
|
package/docs/session-api.md
CHANGED
|
@@ -381,6 +381,15 @@ The bootstrap ML-KEM exchange protects the initial root. Healing periodically
|
|
|
381
381
|
re-runs it so a later post-quantum compromise cannot unwind an old session.
|
|
382
382
|
The session owns the state machine; the caller owns scheduling and transport.
|
|
383
383
|
|
|
384
|
+
The browser SDK separately coordinates queued scheduled producers with epoch
|
|
385
|
+
changes, persists per-recipient chunk receipts, and implements WebRTC pause and
|
|
386
|
+
CANCEL controls. Those transport/outbox services are not part of
|
|
387
|
+
`p2party/session`. A session integrator must coordinate its own pending output
|
|
388
|
+
and recovery when `pqEpoch` changes; the session does not selectively resend
|
|
389
|
+
previously returned frames or interpret cover receipt subtypes. See the
|
|
390
|
+
[wire format](wire-format.md#scheduled-control-cells--65490-bytes) for the browser
|
|
391
|
+
contract.
|
|
392
|
+
|
|
384
393
|
Control frames are the same 65,490-byte size as chunk frames, so the outer
|
|
385
394
|
framing MUST record which kind a record is — the session will reject a control
|
|
386
395
|
frame handed to `decrypt()` and vice versa.
|
|
@@ -450,8 +459,16 @@ interface SessionCryptoOptions {
|
|
|
450
459
|
|
|
451
460
|
Supplying bytes is the reproducible, offline-safe path. Resolve the exported
|
|
452
461
|
`p2party/libcrypto.wasm` package subpath or pin a self-hosted copy from the same
|
|
453
|
-
release.
|
|
454
|
-
|
|
462
|
+
release.
|
|
463
|
+
|
|
464
|
+
Omitting `wasmBinary` does not always mean a network call. On Node and Bun the
|
|
465
|
+
loader reads this release's WASM from the installed package — the file next to
|
|
466
|
+
the bundle — and hashes it against the build-pinned SHA-384 before use, so an
|
|
467
|
+
offline or air-gapped install needs no configuration. It falls back to the
|
|
468
|
+
immutable versioned p2party CDN artifact, under the same SRI, only when that
|
|
469
|
+
read fails or the digest does not match. In a browser, where there is no
|
|
470
|
+
filesystem, the CDN fetch is the only path. An explicit `setWasmSourceUrl()` is
|
|
471
|
+
honoured everywhere, including on Node, in preference to the packaged copy.
|
|
455
472
|
|
|
456
473
|
See [Protocol-v4 security](protocol-v4-security.md) for the exact claims and
|
|
457
474
|
non-claims of the session this API constructs.
|
package/docs/wire-format.md
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
# Wire format
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[`src/utils/constants.ts`](../src/utils/constants.ts)
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Chunk geometry is defined in
|
|
4
|
+
[`src/utils/constants.ts`](../src/utils/constants.ts) and byte-matched in
|
|
5
|
+
`utils.h`. Scheduled control subtypes are defined in
|
|
6
|
+
[`coverCell.ts`](../src/cryptography/coverCell.ts); immediate transfer controls
|
|
7
|
+
are defined in [`immediateDisposition.ts`](../src/handlers/immediateDisposition.ts).
|
|
8
|
+
For what these frames do and do not protect, read the
|
|
7
9
|
[protocol-v4 security boundary](protocol-v4-security.md).
|
|
8
10
|
|
|
9
|
-
|
|
10
11
|
## Outer frame types
|
|
11
12
|
|
|
12
13
|
One tag byte leads every frame on a data channel.
|
|
13
14
|
|
|
14
|
-
| Tag | Name
|
|
15
|
-
| --- |
|
|
16
|
-
| 1 | `HANDSHAKE`
|
|
17
|
-
| 2 | `CHUNK`
|
|
18
|
-
| 3 | `RECEIPT`
|
|
19
|
-
| 4 | `COVER`
|
|
20
|
-
| 5 | `PQ_CONTROL`
|
|
15
|
+
| Tag | Name | Size on the wire | Carries |
|
|
16
|
+
| --- | ----------------------- | ---------------- | ------------------------------------- |
|
|
17
|
+
| 1 | `HANDSHAKE` | step-dependent | HELLO, CONFIRM, FINISH |
|
|
18
|
+
| 2 | `CHUNK` | 65,490 B | one fixed application cell |
|
|
19
|
+
| 3 | `RECEIPT` | 65 B | SHA-512 acknowledgement token |
|
|
20
|
+
| 4 | `COVER` | 65,490 B | scheduled dummy or encrypted control |
|
|
21
|
+
| 5 | `PQ_CONTROL` | 65,490 B | sparse-PQ OFFER / ADVANCE / ACK |
|
|
22
|
+
| 6 | `IMMEDIATE_DISPOSITION` | 66 B | immediate resume capability or CANCEL |
|
|
21
23
|
|
|
22
|
-
Tags 2, 4, and 5
|
|
23
|
-
|
|
24
|
+
Tags 2, 4, and 5 have equal payload lengths. The frame tags travel inside
|
|
25
|
+
WebRTC's DTLS transport; packetization, timing and total traffic remain
|
|
26
|
+
observable. Scheduled real data uses tag 2 in a scheduled lane slot.
|
|
24
27
|
|
|
25
28
|
## Chunk frame — 65,490 bytes
|
|
26
29
|
|
|
@@ -52,6 +55,81 @@ application cell from a decoy or from a healing exchange by looking at the wire.
|
|
|
52
55
|
|
|
53
56
|
Both per-chunk acknowledgements and the terminal content-hash acknowledgement
|
|
54
57
|
use this exact geometry, so a completion is not distinguishable by size.
|
|
58
|
+
Chunk tokens bind the transfer Merkle root, original chunk index and leaf hash.
|
|
59
|
+
The sender resolves them through its staged chunk index and persists the
|
|
60
|
+
per-recipient acknowledgement before updating the live have-set. A terminal
|
|
61
|
+
immediate receipt must equal the message's content hash. These frames use the
|
|
62
|
+
authenticated concrete message channel and its current identity gate.
|
|
63
|
+
|
|
64
|
+
## Scheduled control cells — 65,490 bytes
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
type=4(1) || edge binding(32) || counter(8) || reserved zero(8) ||
|
|
68
|
+
PQ epoch(8) || nonce(12) || ciphertext(65,405) || AEAD tag(16)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
All 69 header bytes are authenticated. The key binds the room's suite, edge,
|
|
72
|
+
direction and PQ epoch. After authentication and counter replay rejection,
|
|
73
|
+
the plaintext is `subtype(1) || payload length(4, BE) || payload || zero pad`:
|
|
74
|
+
|
|
75
|
+
| Subtype | Meaning | Payload |
|
|
76
|
+
| ------- | ---------------- | ---------------------------------------------------------------------- |
|
|
77
|
+
| 1 | Dummy | Empty |
|
|
78
|
+
| 2 | CANCEL | Transfer Merkle root (64 B) |
|
|
79
|
+
| 3 | Terminal receipt | Transfer Merkle root (64 B), followed by the same root as token (64 B) |
|
|
80
|
+
| 4 | Chunk receipts | Transfer Merkle root (64 B), followed by 1–64 chunk tokens (64 B each) |
|
|
81
|
+
|
|
82
|
+
Subtype 4's authenticated payload length determines the token count; partial
|
|
83
|
+
tokens, empty batches and batches over 64 tokens are rejected. Only tokens for
|
|
84
|
+
one transfer root share a batch. A subtype 3 token unequal to its root cannot
|
|
85
|
+
complete a send. Each chunk token must resolve to the tracked transfer, root
|
|
86
|
+
and a valid original index. The durable owner also binds room, recipient ID
|
|
87
|
+
and public key; a stale owner cannot advance live state after a storage await.
|
|
88
|
+
A recipient's CANCEL records refusal only for that recipient.
|
|
89
|
+
|
|
90
|
+
Receipts and CANCELs replace dummy slots in the existing schedule. The pending
|
|
91
|
+
outbound control queue is bounded to 4,096 token/control units; dropping a
|
|
92
|
+
queued acknowledgement may require a later retransmission. A terminal receipt
|
|
93
|
+
supersedes unsent chunk acknowledgements for the same root, preserving the
|
|
94
|
+
first available reverse slot for completion even at long cadences. Other roots
|
|
95
|
+
and queued CANCEL controls retain their order. Live have-set
|
|
96
|
+
membership uses an O(1) `Set` lookup without copying the set per slot. A resumed
|
|
97
|
+
producer scans original indices once and emits missing chunks. It also selects
|
|
98
|
+
index zero once per attempt to recover a lost terminal receipt: if that index
|
|
99
|
+
was already acknowledged, this costs one extra completion probe. Index scanning
|
|
100
|
+
and durable token lookups do not imply globally sublinear processing, and fewer
|
|
101
|
+
real chunks do not reduce the fixed scheduled cell count.
|
|
102
|
+
|
|
103
|
+
Subtype 4 is a protocol-v4 wire addition. Earlier clients reject and drop this
|
|
104
|
+
unknown subtype and keep using subtype 3 completion. They cannot selectively
|
|
105
|
+
resume scheduled chunks; a missing terminal receipt can require whole-transfer
|
|
106
|
+
replay. Subtype 3 is never reused for chunk tokens, because earlier clients
|
|
107
|
+
treated any subtype 3 receipt for a tracked root as completion. These additions
|
|
108
|
+
do not introduce a fallback to an earlier cryptographic protocol version.
|
|
109
|
+
|
|
110
|
+
## Immediate pause and cancellation — 66 bytes
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
type=6(1) || kind(1) || transfer Merkle root(64)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Kind 0 announces resume support; kind 1 is explicit CANCEL. An immediate
|
|
117
|
+
message channel opened with DCEP protocol `p2party-resume-v1` promises that a
|
|
118
|
+
bare close may pause the transfer. A supporting receiver echoes kind 0 after
|
|
119
|
+
the identity gate opens; the opener then also treats a bare remote close as
|
|
120
|
+
pause. CANCEL is accepted once per concrete channel, only under its current
|
|
121
|
+
authenticated gate and exact root. Malformed, unknown, stale and cross-root
|
|
122
|
+
controls have no effect. This control relies on the authenticated DTLS/SCTP
|
|
123
|
+
channel; it has no separate application AEAD envelope.
|
|
124
|
+
|
|
125
|
+
An explicit cancellation sends kind 1 before closing. A transient failed
|
|
126
|
+
attempt closes without CANCEL, allowing a supporting receiver to retain its
|
|
127
|
+
partial data for bounded resume. Earlier clients ignore type 6 and retain
|
|
128
|
+
their close-as-cancel behavior. At a fresh immediate attempt the new sender
|
|
129
|
+
starts a fresh live chunk have-set and rebuilds it from the receiver's paced
|
|
130
|
+
receipt replay, so deleted legacy partials cause full resend
|
|
131
|
+
instead of trusting stale acknowledgements. The store-free session API does
|
|
132
|
+
not implement this WebRTC channel contract.
|
|
55
133
|
|
|
56
134
|
## Handshake ladder
|
|
57
135
|
|
|
@@ -88,6 +166,16 @@ rather than something both sides have to reconcile. Each side persists its
|
|
|
88
166
|
mutated state **before** dispatching, and application traffic is blocked while
|
|
89
167
|
an epoch is in flight.
|
|
90
168
|
|
|
169
|
+
The browser scheduler starts a local exchange only when no real producer,
|
|
170
|
+
derive-to-enqueue reservation, message channel or queued receive work remains.
|
|
171
|
+
Dummy/control cover alone does not hold it back. A remote exchange blocks new
|
|
172
|
+
application admission and drains reservations before the durable transition;
|
|
173
|
+
it does not wait for a long scheduled send to finish. A committed new epoch
|
|
174
|
+
retires old-key real producers and publishes a cover generation change on the
|
|
175
|
+
same runtime. The sender re-derives the remaining transfer under that epoch;
|
|
176
|
+
fixed lane timing continues with dummy tails. An unrelated generation notice
|
|
177
|
+
cannot authorize reuse of the same runtime without an actual PQ epoch advance.
|
|
178
|
+
|
|
91
179
|
## Room policy — 32 bytes
|
|
92
180
|
|
|
93
181
|
A room's policy is a fixed 32-byte record (magic `"P2RP"`) that pins the ML-KEM
|
|
@@ -1,15 +1,64 @@
|
|
|
1
|
+
import { type RequestRoomLeaveOptions } from "../utils/roomLeaveCoordinator";
|
|
1
2
|
import type { BaseQueryFn } from "@reduxjs/toolkit/query";
|
|
2
|
-
import type { WebSocketMessageCandidateSend, WebSocketMessageDescriptionSend, WebSocketMessageChallengeResponse, WebSocketMessageRoomIdRequest, WebSocketMessagePeersRequest, WebSocketMessageConnectionResponse, WebSocketMessagePongResponse, WebSocketPeerConnectionParams } from "../utils/interfaces";
|
|
3
|
+
import type { WebSocketMessageCandidateSend, WebSocketMessageDescriptionSend, WebSocketMessageChallengeResponse, WebSocketMessageRoomIdRequest, WebSocketMessagePeersRequest, WebSocketMessageConnectionResponse, WebSocketMessagePongResponse, WebSocketMessageLeaveRoomRequest, WebSocketPeerConnectionParams, WebSocketMessagePeerConnectionRequest } from "../utils/interfaces";
|
|
4
|
+
import type { BaseQueryApi } from "@reduxjs/toolkit/query";
|
|
3
5
|
export interface WebSocketParams {
|
|
4
6
|
signalingServerUrl: string;
|
|
5
7
|
}
|
|
6
8
|
export interface WebSocketMessage {
|
|
7
|
-
content: WebSocketMessagePongResponse | WebSocketMessageCandidateSend | WebSocketMessageDescriptionSend | WebSocketMessageChallengeResponse | WebSocketMessageRoomIdRequest | WebSocketMessagePeersRequest | WebSocketMessageConnectionResponse;
|
|
9
|
+
content: WebSocketMessagePongResponse | WebSocketMessageCandidateSend | WebSocketMessageDescriptionSend | WebSocketMessageChallengeResponse | WebSocketMessageRoomIdRequest | WebSocketMessageLeaveRoomRequest | WebSocketMessagePeersRequest | WebSocketMessageConnectionResponse | WebSocketMessagePeerConnectionRequest;
|
|
8
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* The host the reconnect controller reads Redux state from and dispatches
|
|
13
|
+
* through. Bound on every connect attempt; the tests rebind it directly. It is
|
|
14
|
+
* NOT the store import, so this module keeps its existing dependency edges.
|
|
15
|
+
*/
|
|
16
|
+
type SignalingReconnectHost = {
|
|
17
|
+
dispatch: (action: unknown) => unknown;
|
|
18
|
+
getState: () => unknown;
|
|
19
|
+
};
|
|
20
|
+
export declare const setSignalingReconnectHost: (host: SignalingReconnectHost | null) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Record how a socket ended, so the reconnect ladder can refuse a close the
|
|
23
|
+
* server means as final. `selfInitiated` is what keeps the SDK's own protective
|
|
24
|
+
* closes out of the policy: `onOverflow` closes with 1008 when this tab's
|
|
25
|
+
* ingress backlog is exceeded, and local overload is exactly what bounded
|
|
26
|
+
* backoff is for.
|
|
27
|
+
*/
|
|
28
|
+
export declare const noteSignalingSocketClose: (close: Readonly<{
|
|
29
|
+
code: number;
|
|
30
|
+
selfInitiated: boolean;
|
|
31
|
+
}>) => void;
|
|
32
|
+
/**
|
|
33
|
+
* A ladder rung is a floor, never shortened, and at most 25 % longer. Without
|
|
34
|
+
* this every client a server restart disconnected walks the identical
|
|
35
|
+
* 1/2/4/8 s ladder and re-handshakes in lockstep, which is the same stampede
|
|
36
|
+
* the restart caused. The spread lives here rather than in the controller
|
|
37
|
+
* because the controller's ladder is exact by contract and its tests assert
|
|
38
|
+
* exact delays; the scheduler this module injects is the seam that perturbs it.
|
|
39
|
+
*/
|
|
40
|
+
export declare const signalingReconnectDelayWithJitter: (delayMs: number) => number;
|
|
41
|
+
/**
|
|
42
|
+
* C4: a socket close that carries no browser wake event is otherwise terminal.
|
|
43
|
+
* `settleClosedSocket` detects; this reconnects. Shared by the close handler,
|
|
44
|
+
* the error handler and the heartbeat watchdog, so a peer whose socket died
|
|
45
|
+
* while its edges stayed authenticated is found again within 1-30 s instead of
|
|
46
|
+
* never.
|
|
47
|
+
*/
|
|
48
|
+
export declare const signalingReconnect: import("../utils/signalingReconnectController").SignalingReconnectController;
|
|
49
|
+
type CurrentSocketApi = Pick<BaseQueryApi, "dispatch" | "getState">;
|
|
50
|
+
/**
|
|
51
|
+
* Send a correlated leave on the exact authenticated socket and wait for its
|
|
52
|
+
* exact acknowledgement. The caller must make local room transport terminal
|
|
53
|
+
* before invoking this network-only boundary.
|
|
54
|
+
*/
|
|
55
|
+
export declare const requestCurrentSocketRoomLeave: (api: CurrentSocketApi, roomUrl: string, options?: RequestRoomLeaveOptions) => Promise<void>;
|
|
9
56
|
declare const signalingServerApi: import("@reduxjs/toolkit/query").Api<BaseQueryFn<WebSocketParams, undefined>, {
|
|
10
57
|
connectWebSocket: import("@reduxjs/toolkit/query").MutationDefinition<string, BaseQueryFn<WebSocketParams, undefined>, never, undefined, "signalingServerApi", undefined>;
|
|
11
58
|
disconnectWebSocket: import("@reduxjs/toolkit/query").MutationDefinition<undefined, BaseQueryFn<WebSocketParams, undefined>, never, undefined, "signalingServerApi", undefined>;
|
|
12
59
|
sendMessage: import("@reduxjs/toolkit/query").MutationDefinition<WebSocketMessage, BaseQueryFn<WebSocketParams, undefined>, never, undefined, "signalingServerApi", undefined>;
|
|
13
60
|
connectWithPeer: import("@reduxjs/toolkit/query").MutationDefinition<WebSocketPeerConnectionParams, BaseQueryFn<WebSocketParams, undefined>, never, undefined, "signalingServerApi", undefined>;
|
|
14
61
|
}, "signalingServerApi", never, typeof import("@reduxjs/toolkit/query").coreModuleName>;
|
|
62
|
+
/** Queue one room request or reject immediately when RTK reports no send. */
|
|
63
|
+
export declare const sendRoomRequestMessage: (api: Pick<BaseQueryApi, "dispatch">, request: WebSocketMessageRoomIdRequest) => Promise<void>;
|
|
15
64
|
export default signalingServerApi;
|
|
@@ -3,11 +3,20 @@ import type { RatchetState } from "../../cryptography/ratchet";
|
|
|
3
3
|
import type { RatchetGateLease } from "../../handlers/ratchetGate";
|
|
4
4
|
import type { CoverRuntime } from "../../handlers/coverRuntime";
|
|
5
5
|
import type { SparsePqHealingState } from "../../handlers/pqHealingRuntime";
|
|
6
|
+
import type { PeerHandshakeFailureReason } from "../../reducers/roomSlice";
|
|
7
|
+
import type { PeerSocketGenerationSource } from "./peerSocketGeneration";
|
|
6
8
|
export interface IRTCPeerConnection extends RTCPeerConnection {
|
|
7
9
|
/** The room this transport belongs to. A room/peer pair owns one PC. */
|
|
8
10
|
roomId: string;
|
|
9
11
|
withPeerId: string;
|
|
10
12
|
withPeerPublicKey: string;
|
|
13
|
+
/**
|
|
14
|
+
* The peer signaling socket this transport was built against. A different
|
|
15
|
+
* generation for the same peerId means the peer's session was replaced, and
|
|
16
|
+
* this transport can never be signalled again (C1). Absent against a
|
|
17
|
+
* pre-generation server, where it must never be read as a change.
|
|
18
|
+
*/
|
|
19
|
+
peerSocketGeneration?: string;
|
|
11
20
|
makingOffer: boolean;
|
|
12
21
|
ignoreOffer: boolean;
|
|
13
22
|
receiveMessageModule: LibCrypto;
|
|
@@ -16,8 +25,42 @@ export interface IRTCPeerConnection extends RTCPeerConnection {
|
|
|
16
25
|
mainChannel?: IRTCDataChannel;
|
|
17
26
|
/** Gate ownership captured when this concrete transport is created. */
|
|
18
27
|
ratchetGateLease: RatchetGateLease;
|
|
28
|
+
/**
|
|
29
|
+
* Set when this edge's handshake failed for a reason a fresh transport
|
|
30
|
+
* cannot fix (a wrong PIN, a PIN throttle, a policy/suite mismatch). The
|
|
31
|
+
* `main` channel's close handler consults it so a terminal failure is never
|
|
32
|
+
* followed by a re-dial. Cleared only by building a new transport.
|
|
33
|
+
*/
|
|
34
|
+
handshakeTerminalFailure?: PeerHandshakeFailureReason;
|
|
35
|
+
/**
|
|
36
|
+
* How many times this RTCPeerConnection has been re-bound IN PLACE onto a
|
|
37
|
+
* new remote DTLS certificate since it last authenticated. The re-bind
|
|
38
|
+
* budget (see decideRemoteTransportChangeResponse) reads it to guarantee
|
|
39
|
+
* that a peer which keeps presenting fresh certificates converges on a
|
|
40
|
+
* replacement transport instead of looping; the handshake success path
|
|
41
|
+
* clears it.
|
|
42
|
+
*/
|
|
43
|
+
remoteTransportRebinds: number;
|
|
44
|
+
/**
|
|
45
|
+
* Deadline armed by the in-place re-bind above: if this transport has not
|
|
46
|
+
* re-authenticated when it fires, the edge is torn down and rebuilt instead
|
|
47
|
+
* of waiting out the peer's 30 s handshake step timeout. Cleared by the
|
|
48
|
+
* handshake success path, by the teardown, and re-armed by a later re-bind.
|
|
49
|
+
*/
|
|
50
|
+
rebindDeadline?: ReturnType<typeof setTimeout>;
|
|
51
|
+
/**
|
|
52
|
+
* The `clearTimeout` that matches whatever scheduled `rebindDeadline`. The
|
|
53
|
+
* arming site owns the scheduler (tests inject one), while the clearing
|
|
54
|
+
* sites — handshake success, peer teardown — only hold the connection.
|
|
55
|
+
*/
|
|
56
|
+
rebindDeadlineClear?: (handle: ReturnType<typeof setTimeout>) => void;
|
|
19
57
|
/** Resolves once the per-transport WASM dependency is ready. */
|
|
20
58
|
initialization?: Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Re-proves signaling and applies the latest room ICE configuration before
|
|
61
|
+
* any restart. Installed by the connection owner and shared by repair paths.
|
|
62
|
+
*/
|
|
63
|
+
ensureFreshIceConfiguration?: () => Promise<boolean | void>;
|
|
21
64
|
ratchetState?: RatchetState;
|
|
22
65
|
ratchetEstablished?: Promise<void>;
|
|
23
66
|
messageKeyCache?: Map<string, Uint8Array>;
|
|
@@ -61,6 +104,13 @@ export interface IRTCDataChannel extends RTCDataChannel {
|
|
|
61
104
|
* delete its storage artifacts after the active handler reaches quiescence.
|
|
62
105
|
*/
|
|
63
106
|
cancelReceiveTransfer?: () => Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Set by any close THIS side initiated (the pre-authentication frame guard,
|
|
109
|
+
* the transfer's terminal-close runner, the resume path's cleanup). Read by
|
|
110
|
+
* `isAuthenticatedPeerCancel` so our own close is never reported as the peer
|
|
111
|
+
* cancelling the transfer (C7).
|
|
112
|
+
*/
|
|
113
|
+
closedLocally?: boolean;
|
|
64
114
|
}
|
|
65
115
|
export interface IRTCMessage {
|
|
66
116
|
id: string;
|
|
@@ -79,6 +129,15 @@ export interface RTCPeerConnectionParams {
|
|
|
79
129
|
peerPublicKey: string;
|
|
80
130
|
roomId: string;
|
|
81
131
|
rtcConfig?: RTCConfiguration;
|
|
132
|
+
/** The peer's signaling-socket generation, as last named by the server. */
|
|
133
|
+
peerSocketGeneration?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Which inbound frame named that generation. Only a `connection` frame
|
|
136
|
+
* proves a fresh remote transport; a roster introduction is record-only
|
|
137
|
+
* while this side's transport is connected (see peerSocketGeneration).
|
|
138
|
+
* Omitted means advisory.
|
|
139
|
+
*/
|
|
140
|
+
peerSocketGenerationSource?: PeerSocketGenerationSource;
|
|
82
141
|
}
|
|
83
142
|
export interface RTCSetDescriptionParams {
|
|
84
143
|
peerId: string;
|
|
@@ -92,6 +151,14 @@ export interface RTCSetCandidateParams {
|
|
|
92
151
|
roomId: string;
|
|
93
152
|
candidate: RTCIceCandidateInit | RTCIceCandidate;
|
|
94
153
|
}
|
|
154
|
+
export interface RTCApplyRoomConfigurationParams {
|
|
155
|
+
roomId: string;
|
|
156
|
+
rtcConfig: RTCConfiguration;
|
|
157
|
+
/** Gather a new ICE generation after every live PC accepts the config. */
|
|
158
|
+
restartIce?: boolean;
|
|
159
|
+
/** Expiry must close on failure; rollback would restore expired secrets. */
|
|
160
|
+
failurePolicy?: "rollback" | "close";
|
|
161
|
+
}
|
|
95
162
|
export interface RTCOpenChannelParams {
|
|
96
163
|
roomId: string;
|
|
97
164
|
channel: string | RTCDataChannel;
|
|
@@ -103,7 +170,7 @@ export interface RTCOpenChannelParams {
|
|
|
103
170
|
export interface RTCSendMessageParams {
|
|
104
171
|
/** Random 32-byte lowercase-hex identity allocated by the public API. */
|
|
105
172
|
transferId: string;
|
|
106
|
-
data
|
|
173
|
+
data?: string | File;
|
|
107
174
|
label: string;
|
|
108
175
|
roomId: string;
|
|
109
176
|
minChunks?: number;
|
|
@@ -148,6 +215,8 @@ export interface RTCDisconnectFromPeerParams {
|
|
|
148
215
|
roomId?: string;
|
|
149
216
|
alsoDeleteData?: boolean;
|
|
150
217
|
}
|
|
218
|
+
/** Internal dependency injected into bulk teardown queries to avoid API cycles. */
|
|
219
|
+
export type DisconnectPeerTransport = (params: RTCDisconnectFromPeerParams) => Promise<void>;
|
|
151
220
|
export interface RTCDisconnectFromChannelLabelParams {
|
|
152
221
|
roomId: string;
|
|
153
222
|
label: string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a peerId still names the same signaling session.
|
|
3
|
+
*
|
|
4
|
+
* The server binds one stable peerId to an identity public key, so a second
|
|
5
|
+
* tab or a phrase-restored second device reconnects as the SAME peerId and
|
|
6
|
+
* evicts its predecessor. Without a per-socket generation the edge stayed
|
|
7
|
+
* bound to the evicted socket: the transport never failed (no SDP, no ICE
|
|
8
|
+
* event), so nothing rebuilt it, and a responder on the new session creates no
|
|
9
|
+
* channel and can never offer — the split was permanent (C1).
|
|
10
|
+
*
|
|
11
|
+
* "unknown" is the fail-safe verdict: an absent or malformed generation (a
|
|
12
|
+
* pre-generation server, or a fresh transport that has not recorded one yet)
|
|
13
|
+
* must behave exactly as 0.14.7 does, never as a teardown trigger. The server
|
|
14
|
+
* leaves the field empty until a socket has proved ownership of its key and
|
|
15
|
+
* empties it again whenever a fresh challenge is issued, so "" and undefined
|
|
16
|
+
* are the same statement: no information.
|
|
17
|
+
*/
|
|
18
|
+
export type PeerSocketGenerationVerdict = "unknown" | "unchanged" | "changed";
|
|
19
|
+
/**
|
|
20
|
+
* Which inbound frame carried a generation, and therefore how much authority
|
|
21
|
+
* it has.
|
|
22
|
+
*
|
|
23
|
+
* - "connection": the peer's own `connection` frame. It is emitted from one
|
|
24
|
+
* place only (handleConnectToPeer's connectWithPeer), and that place is
|
|
25
|
+
* reachable only after a FRESH transport has been built — both baseQuery and
|
|
26
|
+
* handleConnectToPeer early-return on a live one. A changed generation there
|
|
27
|
+
* always means a genuinely new remote transport.
|
|
28
|
+
* - "roster": a `peers` introduction. The roster is re-requested on join, on
|
|
29
|
+
* re-dial AND on reconcile, and a signaling socket now reconnects on its
|
|
30
|
+
* own, so a benign reconnect of a healthy session routinely delivers a
|
|
31
|
+
* changed generation for an edge that is working perfectly. Acting on that
|
|
32
|
+
* is a regression of cluster C4, so it is record-only while the local
|
|
33
|
+
* transport is connected.
|
|
34
|
+
*/
|
|
35
|
+
export type PeerSocketGenerationSource = "connection" | "roster";
|
|
36
|
+
export declare const isCanonicalSocketGeneration: (value: unknown) => value is string;
|
|
37
|
+
export declare const comparePeerSocketGeneration: (recorded: string | undefined, observed: unknown) => PeerSocketGenerationVerdict;
|
|
38
|
+
export declare const markPeerSocketGenerationRetired: (roomId: string, peerId: string, generation: unknown) => void;
|
|
39
|
+
export declare const isPeerSocketGenerationRetired: (roomId: string, peerId: string, generation: unknown) => boolean;
|
|
40
|
+
export declare const clearRetiredPeerSocketGenerations: (roomId: string, peerId: string) => void;
|
|
41
|
+
export interface PeerSocketGenerationObservation {
|
|
42
|
+
roomId: string;
|
|
43
|
+
peerId: string;
|
|
44
|
+
/** The generation the local transport was built against, if any. */
|
|
45
|
+
recorded: string | undefined;
|
|
46
|
+
/** The generation this frame names for the peer. */
|
|
47
|
+
observed: unknown;
|
|
48
|
+
/** Which inbound frame carried it; an unattributed frame is advisory. */
|
|
49
|
+
source: PeerSocketGenerationSource | undefined;
|
|
50
|
+
/** The local transport's current state. */
|
|
51
|
+
connectionState: RTCPeerConnectionState;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether an observed generation is reason enough to retire the local
|
|
55
|
+
* transport for this edge and rebuild it against the peer's live session.
|
|
56
|
+
*
|
|
57
|
+
* Deliberately conservative in two directions. A roster introduction is acted
|
|
58
|
+
* on only when the local transport is demonstrably not working — a healthy
|
|
59
|
+
* edge must survive an ordinary signaling reconnect (C4). And a generation
|
|
60
|
+
* this edge has already retired never triggers a second teardown, however it
|
|
61
|
+
* arrives.
|
|
62
|
+
*
|
|
63
|
+
* "not working" excludes "disconnected". handleConnectToPeer answers that state
|
|
64
|
+
* with an ICE restart and only tears the edge down if it is still disconnected
|
|
65
|
+
* 8 s later, and the restart usually succeeds — so a roster frame arriving in
|
|
66
|
+
* that window would replace a transport that was about to heal itself.
|
|
67
|
+
*/
|
|
68
|
+
export declare const shouldRetireTransportForSocketGeneration: ({ roomId, peerId, recorded, observed, source, connectionState, }: PeerSocketGenerationObservation) => boolean;
|
|
@@ -5,6 +5,8 @@ export declare const COVER_CELL_PLAINTEXT_BYTES: number;
|
|
|
5
5
|
export declare const COVER_CELL_BINDING_BYTES = 32;
|
|
6
6
|
export declare const COVER_CELL_ROOT_BYTES = 32;
|
|
7
7
|
export declare const COVER_CELL_TOKEN_BYTES: number;
|
|
8
|
+
/** Bound token lookup/persistence work per authenticated scheduled cell. */
|
|
9
|
+
export declare const MAX_COVER_CHUNK_RECEIPTS = 64;
|
|
8
10
|
export declare const COVER_CELL_MAX_PAYLOAD_BYTES: number;
|
|
9
11
|
export type CoverCellDirection = "initiator-to-responder" | "responder-to-initiator";
|
|
10
12
|
export type CoverCellContent = {
|
|
@@ -21,6 +23,11 @@ export type CoverCellContent = {
|
|
|
21
23
|
readonly subtype: "receipt";
|
|
22
24
|
readonly merkleRoot: Uint8Array;
|
|
23
25
|
readonly token: Uint8Array;
|
|
26
|
+
} | {
|
|
27
|
+
/** Subtype 4: old clients drop this unknown subtype, never treat it as completion. */
|
|
28
|
+
readonly subtype: "chunk-receipts";
|
|
29
|
+
readonly merkleRoot: Uint8Array;
|
|
30
|
+
readonly tokens: readonly Uint8Array[];
|
|
24
31
|
};
|
|
25
32
|
export type CoverCellErrorCode = "authentication-failed" | "binding-mismatch" | "epoch-mismatch" | "invalid-cell" | "invalid-direction" | "invalid-padding" | "replayed-counter";
|
|
26
33
|
export declare class CoverCellError extends Error {
|