Skip to main content

sipx_call/
coupling.rs

1//! Two dialogs driven as one call.
2//!
3//! [`CouplingState`] is the sans-I/O offer/answer and lifecycle policy. [`EarlyCoupling`] owns the
4//! joined pending legs through cancellation, refusal or confirmation; [`Coupling`] then owns the
5//! two calls, optional media bridge, and confirmed signalling loop. Listener configuration,
6//! initial leg creation, routing and target choice stay above this crate.
7
8use std::collections::VecDeque;
9use std::net::IpAddr;
10
11use sipx_media::Bridge;
12use sipx_sdp::Direction;
13use sipx_sip::{Method, Reason, Uri};
14use sipx_transport::Incoming;
15use tokio::sync::mpsc;
16
17use crate::call::{CouplingDialEvent, sleep_until};
18use crate::dispatch::CouplingInvitation;
19use crate::{Call, Calls, DialOptions, Dialing, Error, Invitation, Result, Ringing};
20
21const DEFERRED_CAPACITY: usize = 16;
22
23/// One leg of a coupling.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Leg {
26    /// The first leg supplied to the coupling.
27    One,
28    /// The second leg supplied to the coupling.
29    Two,
30}
31
32impl Leg {
33    const fn peer(self) -> Self {
34        match self {
35            Self::One => Self::Two,
36            Self::Two => Self::One,
37        }
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42struct PerLeg<T> {
43    one: T,
44    two: T,
45}
46
47impl<T> PerLeg<T> {
48    const fn new(one: T, two: T) -> Self {
49        Self { one, two }
50    }
51
52    const fn get(&self, leg: Leg) -> &T {
53        match leg {
54            Leg::One => &self.one,
55            Leg::Two => &self.two,
56        }
57    }
58
59    const fn get_mut(&mut self, leg: Leg) -> &mut T {
60        match leg {
61            Leg::One => &mut self.one,
62            Leg::Two => &mut self.two,
63        }
64    }
65}
66
67/// Where an SDP offer legally arrived.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum OfferAxis {
70    /// The initial INVITE.
71    InitialInvite,
72    /// A reliable provisional response.
73    ReliableProvisional,
74    /// PRACK.
75    Prack,
76    /// UPDATE.
77    Update,
78    /// An in-dialog INVITE.
79    Reinvite,
80}
81
82/// What the offer/answer policy asks its I/O driver to do.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum OfferAction {
85    /// Relay this offer to the peer leg.
86    Relay {
87        /// The leg on which the offer arrived.
88        source: Leg,
89        /// The carrier on which its answer must return.
90        axis: OfferAxis,
91    },
92    /// Refuse the offer on its source leg.
93    Refuse {
94        /// The SIP status to send.
95        status: u16,
96    },
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100enum NegotiationState {
101    Idle,
102    Offering(OfferAxis),
103    Answering(OfferAxis),
104}
105
106impl NegotiationState {
107    const fn is_idle(self) -> bool {
108        matches!(self, Self::Idle)
109    }
110}
111
112/// The action an inbound CANCEL has on the peer leg.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum CancelAction {
115    /// Cancel the peer's still-pending INVITE.
116    CancelPeer,
117    /// The peer confirmed while the source INVITE remained pending; end that dialog with BYE.
118    ByePeer,
119    /// The peer is already confirmed; CANCEL cannot erase its dialog.
120    AcknowledgeOnly,
121}
122
123/// What a final failure on one leg requires on its peer.
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum FailureAction {
126    /// Refuse the peer's still-pending INVITE with the same final status.
127    RejectPeer {
128        /// The status received on the failed outbound leg.
129        status: u16,
130    },
131    /// The peer is confirmed, so end it with BYE rather than inventing a final INVITE response.
132    ByePeer,
133}
134
135/// Sans-I/O policy shared by early and confirmed coupling drivers.
136#[derive(Debug)]
137pub struct CouplingState {
138    negotiation: PerLeg<NegotiationState>,
139    confirmed: PerLeg<bool>,
140}
141
142impl Default for CouplingState {
143    fn default() -> Self {
144        Self {
145            negotiation: PerLeg::new(NegotiationState::Idle, NegotiationState::Idle),
146            confirmed: PerLeg::new(false, false),
147        }
148    }
149}
150
151impl CouplingState {
152    /// A fresh early coupling, with neither dialog confirmed.
153    #[must_use]
154    pub fn new() -> Self {
155        Self::default()
156    }
157
158    /// Record that one leg has reached a confirmed dialog.
159    pub fn confirm(&mut self, leg: Leg) {
160        *self.confirmed.get_mut(leg) = true;
161    }
162
163    /// Whether this leg has reached a confirmed dialog.
164    #[must_use]
165    pub fn is_confirmed(&self, leg: Leg) -> bool {
166        *self.confirmed.get(leg)
167    }
168
169    /// Begin relaying an offer.
170    ///
171    /// A collision with an offer this coupling sent on the source leg is refused 491. The remote
172    /// UAC owns the randomized retry required by RFC 3261 ยง14.1; when it arrives after completion,
173    /// it enters through this method as a new transaction.
174    #[must_use]
175    pub fn begin_offer(&mut self, source: Leg, axis: OfferAxis) -> OfferAction {
176        let peer = source.peer();
177        match *self.negotiation.get(source) {
178            NegotiationState::Offering(_) => {
179                return OfferAction::Refuse { status: 491 };
180            }
181            NegotiationState::Answering(_) => return OfferAction::Refuse { status: 500 },
182            NegotiationState::Idle => {}
183        }
184        if !self.negotiation.get(peer).is_idle() {
185            return OfferAction::Refuse { status: 491 };
186        }
187        self.start(source, axis)
188    }
189
190    /// Complete the exchange sourced on `source`.
191    ///
192    /// Returns whether an exchange from that leg was outstanding.
193    #[must_use]
194    pub fn complete(&mut self, source: Leg) -> bool {
195        if !self.matches_exchange(source) {
196            return false;
197        }
198        self.clear_exchange(source);
199        true
200    }
201
202    /// Fail the exchange sourced on `source`.
203    ///
204    /// Failure settles the offer/answer axis just as a final answer does; there is no answer left
205    /// outstanding after a final refusal.
206    #[must_use]
207    pub fn fail(&mut self, source: Leg) -> bool {
208        self.complete(source)
209    }
210
211    /// Whether both per-leg offer/answer machines are idle.
212    #[must_use]
213    pub fn is_idle(&self) -> bool {
214        self.negotiation.one.is_idle() && self.negotiation.two.is_idle()
215    }
216
217    /// Map a CANCEL on `source` according to the peer leg's confirmation state.
218    #[must_use]
219    pub fn cancel(&self, source: Leg) -> CancelAction {
220        match (self.is_confirmed(source), self.is_confirmed(source.peer())) {
221            (false, false) => CancelAction::CancelPeer,
222            (false, true) => CancelAction::ByePeer,
223            (true, _) => CancelAction::AcknowledgeOnly,
224        }
225    }
226
227    /// Map a final failure on `source` according to the peer leg's confirmation state.
228    #[must_use]
229    pub fn final_failure(&self, source: Leg, status: u16) -> FailureAction {
230        if self.is_confirmed(source.peer()) {
231            FailureAction::ByePeer
232        } else {
233            FailureAction::RejectPeer { status }
234        }
235    }
236
237    fn start(&mut self, source: Leg, axis: OfferAxis) -> OfferAction {
238        let peer = source.peer();
239        *self.negotiation.get_mut(source) = NegotiationState::Answering(axis);
240        *self.negotiation.get_mut(peer) = NegotiationState::Offering(axis);
241        OfferAction::Relay { source, axis }
242    }
243
244    fn matches_exchange(&self, source: Leg) -> bool {
245        let peer = source.peer();
246        matches!(
247            (*self.negotiation.get(source), *self.negotiation.get(peer)),
248            (NegotiationState::Answering(one), NegotiationState::Offering(two)) if one == two
249        )
250    }
251
252    fn clear_exchange(&mut self, source: Leg) {
253        *self.negotiation.get_mut(source) = NegotiationState::Idle;
254        *self.negotiation.get_mut(source.peer()) = NegotiationState::Idle;
255    }
256}
257
258/// Why the confirmed-dialog driver returned.
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub enum CouplingEnd {
261    /// One leg received and accepted a BYE.
262    Bye(Leg),
263    /// One routed inbox closed, so the peer was ended rather than orphaned.
264    InboxClosed(Leg),
265}
266
267/// The two calls and routed inboxes produced by an early coupling once both dialogs confirm.
268#[derive(Debug)]
269pub struct ConfirmedCoupling {
270    coupling: Coupling,
271    one_incoming: mpsc::Receiver<Incoming>,
272    two_incoming: mpsc::Receiver<Incoming>,
273}
274
275impl ConfirmedCoupling {
276    /// Take the confirmed owner and its two routed request streams.
277    #[must_use]
278    pub fn into_parts(self) -> (Coupling, mpsc::Receiver<Incoming>, mpsc::Receiver<Incoming>) {
279        (self.coupling, self.one_incoming, self.two_incoming)
280    }
281}
282
283/// Two pending user-agent legs owned from an early dialog through confirmation or failure.
284#[derive(Debug)]
285pub struct EarlyCoupling {
286    invitation: CouplingInvitation,
287    ringing: Ringing,
288    dialing: Option<Dialing>,
289    outgoing_incoming: mpsc::Receiver<Incoming>,
290    endpoint: sipx_transport::Handle,
291    media_address: IpAddr,
292    state: CouplingState,
293    deferred: PerLeg<VecDeque<Incoming>>,
294    delayed_offer_pending: bool,
295}
296
297async fn ring_source_leg(
298    endpoint: &sipx_transport::Handle,
299    incoming: &Incoming,
300    media_address: IpAddr,
301    delayed_direction: Option<Direction>,
302    answered_early: bool,
303) -> Result<Ringing> {
304    if let Some(direction) = delayed_direction {
305        crate::ring_offer_early(
306            endpoint,
307            incoming,
308            183,
309            "Session Progress",
310            media_address,
311            direction,
312        )
313        .await
314    } else if answered_early {
315        crate::ring_early(endpoint, incoming, 183, "Session Progress", media_address).await
316    } else {
317        crate::ring(endpoint, incoming, 180, "Ringing", true).await
318    }
319}
320
321fn outbound_failure_response(error: &Error) -> (u16, String) {
322    match error {
323        Error::Rejected { status, reason } => (*status, reason.clone()),
324        _ => (503, "Service Unavailable".to_owned()),
325    }
326}
327
328impl EarlyCoupling {
329    /// Consume an inbound invitation and create its relayed target leg under one owner.
330    ///
331    /// Target selection remains application policy. The coupling maps the source offer's audio
332    /// direction onto fresh target-leg SDP; endpoint addresses, ports and key material are never
333    /// copied between the two user-agent dialogs.
334    pub async fn dial(
335        invitation: Invitation,
336        calls: &Calls,
337        endpoint: &sipx_transport::Handle,
338        target: sipx_transport::Target,
339        to: &Uri,
340        options: &DialOptions,
341        media_address: IpAddr,
342    ) -> Result<Self> {
343        let source_offer = !invitation.request().request.body().is_empty();
344        let direction = relayed_direction(&invitation.request().request);
345        if source_offer && direction.is_none() {
346            invitation
347                .refuse(endpoint, 488, "Not Acceptable Here")
348                .await?;
349            return Err(Error::Sdp(
350                "the source initial INVITE carried no usable audio offer".to_owned(),
351            ));
352        }
353        let invitation = invitation.into_coupling();
354        let mut state = CouplingState::new();
355        if direction.is_some() {
356            let _relay = state.begin_offer(Leg::One, OfferAxis::InitialInvite);
357        }
358        let cancellation = invitation.cancellation();
359        let options = direction.map_or_else(
360            || options.clone(),
361            |direction| options.clone().with_initial_direction(direction),
362        );
363        let mut dialing = Box::pin(async {
364            match direction {
365                Some(_) => crate::dial_early(endpoint, target, to, &options)
366                    .await
367                    .map(|dialing| (dialing, None)),
368                None => {
369                    crate::call::dial_early_without_offer_for_coupling(
370                        endpoint, target, to, &options,
371                    )
372                    .await
373                }
374            }
375        });
376        let outbound = tokio::select! {
377            result = &mut dialing => match result {
378                Ok(outbound) => outbound,
379                Err(error) => {
380                    let (status, reason) = outbound_failure_response(&error);
381                    invitation.refuse(endpoint, status, reason).await?;
382                    return Err(error);
383                }
384            },
385            () = cancellation.cancelled() => {
386                match dialing.await {
387                    Ok((dialing, _)) => dialing.cancel().await,
388                    Err(error) => tracing::debug!(%error, "outbound leg ended while cancellation waited for a provisional"),
389                }
390                return Err(Error::InvitationCancelled);
391            }
392        };
393        let (outbound, delayed_direction) = outbound;
394        let Some(dialog) = outbound.dialog() else {
395            let mut call = outbound.answered().await?;
396            call.hang_up().await?;
397            invitation
398                .refuse(endpoint, 503, "Service Unavailable")
399                .await?;
400            return Err(Error::NoDialog);
401        };
402        let outgoing_incoming = calls.register(dialog);
403        let answered_early = outbound.has_early_session();
404        if delayed_direction.is_some() {
405            let _relay = state.begin_offer(Leg::Two, OfferAxis::ReliableProvisional);
406        }
407        let ringing = ring_source_leg(
408            endpoint,
409            &invitation.incoming,
410            media_address,
411            delayed_direction,
412            answered_early,
413        )
414        .await;
415        let ringing = match ringing {
416            Ok(ringing) => ringing,
417            Err(error) => {
418                outbound.cancel().await;
419                let (status, reason) = outbound_failure_response(&error);
420                invitation.refuse(endpoint, status, reason).await?;
421                return Err(error);
422            }
423        };
424        if answered_early && ringing.has_early_session() {
425            let _completed = state.complete(Leg::One);
426        }
427        Ok(Self {
428            invitation,
429            ringing,
430            dialing: Some(outbound),
431            outgoing_incoming,
432            endpoint: endpoint.clone(),
433            media_address,
434            state,
435            deferred: PerLeg::new(VecDeque::new(), VecDeque::new()),
436            delayed_offer_pending: delayed_direction.is_some(),
437        })
438    }
439
440    /// Join an inbound invitation already rung to its pending outbound invitation.
441    ///
442    /// Routing and target selection happen before this handoff. From this point the coupling is
443    /// the sole owner of the invitation, ringing state and dialing state, including their media
444    /// sessions and cancellation obligations.
445    #[must_use]
446    pub fn new(
447        invitation: Invitation,
448        ringing: Ringing,
449        dialing: Dialing,
450        outgoing_incoming: mpsc::Receiver<Incoming>,
451        endpoint: &sipx_transport::Handle,
452        media_address: IpAddr,
453    ) -> Self {
454        Self {
455            invitation: invitation.into_coupling(),
456            ringing,
457            dialing: Some(dialing),
458            outgoing_incoming,
459            endpoint: endpoint.clone(),
460            media_address,
461            state: CouplingState::new(),
462            deferred: PerLeg::new(VecDeque::new(), VecDeque::new()),
463            delayed_offer_pending: false,
464        }
465    }
466
467    /// The shared offer/answer and lifecycle policy while either leg remains early.
468    #[must_use]
469    pub fn state(&self) -> &CouplingState {
470        &self.state
471    }
472
473    /// Drive both early legs until they confirm, are cancelled, or receive a final refusal.
474    ///
475    /// A matching inbound CANCEL withdraws the pending outbound INVITE. If its 2xx crossed the
476    /// CANCEL, that now-confirmed outbound call receives BYE instead. An outbound 4xx/5xx is sent
477    /// as the inbound INVITE's final response with the same status and reason.
478    pub async fn confirmed(mut self) -> Result<ConfirmedCoupling> {
479        let cancellation = self.invitation.cancellation();
480        let mut outbound_call = None;
481
482        loop {
483            if outbound_call.is_some()
484                && (!self.ringing.has_early_session() || self.ringing.is_acknowledged())
485            {
486                return Box::pin(self.finish_confirmation(outbound_call.take())).await;
487            }
488            if let Some(request) = self.deferred.one.pop_front() {
489                self.handle_early_request(Leg::One, request, &mut outbound_call)
490                    .await?;
491                continue;
492            }
493            if let Some(request) = self.deferred.two.pop_front() {
494                self.handle_early_request(Leg::Two, request, &mut outbound_call)
495                    .await?;
496                continue;
497            }
498
499            tokio::select! {
500                () = cancellation.cancelled() => {
501                    match self.state.cancel(Leg::One) {
502                        CancelAction::CancelPeer => {
503                            if let Some(dialing) = self.dialing.take() {
504                                dialing.cancel().await;
505                            }
506                        }
507                        CancelAction::ByePeer => {
508                            if let Some(mut call) = outbound_call {
509                                call.hang_up().await?;
510                            }
511                        }
512                        CancelAction::AcknowledgeOnly => {}
513                    }
514                    return Err(Error::InvitationCancelled);
515                }
516                request = self.invitation.requests.recv() => {
517                    let Some(request) = request else {
518                        if let Some(mut call) = outbound_call {
519                            call.hang_up().await?;
520                        } else if let Some(dialing) = self.dialing.take() {
521                            dialing.cancel().await;
522                        }
523                        return Err(Error::InvitationCancelled);
524                    };
525                    self.handle_early_request(Leg::One, request, &mut outbound_call).await?;
526                }
527                step = async {
528                    if outbound_call.is_some() {
529                        return Ok(CouplingDialEvent::Incoming(Box::new(
530                            self.outgoing_incoming.recv().await,
531                        )));
532                    }
533                    let Some(dialing) = self.dialing.as_mut() else {
534                        return std::future::pending::<Result<CouplingDialEvent>>().await;
535                    };
536                    dialing.coupling_step(&mut self.outgoing_incoming).await
537                } => {
538                    self.handle_dial_event(step, &mut outbound_call).await?;
539                }
540            }
541        }
542    }
543
544    async fn handle_dial_event(
545        &mut self,
546        event: Result<CouplingDialEvent>,
547        outbound_call: &mut Option<Call>,
548    ) -> Result<()> {
549        match event {
550            Ok(CouplingDialEvent::Progress) => Ok(()),
551            Ok(CouplingDialEvent::ReliableOffer(direction)) => {
552                match self
553                    .state
554                    .begin_offer(Leg::Two, OfferAxis::ReliableProvisional)
555                {
556                    OfferAction::Relay { .. } => {}
557                    OfferAction::Refuse { .. } => return Err(Error::NoDialog),
558                }
559                let ringing = crate::ring_offer_early(
560                    &self.endpoint,
561                    &self.invitation.incoming,
562                    183,
563                    "Session Progress",
564                    self.media_address,
565                    direction,
566                )
567                .await?;
568                self.ringing = ringing;
569                self.delayed_offer_pending = true;
570                Ok(())
571            }
572            Ok(CouplingDialEvent::Answered(call)) => {
573                self.state.confirm(Leg::Two);
574                *outbound_call = Some(*call);
575                self.dialing = None;
576                Ok(())
577            }
578            Ok(CouplingDialEvent::Incoming(request)) => {
579                let Some(request) = *request else {
580                    if let Some(mut call) = outbound_call.take() {
581                        // discard: the peer inbox has already closed, so there is no caller left
582                        // to receive this cleanup failure. The BYE transmit is counted by the
583                        // transport; retain the primary `NoResponse` cause and make the secondary
584                        // failure observable here.
585                        if let Err(error) = call.hang_up().await {
586                            tracing::warn!(%error, "could not hang up an orphaned coupled call");
587                        }
588                    } else if let Some(dialing) = self.dialing.take() {
589                        dialing.cancel().await;
590                    }
591                    self.invitation
592                        .refuse(&self.endpoint, 503, "Service Unavailable")
593                        .await?;
594                    return Err(Error::NoResponse);
595                };
596                if request.request.method != Method::Bye {
597                    return self
598                        .handle_early_request(Leg::Two, request, outbound_call)
599                        .await;
600                }
601                let Some(call) = outbound_call.as_mut() else {
602                    return Ok(());
603                };
604                if call.handle(&request).await? && call.is_ended() {
605                    self.invitation
606                        .refuse(&self.endpoint, 487, "Request Terminated")
607                        .await?;
608                    return Err(Error::Rejected {
609                        status: 487,
610                        reason: "Request Terminated".to_owned(),
611                    });
612                }
613                Ok(())
614            }
615            Err(Error::Rejected { status, reason }) => {
616                self.dialing = None;
617                self.invitation
618                    .refuse(&self.endpoint, status, reason.clone())
619                    .await?;
620                Err(Error::Rejected { status, reason })
621            }
622            Err(error) => {
623                self.dialing = None;
624                self.invitation
625                    .refuse(&self.endpoint, 503, "Service Unavailable")
626                    .await?;
627                Err(error)
628            }
629        }
630    }
631
632    async fn finish_confirmation(self, outbound: Option<Call>) -> Result<ConfirmedCoupling> {
633        let Some(mut outbound) = outbound else {
634            return Err(Error::NoResponse);
635        };
636        if let Err(error) = self.invitation.claim() {
637            outbound.hang_up().await?;
638            return Err(error);
639        }
640        let inbound = if self.ringing.has_early_session() {
641            let mut ringing = self.ringing;
642            crate::answer_early(&self.endpoint, &self.invitation.incoming, &mut ringing).await
643        } else {
644            crate::answer_ringing(
645                &self.endpoint,
646                &self.invitation.incoming,
647                self.media_address,
648                &self.ringing,
649            )
650            .await
651        };
652        let inbound = match inbound {
653            Ok(inbound) => inbound,
654            Err(error) => {
655                // The target already has a confirmed dialog. An inbound answer failure cannot
656                // make that ownership disappear; end it before returning the primary cause.
657                if let Err(cleanup) = outbound.hang_up().await {
658                    tracing::warn!(%cleanup, "could not end confirmed peer after inbound answer failed");
659                }
660                return Err(error);
661            }
662        };
663        Ok(ConfirmedCoupling {
664            coupling: Coupling::new(inbound, outbound),
665            one_incoming: self.invitation.requests,
666            two_incoming: self.outgoing_incoming,
667        })
668    }
669
670    async fn handle_early_request(
671        &mut self,
672        leg: Leg,
673        incoming: Incoming,
674        outbound_call: &mut Option<Call>,
675    ) -> Result<()> {
676        if leg == Leg::One && incoming.request.method == Method::Prack {
677            let matched = match self.ringing.on_prack(&incoming).await {
678                Ok(matched) => matched,
679                Err(error) => {
680                    if self.delayed_offer_pending {
681                        let _failed = self.state.fail(Leg::Two);
682                        if let Some(dialing) = self.dialing.take() {
683                            dialing.cancel().await;
684                        }
685                        self.invitation
686                            .refuse(&self.endpoint, 488, "Not Acceptable Here")
687                            .await?;
688                    }
689                    return Err(error);
690                }
691            };
692            if matched && self.delayed_offer_pending {
693                let Some(dialing) = self.dialing.as_mut() else {
694                    return Err(Error::NoDialog);
695                };
696                if let Err(error) = dialing.complete_coupled_prack().await {
697                    let _failed = self.state.fail(Leg::Two);
698                    if let Some(dialing) = self.dialing.take() {
699                        dialing.cancel().await;
700                    }
701                    return Err(error);
702                }
703                self.delayed_offer_pending = false;
704                let _completed = self.state.complete(Leg::Two);
705            }
706            return Ok(());
707        }
708        if incoming.request.method != Method::Update {
709            Call::refuse_with(&self.endpoint, &incoming, 405, "Method Not Allowed").await?;
710            return Ok(());
711        }
712        if !crate::update::carries_offer(&incoming.request) {
713            return self
714                .handle_offerless_update(leg, &incoming, outbound_call)
715                .await;
716        }
717        self.handle_early_offer(leg, incoming, outbound_call).await
718    }
719
720    async fn handle_offerless_update(
721        &mut self,
722        leg: Leg,
723        incoming: &Incoming,
724        outbound_call: &mut Option<Call>,
725    ) -> Result<()> {
726        let handled = match leg {
727            Leg::One => self.ringing.on_update(incoming).await?,
728            Leg::Two => match outbound_call {
729                Some(call) => call.handle(incoming).await?,
730                None => match self.dialing.as_mut() {
731                    Some(dialing) => dialing.on_update(incoming).await?,
732                    None => false,
733                },
734            },
735        };
736        if !handled {
737            Call::refuse_with(&self.endpoint, incoming, 481, "No Dialog").await?;
738        }
739        Ok(())
740    }
741
742    async fn handle_early_offer(
743        &mut self,
744        leg: Leg,
745        incoming: Incoming,
746        outbound_call: &mut Option<Call>,
747    ) -> Result<()> {
748        let Some(direction) = relayed_direction(&incoming.request) else {
749            return Call::refuse_with(&self.endpoint, &incoming, 488, "Not Acceptable Here").await;
750        };
751        match self.state.begin_offer(leg, OfferAxis::Update) {
752            OfferAction::Refuse { status } => {
753                let reason = if status == 491 {
754                    "Request Pending"
755                } else {
756                    "Server Internal Error"
757                };
758                return Call::refuse_with(&self.endpoint, &incoming, status, reason).await;
759            }
760            OfferAction::Relay { .. } => {}
761        }
762
763        let (relayed, inbox_closed) = match leg {
764            Leg::One => match outbound_call {
765                Some(call) => {
766                    drive_early_outgoing_offer(
767                        call.update(direction),
768                        &mut self.state,
769                        Leg::Two,
770                        &mut self.outgoing_incoming,
771                        &mut self.deferred.two,
772                        &self.endpoint,
773                    )
774                    .await?
775                }
776                None => match self.dialing.as_mut() {
777                    Some(dialing) => {
778                        drive_early_outgoing_offer(
779                            dialing.update(direction),
780                            &mut self.state,
781                            Leg::Two,
782                            &mut self.outgoing_incoming,
783                            &mut self.deferred.two,
784                            &self.endpoint,
785                        )
786                        .await?
787                    }
788                    None => (Err(Error::NoDialog), false),
789                },
790            },
791            Leg::Two => {
792                drive_early_outgoing_offer(
793                    self.ringing.update(direction),
794                    &mut self.state,
795                    Leg::One,
796                    &mut self.invitation.requests,
797                    &mut self.deferred.one,
798                    &self.endpoint,
799                )
800                .await?
801            }
802        };
803        if let Err(error) = relayed {
804            let _settled = self.state.fail(leg);
805            if let Error::Rejected { status, reason } = error {
806                return Call::refuse_with(&self.endpoint, &incoming, status, reason).await;
807            }
808            return Err(error);
809        }
810
811        let handled = match leg {
812            Leg::One => self.ringing.on_update(&incoming).await,
813            Leg::Two => match outbound_call {
814                Some(call) => call.handle(&incoming).await,
815                None => match self.dialing.as_mut() {
816                    Some(dialing) => dialing.on_update(&incoming).await,
817                    None => Err(Error::NoDialog),
818                },
819            },
820        };
821        let _settled = match &handled {
822            Ok(_) => self.state.complete(leg),
823            Err(_) => self.state.fail(leg),
824        };
825        if !handled? {
826            Call::refuse_with(&self.endpoint, &incoming, 481, "No Dialog").await?;
827        }
828        if inbox_closed {
829            return Err(Error::NoResponse);
830        }
831        Ok(())
832    }
833}
834
835async fn drive_early_outgoing_offer<F>(
836    outgoing: F,
837    state: &mut CouplingState,
838    far_leg: Leg,
839    incoming: &mut mpsc::Receiver<Incoming>,
840    deferred: &mut VecDeque<Incoming>,
841    responder: &sipx_transport::Handle,
842) -> Result<(Result<()>, bool)>
843where
844    F: std::future::Future<Output = Result<()>>,
845{
846    tokio::pin!(outgoing);
847    let mut inbox_closed = false;
848    loop {
849        tokio::select! {
850            biased;
851            received = incoming.recv(), if !inbox_closed && deferred.len() < DEFERRED_CAPACITY => {
852                let Some(request) = received else {
853                    inbox_closed = true;
854                    continue;
855                };
856                let Some(axis) = offer_axis(&request) else {
857                    deferred.push_back(request);
858                    continue;
859                };
860                match state.begin_offer(far_leg, axis) {
861                    OfferAction::Refuse { status } => {
862                        let reason = if status == 491 {
863                            "Request Pending"
864                        } else {
865                            "Server Internal Error"
866                        };
867                        Call::refuse_with(responder, &request, status, reason).await?;
868                    }
869                    OfferAction::Relay { .. } => deferred.push_back(request),
870                }
871            }
872            result = &mut outgoing => return Ok((result, inbox_closed)),
873        }
874    }
875}
876
877/// Two confirmed calls owned and driven as one.
878#[derive(Debug)]
879pub struct Coupling {
880    // Declared first so it drops before either call and releases its session handles first.
881    bridge: Option<Bridge>,
882    one: Call,
883    two: Call,
884    state: CouplingState,
885    deferred: PerLeg<VecDeque<Incoming>>,
886}
887
888impl Coupling {
889    /// Take sole ownership of two confirmed calls.
890    #[must_use]
891    pub fn new(one: Call, two: Call) -> Self {
892        let mut state = CouplingState::new();
893        state.confirm(Leg::One);
894        state.confirm(Leg::Two);
895        Self {
896            bridge: None,
897            one,
898            two,
899            state,
900            deferred: PerLeg::new(VecDeque::new(), VecDeque::new()),
901        }
902    }
903
904    /// Attach the existing channel-based media bridge.
905    ///
906    /// Without this call the coupling is signalling-only. Calling it again replaces the bridge
907    /// after a renegotiation moved either session.
908    pub fn bridge_media(&mut self) -> bool {
909        let bridge = Bridge::connect(self.one.media_handle(), self.two.media_handle());
910        let transcoding = bridge.is_transcoding();
911        self.bridge = Some(bridge);
912        transcoding
913    }
914
915    /// Whether this coupling currently forwards media.
916    #[must_use]
917    pub fn has_media_bridge(&self) -> bool {
918        self.bridge.is_some()
919    }
920
921    /// The offer/answer policy, for an early-dialog driver or application adapter.
922    #[must_use]
923    pub fn state(&self) -> &CouplingState {
924        &self.state
925    }
926
927    /// Mutably borrow the offer/answer policy while retaining ownership of both calls.
928    pub fn state_mut(&mut self) -> &mut CouplingState {
929        &mut self.state
930    }
931
932    /// Borrow the calls for inspection without transferring either one out.
933    #[must_use]
934    pub fn calls(&self) -> (&Call, &Call) {
935        (&self.one, &self.two)
936    }
937
938    /// Drive both routed inboxes until either dialog ends.
939    ///
940    /// A BYE is first handled on the receiving leg (including its 200), then mapped to a BYE on
941    /// the peer. Closing an inbox is terminal too: returning while leaving its peer alive would
942    /// orphan a dialog the coupling still owns.
943    pub async fn run(
944        &mut self,
945        one_incoming: &mut mpsc::Receiver<Incoming>,
946        two_incoming: &mut mpsc::Receiver<Incoming>,
947    ) -> Result<CouplingEnd> {
948        loop {
949            if let Some(incoming) = self.deferred.one.pop_front() {
950                if let Some(end) = self
951                    .handle(Leg::One, incoming, one_incoming, two_incoming)
952                    .await?
953                {
954                    return Ok(end);
955                }
956                continue;
957            }
958            if let Some(incoming) = self.deferred.two.pop_front() {
959                if let Some(end) = self
960                    .handle(Leg::Two, incoming, one_incoming, two_incoming)
961                    .await?
962                {
963                    return Ok(end);
964                }
965                continue;
966            }
967            let one_deadline = self.one.session_deadline();
968            let two_deadline = self.two.session_deadline();
969            tokio::select! {
970                incoming = one_incoming.recv() => {
971                    let Some(incoming) = incoming else {
972                        self.two.hang_up().await?;
973                        return Ok(CouplingEnd::InboxClosed(Leg::One));
974                    };
975                    if let Some(end) = self
976                        .handle(Leg::One, incoming, one_incoming, two_incoming)
977                        .await?
978                    {
979                        return Ok(end);
980                    }
981                }
982                incoming = two_incoming.recv() => {
983                    let Some(incoming) = incoming else {
984                        self.one.hang_up().await?;
985                        return Ok(CouplingEnd::InboxClosed(Leg::Two));
986                    };
987                    if let Some(end) = self
988                        .handle(Leg::Two, incoming, one_incoming, two_incoming)
989                        .await?
990                    {
991                        return Ok(end);
992                    }
993                }
994                () = sleep_until(one_deadline) => {
995                    if let Err(error) = self.one.on_session_deadline().await {
996                        if self.one.is_ended() {
997                            // discard: the session-timer failure is the cause returned to the
998                            // owner. A failed peer BYE is counted at transport and logged here so
999                            // replacing the primary error cannot hide or misclassify it.
1000                            if let Err(cleanup_error) = self.two.hang_up().await {
1001                                tracing::warn!(%cleanup_error, "could not clean up coupled peer");
1002                            }
1003                        }
1004                        return Err(error);
1005                    }
1006                }
1007                () = sleep_until(two_deadline) => {
1008                    if let Err(error) = self.two.on_session_deadline().await {
1009                        if self.two.is_ended() {
1010                            // discard: the session-timer failure is the cause returned to the
1011                            // owner. A failed peer BYE is counted at transport and logged here so
1012                            // replacing the primary error cannot hide or misclassify it.
1013                            if let Err(cleanup_error) = self.one.hang_up().await {
1014                                tracing::warn!(%cleanup_error, "could not clean up coupled peer");
1015                            }
1016                        }
1017                        return Err(error);
1018                    }
1019                }
1020            }
1021        }
1022    }
1023
1024    async fn handle(
1025        &mut self,
1026        leg: Leg,
1027        incoming: Incoming,
1028        one_incoming: &mut mpsc::Receiver<Incoming>,
1029        two_incoming: &mut mpsc::Receiver<Incoming>,
1030    ) -> Result<Option<CouplingEnd>> {
1031        let is_bye = incoming.request.method == Method::Bye;
1032        if let Some(axis) = offer_axis(&incoming) {
1033            return self
1034                .relay_offer(leg, axis, incoming, one_incoming, two_incoming)
1035                .await;
1036        }
1037        let reason = incoming
1038            .request
1039            .headers
1040            .typed::<Reason>()
1041            .and_then(std::result::Result::ok)
1042            .and_then(|reason| reason.0.into_iter().next());
1043        let (call, peer) = match leg {
1044            Leg::One => (&mut self.one, &mut self.two),
1045            Leg::Two => (&mut self.two, &mut self.one),
1046        };
1047        if !call.handle(&incoming).await? {
1048            call.refuse_unclaimed(&incoming).await;
1049            return Ok(None);
1050        }
1051        if !is_bye || !call.is_ended() {
1052            return Ok(None);
1053        }
1054        match reason {
1055            Some(reason) => peer.hang_up_with_reason(reason).await?,
1056            None => peer.hang_up().await?,
1057        }
1058        Ok(Some(CouplingEnd::Bye(leg)))
1059    }
1060
1061    async fn relay_offer(
1062        &mut self,
1063        leg: Leg,
1064        axis: OfferAxis,
1065        incoming: Incoming,
1066        one_incoming: &mut mpsc::Receiver<Incoming>,
1067        two_incoming: &mut mpsc::Receiver<Incoming>,
1068    ) -> Result<Option<CouplingEnd>> {
1069        let source = self.call(leg);
1070        if !source.dialog.matches(&incoming.request) {
1071            source.refuse_unclaimed(&incoming).await;
1072            return Ok(None);
1073        }
1074        if source.dialog.is_out_of_order(&incoming.request) {
1075            source
1076                .refuse(&incoming, 500, "Server Internal Error")
1077                .await?;
1078            return Ok(None);
1079        }
1080        let Some(direction) = source.can_accept_offer(incoming.request.body()) else {
1081            source.refuse(&incoming, 488, "Not Acceptable Here").await?;
1082            return Ok(None);
1083        };
1084        match self.state.begin_offer(leg, axis) {
1085            OfferAction::Refuse { status } => {
1086                let reason = if status == 491 {
1087                    "Request Pending"
1088                } else {
1089                    "Server Internal Error"
1090                };
1091                self.call(leg).refuse(&incoming, status, reason).await?;
1092                return Ok(None);
1093            }
1094            OfferAction::Relay { .. } => {}
1095        }
1096
1097        let (relayed, inbox_closed) = match leg {
1098            Leg::One => {
1099                drive_outgoing_offer(
1100                    &mut self.two,
1101                    &mut self.state,
1102                    Leg::Two,
1103                    axis,
1104                    direction,
1105                    two_incoming,
1106                    &mut self.deferred.two,
1107                )
1108                .await?
1109            }
1110            Leg::Two => {
1111                drive_outgoing_offer(
1112                    &mut self.one,
1113                    &mut self.state,
1114                    Leg::One,
1115                    axis,
1116                    direction,
1117                    one_incoming,
1118                    &mut self.deferred.one,
1119                )
1120                .await?
1121            }
1122        };
1123
1124        if let Err(error) = relayed {
1125            let _settled = self.state.fail(leg);
1126            if let Error::Rejected { status, reason } = error {
1127                self.call(leg).refuse(&incoming, status, reason).await?;
1128                return Ok(None);
1129            }
1130            return Err(error);
1131        }
1132
1133        let handled = match leg {
1134            Leg::One => self.one.handle(&incoming).await,
1135            Leg::Two => self.two.handle(&incoming).await,
1136        };
1137        let _settled = match &handled {
1138            Ok(_) => self.state.complete(leg),
1139            Err(_) => self.state.fail(leg),
1140        };
1141        let handled = handled?;
1142        if !handled {
1143            self.call(leg).refuse_unclaimed(&incoming).await;
1144        }
1145        if self.bridge.is_some() {
1146            self.bridge_media();
1147        }
1148        if inbox_closed {
1149            self.call_mut(leg).hang_up().await?;
1150            return Ok(Some(CouplingEnd::InboxClosed(leg.peer())));
1151        }
1152        Ok(None)
1153    }
1154
1155    fn call(&self, leg: Leg) -> &Call {
1156        match leg {
1157            Leg::One => &self.one,
1158            Leg::Two => &self.two,
1159        }
1160    }
1161
1162    fn call_mut(&mut self, leg: Leg) -> &mut Call {
1163        match leg {
1164            Leg::One => &mut self.one,
1165            Leg::Two => &mut self.two,
1166        }
1167    }
1168}
1169
1170/// Drive the outgoing half of a relayed offer while continuing to read that leg's routed inbox.
1171///
1172/// The outgoing method mutably borrows `far`, so ordinary requests are deferred until it settles.
1173/// An offer cannot wait: it collided with the outstanding offer and needs its final 491 while the
1174/// collision is still real. A cloned endpoint handle can send that response without touching the
1175/// call's dialog state.
1176async fn drive_outgoing_offer(
1177    far: &mut Call,
1178    state: &mut CouplingState,
1179    far_leg: Leg,
1180    axis: OfferAxis,
1181    direction: Direction,
1182    incoming: &mut mpsc::Receiver<Incoming>,
1183    deferred: &mut VecDeque<Incoming>,
1184) -> Result<(Result<()>, bool)> {
1185    let responder = far.responder();
1186    let outgoing = async {
1187        if axis == OfferAxis::Update {
1188            far.update(direction).await
1189        } else {
1190            far.reinvite(direction).await
1191        }
1192    };
1193    tokio::pin!(outgoing);
1194    let mut inbox_closed = false;
1195
1196    loop {
1197        tokio::select! {
1198            biased;
1199            received = incoming.recv(), if !inbox_closed && deferred.len() < DEFERRED_CAPACITY => {
1200                let Some(request) = received else {
1201                    inbox_closed = true;
1202                    continue;
1203                };
1204                let Some(incoming_axis) = offer_axis(&request) else {
1205                    deferred.push_back(request);
1206                    continue;
1207                };
1208                match state.begin_offer(far_leg, incoming_axis) {
1209                    OfferAction::Refuse { status } => {
1210                        let reason = if status == 491 {
1211                            "Request Pending"
1212                        } else {
1213                            "Server Internal Error"
1214                        };
1215                        Call::refuse_with(&responder, &request, status, reason).await?;
1216                    }
1217                    OfferAction::Relay { .. } => deferred.push_back(request),
1218                }
1219            }
1220            result = &mut outgoing => return Ok((result, inbox_closed)),
1221        }
1222    }
1223}
1224
1225fn offer_axis(incoming: &Incoming) -> Option<OfferAxis> {
1226    if !crate::update::carries_offer(&incoming.request) {
1227        return None;
1228    }
1229    match incoming.request.method {
1230        Method::Invite => Some(OfferAxis::Reinvite),
1231        Method::Update => Some(OfferAxis::Update),
1232        _ => None,
1233    }
1234}
1235
1236fn relayed_direction(request: &sipx_sip::Request) -> Option<Direction> {
1237    sipx_sdp::parse(&String::from_utf8_lossy(request.body()))
1238        .ok()
1239        .and_then(|description| {
1240            description
1241                .media
1242                .into_iter()
1243                .find(|media| media.media == "audio" && !media.is_rejected())
1244                .map(|media| media.direction())
1245        })
1246}
1247
1248#[cfg(test)]
1249#[allow(
1250    clippy::unwrap_used,
1251    clippy::expect_used,
1252    clippy::panic,
1253    clippy::indexing_slicing
1254)]
1255mod tests {
1256    use super::*;
1257
1258    fn request_with(body: &'static [u8]) -> sipx_sip::Request {
1259        let host = sipx_sip::HostName::new("callee.example").expect("valid host");
1260        let mut request =
1261            sipx_sip::Request::new(Method::Invite, Uri::sip(sipx_sip::Host::Name(host)));
1262        request.set_body(bytes::Bytes::from_static(body));
1263        request
1264    }
1265
1266    #[test]
1267    fn malformed_sdp_has_no_relay_direction() {
1268        assert_eq!(
1269            relayed_direction(&request_with(b"not a session description")),
1270            None
1271        );
1272    }
1273
1274    #[test]
1275    fn relay_preserves_endpoint_relative_direction() {
1276        let request = request_with(
1277            b"v=0\r\no=- 1 1 IN IP4 127.0.0.1\r\ns=-\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\nm=audio 4000 RTP/AVP 0\r\na=sendonly\r\n",
1278        );
1279        assert_eq!(relayed_direction(&request), Some(Direction::SendOnly));
1280    }
1281
1282    #[test]
1283    fn every_offer_axis_uses_two_per_leg_states_and_returns_to_idle() {
1284        for axis in [
1285            OfferAxis::InitialInvite,
1286            OfferAxis::ReliableProvisional,
1287            OfferAxis::Prack,
1288            OfferAxis::Update,
1289            OfferAxis::Reinvite,
1290        ] {
1291            let mut state = CouplingState::new();
1292            assert_eq!(
1293                state.begin_offer(Leg::One, axis),
1294                OfferAction::Relay {
1295                    source: Leg::One,
1296                    axis
1297                }
1298            );
1299            assert!(!state.is_idle());
1300            assert!(state.complete(Leg::One));
1301            assert!(state.is_idle());
1302        }
1303    }
1304
1305    #[test]
1306    fn glare_is_refused_and_a_new_retry_is_accepted_after_completion() {
1307        let mut state = CouplingState::new();
1308        assert!(matches!(
1309            state.begin_offer(Leg::One, OfferAxis::Reinvite),
1310            OfferAction::Relay { .. }
1311        ));
1312        assert_eq!(
1313            state.begin_offer(Leg::Two, OfferAxis::Update),
1314            OfferAction::Refuse { status: 491 }
1315        );
1316        assert_eq!(
1317            state.begin_offer(Leg::Two, OfferAxis::Prack),
1318            OfferAction::Refuse { status: 491 }
1319        );
1320        assert!(state.complete(Leg::One));
1321        assert_eq!(
1322            state.begin_offer(Leg::Two, OfferAxis::Update),
1323            OfferAction::Relay {
1324                source: Leg::Two,
1325                axis: OfferAxis::Update
1326            }
1327        );
1328    }
1329
1330    #[test]
1331    fn cancel_crosses_only_before_the_peer_is_confirmed() {
1332        let mut state = CouplingState::new();
1333        assert_eq!(state.cancel(Leg::One), CancelAction::CancelPeer);
1334        state.confirm(Leg::Two);
1335        assert_eq!(state.cancel(Leg::One), CancelAction::ByePeer);
1336        state.confirm(Leg::One);
1337        assert_eq!(state.cancel(Leg::One), CancelAction::AcknowledgeOnly);
1338    }
1339
1340    #[test]
1341    fn final_failure_preserves_status_until_the_peer_is_confirmed() {
1342        let mut state = CouplingState::new();
1343        assert_eq!(
1344            state.final_failure(Leg::Two, 486),
1345            FailureAction::RejectPeer { status: 486 }
1346        );
1347        assert_eq!(
1348            state.final_failure(Leg::Two, 503),
1349            FailureAction::RejectPeer { status: 503 }
1350        );
1351        state.confirm(Leg::One);
1352        assert_eq!(state.final_failure(Leg::Two, 486), FailureAction::ByePeer);
1353    }
1354}