# PeerBandwidthTracker _(no submodules)_ ## Purpose `PeerBandwidthTracker` keeps a rolling-window record of per-peer transfer activity for the WebRTC mesh: bytes in and out, request counts in both directions, and an EWMA-smoothed round-trip time. It exposes a composite `score` in `[0, 1]` that `@suaveplan/drive-webrtc`'s `PeerManager` uses to rank candidate sources, and a `canServe` / `assertCanServe` pair the chunk-source layer calls before serving a remote request, so noisy or abusive peers can be detected and throttled without coupling every caller to per-peer accounting. ## Features - Rolling window of `windowMs` (default 60s) with explicit `rollWindow` hook callable from a periodic timer. - Per-peer byte / request counters in both directions. - EWMA RTT (`α = 0.2`) preserved across window rolls. - Composite score: `0.5 * (1 − bytesInLoad) + 0.3 * (1 − requestsInLoad) + 0.2 * rttFactor`. - New peers with no samples start at `score = 1.0` so they are not unfairly demoted on first sight. - `assertCanServe` throws `DriveWebRtcError(PEER_BANDWIDTH_EXCEEDED)`. - Injectable `clock` for deterministic tests. ## Usage: Basic Example ```ts import { PeerBandwidthTracker } from "@suaveplan/drive-webrtc"; const tracker = new PeerBandwidthTracker(); tracker.recordBytesIn("peer-1", 16_000); tracker.recordRtt("peer-1", 42); const metrics = tracker.get("peer-1"); console.log(metrics.score, metrics.rttMs); ``` ## Usage: Advanced Example ```ts import { PeerBandwidthTracker, DriveWebRtcError } from "@suaveplan/drive-webrtc"; import { FakeClock } from "@suaveplan/testing"; const clock = new FakeClock(); const tracker = new PeerBandwidthTracker({ windowMs: 1_000, maxBytesPerWindow: 100_000, maxRequestsPerWindow: 50, clock: { now: () => clock.now() }, }); for (let i = 0; i < 50; i++) tracker.recordRequestIn("peer-1"); try { tracker.assertCanServe("peer-1"); } catch (err) { if (err instanceof DriveWebRtcError) { // err.code === "DRIVE_WEBRTC.PEER_BANDWIDTH_EXCEEDED" } } clock.advance(1_001); tracker.rollWindow(); tracker.assertCanServe("peer-1"); // recovered ``` ## API Reference ### `new PeerBandwidthTracker(config?)` Construct a tracker. `config` accepts `windowMs`, `maxBytesPerWindow`, `maxRequestsPerWindow`, `clock`. ### `recordBytesIn(peerId, bytes)` / `recordBytesOut(peerId, bytes)` Add bytes to the inbound or outbound counter, lazily rolling the window if the peer's window has expired. ### `recordRequestIn(peerId)` / `recordRequestOut(peerId)` Increment the inbound / outbound request counter by one. ### `recordRtt(peerId, rttMs)` Update the EWMA RTT estimate (`α = 0.2`). The first sample seeds the estimate verbatim. ### `canServe(peerId): boolean` Returns `false` when the peer's total bytes or total requests in the current window meet or exceed the configured thresholds. ### `assertCanServe(peerId): void` Throws `DriveWebRtcError(PEER_BANDWIDTH_EXCEEDED)` when `canServe` returns `false`. ### `get(peerId): PeerMetrics` Snapshot of the peer's current window. Returns a zero-valued metrics object with `score = 1` if the peer has never been touched. ### `snapshot(): readonly PeerMetrics[]` Array of metrics for every tracked peer. ### `forget(peerId)` Drop all state for the peer — call on disconnect. ### `rollWindow(now?)` Force-roll every peer's window. Counters past `windowMs` are zeroed; RTT estimates persist. ## Implementation Notes - The window rolls lazily on every read / record call so callers do not need to schedule `rollWindow` for correctness — it is provided for callers that want predictable timing. - Scores compose three normalised loads with hand-picked weights (50% inbound bytes, 30% inbound requests, 20% RTT). The weights match the upstream design intent of biasing against peers that are saturating us with traffic versus peers that are slow. - When `maxBytesPerWindow` / `maxRequestsPerWindow` are `Infinity` (default), the load components collapse to zero and the score is driven entirely by RTT. - The class never throws on `record*` — recording is always safe; only `assertCanServe` raises.