sipx_media/session.rs
1//! A media session: RTP sockets, paced sending, and buffered receiving.
2//!
3//! Three decisions shape this.
4//!
5//! **Symmetric RTP.** Media is sent back to where it arrives from, not to the address the SDP
6//! advertised. Behind a NAT the advertised address is a private one and the only path back is
7//! the pinhole the far end opened by sending. The SDP address is used until the first packet
8//! arrives, then the observed source wins.
9//!
10//! **The clock lives in one place.** Audio is paced by a single interval timer at the
11//! packetisation interval. Sending on a channel's readiness instead makes the packet rate
12//! depend on how fast the application produces samples, which is how a call ends up sending
13//! 200 packets per second to a jitter buffer expecting 50.
14//!
15//! **Mute substitutes silence; it does not stop the stream** ([`MediaSession::set_muted`], story
16//! `M-18`). A muted session sends exactly the packets it would have sent unmuted, on the same
17//! pacing, sequence numbers and timestamps, with the audio replaced by encoded silence. The
18//! alternative — suppressing the packets while muted — was rejected on three counts: it closes
19//! the NAT pinhole and invites a media-inactivity teardown on any path with an SBC in it; it
20//! leaves the far end's jitter buffer to restart on unmute, so the first word after it is the one
21//! that gets clipped; and it makes "muted" indistinguishable on the wire from "the far end has
22//! gone away", which is the one thing a receiver most needs to be able to tell apart.
23//!
24//! **Playback is a queue with a handle on it** ([`MediaSession::start_playback`], story `M-17`).
25//! Clips are played in the order they were started, one at a time; a second clip started while
26//! one is running waits behind it rather than replacing it. Stopping is the explicit verb, and it
27//! reaches into the send path: a stopped clip's frames are dropped as the send loop takes them
28//! off the queue, so a stop costs at most [`Playback::STOP_BOUND_PACKETS`] packets on the wire
29//! rather than however many the queue happened to be holding.
30//!
31//! **The RFC 3550 §6 consequence, either way, is that the reports must stay truthful**, and that
32//! is what fixes *where* the gate goes rather than what it does. A sender report's packet and
33//! octet counts (§6.4.1) describe what this side put on the wire, and the far end's loss estimate
34//! is computed from the sequence numbers it received against the ones it expected. So the gate
35//! sits **before the packet is built**: what goes out is counted, what is counted went out, and
36//! the sequence space advances once per packet sent. A mute implemented one step later — building
37//! the packet, then discarding the datagram — would make this side's own reports overstate what
38//! it sent *and* manufacture a burst of apparent loss at the far end out of a caller who was
39//! merely quiet. Silence substitution keeps the numbers describing a stream that never stopped;
40//! had suppression been chosen, the same rule would have required the counters and the sequence
41//! number to stay put for the duration.
42//!
43//! Dropping a stopped clip's frames is not the case that rule forbids, and the difference is
44//! worth being exact about. A mute is a session that is *still talking* and must go on saying
45//! something; a stopped playback is a session with **nothing left to say**, which is the state a
46//! session is in whenever the application is not feeding it — the send loop simply parks on its
47//! queue. So the counters and the sequence number stay put, exactly as they do between clips, and
48//! what a stop leaves behind is silence in the ordinary sense: no packets, no gap, nothing for a
49//! receiver to score.
50
51use std::net::SocketAddr;
52use std::sync::Arc;
53use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
54use std::time::Duration;
55
56use bytes::Bytes;
57use sipx_audio::g711;
58use sipx_rtp::dtmf::{self, Digit, Event as DtmfEvent};
59use sipx_rtp::rtcp::{ReceiverReport, Rtcp, Sdes, StreamStats};
60use sipx_rtp::{JitterBuffer, Packet};
61use sipx_sdp::ice::ComponentId;
62use tokio::net::UdpSocket;
63use tokio::sync::{Mutex, mpsc, watch};
64
65use crate::counters::{DiscardMeters, MediaDiscardCounts};
66use crate::ice;
67
68/// Which G.711 flavour a session carries.
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum Codec {
71 /// µ-law, payload type 0.
72 Pcmu,
73 /// A-law, payload type 8.
74 Pcma,
75 /// Signed 16-bit network-order linear PCM (RFC 3551 §4.5.11).
76 ///
77 /// Static payload type 11 is mono at 44.1 kHz. Other negotiated rates use a dynamic payload
78 /// assignment and override [`Config::clock_rate`].
79 L16,
80 /// Opus (RFC 6716), on whatever dynamic payload type was negotiated.
81 ///
82 /// Unlike the G.711 pair this carries *state*: an Opus encoder and decoder each hold a
83 /// model of the signal they have seen, which is how the codec achieves what it does and why
84 /// it cannot be a pure function of one frame. The state lives in the send and receive
85 /// loops, one each, so nothing is shared and nothing is locked.
86 #[cfg(feature = "opus")]
87 Opus,
88}
89
90/// Which half of a negotiated codec could not be constructed.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum CodecDirection {
93 /// The encoder for media sent to the peer.
94 Encoder,
95 /// The decoder for media received from the peer.
96 Decoder,
97}
98
99impl std::fmt::Display for CodecDirection {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 match self {
102 Self::Encoder => f.write_str("encoder"),
103 Self::Decoder => f.write_str("decoder"),
104 }
105 }
106}
107
108/// A negotiated media session that cannot be constructed safely.
109#[derive(Debug, thiserror::Error)]
110#[non_exhaustive]
111pub enum SetupError {
112 /// Packet pacing cannot represent a frame shorter than one millisecond.
113 #[error("packet duration must be at least 1 ms, got {0:?}")]
114 PacketDurationTooShort(Duration),
115 /// A configured RTCP timer must make forward progress.
116 #[error("RTCP interval must be at least 1 ms, got {0:?}")]
117 RtcpIntervalTooShort(Duration),
118 /// RFC 5761 cannot distinguish this marked RTP payload from an RTCP packet type.
119 #[error("RTP payload type {0} collides with RTCP while rtcp-mux is active")]
120 RtcpMuxPayloadCollision(u8),
121 /// The codec agreed through SDP could not create one of its stateful directions.
122 #[error("cannot construct {codec:?} {direction}: {reason}")]
123 Codec {
124 /// The negotiated wire codec.
125 codec: Codec,
126 /// Whether setup failed for sending or receiving.
127 direction: CodecDirection,
128 /// The codec library's diagnostic. It contains no media or key material.
129 reason: String,
130 },
131}
132
133/// Binding or constructing a media session failed.
134#[derive(Debug, thiserror::Error)]
135#[non_exhaustive]
136pub enum StartError {
137 /// A socket could not be bound.
138 #[error("io: {0}")]
139 Io(#[from] std::io::Error),
140 /// The negotiated session could not be constructed.
141 #[error(transparent)]
142 Setup(#[from] SetupError),
143}
144
145/// A DTLS handshake could not take a bound media port into an SRTP session.
146#[cfg(feature = "dtls")]
147#[derive(Debug, thiserror::Error)]
148#[non_exhaustive]
149pub enum DtlsStartError {
150 /// The media socket could not be converted or configured for the handshake.
151 #[error("io: {0}")]
152 Io(#[from] std::io::Error),
153 /// Something else retained the not-yet-started socket.
154 #[error("the media socket is already shared")]
155 SocketShared,
156 /// Certificate verification, profile negotiation or key export failed.
157 #[error("{0}")]
158 Handshake(#[from] crate::dtls::Error),
159 /// The bounded blocking handshake worker did not return normally.
160 #[error("the DTLS handshake worker failed: {0}")]
161 Worker(String),
162}
163
164impl Codec {
165 /// The static payload type.
166 #[must_use]
167 pub fn payload_type(self) -> u8 {
168 match self {
169 Self::Pcmu => 0,
170 Self::Pcma => 8,
171 Self::L16 => 11,
172 // Opus has no static type — RFC 7587 §7 assigns none — so 111 is convention and
173 // nothing more. What goes on the wire is whatever SDP negotiated, which
174 // [`Config::payload_type`] carries.
175 #[cfg(feature = "opus")]
176 Self::Opus => 111,
177 }
178 }
179
180 /// The RTP clock rate, which is not always the sample rate.
181 ///
182 /// RFC 7587 §7 fixes Opus's RTP clock at 48000 whatever the audio is sampled at. A stack
183 /// that used the sample rate instead produces timestamps the far end reads at the wrong
184 /// speed.
185 #[must_use]
186 pub fn clock_rate(self) -> u32 {
187 match self {
188 Self::Pcmu | Self::Pcma => 8_000,
189 Self::L16 => 44_100,
190 #[cfg(feature = "opus")]
191 Self::Opus => sipx_audio::opus::CLOCK_RATE,
192 }
193 }
194
195 /// The codec for a payload type, if it is one we carry.
196 #[must_use]
197 pub fn from_payload_type(payload_type: u8) -> Option<Self> {
198 match payload_type {
199 0 => Some(Self::Pcmu),
200 8 => Some(Self::Pcma),
201 // RFC 3551 §6: 11 is mono L16 at 44.1 kHz. Type 10 is stereo, which this mono
202 // application boundary deliberately does not claim.
203 11 => Some(Self::L16),
204 // Deliberately never Opus. A dynamic payload type means whatever `a=rtpmap` said,
205 // and the number alone means nothing: guessing Opus from 111 would decode somebody
206 // else's G.729 as Opus. The negotiated number lives on the session's config.
207 _ => None,
208 }
209 }
210
211 fn encode(self, samples: &[i16]) -> Vec<u8> {
212 match self {
213 Self::Pcmu => g711::ulaw_encode_all(samples),
214 Self::Pcma => g711::alaw_encode_all(samples),
215 Self::L16 => sipx_audio::l16::encode(samples),
216 // Unreachable: an Opus session encodes through [`Encoding`], which holds the state
217 // this signature has nowhere to put.
218 #[cfg(feature = "opus")]
219 Self::Opus => Vec::new(),
220 }
221 }
222
223 fn decode(self, payload: &[u8]) -> Option<Vec<i16>> {
224 match self {
225 Self::Pcmu => Some(g711::ulaw_decode_all(payload)),
226 Self::Pcma => Some(g711::alaw_decode_all(payload)),
227 Self::L16 => sipx_audio::l16::decode(payload).ok(),
228 #[cfg(feature = "opus")]
229 Self::Opus => None,
230 }
231 }
232}
233
234/// Encoding for one outgoing stream.
235///
236/// Owned by the send loop, which is the whole design: a stateful codec behind a lock would put
237/// a mutex in the packet path for no reason, since exactly one task ever encodes.
238#[derive(Debug)]
239enum Encoding {
240 /// Stateless — a pure function of the samples.
241 Direct(Codec),
242 #[cfg(feature = "opus")]
243 Opus(Box<sipx_audio::opus::Encoder>),
244}
245
246impl Encoding {
247 #[cfg_attr(not(feature = "opus"), allow(clippy::unnecessary_wraps))]
248 fn for_codec(codec: Codec, channels: usize) -> Result<Self, SetupError> {
249 match codec {
250 #[cfg(feature = "opus")]
251 Codec::Opus => match sipx_audio::opus::Encoder::new(channels) {
252 Ok(encoder) => Ok(Self::Opus(Box::new(encoder))),
253 Err(error) => Err(SetupError::Codec {
254 codec,
255 direction: CodecDirection::Encoder,
256 reason: error.to_string(),
257 }),
258 },
259 other => {
260 // discard: `channels` exists only for the feature-gated stateful codec.
261 let _ = channels;
262 Ok(Self::Direct(other))
263 }
264 }
265 }
266
267 // `None` is unreachable without the `opus` feature, because G.711 cannot refuse a frame.
268 // The signature stays fallible in both builds so the send loop is one piece of code rather
269 // than two that have to be kept saying the same thing.
270 #[cfg_attr(not(feature = "opus"), allow(clippy::unnecessary_wraps))]
271 fn encode(&mut self, samples: &[i16]) -> Option<Vec<u8>> {
272 match self {
273 Self::Direct(codec) => Some(codec.encode(samples)),
274 #[cfg(feature = "opus")]
275 Self::Opus(encoder) => match encoder.encode(samples) {
276 Ok(packet) => Some(packet),
277 Err(error) => {
278 // discard: the send loop counts the `None` returned from this callback.
279 tracing::debug!(%error, "dropping a frame Opus could not encode");
280 None
281 }
282 },
283 }
284 }
285}
286
287/// Decoding for one incoming stream. Owned by the receive loop, for the same reason.
288#[derive(Debug)]
289enum Decoding {
290 Direct(Codec),
291 #[cfg(feature = "opus")]
292 Opus(Box<sipx_audio::opus::Decoder>),
293}
294
295impl Decoding {
296 #[cfg_attr(not(feature = "opus"), allow(clippy::unnecessary_wraps))]
297 fn for_codec(codec: Codec, channels: usize) -> Result<Self, SetupError> {
298 match codec {
299 #[cfg(feature = "opus")]
300 Codec::Opus => match sipx_audio::opus::Decoder::new(channels) {
301 Ok(decoder) => Ok(Self::Opus(Box::new(decoder))),
302 Err(error) => Err(SetupError::Codec {
303 codec,
304 direction: CodecDirection::Decoder,
305 reason: error.to_string(),
306 }),
307 },
308 other => {
309 // discard: `channels` exists only for the feature-gated stateful codec.
310 let _ = channels;
311 Ok(Self::Direct(other))
312 }
313 }
314 }
315
316 #[cfg_attr(not(feature = "opus"), allow(clippy::unnecessary_wraps))]
317 fn decode(&mut self, payload: &[u8]) -> Option<Vec<i16>> {
318 match self {
319 Self::Direct(codec) => codec.decode(payload),
320 #[cfg(feature = "opus")]
321 Self::Opus(decoder) => match decoder.decode(payload) {
322 Ok(samples) => Some(samples),
323 Err(error) => {
324 // A packet the codec rejects is dropped, not played. A decoder pushed past
325 // a malformed packet produces noise, and noise is louder than a gap.
326 // discard: `deliver` counts the `None` returned from this callback.
327 tracing::debug!(%error, "dropping a packet Opus could not decode");
328 None
329 }
330 },
331 }
332 }
333}
334
335/// How a session is configured.
336#[derive(Debug, Clone)]
337pub struct Config {
338 /// Where to send until symmetric RTP learns better.
339 pub remote: SocketAddr,
340 /// Which codec.
341 pub codec: Codec,
342 /// The payload type to send with, when the codec's own is not the negotiated one.
343 ///
344 /// `None` uses [`Codec::payload_type`]. A *dynamic* codec has no number of its own — Opus
345 /// has none at all (RFC 7587 §7) — so the number comes from the `a=rtpmap` the two sides
346 /// agreed on, and it may differ from the one sipx would have proposed. Assuming otherwise
347 /// sends audio on a number the far end has assigned to something else.
348 pub payload_type: Option<u8>,
349 /// The payload type accepted for this codec when our description assigned a different
350 /// dynamic number from the peer's.
351 ///
352 /// `None` mirrors [`Self::wire_payload_type`], which preserves the ordinary symmetric case.
353 /// Offer/answer may nevertheless assign the same codec independently in each direction:
354 /// outgoing packets use the peer's number and incoming packets use ours (RFC 3264 §6.1).
355 pub receive_payload_type: Option<u8>,
356 /// How many channels the codec carries. One, for telephony.
357 pub channels: usize,
358 /// SRTP keys, if the media is to be encrypted (RFC 3711).
359 ///
360 /// `None` sends and expects plain RTP. There is deliberately no middle setting — no "accept
361 /// either" — because a session that falls back to cleartext when a packet fails to
362 /// authenticate is a session an attacker can downgrade by sending one bad packet.
363 pub srtp: Option<SrtpKeys>,
364 /// How much audio each packet carries. 20 ms is universal; values below 1 ms are rejected
365 /// by [`Self::validate`] and every session-start API.
366 pub packet_duration: Duration,
367 /// RTP-clock samples per second for the exact negotiated format.
368 ///
369 /// G.711 is always 8000; L16 may use its static 44100 clock or a dynamically mapped rate.
370 pub clock_rate: u32,
371 /// How many packets the jitter buffer holds, and never fewer.
372 pub jitter_depth: usize,
373 /// The deepest it may grow when the network misbehaves, in packets.
374 ///
375 /// `None` fixes the depth at [`Self::jitter_depth`], which is what the comparison tests in
376 /// `sipx-rtp` measure the adaptive buffer against. Adapting is the default because being
377 /// too shallow is audible and being too deep is not — but the ceiling is a real ceiling: a
378 /// call with a quarter-second of delay is still a call, and one with three seconds is not.
379 pub jitter_max_depth: Option<usize>,
380 /// How often to send RTCP receiver reports. `None` disables RTCP entirely; a configured
381 /// interval must be at least 1 ms.
382 ///
383 /// RFC 3550 §6.2 scales the interval with the session's bandwidth and membership; for a
384 /// two-party call that arithmetic lands at the five-second minimum, so sipx uses it
385 /// directly rather than implementing a calculation that would always return the same
386 /// answer.
387 pub rtcp_interval: Option<Duration>,
388 /// Whether RTCP uses the RTP socket or its adjacent control socket (RFC 5761).
389 pub rtcp_mode: sipx_sdp::RtcpMode,
390 /// The payload type carrying `telephone-event`, if the SDP negotiated one.
391 ///
392 /// It is dynamic, so the number is whatever the answer said — assuming 101 because that
393 /// is what sipx offers would decode another endpoint's codec as keypresses.
394 pub dtmf_payload_type: Option<u8>,
395}
396
397impl Config {
398 const MIN_INTERVAL: Duration = Duration::from_millis(1);
399
400 /// The payload type this session puts on the wire.
401 #[must_use]
402 pub fn wire_payload_type(&self) -> u8 {
403 self.payload_type
404 .unwrap_or_else(|| self.codec.payload_type())
405 }
406
407 /// The payload type this session accepts for its negotiated codec.
408 #[must_use]
409 pub fn receive_wire_payload_type(&self) -> u8 {
410 self.receive_payload_type
411 .unwrap_or_else(|| self.wire_payload_type())
412 }
413
414 /// A session to a peer in this codec, with the settings everything uses.
415 #[must_use]
416 pub fn new(remote: SocketAddr, codec: Codec) -> Self {
417 Self {
418 remote,
419 codec,
420 payload_type: None,
421 receive_payload_type: None,
422 channels: 1,
423 srtp: None,
424 packet_duration: Duration::from_millis(20),
425 clock_rate: codec.clock_rate(),
426 jitter_depth: 3,
427 jitter_max_depth: Some(12),
428 rtcp_interval: Some(Duration::from_secs(5)),
429 rtcp_mode: sipx_sdp::RtcpMode::Separate,
430 dtmf_payload_type: Some(dtmf::DEFAULT_PAYLOAD_TYPE),
431 }
432 }
433
434 /// How many samples one packet carries.
435 #[must_use]
436 pub fn samples_per_packet(&self) -> usize {
437 let millis = u64::try_from(self.packet_duration.as_millis()).unwrap_or(20);
438 usize::try_from(u64::from(self.clock_rate) * millis / 1000).unwrap_or(160)
439 }
440
441 /// Check the values used by worker timers before any worker or socket starts.
442 ///
443 /// # Errors
444 ///
445 /// Returns [`SetupError::PacketDurationTooShort`] or
446 /// [`SetupError::RtcpIntervalTooShort`] for a duration below one millisecond.
447 pub fn validate(&self) -> Result<(), SetupError> {
448 if self.packet_duration < Self::MIN_INTERVAL {
449 return Err(SetupError::PacketDurationTooShort(self.packet_duration));
450 }
451 if let Some(interval) = self.rtcp_interval
452 && interval < Self::MIN_INTERVAL
453 {
454 return Err(SetupError::RtcpIntervalTooShort(interval));
455 }
456 if self.rtcp_mode == sipx_sdp::RtcpMode::Mux {
457 for payload in [
458 Some(self.wire_payload_type()),
459 Some(self.receive_wire_payload_type()),
460 self.dtmf_payload_type,
461 ]
462 .into_iter()
463 .flatten()
464 {
465 if (64..=95).contains(&payload) {
466 return Err(SetupError::RtcpMuxPayloadCollision(payload));
467 }
468 }
469 }
470 Ok(())
471 }
472}
473
474/// Everything that can fail before a session's first worker is spawned.
475struct Prepared {
476 encoding: Encoding,
477 decoding: Decoding,
478}
479
480impl Prepared {
481 fn new(config: &Config) -> Result<Self, SetupError> {
482 config.validate()?;
483 // Construct both directions before spawning either. A half-started negotiated codec is
484 // not a media session, and substituting another codec would break the payload contract.
485 let encoding = Encoding::for_codec(config.codec, config.channels)?;
486 let decoding = Decoding::for_codec(config.codec, config.channels)?;
487 Ok(Self { encoding, decoding })
488 }
489}
490
491/// What the paced send queue carries.
492///
493/// Audio and DTMF share one queue because they share one clock and one sequence number space.
494/// A separate path for events would have to interleave them anyway, and would get the
495/// sequence numbering wrong the first time both were busy.
496#[derive(Debug)]
497enum Frame {
498 /// One packet's worth of samples.
499 ///
500 /// `playback` is the stop signal of the clip this frame belongs to, when it belongs to one
501 /// (`M-17`). The send loop reads it and drops the frame if that playback has been stopped,
502 /// which is what makes a stop cost a bounded number of packets rather than the whole depth of
503 /// this queue. `None` for a frame the application sent directly through
504 /// [`MediaSession::send`], which nothing can cancel.
505 Audio {
506 samples: Vec<i16>,
507 playback: Option<Arc<Stop>>,
508 },
509 /// One telephone event, tagged with the keypress it belongs to.
510 ///
511 /// The tag is what holds a tone together. Every packet of one keypress must carry the same
512 /// RTP timestamp, including the three end retransmissions — and the send loop cannot tell
513 /// from an end packet alone whether more of them are coming. Without the tag it started a
514 /// new tone on each retransmission, and one keypress arrived as three digits.
515 ///
516 /// `offset` is the packet's segment start within the event: zero until a keypress
517 /// outlives the 16-bit duration field, after which each further segment stamps its
518 /// packets that much past the event's start (RFC 4733 §2.5.1.3).
519 Dtmf {
520 event: DtmfEvent,
521 offset: u32,
522 tone: u64,
523 },
524 /// Payload to put on the wire exactly as given.
525 ///
526 /// For a bridge between two calls that agreed on the same codec. G.711 survives a decode
527 /// and re-encode exactly, so for the codec sipx ships today this saves work rather than
528 /// quality; for any codec whose decode is not invertible it saves both. See
529 /// [`crate::bridge`].
530 Encoded { payload_type: u8, payload: Bytes },
531}
532
533/// The master keys for one SRTP session, one direction each.
534///
535/// Separate directions because RFC 3711 keys them separately: each side offers its own key in
536/// SDP and uses the other's to decrypt. Sharing one key between directions would give both ends
537/// the same keystream for the same packet index, which is the classic way to lose a stream
538/// cipher.
539#[derive(Clone, PartialEq, Eq)]
540pub struct SrtpKeys {
541 /// Master key and salt this side encrypts with — the one offered in our SDP.
542 pub local: (Vec<u8>, Vec<u8>),
543 /// Master key and salt the far end encrypts with — the one from its SDP.
544 pub remote: (Vec<u8>, Vec<u8>),
545}
546
547impl SrtpKeys {
548 /// The keys an SDES answer settled on, **after** checking it against what was offered
549 /// (RFC 4568 §5.1.3; `docs/specs/srtp.md` §5.4).
550 ///
551 /// This is the seam between the signalling and the media path, and the reason it is fallible
552 /// rather than an `Option`: an answer that echoed a tag this side never sent has agreed to
553 /// nothing, and the two outcomes that are not an error are both worse than one. Returning
554 /// `None` would place the call unencrypted, so a user who asked for a secure call gets an
555 /// insecure one and nothing says so; dropping the stream would end the call with no reason
556 /// anyone can act on. The error carries which tag came back and why it was refused.
557 ///
558 /// `answered` is `None` when the answer carried no `a=crypto` this side can perform — the
559 /// shape in which "a suite that was never offered" arrives, since
560 /// [`sipx_sdp::crypto::Crypto::parse`] refuses one sipx cannot key.
561 ///
562 /// # Errors
563 ///
564 /// [`sipx_sdp::SdpError::Invalid`] when the answer accepted a tag and suite pair that was
565 /// never offered, or carried no key. It never names key material.
566 pub fn from_answer(
567 offered: &[sipx_sdp::crypto::Crypto],
568 answered: Option<&sipx_sdp::crypto::Crypto>,
569 ) -> Result<Self, sipx_sdp::SdpError> {
570 let ours = sipx_sdp::crypto::Crypto::verify_answer(offered, answered)?;
571 // `verify_answer` returning `Ok` is what makes this `answered` usable at all, so the
572 // far half is read only here and never from an answer that was not checked.
573 let theirs = answered.ok_or(sipx_sdp::SdpError::Invalid {
574 field: "crypto",
575 value: "the answer carried no crypto attribute this side can perform".to_owned(),
576 })?;
577 Ok(Self {
578 local: (ours.master_key().to_vec(), ours.master_salt().to_vec()),
579 remote: (theirs.master_key().to_vec(), theirs.master_salt().to_vec()),
580 })
581 }
582}
583
584impl std::fmt::Debug for SrtpKeys {
585 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
586 // Keys. A derived `Debug` puts them in whatever log the caller writes.
587 f.write_str("SrtpKeys { .. }")
588 }
589}
590
591/// A packet's payload as it arrived, still encoded.
592#[derive(Debug, Clone)]
593pub struct Encoded {
594 /// What it is encoded in.
595 pub payload_type: u8,
596 /// The bytes.
597 pub payload: Bytes,
598}
599
600/// One peer RTCP report block describing this session's outbound RTP stream.
601#[derive(Debug, Clone, Copy, PartialEq)]
602pub struct RtcpQualitySample {
603 /// SSRC of the peer that sent the sender or receiver report.
604 pub reporter_ssrc: u32,
605 /// Local stream SSRC named by the report block.
606 pub stream_ssrc: u32,
607 /// Loss in this report interval, between zero and one.
608 pub loss: f64,
609 /// Packets lost since the peer began observing the stream.
610 pub cumulative_lost: i32,
611 /// Peer-observed interarrival jitter in time rather than RTP timestamp units.
612 pub jitter: Duration,
613 /// Round-trip time derived from `LSR` and `DLSR`, when the report carries a usable echo.
614 pub round_trip: Option<Duration>,
615}
616
617/// Application-owned handling for peer RTCP quality reports.
618///
619/// The callback runs on the RTCP receive worker after parsing and outside sipx locks. It must
620/// return promptly; applications that do blocking export put a bounded queue behind it. sipx
621/// catches a callback panic so application code cannot terminate the media worker.
622#[derive(Clone)]
623pub struct RtcpQualityHook(Arc<dyn Fn(RtcpQualitySample) + Send + Sync + 'static>);
624
625impl RtcpQualityHook {
626 /// Wrap an application callback.
627 pub fn new(callback: impl Fn(RtcpQualitySample) + Send + Sync + 'static) -> Self {
628 Self(Arc::new(callback))
629 }
630
631 fn observe(&self, sample: RtcpQualitySample) {
632 (self.0)(sample);
633 }
634}
635
636impl std::fmt::Debug for RtcpQualityHook {
637 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638 f.write_str("RtcpQualityHook { .. }")
639 }
640}
641
642type QualityHookSlot = Arc<std::sync::RwLock<Option<RtcpQualityHook>>>;
643
644fn current_quality_hook(slot: &QualityHookSlot) -> Option<RtcpQualityHook> {
645 match slot.read() {
646 Ok(held) => held.clone(),
647 Err(poisoned) => poisoned.into_inner().clone(),
648 }
649}
650
651fn replace_quality_hook(slot: &QualityHookSlot, hook: Option<RtcpQualityHook>) {
652 match slot.write() {
653 Ok(mut held) => *held = hook,
654 Err(poisoned) => *poisoned.into_inner() = hook,
655 }
656}
657
658/// A running media session.
659#[derive(Debug)]
660pub struct MediaSession {
661 /// The bound RTP socket, retained so an SDP renegotiation can rebuild codec workers without
662 /// rebinding the address the peer already knows.
663 socket: Arc<UdpSocket>,
664 /// The paired RTCP socket, for the same purpose.
665 rtcp_socket: Option<Arc<UdpSocket>>,
666 outgoing: mpsc::Sender<Frame>,
667 digits: Mutex<mpsc::Receiver<(Digit, Duration)>>,
668 /// Distinguishes one keypress from the next.
669 tones: AtomicU64,
670 incoming: Mutex<mpsc::Receiver<Vec<i16>>>,
671 encoded: Mutex<mpsc::Receiver<Encoded>>,
672 /// Whether received packets are handed on encoded rather than decoded to samples.
673 relay: Arc<AtomicBool>,
674 /// Whether this side's outbound audio is gated to silence (`M-18`).
675 muted: Arc<AtomicBool>,
676 /// Clips waiting to be played, in the order they were started (`M-17`).
677 clips: mpsc::Sender<Clip>,
678 /// Names the next playback. Never reused within a session.
679 playbacks: AtomicU64,
680 /// How many started clips have not yet resolved, for [`Self::flush`].
681 outstanding: Arc<AtomicUsize>,
682 /// A counter of full keypresses received, bumped by the receive loop after the digit is on
683 /// its way to the application. What an [`Interrupt::OnDigit`] playback watches.
684 keypresses: Arc<watch::Sender<u64>>,
685 codec: Codec,
686 wire_payload_type: u8,
687 receive_payload_type: u8,
688 /// Retained non-secret wire facts for validated runtime attachment after a host restart.
689 dtmf_payload_type: Option<u8>,
690 rtcp_mode: sipx_sdp::RtcpMode,
691 encrypted: bool,
692 local_addr: SocketAddr,
693 samples_per_packet: usize,
694 packet_duration: Duration,
695 clock_rate: u32,
696 /// The local SSRC carried by both RTP and RTCP for this generation.
697 ssrc: u32,
698 /// Application observation follows the logical session across worker replacement.
699 quality_hook: QualityHookSlot,
700 sent: Arc<AtomicU64>,
701 received: Arc<AtomicU64>,
702 /// Losses owned by this session, including candidate gathering on the port it consumed.
703 discards: Arc<DiscardMeters>,
704 stats: Arc<Mutex<StreamStats>>,
705 /// What the far end last told us, and when.
706 feedback: Arc<Mutex<Feedback>>,
707 /// The running ICE driver, for the exchanges that come after the one that started the session
708 /// ([`Self::renegotiate_ice`], `docs/specs/ice.md` §13.5).
709 ///
710 /// The loops were handed their own clones when they were spawned; this one is the signalling
711 /// layer's, and it is what makes a re-offer able to reach the agent at all. `None` is a stream
712 /// with no ICE, which is the default and must stay indistinguishable from the pre-ICE session.
713 ice: Option<crate::ice::driver::Handle>,
714 /// Browser-component security facts, when the one-owner path established this session.
715 browser_ingress: Option<Arc<std::sync::Mutex<crate::browser::ComponentIngress>>>,
716 /// Every asynchronous worker this session started. A handle remains registered while
717 /// `shutdown` awaits it, which makes a cancelled shutdown retryable instead of detached.
718 owners: Mutex<Vec<tokio::task::JoinHandle<()>>>,
719 /// Stopped generations replaced in place but not yet completely joined. The replacement owns
720 /// them before its first await, so cancellation cannot turn reconfiguration into detachment.
721 retired: Mutex<Vec<MediaSession>>,
722 #[cfg(all(test, feature = "dtls"))]
723 browser_profile_tasks: Option<Arc<crate::browser::ProfileTasks>>,
724 #[cfg(all(test, feature = "dtls"))]
725 browser_preparing_peak: Option<usize>,
726 stop: Arc<Stop>,
727}
728
729/// A sole-consumer linear-PCM view of one session's received audio.
730///
731/// The handle owns its rate-conversion history, so consecutive RTP frames remain one continuous
732/// output stream. Creating one does not spawn work; it reads the same bounded receive queue as
733/// [`MediaSession::recv`].
734#[derive(Debug)]
735pub struct PcmCapture<'a> {
736 session: &'a MediaSession,
737 format: sipx_audio::PcmFormat,
738 resampler: sipx_audio::LinearResampler,
739}
740
741impl PcmCapture<'_> {
742 /// The application format this capture emits.
743 #[must_use]
744 pub const fn format(&self) -> sipx_audio::PcmFormat {
745 self.format
746 }
747
748 /// Take the next non-empty converted PCM chunk.
749 pub async fn recv(&mut self) -> Option<sipx_audio::Pcm> {
750 loop {
751 let frame = self.session.recv().await?;
752 let converted = self.resampler.push_i16(&frame);
753 if !converted.is_empty() {
754 return Some(sipx_audio::Pcm::from_i16(self.format, converted));
755 }
756 }
757 }
758
759 /// Record at least `samples` in the chosen output format, or until `within` elapses.
760 ///
761 /// `within` bounds failure rather than defining stream silence, matching
762 /// [`MediaSession::record_at_least`]. Whatever arrived before the bound is retained.
763 pub async fn record_at_least(&mut self, samples: usize, within: Duration) -> sipx_audio::Pcm {
764 let deadline = tokio::time::Instant::now() + within;
765 let mut recorded = Vec::with_capacity(samples);
766 while recorded.len() < samples {
767 match tokio::time::timeout_at(deadline, self.session.recv()).await {
768 Ok(Some(frame)) => recorded.extend(self.resampler.push_i16(&frame)),
769 Ok(None) | Err(_) => break,
770 }
771 }
772 recorded.truncate(samples);
773 sipx_audio::Pcm::from_i16(self.format, recorded)
774 }
775}
776
777/// What this side has sent, as a sender report describes it (RFC 3550 §6.4.1).
778#[derive(Debug, Default)]
779struct Outbound {
780 packets: AtomicU64,
781 octets: AtomicU64,
782 /// The timestamp of the most recent packet, so a report can relate the RTP clock to the
783 /// wallclock — which is what lets a receiver synchronise two streams.
784 timestamp: std::sync::atomic::AtomicU32,
785}
786
787/// What the far end has told us, from the RTCP it sends back.
788#[derive(Debug, Default, Clone, Copy)]
789struct Feedback {
790 /// The most recent round-trip time, computed per RFC 3550 §6.4.1.
791 ///
792 /// Most recent rather than averaged: the calculation is a difference of two clocks, and a
793 /// clock that steps mid-call poisons an average for the rest of the session while it
794 /// only spoils one sample.
795 round_trip: Option<Duration>,
796 /// The middle 32 bits of the last sender report the far end sent us, and when it arrived.
797 /// Echoed back in our own reports so the far end can measure the round trip too.
798 last_sender_report: u32,
799 received_at: Option<tokio::time::Instant>,
800}
801
802/// Application-visible state updated by either RTCP receive shape.
803#[derive(Clone)]
804struct RtcpObservation {
805 feedback: Arc<Mutex<Feedback>>,
806 quality_hook: QualityHookSlot,
807 clock_rate: u32,
808}
809
810/// A stop signal: for a session's tasks, and — the same shape, one scope down — for one
811/// playback (`M-17`).
812///
813/// A flag *and* a notify. `Notify::notify_waiters` only wakes tasks already parked on it, so a
814/// loop that happens to be blocked on its channel when stop is called would never learn — and
815/// would go on sending audio into a call that had been hung up. The flag makes the signal
816/// durable; the notify makes it prompt.
817#[derive(Debug, Default)]
818pub(crate) struct Stop {
819 stopped: AtomicBool,
820 notify: tokio::sync::Notify,
821}
822
823impl Stop {
824 pub(crate) fn stop(&self) {
825 self.stopped.store(true, Ordering::SeqCst);
826 self.notify.notify_waiters();
827 }
828
829 pub(crate) fn is_stopped(&self) -> bool {
830 self.stopped.load(Ordering::SeqCst)
831 }
832
833 pub(crate) async fn wait(&self) {
834 // Register before reading the durable flag. The opposite order has a lost-wake window:
835 // `stop` can set the flag and notify after the check but before `notified()` registers,
836 // leaving this future asleep forever despite the flag saying it is stopped.
837 let notified = self.notify.notified();
838 tokio::pin!(notified);
839 notified.as_mut().enable();
840 if !self.is_stopped() {
841 notified.await;
842 }
843 }
844}
845
846/// The state every loop in a session shares, built in one place.
847///
848/// One place because two of these must not be rolled twice: RFC 3550 §8.1 requires a
849/// participant's RTCP to carry the same SSRC as its RTP, so the send loop and the report loop
850/// cannot each choose one, and §6.5.1's CNAME has to be stable for the session.
851struct Shared {
852 sent: Arc<AtomicU64>,
853 received: Arc<AtomicU64>,
854 discards: Arc<DiscardMeters>,
855 outbound: Arc<Outbound>,
856 feedback: Arc<Mutex<Feedback>>,
857 /// Zero until the first packet names the far end's synchronisation source.
858 stats: Arc<Mutex<StreamStats>>,
859 stop: Arc<Stop>,
860 ssrc: u32,
861 cname: String,
862 /// Whether received packets are handed on encoded rather than decoded to samples.
863 relay: Arc<AtomicBool>,
864 /// Whether this side's outbound audio is gated to silence (`M-18`).
865 muted: Arc<AtomicBool>,
866 /// A counter of full keypresses received, for an `Interrupt::OnDigit` playback to watch.
867 keypresses: Arc<watch::Sender<u64>>,
868 quality_hook: QualityHookSlot,
869}
870
871impl Shared {
872 fn new(local_addr: SocketAddr, discards: Arc<DiscardMeters>) -> Self {
873 Self::with_stop(local_addr, discards, Arc::new(Stop::default()))
874 }
875
876 fn with_stop(local_addr: SocketAddr, discards: Arc<DiscardMeters>, stop: Arc<Stop>) -> Self {
877 Self {
878 sent: Arc::new(AtomicU64::new(0)),
879 received: Arc::new(AtomicU64::new(0)),
880 discards,
881 outbound: Arc::new(Outbound::default()),
882 feedback: Arc::new(Mutex::new(Feedback::default())),
883 stats: Arc::new(Mutex::new(StreamStats::new(0))),
884 stop,
885 ssrc: rand::random(),
886 // Unique in user@host form and stable for the session: a random token distinguishes
887 // sessions on this host, the local address distinguishes hosts, and neither needs a
888 // name lookup on the media path.
889 cname: format!("{:08x}@{}", rand::random::<u32>(), local_addr),
890 relay: Arc::new(AtomicBool::new(false)),
891 muted: Arc::new(AtomicBool::new(false)),
892 keypresses: Arc::new(watch::Sender::new(0u64)),
893 quality_hook: Arc::new(std::sync::RwLock::new(None)),
894 }
895 }
896
897 fn rtcp_observation(&self, clock_rate: u32) -> RtcpObservation {
898 RtcpObservation {
899 feedback: Arc::clone(&self.feedback),
900 quality_hook: Arc::clone(&self.quality_hook),
901 clock_rate,
902 }
903 }
904}
905
906/// Identifies one playback on one session.
907///
908/// Carried by [`Playback`] and by the completion event a call reports it through, so a caller
909/// that started several clips can tell which of them the report is about.
910#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
911pub struct PlaybackId(u64);
912
913impl std::fmt::Display for PlaybackId {
914 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
915 write!(f, "{}", self.0)
916 }
917}
918
919/// How a playback ended.
920///
921/// A caller needs to be able to tell these apart: "the announcement finished", "the application
922/// cut it off", "the caller pressed a key", "the call went away underneath it" and "it never
923/// played at all" lead to different next steps. [`Self::completed`] is the one-bit answer for
924/// callers that only need to know whether the clip ran out.
925#[derive(Debug, Clone, Copy, PartialEq, Eq)]
926#[non_exhaustive]
927pub enum PlaybackEnd {
928 /// The whole clip reached the send queue.
929 Completed,
930 /// [`Playback::stop`] cut it short.
931 Stopped,
932 /// A keypress from the far end cut it short — it was started [`Interrupt::OnDigit`] and the
933 /// far end pressed a key (RFC 4733). The keypress itself is still delivered to whoever is
934 /// reading [`MediaSession::recv_digit`]; interrupting consumes nothing.
935 Interrupted,
936 /// The session stopped, or the call ended, under a playback still running.
937 SessionEnded,
938 /// The playback queue was full ([`Playback::QUEUE_DEPTH`] clips already waiting), so nothing was
939 /// played at all.
940 Refused,
941}
942
943impl PlaybackEnd {
944 /// Whether the clip ran to its end, as opposed to being cut short by anything.
945 #[must_use]
946 pub fn completed(self) -> bool {
947 matches!(self, Self::Completed)
948 }
949}
950
951/// Whether a keypress from the far end cuts a playback short.
952///
953/// This is the switch under the application contract's `gather{prompt, interruptible}`
954/// (`docs/specs/app-contract.md` §6.2): the prompt of a gather is interruptible by definition,
955/// and a bare `play` is not unless it says so.
956#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
957pub enum Interrupt {
958 /// The clip plays to its end whatever the far end presses.
959 #[default]
960 Never,
961 /// The first full keypress (RFC 4733) received *after this clip reaches the head of the
962 /// queue* stops it.
963 ///
964 /// After, not before: a key pressed while an earlier clip was still playing belongs to that
965 /// clip, and letting it arm this one would have a single keypress skip a whole prompt
966 /// sequence.
967 OnDigit,
968}
969
970/// A playback in progress, or one that has already ended.
971///
972/// Returned by [`MediaSession::start_playback`] without waiting for the clip: the point of the
973/// handle is that the caller goes on to do something else — collect digits, watch for a hangup —
974/// while the audio plays, and can reach back to stop it.
975///
976/// Cloneable, and deliberately so: the handle is a control surface, not ownership of a resource.
977/// A call hands one clone to the application and keeps another to report the playback's end on
978/// its event stream.
979#[derive(Debug, Clone)]
980pub struct Playback {
981 id: PlaybackId,
982 stop: Arc<Stop>,
983 end: watch::Receiver<Option<PlaybackEnd>>,
984}
985
986impl Playback {
987 /// How many packets of a stopped playback may still reach the wire after it is cut.
988 ///
989 /// **Two**, at 20 ms each by default — a number rather than "promptly", because the whole
990 /// difference between playback that can be controlled and playback that cannot is whether an
991 /// application can say how long barge-in takes.
992 ///
993 /// Where it comes from: [`MediaSession::start_playback`] runs ahead of the wire, so when a
994 /// clip is stopped the send queue is generally holding its next few dozen packets. Those are
995 /// not sent. The send loop tests each frame's playback against this signal as it takes the
996 /// frame off the queue and discards a stopped one *without spending a packet interval on it*,
997 /// so the whole backlog drains inside one tick. What can still go out is the packet the send
998 /// loop had already committed to the socket when the signal was set, and — allowing for the
999 /// stop landing between taking a frame and sending it — the one after it.
1000 ///
1001 /// The same bound covers [`Interrupt::OnDigit`]: an interruption sets the same signal, one
1002 /// task hop after the keypress is delivered.
1003 pub const STOP_BOUND_PACKETS: u64 = 2;
1004
1005 /// How many clips may be waiting behind the one playing before further ones are refused.
1006 ///
1007 /// A bound rather than an unbounded queue because the caller of
1008 /// [`MediaSession::start_playback`] is not always a program somebody wrote by hand — under the
1009 /// application contract it is a remote app sending instructions, and a queue that grows
1010 /// without limit turns a buggy app into this process's memory problem. Deep enough that no
1011 /// prompt sequence a call actually has time for will reach it.
1012 pub const QUEUE_DEPTH: usize = 32;
1013
1014 /// Which playback this is.
1015 #[must_use]
1016 pub fn id(&self) -> PlaybackId {
1017 self.id
1018 }
1019
1020 /// Cut this playback short.
1021 ///
1022 /// Takes effect within [`Self::STOP_BOUND_PACKETS`] packets. Idempotent, and harmless on a
1023 /// playback that has already ended. Does not wait: [`Self::finished`] is how a caller learns
1024 /// it has landed.
1025 pub fn stop(&self) {
1026 self.stop.stop();
1027 }
1028
1029 /// Whether this playback has been asked to stop — by [`Self::stop`] or by a keypress.
1030 #[must_use]
1031 pub fn is_stopped(&self) -> bool {
1032 self.stop.is_stopped()
1033 }
1034
1035 /// How it ended, if it has, without waiting.
1036 #[must_use]
1037 pub fn end(&self) -> Option<PlaybackEnd> {
1038 *self.end.borrow()
1039 }
1040
1041 /// Wait for it to end, and stop it if the wait itself is abandoned.
1042 ///
1043 /// The difference from [`Self::finished`] is what happens when *this future* is dropped
1044 /// before the clip ends — a caller that wrapped the wait in a `timeout`, or lost a `select!`.
1045 /// It stops the playback, because that is what abandoning a `play` has always meant: the
1046 /// audio stops with the caller's interest in it, rather than playing on out of a task the
1047 /// caller no longer holds a handle to.
1048 ///
1049 /// [`MediaSession::play`] is this method, which is how it keeps that property now that the
1050 /// clip is fed by a task of its own rather than by the caller.
1051 pub async fn play_out(&self) -> PlaybackEnd {
1052 /// Stops the playback if the wait is dropped before it settles. Not on the way out of a
1053 /// clip that ended on its own: the last packets of a completed clip are still in the send
1054 /// queue, and stopping then would discard them and clip the tail off every announcement.
1055 struct StopIfAbandoned<'a>(&'a Playback);
1056 impl Drop for StopIfAbandoned<'_> {
1057 fn drop(&mut self) {
1058 if self.0.end().is_none() {
1059 self.0.stop();
1060 }
1061 }
1062 }
1063
1064 let guard = StopIfAbandoned(self);
1065 guard.0.finished().await
1066 }
1067
1068 /// Wait for it to end, and say how. Observation only: dropping this wait does not touch the
1069 /// playback, which goes on to whatever end it was going to reach.
1070 ///
1071 /// Resolves when the decision is made rather than when the last packet is on the wire — which
1072 /// is what a caller wants of an interruption, since the next thing it does is act on the
1073 /// keypress. The stopped clip's remaining audio is already guaranteed not to be sent by then.
1074 ///
1075 /// Takes `&self`, so several parties may await the same playback.
1076 pub async fn finished(&self) -> PlaybackEnd {
1077 let mut end = self.end.clone();
1078 loop {
1079 let settled = *end.borrow_and_update();
1080 if let Some(settled) = settled {
1081 return settled;
1082 }
1083 if end.changed().await.is_err() {
1084 // The queue task is gone without having recorded an end, which only happens when
1085 // the session went away underneath this clip.
1086 return PlaybackEnd::SessionEnded;
1087 }
1088 }
1089 }
1090}
1091
1092/// One clip on its way to the send queue, as the playback task receives it.
1093#[derive(Debug)]
1094struct Clip {
1095 samples: Vec<i16>,
1096 samples_per_packet: usize,
1097 interrupt: Interrupt,
1098 /// Shared with every [`Frame::Audio`] this clip produces, so stopping the playback also
1099 /// discards whatever of it the send queue is already holding.
1100 stop: Arc<Stop>,
1101 end: watch::Sender<Option<PlaybackEnd>>,
1102 /// How many full keypresses the receive loop has delivered. Watched, not consumed: an
1103 /// interruption must not take the digit away from the application.
1104 keypresses: watch::Receiver<u64>,
1105 /// How many clips the session has accepted and not yet resolved, so
1106 /// [`MediaSession::flush`] can tell a queue with work left in it from an empty one.
1107 /// Decremented by this clip's destructor, so it balances whether the clip played, was
1108 /// refused, or was dropped with the session.
1109 outstanding: Arc<AtomicUsize>,
1110 discards: Arc<DiscardMeters>,
1111}
1112
1113impl Clip {
1114 /// Record how this clip ended, for whoever is holding its [`Playback`].
1115 fn finish(&self, end: PlaybackEnd) {
1116 // Failure means every handle has been dropped, which is a caller that started a clip and
1117 // never looked back — a legitimate thing to do with an announcement.
1118 if self.end.send(Some(end)).is_err() {
1119 self.discards
1120 .playback_completion_unobserved
1121 .fetch_add(1, Ordering::Relaxed);
1122 }
1123 }
1124}
1125
1126impl Drop for Clip {
1127 fn drop(&mut self) {
1128 self.outstanding.fetch_sub(1, Ordering::SeqCst);
1129 }
1130}
1131
1132/// A bound media port that is not yet carrying anything.
1133///
1134/// This exists because of an ordering constraint in offer/answer: an SDP offer has to name the
1135/// port audio will arrive on, but the codec and the far end's address are not known until the
1136/// answer comes back. So the socket is bound first, its port goes into the offer, and the
1137/// session starts once there is something to start it with.
1138///
1139/// Binding twice instead — once to learn the port, once to start — fails with "address already
1140/// in use", which is how this type came to exist.
1141#[derive(Debug)]
1142pub struct MediaPort {
1143 socket: Arc<UdpSocket>,
1144 /// The control port, one above the media one (RFC 3550 §11).
1145 ///
1146 /// `None` when it could not be had. Media still works without it; what is lost is
1147 /// everything the far end would have told us about what it is hearing — including the
1148 /// round-trip time, which has nowhere else to come from.
1149 rtcp: Option<Arc<UdpSocket>>,
1150 local_addr: SocketAddr,
1151 /// Created at bind time so gathering losses survive the transition into a session.
1152 discards: Arc<DiscardMeters>,
1153}
1154
1155impl MediaPort {
1156 /// Bind a port, and the control port above it. Port 0 asks the OS to choose.
1157 ///
1158 /// RFC 3550 §11: RTP on an even port, RTCP on the next one up. They are bound together
1159 /// because a session that sends reports and cannot receive them is half a control
1160 /// protocol — it can tell the far end what it is hearing and can never learn what the far
1161 /// end hears, and the round-trip time comes from exactly that.
1162 ///
1163 /// Failing to get the control port is not failing to place the call. The pair is attempted,
1164 /// and if no pair is free the media port is taken alone and reporting is one-way.
1165 pub async fn bind(bind: SocketAddr) -> std::io::Result<Self> {
1166 const ATTEMPTS: usize = 16;
1167
1168 if bind.port() == 0 {
1169 for _ in 0..ATTEMPTS {
1170 let socket = UdpSocket::bind(bind).await?;
1171 let local_addr = socket.local_addr()?;
1172 // An odd media port has no room for its control port above it by convention.
1173 // Dropping the socket lets the OS hand the number out again.
1174 if local_addr.port() % 2 != 0 {
1175 continue;
1176 }
1177 if let Some(rtcp) = bind_control_port(local_addr).await {
1178 return Ok(Self {
1179 socket: Arc::new(socket),
1180 rtcp: Some(rtcp),
1181 local_addr,
1182 discards: Arc::new(DiscardMeters::default()),
1183 });
1184 }
1185 }
1186 }
1187
1188 let socket = Arc::new(UdpSocket::bind(bind).await?);
1189 let local_addr = socket.local_addr()?;
1190 let rtcp = bind_control_port(local_addr).await;
1191 if rtcp.is_none() {
1192 tracing::debug!(%local_addr, "no control port; RTCP will be send-only");
1193 }
1194 Ok(Self {
1195 socket,
1196 rtcp,
1197 local_addr,
1198 discards: Arc::new(DiscardMeters::default()),
1199 })
1200 }
1201
1202 /// The port audio will arrive on — what goes in the SDP.
1203 #[must_use]
1204 pub fn local_addr(&self) -> SocketAddr {
1205 self.local_addr
1206 }
1207
1208 /// Whether this port got the control port above the media one (RFC 3550 §11).
1209 ///
1210 /// It decides what ICE may offer: `docs/specs/ice.md` §6.1 puts component 2 in the offer
1211 /// **only** when the control port was actually obtained, because a candidate for a socket
1212 /// that was never bound is an address the peer will check and nothing will answer on.
1213 #[must_use]
1214 pub fn has_control_port(&self) -> bool {
1215 self.rtcp.is_some()
1216 }
1217
1218 /// Run DTLS on this port and return it with the derived SRTP master material.
1219 ///
1220 /// The handshake borrows a duplicated descriptor for the same bound socket. No RTP worker is
1221 /// running yet, so it is the only reader; once it finishes, that duplicate is dropped and the
1222 /// original descriptor is restored to Tokio for [`Self::start`]. The timeout is enforced by
1223 /// the DTLS socket itself, making the blocking worker bounded even if this future is cancelled.
1224 #[cfg(feature = "dtls")]
1225 pub async fn key_with_dtls(
1226 self,
1227 identity: crate::dtls::openssl::Identity,
1228 peer: SocketAddr,
1229 role: crate::dtls::Role,
1230 fingerprint: sipx_sdp::fingerprint::Fingerprint,
1231 timeout: Duration,
1232 ) -> Result<(Self, SrtpKeys), DtlsStartError> {
1233 let Self {
1234 socket,
1235 rtcp,
1236 local_addr,
1237 discards,
1238 } = self;
1239 let socket = Arc::try_unwrap(socket).map_err(|_| DtlsStartError::SocketShared)?;
1240 let socket = socket.into_std()?;
1241 socket.set_nonblocking(false)?;
1242 let handshake_socket = socket.try_clone()?;
1243
1244 let keys = tokio::task::spawn_blocking(move || {
1245 let mut handshake =
1246 crate::dtls::openssl::Session::new(handshake_socket, peer, &identity, timeout)
1247 .map_err(|error| crate::dtls::Error::Dtls(error.to_string()))?;
1248 crate::dtls::establish(&mut handshake, role, Some(&fingerprint))
1249 })
1250 .await
1251 .map_err(|error| DtlsStartError::Worker(error.to_string()))??
1252 .into_srtp_keys();
1253
1254 socket.set_read_timeout(None)?;
1255 socket.set_write_timeout(None)?;
1256 socket.set_nonblocking(true)?;
1257 let socket = UdpSocket::from_std(socket)?;
1258 Ok((
1259 Self {
1260 socket: Arc::new(socket),
1261 rtcp,
1262 local_addr,
1263 discards,
1264 },
1265 keys,
1266 ))
1267 }
1268
1269 /// Gather ICE candidates on this port's sockets (RFC 8445 §5.1.1).
1270 ///
1271 /// Called between binding and offering: the sockets are exclusively ours until
1272 /// [`Self::start`] or [`Self::start_with_ice`] spawns the loops that read them, which is the
1273 /// window a STUN transaction to a configured server needs.
1274 ///
1275 /// The result carries the `a=candidate` lines for the description
1276 /// ([`ice::LocalDescription::attributes`]) and the agent that will drive them.
1277 pub async fn gather(&self, gathering: &ice::Gathering) -> ice::LocalDescription {
1278 self.gather_with_rtcp_mode(gathering, sipx_sdp::RtcpMode::Separate)
1279 .await
1280 }
1281
1282 /// Gather ICE candidates for the negotiated RTCP shape.
1283 ///
1284 /// A muxed stream has only component 1. The default [`Self::gather`] retains the historical
1285 /// two-component behavior for callers that have not selected RFC 5761.
1286 pub async fn gather_with_rtcp_mode(
1287 &self,
1288 gathering: &ice::Gathering,
1289 rtcp_mode: sipx_sdp::RtcpMode,
1290 ) -> ice::LocalDescription {
1291 let mut bases = vec![ice::gather::Base {
1292 index: ice::LocalBase(0),
1293 component: ComponentId::RTP,
1294 socket: &self.socket,
1295 }];
1296 // Component 2 only when the control port was actually obtained (`ice.md` §6.1).
1297 if rtcp_mode == sipx_sdp::RtcpMode::Separate
1298 && let Some(rtcp) = &self.rtcp
1299 {
1300 bases.push(ice::gather::Base {
1301 index: ice::LocalBase(1),
1302 component: ComponentId::RTCP,
1303 socket: rtcp,
1304 });
1305 }
1306 ice::gather::gather(&bases, gathering, Arc::clone(&self.discards)).await
1307 }
1308
1309 /// Start carrying media, now that negotiation has said where and in what.
1310 ///
1311 /// Validation and construction finish before the first worker is spawned. On error this
1312 /// consumes and releases the bound port.
1313 ///
1314 /// # Errors
1315 ///
1316 /// Returns [`SetupError`] when timing is invalid or the negotiated codec cannot be built.
1317 pub fn start(self, config: Config) -> Result<MediaSession, SetupError> {
1318 let prepared = Prepared::new(&config)?;
1319 Ok(MediaSession::on_socket(
1320 &self.socket,
1321 self.rtcp,
1322 self.local_addr,
1323 config,
1324 None,
1325 prepared,
1326 self.discards,
1327 ))
1328 }
1329
1330 /// Start carrying media with ICE driving the path (`docs/specs/ice.md` §2, §11).
1331 ///
1332 /// The `local` description must already have been given the peer's half through
1333 /// [`ice::LocalDescription::accept`]. If that returned `false` — the peer offered no candidates,
1334 /// or RFC 8839 §5.3's `ice-mismatch` applies — no agent is driven and this is
1335 /// [`Self::start`]: no check is sent, no timer runs, and the stream is carried by symmetric
1336 /// RTP exactly as it is today.
1337 ///
1338 /// # Errors
1339 ///
1340 /// Returns [`SetupError`] before starting ICE or media workers when session construction is
1341 /// invalid.
1342 pub fn start_with_ice(
1343 self,
1344 config: Config,
1345 local: ice::LocalDescription,
1346 ) -> Result<MediaSession, SetupError> {
1347 let prepared = Prepared::new(&config)?;
1348 if !local.running() {
1349 return Ok(MediaSession::on_socket(
1350 &self.socket,
1351 self.rtcp,
1352 self.local_addr,
1353 config,
1354 None,
1355 prepared,
1356 self.discards,
1357 ));
1358 }
1359 Ok(MediaSession::on_socket(
1360 &self.socket,
1361 self.rtcp,
1362 self.local_addr,
1363 config,
1364 Some(local),
1365 prepared,
1366 self.discards,
1367 ))
1368 }
1369
1370 /// Start the fail-closed browser-audio runtime on this already-bound component.
1371 ///
1372 /// ICE begins first on the retained socket. Its selected component becomes the only DTLS
1373 /// peer; DTLS records pass through the component owner rather than a duplicated descriptor.
1374 /// Only a verified handshake installs SRTP/SRTCP keys and attaches the media workers.
1375 #[cfg(feature = "dtls")]
1376 #[allow(clippy::too_many_arguments)]
1377 pub async fn start_browser_audio(
1378 self,
1379 mut config: Config,
1380 local_ice: ice::LocalDescription,
1381 ice_generation: u64,
1382 identity: crate::dtls::openssl::Identity,
1383 role: crate::dtls::Role,
1384 peer_fingerprint: sipx_sdp::fingerprint::Fingerprint,
1385 timeout: Duration,
1386 ) -> Result<MediaSession, crate::browser::BrowserStartError> {
1387 if config.rtcp_mode != sipx_sdp::RtcpMode::Mux {
1388 return Err(crate::browser::BrowserStartError::RtcpMuxRequired);
1389 }
1390 let prepared = Prepared::new(&config)?;
1391 let stop = Arc::new(Stop::default());
1392 let (runtime, keys) = crate::browser::prepare(
1393 Arc::clone(&self.socket),
1394 local_ice,
1395 ice_generation,
1396 identity,
1397 role,
1398 peer_fingerprint,
1399 timeout,
1400 Arc::clone(&stop),
1401 Arc::clone(&self.discards),
1402 )
1403 .await?;
1404 let selected = crate::browser::lock_ingress(&runtime.ingress)
1405 .snapshot()
1406 .selected
1407 .ok_or(crate::browser::BrowserStartError::IceStopped)?;
1408 config.remote = selected.remote;
1409 config.srtp = Some(keys);
1410 Ok(MediaSession::on_browser(
1411 runtime,
1412 self.local_addr,
1413 config,
1414 prepared,
1415 self.discards,
1416 ))
1417 }
1418}
1419
1420impl MediaSession {
1421 /// Bind a socket and start the session in one step.
1422 ///
1423 /// Only for callers that already know the far end — an answerer, which has the offer in
1424 /// hand. A caller making the offer needs [`MediaPort`] instead.
1425 ///
1426 /// # Errors
1427 ///
1428 /// Returns [`StartError::Setup`] before binding for invalid timing or codec construction,
1429 /// and [`StartError::Io`] if the media sockets cannot be bound.
1430 pub async fn start(bind: SocketAddr, config: Config) -> Result<Self, StartError> {
1431 // Validation and stateful codec setup happen before binding, so a rejected setup never
1432 // occupies even a temporary port and can never leave a worker behind.
1433 let prepared = Prepared::new(&config)?;
1434 let port = MediaPort::bind(bind).await?;
1435 Ok(Self::on_socket(
1436 &port.socket,
1437 port.rtcp,
1438 port.local_addr,
1439 config,
1440 None,
1441 prepared,
1442 port.discards,
1443 ))
1444 }
1445
1446 // All fallible preparation precedes this constructor. Keeping worker creation contiguous is
1447 // what makes it reviewable that every worker shares the same sockets and stop token.
1448 #[allow(clippy::too_many_lines)]
1449 fn on_socket(
1450 socket: &Arc<UdpSocket>,
1451 rtcp: Option<Arc<UdpSocket>>,
1452 local_addr: SocketAddr,
1453 config: Config,
1454 ice: Option<ice::LocalDescription>,
1455 prepared: Prepared,
1456 discards: Arc<DiscardMeters>,
1457 ) -> Self {
1458 let samples_per_packet = config.samples_per_packet();
1459 let packet_duration = config.packet_duration;
1460 let clock_rate = config.clock_rate;
1461 let config_codec = config.codec;
1462 let wire_payload_type = config.wire_payload_type();
1463 let receive_payload_type = config.receive_wire_payload_type();
1464 let dtmf_payload_type = config.dtmf_payload_type;
1465 let rtcp_interval = config.rtcp_interval;
1466 let rtcp_mode = config.rtcp_mode;
1467 let encrypted = config.srtp.is_some();
1468 // A muxed session has exactly one running socket owner. The adjacent socket was reserved
1469 // before negotiation and is released now; no control worker may race the RTP reader.
1470 let rtcp = match rtcp_mode {
1471 sipx_sdp::RtcpMode::Separate => rtcp,
1472 sipx_sdp::RtcpMode::Mux => None,
1473 };
1474 let (outgoing_tx, outgoing_rx) = mpsc::channel::<Frame>(64);
1475 let (incoming_tx, incoming_rx) = mpsc::channel::<Vec<i16>>(256);
1476 let (encoded_tx, encoded_rx) = mpsc::channel::<Encoded>(256);
1477 let (digits_tx, digits_rx) = mpsc::channel::<(Digit, Duration)>(32);
1478
1479 let shared = Shared::new(local_addr, discards);
1480
1481 // Where to send. Starts at the SDP address and is replaced by the first observed
1482 // source: behind a NAT the advertised address is private and unreachable.
1483 let remote = Arc::new(Mutex::new(config.remote));
1484
1485 // Taken before `config` is moved into the receive loop. Both control loops need them,
1486 // and cloning a pair of keys is cheaper than cloning the whole configuration twice.
1487 let srtp_keys = config.srtp.clone();
1488
1489 // See `ice::driver::Destinations::rtcp`: `None` here, and for every stream without ICE,
1490 // leaves the report loop on RFC 3550 §11's convention.
1491 let rtcp_remote: Arc<Mutex<Option<SocketAddr>>> = Arc::new(Mutex::new(None));
1492 let (ice, ice_owner) = ice.map_or((None, None), |local| {
1493 let (handle, owner) = spawn_ice(
1494 local,
1495 socket,
1496 rtcp.as_ref(),
1497 &ice::driver::Destinations {
1498 rtp: Arc::clone(&remote),
1499 rtcp: Arc::clone(&rtcp_remote),
1500 },
1501 &shared.stop,
1502 &shared.discards,
1503 );
1504 (Some(handle), Some(owner))
1505 });
1506
1507 let send_owner = tokio::spawn(send_loop(
1508 Arc::clone(socket),
1509 outgoing_rx,
1510 Sending {
1511 remote: Arc::clone(&remote),
1512 config: config.clone(),
1513 ssrc: shared.ssrc,
1514 sent: Arc::clone(&shared.sent),
1515 outbound: Arc::clone(&shared.outbound),
1516 muted: Arc::clone(&shared.muted),
1517 ice: ice.clone(),
1518 stop: Arc::clone(&shared.stop),
1519 encoding: prepared.encoding,
1520 discards: Arc::clone(&shared.discards),
1521 },
1522 ));
1523 let (clips_tx, playback_owner) = spawn_playback_queue(&outgoing_tx, &shared.stop);
1524 let receive_owner = tokio::spawn(receive_loop(
1525 ReceiveInput::socket(Arc::clone(socket)),
1526 Inbound {
1527 audio: incoming_tx,
1528 encoded: encoded_tx,
1529 relay: Arc::clone(&shared.relay),
1530 digits: Keypresses {
1531 to: digits_tx,
1532 arrivals: Arc::clone(&shared.keypresses),
1533 },
1534 remote: Arc::clone(&remote),
1535 config,
1536 received: Arc::clone(&shared.received),
1537 stats: Arc::clone(&shared.stats),
1538 rtcp_observation: shared.rtcp_observation(clock_rate),
1539 ssrc: shared.ssrc,
1540 symmetric: ice.is_none(),
1541 ice: ice.clone(),
1542 browser_ingress: None,
1543 stop: Arc::clone(&shared.stop),
1544 decoding: prepared.decoding,
1545 discards: Arc::clone(&shared.discards),
1546 },
1547 ));
1548
1549 let rtcp_socket = rtcp.clone();
1550 let mut owners = vec![send_owner, playback_owner, receive_owner];
1551 owners.extend(spawn_control(Control {
1552 media: Arc::clone(socket),
1553 rtcp,
1554 remote: Arc::clone(&remote),
1555 rtcp_remote,
1556 interval: rtcp_interval,
1557 mode: rtcp_mode,
1558 ssrc: shared.ssrc,
1559 cname: shared.cname.clone(),
1560 stats: Arc::clone(&shared.stats),
1561 outbound: Arc::clone(&shared.outbound),
1562 rtcp_observation: shared.rtcp_observation(clock_rate),
1563 srtp: srtp_keys,
1564 ice: ice.clone(),
1565 stop: Arc::clone(&shared.stop),
1566 discards: Arc::clone(&shared.discards),
1567 #[cfg(feature = "dtls")]
1568 profile_tasks: None,
1569 }));
1570 if let Some(owner) = ice_owner {
1571 owners.push(owner);
1572 }
1573
1574 Self {
1575 socket: Arc::clone(socket),
1576 rtcp_socket,
1577 ice,
1578 outgoing: outgoing_tx,
1579 digits: Mutex::new(digits_rx),
1580 tones: AtomicU64::new(0),
1581 incoming: Mutex::new(incoming_rx),
1582 encoded: Mutex::new(encoded_rx),
1583 relay: shared.relay,
1584 muted: shared.muted,
1585 clips: clips_tx,
1586 playbacks: AtomicU64::new(0),
1587 outstanding: Arc::new(AtomicUsize::new(0)),
1588 keypresses: shared.keypresses,
1589 codec: config_codec,
1590 wire_payload_type,
1591 receive_payload_type,
1592 dtmf_payload_type,
1593 rtcp_mode,
1594 encrypted,
1595 local_addr,
1596 samples_per_packet,
1597 packet_duration,
1598 clock_rate,
1599 ssrc: shared.ssrc,
1600 quality_hook: shared.quality_hook,
1601 sent: shared.sent,
1602 received: shared.received,
1603 discards: shared.discards,
1604 stats: shared.stats,
1605 feedback: shared.feedback,
1606 browser_ingress: None,
1607 owners: Mutex::new(owners),
1608 retired: Mutex::new(Vec::new()),
1609 #[cfg(all(test, feature = "dtls"))]
1610 browser_profile_tasks: None,
1611 #[cfg(all(test, feature = "dtls"))]
1612 browser_preparing_peak: None,
1613 stop: shared.stop,
1614 }
1615 }
1616
1617 #[cfg(feature = "dtls")]
1618 #[allow(clippy::too_many_lines)]
1619 fn on_browser(
1620 runtime: crate::browser::Runtime,
1621 local_addr: SocketAddr,
1622 config: Config,
1623 prepared: Prepared,
1624 discards: Arc<DiscardMeters>,
1625 ) -> Self {
1626 let crate::browser::Runtime {
1627 socket,
1628 media,
1629 ice,
1630 ingress,
1631 owner,
1632 ice_owner,
1633 stop: runtime_stop,
1634 profile_tasks,
1635 } = runtime;
1636 let samples_per_packet = config.samples_per_packet();
1637 let packet_duration = config.packet_duration;
1638 let clock_rate = config.clock_rate;
1639 let config_codec = config.codec;
1640 let wire_payload_type = config.wire_payload_type();
1641 let receive_payload_type = config.receive_wire_payload_type();
1642 let dtmf_payload_type = config.dtmf_payload_type;
1643 let rtcp_mode = config.rtcp_mode;
1644 let encrypted = config.srtp.is_some();
1645 let rtcp_interval = config.rtcp_interval;
1646 let (outgoing_tx, outgoing_rx) = mpsc::channel::<Frame>(64);
1647 let (incoming_tx, incoming_rx) = mpsc::channel::<Vec<i16>>(256);
1648 let (encoded_tx, encoded_rx) = mpsc::channel::<Encoded>(256);
1649 let (digits_tx, digits_rx) = mpsc::channel::<(Digit, Duration)>(32);
1650 let shared = Shared::with_stop(local_addr, discards, runtime_stop);
1651 #[cfg(all(test, feature = "dtls"))]
1652 let preparing_peak = profile_tasks.counts().1;
1653 let remote = Arc::new(Mutex::new(config.remote));
1654 let srtp_keys = config.srtp.clone();
1655 let rtcp_remote: Arc<Mutex<Option<SocketAddr>>> = Arc::new(Mutex::new(None));
1656 let ice = Some(ice);
1657
1658 let send_owner = tokio::spawn(crate::browser::profile_task(
1659 Arc::clone(&profile_tasks),
1660 send_loop(
1661 Arc::clone(&socket),
1662 outgoing_rx,
1663 Sending {
1664 remote: Arc::clone(&remote),
1665 config: config.clone(),
1666 ssrc: shared.ssrc,
1667 sent: Arc::clone(&shared.sent),
1668 outbound: Arc::clone(&shared.outbound),
1669 muted: Arc::clone(&shared.muted),
1670 ice: ice.clone(),
1671 stop: Arc::clone(&shared.stop),
1672 encoding: prepared.encoding,
1673 discards: Arc::clone(&shared.discards),
1674 },
1675 ),
1676 ));
1677 let (clips_tx, playback_owner) =
1678 spawn_browser_playback_queue(&outgoing_tx, &shared.stop, Arc::clone(&profile_tasks));
1679 let receive_owner = tokio::spawn(crate::browser::profile_task(
1680 Arc::clone(&profile_tasks),
1681 receive_loop(
1682 ReceiveInput::Browser(media),
1683 Inbound {
1684 audio: incoming_tx,
1685 encoded: encoded_tx,
1686 relay: Arc::clone(&shared.relay),
1687 digits: Keypresses {
1688 to: digits_tx,
1689 arrivals: Arc::clone(&shared.keypresses),
1690 },
1691 remote: Arc::clone(&remote),
1692 config,
1693 received: Arc::clone(&shared.received),
1694 stats: Arc::clone(&shared.stats),
1695 rtcp_observation: shared.rtcp_observation(clock_rate),
1696 ssrc: shared.ssrc,
1697 symmetric: false,
1698 ice: ice.clone(),
1699 browser_ingress: Some(Arc::clone(&ingress)),
1700 stop: Arc::clone(&shared.stop),
1701 decoding: prepared.decoding,
1702 discards: Arc::clone(&shared.discards),
1703 },
1704 ),
1705 ));
1706
1707 let mut owners = vec![owner, ice_owner, send_owner, playback_owner, receive_owner];
1708 owners.extend(spawn_control(Control {
1709 media: Arc::clone(&socket),
1710 rtcp: None,
1711 remote: Arc::clone(&remote),
1712 rtcp_remote,
1713 interval: rtcp_interval,
1714 mode: sipx_sdp::RtcpMode::Mux,
1715 ssrc: shared.ssrc,
1716 cname: shared.cname.clone(),
1717 stats: Arc::clone(&shared.stats),
1718 outbound: Arc::clone(&shared.outbound),
1719 rtcp_observation: shared.rtcp_observation(clock_rate),
1720 srtp: srtp_keys,
1721 ice: ice.clone(),
1722 stop: Arc::clone(&shared.stop),
1723 discards: Arc::clone(&shared.discards),
1724 #[cfg(feature = "dtls")]
1725 profile_tasks: Some(Arc::clone(&profile_tasks)),
1726 }));
1727
1728 Self {
1729 socket,
1730 rtcp_socket: None,
1731 ice,
1732 browser_ingress: Some(ingress),
1733 owners: Mutex::new(owners),
1734 retired: Mutex::new(Vec::new()),
1735 #[cfg(all(test, feature = "dtls"))]
1736 browser_profile_tasks: Some(profile_tasks),
1737 #[cfg(all(test, feature = "dtls"))]
1738 browser_preparing_peak: Some(preparing_peak),
1739 outgoing: outgoing_tx,
1740 digits: Mutex::new(digits_rx),
1741 tones: AtomicU64::new(0),
1742 incoming: Mutex::new(incoming_rx),
1743 encoded: Mutex::new(encoded_rx),
1744 relay: shared.relay,
1745 muted: shared.muted,
1746 clips: clips_tx,
1747 playbacks: AtomicU64::new(0),
1748 outstanding: Arc::new(AtomicUsize::new(0)),
1749 keypresses: shared.keypresses,
1750 codec: config_codec,
1751 wire_payload_type,
1752 receive_payload_type,
1753 dtmf_payload_type,
1754 rtcp_mode,
1755 encrypted,
1756 local_addr,
1757 samples_per_packet,
1758 packet_duration,
1759 clock_rate,
1760 ssrc: shared.ssrc,
1761 quality_hook: shared.quality_hook,
1762 sent: shared.sent,
1763 received: shared.received,
1764 discards: shared.discards,
1765 stats: shared.stats,
1766 feedback: shared.feedback,
1767 stop: shared.stop,
1768 }
1769 }
1770
1771 /// The address media arrives on, for the SDP.
1772 #[must_use]
1773 pub fn local_addr(&self) -> SocketAddr {
1774 self.local_addr
1775 }
1776
1777 /// This session generation's local RTP synchronisation source.
1778 #[must_use]
1779 pub fn local_ssrc(&self) -> u32 {
1780 self.ssrc
1781 }
1782
1783 /// Install or clear the application callback for peer RTCP quality reports.
1784 ///
1785 /// The slot is shared by this session's RTP/RTCP workers. Registering a callback does not
1786 /// enable RTCP when [`Config::rtcp_interval`] is `None`.
1787 pub fn set_rtcp_quality_hook(&self, hook: Option<RtcpQualityHook>) {
1788 replace_quality_hook(&self.quality_hook, hook);
1789 }
1790
1791 /// The callback currently installed for peer RTCP quality reports.
1792 #[must_use]
1793 pub fn rtcp_quality_hook(&self) -> Option<RtcpQualityHook> {
1794 current_quality_hook(&self.quality_hook)
1795 }
1796
1797 /// Whether ICE is driving this stream's path.
1798 ///
1799 /// The signalling layer's question before it builds any later description: RFC 8839 §4.4 makes
1800 /// the ICE attributes mandatory on every subsequent offer and answer for a stream doing ICE,
1801 /// and §6 makes their *absence* mean the peer has stopped. A session carrying no agent must
1802 /// therefore not grow ICE attributes on a re-offer, and one carrying an agent must not lose
1803 /// them.
1804 #[must_use]
1805 pub fn runs_ice(&self) -> bool {
1806 self.ice.is_some()
1807 }
1808
1809 /// The candidate path ICE actually selected for RTP.
1810 ///
1811 /// `Checking` is honest intermediate state: an ICE exchange was negotiated, but no nominated
1812 /// pair has replaced the default destination yet. A terminal diagnostic can therefore report
1813 /// what happened without inferring it from the policy that was requested.
1814 #[must_use]
1815 pub fn ice_path(&self) -> crate::ice::IcePath {
1816 self.ice.as_ref().map_or(
1817 crate::ice::IcePath::Disabled,
1818 crate::ice::driver::Handle::path,
1819 )
1820 }
1821
1822 /// Security and nominated-pair facts for a browser-audio component.
1823 #[must_use]
1824 pub fn browser_component(&self) -> Option<crate::browser::BrowserComponentSnapshot> {
1825 self.browser_ingress
1826 .as_ref()
1827 .map(|ingress| crate::browser::lock_ingress(ingress).snapshot())
1828 }
1829
1830 #[cfg(all(test, feature = "dtls"))]
1831 pub(crate) fn browser_task_counts(&self) -> Option<(usize, usize, usize)> {
1832 self.browser_profile_tasks.as_ref().and_then(|tasks| {
1833 self.browser_preparing_peak.map(|preparing_peak| {
1834 let (active, peak) = tasks.counts();
1835 (preparing_peak, active, peak)
1836 })
1837 })
1838 }
1839
1840 #[cfg(all(test, feature = "dtls"))]
1841 pub(crate) fn browser_task_probe(&self) -> Option<Arc<crate::browser::ProfileTasks>> {
1842 self.browser_profile_tasks.clone()
1843 }
1844
1845 /// Rebuild codec and packet workers on this session's existing sockets.
1846 ///
1847 /// Used when a later SDP exchange changes the remote address, codec, payload type, or keys.
1848 /// The local RTP/RTCP addresses do not change: they are already published to the peer, and a
1849 /// replacement that rebound an ephemeral port would make the new description false. Mute and
1850 /// encoded-relay policy survive the transition. The stopped generation remains owned until
1851 /// all of its workers have joined; if this future is cancelled during that join, the next
1852 /// reconfiguration or shutdown resumes the cleanup.
1853 ///
1854 /// Returns `false` without changing the session when ICE owns the destinations. Rebuilding an
1855 /// ICE-backed session requires the agent and its selected pair to move with the workers; a
1856 /// caller must refuse that renegotiation rather than silently fall back to an unchecked path.
1857 ///
1858 /// # Errors
1859 ///
1860 /// Returns [`SetupError`] before stopping the current workers if the new timing or codec
1861 /// cannot be constructed.
1862 pub async fn reconfigure(&mut self, config: Config) -> Result<bool, SetupError> {
1863 if self.ice.is_some() {
1864 return Ok(false);
1865 }
1866 let prepared = Prepared::new(&config)?;
1867 self.reap_retired().await;
1868 let muted = self.is_muted();
1869 let relay = self.relay.load(Ordering::SeqCst);
1870 let quality_hook = self.rtcp_quality_hook();
1871 let socket = Arc::clone(&self.socket);
1872 let rtcp = self.rtcp_socket.clone();
1873 let local_addr = self.local_addr;
1874
1875 self.stop.stop();
1876 let replacement = Self::on_socket(
1877 &socket,
1878 rtcp,
1879 local_addr,
1880 config,
1881 None,
1882 prepared,
1883 Arc::clone(&self.discards),
1884 );
1885 replacement.set_muted(muted);
1886 replacement.set_relay(relay);
1887 replacement.set_rtcp_quality_hook(quality_hook);
1888 let previous = std::mem::replace(self, replacement);
1889 self.retired.get_mut().push(previous);
1890 self.reap_retired().await;
1891 Ok(true)
1892 }
1893
1894 /// Apply a later exchange's ICE half, and read back what this side must now signal
1895 /// (RFC 8839 §4.4; `docs/specs/ice.md` §13.5).
1896 ///
1897 /// `local` carries fresh credentials and a fresh tiebreaker when this exchange is a restart —
1898 /// §4.4.1.1.1 says a new ICE session, and the answer to one names the answerer's *own* new
1899 /// credentials rather than the ones the finished session keyed its checks with. `None` is
1900 /// every ordinary re-offer: hold, resume, a codec change, a session refresh. `peer` is the
1901 /// description that arrived, when one has.
1902 ///
1903 /// Returns `None` when this stream is not running ICE, or when the driver has already stopped.
1904 /// Both mean the same thing to a caller: answer without ICE attributes rather than block on a
1905 /// session that is ending.
1906 ///
1907 /// Whether the peer's half *is* a restart is deliberately not asked here. That is
1908 /// §4.4.1.1.1's question about the peer's two credentials, the agent has always answered it,
1909 /// and a second implementation of it on this side would be a second thing to keep right.
1910 pub async fn renegotiate_ice(
1911 &self,
1912 local: Option<(sipx_sdp::ice::Credentials, u64)>,
1913 peer: Option<&ice::Negotiation>,
1914 ) -> Option<ice::Local> {
1915 let handle = self.ice.as_ref()?;
1916 let peer = match peer {
1917 Some(ice::Negotiation::Ice {
1918 credentials,
1919 candidates,
1920 lite,
1921 }) => Some(crate::ice::driver::Peer {
1922 credentials: credentials.clone(),
1923 candidates: candidates.clone(),
1924 lite: *lite,
1925 }),
1926 // `Absent` and `Mismatch` alike: RFC 8839 §5.3 says ICE MUST NOT be used for a
1927 // mismatched stream, and §6 says no candidates means no ICE. Neither is a description
1928 // to feed an agent — but this side still re-signals its own half, because the running
1929 // session is what the peer is sending media to.
1930 _ => None,
1931 };
1932 handle.renegotiated(local, peer).await
1933 }
1934
1935 /// Queue one packet's worth of samples.
1936 ///
1937 /// Queued rather than sent: the pacing timer decides when it goes out.
1938 pub async fn send(&self, samples: Vec<i16>) -> bool {
1939 self.outgoing
1940 .send(Frame::Audio {
1941 samples,
1942 playback: None,
1943 })
1944 .await
1945 .is_ok()
1946 }
1947
1948 /// Send a DTMF digit, held for `duration`.
1949 ///
1950 /// The packets go through the same paced queue as audio, so the tone occupies the slots
1951 /// audio would have. That is deliberate: RFC 4733 events replace the audio for their
1952 /// duration rather than being sent alongside it, and sending both means the far end hears
1953 /// the keypress twice.
1954 pub async fn send_digit(&self, digit: Digit, duration: Duration) -> bool {
1955 let per_packet = self.samples_per_packet;
1956 let packets = (duration.as_millis() / self.packet_duration.as_millis().max(1)).max(1);
1957 let events = dtmf::tone(
1958 digit,
1959 usize::try_from(packets).unwrap_or(1),
1960 u16::try_from(per_packet).unwrap_or(160),
1961 );
1962 let tone = self.tones.fetch_add(1, Ordering::Relaxed);
1963 for packet in events {
1964 if self
1965 .outgoing
1966 .send(Frame::Dtmf {
1967 event: packet.event,
1968 offset: packet.segment_offset,
1969 tone,
1970 })
1971 .await
1972 .is_err()
1973 {
1974 return false;
1975 }
1976 }
1977 true
1978 }
1979
1980 /// Take the next DTMF digit the far end pressed, and how long it was held.
1981 ///
1982 /// The duration comes from the RFC 4733 event itself (its `duration` field, converted from
1983 /// the negotiated clock rate to wall-clock time), not from timing our own arrival: the event
1984 /// carries the sender's own clock, and measuring anything else would make the number depend
1985 /// on jitter rather than on how long the key was actually down.
1986 pub async fn recv_digit(&self) -> Option<(Digit, Duration)> {
1987 self.digits.lock().await.recv().await
1988 }
1989
1990 /// Collect the digits the far end presses, for at most `within`, stopping once it has been
1991 /// quiet for `gap`.
1992 ///
1993 /// Two questions, two bounds — the same split [`Self::record_at_least`] made on the audio
1994 /// path, and the reason this takes two durations rather than one.
1995 ///
1996 /// `within` bounds the wait for the **first** digit, and with it the whole collection. It is a
1997 /// **bound on failure**: how long this side is prepared to wait before concluding no digits
1998 /// are coming, so it belongs an order of magnitude above the honest answer — a whole call's
1999 /// worth, typically — rather than close to it. Nothing about it is a measurement: how long a
2000 /// caller takes to press the first key is a property of the caller, and how long the keypress
2001 /// takes to get here is a property of the machines between them.
2002 ///
2003 /// `gap` is a **definition of silence**: how long a caller has to leave a hole for the
2004 /// dialling to be treated as finished. It is the only question a fixed window can answer here,
2005 /// and it can only be asked once a digit has arrived, because a caller who has not dialled is
2006 /// not a caller who has stopped dialling.
2007 ///
2008 /// Whatever was collected is returned, including nothing. A collection cut short by `within`
2009 /// keeps the digits it already has.
2010 ///
2011 /// # Inferring the end of the dialling (`M-34`)
2012 ///
2013 /// RFC 4733 carries keypresses, not a completion signal: there is no "the caller is done"
2014 /// event to wait for, so *the digits ended* is always this side's inference from silence, and
2015 /// `gap` is the whole of that inference. What makes it safe to draw is that the input it draws
2016 /// on is exact rather than approximate. A digit is delivered here **once**, when the first
2017 /// packet carrying that tone's end bit arrives; the tone is identified by its own RTP
2018 /// timestamp, which is constant across every packet of the tone, so the end retransmissions
2019 /// RFC 4733 §2.5.1.3 asks for are absorbed rather than counted again, and "44" is told from a
2020 /// single long "4" by the timestamp changing. So a `gap` that elapses means no *keypress*
2021 /// completed in it — never that a packet was missed mid-tone.
2022 ///
2023 /// A digit that arrives a millisecond after `gap` expires is **not lost** — up to the 32 the
2024 /// keypress channel holds, past which the receive loop drops rather than blocks, deliberately
2025 /// and by the same reasoning as every other queue here. Within that bound it stays queued and
2026 /// is the first digit the next [`Self::recv_digit`] or `collect_digits` yields. It is in the
2027 /// wrong collection, though, and no wall clock can fix that — which is why `gap` is set past
2028 /// any plausible scheduling delay rather than close to the spacing digits actually arrive
2029 /// with, and why an application that knows how many digits it wants should stop at that count
2030 /// with [`Self::recv_digit`] instead of waiting for a silence at all.
2031 ///
2032 /// # Why this takes two durations (`M-34`)
2033 ///
2034 /// It used to take one, spent on both questions, and that is the defect `X-40` measured one
2035 /// layer up: a single window covering both "has it started" and "has it ended" is beaten by
2036 /// whichever of the two is slower on the day, and the result is not a degraded collection but
2037 /// an **empty** one, since the loop ends before its first iteration. `sipx answer` produced a
2038 /// valid recording of zero samples that way. Widening the single window would have moved that
2039 /// cliff rather than removed it, and left the same defect for a slower caller.
2040 pub async fn collect_digits(&self, within: Duration, gap: Duration) -> String {
2041 let deadline = tokio::time::Instant::now() + within;
2042 let mut out = String::new();
2043
2044 // The first digit. Nothing has been pressed yet, so there is no silence to interpret —
2045 // only the caller's own bound on how long to wait for dialling that may never start.
2046 match tokio::time::timeout_at(deadline, self.recv_digit()).await {
2047 Ok(Some((digit, _held))) => out.push(digit.as_char()),
2048 // The session ended, or the bound elapsed. Either way nobody dialled.
2049 Ok(None) | Err(_) => return out,
2050 }
2051
2052 // The rest of the sequence. A gap now does mean the dialling has finished, and `within`
2053 // still caps a far end that keeps pressing keys forever.
2054 loop {
2055 let next = tokio::time::Instant::now() + gap;
2056 match tokio::time::timeout_at(next.min(deadline), self.recv_digit()).await {
2057 Ok(Some((digit, _held))) => out.push(digit.as_char()),
2058 // The caller stopped, the session ended, or the collection's time is up. All
2059 // three mean this is every digit there is — and it is kept.
2060 Ok(None) | Err(_) => return out,
2061 }
2062 }
2063 }
2064
2065 /// The codec this session negotiated.
2066 #[must_use]
2067 pub fn codec(&self) -> Codec {
2068 self.codec
2069 }
2070
2071 /// The payload type this negotiated stream puts on the wire.
2072 ///
2073 /// Static codecs usually return their assigned number. Dynamic codecs return the number
2074 /// from the negotiated description, which need not be the number this endpoint prefers in
2075 /// an offer.
2076 #[must_use]
2077 pub fn wire_payload_type(&self) -> u8 {
2078 self.wire_payload_type
2079 }
2080
2081 /// The payload type this negotiated stream accepts from the wire.
2082 #[must_use]
2083 pub fn receive_payload_type(&self) -> u8 {
2084 self.receive_payload_type
2085 }
2086
2087 /// The negotiated RTP payload type for telephone events, when enabled.
2088 #[must_use]
2089 pub fn dtmf_payload_type(&self) -> Option<u8> {
2090 self.dtmf_payload_type
2091 }
2092
2093 /// Whether RTP and RTCP share one socket for this session.
2094 #[must_use]
2095 pub fn rtcp_mode(&self) -> sipx_sdp::RtcpMode {
2096 self.rtcp_mode
2097 }
2098
2099 /// Whether this session was constructed with SRTP key material.
2100 ///
2101 /// The key bytes remain owned by the workers and are never exposed by this fact.
2102 #[must_use]
2103 pub fn is_encrypted(&self) -> bool {
2104 self.encrypted
2105 }
2106
2107 /// The RTP timestamp clock negotiated for this stream.
2108 ///
2109 /// This is intentionally the wire clock rather than an inferred playback rate. In
2110 /// particular, RFC 7587 fixes Opus at 48 kHz on the RTP timeline.
2111 #[must_use]
2112 pub fn clock_rate(&self) -> u32 {
2113 self.clock_rate
2114 }
2115
2116 /// Hand received packets on still encoded, rather than decoding them to samples.
2117 ///
2118 /// Switchable at run time because a bridge is formed between calls that are already
2119 /// running: the decision belongs to whoever connects them, and it is not known when the
2120 /// session starts.
2121 pub fn set_relay(&self, relay: bool) {
2122 self.relay.store(relay, Ordering::SeqCst);
2123 }
2124
2125 /// Gate this session's outbound audio, or let it through again (`M-18`).
2126 ///
2127 /// Returns what the gate was set to before, so a caller that only wants to report real
2128 /// transitions does not have to read the flag and then write it — two steps that race each
2129 /// other when a call is muted from more than one place.
2130 ///
2131 /// **What muting does to the stream.** Every audio frame the send loop takes off the queue is
2132 /// replaced by the same number of samples of silence, encoded in this session's own codec and
2133 /// sent on its own payload type. The stream keeps its pacing, its sequence numbers and its
2134 /// timestamps; what changes is only what the far end decodes. See the module documentation
2135 /// for why this rather than suppressing the packets, and for the RFC 3550 §6 consequence.
2136 ///
2137 /// Reception is not affected in any way, and neither is DTMF: an RFC 4733 event is generated
2138 /// by this endpoint on purpose, the way a keypad tone is on a handset, so it goes out muted
2139 /// or not.
2140 pub fn set_muted(&self, muted: bool) -> bool {
2141 self.muted.swap(muted, Ordering::SeqCst)
2142 }
2143
2144 /// Whether this session's outbound audio is gated to silence.
2145 #[must_use]
2146 pub fn is_muted(&self) -> bool {
2147 self.muted.load(Ordering::SeqCst)
2148 }
2149
2150 /// Take the next packet as it arrived, still encoded. Only ever yields under
2151 /// [`Self::set_relay`].
2152 pub async fn recv_encoded(&self) -> Option<Encoded> {
2153 self.encoded.lock().await.recv().await
2154 }
2155
2156 /// Put a payload on the wire exactly as given, bypassing the codec.
2157 pub async fn send_encoded(&self, encoded: Encoded) -> bool {
2158 self.outgoing
2159 .send(Frame::Encoded {
2160 payload_type: encoded.payload_type,
2161 payload: encoded.payload,
2162 })
2163 .await
2164 .is_ok()
2165 }
2166
2167 /// Take the next packet's worth of received samples.
2168 pub async fn recv(&self) -> Option<Vec<i16>> {
2169 self.incoming.lock().await.recv().await
2170 }
2171
2172 /// Receive this session as linear PCM at an application-chosen rate and depth.
2173 ///
2174 /// # Errors
2175 ///
2176 /// Returns [`sipx_audio::PcmError::UnsupportedSampleRate`] when the requested format's rate
2177 /// cannot be converted safely.
2178 pub fn capture(
2179 &self,
2180 format: sipx_audio::PcmFormat,
2181 ) -> Result<PcmCapture<'_>, sipx_audio::PcmError> {
2182 Ok(PcmCapture {
2183 session: self,
2184 format,
2185 resampler: sipx_audio::LinearResampler::new(self.clock_rate, format.sample_rate())?,
2186 })
2187 }
2188
2189 /// Take received samples until the session goes quiet for `idle`.
2190 ///
2191 /// The idle timeout rather than a packet count, because the caller generally knows how long
2192 /// the far end will talk for and not how many packets that becomes.
2193 ///
2194 /// `idle` answers one question — *has the far end stopped talking* — and it answers it by
2195 /// wall clock. A caller that already knows how many samples it expects is asking a different
2196 /// question and wants [`Self::record_at_least`]; see there for what goes wrong when the two
2197 /// are confused (`X-28`).
2198 pub async fn record_until_idle(&self, idle: Duration) -> Vec<i16> {
2199 let mut samples = Vec::new();
2200 // `idle` is a definition of silence, and on the first pass it is also the deadline for the
2201 // stream to start — which is the trap the documentation above is about, kept deliberately
2202 // rather than hidden. The remedy is not a wider window here; it is [`Self::record_at_least`]
2203 // for every caller that knows the count, which is nearly all of them (`X-28`, `X-44`).
2204 while let Ok(Some(frame)) = tokio::time::timeout(idle, self.recv()).await {
2205 samples.extend_from_slice(&frame);
2206 }
2207 samples
2208 }
2209
2210 /// Take received samples until `samples` of them have arrived, or `within` elapses.
2211 ///
2212 /// The wait for a caller that knows the size of what the far end was given — a test that
2213 /// played a clip of its own, most often. `within` is a **bound on failure**, not a
2214 /// measurement: it is how long this side is prepared to wait before concluding the audio is
2215 /// not coming, so it should be far longer than the clip rather than close to it. Whatever
2216 /// arrived is returned, so a caller that got fewer samples than it asked for can say so
2217 /// itself.
2218 ///
2219 /// # Why this exists (`X-28`)
2220 ///
2221 /// [`Self::record_until_idle`] spends one duration on two different jobs: how long to wait
2222 /// for the stream to *start*, and how long a gap means it has *ended*. Neither is a property
2223 /// of the audio — both are properties of how fast the machine happens to be — so a caller
2224 /// that knows the count and uses the idle window instead is racing a fixed wall clock
2225 /// against a pipeline that is merely slow. On a loaded machine that pipeline is slow in
2226 /// exactly the two places the single window covers: the first packet is the one that waits
2227 /// out both jitter buffers filling, and a stalled scheduler opens mid-stream gaps wider than
2228 /// any packet interval. The observed result is a recording of **zero** samples — not a
2229 /// degraded one — because once the first frame lands the rest follow at the packet rate.
2230 ///
2231 /// Widening the window would not have fixed that; it would have moved the cliff.
2232 pub async fn record_at_least(&self, samples: usize, within: Duration) -> Vec<i16> {
2233 let deadline = tokio::time::Instant::now() + within;
2234 let mut recorded = Vec::with_capacity(samples);
2235 while recorded.len() < samples {
2236 match tokio::time::timeout_at(deadline, self.recv()).await {
2237 Ok(Some(frame)) => recorded.extend_from_slice(&frame),
2238 // The session ended, or the bound elapsed. Either way this is everything there
2239 // is, and it is short — which is the caller's to report, not this method's.
2240 Ok(None) | Err(_) => break,
2241 }
2242 }
2243 recorded
2244 }
2245
2246 /// How many samples one packet of this session's audio carries.
2247 ///
2248 /// Settled once when the session started, from the negotiated codec's clock rate and the
2249 /// packet duration. Exposed so a caller playing a clip does not have to recompute what the
2250 /// session already decided — and get it wrong for a codec whose rate is not 8 kHz.
2251 #[must_use]
2252 pub fn samples_per_packet(&self) -> usize {
2253 self.samples_per_packet
2254 }
2255
2256 /// Send a whole clip, paced by the send loop, and wait for it.
2257 ///
2258 /// Returns whether the clip reached the end. `false` means it did not: the send queue closed
2259 /// part way — the call ended, or the session was stopped, under a playback still running — or
2260 /// something cut it short. The caller needs to be able to tell those apart: "the clip
2261 /// finished" and "the clip was cut off" are different things to anything waiting on the
2262 /// playback, and returning `()` made them indistinguishable.
2263 ///
2264 /// This is [`Self::start_playback`] with the handle thrown away and the answer awaited
2265 /// through [`Playback::play_out`], so it stays cancel-on-drop: a caller that wraps it in a
2266 /// `timeout` still stops the audio when the timeout fires. A caller that wants to stop the
2267 /// clip explicitly, or to have a keypress stop it, needs the handle.
2268 pub async fn play(&self, samples: &[i16], samples_per_packet: usize) -> bool {
2269 self.start_clip(samples.to_vec(), samples_per_packet, Interrupt::Never)
2270 .play_out()
2271 .await
2272 .completed()
2273 }
2274
2275 /// Convert and play an explicit linear-PCM buffer.
2276 ///
2277 /// # Errors
2278 ///
2279 /// Returns [`sipx_audio::PcmError`] before queuing anything when its rate or representation
2280 /// cannot be converted.
2281 pub async fn play_pcm(&self, pcm: &sipx_audio::Pcm) -> Result<bool, sipx_audio::PcmError> {
2282 Ok(self
2283 .start_pcm_playback(pcm, Interrupt::Never)?
2284 .play_out()
2285 .await
2286 .completed())
2287 }
2288
2289 /// Convert an explicit PCM buffer and start it as a controllable playback.
2290 ///
2291 /// # Errors
2292 ///
2293 /// Returns [`sipx_audio::PcmError`] before creating a playback when conversion is refused.
2294 pub fn start_pcm_playback(
2295 &self,
2296 pcm: &sipx_audio::Pcm,
2297 interrupt: Interrupt,
2298 ) -> Result<Playback, sipx_audio::PcmError> {
2299 let samples = pcm.to_i16(self.clock_rate)?;
2300 Ok(self.start_playback(samples, interrupt))
2301 }
2302
2303 /// Start a clip and hand back a handle to it, without waiting (`M-17`).
2304 ///
2305 /// The clip is played at this session's own packet size, so it is right under a codec whose
2306 /// clock is not 8 kHz without the caller knowing the rate.
2307 ///
2308 /// # Clips queue; they do not replace
2309 ///
2310 /// Starting a second playback while one is running puts it **behind** the one playing, and it
2311 /// begins when that one ends — however that one ends. This is the choice the story left open,
2312 /// and it is recorded in [`docs/designs/app-sdk.md`](../../../docs/designs/app-sdk.md). The
2313 /// reasoning in short: replacement would make "stop" an implicit side effect of "play", so an
2314 /// application that wanted a prompt followed by a menu would hear only the menu, and the first
2315 /// clip's cancellation would be an event nobody asked for. Replacement is still available and
2316 /// still says what it means — [`Playback::stop`] the one playing, then start the next.
2317 ///
2318 /// Queueing while a clip is stopping is the case worth naming, because it is what barge-in
2319 /// does: stop the prompt, then immediately play something else. The clip being stopped
2320 /// releases the queue at once and its unsent packets are discarded rather than played, so the
2321 /// new clip starts within [`Playback::STOP_BOUND_PACKETS`] packets — it does not have to wait
2322 /// out the backlog of the clip it replaced.
2323 ///
2324 /// A queue [`Playback::QUEUE_DEPTH`] deep. A clip that arrives at a full queue is not played and its
2325 /// handle resolves immediately as [`PlaybackEnd::Refused`], rather than being silently
2326 /// dropped or waiting for room that a live call may never have.
2327 pub fn start_playback(&self, samples: Vec<i16>, interrupt: Interrupt) -> Playback {
2328 self.start_clip(samples, self.samples_per_packet, interrupt)
2329 }
2330
2331 /// Queue a clip at an explicit packet size.
2332 ///
2333 /// Separate from [`Self::start_playback`] only because [`Self::play`] takes the size from its
2334 /// caller and has done since before this session knew its own.
2335 fn start_clip(
2336 &self,
2337 samples: Vec<i16>,
2338 samples_per_packet: usize,
2339 interrupt: Interrupt,
2340 ) -> Playback {
2341 let id = PlaybackId(self.playbacks.fetch_add(1, Ordering::Relaxed));
2342 let stop = Arc::new(Stop::default());
2343 let (end_tx, end_rx) = watch::channel(None);
2344 let playback = Playback {
2345 id,
2346 stop: Arc::clone(&stop),
2347 end: end_rx,
2348 };
2349
2350 // Counted before the hand-off, so a `flush` racing this call never sees a queue it
2351 // believes is empty. The clip's destructor is what takes it back down again, on every
2352 // path out including the two below.
2353 self.outstanding.fetch_add(1, Ordering::SeqCst);
2354 let clip = Clip {
2355 samples,
2356 samples_per_packet: samples_per_packet.max(1),
2357 interrupt,
2358 stop,
2359 end: end_tx,
2360 keypresses: self.keypresses.subscribe(),
2361 outstanding: Arc::clone(&self.outstanding),
2362 discards: Arc::clone(&self.discards),
2363 };
2364
2365 // `try_send` rather than an await, so starting a playback is not itself something that
2366 // can park — a handle the caller cannot yet hold is a handle it cannot stop.
2367 match self.clips.try_send(clip) {
2368 Ok(()) => {}
2369 Err(mpsc::error::TrySendError::Full(clip)) => clip.finish(PlaybackEnd::Refused),
2370 Err(mpsc::error::TrySendError::Closed(clip)) => clip.finish(PlaybackEnd::SessionEnded),
2371 }
2372 playback
2373 }
2374
2375 /// How many packets have been sent.
2376 #[must_use]
2377 pub fn packets_sent(&self) -> u64 {
2378 self.sent.load(Ordering::Relaxed)
2379 }
2380
2381 /// How many have been received.
2382 #[must_use]
2383 pub fn packets_received(&self) -> u64 {
2384 self.received.load(Ordering::Relaxed)
2385 }
2386
2387 /// A synchronous snapshot of everything this session's media path has discarded.
2388 ///
2389 /// This includes candidate-gathering losses on the [`MediaPort`] the session consumed.
2390 /// Each field is monotonic, but independent workers can advance different fields while this
2391 /// snapshot is read, so relationships across fields are exact only while the session is quiet.
2392 #[must_use]
2393 pub fn discard_counts(&self) -> MediaDiscardCounts {
2394 self.discards.snapshot()
2395 }
2396
2397 /// How the call is going: loss, jitter, round-trip time and an estimated score.
2398 ///
2399 /// Readable at any point, not only at the end. The round-trip time is `None` until a report
2400 /// has come back from the far end carrying an echo of one of ours — which needs both a
2401 /// control port on this side and a peer that answers, so it stays `None` against a peer
2402 /// that does not do RTCP rather than being filled in with a guess.
2403 pub async fn quality(&self) -> sipx_rtp::Quality {
2404 // The counters directly, never `report_block()`: that one closes the reporting interval
2405 // (RFC 3550 §6.4.1), so an application polling quality every second would quietly empty
2406 // the window the next RTCP report was going to describe, and the far end would be told
2407 // the call was clean. `pending_report_block()` would be safe now, but the whole-call
2408 // figures below are not in a report block at all.
2409 let (lost, expected, jitter_units) = {
2410 let stats = self.stats.lock().await;
2411 (stats.cumulative_lost(), stats.expected(), stats.jitter())
2412 };
2413
2414 // Loss over the whole call, not since the last report. The per-report fraction is the
2415 // right number to *send* and the wrong one to *show*: it swings between 0 and 1 with
2416 // each interval, and an application sampling it sees whichever interval it happened to
2417 // catch rather than how the call has gone.
2418 // `f64` throughout: a packet count large enough to lose precision here is a call that
2419 // has been running for tens of thousands of years.
2420 let fraction = if expected > 0 {
2421 let (missing, due) = (
2422 u32::try_from(lost.max(0)).map_or(f64::from(u32::MAX), f64::from),
2423 u32::try_from(expected).map_or(f64::from(u32::MAX), f64::from),
2424 );
2425 missing / due
2426 } else {
2427 0.0
2428 };
2429 // The jitter field is in timestamp units; turning it into a duration needs the clock
2430 // rate, which is the one number that makes it comparable between codecs.
2431 let jitter = Duration::from_secs_f64(
2432 (f64::from(jitter_units) / f64::from(self.clock_rate)).max(0.0),
2433 );
2434 let round_trip = self.feedback.lock().await.round_trip;
2435
2436 sipx_rtp::Quality {
2437 loss: fraction,
2438 cumulative_lost: lost,
2439 jitter,
2440 round_trip,
2441 mos: sipx_rtp::Quality::mos(fraction, jitter, round_trip),
2442 }
2443 }
2444
2445 /// The receiver report this session would send right now (RFC 3550 §6.4.1).
2446 ///
2447 /// **Safe to poll**, as often as a dashboard likes: reading does not close the reporting
2448 /// interval, so it cannot make the *next* RTCP report claim a clean interval that was in fact
2449 /// lossy. That is a decision and not an accident (`M-33`) — §6.4.1 defines `fraction_lost` as
2450 /// loss since the previous SR or RR *packet*, so the interval boundary is a report having been
2451 /// sent, and a read is not one. The RTCP loop closes it, via
2452 /// [`StreamStats::report_block`](sipx_rtp::rtcp::StreamStats::report_block); this reads with
2453 /// [`pending_report_block`](sipx_rtp::rtcp::StreamStats::pending_report_block).
2454 ///
2455 /// `fraction_lost` is therefore whatever has accumulated since the last report went out, which
2456 /// makes it a poor thing to *display*: it swings with each interval, and a poller sees whichever
2457 /// interval it happened to catch. [`Self::quality`] is the figure for a caller to show.
2458 ///
2459 /// The two echo fields are zero here. They are filled in by the sending loop, which is the only
2460 /// place that knows how long a peer's sender report has been held.
2461 pub async fn stats(&self) -> sipx_rtp::rtcp::ReportBlock {
2462 self.stats.lock().await.pending_report_block()
2463 }
2464
2465 /// Wait until everything queued has actually been sent.
2466 ///
2467 /// Sending is paced, so `play` and `send_digit` return as soon as the packets are queued —
2468 /// which is long before they are on the wire. Hanging up at that point discards the tail:
2469 /// the last word of a clip, or the last digit of a PIN. Anything still queued after
2470 /// `within` is given up on, so this cannot hold a caller open indefinitely.
2471 pub async fn flush(&self, within: Duration) {
2472 let deadline = tokio::time::Instant::now() + within;
2473 // Both queues: a clip started but not yet fed to the send loop has nothing in the send
2474 // queue to see, and a flush that only looked there would hang up over the top of it.
2475 while self.outstanding.load(Ordering::SeqCst) > 0
2476 || self.outgoing.capacity() < self.outgoing.max_capacity()
2477 {
2478 if tokio::time::Instant::now() >= deadline || self.stop.is_stopped() {
2479 return;
2480 }
2481 tokio::time::sleep(self.packet_duration.max(Duration::from_millis(5))).await;
2482 }
2483 // The last packet has left the queue but not yet the socket.
2484 tokio::time::sleep(self.packet_duration).await;
2485 }
2486
2487 /// Stop the session and release its socket.
2488 pub fn stop(&self) {
2489 self.stop.stop();
2490 }
2491
2492 /// Stop and join every worker owned by this session.
2493 ///
2494 /// Handles stay in the registry until their await completes. Cancelling this future therefore
2495 /// leaves the current handle owned, and a later call resumes the same drain.
2496 pub async fn shutdown(&self) {
2497 self.stop.stop();
2498 if let Some(ingress) = &self.browser_ingress {
2499 crate::browser::lock_ingress(ingress).close();
2500 }
2501 let mut owners = self.owners.lock().await;
2502 while let Some(owner) = owners.last_mut() {
2503 // discard: every worker shares the observed stop token; awaiting proves it is reaped,
2504 // while a cancellation JoinError adds no packet-level discard to count.
2505 let _ = owner.await;
2506 owners.pop();
2507 }
2508 drop(owners);
2509 self.reap_retired().await;
2510 }
2511
2512 async fn reap_retired(&self) {
2513 let mut retired = self.retired.lock().await;
2514 while let Some(previous) = retired.last() {
2515 Box::pin(previous.shutdown()).await;
2516 retired.pop();
2517 }
2518 }
2519
2520 #[cfg(test)]
2521 async fn owned_task_count(&self) -> usize {
2522 self.owners.lock().await.len() + self.retired.lock().await.len()
2523 }
2524
2525 /// Whether the session has been stopped.
2526 #[must_use]
2527 pub fn is_stopped(&self) -> bool {
2528 self.stop.is_stopped()
2529 }
2530}
2531
2532impl Drop for MediaSession {
2533 fn drop(&mut self) {
2534 // A session that outlives its call keeps a socket and two tasks alive. On a server
2535 // taking calls all day that is the difference between steady and unbounded.
2536 self.stop.stop();
2537 if let Some(ingress) = &self.browser_ingress {
2538 crate::browser::lock_ingress(ingress).close();
2539 }
2540 for owner in self.owners.get_mut().drain(..) {
2541 owner.abort();
2542 }
2543 }
2544}
2545
2546/// Where a received packet goes, gathered so the receive loop reads as a loop.
2547fn delivery<'a>(
2548 audio: &'a mpsc::Sender<Vec<i16>>,
2549 encoded: &'a mpsc::Sender<Encoded>,
2550 relay: &'a AtomicBool,
2551 discards: &'a DiscardMeters,
2552) -> Delivery<'a> {
2553 Delivery {
2554 audio,
2555 encoded,
2556 relay,
2557 discards,
2558 }
2559}
2560
2561/// Release everything the jitter buffer is holding, because nothing more is coming.
2562#[allow(clippy::too_many_arguments)]
2563async fn flush(
2564 buffer: &mut JitterBuffer,
2565 to: &Delivery<'_>,
2566 decoding: &mut Decoding,
2567 digits: &Keypresses,
2568 dtmf: &mut sipx_rtp::dtmf::Receiver,
2569 config: &Config,
2570 stop: &Stop,
2571) -> bool {
2572 for packet in buffer.drain() {
2573 if !deliver(to, decoding, digits, dtmf, config, stop, &packet).await {
2574 return false;
2575 }
2576 }
2577 true
2578}
2579
2580/// Authenticate and decrypt a datagram, or drop it.
2581///
2582/// `None` means the packet does not belong to this stream and nothing about it should reach the
2583/// parser, the jitter buffer or the statistics — which is the point of authenticating at all:
2584/// a forged packet must not be able to move any state.
2585fn authenticated(
2586 context: Option<&mut sipx_rtp::SrtpContext>,
2587 bytes: Bytes,
2588 source: SocketAddr,
2589 discards: &DiscardMeters,
2590 browser_ingress: Option<&Arc<std::sync::Mutex<crate::browser::ComponentIngress>>>,
2591) -> Option<Bytes> {
2592 let Some(context) = context else {
2593 return Some(bytes);
2594 };
2595 match context.unprotect(&bytes) {
2596 Ok(plain) => Some(Bytes::from(plain)),
2597 Err(error) => {
2598 discards
2599 .srtp_unprotect_failures
2600 .fetch_add(1, Ordering::Relaxed);
2601 if let Some(ingress) = browser_ingress {
2602 let mut ingress = crate::browser::lock_ingress(ingress);
2603 match &error {
2604 sipx_rtp::srtp::SrtpError::TooShort(_) => {
2605 ingress.note_malformed(crate::browser::IngressClass::Srtp);
2606 }
2607 sipx_rtp::srtp::SrtpError::Replayed(_) => {
2608 ingress.note_replay(crate::browser::IngressClass::Srtp);
2609 }
2610 _ => ingress.note_authentication_failure(crate::browser::IngressClass::Srtp),
2611 }
2612 }
2613 // discard: `srtp_unprotect_failures` was incremented above; browser sessions also
2614 // classified the same refusal into malformed, replay or authentication exactly once.
2615 tracing::debug!(%error, %source, "dropping a packet that failed SRTP");
2616 None
2617 }
2618 }
2619}
2620
2621/// An SRTP context from a master key and salt, or `None` when the media is not encrypted.
2622fn srtp_context(keys: Option<&(Vec<u8>, Vec<u8>)>) -> Option<sipx_rtp::SrtpContext> {
2623 let (key, salt) = keys?;
2624 match sipx_rtp::SrtpContext::new(key, salt) {
2625 Ok(context) => Some(context),
2626 Err(error) => {
2627 // discard: this refuses configuration before any packet exists; runtime discard
2628 // counters count media the path was actually handed, not setup values.
2629 // Carrying on unencrypted would be the worst of the three options: the far end
2630 // expects SRTP, so the media is useless to it *and* readable to everyone else.
2631 tracing::error!(%error, "SRTP keys were refused; this session will carry nothing");
2632 None
2633 }
2634 }
2635}
2636
2637/// Bind the control port for a media port, if it is free.
2638async fn bind_control_port(media: SocketAddr) -> Option<Arc<UdpSocket>> {
2639 let port = media.port().checked_add(1)?;
2640 UdpSocket::bind(SocketAddr::new(media.ip(), port))
2641 .await
2642 .ok()
2643 .map(Arc::new)
2644}
2645
2646#[allow(clippy::too_many_arguments)]
2647/// The RTP clock for one outgoing stream.
2648///
2649/// One type rather than a pile of locals, because these six values are one thing: a tone and
2650/// the audio around it share a timeline, and the bookkeeping that keeps them from overlapping
2651/// is the fiddliest part of sending. Keeping it here leaves the send loop about pacing, which
2652/// is what the send loop is about.
2653struct SendClock {
2654 sequence: u16,
2655 timestamp: u32,
2656 /// The tone in progress, if any, and the timestamp it started at.
2657 current_tone: Option<u64>,
2658 tone_started_at: Option<u32>,
2659 /// What the clock owes the tone in progress, charged when it is over.
2660 tone_duration: u32,
2661 /// Whether the next audio packet begins a new talkspurt — RFC 3550's marker bit says so,
2662 /// and a tone interrupts the audio.
2663 ending_a_tone: bool,
2664}
2665
2666impl SendClock {
2667 /// A clock starting at a random point, as RFC 3550 §5.1 requires of both counters.
2668 fn new() -> Self {
2669 Self {
2670 sequence: rand::random(),
2671 timestamp: rand::random(),
2672 current_tone: None,
2673 tone_started_at: None,
2674 tone_duration: 0,
2675 ending_a_tone: false,
2676 }
2677 }
2678
2679 /// Move past a packet that has been sent. Both counters wrap, and both are supposed to.
2680 fn advance(&mut self, samples: u32) {
2681 self.sequence = self.sequence.wrapping_add(1);
2682 self.timestamp = self.timestamp.wrapping_add(samples);
2683 }
2684
2685 /// Build one packet of audio.
2686 ///
2687 /// `None` when the codec refused the frame, which is not the same as an empty packet: an
2688 /// empty packet would have the far end decode nothing as audio.
2689 fn audio(
2690 &mut self,
2691 encoding: &mut Encoding,
2692 payload_type: u8,
2693 ssrc: u32,
2694 samples: &[i16],
2695 ) -> Option<(Packet, u32)> {
2696 // A tone that has just finished owes the clock its duration; pay it before stamping the
2697 // audio that follows, or the audio overlaps the keypress.
2698 self.timestamp = self
2699 .timestamp
2700 .wrapping_add(std::mem::take(&mut self.tone_duration));
2701 self.current_tone = None;
2702 self.tone_started_at = None;
2703
2704 let encoded = encoding.encode(samples)?;
2705 let mut packet = Packet::new(
2706 payload_type,
2707 self.sequence,
2708 self.timestamp,
2709 ssrc,
2710 Bytes::from(encoded),
2711 );
2712 packet.marker = std::mem::take(&mut self.ending_a_tone);
2713
2714 // The timestamp advances by the samples this packet actually carried, not by the
2715 // configured packet size. They are usually the same, and when they are not — a caller
2716 // sending 10 ms frames on a 20 ms config — advancing by the configured size builds a
2717 // timeline at the wrong rate, and the far end plays the call with a gap between every
2718 // packet.
2719 Some((packet, u32::try_from(samples.len()).unwrap_or(0)))
2720 }
2721
2722 /// Build one packet of an RFC 4733 tone.
2723 fn tone(
2724 &mut self,
2725 payload_type: u8,
2726 ssrc: u32,
2727 event: DtmfEvent,
2728 offset: u32,
2729 tone: u64,
2730 ) -> (Packet, u32) {
2731 // A new keypress starts here; anything with the same tag continues the one in progress
2732 // and reuses its timestamp. That shared timestamp is what marks the packets as one
2733 // press — including the end retransmissions, which is the case that gets this wrong.
2734 let starting = self.current_tone != Some(tone);
2735 if starting {
2736 // The previous tone's duration is charged to the clock now, so audio resumes past
2737 // it rather than on top of it.
2738 self.timestamp = self
2739 .timestamp
2740 .wrapping_add(std::mem::take(&mut self.tone_duration));
2741 self.current_tone = Some(tone);
2742 self.tone_started_at = Some(self.timestamp);
2743 }
2744
2745 // Within a keypress, the segment offset moves the stamp: a segment past the duration
2746 // field's range starts at its own timestamp (RFC 4733 §2.5.1.3). The marker stays on
2747 // the event's first packet only — a segment continues a keypress, it does not start one.
2748 let mut packet = Packet::new(
2749 payload_type,
2750 self.sequence,
2751 self.tone_started_at
2752 .unwrap_or(self.timestamp)
2753 .wrapping_add(offset),
2754 ssrc,
2755 event.encode(),
2756 );
2757 packet.marker = starting;
2758 if event.end {
2759 // The whole event's length: the final segment's start plus what it carried.
2760 self.tone_duration = offset.wrapping_add(u32::from(event.duration));
2761 self.ending_a_tone = true;
2762 }
2763 (packet, 0)
2764 }
2765}
2766
2767/// Everything the send loop needs beyond its socket and its channel.
2768struct Sending {
2769 remote: Arc<Mutex<SocketAddr>>,
2770 config: Config,
2771 ssrc: u32,
2772 sent: Arc<AtomicU64>,
2773 outbound: Arc<Outbound>,
2774 muted: Arc<AtomicBool>,
2775 /// Where the fact that a packet went out is reported, so §11's keepalive is only sent on a
2776 /// pair that has actually been quiet for Tr.
2777 ice: Option<crate::ice::driver::Handle>,
2778 stop: Arc<Stop>,
2779 /// Constructed before this worker is spawned, so startup cannot fail inside the task.
2780 encoding: Encoding,
2781 discards: Arc<DiscardMeters>,
2782}
2783
2784// This is the single owner of the RTP send sequence, codec, SRTP context, pacing and their discard
2785// meters. Splitting those state transitions across helpers would make their ordering harder to audit.
2786#[allow(clippy::too_many_lines)]
2787async fn send_loop(socket: Arc<UdpSocket>, mut outgoing: mpsc::Receiver<Frame>, sending: Sending) {
2788 let Sending {
2789 remote,
2790 config,
2791 ssrc,
2792 sent,
2793 outbound,
2794 muted,
2795 ice,
2796 stop,
2797 mut encoding,
2798 discards,
2799 } = sending;
2800 let mut clock = SendClock::new();
2801 // One context, owned by this loop. SRTP keeps a rollover counter and a replay window per
2802 // stream, and a context behind a lock would put a mutex in the packet path for state exactly
2803 // one task ever touches.
2804 let mut protect = srtp_context(config.srtp.as_ref().map(|keys| &keys.local));
2805
2806 // One clock for the whole stream. Sending on channel readiness instead makes the packet
2807 // rate depend on how fast the application produces samples.
2808 let mut tick = tokio::time::interval(config.packet_duration);
2809 tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
2810
2811 loop {
2812 if stop.is_stopped() {
2813 return;
2814 }
2815 tokio::select! {
2816 () = stop.wait() => return,
2817 _ = tick.tick() => {}
2818 }
2819
2820 let Some(frame) = next_frame(&mut outgoing, &stop).await else {
2821 return;
2822 };
2823
2824 // The mute gate goes here — before the packet is built, and therefore before the
2825 // sequence number, the send counters and the sender report's octet count have been moved
2826 // by it. See [`gated`].
2827 let frame = gated(frame, &muted, config.samples_per_packet());
2828
2829 let (packet, advance) = match &frame {
2830 Frame::Audio { samples, .. } => {
2831 let Some(built) =
2832 clock.audio(&mut encoding, config.wire_payload_type(), ssrc, samples)
2833 else {
2834 discards
2835 .opus_encode_failures
2836 .fetch_add(1, Ordering::Relaxed);
2837 continue;
2838 };
2839 built
2840 }
2841 Frame::Encoded {
2842 payload_type,
2843 payload,
2844 } => {
2845 // Verbatim, on this leg's own sequence and timestamp. The advance is the
2846 // configured packet size: the bytes came from a stream with the same
2847 // packetisation, and nothing here can look inside them to check.
2848 let packet = Packet::new(
2849 *payload_type,
2850 clock.sequence,
2851 clock.timestamp,
2852 ssrc,
2853 payload.clone(),
2854 );
2855 (
2856 packet,
2857 u32::try_from(config.samples_per_packet()).unwrap_or(0),
2858 )
2859 }
2860 Frame::Dtmf {
2861 event,
2862 offset,
2863 tone,
2864 } => {
2865 let Some(payload_type) = config.dtmf_payload_type else {
2866 // Nothing negotiated `telephone-event`, so there is no payload type to send
2867 // it on. Dropping is right: guessing one means sending keypresses on
2868 // whatever the far end uses that number for.
2869 continue;
2870 };
2871 clock.tone(payload_type, ssrc, *event, *offset, *tone)
2872 }
2873 };
2874
2875 let destination = *remote.lock().await;
2876 let payload_len = packet.payload.len();
2877 let encoded = packet.encode();
2878 let datagram = match protect.as_mut() {
2879 Some(context) => match context.protect(&encoded) {
2880 Ok(protected) => Bytes::from(protected),
2881 Err(error) => {
2882 // Sending it in the clear instead is not an option: the far end negotiated
2883 // encryption and a cleartext packet is both unreadable to it and readable to
2884 // everyone else.
2885 // discard: `Packet::encode` always makes the complete header `protect`
2886 // requires, so this error branch is structurally unreachable here.
2887 tracing::warn!(%error, "dropping a packet SRTP could not protect");
2888 continue;
2889 }
2890 },
2891 None => encoded,
2892 };
2893 if socket.send_to(&datagram, destination).await.is_err() {
2894 return;
2895 }
2896 // §11: a keepalive goes out only when nothing has been sent on the selected pair for Tr,
2897 // so the agent has to be told that something was. Reported after the send and not before,
2898 // because a packet that did not leave has not held the binding open.
2899 if let Some(handle) = &ice {
2900 handle.data_sent(ComponentId::RTP);
2901 }
2902 sent.fetch_add(1, Ordering::Relaxed);
2903 // What a sender report describes. The octet count is payload only, headers excluded
2904 // (RFC 3550 §6.4.1) — counting headers would overstate the bandwidth by a fifth on
2905 // 20 ms G.711 and by far more on anything smaller.
2906 outbound.packets.fetch_add(1, Ordering::Relaxed);
2907 outbound
2908 .octets
2909 .fetch_add(payload_len as u64, Ordering::Relaxed);
2910 outbound
2911 .timestamp
2912 .store(packet.timestamp, Ordering::Relaxed);
2913
2914 // Both counters wrap, and both are supposed to.
2915 //
2916 // The timestamp advances by the samples this packet actually carried, not by the
2917 // configured packet size. They are usually the same, and when they are not — a caller
2918 // sending 10 ms frames on a 20 ms config — advancing by the configured size builds a
2919 // timeline at the wrong rate, and the far end plays the call with a gap between every
2920 // packet.
2921 clock.advance(advance);
2922 }
2923}
2924
2925/// The next frame the send loop should put on the wire, or `None` when there will not be another.
2926///
2927/// Both awaits check the stop signal. A loop parked on its channel when the call is hung up would
2928/// otherwise go on sending audio into a torn-down call.
2929///
2930/// Frames belonging to a stopped playback are skipped here rather than sent, and skipping one
2931/// costs nothing — this takes the next frame straight away rather than returning to the caller's
2932/// pacing tick. That is what bounds a stop at [`Playback::STOP_BOUND_PACKETS`]: whatever backlog
2933/// the queue was holding for the stopped clip drains inside one packet interval, and none of it
2934/// reaches the wire.
2935async fn next_frame(outgoing: &mut mpsc::Receiver<Frame>, stop: &Stop) -> Option<Frame> {
2936 loop {
2937 let frame = tokio::select! {
2938 () = stop.wait() => return None,
2939 received = outgoing.recv() => received?,
2940 };
2941 if stop.is_stopped() {
2942 return None;
2943 }
2944 if !discarded(&frame) {
2945 return Some(frame);
2946 }
2947 }
2948}
2949
2950/// One frame as the session actually sends it: itself, or what a muted session puts in its place
2951/// (`M-18`).
2952///
2953/// Applied *before* the packet is built, which is the part RFC 3550 §6 constrains: a mute that
2954/// dropped the finished datagram at the socket instead would leave the sequence number, the send
2955/// counters and the sender report's octet count (§6.4.1) all describing packets that never went
2956/// out, and would open a sequence gap the far end scores as loss.
2957///
2958/// Audio becomes the same number of samples of silence, so the timestamp this frame advances the
2959/// clock by is the one it would have advanced it by unmuted — the far end's timeline does not
2960/// move under it.
2961///
2962/// A relayed payload ([`Frame::Encoded`], a bridge passing bytes across) becomes one packet's
2963/// worth of silence in *this* session's codec. Its bytes cannot be silenced in place: they are an
2964/// opaque payload in whatever the other leg negotiated, and there is nothing here that can look
2965/// inside them. Substituting this session's own silence keeps the leg saying nothing rather than
2966/// saying what the muted party said.
2967///
2968/// A telephone event passes through. It is not audio: it is generated by this endpoint on
2969/// purpose, the way a keypad tone is on a handset, and a mute that swallowed keypresses would
2970/// make a muted caller unable to answer an IVR.
2971fn gated(frame: Frame, muted: &AtomicBool, samples_per_packet: usize) -> Frame {
2972 if !muted.load(Ordering::SeqCst) {
2973 return frame;
2974 }
2975 match frame {
2976 Frame::Audio { samples, playback } => Frame::Audio {
2977 samples: vec![0; samples.len()],
2978 playback,
2979 },
2980 Frame::Encoded { .. } => Frame::Audio {
2981 samples: vec![0; samples_per_packet],
2982 playback: None,
2983 },
2984 event @ Frame::Dtmf { .. } => event,
2985 }
2986}
2987
2988/// Whether this frame belongs to a playback that has been stopped, and so must not be sent
2989/// (`M-17`).
2990///
2991/// Read *before* the packet is built, for the same RFC 3550 §6 reason the mute gate is
2992/// ([`gated`]): the sequence number, the send counters and the sender report's octet count must
2993/// describe packets that actually went out. A frame discarded here never touches any of them,
2994/// which leaves the stream in exactly the state it is in whenever the application has nothing to
2995/// say — no gap for the far end to score as loss, because a gap needs a sequence number that was
2996/// allocated and never sent.
2997fn discarded(frame: &Frame) -> bool {
2998 matches!(frame, Frame::Audio { playback: Some(playback), .. } if playback.is_stopped())
2999}
3000
3001/// Start the playback queue and hand back the end a session keeps (`M-17`).
3002fn spawn_playback_queue(
3003 outgoing: &mpsc::Sender<Frame>,
3004 stop: &Arc<Stop>,
3005) -> (mpsc::Sender<Clip>, tokio::task::JoinHandle<()>) {
3006 let (clips_tx, clips_rx) = mpsc::channel::<Clip>(Playback::QUEUE_DEPTH);
3007 let owner = tokio::spawn(playback_loop(clips_rx, outgoing.clone(), Arc::clone(stop)));
3008 (clips_tx, owner)
3009}
3010
3011#[cfg(feature = "dtls")]
3012fn spawn_browser_playback_queue(
3013 outgoing: &mpsc::Sender<Frame>,
3014 stop: &Arc<Stop>,
3015 profile_tasks: Arc<crate::browser::ProfileTasks>,
3016) -> (mpsc::Sender<Clip>, tokio::task::JoinHandle<()>) {
3017 let (clips_tx, clips_rx) = mpsc::channel::<Clip>(Playback::QUEUE_DEPTH);
3018 let owner = tokio::spawn(crate::browser::profile_task(
3019 profile_tasks,
3020 playback_loop(clips_rx, outgoing.clone(), Arc::clone(stop)),
3021 ));
3022 (clips_tx, owner)
3023}
3024
3025/// The playback queue: one clip at a time, in the order they were started (`M-17`).
3026///
3027/// One task owns it, which is what makes "clips queue" true by construction: the order clips are
3028/// handed to the channel is the order they reach the send queue, and no two of them can ever
3029/// interleave their packets.
3030///
3031/// A task rather than a lock around the send queue. One owner is what makes the ordering a
3032/// property of the type instead of something every caller has to be careful about: two clips
3033/// started at once cannot interleave their packets, and the order they were started in is the
3034/// order the far end hears them, whatever order the callers' tasks happened to be scheduled in.
3035async fn playback_loop(
3036 mut clips: mpsc::Receiver<Clip>,
3037 outgoing: mpsc::Sender<Frame>,
3038 stop: Arc<Stop>,
3039) {
3040 loop {
3041 let clip = tokio::select! {
3042 () = stop.wait() => return,
3043 next = clips.recv() => match next {
3044 Some(clip) => clip,
3045 None => return,
3046 },
3047 };
3048
3049 let end = feed(&clip, &outgoing, &stop).await;
3050 clip.finish(end);
3051 if end == PlaybackEnd::SessionEnded {
3052 // Whatever is still queued goes down with the receiver, and every handle waiting on
3053 // one of those clips learns the same thing when its sender drops.
3054 return;
3055 }
3056 }
3057}
3058
3059/// Hand one clip to the send queue, packet by packet, until it runs out or something cuts it
3060/// short.
3061async fn feed(clip: &Clip, outgoing: &mpsc::Sender<Frame>, stop: &Stop) -> PlaybackEnd {
3062 // Armed at the head of the queue, not when the clip was started: a key pressed while an
3063 // earlier clip was still playing belongs to that clip. Reading the counter here is what marks
3064 // everything before this moment as already seen.
3065 let mut keypresses = match clip.interrupt {
3066 Interrupt::OnDigit => {
3067 let mut keypresses = clip.keypresses.clone();
3068 let _seen = *keypresses.borrow_and_update();
3069 Some(keypresses)
3070 }
3071 Interrupt::Never => None,
3072 };
3073
3074 for chunk in clip.samples.chunks(clip.samples_per_packet) {
3075 if clip.stop.is_stopped() {
3076 return PlaybackEnd::Stopped;
3077 }
3078 let mut samples = chunk.to_vec();
3079 // The last chunk may be short. Padding with silence keeps every packet the same size,
3080 // which is what a far-end jitter buffer expects.
3081 samples.resize(clip.samples_per_packet, 0);
3082 let frame = Frame::Audio {
3083 samples,
3084 playback: Some(Arc::clone(&clip.stop)),
3085 };
3086
3087 // Biased so that a stop or a keypress wins over queueing one more packet. Without it a
3088 // clip whose send queue happens to have room would go on feeding it for as long as the
3089 // scheduler kept picking that branch.
3090 tokio::select! {
3091 biased;
3092 () = stop.wait() => return PlaybackEnd::SessionEnded,
3093 () = clip.stop.wait() => return PlaybackEnd::Stopped,
3094 () = keypress(keypresses.as_mut()) => {
3095 // Set here rather than left to the caller: it is what tells the send loop to
3096 // discard the packets of this clip it is already holding, and until it is set
3097 // they would go out.
3098 clip.stop.stop();
3099 return PlaybackEnd::Interrupted;
3100 }
3101 queued = outgoing.send(frame) => {
3102 if queued.is_err() {
3103 return PlaybackEnd::SessionEnded;
3104 }
3105 }
3106 }
3107 }
3108 PlaybackEnd::Completed
3109}
3110
3111/// Resolve when the far end presses a key, or never.
3112///
3113/// Never in two cases, and they are the same case to a caller: the clip was not started
3114/// interruptible, or the receive loop is gone — in which case no keypress is coming and treating
3115/// the channel's closure as one would cut every remaining clip short at the end of a call.
3116async fn keypress(keypresses: Option<&mut watch::Receiver<u64>>) {
3117 if let Some(keypresses) = keypresses
3118 && keypresses.changed().await.is_ok()
3119 {
3120 return;
3121 }
3122 std::future::pending::<()>().await;
3123}
3124
3125/// Where a keypress goes: the application's channel, and the tick an interruptible playback
3126/// watches.
3127///
3128/// One type rather than two parameters, because the ordering between them is load-bearing — see
3129/// [`deliver`].
3130struct Keypresses {
3131 to: mpsc::Sender<(Digit, Duration)>,
3132 arrivals: Arc<watch::Sender<u64>>,
3133}
3134
3135/// Everything the receive loop needs, grouped because eight positional arguments is a
3136/// mis-ordering waiting to happen — two of them are `Arc<AtomicU64>`-shaped and swapping them
3137/// would compile.
3138struct Inbound {
3139 audio: mpsc::Sender<Vec<i16>>,
3140 encoded: mpsc::Sender<Encoded>,
3141 relay: Arc<AtomicBool>,
3142 digits: Keypresses,
3143 remote: Arc<Mutex<SocketAddr>>,
3144 config: Config,
3145 received: Arc<AtomicU64>,
3146 stats: Arc<Mutex<StreamStats>>,
3147 rtcp_observation: RtcpObservation,
3148 ssrc: u32,
3149 /// Whether the first packet's source replaces the advertised address (symmetric RTP).
3150 ///
3151 /// False for a stream ICE is driving: RFC 8445 §8.1.1's selected pair replaces this, and it
3152 /// has to *replace* it rather than race it — a stream that also learned from the first RTP
3153 /// packet to arrive would let an off-path sender who guessed the port undo the one thing a
3154 /// checked path bought.
3155 symmetric: bool,
3156 /// Where a STUN datagram goes (RFC 5764 §5.1.2), when ICE is running.
3157 ice: Option<crate::ice::driver::Handle>,
3158 browser_ingress: Option<Arc<std::sync::Mutex<crate::browser::ComponentIngress>>>,
3159 stop: Arc<Stop>,
3160 /// Constructed before this worker is spawned, so startup cannot fail inside the task.
3161 decoding: Decoding,
3162 discards: Arc<DiscardMeters>,
3163}
3164
3165/// Split a datagram arriving on a port that carries media three ways (RFC 5764 §5.1.2).
3166///
3167/// The first byte decides, and it decides **before anything else looks at the datagram**: a
3168/// connectivity check must never reach the jitter buffer and an RTP packet must never reach the
3169/// ICE agent. Returns the bytes only for the RTP path; a check goes to the agent and anything
3170/// else is dropped by name rather than handed to whichever parser happens to be first.
3171///
3172/// A check is dropped rather than kept when no agent is running, which is what the media loops
3173/// did with one before ICE existed — it would fail to parse as RTP one line later.
3174fn demultiplex<'a>(
3175 datagram: &'a [u8],
3176 from: SocketAddr,
3177 on: ice::LocalBase,
3178 ice: Option<&ice::driver::Handle>,
3179) -> Option<&'a [u8]> {
3180 match crate::dtls::classify(datagram) {
3181 crate::dtls::Arriving::Rtp => Some(datagram),
3182 crate::dtls::Arriving::Stun => {
3183 if let Some(handle) = ice {
3184 handle.datagram(from, on, datagram.to_vec());
3185 }
3186 None
3187 }
3188 crate::dtls::Arriving::Dtls | crate::dtls::Arriving::Unknown => None,
3189 }
3190}
3191
3192/// The RFC 5761 second-stage class inside RFC 5764's RTP-or-RTCP first-byte range.
3193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3194enum MuxedPacket<'a> {
3195 Rtp(&'a [u8]),
3196 Rtcp(&'a [u8]),
3197}
3198
3199fn classify_muxed(datagram: &[u8]) -> Option<MuxedPacket<'_>> {
3200 let packet_type = *datagram.get(1)?;
3201 if (192..=223).contains(&packet_type) {
3202 Some(MuxedPacket::Rtcp(datagram))
3203 } else {
3204 Some(MuxedPacket::Rtp(datagram))
3205 }
3206}
3207
3208/// Everything the two RTCP loops need, grouped because they share most of it.
3209struct Control {
3210 media: Arc<UdpSocket>,
3211 rtcp: Option<Arc<UdpSocket>>,
3212 remote: Arc<Mutex<SocketAddr>>,
3213 rtcp_remote: Arc<Mutex<Option<SocketAddr>>>,
3214 interval: Option<Duration>,
3215 mode: sipx_sdp::RtcpMode,
3216 ssrc: u32,
3217 cname: String,
3218 stats: Arc<Mutex<StreamStats>>,
3219 outbound: Arc<Outbound>,
3220 rtcp_observation: RtcpObservation,
3221 srtp: Option<SrtpKeys>,
3222 ice: Option<ice::driver::Handle>,
3223 stop: Arc<Stop>,
3224 discards: Arc<DiscardMeters>,
3225 #[cfg(feature = "dtls")]
3226 profile_tasks: Option<Arc<crate::browser::ProfileTasks>>,
3227}
3228
3229/// Start the report loops, as far as this session's configuration and sockets allow.
3230fn spawn_control(control: Control) -> Vec<tokio::task::JoinHandle<()>> {
3231 let mut owners = Vec::with_capacity(2);
3232 #[cfg(feature = "dtls")]
3233 let profile_tasks = control.profile_tasks.clone();
3234 if let Some(interval) = control.interval {
3235 let reporter = rtcp_loop(
3236 // Reports go out from the control port when there is one, which is what a peer
3237 // expects to see them come from; from the media port otherwise, which some peers
3238 // will refuse but is better than not reporting at all.
3239 control.rtcp.clone().unwrap_or(control.media),
3240 control.remote,
3241 control.rtcp_remote,
3242 interval,
3243 control.mode,
3244 control.ssrc,
3245 control.cname,
3246 control.stats,
3247 control.outbound,
3248 Arc::clone(&control.rtcp_observation.feedback),
3249 control.srtp.clone(),
3250 Arc::clone(&control.stop),
3251 );
3252 #[cfg(feature = "dtls")]
3253 let owner = if let Some(tasks) = profile_tasks.clone() {
3254 tokio::spawn(crate::browser::profile_task(tasks, reporter))
3255 } else {
3256 tokio::spawn(reporter)
3257 };
3258 #[cfg(not(feature = "dtls"))]
3259 let owner = tokio::spawn(reporter);
3260 owners.push(owner);
3261 }
3262 if control.mode == sipx_sdp::RtcpMode::Separate
3263 && let Some(port) = control.rtcp
3264 {
3265 let receiver = rtcp_receive_loop(
3266 port,
3267 control.ssrc,
3268 control.rtcp_observation,
3269 control.srtp,
3270 control.ice,
3271 control.stop,
3272 control.discards,
3273 );
3274 #[cfg(feature = "dtls")]
3275 let owner = if let Some(tasks) = profile_tasks {
3276 tokio::spawn(crate::browser::profile_task(tasks, receiver))
3277 } else {
3278 tokio::spawn(receiver)
3279 };
3280 #[cfg(not(feature = "dtls"))]
3281 let owner = tokio::spawn(receiver);
3282 owners.push(owner);
3283 }
3284 owners
3285}
3286
3287/// Start the ICE driver for this session, over the sockets the session is running on.
3288///
3289/// The base numbering is the one gathering used and the only one there is: base 0 is the media
3290/// socket, base 1 the control port when there is one. The agent names a socket by that index
3291/// alone (`docs/specs/ice.md` §2), so the two lists have to be built the same way in both places
3292/// or a check leaves the wrong port.
3293fn spawn_ice(
3294 local: ice::LocalDescription,
3295 socket: &Arc<UdpSocket>,
3296 rtcp: Option<&Arc<UdpSocket>>,
3297 destinations: &ice::driver::Destinations,
3298 stop: &Arc<Stop>,
3299 discards: &Arc<DiscardMeters>,
3300) -> (ice::driver::Handle, tokio::task::JoinHandle<()>) {
3301 let (agent, pending) = local.into_driver_parts();
3302 let mut sockets = vec![Arc::clone(socket)];
3303 if let Some(control) = rtcp {
3304 sockets.push(Arc::clone(control));
3305 }
3306 ice::driver::spawn(
3307 agent,
3308 pending,
3309 sockets,
3310 destinations.clone(),
3311 Arc::clone(stop),
3312 Arc::clone(discards),
3313 )
3314}
3315
3316/// Decide whether a packet belongs to the stream this session is carrying.
3317///
3318/// RTP has no authentication, so this is not a security control — anyone who can guess the port
3319/// can still forge a first packet. What it buys is that once a stream is established, a *later*
3320/// forged packet with a different SSRC cannot redirect our media or poison the jitter buffer,
3321/// which is the difference between a race an attacker has to win and one they can win at
3322/// leisure.
3323async fn accept_source(
3324 stream: &mut Option<u32>,
3325 packet: &Packet,
3326 source: SocketAddr,
3327 remote: &Arc<Mutex<SocketAddr>>,
3328 stats: &Arc<Mutex<StreamStats>>,
3329 symmetric: bool,
3330 discards: &DiscardMeters,
3331) -> bool {
3332 match *stream {
3333 None => {
3334 // Symmetric RTP: the observed source replaces the advertised address, because
3335 // behind a NAT the advertised one is private and this is the only path back.
3336 // Deliberately after the packet parses, so a stray STUN probe cannot move it.
3337 //
3338 // Not for a stream ICE is driving. There the address is the selected pair's
3339 // (RFC 8445 §8.1.1) and an unauthenticated packet must not be able to move it —
3340 // `docs/specs/ice.md` §11.3. The SSRC is still learned either way, because that is
3341 // what keeps a second source out of the jitter buffer.
3342 if symmetric {
3343 *remote.lock().await = source;
3344 }
3345 *stream = Some(packet.ssrc);
3346 // The far end names itself in its first packet, and the statistics carry that name
3347 // into every report block (RFC 3550 §6.4.1: a block's SSRC is the source it
3348 // describes).
3349 stats.lock().await.set_ssrc(packet.ssrc);
3350 true
3351 }
3352 Some(established) if established != packet.ssrc => {
3353 // Another source on our port. Dropped rather than mixed in: one packet with a high
3354 // sequence number would otherwise advance the jitter buffer past every genuine
3355 // packet still to come, and the call goes silent.
3356 discards.foreign_ssrc.fetch_add(1, Ordering::Relaxed);
3357 tracing::debug!(
3358 %source,
3359 ssrc = packet.ssrc,
3360 "ignoring a packet from a different synchronisation source"
3361 );
3362 false
3363 }
3364 Some(_) => true,
3365 }
3366}
3367
3368enum ReceiveInput {
3369 Socket {
3370 socket: Arc<UdpSocket>,
3371 datagram: Vec<u8>,
3372 },
3373 #[cfg(feature = "dtls")]
3374 Browser(crate::browser::MediaIngress),
3375}
3376
3377enum ReceivedDatagram {
3378 Rtp { source: SocketAddr, bytes: Vec<u8> },
3379 Rtcp { bytes: Vec<u8> },
3380 Silence,
3381 Closed,
3382}
3383
3384impl ReceiveInput {
3385 fn socket(socket: Arc<UdpSocket>) -> Self {
3386 Self::Socket {
3387 socket,
3388 datagram: vec![0u8; 2048],
3389 }
3390 }
3391
3392 async fn next(
3393 &mut self,
3394 flush_after: Duration,
3395 config: &Config,
3396 ice: Option<&crate::ice::driver::Handle>,
3397 stop: &Stop,
3398 ) -> ReceivedDatagram {
3399 loop {
3400 match self {
3401 Self::Socket { socket, datagram } => {
3402 let read = tokio::select! {
3403 () = stop.wait() => return ReceivedDatagram::Closed,
3404 read = tokio::time::timeout(flush_after, socket.recv_from(datagram)) => read,
3405 };
3406 let (length, source) = match read {
3407 Ok(Ok(received)) => received,
3408 Ok(Err(_)) => return ReceivedDatagram::Closed,
3409 Err(_elapsed) => return ReceivedDatagram::Silence,
3410 };
3411 let arrived = datagram.get(..length).unwrap_or_default();
3412 let Some(media) = demultiplex(arrived, source, crate::ice::LocalBase(0), ice)
3413 else {
3414 continue;
3415 };
3416 if config.rtcp_mode == sipx_sdp::RtcpMode::Mux {
3417 match classify_muxed(media) {
3418 Some(MuxedPacket::Rtp(media)) => {
3419 return ReceivedDatagram::Rtp {
3420 source,
3421 bytes: media.to_vec(),
3422 };
3423 }
3424 Some(MuxedPacket::Rtcp(control)) => {
3425 return ReceivedDatagram::Rtcp {
3426 bytes: control.to_vec(),
3427 };
3428 }
3429 None => continue,
3430 }
3431 }
3432 return ReceivedDatagram::Rtp {
3433 source,
3434 bytes: media.to_vec(),
3435 };
3436 }
3437 #[cfg(feature = "dtls")]
3438 Self::Browser(media) => {
3439 let next = tokio::select! {
3440 () = stop.wait() => return ReceivedDatagram::Closed,
3441 next = tokio::time::timeout(flush_after, async {
3442 tokio::select! {
3443 packet = media.srtp.recv() => packet.map(|packet| (false, packet)),
3444 packet = media.srtcp.recv() => packet.map(|packet| (true, packet)),
3445 }
3446 }) => next,
3447 };
3448 return match next {
3449 Err(_elapsed) => ReceivedDatagram::Silence,
3450 Ok(Some((true, packet))) => ReceivedDatagram::Rtcp {
3451 bytes: packet.bytes,
3452 },
3453 Ok(Some((false, packet))) => ReceivedDatagram::Rtp {
3454 source: packet.source,
3455 bytes: packet.bytes,
3456 },
3457 Ok(None) => ReceivedDatagram::Closed,
3458 };
3459 }
3460 }
3461 }
3462 }
3463}
3464
3465// This is the single ordered path from demultiplexing through authentication, source pinning,
3466// statistics and delivery. Its length keeps that security-sensitive order visible in one place.
3467#[allow(clippy::too_many_lines)]
3468async fn receive_loop(mut input: ReceiveInput, inbound: Inbound) {
3469 let Inbound {
3470 audio: incoming,
3471 encoded,
3472 relay,
3473 digits,
3474 remote,
3475 config,
3476 received,
3477 stats,
3478 rtcp_observation,
3479 ssrc,
3480 symmetric,
3481 ice,
3482 browser_ingress,
3483 stop,
3484 mut decoding,
3485 discards,
3486 } = inbound;
3487 let mut buffer = match config.jitter_max_depth {
3488 Some(max) => JitterBuffer::adaptive(config.jitter_depth, max),
3489 None => JitterBuffer::new(config.jitter_depth),
3490 };
3491 let mut unprotect = srtp_context(config.srtp.as_ref().map(|keys| &keys.remote));
3492 let mut unprotect_rtcp = srtp_context(config.srtp.as_ref().map(|keys| &keys.remote));
3493 let mut dtmf = sipx_rtp::dtmf::Receiver::new();
3494 let started = tokio::time::Instant::now();
3495 // The synchronisation source this session is carrying; see `accept_source` for why one is
3496 // pinned at all, and for what it does and does not buy.
3497 let mut stream: Option<u32> = None;
3498
3499 // When the far end stops, whatever the buffer is still holding has to come out. Without
3500 // this the last `depth - 1` packets are never played: in a continuous call that is
3501 // invisible, but at the end of every clip it clips the tail off.
3502 let flush_after = config
3503 .packet_duration
3504 .saturating_mul(4)
3505 .max(Duration::from_millis(60));
3506
3507 loop {
3508 if stop.is_stopped() {
3509 return;
3510 }
3511 let (media, source) = match input.next(flush_after, &config, ice.as_ref(), &stop).await {
3512 ReceivedDatagram::Rtp { source, bytes } => (bytes, source),
3513 ReceivedDatagram::Rtcp { bytes } => {
3514 process_rtcp(
3515 &bytes,
3516 ssrc,
3517 &rtcp_observation,
3518 &mut unprotect_rtcp,
3519 &discards,
3520 browser_ingress.as_ref(),
3521 )
3522 .await;
3523 continue;
3524 }
3525 ReceivedDatagram::Closed => return,
3526 ReceivedDatagram::Silence => {
3527 // Silence. Release what is held rather than holding it against a packet that is
3528 // not coming — otherwise the last `depth - 1` packets of every clip are lost.
3529 if !flush(
3530 &mut buffer,
3531 &delivery(&incoming, &encoded, &relay, &discards),
3532 &mut decoding,
3533 &digits,
3534 &mut dtmf,
3535 &config,
3536 &stop,
3537 )
3538 .await
3539 {
3540 return;
3541 }
3542 continue;
3543 }
3544 };
3545 let bytes = Bytes::from(media);
3546 // Authenticated before it is parsed. A packet that fails is dropped and nothing about it
3547 // reaches the parser, the jitter buffer or the statistics — which is the point of
3548 // authenticating at all: forged packets must not be able to move any state.
3549 let Some(bytes) = authenticated(
3550 unprotect.as_mut(),
3551 bytes,
3552 source,
3553 &discards,
3554 browser_ingress.as_ref(),
3555 ) else {
3556 continue;
3557 };
3558 let Ok(packet) = Packet::decode(&bytes) else {
3559 // A malformed packet is dropped, not fatal. Media ports attract stray traffic —
3560 // STUN probes, port scans, the occasional scanner — and none of it should end a
3561 // call.
3562 if let Some(ingress) = &browser_ingress {
3563 crate::browser::lock_ingress(ingress)
3564 .note_malformed(crate::browser::IngressClass::Srtp);
3565 }
3566 continue;
3567 };
3568
3569 if !accept_source(
3570 &mut stream,
3571 &packet,
3572 source,
3573 &remote,
3574 &stats,
3575 symmetric,
3576 &discards,
3577 )
3578 .await
3579 {
3580 continue;
3581 }
3582
3583 received.fetch_add(1, Ordering::Relaxed);
3584
3585 // The arrival clock has to be in the same units as the RTP timestamp — 8000 per second
3586 // for G.711 — or the jitter estimate measures the difference between two unit systems
3587 // rather than between two packets.
3588 // One arrival instant, used by both. Reading the clock twice would have the jitter
3589 // buffer and the RTCP report disagree about when the same packet turned up, by however
3590 // long the lock took — and the whole point of both numbers is that they are comparable.
3591 let arrival = arrival_in_timestamp_units(started, config.clock_rate);
3592 note_arrival(&stats, &packet, &config, arrival).await;
3593
3594 buffer.push_at(packet, arrival);
3595
3596 while let Some(packet) = buffer.pop() {
3597 if !deliver(
3598 &Delivery {
3599 audio: &incoming,
3600 encoded: &encoded,
3601 relay: &relay,
3602 discards: &discards,
3603 },
3604 &mut decoding,
3605 &digits,
3606 &mut dtmf,
3607 &config,
3608 &stop,
3609 &packet,
3610 )
3611 .await
3612 {
3613 return;
3614 }
3615 }
3616 }
3617}
3618
3619/// Hand one packet's audio to the application.
3620///
3621/// Returns whether the loop should keep running.
3622/// Score one arrival against the stream's statistics (RFC 3550 §6.4.1).
3623///
3624/// A telephone event's timestamp is the event's *start* (RFC 4733 §2.5.1.2) and not this
3625/// packet's sampling instant, so its transit grows per packet by design and would fabricate
3626/// jitter out of a keypress. It still counts for loss and sequence continuity.
3627async fn note_arrival(
3628 stats: &Arc<Mutex<StreamStats>>,
3629 packet: &Packet,
3630 config: &Config,
3631 arrival: u32,
3632) {
3633 let mut stats = stats.lock().await;
3634 if config.dtmf_payload_type == Some(packet.payload_type) {
3635 stats.on_untimed_packet(packet.sequence);
3636 } else {
3637 stats.on_packet(packet.sequence, packet.timestamp, arrival);
3638 }
3639}
3640
3641/// The local clock in RTP timestamp units.
3642fn arrival_in_timestamp_units(started: tokio::time::Instant, clock_rate: u32) -> u32 {
3643 let elapsed = started.elapsed().as_secs_f64() * f64::from(clock_rate);
3644 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
3645 let units = elapsed as u64;
3646 u32::try_from(units & u64::from(u32::MAX)).unwrap_or(0)
3647}
3648
3649/// One report interval, randomized as RFC 3550 §6.3.1 requires.
3650///
3651/// The computed interval is scaled by a factor drawn uniformly from [0.5, 1.5] and divided
3652/// by e − 3/2 ≈ 1.21828. The randomness is what keeps the participants' reports from
3653/// falling into lockstep — a fixed timer synchronises with any peer that computed the same
3654/// interval — and the division compensates so the mean stays at the configured value.
3655fn randomized_rtcp_interval(base: Duration, unit: f64) -> Duration {
3656 const COMPENSATION: f64 = std::f64::consts::E - 1.5;
3657 // A draw is a number in [0, 1); anything else — including NaN — collapses to the
3658 // middle of the range rather than panicking the report loop.
3659 let unit = if unit.is_finite() {
3660 unit.clamp(0.0, 1.0)
3661 } else {
3662 0.5
3663 };
3664 base.mul_f64((0.5 + unit) / COMPENSATION)
3665}
3666
3667/// Send a report every interval, describing what we have sent and what we have heard.
3668///
3669/// A **sender** report when we have sent anything, a receiver report otherwise — RFC 3550 §6.4
3670/// draws that line, and it is not cosmetic. Only a sender report carries the NTP timestamp the
3671/// far end echoes back, so a session that only ever sent receiver reports could never be told
3672/// its own round-trip time.
3673#[allow(clippy::too_many_arguments)]
3674async fn rtcp_loop(
3675 socket: Arc<UdpSocket>,
3676 remote: Arc<Mutex<SocketAddr>>,
3677 rtcp_remote: Arc<Mutex<Option<SocketAddr>>>,
3678 interval: Duration,
3679 mode: sipx_sdp::RtcpMode,
3680 ssrc: u32,
3681 cname: String,
3682 stats: Arc<Mutex<StreamStats>>,
3683 outbound: Arc<Outbound>,
3684 feedback: Arc<Mutex<Feedback>>,
3685 srtp: Option<SrtpKeys>,
3686 stop: Arc<Stop>,
3687) {
3688 // Owned by this loop, like the RTP contexts: SRTCP keeps its own index, and one task sends.
3689 let mut protect = srtp_context(srtp.as_ref().map(|keys| &keys.local));
3690
3691 loop {
3692 // Drawn afresh every cycle: reusing one draw for the whole session would leave the
3693 // reports evenly spaced again, just at a different spacing.
3694 let wait = randomized_rtcp_interval(interval, rand::random::<f64>());
3695 tokio::select! {
3696 () = stop.wait() => return,
3697 () = tokio::time::sleep(wait) => {}
3698 }
3699 if stop.is_stopped() {
3700 return;
3701 }
3702
3703 let sent_packets = outbound.packets.load(Ordering::Relaxed);
3704 let block = {
3705 let mut stats = stats.lock().await;
3706 // Asked of the counters rather than of a report, because `report_block()` closes the
3707 // reporting interval and RFC 3550 §6.4.1 bounds that interval by a report *packet*
3708 // going out. A tick that turns out to have nothing to say must leave the window for
3709 // the tick that does.
3710 if stats.extended_highest_sequence() == 0 && sent_packets == 0 {
3711 None
3712 } else {
3713 Some(stats.report_block())
3714 }
3715 };
3716 // Nothing has happened in either direction, so there is nothing to report on.
3717 let Some(block) = block else {
3718 continue;
3719 };
3720 let heard_anything = block.extended_highest_sequence != 0;
3721
3722 // Echo the far end's last sender report and how long we have sat on it, so *it* can
3723 // measure the round trip. Without these two fields the exchange is one-way: we could
3724 // learn our own round-trip time and the far end never could.
3725 let block = if heard_anything {
3726 let echo = *feedback.lock().await;
3727 let mut block = block;
3728 block.last_sender_report = echo.last_sender_report;
3729 block.delay_since_last_sender_report = echo.received_at.map_or(0, |at| {
3730 // In units of 1/65536 of a second.
3731 let held = tokio::time::Instant::now().saturating_duration_since(at);
3732 u32::try_from((held.as_nanos() * 65_536) / 1_000_000_000).unwrap_or(u32::MAX)
3733 });
3734 vec![block]
3735 } else {
3736 Vec::new()
3737 };
3738
3739 let report = if sent_packets > 0 {
3740 Rtcp::Sender(sipx_rtp::rtcp::SenderReport {
3741 ssrc,
3742 ntp_timestamp: sipx_rtp::quality::ntp_now(),
3743 rtp_timestamp: outbound.timestamp.load(Ordering::Relaxed),
3744 packet_count: u32::try_from(sent_packets).unwrap_or(u32::MAX),
3745 octet_count: u32::try_from(outbound.octets.load(Ordering::Relaxed))
3746 .unwrap_or(u32::MAX),
3747 reports: block,
3748 })
3749 } else {
3750 // The first word after the header is the SSRC of the packet's *sender* — us — not
3751 // of the stream being described (RFC 3550 §6.4.2); the described stream is named
3752 // inside the block.
3753 Rtcp::Receiver(ReceiverReport {
3754 ssrc,
3755 reports: block,
3756 })
3757 };
3758
3759 // Never a bare report: RFC 3550 §6.1 requires a compound of at least two packets
3760 // with an SDES CNAME in each.
3761 let datagram = Rtcp::encode_compound(&[report, Rtcp::Sdes(Sdes::cname(ssrc, &cname))]);
3762 // RFC 3711 §3.4. A report says who is talking to whom and how well — exactly the
3763 // metadata that encrypting the media was meant to withhold.
3764 let datagram = match protect.as_mut() {
3765 Some(context) => match context.protect_rtcp(&datagram) {
3766 Ok(protected) => Bytes::from(protected),
3767 Err(error) => {
3768 // discard: the compound encoder above always emits the eight-byte header
3769 // `protect_rtcp` requires, so this error branch is structurally unreachable.
3770 tracing::warn!(%error, "dropping a report SRTCP could not protect");
3771 continue;
3772 }
3773 },
3774 None => datagram,
3775 };
3776
3777 // RTCP conventionally travels on the RTP port plus one (RFC 3550 §11) — unless ICE has
3778 // selected a pair for component 2, in which case the checked path is where the reports
3779 // go and the convention is only what got them there.
3780 let destination = *remote.lock().await;
3781 let rtcp_to = if mode == sipx_sdp::RtcpMode::Mux {
3782 destination
3783 } else {
3784 let selected = *rtcp_remote.lock().await;
3785 selected.unwrap_or_else(|| {
3786 SocketAddr::new(destination.ip(), destination.port().saturating_add(1))
3787 })
3788 };
3789 if socket.send_to(&datagram, rtcp_to).await.is_err() {
3790 return;
3791 }
3792 }
3793}
3794
3795/// Take in what the far end sends back.
3796///
3797/// Two things come out of this. A **sender report** from the far end has to be remembered so
3798/// our next report can echo it, which is how the far end measures its round trip. A **report
3799/// block describing us** carries the far end's echo of *our* report, which is how we measure
3800/// ours.
3801async fn rtcp_receive_loop(
3802 socket: Arc<UdpSocket>,
3803 ssrc: u32,
3804 rtcp_observation: RtcpObservation,
3805 srtp: Option<SrtpKeys>,
3806 ice: Option<crate::ice::driver::Handle>,
3807 stop: Arc<Stop>,
3808 discards: Arc<DiscardMeters>,
3809) {
3810 let mut unprotect = srtp_context(srtp.as_ref().map(|keys| &keys.remote));
3811 let mut datagram = vec![0u8; 2048];
3812 loop {
3813 let read = tokio::select! {
3814 () = stop.wait() => return,
3815 read = socket.recv_from(&mut datagram) => read,
3816 };
3817 if stop.is_stopped() {
3818 return;
3819 }
3820 let Ok((len, source)) = read else {
3821 return;
3822 };
3823
3824 // The control port carries the same three protocols the media port does, because ICE
3825 // checks component 2 over it.
3826 let arrived = datagram.get(..len).unwrap_or(&[]);
3827 let Some(control) = demultiplex(arrived, source, ice::LocalBase(1), ice.as_ref()) else {
3828 continue;
3829 };
3830
3831 process_rtcp(
3832 control,
3833 ssrc,
3834 &rtcp_observation,
3835 &mut unprotect,
3836 &discards,
3837 None,
3838 )
3839 .await;
3840 }
3841}
3842
3843/// Authenticate, decode and apply one RTCP compound packet.
3844///
3845/// Called by either the adjacent control-port owner or the muxed media-port owner; there is never
3846/// more than one caller for a running session.
3847async fn process_rtcp(
3848 control: &[u8],
3849 ssrc: u32,
3850 rtcp_observation: &RtcpObservation,
3851 unprotect: &mut Option<sipx_rtp::SrtpContext>,
3852 discards: &DiscardMeters,
3853 browser_ingress: Option<&Arc<std::sync::Mutex<crate::browser::ComponentIngress>>>,
3854) {
3855 let bytes = Bytes::copy_from_slice(control);
3856 let bytes = match unprotect.as_mut() {
3857 Some(context) => match context.unprotect_rtcp(&bytes) {
3858 Ok(plain) => Bytes::from(plain),
3859 Err(error) => {
3860 discards
3861 .srtcp_unprotect_failures
3862 .fetch_add(1, Ordering::Relaxed);
3863 if let Some(ingress) = browser_ingress {
3864 let mut ingress = crate::browser::lock_ingress(ingress);
3865 match &error {
3866 sipx_rtp::srtp::SrtpError::TooShort(_) => {
3867 ingress.note_malformed(crate::browser::IngressClass::Srtcp);
3868 }
3869 sipx_rtp::srtp::SrtpError::ReplayedRtcp(_) => {
3870 ingress.note_replay(crate::browser::IngressClass::Srtcp);
3871 }
3872 _ => {
3873 ingress
3874 .note_authentication_failure(crate::browser::IngressClass::Srtcp);
3875 }
3876 }
3877 }
3878 // discard: `srtcp_unprotect_failures` was incremented above; browser sessions
3879 // also classified the refusal into malformed, replay or authentication once.
3880 tracing::debug!(%error, "dropping a report that failed SRTCP");
3881 return;
3882 }
3883 },
3884 None => bytes,
3885 };
3886 // Malformed control input is a drop, not a session failure, on either socket shape.
3887 let Ok(packets) = Rtcp::decode_compound(&bytes) else {
3888 if let Some(ingress) = browser_ingress {
3889 crate::browser::lock_ingress(ingress)
3890 .note_malformed(crate::browser::IngressClass::Srtcp);
3891 }
3892 return;
3893 };
3894 if let Some(ingress) = browser_ingress {
3895 crate::browser::lock_ingress(ingress).note_srtcp_processed();
3896 }
3897
3898 let arrival = tokio::time::Instant::now();
3899 for packet in packets {
3900 match packet {
3901 Rtcp::Sender(report) => {
3902 {
3903 let mut held = rtcp_observation.feedback.lock().await;
3904 held.last_sender_report = sipx_rtp::quality::middle_32(report.ntp_timestamp);
3905 held.received_at = Some(arrival);
3906 }
3907 note_quality(
3908 report.ssrc,
3909 feedback_of(&report.reports, ssrc),
3910 &rtcp_observation.feedback,
3911 &rtcp_observation.quality_hook,
3912 rtcp_observation.clock_rate,
3913 )
3914 .await;
3915 }
3916 Rtcp::Receiver(report) => {
3917 note_quality(
3918 report.ssrc,
3919 feedback_of(&report.reports, ssrc),
3920 &rtcp_observation.feedback,
3921 &rtcp_observation.quality_hook,
3922 rtcp_observation.clock_rate,
3923 )
3924 .await;
3925 }
3926 Rtcp::Sdes(_) | Rtcp::Other { .. } => {}
3927 }
3928 }
3929}
3930
3931/// The block in this report that describes *our* stream, if there is one.
3932///
3933/// A report may carry blocks about several sources. Taking the first regardless would have a
3934/// three-party call measuring its round trip to whoever happened to be listed first.
3935fn feedback_of(blocks: &[sipx_rtp::ReportBlock], ssrc: u32) -> Option<sipx_rtp::ReportBlock> {
3936 blocks.iter().find(|block| block.ssrc == ssrc).copied()
3937}
3938
3939async fn note_quality(
3940 reporter_ssrc: u32,
3941 block: Option<sipx_rtp::ReportBlock>,
3942 feedback: &Arc<Mutex<Feedback>>,
3943 quality_hook: &QualityHookSlot,
3944 clock_rate: u32,
3945) {
3946 let Some(block) = block else {
3947 return;
3948 };
3949 let now = sipx_rtp::quality::middle_32(sipx_rtp::quality::ntp_now());
3950 let round_trip = sipx_rtp::quality::round_trip(
3951 now,
3952 block.last_sender_report,
3953 block.delay_since_last_sender_report,
3954 );
3955 if let Some(trip) = round_trip {
3956 feedback.lock().await.round_trip = Some(trip);
3957 }
3958 let Some(hook) = current_quality_hook(quality_hook) else {
3959 return;
3960 };
3961 let jitter = if clock_rate == 0 {
3962 Duration::ZERO
3963 } else {
3964 Duration::from_secs_f64(f64::from(block.jitter) / f64::from(clock_rate))
3965 };
3966 let sample = RtcpQualitySample {
3967 reporter_ssrc,
3968 stream_ssrc: block.ssrc,
3969 loss: f64::from(block.fraction_lost) / 256.0,
3970 cumulative_lost: block.cumulative_lost,
3971 jitter,
3972 round_trip,
3973 };
3974 if std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| hook.observe(sample))).is_err() {
3975 tracing::warn!(
3976 reporter_ssrc,
3977 stream_ssrc = block.ssrc,
3978 "RTCP quality callback panicked; media reporting continues"
3979 );
3980 }
3981}
3982
3983/// Where a received packet goes.
3984struct Delivery<'a> {
3985 audio: &'a mpsc::Sender<Vec<i16>>,
3986 encoded: &'a mpsc::Sender<Encoded>,
3987 relay: &'a AtomicBool,
3988 discards: &'a DiscardMeters,
3989}
3990
3991async fn deliver(
3992 to: &Delivery<'_>,
3993 decoding: &mut Decoding,
3994 digits: &Keypresses,
3995 dtmf: &mut sipx_rtp::dtmf::Receiver,
3996 config: &Config,
3997 stop: &Stop,
3998 packet: &Packet,
3999) -> bool {
4000 // A telephone event is a keypress, not audio. It goes to the DTMF path and never to the
4001 // audio one — decoding a four-byte event payload as µ-law injects four garbage samples and
4002 // is heard as a click.
4003 if config.dtmf_payload_type == Some(packet.payload_type) {
4004 if let Some(event) = DtmfEvent::decode(&packet.payload)
4005 && let Some(digit) = dtmf.push(packet.timestamp, &event)
4006 {
4007 // The event's own duration, in its own clock units (RFC 4733 §2.2 — the same
4008 // clock the session negotiated for audio), converted to wall-clock time here so
4009 // every consumer of the channel gets a real duration without knowing the clock
4010 // rate itself.
4011 let millis = u64::from(event.duration) * 1000 / u64::from(config.clock_rate.max(1));
4012 // A full channel means the application is not reading digits. Dropping is
4013 // right: a keypress delivered late is worse than one not delivered, since the
4014 // application has already moved on.
4015 if digits
4016 .to
4017 .try_send((digit, Duration::from_millis(millis)))
4018 .is_ok()
4019 {
4020 // Announced only once the digit is on its way to the application, and only when
4021 // it got there (`M-17`). This is the ordering that makes interrupt-on-digit safe
4022 // to build a `gather` on: a playback can only ever be cut short by a keypress
4023 // that the application will go on to read, so the digit that stopped the prompt
4024 // is never the one the prompt swallowed.
4025 digits
4026 .arrivals
4027 .send_modify(|count| *count = count.wrapping_add(1));
4028 } else {
4029 to.discards
4030 .dtmf_delivery_failures
4031 .fetch_add(1, Ordering::Relaxed);
4032 tracing::debug!(%digit, "dropping a DTMF digit the application queue could not take");
4033 }
4034 }
4035 return true;
4036 }
4037
4038 // Relaying: hand the payload on exactly as it arrived. The bridge on the other side will
4039 // put it on its own wire with its own sequence and timestamp, which is right — the two
4040 // legs are separate RTP streams that happen to carry the same audio.
4041 if to.relay.load(Ordering::SeqCst) {
4042 let encoded = Encoded {
4043 payload_type: packet.payload_type,
4044 payload: packet.payload.clone(),
4045 };
4046 return tokio::select! {
4047 () = stop.wait() => false,
4048 result = to.encoded.send(encoded) => result.is_ok(),
4049 };
4050 }
4051
4052 // The negotiated payload type is the session's codec, whatever number it was given. Every
4053 // other number is looked up among the static types, and one that is neither is dropped
4054 // rather than decoded as the negotiated codec — decoding somebody else's format produces a
4055 // burst of noise, which is worse than a gap.
4056 if packet.payload_type != config.receive_wire_payload_type()
4057 && Codec::from_payload_type(packet.payload_type).is_none()
4058 {
4059 to.discards
4060 .unknown_payload_type
4061 .fetch_add(1, Ordering::Relaxed);
4062 tracing::debug!(
4063 payload_type = packet.payload_type,
4064 "dropping a packet with an unknown payload type"
4065 );
4066 return true;
4067 }
4068
4069 // The stop signal is checked here too. This is the one await that can park indefinitely —
4070 // a full channel means the application has stopped reading — and a task parked here when
4071 // the call is hung up would hold its socket and its port for the life of the process.
4072 let Some(samples) = decoding.decode(&packet.payload) else {
4073 to.discards
4074 .opus_decode_failures
4075 .fetch_add(1, Ordering::Relaxed);
4076 return true;
4077 };
4078
4079 tokio::select! {
4080 () = stop.wait() => false,
4081 result = to.audio.send(samples) => result.is_ok(),
4082 }
4083}
4084
4085#[cfg(test)]
4086#[allow(
4087 clippy::unwrap_used,
4088 clippy::expect_used,
4089 clippy::panic,
4090 clippy::indexing_slicing,
4091 clippy::cast_possible_truncation
4092)]
4093mod tests {
4094 use super::*;
4095
4096 fn any() -> SocketAddr {
4097 "127.0.0.1:0".parse().expect("valid")
4098 }
4099
4100 fn tone(samples: usize) -> Vec<i16> {
4101 (0..samples)
4102 .map(|i| {
4103 let phase = f64::from(u32::try_from(i).unwrap_or(0)) * 0.05;
4104 (phase.sin() * 8000.0).round() as i16
4105 })
4106 .collect()
4107 }
4108
4109 /// RTCP conventionally travels on the RTP port plus one (RFC 3550 §11), so observing
4110 /// it takes a pair of adjacent ports: the OS picks the first, and the second either
4111 /// binds or the pair is retried.
4112 async fn adjacent_ports() -> (UdpSocket, UdpSocket) {
4113 for _ in 0..32 {
4114 let rtp = UdpSocket::bind(any()).await.expect("binds");
4115 let port = rtp.local_addr().expect("addr").port();
4116 let Some(next) = port.checked_add(1) else {
4117 continue;
4118 };
4119 let rtcp_addr: SocketAddr = format!("127.0.0.1:{next}").parse().expect("valid");
4120 if let Ok(rtcp) = UdpSocket::bind(rtcp_addr).await {
4121 return (rtp, rtcp);
4122 }
4123 }
4124 panic!("no adjacent port pair could be bound");
4125 }
4126
4127 /// RFC 5761 mux has one ICE component even though [`MediaPort`] reserved an adjacent control
4128 /// socket for the no-mux fallback. This pins the public gathering entry point rather than the
4129 /// lower gatherer, so a call path cannot accidentally advertise a second receive owner.
4130 #[tokio::test]
4131 async fn mux_mode_gathers_exactly_one_ice_component() {
4132 let port = MediaPort::bind(any()).await.expect("binds adjacent ports");
4133 let credentials = sipx_sdp::ice::Credentials::new("mux1", "muxPassword0123456789AB")
4134 .expect("valid ICE credentials");
4135 let gathering = ice::Gathering::new(credentials, false);
4136
4137 let mux = port
4138 .gather_with_rtcp_mode(&gathering, sipx_sdp::RtcpMode::Mux)
4139 .await;
4140 assert_eq!(mux.candidates().len(), 1);
4141 assert!(
4142 mux.candidates()
4143 .iter()
4144 .all(|candidate| candidate.component == ComponentId::RTP)
4145 );
4146 assert_eq!(mux.default_destination(ComponentId::RTCP), None);
4147 }
4148
4149 /// How long a test here waits for audio it played to arrive before calling it lost (`X-28`).
4150 ///
4151 /// A bound on failure, not a window to measure in. Every clip below is well under a second,
4152 /// so this is more than an order of magnitude past the honest answer, and its only job is to
4153 /// stop a broken pipeline hanging the suite. The `record_until_idle(300ms)` these replaced
4154 /// was a measurement, and under load it measured the machine rather than the audio — see
4155 /// [`MediaSession::record_at_least`].
4156 ///
4157 /// The tests that still use `record_until_idle` do so deliberately: each asserts its
4158 /// recording is *empty*, so the fixed window is a window to look in rather than a deadline
4159 /// to beat, and a loaded machine can only make them pass. Waiting by count for samples that
4160 /// must never arrive would be a ten-second sleep apiece.
4161 const DELIVERY_BOUND: Duration = Duration::from_secs(10);
4162
4163 async fn pair(codec: Codec) -> (MediaSession, MediaSession) {
4164 // Each side needs the other's port, so bind one first and point the second at it.
4165 let placeholder: SocketAddr = "127.0.0.1:1".parse().expect("valid");
4166 let left = MediaSession::start(any(), Config::new(placeholder, codec))
4167 .await
4168 .expect("binds");
4169 let right = MediaSession::start(any(), Config::new(left.local_addr(), codec))
4170 .await
4171 .expect("binds");
4172 (left, right)
4173 }
4174
4175 /// `MediaSession` is the ownership boundary for every socket worker it starts. Returning
4176 /// from shutdown with an empty registry is the happens-before used by callers that report
4177 /// zero post-drain work; no wall-clock grace period stands in for a join.
4178 #[tokio::test]
4179 async fn ordinary_shutdown_joins_every_owned_worker() {
4180 let placeholder: SocketAddr = "127.0.0.1:1".parse().expect("valid");
4181 let session = MediaSession::start(any(), Config::new(placeholder, Codec::Pcmu))
4182 .await
4183 .expect("session starts");
4184
4185 assert!(
4186 session.owned_task_count().await >= 5,
4187 "send, receive, playback and both separate-RTCP workers are owned"
4188 );
4189 session.shutdown().await;
4190 assert_eq!(session.owned_task_count().await, 0);
4191 }
4192
4193 /// A shutdown future can itself be cancelled by an outer lifecycle deadline. The handle it
4194 /// was joining must remain in the session so the owner can retry and still prove reaping.
4195 #[tokio::test]
4196 async fn cancelled_shutdown_keeps_the_in_flight_worker_owned() {
4197 let placeholder: SocketAddr = "127.0.0.1:1".parse().expect("valid");
4198 let session = Arc::new(
4199 MediaSession::start(any(), Config::new(placeholder, Codec::Pcmu))
4200 .await
4201 .expect("session starts"),
4202 );
4203 let (worker_started_tx, worker_started_rx) = tokio::sync::oneshot::channel();
4204 let (release_tx, release_rx) = tokio::sync::oneshot::channel();
4205 session.owners.lock().await.push(tokio::spawn(async move {
4206 let _ = worker_started_tx.send(());
4207 let _ = release_rx.await;
4208 }));
4209 worker_started_rx.await.expect("test worker starts");
4210
4211 let shutdown_session = Arc::clone(&session);
4212 let shutdown = tokio::spawn(async move { shutdown_session.shutdown().await });
4213 tokio::time::timeout(Duration::from_secs(1), async {
4214 loop {
4215 if session.owners.try_lock().is_err() {
4216 break;
4217 }
4218 tokio::task::yield_now().await;
4219 }
4220 })
4221 .await // bound on failure: ownership-lock acquisition has no timing semantics.
4222 .expect("shutdown begins joining");
4223 shutdown.abort();
4224 let _ = shutdown.await;
4225
4226 assert!(session.owned_task_count().await >= 1);
4227 release_tx.send(()).expect("test worker remains owned");
4228 session.shutdown().await;
4229 assert_eq!(session.owned_task_count().await, 0);
4230 }
4231
4232 /// Reconfiguration installs a new generation before joining the old one. Cancellation in
4233 /// that join must leave the old generation attached to the replacement so retry can finish
4234 /// the same drain and release the shared socket deterministically.
4235 #[tokio::test]
4236 async fn cancelled_reconfigure_retains_the_old_generation_for_retry() {
4237 let placeholder: SocketAddr = "127.0.0.1:1".parse().expect("valid");
4238 let config = Config::new(placeholder, Codec::Pcmu);
4239 let session = Arc::new(Mutex::new(
4240 MediaSession::start(any(), config.clone())
4241 .await
4242 .expect("session starts"),
4243 ));
4244 let (stop_seen_tx, stop_seen_rx) = tokio::sync::oneshot::channel();
4245 let (release_tx, release_rx) = tokio::sync::oneshot::channel();
4246 {
4247 let mut session = session.lock().await;
4248 let old_stop = Arc::clone(&session.stop);
4249 session.owners.get_mut().push(tokio::spawn(async move {
4250 old_stop.wait().await;
4251 let _ = stop_seen_tx.send(());
4252 let _ = release_rx.await;
4253 }));
4254 }
4255
4256 let reconfiguring_session = Arc::clone(&session);
4257 let retry_config = config.clone();
4258 let reconfiguring = tokio::spawn(async move {
4259 reconfiguring_session
4260 .lock()
4261 .await
4262 .reconfigure(retry_config)
4263 .await
4264 });
4265 tokio::time::timeout(Duration::from_secs(1), stop_seen_rx)
4266 .await // bound on failure: the old generation's stop signal has no timing semantics.
4267 .expect("old generation observes stop")
4268 .expect("stop observer remains alive");
4269 reconfiguring.abort();
4270 let _ = reconfiguring.await;
4271
4272 let local_addr = {
4273 let mut session = session.lock().await;
4274 assert_eq!(
4275 session.retired.get_mut().len(),
4276 1,
4277 "the cancelled join retained its old generation"
4278 );
4279 release_tx.send(()).expect("old generation remains owned");
4280 assert!(session.reconfigure(config).await.expect("retry succeeds"));
4281 assert_eq!(session.retired.get_mut().len(), 0);
4282 session.shutdown().await;
4283 session.local_addr()
4284 };
4285 let session = Arc::try_unwrap(session)
4286 .expect("the test owns the replacement")
4287 .into_inner();
4288 drop(session);
4289 let rebound = UdpSocket::bind(local_addr)
4290 .await
4291 .expect("retry joined every old socket worker");
4292 drop(rebound);
4293 }
4294
4295 /// The failing-first test for this story.
4296 #[tokio::test]
4297 async fn audio_played_into_a_session_arrives_at_the_far_end() {
4298 let (left, right) = pair(Codec::Pcmu).await;
4299 let source = tone(1600); // 200 ms
4300
4301 // `right` speaks; `left` listens. `left` does not know `right`'s address until a
4302 // packet arrives, which is exactly what symmetric RTP is for.
4303 right.play(&source, 160).await;
4304 let recorded = left.record_at_least(source.len(), DELIVERY_BOUND).await;
4305
4306 assert_eq!(recorded.len(), source.len(), "every packet arrived");
4307
4308 // G.711 is lossy, so the samples cannot be compared directly — but the codec is
4309 // idempotent, so encoding the source and encoding what came back must agree exactly.
4310 assert_eq!(
4311 g711::ulaw_encode_all(&source),
4312 g711::ulaw_encode_all(&recorded),
4313 "the audio that arrived is the audio that was sent"
4314 );
4315 }
4316
4317 /// M-43 / `linear-pcm.md` §3: playback converts both depth and rate before codec encoding.
4318 /// The decoded output is asserted so an implementation that merely accepts the format cannot
4319 /// pass while still putting byte-depth garbage on the wire.
4320 #[tokio::test]
4321 async fn pcm_playback_converts_unsigned_eight_and_signed_sixteen_bit_sources() {
4322 let (left, right) = pair(Codec::Pcmu).await;
4323 let eight = sipx_audio::Pcm::new(
4324 sipx_audio::PcmFormat::new(8_000, sipx_audio::PcmEncoding::Unsigned8).expect("format"),
4325 sipx_audio::PcmSamples::Unsigned8(vec![128; 160]),
4326 )
4327 .expect("samples");
4328 assert!(right.play_pcm(&eight).await.expect("converts"));
4329 let silent = left.record_at_least(160, DELIVERY_BOUND).await;
4330 assert_eq!(silent.len(), 160);
4331 assert!(silent.iter().all(|sample| sample.abs() <= 4));
4332
4333 let source = tone(320);
4334 let sixteen = sipx_audio::Pcm::new(
4335 sipx_audio::PcmFormat::new(16_000, sipx_audio::PcmEncoding::Signed16).expect("format"),
4336 sipx_audio::PcmSamples::Signed16(source),
4337 )
4338 .expect("samples");
4339 assert!(right.play_pcm(&sixteen).await.expect("resamples"));
4340 let downsampled = left.record_at_least(160, DELIVERY_BOUND).await;
4341 assert_eq!(
4342 downsampled.len(),
4343 160,
4344 "16 kHz becomes the same duration at 8 kHz"
4345 );
4346 assert!(downsampled.iter().any(|sample| sample.abs() > 1_000));
4347 }
4348
4349 /// M-43 / `linear-pcm.md` §3: capture owns a continuous resampler and emits the caller's
4350 /// chosen rate instead of exposing the negotiated codec clock as an application assumption.
4351 #[tokio::test]
4352 async fn pcm_capture_resamples_received_audio_to_the_callers_rate() {
4353 let (left, right) = pair(Codec::Pcmu).await;
4354 let source = tone(320);
4355 let format =
4356 sipx_audio::PcmFormat::new(16_000, sipx_audio::PcmEncoding::Signed16).expect("format");
4357 let mut capture = left.capture(format).expect("capture format");
4358 right.play(&source, 160).await;
4359 let pcm = capture.record_at_least(639, DELIVERY_BOUND).await;
4360 assert_eq!(pcm.format(), format);
4361 assert_eq!(pcm.samples().len(), 639);
4362 }
4363
4364 /// Mute, from the receiving side (`M-18`): the far end gets every packet it would have got,
4365 /// and decodes silence out of all of them. Asserting the count as well as the content is the
4366 /// point — it is what distinguishes the decision that was made from the one that was not.
4367 #[tokio::test]
4368 async fn a_muted_session_sends_silence_rather_than_stopping() {
4369 let (left, right) = pair(Codec::Pcmu).await;
4370 let source = tone(800); // five packets
4371
4372 right.set_muted(true);
4373 right.play(&source, 160).await;
4374 let recorded = left.record_at_least(source.len(), DELIVERY_BOUND).await;
4375
4376 assert_eq!(recorded.len(), source.len(), "the stream did not stop");
4377 assert!(
4378 recorded.iter().all(|sample| *sample == 0),
4379 "a muted session put audio on the wire"
4380 );
4381 // RFC 3550 §6.4.1: what we say we sent is what arrived, and the far end saw no gap in the
4382 // sequence space to score as loss.
4383 assert_eq!(right.packets_sent(), 5);
4384 assert_eq!(left.packets_received(), 5);
4385 assert_eq!(left.quality().await.cumulative_lost, 0);
4386 }
4387
4388 /// The gate opens again, on the same session — no renegotiation is involved at this layer or
4389 /// any other.
4390 #[tokio::test]
4391 async fn unmuting_a_session_restores_the_audio() {
4392 let (left, right) = pair(Codec::Pcmu).await;
4393 let source = tone(480);
4394
4395 right.set_muted(true);
4396 right.play(&source, 160).await;
4397 // Drained by count, not by an idle window, and that matters more here than anywhere
4398 // else in this module (`X-28`): a short first recording leaves the rest of the muted
4399 // silence in the channel, where the second recording picks it up and compares it
4400 // against the source. The failure then reads as "unmuting did not restore the audio",
4401 // which is a lie about the code under test.
4402 let muted = left.record_at_least(source.len(), DELIVERY_BOUND).await;
4403 assert!(muted.iter().all(|sample| *sample == 0));
4404
4405 assert!(right.set_muted(false), "the gate was closed before this");
4406 right.play(&source, 160).await;
4407 let after = left.record_at_least(source.len(), DELIVERY_BOUND).await;
4408
4409 assert_eq!(
4410 g711::ulaw_encode_all(&source),
4411 g711::ulaw_encode_all(&after),
4412 "the audio that arrived after unmuting is the audio that was sent"
4413 );
4414 }
4415
4416 /// A relayed payload is opaque — a bridge's bytes in whatever the other leg negotiated — so a
4417 /// muted session substitutes its own silence for it rather than passing it on.
4418 #[tokio::test]
4419 async fn a_muted_session_does_not_relay_a_payload_either() {
4420 let (left, right) = pair(Codec::Pcmu).await;
4421
4422 right.set_muted(true);
4423 for _ in 0..3 {
4424 assert!(
4425 right
4426 .send_encoded(Encoded {
4427 payload_type: 0,
4428 payload: Bytes::from(g711::ulaw_encode_all(&tone(160))),
4429 })
4430 .await
4431 );
4432 }
4433 let recorded = left.record_at_least(480, DELIVERY_BOUND).await;
4434
4435 assert_eq!(recorded.len(), 480, "the stream did not stop");
4436 assert!(
4437 recorded.iter().all(|sample| *sample == 0),
4438 "a muted session forwarded somebody else's audio"
4439 );
4440 }
4441
4442 /// A keypress is not audio. It is generated by this endpoint on purpose, and a mute that
4443 /// swallowed it would leave a muted caller unable to answer an IVR.
4444 #[tokio::test]
4445 async fn a_muted_session_still_sends_a_keypress() {
4446 let (left, right) = pair(Codec::Pcmu).await;
4447 right.play(&tone(320), 160).await;
4448 let _ = left.record_at_least(320, DELIVERY_BOUND).await;
4449
4450 right.set_muted(true);
4451 right
4452 .send_digit(
4453 Digit::from_char('9').expect("a digit"),
4454 Duration::from_millis(100),
4455 )
4456 .await;
4457
4458 let (digit, _duration) = tokio::time::timeout(Duration::from_secs(2), left.recv_digit())
4459 .await
4460 .expect("no timeout")
4461 .expect("a digit arrives");
4462 assert_eq!(digit.as_char(), '9');
4463 }
4464
4465 /// Reception is not part of the gate: a muted session hears everything it would have heard.
4466 #[tokio::test]
4467 async fn a_muted_session_still_receives() {
4468 let (left, right) = pair(Codec::Pcmu).await;
4469 let source = tone(480);
4470
4471 left.set_muted(true);
4472 right.play(&source, 160).await;
4473 let recorded = left.record_at_least(source.len(), DELIVERY_BOUND).await;
4474
4475 assert_eq!(
4476 g711::ulaw_encode_all(&source),
4477 g711::ulaw_encode_all(&recorded),
4478 "muting this side must not touch what it hears"
4479 );
4480 }
4481
4482 /// A queue has to say what it does when it is full. Refusing, with the handle resolving to
4483 /// say so, beats the two alternatives: silently dropping the clip leaves the caller waiting
4484 /// on audio that is never coming, and waiting for room parks a call's control path on a
4485 /// backlog it may never work through.
4486 #[tokio::test]
4487 async fn a_full_playback_queue_refuses_rather_than_dropping_or_waiting() {
4488 let (left, _right) = pair(Codec::Pcmu).await;
4489
4490 // One clip reaches the head of the queue and starts feeding, so the queue proper only
4491 // has room for `Playback::QUEUE_DEPTH` behind it.
4492 let mut started = Vec::new();
4493 for _ in 0..=Playback::QUEUE_DEPTH + 1 {
4494 started.push(left.start_playback(tone(160 * 200), Interrupt::Never));
4495 }
4496
4497 let refused = started
4498 .iter()
4499 .filter(|playback| playback.end() == Some(PlaybackEnd::Refused))
4500 .count();
4501 assert!(
4502 refused > 0,
4503 "a queue {} deep must refuse the clip past its depth rather than \
4504 growing without bound",
4505 Playback::QUEUE_DEPTH
4506 );
4507 assert!(
4508 started
4509 .first()
4510 .is_some_and(|playback| playback.end().is_none()),
4511 "and it must refuse the newest clip, not the one already playing"
4512 );
4513 }
4514
4515 /// A playback started on a session that has already stopped resolves at once rather than
4516 /// hanging. The `Call` this reports through has no other way to learn it will never play.
4517 #[tokio::test]
4518 async fn a_playback_started_on_a_stopped_session_resolves_at_once() {
4519 let (left, _right) = pair(Codec::Pcmu).await;
4520 left.stop();
4521 // The playback task takes the stop signal on its next poll; until then a clip is accepted
4522 // and then resolved by the task itself.
4523 let playback = left.start_playback(tone(320), Interrupt::Never);
4524 let end = tokio::time::timeout(Duration::from_secs(2), playback.finished())
4525 .await
4526 .expect("a stopped session must not leave a playback hanging");
4527 assert_eq!(end, PlaybackEnd::SessionEnded);
4528 }
4529
4530 /// `play` is cancel-on-drop, and has to stay that way: callers wrap it in a `timeout` to cap
4531 /// how long a clip may run, and feeding the clip from a task of its own would have quietly
4532 /// turned that into a timeout that returns while the audio plays on.
4533 #[tokio::test]
4534 async fn abandoning_a_play_stops_the_clip_rather_than_leaving_it_running() {
4535 let (left, right) = pair(Codec::Pcmu).await;
4536 // Far longer than the timeout, so a clip that survives it is unmistakable.
4537 let long = tone(160 * 250);
4538
4539 let capped = tokio::time::timeout(Duration::from_millis(60), left.play(&long, 160)).await;
4540 assert!(capped.is_err(), "the timeout is what ends this play");
4541
4542 let before = left.packets_sent();
4543 // A definition of silence: how long a hole has to be before "the clip stopped" is true.
4544 // Both assertions are negative — no more than the bound went out, and the far end did not
4545 // hear the whole clip — so load lengthens the window and can only make them fail, and
4546 // there is no arrival to poll for (`X-44`).
4547 tokio::time::sleep(Duration::from_millis(400)).await;
4548 assert!(
4549 left.packets_sent() - before <= Playback::STOP_BOUND_PACKETS,
4550 "abandoning the play must stop the clip, not leave it going"
4551 );
4552 assert!(
4553 right.packets_received() < 250,
4554 "the far end heard the whole clip despite the timeout"
4555 );
4556 }
4557
4558 /// A frame that belongs to no playback is never discarded — [`MediaSession::send`] is the
4559 /// path a bridge and a conference mixer use, and nothing there has a handle to stop.
4560 #[test]
4561 fn only_a_stopped_playback_s_frames_are_discarded() {
4562 let stop = Arc::new(Stop::default());
4563 let untagged = Frame::Audio {
4564 samples: vec![0; 160],
4565 playback: None,
4566 };
4567 let tagged = Frame::Audio {
4568 samples: vec![0; 160],
4569 playback: Some(Arc::clone(&stop)),
4570 };
4571 assert!(!discarded(&untagged));
4572 assert!(!discarded(&tagged));
4573 stop.stop();
4574 assert!(discarded(&tagged));
4575 assert!(!discarded(&untagged));
4576 }
4577
4578 #[tokio::test]
4579 async fn audio_flows_in_both_directions_at_once() {
4580 let (left, right) = pair(Codec::Pcmu).await;
4581 let from_left = tone(800);
4582 let from_right: Vec<i16> = tone(800).iter().map(|s| -s).collect();
4583
4584 // Left must learn right's address, which it does from right's first packet — so wait for
4585 // that packet to have *arrived* rather than for a window to pass (`X-44`). The primer is
4586 // one packet's worth, and recording it here is also what makes the count below exact:
4587 // what `left` hears afterwards is everything except this primer.
4588 right.play(&from_right[..160], 160).await;
4589 assert_eq!(
4590 left.record_at_least(160, DELIVERY_BOUND).await.len(),
4591 160,
4592 "left never heard right's primer, so it cannot have learned right's address"
4593 );
4594
4595 let (left_recorded, right_recorded) = tokio::join!(
4596 async {
4597 left.play(&from_left, 160).await;
4598 // What `left` hears is what `right` still has to play: everything after the
4599 // 160-sample primer above.
4600 left.record_at_least(from_right.len() - 160, DELIVERY_BOUND)
4601 .await
4602 },
4603 async {
4604 right.play(&from_right[160..], 160).await;
4605 right.record_at_least(from_left.len(), DELIVERY_BOUND).await
4606 }
4607 );
4608
4609 assert!(!left_recorded.is_empty(), "left heard nothing");
4610 assert!(!right_recorded.is_empty(), "right heard nothing");
4611 }
4612
4613 #[tokio::test]
4614 async fn a_pcma_session_carries_a_law() {
4615 let (left, right) = pair(Codec::Pcma).await;
4616 let source = tone(480);
4617 right.play(&source, 160).await;
4618 let recorded = left.record_at_least(source.len(), DELIVERY_BOUND).await;
4619 assert_eq!(
4620 g711::alaw_encode_all(&source),
4621 g711::alaw_encode_all(&recorded)
4622 );
4623 }
4624
4625 /// M-43: dynamic L16 uses the negotiated clock and payload assignment, while the encoded
4626 /// samples remain signed network-order PCM and therefore arrive bit-for-bit unchanged.
4627 #[tokio::test]
4628 async fn a_dynamic_eight_kilohertz_l16_session_carries_linear_pcm() {
4629 let placeholder: SocketAddr = "127.0.0.1:1".parse().expect("valid");
4630 let mut left_config = Config::new(placeholder, Codec::L16);
4631 left_config.clock_rate = 8_000;
4632 left_config.payload_type = Some(96);
4633 left_config.receive_payload_type = Some(96);
4634 let left = MediaSession::start(any(), left_config)
4635 .await
4636 .expect("binds");
4637
4638 let mut right_config = Config::new(left.local_addr(), Codec::L16);
4639 right_config.clock_rate = 8_000;
4640 right_config.payload_type = Some(96);
4641 right_config.receive_payload_type = Some(96);
4642 let right = MediaSession::start(any(), right_config)
4643 .await
4644 .expect("binds");
4645
4646 let source = tone(480);
4647 right.play(&source, 160).await;
4648 let recorded = left.record_at_least(source.len(), DELIVERY_BOUND).await;
4649 assert_eq!(recorded, source);
4650 assert_eq!(left.clock_rate(), 8_000);
4651 assert_eq!(left.wire_payload_type(), 96);
4652 }
4653
4654 /// Media ports attract stray traffic — STUN probes, port scans. None of it should end a
4655 /// call.
4656 #[tokio::test]
4657 async fn junk_on_the_media_port_does_not_stop_the_session() {
4658 let (left, right) = pair(Codec::Pcmu).await;
4659
4660 let junk = UdpSocket::bind(any()).await.expect("binds");
4661 for _ in 0..5 {
4662 junk.send_to(b"not an RTP packet", left.local_addr())
4663 .await
4664 .expect("sends");
4665 }
4666
4667 let source = tone(320);
4668 right.play(&source, 160).await;
4669 let recorded = left.record_at_least(source.len(), DELIVERY_BOUND).await;
4670 assert_eq!(
4671 g711::ulaw_encode_all(&source),
4672 g711::ulaw_encode_all(&recorded),
4673 "the session survived the junk"
4674 );
4675 }
4676
4677 /// Symmetric RTP: `left` was configured with a useless address and still answers, because
4678 /// the observed source replaced it.
4679 #[tokio::test]
4680 async fn media_returns_to_where_it_came_from_not_where_the_sdp_said() {
4681 let (left, right) = pair(Codec::Pcmu).await;
4682
4683 // Left latches the source address off right's first packet, so wait for that packet to
4684 // have arrived rather than for a window to pass (`X-44`). A fixed window here was racing
4685 // the same pipeline `X-28` measured — two 20 ms pacers and a jitter buffer entitled to
4686 // grow — and losing it produced a reply sent to 127.0.0.1:1 and an empty recording.
4687 right.play(&tone(320), 160).await;
4688 assert_eq!(
4689 left.record_at_least(320, DELIVERY_BOUND).await.len(),
4690 320,
4691 "left never heard right, so it cannot have latched right's address"
4692 );
4693
4694 let reply = tone(320);
4695 left.play(&reply, 160).await;
4696 let heard = right.record_at_least(reply.len(), DELIVERY_BOUND).await;
4697
4698 assert!(
4699 !heard.is_empty(),
4700 "left was configured with 127.0.0.1:1 and must have learned the real address"
4701 );
4702 }
4703
4704 /// ICE and symmetric RTP must not race to write the same destination. `on_socket` supplies
4705 /// `symmetric = false` exactly when an ICE driver owns the session; a valid ordinary RTP
4706 /// packet may establish the SSRC, but cannot replace the nominated pair.
4707 #[tokio::test]
4708 async fn an_ice_owned_destination_cannot_be_replaced_by_ordinary_rtp() {
4709 let nominated: SocketAddr = "127.0.0.1:40000".parse().expect("valid");
4710 let unsolicited: SocketAddr = "127.0.0.1:50000".parse().expect("valid");
4711 let remote = Arc::new(Mutex::new(nominated));
4712 let stats = Arc::new(Mutex::new(StreamStats::new(1)));
4713 let discards = DiscardMeters::default();
4714 let packet = Packet::new(0, 1, 160, 7, Bytes::from(vec![0xff; 160]));
4715 let mut stream = None;
4716
4717 assert!(
4718 accept_source(
4719 &mut stream,
4720 &packet,
4721 unsolicited,
4722 &remote,
4723 &stats,
4724 false,
4725 &discards,
4726 )
4727 .await,
4728 "the nominated path may still receive the stream"
4729 );
4730 assert_eq!(
4731 *remote.lock().await,
4732 nominated,
4733 "RFC 8445 nomination, not the first ordinary RTP source, owns the destination"
4734 );
4735 }
4736
4737 #[tokio::test]
4738 async fn packets_are_counted_on_both_sides() {
4739 let (left, right) = pair(Codec::Pcmu).await;
4740 right.play(&tone(800), 160).await;
4741 // Counted, not timed (`X-28`). The recording is discarded, but waiting for it is what
4742 // gives all five packets time to land — so an idle window that closed early made
4743 // `packets_received` short and blamed the counters.
4744 let _ = left.record_at_least(800, DELIVERY_BOUND).await;
4745
4746 assert_eq!(right.packets_sent(), 5);
4747 assert_eq!(left.packets_received(), 5);
4748 }
4749
4750 /// A short final chunk is padded so every packet is the same size, which is what a far-end
4751 /// jitter buffer expects.
4752 #[tokio::test]
4753 async fn a_partial_final_frame_is_padded_rather_than_sent_short() {
4754 let (left, right) = pair(Codec::Pcmu).await;
4755 right.play(&tone(400), 160).await; // 2.5 packets
4756 let recorded = left.record_at_least(480, DELIVERY_BOUND).await;
4757 assert_eq!(recorded.len(), 480, "three whole packets");
4758 assert_eq!(&recorded[400..], &[0i16; 80], "padded with silence");
4759 }
4760
4761 /// The acceptance test for M-7: a keypress crosses a real media session and arrives once.
4762 #[tokio::test]
4763 async fn a_dtmf_digit_survives_a_media_session() {
4764 let (left, right) = pair(Codec::Pcmu).await;
4765
4766 // Establish the stream so `left` knows where `right` is.
4767 right.play(&tone(320), 160).await;
4768 assert_eq!(left.record_at_least(320, DELIVERY_BOUND).await.len(), 320);
4769
4770 right
4771 .send_digit(
4772 Digit::from_char('5').expect("a digit"),
4773 Duration::from_millis(100),
4774 )
4775 .await;
4776
4777 let (digit, duration) = tokio::time::timeout(Duration::from_secs(2), left.recv_digit())
4778 .await
4779 .expect("no timeout")
4780 .expect("a digit arrives");
4781 assert_eq!(digit.as_char(), '5');
4782 assert!(
4783 duration >= Duration::from_millis(80) && duration <= Duration::from_millis(140),
4784 "the reported duration must reflect how long the digit was held: {duration:?}"
4785 );
4786
4787 // Exactly once, however many packets carried it.
4788 assert!(
4789 tokio::time::timeout(Duration::from_millis(300), left.recv_digit())
4790 .await
4791 .is_err(),
4792 "one keypress must not be reported twice"
4793 );
4794 }
4795
4796 /// A whole sequence, as an application collecting a PIN would see it.
4797 #[tokio::test]
4798 async fn a_sequence_of_keypresses_arrives_in_order() {
4799 let (left, right) = pair(Codec::Pcmu).await;
4800 right.play(&tone(160), 160).await;
4801 let _ = left.record_at_least(160, DELIVERY_BOUND).await;
4802
4803 for c in "1234".chars() {
4804 right
4805 .send_digit(
4806 Digit::from_char(c).expect("a digit"),
4807 Duration::from_millis(80),
4808 )
4809 .await;
4810 }
4811
4812 let collected = left.collect_digits(FIRST_DIGIT_BOUND, DIGIT_GAP).await;
4813 assert_eq!(collected, "1234");
4814 }
4815
4816 /// How long a collection here waits for the **first** digit before calling it lost (`M-34`).
4817 ///
4818 /// A bound on failure, like [`DELIVERY_BOUND`], and for the same reason: how long a caller
4819 /// takes to press the first key is a property of the caller and of the machine carrying the
4820 /// call, never of the digits.
4821 const FIRST_DIGIT_BOUND: Duration = Duration::from_secs(10);
4822
4823 /// How long a silence means the caller has stopped dialling, for the tests here (`M-34`).
4824 ///
4825 /// A definition of silence, so it is set past any scheduling delay rather than close to the
4826 /// spacing the digits actually arrive with — fifty missed packet intervals, which is `X-28`'s
4827 /// treatment of the windows that genuinely have to stay wall-clock.
4828 const DIGIT_GAP: Duration = Duration::from_secs(1);
4829
4830 /// A first digit the caller is slow to press is still collected (`M-34`).
4831 ///
4832 /// The defect this pins: `collect_digits` spent one window on both "how long to wait for the
4833 /// first digit" and "how long a gap means the digits ended", so a caller who took longer than
4834 /// that window to press anything collected **nothing at all** — not a short sequence, an empty
4835 /// one, because the loop ended before its first iteration. That is the same one-window shape
4836 /// that made `sipx answer` write a valid WAV with zero samples (`X-40`), one layer down, and
4837 /// the reproduction is the same: delay the first thing the far end sends.
4838 #[tokio::test]
4839 async fn a_first_digit_that_arrives_late_is_still_collected() {
4840 let (left, right) = pair(Codec::Pcmu).await;
4841
4842 // Establish the stream first, so the only variable below is *when* the digits start.
4843 right.play(&tone(160), 160).await;
4844 let _ = left.record_at_least(160, DELIVERY_BOUND).await;
4845
4846 // Longer than the gap, so a collection that spends its gap on the first digit has already
4847 // given up by the time the caller presses anything. Load can only push the digits later,
4848 // which makes the pre-split failure more certain rather than less.
4849 let late = Duration::from_secs(2);
4850
4851 let (collected, ()) =
4852 tokio::join!(left.collect_digits(FIRST_DIGIT_BOUND, DIGIT_GAP), async {
4853 tokio::time::sleep(late).await;
4854 for c in "1234".chars() {
4855 right
4856 .send_digit(
4857 Digit::from_char(c).expect("a digit"),
4858 Duration::from_millis(80),
4859 )
4860 .await;
4861 }
4862 });
4863
4864 assert_eq!(
4865 collected, "1234",
4866 "a caller slow to press the first key has not finished dialling"
4867 );
4868 }
4869
4870 /// Digits that never arrive still end the collection, and end it empty (`M-34`).
4871 ///
4872 /// The other half of the split: separating the two bounds must not turn "nobody pressed
4873 /// anything" into a wait that never ends, and it must not invent digits to return.
4874 #[tokio::test]
4875 async fn a_collection_with_no_digits_at_all_ends_empty() {
4876 let (left, right) = pair(Codec::Pcmu).await;
4877
4878 right.play(&tone(160), 160).await;
4879 let _ = left.record_at_least(160, DELIVERY_BOUND).await;
4880
4881 let collected = tokio::time::timeout(
4882 DELIVERY_BOUND,
4883 left.collect_digits(Duration::from_millis(300), Duration::from_millis(300)),
4884 )
4885 .await
4886 .expect("the collection is bounded when no digit ever arrives");
4887
4888 assert_eq!(collected, "", "audio alone is not a keypress");
4889 }
4890
4891 /// DTMF must not become audio and audio must not become digits.
4892 #[tokio::test]
4893 async fn keypresses_and_audio_stay_on_their_own_paths() {
4894 let (left, right) = pair(Codec::Pcmu).await;
4895
4896 right.play(&tone(320), 160).await;
4897 let audio = left.record_at_least(320, DELIVERY_BOUND).await;
4898 assert_eq!(audio.len(), 320, "audio arrived");
4899 assert!(
4900 tokio::time::timeout(Duration::from_millis(100), left.recv_digit())
4901 .await
4902 .is_err(),
4903 "audio must not be reported as a keypress"
4904 );
4905
4906 right
4907 .send_digit(
4908 Digit::from_char('#').expect("a digit"),
4909 Duration::from_millis(80),
4910 )
4911 .await;
4912 let (digit, _duration) = tokio::time::timeout(Duration::from_secs(2), left.recv_digit())
4913 .await
4914 .expect("no timeout")
4915 .expect("a digit");
4916 assert_eq!(digit.as_char(), '#');
4917
4918 let after = left.record_until_idle(Duration::from_millis(200)).await;
4919 assert!(
4920 after.is_empty(),
4921 "a keypress must not become audio samples: {after:?}"
4922 );
4923 }
4924
4925 /// With nothing negotiated for `telephone-event`, a digit cannot be sent — and guessing a
4926 /// payload type would put keypresses on whatever the far end uses that number for.
4927 #[tokio::test]
4928 async fn a_digit_is_not_sent_when_no_payload_type_was_negotiated() {
4929 let listener = UdpSocket::bind(any()).await.expect("binds");
4930 let mut config = Config::new(listener.local_addr().expect("addr"), Codec::Pcmu);
4931 config.dtmf_payload_type = None;
4932 let session = MediaSession::start(any(), config).await.expect("binds");
4933
4934 session
4935 .send_digit(
4936 Digit::from_char('7').expect("a digit"),
4937 Duration::from_millis(80),
4938 )
4939 .await;
4940
4941 let mut datagram = vec![0u8; 2048];
4942 assert!(
4943 tokio::time::timeout(
4944 Duration::from_millis(300),
4945 listener.recv_from(&mut datagram)
4946 )
4947 .await
4948 .is_err(),
4949 "nothing should go on the wire"
4950 );
4951 }
4952
4953 /// Statistics are readable mid-call, and count what the receive path actually saw.
4954 /// Numbers that only appear when a call ends cannot be used to do anything about it.
4955 #[tokio::test]
4956 async fn a_session_reports_the_loss_it_saw() {
4957 let (left, right) = pair(Codec::Pcmu).await;
4958
4959 // Ten packets from `right`, of which two are dropped in flight by sending them from a
4960 // socket the far end will ignore — simpler: send nine of ten sequence numbers by
4961 // hand, so the gap is exact.
4962 let raw = UdpSocket::bind(any()).await.expect("binds");
4963 for sequence in 1u16..=10 {
4964 if sequence == 4 || sequence == 8 {
4965 continue;
4966 }
4967 let packet = Packet::new(
4968 0,
4969 sequence,
4970 u32::from(sequence) * 160,
4971 0xAB,
4972 Bytes::from(vec![0xFFu8; 160]),
4973 );
4974 raw.send_to(&packet.encode(), left.local_addr())
4975 .await
4976 .expect("sends");
4977 tokio::time::sleep(Duration::from_millis(5)).await;
4978 }
4979 // Wait for all eight to have been through the receive path, rather than sleeping 150 ms
4980 // and assuming they have (`X-29`). The counts below are exact, so a packet still in
4981 // flight does not degrade the answer — it changes it, and reports loss that was never
4982 // injected. The bound is on failure, not a window to measure in.
4983 //
4984 // The precondition this leans on, since it is not obvious: `packets_received()` reaching 8
4985 // implies the statistics have seen all 8 only because nothing suspends between
4986 // `received.fetch_add` (`:2258`) and `note_arrival`'s lock (`:2301-2313`) — an uncontended
4987 // `Mutex::lock().await` on a `current_thread` runtime does not yield. Move these tests to a
4988 // multi-thread runtime, or add an await in that gap, and the counter can lead the
4989 // statistics: inserting a 20 ms sleep between the two fails this test with
4990 // `extended_highest_sequence left: 9 right: 10`.
4991 let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
4992 while left.packets_received() != 8 {
4993 assert!(
4994 tokio::time::Instant::now() < deadline,
4995 "the eight hand-sent packets never reached the receive path"
4996 );
4997 tokio::time::sleep(Duration::from_millis(5)).await;
4998 }
4999
5000 let block = left.stats().await;
5001 assert_eq!(
5002 block.extended_highest_sequence, 10,
5003 "sequence 10 was the highest"
5004 );
5005 assert_eq!(block.cumulative_lost, 2, "four and eight never arrived");
5006 assert!(block.fraction_lost > 0, "and the interval shows loss");
5007
5008 drop(right);
5009 }
5010
5011 #[test]
5012 fn the_packet_size_follows_the_clock_rate_and_interval() {
5013 let config = Config::new("127.0.0.1:1".parse().expect("valid"), Codec::Pcmu);
5014 assert_eq!(config.samples_per_packet(), 160, "8 kHz at 20 ms");
5015
5016 let mut ten_ms = config.clone();
5017 ten_ms.packet_duration = Duration::from_millis(10);
5018 assert_eq!(ten_ms.samples_per_packet(), 80);
5019 }
5020
5021 #[tokio::test]
5022 async fn zero_packet_duration_is_rejected_before_binding_or_spawning() {
5023 let reservation = UdpSocket::bind(any()).await.expect("reserves a port");
5024 let address = reservation.local_addr().expect("has an address");
5025 drop(reservation);
5026
5027 let mut config = Config::new("127.0.0.1:9".parse().expect("valid"), Codec::Pcmu);
5028 config.packet_duration = Duration::ZERO;
5029 let error = MediaSession::start(address, config)
5030 .await
5031 .expect_err("zero cannot pace a worker");
5032 assert!(matches!(
5033 error,
5034 StartError::Setup(SetupError::PacketDurationTooShort(Duration::ZERO))
5035 ));
5036
5037 let rebound = UdpSocket::bind(address)
5038 .await
5039 .expect("rejected setup left no socket behind");
5040 drop(rebound);
5041 }
5042
5043 #[tokio::test]
5044 async fn zero_rtcp_interval_is_rejected_before_binding_or_spawning() {
5045 let reservation = UdpSocket::bind(any()).await.expect("reserves a port");
5046 let address = reservation.local_addr().expect("has an address");
5047 drop(reservation);
5048
5049 let mut config = Config::new("127.0.0.1:9".parse().expect("valid"), Codec::Pcmu);
5050 config.rtcp_interval = Some(Duration::ZERO);
5051 let error = MediaSession::start(address, config)
5052 .await
5053 .expect_err("zero cannot schedule reports");
5054 assert!(matches!(
5055 error,
5056 StartError::Setup(SetupError::RtcpIntervalTooShort(Duration::ZERO))
5057 ));
5058
5059 let rebound = UdpSocket::bind(address)
5060 .await
5061 .expect("rejected setup left no socket behind");
5062 drop(rebound);
5063 }
5064
5065 #[test]
5066 fn rtcp_mux_refuses_payload_types_that_collide_with_rtcp() {
5067 let mut config = Config::new(any(), Codec::Pcmu);
5068 config.rtcp_mode = sipx_sdp::RtcpMode::Mux;
5069 config.payload_type = Some(72);
5070 assert!(matches!(
5071 config.validate(),
5072 Err(SetupError::RtcpMuxPayloadCollision(72))
5073 ));
5074
5075 config.payload_type = Some(0);
5076 config.dtmf_payload_type = Some(95);
5077 assert!(matches!(
5078 config.validate(),
5079 Err(SetupError::RtcpMuxPayloadCollision(95))
5080 ));
5081 }
5082
5083 #[tokio::test]
5084 async fn one_millisecond_media_and_report_intervals_keep_running() {
5085 let peer = UdpSocket::bind(any()).await.expect("binds peer");
5086 let mut config = Config::new(peer.local_addr().expect("has an address"), Codec::Pcmu);
5087 config.packet_duration = Duration::from_millis(1);
5088 config.rtcp_interval = Some(Duration::from_millis(1));
5089 let samples = config.samples_per_packet();
5090 let session = MediaSession::start(any(), config)
5091 .await
5092 .expect("minimum intervals are valid");
5093
5094 assert!(session.send(vec![0; samples]).await);
5095 let mut datagram = vec![0u8; 2048];
5096 tokio::time::timeout(Duration::from_secs(1), peer.recv_from(&mut datagram))
5097 .await
5098 .expect("the pacing worker remains alive")
5099 .expect("receives a packet");
5100 session.stop();
5101 }
5102
5103 #[cfg(feature = "opus")]
5104 #[test]
5105 fn refused_opus_encoder_has_no_fallback_pipeline() {
5106 let error =
5107 Encoding::for_codec(Codec::Opus, 3).expect_err("Opus carries at most two channels");
5108 assert!(matches!(
5109 error,
5110 SetupError::Codec {
5111 codec: Codec::Opus,
5112 direction: CodecDirection::Encoder,
5113 ..
5114 }
5115 ));
5116 }
5117
5118 #[cfg(feature = "opus")]
5119 #[test]
5120 fn refused_opus_decoder_has_no_fallback_pipeline() {
5121 let error =
5122 Decoding::for_codec(Codec::Opus, 3).expect_err("Opus carries at most two channels");
5123 assert!(matches!(
5124 error,
5125 SetupError::Codec {
5126 codec: Codec::Opus,
5127 direction: CodecDirection::Decoder,
5128 ..
5129 }
5130 ));
5131 }
5132
5133 /// The RTP timestamp must advance by the samples actually sent. Advancing by the
5134 /// configured packet size instead builds a timeline at the wrong rate, and the far end
5135 /// plays the call with a gap between every packet.
5136 #[tokio::test]
5137 async fn the_timestamp_follows_the_frame_actually_sent() {
5138 let listener = UdpSocket::bind(any()).await.expect("binds");
5139 let listen_addr = listener.local_addr().expect("has an address");
5140 let session = MediaSession::start(any(), Config::new(listen_addr, Codec::Pcmu))
5141 .await
5142 .expect("binds");
5143
5144 // Half-sized frames on a config that says 160.
5145 for _ in 0..3 {
5146 session.send(vec![0i16; 80]).await;
5147 }
5148
5149 let mut stamps = Vec::new();
5150 let mut datagram = vec![0u8; 2048];
5151 for _ in 0..3 {
5152 let (len, _) =
5153 tokio::time::timeout(Duration::from_secs(2), listener.recv_from(&mut datagram))
5154 .await
5155 .expect("no timeout")
5156 .expect("receives");
5157 let packet =
5158 Packet::decode(&Bytes::copy_from_slice(&datagram[..len])).expect("a valid packet");
5159 stamps.push(packet.timestamp);
5160 }
5161
5162 assert_eq!(
5163 stamps[1].wrapping_sub(stamps[0]),
5164 80,
5165 "80 samples sent must advance the clock by 80, not by the configured 160"
5166 );
5167 assert_eq!(stamps[2].wrapping_sub(stamps[1]), 80);
5168 }
5169
5170 /// A dynamic payload number means only what SDP assigned it. A number that names neither
5171 /// the negotiated codec nor a known static codec is loss, and that loss must be observable.
5172 #[tokio::test]
5173 async fn an_unknown_payload_type_is_dropped_rather_than_decoded_as_audio() {
5174 let raw = UdpSocket::bind(any()).await.expect("binds");
5175 let mut config = Config::new(raw.local_addr().expect("address"), Codec::Pcmu);
5176 config.jitter_depth = 1;
5177 config.jitter_max_depth = None;
5178 config.dtmf_payload_type = None;
5179 let session = MediaSession::start(any(), config).await.expect("starts");
5180
5181 // First establish the SSRC, then offer an unassigned dynamic payload on that same stream.
5182 let valid = Packet::new(0, 1, 160, 7, Bytes::from(vec![0xFF; 160]));
5183 raw.send_to(&valid.encode(), session.local_addr())
5184 .await
5185 .expect("sends");
5186 let heard = session.record_at_least(160, DELIVERY_BOUND).await;
5187 assert_eq!(heard.len(), 160);
5188
5189 let unknown = Packet::new(96, 2, 320, 7, Bytes::from_static(&[1, 2, 3, 4]));
5190 raw.send_to(&unknown.encode(), session.local_addr())
5191 .await
5192 .expect("sends");
5193
5194 let deadline = tokio::time::Instant::now() + DELIVERY_BOUND;
5195 while session.discard_counts().unknown_payload_type == 0 {
5196 assert!(
5197 tokio::time::Instant::now() < deadline,
5198 "the unknown payload never reached the discard site"
5199 );
5200 tokio::time::sleep(Duration::from_millis(5)).await;
5201 }
5202
5203 let after = session.record_until_idle(Duration::from_millis(200)).await;
5204 assert!(
5205 after.is_empty(),
5206 "an unknown payload must not become audio samples: {after:?}"
5207 );
5208 assert_eq!(session.discard_counts().unknown_payload_type, 1);
5209 }
5210
5211 /// S-36 / RFC 3264 §6.1: each description assigns the dynamic number its author receives.
5212 /// Therefore a session sends with the peer's answer number while accepting the different
5213 /// number from its own offer; collapsing both directions into one number loses one stream.
5214 #[tokio::test]
5215 async fn asymmetric_dynamic_payload_types_are_honoured_in_both_directions() {
5216 let raw = UdpSocket::bind(any()).await.expect("binds");
5217 let mut config = Config::new(raw.local_addr().expect("address"), Codec::Pcmu);
5218 config.payload_type = Some(96);
5219 config.receive_payload_type = Some(111);
5220 config.jitter_depth = 1;
5221 config.jitter_max_depth = None;
5222 config.dtmf_payload_type = None;
5223 let session = MediaSession::start(any(), config).await.expect("starts");
5224
5225 session.send(vec![0; 160]).await;
5226 let mut datagram = vec![0; 2048];
5227 let (len, _) = tokio::time::timeout(DELIVERY_BOUND, raw.recv_from(&mut datagram))
5228 .await
5229 .expect("outbound packet arrives")
5230 .expect("receives");
5231 let outbound = Packet::decode(&Bytes::copy_from_slice(&datagram[..len])).expect("RTP");
5232 assert_eq!(outbound.payload_type, 96, "send with the peer's number");
5233
5234 let inbound = Packet::new(111, 1, 160, 7, Bytes::from(vec![0xFF; 160]));
5235 raw.send_to(&inbound.encode(), session.local_addr())
5236 .await
5237 .expect("sends inbound packet");
5238 let heard = session.record_at_least(160, DELIVERY_BOUND).await;
5239 assert_eq!(heard.len(), 160, "receive with our number");
5240 }
5241
5242 /// M-32's failing-first witness: unlike every other media discard in the original census,
5243 /// this loss had neither a trace nor a number. Fill the application queue, offer one more
5244 /// complete keypress, and assert the loss itself rather than a timeout in a consumer.
5245 #[tokio::test]
5246 async fn a_dtmf_digit_refused_by_the_application_queue_is_counted() {
5247 let (audio, _audio_rx) = mpsc::channel(1);
5248 let (encoded, _encoded_rx) = mpsc::channel(1);
5249 let relay = AtomicBool::new(false);
5250 let discards = Arc::new(DiscardMeters::default());
5251 let delivery = Delivery {
5252 audio: &audio,
5253 encoded: &encoded,
5254 relay: &relay,
5255 discards: &discards,
5256 };
5257 let (digits_tx, _digits_rx) = mpsc::channel(32);
5258 let arrivals = Arc::new(watch::Sender::new(0));
5259 let digits = Keypresses {
5260 to: digits_tx,
5261 arrivals,
5262 };
5263 let mut decoding = Decoding::for_codec(Codec::Pcmu, 1).expect("codec");
5264 let mut receiver = sipx_rtp::dtmf::Receiver::new();
5265 let config = Config::new(any(), Codec::Pcmu);
5266 let stop = Stop::default();
5267
5268 for sequence in 0u16..33 {
5269 let event = DtmfEvent {
5270 digit: Digit::Number(5),
5271 end: true,
5272 volume: 10,
5273 duration: 160,
5274 };
5275 let packet = Packet::new(
5276 dtmf::DEFAULT_PAYLOAD_TYPE,
5277 sequence,
5278 u32::from(sequence) * 160,
5279 1,
5280 event.encode(),
5281 );
5282 assert!(
5283 deliver(
5284 &delivery,
5285 &mut decoding,
5286 &digits,
5287 &mut receiver,
5288 &config,
5289 &stop,
5290 &packet,
5291 )
5292 .await
5293 );
5294 }
5295
5296 assert_eq!(discards.snapshot().dtmf_delivery_failures, 1);
5297 }
5298
5299 /// Once a stream is established, a packet from a different synchronisation source is
5300 /// dropped. Without this, one forged packet with a high sequence number advances the
5301 /// jitter buffer past every genuine packet still to come, and the call goes silent.
5302 #[tokio::test]
5303 async fn a_packet_from_another_source_cannot_silence_the_stream() {
5304 let (left, right) = pair(Codec::Pcmu).await;
5305
5306 right.play(&tone(320), 160).await;
5307 assert_eq!(left.record_at_least(320, DELIVERY_BOUND).await.len(), 320);
5308
5309 // A forged packet: valid RTP, different SSRC, sequence number far in the future.
5310 let forged = Packet::new(0, 60_000, 0, 0xBAD0_BAD0, Bytes::from(vec![0xFFu8; 160]));
5311 let attacker = UdpSocket::bind(any()).await.expect("binds");
5312 attacker
5313 .send_to(&forged.encode(), left.local_addr())
5314 .await
5315 .expect("sends");
5316 // Order on the observable effect, not on elapsed wall time: under load a fixed sleep can
5317 // let the genuine stream resume before this packet reaches the discard site.
5318 let deadline = tokio::time::Instant::now() + DELIVERY_BOUND;
5319 while left.discard_counts().foreign_ssrc == 0 {
5320 assert!(
5321 tokio::time::Instant::now() < deadline,
5322 "the foreign packet never reached the discard site"
5323 );
5324 tokio::time::sleep(Duration::from_millis(5)).await;
5325 }
5326
5327 // The genuine stream still gets through.
5328 let more = tone(320);
5329 right.play(&more, 160).await;
5330 let heard = left.record_at_least(more.len(), DELIVERY_BOUND).await;
5331 assert_eq!(
5332 g711::ulaw_encode_all(&more),
5333 g711::ulaw_encode_all(&heard),
5334 "the forged packet must not have poisoned the buffer"
5335 );
5336 }
5337
5338 /// RFC 3550 §6.4.2: a report's first field is the SSRC of the *reporter*, and §8.1
5339 /// requires that to be the SSRC the reporter's own RTP carries; each report block names
5340 /// the source it describes. A report saying "SSRC 0 heard SSRC 0" is unusable.
5341 #[tokio::test]
5342 async fn rtcp_reports_name_both_parties_by_their_real_ssrcs() {
5343 let (peer_media, peer_control) = adjacent_ports().await;
5344 let mut config = Config::new(peer_media.local_addr().expect("addr"), Codec::Pcmu);
5345 config.rtcp_interval = Some(Duration::from_millis(200));
5346 let session = MediaSession::start(any(), config).await.expect("binds");
5347
5348 // The peer speaks first, so the session latches its address and its
5349 // synchronisation source.
5350 for sequence in 1u16..=5 {
5351 let packet = Packet::new(
5352 0,
5353 sequence,
5354 u32::from(sequence) * 160,
5355 0x5EED_CAFE,
5356 Bytes::from(vec![0xFFu8; 160]),
5357 );
5358 peer_media
5359 .send_to(&packet.encode(), session.local_addr())
5360 .await
5361 .expect("sends");
5362 tokio::time::sleep(Duration::from_millis(10)).await;
5363 }
5364
5365 // And the session speaks, so the SSRC its own RTP carries is observable.
5366 session.play(&tone(320), 160).await;
5367 let mut datagram = vec![0u8; 2048];
5368 let (len, _) =
5369 tokio::time::timeout(Duration::from_secs(2), peer_media.recv_from(&mut datagram))
5370 .await
5371 .expect("no timeout")
5372 .expect("receives");
5373 let rtp_ssrc = Packet::decode(&Bytes::copy_from_slice(&datagram[..len]))
5374 .expect("a valid packet")
5375 .ssrc;
5376
5377 let (len, _) = tokio::time::timeout(
5378 Duration::from_secs(3),
5379 peer_control.recv_from(&mut datagram),
5380 )
5381 .await
5382 .expect("a report arrives")
5383 .expect("receives");
5384 let packets =
5385 Rtcp::decode_compound(&Bytes::copy_from_slice(&datagram[..len])).expect("valid RTCP");
5386 let (reporter, blocks) = match &packets[0] {
5387 Rtcp::Sender(report) => (report.ssrc, report.reports.clone()),
5388 Rtcp::Receiver(report) => (report.ssrc, report.reports.clone()),
5389 other => panic!("a report must lead, got {other:?}"),
5390 };
5391 assert_eq!(
5392 reporter, rtp_ssrc,
5393 "the reporter names itself by the SSRC its RTP carries"
5394 );
5395 assert_eq!(blocks[0].ssrc, 0x5EED_CAFE, "the block names the far end");
5396 }
5397
5398 /// RFC 3550 §6.1: every RTCP packet travels in a compound of at least two, the first a
5399 /// report, and each compound carries an SDES CNAME. The CNAME is what lets a receiver
5400 /// tie streams to one participant across an SSRC change, so it must be stable.
5401 #[tokio::test]
5402 async fn rtcp_goes_out_compound_with_a_stable_cname() {
5403 let (peer_media, peer_control) = adjacent_ports().await;
5404 let mut config = Config::new(peer_media.local_addr().expect("addr"), Codec::Pcmu);
5405 config.rtcp_interval = Some(Duration::from_millis(150));
5406 let session = MediaSession::start(any(), config).await.expect("binds");
5407
5408 // The peer speaks so there is something to report on.
5409 for sequence in 1u16..=5 {
5410 let packet = Packet::new(
5411 0,
5412 sequence,
5413 u32::from(sequence) * 160,
5414 0xABCD,
5415 Bytes::from(vec![0xFFu8; 160]),
5416 );
5417 peer_media
5418 .send_to(&packet.encode(), session.local_addr())
5419 .await
5420 .expect("sends");
5421 tokio::time::sleep(Duration::from_millis(10)).await;
5422 }
5423
5424 let mut cnames = Vec::new();
5425 let mut datagram = vec![0u8; 2048];
5426 for _ in 0..2 {
5427 let (len, _) = tokio::time::timeout(
5428 Duration::from_secs(3),
5429 peer_control.recv_from(&mut datagram),
5430 )
5431 .await
5432 .expect("a report arrives")
5433 .expect("receives");
5434 let packets = Rtcp::decode_compound(&Bytes::copy_from_slice(&datagram[..len]))
5435 .expect("valid RTCP");
5436 assert!(packets.len() >= 2, "a lone report is not a compound");
5437 assert!(
5438 matches!(packets[0], Rtcp::Sender(_) | Rtcp::Receiver(_)),
5439 "a report leads the compound"
5440 );
5441 let sdes = packets
5442 .iter()
5443 .find_map(|packet| match packet {
5444 Rtcp::Sdes(sdes) => Some(sdes),
5445 _ => None,
5446 })
5447 .expect("an SDES in every compound");
5448 let cname = sdes.chunks[0]
5449 .items
5450 .iter()
5451 .find(|item| item.kind == sipx_rtp::rtcp::SDES_CNAME)
5452 .expect("a CNAME item");
5453 assert!(!cname.value.is_empty());
5454 cnames.push(cname.value.clone());
5455 }
5456 assert_eq!(cnames[0], cnames[1], "the CNAME does not change mid-call");
5457 }
5458
5459 /// RFC 4733 §2.5.1.2: every packet of one telephone event carries the *same* timestamp
5460 /// — the event's start — while being sent one packetisation interval apart, so its
5461 /// transit grows by one interval per packet by design. RFC 3550 §6.4.1 defines jitter
5462 /// over packets whose timestamps track sampling instants; a keypress must not fabricate
5463 /// jitter, but it must still count for loss and sequence continuity.
5464 #[tokio::test]
5465 async fn a_keypress_does_not_register_as_jitter() {
5466 let raw = UdpSocket::bind(any()).await.expect("binds");
5467 let session = MediaSession::start(
5468 any(),
5469 Config::new(raw.local_addr().expect("addr"), Codec::Pcmu),
5470 )
5471 .await
5472 .expect("binds");
5473
5474 // One long keypress: same timestamp throughout, spaced a packet interval apart,
5475 // with one packet lost in flight.
5476 for sequence in 1u16..=10 {
5477 if sequence == 4 {
5478 continue;
5479 }
5480 let duration = sequence * 160;
5481 let event = DtmfEvent::new(Digit::Number(5), duration);
5482 let packet = Packet::new(101, sequence, 5000, 0xAB, event.encode());
5483 raw.send_to(&packet.encode(), session.local_addr())
5484 .await
5485 .expect("sends");
5486 tokio::time::sleep(Duration::from_millis(20)).await;
5487 }
5488 // As in `a_session_reports_the_loss_it_saw`: wait for the nine to arrive rather than
5489 // sleeping 100 ms and assuming they have (`X-29`). `extended_highest_sequence` and
5490 // `cumulative_lost` are asserted exactly, so a straggler reports loss nobody injected.
5491 // Same precondition as that test, and it is the same fragility: the counter only implies
5492 // the statistics because nothing suspends between `received.fetch_add` (`:2258`) and
5493 // `note_arrival`'s lock (`:2301-2313`) on a `current_thread` runtime.
5494 let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
5495 while session.packets_received() != 9 {
5496 assert!(
5497 tokio::time::Instant::now() < deadline,
5498 "the nine hand-sent keypress packets never reached the receive path"
5499 );
5500 tokio::time::sleep(Duration::from_millis(5)).await;
5501 }
5502
5503 let block = session.stats().await;
5504 assert_eq!(block.jitter, 0, "a keypress is not network jitter");
5505 assert_eq!(
5506 block.extended_highest_sequence, 10,
5507 "the events still advance the sequence accounting"
5508 );
5509 assert_eq!(block.cumulative_lost, 1, "and still count for loss");
5510 }
5511
5512 /// RFC 3550 §6.4: a participant that sent data during the interval sends a *sender*
5513 /// report. The RR it would otherwise send carries no NTP/RTP pair and no counts, and
5514 /// without those the far end can never compute round-trip time or line the clocks up.
5515 #[tokio::test]
5516 async fn an_active_sender_reports_with_a_sender_report() {
5517 let (peer_media, peer_control) = adjacent_ports().await;
5518 let mut config = Config::new(peer_media.local_addr().expect("addr"), Codec::Pcmu);
5519 config.rtcp_interval = Some(Duration::from_millis(200));
5520 let session = MediaSession::start(any(), config).await.expect("binds");
5521
5522 // Both directions are busy: the peer speaks once, the session streams audio.
5523 let packet = Packet::new(0, 1, 160, 0xABCD, Bytes::from(vec![0xFFu8; 160]));
5524 peer_media
5525 .send_to(&packet.encode(), session.local_addr())
5526 .await
5527 .expect("sends");
5528 session.play(&tone(1600), 160).await;
5529
5530 // The first interval may legitimately elapse before the first packet leaves — an
5531 // RR is correct then — so wait for the first report from an interval in which
5532 // data went out. A stack that never sends one fails here by timing out.
5533 let mut datagram = vec![0u8; 2048];
5534 let report = tokio::time::timeout(Duration::from_secs(5), async {
5535 loop {
5536 let (len, _) = peer_control
5537 .recv_from(&mut datagram)
5538 .await
5539 .expect("receives");
5540 let packets = Rtcp::decode_compound(&Bytes::copy_from_slice(&datagram[..len]))
5541 .expect("valid RTCP");
5542 match &packets[0] {
5543 Rtcp::Sender(report) => return report.clone(),
5544 Rtcp::Receiver(_) => {}
5545 other => panic!("a report must lead the compound, got {other:?}"),
5546 }
5547 }
5548 })
5549 .await
5550 .expect("an active sender must send a sender report");
5551
5552 assert!(report.packet_count >= 1, "it counts the packets it sent");
5553 assert!(report.octet_count >= 160, "and the payload bytes");
5554 // RFC 3550 §4: the high word is seconds since 1900 — around 3.97 billion now.
5555 let seconds = report.ntp_timestamp >> 32;
5556 assert!(
5557 (3_700_000_000..4_294_967_295).contains(&seconds),
5558 "the NTP word counts seconds since 1900: {seconds}"
5559 );
5560 assert_eq!(report.reports.len(), 1, "reception is appended as a block");
5561 }
5562
5563 fn peer_sender_report(ntp_timestamp: u64) -> Bytes {
5564 Rtcp::encode_compound(&[
5565 Rtcp::Sender(sipx_rtp::rtcp::SenderReport {
5566 ssrc: 0x5EED_CAFE,
5567 ntp_timestamp,
5568 rtp_timestamp: 160,
5569 packet_count: 1,
5570 octet_count: 160,
5571 reports: Vec::new(),
5572 }),
5573 Rtcp::Sdes(Sdes::cname(0x5EED_CAFE, "peer@example.invalid")),
5574 ])
5575 }
5576
5577 async fn wait_for_rtcp_echo(socket: &UdpSocket, expected: u32) -> SocketAddr {
5578 let mut datagram = vec![0u8; 2048];
5579 tokio::time::timeout(Duration::from_secs(3), async {
5580 loop {
5581 let (len, from) = socket.recv_from(&mut datagram).await.expect("receives");
5582 let packets = Rtcp::decode_compound(&Bytes::copy_from_slice(&datagram[..len]))
5583 .expect("valid RTCP");
5584 let echoed = packets.iter().any(|packet| match packet {
5585 Rtcp::Sender(report) => report
5586 .reports
5587 .iter()
5588 .any(|block| block.last_sender_report == expected),
5589 Rtcp::Receiver(report) => report
5590 .reports
5591 .iter()
5592 .any(|block| block.last_sender_report == expected),
5593 Rtcp::Sdes(_) | Rtcp::Other { .. } => false,
5594 });
5595 if echoed {
5596 return from;
5597 }
5598 }
5599 })
5600 .await
5601 .expect("the peer sender report is processed and echoed")
5602 }
5603
5604 /// `MUX-PKT-1`, failing first: RTCP arriving on the negotiated RTP port reaches RTCP state,
5605 /// and the response leaves from and returns to that same port.
5606 #[tokio::test]
5607 async fn muxed_rtcp_arriving_on_the_rtp_port_is_processed_not_dropped() {
5608 let peer = UdpSocket::bind(any()).await.expect("binds peer");
5609 let mut config = Config::new(peer.local_addr().expect("peer address"), Codec::Pcmu);
5610 config.rtcp_mode = sipx_sdp::RtcpMode::Mux;
5611 config.rtcp_interval = Some(Duration::from_millis(20));
5612 let session = MediaSession::start(any(), config).await.expect("starts");
5613
5614 let rtp = Packet::new(0, 1, 160, 0x5EED_CAFE, Bytes::from(vec![0xFF; 160]));
5615 peer.send_to(&rtp.encode(), session.local_addr())
5616 .await
5617 .expect("sends RTP");
5618 let ntp = 0x0123_4567_89AB_CDEF;
5619 peer.send_to(&peer_sender_report(ntp), session.local_addr())
5620 .await
5621 .expect("sends RTCP on the RTP port");
5622
5623 let report_source = wait_for_rtcp_echo(&peer, sipx_rtp::quality::middle_32(ntp)).await;
5624 assert_eq!(
5625 report_source,
5626 session.local_addr(),
5627 "muxed RTCP leaves from the exact socket address advertised for RTP"
5628 );
5629 session.stop();
5630 }
5631
5632 /// `MUX-PKT-2`: omission of mux leaves the established adjacent control-port path live.
5633 #[tokio::test]
5634 async fn separate_rtcp_still_uses_and_processes_the_control_port() {
5635 let (peer_media, peer_control) = adjacent_ports().await;
5636 let mut config = Config::new(peer_media.local_addr().expect("peer address"), Codec::Pcmu);
5637 config.rtcp_mode = sipx_sdp::RtcpMode::Separate;
5638 config.rtcp_interval = Some(Duration::from_millis(20));
5639 let session = MediaSession::start(any(), config).await.expect("starts");
5640
5641 let rtp = Packet::new(0, 1, 160, 0x5EED_CAFE, Bytes::from(vec![0xFF; 160]));
5642 peer_media
5643 .send_to(&rtp.encode(), session.local_addr())
5644 .await
5645 .expect("sends RTP");
5646 let control_addr = session
5647 .rtcp_socket
5648 .as_ref()
5649 .expect("the session bound its adjacent control port")
5650 .local_addr()
5651 .expect("control address");
5652 let ntp = 0x89AB_CDEF_0123_4567;
5653 peer_control
5654 .send_to(&peer_sender_report(ntp), control_addr)
5655 .await
5656 .expect("sends RTCP on the control port");
5657
5658 let _ = wait_for_rtcp_echo(&peer_control, sipx_rtp::quality::middle_32(ntp)).await;
5659 session.stop();
5660 }
5661
5662 /// RFC 3550 §6.3.1: each report interval is drawn uniformly from [0.5, 1.5] of the
5663 /// computed value and divided by e − 3/2 ≈ 1.21828. Without the randomness every
5664 /// participant that computed the same interval reports at the same instant, forever.
5665 #[test]
5666 fn the_rtcp_interval_is_randomised_over_the_rfc_range() {
5667 let base = Duration::from_secs(5);
5668 let compensation = std::f64::consts::E - 1.5;
5669
5670 let low = randomized_rtcp_interval(base, 0.0);
5671 let high = randomized_rtcp_interval(base, 1.0);
5672 assert!((low.as_secs_f64() - 2.5 / compensation).abs() < 1e-9);
5673 assert!((high.as_secs_f64() - 7.5 / compensation).abs() < 1e-9);
5674 assert!(
5675 low < base && base < high,
5676 "the range straddles the configured value: {low:?}..{high:?}"
5677 );
5678
5679 // A draw outside the unit range must not panic the media path or leave the range.
5680 assert!(randomized_rtcp_interval(base, f64::NAN) >= low);
5681 assert!(randomized_rtcp_interval(base, 7.0) <= high);
5682 }
5683
5684 #[test]
5685 fn codecs_map_to_their_static_payload_types() {
5686 assert_eq!(Codec::Pcmu.payload_type(), 0);
5687 assert_eq!(Codec::Pcma.payload_type(), 8);
5688 assert_eq!(Codec::L16.payload_type(), 11);
5689 assert_eq!(Codec::from_payload_type(0), Some(Codec::Pcmu));
5690 assert_eq!(Codec::from_payload_type(8), Some(Codec::Pcma));
5691 assert_eq!(Codec::from_payload_type(11), Some(Codec::L16));
5692 assert_eq!(Codec::from_payload_type(10), None, "stereo L16 is not ours");
5693 assert_eq!(Codec::from_payload_type(9), None, "G.722 is not ours");
5694 }
5695}