Skip to content

Commit 62c4b19

Browse files
committed
fix: #44 issue, bump version to 0.2.92 and implement route set update from 200 OK response
1 parent fc5a51c commit 62c4b19

File tree

4 files changed

+136
-1
lines changed

4 files changed

+136
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rsipstack"
3-
version = "0.2.91"
3+
version = "0.2.92"
44
edition = "2021"
55
description = "SIP Stack Rust library for building SIP applications"
66
license = "MIT"

src/dialog/client_dialog.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ impl ClientInviteDialog {
541541
}
542542
match resp.status_code {
543543
StatusCode::OK => {
544+
self.inner.update_route_set_from_response(&resp);
544545
// 200 response to INVITE always contains Contact header
545546
let contact = resp.contact_header()?;
546547
self.inner

src/dialog/dialog.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,34 @@ impl DialogInner {
479479
*self.remote_contact.lock().unwrap() = contact;
480480
}
481481

482+
/// Update the stored route set from Record-Route headers present in a response.
483+
///
484+
/// Client dialogs learn their route set from the 2xx response that establishes
485+
/// the dialog (RFC 3261 §12.1.2). Persisting it here ensures all subsequent
486+
/// in-dialog requests reuse the same proxy chain instead of targeting the
487+
/// remote contact directly.
488+
pub(crate) fn update_route_set_from_response(&self, resp: &Response) {
489+
if !matches!(self.role, TransactionRole::Client) {
490+
return;
491+
}
492+
493+
let mut new_route_set: Vec<Route> = resp
494+
.headers()
495+
.iter()
496+
.filter_map(|header| match header {
497+
Header::RecordRoute(rr) => Some(Route::from(rr.value())),
498+
_ => None,
499+
})
500+
.collect();
501+
502+
if new_route_set.is_empty() {
503+
return;
504+
}
505+
506+
new_route_set.reverse();
507+
*self.route_set.lock().unwrap() = new_route_set;
508+
}
509+
482510
pub(super) fn build_vias_from_request(&self) -> Result<Vec<Via>> {
483511
let mut vias = vec![];
484512
for header in self.initial_request.headers.iter() {

src/dialog/tests/test_client_dialog.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,3 +369,109 @@ async fn test_make_request_preserves_remote_target_and_route_order() -> crate::R
369369

370370
Ok(())
371371
}
372+
373+
#[tokio::test]
374+
async fn test_route_set_updates_from_200_ok_response() -> crate::Result<()> {
375+
let endpoint = create_test_endpoint().await?;
376+
let (state_sender, _) = unbounded_channel();
377+
378+
let dialog_id = DialogId {
379+
call_id: "route-update-call".to_string(),
380+
from_tag: "from-tag".to_string(),
381+
to_tag: "".to_string(),
382+
};
383+
384+
let invite_req = create_invite_request("from-tag", "", "route-update-call");
385+
let (tu_sender, _tu_receiver) = unbounded_channel();
386+
387+
let dialog_inner = DialogInner::new(
388+
TransactionRole::Client,
389+
dialog_id,
390+
invite_req,
391+
endpoint.inner.clone(),
392+
state_sender,
393+
None,
394+
Some(Uri::try_from("sip:alice@alice.example.com:5060")?),
395+
tu_sender,
396+
)?;
397+
398+
let client_dialog = ClientInviteDialog {
399+
inner: Arc::new(dialog_inner),
400+
};
401+
402+
let remote_target = Uri::try_from("sip:uas@192.0.2.55:5088;transport=tcp")?;
403+
client_dialog
404+
.inner
405+
.set_remote_target(remote_target.clone(), None);
406+
407+
let mut headers: Vec<Header> = vec![
408+
Via::new("SIP/2.0/TCP proxy.example.com:5060;branch=z9hG4bKproxy").into(),
409+
CSeq::new("1 INVITE").into(),
410+
From::new("Alice <sip:alice@example.com>;tag=from-tag").into(),
411+
To::new("Bob <sip:bob@example.com>;tag=bob-tag").into(),
412+
CallId::new("route-update-call").into(),
413+
Header::RecordRoute(RecordRoute::new(
414+
"<sip:edge1.example.net:5070;transport=tcp;lr>",
415+
)),
416+
Header::RecordRoute(RecordRoute::new(
417+
"<sip:edge2.example.net:5080;transport=tcp;lr>",
418+
)),
419+
];
420+
headers.push(ContentLength::new("0").into());
421+
422+
let success_resp = Response {
423+
status_code: StatusCode::OK,
424+
version: rsip::Version::V2,
425+
headers: headers.into(),
426+
body: vec![],
427+
};
428+
429+
client_dialog
430+
.inner
431+
.update_route_set_from_response(&success_resp);
432+
433+
let outbound_addr =
434+
SipAddr::try_from(&Uri::try_from("sip:uac.example.com:5060;transport=tcp")?)?;
435+
let bye_request = client_dialog.inner.make_request(
436+
rsip::Method::Bye,
437+
None,
438+
Some(outbound_addr),
439+
None,
440+
None,
441+
None,
442+
)?;
443+
444+
let routes: Vec<String> = bye_request
445+
.headers
446+
.iter()
447+
.filter_map(|header| match header {
448+
Header::Route(route) => Some(route.value().to_string()),
449+
_ => None,
450+
})
451+
.collect();
452+
453+
assert_eq!(
454+
routes,
455+
vec![
456+
"<sip:edge2.example.net:5080;transport=tcp;lr>".to_string(),
457+
"<sip:edge1.example.net:5070;transport=tcp;lr>".to_string(),
458+
],
459+
"Route set must be reversed compared to the Record-Route header order",
460+
);
461+
462+
let destination = destination_from_request(&bye_request)
463+
.expect("route-enabled request should resolve to a destination");
464+
let expected_destination =
465+
SipAddr::try_from(&Uri::try_from("sip:edge2.example.net:5080;transport=tcp")?)?;
466+
assert_eq!(
467+
destination, expected_destination,
468+
"First Route entry must determine the transport destination",
469+
);
470+
471+
assert_eq!(
472+
bye_request.uri, remote_target,
473+
"Record-Route application must not change the remote target",
474+
);
475+
476+
Ok(())
477+
}

0 commit comments

Comments
 (0)