Skip to content

feat(sip): keep a Status-Line's custom reason phrase as Response::wire_reason - #191

Merged
yeoleobun merged 1 commit into
restsend:mainfrom
tgeorge06:feat/keep-status-line-reason-phrase
Oct 8, 2026
Merged

yeoleobun merged 1 commit into
restsend:mainfrom
tgeorge06:feat/keep-status-line-reason-phrase

Conversation

@tgeorge06

Copy link
Copy Markdown
Contributor

Fixes #190.

Problem

parse_response_line (src/sip/parser.rs:103) passes the Status-Line's reason phrase to StatusCode::try_from((code, reason)), which keeps it only for an unknown code (StatusCode::Other, src/sip/status_code.rs:330). For a known code the phrase is dropped, so SIP/2.0 403 Caller Origination Number is Invalid becomes a Response with StatusCode::Forbidden and nothing else. Far ends often explain a refusal only in that phrase (RFC 3261 §7.2, §21: the phrase is a textual description, the listed ones are only recommendations), and an application has no way to read it: Response::reason_phrase() returns the Reason header, and the MessageInspector hooks see the already parsed message.

Change

A new public field on Response:

/// The reason phrase a parsed Status-Line carried (trimmed) when it
/// differs from the standard text for its code (...); `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>,

parse_response_line sets it; Default, make_response and the dialog's response builder set None. The rest of the diff is wire_reason: None in the existing Response { .. } literals (2 in src, 23 in tests). About 25 lines of library code; the rest is tests.

Contract

Status-Line status_code wire_reason
403 Caller Origination Number is Invalid Forbidden Some("Caller Origination Number is Invalid")
503 \tOverloaded Try Later ServiceUnavailable Some("Overloaded Try Later") (trimmed)
403 Forbidden, 486 busy here (standard text, any case) as before None
404, 404 (no phrase) as before None
499 Odd Thing (unknown code) Other(499, "Odd Thing") as before None
a locally built response, Response::default() as before None

Unchanged on purpose:

  • What goes on the wire. Display and to_bytes still write the standard text, so a response that is forwarded or rebuilt from a received one sends exactly what it sent before.
  • StatusCode and its equality: Forbidden is still Forbidden whatever the phrase.
  • No allocation for the common case (the standard phrase).

Points to note:

  • Every transport (UDP, TCP/TLS stream codec, WebSocket) and every TryFrom for SipMessage / Response goes through parse_response_line, so the field is set the same way for all of them.
  • Response derives PartialEq, so two parsed responses that differ only in a custom phrase now compare unequal. Nothing in the library compares whole responses (retransmission matching compares status and body).
  • Adding a public field breaks downstream Response { .. } literals that do not use ..Default::default(), as adding Credential::auth_username did. A private field with an accessor would break every outside literal, so a public field is the smaller change.
  • The name: reason_phrase is taken by the method that reads the Reason header, so this uses wire_reason. Happy to rename it (for example status_line_reason).

Tests

src/sip/parser.rs:

  • a_custom_reason_phrase_is_kept_beside_the_standard_code: the 403 above keeps Forbidden and the phrase, to_string() and to_bytes() still start with SIP/2.0 403 Forbidden, and surrounding spaces and tabs are trimmed.
  • the_standard_phrase_or_none_keeps_no_wire_reason: standard text, a case variant, no phrase, an empty phrase, an unknown code (its phrase still in StatusCode::Other) and Response::default() give None.

On main (2dfa0d1) the phrase is not in the parsed message at all; a probe asserting it appears in the Debug or Display output fails:

---- sip::parser::red_probe::the_phrase_survives_somewhere_in_the_parsed_response stdout ----
phrase lost: Response(Response { status_code: Forbidden, version: V2, headers: Headers([...

Checks

  • cargo test --features bench: 412 lib tests passed, 0 failed (410 on main plus the 2 new ones), and 65 doc tests passed. Plain cargo test passes too.
  • cargo check --no-default-features --features platform-embassy: the same output as on main.
  • cargo clippy --features bench --all-targets: the same output as on main, none in the changed code. (On main it stops at a clippy::never_loop error, unrelated to this PR; with -A clippy::never_loop the warnings are the same as on main.)
  • rustfmt --check on the changed files: clean.

…e_reason

A response such as "SIP/2.0 403 Caller Origination Number is Invalid"
parsed to StatusCode::Forbidden and the phrase was dropped: it was not
kept anywhere in the parsed Response. Parsed responses now keep a phrase
that differs from the standard text of a known code in the new
Response::wire_reason field.

The standard phrase (compared case-insensitively), an empty phrase, an
unknown code (StatusCode::Other already keeps its phrase) and every
locally built response leave it None. Display and to_bytes still write
the standard phrase, so nothing changes on the wire.
@yeoleobun
yeoleobun merged commit b47f434 into restsend:main Oct 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A Status-Line's custom reason phrase is dropped for every known status code

2 participants