diff --git a/src/dialog/client_dialog.rs b/src/dialog/client_dialog.rs index 157ab011..73f99fbc 100644 --- a/src/dialog/client_dialog.rs +++ b/src/dialog/client_dialog.rs @@ -794,8 +794,7 @@ impl ClientInviteDialog { .unwrap() .replace(contact.clone()); - *self.inner.remote_uri.lock().unwrap() = - resp.remote_uri(tx.destination.as_ref())?; + *self.inner.remote_uri.lock().unwrap() = resp.contact_uri()?; self.inner .transition(DialogState::Confirmed(dialog_id.clone(), resp))?; } diff --git a/src/dialog/tests/test_client_dialog.rs b/src/dialog/tests/test_client_dialog.rs index 68068d03..8171b841 100644 --- a/src/dialog/tests/test_client_dialog.rs +++ b/src/dialog/tests/test_client_dialog.rs @@ -10,7 +10,7 @@ use crate::{ dialog::{DialogInner, DialogState, TerminatedReason}, DialogId, }, - rsip_ext::destination_from_request, + rsip_ext::{destination_from_request, RsipResponseExt}, }; use async_trait::async_trait; use rsip::{headers::*, prelude::HeadersExt, Request, Response, StatusCode, Uri}; @@ -476,6 +476,85 @@ async fn test_route_set_updates_from_200_ok_response() -> crate::Result<()> { Ok(()) } +#[tokio::test] +async fn test_confirmed_dialog_bye_keeps_contact_uri_with_outbound_route() -> crate::Result<()> { + let endpoint = create_test_endpoint().await?; + let (state_sender, _) = unbounded_channel(); + + let dialog_id = DialogId { + call_id: "bye-contact-call".to_string(), + local_tag: "from-tag".to_string(), + remote_tag: "to-tag".to_string(), + }; + + let invite_req = create_invite_request("from-tag", "", "bye-contact-call"); + let (tu_sender, _tu_receiver) = unbounded_channel(); + + let dialog_inner = DialogInner::new( + TransactionRole::Client, + dialog_id, + invite_req, + endpoint.inner.clone(), + state_sender, + None, + Some(Uri::try_from("sip:alice@alice.example.com:5060")?), + tu_sender, + )?; + + let client_dialog = ClientInviteDialog { + inner: Arc::new(dialog_inner), + }; + + let mut headers: Vec
= vec![ + Via::new("SIP/2.0/UDP proxy.example.com:5060;branch=z9hG4bKproxy").into(), + CSeq::new("1 INVITE").into(), + From::new("Alice ;tag=from-tag").into(), + To::new("Bob ;tag=to-tag").into(), + CallId::new("bye-contact-call").into(), + Header::RecordRoute(RecordRoute::new("")), + Contact::new("").into(), + ]; + headers.push(ContentLength::new("0").into()); + + let success_resp = Response { + status_code: StatusCode::OK, + version: rsip::Version::V2, + headers: headers.into(), + body: vec![], + }; + + client_dialog + .inner + .update_route_set_from_response(&success_resp); + *client_dialog.inner.remote_uri.lock().unwrap() = success_resp.contact_uri()?; + + let outbound_addr = SipAddr::try_from(&Uri::try_from("sip:uac.example.com:5060")?)?; + let bye_request = client_dialog.inner.make_request( + rsip::Method::Bye, + None, + Some(outbound_addr), + None, + None, + None, + )?; + + assert_eq!( + bye_request.uri, + Uri::try_from("sip:bob@198.51.100.20:5090;ob")?, + "BYE Request-URI must preserve the Contact target learned from 200 OK", + ); + + let destination = destination_from_request(&bye_request) + .expect("route-enabled BYE should resolve to a destination"); + let expected_destination = Uri::try_from("sip:proxy.example.com:5060;lr")?; + assert_eq!( + &*destination, &expected_destination, + "Transport destination must still come from the Route set", + ); + + Ok(()) +} + /// Verifies CANCEL request construction per RFC 3261 Section 9.1. /// /// RFC 3261 9.1 states: diff --git a/src/rsip_ext.rs b/src/rsip_ext.rs index f8e96bfd..283a44af 100644 --- a/src/rsip_ext.rs +++ b/src/rsip_ext.rs @@ -22,6 +22,7 @@ pub trait RsipResponseExt { fn reason_phrase(&self) -> Option<&str>; fn via_received(&self) -> Option; fn content_type(&self) -> Option; + fn contact_uri(&self) -> Result; fn remote_uri(&self, destination: Option<&SipAddr>) -> Result; } @@ -60,16 +61,20 @@ impl RsipResponseExt for rsip::Response { None } - fn remote_uri(&self, destination: Option<&SipAddr>) -> Result { + fn contact_uri(&self) -> Result { let contact = self.contact_header()?; - // update remote uri - let mut contact_uri = if let Ok(typed_contact) = contact.typed() { - typed_contact.uri + if let Ok(typed_contact) = contact.typed() { + Ok(typed_contact.uri) } else { let mut uri = extract_uri_from_contact(contact.value())?; uri.headers.clear(); - uri - }; + Ok(uri) + } + } + + fn remote_uri(&self, destination: Option<&SipAddr>) -> Result { + // update remote uri + let mut contact_uri = self.contact_uri()?; for param in contact_uri.params.iter() { if let rsip::Param::Other(name, _) = param {