Repository navigation
Vendor and trim the WebRTC stack - #8163
Conversation
Quest-Claim: e2997f6b1410b6c9
Verbatim upstream sources, not yet compiled, so later commits show only our changes: - str0m dc317173b16fc6ed86633e6b08b229c9195deb87 (src, tests), with str0m-proto 0.7.0, is 0.11.1, str0m-aws-lc-rs 0.6.0, and the test-only str0m-netem 0.4.0 from the same revision - dimpl 0.9.0 (src, tests) - sctp-proto 0.10.7 (src, tests) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
str0m's tests of GCC, VLA, RED, DTMF, G.722, comfort noise, AMR-WB, stats, app feedback, RTCP XR, PII redaction, and drv go with that code. dimpl's DTLS 1.3, version auto-selection, and OpenSSL/wolfSSL interop suites go too: the interop suites would build OpenSSL and wolfSSL from source for moq-webrtc's tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each upstream crate becomes a private module under moq-webrtc's `vendor`, keeping its file layout: - crate-root paths point at the module (`crate::vendor::str0m::`), and sibling crates at theirs; - upstream cargo features resolve to `all()` or `any()` for this build, with `_internal_test_exports` becoming `cfg(test)`; - dimpl and sctp-proto log through tracing like the rest of the crate, rather than through `log`, which nothing here bridged; - sctp-proto drops `no_std`, and doctests become `ignore`d examples. The upstream test suites run as unit tests of the module. Their OpenSSL-generated certificates come from rcgen instead, `is`'s HMAC-SHA1 and str0m's reference SHA-1 from aws-lc-rs, and sctp-proto's harness no longer mirrors its in-memory packets onto real UDP ports, which collided between parallel test processes. The SDP origin keeps `o=str0m-0.24.1`. The gateway's log-leak test uses a canary token, since DTLS now logs "master secret" under the crate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
str0m's ICE agent has no clock until its first timeout, and asks for none before it, so Rtc only drove it on some other component's timer. dimpl 0.9 arms no timer on a passive DTLS side before its ClientHello, so an answerer whose offer carried no candidates never checked one trickled in later, and the p2p server-reflexive test timed out. A remote candidate now asks for an immediate first timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rustfmt only: tabs and a 120-column width, as for the other vendored code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop dimpl's DTLS 1.3 engine, its auto-sense handshake, the RustCrypto backend, and str0m's DtlsVersion knob. moq-webrtc always ran DTLS 1.2, so the wire is unchanged; the upstream tests for the removed paths go with them and the DTLS 1.2 halves of the version-parameterized tests stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
str0m flattened every dimpl error into CryptoError::Other(String). Carry it as CryptoError::DtlsImpl instead, so a handshake timeout stays TimeoutError::Handshake for whoever matches it next. The message text is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The vendored str0m no longer computes peer or media stats, sends or parses RTCP extended reports, or handles application feedback. Its internal test exports become plain test helpers. Without XR's RRTR and DLRR blocks the receive side has no RTT measurement, so NACK retries fall back to the default 100 ms RTT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
moq-webrtc never enabled str0m's BWE, so its sends always went through the null pacer, which never asks for padding. Remove the GCC estimator, the leaky-bucket pacer and its controller, send-side probes and padding, and the TWCC send register that only fed the estimator. Outgoing packets still carry transport-wide sequence numbers, receive-side TWCC feedback and SSRC 0 probe handling stay, and REMB keeps its BweKind::Remb event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The vendored str0m keeps the codecs MoQ bridges (Opus, H.264, H.265, VP8, VP9, AV1) plus AMR-WB. Remote offers still parse: rtpmap lines for the removed formats become unknown codecs, which the answer already declined since none were enabled, and static payload types without an rtpmap stay accepted. RED, telephone-event, and H.266 fmtp parsing go with their codecs, as do the RED send sink, RED receive recovery, the telephone-event writer, and the payload queue's send deadlines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The VLA header extension and its simulcast and spatial layer allocation structs were never registered by moq-webrtc. The LEB128 encoder it hosted moves into the AV1 packetizer, its only other user. SDP simulcast and RIDs stay because moq-webrtc reads per-layer REMB through them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Flattening upstream's feature flags left `cfg(any())` items, `cfg(all())` markers, and single-backend dispatch for crypto providers that no longer exist. Remove them, along with two files upstream never compiled (str0m's `crypto/srtp.rs` and an empty dimpl `message/config.rs`), the no-op `log_stat!` macro, and docs describing feature flags the copy lacks. The tests' per-peer `L_CRYPTO`/`R_CRYPTO` switch stays for a second backend. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
str0m's 5.7 MB of pcap captures are read only by its #[cfg(test)] modules, so the published crate does not need them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
str0m, dimpl, and sctp-proto now live in moq-webrtc's source, so a flaw in them is in scope for the threat model rather than upstream's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delete the quest and its links, and point the quests that cite str0m or dimpl source at the vendored files. The typed DTLS timeout quest no longer needs a text match: the vendored dtls.rs keeps dimpl's variant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 4.4 MB capture was 240 frames, but its keyframe test reads only the first 5 s and the one keyframe is the first frame. Keep the first 27 frames (0.87 s, 475 KiB) and the matching rows of h265.txt; the test still checks the keyframe and 23 delta frames. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The import rewrite slid `use crate::vendor::str0m` under `#[cfg(feature = "server")]`, which then gated it instead of axum, so a build without the server feature failed to compile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e workspace-hack Trimming dimpl's other crypto backends left `signature` unused, and an empty `message/config.rs` that an earlier commit meant to delete was still tracked. The vendored stack's dependencies also shift the unified features, so the workspace-hack is regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head b17dd3c.
No actionable findings in the inspected adaptation and removal paths. Vendoring behind private modules is consistent with the planned provider replacement; keeping the provider seam and upstream tests is the right direction. The initial ICE timeout now starts passive-answerer checks without relying on a DTLS timer. The retained null-pacer/RTX, receive-TWCC/REMB, simulcast/RID and DTLS 1.2 paths appear preserved, with the XR/NACK fallback change explicitly documented.
Verification limits: GitHub-only static review using paginated file listings, the post-copy commits and selected full source/caller paths. This was not an exhaustive audit of all 132k imported lines or a byte-for-byte upstream provenance check. I did not run builds, tests, browser interop, package verification or inspect binary captures. The reported runtime and interoperability results remain author-provided.
There was a problem hiding this comment.
ℹ️ No critical issues — two things worth a look before merging.
Reviewed changes
Reviewed the full import of the WebRTC stack into rs/moq-webrtc/src/vendor and each trim against the upstream sources at the recorded revisions.
- Vendoring is faithful. str0m (
str0m@dc317173, still 0.24.1 in its ownCargo.toml), str0m-proto 0.7.0,is0.11.1, str0m-aws-lc-rs 0.6.0, netem 0.4.0, dimpl 0.9.0 (c17b3b8), and sctp-proto 0.10.7 match upstream's workspace deps. Diffing the checkout against the upstream tarball (ignoring whitespace) shows only formatting andcrate::vendor::…path rewrites in kept code. - Trim matches the plan. BWE/GCC, the leaky pacer, send probes/padding and the TWCC send register, RTCP XR/app-feedback, stats, H.266/RED/DTMF/G.7xx/CN, VLA, internal test exports, DTLS 1.3/
auto.rs, the RustCrypto and non-aws-lc backends, and deadcfgbranches are gone; AMR-WB and the PSK suites stay. The static-payload acceptance rule is preserved exactly (matches!(pt, 0 | 8 | 9 | 13)replacesCodecSpec::from_static_pt), outgoing packets still carry a TWCC sequence, andRtpHeader::unpad_payloadstill strips received padding. - The ICE timer fix is bounded.
Rtc::poll_timeoutonly substituteslast_nowwhileis::Agent::poll_timeoutisNone, which theiscode returns only before the firsthandle_timeout, so the extra immediate timeout cannot spin.add_remote_candidatecallswake_all, so the recompute is reached. - Typed DTLS errors and packaging.
CryptoError::DtlsImplis only constructed instr0m/dtls.rs, so the new"DTLS error: "prefix is not doubled, and the test pins the unchanged message text. Ports, licenses (str0m ships onlyLICENSE-MIT.txt, whose repo coversis/str0m-proto/str0m-aws-lc-rs/netem; thepacket/Apache notice is kept), the H.265 capture trim (475 KiB < the 500 KiB cap), and the quest edits (retargeted section lines andRtc::init_dtlsall exist) check out. No public API is exposed and the privatesession/vendormodules keep the surface unchanged.
ℹ️ Removing RTCP XR also raises the video frame recovery deadline to 300 ms
The XR deletion removes the only production writer of StreamRxStats::rtt, and that field feeds recovery_deadline (3×RTT) as well as the NACK retry interval. So besides the retry interval moving to 105 ms, an incomplete video frame from a publisher is now held for 300 ms (3× the 100 ms default) before it is emitted without the missing packet, rather than 3×RTT (about 60 ms at 20 ms RTT). The Impact section only mentions the NACK interval.
Technical details
# RTCP XR removal widens the receive-side recovery deadline
## Affected sites
- `rs/moq-webrtc/src/vendor/str0m/streams/receive.rs:619-626` — `recovery_deadline` falls back to `DEFAULT_RTT * 3` when `stats.rtt` is `None`.
- `rs/moq-webrtc/src/vendor/str0m/streams/receive.rs:663` — `nack_report(now, self.stats.rtt)` uses `DEFAULT_RTT + 5 ms` when `None` (`streams/register_nack.rs:208`).
- `rs/moq-webrtc/src/vendor/str0m/streams/receive.rs:867` — the only remaining `stats.rtt = Some(..)` is inside a test.
- `rs/moq-webrtc/src/vendor/str0m/packet/buffer_rx.rs:499` — `timeout_allows_progress` gates emitting a frame on that deadline; `session.rs:738` (`update_receive_recovery`) recomputes it per video buffer.
- Baseline `31cff5a09:rs/moq-webrtc/src/vendor/str0m/streams/receive.rs` — `set_dlrr_item` was what set `stats.rtt` from the peer's DLRR.
## Required outcome
- Either state the full effect in Impact (recovery deadline, not just the retry interval), or keep the small RRTR/DLRR subset of XR so the receiver keeps a measured RTT while the rest of XR stays removed.ℹ️ AMR-WB keeps its code but loses its only negotiation test
0b4a3fd4c deleted vendor/str0m/tests/amr-wb.rs, whose only purpose was the AMR-WB SDP negotiation (a=rtpmap:122 AMR-WB/16000/1, octet-align/mode-set) plus a round-trip over every frame type and SID. AMR-WB is explicitly kept (“AMR-WB stays”), and the quest plan says to keep upstream's tests for what remains, but vendor/str0m/tests/mod.rs has no mod amr_wb and nothing else in the harness or sdp-negotiation.rs mentions AMR-WB.
Technical details
# Restore the AMR-WB negotiation test, or remove AMR-WB too
## Affected sites
- `rs/moq-webrtc/src/vendor/str0m/tests/mod.rs` — no `mod amr_wb;` entry, so the ported test is gone.
- Deleted in `0b4a3fd4c`: `rs/moq-webrtc/src/vendor/str0m/tests/amr-wb.rs` (273 lines).
- `rs/moq-webrtc/src/vendor/str0m/packet/amr_wb.rs:191+` — unit tests cover packetize/depacketize in both layouts, but not SDP negotiation or the `octet-align`/`mode-set` fmtp path in `sdp/data.rs`.
## Required outcome
- Keep the test with the retained codec (add `#[path = "amr-wb.rs"] mod amr_wb;` to `tests/mod.rs`), or delete AMR-WB as well so the tree has no untested retained codec.deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
RTCP XR in str0m is only the RRTR/DLRR pair, the receive side's one RTT measurement. Without it NACK retries and the video recovery deadline fell back to the 100 ms default, and str0m stopped answering a remote RRTR with DLRR. Restore it with its tests, and restore upstream's AMR-WB negotiation test, since AMR-WB stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head 3f4cb05, focusing on the restoration since b17dd3c.
No actionable findings in this update. Retaining RRTR/DLRR is the better direction: it restores measured receive-side RTT for both NACK retries and frame recovery deadlines, rather than accepting the fixed fallback discussed in the prior reviews. Serialization, session replies, local-SSRC routing and receive-stream updates are connected consistently. The restored DLRR and AMR-WB tests are registered, and the NACK regression covers measured RTT in both RTP and frame modes.
Verification limits: GitHub-only static review, including comparison of restored XR/DLRR and AMR-WB code with the original import. No builds, tests or browser interoperability run; the reported browser smoke test predates this restoration. This incremental review does not expand the earlier review into an exhaustive audit of the vendored stack.
|
Maintainer decision (2026-11-07, /quest-complete):
(Written by Claude Opus 5.5 with high) |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the delta since the prior pullfrog review (b17dd3ca1 → 3f4cb05a7): the restored RTCP XR path and its tests, plus the epic plan update.
- Restored upstream's RTCP XR (RRTR/DLRR) end to end —
xr.rs, theRtcp::ExtendedReportvariant and parsing,RtcpFb::Rrtr/DlrrItemflattening, session-levelpending_rrtrswith DLRR piggybacked on Sender Reports,handle_dlrr, andset_dlrr_itemsettingStreamRxStats::rtt. The restored code is byte-for-byte upstreamdc317173modulo path rewrites and formatting, and the receive-side RTT feeds bothrecovery_deadlineand the NACK retry interval again. - Restored
tests/dlrr.rs, thedlrr_for_local_ssrc_updates_receive_streamsunit test, andnack_retries_follow_measured_rtt_in_both_receive_modes, which assert the DLRR round trip, the local-SSRC filter, and RTT-following retry spacing. - Restored
tests/amr-wb.rsverbatim and re-registered it intests/mod.rs, so the retained AMR-WB codec keeps its SDP negotiation coverage. - Updated the epic README so its trim list drops RTCP XR and states why XR stays.
Verified locally against the head: cargo test -p moq-webrtc --features p2p --no-run compiles, and the dlrr, amr_wb, and nack_retries_follow tests pass.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
…red stack Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
quest land watch: the merge queue removed this pull request at c6f49f0 because it conflicts with The land fixer works on it in the background, and the watcher re-enqueues it at the back of the queue once it is fixed or judged flaky, or hands it back here with what it needs. (written by quest land watch) |

Finishes quest/m1/webrtc-vendor.md: moq-webrtc builds str0m, dimpl,
is, and sctp-proto from private modules underrs/moq-webrtc/src/vendor, trimmed to what MoQ uses, DTLS 1.2 only, with no crates.io copy of the stack left inCargo.lock.What changed
Vendored unchanged first (
31cff5a09e), so every later commit diffs against upstream:dc317173, with str0m-proto 0.7.0,is0.11.1, str0m-aws-lc-rs 0.6.0, and the test-only str0m-netem 0.4.0 from that revisionc17b3b8), sctp-proto 0.10.7str0m(covering its repository's other crates, which ship none),dimpl,sctp_proto, and webrtc-rs's Apache notice onstr0m/packet.vendor/mod.rsrecords every upstream version.Wired in (
9e34275256): crate-root paths becomecrate::vendor::<crate>::, upstream cargo features resolve toall()/any()(since cleaned out), dimpl and sctp-proto log throughtracing, and the upstream suites run as moq-webrtc unit tests. The vendored modules opt out of the workspace lints rather than churning upstream code.Removed, per the plan:
bwe/), the leaky pacer and its controller, send-side probes and padding, the TWCC send register; H.266, RED, DTMF/telephone-event, G.711/G.722 and comfort noise; the VLA header extension with its simulcast and spatial layer allocations; RTCP application feedback; stats;_internal_test_exports(nowcfg(test)helpers)CryptoProvidertrait for moq-webrtc on rinddtls13/,auto.rs, the RustCrypto backend, and str0m'sDtlsVersionknobcfgbranches flattening left behind, two files upstream never compiled, and their testsis, str0m-aws-lc-rs, netem(Rust lines, tests included.)
Kept: dimpl's typed
TimeoutError::Handshakenow reaches moq-webrtc asRtcError::Dtls(DtlsError::CryptoError(CryptoError::DtlsImpl(_)))instead of str0m'sCryptoError::Other(String), with the same message text. The typed DTLS timeout has not landed, so the ICE module has no text match to replace; that quest now says to match the variant.Fix found on the way (
4a8dd48595): dimpl 0.9 arms no timer on a passive DTLS side before its ClientHello, so an answerer whose offer carried no candidates never checked one trickled in later, and the p2p server-reflexive test timed out. A remote candidate now asks str0m for an immediate first timeout.Packaging and docs: str0m's pcap captures are excluded from the published crate, its 4.4 MB H.265 capture is trimmed to the first 27 frames (475 KiB, under
sh/large-files.sh's cap; the test still checks the keyframe and 23 delta frames),signaturegoes from the workspace, and the workspace-hack is regenerated.doc/bin/rtc.md, the OSS scanner's threat model, and the quests citing str0m or dimpl source now point at the vendored files.Impact
Public API: none. The vendored modules are private, and the
pub fns taking str0m types live in private modules.Wire:
FEFD),TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, andSRTP_AEAD_AES_256_GCMagainst this branch (see Validation).Choices made without asking
rids_rx, and WHIP publishers send simulcast.set_dlrr_iteminstreams/receive.rs). Removing it moved NACK retries to the 100 ms default and the video recovery deadline (recovery_deadline, 3x RTT, gatingbuffer_rx.rs'stimeout_allows_progress) to a fixed 300 ms: too long on a 20 ms link, too short past 100 ms RTT. It also changed the wire, which the plan says stays unchanged. The epic README now says why XR stays. Re-dropping it means taking that regression and stating it in Impact; not recommended.tests/amr-wb.rs) is restored with it.ccmcrate they alone use): not on the removal list, and certificate mode already refuses them, so they never reach the wire.L_CRYPTO/R_CRYPTOper-peer provider switch stays, trimmed to aws-lc-rs, so the rind quest can pit its provider against aws-lc-rs.Validation
cargo nextest run -p moq-webrtc --features p2p: 1683 passed, 3 skipped (upstream's#[ignore]d measurements). Withoutp2p: 1661 passed, 3 skipped.cargo clippy --locked --all-targets -- -D warningson moq-webrtc with default features,--features p2p,--no-default-features, and--no-default-features --features p2p, and on moq-cli.just cipasses locally onc6f49f0463. That commit fixes the PR's ownCheckfailures on3f4cb05a71: it replaces em dashes in 39 vendored files with-(as moq-dev/moq#7501 did elsewhere) and biome-formatsstr0m/docs/chrome-sdp.json, whichsdp/data.rsreads as a test fixture. Thetest jsfailure on that run (job) is injs/net/src/connection/connect.test.ts, which this PR does not touch, and it passed in the local run. A hedge timer from the previous test fired under the clock guard during "an http:// fingerprint longer than a SHA-256 is refused".cargo fmt --all --check,cargo docwith-D warningson moq-webrtc,cargo shear(moq-webrtc clean; its kio warnings are pre-existing),cargo sort --check,cargo hakari generate --diff,sh/rs/hakari.py,cargo deny check,sh/large-files.sh,sh/markdown.sh check,quest check --base origin/main, andcargo package -p moq-webrtc --list(no pcaps).b17dd3ca15(not rerun after restoring XR, which the str0m suites above cover), local relay: a headless page publishes a canvas plus oscillator over WHIP tomoq import rtc --listen, and a second page plays the broadcast back over WHEP frommoq export rtc --listen.Suggested follow-ups
SCTP behind the data-channel feature (from the epic): WHIP/WHEP builds still compile
sctp_protoand str0m's SCTP glue.Drop dimpl's PSK suites and the
ccmdependency, if MoQ will never use PSK DTLS.Trim dead upstream API that moq-webrtc never calls, once the rind and clock quests have settled which seams they need.
Flaky
connect.test.tshedge timer:hedgeWebTransport's 500 ms timer can outlive its test and trip the host-clock guard in the next one (seen on this run).Review
mechanical fix by quest land watch:
merged main at ce4b849a1c00f2650e2b688c164d2c00d406d7de with the quest merge driver(head ad48043)Round 1 of a new session (
quest review --base origin/main): Codex, gpt-6.1-sol, medium effort, on headc6f49f046362ace9202a26294cda8367937ebbfdagainst basea46d23f897. No findings.Earlier: round 2 on
3f4cb05a71had no findings, and pullfrog's review ofb17dd3ca15was addressed in3f4cb05a71(RTCP XR kept, which the maintainer confirmed;tests/amr-wb.rsrestored).🤖 Generated with Claude Code
(Written by Claude Opus 5.5)