p2party 0.14.2 → 0.14.7
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 +230 -30
- package/docs/getting-started.md +15 -0
- package/docs/protocol-v4-security.md +40 -0
- package/docs/references.md +28 -8
- package/docs/session-api.md +10 -2
- package/lib/api/signalingServerApi.d.ts +13 -2
- package/lib/api/webrtc/applyIceConfiguration.d.ts +9 -0
- package/lib/api/webrtc/dataChannelHandler.d.ts +10 -0
- package/lib/api/webrtc/deleteRoomData.d.ts +13 -0
- package/lib/api/webrtc/disconnectFromAllRoomsQuery.d.ts +2 -1
- package/lib/api/webrtc/disconnectFromRoomQuery.d.ts +2 -1
- package/lib/api/webrtc/disconnectQuery.d.ts +2 -1
- package/lib/api/webrtc/iceRepair.d.ts +3 -1
- package/lib/api/webrtc/index.d.ts +2 -1
- package/lib/api/webrtc/interfaces.d.ts +37 -0
- package/lib/api/webrtc/negotiationLock.d.ts +24 -0
- package/lib/api/webrtc/pendingOffer.d.ts +29 -0
- package/lib/api/webrtc/rebindDeadline.d.ts +61 -0
- package/lib/api/webrtc/remoteTransportChange.d.ts +142 -0
- package/lib/api/webrtc/retiredRemoteTransports.d.ts +70 -0
- package/lib/api/webrtc/roomEdgeBudget.d.ts +10 -0
- package/lib/api/webrtc/setDescriptionQuery.d.ts +8 -1
- package/lib/api/webrtc/settleOnClose.d.ts +36 -0
- package/lib/blindRendezvous/admissionInput.d.ts +11 -0
- package/lib/blindRendezvous/admissionPolicy.d.ts +217 -0
- package/lib/blindRendezvous/canonicalSchema.d.ts +139 -0
- package/lib/blindRendezvous/carrierEnvelope.d.ts +105 -0
- package/lib/blindRendezvous/coreReduce.d.ts +199 -0
- package/lib/blindRendezvous/derivations.d.ts +381 -0
- package/lib/blindRendezvous/identityHelloAdmission.d.ts +89 -0
- package/lib/blindRendezvous/identityHelloAdmissionPersistence.d.ts +133 -0
- package/lib/blindRendezvous/identityHelloHistoricalEvidence.d.ts +52 -0
- package/lib/blindRendezvous/identityHelloOpen.d.ts +167 -0
- package/lib/blindRendezvous/identityHelloTrial.d.ts +271 -0
- package/lib/blindRendezvous/localAdmissibleProject.d.ts +101 -0
- package/lib/blindRendezvous/localPresenceKeyBinding.d.ts +108 -0
- package/lib/blindRendezvous/localStableIdentityCustody.d.ts +46 -0
- package/lib/blindRendezvous/normativeKernel.d.ts +222 -0
- package/lib/blindRendezvous/operation.d.ts +137 -0
- package/lib/blindRendezvous/operationPayload.d.ts +134 -0
- package/lib/blindRendezvous/pairBootstrapSuite.d.ts +93 -0
- package/lib/blindRendezvous/pairCrypto.d.ts +122 -0
- package/lib/blindRendezvous/pairLedger.d.ts +154 -0
- package/lib/blindRendezvous/pairOpenBudget.d.ts +67 -0
- package/lib/blindRendezvous/pairOpenReservation.d.ts +157 -0
- package/lib/blindRendezvous/pairPlaintext.d.ts +240 -0
- package/lib/blindRendezvous/pairStepEnvelope.d.ts +60 -0
- package/lib/blindRendezvous/plannerState.d.ts +61 -0
- package/lib/blindRendezvous/projections.d.ts +71 -0
- package/lib/blindRendezvous/publicWindow.d.ts +330 -0
- package/lib/blindRendezvous/retainedOperationStore.d.ts +187 -0
- package/lib/blindRendezvous/roomInviteV2.d.ts +92 -0
- package/lib/blindRendezvous/roomPolicyV4.d.ts +90 -0
- package/lib/blindRendezvous/schemas.d.ts +221 -0
- package/lib/blindRendezvous/storeDescriptor.d.ts +160 -0
- package/lib/cryptography/aeadWasm.d.ts +18 -0
- package/lib/cryptography/byteInput.d.ts +12 -0
- package/lib/cryptography/carrierAead.d.ts +23 -0
- package/lib/cryptography/chacha20poly1305.d.ts +7 -0
- package/lib/cryptography/fips202.d.ts +10 -0
- package/lib/cryptography/hpke.d.ts +84 -0
- package/lib/cryptography/hpkeSuite.d.ts +39 -0
- package/lib/cryptography/hybridKem.d.ts +42 -0
- package/lib/cryptography/hybridKemSuite.d.ts +22 -0
- package/lib/cryptography/interfaces.d.ts +3 -0
- package/lib/cryptography/mlkem.d.ts +27 -1
- package/lib/cryptography/mnemonic.d.ts +65 -1
- package/lib/cryptography/ownedEd25519KeyMaterial.d.ts +27 -0
- package/lib/cryptography/webCryptoSha256.d.ts +20 -0
- package/lib/cryptography/x25519.d.ts +18 -0
- package/lib/cryptography/xchacha20poly1305.d.ts +7 -0
- package/lib/db/api.d.ts +6 -3
- package/lib/db/identityEd25519ClientBoundary.d.ts +13 -0
- package/lib/db/src/getDB.d.ts +2 -2
- package/lib/db/types.d.ts +52 -4
- package/lib/db.worker.js +1 -1
- package/lib/handlers/coverChannelRegistry.d.ts +22 -0
- package/lib/handlers/edgeTeardownFollowUp.d.ts +35 -0
- package/lib/handlers/handleChallenge.d.ts +2 -1
- package/lib/handlers/handleConnectToPeer.d.ts +1 -1
- package/lib/handlers/handleHandshake.d.ts +2 -6
- package/lib/handlers/handleSendMessage.d.ts +60 -7
- package/lib/handlers/handleWebSocketMessage.d.ts +1 -1
- package/lib/handlers/handshakeCore.d.ts +2 -7
- package/lib/handlers/handshakeFrame.d.ts +25 -0
- package/lib/handlers/ratchetGateWait.d.ts +13 -0
- package/lib/handlers/reconcile.d.ts +24 -0
- package/lib/handlers/requestRoom.d.ts +30 -0
- package/lib/handlers/roomResponse.d.ts +53 -0
- package/lib/index.d.ts +99 -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/middleware/roomListenerMiddleware.d.ts +4 -1
- package/lib/reducers/commonSlice.d.ts +3 -2
- package/lib/reducers/keyPairSlice.d.ts +3 -6
- package/lib/reducers/roomSlice.d.ts +81 -5
- 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 +12 -0
- package/lib/utils/correlationId.d.ts +4 -0
- package/lib/utils/identityRestore.d.ts +108 -0
- package/lib/utils/interfaces.d.ts +25 -6
- package/lib/utils/roomConnectAdmission.d.ts +15 -0
- package/lib/utils/roomLeaveCoordinator.d.ts +38 -0
- package/lib/utils/roomRequestCoordinator.d.ts +104 -0
- package/lib/utils/roomRosterReconciler.d.ts +83 -0
- package/lib/utils/sdpFingerprint.d.ts +6 -0
- package/lib/utils/signalingAttemptLifecycle.d.ts +33 -0
- package/lib/utils/signalingAuth.d.ts +21 -2
- package/lib/utils/signalingBounds.d.ts +6 -0
- package/lib/utils/signalingFrame.d.ts +21 -0
- package/lib/utils/signalingHeartbeatWatchdog.d.ts +45 -0
- package/lib/utils/signalingIngressQueue.d.ts +49 -0
- package/lib/utils/signalingPeerRequestDebouncer.d.ts +13 -0
- package/lib/utils/signalingRoomLifecycle.d.ts +7 -0
- package/lib/utils/signalingRoomOperationLifecycle.d.ts +14 -0
- package/lib/utils/signalingServerBoundary.d.ts +11 -0
- package/lib/utils/terminalSettlement.d.ts +53 -0
- package/lib/utils/transportIdentity.d.ts +5 -0
- package/package.json +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { RTCPeerConnectionParams, RTCSendMessageParams, RTCSetDescriptionParams, RTCSetCandidateParams, RTCOpenChannelParams, RTCDisconnectFromRoomParams, RTCDisconnectFromAllRoomsParams, RTCDisconnectFromPeerParams, RTCDisconnectFromChannelLabelParams, RTCDisconnectFromPeerChannelLabelParams, RTCDisconnectParams } from "./interfaces";
|
|
1
|
+
import type { RTCPeerConnectionParams, RTCSendMessageParams, RTCSetDescriptionParams, RTCSetCandidateParams, RTCOpenChannelParams, RTCDisconnectFromRoomParams, RTCDisconnectFromAllRoomsParams, RTCDisconnectFromPeerParams, RTCDisconnectFromChannelLabelParams, RTCDisconnectFromPeerChannelLabelParams, RTCDisconnectParams, RTCApplyRoomConfigurationParams } from "./interfaces";
|
|
2
2
|
import type { SendMessageResult } from "../../handlers/handleSendMessage";
|
|
3
3
|
declare const webrtcApi: import("@reduxjs/toolkit/query").Api<import("@reduxjs/toolkit/query").BaseQueryFn<import("./baseQuery").RTCPeerConnectionParamsExtend, undefined>, {
|
|
4
4
|
connectWithPeer: import("@reduxjs/toolkit/query").MutationDefinition<RTCPeerConnectionParams, import("@reduxjs/toolkit/query").BaseQueryFn<import("./baseQuery").RTCPeerConnectionParamsExtend, undefined>, never, undefined, "webrtcApi", undefined>;
|
|
5
5
|
setDescription: import("@reduxjs/toolkit/query").MutationDefinition<RTCSetDescriptionParams, import("@reduxjs/toolkit/query").BaseQueryFn<import("./baseQuery").RTCPeerConnectionParamsExtend, undefined>, never, undefined, "webrtcApi", undefined>;
|
|
6
6
|
setCandidate: import("@reduxjs/toolkit/query").MutationDefinition<RTCSetCandidateParams, import("@reduxjs/toolkit/query").BaseQueryFn<import("./baseQuery").RTCPeerConnectionParamsExtend, undefined>, never, undefined, "webrtcApi", undefined>;
|
|
7
|
+
applyRoomConfiguration: import("@reduxjs/toolkit/query").MutationDefinition<RTCApplyRoomConfigurationParams, import("@reduxjs/toolkit/query").BaseQueryFn<import("./baseQuery").RTCPeerConnectionParamsExtend, undefined>, never, undefined, "webrtcApi", undefined>;
|
|
7
8
|
openChannel: import("@reduxjs/toolkit/query").MutationDefinition<RTCOpenChannelParams, import("@reduxjs/toolkit/query").BaseQueryFn<import("./baseQuery").RTCPeerConnectionParamsExtend, undefined>, never, undefined, "webrtcApi", undefined>;
|
|
8
9
|
sendMessage: import("@reduxjs/toolkit/query").MutationDefinition<RTCSendMessageParams, import("@reduxjs/toolkit/query").BaseQueryFn<import("./baseQuery").RTCPeerConnectionParamsExtend, undefined>, never, SendMessageResult | undefined, "webrtcApi", undefined>;
|
|
9
10
|
disconnect: import("@reduxjs/toolkit/query").MutationDefinition<RTCDisconnectParams, import("@reduxjs/toolkit/query").BaseQueryFn<import("./baseQuery").RTCPeerConnectionParamsExtend, undefined>, never, undefined, "webrtcApi", undefined>;
|
|
@@ -16,8 +16,35 @@ export interface IRTCPeerConnection extends RTCPeerConnection {
|
|
|
16
16
|
mainChannel?: IRTCDataChannel;
|
|
17
17
|
/** Gate ownership captured when this concrete transport is created. */
|
|
18
18
|
ratchetGateLease: RatchetGateLease;
|
|
19
|
+
/**
|
|
20
|
+
* How many times this RTCPeerConnection has been re-bound IN PLACE onto a
|
|
21
|
+
* new remote DTLS certificate since it last authenticated. The re-bind
|
|
22
|
+
* budget (see decideRemoteTransportChangeResponse) reads it to guarantee
|
|
23
|
+
* that a peer which keeps presenting fresh certificates converges on a
|
|
24
|
+
* replacement transport instead of looping; the handshake success path
|
|
25
|
+
* clears it.
|
|
26
|
+
*/
|
|
27
|
+
remoteTransportRebinds: number;
|
|
28
|
+
/**
|
|
29
|
+
* Deadline armed by the in-place re-bind above: if this transport has not
|
|
30
|
+
* re-authenticated when it fires, the edge is torn down and rebuilt instead
|
|
31
|
+
* of waiting out the peer's 30 s handshake step timeout. Cleared by the
|
|
32
|
+
* handshake success path, by the teardown, and re-armed by a later re-bind.
|
|
33
|
+
*/
|
|
34
|
+
rebindDeadline?: ReturnType<typeof setTimeout>;
|
|
35
|
+
/**
|
|
36
|
+
* The `clearTimeout` that matches whatever scheduled `rebindDeadline`. The
|
|
37
|
+
* arming site owns the scheduler (tests inject one), while the clearing
|
|
38
|
+
* sites — handshake success, peer teardown — only hold the connection.
|
|
39
|
+
*/
|
|
40
|
+
rebindDeadlineClear?: (handle: ReturnType<typeof setTimeout>) => void;
|
|
19
41
|
/** Resolves once the per-transport WASM dependency is ready. */
|
|
20
42
|
initialization?: Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Re-proves signaling and applies the latest room ICE configuration before
|
|
45
|
+
* any restart. Installed by the connection owner and shared by repair paths.
|
|
46
|
+
*/
|
|
47
|
+
ensureFreshIceConfiguration?: () => Promise<boolean | void>;
|
|
21
48
|
ratchetState?: RatchetState;
|
|
22
49
|
ratchetEstablished?: Promise<void>;
|
|
23
50
|
messageKeyCache?: Map<string, Uint8Array>;
|
|
@@ -92,6 +119,14 @@ export interface RTCSetCandidateParams {
|
|
|
92
119
|
roomId: string;
|
|
93
120
|
candidate: RTCIceCandidateInit | RTCIceCandidate;
|
|
94
121
|
}
|
|
122
|
+
export interface RTCApplyRoomConfigurationParams {
|
|
123
|
+
roomId: string;
|
|
124
|
+
rtcConfig: RTCConfiguration;
|
|
125
|
+
/** Gather a new ICE generation after every live PC accepts the config. */
|
|
126
|
+
restartIce?: boolean;
|
|
127
|
+
/** Expiry must close on failure; rollback would restore expired secrets. */
|
|
128
|
+
failurePolicy?: "rollback" | "close";
|
|
129
|
+
}
|
|
95
130
|
export interface RTCOpenChannelParams {
|
|
96
131
|
roomId: string;
|
|
97
132
|
channel: string | RTCDataChannel;
|
|
@@ -148,6 +183,8 @@ export interface RTCDisconnectFromPeerParams {
|
|
|
148
183
|
roomId?: string;
|
|
149
184
|
alsoDeleteData?: boolean;
|
|
150
185
|
}
|
|
186
|
+
/** Internal dependency injected into bulk teardown queries to avoid API cycles. */
|
|
187
|
+
export type DisconnectPeerTransport = (params: RTCDisconnectFromPeerParams) => Promise<void>;
|
|
151
188
|
export interface RTCDisconnectFromChannelLabelParams {
|
|
152
189
|
roomId: string;
|
|
153
190
|
label: string;
|
|
@@ -6,7 +6,31 @@ export declare const getRoomPeerMutex: (roomId: string, peerId: string) => Async
|
|
|
6
6
|
* enter the same critical section concurrently.
|
|
7
7
|
*/
|
|
8
8
|
export declare const releaseRoomPeerMutex: (roomId: string, peerId: string) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Serialize one negotiation operation and release its registry key once every
|
|
11
|
+
* queued owner drains. This must wrap nonterminal frames too: otherwise valid
|
|
12
|
+
* unknown-peer SDP/ICE identifiers can grow the mutex registry forever.
|
|
13
|
+
*/
|
|
14
|
+
export declare const runRoomPeerExclusive: <T>(roomId: string, peerId: string, operation: () => Promise<T>) => Promise<T>;
|
|
9
15
|
/** Narrow test visibility without exposing the mutable registry. */
|
|
10
16
|
export declare const hasRoomPeerMutex: (roomId: string, peerId: string) => boolean;
|
|
17
|
+
/** Narrow test visibility without exposing the mutable registry. */
|
|
18
|
+
export declare const roomPeerMutexCount: () => number;
|
|
11
19
|
export declare const getRoomIdentityMutex: (roomId: string, peerPublicKey: string) => AsyncMutex;
|
|
12
20
|
export declare const releaseRoomIdentityMutex: (roomId: string, peerPublicKey: string) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Serializes caller configuration mutation and a room-response live sweep +
|
|
23
|
+
* Redux publication with the final configuration read, construction, and
|
|
24
|
+
* registry insertion of a new PC. The key is the stable room URL, including
|
|
25
|
+
* while the room is still awaiting its server-assigned id.
|
|
26
|
+
*/
|
|
27
|
+
export declare const getRoomIceConfigurationMutex: (roomUrl: string) => AsyncMutex;
|
|
28
|
+
export declare const releaseRoomIceConfigurationMutex: (roomUrl: string) => void;
|
|
29
|
+
/** Narrow test visibility without exposing the mutable registry. */
|
|
30
|
+
export declare const hasRoomIceConfigurationMutex: (roomUrl: string) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Server-assigned room ids are globally unique, so validation, live apply, and
|
|
33
|
+
* publication of assignments must be one global transaction. Room responses
|
|
34
|
+
* are rare; serializing them is simpler and safer than multi-key lock ordering.
|
|
35
|
+
*/
|
|
36
|
+
export declare const runRoomResponseCommitExclusive: <T>(operation: () => Promise<T>) => Promise<T>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IRTCPeerConnection } from "./interfaces";
|
|
2
|
+
/**
|
|
3
|
+
* How long a fresh transport may hold its first offer without any remote
|
|
4
|
+
* description before the transport is given up on and the room re-dialed.
|
|
5
|
+
*
|
|
6
|
+
* Must outlive the signaling server's dark-socket drop (one missed 10 s ping
|
|
7
|
+
* plus its 5 s pong window): an offer sent to a peer that was still on the
|
|
8
|
+
* roster but whose socket had already gone dark is lost when the server
|
|
9
|
+
* closes that socket, and by the time this fires the roster no longer lists
|
|
10
|
+
* the peer, so the re-dial builds nothing for it until it is really back.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PENDING_OFFER_TIMEOUT_MS = 15000;
|
|
13
|
+
/**
|
|
14
|
+
* True iff this transport holds a local offer that no remote description has
|
|
15
|
+
* ever answered, i.e. its FIRST offer is still pending.
|
|
16
|
+
*
|
|
17
|
+
* An ICE-restart offer on a negotiated transport is excluded: there the
|
|
18
|
+
* remote description of the previous round is still applied, and the ICE
|
|
19
|
+
* repair paths own that timeout. Only the identity initiator ever offers on
|
|
20
|
+
* a fresh transport (the responder creates no channel there), so a pending
|
|
21
|
+
* first offer belongs to exactly one side of an edge.
|
|
22
|
+
*
|
|
23
|
+
* Such a transport never fails on its own: without a remote description no
|
|
24
|
+
* ICE checks run, so connectionState stays "new" and nothing tears it down.
|
|
25
|
+
* A peer that later re-dials with a `connection` frame (its old transport
|
|
26
|
+
* died while its socket was dark, so it never saw the offer) finds this PC
|
|
27
|
+
* reused by ensureConnection, which would leave the edge half-built forever.
|
|
28
|
+
*/
|
|
29
|
+
export declare const hasUnansweredFirstOffer: (epc: Pick<IRTCPeerConnection, "signalingState" | "remoteDescription">) => boolean;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { BaseQueryApi } from "@reduxjs/toolkit/query";
|
|
2
|
+
import type { DisconnectPeerTransport, IRTCPeerConnection } from "./interfaces";
|
|
3
|
+
/**
|
|
4
|
+
* The timer primitives the deadline uses. Injected so a test drives the
|
|
5
|
+
* deadline by hand instead of waiting out real seconds; production passes
|
|
6
|
+
* nothing and gets the platform's own.
|
|
7
|
+
*/
|
|
8
|
+
export interface RebindDeadlineScheduler {
|
|
9
|
+
readonly setTimeout: (callback: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
|
|
10
|
+
readonly clearTimeout: (handle: ReturnType<typeof setTimeout>) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Release a pending re-bind deadline. Idempotent, and safe on a connection
|
|
14
|
+
* that never armed one.
|
|
15
|
+
*/
|
|
16
|
+
export declare const clearRebindAuthenticationDeadline: (epc: Pick<IRTCPeerConnection, "rebindDeadline" | "rebindDeadlineClear">) => void;
|
|
17
|
+
export interface ArmRebindAuthenticationDeadlineParams {
|
|
18
|
+
epc: IRTCPeerConnection;
|
|
19
|
+
roomId: string;
|
|
20
|
+
peerId: string;
|
|
21
|
+
api: Pick<BaseQueryApi, "dispatch" | "getState">;
|
|
22
|
+
/** Tears this room/peer transport down through the transport-failure path. */
|
|
23
|
+
disconnectPeer: DisconnectPeerTransport;
|
|
24
|
+
deadlineMs?: number;
|
|
25
|
+
schedule?: RebindDeadlineScheduler;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Give one in-place re-bind a wall-clock deadline to re-authenticate in.
|
|
29
|
+
*
|
|
30
|
+
* A re-bind keeps the RTCPeerConnection — and with it the SCTP association
|
|
31
|
+
* that was negotiated against the RETIRED remote certificate. Chromium will
|
|
32
|
+
* happily open our own new outbound streams on it while never delivering the
|
|
33
|
+
* peer's: on the process-freeze harness the woken responder re-binds its
|
|
34
|
+
* surviving PC, its own message channel reaches the peer two seconds later,
|
|
35
|
+
* and the peer's `main` — opened on its brand-new PC before the re-bind even
|
|
36
|
+
* happened — never arrives at all. The re-bind is then a silent black hole:
|
|
37
|
+
* ICE stays connected so no failure path replaces the transport, and the edge
|
|
38
|
+
* only heals when the PEER's handshake step timeout (30 s) tears its own side
|
|
39
|
+
* down and the normal re-dial rebuilds everything, ~37 s after the wake.
|
|
40
|
+
*
|
|
41
|
+
* So the re-bind gets a budget. When it expires with nothing authenticated,
|
|
42
|
+
* this side does what the peer's timeout would eventually have done, sooner:
|
|
43
|
+
* tear the edge down through the transport-failure path (which mints a fresh
|
|
44
|
+
* local certificate on the rebuilt transport) and ask the room to re-introduce
|
|
45
|
+
* the pair.
|
|
46
|
+
*
|
|
47
|
+
* Three guards make a late timer harmless, because the same connection object
|
|
48
|
+
* outlives the re-bind that armed the deadline:
|
|
49
|
+
*
|
|
50
|
+
* - the connection still carries the gate lease this re-bind installed (a
|
|
51
|
+
* LATER re-bind of the same transport installed its own and re-armed);
|
|
52
|
+
* - that lease is still the edge's current one (a replacement transport, from
|
|
53
|
+
* a re-dial or a "replace" decision, claims a fresh lease — this deadline
|
|
54
|
+
* then belongs to a transport nobody uses any more);
|
|
55
|
+
* - the edge has not authenticated in the meantime (a live ratchet, or the
|
|
56
|
+
* gate open under this very lease).
|
|
57
|
+
*
|
|
58
|
+
* The teardown itself is not a guard: `webrtcDisconnectPeerQuery` clears the
|
|
59
|
+
* deadline, so a torn-down connection never fires it at all.
|
|
60
|
+
*/
|
|
61
|
+
export declare const armRebindAuthenticationDeadline: ({ epc, roomId, peerId, api, disconnectPeer, deadlineMs, schedule, }: ArmRebindAuthenticationDeadlineParams) => void;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { BaseQueryApi } from "@reduxjs/toolkit/query";
|
|
2
|
+
import type { RebindDeadlineScheduler } from "./rebindDeadline";
|
|
3
|
+
import type { DisconnectPeerTransport, IRTCDataChannel, IRTCPeerConnection } from "./interfaces";
|
|
4
|
+
/**
|
|
5
|
+
* True iff both SDPs declare a sha-256 DTLS fingerprint and the two differ.
|
|
6
|
+
*
|
|
7
|
+
* A remote description whose fingerprint differs from the one already applied
|
|
8
|
+
* on this RTCPeerConnection means the peer built a NEW RTCPeerConnection (a
|
|
9
|
+
* fresh DTLS certificate is minted per PC) while this side kept its old one.
|
|
10
|
+
* The browser accepts the change and re-runs DTLS on the same PC, but every
|
|
11
|
+
* piece of edge authentication this side holds — handshake transcript (which
|
|
12
|
+
* binds both fingerprints), ratchet, PQ runtime, message keys — was proven
|
|
13
|
+
* against the OLD certificate and must not survive onto the new transport.
|
|
14
|
+
*
|
|
15
|
+
* Missing or malformed fingerprints on either side are not a change: there is
|
|
16
|
+
* nothing to compare, and the handshake itself rejects such SDPs later.
|
|
17
|
+
*/
|
|
18
|
+
export declare const detectRemoteTransportChange: (previousSdp: string | undefined, nextSdp: string) => boolean;
|
|
19
|
+
/**
|
|
20
|
+
* True iff this room/peer edge has been authenticated on THIS
|
|
21
|
+
* RTCPeerConnection: a live ratchet, a ratchet gate opened under this
|
|
22
|
+
* transport's own lease, or an open `main` (a handshake in flight over a live
|
|
23
|
+
* SCTP association). A transport that only ever negotiated (main still
|
|
24
|
+
* connecting, gate pending) has proven nothing yet.
|
|
25
|
+
*/
|
|
26
|
+
export declare const isEdgeAuthenticatedOnTransport: (epc: Pick<IRTCPeerConnection, "ratchetState" | "ratchetGateLease" | "mainChannel">, roomId: string, peerId: string) => boolean;
|
|
27
|
+
export type RemoteTransportChangeResponse = "reset-in-place" | "replace";
|
|
28
|
+
export interface RemoteTransportChangeDecisionParams {
|
|
29
|
+
epc: Pick<IRTCPeerConnection, "ratchetState" | "ratchetGateLease" | "mainChannel" | "remoteTransportRebinds">;
|
|
30
|
+
roomId: string;
|
|
31
|
+
peerId: string;
|
|
32
|
+
descriptionType: RTCSdpType;
|
|
33
|
+
/** Whether this side is the identity initiator (the side that opens main). */
|
|
34
|
+
isInitiator: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* What to do with an existing RTCPeerConnection whose remote DTLS fingerprint
|
|
38
|
+
* is about to change.
|
|
39
|
+
*
|
|
40
|
+
* A never-authenticated transport is simply re-bound in place: nothing proven
|
|
41
|
+
* against the old certificate exists, and the browser renegotiates DTLS on
|
|
42
|
+
* the same PC (the T1 path, `resetEdgeAuthenticationForNewRemoteTransport`).
|
|
43
|
+
*
|
|
44
|
+
* An AUTHENTICATED transport whose peer shows up with a new certificate is
|
|
45
|
+
* the stale side of the edge: its SCTP association is dead on the far side
|
|
46
|
+
* while it is still writable locally. Whether it can be re-bound in place
|
|
47
|
+
* depends on who has to open the next `main` on it:
|
|
48
|
+
*
|
|
49
|
+
* - The peer's fresh transport OFFERS: it carries the peer's channels, so a
|
|
50
|
+
* fresh PC of our own answers it cleanly whatever our role. Replace.
|
|
51
|
+
* - We are the identity initiator and receive an ANSWER: re-binding in place
|
|
52
|
+
* would make us create the replacement `main` on the OLD, still-writable
|
|
53
|
+
* SCTP association, whose onopen fires before DTLS re-runs against the new
|
|
54
|
+
* certificate; the handshake then reads the old remote certificate from
|
|
55
|
+
* getStats and fails as a fingerprint mismatch. Replace the PC and re-dial.
|
|
56
|
+
* - We are the responder and receive an ANSWER: we open no channel on the
|
|
57
|
+
* reset PC. The peer's fresh transport (the initiator) already holds the
|
|
58
|
+
* next `main`, which opens only once the browser has re-run DTLS and SCTP
|
|
59
|
+
* against the new certificate, so the in-place re-bind completes with a
|
|
60
|
+
* clean handshake. Replacing here would instead leave the peer's fresh PC
|
|
61
|
+
* bound to our closed one with nothing to re-offer to our channel-less
|
|
62
|
+
* replacement (a never-connected transport does not re-dial on failure).
|
|
63
|
+
*
|
|
64
|
+
* All of that is bounded by one invariant: AT MOST ONE in-place re-bind per
|
|
65
|
+
* unauthenticated transport. An in-place re-bind changes only the REMOTE
|
|
66
|
+
* certificate — ours is minted with the RTCPeerConnection and lives as long as
|
|
67
|
+
* it does. A PC whose ICE stays connected never fails, so neither the
|
|
68
|
+
* ICE-failure re-dial nor the grace-timer teardown ever replaces it, and it
|
|
69
|
+
* keeps answering with a certificate the peer may already have retired (T9)
|
|
70
|
+
* while the peer keeps offering from ever newer ones: a livelock in which both
|
|
71
|
+
* sides negotiate forever and no `main` ever opens. Requiring the first
|
|
72
|
+
* re-bind to have actually authenticated before granting a second one caps the
|
|
73
|
+
* loop at one round — the second fingerprint change replaces the transport,
|
|
74
|
+
* which mints a new local certificate the peer has never retired, and the edge
|
|
75
|
+
* converges.
|
|
76
|
+
*/
|
|
77
|
+
export declare const decideRemoteTransportChangeResponse: ({ epc, roomId, peerId, descriptionType, isInitiator, }: RemoteTransportChangeDecisionParams) => RemoteTransportChangeResponse;
|
|
78
|
+
export interface ResetEdgeAuthenticationParams {
|
|
79
|
+
epc: IRTCPeerConnection;
|
|
80
|
+
roomId: string;
|
|
81
|
+
peerId: string;
|
|
82
|
+
api: Pick<BaseQueryApi, "dispatch" | "getState">;
|
|
83
|
+
/**
|
|
84
|
+
* The shared open-channel registry. Passed in explicitly (the caller already
|
|
85
|
+
* holds it) so this helper stays store-free and unit-testable: it owns the
|
|
86
|
+
* retirement of the generation's channels, which live in BOTH this array and
|
|
87
|
+
* `epc.messageChannels`.
|
|
88
|
+
*/
|
|
89
|
+
dataChannels: IRTCDataChannel[];
|
|
90
|
+
/**
|
|
91
|
+
* Tears this room/peer transport down through the transport-failure path.
|
|
92
|
+
* Injected exactly as setDescriptionQuery's own teardown is, so the deadline
|
|
93
|
+
* armed below needs no import of the API that registers it.
|
|
94
|
+
*/
|
|
95
|
+
disconnectPeer: DisconnectPeerTransport;
|
|
96
|
+
/** Test seams for the re-bind deadline; production takes the defaults. */
|
|
97
|
+
deadlineMs?: number;
|
|
98
|
+
schedule?: RebindDeadlineScheduler;
|
|
99
|
+
reason: Error;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Drop everything that authenticated this room/peer edge against the previous
|
|
103
|
+
* remote DTLS certificate, WITHOUT tearing the RTCPeerConnection down.
|
|
104
|
+
*
|
|
105
|
+
* This is the "reset-in-place" response of decideRemoteTransportChangeResponse:
|
|
106
|
+
* a transport that never authenticated, or the responder's transport when the
|
|
107
|
+
* initiator answers from a fresh one. The PC itself survives: the browser
|
|
108
|
+
* renegotiates DTLS on it against the new remote certificate, and the next
|
|
109
|
+
* `main` (created by the identity initiator on a never-authenticated PC, or
|
|
110
|
+
* arriving from the initiator's fresh transport on the responder's) opens
|
|
111
|
+
* only over the re-run DTLS/SCTP, so the new handshake binds the new
|
|
112
|
+
* fingerprint pair. The old `main` is closed here; its onclose handler
|
|
113
|
+
* observes a stale gate lease (replaced below) and returns early, so closing
|
|
114
|
+
* it never cascades into a disconnectFromPeer of the edge we are keeping.
|
|
115
|
+
*
|
|
116
|
+
* EVERY other channel of the retired generation goes with it. A per-message
|
|
117
|
+
* channel belongs to the ratchet generation that opened it: it captured that
|
|
118
|
+
* generation's gate lease, so once the lease is replaced here its next inbound
|
|
119
|
+
* frame can only be rejected as application data before peer authentication —
|
|
120
|
+
* and that rejection closes the channel, which on a live transfer cascades
|
|
121
|
+
* into closing `main` and tearing down the very edge this re-bind exists to
|
|
122
|
+
* keep. (Observed on the process-freeze harness: an 8 MiB transfer in flight
|
|
123
|
+
* across the freeze left its data channel OPEN on the surviving PC, and the
|
|
124
|
+
* first frame after the re-bind killed the edge.) A send in flight over such a
|
|
125
|
+
* channel loses nothing: the gate lease replaced here is exactly what tells
|
|
126
|
+
* handleSendMessage that the close was OURS, so the transfer resumes on the
|
|
127
|
+
* re-authenticated generation (re-opening its channel, re-stepping the ratchet)
|
|
128
|
+
* instead of being read as a cancel by the peer. Cover lanes are NOT swept
|
|
129
|
+
* here — `teardownCoverEdge` above closes each one, and the close listener
|
|
130
|
+
* `registerCoverChannelUntilClose` installed unregisters it from both
|
|
131
|
+
* `epc.coverChannels` and the shared array.
|
|
132
|
+
*
|
|
133
|
+
* Detaching those handlers also skips the receive-side bookkeeping a normal
|
|
134
|
+
* close would have run — `forgetCompletedReceiveMessageKey`. That is
|
|
135
|
+
* deliberate: retiring a key durably is asynchronous I/O and this reset is a
|
|
136
|
+
* synchronous step on the description path. Every key is wiped from RAM below
|
|
137
|
+
* (message-key cache filled with zeros, ratchet wiped, PQ runtime destroyed),
|
|
138
|
+
* and the durable edge checkpoint is keyed by (roomId, peerPublicKey) and
|
|
139
|
+
* rewritten whole by the next handshake's persist, so a completed transfer's
|
|
140
|
+
* key survives only until this edge re-authenticates.
|
|
141
|
+
*/
|
|
142
|
+
export declare const resetEdgeAuthenticationForNewRemoteTransport: ({ epc, roomId, peerId, api, dataChannels, disconnectPeer, deadlineMs, schedule, reason, }: ResetEdgeAuthenticationParams) => void;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote DTLS certificates this side has stopped believing in, per
|
|
3
|
+
* (roomId, peerId) edge.
|
|
4
|
+
*
|
|
5
|
+
* A description is only ever meaningful for the transport pair it belongs to,
|
|
6
|
+
* but nothing on the wire correlates an answer with the offer it answers (the
|
|
7
|
+
* SDK cannot add signaling fields without a server change). So a description
|
|
8
|
+
* that a peer produced on a transport we already retired can land on a
|
|
9
|
+
* transport of ours that has no remote description yet — where the
|
|
10
|
+
* fingerprint-change detection of `remoteTransportChange` returns false BY
|
|
11
|
+
* CONSTRUCTION, because there is nothing to compare against — and bind our
|
|
12
|
+
* fresh RTCPeerConnection to a certificate that is already dead.
|
|
13
|
+
*
|
|
14
|
+
* That is exactly how a frozen phone whose signaling socket stayed open
|
|
15
|
+
* deadlocks an edge: the peer's queued ICE-restart offer is answered from its
|
|
16
|
+
* old, still-authenticated transport after the thaw; the answer arrives at
|
|
17
|
+
* our brand-new PC, is accepted, and the correct answer that follows it is
|
|
18
|
+
* then dropped as "stale" because the PC is already `stable`. Neither side's
|
|
19
|
+
* transport ever connects, so no failure path re-dials either.
|
|
20
|
+
*
|
|
21
|
+
* Remembering the fingerprint of every transport we retire closes that hole:
|
|
22
|
+
* an ANSWER carrying one is discarded before it is applied (setDescriptionQuery
|
|
23
|
+
* explains why an offer is instead accepted, and un-retires the transport it
|
|
24
|
+
* binds through `forgetRetiredRemoteTransport`).
|
|
25
|
+
*
|
|
26
|
+
* The set is bounded twice, because it is keyed by a peer-supplied identifier
|
|
27
|
+
* and must never grow without limit nor lock a peer out forever:
|
|
28
|
+
*
|
|
29
|
+
* - at most MAX_RETIRED_REMOTE_TRANSPORTS_PER_EDGE fingerprints per edge, the
|
|
30
|
+
* oldest evicted first;
|
|
31
|
+
* - each entry expires RETIRED_REMOTE_TRANSPORT_TTL_MS after it was retired.
|
|
32
|
+
*
|
|
33
|
+
* Chromium mints a fresh certificate for every RTCPeerConnection and this
|
|
34
|
+
* codebase never passes `RTCConfiguration.certificates` for its own
|
|
35
|
+
* connections (roomSlice only CLONES a caller-supplied configuration), so a
|
|
36
|
+
* rebuilt peer transport always presents a fingerprint we have never retired.
|
|
37
|
+
* If a caller DOES pin a certificate, its peer's replacement transport
|
|
38
|
+
* presents the very fingerprint we retired and its descriptions are discarded
|
|
39
|
+
* until the TTL expires; the edge then heals on the next re-dial, one TTL
|
|
40
|
+
* later, instead of immediately.
|
|
41
|
+
*/
|
|
42
|
+
export declare const MAX_RETIRED_REMOTE_TRANSPORTS_PER_EDGE = 4;
|
|
43
|
+
export declare const RETIRED_REMOTE_TRANSPORT_TTL_MS = 120000;
|
|
44
|
+
/**
|
|
45
|
+
* Remember that this side has stopped believing in the remote transport that
|
|
46
|
+
* presented `transport` (an SDP blob or its raw sha-256 fingerprint) on the
|
|
47
|
+
* (roomId, peerId) edge. Returns whether a fingerprint was actually recorded.
|
|
48
|
+
*/
|
|
49
|
+
export declare const retireRemoteTransport: (roomId: string, peerId: string, transport: string | Uint8Array | null | undefined, now?: number) => boolean;
|
|
50
|
+
/**
|
|
51
|
+
* True iff `sdp` declares the certificate of a remote transport already
|
|
52
|
+
* retired for this edge, i.e. the description must not be applied.
|
|
53
|
+
*/
|
|
54
|
+
export declare const isRetiredRemoteTransport: (roomId: string, peerId: string, sdp: string | null | undefined, now?: number) => boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Un-retire one transport of an edge: this side is bound to it again.
|
|
57
|
+
*
|
|
58
|
+
* A retired fingerprint means "we are not bound to this remote certificate",
|
|
59
|
+
* so the moment a description from it IS applied — the peer offered on the
|
|
60
|
+
* transport we had given up on, proving it negotiates there — the retirement
|
|
61
|
+
* is void. Without this, the answer to our own next ICE restart on that very
|
|
62
|
+
* binding would be discarded as retired.
|
|
63
|
+
*/
|
|
64
|
+
export declare const forgetRetiredRemoteTransport: (roomId: string, peerId: string, transport: string | Uint8Array | null | undefined) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Forget every retired transport of one edge. Called when the edge itself is
|
|
67
|
+
* terminal — its room was left or purged — so a room the user rejoins later
|
|
68
|
+
* starts with a clean slate.
|
|
69
|
+
*/
|
|
70
|
+
export declare const forgetRetiredRemoteTransports: (roomId: string, peerId: string) => void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Peer } from "../../reducers/roomSlice";
|
|
2
|
+
import type { IRTCPeerConnection } from "./interfaces";
|
|
3
|
+
/**
|
|
4
|
+
* Enforce one cumulative edge budget across Redux roster state and concrete
|
|
5
|
+
* transports, including PCs inserted while their WASM initialization awaits.
|
|
6
|
+
*
|
|
7
|
+
* Callers run this under the stable room-configuration mutex immediately
|
|
8
|
+
* before registry insertion, making concurrent identities observe each other.
|
|
9
|
+
*/
|
|
10
|
+
export declare const assertRoomEdgeBudget: (roomId: string, peers: readonly Peer[], peerConnections: readonly IRTCPeerConnection[], candidatePublicKey: string) => void;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { BaseQueryFn } from "@reduxjs/toolkit/query";
|
|
2
|
-
import type { RTCSetDescriptionParams, IRTCPeerConnection, IRTCIceCandidate, IRTCDataChannel } from "./interfaces";
|
|
2
|
+
import type { RTCSetDescriptionParams, IRTCPeerConnection, IRTCIceCandidate, IRTCDataChannel, DisconnectPeerTransport } from "./interfaces";
|
|
3
3
|
export interface RTCSetDescriptionParamsExtension extends RTCSetDescriptionParams {
|
|
4
4
|
peerConnections: IRTCPeerConnection[];
|
|
5
5
|
iceCandidates: IRTCIceCandidate[];
|
|
6
6
|
dataChannels: IRTCDataChannel[];
|
|
7
|
+
/**
|
|
8
|
+
* Tears one room/peer transport down through the same path as a transport
|
|
9
|
+
* failure (disconnectFromPeer). Injected, like the bulk teardown queries
|
|
10
|
+
* do, so this module needs no import of the API that registers it and a
|
|
11
|
+
* test can bind the real query to its own transport registry.
|
|
12
|
+
*/
|
|
13
|
+
disconnectPeer: DisconnectPeerTransport;
|
|
7
14
|
}
|
|
8
15
|
declare const webrtcSetDescriptionQuery: BaseQueryFn<RTCSetDescriptionParamsExtension, undefined>;
|
|
9
16
|
export default webrtcSetDescriptionQuery;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** The slice of an RTCPeerConnection that proves the transport is gone. */
|
|
2
|
+
export type PcOperationTarget = Pick<RTCPeerConnection, "signalingState" | "connectionState">;
|
|
3
|
+
export interface AwaitPcOperationOptions {
|
|
4
|
+
/** How often the transport is inspected while the operation is in flight. */
|
|
5
|
+
readonly pollMs?: number;
|
|
6
|
+
/** Wall-clock budget after which the operation is abandoned. */
|
|
7
|
+
readonly maxMs?: number;
|
|
8
|
+
/** Named in the timeout warning so a stuck operation is identifiable. */
|
|
9
|
+
readonly name?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const isPcOperationTargetClosed: (epc: PcOperationTarget) => boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Await one RTCPeerConnection operation without ever waiting forever on it.
|
|
14
|
+
*
|
|
15
|
+
* Closing an RTCPeerConnection aborts every operation in flight on it, and the
|
|
16
|
+
* specification aborts them *without settling* their promises — while close()
|
|
17
|
+
* itself fires no `connectionstatechange`. An `await pc.addIceCandidate(...)`
|
|
18
|
+
* or `await pc.setRemoteDescription(...)` on a connection that closes
|
|
19
|
+
* underneath it therefore never returns. In this codebase those awaits run
|
|
20
|
+
* inside the (room, peer) negotiation mutex, which is entered from the socket's
|
|
21
|
+
* strictly serial ingress queue: one such await strands the edge's mutex
|
|
22
|
+
* forever AND stops every later signaling frame on that socket, so a peer's
|
|
23
|
+
* re-introductions go unanswered indefinitely.
|
|
24
|
+
*
|
|
25
|
+
* The returned promise settles three ways:
|
|
26
|
+
* - the operation's own result (or rejection, rethrown for the caller's
|
|
27
|
+
* existing error handling),
|
|
28
|
+
* - `"closed"` as soon as a poll observes the transport closed — the operation
|
|
29
|
+
* is abandoned, since nothing it could still do matters,
|
|
30
|
+
* - `"timeout"` once the budget expires, which callers treat exactly like a
|
|
31
|
+
* failure of that operation.
|
|
32
|
+
*
|
|
33
|
+
* An abandoned operation's later rejection is consumed here, so walking away
|
|
34
|
+
* from it can never surface as an unhandled rejection.
|
|
35
|
+
*/
|
|
36
|
+
export declare const awaitPcOperation: <T>(epc: PcOperationTarget, operation: Promise<T>, { pollMs, maxMs, name, }?: AwaitPcOperationOptions) => Promise<T | "closed" | "timeout">;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closed error for malformed or fabricated inputs at the trusted admission
|
|
3
|
+
* kernel boundary. Policy denials are ordinary result values and never use
|
|
4
|
+
* this error.
|
|
5
|
+
*/
|
|
6
|
+
export declare class AdmissionInputError extends Error {
|
|
7
|
+
readonly name = "AdmissionInputError";
|
|
8
|
+
readonly code: "invalid-admission-kernel-input";
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
export declare const invalidAdmissionInput: (message: string) => never;
|