# @suaveplan/drive-webrtc — WebRTC P2P Mesh Capability Browser-tier WebRTC peer-to-peer mesh plugin for `@suaveplan/drive`: signalling client, RTC client (offer/answer/ICE/reconnect), peer manager with bandwidth tracking + topology strategies, data-channel pool, chunk discovery + integrity verification, sub-chunk splitter/reassembler, LRU cache, rate limiter, security validators, and a `WebRTCDrivePlugin` orchestrator that wires them all together. ## Requirements ### Requirement: Signalling client surface The package SHALL expose `SignalingClient` over a WebSocket transport: - Injectable `webSocketCtor` (defaults to `globalThis.WebSocket`) for testability. - Zod-validated inbound messages via `SignalingMessageSchema`; malformed payloads MUST emit `"error"` (not close the socket) with `DriveWebRtcError(SIGNALING_MESSAGE_INVALID)`. - Reconnect with configurable backoff array + `maxReconnectAttempts`; emits `"reconnect:attempt"`, `"reconnect:success"`, `"reconnect:exhausted"` events. - `send` while not open MUST throw `DriveWebRtcError(SIGNALING_UNREACHABLE)`. - AbortSignal threaded through `connect`. #### Scenario: Pre-aborted connect rejects - **WHEN** `client.connect(signal)` is called with an already-aborted signal - **THEN** the returned promise MUST reject with `DriveWebRtcError`. ### Requirement: WebRTC client + ICE-restart policy The package SHALL expose `WebRTCClient` and `resolveConfig`: - `resolveConfig(input)` Zod-validates via `WebRTCPluginConfigSchema`; on failure throws `DriveWebRtcError(CONFIG_INVALID, { cause: zodError })`. - `WebRTCClient` lazy-creates one `RTCPeerConnection` per `remotePeerId` and caches in an internal map. - On `iceConnectionState === "failed"`, automatically calls `pc.restartIce()` up to `maxIceRestartAttempts` (default 3) and emits `"ice:restart"` with `{ peerId, attempt, timestamp }`. - After exhaustion, no further restart attempts fire; caller decides to evict. - `createOffer` / `handleOffer` / `handleAnswer` / `addIceCandidate` translate DOMExceptions into `DriveWebRtcError(PEER_HANDSHAKE_FAILED | ICE_FAILED)` with original error in `cause`. ### Requirement: Real signature verification `ChunkIntegrityManager.verifySignature` MUST perform real Ed25519 verification via `@suaveplan/crypto-curve` `ed25519.verify(signature, message, publicKey)` over canonical `hash || utf8(chunkId)`. NO placeholder, NO "forward-looking" wording allowed in implementation or docs. #### Scenario: Tampered signature rejected - **WHEN** `verifySignature` is called with a signature whose any byte has been flipped - **THEN** it MUST return `{ ok: false, reason: "bad-signature", code: "CHUNK_INTEGRITY_FAILED" }`. ### Requirement: Hash verification `computeChunkHash` MUST compute SHA-256 over the chunk bytes; `verifyChunkIntegrity` MUST recompute the hash and compare constant-time via `constantTimeEqual` from `@suaveplan/crypto-utils`. ### Requirement: Sub-chunk splitter + reassembler The package SHALL expose `splitIntoSubChunks`, `sendSubChunks`, and `SubChunkReassembler`: - Default `maxBytes = 16_000` (DataChannel safe payload). - Returns parts tagged `{ chunkId, partId, index, total, bytes }`. - `sendSubChunks(parts, callback, { signal?, delayMs? })` is transport-agnostic, AbortSignal-aware on both pre-iteration and inter-part delays. - `SubChunkReassembler.addPart` returns a discriminated union (`incomplete` / `complete` / `duplicate`). - `assertComplete(chunkId)` throws `DriveWebRtcError(SUB_CHUNK_MISSING)` when any index is absent. - `cleanup()` purges expired in-progress assemblies past `timeoutMs`; `assertComplete` then throws `SUB_CHUNK_TIMEOUT`. ### Requirement: Peer manager + topology strategies The package SHALL expose `PeerManager`, `PeerBandwidthTracker`, and three `TopologyStrategy` implementations: `FullMeshStrategy`, `SuperPeerStrategy`, `HubAndSpokeStrategy`. Each strategy implements `selectConnections` and `admitOrEvict` for capacity-bound peer admission. `PeerBandwidthTracker` MUST compose `score = 0.5*(1-bytesInLoad) + 0.3*(1-requestsInLoad) + 0.2*rttFactor` and expose a snapshot for ranking. Score MUST default to `1.0` when no samples have been recorded. ### Requirement: Data channel manager `DataChannelManager` SHALL pool `RTCDataChannel`s per `(peerId, label)`, back-pressure via `bufferedAmount`/`bufferedamountlow`, and route messages by label. `send` MUST throw `DriveWebRtcError(DATA_CHANNEL_CLOSED)` when the channel is not open. `open(peerId)` on an unknown peer throws `DriveWebRtcError(PEER_NOT_FOUND)`. ### Requirement: Chunk discovery with peer fallback `ChunkDiscoveryManager.request(chunkId)` SHALL: 1. De-duplicate concurrent requests for the same `chunkId`. 2. Rank candidate peers by `PeerBandwidthTracker` score. 3. Iterate candidates in order, sending `request-chunk` and awaiting reassembly within `requestTimeoutMs`. 4. Verify via `ChunkIntegrityManager.verify(chunk, publicKey?)`. 5. On any candidate failure (timeout / disconnect / integrity), emit `"request:fail"` and move to next candidate. 6. After all candidates exhausted, emit `"request:exhausted"` and throw `DriveWebRtcError(CHUNK_NOT_AVAILABLE)`. ### Requirement: WebRTCDrivePlugin orchestration The package SHALL expose `WebRTCDrivePlugin` with `name: "drive-webrtc"` and lifecycle methods `start(signal?)` / `stop(reason?)` / `stats()`. The plugin MUST wire `SignalingClient`, `WebRTCClient`, `PeerManager`, `DataChannelManager`, `ChunkIntegrityManager`, `ChunkDiscoveryManager`, and `WebRTCChunkSource` internally and forward their events through a unified `WebRTCEventMap`. #### Scenario: stop is idempotent - **WHEN** `plugin.stop()` is invoked twice in succession - **THEN** the second invocation MUST NOT throw and MUST NOT re-close already- closed transports. ### Requirement: Subpath exports + tree-shaking `package.json` SHALL expose 11 subpath exports covering `./client/webrtc-client`, `./chunk-source/chunk-source`, `./plugin/webrtc-plugin`, `./signaling/signaling-client`, `./peer-manager/peer-manager`, `./data-channel/data-channel-manager`, `./rate-limiter/rate-limiter`, `./lru-cache/lru-cache`, `./integrity/chunk-integrity`, `./security/security-validator`, `./sub-chunk/sub-chunk-splitter`. Leaf subpaths MUST NOT drag in unrelated subsystems (verified via per-subpath bundle- size snapshot tests). ### Requirement: Universal-tier-clean tests Tests MUST NOT use `vi.stubGlobal`, `vi.mock`, or any runtime monkey-patch. WebSocket/RTCPeerConnection paths are exercised via injected structural real implementations (`FakeWebSocket`, `FakeRTCPeerConnection`, `FakeRTCDataChannel`) using the `webSocketCtor` / `rtcPeerConnectionCtor` options. All test imports come from `@suaveplan/testing/runner`. ### Requirement: OTel observability The package SHALL register the following instruments under tracer `@suaveplan/drive-webrtc`: - Counter `drive.webrtc.peers_connected_total` - Counter `drive.webrtc.chunks_served_total{outcome}` - Counter `drive.webrtc.chunks_rejected_total{reason}` - Counter `drive.webrtc.signaling_messages_total{direction,outcome}` - Histogram `drive.webrtc.peer_rtt_ms` - Histogram `drive.webrtc.chunk_transfer_ms` - Histogram `drive.webrtc.signaling_reconnect_ms` ### Requirement: Coverage + gate `bunx turbo lint typecheck test:coverage --filter=@suaveplan/drive-webrtc` MUST be green and istanbul coverage MUST be 100% on every touched module before any release.