sipx_call/call.rs
1//! Establishing a call: INVITE with an SDP offer, media bound to the answer, and BYE.
2
3use std::future::Future;
4use std::net::{IpAddr, SocketAddr};
5use std::pin::Pin;
6use std::sync::Arc;
7use std::time::Duration;
8
9use tokio::time::Instant;
10use tokio_util::sync::CancellationToken;
11
12use bytes::Bytes;
13use sipx_media::ice::{LocalDescription, Negotiation as IceNegotiation};
14use sipx_media::{Codec, Interrupt, MediaPort, MediaSession, Playback};
15use sipx_sdp::ice::{ComponentId, Credentials as IceCredentials};
16use sipx_sdp::{Capabilities, Connection, Direction, SessionDescription};
17use sipx_sip::build::{RequestBuilder, ResponseBuilder};
18use sipx_sip::session::{self, MinSe, SessionExpires};
19use sipx_sip::update::{self, Reception};
20use sipx_sip::{
21 HeaderName, HistoryInfo, Method, Reason, ReasonValue, Request, Response, StatusCode, Uri,
22};
23use sipx_transport::{Handle, Incoming, Target, TransportKind};
24
25pub use sipx_sip::auth::Credentials;
26
27use crate::dialog::{Dialog, strip_header_params};
28use crate::error::{Error, Result};
29use crate::event::{CallEvent, CallEvents, EndCause, EventSink};
30use crate::extension::{self, ApplicationRequest};
31use crate::identity::OutboundIdentityPolicy;
32use crate::media_policy::{Codecs, IcePolicy, Keying, MediaPolicy, MediaProfile, NegotiatedKeying};
33use crate::snapshot::{
34 DialogNotQuiescent, DialogPersistenceError, DialogRestoreContext, DialogSnapshot,
35 SessionSnapshot, SnapshotParts,
36};
37use crate::transfer::{
38 Referral, Replaces, Transfer, TransferState, is_terminated, parse_sipfrag, sipfrag,
39};
40
41/// 200 OK.
42///
43/// `StatusCode::new` is fallible because most codes come from the wire; this one is a literal
44/// that is always in range. Threading a `Result` out of every call site for it would mean
45/// inventing an error that can never happen — and the previous attempt reported it as "no
46/// final response to the INVITE", which would have been actively misleading.
47const OK: u16 = 200;
48
49pub(crate) fn ok_status() -> StatusCode {
50 StatusCode::new(OK).unwrap_or_else(|| unreachable!("200 is a valid status code"))
51}
52
53/// Queue the events construction already knows happened: `Ringing`, if the far end rang first,
54/// then `Answered` — every `Call` gets exactly this sequence at birth, on both the caller's and
55/// the callee's side, which is why both construction sites share it rather than repeating it.
56pub(crate) fn emit_construction_events(events: &EventSink, ringing: Option<bool>) {
57 if let Some(reliable) = ringing {
58 events.emit(CallEvent::Ringing { reliable });
59 }
60 events.emit(CallEvent::Answered);
61}
62
63/// A fresh token for a `Call-ID` or a `tag`.
64///
65/// Its own function rather than the user agent's digest `cnonce`: a dialog identifier is not an
66/// authentication nonce, and borrowing one ties this layer to the one that handles credentials
67/// for no reason beyond both wanting random hex.
68fn token_with_rng<R>(rng: &mut R) -> String
69where
70 R: rand::CryptoRng + ?Sized,
71{
72 let value = rand::RngCore::next_u64(rng);
73 format!("{value:016x}")
74}
75
76pub(crate) fn token() -> String {
77 token_with_rng(&mut rand::rng())
78}
79
80/// The two address roles of one media socket.
81///
82/// `advertised` is written into SDP and may be a public NAT mapping the host does not own.
83/// `bind` selects the local interface on which the RTP socket is opened. Passing an [`IpAddr`]
84/// keeps the historical behaviour by using it for both roles.
85///
86/// When ICE is enabled, these addresses are only the local gathering base and initial SDP
87/// default. A nominated ICE pair owns the live destination; symmetric RTP cannot replace it.
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub struct MediaAddress {
90 advertised: IpAddr,
91 bind: IpAddr,
92}
93
94impl MediaAddress {
95 /// Advertise and bind the same address.
96 #[must_use]
97 pub const fn new(advertised: IpAddr) -> Self {
98 Self {
99 advertised,
100 bind: advertised,
101 }
102 }
103
104 /// Bind RTP on `bind` while continuing to advertise the address passed to [`Self::new`].
105 ///
106 /// An unspecified bind address is valid. The advertised address must be reachable by the
107 /// peer; an unspecified one is refused before signalling. With ICE enabled,
108 /// the nominated pair takes precedence over symmetric-RTP source learning.
109 #[must_use]
110 pub const fn with_bind(mut self, bind: IpAddr) -> Self {
111 self.bind = bind;
112 self
113 }
114
115 /// The address serialized into SDP.
116 #[must_use]
117 pub const fn advertised(self) -> IpAddr {
118 self.advertised
119 }
120
121 /// The local address supplied to the RTP socket bind.
122 #[must_use]
123 pub const fn bind(self) -> IpAddr {
124 self.bind
125 }
126
127 fn validate(self) -> Result<Self> {
128 if self.advertised.is_unspecified() {
129 return Err(Error::UnspecifiedMediaAddress);
130 }
131 Ok(self)
132 }
133}
134
135impl From<IpAddr> for MediaAddress {
136 fn from(address: IpAddr) -> Self {
137 Self::new(address)
138 }
139}
140
141/// A call in progress.
142#[derive(Debug)]
143pub struct Call {
144 /// The dialog it runs in.
145 pub dialog: Dialog,
146 /// The successful final response that established this dialog.
147 ///
148 /// A caller retains the actual 2xx it received; an answerer records the 200 it sent. Keeping
149 /// this fact on the call lets applications report response-code distributions without
150 /// inventing `200` for a peer that answered with a different successful status.
151 initial_status: u16,
152 media: Arc<MediaSession>,
153 /// Replaced sessions whose workers have been stopped but not yet completely joined. A
154 /// cancelled renegotiation leaves this ownership in the call for retry or terminal cleanup.
155 retired_media: Vec<Arc<MediaSession>>,
156 endpoint: Handle,
157 /// Where in-dialog requests go: the peer's `Contact`, not where the INVITE was sent.
158 target: Target,
159 /// Set while a 2xx is still being retransmitted; cleared when the ACK arrives.
160 ack_stop: Option<CancellationToken>,
161 /// Completion of the successful-final-response retransmitter. Retained separately from its
162 /// stop signal so an ACK or terminal call path proves that the worker has actually exited.
163 ack_retransmission: Option<OwnedTask>,
164 /// Capabilities behind an offer sent in a re-INVITE's 2xx, awaiting its answer in the ACK.
165 delayed_offer: Option<Capabilities>,
166 ended: bool,
167 /// Where this side receives media, so a re-offer can name the same address.
168 media_address: IpAddr,
169 /// Where replacement media sockets bind during an in-dialog renegotiation.
170 media_bind_address: IpAddr,
171 /// The codec set this call was placed or answered with, so a re-offer offers the same
172 /// set — a re-INVITE that silently narrowed to G.711 would move an Opus call mid-call.
173 codecs: Codecs,
174 /// Named composition policy retained for renegotiation and diagnostics.
175 profile: MediaProfile,
176 /// What the running session negotiated, for comparison against a re-offer.
177 current: Negotiated,
178 /// The peer's ICE credentials as this side last saw them (RFC 8839 §4.4.1.1.1).
179 ///
180 /// The only thing a restart can be recognised against: a later offer restarts ICE when **both**
181 /// its `ice-ufrag` and its `ice-pwd` differ from these. `None` is a call that never ran ICE, or
182 /// one whose peer has not described it — and neither can restart something that never started.
183 peer_ice: Option<sipx_sdp::ice::Credentials>,
184 /// Whether the call is on hold, and which way.
185 hold: Direction,
186 /// Whether the media is encrypted.
187 encrypted: bool,
188 /// The initial keying policy, retained so a later offer cannot silently downgrade it.
189 keying: Keying,
190 /// A transfer the far end has asked for and we have not yet answered.
191 referral: Option<Referral>,
192 /// A transfer we asked for, and what has become of it.
193 transfer: Option<Transfer>,
194 /// The RFC 4028 session timer, if one was negotiated.
195 session: Option<SessionState>,
196 /// Whose turn it is to offer and to answer (RFC 3311 §5, RFC 3264).
197 ///
198 /// Idle here: a `Call` exists only once the INVITE's offer/answer has completed, so nothing
199 /// is outstanding at construction on either side.
200 negotiation: update::Negotiation,
201 /// Whether the peer's `Allow` listed UPDATE (RFC 3311 §4).
202 ///
203 /// Read from the message that introduced the peer — the INVITE for a UAS, the 2xx for a
204 /// UAC — and refreshed from any later one. It is the only permission there is: RFC 4028
205 /// §7.4 turns it into the choice between UPDATE and a re-INVITE for a session refresh, and
206 /// a refresh sent by a method the far end does not implement draws a 405 and tears down a
207 /// call that was working.
208 peer_allows_update: bool,
209 /// Where this call's events go (story `C-3`). Every state change below is emitted through
210 /// this at the point it happens, not reconstructed afterwards from the fields above.
211 events: EventSink,
212 /// The one receiver [`Self::events`] hands out, until it does.
213 events_rx: Option<CallEvents>,
214 /// The diversion history received on the request or final response that established this
215 /// call. Retained because the application cannot recover it after the transaction stream is
216 /// consumed by call setup.
217 history: Option<HistoryInfo>,
218 /// Digest credentials retained for authenticated requests originated inside this dialog.
219 dialog_credentials: Option<Credentials>,
220 /// Case-sensitive private method tokens the application explicitly admitted.
221 admitted_dialog_methods: Vec<Bytes>,
222}
223
224/// A call-owned task that cannot detach if the call is abandoned without explicit shutdown.
225#[derive(Debug)]
226struct OwnedTask(tokio::task::JoinHandle<()>);
227
228impl OwnedTask {
229 fn new(owner: tokio::task::JoinHandle<()>) -> Self {
230 Self(owner)
231 }
232
233 async fn joined(&mut self) {
234 // discard: the caller has already selected the task's terminal protocol outcome; this
235 // await is only the ownership barrier and a cancellation JoinError cannot change it.
236 let _ = (&mut self.0).await;
237 }
238}
239
240impl Drop for OwnedTask {
241 fn drop(&mut self) {
242 self.0.abort();
243 }
244}
245
246async fn cancel_and_join(stop: &CancellationToken, owner: &mut OwnedTask) {
247 stop.cancel();
248 owner.joined().await;
249}
250
251async fn until_cancelled<F: Future>(stop: &CancellationToken, operation: F) -> Option<F::Output> {
252 tokio::select! {
253 biased;
254 () = stop.cancelled() => None,
255 output = operation => Some(output),
256 }
257}
258
259trait Retirable {
260 async fn finish(&self);
261}
262
263impl Retirable for Arc<MediaSession> {
264 async fn finish(&self) {
265 self.shutdown().await;
266 }
267}
268
269async fn drain_retired<T: Retirable>(retired: &mut Vec<T>) {
270 while let Some(previous) = retired.last() {
271 previous.finish().await;
272 retired.pop();
273 }
274}
275
276/// The pure half of accepting a peer's in-dialog offer.
277///
278/// Kept as one value so [`Call::can_accept_offer`] and [`Call::renegotiate`] cannot drift: the
279/// coupling asks the first question before it changes its other leg, and the call later applies
280/// exactly the description that passed that check.
281struct PreparedRenegotiation {
282 offer: SessionDescription,
283 negotiated: Negotiated,
284 answer: SessionDescription,
285 direction: Direction,
286}
287
288/// Refuse a socket-ownership change until renegotiation can replace the media session atomically.
289fn preserve_rtcp_mode(current: sipx_sdp::RtcpMode, proposed: sipx_sdp::RtcpMode) -> Result<()> {
290 if current == proposed {
291 Ok(())
292 } else {
293 Err(Error::RtcpModeChange { current, proposed })
294 }
295}
296
297/// The mode one answer selected for its corresponding offered audio section.
298fn exchanged_rtcp_mode(
299 offer: &SessionDescription,
300 answer: &SessionDescription,
301) -> sipx_sdp::RtcpMode {
302 offer
303 .media
304 .iter()
305 .zip(&answer.media)
306 .find(|(offered, _)| offered.media == "audio")
307 .map_or(sipx_sdp::RtcpMode::Separate, |(offered, answered)| {
308 sipx_sdp::RtcpMode::from_exchange(offered, answered)
309 })
310}
311
312/// The RTCP shape this implementation will select when answering `offer`.
313fn answering_rtcp_mode(offer: &SessionDescription) -> sipx_sdp::RtcpMode {
314 offer
315 .media
316 .iter()
317 .find(|media| media.media == "audio" && !media.is_rejected())
318 .filter(|media| media.rtcp_mux())
319 .map_or(sipx_sdp::RtcpMode::Separate, |_| sipx_sdp::RtcpMode::Mux)
320}
321
322/// A negotiated session timer and the deadline it is currently counting down to.
323#[derive(Debug, Clone, Copy)]
324struct SessionState {
325 terms: session::Session,
326 /// When [`Call::on_session_deadline`] should be called.
327 ///
328 /// Held as an absolute instant rather than recomputed from "now" on each poll, so that a
329 /// call driven by a loop that also does other work cannot have its timer pushed back
330 /// indefinitely by its own busyness.
331 act_at: Instant,
332}
333
334impl SessionState {
335 fn armed(terms: session::Session) -> Self {
336 Self {
337 terms,
338 act_at: Instant::now() + terms.act_after(),
339 }
340 }
341}
342
343fn retired_media_snapshot_refusal(count: usize) -> Option<DialogNotQuiescent> {
344 (count != 0).then_some(DialogNotQuiescent::MediaCleanup)
345}
346
347impl Call {
348 /// Capture the bounded protocol state needed to continue this confirmed dialog.
349 ///
350 /// `now` is explicit and only the session timer's remaining duration is retained. Sockets,
351 /// endpoint handles, media sessions, tasks, transactions, credentials, keys, entropy and
352 /// process-local clock instants never enter [`DialogSnapshot`]. Capture refuses any call with
353 /// active work whose safe continuation would require one of those runtime values.
354 pub fn dialog_snapshot(
355 &self,
356 now: Instant,
357 ) -> std::result::Result<DialogSnapshot, DialogPersistenceError> {
358 if self.ended {
359 return Err(DialogPersistenceError::NotQuiescent(
360 DialogNotQuiescent::Ended,
361 ));
362 }
363 if self.ack_retransmission.is_some() {
364 return Err(DialogPersistenceError::NotQuiescent(
365 DialogNotQuiescent::AwaitingAck,
366 ));
367 }
368 if !self.negotiation.is_idle() {
369 return Err(DialogPersistenceError::NotQuiescent(
370 DialogNotQuiescent::OfferAnswer,
371 ));
372 }
373 if let Some(reason) = retired_media_snapshot_refusal(self.retired_media.len()) {
374 return Err(DialogPersistenceError::NotQuiescent(reason));
375 }
376 if self.referral.is_some() || self.transfer.is_some() {
377 return Err(DialogPersistenceError::NotQuiescent(
378 DialogNotQuiescent::Transfer,
379 ));
380 }
381 if self.media.runs_ice() {
382 return Err(DialogPersistenceError::NotQuiescent(
383 DialogNotQuiescent::Ice,
384 ));
385 }
386 let session = self
387 .session
388 .map(|state| {
389 let remaining = state
390 .act_at
391 .checked_duration_since(now)
392 .filter(|remaining| !remaining.is_zero())
393 .ok_or({
394 DialogPersistenceError::SessionActionDue(if state.terms.we_refresh {
395 crate::DialogSessionAction::Refresh
396 } else {
397 crate::DialogSessionAction::Expire
398 })
399 })?;
400 Ok(SessionSnapshot {
401 interval: state.terms.interval,
402 we_refresh: state.terms.we_refresh,
403 remaining,
404 })
405 })
406 .transpose()?;
407
408 DialogSnapshot::from_parts(SnapshotParts {
409 role: self.dialog.role,
410 id: self.dialog.id.clone(),
411 local_party: strip_header_params(&self.dialog.local_uri),
412 remote_party: strip_header_params(&self.dialog.remote_uri),
413 remote_target: self.dialog.remote_target.clone(),
414 route_set: self.dialog.route_set.clone(),
415 local_cseq: self.dialog.local_cseq,
416 remote_cseq: self.dialog.remote_cseq,
417 protected_signalling: self.target.transport.is_secure(),
418 media_keying: self.negotiated_keying(),
419 media_profile: self.profile,
420 codecs: self.codecs,
421 codec: self.current.codec,
422 clock_rate: self.current.clock_rate,
423 payload_type: self.current.wire_payload_type(),
424 receive_payload_type: self.current.receive_wire_payload_type(),
425 dtmf_payload_type: self.current.dtmf,
426 rtcp_mode: self.current.rtcp_mode,
427 hold: self.hold,
428 peer_allows_update: self.peer_allows_update,
429 session,
430 })
431 }
432
433 /// Attach validated durable dialog state to fresh endpoint and media drivers.
434 ///
435 /// Restoration is synchronous and performs no I/O. Every snapshot and context invariant is
436 /// checked before handles are cloned or events are published, so a refusal creates no task or
437 /// transaction and leaves the borrowed context running exactly as supplied. Snapshot storage,
438 /// authorization, encryption at rest, distribution and single-owner election belong to the
439 /// host; a successful decode proves format validity, not permission to resume a call.
440 pub fn restore_dialog(
441 snapshot: &DialogSnapshot,
442 context: &DialogRestoreContext,
443 ) -> std::result::Result<Self, DialogPersistenceError> {
444 let session = snapshot.validate_restore(context)?;
445 // The only mutation in restoration, after every fallible snapshot/context check. It is
446 // an atomic one-owner claim rather than runtime work: no task, transaction, socket or
447 // media worker starts here, and a concurrent duplicate restore gets a typed refusal.
448 context.claim()?;
449 let (events, events_rx) = EventSink::new();
450 Ok(Self {
451 dialog: snapshot.dialog(),
452 initial_status: OK,
453 media: Arc::clone(&context.media),
454 retired_media: Vec::new(),
455 endpoint: context.endpoint.clone(),
456 target: context.target.clone(),
457 ack_stop: None,
458 ack_retransmission: None,
459 delayed_offer: None,
460 ended: false,
461 media_address: context.media_address.advertised(),
462 media_bind_address: context.media_address.bind(),
463 codecs: snapshot.codecs_value(),
464 profile: snapshot.media_profile_value(),
465 current: snapshot.negotiated(context.remote_media),
466 peer_ice: None,
467 // Validation proved the freshly built media driver carries the durable direction.
468 // Install the injected runtime fact so restoration never trusts snapshot state alone.
469 hold: context.direction,
470 encrypted: context.media.is_encrypted(),
471 keying: context.policy.keying,
472 referral: None,
473 transfer: None,
474 session: session.map(|(interval, we_refresh, act_at)| SessionState {
475 terms: session::Session {
476 interval,
477 we_refresh,
478 },
479 act_at,
480 }),
481 negotiation: update::Negotiation::idle(),
482 peer_allows_update: snapshot.peer_allows_update_value(),
483 // Application-owned extension admission and credentials are runtime policy, not
484 // durable dialog facts. The host must install them again after restoration.
485 dialog_credentials: None,
486 admitted_dialog_methods: Vec::new(),
487 events,
488 events_rx: Some(events_rx),
489 history: None,
490 })
491 }
492
493 /// Signal and join the successful-final-response retransmitter, if one is active.
494 ///
495 /// The handle stays in `self` while it is awaited. Cancelling the caller therefore leaves the
496 /// ownership intact for the next ACK or terminal path instead of detaching the retransmitter.
497 async fn stop_ack_retransmission(&mut self) {
498 if let Some(stop) = self.ack_stop.take() {
499 if let Some(owner) = self.ack_retransmission.as_mut() {
500 cancel_and_join(&stop, owner).await;
501 } else {
502 stop.cancel();
503 }
504 } else if let Some(owner) = self.ack_retransmission.as_mut() {
505 owner.joined().await;
506 }
507 self.ack_retransmission = None;
508 }
509
510 async fn reap_retired_media(&mut self) {
511 drain_retired(&mut self.retired_media).await;
512 }
513
514 /// The successful final response that established this call.
515 #[must_use]
516 pub fn initial_status(&self) -> u16 {
517 self.initial_status
518 }
519
520 /// The audio.
521 #[must_use]
522 pub fn media(&self) -> &MediaSession {
523 &self.media
524 }
525
526 /// A shared media handle for an owning actor that must move one operation into a bounded task.
527 ///
528 /// Most applications should use [`Self::media`]. This form exists for interactive owners that
529 /// must keep accepting control commands while a recording receives frames; sharing the session
530 /// does not share or clone the `Call`'s signalling state.
531 pub fn media_handle(&self) -> Arc<MediaSession> {
532 Arc::clone(&self.media)
533 }
534
535 /// Install or clear the application callback for peer RTCP quality reports.
536 ///
537 /// This is call-owned policy: it remains installed across an ordinary re-INVITE, a media
538 /// session replacement, and an ICE restart. The callback itself must return promptly; see
539 /// [`sipx_media::RtcpQualityHook`].
540 pub fn set_rtcp_quality_hook(&self, hook: Option<sipx_media::RtcpQualityHook>) {
541 self.media.set_rtcp_quality_hook(hook);
542 }
543
544 /// The peer RTCP quality callback currently attached to this call.
545 #[must_use]
546 pub fn rtcp_quality_hook(&self) -> Option<sipx_media::RtcpQualityHook> {
547 self.media.rtcp_quality_hook()
548 }
549
550 /// A response handle for a coupling that must answer glare while an outgoing request borrows
551 /// this call's dialog state.
552 pub(crate) fn responder(&self) -> Handle {
553 self.endpoint.clone()
554 }
555
556 /// Send a DTMF digit.
557 pub async fn send_digit(&self, digit: sipx_rtp::Digit, duration: Duration) -> bool {
558 self.media.send_digit(digit, duration).await
559 }
560
561 /// Send a string of digits, each held for `duration`.
562 ///
563 /// Characters that are not DTMF digits are skipped rather than rejected: a caller passing
564 /// a formatted number should not have to strip the spaces and dashes itself.
565 pub async fn send_digits(&self, digits: &str, duration: Duration) -> bool {
566 for c in digits.chars() {
567 let Some(digit) = sipx_rtp::Digit::from_char(c) else {
568 continue;
569 };
570 if !self.media.send_digit(digit, duration).await {
571 return false;
572 }
573 }
574 true
575 }
576
577 /// Take the next digit the far end pressed.
578 ///
579 /// The digit only; a caller that wants how long it was held can read [`Self::media`]'s own
580 /// [`MediaSession::recv_digit`], which this delegates to.
581 pub async fn recv_digit(&self) -> Option<sipx_rtp::Digit> {
582 self.media
583 .recv_digit()
584 .await
585 .map(|(digit, _duration)| digit)
586 }
587
588 /// Play a clip and wait for it, reporting on the event stream when it stops.
589 ///
590 /// Paced by the send loop, so this resolves when the audio has actually gone out rather than
591 /// when it was queued. Emits [`CallEvent::PlaybackFinished`] either way, with `completed`
592 /// saying which happened: the clip ran to the end, or something cut it short. A host driving
593 /// the call from its events needs that distinction — "the announcement finished" and "the
594 /// caller hung up during the announcement" lead to different next steps.
595 ///
596 /// The packet size is the session's own, so a clip plays correctly under a codec whose clock
597 /// is not 8 kHz without the caller knowing the rate.
598 ///
599 /// This is [`Self::start_playback`] awaited, with [`Interrupt::Never`] — the clip runs to its
600 /// end whatever the far end presses. A caller that wants to stop it, or wants a keypress to,
601 /// needs the handle. Cancel-on-drop, like [`MediaSession::play`]: abandoning this future — a
602 /// `timeout` that fires, a lost `select!` — stops the clip rather than leaving it playing.
603 pub async fn play(&self, samples: &[i16]) -> bool {
604 let playback = self
605 .media
606 .start_playback(samples.to_vec(), Interrupt::Never);
607 let end = playback.play_out().await;
608 // Emitted from here rather than from a watcher task, so a caller that awaits this call
609 // can read the event immediately afterwards instead of racing a spawn.
610 self.events.emit(CallEvent::PlaybackFinished {
611 playback: playback.id(),
612 completed: end.completed(),
613 });
614 end.completed()
615 }
616
617 /// Convert and play explicit linear PCM, reporting completion on the call event stream.
618 ///
619 /// # Errors
620 ///
621 /// Returns [`sipx_audio::PcmError`] before queuing audio when the format cannot be converted.
622 pub async fn play_pcm(
623 &self,
624 pcm: &sipx_audio::Pcm,
625 ) -> std::result::Result<bool, sipx_audio::PcmError> {
626 let playback = self.media.start_pcm_playback(pcm, Interrupt::Never)?;
627 let end = playback.play_out().await;
628 self.events.emit(CallEvent::PlaybackFinished {
629 playback: playback.id(),
630 completed: end.completed(),
631 });
632 Ok(end.completed())
633 }
634
635 /// Start a clip and hand back a handle to it, without waiting (`M-17`).
636 ///
637 /// The primitive under "play a prompt and collect digits": the caller goes on to read digits
638 /// while the audio plays, and can reach back through the handle to stop the prompt — or ask
639 /// for [`Interrupt::OnDigit`] and have the far end's first keypress stop it. That keypress is
640 /// **not** consumed by interrupting; it arrives at [`Self::recv_digit`] like any other, which
641 /// is what makes the application contract's `gather{prompt, interruptible}`
642 /// (`docs/specs/app-contract.md` §6.2) buildable rather than a menu that eats the first digit
643 /// of every PIN.
644 ///
645 /// Clips **queue**: a second playback started while one is running begins when that one ends.
646 /// See [`MediaSession::start_playback`] for why, and for what a clip queued while another is
647 /// stopping does. The bound on stopping is [`Playback::STOP_BOUND_PACKETS`] packets.
648 ///
649 /// Reports [`CallEvent::PlaybackFinished`] for this playback however it ends, without the
650 /// caller having to await the handle — a watcher task does it, so a fire-and-forget
651 /// announcement is still observable to a host driving the call from its events.
652 pub fn start_playback(&self, samples: Vec<i16>, interrupt: Interrupt) -> Playback {
653 let playback = self.media.start_playback(samples, interrupt);
654 let watcher = playback.clone();
655 let emitter = self.events.emitter();
656 tokio::spawn(async move {
657 let end = watcher.finished().await;
658 emitter.emit(CallEvent::PlaybackFinished {
659 playback: watcher.id(),
660 completed: end.completed(),
661 });
662 });
663 playback
664 }
665
666 /// Convert explicit linear PCM and start a controllable playback.
667 ///
668 /// # Errors
669 ///
670 /// Returns [`sipx_audio::PcmError`] before creating a playback when conversion is refused.
671 pub fn start_pcm_playback(
672 &self,
673 pcm: &sipx_audio::Pcm,
674 interrupt: Interrupt,
675 ) -> std::result::Result<Playback, sipx_audio::PcmError> {
676 let playback = self.media.start_pcm_playback(pcm, interrupt)?;
677 let watcher = playback.clone();
678 let emitter = self.events.emitter();
679 tokio::spawn(async move {
680 let end = watcher.finished().await;
681 emitter.emit(CallEvent::PlaybackFinished {
682 playback: watcher.id(),
683 completed: end.completed(),
684 });
685 });
686 Ok(playback)
687 }
688
689 /// Record until the far end goes quiet for `idle`, and report the result on the event stream.
690 ///
691 /// Emits [`CallEvent::RecordingFinished`] carrying how much audio was captured — measured
692 /// from the samples themselves and the session's clock rate, not by timing the call, so the
693 /// number describes the recording rather than how long this side waited for it. The trailing
694 /// `idle` silence is not part of it: it is how the end was detected, not something the far
695 /// end said.
696 pub async fn record_until_idle(&self, idle: Duration) -> Vec<i16> {
697 let samples = self.media.record_until_idle(idle).await;
698 self.finished_recording(samples)
699 }
700
701 /// Record until `samples` samples have arrived or `within` elapses, and report the result on
702 /// the event stream.
703 ///
704 /// The counted wait, for a caller that knows how much audio the far end was given;
705 /// [`MediaSession::record_at_least`] has the reasoning, and why `within` is a bound on
706 /// failure rather than a window to measure in. Emits the same
707 /// [`CallEvent::RecordingFinished`] as [`Self::record_until_idle`], measured the same way —
708 /// from the samples, not from how long this side waited for them.
709 pub async fn record_at_least(&self, samples: usize, within: Duration) -> Vec<i16> {
710 let samples = self.media.record_at_least(samples, within).await;
711 self.finished_recording(samples)
712 }
713
714 /// Announce a finished recording and hand it back.
715 ///
716 /// Shared by both recording verbs so the duration on the event cannot come to mean one thing
717 /// for one of them and something else for the other.
718 fn finished_recording(&self, samples: Vec<i16>) -> Vec<i16> {
719 let rate = u64::from(self.media.clock_rate()).max(1);
720 let duration = Duration::from_micros(samples.len() as u64 * 1_000_000 / rate);
721 self.events.emit(CallEvent::RecordingFinished { duration });
722 samples
723 }
724
725 /// Stop contributing audio to the far end, without telling it anything (story `M-18`).
726 ///
727 /// # Mute is not hold
728 ///
729 /// This is the distinction the whole verb exists for, and getting it wrong is how a call ends
730 /// up renegotiated when all that was wanted was a quiet microphone:
731 ///
732 /// | | `mute` | [`reinvite(Direction::SendOnly)`](Self::reinvite) |
733 /// |---|---|---|
734 /// | Signalling | none — no re-INVITE, nothing on the wire | a re-INVITE the far end must answer |
735 /// | The SDP direction | unchanged; the session is the one that was negotiated | changed, and that *is* the mechanism |
736 /// | What the far end knows | nothing; [`is_on_hold`](Self::is_on_hold) there is unaffected | that this call is on hold, and it may play its own hold music |
737 /// | The RTP stream | keeps flowing, carrying silence | governed by the new direction |
738 /// | Can fail | no | yes — the far end can refuse the renegotiation |
739 ///
740 /// Hold is a state two parties agree on. Mute is a decision one party makes about its own
741 /// microphone, and a far end that could tell the difference between a muted caller and a
742 /// silent one would be reading something it was never sent.
743 ///
744 /// # What it does and does not gate
745 ///
746 /// Outbound audio only, and it is a gate rather than a suppressor: [`Self::play`] still runs
747 /// and still resolves the same way, the packets still go out at the same pacing, and what the
748 /// far end decodes out of them is silence. Reception is untouched — [`Self::recv_digit`],
749 /// [`Self::record_until_idle`] and [`MediaSession::quality`] all keep working while muted —
750 /// and so is DTMF in the sending direction: [`Self::send_digits`] is an explicit act by this
751 /// endpoint, like a keypad tone on a handset, not something the microphone picked up.
752 ///
753 /// Emits [`CallEvent::Muted`] on the transition, and nothing when the call was already muted.
754 pub fn mute(&self) {
755 if !self.media.set_muted(true) {
756 self.events.emit(CallEvent::Muted);
757 }
758 }
759
760 /// Contribute audio to the far end again, undoing [`Self::mute`].
761 ///
762 /// Emits [`CallEvent::Unmuted`] on the transition, and nothing when the call was not muted.
763 /// Like [`Self::mute`] it sends nothing: there is no renegotiation to undo, because muting
764 /// never made one.
765 pub fn unmute(&self) {
766 if self.media.set_muted(false) {
767 self.events.emit(CallEvent::Unmuted);
768 }
769 }
770
771 /// Whether this side's outbound audio is muted.
772 ///
773 /// Local state, and a different question from [`Self::is_on_hold`], which reports what the
774 /// *far end* has signalled about the session.
775 #[must_use]
776 pub fn is_muted(&self) -> bool {
777 self.media.is_muted()
778 }
779
780 /// Whether the media is encrypted (RFC 3711).
781 ///
782 /// Worth asking, and worth being able to answer without a packet capture. A call whose
783 /// signalling is encrypted and whose audio is not looks identical from the outside to one
784 /// where both are — which is exactly the confusion that makes people believe `sips:` covers
785 /// the media. It does not.
786 #[must_use]
787 pub fn is_encrypted(&self) -> bool {
788 self.encrypted
789 }
790
791 /// The keying mechanism this established call actually negotiated.
792 ///
793 /// Unlike the initial [`Keying`] policy, the result contains no `Auto`: by confirmation the
794 /// compatibility choice has resolved to either plain RTP or SDES-SRTP.
795 #[must_use]
796 pub fn negotiated_keying(&self) -> NegotiatedKeying {
797 if !self.encrypted {
798 NegotiatedKeying::Plain
799 } else if self.keying == Keying::DtlsSrtp {
800 NegotiatedKeying::DtlsSrtp
801 } else {
802 NegotiatedKeying::Sdes
803 }
804 }
805
806 /// The named media profile this established call retained.
807 #[must_use]
808 pub const fn media_profile(&self) -> MediaProfile {
809 self.profile
810 }
811
812 /// Nominated-pair, generation, state, and bounded ingress facts for browser audio.
813 #[must_use]
814 pub fn browser_component(&self) -> Option<sipx_media::browser::BrowserComponentSnapshot> {
815 self.media.browser_component()
816 }
817
818 /// RTP payload type selected for sending the established audio codec.
819 #[must_use]
820 pub fn negotiated_payload_type(&self) -> u8 {
821 self.current.wire_payload_type()
822 }
823
824 /// RTP payload type accepted when receiving the established audio codec.
825 ///
826 /// Usually equal to [`Self::negotiated_payload_type`], but each SDP description may assign a
827 /// different dynamic number to the same format (RFC 3264 §6.1).
828 #[must_use]
829 pub fn negotiated_receive_payload_type(&self) -> u8 {
830 self.current.receive_wire_payload_type()
831 }
832
833 /// RTP clock rate selected for the established audio codec.
834 #[must_use]
835 pub fn negotiated_clock_rate(&self) -> u32 {
836 self.media.clock_rate()
837 }
838
839 /// Whether the call has ended, from either side.
840 #[must_use]
841 pub fn is_ended(&self) -> bool {
842 self.ended
843 }
844
845 /// This call's event stream (story `C-3`).
846 ///
847 /// `Some` the first time this is called, `None` every time after — there is exactly one
848 /// consumer, per the vision's "own it, don't share it" (principle 3), so the receiver is
849 /// handed out rather than cloned.
850 pub fn events(&mut self) -> Option<CallEvents> {
851 self.events_rx.take()
852 }
853
854 /// Retain credentials for authenticated requests originated inside this dialog.
855 ///
856 /// Outbound calls inherit [`DialOptions::credentials`]. This setter supplies the equivalent
857 /// policy for answered calls or rotates the credentials on an existing call.
858 pub fn set_dialog_credentials(&mut self, credentials: Credentials) {
859 self.dialog_credentials = Some(credentials);
860 }
861
862 /// Admit one private, case-sensitive method token to the application-owned dialog path.
863 ///
864 /// Known SIP methods are refused: their ownership is decided by the stack, not converted into
865 /// a private extension by application policy.
866 pub fn admit_dialog_method(&mut self, method: &Method) -> Result<()> {
867 let token = extension::validate_method_for_admission(method)?;
868 if !self.admitted_dialog_methods.contains(&token) {
869 self.admitted_dialog_methods.push(token);
870 }
871 Ok(())
872 }
873
874 /// Send an application-owned request inside this dialog.
875 ///
876 /// The dialog supplies the Request-URI, route set, identifiers and next `CSeq`. `headers` may
877 /// contain application fields such as `Content-Type`, but never routing, dialog, framing, or
878 /// authorization fields. A supported 401/407 challenge is retried once when dialog credentials
879 /// are available.
880 pub async fn send_dialog_request(
881 &mut self,
882 method: Method,
883 headers: &[sipx_sip::Header],
884 body: Bytes,
885 ) -> Result<Response> {
886 if self.ended {
887 return Err(Error::DialogEnded);
888 }
889 if !extension::application_owned(&method, &self.admitted_dialog_methods) {
890 return Err(Error::StackOwnedDialogMethod(method));
891 }
892 extension::validate_request_parts(headers, &body)?;
893
894 let credentials = self.dialog_credentials.clone();
895 let first = self
896 .send_application_attempt(&method, headers, body.clone(), None)
897 .await?;
898 if first.status.is_success() {
899 return Ok(first);
900 }
901
902 let failure = rejection(&first);
903 let Error::AuthenticationChallenge { challenge, .. } = failure else {
904 return Err(failure);
905 };
906 let Some(credentials) = credentials else {
907 return Err(Error::Rejected {
908 status: first.status.code(),
909 reason: String::from_utf8_lossy(&first.reason).into_owned(),
910 });
911 };
912 let cnonce = token();
913 let authorization = Authorization {
914 challenge: &challenge,
915 credentials: &credentials,
916 nonce_count: 1,
917 cnonce: &cnonce,
918 };
919 let response = self
920 .send_application_attempt(&method, headers, body, Some(&authorization))
921 .await?;
922 if !response.status.is_success() {
923 return Err(rejection(&response));
924 }
925 Ok(response)
926 }
927
928 async fn send_application_attempt(
929 &mut self,
930 method: &Method,
931 headers: &[sipx_sip::Header],
932 body: Bytes,
933 authorization: Option<&Authorization<'_>>,
934 ) -> Result<Response> {
935 let cseq = self.dialog.next_cseq();
936 let mut request = application_request(&self.dialog, method, cseq, headers, body)?;
937 if let Some(authorization) = authorization {
938 authorize_invite(&mut request, authorization)?;
939 }
940 let mut responses = self.endpoint.send(request, self.target.clone()).await?;
941 responses.final_response().await.ok_or(Error::NoResponse)
942 }
943
944 /// Feed an in-dialog request to the call.
945 ///
946 /// Returns whether it belonged here. Without this an incoming BYE reaches nothing and the
947 /// local media session goes on sending RTP into a call the far end has torn down — worse
948 /// than a call that never connects, because it does not stop.
949 pub async fn handle(&mut self, incoming: &Incoming) -> Result<bool> {
950 if !self.dialog.matches(&incoming.request) {
951 return Ok(false);
952 }
953
954 match incoming.request.method {
955 Method::Ack => {
956 // The 2xx got through; stop retransmitting it.
957 self.stop_ack_retransmission().await;
958 self.accept_delayed_offer_answer(incoming.request.body())
959 .await?;
960 Ok(true)
961 }
962 // An INVITE inside an existing dialog is a re-INVITE: a renegotiation of the call
963 // already running, not a new one.
964 Method::Invite => {
965 self.on_reinvite(incoming).await?;
966 Ok(true)
967 }
968 // RFC 3311 §5.1: an in-dialog renegotiation that does not disturb any INVITE
969 // transaction. In a confirmed dialog that is mostly a session refresh (RFC 4028
970 // §7.4), but a peer may equally use it to move the media, and either way it has to
971 // be answered promptly — §5.2 gives the UAS no window in which to ask anybody.
972 Method::Update => {
973 self.on_update(incoming).await?;
974 Ok(true)
975 }
976 // A REFER is not answered here, and that is deliberate. Every other in-dialog
977 // request has one correct response; a REFER asks *may I place a call on your
978 // behalf*, and only the application knows whether it may. `accept_referral` and
979 // `refuse_referral` are the two answers, and until one is given the transferor is
980 // waiting — which is honest, because it is.
981 Method::Refer => {
982 self.on_refer(incoming).await?;
983 Ok(true)
984 }
985 Method::Notify => {
986 self.on_notify(incoming).await?;
987 Ok(true)
988 }
989 // RFC 3261 §11.2: OPTIONS may be sent inside a dialog, where it is the cheapest
990 // keep-alive there is. Answered here rather than left to the application because
991 // `sipx_sip::update::ALLOW` — the one list this stack advertises, and the one a 405
992 // from [`serve`] carries — names OPTIONS. An advertisement that is not true is worse
993 // than a narrower one: a peer that reads the list and is then refused has been told
994 // two different things by the same endpoint.
995 Method::Options => {
996 self.on_options(incoming).await?;
997 Ok(true)
998 }
999 Method::Bye => {
1000 // §12.2.2 applies to every in-dialog request, not only the ones that
1001 // renegotiate: a BYE from behind the current sequence number is a stale
1002 // duplicate, and honouring it ends a call that is still running.
1003 if self.out_of_order(&incoming.request) {
1004 self.refuse(incoming, 500, "Server Internal Error").await?;
1005 return Ok(true);
1006 }
1007 self.record_remote_cseq(&incoming.request);
1008
1009 self.media.stop();
1010 self.ended = true;
1011 // Nothing left to keep alive, and leaving an elapsed deadline armed would have
1012 // `session_deadline` keep returning a time in the past, spinning any loop that
1013 // selects on it.
1014 self.session = None;
1015 self.stop_ack_retransmission().await;
1016 // Emitted here, at the point `ended` actually flips, rather than after the 200
1017 // OK below — the call is over the moment the far end's BYE is accepted, whether
1018 // or not building or sending the response then succeeds.
1019 self.events.end(EndCause::RemoteBye);
1020 let responded: Result<()> = async {
1021 let response =
1022 ResponseBuilder::to_request(&incoming.request, ok_status(), "OK")?.build();
1023 self.endpoint.respond(&incoming.key, response).await?;
1024 Ok(())
1025 }
1026 .await;
1027 self.media.shutdown().await;
1028 self.reap_retired_media().await;
1029 responded?;
1030 Ok(true)
1031 }
1032 ref method if extension::application_owned(method, &self.admitted_dialog_methods) => {
1033 if self.out_of_order(&incoming.request) {
1034 self.refuse(incoming, 500, "Server Internal Error").await?;
1035 return Ok(true);
1036 }
1037 if incoming.request.body().len() > extension::MAX_APPLICATION_BODY {
1038 self.refuse(incoming, 413, "Content Too Large").await?;
1039 return Err(Error::ApplicationBodyTooLarge {
1040 actual: incoming.request.body().len(),
1041 limit: extension::MAX_APPLICATION_BODY,
1042 });
1043 }
1044 if !incoming.request.body().is_empty()
1045 && incoming
1046 .request
1047 .headers
1048 .get(&HeaderName::ContentType)
1049 .is_none()
1050 {
1051 self.refuse(incoming, 415, "Unsupported Media Type").await?;
1052 return Err(Error::ApplicationContentTypeRequired);
1053 }
1054 self.record_remote_cseq(&incoming.request);
1055 self.events
1056 .emit(CallEvent::ApplicationRequest(ApplicationRequest::new(
1057 self.endpoint.clone(),
1058 incoming.key.clone(),
1059 &incoming.request,
1060 )?));
1061 Ok(true)
1062 }
1063 _ => Ok(false),
1064 }
1065 }
1066
1067 /// The negotiated session interval, and whether this side is the one refreshing it.
1068 ///
1069 /// `None` means no timer was agreed, so nothing will ever notice a far end that stops
1070 /// answering — worth being able to check, because that is a property of the *peer*, not of
1071 /// what this side asked for.
1072 #[must_use]
1073 pub fn session_interval(&self) -> Option<(Duration, bool)> {
1074 self.session
1075 .map(|state| (state.terms.interval, state.terms.we_refresh))
1076 }
1077
1078 /// The diversion history received while this call was established.
1079 #[must_use]
1080 pub fn history(&self) -> Option<&HistoryInfo> {
1081 self.history.as_ref()
1082 }
1083
1084 /// When [`Self::on_session_deadline`] next needs to be called, if a timer was negotiated.
1085 ///
1086 /// Returned as an instant rather than as a future on purpose. A future would borrow the
1087 /// call for as long as it was being awaited, which is exactly the borrow
1088 /// [`Self::handle`] needs in the other arm of the `select!` this is written for.
1089 #[must_use]
1090 pub fn session_deadline(&self) -> Option<Instant> {
1091 self.session.map(|state| state.act_at)
1092 }
1093
1094 /// Do whatever the session timer's deadline asked for (RFC 4028 §10).
1095 ///
1096 /// For the refresher that is an UPDATE or a re-INVITE — whichever the peer's `Allow` says
1097 /// it can take (§7.4); for the other side it is a BYE,
1098 /// because nothing arrived and the far end is presumed gone. Calling this early is harmless
1099 /// — it re-reads the deadline and does nothing if it has not passed.
1100 pub async fn on_session_deadline(&mut self) -> Result<()> {
1101 let Some(state) = self.session else {
1102 return Ok(());
1103 };
1104 if Instant::now() < state.act_at {
1105 return Ok(());
1106 }
1107 if !state.terms.we_refresh {
1108 // §10: the side that is not refreshing "SHOULD send a BYE to terminate the
1109 // session". The media stops with it — a half-torn-down call that keeps streaming
1110 // is the failure this whole mechanism exists to end, not a gentler version of it.
1111 self.end(EndCause::Timeout).await?;
1112 return Err(Error::SessionExpired);
1113 }
1114 match self.refresh_session().await {
1115 Ok(()) => Ok(()),
1116 // §10: a refresh that times out or draws a 408 or 481 means the dialog is gone at
1117 // the far end, and RFC 3261 §12.2.1.2 says to BYE. Any other failure is about the
1118 // refresh, not the call: a 491 glare or a 500 leaves the session running until the
1119 // deadline we do not move, so the next attempt is the retry.
1120 Err(Error::NoResponse) => {
1121 self.end(EndCause::Timeout).await?;
1122 Err(Error::SessionExpired)
1123 }
1124 Err(Error::Rejected { status, reason }) => {
1125 const REQUEST_TIMEOUT: u16 = 408;
1126 const NO_SUCH_DIALOG: u16 = 481;
1127 if status == REQUEST_TIMEOUT || status == NO_SUCH_DIALOG {
1128 self.end(EndCause::Timeout).await?;
1129 return Err(Error::SessionExpired);
1130 }
1131 // Push the retry out so a peer answering 500 to every refresh is not asked
1132 // again immediately for the rest of the session interval.
1133 self.rearm();
1134 Err(Error::Rejected { status, reason })
1135 }
1136 Err(other) => {
1137 self.rearm();
1138 Err(other)
1139 }
1140 }
1141 }
1142
1143 /// Restart the countdown, because the session was refreshed.
1144 fn rearm(&mut self) {
1145 if let Some(state) = self.session.as_mut() {
1146 state.act_at = Instant::now() + state.terms.act_after();
1147 }
1148 }
1149
1150 /// Whether the far end has put the call on hold.
1151 #[must_use]
1152 pub fn is_on_hold(&self) -> bool {
1153 !self.hold.receives()
1154 }
1155
1156 /// Renegotiate an established call from a re-INVITE.
1157 ///
1158 /// The rule that shapes this: **a renegotiation that fails must leave the call running.** A
1159 /// re-INVITE tries to change something about a call that already works, so answering 488
1160 /// and carrying on is right; tearing the call down because the new offer was unusable
1161 /// would lose a call that was fine a moment ago.
1162 async fn on_reinvite(&mut self, incoming: &Incoming) -> Result<()> {
1163 if self.out_of_order(&incoming.request) {
1164 return self.refuse(incoming, 500, "Server Internal Error").await;
1165 }
1166 self.record_remote_cseq(&incoming.request);
1167
1168 if incoming.request.body().is_empty() {
1169 return self.offer_in_reinvite_success(incoming).await;
1170 }
1171
1172 // §5.2 rule 2's other source, and the reason the spec names INVITE alongside UPDATE: a
1173 // re-INVITE's offer is one this side owes an answer to until it produces one.
1174 if crate::update::carries_offer(&incoming.request) {
1175 self.negotiation.received_offer();
1176 }
1177 let renegotiated = self.renegotiate(incoming.request.body()).await;
1178 // On every path out of here the debt is settled: a 488 kills the offer and a 2xx
1179 // answers it, and a failure to renegotiate at all leaves nothing to answer.
1180 self.negotiation.sent_answer();
1181 let Some(answer_sdp) = renegotiated? else {
1182 return self.refuse_unacceptable(incoming).await;
1183 };
1184
1185 // RFC 4028 §7.2: any re-INVITE inside the dialog refreshes the session, whether or not
1186 // it was sent for that reason. Only counting the ones that carry `Session-Expires`
1187 // would hang up on a peer that is demonstrably alive and talking to us.
1188 self.rearm();
1189
1190 // RFC 3261 §12.2.2: a re-INVITE is a target refresh request, so its `Contact` replaces
1191 // the dialog's remote target. Without this the BYE still goes to where the peer was
1192 // when the call started, and a peer that has moved can never be told it is over.
1193 self.dialog.refresh_target(&incoming.request.headers);
1194 self.target = in_dialog_target(
1195 &self.dialog,
1196 Target::new(incoming.source, incoming.transport),
1197 );
1198
1199 let response = ResponseBuilder::to_request(&incoming.request, ok_status(), "OK")?
1200 .header(
1201 HeaderName::Contact,
1202 Bytes::from(contact_for(&self.endpoint, self.target.transport)),
1203 )?
1204 .header(
1205 HeaderName::Allow,
1206 Bytes::from_static(update::ALLOW.as_bytes()),
1207 )?
1208 .header(
1209 HeaderName::ContentType,
1210 Bytes::from_static(b"application/sdp"),
1211 )?
1212 .body(Bytes::from(answer_sdp.to_string_sdp()))
1213 .build();
1214 self.endpoint
1215 .respond(&incoming.key, response.clone())
1216 .await?;
1217
1218 // RFC 3261 §13.3.1.4 applies to the 2xx of *any* INVITE, not only the first: it is
1219 // retransmitted until the ACK arrives. The server transaction deliberately absorbs
1220 // retransmitted INVITEs without answering them again (RFC 6026), so if the TU does not
1221 // resend, one lost 200 deadlocks the renegotiation until the peer's Timer B — a single
1222 // dropped packet breaking hold and resume for half a minute.
1223 self.stop_ack_retransmission().await;
1224 let ack_stop = CancellationToken::new();
1225 let ack_retransmission = tokio::spawn(retransmit_until_acked(
1226 self.endpoint.clone(),
1227 incoming.key.clone(),
1228 response,
1229 ack_stop.clone(),
1230 ));
1231 self.ack_stop = Some(ack_stop);
1232 self.ack_retransmission = Some(OwnedTask::new(ack_retransmission));
1233 Ok(())
1234 }
1235
1236 /// Put our offer in the 2xx to a bodyless re-INVITE (RFC 3261 §14.2).
1237 async fn offer_in_reinvite_success(&mut self, incoming: &Incoming) -> Result<()> {
1238 if self.profile == MediaProfile::BrowserAudio
1239 || self.encrypted
1240 || !self.negotiation.may_offer()
1241 {
1242 return self.refuse_unacceptable(incoming).await;
1243 }
1244
1245 let mut capabilities = self
1246 .codecs
1247 .capabilities(self.media_address, self.media.local_addr().port());
1248 if self.current.rtcp_mode == sipx_sdp::RtcpMode::Mux {
1249 capabilities = capabilities.with_rtcp_mux();
1250 }
1251 capabilities.direction = self.hold;
1252 capabilities.session_version = self
1253 .dialog
1254 .remote_cseq
1255 .map_or(u64::from(self.dialog.local_cseq), u64::from);
1256 let mut offer = offer_from(&capabilities);
1257 self.offer_ice(&mut offer, IceOffer::Continue).await;
1258
1259 // The request itself is a target refresh even though its offer was delayed.
1260 self.rearm();
1261 self.dialog.refresh_target(&incoming.request.headers);
1262 self.target = in_dialog_target(
1263 &self.dialog,
1264 Target::new(incoming.source, incoming.transport),
1265 );
1266
1267 let response = ResponseBuilder::to_request(&incoming.request, ok_status(), "OK")?
1268 .header(
1269 HeaderName::Contact,
1270 Bytes::from(contact_for(&self.endpoint, self.target.transport)),
1271 )?
1272 .header(
1273 HeaderName::Allow,
1274 Bytes::from_static(update::ALLOW.as_bytes()),
1275 )?
1276 .header(
1277 HeaderName::ContentType,
1278 Bytes::from_static(b"application/sdp"),
1279 )?
1280 .body(Bytes::from(offer.to_string_sdp()))
1281 .build();
1282
1283 self.negotiation.sent_offer();
1284 self.delayed_offer = Some(capabilities);
1285 if let Err(error) = self.endpoint.respond(&incoming.key, response.clone()).await {
1286 self.delayed_offer = None;
1287 self.negotiation.received_answer();
1288 return Err(error.into());
1289 }
1290
1291 self.stop_ack_retransmission().await;
1292 let ack_stop = CancellationToken::new();
1293 let ack_retransmission = tokio::spawn(retransmit_until_acked(
1294 self.endpoint.clone(),
1295 incoming.key.clone(),
1296 response,
1297 ack_stop.clone(),
1298 ));
1299 self.ack_stop = Some(ack_stop);
1300 self.ack_retransmission = Some(OwnedTask::new(ack_retransmission));
1301 Ok(())
1302 }
1303
1304 /// Settle the answer carried by the ACK of a delayed-offer re-INVITE.
1305 async fn accept_delayed_offer_answer(&mut self, body: &[u8]) -> Result<()> {
1306 let Some(offered) = self.delayed_offer.take() else {
1307 return Ok(());
1308 };
1309 // Clear the exchange on every path: ACK has no response with which to repair a malformed
1310 // answer, and retaining this flag would turn one peer error into permanent glare.
1311 self.negotiation.received_answer();
1312
1313 let answer = sipx_sdp::parse(&String::from_utf8_lossy(body))
1314 .map_err(|error| Error::Sdp(error.to_string()))?;
1315 let settled = settle_answer(&offered, &answer, self.codecs)?;
1316 preserve_rtcp_mode(self.current.rtcp_mode, settled.negotiated.rtcp_mode)?;
1317 self.accept_answer_ice(&answer).await;
1318 self.move_media_if_changed(settled.negotiated).await
1319 }
1320
1321 /// Apply an offer that arrived in-dialog, and produce the answer to send back.
1322 ///
1323 /// `None` means the description is unusable and the caller must refuse — 488 for a
1324 /// re-INVITE (`M-8`) and for an UPDATE (RFC 3311 §5.2), which is the same rule for the same
1325 /// reason: **a renegotiation that fails leaves the call running.** Both requests try to
1326 /// change something that already works, so refusing the change and keeping the session is
1327 /// right; tearing the call down because the new offer was unusable would lose a call that
1328 /// was fine a moment ago.
1329 ///
1330 /// Shared by the two paths because they ask exactly the same question of exactly the same
1331 /// session and differ only in what carries the answer back.
1332 fn prepare_renegotiation(&self, body: &[u8]) -> Option<PreparedRenegotiation> {
1333 if self.keying == Keying::DtlsSrtp {
1334 return None;
1335 }
1336 let offer = sipx_sdp::parse(&String::from_utf8_lossy(body)).ok()?;
1337 let mut negotiated = negotiated(&offer, self.codecs).ok()?;
1338
1339 let mut capabilities = self
1340 .codecs
1341 .capabilities(self.media_address, self.media.local_addr().port());
1342 if self.current.rtcp_mode == sipx_sdp::RtcpMode::Mux {
1343 capabilities = capabilities.with_rtcp_mux();
1344 }
1345 let answer = sipx_sdp::answer(&offer, &capabilities);
1346 if answer
1347 .media
1348 .iter()
1349 .all(sipx_sdp::MediaDescription::is_rejected)
1350 {
1351 return None;
1352 }
1353 let proposed_mode = exchanged_rtcp_mode(&offer, &answer);
1354 // A muxed session owns one receive socket. Answering an offer that removed mux while
1355 // silently retaining that owner would put the wire and the running state in disagreement.
1356 // Refuse the offer with 488; the typed error is shared with the outbound paths below.
1357 preserve_rtcp_mode(self.current.rtcp_mode, proposed_mode).ok()?;
1358 negotiated.rtcp_mode = proposed_mode;
1359 let direction = offer
1360 .media
1361 .iter()
1362 .find(|media| media.media == "audio" && !media.is_rejected())
1363 .map(sipx_sdp::MediaDescription::direction)?;
1364 Some(PreparedRenegotiation {
1365 offer,
1366 negotiated,
1367 answer,
1368 direction,
1369 })
1370 }
1371
1372 /// Whether this call can answer an in-dialog offer, without changing call or media state.
1373 ///
1374 /// The coupling uses this before opening an exchange on its other leg. Syntax alone is not
1375 /// enough: the source call may have no common codec or may use DTLS-SRTP, whose renegotiation
1376 /// this layer deliberately refuses.
1377 pub(crate) fn can_accept_offer(&self, body: &[u8]) -> Option<Direction> {
1378 self.prepare_renegotiation(body)
1379 .map(|prepared| prepared.direction)
1380 }
1381
1382 async fn renegotiate(&mut self, body: &[u8]) -> Result<Option<SessionDescription>> {
1383 let Some(mut prepared) = self.prepare_renegotiation(body) else {
1384 return Ok(None);
1385 };
1386 self.answer_ice(&prepared.offer, &mut prepared.answer).await;
1387
1388 // Hold is a direction, not a separate state: `sendonly` or `inactive` from the far end
1389 // means it will not play what we send.
1390 let was_on_hold = self.is_on_hold();
1391 self.hold = prepared.direction;
1392 // Emitted right where `hold` changes, not by polling it afterwards — a renegotiation
1393 // that does not change the direction (a keep-alive, say) must not report a hold that
1394 // never happened.
1395 match (was_on_hold, self.is_on_hold()) {
1396 (false, true) => self.events.emit(CallEvent::Hold),
1397 (true, false) => self.events.emit(CallEvent::Resumed),
1398 _ => {}
1399 }
1400
1401 self.move_media_if_changed(prepared.negotiated).await?;
1402 Ok(Some(prepared.answer))
1403 }
1404
1405 /// Give the running agent the ICE half of an answer to one of our later offers.
1406 ///
1407 /// The offering side's mirror of [`Self::answer_ice`], and it signals nothing: the answer is
1408 /// the end of this exchange, so what comes back from the agent has no description left to go
1409 /// into. What matters is that the agent hears it at all — a restart this side offered is only
1410 /// half a restart until the peer's new credentials and candidates arrive.
1411 async fn accept_answer_ice(&mut self, answer: &SessionDescription) {
1412 if !self.media.runs_ice() {
1413 return;
1414 }
1415 let peer = answer
1416 .media
1417 .first()
1418 .map_or(IceNegotiation::Absent, |audio| {
1419 sipx_media::ice::negotiate(answer, audio)
1420 });
1421 // Recorded for the same reason the initial exchange records it: the next offer from the
1422 // peer is a restart only if it differs from what was last seen, and an answer is a
1423 // description like any other.
1424 self.peer_ice_restarted(&peer);
1425 // discard: this is the media path, and M12's clause is about the signalling one — the
1426 // counters for a media session that could not apply a renegotiation are `M-32`, which is
1427 // why `sipx-media` is not in the guard's `CRATES`. Nothing signalling is lost here in any
1428 // case: the peer's ICE half was recorded on the line above, which is what the *next* offer
1429 // is compared against, and a renegotiation that does not take leaves the candidate pair
1430 // already carrying the call in use.
1431 let _ = self.media.renegotiate_ice(None, Some(&peer)).await;
1432 }
1433
1434 /// Put this side's ICE half into a later offer (RFC 8839 §4.4; `ice.md` §13.5).
1435 ///
1436 /// The offering counterpart of [`Self::answer_ice`], and it carries the same rule: a stream
1437 /// doing ICE restates its half in **every** subsequent offer, because §6 makes their absence
1438 /// mean this side has stopped. [`Self::restart_ice`] is the one caller that also draws new
1439 /// credentials, and drawing them is the whole of what it does — §4.4.1.1.1 says both values
1440 /// changing *is* the restart, so there is no second flag to set on the wire.
1441 async fn offer_ice(&mut self, offer: &mut SessionDescription, ice: IceOffer) {
1442 if !self.media.runs_ice() {
1443 return;
1444 }
1445 let local = match ice {
1446 IceOffer::Continue => None,
1447 IceOffer::Restart => fresh_ice_parameters(),
1448 };
1449 // No peer half: this is an offer, and the answer that responds to it comes back through
1450 // `Dialing`/`renegotiate` like any other.
1451 let Some(signalled) = self.media.renegotiate_ice(local, None).await else {
1452 return;
1453 };
1454 let Some(audio) = offer.media.first_mut() else {
1455 return;
1456 };
1457 audio
1458 .attributes
1459 .retain(|attribute| !is_ice_attribute(attribute));
1460 audio.attributes.extend(ice_attributes(&signalled));
1461 }
1462
1463 /// Put this side's ICE half into the answer to a later offer (RFC 8839 §4.4; `ice.md` §13.5).
1464 ///
1465 /// Three things happen here and they are one operation because they must not be reordered:
1466 /// the offer is read for the peer's half, this side takes new parameters when §4.4.1.1.1 says
1467 /// the offer is a restart, and both are handed to the running agent — which is what decides
1468 /// whether the session is rebuilt. What comes back is what this answer signals.
1469 ///
1470 /// **A stream doing ICE re-signals on every exchange**, not only on a restart. §6 makes the
1471 /// absence of `candidate` attributes mean the peer has stopped doing ICE, so an answer that
1472 /// dropped them mid-call would tell the far end to fall back to symmetric RTP on a path it had
1473 /// already agreed to check. Hold, resume, a codec change and a session refresh all come
1474 /// through here, and none of them is a restart.
1475 ///
1476 /// A call with no agent is left exactly as it was: no attributes, no round trip to a driver
1477 /// that does not exist.
1478 async fn answer_ice(&mut self, offer: &SessionDescription, answer: &mut SessionDescription) {
1479 if !self.media.runs_ice() {
1480 return;
1481 }
1482 let peer = offer.media.first().map_or(IceNegotiation::Absent, |audio| {
1483 sipx_media::ice::negotiate(offer, audio)
1484 });
1485 // §4.4.1.1.1 is a question about the *peer's* two credentials, and this side answers it
1486 // only to know whether to draw its own new ones. The agent asks it again for itself, from
1487 // the credentials it is actually keyed to; see `MediaSession::renegotiate_ice`.
1488 let local = self
1489 .peer_ice_restarted(&peer)
1490 .then(fresh_ice_parameters)
1491 .flatten();
1492 let Some(signalled) = self.media.renegotiate_ice(local, Some(&peer)).await else {
1493 return;
1494 };
1495 let Some(audio) = answer.media.first_mut() else {
1496 return;
1497 };
1498 audio
1499 .attributes
1500 .retain(|attribute| !is_ice_attribute(attribute));
1501 audio.attributes.extend(ice_attributes(&signalled));
1502 }
1503
1504 /// Whether this offer restarts ICE (RFC 8839 §4.4.1.1.1).
1505 ///
1506 /// **Both** credentials changed, and only both. One alone is not a restart, which is the case
1507 /// the rule is worded to exclude: a peer may legitimately re-send a description with one value
1508 /// re-derived and the other unchanged, and treating that as a restart would tear down a
1509 /// working session for nothing.
1510 ///
1511 /// The comparison is against what this side last *saw*, which is why it is recorded here
1512 /// rather than derived from the SDP twice — "the same value moving between the session level
1513 /// and the media level is not a restart" is only true if what is compared is the effective
1514 /// value for the stream, which is what [`sipx_media::ice::negotiate`] resolves.
1515 fn peer_ice_restarted(&mut self, peer: &IceNegotiation) -> bool {
1516 let IceNegotiation::Ice { credentials, .. } = peer else {
1517 return false;
1518 };
1519 let restarted = self.peer_ice.as_ref().is_some_and(|seen| {
1520 seen.ufrag() != credentials.ufrag() && seen.pwd() != credentials.pwd()
1521 });
1522 self.peer_ice = Some(credentials.clone());
1523 restarted
1524 }
1525
1526 /// Answer an UPDATE that arrived in this dialog (RFC 3311 §5.2).
1527 ///
1528 /// The three refusals are three different answers, and the difference is the point: 491
1529 /// means the two sides collided and both should wait a randomised interval before trying
1530 /// again; a 500 with `Retry-After` means the request was well formed and simply early. A
1531 /// peer told the wrong one either backs off when it did not need to or retries straight
1532 /// into the same wall.
1533 ///
1534 /// Whichever it is, **the dialog survives** — including the 488 for a description this side
1535 /// cannot use. Every one of these is about a change that will not happen, not about the
1536 /// session that is already running.
1537 async fn on_update(&mut self, incoming: &Incoming) -> Result<()> {
1538 if self.out_of_order(&incoming.request) {
1539 return self.refuse(incoming, 500, "Server Internal Error").await;
1540 }
1541 self.record_remote_cseq(&incoming.request);
1542
1543 let has_offer = crate::update::carries_offer(&incoming.request);
1544 if let Reception::Refuse(refusal) = self.negotiation.receive(has_offer) {
1545 return crate::update::refuse(&self.endpoint, incoming, refusal).await;
1546 }
1547
1548 let mut builder = ResponseBuilder::to_request(&incoming.request, ok_status(), "OK")?
1549 .header(
1550 HeaderName::Contact,
1551 Bytes::from(contact_for(&self.endpoint, self.target.transport)),
1552 )?
1553 .header(
1554 HeaderName::Allow,
1555 Bytes::from_static(update::ALLOW.as_bytes()),
1556 )?;
1557
1558 if has_offer {
1559 // §5.2: the UAS "MUST adjust the session parameters accordingly and generate an
1560 // answer in the 2xx response".
1561 //
1562 // The result is captured rather than propagated with `?`, because `renegotiate`
1563 // can fail on something that has nothing to do with the peer — a media port that
1564 // will not bind. Returning through the `?` would leave this UPDATE forever in
1565 // progress and the offer forever owed, and every later UPDATE on the dialog would
1566 // draw §5.2's "you are too early" for a transaction nobody is waiting on.
1567 let renegotiated = self.renegotiate(incoming.request.body()).await;
1568 let Some(answer_sdp) = renegotiated.inspect_err(|_| self.negotiation.answered())?
1569 else {
1570 // The offer is dead, so nothing is owed for it any more — and this is a final
1571 // response, so no UPDATE is in progress either.
1572 self.negotiation.answered();
1573 return self.refuse_unacceptable(incoming).await;
1574 };
1575 builder = builder
1576 .header(
1577 HeaderName::ContentType,
1578 Bytes::from_static(b"application/sdp"),
1579 )?
1580 .body(Bytes::from(answer_sdp.to_string_sdp()));
1581 }
1582
1583 // RFC 4028 §7.4: an UPDATE refreshes the session whether or not it was sent for that
1584 // reason, so the 2xx names the terms in force and the deadline moves. Only counting the
1585 // ones that carry `Session-Expires` would hang up on a peer that is demonstrably alive.
1586 if let Some(state) = self.session {
1587 let expires = SessionExpires {
1588 interval: state.terms.interval,
1589 refresher: Some(if state.terms.we_refresh {
1590 session::Refresher::Uas
1591 } else {
1592 session::Refresher::Uac
1593 }),
1594 };
1595 builder = builder
1596 .header(HeaderName::SessionExpires, Bytes::from(expires.to_string()))?
1597 .header(HeaderName::Supported, Bytes::from_static(b"timer"))?;
1598 }
1599
1600 // §5.1: UPDATE is a target refresh request, so its `Contact` replaces the dialog's
1601 // remote target — the same rule RFC 3261 §12.2.2 gives a re-INVITE, and for the same
1602 // reason: without it the BYE goes to where the peer used to be.
1603 self.dialog.refresh_target(&incoming.request.headers);
1604 self.target = in_dialog_target(
1605 &self.dialog,
1606 Target::new(incoming.source, incoming.transport),
1607 );
1608 self.peer_allows_update = update::peer_allows(&incoming.request.headers);
1609
1610 let sent = self.endpoint.respond(&incoming.key, builder.build()).await;
1611 // Cleared whether or not the response got out. A send that failed will not be retried
1612 // here, so leaving the exchange open would answer every later UPDATE on this dialog
1613 // with §5.2's "you are too early" — permanently, for a transaction nobody is waiting
1614 // on any more.
1615 self.negotiation.answered();
1616 sent?;
1617 self.rearm();
1618 Ok(())
1619 }
1620
1621 /// Renegotiate this call with an UPDATE (RFC 3311).
1622 ///
1623 /// [`Self::reinvite`] remains the right way to renegotiate a *confirmed* dialog — §5.1
1624 /// recommends it, because an UPDATE must be answered promptly and leaves the far end no
1625 /// window in which to ask a user whether the change is acceptable. This is here for the
1626 /// cases where that does not apply: a peer that asked for UPDATE, or a change that nobody
1627 /// would be asked about.
1628 ///
1629 /// Refuses locally rather than putting an illegal request on the wire when an offer of ours
1630 /// is unanswered or one of theirs is unanswered by us (§5.1, RFC 3264): the far end would
1631 /// answer 491 or 500 and the round trip would have told us only what we already knew.
1632 pub async fn update(&mut self, direction: Direction) -> Result<()> {
1633 if self.profile == MediaProfile::BrowserAudio {
1634 return Err(sipx_sdp::browser_audio::ProfileError::ProfileRemoved.into());
1635 }
1636 if self.keying == Keying::DtlsSrtp {
1637 return Err(Error::DtlsRenegotiation);
1638 }
1639 if !self.negotiation.may_offer() {
1640 return Err(Error::Rejected {
1641 status: sipx_sip::update::Refusal::Glare.status(),
1642 reason: "an offer is already outstanding on this dialog".to_owned(),
1643 });
1644 }
1645
1646 let mut capabilities = self
1647 .codecs
1648 .capabilities(self.media_address, self.media.local_addr().port());
1649 if self.current.rtcp_mode == sipx_sdp::RtcpMode::Mux {
1650 capabilities = capabilities.with_rtcp_mux();
1651 }
1652 capabilities.direction = direction;
1653 // As for a re-INVITE: the version must increase with each modified offer, so the far
1654 // end can tell a changed description from a repeated one.
1655 capabilities.session_version = u64::from(self.dialog.local_cseq.saturating_add(1));
1656 let offer = offer_from(&capabilities);
1657
1658 let (builder, routes) =
1659 crate::update::request(&self.endpoint, &mut self.dialog, &self.target, Some(&offer))?;
1660 let request = crate::update::finish(builder, &routes)?;
1661
1662 self.negotiation.sent_offer();
1663 let response = crate::update::send(&self.endpoint, request, self.target.clone()).await;
1664 // Whatever came back closed the exchange: a 2xx carries the answer, and a failure means
1665 // there will never be one. Leaving the flag set would refuse every later offer of ours.
1666 self.negotiation.received_answer();
1667 let response = response?;
1668 if !response.status.is_success() {
1669 return Err(crate::update::rejected(&response));
1670 }
1671
1672 self.dialog.refresh_target(&response.headers);
1673 self.target = in_dialog_target(&self.dialog, self.target.clone());
1674 self.peer_allows_update = update::peer_allows(&response.headers);
1675
1676 if let Ok(answer) = sipx_sdp::parse(&String::from_utf8_lossy(response.body())) {
1677 // The answer's ICE half, before the codec comparison: on a restart it carries the
1678 // peer's new credentials and candidates, and an agent that is not told about them
1679 // checks a path nobody is answering on. On an ordinary re-offer it is the same half
1680 // again, which the agent merges (RFC 8839 §4.2) rather than replaces — so a
1681 // re-answer cannot silence ICE on a call that is working.
1682 if let Ok(renegotiated) = negotiated(&answer, self.codecs) {
1683 preserve_rtcp_mode(self.current.rtcp_mode, renegotiated.rtcp_mode)?;
1684 // Do not let an answer that failed the mode guard mutate the running ICE
1685 // generation. Socket ownership and candidate state move together or neither does.
1686 self.accept_answer_ice(&answer).await;
1687 self.move_media_if_changed(renegotiated).await?;
1688 }
1689 }
1690 self.hold = direction;
1691 self.adopt_session(&response);
1692 self.rearm();
1693 Ok(())
1694 }
1695
1696 /// Whether an in-dialog request arrived out of order.
1697 ///
1698 /// RFC 3261 §12.2.2 rejects a request behind the dialog's sequence number with a 500
1699 /// rather than applying it, and §12.2.1.1 requires each new in-dialog request to
1700 /// *increment* the number — so a repeat of the current one is a duplicate that has escaped
1701 /// the transaction layer's absorption window, not a fresh request, and is refused on the
1702 /// same grounds. This is not only the re-INVITE case: a stale BYE honoured here ends a
1703 /// call that a later request has already changed.
1704 fn out_of_order(&self, request: &Request) -> bool {
1705 self.dialog.is_out_of_order(request)
1706 }
1707
1708 /// Record the sequence number of an in-dialog request this side has accepted.
1709 fn record_remote_cseq(&mut self, request: &Request) {
1710 self.dialog.record_remote_cseq(request);
1711 }
1712
1713 /// Refuse a renegotiation with 488, saying why (RFC 3311 §5.2, RFC 3261 §20.43).
1714 ///
1715 /// The `Warning` is a SHOULD, and it is the difference between a peer that can log why its
1716 /// renegotiation was refused and one that can only log that it was.
1717 async fn refuse_unacceptable(&self, incoming: &Incoming) -> Result<()> {
1718 let status = StatusCode::new(488).unwrap_or_else(ok_status);
1719 let response =
1720 ResponseBuilder::to_request(&incoming.request, status, "Not Acceptable Here")?
1721 .header(
1722 HeaderName::Warning,
1723 Bytes::from(crate::update::warning(&self.endpoint)),
1724 )?
1725 .build();
1726 self.endpoint.respond(&incoming.key, response).await?;
1727 Ok(())
1728 }
1729
1730 /// Answer an in-dialog OPTIONS (RFC 3261 §11.2).
1731 ///
1732 /// The point of OPTIONS is the capability list, so a 200 with an empty `Allow` is a wasted
1733 /// exchange: the peer asked what we can do and learned nothing. No `Contact` and no session
1734 /// description — §11.2 allows a description here, and sending one would be an offer nobody
1735 /// asked for inside a call that already has one.
1736 async fn on_options(&mut self, incoming: &Incoming) -> Result<()> {
1737 // §12.2.2 applies to every in-dialog request, this one included. Going through the
1738 // dialog's own guard rather than past it is the point: a path that keeps its own copy of
1739 // the rule is a path the rule can be forgotten on.
1740 if self.out_of_order(&incoming.request) {
1741 return self.refuse(incoming, 500, "Server Internal Error").await;
1742 }
1743 self.record_remote_cseq(&incoming.request);
1744
1745 let response = ResponseBuilder::to_request(&incoming.request, ok_status(), "OK")?
1746 .header(
1747 HeaderName::Allow,
1748 Bytes::from_static(update::ALLOW.as_bytes()),
1749 )?
1750 .header(HeaderName::Accept, Bytes::from_static(b"application/sdp"))?
1751 .build();
1752 self.endpoint.respond(&incoming.key, response).await?;
1753 Ok(())
1754 }
1755
1756 /// Answer a request that reached this call and that [`Self::handle`] did not claim.
1757 ///
1758 /// Three different things bring one here, and they are three different answers — collapsing
1759 /// them was a real defect, not an untidiness. The first version answered 481 to everything
1760 /// that failed [`Dialog::matches`](crate::Dialog::matches), and `matches` is false for any
1761 /// request with no `To` tag: so a bare INVITE or CANCEL reaching a one-call [`serve`] drew
1762 /// RFC 3261 §12.2.2's "the dialog you named does not exist" for a request that named no
1763 /// dialog at all.
1764 ///
1765 /// - **It matches this dialog**, but the method is one this call does not implement:
1766 /// §8.2.1's **405**, with the `Allow` that section makes mandatory.
1767 /// - **It names a dialog that is not this one** — it carries a `To` tag, or its method
1768 /// exists only inside a dialog: §12.2.2's **481**.
1769 /// - **It names no dialog**, so it is a new exchange arriving where exactly one call is
1770 /// being served: **486 Busy Here** for an INVITE (§21.4.24 — "not willing or able to take
1771 /// additional calls", which is precisely the one-call contract), and 405 for anything
1772 /// else. A dispatcher is the answer to wanting more than one call here, and the 486 says
1773 /// so in the only vocabulary the peer has.
1774 ///
1775 /// An ACK gets nothing, because SIP has no response to one.
1776 ///
1777 /// Failures are logged rather than returned. This exists so nothing is discarded in silence
1778 /// (`T-19`, story `C-4`), and handing the caller an error to ignore would put the silence
1779 /// back one level up.
1780 pub(crate) async fn refuse_unclaimed(&self, incoming: &Incoming) {
1781 // There is no response to an ACK, and an ACK for a 2xx is a transaction of its own
1782 // (RFC 3261 §17.1.1.3). Nothing to send; a stray one is still worth a line.
1783 if incoming.request.method == Method::Ack {
1784 tracing::debug!("an ACK reached a call that did not claim it");
1785 return;
1786 }
1787 let request = &incoming.request;
1788 let (code, reason) = if self.dialog.matches(request) {
1789 (405u16, "Method Not Allowed")
1790 } else if crate::dialog::to_tag(&request.headers).is_some()
1791 || crate::dispatch::dialog_only(&request.method)
1792 {
1793 (481, "Call/Transaction Does Not Exist")
1794 } else if request.method == Method::Invite {
1795 (486, "Busy Here")
1796 } else {
1797 (405, "Method Not Allowed")
1798 };
1799 let Some(status) = StatusCode::new(code) else {
1800 return;
1801 };
1802 let allow = code == 405;
1803 let built =
1804 ResponseBuilder::to_request(&incoming.request, status, reason).and_then(|builder| {
1805 if allow {
1806 builder.header(
1807 HeaderName::Allow,
1808 Bytes::from_static(update::ALLOW.as_bytes()),
1809 )
1810 } else {
1811 Ok(builder)
1812 }
1813 });
1814 match built {
1815 Ok(builder) => {
1816 // discard: the refusal is lost and **this loss reaches no counter**, which is
1817 // stated rather than papered over. `DiscardCounts::unanswered` is not it: that is
1818 // bumped only by the driver's 180 s sweep over transactions still handed over, so
1819 // it covers a `respond` that was never called and not one that was called and
1820 // failed — and on `Error::NoTransaction` there is no transaction left to sweep at
1821 // all. What bounds the damage is the peer: it retransmits the request and its own
1822 // transaction times out, so nothing hangs. Closing this needs a counter for
1823 // responses the endpoint could not send, which is a change to
1824 // `sipx_transport::Handle::respond`'s contract rather than to this call site.
1825 if let Err(error) = self.endpoint.respond(&incoming.key, builder.build()).await {
1826 tracing::warn!(%error, code, "could not refuse an unclaimed request");
1827 }
1828 }
1829 // discard: the same loss one step earlier and with the same downstream count — a
1830 // refusal that cannot be built is a refusal that is not sent.
1831 Err(error) => tracing::warn!(%error, code, "could not build the refusal"),
1832 }
1833 }
1834
1835 /// Refuse a renegotiation without ending the call.
1836 pub(crate) async fn refuse(
1837 &self,
1838 incoming: &Incoming,
1839 code: u16,
1840 reason: impl Into<Bytes>,
1841 ) -> Result<()> {
1842 Self::refuse_with(&self.endpoint, incoming, code, reason).await
1843 }
1844
1845 /// Refuse through a cloned endpoint while the owning call is driving an outgoing exchange.
1846 pub(crate) async fn refuse_with(
1847 endpoint: &Handle,
1848 incoming: &Incoming,
1849 code: u16,
1850 reason: impl Into<Bytes>,
1851 ) -> Result<()> {
1852 let status = StatusCode::new(code).unwrap_or_else(ok_status);
1853 let response = ResponseBuilder::to_request(&incoming.request, status, reason)?.build();
1854 endpoint.respond(&incoming.key, response).await?;
1855 Ok(())
1856 }
1857
1858 /// Rebuild the media session, but only if where or how the media flows actually changed.
1859 ///
1860 /// Restarting an unchanged session would drop packets for no reason on every re-INVITE, and
1861 /// some peers send one every thirty seconds as a keep-alive.
1862 async fn move_media_if_changed(&mut self, to: Negotiated) -> Result<()> {
1863 self.reap_retired_media().await;
1864 // The payload type is the codec's number on the wire: a re-offer can move Opus from
1865 // 111 to 96 and leave the codec unchanged, and a session not rebuilt for that goes on
1866 // sending on the number the far end just reassigned.
1867 //
1868 // Compared as the *wire* number, not as the raw `Option`. A peer may add or drop the
1869 // redundant `a=rtpmap:0 PCMU/8000` between two descriptions of the same static codec, and
1870 // `Some(0)` against `None` would read as a change when nothing changed — rebuilding the
1871 // session, and dropping audio, on a re-INVITE that only reworded the SDP.
1872 if to.remote != self.current.remote
1873 || to.codec != self.current.codec
1874 || to.clock_rate != self.current.clock_rate
1875 || to.wire_payload_type() != self.current.wire_payload_type()
1876 || to.receive_wire_payload_type() != self.current.receive_wire_payload_type()
1877 || to.rtcp_mode != self.current.rtcp_mode
1878 {
1879 let port = MediaPort::bind(SocketAddr::new(self.media_bind_address, 0))
1880 .await
1881 .map_err(Error::Io)?;
1882 let replacement = port.start(to.media_config())?;
1883 // Mute is a property of the call, not of the session that happens to be carrying it
1884 // (`M-18`). Without this a re-INVITE that moves the media — the far end changing
1885 // address or codec, which this side did not ask for and cannot refuse — unmutes the
1886 // call behind the application's back.
1887 replacement.set_muted(self.media.is_muted());
1888 replacement.set_rtcp_quality_hook(self.media.rtcp_quality_hook());
1889 let previous = std::mem::replace(&mut self.media, Arc::new(replacement));
1890 self.retired_media.push(previous);
1891 self.reap_retired_media().await;
1892 }
1893 self.current = to;
1894 Ok(())
1895 }
1896
1897 /// Restart ICE on this call (RFC 8445 §9, RFC 8839 §4.4.1.1.1; `ice.md` §13.5).
1898 ///
1899 /// Sends a re-INVITE whose offer carries **new** `ice-ufrag` and `ice-pwd` for this stream,
1900 /// which is the entire signal — the peer reads both having changed and begins a new ICE
1901 /// session. Everything else about the call is unchanged, including its direction, so a
1902 /// restart does not resume a call that was on hold.
1903 ///
1904 /// Media keeps flowing on the pair the finished session selected until the new one selects its
1905 /// own. That is what makes a restart usable in the situation it exists for: the path has become
1906 /// doubtful, not yet unusable, and going silent while checks converge would turn a recoverable
1907 /// call into a dropped one.
1908 ///
1909 /// A call not running ICE is left alone and reports success. There is nothing to restart, and
1910 /// making the caller distinguish "no ICE" from "restart failed" would push the check to every
1911 /// call site.
1912 ///
1913 /// # Errors
1914 ///
1915 /// Returns [`Error`] when the re-INVITE cannot be built or sent, or when the far end refuses
1916 /// it — the same failures as any other renegotiation, and like them it leaves the call running.
1917 pub async fn restart_ice(&mut self) -> Result<()> {
1918 if !self.media.runs_ice() {
1919 return Ok(());
1920 }
1921 self.reoffer(self.hold, IceOffer::Restart).await
1922 }
1923
1924 /// Send a re-INVITE renegotiating this call.
1925 ///
1926 /// `direction` puts the call on hold (`SendOnly` or `Inactive`) or takes it off
1927 /// (`SendRecv`).
1928 ///
1929 /// Note what hold is **not**: RFC 8839 §4.4.1.1.1 makes `c=0.0.0.0` imply an ICE restart, so a
1930 /// hold spelled with a null connection address would restart ICE on every mute. Hold here is a
1931 /// direction and nothing else (RFC 3264), which is what it has always been, and this is the
1932 /// story that makes that a decision rather than an accident.
1933 pub async fn reinvite(&mut self, direction: Direction) -> Result<()> {
1934 self.reoffer(direction, IceOffer::Continue).await
1935 }
1936
1937 /// The re-INVITE both public entry points send, and the one place their difference lives.
1938 ///
1939 /// `ice` is a parameter rather than a field on [`Call`] because it is a property of *this*
1940 /// offer and of nothing else. Held as state it would be a fourth `bool` on a struct that
1941 /// already has three — which is what `clippy::struct_excessive_bools` objects to, and the
1942 /// objection is right: a flag set before a call and cleared after it is a state machine
1943 /// written in the hardest way to read.
1944 async fn reoffer(&mut self, direction: Direction, ice: IceOffer) -> Result<()> {
1945 if self.profile == MediaProfile::BrowserAudio {
1946 return Err(sipx_sdp::browser_audio::ProfileError::ProfileRemoved.into());
1947 }
1948 if self.keying == Keying::DtlsSrtp {
1949 return Err(Error::DtlsRenegotiation);
1950 }
1951 let (local, remote) = self.dialog.local_and_remote();
1952 let cseq = self.dialog.next_cseq();
1953
1954 let mut capabilities = self
1955 .codecs
1956 .capabilities(self.media_address, self.media.local_addr().port());
1957 if self.current.rtcp_mode == sipx_sdp::RtcpMode::Mux {
1958 capabilities = capabilities.with_rtcp_mux();
1959 }
1960 capabilities.direction = direction;
1961 // The session version must increase with each modified offer, so the far end can tell
1962 // a changed description from a repeated one.
1963 capabilities.session_version = u64::from(cseq);
1964 let mut offer = offer_from(&capabilities);
1965 self.offer_ice(&mut offer, ice).await;
1966
1967 let (uri, routes) = self.dialog.request_target();
1968 let builder = RequestBuilder::new(Method::Invite, uri)
1969 .header(HeaderName::To, Bytes::from(remote))?
1970 .header(HeaderName::From, Bytes::from(local))?
1971 .header(
1972 HeaderName::CallId,
1973 Bytes::from(self.dialog.id.call_id.clone()),
1974 )?
1975 .cseq(cseq, &Method::Invite)?
1976 .header(
1977 HeaderName::Contact,
1978 Bytes::from(contact_for(&self.endpoint, self.target.transport)),
1979 )?
1980 .header(
1981 HeaderName::Allow,
1982 Bytes::from_static(update::ALLOW.as_bytes()),
1983 )?
1984 .header(
1985 HeaderName::ContentType,
1986 Bytes::from_static(b"application/sdp"),
1987 )?
1988 .max_forwards(70)
1989 .body(Bytes::from(offer.to_string_sdp()));
1990
1991 // RFC 4028 §7.4: a refresh names the current interval and the current refresher, so
1992 // that proxies on the path can see the value in force and object to it. Any re-INVITE
1993 // refreshes the session (§7.2), so these go on every one rather than only on the ones
1994 // sent because the timer asked.
1995 let mut builder = builder.header(HeaderName::Supported, Bytes::from_static(b"timer"))?;
1996 if let Some(state) = self.session {
1997 let expires = SessionExpires {
1998 interval: state.terms.interval,
1999 refresher: Some(if state.terms.we_refresh {
2000 session::Refresher::Uac
2001 } else {
2002 session::Refresher::Uas
2003 }),
2004 };
2005 builder = builder
2006 .header(HeaderName::SessionExpires, Bytes::from(expires.to_string()))?
2007 .header(
2008 HeaderName::MinSe,
2009 Bytes::from(session::ABSOLUTE_MIN_INTERVAL.as_secs().to_string()),
2010 )?;
2011 }
2012
2013 let request = add_routes(builder, &routes)?.build();
2014 // RFC 3311 §5.2 rule 2 names an offer sent "in an UPDATE, PRACK or INVITE", and this is
2015 // the INVITE case: the offer is outstanding for as long as the response takes. Marked
2016 // and cleared around the whole exchange, so a failure cannot leave the flag set and
2017 // refuse every later offer of ours.
2018 self.negotiation.sent_offer();
2019 let exchange = async {
2020 let mut responses = self.endpoint.send(request, self.target.clone()).await?;
2021 responses.final_response().await.ok_or(Error::NoResponse)
2022 }
2023 .await;
2024 self.negotiation.received_answer();
2025 let response = exchange?;
2026
2027 if !response.status.is_success() {
2028 // The far end refused the change. The call it refused to change is still running,
2029 // so this is an error about the renegotiation, not about the call.
2030 const INTERVAL_TOO_SMALL: u16 = 422;
2031 if response.status.code() == INTERVAL_TOO_SMALL
2032 && let Some(required) = required_interval(&response)
2033 && let Some(state) = self.session.as_mut()
2034 {
2035 // §10: only a 2xx extends the expiration, so adopting the longer interval does
2036 // *not* buy time — the refresh still has to succeed before the deadline that
2037 // was already running. The next attempt is the one that must land.
2038 state.terms.interval = required.max(session::ABSOLUTE_MIN_INTERVAL);
2039 }
2040 return Err(Error::Rejected {
2041 status: response.status.code(),
2042 reason: String::from_utf8_lossy(&response.reason).into_owned(),
2043 });
2044 }
2045
2046 // RFC 3261 §12.2.1.2: the 2xx to a target refresh request refreshes the target here
2047 // too, and it must be applied before the ACK — which is itself an in-dialog request
2048 // and belongs at the peer's new location.
2049 self.dialog.refresh_target(&response.headers);
2050 self.target = in_dialog_target(&self.dialog, self.target.clone());
2051
2052 send_ack(&self.endpoint, &self.dialog, self.target.clone()).await?;
2053
2054 if let Ok(answer) = sipx_sdp::parse(&String::from_utf8_lossy(response.body())) {
2055 // The answer's ICE half, before the codec comparison: on a restart it carries the
2056 // peer's new credentials and candidates, and an agent that is not told about them
2057 // checks a path nobody is answering on. On an ordinary re-offer it is the same half
2058 // again, which the agent merges (RFC 8839 §4.2) rather than replaces — so a
2059 // re-answer cannot silence ICE on a call that is working.
2060 if let Ok(settled) = settle_answer(&capabilities, &answer, self.codecs) {
2061 preserve_rtcp_mode(self.current.rtcp_mode, settled.negotiated.rtcp_mode)?;
2062 self.accept_answer_ice(&answer).await;
2063 self.move_media_if_changed(settled.negotiated).await?;
2064 }
2065 }
2066 self.hold = direction;
2067 // §7.2: the session expiration is measured from the 2xx, and a re-INVITE sent for any
2068 // other reason refreshes it just the same.
2069 self.adopt_session(&response);
2070 self.rearm();
2071 Ok(())
2072 }
2073
2074 /// Take the session terms from the 2xx to a refresh we sent (RFC 4028 §7.2).
2075 ///
2076 /// Shared by the re-INVITE and the UPDATE paths: §7.2 measures the expiration from the 2xx
2077 /// and says nothing about which request drew it, so reading it in two places would be two
2078 /// chances to read it differently.
2079 fn adopt_session(&mut self, response: &Response) {
2080 if let Some(agreed) = session::adopt(
2081 response
2082 .headers
2083 .typed::<SessionExpires>()
2084 .and_then(std::result::Result::ok),
2085 self.session.map(|state| state.terms.interval),
2086 ) && let Some(state) = self.session.as_mut()
2087 {
2088 state.terms = agreed;
2089 }
2090 }
2091
2092 /// Refresh the session, by whichever method the peer allows (RFC 4028 §7.4).
2093 ///
2094 /// > "If a UAC knows that its peer supports the UPDATE method, it is RECOMMENDED that
2095 /// > UPDATE be used instead of a re-INVITE."
2096 ///
2097 /// It is only *known* from the peer's `Allow` (RFC 3311 §4), so that is what decides.
2098 /// Guessing the other way costs a working call: a refresh the far end answers 405 is a
2099 /// refresh that never happens, and the deadline behind it hangs up on a peer that is alive.
2100 ///
2101 /// The UPDATE carries **no body**. A refresh changes nothing — the description in force
2102 /// stays in force — and re-offering it would put a liveness check under §5.2's offer/answer
2103 /// rules, where it could be refused 491 or 500 for a reason that has nothing to do with
2104 /// whether the far end is still there.
2105 async fn refresh_session(&mut self) -> Result<()> {
2106 if !self.peer_allows_update {
2107 return self.reinvite(self.hold).await;
2108 }
2109
2110 let (mut builder, routes) =
2111 crate::update::request(&self.endpoint, &mut self.dialog, &self.target, None)?;
2112 // §7.4: a refresh names the interval and the refresher in force, so proxies on the path
2113 // can see the value and object to it. `Min-SE` is this side's own floor, and it is a
2114 // defence rather than a courtesy (§11.2).
2115 builder = builder.header(HeaderName::Supported, Bytes::from_static(b"timer"))?;
2116 if let Some(state) = self.session {
2117 let expires = SessionExpires {
2118 interval: state.terms.interval,
2119 refresher: Some(if state.terms.we_refresh {
2120 session::Refresher::Uac
2121 } else {
2122 session::Refresher::Uas
2123 }),
2124 };
2125 builder = builder
2126 .header(HeaderName::SessionExpires, Bytes::from(expires.to_string()))?
2127 .header(
2128 HeaderName::MinSe,
2129 Bytes::from(session::ABSOLUTE_MIN_INTERVAL.as_secs().to_string()),
2130 )?;
2131 }
2132
2133 let request = crate::update::finish(builder, &routes)?;
2134 let response = crate::update::send(&self.endpoint, request, self.target.clone()).await?;
2135
2136 if !response.status.is_success() {
2137 const INTERVAL_TOO_SMALL: u16 = 422;
2138 if response.status.code() == INTERVAL_TOO_SMALL
2139 && let Some(required) = required_interval(&response)
2140 && let Some(state) = self.session.as_mut()
2141 {
2142 // As on the re-INVITE path: only a 2xx extends the expiration, so adopting the
2143 // longer interval does not buy time. The next attempt is the one that has to
2144 // land, and it has to land before the deadline that is already running.
2145 state.terms.interval = required.max(session::ABSOLUTE_MIN_INTERVAL);
2146 }
2147 return Err(crate::update::rejected(&response));
2148 }
2149
2150 self.dialog.refresh_target(&response.headers);
2151 self.target = in_dialog_target(&self.dialog, self.target.clone());
2152 self.adopt_session(&response);
2153 self.rearm();
2154 Ok(())
2155 }
2156
2157 /// Ask the far end to transfer this call to `target` (RFC 3515).
2158 ///
2159 /// Returns once the transferee has accepted the *request*, which is not the same as the
2160 /// transfer having worked: a `202 Accepted` means "I will try". What became of it arrives
2161 /// afterwards, as NOTIFY, and shows up in [`Self::transfer`]. Reporting success here would
2162 /// tell a user their call was handed over when it may have been refused or rung out.
2163 pub async fn refer(&mut self, target: &Uri) -> Result<()> {
2164 let refer_to = String::from_utf8_lossy(&target.to_bytes()).into_owned();
2165 self.refer_to_raw(&refer_to).await
2166 }
2167
2168 /// Ask the far end to replace `other` with a call to this one's peer (RFC 3891 + 3515).
2169 ///
2170 /// The attended half of a transfer. Where a blind transfer says "call this number", this
2171 /// says "call this number, and when you get through, take the place of the call I already
2172 /// have with them" — which is what makes the handover seamless rather than a second ring.
2173 pub async fn refer_attended(&mut self, other: &Call) -> Result<()> {
2174 let replaces = Replaces {
2175 call_id: other.dialog.id.call_id.clone(),
2176 // From the point of view of the party that will receive the eventual INVITE, our
2177 // *remote* tag on `other` is that party's own local tag. Writing our own tag here
2178 // produces a header that names nothing and a transfer that always fails.
2179 to_tag: other.dialog.id.remote_tag.clone(),
2180 from_tag: other.dialog.id.local_tag.clone(),
2181 early_only: false,
2182 };
2183 let target = String::from_utf8_lossy(&other.dialog.remote_target.to_bytes()).into_owned();
2184 // `?` separates a URI from the headers it asks to be put in the request built from it
2185 // (RFC 3261 §19.1.1), and `Replaces` is one of those headers.
2186 let refer_to = format!(
2187 "{target}?Replaces={}",
2188 escape_uri_header(&replaces.to_header())
2189 );
2190 self.refer_to_raw(&refer_to).await
2191 }
2192
2193 /// Send a REFER whose `Refer-To` is this text.
2194 async fn refer_to_raw(&mut self, refer_to: &str) -> Result<()> {
2195 let (local, remote) = self.dialog.local_and_remote();
2196 let cseq = self.dialog.next_cseq();
2197
2198 let (uri, routes) = self.dialog.request_target();
2199 let builder = RequestBuilder::new(Method::Refer, uri)
2200 .header(HeaderName::To, Bytes::from(remote))?
2201 .header(HeaderName::From, Bytes::from(local.clone()))?
2202 .header(
2203 HeaderName::CallId,
2204 Bytes::from(self.dialog.id.call_id.clone()),
2205 )?
2206 .cseq(cseq, &Method::Refer)?
2207 .header(
2208 HeaderName::Contact,
2209 Bytes::from(contact_for(&self.endpoint, self.target.transport)),
2210 )?
2211 .header(HeaderName::ReferTo, Bytes::from(format!("<{refer_to}>")))?
2212 // RFC 3892. The transferee is being asked to call a stranger on our say-so; saying
2213 // who we are is the only basis it has for deciding whether to.
2214 .header(
2215 HeaderName::ReferredBy,
2216 Bytes::from(strip_header_params(&local)),
2217 )?
2218 .max_forwards(70);
2219
2220 let request = add_routes(builder, &routes)?.build();
2221 let mut responses = self.endpoint.send(request, self.target.clone()).await?;
2222 let response = responses.final_response().await.ok_or(Error::NoResponse)?;
2223
2224 if !response.status.is_success() {
2225 return Err(Error::Rejected {
2226 status: response.status.code(),
2227 reason: String::from_utf8_lossy(&response.reason).into_owned(),
2228 });
2229 }
2230
2231 // Nothing is known yet beyond "it was taken on". The first NOTIFY replaces this.
2232 self.transfer = Some(Transfer {
2233 state: TransferState::Trying,
2234 finished: false,
2235 });
2236 Ok(())
2237 }
2238
2239 /// The transfer the far end has asked for, if it has asked and we have not answered.
2240 #[must_use]
2241 pub fn referral(&self) -> Option<&Referral> {
2242 self.referral.as_ref()
2243 }
2244
2245 /// A transfer we asked for, and what has become of it. `None` if we asked for none.
2246 #[must_use]
2247 pub fn transfer(&self) -> Option<&Transfer> {
2248 self.transfer.as_ref()
2249 }
2250
2251 /// Accept the transfer, place the call, and report the outcome (RFC 3515 §2.4.5).
2252 ///
2253 /// `target` is where to *send* the new INVITE; the `Refer-To` URI is what goes in it. The
2254 /// two are separate for the same reason they are separate in [`dial`]: resolving a URI to
2255 /// an address is RFC 3263's job and lives in the transport, not here.
2256 ///
2257 /// The original call is left running. Whether to hang up on the transferor is a policy
2258 /// decision — a blind transfer usually ends it, an attended one does not — and it belongs
2259 /// to whoever is making that decision, not to this function.
2260 pub async fn accept_referral(&mut self, target: Target, options: &DialOptions) -> Result<Call> {
2261 let Some(referral) = self.referral.take() else {
2262 return Err(Error::NoReferral);
2263 };
2264
2265 let accepted = ResponseBuilder::to_request(
2266 &referral.request,
2267 StatusCode::new(202).unwrap_or_else(|| unreachable!("202 is a valid status code")),
2268 "Accepted",
2269 )?
2270 .build();
2271 self.endpoint.respond(&referral.key, accepted).await?;
2272
2273 // "I am trying", straight away. RFC 3515 §2.4.4 asks for an immediate NOTIFY so the
2274 // transferor knows the subscription exists before anything can go wrong with the call.
2275 self.notify_transfer(&referral, 100, "Trying", false)
2276 .await?;
2277
2278 let placed = dial(&self.endpoint, target, &referral.target, options).await;
2279
2280 let (status, reason) = match &placed {
2281 Ok(_) => (200, "OK".to_owned()),
2282 Err(Error::Rejected { status, reason }) => (*status, reason.clone()),
2283 // Anything else never reached the target at all. 503 is what a proxy would say for
2284 // the same situation, and it tells the transferor something true.
2285 Err(_) => (503, "Service Unavailable".to_owned()),
2286 };
2287 // Terminating, whether it worked or not. A transferee that reports the outcome and then
2288 // says nothing leaves a subscription open on both sides for a transfer that is over.
2289 self.notify_transfer(&referral, status, &reason, true)
2290 .await?;
2291
2292 placed
2293 }
2294
2295 /// Refuse the transfer (RFC 3515 §2.4.2).
2296 ///
2297 /// No subscription is created by a REFER that was not accepted, so nothing further is owed
2298 /// and no NOTIFY is sent. The transferor learns the outcome from the status, which is why
2299 /// it should be one they can act on — 603 for "no", 488 for "not that target".
2300 pub async fn refuse_referral(&mut self, status: u16, reason: &'static str) -> Result<()> {
2301 let Some(referral) = self.referral.take() else {
2302 return Err(Error::NoReferral);
2303 };
2304 let code = StatusCode::new(status).ok_or(Error::NoReferral)?;
2305 let response = ResponseBuilder::to_request(&referral.request, code, reason)?.build();
2306 self.endpoint.respond(&referral.key, response).await?;
2307 Ok(())
2308 }
2309
2310 /// Note a REFER, or refuse one that cannot be honoured whatever the application thinks.
2311 async fn on_refer(&mut self, incoming: &Incoming) -> Result<()> {
2312 let sequence = incoming
2313 .request
2314 .headers
2315 .typed::<sipx_sip::headers::CSeq>()
2316 .and_then(std::result::Result::ok)
2317 .map_or(0, |cseq| cseq.sequence);
2318
2319 let refer_to = incoming.request.headers.value(&HeaderName::ReferTo);
2320 let target = refer_to.as_deref().and_then(|value| {
2321 let text = String::from_utf8_lossy(value);
2322 Uri::parse(Bytes::from(unbracket(text.trim()))).ok()
2323 });
2324
2325 let Some(target) = target else {
2326 // A missing or unparseable `Refer-To` is not a decision for the application: there
2327 // is nowhere to transfer to, and 400 says exactly that.
2328 self.refuse_now(incoming, 400, "Bad Request").await?;
2329 return Ok(());
2330 };
2331
2332 // An attended transfer's `Refer-To` carries a `Replaces` (RFC 3891 + 3515), built by
2333 // `refer_attended` above as a URI header parameter. A substring check on the raw value
2334 // rather than a full URI-header parse: `Uri` does not expose its header component, and
2335 // this only has to distinguish "asks to replace a dialog" from "does not", not validate
2336 // one.
2337 let attended = refer_to.as_deref().is_some_and(contains_replaces);
2338
2339 self.referral = Some(Referral {
2340 target: target.clone(),
2341 referred_by: incoming
2342 .request
2343 .headers
2344 .value(&HeaderName::ReferredBy)
2345 .map(|value| String::from_utf8_lossy(&value).into_owned()),
2346 event_id: sequence,
2347 key: incoming.key.clone(),
2348 request: incoming.request.clone(),
2349 });
2350 self.events
2351 .emit(CallEvent::TransferRequested { target, attended });
2352 Ok(())
2353 }
2354
2355 /// Take in what the transferee says about a transfer we asked for.
2356 async fn on_notify(&mut self, incoming: &Incoming) -> Result<()> {
2357 // Answered first and unconditionally. A NOTIFY we do not understand is still a request
2358 // that must not be left to time out, and the subscription is ours whether or not this
2359 // particular notification made sense.
2360 let ok = ResponseBuilder::to_request(&incoming.request, ok_status(), "OK")?.build();
2361 self.endpoint.respond(&incoming.key, ok).await?;
2362
2363 let is_refer = incoming
2364 .request
2365 .headers
2366 .value(&HeaderName::Event)
2367 .is_some_and(|value| {
2368 String::from_utf8_lossy(&value)
2369 .split(';')
2370 .next()
2371 .unwrap_or("")
2372 .trim()
2373 .eq_ignore_ascii_case("refer")
2374 });
2375 if !is_refer {
2376 return Ok(());
2377 }
2378
2379 let finished = incoming
2380 .request
2381 .headers
2382 .value(&HeaderName::SubscriptionState)
2383 .is_some_and(|value| is_terminated(&value));
2384
2385 let state = parse_sipfrag(incoming.request.body())
2386 .map(|(status, reason)| TransferState::from_status(status, &reason));
2387
2388 let transfer = self.transfer.get_or_insert(Transfer {
2389 state: TransferState::Trying,
2390 finished: false,
2391 });
2392 if let Some(state) = state {
2393 transfer.state = state.clone();
2394 self.events.emit(CallEvent::TransferProgress(state));
2395 }
2396 // Once terminated, always terminated: a stray notification afterwards must not reopen a
2397 // subscription the transferee has already closed.
2398 transfer.finished |= finished;
2399 Ok(())
2400 }
2401
2402 /// Report progress on a transfer we accepted.
2403 async fn notify_transfer(
2404 &mut self,
2405 referral: &Referral,
2406 status: u16,
2407 reason: &str,
2408 terminate: bool,
2409 ) -> Result<()> {
2410 let (local, remote) = self.dialog.local_and_remote();
2411 let cseq = self.dialog.next_cseq();
2412 let subscription = if terminate {
2413 // `noresource` is the reason RFC 6665 §4.1.3 gives for "the thing you subscribed to
2414 // no longer exists", which is what a finished transfer is.
2415 "terminated;reason=noresource".to_owned()
2416 } else {
2417 "active;expires=60".to_owned()
2418 };
2419
2420 let (uri, routes) = self.dialog.request_target();
2421 let builder = RequestBuilder::new(Method::Notify, uri)
2422 .header(HeaderName::To, Bytes::from(remote))?
2423 .header(HeaderName::From, Bytes::from(local))?
2424 .header(
2425 HeaderName::CallId,
2426 Bytes::from(self.dialog.id.call_id.clone()),
2427 )?
2428 .cseq(cseq, &Method::Notify)?
2429 .header(
2430 HeaderName::Contact,
2431 Bytes::from(contact_for(&self.endpoint, self.target.transport)),
2432 )?
2433 // The `id` ties this to the REFER that created the subscription, so a transferor
2434 // with two transfers in flight can tell which one this is about (RFC 3515 §2.4.4).
2435 .header(
2436 HeaderName::Event,
2437 Bytes::from(format!("refer;id={}", referral.event_id)),
2438 )?
2439 .header(HeaderName::SubscriptionState, Bytes::from(subscription))?
2440 .header(
2441 HeaderName::ContentType,
2442 Bytes::from_static(b"message/sipfrag;version=2.0"),
2443 )?
2444 .max_forwards(70)
2445 .body(Bytes::from(sipfrag(status, reason)));
2446
2447 let request = add_routes(builder, &routes)?.build();
2448 let mut responses = self.endpoint.send(request, self.target.clone()).await?;
2449 // A NOTIFY the transferor never answers does not undo the transfer; the call it asked
2450 // for has already happened either way.
2451 //
2452 // discard: nothing is thrown away that anyone could act on. The NOTIFY itself was handed
2453 // over — one the endpoint could not put on the wire is counted at the transmit by
2454 // `sipx_transport::UnsentCounts` — and what is dropped here is only *waiting* for its
2455 // answer. The bound bounds a failure (`X-29`): the transfer's outcome does not depend on
2456 // the reply arriving.
2457 let _ = tokio::time::timeout(Duration::from_secs(2), responses.final_response()).await;
2458 Ok(())
2459 }
2460
2461 /// Refuse a request outright, without involving the application.
2462 async fn refuse_now(
2463 &mut self,
2464 incoming: &Incoming,
2465 status: u16,
2466 reason: &'static str,
2467 ) -> Result<()> {
2468 let Some(code) = StatusCode::new(status) else {
2469 return Ok(());
2470 };
2471 let response = ResponseBuilder::to_request(&incoming.request, code, reason)?.build();
2472 self.endpoint.respond(&incoming.key, response).await?;
2473 Ok(())
2474 }
2475
2476 /// End the call, for `cause`, which is emitted the moment `ended` flips — before the BYE is
2477 /// even built, so a call is reported over regardless of whether transmitting the BYE then
2478 /// succeeds. Shared by [`Self::hang_up`] (this side decided to end it) and the session-timer
2479 /// path in [`Self::on_session_deadline`] (the far end stopped answering), so both go through
2480 /// exactly the same teardown and the event this emits cannot drift from which one happened.
2481 ///
2482 /// Anything still queued is sent first, then the media stops, then the BYE goes out.
2483 /// Stopping first would discard the tail of whatever was playing — the last word of a
2484 /// clip, the last digit of a PIN — because sending is paced and the queue outlives the
2485 /// call by however much is left in it.
2486 async fn end(&mut self, cause: EndCause) -> Result<()> {
2487 let reason = match cause {
2488 EndCause::Timeout => request_timeout_reason(),
2489 _ => normal_clearing_reason(),
2490 };
2491 self.end_with_reason(cause, &reason).await
2492 }
2493
2494 async fn begin_end(
2495 &mut self,
2496 cause: EndCause,
2497 reason: &ReasonValue,
2498 ) -> Result<Option<(Request, u32)>> {
2499 if self.ended {
2500 self.finish_media_ownership().await;
2501 return Ok(None);
2502 }
2503 self.media.flush(Duration::from_secs(5)).await;
2504 self.media.stop();
2505 self.ended = true;
2506 self.session = None;
2507 self.stop_ack_retransmission().await;
2508 self.events.end(cause);
2509
2510 let cseq = self.dialog.next_cseq();
2511 match bye_request(&self.dialog, cseq, reason) {
2512 Ok(bye) => Ok(Some((bye, cseq))),
2513 Err(error) => {
2514 self.finish_media_ownership().await;
2515 Err(error)
2516 }
2517 }
2518 }
2519
2520 async fn finish_media_ownership(&mut self) {
2521 self.stop_ack_retransmission().await;
2522 self.media.shutdown().await;
2523 self.reap_retired_media().await;
2524 }
2525
2526 async fn end_with_reason(&mut self, cause: EndCause, reason: &ReasonValue) -> Result<()> {
2527 let Some((bye, _)) = self.begin_end(cause, reason).await? else {
2528 return Ok(());
2529 };
2530 let sent: Result<()> = async {
2531 let mut responses = self.endpoint.send(bye, self.target.clone()).await?;
2532 // A BYE that is never answered still ends the call locally: the alternative is a call
2533 // that cannot be hung up because the far end has already gone.
2534 //
2535 // discard: the BYE was handed over, and one the endpoint could not put on the wire is
2536 // counted at the transmit as `sipx_transport::UnsentCounts::bye` — the number an operator
2537 // asking "why did that call linger" needs. What is dropped here is only waiting for the
2538 // 200, and this side has already ended the call either way. The bound bounds a failure
2539 // (`X-29`).
2540 let _ = tokio::time::timeout(Duration::from_secs(2), responses.final_response()).await;
2541 Ok(())
2542 }
2543 .await;
2544 self.finish_media_ownership().await;
2545 sent
2546 }
2547
2548 /// End the call because this side decided to.
2549 pub async fn hang_up(&mut self) -> Result<()> {
2550 self.end(EndCause::LocalHangup).await
2551 }
2552
2553 /// End the call and return the valid final response to the originated BYE.
2554 ///
2555 /// Unlike [`Self::hang_up`], this is an evidence-producing teardown: `within` bounds failure,
2556 /// and success requires the final response to name this exact dialog and the BYE's exact
2557 /// `CSeq`.
2558 /// A valid non-2xx is returned as [`Error::Rejected`], while a mismatched response is
2559 /// [`Error::InvalidDialogResponse`].
2560 pub async fn hang_up_observed(&mut self, within: Duration) -> Result<u16> {
2561 let reason = normal_clearing_reason();
2562 let Some((bye, cseq)) = self.begin_end(EndCause::LocalHangup, &reason).await? else {
2563 return Err(Error::InvalidDialogResponse);
2564 };
2565 let observed = async {
2566 let mut responses = self.endpoint.send(bye, self.target.clone()).await?;
2567 // Fixed duration bounds a failed teardown; the final response is the happens-before.
2568 let response = tokio::time::timeout(within, responses.final_response())
2569 .await
2570 .map_err(|_| Error::SignallingTeardownTimeout(within))?
2571 .ok_or(Error::SignallingTeardownTimeout(within))?;
2572 if !crate::signalling::response_matches_dialog(&response, &self.dialog, cseq) {
2573 return Err(Error::InvalidDialogResponse);
2574 }
2575 let status = response.status.code();
2576 if !response.status.is_success() {
2577 return Err(Error::Rejected {
2578 status,
2579 reason: String::from_utf8_lossy(&response.reason).into_owned(),
2580 });
2581 }
2582 Ok(status)
2583 }
2584 .await;
2585 self.finish_media_ownership().await;
2586 observed
2587 }
2588
2589 /// End the call with an explicit protocol cause.
2590 ///
2591 /// This is the coupled-leg shape from RFC 3326 §3.1: a controller which knows the winning
2592 /// response can tell the other dialog why it is being ended instead of reducing every
2593 /// teardown to a local hangup.
2594 pub async fn hang_up_with_reason(&mut self, reason: ReasonValue) -> Result<()> {
2595 self.end_with_reason(EndCause::LocalHangup, &reason).await
2596 }
2597}
2598
2599/// Drive a call until it ends, honouring its session timer.
2600///
2601/// The loop a call needs is not just "read the next message": a session timer is a deadline,
2602/// and a call that only ever wakes on incoming traffic can never notice that no traffic has
2603/// arrived. This is that loop, written once so that the RFC 4028 half of it is not something
2604/// every caller has to remember.
2605///
2606/// Returns when the far end hangs up, or [`Error::SessionExpired`] when it stops answering.
2607///
2608/// # One call, or one of many
2609///
2610/// This is **the one-call convenience over [`Dispatcher`](crate::Dispatcher)** (story `C-4`), and
2611/// the receiver it takes is what makes it both things at once. Handed the endpoint's own
2612/// `Receiver<Incoming>` it is the single-call program it has always been; handed an inbox a
2613/// dispatcher routed, it drives one call of any number on the same endpoint. There is no second
2614/// loop for the many-call case, which is the point — a hand-rolled demultiplexer beside this one
2615/// is a fresh chance to drop an ACK.
2616///
2617/// The one-call form claims the whole endpoint, so it is right only when this is the only call
2618/// on it. Anything else arriving there is not this call's, and is answered as such below.
2619///
2620/// # Nothing is discarded
2621///
2622/// A request [`Call::handle`] does not claim is **answered**, not dropped: 405 with `Allow` when
2623/// it belongs to this dialog but names a method this call does not implement (RFC 3261 §8.2.1),
2624/// 481 when it names a dialog that is not this one (§12.2.2), 486 for a second INVITE arriving
2625/// where one call is being served (§21.4.24), and nothing at all for an ACK, which SIP has no
2626/// response to. This used to be a silent drop, and it is the call-layer twin of what `T-19`
2627/// removed at the transport layer.
2628pub async fn serve(
2629 call: &mut Call,
2630 incoming: &mut tokio::sync::mpsc::Receiver<Incoming>,
2631) -> Result<()> {
2632 while !call.is_ended() {
2633 let deadline = call.session_deadline();
2634 tokio::select! {
2635 message = incoming.recv() => match message {
2636 Some(message) => {
2637 if !call.handle(&message).await? {
2638 call.refuse_unclaimed(&message).await;
2639 }
2640 }
2641 // The endpoint has shut down. The call cannot be worked any further, and
2642 // pretending otherwise would spin on a closed channel.
2643 None => return Ok(()),
2644 },
2645 () = sleep_until(deadline) => call.on_session_deadline().await?,
2646 // DTMF arrives over RTP, not signalling, so nothing above ever sees it — this is
2647 // the one place a digit becomes a `CallEvent`. Read fresh from `call.media()` on
2648 // every pass rather than once outside the loop, so a re-INVITE that moves the
2649 // media session (`move_media_if_changed`) is followed automatically: the next
2650 // iteration's future is built against whichever session is current.
2651 digit = call.media().recv_digit() => {
2652 if let Some((digit, duration)) = digit {
2653 call.events.emit(CallEvent::Dtmf { digit, duration });
2654 }
2655 }
2656 }
2657 }
2658 Ok(())
2659}
2660
2661/// Wait for a deadline, or forever if there is none.
2662///
2663/// A free function rather than a method so that it borrows nothing: a future that borrowed the
2664/// call would collide with the `&mut` the other arm of the `select!` needs.
2665pub(crate) async fn sleep_until(deadline: Option<Instant>) {
2666 match deadline {
2667 Some(at) => tokio::time::sleep_until(at).await,
2668 None => std::future::pending().await,
2669 }
2670}
2671
2672/// Percent-escape a value going into a URI header field.
2673///
2674/// A `Replaces` value contains `;` and `=`, both of which end a URI header in the grammar of
2675/// RFC 3261 §19.1.1. Left unescaped, the `Refer-To` would be truncated at the first semicolon,
2676/// the transferee would place an ordinary call, and the transfer would appear to work while the
2677/// original call was never replaced.
2678fn escape_uri_header(value: &str) -> String {
2679 let mut out = String::with_capacity(value.len());
2680 for byte in value.bytes() {
2681 match byte {
2682 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' | b'@' => {
2683 out.push(byte as char);
2684 }
2685 _ => {
2686 use std::fmt::Write as _;
2687 // discard: nothing can be lost. `write!` into a `String` returns `fmt::Error`
2688 // only if the formatter itself fails, and `String`'s never does — there is no
2689 // I/O and no allocation failure path to report.
2690 let _ = write!(out, "%{byte:02X}");
2691 }
2692 }
2693 }
2694 out
2695}
2696
2697/// Whether a `Refer-To` value carries a `Replaces` header parameter — RFC 3891's marker of an
2698/// attended transfer, as [`Call::refer_attended`] builds it (`<target>?Replaces=...`).
2699///
2700/// A substring check rather than a full URI-header parse: `Uri` does not expose its header
2701/// component, and telling "asks to replace a dialog" from "does not" is all this needs to do.
2702fn contains_replaces(value: &[u8]) -> bool {
2703 String::from_utf8_lossy(value)
2704 .to_ascii_lowercase()
2705 .contains("replaces=")
2706}
2707
2708/// Strip the angle brackets a `Refer-To` almost always carries.
2709///
2710/// `Refer-To: <sip:x@y>` and `Refer-To: sip:x@y` are both legal; only the first can carry URI
2711/// parameters unambiguously, so it is the one everything sends. Any display name before the
2712/// bracket goes with them.
2713fn unbracket(value: &str) -> String {
2714 match (value.find('<'), value.rfind('>')) {
2715 (Some(open), Some(close)) if close > open => value
2716 .get(open + 1..close)
2717 .unwrap_or(value)
2718 .trim()
2719 .to_owned(),
2720 _ => value.to_owned(),
2721 }
2722}
2723
2724/// Add the dialog's route set, in order, as `Route` headers.
2725///
2726/// Without these, a request through a Record-Routing proxy — which is to say almost any real
2727/// deployment — is addressed straight at the peer's `Contact`, which the proxy will not relay
2728/// and the peer may not be reachable at. The call establishes and cannot be ended.
2729pub(crate) fn add_routes(
2730 mut builder: RequestBuilder,
2731 routes: &[String],
2732) -> std::result::Result<RequestBuilder, sipx_sip::error::BuildError> {
2733 for route in routes {
2734 builder = builder.header(HeaderName::Route, Bytes::from(route.clone()))?;
2735 }
2736 Ok(builder)
2737}
2738
2739/// Build one application-owned request entirely from live dialog state.
2740fn application_request(
2741 dialog: &Dialog,
2742 method: &Method,
2743 cseq: u32,
2744 headers: &[sipx_sip::Header],
2745 body: Bytes,
2746) -> Result<Request> {
2747 let (local, remote) = dialog.local_and_remote();
2748 let (uri, routes) = dialog.request_target();
2749 let mut builder = RequestBuilder::new(method.clone(), uri)
2750 .header(HeaderName::To, Bytes::from(remote))?
2751 .header(HeaderName::From, Bytes::from(local))?
2752 .header(HeaderName::CallId, Bytes::from(dialog.id.call_id.clone()))?
2753 .cseq(cseq, method)?
2754 .max_forwards(70);
2755 for header in headers {
2756 builder = builder.header(
2757 header.name().clone(),
2758 Bytes::copy_from_slice(header.raw_value()),
2759 )?;
2760 }
2761 Ok(add_routes(builder, &routes)?.body(body).build())
2762}
2763
2764fn bye_request(dialog: &Dialog, cseq: u32, reason: &ReasonValue) -> Result<Request> {
2765 let (local, remote) = dialog.local_and_remote();
2766 let (uri, routes) = dialog.request_target();
2767 let builder = RequestBuilder::new(Method::Bye, uri)
2768 .header(HeaderName::To, Bytes::from(remote))?
2769 .header(HeaderName::From, Bytes::from(local))?
2770 .header(HeaderName::CallId, Bytes::from(dialog.id.call_id.clone()))?
2771 .header(HeaderName::Reason, Reason::from(reason.clone()).to_bytes())?
2772 .cseq(cseq, &Method::Bye)?
2773 .max_forwards(70);
2774 Ok(add_routes(builder, &routes)?.build())
2775}
2776
2777/// How a call is placed.
2778#[derive(Debug, Clone)]
2779pub struct DialOptions {
2780 /// Our own address of record.
2781 pub from: String,
2782 /// Where this side receives media.
2783 pub media_address: IpAddr,
2784 /// The local interface on which the media socket is opened.
2785 ///
2786 /// This defaults to [`Self::media_address`] when constructed with [`Self::new`]. Set it
2787 /// independently when the SDP address is a public mapping which is not locally bindable. ICE
2788 /// nomination still owns the eventual media path when ICE is enabled; ordinary RTP cannot
2789 /// override that result.
2790 ///
2791 /// # Beta API migration
2792 ///
2793 /// Adding this public field deliberately breaks external `DialOptions` struct literals and
2794 /// exhaustive patterns. Add `media_bind_address` (normally equal to `media_address`) or move
2795 /// to [`Self::new`] and the builder methods. Constructor-based callers remain compatible.
2796 pub media_bind_address: IpAddr,
2797 /// Direction advertised by the initial SDP offer.
2798 ///
2799 /// `SendRecv` is the ordinary endpoint default. A two-dialog owner uses this to map the
2800 /// source leg's initial offer onto fresh SDP for its target leg without copying endpoint
2801 /// addresses, ports or key material.
2802 pub initial_direction: Direction,
2803 /// How long to wait for an answer before giving up and cancelling.
2804 ///
2805 /// `None` waits as long as the transaction layer does — 64·T1, or 32 seconds with the
2806 /// default constants. A bound *here* rather than around the call is what makes giving up
2807 /// correct: dropping the future partway through leaves the far end believing it is in a
2808 /// call, and only code inside the exchange can send the CANCEL that stops it.
2809 pub timeout: Option<Duration>,
2810 /// Ask for an RFC 4028 session timer of this length.
2811 ///
2812 /// `None` is the default and means no timer is requested. That is not the same as no timer
2813 /// being *run*: a far end that asks for one gets it, because refusing to refresh a session
2814 /// the peer is timing would have it hang up on a call that is working.
2815 pub session_expires: Option<Duration>,
2816 /// The pre-loaded route set to put on the INVITE, outermost proxy first (RFC 3608 §6.1).
2817 ///
2818 /// Empty by default: the INVITE goes to `target` and no further. Set it from a registrar's
2819 /// `Service-Route` — `UserAgent::service_route().rendered()` produces exactly this — when the
2820 /// registration says outbound requests must traverse proxies. Without it, a call placed
2821 /// through a registration reaches a proxy holding no state for it. This field serializes the
2822 /// `Route` headers; the application must resolve the outer hop and supply that transport
2823 /// destination as the [`Target`] passed to [`dial`]. The call layer does not resolve a Route
2824 /// URI or override the caller's target.
2825 pub service_route: Vec<String>,
2826 /// Application-supplied fields on the initial INVITE.
2827 ///
2828 /// Values have already passed [`sipx_sip::Header::build`]'s line-injection checks. The call
2829 /// layer retains them in the options so authentication and session-timer retries send the
2830 /// same request metadata as the first attempt. Applications remain responsible for refusing
2831 /// stack-owned routing and dialog fields before constructing these values.
2832 pub headers: Vec<sipx_sip::Header>,
2833 /// The media policy for this call.
2834 ///
2835 /// The default is G.711, no ICE. In particular, enabling a crate feature never changes what
2836 /// goes on the wire without an application selecting it.
2837 pub media: MediaPolicy,
2838 /// Credentials to answer a 401 or 407 during this call attempt.
2839 ///
2840 /// Owned by the application and retained only in the options it passes. Their `Debug`
2841 /// representation redacts the password, and the call path never logs an authorization value.
2842 /// [`dial`] and [`dial_once`] perform the bounded retry; [`dial_early`] surfaces
2843 /// [`Error::AuthenticationChallenge`] because its handle names the original INVITE.
2844 pub credentials: Option<Credentials>,
2845 /// Authentication service selected for this call's initial INVITE attempts.
2846 ///
2847 /// `None` is the wire-compatible default: no `Date` or `Identity` is added and no authority,
2848 /// credential, or time input is consulted. The policy owns those explicit caller inputs.
2849 pub identity: Option<OutboundIdentityPolicy>,
2850}
2851
2852impl DialOptions {
2853 /// Options for a call from an address of record.
2854 #[must_use]
2855 pub fn new(from: impl Into<String>, media_address: IpAddr) -> Self {
2856 Self {
2857 from: from.into(),
2858 media_address,
2859 media_bind_address: media_address,
2860 initial_direction: Direction::SendRecv,
2861 timeout: None,
2862 session_expires: None,
2863 service_route: Vec::new(),
2864 headers: Vec::new(),
2865 media: MediaPolicy::default(),
2866 credentials: None,
2867 identity: None,
2868 }
2869 }
2870
2871 /// Offer these codecs, most preferred first.
2872 ///
2873 /// [`Codecs::Opus`] puts Opus ahead of the G.711 pair in the offer; the far end's answer
2874 /// decides what the call carries, and a peer without Opus still gets G.711.
2875 #[must_use]
2876 pub fn with_codecs(mut self, codecs: Codecs) -> Self {
2877 self.media.codecs = codecs;
2878 self
2879 }
2880
2881 /// Advertise this direction in the initial offer.
2882 #[must_use]
2883 pub const fn with_initial_direction(mut self, direction: Direction) -> Self {
2884 self.initial_direction = direction;
2885 self
2886 }
2887
2888 /// Key this call with the selected mechanism.
2889 #[must_use]
2890 pub fn with_keying(mut self, keying: Keying) -> Self {
2891 self.media.keying = keying;
2892 self
2893 }
2894
2895 /// Use this complete media policy for the call.
2896 #[must_use]
2897 pub fn with_media_policy(mut self, media: MediaPolicy) -> Self {
2898 self.media = media;
2899 self
2900 }
2901
2902 /// Bind RTP on this local address without changing the address advertised in SDP.
2903 #[must_use]
2904 pub const fn with_media_bind_address(mut self, address: IpAddr) -> Self {
2905 self.media_bind_address = address;
2906 self
2907 }
2908
2909 /// Traverse these proxies on the way out, outermost first (RFC 3608).
2910 ///
2911 /// The values are `Route` header values — `<sip:proxy.example;lr>` — which is what
2912 /// `ServiceRoute::rendered` returns. Order is normative: §6.1 requires a UA that exercises a
2913 /// service route to preserve the order the registrar listed. This only serializes headers:
2914 /// resolve the outer hop in the application and pass that address as the `target` to [`dial`].
2915 #[must_use]
2916 pub fn with_service_route(mut self, hops: Vec<String>) -> Self {
2917 self.service_route = hops;
2918 self
2919 }
2920
2921 /// Add a validated application-owned field to every initial INVITE attempt.
2922 #[must_use]
2923 pub fn with_header(mut self, header: sipx_sip::Header) -> Self {
2924 self.headers.push(header);
2925 self
2926 }
2927
2928 /// Answer a digest challenge with these credentials (RFC 3261 §22).
2929 #[must_use]
2930 pub fn with_credentials(mut self, credentials: Credentials) -> Self {
2931 self.credentials = Some(credentials);
2932 self
2933 }
2934
2935 /// Sign every initial INVITE attempt with this caller-owned authentication policy.
2936 #[must_use]
2937 pub fn with_identity(mut self, identity: OutboundIdentityPolicy) -> Self {
2938 self.identity = Some(identity);
2939 self
2940 }
2941
2942 /// Detect a far end that vanishes, by refreshing the session on this interval (RFC 4028).
2943 ///
2944 /// Without this, a peer that loses power leaves the call up forever: there is no BYE, the
2945 /// socket never closes, and nothing else in SIP notices. The interval is raised to the
2946 /// RFC's ninety-second floor if it is shorter, because a shorter one is an amplification
2947 /// vector rather than a configuration choice.
2948 #[must_use]
2949 pub fn with_session_timer(mut self, interval: Duration) -> Self {
2950 self.session_expires = Some(interval.max(session::ABSOLUTE_MIN_INTERVAL));
2951 self
2952 }
2953
2954 /// Give up after this long.
2955 #[must_use]
2956 pub fn with_timeout(mut self, timeout: Duration) -> Self {
2957 self.timeout = Some(timeout);
2958 self
2959 }
2960}
2961
2962/// Place a call.
2963/// What this side offers, and the description that carries it.
2964///
2965/// A key is offered only when the transport protects it: SDES puts the master key in the SDP
2966/// body, so offering one over cleartext SIP publishes it (RFC 4568 §7.1).
2967///
2968/// Both the receive address and the codec set come from the caller's [`DialOptions`], so they are
2969/// taken as one rather than passed apart: they are two halves of the same decision about what this
2970/// side is offering, and splitting them invites a call site that reads the set from somewhere else.
2971#[derive(Debug)]
2972enum PendingKeying {
2973 Sdes,
2974 #[cfg(feature = "dtls")]
2975 Dtls(sipx_media::dtls::openssl::Identity),
2976}
2977
2978/// Refuse an impossible named profile before binding, gathering, certificate creation, or SIP I/O.
2979fn validate_profile_preflight(policy: MediaPolicy, transport: TransportKind) -> Result<()> {
2980 if policy.profile == MediaProfile::Standard {
2981 return Ok(());
2982 }
2983 if !cfg!(feature = "opus") {
2984 return Err(sipx_sdp::browser_audio::ProfileError::OpusUnavailable.into());
2985 }
2986 if !cfg!(feature = "dtls") {
2987 return Err(Error::DtlsUnavailable);
2988 }
2989 if transport != TransportKind::Wss {
2990 return Err(sipx_sdp::browser_audio::ProfileError::InsecureSignalling.into());
2991 }
2992 if policy.ice == IcePolicy::Disabled {
2993 return Err(sipx_sdp::browser_audio::ProfileError::IceRequired.into());
2994 }
2995 if policy.keying != Keying::DtlsSrtp {
2996 return Err(sipx_sdp::browser_audio::ProfileError::WeakerMedia.into());
2997 }
2998 #[cfg(feature = "opus")]
2999 if policy.codecs != Codecs::Opus {
3000 return if policy.codecs.carries(Codec::Opus) {
3001 Err(sipx_sdp::browser_audio::ProfileError::CodecSetIncomplete.into())
3002 } else {
3003 Err(sipx_sdp::browser_audio::ProfileError::OpusUnavailable.into())
3004 };
3005 }
3006 Ok(())
3007}
3008
3009/// Build the capabilities selected by policy and retain anything the later handshake needs.
3010fn media_capabilities(
3011 policy: MediaPolicy,
3012 address: IpAddr,
3013 port: u16,
3014 secure_signalling: bool,
3015) -> Result<(Capabilities, PendingKeying)> {
3016 if policy.profile == MediaProfile::Standard
3017 && policy.keying == Keying::DtlsSrtp
3018 && policy.ice != IcePolicy::Disabled
3019 {
3020 return Err(Error::Sdp(
3021 "DTLS-SRTP cannot yet be combined with ICE on one media port".to_owned(),
3022 ));
3023 }
3024 // Offer mux on every ordinary audio exchange. A peer that omits it in the answer selects the
3025 // established adjacent-port fallback; no retry or second offer is needed (RFC 5761 §5.1.1).
3026 let capabilities = policy.codecs.capabilities(address, port).with_rtcp_mux();
3027 match policy.keying {
3028 Keying::Auto => Ok((
3029 capabilities.with_srtp(secure_signalling),
3030 PendingKeying::Sdes,
3031 )),
3032 Keying::Plain => Ok((capabilities, PendingKeying::Sdes)),
3033 Keying::Sdes => {
3034 if !secure_signalling {
3035 return Err(Error::Sdp(
3036 "SDES-SRTP requires protected signalling".to_owned(),
3037 ));
3038 }
3039 Ok((capabilities.with_srtp(true), PendingKeying::Sdes))
3040 }
3041 Keying::DtlsSrtp => {
3042 #[cfg(feature = "dtls")]
3043 {
3044 let identity = sipx_media::dtls::openssl::Identity::generate()
3045 .map_err(|error| Error::Dtls(error.to_string()))?;
3046 let fingerprint = identity
3047 .fingerprint()
3048 .map_err(|error| Error::Dtls(error.to_string()))?;
3049 Ok((
3050 capabilities.with_dtls_srtp(fingerprint),
3051 PendingKeying::Dtls(identity),
3052 ))
3053 }
3054 #[cfg(not(feature = "dtls"))]
3055 {
3056 Err(Error::DtlsUnavailable)
3057 }
3058 }
3059 }
3060}
3061
3062async fn offered_media(
3063 options: &DialOptions,
3064 port: &MediaPort,
3065 transport: TransportKind,
3066) -> Result<(
3067 Capabilities,
3068 SessionDescription,
3069 Option<LocalDescription>,
3070 PendingKeying,
3071)> {
3072 let local_ice = match options.media.gathering(true)? {
3073 // An initial offer has not settled mux yet, so retain component 2 and its `a=rtcp`
3074 // destination for RFC 5761's no-second-exchange fallback.
3075 Some(gathering) => {
3076 let rtcp_mode = if options.media.profile == MediaProfile::BrowserAudio {
3077 sipx_sdp::RtcpMode::Mux
3078 } else {
3079 sipx_sdp::RtcpMode::Separate
3080 };
3081 Some(port.gather_with_rtcp_mode(&gathering, rtcp_mode).await)
3082 }
3083 None => None,
3084 };
3085 let advertised = local_ice
3086 .as_ref()
3087 .and_then(|local| local.default_destination(ComponentId::RTP))
3088 .unwrap_or_else(|| SocketAddr::new(options.media_address, port.local_addr().port()));
3089 let (capabilities, keying) = media_capabilities(
3090 options.media,
3091 advertised.ip(),
3092 advertised.port(),
3093 transport.is_secure(),
3094 )?;
3095 let mut capabilities = capabilities;
3096 capabilities.direction = options.initial_direction;
3097 let offer = if options.media.profile == MediaProfile::BrowserAudio {
3098 let local = local_ice
3099 .as_ref()
3100 .ok_or(sipx_sdp::browser_audio::ProfileError::IceRequired)?;
3101 let fingerprint = capabilities
3102 .dtls()
3103 .cloned()
3104 .ok_or(sipx_sdp::browser_audio::ProfileError::FingerprintRequired)?;
3105 sipx_sdp::browser_audio::offer(&sipx_sdp::browser_audio::BrowserAudioLocal {
3106 address: advertised.ip(),
3107 port: advertised.port(),
3108 session_id: capabilities.session_id,
3109 session_version: capabilities.session_version,
3110 direction: options.initial_direction,
3111 ice: local.credentials().clone(),
3112 candidates: local.candidates().to_vec(),
3113 fingerprint,
3114 setup: sipx_sdp::fingerprint::SetupCapabilities::both(),
3115 })?
3116 } else {
3117 let mut offer = offer_from(&capabilities);
3118 if let Some(local) = &local_ice {
3119 add_ice(&mut offer, local, &[]);
3120 }
3121 offer
3122 };
3123 Ok((capabilities, offer, local_ice, keying))
3124}
3125
3126/// Put one gathered local description into the audio stream it belongs to.
3127fn add_ice(
3128 description: &mut SessionDescription,
3129 local: &LocalDescription,
3130 additional: &[sipx_sdp::Attribute],
3131) {
3132 let Some(default) = local.default_destination(ComponentId::RTP) else {
3133 return;
3134 };
3135 description.connection = Some(Connection::new(default.ip()));
3136 if let Some(audio) = description.media.first_mut() {
3137 audio.port = default.port();
3138 if let Some(control) = local.default_destination(ComponentId::RTCP) {
3139 let address_type = if control.is_ipv6() { "IP6" } else { "IP4" };
3140 audio.attributes.push(sipx_sdp::Attribute::valued(
3141 "rtcp",
3142 format!("{} IN {address_type} {}", control.port(), control.ip()),
3143 ));
3144 }
3145 audio.attributes.extend(local.attributes());
3146 audio.attributes.extend_from_slice(additional);
3147 }
3148}
3149
3150/// The `a=` names RFC 8839 §5 gives ICE, so a later description can replace its own half.
3151///
3152/// Replaced rather than appended: `sipx_sdp::answer` copies the stream it is answering, so an
3153/// answer built from an offer that carried ICE starts out holding the *peer's* `ice-ufrag`,
3154/// `ice-pwd` and candidates. Extending that with ours would produce a description claiming both
3155/// sets, and a peer reading the first `ice-ufrag` it finds would key its checks to its own
3156/// credentials.
3157const ICE_ATTRIBUTES: &[&str] = &[
3158 "ice-ufrag",
3159 "ice-pwd",
3160 "ice-options",
3161 "ice-lite",
3162 "ice-pacing",
3163 "candidate",
3164 "remote-candidates",
3165];
3166
3167/// Whether an attribute is one of the ICE names a later description restates.
3168///
3169/// `ice-mismatch` is deliberately **not** here. RFC 8839 §5.3 makes it a statement about the
3170/// exchange rather than a parameter of this side's ICE session, and a stream that carries it is
3171/// one ICE is not running for at all.
3172fn is_ice_attribute(attribute: &sipx_sdp::Attribute) -> bool {
3173 ICE_ATTRIBUTES
3174 .iter()
3175 .any(|name| attribute.name.eq_ignore_ascii_case(name))
3176}
3177
3178/// This side's ICE half for a later offer or answer (RFC 8839 §4.4; `ice.md` §13.5).
3179///
3180/// The same three lines an initial description carries, from the agent rather than from the
3181/// gathering that has long since finished — `ice2` included, because §13.5's re-signalling has to
3182/// restate the whole half and a peer that stopped seeing `ice-options` would read it as a change.
3183fn ice_attributes(local: &sipx_media::ice::Local) -> Vec<sipx_sdp::Attribute> {
3184 let mut attributes = vec![
3185 sipx_sdp::Attribute::valued("ice-ufrag", local.credentials.ufrag()),
3186 sipx_sdp::Attribute::valued("ice-pwd", local.credentials.pwd()),
3187 sipx_sdp::Attribute::valued("ice-options", sipx_sdp::ice::ICE2),
3188 ];
3189 attributes.extend(
3190 local
3191 .candidates
3192 .iter()
3193 .map(|candidate| sipx_sdp::Attribute::valued("candidate", candidate.to_value())),
3194 );
3195 attributes
3196}
3197
3198/// What a later offer says about the ICE session already running (RFC 8839 §4.4; `ice.md` §13.5).
3199///
3200/// Two variants and not a `bool`, because the wire difference between them is not a flag: a
3201/// continuing offer restates the credentials in force, and a restart states new ones. §4.4.1.1.1
3202/// makes *that change* the entire signal, so there is nothing else for either variant to set.
3203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3204enum IceOffer {
3205 /// Restate this side's half unchanged — hold, resume, a codec change, a session refresh.
3206 Continue,
3207 /// Draw new credentials and a new tiebreaker, which is what begins a new ICE session.
3208 Restart,
3209}
3210
3211/// The peer's ICE credentials as the description in this body states them, for [`Call::peer_ice`].
3212///
3213/// Read from the message that completed the initial exchange — the 2xx for a caller, the INVITE
3214/// for a callee — because a restart is only ever recognisable as a *change* from what was last
3215/// seen, and a call that recorded nothing would read the peer's first re-offer as one.
3216///
3217/// `None` for a description with no ICE, which is the ordinary call: nothing that arrives later can
3218/// restart a session that never began.
3219fn peer_ice_credentials(body: &[u8]) -> Option<sipx_sdp::ice::Credentials> {
3220 let description = sipx_sdp::parse(&String::from_utf8_lossy(body)).ok()?;
3221 let audio = description.media.first()?;
3222 match sipx_media::ice::negotiate(&description, audio) {
3223 IceNegotiation::Ice { credentials, .. } => Some(credentials),
3224 IceNegotiation::Absent | IceNegotiation::Mismatch => None,
3225 }
3226}
3227
3228/// Credentials and a tiebreaker for a new ICE session (RFC 8839 §5.4, RFC 8445 §7.1.3).
3229///
3230/// Drawn per session and never reused, for the reason `ice.md` §13.4 gives about the initial
3231/// exchange and which applies unchanged to a restart: credentials that outlive the session they
3232/// authenticated make one session's checks valid in another. A tiebreaker carried across would
3233/// resolve a role conflict the way the *previous* session resolved it.
3234///
3235/// `None` when credentials could not be built — the same failure `MediaPolicy::gathering` reports
3236/// on the initial exchange, from the same generator, so it is not reachable in practice. It
3237/// degrades rather than failing the renegotiation: the agent keeps the credentials it has, the
3238/// answer restates them, and the peer keys its new session's checks to those. RFC 8839 §4.4.1.1.1
3239/// asks the answerer for new ones; reusing them is worse than complying and much better than
3240/// refusing a re-offer on a call that is working.
3241fn fresh_ice_parameters() -> Option<(sipx_sdp::ice::Credentials, u64)> {
3242 let credentials = IceCredentials::new(token(), format!("{}{}", token(), token()))?;
3243 Some((credentials, rand::random()))
3244}
3245
3246/// The INVITE that opens a call.
3247///
3248/// Its own function only because `dial` had grown past the point where the interesting part —
3249/// what happens to the *response* — was visible among the header construction.
3250/// What identifies the dialog an INVITE is trying to create.
3251///
3252/// Held apart from the message so a retry can keep it. RFC 4028 §7.3 says a request re-sent
3253/// after a `422` "SHOULD have the same value as the Call-ID, To, and From of the previous
3254/// request" — a fresh identity would look to the far end like a second, unrelated call attempt
3255/// rather than the answer to the counter-offer it just made.
3256#[derive(Debug, Clone)]
3257struct Identity {
3258 call_id: String,
3259 from_tag: String,
3260 cseq: u32,
3261}
3262
3263impl Identity {
3264 fn fresh() -> Self {
3265 Self {
3266 call_id: format!("{}@sipx", token()),
3267 from_tag: token(),
3268 cseq: 1,
3269 }
3270 }
3271
3272 /// The same dialog, one transaction later.
3273 fn again(&self) -> Self {
3274 Self {
3275 cseq: self.cseq.saturating_add(1),
3276 ..self.clone()
3277 }
3278 }
3279}
3280
3281/// Everything that goes into the INVITE besides where it is being sent.
3282struct Invitation<'a> {
3283 to: &'a Uri,
3284 from: &'a str,
3285 via: &'a str,
3286 offer: Option<&'a SessionDescription>,
3287 session_expires: Option<Duration>,
3288 identity: &'a Identity,
3289 /// The pre-loaded route set, outermost first (RFC 3608 §6.1).
3290 service_route: &'a [String],
3291 /// Validated application-owned fields, preserved across retries.
3292 headers: &'a [sipx_sip::Header],
3293}
3294
3295fn build_invite(
3296 endpoint: &Handle,
3297 target: &Target,
3298 invitation: &Invitation<'_>,
3299) -> Result<Request> {
3300 let &Invitation {
3301 to,
3302 from,
3303 via,
3304 offer,
3305 session_expires,
3306 identity,
3307 service_route,
3308 headers,
3309 } = invitation;
3310 let Identity {
3311 call_id,
3312 from_tag,
3313 cseq,
3314 } = identity;
3315 let mut builder = RequestBuilder::new(Method::Invite, to.clone())
3316 .header(HeaderName::Via, Bytes::from(via.to_owned()))?
3317 .header(
3318 HeaderName::To,
3319 Bytes::from(format!("<{}>", String::from_utf8_lossy(&to.to_bytes()))),
3320 )?
3321 .header(
3322 HeaderName::From,
3323 Bytes::from(format!("{from};tag={from_tag}")),
3324 )?
3325 .header(HeaderName::CallId, Bytes::from(call_id.clone()))?
3326 .cseq(*cseq, &Method::Invite)?
3327 .header(
3328 HeaderName::Contact,
3329 Bytes::from(contact_for(endpoint, target.transport)),
3330 )?
3331 .max_forwards(70);
3332 if let Some(offer) = offer {
3333 builder = builder
3334 .header(
3335 HeaderName::ContentType,
3336 Bytes::from_static(b"application/sdp"),
3337 )?
3338 .body(Bytes::from(offer.to_string_sdp()));
3339 }
3340
3341 // One `Supported` row listing everything this side can do. Both tags are statements of
3342 // capability rather than requests: `timer` tells a far end that wants liveness detection
3343 // that it may have it, and `100rel` (RFC 3262 §4) is what permits the far end to send a
3344 // reliable provisional at all — §3 forbids it outright if we stay quiet, which means a
3345 // silent UAC gets unreliable ringing even from a UAS that would rather not send it.
3346 builder = builder.header(
3347 HeaderName::Supported,
3348 Bytes::from_static(b"timer, 100rel, histinfo"),
3349 )?;
3350 builder = builder.header(
3351 HeaderName::HistoryInfo,
3352 HistoryInfo::initial(to.clone()).to_bytes(),
3353 )?;
3354 // RFC 3311 §4: "A UAC compliant to this specification SHOULD also include an Allow header
3355 // field in the INVITE request, listing the method UPDATE." It is the only way the far end
3356 // is permitted to decide it may renegotiate the early session or refresh with an UPDATE
3357 // rather than a re-INVITE, so leaving it off does not merely omit a courtesy — it silently
3358 // forces every peer onto the heavier method for the life of the dialog.
3359 builder = builder.header(
3360 HeaderName::Allow,
3361 Bytes::from_static(update::ALLOW.as_bytes()),
3362 )?;
3363 if let Some(interval) = session_expires {
3364 // No `refresher` parameter. RFC 4028 Table 2 row 4 lets the UAS choose when the UAC
3365 // has not, and the UAS is the side that knows whether it is behind a NAT or a proxy
3366 // that cares. Naming ourselves would override a better-informed decision.
3367 let expires = SessionExpires {
3368 interval,
3369 refresher: None,
3370 };
3371 builder = builder
3372 .header(HeaderName::SessionExpires, Bytes::from(expires.to_string()))?
3373 .header(
3374 HeaderName::MinSe,
3375 Bytes::from(session::ABSOLUTE_MIN_INTERVAL.as_secs().to_string()),
3376 )?;
3377 }
3378 for header in headers {
3379 builder = builder.header(
3380 header.name().clone(),
3381 Bytes::copy_from_slice(header.raw_value()),
3382 )?;
3383 }
3384 // Pre-loaded Route, before the request goes anywhere. RFC 3608 §6.1 has the service route
3385 // used "as a preloaded Route header field in outgoing initial requests", and §6.1 requires
3386 // the order preserved — which `add_routes` does by appending in sequence. This is the only
3387 // place the INVITE can acquire it: a Route added after the transaction is created would not
3388 // be on the message the far end matched.
3389 let builder = add_routes(builder, service_route)?;
3390 Ok(builder.build())
3391}
3392
3393/// One digest answer to attach to a retried INVITE.
3394struct Authorization<'a> {
3395 challenge: &'a sipx_sip::auth::Challenge,
3396 credentials: &'a Credentials,
3397 nonce_count: u32,
3398 cnonce: &'a str,
3399}
3400
3401/// Add the header a 401 or 407 asked for, covering this request's method and URI.
3402fn authorize_invite(request: &mut Request, authorization: &Authorization<'_>) -> Result<()> {
3403 let uri = String::from_utf8_lossy(&request.uri.to_bytes()).into_owned();
3404 let method = String::from_utf8_lossy(request.method.as_bytes()).into_owned();
3405 let value = sipx_sip::auth::respond(
3406 authorization.challenge,
3407 authorization.credentials,
3408 &method,
3409 &uri,
3410 authorization.nonce_count,
3411 authorization.cnonce,
3412 );
3413 request.headers.push(sipx_sip::Header::build(
3414 authorization.challenge.response_header(),
3415 Bytes::from(value),
3416 )?);
3417 Ok(())
3418}
3419
3420/// The `Min-SE` a `422` demands, if it named one (RFC 4028 §6).
3421fn required_interval(response: &Response) -> Option<Duration> {
3422 response
3423 .headers
3424 .typed::<MinSe>()
3425 .and_then(std::result::Result::ok)
3426 .map(|min| min.0)
3427}
3428
3429/// Place a call, retrying once if the far end refuses the session interval.
3430///
3431/// RFC 4028 §7.3: a `422` is not a refusal of the call, it is a counter-offer of an interval.
3432/// Retrying is bounded to a single attempt on purpose — a peer that answers 422 to its own
3433/// stated minimum is broken, and a loop there is an outbound call flood.
3434pub async fn dial(
3435 endpoint: &Handle,
3436 target: Target,
3437 to: &Uri,
3438 options: &DialOptions,
3439) -> Result<Call> {
3440 dial_retrying(endpoint, target, to, options, true, None).await
3441}
3442
3443/// Place a call until it completes or `cancelled` resolves.
3444///
3445/// This has the same bounded authentication and session-interval retries as [`dial`]. If
3446/// cancellation wins while an INVITE is outstanding, the invitation is withdrawn before this
3447/// returns, including the ACK-then-BYE race when a successful final response was already in
3448/// flight. The returned [`Error::Cancelled`] distinguishes that local stop from a peer refusal.
3449pub async fn dial_until<F>(
3450 endpoint: &Handle,
3451 target: Target,
3452 to: &Uri,
3453 options: &DialOptions,
3454 cancelled: F,
3455) -> Result<Call>
3456where
3457 F: Future<Output = ()> + Send,
3458{
3459 tokio::pin!(cancelled);
3460 let cancelled: Pin<&mut (dyn Future<Output = ()> + Send)> = cancelled.as_mut();
3461 dial_retrying(endpoint, target, to, options, true, Some(cancelled)).await
3462}
3463
3464/// Place a call, surfacing a `422` instead of retrying it.
3465///
3466/// [`dial`] is this plus one retry, and is what almost everything wants. This is here for a
3467/// caller that would rather decide for itself what to do about an interval it does not like —
3468/// a gateway with a policy about how often it is willing to be woken, say.
3469pub async fn dial_once(
3470 endpoint: &Handle,
3471 target: Target,
3472 to: &Uri,
3473 options: &DialOptions,
3474) -> Result<Call> {
3475 dial_retrying(endpoint, target, to, options, false, None).await
3476}
3477
3478type Cancelled<'a> = Pin<&'a mut (dyn Future<Output = ()> + Send)>;
3479
3480/// Drive the two bounded retry reasons an initial INVITE has: authentication and session interval.
3481async fn dial_retrying(
3482 endpoint: &Handle,
3483 target: Target,
3484 to: &Uri,
3485 options: &DialOptions,
3486 retry_interval: bool,
3487 mut cancelled: Option<Cancelled<'_>>,
3488) -> Result<Call> {
3489 let credentials = options.credentials.clone();
3490 let mut attempted = options.clone();
3491 let mut identity = Identity::fresh();
3492 let mut challenge: Option<Box<sipx_sip::auth::Challenge>> = None;
3493 let mut nonce_use: Option<(String, u32)> = None;
3494 let mut stale_retried = false;
3495 let mut interval_retried = false;
3496
3497 loop {
3498 let cnonce = token();
3499 let authorization =
3500 challenge
3501 .as_deref()
3502 .zip(credentials.as_ref())
3503 .map(|(challenge, credentials)| Authorization {
3504 challenge,
3505 credentials,
3506 nonce_count: nonce_count_for(&mut nonce_use, &challenge.nonce),
3507 cnonce: &cnonce,
3508 });
3509 let result = dial_with(
3510 endpoint,
3511 target.clone(),
3512 to,
3513 &attempted,
3514 &identity,
3515 authorization.as_ref(),
3516 &mut cancelled,
3517 )
3518 .await;
3519
3520 match result {
3521 Err(Error::AuthenticationChallenge {
3522 status,
3523 reason,
3524 challenge: received,
3525 }) => {
3526 let rejected = || Error::Rejected {
3527 status,
3528 reason: reason.clone(),
3529 };
3530 if credentials.is_none() {
3531 return Err(rejected());
3532 }
3533 if challenge.is_none() {
3534 challenge = Some(received);
3535 } else if received.stale && !stale_retried {
3536 stale_retried = true;
3537 challenge = Some(received);
3538 } else {
3539 return Err(rejected());
3540 }
3541 }
3542 Err(Error::IntervalTooBrief(required)) if retry_interval && !interval_retried => {
3543 interval_retried = true;
3544 attempted.session_expires = Some(required.max(session::ABSOLUTE_MIN_INTERVAL));
3545 }
3546 other => return other,
3547 }
3548 identity = identity.again();
3549 }
3550}
3551
3552/// The count of requests sent under `nonce`, starting over when the nonce changes.
3553fn nonce_count_for(nonce_use: &mut Option<(String, u32)>, nonce: &str) -> u32 {
3554 let count = match nonce_use {
3555 Some((last, count)) if last == nonce => count.saturating_add(1),
3556 _ => 1,
3557 };
3558 *nonce_use = Some((nonce.to_owned(), count));
3559 count
3560}
3561
3562/// Bind the media port and build the INVITE that will advertise it.
3563///
3564/// Split out of `dial_with` only for length: what is interesting there is what happens to the
3565/// *response*, and it was buried under header construction.
3566async fn open_invitation(
3567 endpoint: &Handle,
3568 target: &Target,
3569 to: &Uri,
3570 options: &DialOptions,
3571 identity: &Identity,
3572 authorization: Option<&Authorization<'_>>,
3573) -> Result<(
3574 MediaPort,
3575 Capabilities,
3576 Option<LocalDescription>,
3577 PendingKeying,
3578 Request,
3579)> {
3580 validate_profile_preflight(options.media, target.transport)?;
3581 MediaAddress::new(options.media_address)
3582 .with_bind(options.media_bind_address)
3583 .validate()?;
3584 // The offer has to name the port audio will arrive on, and only a bound socket knows it.
3585 // So the port is bound now and the session started once the answer says where and in what.
3586 let port = MediaPort::bind(SocketAddr::new(options.media_bind_address, 0))
3587 .await
3588 .map_err(Error::Io)?;
3589
3590 let (capabilities, offer, ice, keying) =
3591 offered_media(options, &port, target.transport).await?;
3592
3593 // The `Via` is built here rather than left to the transport, because a CANCEL has to carry
3594 // the *same* branch as the INVITE it cancels — that identity is what matches the two at the
3595 // far end (RFC 3261 §9.1). Letting the transport generate it would leave this layer unable
3596 // to name the transaction it started.
3597 let via = format!(
3598 "SIP/2.0/{} {};rport;branch={}",
3599 target.transport.as_str(),
3600 endpoint.sent_by_for(target.transport),
3601 sipx_transport::new_branch()
3602 );
3603
3604 let mut invite = build_invite(
3605 endpoint,
3606 target,
3607 &Invitation {
3608 to,
3609 from: options.from.as_str(),
3610 via: &via,
3611 offer: Some(&offer),
3612 session_expires: options.session_expires,
3613 identity,
3614 service_route: &options.service_route,
3615 headers: &options.headers,
3616 },
3617 )?;
3618 if let Some(authorization) = authorization {
3619 authorize_invite(&mut invite, authorization)?;
3620 }
3621 if let Some(identity) = &options.identity {
3622 identity.sign(&mut invite)?;
3623 }
3624 Ok((port, capabilities, ice, keying, invite))
3625}
3626
3627/// Build the RFC 3262 delayed-offer form of an INVITE.
3628///
3629/// No media socket is bound yet because the remote offer determines whether there is a session
3630/// to answer. The socket is created when that offer arrives in a reliable provisional, before
3631/// its answer is placed in PRACK.
3632fn open_offerless_invitation(
3633 endpoint: &Handle,
3634 target: &Target,
3635 to: &Uri,
3636 options: &DialOptions,
3637 identity: &Identity,
3638) -> Result<Request> {
3639 MediaAddress::new(options.media_address)
3640 .with_bind(options.media_bind_address)
3641 .validate()?;
3642 let via = format!(
3643 "SIP/2.0/{} {};rport;branch={}",
3644 target.transport.as_str(),
3645 endpoint.sent_by_for(target.transport),
3646 sipx_transport::new_branch()
3647 );
3648 let mut invite = build_invite(
3649 endpoint,
3650 target,
3651 &Invitation {
3652 to,
3653 from: options.from.as_str(),
3654 via: &via,
3655 offer: None,
3656 session_expires: options.session_expires,
3657 identity,
3658 service_route: &options.service_route,
3659 headers: &options.headers,
3660 },
3661 )?;
3662 if let Some(identity) = &options.identity {
3663 identity.sign(&mut invite)?;
3664 }
3665 Ok(invite)
3666}
3667
3668/// Take back an invitation the caller has stopped waiting for.
3669///
3670/// Split out of `dial_with` for length, but it is the part with all the hazards in it, so it
3671/// keeps its own name: everything here is about not leaving the far end in a call.
3672async fn withdraw(
3673 endpoint: &Handle,
3674 invite: &Request,
3675 target: Target,
3676 responses: &mut sipx_transport::Responses,
3677 reason: &ReasonValue,
3678) {
3679 // Giving up is not just ceasing to wait. The far end is ringing and has been told
3680 // nothing; without a CANCEL it goes on ringing, and someone answering afterwards
3681 // ends up in a call with a party that has left.
3682 //
3683 // The transport operation owns §9.1's race: it waits until the exact INVITE has a provisional,
3684 // or returns the final/timeout/transport event that won instead. Events it observes remain on
3685 // `responses`, so the crossing-2xx safeguard below sees the same transaction history.
3686 let grace = tokio::time::Instant::now() + Duration::from_secs(2);
3687 match tokio::time::timeout_at(
3688 grace,
3689 Box::pin(endpoint.cancel_invite(responses, Some(Reason::from(reason.clone())))),
3690 )
3691 .await
3692 {
3693 Ok(Ok(sipx_transport::CancelInviteOutcome::Sent(_cancellation))) => {}
3694 Ok(Ok(sipx_transport::CancelInviteOutcome::FinalResponse { response, .. })) => {
3695 if response.status.is_success() {
3696 ack_then_bye(endpoint, invite, &response, target).await;
3697 }
3698 return;
3699 }
3700 Ok(Ok(_)) | Err(_) => return,
3701 // The loss is counted where transport output is attempted. This is already the giving-up
3702 // path; its remaining remedy is still to catch a crossing 2xx and ACK-then-BYE it.
3703 Ok(Err(error)) => tracing::debug!(%error, "could not create CANCEL transaction"),
3704 }
3705
3706 // CANCEL cannot close the race it exists to manage: a 200 already in flight
3707 // arrives anyway, and RFC 3261 §15 says a UAC that will not proceed must
3708 // acknowledge it and then hang up rather than leave it unanswered.
3709 while let Ok(Some(event)) = tokio::time::timeout_at(grace, responses.next()).await {
3710 let sipx_sip::transaction::TuEvent::Response(late) = event else {
3711 continue;
3712 };
3713 if !late.status.is_final() {
3714 continue;
3715 }
3716 if late.status.is_success() {
3717 ack_then_bye(endpoint, invite, &late, target.clone()).await;
3718 }
3719 break;
3720 }
3721}
3722
3723/// Acknowledge a 2xx this side will not proceed with, and hang the dialog up (RFC 3261 §15).
3724///
3725/// Both callers reach it having already put a 2xx beyond recall: [`withdraw`], where a CANCEL lost
3726/// its race, and [`dial_with`], where establishing the call failed after the far end already
3727/// believed one existed. Neither may simply walk away — an unacknowledged 2xx is retransmitted for
3728/// 32 seconds and then streamed at a port this side has closed.
3729///
3730/// Every step is best-effort by design. This runs on a path that is already failing, and a BYE
3731/// that cannot be built or sent must not mask the error that brought us here.
3732async fn ack_then_bye(endpoint: &Handle, invite: &Request, response: &Response, target: Target) {
3733 let Some(dialog) = Dialog::from_response(invite, response) else {
3734 return;
3735 };
3736 let in_dialog = in_dialog_target(&dialog, target);
3737 // discard: counted as `sipx_transport::UnsentCounts::ack`. An ACK for a 2xx that does not go
3738 // out is the worst of the three — it has no transaction to retry it (RFC 3261 §13.2.2.4), so
3739 // the far end retransmits its 2xx for thirty-two seconds and then streams at a port this side
3740 // has closed. This one goes out through `send_directly`, so the `Result` dropped here *does*
3741 // report the transmit; it is dropped because the BYE below is the only remedy and is attempted
3742 // whether or not the ACK landed, and returning the error would only mask the failure that
3743 // brought us here.
3744 let _ = send_ack(endpoint, &dialog, in_dialog.clone()).await;
3745 if let Ok(bye) = bye_request(
3746 &dialog,
3747 dialog.local_cseq.saturating_add(1),
3748 &normal_clearing_reason(),
3749 ) {
3750 // discard: counted as `sipx_transport::UnsentCounts::bye` — the number an operator asking
3751 // "why did that call linger" needs, because a BYE that does not reach the wire leaves a
3752 // dialog up at the far end that no timer reaps unless RFC 4028 session timers happen to be
3753 // running. The count is taken at the transmit and not from this `Result`, which reports
3754 // only that the transaction was created. Nothing here can retry it: this is the failure
3755 // path itself.
3756 let _ = endpoint.send(bye, in_dialog).await;
3757 }
3758}
3759
3760/// What a non-2xx final response means to the caller.
3761///
3762/// RFC 4028 §6's 422 is separated out because it is the one rejection that is *actionable*: it
3763/// names the interval the far end would accept, so a caller can retry with it rather than only
3764/// learn that it failed. Every other status is reported as it arrived.
3765fn rejection(response: &Response) -> Error {
3766 const INTERVAL_TOO_SMALL: u16 = 422;
3767 if response.status.code() == INTERVAL_TOO_SMALL
3768 && let Some(required) = required_interval(response)
3769 {
3770 return Error::IntervalTooBrief(required);
3771 }
3772 if matches!(response.status.code(), 401 | 407) {
3773 let from_proxy = response.status.code() == 407;
3774 let header = if from_proxy {
3775 HeaderName::ProxyAuthenticate
3776 } else {
3777 HeaderName::WwwAuthenticate
3778 };
3779 let challenges = response
3780 .headers
3781 .get_all(&header)
3782 .filter_map(|header| sipx_sip::auth::Challenge::parse(&header.value(), from_proxy))
3783 .collect();
3784 if let Some(challenge) = sipx_sip::auth::strongest(challenges) {
3785 return Error::AuthenticationChallenge {
3786 status: response.status.code(),
3787 reason: String::from_utf8_lossy(&response.reason).into_owned(),
3788 challenge: Box::new(challenge),
3789 };
3790 }
3791 }
3792 Error::Rejected {
3793 status: response.status.code(),
3794 reason: String::from_utf8_lossy(&response.reason).into_owned(),
3795 }
3796}
3797
3798#[allow(
3799 clippy::too_many_lines,
3800 reason = "the establishment sequence keeps every post-2xx path visibly ACK-safe"
3801)]
3802async fn dial_with(
3803 endpoint: &Handle,
3804 target: Target,
3805 to: &Uri,
3806 options: &DialOptions,
3807 identity: &Identity,
3808 authorization: Option<&Authorization<'_>>,
3809 cancelled: &mut Option<Cancelled<'_>>,
3810) -> Result<Call> {
3811 let media_address = options.media_address;
3812 let (port, capabilities, ice, keying, invite) =
3813 open_invitation(endpoint, &target, to, options, identity, authorization).await?;
3814
3815 let mut responses = endpoint.send(invite.clone(), target.clone()).await?;
3816
3817 let mut acknowledging = Acknowledging {
3818 endpoint,
3819 invite: &invite,
3820 target: &target,
3821 capabilities: &capabilities,
3822 seen: sipx_sip::rel::Sequence::default(),
3823 };
3824 let (response, ringing) = match await_final(
3825 &mut responses,
3826 options.timeout,
3827 &mut acknowledging,
3828 cancelled,
3829 )
3830 .await
3831 {
3832 Waited::Final { response, ringing } => (response, ringing),
3833 Waited::Gone => return Err(Error::NoResponse),
3834 Waited::Transport(error) => return Err(Error::Transport(error)),
3835 Waited::GaveUp => {
3836 withdraw(
3837 endpoint,
3838 &invite,
3839 target.clone(),
3840 &mut responses,
3841 &request_timeout_reason(),
3842 )
3843 .await;
3844 return Err(Error::Cancelled(options.timeout.unwrap_or(Duration::ZERO)));
3845 }
3846 Waited::Cancelled => {
3847 withdraw(
3848 endpoint,
3849 &invite,
3850 target.clone(),
3851 &mut responses,
3852 &normal_clearing_reason(),
3853 )
3854 .await;
3855 return Err(Error::Cancelled(Duration::ZERO));
3856 }
3857 };
3858
3859 if !response.status.is_success() {
3860 // A non-2xx is acknowledged by the transaction layer itself, so there is nothing to
3861 // send here — only a media port to release, which happens when `port` drops.
3862 return Err(rejection(&response));
3863 }
3864
3865 // From here the far end believes a dialog exists, so *every* path must acknowledge.
3866 // Returning an error without one leaves it retransmitting its 200 for 32 seconds and then
3867 // streaming media at a port we have closed.
3868 // Where in-dialog requests go if the 2xx carries no `Contact` to refresh the target with.
3869 let fallback = target.clone();
3870 match establish(
3871 &invite,
3872 &response,
3873 fallback,
3874 port,
3875 ice,
3876 &capabilities,
3877 options,
3878 ) {
3879 Ok((dialog, port, in_dialog, settled, ice, answer)) => {
3880 let ack = build_ack(endpoint, &dialog, &in_dialog)?;
3881 endpoint
3882 .send_directly(ack.clone(), in_dialog.clone())
3883 .await?;
3884 // The stream stays open rather than being dropped here: a retransmitted 2xx means
3885 // this ACK was lost and RFC 3261 §13.2.2.4 requires another (see
3886 // `reack_retransmitted_2xx`).
3887 tokio::spawn(reack_retransmitted_2xx(
3888 endpoint.clone(),
3889 responses,
3890 ack,
3891 in_dialog.clone(),
3892 ));
3893 // RFC 5763 peers are allowed to wait for the SIP exchange to complete before opening
3894 // the media connection. In particular, the ACK must be on the wire before a selected
3895 // DTLS handshake can wait for that peer, or two correct endpoints can deadlock.
3896 let (media, settled) = match key_and_start(
3897 port,
3898 ice,
3899 settled,
3900 keying,
3901 &answer,
3902 false,
3903 options.media.profile,
3904 )
3905 .await
3906 {
3907 Ok(started) => started,
3908 Err(error) => {
3909 // The 2xx was already acknowledged, so RFC 3261 §15 tears down the dialog
3910 // whose selected media path could not be keyed. The transport counts an
3911 // unsent BYE; the result is discarded so it cannot mask the DTLS error.
3912 if let Ok(bye) = bye_request(
3913 &dialog,
3914 dialog.local_cseq.saturating_add(1),
3915 &normal_clearing_reason(),
3916 ) {
3917 // discard: the original DTLS failure is the cause returned to the
3918 // caller; a best-effort teardown failure must not replace it.
3919 let _ = endpoint.send(bye, in_dialog).await;
3920 }
3921 return Err(error);
3922 }
3923 };
3924 // Emitted at construction — the earliest point this call has a stream anyone could
3925 // read from — from what was actually observed while waiting for the final response,
3926 // not reconstructed later from anything left lying around.
3927 let (events, events_rx) = EventSink::new();
3928 emit_construction_events(&events, ringing);
3929 Ok(Call {
3930 dialog,
3931 initial_status: response.status.code(),
3932 media: Arc::new(media),
3933 retired_media: Vec::new(),
3934 endpoint: endpoint.clone(),
3935 target: in_dialog,
3936 ack_stop: None,
3937 ack_retransmission: None,
3938 delayed_offer: None,
3939 ended: false,
3940 media_address,
3941 media_bind_address: options.media_bind_address,
3942 codecs: options.media.codecs,
3943 profile: options.media.profile,
3944 current: settled.negotiated,
3945 peer_ice: peer_ice_credentials(response.body()),
3946 encrypted: options.media.profile == MediaProfile::BrowserAudio
3947 || settled.srtp.is_some(),
3948 keying: options.media.keying,
3949 hold: Direction::SendRecv,
3950 referral: None,
3951 transfer: None,
3952 session: session::adopt(
3953 response
3954 .headers
3955 .typed::<SessionExpires>()
3956 .and_then(std::result::Result::ok),
3957 options.session_expires,
3958 )
3959 .map(SessionState::armed),
3960 negotiation: update::Negotiation::idle(),
3961 // From the 2xx, which RFC 3311 §4 asks to carry it. A dialog that reaches here
3962 // has completed one offer/answer exchange, so nothing is outstanding either way.
3963 peer_allows_update: update::peer_allows(&response.headers),
3964 events,
3965 events_rx: Some(events_rx),
3966 history: HistoryInfo::from_headers(&response.headers)
3967 .and_then(std::result::Result::ok),
3968 dialog_credentials: options.credentials.clone(),
3969 admitted_dialog_methods: Vec::new(),
3970 })
3971 }
3972 Err(error) => {
3973 // RFC 3261 §15: a UAC that cannot proceed after a 2xx acknowledges it and then
3974 // sends BYE. Walking away silently is what leaves the far end streaming.
3975 ack_then_bye(endpoint, &invite, &response, target).await;
3976 Err(error)
3977 }
3978 }
3979}
3980
3981/// Everything after a 2xx that can fail, kept together so the caller can ACK on either path.
3982///
3983/// `offered` and `options` are taken whole rather than as the two fields read out of them, because
3984/// both are the same question asked twice — what this side put in the offer — and a call site that
3985/// passed a crypto list from one place and a codec set from another could pass two that disagree.
3986fn establish(
3987 invite: &Request,
3988 response: &Response,
3989 fallback: Target,
3990 port: MediaPort,
3991 ice: Option<LocalDescription>,
3992 offered: &Capabilities,
3993 options: &DialOptions,
3994) -> Result<(
3995 Dialog,
3996 MediaPort,
3997 Target,
3998 Settled,
3999 Option<LocalDescription>,
4000 SessionDescription,
4001)> {
4002 let answer = sipx_sdp::parse(&String::from_utf8_lossy(response.body()))
4003 .map_err(|error| Error::Sdp(error.to_string()))?;
4004 validate_establishment_answer(options.media.profile, invite.body(), &answer)?;
4005 let settled = settle_answer(offered, &answer, options.media.codecs)?;
4006 let dialog = Dialog::from_response(invite, response).ok_or(Error::NoDialog)?;
4007 let target = in_dialog_target(&dialog, fallback);
4008 let ice = match ice {
4009 Some(mut local) => {
4010 let negotiation = answer
4011 .media
4012 .first()
4013 .map_or(IceNegotiation::Absent, |audio| {
4014 sipx_media::ice::negotiate(&answer, audio)
4015 });
4016 local.accept(&negotiation);
4017 Some(local)
4018 }
4019 None => None,
4020 };
4021 Ok((dialog, port, target, settled, ice, answer))
4022}
4023
4024/// Hold the named profile boundary ahead of every stateful part of answer application.
4025///
4026/// The generic codec settlement below this function is intentionally more permissive than the
4027/// browser-audio contract. Running the complete exchange validator here keeps an invalid answer
4028/// from reaching `LocalDescription::accept`, ACK transmission, ICE checks, or DTLS setup.
4029fn validate_establishment_answer(
4030 profile: MediaProfile,
4031 offered: &[u8],
4032 answer: &SessionDescription,
4033) -> Result<()> {
4034 if profile == MediaProfile::BrowserAudio {
4035 let offered = sipx_sdp::parse(&String::from_utf8_lossy(offered))
4036 .map_err(|error| Error::Sdp(error.to_string()))?;
4037 // discard: the complete relation is checked here; the generic settlement immediately
4038 // below derives the retained codec/ICE facts from this same validated answer.
4039 let _ = sipx_sdp::browser_audio::validate_answer(
4040 &offered,
4041 answer,
4042 sipx_sdp::fingerprint::SetupCapabilities::both(),
4043 )?;
4044 }
4045 Ok(())
4046}
4047
4048/// What the far end's answer to *our* offer settles.
4049///
4050/// The calling side's counterpart of [`Early::settle`], and the reason it is a function is that
4051/// an answer can now reach us in two places: the 200 that [`establish`] reads, and — once
4052/// [`dial_early`] exists — the reliable provisional that makes an early dialog renegotiable at
4053/// all (RFC 3262 §5). There is no port to bind on either path, because ours was bound before the
4054/// INVITE named it.
4055fn settle_answer(
4056 offered: &Capabilities,
4057 answer: &SessionDescription,
4058 codecs: Codecs,
4059) -> Result<Settled> {
4060 // Both halves or neither, *and* the two halves have to be the ones the two ends agreed on:
4061 // a stream keyed at one end only is a call that connects and carries silence, and one keyed
4062 // on an answer that echoed a tag nobody sent is a call encrypted to nothing. Neither is
4063 // worth having, so both come back as `Error::Sdp` rather than as a quietly plain call.
4064 let answered = answered_crypto(answer);
4065 let mut negotiated = negotiated(answer, codecs)?;
4066 let local_offer = offer_from(offered);
4067 let local_audio = local_offer
4068 .media
4069 .iter()
4070 .find(|media| media.media == "audio" && !media.is_rejected())
4071 .ok_or(Error::NoCommonCodec)?;
4072 negotiated.receive_payload_type = local_audio
4073 .formats
4074 .iter()
4075 .find_map(|format| {
4076 let (codec, payload_type, clock_rate) = codec_of(local_audio, format)?;
4077 (codec == negotiated.codec && clock_rate == negotiated.clock_rate)
4078 .then_some(payload_type)
4079 })
4080 .ok_or(Error::NoCommonCodec)?;
4081 let answered_mux = answer
4082 .media
4083 .iter()
4084 .find(|media| media.media == "audio" && !media.is_rejected())
4085 .is_some_and(sipx_sdp::MediaDescription::rtcp_mux);
4086 negotiated.rtcp_mode = if offered.rtcp_mux && answered_mux {
4087 sipx_sdp::RtcpMode::Mux
4088 } else {
4089 sipx_sdp::RtcpMode::Separate
4090 };
4091 Ok(Settled {
4092 negotiated,
4093 srtp: srtp_keys(offered.crypto.as_slice(), answered.as_ref())?,
4094 })
4095}
4096
4097/// Resolve the local handshake role from the peer description using SDP's shared level fallback.
4098fn dtls_local_setup(
4099 peer_description: &SessionDescription,
4100 local_is_answerer: bool,
4101) -> Result<sipx_sdp::fingerprint::Setup> {
4102 let audio = peer_description.media.first().ok_or(Error::NoCommonCodec)?;
4103 let peer_setup = sipx_sdp::answer::setup_of(peer_description, audio);
4104 let roles = sipx_sdp::fingerprint::SetupCapabilities::both();
4105 if local_is_answerer {
4106 roles
4107 .answer_to(peer_setup.unwrap_or(sipx_sdp::fingerprint::Setup::ActPass))
4108 .map_err(Error::from)
4109 } else {
4110 roles.from_answer(peer_setup).map_err(Error::from)
4111 }
4112}
4113
4114/// Reject an unusable DTLS offer before binding or gathering for its answer.
4115fn validate_dtls_offer_setup(offer: &SessionDescription, policy: MediaPolicy) -> Result<()> {
4116 if policy.keying == Keying::DtlsSrtp {
4117 // discard: validation is the side effect; the selected role is resolved again when the
4118 // handshake starts, after the successful answer has been transmitted.
4119 let _ = dtls_local_setup(offer, true)?;
4120 }
4121 Ok(())
4122}
4123
4124/// Complete selected keying and only then start the media workers on the same bound port.
4125#[cfg_attr(not(feature = "dtls"), allow(unused_mut, clippy::unused_async))]
4126async fn key_and_start(
4127 port: MediaPort,
4128 ice: Option<LocalDescription>,
4129 mut settled: Settled,
4130 keying: PendingKeying,
4131 peer_description: &SessionDescription,
4132 local_is_answerer: bool,
4133 profile: MediaProfile,
4134) -> Result<(MediaSession, Settled)> {
4135 #[cfg(not(feature = "dtls"))]
4136 // discard: these inputs select DTLS roles only; the feature-off build has no such branch.
4137 let _ = (peer_description, local_is_answerer, profile);
4138 #[cfg(feature = "dtls")]
4139 if profile == MediaProfile::BrowserAudio {
4140 let remote_role = if local_is_answerer {
4141 sipx_sdp::browser_audio::BrowserAudioRole::Offerer
4142 } else {
4143 sipx_sdp::browser_audio::BrowserAudioRole::Answerer
4144 };
4145 let remote = sipx_sdp::browser_audio::validate(peer_description, remote_role)?;
4146 if !local_is_answerer
4147 && remote.payloads
4148 != (sipx_sdp::browser_audio::BrowserAudioPayloads {
4149 opus: 111,
4150 pcmu: 0,
4151 pcma: 8,
4152 comfort_noise: 13,
4153 telephone_event: 101,
4154 })
4155 {
4156 return Err(sipx_sdp::browser_audio::ProfileError::CodecSetIncomplete.into());
4157 }
4158 let local = ice.ok_or(sipx_sdp::browser_audio::ProfileError::IceRequired)?;
4159 let PendingKeying::Dtls(identity) = keying else {
4160 return Err(sipx_sdp::browser_audio::ProfileError::WeakerMedia.into());
4161 };
4162 let local_setup = dtls_local_setup(peer_description, local_is_answerer)?;
4163 let role = match local_setup {
4164 sipx_sdp::fingerprint::Setup::Active => sipx_media::dtls::Role::Client,
4165 sipx_sdp::fingerprint::Setup::Passive => sipx_media::dtls::Role::Server,
4166 _ => return Err(sipx_sdp::browser_audio::ProfileError::SetupRole.into()),
4167 };
4168 let media = port
4169 .start_browser_audio(
4170 settled.media_config(),
4171 local,
4172 0,
4173 identity,
4174 role,
4175 remote.fingerprint,
4176 Duration::from_secs(5),
4177 )
4178 .await
4179 .map_err(browser_start_error)?;
4180 return Ok((media, settled));
4181 }
4182 match keying {
4183 PendingKeying::Sdes => {}
4184 #[cfg(feature = "dtls")]
4185 PendingKeying::Dtls(identity) => {
4186 let audio = peer_description.media.first().ok_or(Error::NoCommonCodec)?;
4187 let fingerprint = audio
4188 .fingerprint()
4189 .or_else(|| peer_description.fingerprint())
4190 .ok_or_else(|| Error::Sdp("the DTLS peer supplied no fingerprint".to_owned()))?;
4191 let local_setup = dtls_local_setup(peer_description, local_is_answerer)?;
4192 let role = match local_setup {
4193 sipx_sdp::fingerprint::Setup::Active => sipx_media::dtls::Role::Client,
4194 sipx_sdp::fingerprint::Setup::Passive => sipx_media::dtls::Role::Server,
4195 _ => {
4196 return Err(Error::Sdp(
4197 "the DTLS exchange did not select active or passive".to_owned(),
4198 ));
4199 }
4200 };
4201 let remote = settled.negotiated.remote;
4202 let (keyed, keys) = port
4203 .key_with_dtls(identity, remote, role, fingerprint, Duration::from_secs(5))
4204 .await
4205 .map_err(|error| Error::Dtls(error.to_string()))?;
4206 settled.srtp = Some(keys);
4207 let media = match ice {
4208 Some(local) => keyed.start_with_ice(settled.media_config(), local)?,
4209 None => keyed.start(settled.media_config())?,
4210 };
4211 return Ok((media, settled));
4212 }
4213 }
4214 let media = match ice {
4215 Some(local) => port.start_with_ice(settled.media_config(), local)?,
4216 None => port.start(settled.media_config())?,
4217 };
4218 Ok((media, settled))
4219}
4220
4221#[cfg(feature = "dtls")]
4222fn browser_start_error(error: sipx_media::browser::BrowserStartError) -> Error {
4223 use sipx_media::browser::BrowserStartError;
4224 match error {
4225 BrowserStartError::IceFailed | BrowserStartError::IceStopped => {
4226 sipx_sdp::browser_audio::ProfileError::NoNominatedPair.into()
4227 }
4228 BrowserStartError::RtcpMuxRequired => {
4229 sipx_sdp::browser_audio::ProfileError::RtcpMuxRequired.into()
4230 }
4231 BrowserStartError::DtlsTimeout => sipx_sdp::browser_audio::ProfileError::DtlsTimeout.into(),
4232 BrowserStartError::Dtls(sipx_media::dtls::Error::FingerprintMismatch) => {
4233 sipx_sdp::browser_audio::ProfileError::FingerprintMismatch.into()
4234 }
4235 BrowserStartError::Dtls(sipx_media::dtls::Error::NoProfile) => {
4236 sipx_sdp::browser_audio::ProfileError::NoSrtpProfile.into()
4237 }
4238 BrowserStartError::Setup(error) => Error::Media(error),
4239 other => Error::Dtls(other.to_string()),
4240 }
4241}
4242
4243/// Answer an incoming INVITE.
4244///
4245/// The 200 OK is retransmitted until the ACK arrives, which is the transaction user's job:
4246/// `sipx-sip`'s server transaction moves to `Accepted` and absorbs retransmissions of the
4247/// *request*, but it does not resend the response. Over UDP one lost 200 means the caller
4248/// gives up while this side holds an established call, so this is not optional.
4249///
4250/// Answers from the default codec set, [`Codecs::G711`]. [`answer_with`] takes a selection.
4251pub async fn answer(endpoint: &Handle, incoming: &Incoming, media_address: IpAddr) -> Result<Call> {
4252 answer_at(endpoint, incoming, MediaAddress::new(media_address)).await
4253}
4254
4255/// [`answer`] with independent advertised and bound media addresses.
4256pub async fn answer_at(
4257 endpoint: &Handle,
4258 incoming: &Incoming,
4259 media_address: MediaAddress,
4260) -> Result<Call> {
4261 answer_tagged(
4262 endpoint,
4263 incoming,
4264 media_address,
4265 &token(),
4266 None,
4267 MediaPolicy::default(),
4268 &[],
4269 )
4270 .await
4271}
4272
4273/// [`answer`], from a chosen codec set rather than the default one (`M-30`).
4274///
4275/// The answering counterpart of [`DialOptions::with_codecs`]. `codecs` bounds what the answer may
4276/// settle on, and no more than that: RFC 3264 §6.1 gives the *order* to the offerer, so a caller
4277/// offering G.711 first is answered G.711 first even from [`Codecs::Opus`]. What the selection
4278/// decides is whether Opus is on the table at all — an offer of it answered from
4279/// [`Codecs::G711`] is answered G.711, because a call must never settle on a codec the
4280/// application did not ask to carry.
4281pub async fn answer_with(
4282 endpoint: &Handle,
4283 incoming: &Incoming,
4284 media_address: IpAddr,
4285 codecs: Codecs,
4286) -> Result<Call> {
4287 answer_with_policy_at(
4288 endpoint,
4289 incoming,
4290 MediaAddress::new(media_address),
4291 MediaPolicy::default().with_codecs(codecs),
4292 )
4293 .await
4294}
4295
4296/// Answer using one coherent codec, security and ICE policy.
4297pub async fn answer_with_policy(
4298 endpoint: &Handle,
4299 incoming: &Incoming,
4300 media_address: IpAddr,
4301 policy: MediaPolicy,
4302) -> Result<Call> {
4303 answer_with_policy_at(endpoint, incoming, MediaAddress::new(media_address), policy).await
4304}
4305
4306/// [`answer_with_policy`] with independent advertised and bound media addresses.
4307pub async fn answer_with_policy_at(
4308 endpoint: &Handle,
4309 incoming: &Incoming,
4310 media_address: MediaAddress,
4311 policy: MediaPolicy,
4312) -> Result<Call> {
4313 answer_tagged(
4314 endpoint,
4315 incoming,
4316 media_address,
4317 &token(),
4318 None,
4319 policy,
4320 &[],
4321 )
4322 .await
4323}
4324
4325/// Answer using one coherent media policy and validated application-owned response fields.
4326pub async fn answer_with_policy_and_headers(
4327 endpoint: &Handle,
4328 incoming: &Incoming,
4329 media_address: IpAddr,
4330 policy: MediaPolicy,
4331 headers: &[sipx_sip::Header],
4332) -> Result<Call> {
4333 answer_with_policy_and_headers_at(
4334 endpoint,
4335 incoming,
4336 MediaAddress::new(media_address),
4337 policy,
4338 headers,
4339 )
4340 .await
4341}
4342
4343/// [`answer_with_policy_and_headers`] with independent advertised and bound media addresses.
4344pub async fn answer_with_policy_and_headers_at(
4345 endpoint: &Handle,
4346 incoming: &Incoming,
4347 media_address: MediaAddress,
4348 policy: MediaPolicy,
4349 headers: &[sipx_sip::Header],
4350) -> Result<Call> {
4351 answer_tagged(
4352 endpoint,
4353 incoming,
4354 media_address,
4355 &token(),
4356 None,
4357 policy,
4358 headers,
4359 )
4360 .await
4361}
4362
4363/// The same, with the `To` tag chosen by the caller rather than freshly minted.
4364///
4365/// [`Invitation::answer`](crate::Invitation::answer) uses it so that every response this stack
4366/// sends about one invitation carries one tag — the `200` accepting it, and the `200` that
4367/// [`Dispatcher`](crate::Dispatcher) sends for a CANCEL that arrives too late to stop it. RFC 3261
4368/// §9.2 asks for exactly that agreement ("the `To` tag of the response to the CANCEL and the `To`
4369/// tag in the response to the original request SHOULD be the same"), and it can only be honoured
4370/// by whoever owns both, which is the invitation.
4371pub(crate) async fn answer_tagged(
4372 endpoint: &Handle,
4373 incoming: &Incoming,
4374 media_address: MediaAddress,
4375 tag: &str,
4376 claim: Option<Claim<'_>>,
4377 policy: MediaPolicy,
4378 headers: &[sipx_sip::Header],
4379) -> Result<Call> {
4380 // Ahead of the claim, deliberately: an offer that cannot be read fails here with nothing
4381 // sent, and an invitation that was never taken is one a CANCEL can still end.
4382 let offer = sipx_sdp::parse(&String::from_utf8_lossy(incoming.request.body()))
4383 .map_err(|error| Error::Sdp(error.to_string()))?;
4384 // No provisional was sent on this path, so there is nothing to report as `Ringing`.
4385 answer_negotiated(
4386 endpoint,
4387 incoming,
4388 media_address,
4389 offer,
4390 tag,
4391 None,
4392 claim,
4393 policy,
4394 headers,
4395 )
4396 .await
4397}
4398
4399/// The media an invitation has bound, and what the far end has said about it.
4400///
4401/// An enum rather than two `Option`s because exactly one is true at a time, and the difference
4402/// between them is the whole of RFC 3311 §5.1's precondition: a session that has been offered
4403/// and not answered may not be renegotiated, and one that has been answered may.
4404#[derive(Debug)]
4405enum EarlyMedia {
4406 /// Bound, and named in the INVITE's offer. The far end has not answered it yet.
4407 Offered(MediaPort),
4408 /// The INVITE carried no offer. A reliable provisional may supply one for this side to
4409 /// answer in PRACK (RFC 3262 section 5).
4410 WaitingForOffer,
4411 /// Answered in a reliable provisional (RFC 3262 §5), and renegotiable from here.
4412 Answered(Box<Early>),
4413}
4414
4415/// An invitation this side has placed, which the far end has not yet answered.
4416///
4417/// The calling side's counterpart of [`Ringing`](crate::Ringing), and the reason it is a separate
4418/// entry point is that [`dial`] cannot be both. `dial` waits for the final response inside
4419/// itself, which is what almost every application wants and is why its signature is unchanged;
4420/// but an application that wants to do anything *while* the far end rings has to hold the early
4421/// dialog, and before this there was no moment at which it could.
4422///
4423/// What it holds is what the eventual [`Call`] will need: the INVITE's still-open response
4424/// stream, the media port bound before the offer named it, and — once a provisional creates one —
4425/// the dialog itself. [`Self::answered`] hands all three over rather than rebuilding them, which
4426/// matters most for the dialog: its sequence space already carries the PRACK and any UPDATE sent
4427/// while ringing, and a dialog built afresh from the 2xx would restart that space at the INVITE's
4428/// own number, putting the first BYE behind a request the far end has already seen (RFC 3261
4429/// §12.2.1.1).
4430///
4431/// **Nothing happens on its own.** A `Dialing` dropped without [`Self::answered`] or
4432/// [`Self::cancel`] leaves the far end ringing, exactly as a [`Call`] dropped without
4433/// [`Call::hang_up`] leaves the far end in a call. The discipline is the application's: making it
4434/// implicit would mean withdrawing an invitation from a destructor that cannot await the CANCEL
4435/// it sends, nor the `200` that may cross it.
4436#[derive(Debug)]
4437pub struct Dialing {
4438 endpoint: Handle,
4439 /// The INVITE itself. A CANCEL must repeat its identity, and a PRACK its sequence number.
4440 invite: Request,
4441 /// Where the INVITE was sent, and the fallback for in-dialog requests.
4442 target: Target,
4443 /// Where in-dialog requests go, once a `Contact` has said somewhere better.
4444 in_dialog: Target,
4445 /// The INVITE transaction, still open. `None` once [`Self::answered`] has handed it to
4446 /// `reack_retransmitted_2xx`, which is the only other thing entitled to read from it.
4447 responses: Option<sipx_transport::Responses>,
4448 /// The early dialog a provisional established (RFC 3261 §12.1.1).
4449 dialog: Option<Dialog>,
4450 /// Which reliable provisionals have been acknowledged (RFC 3262 §4).
4451 seen: sipx_sip::rel::Sequence,
4452 /// `None` only after [`Self::answered`] has handed the port to the [`Call`].
4453 media: Option<EarlyMedia>,
4454 /// A delayed offer prepared for the target leg but not `PRACKed` until the source answers it.
4455 coupled_prack: Option<CoupledPrack>,
4456 /// A final response that crossed the held PRACK; confirmed only after that PRACK leaves.
4457 coupled_final: Option<Box<Response>>,
4458 /// The ICE agent gathered for the same port, retained until an answer supplies its peer half.
4459 ice: Option<LocalDescription>,
4460 /// What the INVITE offered, kept because an SRTP answer has to be paired with the offer it
4461 /// answers and because a later UPDATE offers from the same starting point.
4462 capabilities: Option<Capabilities>,
4463 negotiation: update::Negotiation,
4464 peer_allows_update: bool,
4465 /// The direction the last UPDATE from this side set, carried into the [`Call`] so that an
4466 /// invitation put on hold before it was answered is answered on hold.
4467 hold: Direction,
4468 /// Whether anything past a bare `100 Trying` arrived, and whether it was reliable — the
4469 /// same thing [`Waited::Final`] carries, and for the same reason.
4470 ringing: Option<bool>,
4471 /// When to stop waiting, counted from when the INVITE went out rather than from each call
4472 /// to [`Self::answered`] — the far end is ringing against one deadline, not a fresh one per
4473 /// method call.
4474 deadline: Option<tokio::time::Instant>,
4475 options: DialOptions,
4476 /// A final response that arrived before the application was handed anything.
4477 ///
4478 /// A far end that goes straight from the INVITE to a `200` never gives its caller an early
4479 /// dialog. That is not a failure — the call is perfectly good — so it is completed here and
4480 /// [`Self::answered`] hands it over at once. Completed rather than parked, because a `2xx`
4481 /// held while an application decides what to do with a handle is a `2xx` the far end is
4482 /// retransmitting (RFC 3261 §13.2.2.4).
4483 answered_already: Option<Box<Call>>,
4484 /// The event stream begins with this app-visible attempt, before a `Call` exists.
4485 events: Option<EventSink>,
4486 /// Handed out once by [`Self::events`], or moved into the confirmed [`Call`].
4487 events_rx: Option<CallEvents>,
4488}
4489
4490/// What one read from the INVITE transaction produced.
4491enum Arrived {
4492 /// A provisional response.
4493 Provisional(Box<Response>),
4494 /// A final response.
4495 Final(Box<Response>),
4496 /// The deadline passed.
4497 GaveUp,
4498 /// The transaction ended without a final response.
4499 Gone,
4500}
4501
4502/// One early-dial event surfaced to the owning two-dialog coupling.
4503pub(crate) enum CouplingDialEvent {
4504 /// A provisional was consumed and any required PRACK was sent.
4505 Progress,
4506 /// A reliable provisional carried an offer whose PRACK is held for the source leg's answer.
4507 ReliableOffer(Direction),
4508 /// The outbound invitation confirmed.
4509 Answered(Box<Call>),
4510 /// One routed in-dialog request arrived, or that route closed.
4511 Incoming(Box<Option<Incoming>>),
4512}
4513
4514#[derive(Debug)]
4515struct CoupledPrack {
4516 response: Box<Response>,
4517 rseq: u32,
4518 answer: SessionDescription,
4519}
4520
4521/// Place a call and get the early dialog, rather than waiting for the call itself.
4522///
4523/// [`dial`] and [`dial_once`] wait for the final response and hand back a [`Call`]; this hands
4524/// back a [`Dialing`] as soon as the far end has established a dialog, so the application can act
4525/// while it rings — renegotiate the session with an UPDATE (RFC 3311 §5.1), answer one, or read
4526/// the description a provisional carried. [`Dialing::answered`] then waits for the call exactly
4527/// as `dial` would have.
4528///
4529/// It returns as soon as *a dialog* exists, which is not the same as an answered session: a far
4530/// end that rings `180` with no body has established a dialog and described nothing.
4531/// [`Dialing::has_early_session`] is what distinguishes them, and it is what
4532/// [`Dialing::update`] requires.
4533///
4534/// Unlike [`dial`] there is no retry on a `422`. The retry is a *second* INVITE, and the handle
4535/// an application would be holding names the first; [`Error::IntervalTooBrief`] comes back from
4536/// [`Dialing::answered`] instead, as it does from [`dial_once`].
4537///
4538/// # Errors
4539///
4540/// Fails if the INVITE cannot be built or sent, if the deadline passes before any dialog is
4541/// established — in which case the invitation is withdrawn first, so the far end stops ringing —
4542/// or if the transaction ends with no response at all.
4543///
4544/// It also fails if a reliable provisional answers our offer with a description that cannot be
4545/// used: [`Error::Sdp`] for a body that does not parse or an `a=crypto` that fails RFC 4568
4546/// §5.1.3's check, [`Error::NoCommonCodec`] for one that cannot be negotiated. The invitation is
4547/// withdrawn with a CANCEL first (RFC 3261 §9.1), so no handle to a dead invitation comes back. A
4548/// provisional carrying *no* description is not this case and is not an error.
4549pub async fn dial_early(
4550 endpoint: &Handle,
4551 target: Target,
4552 to: &Uri,
4553 options: &DialOptions,
4554) -> Result<Dialing> {
4555 let mut dialing = begin_dial_early(endpoint, target, to, options).await?;
4556 dialing.reach_early_dialog().await?;
4557 Ok(dialing)
4558}
4559
4560/// Place a call until an early dialog exists or `cancelled` resolves.
4561///
4562/// This is the cancellation-safe early-dialog counterpart of [`dial_until`]. If cancellation
4563/// wins after the INVITE has left, the invitation is withdrawn before this returns, including
4564/// the ACK-then-BYE cleanup when a successful final response crossed the cancellation. A caller
4565/// that continues waiting for confirmation can use [`Dialing::answered_until`] with the same
4566/// cancellation signal.
4567///
4568/// # Errors
4569///
4570/// The same setup and early-dialog errors as [`dial_early`]. Local cancellation returns
4571/// [`Error::Cancelled`] after the outstanding invitation has been cleaned up.
4572pub async fn dial_early_until<F>(
4573 endpoint: &Handle,
4574 target: Target,
4575 to: &Uri,
4576 options: &DialOptions,
4577 cancelled: F,
4578) -> Result<Dialing>
4579where
4580 F: Future<Output = ()> + Send,
4581{
4582 let mut dialing = begin_dial_early(endpoint, target, to, options).await?;
4583 tokio::pin!(cancelled);
4584 tokio::select! {
4585 biased;
4586 () = cancelled.as_mut() => {
4587 dialing.give_up().await;
4588 Err(Error::Cancelled(Duration::ZERO))
4589 }
4590 result = dialing.reach_early_dialog() => {
4591 result?;
4592 Ok(dialing)
4593 }
4594 }
4595}
4596
4597async fn begin_dial_early(
4598 endpoint: &Handle,
4599 target: Target,
4600 to: &Uri,
4601 options: &DialOptions,
4602) -> Result<Dialing> {
4603 if options.media.keying == Keying::DtlsSrtp {
4604 return Err(Error::DtlsEarlyMedia);
4605 }
4606 let (port, capabilities, ice, _keying, invite) =
4607 open_invitation(endpoint, &target, to, options, &Identity::fresh(), None).await?;
4608 let responses = endpoint.send(invite.clone(), target.clone()).await?;
4609
4610 let (events, events_rx) = EventSink::new();
4611 Ok(Dialing {
4612 endpoint: endpoint.clone(),
4613 in_dialog: target.clone(),
4614 invite,
4615 target,
4616 responses: Some(responses),
4617 dialog: None,
4618 seen: sipx_sip::rel::Sequence::default(),
4619 media: Some(EarlyMedia::Offered(port)),
4620 coupled_prack: None,
4621 coupled_final: None,
4622 ice,
4623 capabilities: Some(capabilities),
4624 // RFC 3264: the INVITE carried our offer, so an exchange is open until the far end
4625 // answers it — which before the 200 can only happen in a reliable provisional.
4626 negotiation: update::Negotiation::offering(),
4627 peer_allows_update: false,
4628 hold: Direction::SendRecv,
4629 ringing: None,
4630 deadline: options
4631 .timeout
4632 .map(|limit| tokio::time::Instant::now() + limit),
4633 options: options.clone(),
4634 answered_already: None,
4635 events: Some(events),
4636 events_rx: Some(events_rx),
4637 })
4638}
4639
4640/// Place an offerless INVITE and answer an offer from a reliable provisional in PRACK.
4641///
4642/// This is RFC 3262 section 5's delayed-offer shape. An SDP-bearing reliable provisional is not
4643/// merely observed: it is answered on the same dialog sequence and the resulting media is retained
4644/// through confirmation. Like [`dial_early`], this function can return for a bodiless provisional;
4645/// [`Dialing::has_early_session`] distinguishes that case from a negotiated early session.
4646///
4647/// # Errors
4648///
4649/// The same transport, timeout and final-response errors as [`dial_early`], plus the SDP or media
4650/// error produced while answering the provisional offer. DTLS-SRTP is refused because its active
4651/// handshake cannot be started safely before the final response on this path.
4652pub async fn dial_early_without_offer(
4653 endpoint: &Handle,
4654 target: Target,
4655 to: &Uri,
4656 options: &DialOptions,
4657) -> Result<Dialing> {
4658 if options.media.keying == Keying::DtlsSrtp {
4659 return Err(Error::DtlsEarlyMedia);
4660 }
4661 let identity = Identity::fresh();
4662 let invite = open_offerless_invitation(endpoint, &target, to, options, &identity)?;
4663 let responses = endpoint.send(invite.clone(), target.clone()).await?;
4664 let (events, events_rx) = EventSink::new();
4665 let mut dialing = Dialing {
4666 endpoint: endpoint.clone(),
4667 in_dialog: target.clone(),
4668 invite,
4669 target,
4670 responses: Some(responses),
4671 dialog: None,
4672 seen: sipx_sip::rel::Sequence::default(),
4673 media: Some(EarlyMedia::WaitingForOffer),
4674 coupled_prack: None,
4675 coupled_final: None,
4676 ice: None,
4677 capabilities: None,
4678 negotiation: update::Negotiation::idle(),
4679 peer_allows_update: false,
4680 hold: Direction::SendRecv,
4681 ringing: None,
4682 deadline: options
4683 .timeout
4684 .map(|limit| tokio::time::Instant::now() + limit),
4685 options: options.clone(),
4686 answered_already: None,
4687 events: Some(events),
4688 events_rx: Some(events_rx),
4689 };
4690 dialing.reach_early_dialog().await?;
4691 Ok(dialing)
4692}
4693
4694pub(crate) async fn dial_early_without_offer_for_coupling(
4695 endpoint: &Handle,
4696 target: Target,
4697 to: &Uri,
4698 options: &DialOptions,
4699) -> Result<(Dialing, Option<Direction>)> {
4700 if options.media.keying == Keying::DtlsSrtp {
4701 return Err(Error::DtlsEarlyMedia);
4702 }
4703 let identity = Identity::fresh();
4704 let invite = open_offerless_invitation(endpoint, &target, to, options, &identity)?;
4705 let responses = endpoint.send(invite.clone(), target.clone()).await?;
4706 let (events, events_rx) = EventSink::new();
4707 let mut dialing = Dialing {
4708 endpoint: endpoint.clone(),
4709 in_dialog: target.clone(),
4710 invite,
4711 target,
4712 responses: Some(responses),
4713 dialog: None,
4714 seen: sipx_sip::rel::Sequence::default(),
4715 media: Some(EarlyMedia::WaitingForOffer),
4716 coupled_prack: None,
4717 coupled_final: None,
4718 ice: None,
4719 capabilities: None,
4720 negotiation: update::Negotiation::idle(),
4721 peer_allows_update: false,
4722 hold: Direction::SendRecv,
4723 ringing: None,
4724 deadline: options
4725 .timeout
4726 .map(|limit| tokio::time::Instant::now() + limit),
4727 options: options.clone(),
4728 answered_already: None,
4729 events: Some(events),
4730 events_rx: Some(events_rx),
4731 };
4732 let direction = dialing.reach_early_dialog_for_coupling().await?;
4733 Ok((dialing, direction))
4734}
4735
4736impl Dialing {
4737 /// The early dialog, once a provisional has established one (RFC 3261 §12.1.1).
4738 ///
4739 /// Exposed read-only because `C-2` will want to know *which* dialog a provisional's media
4740 /// belongs to — with forking, one invitation can produce several — without this handle
4741 /// having to guess in advance what it will be asked.
4742 #[must_use]
4743 pub fn dialog(&self) -> Option<&Dialog> {
4744 self.dialog
4745 .as_ref()
4746 .or_else(|| self.answered_already.as_ref().map(|call| &call.dialog))
4747 }
4748
4749 /// Whether the far end has answered this invitation's offer, in a reliable provisional.
4750 ///
4751 /// The precondition for [`Self::update`], and worth reading as the question RFC 3311 §5.1
4752 /// actually asks: not "is there a dialog" but "is there an offer/answer exchange still
4753 /// open". A `180` with no body establishes the first and does nothing about the second.
4754 #[must_use]
4755 pub fn has_early_session(&self) -> bool {
4756 matches!(self.media, Some(EarlyMedia::Answered(_)))
4757 }
4758
4759 /// The running early-media session, once a reliable provisional answered the INVITE offer.
4760 ///
4761 /// `None` for a bodiless provisional and before an answer arrives. When this is `Some`, the
4762 /// same session is moved into the [`Call`] returned by [`Self::answered`].
4763 #[must_use]
4764 pub fn media(&self) -> Option<&MediaSession> {
4765 match self.media.as_ref() {
4766 Some(EarlyMedia::Answered(early)) => Some(&early.media),
4767 _ => self.answered_already.as_ref().map(|call| call.media()),
4768 }
4769 }
4770
4771 /// This attempt's event stream, continuing on the confirmed call.
4772 ///
4773 /// Handed out once. A reliable provisional that starts media queues
4774 /// [`CallEvent::EarlyMediaStarted`] before this method can return it; the same receiver later
4775 /// observes [`CallEvent::Answered`] without being replaced at confirmation.
4776 pub fn events(&mut self) -> Option<CallEvents> {
4777 self.events_rx.take().or_else(|| {
4778 self.answered_already
4779 .as_mut()
4780 .and_then(|call| call.events())
4781 })
4782 }
4783
4784 /// Drive this invitation until early media starts or a final response arrives.
4785 ///
4786 /// [`dial_early`] returns on the first early dialog, which may be a bodiless `180`; a later
4787 /// reliable `183` can still answer the offer. This method keeps the handle in the
4788 /// application's ownership while reading through those later provisionals. `true` means
4789 /// [`Self::media`] is now available and [`CallEvent::EarlyMediaStarted`] has been emitted.
4790 /// `false` means the invitation reached a final response first; [`Self::answered`] then hands
4791 /// back the already-completed call (or its final error).
4792 ///
4793 /// # Errors
4794 ///
4795 /// The same provisional, final-refusal, timeout, cancellation, and transaction errors as
4796 /// [`Self::answered`]. `false` reports a successful final response with no early-media phase;
4797 /// the already-completed call is retained for [`Self::answered`].
4798 pub async fn wait_for_early_media(&mut self) -> Result<bool> {
4799 if self.has_early_session() {
4800 return Ok(true);
4801 }
4802 if self.answered_already.is_some() {
4803 return Ok(false);
4804 }
4805 loop {
4806 match self.next_response().await {
4807 Arrived::Provisional(response) => {
4808 if let Err(error) = self.observe(&response).await {
4809 return Err(self.abandon(error).await);
4810 }
4811 if self.has_early_session() {
4812 return Ok(true);
4813 }
4814 }
4815 Arrived::Final(response) => {
4816 let call = self.confirm(*response).await?;
4817 self.answered_already = Some(Box::new(call));
4818 return Ok(false);
4819 }
4820 Arrived::GaveUp => {
4821 self.give_up().await;
4822 return Err(Error::Cancelled(
4823 self.options.timeout.unwrap_or(Duration::ZERO),
4824 ));
4825 }
4826 Arrived::Gone => return Err(Error::NoResponse),
4827 }
4828 }
4829 }
4830
4831 /// Whether the far end has said it accepts UPDATE (RFC 3311 §4).
4832 ///
4833 /// Advisory, not enforced: §4 says a UAS "SHOULD" list it, and refusing to send on a peer
4834 /// that merely omitted the header would fail calls that would have worked. Worth checking
4835 /// before [`Self::update`] if a `405` would be more expensive than not trying.
4836 #[must_use]
4837 pub fn peer_allows_update(&self) -> bool {
4838 self.peer_allows_update
4839 }
4840
4841 /// Renegotiate the early session from this side (RFC 3311 §5.1).
4842 ///
4843 /// One implementation of §5.1, shared with [`Ringing::update`](crate::Ringing::update): the
4844 /// RFC makes UPDATE something either end may send, so there is one body of rules and two
4845 /// callers rather than a copy per role.
4846 ///
4847 /// # Errors
4848 ///
4849 /// [`Error::NoEarlySession`] if the far end has not answered our offer yet
4850 /// ([`Self::has_early_session`]); [`Error::NoDialog`] if no provisional established one; and
4851 /// [`Error::Rejected`] if the far end refuses, including the `491` of an offer that crossed
4852 /// one of ours.
4853 pub async fn update(&mut self, direction: Direction) -> Result<()> {
4854 let Some(early) = self.early_dialog() else {
4855 return Err(Error::NoDialog);
4856 };
4857 crate::update::offer(early, direction).await?;
4858 self.hold = direction;
4859 Ok(())
4860 }
4861
4862 /// Answer an UPDATE that arrived in this early dialog (RFC 3311 §5.2).
4863 ///
4864 /// Returns whether it was one for this dialog, so an application with one inbox can offer
4865 /// everything it receives and act on what is left. The refusals are the same three the
4866 /// answering side gives, because they are the same code.
4867 ///
4868 /// # Errors
4869 ///
4870 /// Fails only if the response could not be built or sent. A *refusal* is a successful call:
4871 /// §5.2's 488 and 500 are responses this stack sends deliberately, not errors here.
4872 pub async fn on_update(&mut self, incoming: &Incoming) -> Result<bool> {
4873 let Some(early) = self.early_dialog() else {
4874 return Ok(false);
4875 };
4876 crate::update::receive(early, incoming).await
4877 }
4878
4879 /// Advance either the INVITE transaction or its routed early-dialog inbox once.
4880 ///
4881 /// Kept crate-private for [`crate::coupling::EarlyCoupling`]: unlike [`Self::answered`], this
4882 /// does not consume the dialing handle or hold it across every provisional. The coupling can
4883 /// therefore service UPDATEs from either pending leg and observe cancellation while the
4884 /// outbound final response is still outstanding.
4885 pub(crate) async fn coupling_step(
4886 &mut self,
4887 incoming: &mut tokio::sync::mpsc::Receiver<Incoming>,
4888 ) -> Result<CouplingDialEvent> {
4889 if let Some(call) = self.answered_already.take() {
4890 return Ok(CouplingDialEvent::Answered(call));
4891 }
4892 if self.coupled_prack.is_none()
4893 && let Some(response) = self.coupled_final.take()
4894 {
4895 return self
4896 .confirm(*response)
4897 .await
4898 .map(Box::new)
4899 .map(CouplingDialEvent::Answered);
4900 }
4901 tokio::select! {
4902 request = incoming.recv() => Ok(CouplingDialEvent::Incoming(Box::new(request))),
4903 arrived = self.next_response() => match arrived {
4904 Arrived::Provisional(response) => {
4905 if self.is_coupled_offer_candidate(&response) {
4906 return match self.stage_coupled_offer(*response).await {
4907 Ok(direction) => Ok(CouplingDialEvent::ReliableOffer(direction)),
4908 Err(error) => Err(self.abandon(error).await),
4909 };
4910 }
4911 if let Err(error) = self.observe(&response).await {
4912 return Err(self.abandon(error).await);
4913 }
4914 Ok(CouplingDialEvent::Progress)
4915 }
4916 Arrived::Final(response) => {
4917 if self.coupled_prack.is_some() {
4918 self.coupled_final = Some(response);
4919 Ok(CouplingDialEvent::Progress)
4920 } else {
4921 self.confirm(*response)
4922 .await
4923 .map(Box::new)
4924 .map(CouplingDialEvent::Answered)
4925 }
4926 },
4927 Arrived::GaveUp => {
4928 self.give_up().await;
4929 Err(Error::Cancelled(
4930 self.options.timeout.unwrap_or(Duration::ZERO),
4931 ))
4932 }
4933 Arrived::Gone => Err(Error::NoResponse),
4934 }
4935 }
4936 }
4937
4938 pub(crate) async fn complete_coupled_prack(&mut self) -> Result<()> {
4939 let Some(pending) = self.coupled_prack.take() else {
4940 return Err(Error::NoDialog);
4941 };
4942 self.acknowledge(&pending.response, pending.rseq, Some(pending.answer))
4943 .await
4944 }
4945
4946 /// Wait for the invitation to be answered, and take the call it becomes.
4947 ///
4948 /// Consuming, because everything it needs moves into the [`Call`]. Provisionals that arrive
4949 /// while waiting are handled exactly as they were before it returned — `PRACK`ed, and read for
4950 /// the answer that makes the session renegotiable — so an application that calls this
4951 /// immediately is in the same position as one that had called [`dial`].
4952 ///
4953 /// # Errors
4954 ///
4955 /// [`Error::Rejected`] if the far end declined, [`Error::IntervalTooBrief`] for a `422`
4956 /// (see [`dial_early`] on why it is not retried), [`Error::Cancelled`] if the deadline
4957 /// passed — the invitation is withdrawn first — and [`Error::NoResponse`] if the
4958 /// transaction ended without a final response.
4959 ///
4960 /// And, from a *provisional* rather than from the answer: [`Error::Sdp`] or
4961 /// [`Error::NoCommonCodec`] if a reliable provisional answers our offer with a description
4962 /// that cannot be used (RFC 3262 §5). That one withdraws the invitation with a CANCEL (RFC
4963 /// 3261 §9.1) rather than waiting for a 2xx to fail on, because a far end that answered no
4964 /// offer of ours may never send one.
4965 pub async fn answered(mut self) -> Result<Call> {
4966 self.drive_answered(None).await
4967 }
4968
4969 /// Wait for confirmation until `cancelled` resolves.
4970 ///
4971 /// Cancellation withdraws the owned invitation before returning, including ACK-then-BYE
4972 /// cleanup for a successful final response already in flight. This closes the ownership gap
4973 /// between [`dial_early_until`] returning an early handle and the final answer arriving.
4974 ///
4975 /// # Errors
4976 ///
4977 /// The same errors as [`Self::answered`]. Local cancellation returns [`Error::Cancelled`]
4978 /// after cleanup completes.
4979 pub async fn answered_until<F>(mut self, cancelled: F) -> Result<Call>
4980 where
4981 F: Future<Output = ()> + Send,
4982 {
4983 tokio::pin!(cancelled);
4984 let cancelled: Pin<&mut (dyn Future<Output = ()> + Send)> = cancelled.as_mut();
4985 self.drive_answered(Some(cancelled)).await
4986 }
4987
4988 async fn drive_answered(&mut self, mut cancelled: Option<Cancelled<'_>>) -> Result<Call> {
4989 if let Some(call) = self.answered_already.take() {
4990 return Ok(*call);
4991 }
4992 loop {
4993 let arrived = match cancelled.as_mut() {
4994 None => self.next_response().await,
4995 Some(cancelled) => {
4996 tokio::select! {
4997 biased;
4998 () = cancelled.as_mut() => {
4999 self.give_up().await;
5000 return Err(Error::Cancelled(Duration::ZERO));
5001 }
5002 arrived = self.next_response() => arrived,
5003 }
5004 }
5005 };
5006 match arrived {
5007 Arrived::Provisional(response) => {
5008 if let Err(error) = self.observe(&response).await {
5009 return Err(self.abandon(error).await);
5010 }
5011 }
5012 Arrived::Final(response) => return self.confirm(*response).await,
5013 Arrived::GaveUp => {
5014 self.give_up().await;
5015 return Err(Error::Cancelled(
5016 self.options.timeout.unwrap_or(Duration::ZERO),
5017 ));
5018 }
5019 Arrived::Gone => return Err(Error::NoResponse),
5020 }
5021 }
5022 }
5023
5024 /// Give up on the invitation, and make sure the far end stops ringing (RFC 3261 §9.1, §15).
5025 ///
5026 /// The counterpart of [`Self::answered`], and the reason both consume the handle. A `200`
5027 /// that crosses the CANCEL is acknowledged and then hung up, which §15 requires and a CANCEL
5028 /// cannot do on its own.
5029 pub async fn cancel(mut self) {
5030 self.give_up().await;
5031 }
5032
5033 /// Cancel this invitation with an explicit protocol cause.
5034 ///
5035 /// A SIP 200 reason represents the RFC 3326 §3.1 case where another coupled or forked leg
5036 /// completed the call; other valid SIP and Q.850 causes are retained unchanged.
5037 pub async fn cancel_with_reason(mut self, reason: ReasonValue) {
5038 self.give_up_with_reason(&reason).await;
5039 }
5040
5041 /// The early dialog's mutable parts, borrowed for one UPDATE.
5042 ///
5043 /// `None` before any provisional has established a dialog, which is a peer there is nothing
5044 /// to send an in-dialog request *to*.
5045 fn early_dialog(&mut self) -> Option<crate::update::EarlyDialog<'_>> {
5046 Some(crate::update::EarlyDialog {
5047 endpoint: &self.endpoint,
5048 dialog: self.dialog.as_mut()?,
5049 target: &mut self.in_dialog,
5050 negotiation: &mut self.negotiation,
5051 peer_allows: &mut self.peer_allows_update,
5052 early: match self.media.as_mut() {
5053 Some(EarlyMedia::Answered(early)) => Some(early),
5054 _ => None,
5055 },
5056 })
5057 }
5058
5059 /// Read responses until a dialog exists, or the invitation is over before one did.
5060 async fn reach_early_dialog(&mut self) -> Result<()> {
5061 loop {
5062 match self.next_response().await {
5063 Arrived::Provisional(response) => {
5064 if let Err(error) = self.observe(&response).await {
5065 return Err(self.abandon(error).await);
5066 }
5067 if self.dialog.is_some() {
5068 return Ok(());
5069 }
5070 }
5071 Arrived::Final(response) => {
5072 let call = self.confirm(*response).await?;
5073 self.answered_already = Some(Box::new(call));
5074 return Ok(());
5075 }
5076 Arrived::GaveUp => {
5077 self.give_up().await;
5078 return Err(Error::Cancelled(
5079 self.options.timeout.unwrap_or(Duration::ZERO),
5080 ));
5081 }
5082 Arrived::Gone => return Err(Error::NoResponse),
5083 }
5084 }
5085 }
5086
5087 async fn reach_early_dialog_for_coupling(&mut self) -> Result<Option<Direction>> {
5088 loop {
5089 match self.next_response().await {
5090 Arrived::Provisional(response) => {
5091 if self.is_coupled_offer_candidate(&response) {
5092 return match self.stage_coupled_offer(*response).await {
5093 Ok(direction) => Ok(Some(direction)),
5094 Err(error) => Err(self.abandon(error).await),
5095 };
5096 }
5097 if let Err(error) = self.observe(&response).await {
5098 return Err(self.abandon(error).await);
5099 }
5100 if self.dialog.is_some() {
5101 return Ok(None);
5102 }
5103 }
5104 Arrived::Final(response) => {
5105 let call = self.confirm(*response).await?;
5106 self.answered_already = Some(Box::new(call));
5107 return Ok(None);
5108 }
5109 Arrived::GaveUp => {
5110 self.give_up().await;
5111 return Err(Error::Cancelled(
5112 self.options.timeout.unwrap_or(Duration::ZERO),
5113 ));
5114 }
5115 Arrived::Gone => return Err(Error::NoResponse),
5116 }
5117 }
5118 }
5119
5120 /// One response from the INVITE transaction, bounded by the invitation's own deadline.
5121 async fn next_response(&mut self) -> Arrived {
5122 let deadline = self.deadline;
5123 let Some(responses) = self.responses.as_mut() else {
5124 return Arrived::Gone;
5125 };
5126 loop {
5127 let event = match deadline {
5128 None => responses.next().await,
5129 Some(deadline) => match tokio::time::timeout_at(deadline, responses.next()).await {
5130 Ok(event) => event,
5131 Err(_elapsed) => return Arrived::GaveUp,
5132 },
5133 };
5134 match event {
5135 Some(sipx_sip::transaction::TuEvent::Response(response)) => {
5136 return if response.status.is_final() {
5137 Arrived::Final(response)
5138 } else {
5139 Arrived::Provisional(response)
5140 };
5141 }
5142 Some(_) => {}
5143 None => return Arrived::Gone,
5144 }
5145 }
5146 }
5147
5148 /// Fold a provisional into the early dialog: the dialog it may create, the answer it may
5149 /// carry, and the PRACK it may require.
5150 ///
5151 /// # Errors
5152 ///
5153 /// Whatever [`Self::adopt_early_answer`] refused. It is the only fatal thing a provisional can
5154 /// do: everything else here is either optional (a dialog it did not establish, an `Allow` it
5155 /// did not carry) or recoverable (a PRACK that did not get through).
5156 async fn observe(&mut self, response: &Response) -> Result<()> {
5157 if !self.observe_metadata(response) {
5158 return Ok(());
5159 }
5160 let reliable = crate::rel::reliable_sequence(response);
5161
5162 if let Some(rseq) = reliable {
5163 // RFC 3262 §5: an answer may only travel in a reliable provisional, so this is the
5164 // only place before the 200 where our INVITE's offer can be closed out. An
5165 // unreliable provisional carrying a description is not one — §5 forbids it, and one
5166 // lost leaves the two sides disagreeing about what is in force with no way to
5167 // notice — so it is ignored rather than adopted.
5168 //
5169 // Held rather than propagated on the spot: the provisional is acknowledged first even
5170 // when its description is refused. RFC 3262 §4 makes the PRACK a MUST for every
5171 // reliable provisional a UAC receives, and the far end retransmits until one arrives;
5172 // failing a beat earlier would leave it resending a response into a CANCEL that has
5173 // already gone.
5174 let adopted = if matches!(self.media, Some(EarlyMedia::WaitingForOffer))
5175 && !response.body().is_empty()
5176 {
5177 self.adopt_early_offer(response).await.map(Some)
5178 } else {
5179 self.adopt_early_answer(response).map(|()| None)
5180 };
5181 if let Some(dialog) = self.dialog.as_mut() {
5182 dialog.refresh_target(&response.headers);
5183 }
5184 // A failure is logged rather than fatal, for `await_final`'s reason: the invitation
5185 // is still running, and abandoning a ringing call because one PRACK did not get
5186 // through is a worse outcome than the unreliability it was fixing.
5187 let prack_answer = adopted.as_ref().ok().and_then(Clone::clone);
5188 if let Err(error) = self.acknowledge(response, rseq, prack_answer).await {
5189 tracing::debug!(%error, "could not acknowledge a reliable provisional");
5190 }
5191 adopted?;
5192 }
5193 Ok(())
5194 }
5195
5196 fn is_coupled_offer_candidate(&self, response: &Response) -> bool {
5197 matches!(self.media, Some(EarlyMedia::WaitingForOffer))
5198 && !response.body().is_empty()
5199 && crate::rel::reliable_sequence(response).is_some()
5200 }
5201
5202 async fn stage_coupled_offer(&mut self, response: Response) -> Result<Direction> {
5203 if !self.observe_metadata(&response) {
5204 return Err(Error::NoDialog);
5205 }
5206 let rseq = crate::rel::reliable_sequence(&response).ok_or(Error::NoDialog)?;
5207 let offer = sipx_sdp::parse(&String::from_utf8_lossy(response.body()))
5208 .map_err(|error| Error::Sdp(error.to_string()))?;
5209 let direction = offer
5210 .media
5211 .iter()
5212 .find(|media| media.media == "audio" && !media.is_rejected())
5213 .map(sipx_sdp::MediaDescription::direction)
5214 .ok_or_else(|| {
5215 Error::Sdp("the reliable provisional carried no usable audio offer".to_owned())
5216 })?;
5217 let answer = self.adopt_early_offer(&response).await?;
5218 if let Some(dialog) = self.dialog.as_mut() {
5219 dialog.refresh_target(&response.headers);
5220 }
5221 self.coupled_prack = Some(CoupledPrack {
5222 response: Box::new(response),
5223 rseq,
5224 answer,
5225 });
5226 Ok(direction)
5227 }
5228
5229 fn observe_metadata(&mut self, response: &Response) -> bool {
5230 const TRYING: u16 = 100;
5231
5232 let reliable = crate::rel::reliable_sequence(response);
5233 if response.status.code() > TRYING && self.ringing.is_none() {
5234 let is_reliable = reliable.is_some();
5235 self.ringing = Some(is_reliable);
5236 if let Some(events) = self.events.as_ref() {
5237 events.emit(CallEvent::Ringing {
5238 reliable: is_reliable,
5239 });
5240 }
5241 }
5242
5243 if self.dialog.is_none() {
5244 if let Some(dialog) = Dialog::from_response(&self.invite, response) {
5245 self.in_dialog = in_dialog_target(&dialog, self.target.clone());
5246 self.dialog = Some(dialog);
5247 }
5248 } else if !self.belongs(response) {
5249 return false;
5250 }
5251
5252 if update::peer_allows(&response.headers) {
5253 self.peer_allows_update = true;
5254 }
5255 true
5256 }
5257
5258 /// Answer a reliable provisional's offer for an offerless INVITE.
5259 async fn adopt_early_offer(&mut self, response: &Response) -> Result<SessionDescription> {
5260 if response.body().is_empty() {
5261 return Err(Error::Sdp(
5262 "an offerless INVITE received a reliable provisional with no offer".to_owned(),
5263 ));
5264 }
5265 let offer = sipx_sdp::parse(&String::from_utf8_lossy(response.body()))
5266 .map_err(|error| Error::Sdp(error.to_string()))?;
5267 let (early, answer) = Early::settle(
5268 MediaAddress::new(self.options.media_address)
5269 .with_bind(self.options.media_bind_address),
5270 self.target.transport.is_secure(),
5271 &offer,
5272 self.options.media,
5273 )
5274 .await?;
5275 self.media = Some(EarlyMedia::Answered(Box::new(early)));
5276 self.negotiation.sent_answer();
5277 if let Some(events) = self.events.as_ref() {
5278 events.emit(CallEvent::EarlyMediaStarted);
5279 }
5280 Ok(answer)
5281 }
5282
5283 /// Whether a response belongs to the dialog this handle holds.
5284 fn belongs(&self, response: &Response) -> bool {
5285 self.dialog.as_ref().is_none_or(|dialog| {
5286 Dialog::from_response(&self.invite, response)
5287 .is_none_or(|fresh| fresh.id.remote_tag == dialog.id.remote_tag)
5288 })
5289 }
5290
5291 /// Take the answer to our INVITE's offer out of a reliable provisional (RFC 3262 §5).
5292 ///
5293 /// This is what makes the early dialog renegotiable at all, and it is the calling side's
5294 /// mirror of [`ring_early`](crate::ring_early).
5295 ///
5296 /// `Ok(())` covers two shapes, and the difference matters. A provisional that carries **no
5297 /// description** is ordinary — a `180` establishes a dialog and answers nothing — and one that
5298 /// arrives after the exchange has already closed is a repeat of an answer we took the first
5299 /// time. Neither is a failure and neither is reported.
5300 ///
5301 /// # Errors
5302 ///
5303 /// A description that *is* there and cannot be used: [`Error::Sdp`] for a body that does not
5304 /// parse or an `a=crypto` that fails RFC 4568 §5.1.3's check, [`Error::NoCommonCodec`] for one
5305 /// that cannot be negotiated. Before `S-25` all three returned `()` and left a `debug` line,
5306 /// so they were indistinguishable from each other and from the silent cases above — and for a
5307 /// caller that never receives a 2xx, indistinguishable from nothing having happened.
5308 ///
5309 /// The session is left where it was on either path — still `Offered`, so the exchange stays
5310 /// open and [`Self::update`] keeps refusing, which is the truthful state. What changes is that
5311 /// the refusal now reaches [`Self::observe`], which withdraws the invitation over it.
5312 fn adopt_early_answer(&mut self, response: &Response) -> Result<()> {
5313 if !matches!(self.media, Some(EarlyMedia::Offered(_))) || response.body().is_empty() {
5314 return Ok(());
5315 }
5316 // Parsed and settled *before* the port is moved out, so that a failure on either step
5317 // leaves `media` exactly as it was rather than emptied.
5318 let answer = sipx_sdp::parse(&String::from_utf8_lossy(response.body()))
5319 .map_err(|error| Error::Sdp(error.to_string()))?;
5320 // The same vocabulary the 2xx path uses: `settle_from` runs this exact function on the
5321 // final response, and a refusal that arrived early is the same refusal. Naming it
5322 // differently here would ask an application to match on two errors for one fault.
5323 //
5324 // `M-30` added the selected codec set to this call. It widens what can be refused here:
5325 // an early answer naming a codec outside the set now fails where it previously could
5326 // not, and `S-25` turns that failure into a CANCEL. Our own offer only names codecs in
5327 // the set, so a conformant answer cannot trip it — an answer that does is naming
5328 // something we never offered, which is exactly what `S-25` exists to refuse rather than
5329 // hang on.
5330 let Some(capabilities) = self.capabilities.clone() else {
5331 return Err(Error::NoDialog);
5332 };
5333 let settled = settle_answer(&capabilities, &answer, self.options.media.codecs)?;
5334 self.accept_remote_ice(&answer);
5335 let Some(EarlyMedia::Offered(port)) = self.media.take() else {
5336 return Ok(());
5337 };
5338 let media = match self.ice.take() {
5339 Some(local) => port.start_with_ice(settled.media_config(), local)?,
5340 None => port.start(settled.media_config())?,
5341 };
5342 self.media = Some(EarlyMedia::Answered(Box::new(Early {
5343 media,
5344 capabilities,
5345 settled,
5346 media_address: self.options.media_address,
5347 media_bind_address: self.options.media_bind_address,
5348 codecs: self.options.media.codecs,
5349 keying: self.options.media.keying,
5350 })));
5351 self.negotiation.received_answer();
5352 if let Some(events) = self.events.as_ref() {
5353 events.emit(CallEvent::EarlyMediaStarted);
5354 }
5355 Ok(())
5356 }
5357
5358 /// Withdraw the invitation because a reliable provisional's description cannot be used.
5359 ///
5360 /// **CANCEL, not ACK-then-BYE** — this is the failure mode `S-25` had to choose, and it is
5361 /// chosen by where we are rather than by what went wrong. RFC 3261 §9.1 withdraws an
5362 /// invitation that has only been answered provisionally; there is no final response here to
5363 /// acknowledge, so the ACK-then-BYE that [`Self::confirm`] performs after a 2xx (§15) has
5364 /// nothing to attach itself to. [`Self::give_up`] is already that request, sent through
5365 /// [`withdraw`], which also covers the one case a CANCEL cannot: a `200` that crossed it is
5366 /// acknowledged and hung up, because by then §15 *does* apply.
5367 ///
5368 /// The alternative considered and rejected was to carry on and let the 2xx fail — which is
5369 /// what happened before this story. It fails safely (nothing is keyed on a refused answer,
5370 /// and `settle_from` re-runs the same check) but it is not a report: a far end that answers
5371 /// no offer of ours will not send a 2xx to fail on, and the caller's only outcome was
5372 /// [`Error::Cancelled`] when its own deadline passed.
5373 async fn abandon(&mut self, error: Error) -> Error {
5374 self.give_up().await;
5375 error
5376 }
5377
5378 /// PRACK a reliable provisional through the early dialog itself (RFC 3262 §4).
5379 ///
5380 /// Through *the* dialog, not a copy of it. The PRACK is an in-dialog request and takes the
5381 /// next number in this side's own sequence space (RFC 3261 §12.2.1.1); the `dial` path
5382 /// builds a throwaway `Dialog` per acknowledgement because it keeps none, which restarts
5383 /// that space at the INVITE's number every time. Here an UPDATE may follow, and it would
5384 /// then reuse the PRACK's number.
5385 async fn acknowledge(
5386 &mut self,
5387 response: &Response,
5388 rseq: u32,
5389 answer: Option<SessionDescription>,
5390 ) -> Result<()> {
5391 // §4: out of order means an earlier one is missing, and a duplicate has already been
5392 // acknowledged. Neither is PRACKed.
5393 if self.seen.accept(rseq) != sipx_sip::rel::Received::Acknowledge {
5394 return Ok(());
5395 }
5396 let dialog = self.dialog.as_mut().ok_or(Error::NoDialog)?;
5397 let invite_cseq = self
5398 .invite
5399 .headers
5400 .typed::<sipx_sip::CSeq>()
5401 .and_then(std::result::Result::ok)
5402 .map_or(1, |cseq| cseq.sequence);
5403 let body = match answer {
5404 Some(answer) => Some(answer),
5405 None => self.capabilities.as_ref().and_then(|capabilities| {
5406 crate::rel::prack_body(
5407 !self.invite.body().is_empty(),
5408 response.body(),
5409 capabilities,
5410 )
5411 }),
5412 };
5413 crate::rel::send_prack(
5414 &self.endpoint,
5415 dialog,
5416 &self.in_dialog,
5417 rseq,
5418 invite_cseq,
5419 body,
5420 )
5421 .await
5422 }
5423
5424 /// Turn a final response into a [`Call`], or into the error it describes.
5425 async fn confirm(&mut self, response: Response) -> Result<Call> {
5426 if !response.status.is_success() {
5427 // A non-2xx is acknowledged by the transaction layer itself, so there is nothing to
5428 // send here — only a media port to release, which happens when this is dropped.
5429 return Err(rejection(&response));
5430 }
5431
5432 // From here the far end believes a dialog exists, so *every* path must acknowledge.
5433 // Returning an error without one leaves it retransmitting its 200 for 32 seconds and
5434 // then streaming media at a port we have closed.
5435 match self.accept(&response) {
5436 Ok((dialog, media, settled)) => {
5437 let ack = build_ack(&self.endpoint, &dialog, &self.in_dialog)?;
5438 self.endpoint
5439 .send_directly(ack.clone(), self.in_dialog.clone())
5440 .await?;
5441 // The stream stays open rather than being dropped: a retransmitted 2xx means
5442 // this ACK was lost and RFC 3261 §13.2.2.4 requires another.
5443 if let Some(responses) = self.responses.take() {
5444 tokio::spawn(reack_retransmitted_2xx(
5445 self.endpoint.clone(),
5446 responses,
5447 ack,
5448 self.in_dialog.clone(),
5449 ));
5450 }
5451 let Some(events) = self.events.take() else {
5452 return Err(Error::NoDialog);
5453 };
5454 events.emit(CallEvent::Answered);
5455 let events_rx = self.events_rx.take();
5456 Ok(Call {
5457 dialog,
5458 initial_status: response.status.code(),
5459 media: Arc::new(media),
5460 retired_media: Vec::new(),
5461 endpoint: self.endpoint.clone(),
5462 target: self.in_dialog.clone(),
5463 ack_stop: None,
5464 ack_retransmission: None,
5465 delayed_offer: None,
5466 ended: false,
5467 media_address: self.options.media_address,
5468 media_bind_address: self.options.media_bind_address,
5469 codecs: self.options.media.codecs,
5470 profile: self.options.media.profile,
5471 current: settled.negotiated,
5472 peer_ice: peer_ice_credentials(response.body()),
5473 encrypted: self.options.media.profile == MediaProfile::BrowserAudio
5474 || settled.srtp.is_some(),
5475 keying: self.options.media.keying,
5476 hold: self.hold,
5477 referral: None,
5478 transfer: None,
5479 session: session::adopt(
5480 response
5481 .headers
5482 .typed::<SessionExpires>()
5483 .and_then(std::result::Result::ok),
5484 self.options.session_expires,
5485 )
5486 .map(SessionState::armed),
5487 negotiation: self.negotiation,
5488 peer_allows_update: self.peer_allows_update
5489 || update::peer_allows(&response.headers),
5490 events,
5491 events_rx,
5492 history: HistoryInfo::from_headers(&response.headers)
5493 .and_then(std::result::Result::ok),
5494 dialog_credentials: self.options.credentials.clone(),
5495 admitted_dialog_methods: Vec::new(),
5496 })
5497 }
5498 Err(error) => {
5499 // RFC 3261 §15: a UAC that cannot proceed after a 2xx acknowledges it and then
5500 // sends BYE. Walking away silently is what leaves the far end streaming.
5501 let dialog = self
5502 .dialog
5503 .take()
5504 .or_else(|| Dialog::from_response(&self.invite, &response));
5505 if let Some(dialog) = dialog {
5506 let in_dialog = in_dialog_target(&dialog, self.target.clone());
5507 // discard: counted as `sipx_transport::UnsentCounts::ack`, exactly as in
5508 // `ack_then_bye` — the same two sends on the same failing path, written inline
5509 // here only because this one already holds the dialog. Via `send_directly`, so
5510 // this `Result` does report the transmit.
5511 let _ = send_ack(&self.endpoint, &dialog, in_dialog.clone()).await;
5512 if let Ok(bye) = bye_request(
5513 &dialog,
5514 dialog.local_cseq.saturating_add(1),
5515 &normal_clearing_reason(),
5516 ) {
5517 // discard: counted as `sipx_transport::UnsentCounts::bye`, at the
5518 // transmit rather than from this `Result`, which reports only that the
5519 // transaction was created. It is dropped so that the error which brought
5520 // us into this branch is the one the caller is given, rather than being
5521 // masked by a teardown failure.
5522 let _ = self.endpoint.send(bye, in_dialog).await;
5523 }
5524 }
5525 Err(error)
5526 }
5527 }
5528 }
5529
5530 /// Everything after a 2xx that can fail, kept together so [`Self::confirm`] can ACK either way.
5531 ///
5532 /// [`establish`] is the same step for [`dial`], and this is not it: nothing here is rebuilt.
5533 /// The dialog is the one the provisional created, and when the early session was already
5534 /// answered the description in force is what *it* settled — including anything an UPDATE has
5535 /// changed since.
5536 fn accept(&mut self, response: &Response) -> Result<(Dialog, MediaSession, Settled)> {
5537 // A 2xx bearing a different `To` tag is a different dialog — a forked branch won — and
5538 // the early one it did not confirm has nothing to contribute to it.
5539 let confirms_early = self.belongs(response);
5540 let fresh = Dialog::from_response(&self.invite, response);
5541 let mut dialog = match (confirms_early, self.dialog.take(), fresh) {
5542 // The early dialog, its sequence space intact. Two ways to get here and one answer:
5543 // the usual one, where the 2xx confirms what the provisional established; and a 2xx
5544 // carrying no usable `To` tag or `Contact`, which establishes no dialog of its own
5545 // but does not unmake the one a provisional already did.
5546 (true, Some(early), _) | (_, Some(early), None) => early,
5547 // A forked branch won: this 2xx names a dialog that is not the early one.
5548 (_, _, Some(fresh)) => fresh,
5549 (_, None, None) => return Err(Error::NoDialog),
5550 };
5551 dialog.refresh_target(&response.headers);
5552 self.in_dialog = in_dialog_target(&dialog, self.target.clone());
5553
5554 let (media, settled) = match self.media.take() {
5555 // The answer arrived in a provisional, and any UPDATE since settled its own. So the
5556 // 2xx's body is *not* read: at this point it can only be a repeat of the answer or,
5557 // worse, a description that undoes the renegotiation. `answer_early` sends no body
5558 // in this exact case, and for the same reason.
5559 Some(EarlyMedia::Answered(early)) if confirms_early => (early.media, early.settled),
5560 Some(EarlyMedia::Answered(early)) => {
5561 // This 2xx confirmed a different fork from the early dialog the handle names.
5562 // Never attach the losing branch's running stream to the winner. Multi-branch
5563 // selection is application policy; until this handle can represent both, the
5564 // honest outcome is to tear the loser down and ACK-then-BYE the unrepresented
5565 // winner through `confirm`'s error path.
5566 drop(early);
5567 return Err(Error::NoDialog);
5568 }
5569 Some(EarlyMedia::Offered(port)) => {
5570 let settled = self.settle_from(response)?;
5571 let media = match self.ice.take() {
5572 Some(local) => port.start_with_ice(settled.media_config(), local)?,
5573 None => port.start(settled.media_config())?,
5574 };
5575 (media, settled)
5576 }
5577 Some(EarlyMedia::WaitingForOffer) => return Err(Error::NoEarlySession),
5578 None => return Err(Error::NoDialog),
5579 };
5580 Ok((dialog, media, settled))
5581 }
5582
5583 /// Read the answer out of the 2xx, for the case where no provisional carried one.
5584 fn settle_from(&mut self, response: &Response) -> Result<Settled> {
5585 let answer = sipx_sdp::parse(&String::from_utf8_lossy(response.body()))
5586 .map_err(|error| Error::Sdp(error.to_string()))?;
5587 let Some(capabilities) = self.capabilities.as_ref() else {
5588 return Err(Error::NoEarlySession);
5589 };
5590 let settled = settle_answer(capabilities, &answer, self.options.media.codecs)?;
5591 self.accept_remote_ice(&answer);
5592 // Our INVITE's offer is answered here rather than in a provisional, so the exchange
5593 // closes now. Without this the first UPDATE on the confirmed call would be refused as
5594 // glare against an offer that has in fact been answered.
5595 self.negotiation.received_answer();
5596 Ok(settled)
5597 }
5598
5599 /// Give the gathered agent the answer to the offer that created it.
5600 fn accept_remote_ice(&mut self, answer: &SessionDescription) {
5601 let negotiation = answer
5602 .media
5603 .first()
5604 .map_or(IceNegotiation::Absent, |audio| {
5605 sipx_media::ice::negotiate(answer, audio)
5606 });
5607 if let Some(local) = self.ice.as_mut() {
5608 local.accept(&negotiation);
5609 }
5610 }
5611
5612 /// Take back the invitation, whatever state it is in.
5613 async fn give_up(&mut self) {
5614 self.give_up_with_reason(&normal_clearing_reason()).await;
5615 }
5616
5617 async fn give_up_with_reason(&mut self, reason: &ReasonValue) {
5618 if let Some(response) = self.coupled_final.take() {
5619 if response.status.is_success() {
5620 ack_then_bye(&self.endpoint, &self.invite, &response, self.target.clone()).await;
5621 }
5622 return;
5623 }
5624 let Some(responses) = self.responses.as_mut() else {
5625 return;
5626 };
5627 withdraw(
5628 &self.endpoint,
5629 &self.invite,
5630 self.target.clone(),
5631 responses,
5632 reason,
5633 )
5634 .await;
5635 }
5636}
5637
5638/// The `200` that carries the answer, with the session-timer headers the negotiation settled on.
5639///
5640/// `agreed` is [`negotiate_session`]'s outcome: `None` when neither side asked for RFC 4028's
5641/// refresh, and otherwise the interval and refresher this answer commits to. `Require: timer` goes
5642/// on only when the negotiation said so — a 2xx that requires an extension the offer did not
5643/// support is a call the caller must reject.
5644///
5645/// # Errors
5646///
5647/// Returns [`Error`] when a header value cannot be built.
5648fn ok_with_answer(
5649 endpoint: &Handle,
5650 incoming: &Incoming,
5651 to_with_tag: &str,
5652 answer: &SessionDescription,
5653 agreed: Option<session::Accepted>,
5654 headers: &[sipx_sip::Header],
5655) -> Result<Response> {
5656 let mut response = ResponseBuilder::to_request(&incoming.request, ok_status(), "OK")?
5657 .set_header(&HeaderName::To, Bytes::from(to_with_tag.to_owned()))?
5658 .header(
5659 HeaderName::Contact,
5660 Bytes::from(contact_for(endpoint, incoming.transport)),
5661 )?
5662 // RFC 3311 §4: the 2xx "SHOULD contain an Allow header field listing the UPDATE
5663 // method". This is where a UAC learns it, and RFC 4028 §7.4 then reads it to decide
5664 // whether a session refresh may be an UPDATE.
5665 .header(
5666 HeaderName::Allow,
5667 Bytes::from_static(update::ALLOW.as_bytes()),
5668 )?
5669 .header(
5670 HeaderName::ContentType,
5671 Bytes::from_static(b"application/sdp"),
5672 )?
5673 .body(Bytes::from(answer.to_string_sdp()));
5674
5675 if let Some(accepted) = agreed {
5676 let expires = SessionExpires {
5677 interval: accepted.interval,
5678 refresher: Some(accepted.refresher),
5679 };
5680 response = response
5681 .header(HeaderName::SessionExpires, Bytes::from(expires.to_string()))?
5682 .header(HeaderName::Supported, Bytes::from_static(b"timer"))?;
5683 if accepted.require {
5684 response = response.header(HeaderName::Require, Bytes::from_static(b"timer"))?;
5685 }
5686 }
5687 Ok(add_response_headers(response, headers)?.build())
5688}
5689
5690fn add_response_headers(
5691 mut response: ResponseBuilder,
5692 headers: &[sipx_sip::Header],
5693) -> std::result::Result<ResponseBuilder, sipx_sip::error::BuildError> {
5694 for header in headers {
5695 response = response.header(
5696 header.name().clone(),
5697 Bytes::copy_from_slice(header.raw_value()),
5698 )?;
5699 }
5700 Ok(response)
5701}
5702
5703/// Read the offer's ICE half and gather for the answer if the policy selects it (`ice.md` §13.4).
5704///
5705/// One function for the three answering paths — the free answer functions, the dispatcher's
5706/// invitation and [`Early::settle`] — because "when does an answerer gather?" is one rule and
5707/// three copies of it are three chances to disagree. Two of them already did: one asked
5708/// `matches!(.., Ice { .. })` and one asked [`IceNegotiation::runs_ice`], which are the same
5709/// question spelled two ways until one of them acquires a case the other lacks.
5710///
5711/// Gathering is deliberately *after* reading the peer's half. A policy selecting ICE never means
5712/// "require the peer to implement it" (`ice.md` §13.4), so an offer carrying no candidate costs no
5713/// gathering, no STUN transaction and no timer.
5714///
5715/// # Errors
5716///
5717/// Returns [`Error`] when the policy cannot produce a gathering configuration.
5718async fn answer_gathering(
5719 port: &MediaPort,
5720 offer: &SessionDescription,
5721 policy: MediaPolicy,
5722) -> Result<(IceNegotiation, Option<LocalDescription>)> {
5723 let remote = offer.media.first().map_or(IceNegotiation::Absent, |audio| {
5724 sipx_media::ice::negotiate(offer, audio)
5725 });
5726 if !remote.runs_ice() {
5727 return Ok((remote, None));
5728 }
5729 let local = match policy.gathering(false)? {
5730 Some(gathering) => Some(
5731 port.gather_with_rtcp_mode(&gathering, answering_rtcp_mode(offer))
5732 .await,
5733 ),
5734 None => None,
5735 };
5736 Ok((remote, local))
5737}
5738
5739/// A session that has been described and answered, but not yet accepted.
5740///
5741/// What an early dialog needs in order to be renegotiable at all. RFC 3311 §5.1 will not let an
5742/// UPDATE carry an offer while an offer/answer exchange is open, so before the 200 there is
5743/// exactly one way to make one legal: the answer to the INVITE's offer travels in a reliable
5744/// provisional (RFC 3262 §5), and this is what that answer settled on.
5745///
5746/// The media port is bound here and handed to the eventual [`Call`] rather than bound again,
5747/// because the answer already told the far end which port to send to. Binding a second one
5748/// would make the 200 contradict the 183 for no reason.
5749#[derive(Debug)]
5750pub(crate) struct Early {
5751 pub(crate) media: MediaSession,
5752 pub(crate) capabilities: Capabilities,
5753 pub(crate) settled: Settled,
5754 pub(crate) media_address: IpAddr,
5755 pub(crate) media_bind_address: IpAddr,
5756 /// The codec set the provisional's answer was built from, kept because the exchange is not
5757 /// over: an UPDATE may reoffer before the 200, and it has to be answered from the same set
5758 /// rather than from the default one.
5759 pub(crate) codecs: Codecs,
5760 /// The exact application policy retained when the provisional becomes a confirmed call.
5761 pub(crate) keying: Keying,
5762}
5763
5764/// A media offer placed in a reliable provisional and awaiting its answer in PRACK.
5765#[derive(Debug)]
5766pub(crate) struct EarlyOffer {
5767 port: MediaPort,
5768 capabilities: Capabilities,
5769 offer: SessionDescription,
5770 ice: Option<LocalDescription>,
5771 keying: PendingKeying,
5772 policy_keying: Keying,
5773 media_address: MediaAddress,
5774 codecs: Codecs,
5775}
5776
5777impl EarlyOffer {
5778 pub(crate) async fn bind(
5779 media_address: MediaAddress,
5780 secure: bool,
5781 direction: Direction,
5782 policy: MediaPolicy,
5783 ) -> Result<Self> {
5784 let media_address = media_address.validate()?;
5785 if policy.keying == Keying::DtlsSrtp {
5786 return Err(Error::DtlsEarlyMedia);
5787 }
5788 let port = MediaPort::bind(SocketAddr::new(media_address.bind(), 0))
5789 .await
5790 .map_err(Error::Io)?;
5791 let local_ice = match policy.gathering(true)? {
5792 // As with the ordinary initial offer, mux is not settled until the answer arrives.
5793 Some(gathering) => Some(
5794 port.gather_with_rtcp_mode(&gathering, sipx_sdp::RtcpMode::Separate)
5795 .await,
5796 ),
5797 None => None,
5798 };
5799 let advertised = local_ice
5800 .as_ref()
5801 .and_then(|local| local.default_destination(ComponentId::RTP))
5802 .unwrap_or_else(|| {
5803 SocketAddr::new(media_address.advertised(), port.local_addr().port())
5804 });
5805 let (mut capabilities, keying) =
5806 media_capabilities(policy, advertised.ip(), advertised.port(), secure)?;
5807 capabilities.direction = direction;
5808 let mut offer = offer_from(&capabilities);
5809 if let Some(local) = &local_ice {
5810 add_ice(&mut offer, local, &[]);
5811 }
5812 Ok(Self {
5813 port,
5814 capabilities,
5815 offer,
5816 ice: local_ice,
5817 keying,
5818 policy_keying: policy.keying,
5819 media_address,
5820 codecs: policy.codecs,
5821 })
5822 }
5823
5824 pub(crate) fn description(&self) -> &SessionDescription {
5825 &self.offer
5826 }
5827
5828 pub(crate) async fn settle(mut self, answer: &SessionDescription) -> Result<Early> {
5829 let settled = settle_answer(&self.capabilities, answer, self.codecs)?;
5830 if let Some(local) = self.ice.as_mut() {
5831 let remote = answer
5832 .media
5833 .first()
5834 .map_or(IceNegotiation::Absent, |audio| {
5835 sipx_media::ice::negotiate(answer, audio)
5836 });
5837 local.accept(&remote);
5838 }
5839 let (media, settled) = key_and_start(
5840 self.port,
5841 self.ice,
5842 settled,
5843 self.keying,
5844 answer,
5845 false,
5846 MediaProfile::Standard,
5847 )
5848 .await?;
5849 Ok(Early {
5850 media,
5851 capabilities: self.capabilities,
5852 settled,
5853 media_address: self.media_address.advertised(),
5854 media_bind_address: self.media_address.bind(),
5855 codecs: self.codecs,
5856 keying: self.policy_keying,
5857 })
5858 }
5859}
5860
5861impl Early {
5862 /// Bind a port and answer `offer` with it.
5863 pub(crate) async fn settle(
5864 media_address: MediaAddress,
5865 secure: bool,
5866 offer: &SessionDescription,
5867 policy: MediaPolicy,
5868 ) -> Result<(Self, SessionDescription)> {
5869 let media_address = media_address.validate()?;
5870 if policy.keying == Keying::DtlsSrtp {
5871 return Err(Error::DtlsEarlyMedia);
5872 }
5873 let negotiated = negotiated(offer, policy.codecs)?;
5874 let port = MediaPort::bind(SocketAddr::new(media_address.bind(), 0))
5875 .await
5876 .map_err(Error::Io)?;
5877 let (remote_ice, mut local_ice) = answer_gathering(&port, offer, policy).await?;
5878 let advertised = local_ice
5879 .as_ref()
5880 .and_then(|local| local.default_destination(ComponentId::RTP))
5881 .unwrap_or_else(|| {
5882 SocketAddr::new(media_address.advertised(), port.local_addr().port())
5883 });
5884 let (capabilities, _keying) =
5885 media_capabilities(policy, advertised.ip(), advertised.port(), secure)?;
5886 let mut answer = sipx_sdp::answer(offer, &capabilities);
5887 if let Some(local) = local_ice.as_mut() {
5888 local.accept(&remote_ice);
5889 add_ice(&mut answer, local, &remote_ice.answer_attributes());
5890 } else if policy.ice != IcePolicy::Disabled
5891 && let Some(audio) = answer.media.first_mut()
5892 {
5893 audio.attributes.extend(remote_ice.answer_attributes());
5894 }
5895 if answer
5896 .media
5897 .iter()
5898 .all(sipx_sdp::MediaDescription::is_rejected)
5899 {
5900 return Err(Error::NoCommonCodec);
5901 }
5902 let settled = Settled {
5903 negotiated,
5904 srtp: srtp_keys_answering(capabilities.crypto.as_ref(), offer_crypto(offer)),
5905 };
5906 let media = match local_ice {
5907 Some(local) => port.start_with_ice(settled.media_config(), local)?,
5908 None => port.start(settled.media_config())?,
5909 };
5910 Ok((
5911 Self {
5912 media,
5913 capabilities,
5914 settled,
5915 media_address: media_address.advertised(),
5916 media_bind_address: media_address.bind(),
5917 codecs: policy.codecs,
5918 keying: policy.keying,
5919 },
5920 answer,
5921 ))
5922 }
5923
5924 /// Take the far end's answer to an offer *we* made, which moves only where we send.
5925 ///
5926 /// Nothing is owed back for an answer, so unlike [`Self::reanswer`] this produces no
5927 /// description. An answer that cannot be read leaves the session where it was: the far end
5928 /// accepted something, and guessing which of our formats it meant is worse than keeping
5929 /// what the last completed exchange settled.
5930 pub(crate) async fn adopt_answer(&mut self, answer: &SessionDescription) {
5931 if let Ok(negotiated) = negotiated(answer, self.codecs) {
5932 let settled = Settled {
5933 negotiated,
5934 srtp: self.settled.srtp.clone(),
5935 };
5936 // A failed replacement leaves the working early stream in place. The UPDATE itself
5937 // was usable, so turning a local socket failure into a peer refusal would describe
5938 // the wrong fault; the eventual call still confirms the last session that ran.
5939 // discard: the peer has already answered our UPDATE, so there is no signalling
5940 // response left to change; the still-running media session is the safe fallback.
5941 let _ = self.replace_media(settled).await;
5942 }
5943 }
5944
5945 /// Answer a *later* offer — one that arrived in an UPDATE — on the port already bound.
5946 ///
5947 /// `None` means the description is unusable, and the caller refuses 488 while the early
5948 /// dialog carries on: the same rule a re-INVITE gets in `M-8`, for the same reason.
5949 ///
5950 /// The port does not move. Our own receive address was published in the answer the peer
5951 /// already has, and changing it because *their* description changed would ask them to
5952 /// renegotiate again to learn where we went.
5953 pub(crate) async fn reanswer(
5954 &mut self,
5955 offer: &SessionDescription,
5956 ) -> Option<SessionDescription> {
5957 let negotiated = negotiated(offer, self.codecs).ok()?;
5958 let answer = sipx_sdp::answer(offer, &self.capabilities);
5959 if answer
5960 .media
5961 .iter()
5962 .all(sipx_sdp::MediaDescription::is_rejected)
5963 {
5964 return None;
5965 }
5966 let settled = Settled {
5967 negotiated,
5968 srtp: srtp_keys_answering(self.capabilities.crypto.as_ref(), offer_crypto(offer)),
5969 };
5970 self.replace_media(settled).await.ok()?;
5971 Some(answer)
5972 }
5973
5974 /// Apply an early UPDATE to the session that is already running.
5975 ///
5976 /// This is the same transition [`Call::move_media_if_changed`] performs for a confirmed
5977 /// dialog, but it happens at UPDATE time rather than being deferred to the INVITE's 2xx. The
5978 /// resulting session is then the one confirmation moves into `Call`, so answer time itself
5979 /// still neither rebinds nor leaves a gap.
5980 async fn replace_media(&mut self, settled: Settled) -> Result<()> {
5981 let to = settled.negotiated;
5982 let changed = to.remote != self.settled.negotiated.remote
5983 || to.codec != self.settled.negotiated.codec
5984 || to.wire_payload_type() != self.settled.negotiated.wire_payload_type()
5985 || settled.is_encrypted() != self.settled.is_encrypted();
5986 if changed && !self.media.reconfigure(settled.media_config()).await? {
5987 return Err(Error::Sdp(
5988 "an ICE-backed early session cannot change its media format in place".to_owned(),
5989 ));
5990 }
5991 self.settled = settled;
5992 Ok(())
5993 }
5994}
5995
5996/// Answer an INVITE that has already been rung (RFC 3262).
5997///
5998/// The tag comes from the [`Ringing`](crate::Ringing) rather than being fresh, and that is the
5999/// whole reason this exists. A provisional that established a dialog has already told the caller
6000/// what this side's tag is (RFC 3261 §12.1.1); a 200 with a different one creates a *second*
6001/// dialog. The caller ACKs the dialog it knows about, this side waits for an ACK to the other,
6002/// and the 200 is retransmitted for 32 seconds into a call that is actually up.
6003///
6004/// Answers from the default codec set, [`Codecs::G711`]. [`answer_ringing_with`] takes a
6005/// selection.
6006pub async fn answer_ringing(
6007 endpoint: &Handle,
6008 incoming: &Incoming,
6009 media_address: IpAddr,
6010 ringing: &crate::Ringing,
6011) -> Result<Call> {
6012 answer_ringing_with(
6013 endpoint,
6014 incoming,
6015 media_address,
6016 ringing,
6017 Codecs::default(),
6018 )
6019 .await
6020}
6021
6022/// [`answer_ringing`], from a chosen codec set rather than the default one (`M-30`).
6023///
6024/// The selection is made here rather than at [`ring`](crate::ring) because `ring` sends a
6025/// bodiless provisional: nothing about the session has been said yet when it goes out, so the
6026/// answer this builds is still the first one. That is exactly what separates this from
6027/// [`answer_early`], where the answer left in the 183 and the choice had to be made with it.
6028pub async fn answer_ringing_with(
6029 endpoint: &Handle,
6030 incoming: &Incoming,
6031 media_address: IpAddr,
6032 ringing: &crate::Ringing,
6033 codecs: Codecs,
6034) -> Result<Call> {
6035 answer_ringing_with_policy(
6036 endpoint,
6037 incoming,
6038 media_address,
6039 ringing,
6040 MediaPolicy::default().with_codecs(codecs),
6041 )
6042 .await
6043}
6044
6045/// [`answer_ringing`], using one coherent codec and ICE policy.
6046pub async fn answer_ringing_with_policy(
6047 endpoint: &Handle,
6048 incoming: &Incoming,
6049 media_address: IpAddr,
6050 ringing: &crate::Ringing,
6051 policy: MediaPolicy,
6052) -> Result<Call> {
6053 answer_ringing_with_policy_at(
6054 endpoint,
6055 incoming,
6056 MediaAddress::new(media_address),
6057 ringing,
6058 policy,
6059 )
6060 .await
6061}
6062
6063/// [`answer_ringing_with_policy`] with independent advertised and bound media addresses.
6064pub async fn answer_ringing_with_policy_at(
6065 endpoint: &Handle,
6066 incoming: &Incoming,
6067 media_address: MediaAddress,
6068 ringing: &crate::Ringing,
6069 policy: MediaPolicy,
6070) -> Result<Call> {
6071 // RFC 3262 §3 and §5: a 2xx must not go out while a reliable provisional carrying a session
6072 // description is unacknowledged. This path never puts a description in one — `ring` sends a
6073 // bodiless provisional, and `ring_early` is the entry point that does, where
6074 // [`answer_early`] enforces the MUST. What is left here is the weaker concern: answering
6075 // before the PRACK means retransmitting a `180` at a caller that has moved on, and the
6076 // ringing is stopped either way when `Ringing` drops.
6077 if !ringing.is_acknowledged() {
6078 tracing::debug!("answering before the reliable provisional was acknowledged");
6079 }
6080 let offer = sipx_sdp::parse(&String::from_utf8_lossy(incoming.request.body()))
6081 .map_err(|error| Error::Sdp(error.to_string()))?;
6082 answer_negotiated(
6083 endpoint,
6084 incoming,
6085 media_address,
6086 offer,
6087 ringing.tag(),
6088 Some(ringing.is_reliable()),
6089 None,
6090 policy,
6091 &[],
6092 )
6093 .await
6094}
6095
6096/// Answer an INVITE that was rung with [`crate::rel::ring_early`].
6097///
6098/// The counterpart of [`answer_ringing`] for a dialog whose offer/answer already completed in
6099/// the provisional. Three things follow from that and none is optional:
6100///
6101/// - **The provisional must already be acknowledged.** RFC 3262 §5 is a MUST: a UAS that put a
6102/// session description in a reliable provisional delays the 2xx until that provisional is
6103/// acknowledged. So this returns [`Error::UnacknowledgedProvisional`] rather than answering, and
6104/// the caller keeps feeding messages to [`Ringing::on_prack`](crate::Ringing::on_prack) until
6105/// [`Ringing::is_acknowledged`](crate::Ringing::is_acknowledged) is true. It cannot wait on
6106/// the caller's behalf: the PRACK arrives on the application's own inbox, and this holds the
6107/// `&mut` that handling it would need.
6108/// - **The 200 carries no session description.** There is nothing left to say: the offer was
6109/// answered in the 183, and anything an UPDATE renegotiated afterwards was answered in its own
6110/// 2xx. Repeating the last answer here would be a second answer to the INVITE's offer, and
6111/// repeating the *first* one would silently undo the renegotiation. That is only safe
6112/// *because* of the rule above — the PRACK is proof the caller holds the answer, and without
6113/// it a lost 183 would leave the caller in a confirmed dialog with no description at all.
6114/// - **The media port is the one the provisional named**, not a fresh one, because that is the
6115/// port the far end has already been told to send to.
6116///
6117/// The `Ringing` is borrowed mutably and emptied rather than consumed, because it owns the
6118/// retransmission of the provisional and must go on owning it until it is dropped.
6119pub async fn answer_early(
6120 endpoint: &Handle,
6121 incoming: &Incoming,
6122 ringing: &mut crate::Ringing,
6123) -> Result<Call> {
6124 if !ringing.is_acknowledged() {
6125 return Err(Error::UnacknowledgedProvisional);
6126 }
6127
6128 // Before anything is taken out of the `Ringing`. A 422 leaves here through the `?`, and it
6129 // is a counter-offer rather than a failure — the caller is expected to be rung again — so
6130 // it must not cost the bound port and the session the early exchange settled.
6131 let agreed = negotiate_session(endpoint, incoming).await?;
6132
6133 let (early, dialog, negotiation, peer_allows_update) = ringing.take_early()?;
6134 let target = in_dialog_target(&dialog, Target::new(incoming.source, incoming.transport));
6135
6136 let to_with_tag = {
6137 let existing = incoming
6138 .request
6139 .headers
6140 .value(&HeaderName::To)
6141 .map(|value| String::from_utf8_lossy(&value).into_owned())
6142 .unwrap_or_default();
6143 format!("{};tag={}", strip_header_params(&existing), ringing.tag())
6144 };
6145
6146 let mut response = ResponseBuilder::to_request(&incoming.request, ok_status(), "OK")?
6147 .set_header(&HeaderName::To, Bytes::from(to_with_tag))?
6148 .header(
6149 HeaderName::Contact,
6150 Bytes::from(contact_for(endpoint, incoming.transport)),
6151 )?
6152 .header(
6153 HeaderName::Allow,
6154 Bytes::from_static(update::ALLOW.as_bytes()),
6155 )?;
6156 if let Some(accepted) = agreed {
6157 let expires = SessionExpires {
6158 interval: accepted.interval,
6159 refresher: Some(accepted.refresher),
6160 };
6161 response = response
6162 .header(HeaderName::SessionExpires, Bytes::from(expires.to_string()))?
6163 .header(HeaderName::Supported, Bytes::from_static(b"timer"))?;
6164 if accepted.require {
6165 response = response.header(HeaderName::Require, Bytes::from_static(b"timer"))?;
6166 }
6167 }
6168 let response = response.build();
6169
6170 let media = early.media;
6171 endpoint.respond(&incoming.key, response.clone()).await?;
6172
6173 let ack_stop = CancellationToken::new();
6174 let ack_retransmission = OwnedTask::new(tokio::spawn(retransmit_until_acked(
6175 endpoint.clone(),
6176 incoming.key.clone(),
6177 response,
6178 ack_stop.clone(),
6179 )));
6180
6181 let (events, events_rx) = EventSink::new();
6182 emit_construction_events(&events, Some(ringing.is_reliable()));
6183
6184 Ok(Call {
6185 dialog,
6186 initial_status: OK,
6187 media: Arc::new(media),
6188 retired_media: Vec::new(),
6189 endpoint: endpoint.clone(),
6190 target,
6191 ack_stop: Some(ack_stop),
6192 ack_retransmission: Some(ack_retransmission),
6193 delayed_offer: None,
6194 ended: false,
6195 media_address: early.media_address,
6196 media_bind_address: early.media_bind_address,
6197 codecs: early.codecs,
6198 profile: MediaProfile::Standard,
6199 current: early.settled.negotiated,
6200 peer_ice: peer_ice_credentials(incoming.request.body()),
6201 hold: Direction::SendRecv,
6202 encrypted: early.settled.is_encrypted(),
6203 keying: early.keying,
6204 referral: None,
6205 transfer: None,
6206 session: agreed.map(|accepted| {
6207 SessionState::armed(session::Session {
6208 interval: accepted.interval,
6209 we_refresh: accepted.refresher == session::Refresher::Uas,
6210 })
6211 }),
6212 negotiation,
6213 peer_allows_update,
6214 events,
6215 events_rx: Some(events_rx),
6216 history: HistoryInfo::from_headers(&incoming.request.headers)
6217 .and_then(std::result::Result::ok),
6218 dialog_credentials: None,
6219 admitted_dialog_methods: Vec::new(),
6220 })
6221}
6222
6223/// Settle the RFC 4028 session timer for an incoming INVITE, refusing it if it asks for too
6224/// little.
6225///
6226/// Sends the `422` itself, because the refusal has to carry the floor and the only thing that
6227/// knows the floor is the negotiation. Returning "too brief" and leaving the caller to build
6228/// the response would make the one header that makes a 422 useful optional.
6229async fn negotiate_session(
6230 endpoint: &Handle,
6231 incoming: &Incoming,
6232) -> Result<Option<session::Accepted>> {
6233 Ok(
6234 match session::answer(
6235 incoming
6236 .request
6237 .headers
6238 .typed::<sipx_sip::headers::misc::Supported>()
6239 .and_then(std::result::Result::ok)
6240 .is_some_and(|s| s.contains(session::OPTION_TAG)),
6241 incoming
6242 .request
6243 .headers
6244 .typed::<SessionExpires>()
6245 .and_then(std::result::Result::ok),
6246 incoming
6247 .request
6248 .headers
6249 .typed::<MinSe>()
6250 .and_then(std::result::Result::ok)
6251 .map(|min| min.0),
6252 session::ABSOLUTE_MIN_INTERVAL,
6253 ) {
6254 session::Answer::TooBrief(floor) => {
6255 // RFC 4028 §6: the 422 has to carry the minimum, or the caller learns only that it
6256 // was wrong and not what would be right, and retries the same interval forever.
6257 const INTERVAL_TOO_SMALL: u16 = 422;
6258 let status = StatusCode::new(INTERVAL_TOO_SMALL)
6259 .unwrap_or_else(|| unreachable!("422 is a valid status code"));
6260 let refusal = ResponseBuilder::to_request(
6261 &incoming.request,
6262 status,
6263 "Session Interval Too Small",
6264 )?
6265 .header(HeaderName::MinSe, Bytes::from(floor.as_secs().to_string()))?
6266 .build();
6267 endpoint.respond(&incoming.key, refusal).await?;
6268 return Err(Error::IntervalTooBrief(floor));
6269 }
6270 session::Answer::None => None,
6271 session::Answer::Accept(accepted) => Some(accepted),
6272 },
6273 )
6274}
6275
6276/// Called immediately before the `200` is handed to the transport, to take the invitation.
6277///
6278/// The dispatcher's hook into a path that otherwise knows nothing about invitations. Returning
6279/// `Err` aborts the answer with nothing sent; returning `Ok` means no later CANCEL may draw a
6280/// `487`, because a `200` is about to be on the wire. [`answer_negotiated`] documents why it is
6281/// invoked exactly where it is.
6282///
6283/// `Send + Sync` so that `&Claim` is `Send` and the futures carrying one stay spawnable, which
6284/// `an_answer_future_is_spawnable` holds to.
6285pub(crate) type Claim<'a> = &'a (dyn Fn() -> Result<()> + Send + Sync);
6286
6287/// Answer an INVITE whose offer has already been parsed.
6288///
6289/// `reliable_ringing` is `Some` exactly when this side rang first (via [`crate::rel::ring`]):
6290/// `Some(reliable)` reports whether that provisional was 100rel-acknowledged, and `None` (the
6291/// [`answer`] path) means there is no ringing to report at all.
6292///
6293/// `claim` is the dispatcher's, and is invoked at one specific line below; see [`Claim`].
6294///
6295/// `codecs` is what this side is willing to carry. It bounds the answer at both ends of the one
6296/// exchange: [`negotiated`] may not settle outside it, and [`Codecs::capabilities`] builds the
6297/// answer from it — so the codec the session starts on is always one the answer named.
6298// Eight, and every one of them is a distinct fact about *this* answer that the caller holds and
6299// this does not. Bundling them into a struct would be a struct with one construction site per
6300// caller and no behaviour, which moves the argument list rather than shortening it.
6301#[allow(clippy::too_many_arguments)]
6302#[allow(
6303 clippy::too_many_lines,
6304 reason = "the answer lifecycle remains in wire order; custom fields add one retained input"
6305)]
6306async fn answer_negotiated(
6307 endpoint: &Handle,
6308 incoming: &Incoming,
6309 media_address: MediaAddress,
6310 offer: SessionDescription,
6311 tag: &str,
6312 reliable_ringing: Option<bool>,
6313 claim: Option<Claim<'_>>,
6314 policy: MediaPolicy,
6315 headers: &[sipx_sip::Header],
6316) -> Result<Call> {
6317 validate_profile_preflight(policy, incoming.transport)?;
6318 let media_address = media_address.validate()?;
6319 if policy.profile == MediaProfile::BrowserAudio {
6320 sipx_sdp::browser_audio::validate(
6321 &offer,
6322 sipx_sdp::browser_audio::BrowserAudioRole::Offerer,
6323 )?;
6324 }
6325 validate_dtls_offer_setup(&offer, policy)?;
6326 let negotiated = negotiated(&offer, policy.codecs)?;
6327
6328 // The port is bound before the session starts, because the answer has to name it *and* the
6329 // session has to be created with the keys that answer settles on. Starting the session first
6330 // — as this did — leaves nowhere to put them.
6331 let port = MediaPort::bind(SocketAddr::new(media_address.bind(), 0))
6332 .await
6333 .map_err(Error::Io)?;
6334
6335 let (remote_ice, mut local_ice) = answer_gathering(&port, &offer, policy).await?;
6336 let advertised = local_ice
6337 .as_ref()
6338 .and_then(|local| local.default_destination(ComponentId::RTP))
6339 .unwrap_or_else(|| SocketAddr::new(media_address.advertised(), port.local_addr().port()));
6340 let (capabilities, keying) = media_capabilities(
6341 policy,
6342 advertised.ip(),
6343 advertised.port(),
6344 incoming.transport.is_secure(),
6345 )?;
6346 let mut answer_sdp = if policy.profile == MediaProfile::BrowserAudio {
6347 let local = local_ice
6348 .as_ref()
6349 .ok_or(sipx_sdp::browser_audio::ProfileError::IceRequired)?;
6350 let fingerprint = capabilities
6351 .dtls()
6352 .cloned()
6353 .ok_or(sipx_sdp::browser_audio::ProfileError::FingerprintRequired)?;
6354 sipx_sdp::browser_audio::answer(
6355 &offer,
6356 &sipx_sdp::browser_audio::BrowserAudioLocal {
6357 address: advertised.ip(),
6358 port: advertised.port(),
6359 session_id: capabilities.session_id,
6360 session_version: capabilities.session_version,
6361 direction: capabilities.direction,
6362 ice: local.credentials().clone(),
6363 candidates: local.candidates().to_vec(),
6364 fingerprint,
6365 setup: sipx_sdp::fingerprint::SetupCapabilities::both(),
6366 },
6367 )?
6368 } else {
6369 sipx_sdp::answer(&offer, &capabilities)
6370 };
6371 if let Some(local) = local_ice.as_mut() {
6372 local.accept(&remote_ice);
6373 if policy.profile == MediaProfile::Standard {
6374 add_ice(&mut answer_sdp, local, &remote_ice.answer_attributes());
6375 }
6376 } else if policy.ice != IcePolicy::Disabled
6377 && let Some(audio) = answer_sdp.media.first_mut()
6378 {
6379 audio.attributes.extend(remote_ice.answer_attributes());
6380 }
6381 if answer_sdp
6382 .media
6383 .iter()
6384 .all(sipx_sdp::MediaDescription::is_rejected)
6385 {
6386 return Err(Error::NoCommonCodec);
6387 }
6388
6389 // Our key from the answer we just built, theirs from the offer we were sent.
6390 let settled = Settled {
6391 negotiated,
6392 srtp: srtp_keys_answering(capabilities.crypto.as_ref(), offer_crypto(&offer)),
6393 };
6394 let to_with_tag = {
6395 let existing = incoming
6396 .request
6397 .headers
6398 .value(&HeaderName::To)
6399 .map(|value| String::from_utf8_lossy(&value).into_owned())
6400 .unwrap_or_default();
6401 format!("{};tag={tag}", strip_header_params(&existing))
6402 };
6403
6404 let agreed = negotiate_session(endpoint, incoming).await?;
6405
6406 let response = ok_with_answer(
6407 endpoint,
6408 incoming,
6409 &to_with_tag,
6410 &answer_sdp,
6411 agreed,
6412 headers,
6413 )?;
6414
6415 // Before the 200, not after. An INVITE with no usable `Contact` cannot form a dialog
6416 // (RFC 3261 §12.1.1), and answering first would put a 2xx on the wire for a call this side
6417 // is then unable to hold: the caller ACKs, believes it has a confirmed dialog, and streams
6418 // media at an endpoint that has forgotten it and can never send the BYE.
6419 let dialog = Dialog::from_request(&incoming.request, tag).ok_or(Error::NoDialog)?;
6420 let target = in_dialog_target(&dialog, Target::new(incoming.source, incoming.transport));
6421
6422 // The last thing before the `200` leaves, and that placement is the whole contract.
6423 //
6424 // Taking the invitation *early* would be simpler, but every fallible step above — parsing the
6425 // offer, binding the port, negotiating the session, building the response, forming the dialog
6426 // — can return `Err` with nothing on the wire. An invitation taken by one of those failures
6427 // is one no CANCEL can ever end: the CANCEL draws its `200`, the `487` is suppressed because
6428 // the invitation looks answered, and the INVITE transaction is left without a final response
6429 // for the caller's Timer B to resolve.
6430 //
6431 // Taking it *here* keeps the guarantee the early claim was for. From this line on, the only
6432 // fallible expression is `respond` itself; everything after it is infallible. So a CANCEL
6433 // arriving from now on finds the invitation taken and correctly sends no `487` behind the
6434 // `200` — and a CANCEL arriving a moment earlier is honoured in full, which is what it is
6435 // owed, because nothing has been sent yet.
6436 //
6437 // `respond` failing is the one case that stays claimed. That is deliberate: a stream
6438 // transport can write part of a response before erroring, so "it failed" is not proof that
6439 // nothing reached the caller, and a `487` chasing a `200` is the worse of the two outcomes.
6440 if let Some(claim) = claim {
6441 claim()?;
6442 }
6443
6444 endpoint.respond(&incoming.key, response.clone()).await?;
6445
6446 let ack_stop = CancellationToken::new();
6447 let mut ack_retransmission = OwnedTask::new(tokio::spawn(retransmit_until_acked(
6448 endpoint.clone(),
6449 incoming.key.clone(),
6450 response,
6451 ack_stop.clone(),
6452 )));
6453
6454 // The answer must leave before an active answerer sends ClientHello. A caller is permitted to
6455 // wait for the final SDP (and then its ACK) before opening the media path.
6456 let started = Box::pin(key_and_start(
6457 port,
6458 local_ice,
6459 settled,
6460 keying,
6461 &offer,
6462 true,
6463 policy.profile,
6464 ))
6465 .await;
6466 let (media, settled) = match started {
6467 Ok(started) => started,
6468 Err(error) => {
6469 cancel_and_join(&ack_stop, &mut ack_retransmission).await;
6470 return Err(error);
6471 }
6472 };
6473
6474 // As in `dial_with`: emitted at construction, from what was actually observed (ringing
6475 // first, if this path came through it) rather than recomputed afterwards.
6476 let (events, events_rx) = EventSink::new();
6477 emit_construction_events(&events, reliable_ringing);
6478
6479 Ok(Call {
6480 dialog,
6481 initial_status: OK,
6482 media: Arc::new(media),
6483 retired_media: Vec::new(),
6484 endpoint: endpoint.clone(),
6485 target,
6486 ack_stop: Some(ack_stop),
6487 ack_retransmission: Some(ack_retransmission),
6488 delayed_offer: None,
6489 ended: false,
6490 media_address: media_address.advertised(),
6491 media_bind_address: media_address.bind(),
6492 codecs: policy.codecs,
6493 profile: policy.profile,
6494 current: settled.negotiated,
6495 peer_ice: peer_ice_credentials(incoming.request.body()),
6496 hold: Direction::SendRecv,
6497 encrypted: policy.profile == MediaProfile::BrowserAudio || settled.srtp.is_some(),
6498 keying: policy.keying,
6499 referral: None,
6500 transfer: None,
6501 session: agreed.map(|accepted| {
6502 SessionState::armed(session::Session {
6503 interval: accepted.interval,
6504 we_refresh: accepted.refresher == session::Refresher::Uas,
6505 })
6506 }),
6507 negotiation: update::Negotiation::idle(),
6508 // From the INVITE, which RFC 3311 §4 asks a compliant UAC to put it on.
6509 peer_allows_update: update::peer_allows(&incoming.request.headers),
6510 events,
6511 events_rx: Some(events_rx),
6512 history: HistoryInfo::from_headers(&incoming.request.headers)
6513 .and_then(std::result::Result::ok),
6514 dialog_credentials: None,
6515 admitted_dialog_methods: Vec::new(),
6516 })
6517}
6518
6519/// Resend a 2xx on the T1 backoff until the ACK arrives or 64·T1 has passed.
6520async fn retransmit_until_acked(
6521 endpoint: Handle,
6522 key: sipx_sip::transaction::TransactionKey,
6523 response: Response,
6524 stop: CancellationToken,
6525) {
6526 let t1 = Duration::from_millis(500);
6527 let mut interval = t1;
6528 let mut elapsed = Duration::ZERO;
6529 let give_up = t1 * 64;
6530
6531 loop {
6532 if until_cancelled(&stop, tokio::time::sleep(interval))
6533 .await
6534 .is_none()
6535 {
6536 return;
6537 }
6538 elapsed += interval;
6539 if elapsed >= give_up {
6540 tracing::warn!("no ACK for our 2xx after 64*T1; giving up");
6541 return;
6542 }
6543 let Some(sent) = until_cancelled(&stop, endpoint.respond(&key, response.clone())).await
6544 else {
6545 return;
6546 };
6547 if sent.is_err() {
6548 return;
6549 }
6550 // Doubling capped at T2, exactly as the INVITE client transaction retransmits.
6551 interval = (interval * 2).min(Duration::from_secs(4));
6552 }
6553}
6554
6555/// What waiting for a final response ended in.
6556enum Waited {
6557 /// A final response arrived.
6558 Final {
6559 /// The response itself.
6560 response: Response,
6561 /// Whether a provisional counting as *ringing* — anything past a bare `100 Trying` —
6562 /// was seen first, and whether it was reliable (RFC 3262). `None` when the far end
6563 /// went straight to the final response, which is the one time no `CallEvent::Ringing`
6564 /// belongs on the eventual call's event stream.
6565 ringing: Option<bool>,
6566 },
6567 /// The deadline passed.
6568 GaveUp,
6569 /// The owner asked this attempt to stop.
6570 Cancelled,
6571 /// The transaction ended without a final response.
6572 Gone,
6573 /// The selected transport could not be established or used.
6574 Transport(sipx_transport::Error),
6575}
6576
6577/// Wait for the final response to an INVITE.
6578///
6579/// The transport response stream retains the provisional observation that RFC 3261 §9.1 needs
6580/// if this wait ends in local cancellation.
6581/// What a UAC needs in order to acknowledge a reliable provisional while it waits.
6582struct Acknowledging<'a> {
6583 endpoint: &'a Handle,
6584 invite: &'a Request,
6585 target: &'a Target,
6586 capabilities: &'a Capabilities,
6587 seen: sipx_sip::rel::Sequence,
6588}
6589
6590async fn await_final(
6591 responses: &mut sipx_transport::Responses,
6592 limit: Option<Duration>,
6593 acknowledging: &mut Acknowledging<'_>,
6594 cancelled: &mut Option<Cancelled<'_>>,
6595) -> Waited {
6596 let deadline = limit.map(|limit| tokio::time::Instant::now() + limit);
6597 let mut ringing = None;
6598 loop {
6599 let event = match (deadline, cancelled.as_mut()) {
6600 (None, None) => responses.next().await,
6601 (Some(deadline), None) => {
6602 match tokio::time::timeout_at(deadline, responses.next()).await {
6603 Ok(event) => event,
6604 Err(_elapsed) => return Waited::GaveUp,
6605 }
6606 }
6607 (None, Some(cancelled)) => {
6608 tokio::select! {
6609 biased;
6610 () = cancelled.as_mut() => return Waited::Cancelled,
6611 event = responses.next() => event,
6612 }
6613 }
6614 (Some(deadline), Some(cancelled)) => {
6615 tokio::select! {
6616 biased;
6617 () = cancelled.as_mut() => return Waited::Cancelled,
6618 () = tokio::time::sleep_until(deadline) => return Waited::GaveUp,
6619 event = responses.next() => event,
6620 }
6621 }
6622 };
6623 match event {
6624 Some(sipx_sip::transaction::TuEvent::Response(response)) => {
6625 if response.status.is_final() {
6626 return Waited::Final {
6627 response: *response,
6628 ringing,
6629 };
6630 }
6631 // A bare `100 Trying` only acknowledges that the request arrived (RFC 3261
6632 // §17.2.1); it is not the far end's phone ringing, and 100rel does not apply
6633 // to it either (RFC 3262 §3), so it is excluded from what `ringing` tracks.
6634 if response.status.code() > 100 {
6635 ringing = Some(crate::rel::reliable_sequence(&response).is_some());
6636 }
6637 // RFC 3262 §4. A failure here is logged rather than fatal: the invitation is
6638 // still running, and abandoning a ringing call because one PRACK did not get
6639 // through would be a worse outcome than the unreliability it was fixing.
6640 if let Err(error) = acknowledge(&response, acknowledging).await {
6641 tracing::debug!(%error, "could not acknowledge a reliable provisional");
6642 }
6643 }
6644 Some(sipx_sip::transaction::TuEvent::TransportError) => {
6645 // Preserve TLS verification failures because treating a rejected certificate as
6646 // "no response" hides the security decision the caller must act on. Other send
6647 // failures retain the call API's established NoResponse behavior; changing those
6648 // exit semantics is outside this transport-selection story.
6649 if let Some(error @ sipx_transport::Error::Tls(_)) =
6650 responses.take_transport_error()
6651 {
6652 return Waited::Transport(error);
6653 }
6654 return Waited::Gone;
6655 }
6656 Some(_) => {}
6657 None => return Waited::Gone,
6658 }
6659 }
6660}
6661
6662/// PRACK a reliable provisional, if that is what this is (RFC 3262 §4).
6663async fn acknowledge(response: &Response, ctx: &mut Acknowledging<'_>) -> Result<()> {
6664 let Some(rseq) = crate::rel::reliable_sequence(response) else {
6665 return Ok(());
6666 };
6667 // §4: out of order means an earlier one is missing, and a duplicate has already been
6668 // acknowledged. Neither is PRACKed — re-acknowledging a retransmission would turn one lost
6669 // packet into a stream of PRACKs, and acknowledging a gap would tell the UAS that
6670 // everything up to this number arrived when it did not.
6671 if ctx.seen.accept(rseq) != sipx_sip::rel::Received::Acknowledge {
6672 return Ok(());
6673 }
6674
6675 // §4: "The provisional response MUST establish a dialog if one is not yet created." The
6676 // PRACK is an in-dialog request and has nowhere to go without it.
6677 let mut dialog = Dialog::from_response(ctx.invite, response).ok_or(Error::NoDialog)?;
6678 let target = in_dialog_target(&dialog, ctx.target.clone());
6679 let invite_cseq = ctx
6680 .invite
6681 .headers
6682 .typed::<sipx_sip::CSeq>()
6683 .and_then(std::result::Result::ok)
6684 .map_or(1, |cseq| cseq.sequence);
6685
6686 let body = crate::rel::prack_body(
6687 !ctx.invite.body().is_empty(),
6688 response.body(),
6689 ctx.capabilities,
6690 );
6691 crate::rel::send_prack(ctx.endpoint, &mut dialog, &target, rseq, invite_cseq, body).await
6692}
6693
6694fn normal_clearing_reason() -> ReasonValue {
6695 ReasonValue::q850(16, Some(b"Normal call clearing".to_vec()))
6696}
6697
6698fn request_timeout_reason() -> ReasonValue {
6699 // A constant defined by the SIP status-code space; construction cannot fail.
6700 StatusCode::new(408).map_or_else(
6701 || ReasonValue::q850(102, Some(b"Recovery on timer expiry".to_vec())),
6702 |status| ReasonValue::sip(status, Some(b"Request Timeout".to_vec())),
6703 )
6704}
6705
6706/// Acknowledge a 2xx (RFC 3261 §13.2.2.4).
6707///
6708/// This ACK is not part of the INVITE transaction and has no transaction of its own: it is
6709/// "passed to the transport layer directly for transmission", carries a *new* branch because
6710/// any proxy must treat it as a new request, and is resent only when a retransmitted 2xx
6711/// arrives. Handing it to the transaction layer instead earns it the retransmission timers of
6712/// a non-INVITE request — a stream of duplicate ACKs toward a response that will never come,
6713/// and a spurious timeout 32 seconds into a call that is up and talking.
6714async fn send_ack(endpoint: &Handle, dialog: &Dialog, target: Target) -> Result<()> {
6715 let ack = build_ack(endpoint, dialog, &target)?;
6716 endpoint.send_directly(ack, target).await?;
6717 Ok(())
6718}
6719
6720/// Keep acknowledging a 2xx for as long as the far end keeps retransmitting it.
6721///
6722/// RFC 3261 §13.2.2.4: the UAC core "MUST generate an ACK for each 2xx received", and a
6723/// retransmitted 2xx says the previous ACK never arrived. Nobody else can do this — the INVITE
6724/// transaction has already passed the response up, and RFC 6026's `Accepted` state exists so
6725/// that the retransmission still has somewhere to arrive. Dropping the stream after the first
6726/// answer leaves the far end retransmitting for 64*T1 and then tearing down, from its side, a
6727/// call this side believes is established and is already sending audio into.
6728///
6729/// The same ACK goes out each time, rather than a freshly built one: it acknowledges one
6730/// response, and a new branch on every repeat would present each as a new request.
6731async fn reack_retransmitted_2xx(
6732 endpoint: Handle,
6733 mut responses: sipx_transport::Responses,
6734 ack: Request,
6735 target: Target,
6736) {
6737 while let Some(event) = responses.next().await {
6738 if let sipx_sip::transaction::TuEvent::Response(response) = event
6739 && response.status.is_success()
6740 && endpoint
6741 .send_directly(ack.clone(), target.clone())
6742 .await
6743 .is_err()
6744 {
6745 return;
6746 }
6747 }
6748}
6749
6750fn build_ack(endpoint: &Handle, dialog: &Dialog, target: &Target) -> Result<Request> {
6751 let (local, remote) = dialog.local_and_remote();
6752 let (uri, routes) = dialog.request_target();
6753 let via = format!(
6754 "SIP/2.0/{} {};rport;branch={}",
6755 target.transport.as_str(),
6756 endpoint.sent_by_for(target.transport),
6757 sipx_transport::new_branch()
6758 );
6759 let ack = RequestBuilder::new(Method::Ack, uri)
6760 .header(HeaderName::Via, Bytes::from(via))?
6761 .header(HeaderName::To, Bytes::from(remote))?
6762 .header(HeaderName::From, Bytes::from(local))?
6763 .header(HeaderName::CallId, Bytes::from(dialog.id.call_id.clone()))?
6764 // The ACK for a 2xx carries the INVITE's sequence number, not a new one: it
6765 // acknowledges that request rather than being one of its own.
6766 .cseq(dialog.local_cseq, &Method::Ack)?
6767 .max_forwards(70);
6768 Ok(add_routes(ack, &routes)?.build())
6769}
6770
6771/// Where in-dialog requests go.
6772///
6773/// RFC 3261 §12.2.1.1: the peer's `Contact`, not the address the INVITE was sent to. Those
6774/// differ whenever a redirect, a B2BUA or a load balancer is involved, and using the original
6775/// address means the ACK and the BYE reach the wrong element.
6776///
6777/// A `Contact` naming a hostname would need resolution, which this layer does not do; the
6778/// address the exchange arrived from is the honest fallback, and behind a NAT it is the only
6779/// one that works.
6780pub(crate) fn in_dialog_target(dialog: &Dialog, fallback: Target) -> Target {
6781 // Over a WebSocket or QUIC the `Contact` is not consulted at all. RFC 7118 §5.2: the peer has no
6782 // listening port, its `Contact` names something that will never resolve, and the connection
6783 // the dialog was established on is the only way to reach it. This is the RFC 5923 rule for
6784 // stream transports made absolute — there is no fallback because there is nowhere to fall
6785 // back to, and honouring a `Contact` here would send the BYE to an address that either does
6786 // not answer or belongs to somebody else.
6787 if matches!(
6788 fallback.transport,
6789 TransportKind::Ws | TransportKind::Wss | TransportKind::Quic
6790 ) {
6791 return fallback;
6792 }
6793
6794 // The first hop, which is the remote target only when the dialog has no route set. With
6795 // one they are different elements, and RFC 3261 §12.2.1.1 hands the request to the former.
6796 let hop = dialog.hop();
6797 let Some(sipx_sip::Host::Ip(ip)) = hop.host() else {
6798 return fallback;
6799 };
6800 let transport = hop
6801 .transport()
6802 .and_then(TransportKind::parse)
6803 .unwrap_or(fallback.transport);
6804 let port = hop.port().unwrap_or_else(|| transport.default_port());
6805 Target::new(SocketAddr::new(*ip, port), transport)
6806}
6807
6808pub(crate) fn offer_from(capabilities: &Capabilities) -> SessionDescription {
6809 let mut sdp = SessionDescription::new(
6810 capabilities.address,
6811 capabilities.session_id,
6812 capabilities.session_version,
6813 );
6814 let mut audio = sipx_sdp::MediaDescription::audio(
6815 capabilities.audio_port,
6816 capabilities.audio_formats.clone(),
6817 );
6818 for (payload, mapping) in &capabilities.rtpmaps {
6819 audio.attributes.push(sipx_sdp::Attribute::valued(
6820 "rtpmap",
6821 format!("{payload} {mapping}"),
6822 ));
6823 }
6824 // The key, and the protocol that matches it. Offering `a=crypto` under `RTP/AVP` asks for a
6825 // stream that is encrypted and declared not to be; offering `RTP/SAVP` with no key asks for
6826 // encryption with nothing to key it. Both come from the same place, so neither can drift.
6827 if let Some(crypto) = &capabilities.crypto {
6828 capabilities.protocol().clone_into(&mut audio.protocol);
6829 audio
6830 .attributes
6831 .push(sipx_sdp::Attribute::valued("crypto", crypto.to_value()));
6832 }
6833 // The same rule for DTLS-SRTP, with the fingerprint in place of the key: `UDP/TLS/RTP/SAVP`
6834 // and an `a=fingerprint` come from one place so a stream cannot claim one and carry the
6835 // other. RFC 5763 §5 requires the *offerer* to say `actpass` and let the answerer choose.
6836 if let Some(fingerprint) = capabilities.dtls() {
6837 capabilities.protocol().clone_into(&mut audio.protocol);
6838 audio.attributes.push(sipx_sdp::Attribute::valued(
6839 "fingerprint",
6840 fingerprint.to_value(),
6841 ));
6842 audio.attributes.push(sipx_sdp::Attribute::valued(
6843 "setup",
6844 sipx_sdp::fingerprint::Setup::ActPass.as_str().to_owned(),
6845 ));
6846 }
6847 if capabilities.rtcp_mux {
6848 audio.attributes.push(sipx_sdp::Attribute::flag("rtcp-mux"));
6849 }
6850 audio.set_direction(capabilities.direction);
6851 sdp.media.push(audio);
6852 sdp
6853}
6854
6855/// What negotiation settled on.
6856#[derive(Debug, Clone, Copy)]
6857pub(crate) struct Negotiated {
6858 pub(crate) remote: SocketAddr,
6859 pub(crate) codec: Codec,
6860 /// RTP clock rate of this exact format.
6861 ///
6862 /// Usually fixed by the codec. L16 can be negotiated at more than one rate, so retaining the
6863 /// format's rate is what keeps packet sizing, resampling, and RTP timestamps in agreement.
6864 pub(crate) clock_rate: u32,
6865 /// The payload type to send `codec` with, when the description gave it a number.
6866 ///
6867 /// `None` only for a bare static type matched by number. Anything an rtpmap touched —
6868 /// Opus always, a remapped static possibly — has no number of its own that means anything:
6869 /// 111 is convention, and what the far end listens for is the number *it* assigned.
6870 pub(crate) payload_type: Option<u8>,
6871 /// The payload type our description assigned to packets arriving for this codec.
6872 ///
6873 /// Separate from [`Self::payload_type`], which belongs to the peer's description and is the
6874 /// number used for sending. They are usually equal but dynamic assignments are directional.
6875 pub(crate) receive_payload_type: Option<u8>,
6876 /// The payload type the far end uses for `telephone-event`, if it offered one.
6877 ///
6878 /// Taken from the description rather than assumed, because it is a *dynamic* type: 101 is
6879 /// what sipx offers, not what everyone uses, and assuming it would send keypresses on
6880 /// whatever the far end put that number to.
6881 pub(crate) dtmf: Option<u8>,
6882 /// Whether RTCP shares the RTP port or uses its adjacent control port.
6883 pub(crate) rtcp_mode: sipx_sdp::RtcpMode,
6884}
6885
6886/// What negotiation settled on, plus the keys — which are not `Copy` and do not belong in a
6887/// type that is.
6888#[derive(Debug, Clone)]
6889pub(crate) struct Settled {
6890 pub(crate) negotiated: Negotiated,
6891 srtp: Option<sipx_media::SrtpKeys>,
6892}
6893
6894impl Negotiated {
6895 /// The number this codec actually goes out with: the one the description assigned, or the
6896 /// codec's own when it is a static type nothing remapped.
6897 ///
6898 /// Mirrors [`sipx_media::Config::wire_payload_type`], which is what the session reads — so this
6899 /// is the value to compare when asking whether the wire changed. The raw [`Self::payload_type`]
6900 /// is not: `Some(0)` and `None` are two descriptions of PCMU and the same byte on the wire.
6901 fn wire_payload_type(&self) -> u8 {
6902 self.payload_type
6903 .unwrap_or_else(|| self.codec.payload_type())
6904 }
6905
6906 fn receive_wire_payload_type(&self) -> u8 {
6907 self.receive_payload_type
6908 .unwrap_or_else(|| self.codec.payload_type())
6909 }
6910
6911 fn media_config(self) -> sipx_media::Config {
6912 let mut config = sipx_media::Config::new(self.remote, self.codec);
6913 config.clock_rate = self.clock_rate;
6914 config.payload_type = self.payload_type;
6915 config.receive_payload_type = self.receive_payload_type;
6916 config.dtmf_payload_type = self.dtmf;
6917 config.rtcp_mode = self.rtcp_mode;
6918 config
6919 }
6920}
6921
6922impl Settled {
6923 /// Whether both halves of the keying are present, so the media is actually encrypted.
6924 pub(crate) fn is_encrypted(&self) -> bool {
6925 self.srtp.is_some()
6926 }
6927
6928 pub(crate) fn media_config(&self) -> sipx_media::Config {
6929 let mut config = self.negotiated.media_config();
6930 config.srtp.clone_from(&self.srtp);
6931 config
6932 }
6933}
6934
6935/// The keys an answer to *our* offer settles on, once it has been checked against what we sent.
6936///
6937/// RFC 4568 §5.1.3 makes the check a MUST on the offerer, and this is the only place a call can
6938/// run it: [`sipx_media::SrtpKeys::from_answer`] is the sole route from an answer to keys, and it
6939/// returns which of *our* offers the answer accepted, so the half we key with is the half we sent
6940/// rather than whichever one happened to be first. `docs/specs/srtp.md` §5.4.
6941///
6942/// `offered` is a slice and not one attribute because that is what the check takes. sipx offers
6943/// exactly one today, and a function that quietly assumed so would have to be found again the day
6944/// it offers two.
6945///
6946/// `Ok(None)` means this side offered no key at all — a plain call, which is the only case where
6947/// the absence of an `a=crypto` in the answer is not a failure. When we did offer, an answer
6948/// carrying nothing usable is refused: that is the shape "a suite that was never offered" arrives
6949/// in, since [`sipx_sdp::crypto::Crypto::parse`] refuses a suite sipx cannot key.
6950///
6951/// # Errors
6952///
6953/// [`Error::Sdp`] when the answer accepted a tag and suite this side never offered, or carried no
6954/// key. Not `None`: dropping to an unencrypted call would hand the user an insecure call presented
6955/// as a secure one, and dropping the stream would end the call with nothing anyone can act on.
6956pub(crate) fn srtp_keys(
6957 offered: &[sipx_sdp::crypto::Crypto],
6958 answered: Option<&sipx_sdp::crypto::Crypto>,
6959) -> Result<Option<sipx_media::SrtpKeys>> {
6960 if offered.is_empty() {
6961 // Nothing was offered, so there is nothing to verify and no local half to key with. An
6962 // answer cannot introduce SDES the offer did not ask for (RFC 4568 §5.1.2).
6963 return Ok(None);
6964 }
6965 sipx_media::SrtpKeys::from_answer(offered, answered)
6966 .map(Some)
6967 .map_err(|error| Error::Sdp(error.to_string()))
6968}
6969
6970/// Pair the key we are *answering* with against the far end's offered one.
6971///
6972/// The other side of [`srtp_keys`], and deliberately not the same function. §5.1.3's check is the
6973/// offerer's: here this side chose the attribute and echoed its tag ([`sipx_sdp::answer`], RFC
6974/// 4568 §5.1.2), so there is nothing to verify — only two halves to put together.
6975///
6976/// `None` unless *both* are present. One key is not a session: a stream keyed at one end only
6977/// is a stream the other end cannot read, and treating a half-offer as success would produce a
6978/// call that connects and carries silence.
6979pub(crate) fn srtp_keys_answering(
6980 ours: Option<&sipx_sdp::crypto::Crypto>,
6981 theirs: Option<sipx_sdp::crypto::Crypto>,
6982) -> Option<sipx_media::SrtpKeys> {
6983 let (ours, theirs) = (ours?, theirs?);
6984 Some(sipx_media::SrtpKeys {
6985 local: (ours.master_key().to_vec(), ours.master_salt().to_vec()),
6986 remote: (theirs.master_key().to_vec(), theirs.master_salt().to_vec()),
6987 })
6988}
6989
6990/// The keying the far end offered, from its description. Same shape as the answered one; named
6991/// separately because reading it from an *offer* and from an *answer* are different moments.
6992pub(crate) fn offer_crypto(sdp: &SessionDescription) -> Option<sipx_sdp::crypto::Crypto> {
6993 answered_crypto(sdp)
6994}
6995
6996/// The keying the far end answered with, from its description.
6997fn answered_crypto(sdp: &SessionDescription) -> Option<sipx_sdp::crypto::Crypto> {
6998 sdp.media
6999 .iter()
7000 .find(|m| m.media == "audio" && !m.is_rejected())?
7001 .crypto()
7002}
7003
7004/// The payload type carrying `telephone-event`, per the description's own rtpmaps.
7005fn telephone_event_payload_type(audio: &sipx_sdp::MediaDescription) -> Option<u8> {
7006 audio.formats.iter().find_map(|format| {
7007 let mapping = audio.rtpmap(format)?;
7008 let encoding = mapping.split('/').next().unwrap_or(mapping);
7009 encoding
7010 .eq_ignore_ascii_case("telephone-event")
7011 .then(|| format.parse::<u8>().ok())
7012 .flatten()
7013 })
7014}
7015
7016/// Where to send media, and in what codec, from a description.
7017///
7018/// `codecs` is the set this side offered or answered from: negotiation may only settle on a
7019/// codec the application selected, so an Opus offer answered from a G.711 set settles on
7020/// G.711, not on a codec the answer never named.
7021pub(crate) fn negotiated(sdp: &SessionDescription, codecs: Codecs) -> Result<Negotiated> {
7022 let audio = sdp
7023 .media
7024 .iter()
7025 .find(|m| m.media == "audio" && !m.is_rejected())
7026 .ok_or(Error::NoCommonCodec)?;
7027
7028 // A stream marked `inactive` carries nothing in either direction. Treating it as a working
7029 // call means holding a media session open for audio that will never come.
7030 if audio.direction() == Direction::Inactive {
7031 return Err(Error::NoCommonCodec);
7032 }
7033
7034 let address = sdp.address_for(audio).ok_or(Error::NoCommonCodec)?;
7035
7036 // The first format both sides can carry. The list is already in the offerer's preference
7037 // order, so the first playable one is the one to use. Playable is judged by what the
7038 // format's rtpmap says, never by a dynamic number alone — which is also the reason
7039 // `Codec::from_payload_type` deliberately never returns Opus: 111 is Opus here only because
7040 // this description said so.
7041 //
7042 // `sipx_sdp::answer` decides the same question when it builds the answer that goes on the
7043 // wire, and the two *must* agree: this settles what the session sends, and the answer is what
7044 // the far end was told to expect. They now agree by construction — both ask
7045 // `sipx_sdp::rtpmap::same_format` whether an offered rtpmap names a format this side has, so
7046 // there is one rule rather than two readings of it (`M-31`). What is left of the difference is
7047 // deliberate and one-directional: the answer also names `telephone-event`, which is not a
7048 // codec to settle on. `the_answer_and_the_negotiated_codec_agree` holds the agreement over a
7049 // table of offers, so this paragraph is a claim with a test under it rather than a hope.
7050 //
7051 // `carries` is part of the search and not a test applied to its result. Rejecting afterwards
7052 // would stop at the offerer's first choice and refuse the whole description if that one
7053 // format is outside our set — so an Opus-first offer reaching a G.711 call would come back
7054 // `NoCommonCodec` while the answer this side builds happily names the PCMU further down the
7055 // same list.
7056 let (codec, payload_type, clock_rate) = audio
7057 .formats
7058 .iter()
7059 .find_map(|format| {
7060 codec_of(audio, format)
7061 .filter(|(codec, _, clock_rate)| codecs.carries_format(*codec, *clock_rate))
7062 })
7063 .ok_or(Error::NoCommonCodec)?;
7064
7065 Ok(Negotiated {
7066 remote: SocketAddr::new(address, audio.port),
7067 codec,
7068 clock_rate,
7069 payload_type,
7070 receive_payload_type: payload_type,
7071 dtmf: telephone_event_payload_type(audio),
7072 // On the answering side this is the offer's request, which sipx accepts. On the offering
7073 // side `settle_answer` additionally requires that this side actually offered the flag.
7074 rtcp_mode: if audio.rtcp_mux() {
7075 sipx_sdp::RtcpMode::Mux
7076 } else {
7077 sipx_sdp::RtcpMode::Separate
7078 },
7079 })
7080}
7081
7082/// The codec a format names, and the payload type to put on the wire for it.
7083///
7084/// A format with an rtpmap is matched by the map: RFC 8866 §6.6 makes it authoritative even
7085/// for a static number, which is how an offer of `8` meaning iLBC is not read as PCMA. The
7086/// number is then *dynamic in meaning* — the map could have hung any name on it — so it goes
7087/// home with the codec rather than being reassumed from [`Codec::payload_type`]. Only a bare
7088/// static type, with no map at all, is matched by number.
7089fn codec_of(audio: &sipx_sdp::MediaDescription, format: &str) -> Option<(Codec, Option<u8>, u32)> {
7090 let payload = format.parse::<u8>().ok()?;
7091 if let Some(rtpmap) = audio.rtpmap(format) {
7092 return codec_format(rtpmap).map(|(codec, clock_rate)| (codec, Some(payload), clock_rate));
7093 }
7094 Codec::from_payload_type(payload).map(|codec| (codec, None, codec.clock_rate()))
7095}
7096
7097/// The codec and RTP clock an rtpmap names.
7098///
7099/// L16 is special only in having more than one supported rate. Its name and mono channel count
7100/// are still parsed by SDP's shared format reader; policy decides below whether the exact rate
7101/// was offered locally.
7102fn codec_format(rtpmap: &str) -> Option<(Codec, u32)> {
7103 let parsed = sipx_sdp::rtpmap::Rtpmap::parse(rtpmap).ok()?;
7104 if parsed.encoding().eq_ignore_ascii_case("L16") && parsed.channels() == 1 {
7105 return Some((Codec::L16, parsed.clock_rate()));
7106 }
7107 codec_named(rtpmap).map(|codec| (codec, codec.clock_rate()))
7108}
7109
7110/// The codec an rtpmap value names, if it is one we carry.
7111///
7112/// **The matching rule is not written here.** [`sipx_sdp::rtpmap::same_format`] decides whether two
7113/// `a=rtpmap` values name the same format, and this asks it once per codec sipx can run, against
7114/// the value that codec is offered with. It used to be written out a second time in this function,
7115/// with the clock rate parsed to a `u32` where [`sipx_sdp::answer`] compared the same field as
7116/// text — so the answer on the wire and the codec the session was built with could name different
7117/// formats for one offer (`M-31`).
7118///
7119/// `sipx-sdp` is the authority and not this crate, because the dependency only runs one way:
7120/// [`sipx_sdp::answer`] builds the answer sipx sends and cannot call up into `sipx-call`, so the
7121/// only arrangement in which one implementation serves both is the lower crate holding it. What
7122/// stays here is the half `sipx-sdp` must not learn — which rtpmaps sipx has a codec for, and
7123/// which codecs the application selected.
7124///
7125/// The order of the search does not matter: the values in [`carried`] are distinct formats, so an
7126/// rtpmap matches at most one of them. Preference order is the *offerer's*, and it is applied by
7127/// [`negotiated`] walking `m=`'s format list.
7128fn codec_named(rtpmap: &str) -> Option<Codec> {
7129 carried()
7130 .iter()
7131 .copied()
7132 .find(|&codec| sipx_sdp::rtpmap::same_format(rtpmap, offered_rtpmap(codec)))
7133}
7134
7135/// Every codec sipx can run, and can therefore read out of an rtpmap.
7136///
7137/// Omitting a new [`Codec`] variant here means it is simply never named by an offer, which is the
7138/// safe direction to fail in — the same reasoning as [`Codecs::carries`]. The exhaustive match in
7139/// [`offered_rtpmap`] is what forces someone to decide.
7140fn carried() -> &'static [Codec] {
7141 &[
7142 Codec::Pcmu,
7143 Codec::Pcma,
7144 Codec::L16,
7145 #[cfg(feature = "opus")]
7146 Codec::Opus,
7147 ]
7148}
7149
7150/// The `a=rtpmap` value sipx offers a codec with.
7151///
7152/// The same strings [`sipx_sdp::Capabilities::g711`] and [`sipx_sdp::Capabilities::with_opus`] put
7153/// on the wire, and they have to be: a codec whose value here disagreed with the one offered would
7154/// be a codec negotiation settles on and no answer ever names, which is the whole of `M-31`.
7155/// `the_answer_and_the_negotiated_codec_agree` is what holds the two together, rather than a
7156/// comment asking them to match.
7157///
7158/// RFC 7587 §7 fixes Opus's RTP clock at 48000 and its rtpmap channel count at 2 whatever the
7159/// audio actually is, so `opus/16000` is nothing we have however it is numbered.
7160const fn offered_rtpmap(codec: Codec) -> &'static str {
7161 match codec {
7162 Codec::Pcmu => "PCMU/8000",
7163 Codec::Pcma => "PCMA/8000",
7164 Codec::L16 => "L16/44100/1",
7165 #[cfg(feature = "opus")]
7166 Codec::Opus => "opus/48000/2",
7167 }
7168}
7169
7170/// The `Contact` this endpoint should advertise for a dialog on this transport.
7171///
7172/// Built from the endpoint's *advertised* address rather than its socket's local one. An
7173/// endpoint bound to `0.0.0.0` has a local address that means nothing to a peer, and behind a
7174/// NAT it is private — either way the peer stores it as the dialog's remote target and every
7175/// in-dialog request it sends becomes unroutable.
7176///
7177/// The transport matters because over a WebSocket there is no address to advertise at all: the
7178/// endpoint gives the invented name RFC 7118 §5.2 requires, and marks the URI with the same
7179/// transport token it puts in the `Via`, so a peer that does route on `Contact` knows not to
7180/// try `sip:` on port 5060.
7181#[must_use]
7182pub fn contact_for(endpoint: &Handle, transport: TransportKind) -> String {
7183 match transport {
7184 TransportKind::Ws | TransportKind::Wss | TransportKind::Quic => format!(
7185 "<sip:sipx@{};transport={}>",
7186 endpoint.sent_by_for(transport),
7187 transport.as_str().to_ascii_lowercase()
7188 ),
7189 _ => format!("<sip:sipx@{}>", endpoint.advertised()),
7190 }
7191}
7192
7193/// Answer an INVITE that asks to take the place of an existing call (RFC 3891).
7194///
7195/// The second half of an attended transfer: the transferor has spoken to the target, and hands
7196/// its original call over by telling one party to call the other with a `Replaces` header
7197/// naming the dialog to displace.
7198///
7199/// **The header must name `replaced`, all three fields of it.** A `Call-ID` travels in every
7200/// message of a dialog and is visible to every element on the path; the tags are random and
7201/// known only to the two parties. Accepting a match on the `Call-ID` alone — or trusting the
7202/// caller to have checked — turns this into a call-hijack primitive, so the check is here and
7203/// not in whoever calls it.
7204///
7205/// On success the replaced call is hung up and its media torn down. On failure the new INVITE
7206/// is refused and the existing call is left exactly as it was: a replacement that cannot be
7207/// honoured must not cost the user the call they already had.
7208///
7209/// Answers from the default codec set, [`Codecs::G711`]. [`answer_replacing_with`] takes a
7210/// selection.
7211pub async fn answer_replacing(
7212 endpoint: &Handle,
7213 incoming: &Incoming,
7214 media_address: IpAddr,
7215 replaced: &mut Call,
7216) -> Result<Call> {
7217 answer_replacing_with(
7218 endpoint,
7219 incoming,
7220 media_address,
7221 replaced,
7222 Codecs::default(),
7223 )
7224 .await
7225}
7226
7227/// [`answer_replacing`], from a chosen codec set rather than the default one (`M-30`).
7228///
7229/// `codecs` applies to the *replacement*, which is the only call being negotiated here. The one
7230/// being replaced is hung up, and nothing renegotiates it on the way out.
7231pub async fn answer_replacing_with(
7232 endpoint: &Handle,
7233 incoming: &Incoming,
7234 media_address: IpAddr,
7235 replaced: &mut Call,
7236 codecs: Codecs,
7237) -> Result<Call> {
7238 let Some(asked_for) = Replaces::of(&incoming.request) else {
7239 refuse_request(endpoint, incoming, 400, "Bad Request").await?;
7240 return Err(Error::NoReplaces);
7241 };
7242
7243 if !asked_for.matches(&replaced.dialog) {
7244 // 481, which RFC 3891 §3 asks for and which also gives nothing away: a caller guessing
7245 // tags gets the same answer whether the Call-ID was right or not, so there is nothing
7246 // to search.
7247 refuse_request(endpoint, incoming, 481, "Call/Transaction Does Not Exist").await?;
7248 return Err(Error::NoReplaces);
7249 }
7250
7251 // Answer first. If this fails the old call is untouched, which is the right way round:
7252 // hanging up first and then failing to answer would leave the user with no call at all.
7253 let taken_over = answer_with(endpoint, incoming, media_address, codecs).await?;
7254
7255 // Then end the one being replaced (RFC 3891 §3). Its media stops with it.
7256 //
7257 // discard: the BYE this sends is counted at the transmit as
7258 // `sipx_transport::UnsentCounts::bye` if the endpoint cannot put it on the wire. The `Result`
7259 // is discarded because the takeover has already succeeded on the line above and reporting a
7260 // teardown failure as the *transfer* failing would be false — the caller has the new call
7261 // either way, and `Call::end` has already marked the old one ended locally before the BYE was
7262 // ever built.
7263 let _ = replaced.hang_up().await;
7264
7265 Ok(taken_over)
7266}
7267
7268/// Refuse a request outright.
7269async fn refuse_request(
7270 endpoint: &Handle,
7271 incoming: &Incoming,
7272 status: u16,
7273 reason: &'static str,
7274) -> Result<()> {
7275 let Some(code) = StatusCode::new(status) else {
7276 return Ok(());
7277 };
7278 let response = ResponseBuilder::to_request(&incoming.request, code, reason)?.build();
7279 endpoint.respond(&incoming.key, response).await?;
7280 Ok(())
7281}
7282
7283#[cfg(test)]
7284#[allow(
7285 clippy::unwrap_used,
7286 clippy::expect_used,
7287 clippy::panic,
7288 clippy::indexing_slicing
7289)]
7290mod tests {
7291 use std::fmt::Write as _;
7292 use std::sync::atomic::{AtomicBool, Ordering};
7293 use std::task::Poll;
7294
7295 use super::*;
7296
7297 #[tokio::test]
7298 async fn successful_response_stop_before_first_poll_is_latched() {
7299 let stop = CancellationToken::new();
7300 stop.cancel();
7301 let operation_polled = Arc::new(AtomicBool::new(false));
7302 let observed = Arc::clone(&operation_polled);
7303 let operation = std::future::poll_fn(move |_context| {
7304 observed.store(true, Ordering::SeqCst);
7305 Poll::<()>::Pending
7306 });
7307
7308 assert!(until_cancelled(&stop, operation).await.is_none());
7309 assert!(
7310 !operation_polled.load(Ordering::SeqCst),
7311 "latched cancellation wins before the handoff is first polled"
7312 );
7313 }
7314
7315 #[tokio::test]
7316 async fn successful_response_stop_interrupts_a_pending_handoff() {
7317 let stop = CancellationToken::new();
7318 let worker_stop = stop.clone();
7319 let (polled_tx, polled_rx) = tokio::sync::oneshot::channel();
7320 let mut polled_tx = Some(polled_tx);
7321 let worker = tokio::spawn(async move {
7322 until_cancelled(
7323 &worker_stop,
7324 std::future::poll_fn(move |_context| {
7325 if let Some(polled) = polled_tx.take() {
7326 let _ = polled.send(());
7327 }
7328 Poll::<()>::Pending
7329 }),
7330 )
7331 .await
7332 });
7333 polled_rx.await.expect("the handoff is pending");
7334
7335 stop.cancel();
7336 assert_eq!(worker.await.expect("worker joins"), None);
7337 }
7338
7339 #[tokio::test]
7340 async fn answer_setup_failure_cancels_and_joins_its_retransmitter() {
7341 let stop = CancellationToken::new();
7342 let worker_stop = stop.clone();
7343 let finished = CancellationToken::new();
7344 let worker_finished = finished.clone();
7345 let mut owner = OwnedTask::new(tokio::spawn(async move {
7346 let _ = until_cancelled(&worker_stop, std::future::pending::<()>()).await;
7347 worker_finished.cancel();
7348 }));
7349
7350 cancel_and_join(&stop, &mut owner).await;
7351 assert!(
7352 finished.is_cancelled(),
7353 "setup failure returns only after the retransmitter exits"
7354 );
7355 }
7356
7357 struct TestRetired {
7358 entered: CancellationToken,
7359 release: CancellationToken,
7360 }
7361
7362 impl Retirable for TestRetired {
7363 async fn finish(&self) {
7364 self.entered.cancel();
7365 self.release.cancelled().await;
7366 }
7367 }
7368
7369 #[tokio::test]
7370 async fn cancelled_confirmed_replacement_retains_the_old_owner_for_retry() {
7371 let entered = CancellationToken::new();
7372 let release = CancellationToken::new();
7373 let mut retired = vec![TestRetired {
7374 entered: entered.clone(),
7375 release: release.clone(),
7376 }];
7377 let mut draining = Box::pin(drain_retired(&mut retired));
7378 tokio::select! {
7379 () = entered.cancelled() => {}
7380 () = &mut draining => panic!("retired generation completed before release"),
7381 }
7382 drop(draining);
7383 assert_eq!(retired.len(), 1, "cancellation preserved the old owner");
7384 assert_eq!(
7385 retired_media_snapshot_refusal(retired.len()),
7386 Some(DialogNotQuiescent::MediaCleanup),
7387 "snapshot capture refuses while cleanup ownership is retained"
7388 );
7389
7390 release.cancel();
7391 drain_retired(&mut retired).await;
7392 assert!(retired.is_empty(), "retry joined and removed the old owner");
7393 assert_eq!(retired_media_snapshot_refusal(retired.len()), None);
7394 }
7395
7396 /// M-49's pre-I/O boundary is pure: failure cannot have bound or gathered a socket.
7397 #[cfg(all(feature = "opus", feature = "dtls"))]
7398 #[test]
7399 fn browser_audio_preflight_is_fail_closed_before_io() {
7400 let policy = MediaPolicy::browser_audio();
7401 assert!(validate_profile_preflight(policy, TransportKind::Wss).is_ok());
7402 assert!(matches!(
7403 validate_profile_preflight(policy, TransportKind::Udp),
7404 Err(Error::Profile(
7405 sipx_sdp::browser_audio::ProfileError::InsecureSignalling
7406 ))
7407 ));
7408 assert!(matches!(
7409 validate_profile_preflight(policy.with_ice(IcePolicy::Disabled), TransportKind::Wss),
7410 Err(Error::Profile(
7411 sipx_sdp::browser_audio::ProfileError::IceRequired
7412 ))
7413 ));
7414 assert!(matches!(
7415 validate_profile_preflight(policy.with_keying(Keying::Plain), TransportKind::Wss),
7416 Err(Error::Profile(
7417 sipx_sdp::browser_audio::ProfileError::WeakerMedia
7418 ))
7419 ));
7420 let opus_only = Codecs::ordered(&[crate::CodecPreference::Opus]).expect("Opus build");
7421 assert!(matches!(
7422 validate_profile_preflight(policy.with_codecs(opus_only), TransportKind::Wss),
7423 Err(Error::Profile(
7424 sipx_sdp::browser_audio::ProfileError::CodecSetIncomplete
7425 ))
7426 ));
7427 }
7428
7429 #[cfg(not(feature = "opus"))]
7430 #[test]
7431 fn browser_audio_reports_missing_opus_as_a_typed_pre_io_error() {
7432 assert!(matches!(
7433 validate_profile_preflight(MediaPolicy::browser_audio(), TransportKind::Wss),
7434 Err(Error::Profile(
7435 sipx_sdp::browser_audio::ProfileError::OpusUnavailable
7436 ))
7437 ));
7438 }
7439
7440 #[cfg(all(feature = "opus", feature = "dtls"))]
7441 async fn browser_answer_fixture() -> (
7442 SessionDescription,
7443 SessionDescription,
7444 sipx_media::ice::LocalDescription,
7445 MediaPort,
7446 ) {
7447 let loopback: IpAddr = "127.0.0.1".parse().expect("loopback");
7448 let port = MediaPort::bind(SocketAddr::new(loopback, 0))
7449 .await
7450 .expect("offer port binds");
7451 let options = DialOptions::new("<sip:caller@example.invalid>", loopback)
7452 .with_media_policy(MediaPolicy::browser_audio());
7453 let (_capabilities, offer, local, _keying) =
7454 offered_media(&options, &port, TransportKind::Wss)
7455 .await
7456 .expect("browser offer gathers");
7457 let local = local.expect("browser offer retains ICE");
7458 let identity = sipx_media::dtls::openssl::Identity::generate().expect("answer identity");
7459 let fingerprint = identity.fingerprint().expect("answer fingerprint");
7460 let answer = sipx_sdp::browser_audio::answer(
7461 &offer,
7462 &sipx_sdp::browser_audio::BrowserAudioLocal {
7463 address: loopback,
7464 port: 40_000,
7465 session_id: 9_002,
7466 session_version: 1,
7467 direction: Direction::SendRecv,
7468 ice: sipx_sdp::ice::Credentials::new("peer", "peerPassword0123456789AB")
7469 .expect("answer credentials"),
7470 candidates: vec![
7471 sipx_sdp::ice::Candidate::parse(
7472 "peer 1 UDP 2130706431 127.0.0.1 40000 typ host",
7473 )
7474 .expect("answer candidate"),
7475 ],
7476 fingerprint,
7477 setup: sipx_sdp::fingerprint::SetupCapabilities::both(),
7478 },
7479 )
7480 .expect("complete browser answer");
7481 (offer, answer, local, port)
7482 }
7483
7484 /// `M-49`: an incomplete final answer is refused at the call boundary before the retained ICE
7485 /// description accepts the peer half or any media owner can start.
7486 #[cfg(all(feature = "opus", feature = "dtls"))]
7487 #[tokio::test]
7488 async fn browser_answer_is_fully_validated_before_ice_state_changes() {
7489 let (offer, mut answer, local, _port) = browser_answer_fixture().await;
7490 answer.media[0]
7491 .attributes
7492 .retain(|attribute| attribute.name != "rtcp-mux");
7493 let ice_before = format!("{local:?}");
7494
7495 assert!(matches!(
7496 validate_establishment_answer(
7497 MediaProfile::BrowserAudio,
7498 offer.to_string_sdp().as_bytes(),
7499 &answer,
7500 ),
7501 Err(Error::Profile(
7502 sipx_sdp::browser_audio::ProfileError::RtcpMuxRequired
7503 ))
7504 ));
7505 assert_eq!(
7506 format!("{local:?}"),
7507 ice_before,
7508 "a refused answer did not reach LocalDescription::accept"
7509 );
7510 }
7511
7512 /// Generic codec negotiation permits an answer to change preference order; the named profile
7513 /// does not. The call boundary therefore uses the complete exchange validator, not only the
7514 /// parser for the answer in isolation.
7515 #[cfg(all(feature = "opus", feature = "dtls"))]
7516 #[tokio::test]
7517 async fn browser_answer_cannot_reorder_the_payloads_selected_by_the_offer() {
7518 let (offer, mut answer, local, _port) = browser_answer_fixture().await;
7519 answer.media[0].formats.swap(0, 1);
7520 let ice_before = format!("{local:?}");
7521
7522 assert!(matches!(
7523 validate_establishment_answer(
7524 MediaProfile::BrowserAudio,
7525 offer.to_string_sdp().as_bytes(),
7526 &answer,
7527 ),
7528 Err(Error::Profile(
7529 sipx_sdp::browser_audio::ProfileError::CodecSetIncomplete
7530 ))
7531 ));
7532 assert_eq!(
7533 format!("{local:?}"),
7534 ice_before,
7535 "a refused answer did not reach LocalDescription::accept"
7536 );
7537 }
7538
7539 /// The call boundary does not inherit the generic parser's extensible-candidate tolerance.
7540 /// Every browser-profile line must belong to the bounded host/server-reflexive set before the
7541 /// retained ICE generation is allowed to see any of them.
7542 #[cfg(all(feature = "opus", feature = "dtls"))]
7543 #[tokio::test]
7544 async fn browser_answer_rejects_every_unusable_candidate_before_ice_acceptance() {
7545 let (offer, answer, local, _port) = browser_answer_fixture().await;
7546 let ice_before = format!("{local:?}");
7547 for candidate in [
7548 "not a candidate",
7549 "relay 1 UDP 2130706430 127.0.0.1 40001 typ relay raddr 0.0.0.0 rport 9",
7550 "prflx 1 UDP 2130706429 127.0.0.1 40002 typ prflx raddr 0.0.0.0 rport 9",
7551 ] {
7552 let mut rejected = answer.clone();
7553 rejected.media[0]
7554 .attributes
7555 .push(sipx_sdp::Attribute::valued("candidate", candidate));
7556 assert!(matches!(
7557 validate_establishment_answer(
7558 MediaProfile::BrowserAudio,
7559 offer.to_string_sdp().as_bytes(),
7560 &rejected,
7561 ),
7562 Err(Error::Profile(
7563 sipx_sdp::browser_audio::ProfileError::IceRequired
7564 ))
7565 ));
7566 assert_eq!(
7567 format!("{local:?}"),
7568 ice_before,
7569 "candidate refusal changed retained ICE state: {candidate}"
7570 );
7571 }
7572 }
7573
7574 const IDENTIFIER_SAMPLE_SIZE: u64 = 4096;
7575
7576 fn bit_counts(values: impl IntoIterator<Item = u64>) -> [usize; 64] {
7577 let mut counts = [0; 64];
7578 for value in values {
7579 for (bit, count) in counts.iter_mut().enumerate() {
7580 *count += usize::from(value & (1_u64 << bit) != 0);
7581 }
7582 }
7583 counts
7584 }
7585
7586 /// Both reliable-provisional shapes use the same address pair: an offer in a 183 for an
7587 /// offerless INVITE, and an answer in a 183 for an INVITE carrying an offer.
7588 #[tokio::test]
7589 async fn early_media_binds_locally_and_advertises_the_chosen_address_in_both_roles() {
7590 let advertised: IpAddr = "198.51.100.44".parse().expect("valid");
7591 let bind: IpAddr = "127.0.0.1".parse().expect("valid");
7592 let addresses = MediaAddress::new(advertised).with_bind(bind);
7593
7594 let offered_early = EarlyOffer::bind(
7595 addresses,
7596 false,
7597 Direction::SendRecv,
7598 MediaPolicy::default(),
7599 )
7600 .await
7601 .expect("binds an early offer");
7602 assert_eq!(offered_early.port.local_addr().ip(), bind);
7603 assert_eq!(
7604 offered_early.description().connection,
7605 Some(Connection::new(advertised))
7606 );
7607
7608 let remote_offer = offered("0", &[]);
7609 let (answered_early, answer) =
7610 Early::settle(addresses, false, &remote_offer, MediaPolicy::default())
7611 .await
7612 .expect("binds an early answer");
7613 assert_eq!(answered_early.media.local_addr().ip(), bind);
7614 assert_eq!(answer.connection, Some(Connection::new(advertised)));
7615 }
7616
7617 /// RFC 3261 §19.3 makes a dialog tag a peer-visible identifier which must be hard to guess.
7618 /// Every hexadecimal position is sampled, rather than accepting a 64-bit-looking string whose
7619 /// high half is fixed by truncation or by a counter.
7620 #[test]
7621 fn dialog_tag_keeps_all_sixty_four_random_bits() {
7622 let values = (0..IDENTIFIER_SAMPLE_SIZE).map(|_| {
7623 let tag = token();
7624 assert_eq!(tag.len(), 16, "exactly 64 bits in hexadecimal");
7625 assert!(
7626 tag.bytes()
7627 .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()),
7628 "the tag is canonical lowercase hexadecimal: {tag}"
7629 );
7630 u64::from_str_radix(&tag, 16).expect("the generator wrote hexadecimal")
7631 });
7632 for (bit, ones) in bit_counts(values).iter().copied().enumerate() {
7633 assert!(
7634 (1664..=2432).contains(&ones), // 128 positions * 2 * exp(-2 * 384^2 / 4096) < 1.4e-29.
7635 "dialog tag bit {bit} had {ones} ones in {IDENTIFIER_SAMPLE_SIZE} samples"
7636 );
7637 }
7638 }
7639
7640 /// `token_with_rng` makes the source property part of type checking, not an inference from a
7641 /// finite sample. A deterministic `RngCore` without `CryptoRng` cannot instantiate this call.
7642 #[test]
7643 fn dialog_tag_requires_a_cryptographic_rng_by_construction() {
7644 fn draw<R: rand::CryptoRng + ?Sized>(rng: &mut R) -> String {
7645 token_with_rng(rng)
7646 }
7647
7648 assert_eq!(draw(&mut rand::rng()).len(), 16);
7649 }
7650
7651 /// An audio description with the given formats and rtpmaps, as a peer would send it.
7652 fn offered(formats: &str, rtpmaps: &[&str]) -> SessionDescription {
7653 let mut body = format!(
7654 "v=0\r\n\
7655 o=- 1 1 IN IP4 192.0.2.1\r\n\
7656 s=-\r\n\
7657 c=IN IP4 192.0.2.1\r\n\
7658 t=0 0\r\n\
7659 m=audio 40000 RTP/AVP {formats}\r\n"
7660 );
7661 for rtpmap in rtpmaps {
7662 let _ = write!(body, "a=rtpmap:{rtpmap}\r\n");
7663 }
7664 sipx_sdp::parse(&body).expect("a description this test wrote")
7665 }
7666
7667 /// The default is the G.711 pair, in every build. The `opus` feature adds a variant to
7668 /// [`Codecs`]; it must never move which one `Default` produces, or turning the feature on to
7669 /// get the *option* of Opus would silently change what every existing call offers.
7670 #[test]
7671 fn the_default_codec_set_is_g711() {
7672 assert_eq!(Codecs::default(), Codecs::G711);
7673 let capabilities = Codecs::default().capabilities("192.0.2.9".parse().unwrap(), 40000);
7674 assert!(
7675 !capabilities
7676 .rtpmaps
7677 .iter()
7678 .any(|(_, value)| value.to_ascii_lowercase().contains("opus")),
7679 "the default offer names no Opus: {:?}",
7680 capabilities.rtpmaps
7681 );
7682 }
7683
7684 /// RFC 8866 §6.6 makes the rtpmap authoritative even over a static number. This is the rule
7685 /// that lets an Opus offer arrive at all — 111 means Opus only because the description said
7686 /// so — and the same rule refuses to read an offer of `8` remapped to something else as PCMA.
7687 #[test]
7688 fn a_format_is_read_from_its_rtpmap_and_not_from_its_number() {
7689 let remapped = offered("8 0", &["8 iLBC/8000", "0 PCMU/8000"]);
7690 let settled = negotiated(&remapped, Codecs::G711).expect("PCMU is common");
7691 assert_eq!(settled.codec, Codec::Pcmu);
7692 assert_eq!(
7693 settled.payload_type,
7694 Some(0),
7695 "the number the far end assigned travels with the codec"
7696 );
7697 }
7698
7699 /// A bare static type with no rtpmap at all is the one case matched by number, which is what
7700 /// keeps every G.711-only peer that sends `m=audio … 0 8` and nothing else working.
7701 #[test]
7702 fn a_bare_static_type_is_still_matched_by_number() {
7703 let settled = negotiated(&offered("0", &[]), Codecs::G711).expect("PCMU is static");
7704 assert_eq!(settled.codec, Codec::Pcmu);
7705 assert_eq!(
7706 settled.payload_type, None,
7707 "nothing named it, so nothing overrides `Codec::payload_type`"
7708 );
7709 }
7710
7711 /// M-43: RFC 3551 assigns mono L16 at 44.1 kHz to static payload 11. The adjacent payload
7712 /// 10 is stereo, which sipx's mono media surface deliberately does not claim.
7713 #[test]
7714 fn l16_static_payload_is_mono_at_forty_four_point_one_kilohertz() {
7715 let l16 = Codecs::ordered(&[crate::CodecPreference::L16]).expect("L16 selection");
7716 let settled = negotiated(&offered("11", &[]), l16).expect("static mono L16");
7717 assert_eq!(settled.codec, Codec::L16);
7718 assert_eq!(settled.payload_type, None);
7719 assert_eq!(settled.clock_rate, 44_100);
7720 assert!(matches!(
7721 negotiated(&offered("10", &[]), l16),
7722 Err(Error::NoCommonCodec)
7723 ));
7724 }
7725
7726 /// M-43: an L16 rate outside the static assignment is identified by rtpmap and its dynamic
7727 /// payload number travels with the session. Only rates this policy actually offers settle.
7728 #[test]
7729 fn l16_dynamic_payload_retains_its_explicit_clock_rate() {
7730 let l16 = Codecs::ordered(&[crate::CodecPreference::L16]).expect("L16 selection");
7731 let settled =
7732 negotiated(&offered("110", &["110 L16/8000/1"]), l16).expect("dynamic mono L16");
7733 assert_eq!(settled.codec, Codec::L16);
7734 assert_eq!(settled.payload_type, Some(110));
7735 assert_eq!(settled.clock_rate, 8_000);
7736
7737 assert!(
7738 matches!(
7739 negotiated(&offered("96", &["96 L16/16000/1"]), l16),
7740 Err(Error::NoCommonCodec)
7741 ),
7742 "an unoffered rate is not silently accepted"
7743 );
7744 assert!(
7745 matches!(
7746 negotiated(&offered("96", &["96 L16/8000/2"]), l16),
7747 Err(Error::NoCommonCodec)
7748 ),
7749 "the PCM API is mono"
7750 );
7751 }
7752
7753 /// Each SDP description owns its dynamic assignment. An 8 kHz L16 answer may send on 110
7754 /// while receiving on the 96 this side offered, with one shared negotiated clock.
7755 #[test]
7756 fn l16_answer_keeps_directional_dynamic_payload_assignments() {
7757 let l16 = Codecs::L16;
7758 let capabilities = l16.capabilities("192.0.2.9".parse().expect("address"), 40_000);
7759 let answer = offered("110", &["110 L16/8000/1"]);
7760 let settled = settle_answer(&capabilities, &answer, l16).expect("dynamic L16 answer");
7761 assert_eq!(settled.negotiated.codec, Codec::L16);
7762 assert_eq!(settled.negotiated.clock_rate, 8_000);
7763 assert_eq!(settled.negotiated.wire_payload_type(), 110);
7764 assert_eq!(settled.negotiated.receive_wire_payload_type(), 96);
7765 }
7766
7767 /// The clock rate and channel count are part of a format's identity (RFC 8866 §6.6), so a
7768 /// name sipx knows at a rate it does not is not a match.
7769 #[test]
7770 fn a_known_name_at_an_unknown_clock_rate_is_not_a_match() {
7771 assert_eq!(codec_named("PCMU/16000"), None);
7772 assert_eq!(codec_named("opus/16000/2"), None);
7773 assert_eq!(codec_named("PCMU/8000"), Some(Codec::Pcmu));
7774 assert_eq!(codec_named("pcma/8000"), Some(Codec::Pcma));
7775 }
7776
7777 /// The default build has no Opus, so an offer of it is not a codec that build can carry —
7778 /// and the offer is answered from what *is* common rather than refused. This is the promise
7779 /// the `opus` feature is off by default in order to make: `tests/opus.rs` is gated on the
7780 /// feature and cannot assert anything about the build that lacks it.
7781 #[cfg(not(feature = "opus"))]
7782 #[test]
7783 fn a_default_build_does_not_carry_an_offered_opus() {
7784 assert_eq!(codec_named("opus/48000/2"), None);
7785 let opus_first = offered("111 0", &["111 opus/48000/2", "0 PCMU/8000"]);
7786 let settled = negotiated(&opus_first, Codecs::G711).expect("G.711 is still offered");
7787 assert_eq!(settled.codec, Codec::Pcmu, "the first format sipx carries");
7788 }
7789
7790 /// Selecting a set is what puts a codec on the table, and negotiation may not step outside
7791 /// it. An Opus offer answered from [`Codecs::G711`] settles on G.711 — not because Opus is
7792 /// absent from the build, but because the answer this side builds never named it, and a
7793 /// session started on a codec no answer named sends packets the far end cannot place.
7794 #[cfg(feature = "opus")]
7795 #[test]
7796 fn negotiation_does_not_settle_outside_the_selected_set() {
7797 assert_eq!(codec_named("opus/48000/2"), Some(Codec::Opus));
7798 let opus_first = offered("111 0", &["111 opus/48000/2", "0 PCMU/8000"]);
7799
7800 let from_g711 = negotiated(&opus_first, Codecs::G711).expect("G.711 is still offered");
7801 assert_eq!(from_g711.codec, Codec::Pcmu);
7802
7803 let from_opus = negotiated(&opus_first, Codecs::Opus).expect("Opus is on the table");
7804 assert_eq!(from_opus.codec, Codec::Opus);
7805 assert_eq!(
7806 from_opus.payload_type,
7807 Some(111),
7808 "on the number this offer assigned, not on a number 111 means by itself"
7809 );
7810 }
7811
7812 /// A peer may spell a static type either way — `m=audio … 0` alone, or the same thing with a
7813 /// redundant `a=rtpmap:0 PCMU/8000` — and RFC 8866 §6.6 allows both for the same codec.
7814 ///
7815 /// So moving between the two spellings is not a *change*, and [`Call::move_media_if_changed`]
7816 /// must not rebuild the session for it: rebuilding costs an audible gap, and some peers
7817 /// re-INVITE every thirty seconds as a keep-alive. `negotiated` does record the difference —
7818 /// `Some(0)` against `None`, which is a true fact about what the description said — so the
7819 /// comparison is on [`Negotiated::wire_payload_type`], where the two collapse to the one byte
7820 /// that actually goes out.
7821 #[test]
7822 fn a_redundant_rtpmap_for_a_static_type_is_not_a_change() {
7823 let mapped = negotiated(&offered("0", &["0 PCMU/8000"]), Codecs::G711).expect("PCMU");
7824 let bare = negotiated(&offered("0", &[]), Codecs::G711).expect("PCMU");
7825
7826 assert_eq!(mapped.codec, bare.codec);
7827 assert_eq!(mapped.payload_type, Some(0), "the rtpmap named it");
7828 assert_eq!(bare.payload_type, None, "nothing named it");
7829 assert_eq!(
7830 mapped.wire_payload_type(),
7831 bare.wire_payload_type(),
7832 "the same byte goes on the wire either way, so the session must not move",
7833 );
7834 }
7835
7836 /// S-36 / RFC 3264 §6.1: the offer and answer may assign different dynamic numbers to the
7837 /// same format. The answer's number is what we send; the offer's remains what we receive.
7838 #[test]
7839 fn an_asymmetric_answer_keeps_each_directions_payload_number() {
7840 let address = "192.0.2.9".parse().expect("address");
7841 let mut capabilities = Capabilities::g711(address, 40_000);
7842 capabilities.audio_formats = vec!["111".to_owned()];
7843 capabilities.rtpmaps = vec![("111".to_owned(), "PCMU/8000".to_owned())];
7844 let answer = offered("96", &["96 PCMU/8000"]);
7845
7846 let settled = settle_answer(&capabilities, &answer, Codecs::G711).expect("same format");
7847 assert_eq!(settled.negotiated.wire_payload_type(), 96);
7848 assert_eq!(settled.negotiated.receive_wire_payload_type(), 111);
7849 let config = settled.media_config();
7850 assert_eq!(
7851 config.wire_payload_type(),
7852 96,
7853 "send with the peer's number"
7854 );
7855 assert_eq!(
7856 config.receive_wire_payload_type(),
7857 111,
7858 "receive with our number"
7859 );
7860 }
7861
7862 /// An *answer* naming a codec outside the selected set is refused, so nothing keys a session
7863 /// on it.
7864 ///
7865 /// Pinned separately from `negotiated` because of where the refusal lands rather than what it
7866 /// returns. It is a failure mode `M-30` adds to `settle_answer`, which had no codec opinion
7867 /// before; on this branch an early answer that trips it is swallowed by
7868 /// `Dialing::adopt_early_answer`, but that function propagates on `main` after `S-25`, so once
7869 /// the two are merged this same refusal ends the invitation over a CANCEL. That is a call
7870 /// termination neither branch produces alone, which is why the precondition is worth holding
7871 /// here rather than waiting for the merge to discover it.
7872 ///
7873 /// True in both feature configurations for two different reasons: with `opus` off no rtpmap can
7874 /// name Opus at all, and with it on `Codecs::G711` does not carry it.
7875 #[test]
7876 fn an_answer_outside_the_selected_set_is_refused() {
7877 let opus_only = offered("111", &["111 opus/48000/2"]);
7878 let capabilities =
7879 Capabilities::g711("127.0.0.1".parse().expect("loopback address"), 40_000);
7880 assert!(matches!(
7881 settle_answer(&capabilities, &opus_only, Codecs::G711),
7882 Err(Error::NoCommonCodec)
7883 ));
7884 }
7885
7886 #[test]
7887 fn the_initial_call_offer_requests_rtcp_mux() {
7888 let (capabilities, _keying) = media_capabilities(
7889 MediaPolicy::default(),
7890 "127.0.0.1".parse().expect("loopback address"),
7891 40_000,
7892 false,
7893 )
7894 .expect("default media capabilities");
7895 let offer = offer_from(&capabilities);
7896
7897 assert!(capabilities.rtcp_mux);
7898 assert!(offer.media.first().expect("audio offer").rtcp_mux());
7899 }
7900
7901 #[test]
7902 fn the_answer_settles_mux_or_the_separate_port_fallback_without_a_retry() {
7903 let capabilities =
7904 Capabilities::g711("127.0.0.1".parse().expect("loopback address"), 40_000)
7905 .with_rtcp_mux();
7906 let separate_answer = offered("0", &["0 PCMU/8000"]);
7907 let separate = settle_answer(&capabilities, &separate_answer, Codecs::G711)
7908 .expect("the answer remains usable");
7909 assert_eq!(separate.negotiated.rtcp_mode, sipx_sdp::RtcpMode::Separate);
7910
7911 let mut mux_answer = separate_answer;
7912 mux_answer
7913 .media
7914 .first_mut()
7915 .expect("audio answer")
7916 .attributes
7917 .push(sipx_sdp::Attribute::flag("rtcp-mux"));
7918 let mux = settle_answer(&capabilities, &mux_answer, Codecs::G711)
7919 .expect("the muxed answer remains usable");
7920 assert_eq!(mux.negotiated.rtcp_mode, sipx_sdp::RtcpMode::Mux);
7921
7922 let not_offered =
7923 Capabilities::g711("127.0.0.1".parse().expect("loopback address"), 40_000);
7924 let unasked = settle_answer(¬_offered, &mux_answer, Codecs::G711)
7925 .expect("an unasked attribute does not break the answer");
7926 assert_eq!(
7927 unasked.negotiated.rtcp_mode,
7928 sipx_sdp::RtcpMode::Separate,
7929 "an answer cannot negotiate a feature that was not offered"
7930 );
7931 }
7932
7933 /// A running one-port session cannot accept an in-dialog offer that drops mux while retaining
7934 /// its old socket owner. The same typed guard is used before inbound state is applied.
7935 #[test]
7936 fn an_inbound_reoffer_cannot_remove_the_running_mux_mode() {
7937 let offered_without_mux = offered("0", &["0 PCMU/8000"]);
7938 let answered_without_mux = offered("0", &["0 PCMU/8000"]);
7939 let proposed = exchanged_rtcp_mode(&offered_without_mux, &answered_without_mux);
7940
7941 assert!(matches!(
7942 preserve_rtcp_mode(sipx_sdp::RtcpMode::Mux, proposed),
7943 Err(Error::RtcpModeChange {
7944 current: sipx_sdp::RtcpMode::Mux,
7945 proposed: sipx_sdp::RtcpMode::Separate,
7946 })
7947 ));
7948 }
7949
7950 /// The outbound mirror: omission in an answer to a later offer is an explicit failure and
7951 /// leaves the established mux session in place instead of binding an unadvertised replacement.
7952 #[test]
7953 fn an_outbound_reoffer_answer_cannot_remove_the_running_mux_mode() {
7954 let answer_without_mux = offered("0", &["0 PCMU/8000"]);
7955 let renegotiated = negotiated(&answer_without_mux, Codecs::G711).expect("PCMU answer");
7956
7957 assert!(matches!(
7958 preserve_rtcp_mode(sipx_sdp::RtcpMode::Mux, renegotiated.rtcp_mode),
7959 Err(Error::RtcpModeChange {
7960 current: sipx_sdp::RtcpMode::Mux,
7961 proposed: sipx_sdp::RtcpMode::Separate,
7962 })
7963 ));
7964 }
7965
7966 /// Session-level RFC 4145 roles are resolved identically by both call roles, including the
7967 /// passive answer that makes the offerer the DTLS client.
7968 #[test]
7969 fn session_level_setup_selects_the_complementary_call_role() {
7970 let mut offer = offered("0", &["0 PCMU/8000"]);
7971 offer
7972 .attributes
7973 .push(sipx_sdp::Attribute::valued("setup", "actpass"));
7974 assert_eq!(
7975 dtls_local_setup(&offer, true).expect("answerer role"),
7976 sipx_sdp::fingerprint::Setup::Active
7977 );
7978
7979 for (answer, expected) in [
7980 ("active", sipx_sdp::fingerprint::Setup::Passive),
7981 ("passive", sipx_sdp::fingerprint::Setup::Active),
7982 ] {
7983 let mut description = offered("0", &["0 PCMU/8000"]);
7984 description
7985 .attributes
7986 .push(sipx_sdp::Attribute::valued("setup", answer));
7987 assert_eq!(
7988 dtls_local_setup(&description, false).expect("offerer role"),
7989 expected
7990 );
7991 }
7992 }
7993
7994 /// `SETUP-2` through the actual call/media boundary: a passive answer makes the sipx offerer
7995 /// run the DTLS client handshake, and a real server completes it with both fingerprints
7996 /// verified before the returned media session starts.
7997 #[cfg(feature = "dtls")]
7998 #[tokio::test]
7999 async fn a_passive_answer_wires_the_offerer_as_the_dtls_client() {
8000 let client_port = MediaPort::bind("127.0.0.1:0".parse().expect("client address"))
8001 .await
8002 .expect("binds client");
8003 let server_port = MediaPort::bind("127.0.0.1:0".parse().expect("server address"))
8004 .await
8005 .expect("binds server");
8006 let client_address = client_port.local_addr();
8007 let server_address = server_port.local_addr();
8008 let client_identity =
8009 sipx_media::dtls::openssl::Identity::generate().expect("client identity");
8010 let server_identity =
8011 sipx_media::dtls::openssl::Identity::generate().expect("server identity");
8012 let client_fingerprint = client_identity.fingerprint().expect("client fingerprint");
8013 let server_fingerprint = server_identity.fingerprint().expect("server fingerprint");
8014
8015 let mut passive_answer = offered("0", &["0 PCMU/8000"]);
8016 passive_answer.attributes.extend([
8017 sipx_sdp::Attribute::valued("setup", "passive"),
8018 sipx_sdp::Attribute::valued("fingerprint", server_fingerprint.to_value()),
8019 ]);
8020 let settled = Settled {
8021 negotiated: Negotiated {
8022 remote: server_address,
8023 codec: Codec::Pcmu,
8024 clock_rate: 8_000,
8025 payload_type: Some(0),
8026 receive_payload_type: Some(0),
8027 dtmf: None,
8028 rtcp_mode: sipx_sdp::RtcpMode::Separate,
8029 },
8030 srtp: None,
8031 };
8032 let handshake_bound = Duration::from_secs(5); // Bounds a failed handshake; not ordering.
8033 let server = server_port.key_with_dtls(
8034 server_identity,
8035 client_address,
8036 sipx_media::dtls::Role::Server,
8037 client_fingerprint,
8038 handshake_bound,
8039 );
8040 let client = key_and_start(
8041 client_port,
8042 None,
8043 settled,
8044 PendingKeying::Dtls(client_identity),
8045 &passive_answer,
8046 false,
8047 MediaProfile::Standard,
8048 );
8049
8050 let (server, client) = tokio::join!(server, client);
8051 let (_server_port, _server_keys) = server.expect("server handshake completes");
8052 let (client_session, client_settled) = client.expect("offerer handshake completes");
8053 assert!(client_settled.is_encrypted());
8054 client_session.stop();
8055 }
8056
8057 /// A `holdconn` DTLS offer is rejected by the call preflight before the answering path can
8058 /// bind a media port or send a successful response.
8059 #[test]
8060 fn a_holdconn_dtls_offer_is_a_typed_pre_response_refusal() {
8061 let mut offer = offered("0", &["0 PCMU/8000"]);
8062 offer
8063 .attributes
8064 .push(sipx_sdp::Attribute::valued("setup", "holdconn"));
8065 let policy = MediaPolicy::default().with_keying(Keying::DtlsSrtp);
8066
8067 assert!(matches!(
8068 validate_dtls_offer_setup(&offer, policy),
8069 Err(Error::DtlsSetup(
8070 sipx_sdp::fingerprint::SetupRoleError::UnresolvedOffer(
8071 sipx_sdp::fingerprint::Setup::HoldConn
8072 )
8073 ))
8074 ));
8075 }
8076
8077 /// An initial mux offer retains component 2 and advertises its explicit `a=rtcp` destination,
8078 /// so an answer omitting mux can take the fallback without a second offer.
8079 #[tokio::test]
8080 async fn an_initial_mux_ice_offer_carries_the_control_fallback() {
8081 let loopback: IpAddr = "127.0.0.1".parse().expect("loopback");
8082 let port = MediaPort::bind(SocketAddr::new(loopback, 0))
8083 .await
8084 .expect("binds media");
8085 let options = DialOptions::new("<sip:caller@example.invalid>", loopback)
8086 .with_media_policy(MediaPolicy::default().with_ice(IcePolicy::Host));
8087 let (_capabilities, offer, local, _keying) =
8088 offered_media(&options, &port, TransportKind::Udp)
8089 .await
8090 .expect("gathers an offer");
8091 let local = local.expect("ICE description");
8092 let audio = offer.media.first().expect("audio offer");
8093
8094 assert!(audio.rtcp_mux(), "mux is offered");
8095 assert!(
8096 local
8097 .candidates()
8098 .iter()
8099 .any(|candidate| candidate.component == ComponentId::RTCP),
8100 "the initial offer retains the component-2 fallback"
8101 );
8102 assert!(
8103 audio.attribute("rtcp").is_some(),
8104 "the fallback control destination is explicit"
8105 );
8106 }
8107
8108 /// Once an answer agrees to mux, its local ICE half contains component 1 alone.
8109 #[tokio::test]
8110 async fn a_muxed_ice_answer_gathers_one_component() {
8111 let mut offer = offered("0", &["0 PCMU/8000"]);
8112 let audio = offer.media.first_mut().expect("audio offer");
8113 audio.attributes.extend([
8114 sipx_sdp::Attribute::flag("rtcp-mux"),
8115 sipx_sdp::Attribute::valued("ice-ufrag", "peer"),
8116 sipx_sdp::Attribute::valued("ice-pwd", "peerPassword0123456789AB"),
8117 sipx_sdp::Attribute::valued("candidate", "1 1 UDP 2130706431 192.0.2.1 40000 typ host"),
8118 ]);
8119 let port = MediaPort::bind("127.0.0.1:0".parse().expect("loopback"))
8120 .await
8121 .expect("binds media");
8122 let (_remote, local) = answer_gathering(
8123 &port,
8124 &offer,
8125 MediaPolicy::default().with_ice(IcePolicy::Host),
8126 )
8127 .await
8128 .expect("gathers answer");
8129 let local = local.expect("ICE description");
8130
8131 assert_eq!(local.candidates().len(), 1);
8132 assert_eq!(local.candidates()[0].component, ComponentId::RTP);
8133 assert_eq!(local.default_destination(ComponentId::RTCP), None);
8134 }
8135
8136 /// An offer with nothing sipx carries is refused rather than answered on a guess.
8137 #[test]
8138 fn an_offer_of_nothing_we_carry_has_no_common_codec() {
8139 let g729 = offered("18", &["18 G729/8000"]);
8140 assert!(matches!(
8141 negotiated(&g729, Codecs::G711),
8142 Err(Error::NoCommonCodec)
8143 ));
8144 }
8145
8146 /// One row of the agreement table: an offer, and the set the application selected.
8147 ///
8148 /// The property is in [`tests::the_answer_and_the_negotiated_codec_agree`]. The rows exist so
8149 /// it is held against a *class* of rtpmap spellings rather than the one spelling that happened
8150 /// to be found — `M-31` was filed because a fix aimed at `08000` alone would leave the shape
8151 /// in place.
8152 struct Agreement {
8153 /// Why this row is in the table. Quoted in every failure, because a table-driven
8154 /// assertion that only prints the values makes the reader guess what was being tested.
8155 why: &'static str,
8156 /// The `m=audio` format list, in the offerer's preference order.
8157 formats: &'static str,
8158 /// The offer's `a=rtpmap` attribute values.
8159 rtpmaps: &'static [&'static str],
8160 /// The set the application selected for this call.
8161 codecs: Codecs,
8162 }
8163
8164 /// The offers the agreement must hold over, in every build.
8165 ///
8166 /// Derived from `docs/specs/sdp-format-identity.md` §4.4's vectors. A `const` table rather than
8167 /// a function that builds one: it is data, and a hundred lines of data is not a hundred lines
8168 /// of control flow for anyone reading it — or for `clippy::too_many_lines`.
8169 const AGREEMENT_TABLE: &[Agreement] = &[
8170 Agreement {
8171 why: "a clock rate with a leading zero is the same rate — `08000` and `8000` are \
8172 numerically equal and textually different, which is the split M-31 was \
8173 filed for",
8174 formats: "0 8",
8175 rtpmaps: &["0 PCMU/08000", "8 PCMA/8000"],
8176 codecs: Codecs::G711,
8177 },
8178 Agreement {
8179 why: "the same split in the *channel* field, so a fix aimed at the clock rate \
8180 alone does not close the story",
8181 formats: "0 8",
8182 rtpmaps: &["0 PCMU/8000/01", "8 PCMA/8000"],
8183 codecs: Codecs::G711,
8184 },
8185 Agreement {
8186 why: "an offer that puts a codec sipx does not carry first: both rules must skip \
8187 it and settle further down the list, not refuse the stream",
8188 formats: "18 0",
8189 rtpmaps: &["18 G729/8000", "0 PCMU/8000"],
8190 codecs: Codecs::G711,
8191 },
8192 Agreement {
8193 why: "a dynamic number carrying a codec sipx does have — 96 means PCMU here only \
8194 because this offer said so (RFC 8866 §6.6), and both rules must read the \
8195 map rather than the number",
8196 formats: "96 0",
8197 rtpmaps: &["96 PCMU/8000", "0 PCMU/8000"],
8198 codecs: Codecs::G711,
8199 },
8200 Agreement {
8201 why: "a bare static type, the one case with no rtpmap for either rule to read",
8202 formats: "0",
8203 rtpmaps: &[],
8204 codecs: Codecs::G711,
8205 },
8206 Agreement {
8207 why: "mono spelled out where RFC 8866 §6.6 would have let it be implied",
8208 formats: "0",
8209 rtpmaps: &["0 PCMU/8000/1"],
8210 codecs: Codecs::G711,
8211 },
8212 Agreement {
8213 why: "stereo G.711 is a different format from mono G.711, and neither rule may \
8214 settle on it",
8215 formats: "0 8",
8216 rtpmaps: &["0 PCMU/8000/2", "8 PCMA/8000"],
8217 codecs: Codecs::G711,
8218 },
8219 Agreement {
8220 why: "a signed clock rate is not a decimal digit string, so it identifies nothing for \
8221 either rule. The *third* witness, and the one that was not predicted: \
8222 `u32::from_str` accepts a leading `+`, so the parsing rule read `+8000` as 8000 \
8223 while the textual one did not — the same split as a leading zero, arrived at from \
8224 the other side. It is why the digits are checked in `sipx-sdp` rather than left \
8225 to `from_str`, and note the single rule resolves it the *opposite* way from a \
8226 leading zero: both callers decline it, and both settle on PCMA below",
8227 formats: "0 8",
8228 rtpmaps: &["0 PCMU/+8000", "8 PCMA/8000"],
8229 codecs: Codecs::G711,
8230 },
8231 Agreement {
8232 why: "a clock rate that overflows u32 — hostile input, and a non-match for both \
8233 rules rather than a panic in either",
8234 formats: "0 8",
8235 rtpmaps: &["0 PCMU/99999999999999", "8 PCMA/8000"],
8236 codecs: Codecs::G711,
8237 },
8238 Agreement {
8239 why: "an rtpmap with no clock rate at all identifies nothing",
8240 formats: "0 8",
8241 rtpmaps: &["0 PCMU", "8 PCMA/8000"],
8242 codecs: Codecs::G711,
8243 },
8244 Agreement {
8245 why: "an empty clock rate is not zero and is not 8000",
8246 formats: "0 8",
8247 rtpmaps: &["0 PCMU/", "8 PCMA/8000"],
8248 codecs: Codecs::G711,
8249 },
8250 Agreement {
8251 why: "whitespace inside the value: a rate neither rule can read, and both must \
8252 fail to read it the same way",
8253 formats: "0 8",
8254 rtpmaps: &["0 PCMU/ 8000", "8 PCMA/8000"],
8255 codecs: Codecs::G711,
8256 },
8257 Agreement {
8258 why: "a fourth field is outside RFC 8866 §6.6's grammar, so the value identifies \
8259 nothing — and must do so for both rules rather than one silently ignoring it",
8260 formats: "0 8",
8261 rtpmaps: &["0 PCMU/8000/1/9", "8 PCMA/8000"],
8262 codecs: Codecs::G711,
8263 },
8264 Agreement {
8265 why: "an Opus-first offer reaching a call that selected G.711: the M-30 case, and \
8266 true in both feature configurations — with `opus` off no rtpmap can name it, \
8267 with it on the set does not carry it",
8268 formats: "111 0",
8269 rtpmaps: &["111 opus/48000/2", "0 PCMU/8000"],
8270 codecs: Codecs::G711,
8271 },
8272 Agreement {
8273 why: "a dynamic number with no rtpmap is uninterpretable whatever the number, so \
8274 the stream is refused rather than guessed at",
8275 formats: "111",
8276 rtpmaps: &[],
8277 codecs: Codecs::G711,
8278 },
8279 Agreement {
8280 why: "a stream offering only telephone-event is not a call: the answer rejects it \
8281 and negotiation must refuse it too",
8282 formats: "101",
8283 rtpmaps: &["101 telephone-event/8000"],
8284 codecs: Codecs::G711,
8285 },
8286 Agreement {
8287 why: "an offer of nothing sipx carries at all — both rules refuse, and the \
8288 agreement holds on the refusing side as well",
8289 formats: "18",
8290 rtpmaps: &["18 G729/8000"],
8291 codecs: Codecs::G711,
8292 },
8293 ];
8294
8295 /// The rows that only exist when the `opus` feature is on, because [`Codecs::Opus`] does.
8296 ///
8297 /// Empty in the default build rather than absent, so the test body has no `cfg` in it and the
8298 /// two configurations run the same code over different data.
8299 #[cfg(feature = "opus")]
8300 const OPUS_AGREEMENT_TABLE: &[Agreement] = &[
8301 Agreement {
8302 why: "Opus on the set that carries it, on the number this offer assigned",
8303 formats: "111 0",
8304 rtpmaps: &["111 opus/48000/2", "0 PCMU/8000"],
8305 codecs: Codecs::Opus,
8306 },
8307 Agreement {
8308 why: "the leading-zero split on Opus's own clock rate, so the class is closed in the \
8309 gated path too and not only for G.711",
8310 formats: "111 0",
8311 rtpmaps: &["111 opus/048000/2", "0 PCMU/8000"],
8312 codecs: Codecs::Opus,
8313 },
8314 Agreement {
8315 why: "Opus at a rate RFC 7587 §7 does not assign is nothing sipx has, whatever number \
8316 is beside it",
8317 formats: "111 0",
8318 rtpmaps: &["111 opus/16000/2", "0 PCMU/8000"],
8319 codecs: Codecs::Opus,
8320 },
8321 ];
8322
8323 /// No Opus in this build, so no Opus rows. See [`OPUS_AGREEMENT_TABLE`].
8324 #[cfg(not(feature = "opus"))]
8325 const OPUS_AGREEMENT_TABLE: &[Agreement] = &[];
8326
8327 /// The answer sipx puts on the wire and the codec it configures the media session with must
8328 /// name the same format. **`M-31`'s failing-first test.**
8329 ///
8330 /// This is the assertion that fails while the two rules disagree: with the answer comparing an
8331 /// rtpmap clock rate as text and `codec_named` parsing it to `u32`, an offer of
8332 /// `a=rtpmap:0 PCMU/08000` settles on `Pcmu` at payload type 0 while the answer names only
8333 /// `8`. sipx would then send µ-law on a number the answer never offered *and* decode the
8334 /// peer's PCMA through a µ-law session — audible garbage rather than silence, with nothing in
8335 /// the stack reporting an error.
8336 ///
8337 /// The property is a biconditional, not a one-way check, because both halves are reachable
8338 /// defects: a codec the answer never named is a session the far end cannot read, and a stream
8339 /// the answer accepted while negotiation refused it is a call that fails after the 200 OK went
8340 /// out. `wire_payload_type` is the value compared because that is the byte that leaves —
8341 /// `Some(0)` and `None` are two descriptions of the same PCMU.
8342 #[test]
8343 fn the_answer_and_the_negotiated_codec_agree() {
8344 let local: IpAddr = "192.0.2.9".parse().expect("a literal address");
8345
8346 for row in AGREEMENT_TABLE.iter().chain(OPUS_AGREEMENT_TABLE) {
8347 let offer = offered(row.formats, row.rtpmaps);
8348 let answered = sipx_sdp::answer(&offer, &row.codecs.capabilities(local, 40000));
8349 let audio = answered
8350 .media
8351 .iter()
8352 .find(|stream| stream.media == "audio")
8353 .expect("the answer has one m= line per offered stream");
8354
8355 match negotiated(&offer, row.codecs) {
8356 Ok(settled) => {
8357 assert!(
8358 !audio.is_rejected(),
8359 "{}: negotiation settled on {:?} while the answer rejected the stream",
8360 row.why,
8361 settled.codec,
8362 );
8363 let wire = settled.wire_payload_type().to_string();
8364 assert!(
8365 audio.formats.contains(&wire),
8366 "{}: negotiation settled on {:?} at payload type {wire}, which the answer \
8367 never named ({:?})",
8368 row.why,
8369 settled.codec,
8370 audio.formats,
8371 );
8372 }
8373 Err(error) => {
8374 assert!(
8375 audio.is_rejected(),
8376 "{}: negotiation refused the stream ({error}) while the answer accepted it \
8377 with formats {:?}",
8378 row.why,
8379 audio.formats,
8380 );
8381 }
8382 }
8383 }
8384 }
8385}