* service: enforce participant name length on join
StartSession checked the token's metadata and attributes against the configured limits but not its name, so a participant could join with a name longer than limits.max_participant_name_length, which UpdateParticipant and the signal UpdateMetadata path reject.
* service: consolidate join grant-limit checks on the validate path
Extract checkGrantLimits and run it from ValidateConnectRequest and
WHIPService.validateCreate so the /rtc/validate endpoint and the WHIP
ingest path reject over-limit name, metadata and attributes the same way
a real join does. StartSession keeps its own checks as the backstop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* service: run the full join limit check in StartSession
StartSession now calls checkConnectLimits, so the RTC node checks
identity, room name, participant name, metadata and attributes itself.
ValidateConnectRequest runs the check after the publish suffix, so it
measures the same identity StartSession sees. checkConnectLimits returns
a plain error, and the HTTP callers map it to 400. The name-only
StartSession test is dropped for TestConnectLimits, which gains a
publish-suffix case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* service: test the StartSession limit check directly
TestStartSessionConnectLimits runs the same over-limit cases as
TestConnectLimits through StartSession on the RTC node. The cases move
into an exported ConnectLimitCases table so both test packages share it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: ANDROIDHASSAN <84630851+ANDROIDHASSAN@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* sfu: report forward scheduling delay next to forwarding latency
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* sfu: keep full forwarding transit when packing scheduling delay
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* sfu: saturate reported forwarding p90s instead of wrapping
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The WHIP create handler never called ValidateCreateRoom, so with room.auto_create: false a publisher token without roomCreate could create any room over WHIP, while the same join over /rtc gets 404.
* sfu: cap retransmissions per down track (rtc.rtx config, off by default)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* sfu: rtx budget review fixes (sequencer undo, sent bytes, newest first, rtx_budget)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* sfu: rtx budget second review (order unchanged when off, keep NACK window, metric in down track)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* sfu: rtx budget sorts NACKs newest first by extended sequence number
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: wait for the recorded data track subscription before revoking in test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: drop a subscription whose canSubscribe was revoked while it was in flight
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Thread the issuing API key from the /rtc join path to the media node via
StartSession, and include it in the "starting RTC session" log and the
twirp API-request log, so participant activity can be correlated with the
key. Logs the key id only, never the secret.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* rtc: measure subscribe pacing delay in shadow
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* prometheus: raise the subscribe pace shadow top bucket to 120 s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: pace video subscribes per node in shadow, record the wait only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: subscribe pacer doc wording, make the wiring test independent of timing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: sample shadow pacing at arrival, skip one-shot, keep sub-millisecond waits
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: take the shadow pacing token before setup, in arrival order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: build shadow pacing buckets in one pass
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add upstream FlexFEC recovery support
* Guard short TWCC extensions in FEC packets
* Release completed FlexFEC states
* Validate FlexFEC payload type range
* Invoke FEC callbacks after unlocking
* Reduce FlexFEC protection state allocations
* Avoid allocations for complete FlexFEC packets
* Optimize ordered FlexFEC packet insertion
* Reuse RTP packet storage for FlexFEC recovery
* Limit pending FlexFEC repair packets
* Lower FlexFEC repair window to two seconds
* Harden upstream FlexFEC handling
* Test FlexFEC with E2EE media
* Fix FlexFEC negotiation with libwebrtc publishers
* Enhance payload length validation in decoder
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* remove the ability to configure payload type for FlexFEC
* moved some helpers into protocol
* Handle big gaps in media sequence numbers
Reset buffers when a big gap in media sequence numbers is detected.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* bump protocol & psrpc deps
* Handle FEC packet recovery failure
Increment FEC packets discarded counter and remove packet.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* do not NACK on fec recovered packet (#4896)
* do not NACK on fec recovered packet
* fix test race in CI
* Fix FEC write return after merging RTX feedback changes
* PR fixes for FlexFEC implementation (#4898)
* do not NACK on fec recovered packet
* refactor common code into unlockAndInvokeFECRecovery
* add FEC stats to RTPStats
* add log for when fecdecoder unavailable after creation
* rename markAsFECRepair to setFECRepairBufferLimit for clarity
* refactor packet generation for tests into util
* fix test race in CI
* move the unlocks back inline for readability
* update RTPstats
* Fix FEC write return after merging RTX feedback changes
* Use shared RTP packet helper in merged FEC NACK test
* Remove redundant return from FEC write handler
* packet post process (#5020)
* packet post process
* remove redundant writeRTX
* send out RTCP (NACK and RR) from packetPostProcess only
* handle FEC decoder on receiver restart (#5032)
* handle FEC decoder on receiver restart
* move onStreamRestartLocked into BufferBaseParams
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Raja Subramanian <raja.gobi@tutanota.com>
Pick up protocol#1879 (round-trip 7s, default response 15s) so a
server-initiated PerformRpc tolerates the same ack and response windows
as the client SDK caller.
Also log an RPC response the v1 caller cannot decode (compressed_payload
or data-stream) instead of dropping it, which otherwise surfaces only as
a response timeout.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
TestDownTrackResumesAfterBandwidthPause called logger.InitFromConfig
(SetLogger) mid-run while the hwstats CPU monitor goroutine, started by
prometheus.Init in forwardstats_test, read the same non-atomic logger
package globals. go test -race flagged the write-vs-read and failed CI.
Set the global logger once in TestMain, before any test starts that
goroutine, and drop the per-test InitFromConfig.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Echo offered video payload types in single peer connection answers
restrictReceiverCodecsToPublishList pinned server payload types on a video
sender that had no media section yet. The later answer then ignored the
payload types the client offered, and Firefox never decoded the track.
Skip transceivers with an empty mid. Fixes#5030.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Shorten the mid guard comment
Point at sendersPendingConfig, which already documents the rule.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
turn_servers already accepts secret_file for the dynamic (static-auth-secret)
mechanism, but the static username/credential pair could only be written inline
in the config, forcing deployments that mount secrets as files to inline them.
Add username_file and credential_file, resolved through a single helper shared
with secret_file: the inline value wins when both are set, file contents are
trimmed, an empty file fails startup, and credential_file must not be readable
by others. username_file carries no permission requirement because the username
is not a secret.
Co-authored-by: Ora Agent <noreply@oratelecom.net>
* Use negotiated data channels in single PC mode
Bump the protocol version to 18. For protocol >= 18 single peer connection
clients, the server creates its publisher data channels as negotiated with
fixed IDs (100/101/102), so it can send on them without waiting for the
client's DCEP OPEN. Dual PC keeps the in-band channels.
* solve comment
* sfu: reset dummy start offset on forwarder restart
Restart() kept dummyStartTSOffset. The next source switch recomputed it
from a refTS that already included the old offset, so every switch after
that was off by the old value and the subscriber's timestamp jumped.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* sfu: log extTimestamp in sequencer packet meta, add Chrome case to AV1 config test
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* sfu: do not apply dummy start offset without a reference layer
With skipReferenceTS, refTS is the last outgoing timestamp, which is
already on the downtrack's timeline. Computing and adding an offset there
re-added the gap between the last packet and a restart on every later
layer switch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
processSourceSwitch added dummyStartTSOffset to extRefTS in 64 bits. When
the offset is negative and the reference layer's 32-bit RTP timestamp has
wrapped but is still below |offset|, the addition borrows from the upper
half and extRefTS ends up 2^32 below the right value. The wrap correction
that follows only compares the lower 32 bits, so it cannot undo it.
The switch point then looks ~47,722 s (2^32 / 90 kHz) behind and is
rejected with errSwitchPointTooFarBehind on every keyframe, until the
reference layer's timestamp climbs past |offset|, which can take hours.
Apply the offset modulo 2^32 before restoring the cycles from extLastTS.
The lower 32 bits, and so the timestamp sent to subscribers, are unchanged.
RED depacketizing now lives in mediatransportutil so the SFU and
replay export share one implementation. Drop the local copies; the
encoder round-trip tests now exercise the shared package.
Callers passed mono.Now() as a time.Time, which made it easy to pass
time.Now() by mistake and mix clocks. Take mono nanoseconds instead.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
LastSenderReportTime() returned time.Unix(0, srNewest.At). But At is a
mono timestamp, so the result is not a real wall time. Connection quality
compares it to time.Now() to decide if RTCP is stale. If the wall clock
drifts from mono, for example after an NTP step, streams can look stale
when they are not, or the reverse.
- Convert At to wall time: now minus the mono time since the report.
- Return zero time when At is 0, so the scorer skips the stale check
(before, it got the 1970 epoch and saw the stream as stale).
Follow-up to #5005, which fixed the same mix-up in DLSR.
Tests: rtpstats and connectionquality pass. No new test, because a
test process cannot make the mono and wall clocks drift.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: apply simulcast rids from SDP for WHIP (one-shot) publishers
In one-shot signalling mode HandleRemoteDescription set the remote offer
directly and never called OnSetRemoteDescriptionOffer, so
updateRidsFromSDP did not run for WHIP participants. Every simulcast layer
then mapped to spatial layer 0 and all but the first were rejected with
'duplicate layer'.
* rtc: test that a one-shot simulcast offer applies rids to the pending track
* rtc: use h/q rids in one-shot simulcast test, cover single-layer offer
Name the simulcast test layers medium/h and low/q per convention, and add
TestOneShotSingleLayerOfferUnchanged to check a one-shot offer without rids
still yields a single layer with rid "" at spatial layer 0.
* agent: fix flaky TestAgent job dispatch
Retry JobRequest until the worker subscribes the job topic, because
registration is async and the topic is subscribed after the register response.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* agent: report last JobRequest error on dispatch timeout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: reclaim abandoned pending tracks
A pending track is freed only when its first media arrives or the
participant leaves. A client that calls AddTrack and then gives up
before any media flows leaves the slot held for the whole session.
After 20 such slots, every later AddTrack is rejected.
Reclaim these slots when the cap is hit, if no media arrived and the
slot is older than the publish timeout. Keep the supervisor monitor in
step for queued tracks that share a track id. Add a publish_timeout
config value, default 30s.
Fixes: #4970
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* rtc: fix publish_timeout comment to not name an internal constant
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* rtc: release supervisor-timed-out pending tracks at the limit
Replace the createdAt sweep with the supervisor's publish timeout. The
supervisor counts time only while the publisher is connected and the
track is unmuted, and never reports a live track, so a successor queued
behind a live track is not released.
Restart the publish timer when a queued publish reaches the front of
the queue, so it does not inherit the clock of the track ahead of it.
Write the publication error under the write lock, since it is now read
from another goroutine.
Drop the publish_timeout config value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: skip stale supervisor timeouts and keep live monitors
TimedOutPublications re-checks the monitor, because the cached error is
cleared only on the next tick. A track that published since is not
reported, so its queued successor is not released.
RemovePublication keeps a monitor that still holds a published track.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: drop late media for released pending tracks
Media or an offer with no matching pending track falls back to the first
pending track of the same type. After a release, late media for the
released track could take another pending track's identity.
Record the released track's signal and SDP client ids, the 32 most
recent, and drop media or offers for them. An AddTrack that reuses the
client id clears it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rtc: clear all ids of a released track when it is re-added
A re-add cleared only the signal client id, so a Firefox republish that
keeps the old SDP id had its offer and media dropped. Keep the ids of
each released track together, and clear the whole track when a new
AddTrack reuses its signal or simulcast codec client id. The limit now
counts released tracks, and a track ages out with all of its ids.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
An unnamed deployment was addressed as "default" and that spelling leaked
into scope keys, so embedders keyed endpoints differently from job dispatch.
Map "_" back to "" at the URL boundary, as the agent name already does,
and drop NormalizeDeployment.