gemini_adk_fluent_rs/telephony/
sip.rs

1//! In-process SIP — answer raw SIP calls with no carrier service in the path.
2//!
3//! *(feature `sip`)* Where [`super::twilio`] relies on a carrier
4//! service to terminate the phone network and hand audio over a WebSocket,
5//! this module terminates the call itself: SIP signalling via
6//! [`rsipstack`](https://docs.rs/rsipstack) (the stack underneath the
7//! `rustpbx` PBX), and G.711-over-RTP media built from this crate's own pure
8//! layers ([`super::rtp`], [`super::sdp`], [`super::g711`]).
9//! Any SIP endpoint — a softphone, an Asterisk/FreeSWITCH PBX, a provider's
10//! SIP trunk — dials the agent directly, and each call attaches to a Live
11//! session through the same [`voice::pump`](crate::voice::pump) as every
12//! other audio surface.
13//!
14//! ```ignore
15//! // `ignore`: `SipAgent` needs the `sip` feature and a bound UDP socket.
16//! let mut agent = SipAgent::bind("0.0.0.0:5060".parse()?).await?;
17//! while let Some(incoming) = agent.next_call().await {
18//!     let session = Live::builder()
19//!         .instruction("Answer the phone politely.")
20//!         .greeting("Greet the caller.")
21//!         .connect_from_env().await?;
22//!     let call = incoming.answer(&session).await?;
23//!     tokio::spawn(async move { call.ended().await; });
24//! }
25//! ```
26//!
27//! RFC 4733 telephone events (DTMF) are negotiated in the SDP answer when
28//! the offer proposes them; keypresses land in session state under the
29//! shared [`super::bridge`] keys, where flow guards read them —
30//! identical to the Twilio path. An `RTP/SAVP` offer with SDES keys is
31//! answered with SRTP ([`super::srtp`]), and [`SipAgent::register`] registers
32//! the agent with a PBX or trunk so it can be reached by address. Media is
33//! symmetric RTP: the agent sends to the offer's address but re-latches onto
34//! the source of the first arriving packet, which keeps NATted softphones
35//! working.
36
37use std::net::{IpAddr, SocketAddr};
38use std::sync::Arc;
39use std::time::Duration;
40
41use tokio::net::UdpSocket;
42use tokio::sync::{mpsc, watch};
43use tokio::task::JoinHandle;
44use tokio_util::sync::CancellationToken;
45
46use rsipstack::EndpointBuilder;
47use rsipstack::dialog::dialog::DialogState;
48use rsipstack::dialog::dialog_layer::DialogLayer;
49use rsipstack::dialog::invite_dialog::InviteDialog;
50use rsipstack::transport::TransportLayer;
51use rsipstack::transport::udp::UdpConnection;
52
53use gemini_adk_rs::State;
54use gemini_adk_rs::live::LiveHandle;
55
56use super::bridge::{self, DtmfDeduper, FillerConfig};
57use super::g711;
58use super::rtp::{self, PT_PCMA, RtpSender, SAMPLES_PER_PACKET};
59use super::sdp::{self, AudioOffer};
60use super::srtp::{CryptoAttribute, MasterKey, SrtpSession};
61use crate::voice::{Playback, VoicePump, pump};
62
63/// Errors from the SIP agent.
64#[derive(Debug)]
65pub enum SipError {
66    /// Binding or socket I/O failed.
67    Io(std::io::Error),
68    /// The SIP stack reported an error.
69    Sip(rsipstack::Error),
70    /// The INVITE carried no answerable audio offer (no `m=audio`, port 0).
71    NoAudioOffer,
72    /// The offer had audio but no G.711 codec this agent can speak.
73    NoCommonCodec,
74    /// The offer asked for SRTP with no crypto suite this agent supports.
75    NoCommonCrypto,
76    /// The registrar refused the registration (its final status code).
77    RegistrationRejected(u16),
78    /// A registrar or contact URI did not parse.
79    InvalidUri(String),
80}
81
82impl std::fmt::Display for SipError {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        match self {
85            Self::Io(e) => write!(f, "sip io error: {e}"),
86            Self::Sip(e) => write!(f, "sip stack error: {e:?}"),
87            Self::NoAudioOffer => write!(f, "INVITE carried no answerable audio offer"),
88            Self::NoCommonCodec => write!(f, "no common G.711 codec with the caller"),
89            Self::NoCommonCrypto => write!(f, "no common SRTP crypto suite with the caller"),
90            Self::RegistrationRejected(code) => {
91                write!(f, "registrar refused the registration: {code}")
92            }
93            Self::InvalidUri(uri) => write!(f, "invalid SIP URI: {uri}"),
94        }
95    }
96}
97
98impl std::error::Error for SipError {}
99
100impl From<std::io::Error> for SipError {
101    fn from(e: std::io::Error) -> Self {
102        Self::Io(e)
103    }
104}
105
106impl From<rsipstack::Error> for SipError {
107    fn from(e: rsipstack::Error) -> Self {
108        Self::Sip(e)
109    }
110}
111
112// ── Agent ────────────────────────────────────────────────────────────────────
113
114/// A SIP user agent server: binds a UDP SIP port and yields incoming calls.
115pub struct SipAgent {
116    dialog_layer: Arc<DialogLayer>,
117    incoming: rsipstack::transaction::TransactionReceiver,
118    cancel: CancellationToken,
119    local_ip: IpAddr,
120    sip_port: u16,
121}
122
123impl SipAgent {
124    /// Bind the SIP signalling port (conventionally 5060/udp) and start the
125    /// endpoint's serve loop in the background.
126    pub async fn bind(addr: SocketAddr) -> Result<SipAgent, SipError> {
127        let cancel = CancellationToken::new();
128        let transport_layer = TransportLayer::new(cancel.child_token());
129        let udp = UdpConnection::create_connection(addr, None, Some(cancel.child_token()))
130            .await
131            .map_err(SipError::Sip)?;
132        let sip_port = udp
133            .get_addr()
134            .addr
135            .port
136            .as_ref()
137            .map(|p| u16::from(*p))
138            .unwrap_or(addr.port());
139        transport_layer.add_transport(udp.into());
140
141        let endpoint = EndpointBuilder::new()
142            .with_user_agent("gemini-rs")
143            .with_cancel_token(cancel.child_token())
144            .with_transport_layer(transport_layer)
145            .build();
146        endpoint
147            .inner
148            .transport_layer
149            .serve_listens()
150            .await
151            .map_err(SipError::Sip)?;
152        let inner = endpoint.inner.clone();
153        tokio::spawn(async move {
154            let _ = inner.serve().await;
155        });
156
157        let incoming = endpoint.incoming_transactions().map_err(SipError::Sip)?;
158        let dialog_layer = Arc::new(DialogLayer::new(endpoint.inner.clone()));
159
160        Ok(SipAgent {
161            dialog_layer,
162            incoming,
163            cancel,
164            local_ip: addr.ip(),
165            sip_port,
166        })
167    }
168
169    /// Wait for the next incoming call. In-dialog requests (BYE, re-INVITE,
170    /// ACK) and non-call methods are handled internally; only new INVITEs
171    /// surface. Returns `None` once the agent is shut down.
172    pub async fn next_call(&mut self) -> Option<IncomingCall> {
173        while let Some(mut tx) = self.incoming.recv().await {
174            use rsipstack::rsip::{Method, StatusCode};
175            match tx.original.method {
176                Method::Invite => {
177                    let offer = match sdp::parse_audio_offer(
178                        String::from_utf8_lossy(&tx.original.body).as_ref(),
179                    ) {
180                        Some(offer) => offer,
181                        None => {
182                            let _ = tx.reply(StatusCode::NotAcceptableHere).await;
183                            continue;
184                        }
185                    };
186                    let (state_tx, state_rx) = mpsc::unbounded_channel();
187                    let contact = format!(
188                        "sip:gemini@{}:{};transport=udp",
189                        advertised_ip(self.local_ip, &offer),
190                        self.sip_port
191                    );
192                    let contact = match rsipstack::rsip::Uri::try_from(contact.as_str()) {
193                        Ok(uri) => uri,
194                        Err(_) => {
195                            let _ = tx.reply(StatusCode::ServerInternalError).await;
196                            continue;
197                        }
198                    };
199                    let dialog = match self.dialog_layer.get_or_create_server_invite(
200                        &tx,
201                        state_tx,
202                        None,
203                        Some(contact),
204                    ) {
205                        Ok(dialog) => dialog,
206                        Err(err) => {
207                            tracing::warn!("rejecting INVITE: {err:?}");
208                            let _ = tx.reply(StatusCode::ServerInternalError).await;
209                            continue;
210                        }
211                    };
212                    use rsipstack::rsip::HeadersExt as _;
213                    let from = tx
214                        .original
215                        .from_header()
216                        .map(std::string::ToString::to_string)
217                        .unwrap_or_default();
218                    let _ = dialog.ringing(None, None);
219                    // Responses (180/200/603) are queued events on the INVITE
220                    // transaction; pumping receive() is what puts them on the
221                    // wire and later delivers the ACK.
222                    tokio::spawn(async move { while tx.receive().await.is_some() {} });
223                    return Some(IncomingCall {
224                        dialog,
225                        state_rx,
226                        offer,
227                        from,
228                        local_ip: self.local_ip,
229                        dialog_layer: self.dialog_layer.clone(),
230                        filler: None,
231                    });
232                }
233                Method::Ack | Method::Bye | Method::Cancel | Method::Info | Method::Update => {
234                    // In-dialog requests: route to the owning dialog.
235                    match self.dialog_layer.match_dialog(&tx) {
236                        Some(mut dialog) => {
237                            tokio::spawn(async move {
238                                let _ = dialog.handle(&mut tx).await;
239                            });
240                        }
241                        None => {
242                            let _ = tx.reply(StatusCode::CallTransactionDoesNotExist).await;
243                        }
244                    }
245                }
246                Method::Options => {
247                    let _ = tx.reply(StatusCode::OK).await;
248                }
249                _ => {
250                    let _ = tx.reply(StatusCode::MethodNotAllowed).await;
251                }
252            }
253        }
254        None
255    }
256
257    /// The SIP port actually bound (useful with port 0).
258    pub fn sip_port(&self) -> u16 {
259        self.sip_port
260    }
261
262    /// Stop the endpoint and every call it produced.
263    pub fn shutdown(&self) {
264        self.cancel.cancel();
265    }
266
267    /// Register this agent with a registrar, so calls to the account's
268    /// address reach it.
269    ///
270    /// The first REGISTER (answering a digest challenge with the account's
271    /// credentials) happens before this returns, so a wrong password or an
272    /// unreachable registrar fails here. After that, the returned
273    /// [`SipRegistration`] refreshes the binding at three quarters of the
274    /// granted lifetime, and retries after a failure, until it is
275    /// [unregistered](SipRegistration::unregister) or the agent shuts down.
276    ///
277    /// ```ignore
278    /// let agent = SipAgent::bind("0.0.0.0:5060".parse()?).await?;
279    /// let registration = agent
280    ///     .register(SipAccount::new("sip:pbx.example.com", "agent", "secret"))
281    ///     .await?;
282    /// // ... take calls with agent.next_call() ...
283    /// registration.unregister().await?;
284    /// ```
285    pub async fn register(&self, account: SipAccount) -> Result<SipRegistration, SipError> {
286        use rsipstack::dialog::authenticate::Credential;
287        use rsipstack::dialog::registration::Registration;
288
289        let registrar = rsipstack::rsip::Uri::try_from(account.registrar.as_str())
290            .map_err(|_| SipError::InvalidUri(account.registrar.clone()))?;
291        let mut registration = Registration::new(
292            self.dialog_layer.endpoint.clone(),
293            Some(Credential {
294                username: account.username.clone(),
295                password: account.password.clone(),
296                realm: account.realm.clone(),
297            }),
298        );
299        if let Some(contact) = &account.contact {
300            let uri = rsipstack::rsip::Uri::try_from(contact.as_str())
301                .map_err(|_| SipError::InvalidUri(contact.clone()))?;
302            registration.contact = Some(rsipstack::rsip::typed::Contact {
303                display_name: None,
304                uri,
305                params: vec![],
306            });
307        }
308
309        let requested = account.expires;
310        let granted = register_once(&mut registration, &registrar, requested).await?;
311        let (state_tx, state_rx) =
312            watch::channel(RegistrationState::Registered { expires: granted });
313        let cancel = self.cancel.child_token();
314        let stop = cancel.clone();
315        let task = tokio::spawn(async move {
316            let mut next = refresh_after(granted);
317            loop {
318                tokio::select! {
319                    _ = stop.cancelled() => break,
320                    _ = tokio::time::sleep(next) => {}
321                }
322                match register_once(&mut registration, &registrar, requested).await {
323                    Ok(granted) => {
324                        next = refresh_after(granted);
325                        let _ = state_tx.send(RegistrationState::Registered { expires: granted });
326                    }
327                    Err(error) => {
328                        tracing::warn!("SIP re-registration failed: {error}");
329                        next = RETRY_AFTER;
330                        let _ = state_tx.send(RegistrationState::Retrying {
331                            error: error.to_string(),
332                        });
333                    }
334                }
335            }
336            // Remove the binding, unless the endpoint itself is gone.
337            let removed = tokio::time::timeout(
338                UNREGISTER_TIMEOUT,
339                register_once(&mut registration, &registrar, Duration::ZERO),
340            )
341            .await
342            .unwrap_or_else(|_| Err(SipError::Io(std::io::ErrorKind::TimedOut.into())));
343            let _ = state_tx.send(RegistrationState::Unregistered);
344            removed.map(|_| ())
345        });
346        Ok(SipRegistration {
347            state: state_rx,
348            cancel: cancel.drop_guard(),
349            task,
350        })
351    }
352}
353
354// ── Registration ─────────────────────────────────────────────────────────────
355
356/// How long after a failed refresh to try again.
357const RETRY_AFTER: Duration = Duration::from_secs(30);
358/// How long un-registering may take before it is abandoned.
359const UNREGISTER_TIMEOUT: Duration = Duration::from_secs(5);
360
361/// A SIP account on a registrar (a PBX or a SIP trunk provider).
362#[derive(Clone)]
363pub struct SipAccount {
364    /// The registrar's URI, e.g. `sip:pbx.example.com`.
365    pub registrar: String,
366    /// The account's user name, also the user part of the registered address.
367    pub username: String,
368    /// The account's password, for digest authentication.
369    pub password: String,
370    /// The authentication realm, when the registrar needs it named.
371    pub realm: Option<String>,
372    /// The Contact URI to register. By default it is built from the agent's
373    /// address, corrected by the address the registrar reports seeing
374    /// (`received`/`rport`), which keeps an agent behind NAT reachable.
375    pub contact: Option<String>,
376    /// The binding lifetime to ask for (the registrar may grant less).
377    pub expires: Duration,
378}
379
380impl SipAccount {
381    /// An account with a one-hour binding.
382    pub fn new(
383        registrar: impl Into<String>,
384        username: impl Into<String>,
385        password: impl Into<String>,
386    ) -> Self {
387        Self {
388            registrar: registrar.into(),
389            username: username.into(),
390            password: password.into(),
391            realm: None,
392            contact: None,
393            expires: Duration::from_secs(3600),
394        }
395    }
396
397    /// Name the authentication realm.
398    pub fn realm(mut self, realm: impl Into<String>) -> Self {
399        self.realm = Some(realm.into());
400        self
401    }
402
403    /// Register this Contact URI instead of the derived one.
404    pub fn contact(mut self, contact: impl Into<String>) -> Self {
405        self.contact = Some(contact.into());
406        self
407    }
408
409    /// Ask for a binding lifetime (rounded down to whole seconds).
410    pub fn expires(mut self, expires: Duration) -> Self {
411        self.expires = expires;
412        self
413    }
414}
415
416impl std::fmt::Debug for SipAccount {
417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418        f.debug_struct("SipAccount")
419            .field("registrar", &self.registrar)
420            .field("username", &self.username)
421            .field("password", &"[redacted]")
422            .field("realm", &self.realm)
423            .field("contact", &self.contact)
424            .field("expires", &self.expires)
425            .finish()
426    }
427}
428
429/// Where a [`SipRegistration`] stands.
430#[derive(Debug, Clone, PartialEq, Eq)]
431#[non_exhaustive]
432pub enum RegistrationState {
433    /// The registrar holds a binding for this long from the last refresh.
434    Registered {
435        /// The lifetime the registrar granted.
436        expires: Duration,
437    },
438    /// The last refresh failed; it is retried shortly. The previous binding
439    /// may still be live until it expires.
440    Retrying {
441        /// Why the refresh failed.
442        error: String,
443    },
444    /// The binding was removed, or the agent shut down.
445    Unregistered,
446}
447
448/// A live registration, kept fresh in the background. See
449/// [`SipAgent::register`].
450pub struct SipRegistration {
451    state: watch::Receiver<RegistrationState>,
452    // Dropped without `unregister`, the registration still stops refreshing
453    // and removes its binding, in the background.
454    cancel: tokio_util::sync::DropGuard,
455    task: JoinHandle<Result<(), SipError>>,
456}
457
458impl SipRegistration {
459    /// The registration's current state.
460    pub fn state(&self) -> RegistrationState {
461        self.state.borrow().clone()
462    }
463
464    /// A receiver that sees every state change, e.g. to alert when a refresh
465    /// starts failing.
466    pub fn watch(&self) -> watch::Receiver<RegistrationState> {
467        self.state.clone()
468    }
469
470    /// Stop refreshing and remove the binding from the registrar
471    /// (a REGISTER with a zero lifetime).
472    pub async fn unregister(self) -> Result<(), SipError> {
473        drop(self.cancel);
474        match self.task.await {
475            Ok(result) => result,
476            Err(_) => Ok(()),
477        }
478    }
479}
480
481/// One REGISTER exchange; the lifetime granted on success.
482async fn register_once(
483    registration: &mut rsipstack::dialog::registration::Registration,
484    registrar: &rsipstack::rsip::Uri,
485    expires: Duration,
486) -> Result<Duration, SipError> {
487    let requested = u32::try_from(expires.as_secs()).unwrap_or(u32::MAX);
488    let response = registration
489        .register(registrar.clone(), Some(requested))
490        .await?;
491    let code = u16::from(response.status_code.clone());
492    if !(200..300).contains(&code) {
493        return Err(SipError::RegistrationRejected(code));
494    }
495    Ok(granted_expires(&response).unwrap_or(expires))
496}
497
498/// The lifetime a 2xx to REGISTER grants: the `expires` parameter of the
499/// Contact, else the `Expires` header.
500fn granted_expires(response: &rsipstack::rsip::Response) -> Option<Duration> {
501    use rsipstack::rsip::Header;
502    let mut header = None;
503    for h in response.headers.iter() {
504        match h {
505            Header::Contact(contact) => {
506                let text = contact.to_string().to_ascii_lowercase();
507                if let Some(value) = text
508                    .split(';')
509                    .find_map(|p| p.trim().strip_prefix("expires="))
510                {
511                    let digits: String = value.chars().take_while(char::is_ascii_digit).collect();
512                    if let Ok(secs) = digits.parse() {
513                        return Some(Duration::from_secs(secs));
514                    }
515                }
516            }
517            Header::Expires(expires) => {
518                header = expires.value().trim().parse().ok().map(Duration::from_secs);
519            }
520            _ => {}
521        }
522    }
523    header
524}
525
526/// When to refresh a binding granted for `expires`: at three quarters of
527/// it, but not sooner than a few seconds.
528fn refresh_after(expires: Duration) -> Duration {
529    (expires * 3 / 4).max(Duration::from_secs(5))
530}
531
532// ── Incoming call ────────────────────────────────────────────────────────────
533
534/// A ringing inbound call: answer it onto a session, or reject it.
535pub struct IncomingCall {
536    dialog: InviteDialog,
537    state_rx: mpsc::UnboundedReceiver<DialogState>,
538    /// The caller's parsed audio offer.
539    pub offer: AudioOffer,
540    /// The caller's `From` header, for screening/logging.
541    pub from: String,
542    local_ip: IpAddr,
543    dialog_layer: Arc<DialogLayer>,
544    filler: Option<FillerConfig>,
545}
546
547impl IncomingCall {
548    /// Play a latency-masking filler clip when the model stays silent too
549    /// long after the caller stops speaking — see
550    /// [`bridge::spawn_latency_filler`]. The clip must be mono PCM16 at
551    /// 8 kHz (the call's playback rate).
552    pub fn filler(mut self, config: FillerConfig) -> Self {
553        self.filler = Some(config);
554        self
555    }
556
557    /// Answer the call onto a connected session: bind an RTP socket, send the
558    /// SDP answer in the 200 OK, and start the media loop.
559    ///
560    /// When the offer proposes RFC 4733 telephone events, the answer accepts
561    /// them and keypresses are written to session state via
562    /// [`bridge::record_dtmf`]. The caller's `From` identity lands under
563    /// [`bridge::KEY_CALLER`].
564    pub async fn answer(self, handle: &LiveHandle) -> Result<SipCall, SipError> {
565        let payload_type = self.offer.g711_payload_type().ok_or_else(|| {
566            let _ = self.dialog.reject(None, None);
567            SipError::NoCommonCodec
568        })?;
569
570        let media_ip = advertised_ip(self.local_ip, &self.offer);
571        let rtp_socket = UdpSocket::bind((self.local_ip, 0)).await?;
572        let rtp_port = rtp_socket.local_addr()?.port();
573        let remote: SocketAddr = format!("{}:{}", self.offer.host, self.offer.port)
574            .parse()
575            .map_err(|_| SipError::NoAudioOffer)?;
576
577        let telephone_event_pt = self.offer.telephone_event_pt;
578        let (answer, srtp) = if self.offer.secure {
579            // SRTP (SDES): decrypt with the caller's key, encrypt with ours,
580            // answering the first offered suite we support.
581            let Some(theirs) = self
582                .offer
583                .crypto
584                .iter()
585                .find_map(|c| CryptoAttribute::parse(c))
586            else {
587                let _ = self
588                    .dialog
589                    .reject(Some(rsipstack::rsip::StatusCode::NotAcceptableHere), None);
590                return Err(SipError::NoCommonCrypto);
591            };
592            let ours = CryptoAttribute {
593                tag: theirs.tag,
594                suite: theirs.suite,
595                key: MasterKey::generate()?,
596            };
597            let answer = sdp::secure_audio_answer(
598                seed() as u64,
599                &media_ip.to_string(),
600                rtp_port,
601                payload_type,
602                telephone_event_pt,
603                &ours.to_value(),
604            );
605            let srtp = SrtpPair {
606                inbound: SrtpSession::new(theirs.suite, &theirs.key),
607                outbound: SrtpSession::new(ours.suite, &ours.key),
608            };
609            (answer, Some(srtp))
610        } else {
611            let answer = sdp::audio_answer(
612                seed() as u64,
613                &media_ip.to_string(),
614                rtp_port,
615                payload_type,
616                telephone_event_pt,
617            );
618            (answer, None)
619        };
620        self.dialog
621            .accept(None, Some(answer.into_bytes()))
622            .map_err(SipError::Sip)?;
623        let _ = handle.state().set(bridge::KEY_CALLER, self.from.clone());
624
625        let cancel = CancellationToken::new();
626        let media = rtp_media(
627            handle,
628            Arc::new(rtp_socket),
629            remote,
630            MediaFormat {
631                payload_type,
632                telephone_event_pt,
633            },
634            self.filler,
635            srtp,
636            cancel.clone(),
637        );
638
639        // Tear the media down when the dialog terminates (BYE, error).
640        let mut state_rx = self.state_rx;
641        let media_cancel = cancel.clone();
642        let dialog_id = self.dialog.id();
643        let dialog_layer = self.dialog_layer;
644        let ended = tokio::spawn(async move {
645            while let Some(state) = state_rx.recv().await {
646                if let DialogState::Terminated(_, _) = state {
647                    break;
648                }
649            }
650            media_cancel.cancel();
651            dialog_layer.remove_dialog(&dialog_id);
652        });
653
654        Ok(SipCall {
655            dialog: self.dialog,
656            media,
657            cancel,
658            ended,
659        })
660    }
661
662    /// Decline the call (486 Busy Here by default).
663    pub fn reject(self) {
664        let _ = self.dialog.reject(None, None);
665    }
666}
667
668// ── Live call ────────────────────────────────────────────────────────────────
669
670/// An answered SIP call with media flowing.
671pub struct SipCall {
672    dialog: InviteDialog,
673    media: MediaTasks,
674    cancel: CancellationToken,
675    ended: JoinHandle<()>,
676}
677
678impl SipCall {
679    /// Wait until the call ends (caller hung up, or [`hangup`](Self::hangup)).
680    pub async fn ended(self) {
681        let _ = self.ended.await;
682        self.media.stop().await;
683    }
684
685    /// Hang up: send BYE and stop the media loop.
686    pub async fn hangup(self) {
687        let _ = self.dialog.bye().await;
688        self.cancel.cancel();
689        let _ = self.ended.await;
690        self.media.stop().await;
691    }
692}
693
694// ── Media loop ───────────────────────────────────────────────────────────────
695
696struct MediaTasks {
697    pump: VoicePump,
698    inbound: JoinHandle<()>,
699    outbound: JoinHandle<()>,
700    filler: Option<JoinHandle<()>>,
701}
702
703impl MediaTasks {
704    async fn stop(self) {
705        self.inbound.abort();
706        self.outbound.abort();
707        if let Some(filler) = self.filler {
708            filler.abort();
709        }
710        self.pump.abort();
711        self.pump.join().await;
712    }
713}
714
715/// The negotiated payload types of a call.
716#[derive(Clone, Copy)]
717struct MediaFormat {
718    /// The G.711 codec.
719    payload_type: u8,
720    /// RFC 4733 telephone events, when negotiated.
721    telephone_event_pt: Option<u8>,
722}
723
724/// The two SRTP directions of a secure call.
725struct SrtpPair {
726    /// Keyed by the caller: unprotects what arrives.
727    inbound: SrtpSession,
728    /// Keyed by us: protects what we send.
729    outbound: SrtpSession,
730}
731
732/// Wire a session's voice pump to G.711-over-RTP on a UDP socket.
733///
734/// Symmetric RTP: packets go to `remote` until the first packet arrives,
735/// whose source address then becomes the send target (NAT re-latch).
736fn rtp_media(
737    handle: &LiveHandle,
738    socket: Arc<UdpSocket>,
739    remote: SocketAddr,
740    format: MediaFormat,
741    filler: Option<FillerConfig>,
742    srtp: Option<SrtpPair>,
743    cancel: CancellationToken,
744) -> MediaTasks {
745    let (srtp_in, srtp_out) = match srtp {
746        Some(pair) => (Some(pair.inbound), Some(pair.outbound)),
747        None => (None, None),
748    };
749    let (mic_tx, mic_rx) = mpsc::channel::<Vec<i16>>(64);
750    let (speaker_tx, speaker_rx) = mpsc::channel::<Playback>(64);
751    let voice_pump = pump(
752        handle,
753        mic_rx,
754        super::TWILIO_HZ,
755        speaker_tx.clone(),
756        super::TWILIO_HZ,
757    );
758    let (peer_tx, peer_rx) = watch::channel(remote);
759
760    let filler = filler.map(|config| bridge::spawn_latency_filler(handle, speaker_tx, config));
761
762    let inbound = tokio::spawn(inbound_loop(
763        socket.clone(),
764        format,
765        handle.state().clone(),
766        mic_tx,
767        peer_tx,
768        srtp_in,
769        cancel.clone(),
770    ));
771    let outbound = tokio::spawn(outbound_loop(
772        socket,
773        format.payload_type,
774        speaker_rx,
775        peer_rx,
776        srtp_out,
777        cancel,
778    ));
779
780    MediaTasks {
781        pump: voice_pump,
782        inbound,
783        outbound,
784        filler,
785    }
786}
787
788async fn inbound_loop(
789    socket: Arc<UdpSocket>,
790    format: MediaFormat,
791    state: State,
792    mic_tx: mpsc::Sender<Vec<i16>>,
793    peer_tx: watch::Sender<SocketAddr>,
794    mut srtp: Option<SrtpSession>,
795    cancel: CancellationToken,
796) {
797    let MediaFormat {
798        payload_type,
799        telephone_event_pt,
800    } = format;
801    let mut buf = [0u8; 2048];
802    let mut latched = false;
803    let mut dtmf = DtmfDeduper::default();
804    // Keypad tones stay away from the model; without RFC 4733 they are the
805    // only record of a keypress, so they are also counted.
806    let mut guard =
807        bridge::KeypadGuard::new(state.clone(), 8_000).record_in_band(telephone_event_pt.is_none());
808    loop {
809        let (len, source) = tokio::select! {
810            _ = cancel.cancelled() => break,
811            received = socket.recv_from(&mut buf) => match received {
812                Ok(pair) => pair,
813                Err(_) => break,
814            },
815        };
816        let decrypted;
817        let datagram = match srtp.as_mut() {
818            // A packet that fails authentication is dropped unread, and
819            // does not re-latch the peer address.
820            Some(srtp) => match srtp.unprotect(&buf[..len]) {
821                Ok(plain) => {
822                    decrypted = plain;
823                    &decrypted[..]
824                }
825                Err(_) => continue,
826            },
827            None => &buf[..len],
828        };
829        let Some(packet) = rtp::parse(datagram) else {
830            continue; // stray non-RTP traffic on the media port
831        };
832        if telephone_event_pt == Some(packet.payload_type) {
833            // RFC 4733 keypress: emit once per end-marked event.
834            if let Some(event) = rtp::parse_telephone_event(&packet.payload)
835                && dtmf.accept(event.end, packet.timestamp)
836                && let Some(digit) = event.digit()
837            {
838                bridge::record_dtmf(&state, digit);
839            }
840            continue;
841        }
842        if packet.payload_type != payload_type {
843            continue; // a payload type we did not negotiate
844        }
845        if !latched {
846            let _ = peer_tx.send(source);
847            latched = true;
848        }
849        let mut samples = if payload_type == PT_PCMA {
850            g711::decode_alaw(&packet.payload)
851        } else {
852            g711::decode_ulaw(&packet.payload)
853        };
854        crate::voice::InputAudioProcessor::process_frame(&mut guard, &mut samples);
855        if mic_tx.send(samples).await.is_err() {
856            break;
857        }
858    }
859}
860
861async fn outbound_loop(
862    socket: Arc<UdpSocket>,
863    payload_type: u8,
864    mut speaker_rx: mpsc::Receiver<Playback>,
865    peer_rx: watch::Receiver<SocketAddr>,
866    mut srtp: Option<SrtpSession>,
867    cancel: CancellationToken,
868) {
869    let silence_byte: u8 = if payload_type == PT_PCMA { 0xD5 } else { 0xFF };
870    let seed = seed();
871    let mut sender = RtpSender::new(payload_type, seed, (seed >> 16) as u16, seed.rotate_left(8));
872    let mut pending: std::collections::VecDeque<i16> = std::collections::VecDeque::new();
873    let mut ticker = tokio::time::interval(Duration::from_millis(20));
874    ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
875
876    loop {
877        tokio::select! {
878            _ = cancel.cancelled() => break,
879            playback = speaker_rx.recv() => match playback {
880                Some(Playback::Chunk(samples)) => pending.extend(samples),
881                // Barge-in on raw RTP: we ARE the buffer — drop it.
882                Some(Playback::Flush) => pending.clear(),
883                None => break,
884            },
885            _ = ticker.tick() => {
886                if pending.is_empty() {
887                    sender.skip_silence(SAMPLES_PER_PACKET as u32);
888                    continue;
889                }
890                let take = pending.len().min(SAMPLES_PER_PACKET);
891                let mut payload = Vec::with_capacity(SAMPLES_PER_PACKET);
892                for sample in pending.drain(..take) {
893                    payload.push(if payload_type == PT_PCMA {
894                        g711::linear_to_alaw(sample)
895                    } else {
896                        g711::linear_to_ulaw(sample)
897                    });
898                }
899                // Constant 20 ms ptime: pad a short tail with silence.
900                payload.resize(SAMPLES_PER_PACKET, silence_byte);
901                let mut datagram = sender.packetize(&payload, SAMPLES_PER_PACKET as u32);
902                if let Some(srtp) = srtp.as_mut() {
903                    match srtp.protect(&datagram) {
904                        Some(protected) => datagram = protected,
905                        None => continue,
906                    }
907                }
908                let target = *peer_rx.borrow();
909                if socket.send_to(&datagram, target).await.is_err() {
910                    break;
911                }
912            }
913        }
914    }
915}
916
917/// The IP to advertise for media. A wildcard bind cannot go into SDP, so
918/// discover the interface that routes toward the caller's offer address.
919fn advertised_ip(local: IpAddr, offer: &AudioOffer) -> IpAddr {
920    if !local.is_unspecified() {
921        return local;
922    }
923    let probe = std::net::UdpSocket::bind("0.0.0.0:0")
924        .and_then(|s| {
925            s.connect((offer.host.as_str(), offer.port))?;
926            s.local_addr()
927        })
928        .map(|a| a.ip());
929    probe.unwrap_or(local)
930}
931
932/// A cheap non-cryptographic seed for SSRC/sequence/timestamp offsets.
933fn seed() -> u32 {
934    let nanos = std::time::SystemTime::now()
935        .duration_since(std::time::UNIX_EPOCH)
936        .map(|d| d.subsec_nanos())
937        .unwrap_or(0);
938    nanos ^ (std::process::id().rotate_left(16))
939}
940
941#[cfg(test)]
942mod tests {
943    use super::*;
944
945    async fn recv_status(socket: &UdpSocket, buf: &mut [u8]) -> Option<String> {
946        let deadline = Duration::from_secs(3);
947        let (len, _) = tokio::time::timeout(deadline, socket.recv_from(buf))
948            .await
949            .ok()?
950            .ok()?;
951        String::from_utf8_lossy(&buf[..len])
952            .lines()
953            .next()
954            .map(str::to_string)
955    }
956
957    /// A throwaway password, random per test run.
958    fn test_password() -> String {
959        MasterKey::generate().unwrap().to_inline()
960    }
961
962    /// A registrar that challenges a REGISTER without credentials (401) and
963    /// grants one with them for 60 s. Every request it sees is forwarded.
964    async fn fake_registrar() -> (SocketAddr, mpsc::UnboundedReceiver<String>) {
965        let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
966        let addr = socket.local_addr().unwrap();
967        let (seen_tx, seen_rx) = mpsc::unbounded_channel();
968        tokio::spawn(async move {
969            let mut buf = [0u8; 4096];
970            while let Ok((len, from)) = socket.recv_from(&mut buf).await {
971                let request = String::from_utf8_lossy(&buf[..len]).to_string();
972                let echoed: Vec<&str> = request
973                    .lines()
974                    .filter(|l| {
975                        let l = l.to_ascii_lowercase();
976                        ["via:", "from:", "call-id:", "cseq:"]
977                            .iter()
978                            .any(|h| l.starts_with(h))
979                    })
980                    .collect();
981                let to = request
982                    .lines()
983                    .find(|l| l.to_ascii_lowercase().starts_with("to:"))
984                    .unwrap_or("To: <sip:unknown@invalid>");
985                let authorized = request.to_ascii_lowercase().contains("\nauthorization:");
986                let (status, extra) = if authorized {
987                    let contact = request
988                        .lines()
989                        .find(|l| l.to_ascii_lowercase().starts_with("contact:"))
990                        .unwrap_or("Contact: <sip:alice@127.0.0.1>");
991                    ("200 OK", format!("{contact};expires=60\r\n"))
992                } else {
993                    (
994                        "401 Unauthorized",
995                        "WWW-Authenticate: Digest realm=\"test\", nonce=\"n1\", algorithm=MD5\r\n"
996                            .to_string(),
997                    )
998                };
999                let response = format!(
1000                    "SIP/2.0 {status}\r\n{}\r\n{to};tag=reg\r\n{extra}Content-Length: 0\r\n\r\n",
1001                    echoed.join("\r\n")
1002                );
1003                let _ = socket.send_to(response.as_bytes(), from).await;
1004                let _ = seen_tx.send(request);
1005            }
1006        });
1007        (addr, seen_rx)
1008    }
1009
1010    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1011    async fn registers_with_digest_auth_and_unregisters() {
1012        let password = test_password();
1013        let (registrar, mut seen) = fake_registrar().await;
1014        let agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1015            .await
1016            .expect("bind agent");
1017
1018        let registration = tokio::time::timeout(
1019            Duration::from_secs(5),
1020            agent.register(
1021                SipAccount::new(format!("sip:{registrar}"), "alice", &password)
1022                    .expires(Duration::from_secs(300)),
1023            ),
1024        )
1025        .await
1026        .expect("registration finishes")
1027        .expect("registrar accepts");
1028        assert_eq!(
1029            registration.state(),
1030            RegistrationState::Registered {
1031                expires: Duration::from_secs(60)
1032            },
1033            "the lifetime the registrar granted, not the one asked for"
1034        );
1035
1036        let first = seen.recv().await.unwrap();
1037        assert!(first.starts_with("REGISTER "), "{first}");
1038        assert!(!first.to_ascii_lowercase().contains("authorization:"));
1039        let answered = loop {
1040            let request = seen.recv().await.unwrap();
1041            if request.to_ascii_lowercase().contains("authorization:") {
1042                break request;
1043            }
1044        };
1045        for part in [
1046            "username=\"alice\"",
1047            "realm=\"test\"",
1048            "nonce=\"n1\"",
1049            "response=",
1050        ] {
1051            assert!(answered.contains(part), "missing {part} in {answered}");
1052        }
1053        assert!(
1054            !answered.contains(&password),
1055            "the password never goes on the wire"
1056        );
1057
1058        tokio::time::timeout(Duration::from_secs(5), registration.unregister())
1059            .await
1060            .expect("unregister finishes")
1061            .expect("registrar accepts the removal");
1062        let mut removed = false;
1063        while let Ok(request) = seen.try_recv() {
1064            removed |= request
1065                .lines()
1066                .any(|l| l.eq_ignore_ascii_case("expires: 0"));
1067        }
1068        assert!(
1069            removed,
1070            "a REGISTER with a zero lifetime removes the binding"
1071        );
1072        agent.shutdown();
1073    }
1074
1075    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1076    async fn a_refused_registration_fails_at_once() {
1077        // A registrar that refuses everyone.
1078        let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1079        let registrar = socket.local_addr().unwrap();
1080        tokio::spawn(async move {
1081            let mut buf = [0u8; 4096];
1082            while let Ok((len, from)) = socket.recv_from(&mut buf).await {
1083                let request = String::from_utf8_lossy(&buf[..len]).to_string();
1084                let echoed: Vec<&str> = request
1085                    .lines()
1086                    .filter(|l| {
1087                        let l = l.to_ascii_lowercase();
1088                        ["via:", "from:", "to:", "call-id:", "cseq:"]
1089                            .iter()
1090                            .any(|h| l.starts_with(h))
1091                    })
1092                    .collect();
1093                let response = format!(
1094                    "SIP/2.0 403 Forbidden\r\n{}\r\nContent-Length: 0\r\n\r\n",
1095                    echoed.join("\r\n")
1096                );
1097                let _ = socket.send_to(response.as_bytes(), from).await;
1098            }
1099        });
1100        let agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1101            .await
1102            .unwrap();
1103        let result = tokio::time::timeout(
1104            Duration::from_secs(5),
1105            agent.register(SipAccount::new(
1106                format!("sip:{registrar}"),
1107                "mallory",
1108                test_password(),
1109            )),
1110        )
1111        .await
1112        .expect("finishes");
1113        assert!(
1114            matches!(result, Err(SipError::RegistrationRejected(403))),
1115            "{:?}",
1116            result.err()
1117        );
1118        agent.shutdown();
1119    }
1120
1121    /// Read datagrams until a SIP response with status `code` arrives.
1122    async fn recv_response(socket: &UdpSocket, code: &str) -> String {
1123        let mut buf = [0u8; 4096];
1124        loop {
1125            let (len, _) = tokio::time::timeout(Duration::from_secs(3), socket.recv_from(&mut buf))
1126                .await
1127                .expect("a response")
1128                .unwrap();
1129            let text = String::from_utf8_lossy(&buf[..len]).to_string();
1130            if text.lines().next().is_some_and(|l| l.contains(code)) {
1131                return text;
1132            }
1133        }
1134    }
1135
1136    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1137    async fn an_srtp_offer_gets_encrypted_media_both_ways() {
1138        use super::super::srtp::SrtpSuite;
1139        use base64::Engine as _;
1140
1141        // The model says 200 ms of a tone once the call is up.
1142        let tone: Vec<u8> = (0..4800i16)
1143            .flat_map(|i| (if i % 48 < 24 { 6000i16 } else { -6000 }).to_le_bytes())
1144            .collect();
1145        let (transport, control) = crate::live::scripted::ScriptedServer::new()
1146            .frame(serde_json::json!({
1147                "serverContent": { "modelTurn": { "parts": [{ "inlineData": {
1148                    "mimeType": "audio/pcm;rate=24000",
1149                    "data": base64::engine::general_purpose::STANDARD.encode(&tone),
1150                } }] } }
1151            }))
1152            .into_transport();
1153        let handle = crate::live::Live::builder()
1154            .connect_with_transport(transport)
1155            .await
1156            .unwrap();
1157
1158        let mut agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1159            .await
1160            .unwrap();
1161        let target = format!("127.0.0.1:{}", agent.sip_port());
1162        let (call_tx, call_rx) = tokio::sync::oneshot::channel();
1163        tokio::spawn(async move {
1164            if let Some(incoming) = agent.next_call().await {
1165                let _ = call_tx.send(incoming);
1166            }
1167            // Keep the agent (and its endpoint) alive for the call.
1168            std::future::pending::<()>().await;
1169        });
1170
1171        // The caller: a SIP socket, an RTP socket, and its own SRTP key.
1172        let uac = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1173        let uac_port = uac.local_addr().unwrap().port();
1174        let media = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1175        let media_port = media.local_addr().unwrap().port();
1176        let caller_key = MasterKey::generate().unwrap();
1177        let sdp_body = format!(
1178            "v=0\r\no=probe 1 1 IN IP4 127.0.0.1\r\ns=call\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\n\
1179             m=audio {media_port} RTP/SAVP 0\r\na=rtpmap:0 PCMU/8000\r\n\
1180             a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:{}\r\n",
1181            caller_key.to_inline()
1182        );
1183        let invite = format!(
1184            "INVITE sip:gemini@{target} SIP/2.0\r\n\
1185             Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKsrtp1\r\n\
1186             Max-Forwards: 70\r\n\
1187             From: <sip:probe@127.0.0.1>;tag=srtp\r\n\
1188             To: <sip:gemini@{target}>\r\n\
1189             Call-ID: srtp-1@127.0.0.1\r\n\
1190             CSeq: 1 INVITE\r\n\
1191             Contact: <sip:probe@127.0.0.1:{uac_port}>\r\n\
1192             Content-Type: application/sdp\r\n\
1193             Content-Length: {}\r\n\r\n{sdp_body}",
1194            sdp_body.len()
1195        );
1196        uac.send_to(invite.as_bytes(), &target).await.unwrap();
1197        let incoming = tokio::time::timeout(Duration::from_secs(3), call_rx)
1198            .await
1199            .unwrap()
1200            .unwrap();
1201        assert!(incoming.offer.secure);
1202        let _call = incoming.answer(&handle).await.expect("answers with SRTP");
1203
1204        let ok = recv_response(&uac, "200").await;
1205        let answer_sdp = ok.split("\r\n\r\n").nth(1).unwrap();
1206        let answer = sdp::parse_audio_offer(answer_sdp).expect("an SDP answer");
1207        assert!(answer.secure, "{answer_sdp}");
1208        let agent_crypto = CryptoAttribute::parse(&answer.crypto[0]).unwrap();
1209        assert_eq!(agent_crypto.tag, 1);
1210        assert_eq!(agent_crypto.suite, SrtpSuite::AesCm128HmacSha1_80);
1211        assert_ne!(
1212            agent_crypto.key, caller_key,
1213            "the agent sends under its own key"
1214        );
1215        let agent_rtp = format!("{}:{}", answer.host, answer.port);
1216
1217        let speak = |key: &MasterKey, first: u16| {
1218            let mut session = SrtpSession::new(SrtpSuite::AesCm128HmacSha1_80, key);
1219            (first..first + 15)
1220                .map(|seq| {
1221                    session
1222                        .protect(&rtp::build(&rtp::RtpPacket {
1223                            payload_type: 0,
1224                            marker: seq == first,
1225                            sequence: seq,
1226                            timestamp: u32::from(seq) * 160,
1227                            ssrc: 0xCA11_E500,
1228                            payload: vec![0x10; 160],
1229                        }))
1230                        .unwrap()
1231                })
1232                .collect::<Vec<_>>()
1233        };
1234        let heard = || {
1235            control
1236                .outbound_frames()
1237                .iter()
1238                .filter_map(|frame| serde_json::from_slice::<serde_json::Value>(frame).ok())
1239                .any(|message| message.pointer("/realtimeInput/audio").is_some())
1240        };
1241
1242        // Caller → agent under a key the agent was not given: dropped.
1243        for packet in speak(&MasterKey::generate().unwrap(), 100) {
1244            media.send_to(&packet, &agent_rtp).await.unwrap();
1245        }
1246        tokio::time::sleep(Duration::from_millis(200)).await;
1247        assert!(
1248            !heard(),
1249            "packets that fail authentication never reach the model"
1250        );
1251
1252        // Under the offered key: decrypted speech reaches the model.
1253        for packet in speak(&caller_key, 200) {
1254            media.send_to(&packet, &agent_rtp).await.unwrap();
1255            tokio::time::sleep(Duration::from_millis(5)).await;
1256        }
1257        tokio::time::sleep(Duration::from_millis(200)).await;
1258        assert!(heard(), "decrypted caller audio reached the model");
1259
1260        // Agent → caller: the model's audio arrives as SRTP under the
1261        // agent's key, and decrypts to non-silent G.711.
1262        control.release();
1263        let mut from_agent = SrtpSession::new(agent_crypto.suite, &agent_crypto.key);
1264        let mut buf = [0u8; 2048];
1265        let (len, _) = tokio::time::timeout(Duration::from_secs(3), media.recv_from(&mut buf))
1266            .await
1267            .expect("the agent sends media")
1268            .unwrap();
1269        let plain = from_agent
1270            .unprotect(&buf[..len])
1271            .expect("authenticates under the answered key");
1272        let packet = rtp::parse(&plain).unwrap();
1273        assert_eq!(packet.payload_type, 0);
1274        assert!(packet.payload.iter().any(|b| *b != 0xFF), "not silence");
1275    }
1276
1277    #[test]
1278    fn an_account_never_prints_its_password() {
1279        let password = test_password();
1280        let account = SipAccount::new("sip:pbx.example.com", "alice", &password);
1281        assert!(!format!("{account:?}").contains(&password));
1282    }
1283
1284    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1285    async fn answers_options_and_rings_then_rejects_an_invite() {
1286        let mut agent = SipAgent::bind("127.0.0.1:0".parse().unwrap())
1287            .await
1288            .expect("bind agent");
1289        let agent_port = agent.sip_port();
1290        // Drive the agent loop concurrently: OPTIONS is answered inside it,
1291        // and the INVITE surfaces through the channel.
1292        let (call_tx, call_rx) = tokio::sync::oneshot::channel();
1293        tokio::spawn(async move {
1294            if let Some(incoming) = agent.next_call().await {
1295                let _ = call_tx.send(incoming);
1296            }
1297        });
1298
1299        let uac = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1300        let uac_port = uac.local_addr().unwrap().port();
1301        let target = format!("127.0.0.1:{agent_port}");
1302        let mut buf = [0u8; 2048];
1303
1304        // OPTIONS gets a 200 without surfacing a call.
1305        let options = format!(
1306            "OPTIONS sip:gemini@{target} SIP/2.0\r\n\
1307             Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKopt1\r\n\
1308             Max-Forwards: 70\r\n\
1309             From: <sip:probe@127.0.0.1>;tag=opt\r\n\
1310             To: <sip:gemini@{target}>\r\n\
1311             Call-ID: options-1@127.0.0.1\r\n\
1312             CSeq: 1 OPTIONS\r\n\
1313             Content-Length: 0\r\n\r\n"
1314        );
1315        uac.send_to(options.as_bytes(), &target).await.unwrap();
1316        let status = recv_status(&uac, &mut buf).await.expect("OPTIONS response");
1317        assert!(
1318            status.contains("200"),
1319            "expected 200 to OPTIONS, got {status}"
1320        );
1321
1322        // An INVITE with a G.711 offer surfaces as an IncomingCall (after
1323        // 100/180 provisional responses); rejecting it sends a final failure.
1324        let sdp_body = "v=0\r\n\
1325             o=probe 1 1 IN IP4 127.0.0.1\r\n\
1326             s=call\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\n\
1327             m=audio 40000 RTP/AVP 0\r\n\
1328             a=rtpmap:0 PCMU/8000\r\n";
1329        let invite = format!(
1330            "INVITE sip:gemini@{target} SIP/2.0\r\n\
1331             Via: SIP/2.0/UDP 127.0.0.1:{uac_port};branch=z9hG4bKinv1\r\n\
1332             Max-Forwards: 70\r\n\
1333             From: <sip:probe@127.0.0.1>;tag=inv\r\n\
1334             To: <sip:gemini@{target}>\r\n\
1335             Call-ID: invite-1@127.0.0.1\r\n\
1336             CSeq: 1 INVITE\r\n\
1337             Contact: <sip:probe@127.0.0.1:{uac_port}>\r\n\
1338             Content-Type: application/sdp\r\n\
1339             Content-Length: {}\r\n\r\n{sdp_body}",
1340            sdp_body.len()
1341        );
1342        uac.send_to(invite.as_bytes(), &target).await.unwrap();
1343
1344        let incoming = tokio::time::timeout(Duration::from_secs(3), call_rx)
1345            .await
1346            .expect("call surfaces")
1347            .expect("agent still running");
1348        assert_eq!(incoming.offer.port, 40_000);
1349        assert_eq!(
1350            incoming.offer.g711_payload_type(),
1351            Some(super::super::rtp::PT_PCMU)
1352        );
1353        assert!(incoming.from.contains("probe"), "from: {}", incoming.from);
1354        incoming.reject();
1355
1356        // Drain provisional responses until the final failure arrives.
1357        let mut saw_final = false;
1358        for _ in 0..6 {
1359            match recv_status(&uac, &mut buf).await {
1360                Some(status) => {
1361                    let code: u32 = status
1362                        .split_whitespace()
1363                        .nth(1)
1364                        .and_then(|c| c.parse().ok())
1365                        .unwrap_or(0);
1366                    if code >= 400 {
1367                        saw_final = true;
1368                        break;
1369                    }
1370                }
1371                None => break,
1372            }
1373        }
1374        assert!(
1375            saw_final,
1376            "expected a final failure response to the rejected INVITE"
1377        );
1378    }
1379}