Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/dialog/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,7 @@ impl DialogInner {

Response {
status_code: status,
wire_reason: None,
headers: resp_headers,
body: body.unwrap_or_default(),
version: *request.version(),
Expand Down
2 changes: 2 additions & 0 deletions src/dialog/tests/test_authenticate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ fn create_request_with_branch(branch: &str) -> Request {
fn create_401_response() -> Response {
Response {
status_code: StatusCode::Unauthorized,
wire_reason: None,
version: crate::sip::Version::V2,
headers: vec![
Via::new("SIP/2.0/UDP alice.example.com:5060;branch=z9hG4bKnashds").into(),
Expand Down Expand Up @@ -161,6 +162,7 @@ async fn test_authenticate_via_header_branch_update() -> crate::Result<()> {
fn create_407_response() -> Response {
Response {
status_code: StatusCode::ProxyAuthenticationRequired,
wire_reason: None,
version: crate::sip::Version::V2,
headers: vec![
Via::new("SIP/2.0/UDP alice.example.com:5060;branch=z9hG4bKnashds").into(),
Expand Down
4 changes: 4 additions & 0 deletions src/dialog/tests/test_client_dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ async fn test_client_dialog_state_transitions() -> crate::Result<()> {
// Transition to Early (after receiving 1xx)
let ringing_resp = Response {
status_code: StatusCode::Ringing,
wire_reason: None,
version: crate::sip::Version::V2,
headers: vec![
Via::new("SIP/2.0/UDP alice.example.com:5060;branch=z9hG4bKnashds").into(),
Expand Down Expand Up @@ -422,6 +423,7 @@ async fn test_route_set_updates_from_200_ok_response() -> crate::Result<()> {

let success_resp = Response {
status_code: StatusCode::OK,
wire_reason: None,
version: crate::sip::Version::V2,
headers: headers.into(),
body: vec![],
Expand Down Expand Up @@ -517,6 +519,7 @@ async fn test_confirmed_dialog_bye_keeps_contact_uri_with_outbound_route() -> cr

let success_resp = Response {
status_code: StatusCode::OK,
wire_reason: None,
version: crate::sip::Version::V2,
headers: headers.into(),
body: vec![],
Expand Down Expand Up @@ -1038,6 +1041,7 @@ async fn test_ack_sent_to_websocket_channel_via_locator() -> crate::Result<()> {

let ok_response = Response {
status_code: StatusCode::OK,
wire_reason: None,
version: crate::sip::Version::V2,
headers: vec![
invite_req.via_header()?.clone().into(),
Expand Down
1 change: 1 addition & 0 deletions src/dialog/tests/test_connection_affinity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ async fn test_server_dialog_bye_is_delivered_over_initial_connection() {
// ── complete the handshake: the browser answers 200 OK for our BYE ─────
let ok_response = Response {
status_code: StatusCode::OK,
wire_reason: None,
version: Version::V2,
headers: vec![
bye_req.via_header().unwrap().clone().into(),
Expand Down
1 change: 1 addition & 0 deletions src/dialog/tests/test_dialog_states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ fn create_response(status: StatusCode, from_tag: &str, to_tag: &str, call_id: &s

Response {
status_code: status,
wire_reason: None,
version: crate::sip::Version::V2,
headers: vec![
Via::new("SIP/2.0/UDP alice.example.com:5060;branch=z9hG4bKnashds").into(),
Expand Down
5 changes: 5 additions & 0 deletions src/dialog/tests/test_session_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ async fn client_learns_remote_uuid_from_response() -> crate::Result<()> {
let resp_header = make_header("Session-ID", format!("{};remote={}", UUID_B, UUID_A));
let mut resp = crate::sip::Response {
status_code: StatusCode::OK,
wire_reason: None,
version: crate::sip::Version::V2,
headers: Default::default(),
body: vec![],
Expand Down Expand Up @@ -268,6 +269,7 @@ async fn make_ack_swaps_remote_uuid_from_response() -> crate::Result<()> {

let resp = crate::sip::Response {
status_code: StatusCode::OK,
wire_reason: None,
version: crate::sip::Version::V2,
headers: vec![
make_header("Session-ID", format!("{};remote={}", UUID_B, UUID_A)),
Expand Down Expand Up @@ -298,6 +300,7 @@ async fn make_ack_without_session_id_untouched() -> crate::Result<()> {
let invite = create_invite_request("alice-tag", "", "ack-test");
let resp = crate::sip::Response {
status_code: StatusCode::OK,
wire_reason: None,
version: crate::sip::Version::V2,
headers: vec![crate::sip::headers::Contact::new("<sip:bob@bob.example.com:5060>").into()]
.into(),
Expand All @@ -317,6 +320,7 @@ async fn session_id_survives_snapshot_restore() -> crate::Result<()> {
let peer = make_header("Session-ID", format!("{};remote={}", UUID_B, UUID_A));
let mut resp = crate::sip::Response {
status_code: StatusCode::OK,
wire_reason: None,
version: crate::sip::Version::V2,
headers: Default::default(),
body: vec![],
Expand Down Expand Up @@ -591,6 +595,7 @@ fn ok_response_with(headers: Vec<Header>) -> crate::sip::Response {
headers.push(crate::sip::headers::Contact::new("<sip:bob@bob.example.com:5060>").into());
crate::sip::Response {
status_code: StatusCode::OK,
wire_reason: None,
version: crate::sip::Version::V2,
headers: headers.into(),
body: vec![],
Expand Down
8 changes: 8 additions & 0 deletions src/sip/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,13 @@ impl core::convert::From<Request> for Vec<u8> {
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Response {
pub status_code: StatusCode,
/// The reason phrase a parsed Status-Line carried (trimmed) when it
/// differs from the standard text for its code (e.g. `403 Caller
/// Origination Number is Invalid`); `None` for the standard phrase, an
/// unknown code (kept in [`StatusCode::Other`]) and every locally built
/// response. Never serialized: `Display` and `to_bytes` still write the
/// standard phrase.
pub wire_reason: Option<String>,
pub version: Version,
pub headers: Headers,
pub body: Vec<u8>,
Expand Down Expand Up @@ -587,6 +594,7 @@ impl Default for Response {
fn default() -> Self {
Response {
status_code: StatusCode::OK,
wire_reason: None,
version: Version::V2,
headers: Headers::default(),
body: Vec::new(),
Expand Down
55 changes: 55 additions & 0 deletions src/sip/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,29 @@ fn parse_response_line(line: &str, headers: Headers, body: Vec<u8>) -> Result<Si
.parse()
.map_err(|_| Error::ParseError(format!("Status-Line: invalid code {:?}", code_str)))?;
let status_code = StatusCode::try_from((code, reason))?;
let wire_reason = wire_reason(&status_code, reason);

Ok(SipMessage::Response(Response {
status_code,
wire_reason,
version,
headers,
body,
}))
}

/// The Status-Line phrase when it is not the standard text for a known code.
/// An unknown code already keeps its phrase in [`StatusCode::Other`].
fn wire_reason(status_code: &StatusCode, reason: &str) -> Option<String> {
if reason.is_empty()
|| matches!(status_code, StatusCode::Other(..))
|| reason.eq_ignore_ascii_case(status_code.text())
{
return None;
}
Some(reason.to_string())
}

fn find_double_crlf(data: &[u8]) -> Option<usize> {
memmem::find(data, b"\r\n\r\n")
}
Expand Down Expand Up @@ -195,4 +209,45 @@ mod tests {
SipMessage::Response(_) => panic!("expected request"),
}
}

fn response(status_line: &str) -> crate::sip::Response {
let raw = format!(
"{status_line}\r\nVia: SIP/2.0/UDP a.example:5060;branch=z9hG4bK-1\r\n\
From: <sip:a@a.example>;tag=1\r\nTo: <sip:b@b.example>;tag=2\r\n\
Call-ID: c1\r\nCSeq: 1 INVITE\r\nContent-Length: 0\r\n\r\n"
);
match SipMessage::try_from(raw.as_str()).unwrap() {
SipMessage::Response(r) => r,
SipMessage::Request(_) => panic!("expected response"),
}
}

#[test]
fn a_custom_reason_phrase_is_kept_beside_the_standard_code() {
let r = response("SIP/2.0 403 Caller Origination Number is Invalid");
assert_eq!(r.status_code, crate::sip::StatusCode::Forbidden);
assert_eq!(
r.wire_reason.as_deref(),
Some("Caller Origination Number is Invalid")
);
// The wire form is unchanged: the standard phrase is still written.
assert!(r.to_string().starts_with("SIP/2.0 403 Forbidden\r\n"));
assert!(r.to_bytes().starts_with(b"SIP/2.0 403 Forbidden\r\n"));
// Surrounding whitespace is trimmed.
let r = response("SIP/2.0 503 \tOverloaded Try Later ");
assert_eq!(r.wire_reason.as_deref(), Some("Overloaded Try Later"));
}

#[test]
fn the_standard_phrase_or_none_keeps_no_wire_reason() {
assert_eq!(response("SIP/2.0 403 Forbidden").wire_reason, None);
assert_eq!(response("SIP/2.0 486 busy here").wire_reason, None);
assert_eq!(response("SIP/2.0 404").wire_reason, None);
assert_eq!(response("SIP/2.0 404 ").wire_reason, None);
// An unknown code keeps its phrase in StatusCode::Other instead.
let r = response("SIP/2.0 499 Odd Thing");
assert_eq!(r.wire_reason, None);
assert_eq!(r.status_code.text(), "Odd Thing");
assert_eq!(crate::sip::Response::default().wire_reason, None);
}
}
1 change: 1 addition & 0 deletions src/transaction/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ fn test_transaction_key() -> Result<()> {
);
let register_resp = Response {
status_code: StatusCode::OK,
wire_reason: None,
version: Version::V2,
headers: vec![
Via::new("SIP/2.0/TLS client.sip.restsend.com:5061;branch=z9hG4bKnashd92").into(),
Expand Down
1 change: 1 addition & 0 deletions src/transaction/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ impl EndpointInner {
headers.unique_push(Header::UserAgent(self.user_agent.clone().into()));
Response {
status_code,
wire_reason: None,
version: *req.version(),
headers,
body: body.unwrap_or_default(),
Expand Down
2 changes: 2 additions & 0 deletions src/transaction/tests/test_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ async fn test_client_transaction() -> Result<()> {
let response = SipMessage::Response(crate::sip::message::Response {
version: crate::sip::Version::V2,
status_code:crate::sip::StatusCode::Trying,
wire_reason: None,
headers: headers.clone(),
body: Default::default(),
});
Expand All @@ -61,6 +62,7 @@ async fn test_client_transaction() -> Result<()> {
let response = SipMessage::Response(crate::sip::message::Response {
version: crate::sip::Version::V2,
status_code:crate::sip::StatusCode::OK,
wire_reason: None,
headers,
body: Default::default(),
});
Expand Down
2 changes: 2 additions & 0 deletions src/transaction/tests/test_provisional_responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async fn test_multiple_provisional_responses() -> crate::Result<()> {
let resp1 = Response {
version: crate::sip::Version::V2,
status_code: StatusCode::SessionProgress, // 183
wire_reason: None,
headers: vec![
Via::new("SIP/2.0/UDP test.example.com:5060;branch=z9hG4bKnashds").into(),
CSeq::new("1 INVITE").into(),
Expand Down Expand Up @@ -73,6 +74,7 @@ async fn test_multiple_provisional_responses() -> crate::Result<()> {
let resp2 = Response {
version: crate::sip::Version::V2,
status_code: StatusCode::SessionProgress, // 183
wire_reason: None,
headers: vec![
Via::new("SIP/2.0/UDP test.example.com:5060;branch=z9hG4bKnashds").into(),
CSeq::new("1 INVITE").into(),
Expand Down
4 changes: 4 additions & 0 deletions src/transaction/tests/test_server_invite_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ async fn test_cleanup_server_invite_completed_keeps_waiting_ack() -> crate::Resu
// Manually set the fields that would be set during respond() + transition(Completed)
let resp = crate::sip::Response {
status_code: StatusCode::ServiceUnavailable,
wire_reason: None,
version: Version::V2,
headers: invite.headers.clone(),
body: Default::default(),
Expand Down Expand Up @@ -158,6 +159,7 @@ async fn test_cleanup_server_invite_terminated_removes_waiting_ack() -> crate::R

let resp = crate::sip::Response {
status_code: StatusCode::BusyHere,
wire_reason: None,
version: Version::V2,
headers: invite.headers.clone(),
body: Default::default(),
Expand Down Expand Up @@ -638,6 +640,7 @@ async fn test_cleanup_server_invite_confirmed_drop_removes_waiting_ack() -> crat

let resp = crate::sip::Response {
status_code: StatusCode::ServiceUnavailable,
wire_reason: None,
version: Version::V2,
headers: invite.headers.clone(),
body: Default::default(),
Expand Down Expand Up @@ -699,6 +702,7 @@ async fn test_timer_cleanup_removes_orphaned_waiting_ack() -> crate::Result<()>

let resp = crate::sip::Response {
status_code: StatusCode::ServiceUnavailable,
wire_reason: None,
version: Version::V2,
headers: invite.headers.clone(),
body: Default::default(),
Expand Down
1 change: 1 addition & 0 deletions src/transport/tests/test_via_received.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ fn test_via_received_ws() {
fn test_via_response_not_modified() {
let response = crate::sip::message::Response {
status_code: crate::sip::StatusCode::try_from(200).unwrap(),
wire_reason: None,
headers: vec![Via::new("SIP/2.0/UDP 127.0.0.1:5060;branch=z9hG4bK-test").into()].into(),
version: crate::sip::Version::V2,
body: Default::default(),
Expand Down
Loading