1const NETWORK_DIAGNOSTICS_VERSION = 1;2const PATH_MODES = new Set(['direct', 'relay', 'custom', 'unknown']);34function record(value, label) {5 if (!value || typeof value !== 'object' || Array.isArray(value)) {6 throw new TypeError(`${label} must be an object`);7 }8 return value;9}1011function exactUnsigned(value, label) {12 if (!Number.isSafeInteger(value) || value < 0) {13 throw new TypeError(`${label} must be an exact unsigned integer`);14 }15 return value;16}1718function optionalExactUnsigned(value, label) {19 return value === null ? null : exactUnsigned(value, label);20}2122function boolean(value, label) {23 if (typeof value !== 'boolean') throw new TypeError(`${label} must be a boolean`);24 return value;25}2627function durationMs(value, label) {28 if (!Number.isFinite(value) || value < 0) {29 throw new TypeError(`${label} must be a finite non-negative duration in milliseconds`);30 }31 return value;32}3334function percentileSummary(value, prefix, sampleCount, overflowTotal) {35 const p50 = value[`${prefix}_p50_ms`];36 const p95 = value[`${prefix}_p95_ms`];37 const max = value[`${prefix}_max_ms`];38 if (sampleCount === 0) {39 if (overflowTotal !== 0 || p50 !== null || p95 !== null || max !== null) {40 throw new TypeError(`${prefix} percentiles require at least one retained sample`);41 }42 return { sampleCount, p50Ms: null, p95Ms: null, maxMs: null, overflowTotal };43 }4445 if (overflowTotal > sampleCount) {46 throw new RangeError(`${prefix}_overflow_total cannot exceed its sample count`);47 }48 const retainedSamples = sampleCount - overflowTotal;49 const p50Available = retainedSamples >= Math.ceil(sampleCount * 0.5);50 const p95Available = retainedSamples >= Math.ceil(sampleCount * 0.95);51 if ((p50 !== null) !== p50Available || (p95 !== null) !== p95Available) {52 throw new TypeError(`${prefix} percentile availability does not match bounded samples`);53 }54 const p50Ms = p50 === null ? null : durationMs(p50, `${prefix}_p50_ms`);55 const p95Ms = p95 === null ? null : durationMs(p95, `${prefix}_p95_ms`);56 const maxMs = durationMs(max, `${prefix}_max_ms`);57 if ((p50Ms !== null && p50Ms > maxMs)58 || (p95Ms !== null && p95Ms > maxMs)59 || (p50Ms !== null && p95Ms !== null && p50Ms > p95Ms)) {60 throw new RangeError(`${prefix} percentiles must be monotonic`);61 }62 return { sampleCount, p50Ms, p95Ms, maxMs, overflowTotal };63}6465function normalizeLeg(value, label) {66 const leg = record(value, label);67 if (!PATH_MODES.has(leg.mode)) throw new TypeError(`${label}.mode is unsupported`);6869 const sampleCount = exactUnsigned(leg.sample_count, `${label}.sample_count`);70 const directSamples = exactUnsigned(leg.direct_samples, `${label}.direct_samples`);71 const relaySamples = exactUnsigned(leg.relay_samples, `${label}.relay_samples`);72 const customSamples = exactUnsigned(leg.custom_samples, `${label}.custom_samples`);73 const unknownSamples = exactUnsigned(leg.unknown_samples, `${label}.unknown_samples`);74 const classifiedSamples = directSamples + relaySamples + customSamples + unknownSamples;75 if (!Number.isSafeInteger(classifiedSamples) || classifiedSamples !== sampleCount) {76 throw new RangeError(`${label} path sample buckets must exactly match sample_count`);77 }7879 const rttSampleCount = exactUnsigned(leg.rtt_sample_count, `${label}.rtt_sample_count`);80 const rttOverflowTotal = exactUnsigned(leg.rtt_overflow_total, `${label}.rtt_overflow_total`);81 const rtt = percentileSummary(leg, 'rtt', rttSampleCount, rttOverflowTotal);82 const rttCurrentMs = leg.rtt_current_ms === null83 ? null84 : durationMs(leg.rtt_current_ms, `${label}.rtt_current_ms`);85 if ((rttSampleCount === 0 || leg.mode === 'unknown') && rttCurrentMs !== null) {86 throw new RangeError(`${label}.rtt_current_ms requires a selected measured path`);87 }8889 const counterRegressionTotal = exactUnsigned(90 leg.counter_regression_total,91 `${label}.counter_regression_total`,92 );93 const complete = boolean(leg.complete, `${label}.complete`);94 if (complete !== (counterRegressionTotal === 0 && rttOverflowTotal === 0)) {95 throw new RangeError(`${label}.complete is inconsistent with bounded telemetry state`);96 }97 if (rttSampleCount > sampleCount) {98 throw new RangeError(`${label}.rtt_sample_count cannot exceed sample_count`);99 }100101 return {102 mode: leg.mode,103 sampleCount,104 pathSamples: {105 direct: directSamples,106 relay: relaySamples,107 custom: customSamples,108 unknown: unknownSamples,109 },110 pathEpoch: exactUnsigned(leg.path_epoch, `${label}.path_epoch`),111 pathSwitchTotal: exactUnsigned(leg.path_switch_total, `${label}.path_switch_total`),112 modeTransitionTotal: exactUnsigned(113 leg.mode_transition_total,114 `${label}.mode_transition_total`,115 ),116 rttCurrentMs,117 rtt,118 txDatagrams: exactUnsigned(leg.tx_datagrams, `${label}.tx_datagrams`),119 txBytes: exactUnsigned(leg.tx_bytes, `${label}.tx_bytes`),120 rxDatagrams: exactUnsigned(leg.rx_datagrams, `${label}.rx_datagrams`),121 rxBytes: exactUnsigned(leg.rx_bytes, `${label}.rx_bytes`),122 lostPackets: exactUnsigned(leg.lost_packets, `${label}.lost_packets`),123 lostBytes: exactUnsigned(leg.lost_bytes, `${label}.lost_bytes`),124 cwndBytes: optionalExactUnsigned(leg.cwnd_bytes, `${label}.cwnd_bytes`),125 mtu: optionalExactUnsigned(leg.mtu, `${label}.mtu`),126 congestionEvents: optionalExactUnsigned(leg.congestion_events, `${label}.congestion_events`),127 blackHolesDetected: optionalExactUnsigned(128 leg.black_holes_detected,129 `${label}.black_holes_detected`,130 ),131 counterRegressionTotal,132 complete,133 };134}135136function normalizeInputAck(value) {137 const ack = record(value, 'network_diagnostics.input_ack');138 const sentTotal = exactUnsigned(ack.sent_total, 'network_diagnostics.input_ack.sent_total');139 const acknowledgedTotal = exactUnsigned(140 ack.acknowledged_total,141 'network_diagnostics.input_ack.acknowledged_total',142 );143 const pendingCount = exactUnsigned(144 ack.pending_count,145 'network_diagnostics.input_ack.pending_count',146 );147 const pendingCapacity = exactUnsigned(148 ack.pending_capacity,149 'network_diagnostics.input_ack.pending_capacity',150 );151 if (acknowledgedTotal > sentTotal) {152 throw new RangeError('acknowledged_total cannot exceed sent_total');153 }154 if (pendingCount > pendingCapacity) {155 throw new RangeError('pending_count cannot exceed pending_capacity');156 }157158 const latencySampleCount = exactUnsigned(159 ack.latency_sample_count,160 'network_diagnostics.input_ack.latency_sample_count',161 );162 const latencyOverflowTotal = exactUnsigned(163 ack.latency_overflow_total,164 'network_diagnostics.input_ack.latency_overflow_total',165 );166167 const malformed = boolean(ack.malformed, 'network_diagnostics.input_ack.malformed');168 const closed = boolean(ack.closed, 'network_diagnostics.input_ack.closed');169 const untrackedTotal = exactUnsigned(170 ack.untracked_total,171 'network_diagnostics.input_ack.untracked_total',172 );173 const complete = boolean(ack.complete, 'network_diagnostics.input_ack.complete');174 if (complete !== (!malformed && !closed && untrackedTotal === 0 && latencyOverflowTotal === 0)) {175 throw new RangeError(176 'network_diagnostics.input_ack.complete is inconsistent with bounded telemetry state',177 );178 }179180 return {181 negotiated: boolean(ack.negotiated, 'network_diagnostics.input_ack.negotiated'),182 sentTotal,183 acknowledgedTotal,184 pendingCount,185 pendingCapacity,186 duplicateTotal: exactUnsigned(187 ack.duplicate_total,188 'network_diagnostics.input_ack.duplicate_total',189 ),190 untrackedTotal,191 malformed,192 closed,193 latency: percentileSummary(ack, 'latency', latencySampleCount, latencyOverflowTotal),194 complete,195 };196}197198/**199 * Strictly validates the versioned network snapshot and returns a presentation-safe200 * allowlist. Peer identities, addresses, and unknown future fields never cross this boundary.201 */202export function normalizeNetworkDiagnostics(value) {203 const diagnostics = record(value, 'network_diagnostics');204 if (diagnostics.version !== NETWORK_DIAGNOSTICS_VERSION) {205 throw new TypeError(`unsupported network_diagnostics version: ${diagnostics.version}`);206 }207208 return {209 version: NETWORK_DIAGNOSTICS_VERSION,210 sessionElapsedMs: exactUnsigned(211 diagnostics.session_elapsed_ms,212 'network_diagnostics.session_elapsed_ms',213 ),214 media: normalizeLeg(diagnostics.media, 'network_diagnostics.media'),215 input: normalizeLeg(diagnostics.input, 'network_diagnostics.input'),216 audio: diagnostics.audio === null217 ? null218 : normalizeLeg(diagnostics.audio, 'network_diagnostics.audio'),219 inputAck: normalizeInputAck(diagnostics.input_ack),220 };221}222223function formatBytes(value) {224 if (value < 1024) return `${value} B`;225 if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KiB`;226 if (value < 1024 * 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(1)} MiB`;227 return `${(value / (1024 * 1024 * 1024)).toFixed(1)} GiB`;228}229230function formatPercentiles(summary) {231 if (summary.sampleCount === 0) return `— · 0 samples · ${summary.overflowTotal} overflow`;232 const p50 = summary.p50Ms === null ? '—' : summary.p50Ms.toFixed(1);233 const p95 = summary.p95Ms === null ? '—' : summary.p95Ms.toFixed(1);234 return `${p50} / ${p95} / ${summary.maxMs.toFixed(1)} ms · ${summary.sampleCount} samples · ${summary.overflowTotal} overflow`;235}236237function formatOptionalBytes(value) {238 return value === null ? '—' : formatBytes(value);239}240241function formatOptionalInteger(value, suffix = '') {242 return value === null ? '—' : `${value}${suffix}`;243}244245export function formatNetworkLeg(leg) {246 if (leg === null) return 'unavailable';247 const paths = leg.pathSamples;248 const currentRtt = leg.rttCurrentMs === null ? '—' : `${leg.rttCurrentMs.toFixed(1)} ms`;249 return `${leg.mode} · RTT current ${currentRtt} · history ${formatPercentiles(leg.rtt)} · paths d/r/c/u ${paths.direct}/${paths.relay}/${paths.custom}/${paths.unknown} · epoch ${leg.pathEpoch} · switches ${leg.pathSwitchTotal} · transitions ${leg.modeTransitionTotal} · tx ${leg.txDatagrams} datagrams / ${formatBytes(leg.txBytes)} · rx ${leg.rxDatagrams} datagrams / ${formatBytes(leg.rxBytes)} · lost ${leg.lostPackets} packets / ${formatBytes(leg.lostBytes)} · cwnd ${formatOptionalBytes(leg.cwndBytes)} · MTU ${formatOptionalInteger(leg.mtu, ' B')} · congestion ${formatOptionalInteger(leg.congestionEvents)} · black holes ${formatOptionalInteger(leg.blackHolesDetected)} · regressions ${leg.counterRegressionTotal} · ${leg.complete ? 'complete' : 'partial'}`;250}251252export function formatInputAck(ack) {253 const negotiation = ack.negotiated ? 'negotiated' : 'not negotiated';254 const health = ack.malformed ? 'malformed' : ack.closed ? 'closed' : 'open';255 return `${negotiation} · ${ack.acknowledgedTotal}/${ack.sentTotal} acknowledged · ${ack.pendingCount}/${ack.pendingCapacity} pending · duplicates ${ack.duplicateTotal} · untracked ${ack.untrackedTotal} · latency ${formatPercentiles(ack.latency)} · ${health} · ${ack.complete ? 'complete' : 'partial'}`;256}257258export function networkDiagnosticsPresentation(diagnostics) {259 if (diagnostics === null) {260 return {261 session: 'unavailable',262 media: 'unavailable',263 input: 'unavailable',264 audio: 'unavailable',265 inputAck: 'unavailable',266 };267 }268 return {269 session: `v${diagnostics.version} · ${(diagnostics.sessionElapsedMs / 1000).toFixed(1)} s`,270 media: formatNetworkLeg(diagnostics.media),271 input: formatNetworkLeg(diagnostics.input),272 audio: formatNetworkLeg(diagnostics.audio),273 inputAck: formatInputAck(diagnostics.inputAck),274 };275}