Repository navigation
fix(transaction): pass a reliable provisional with a new RSeq to the TU - #195
Merged
yeoleobun merged 1 commit intoOct 10, 2026
Merged
Conversation
A second reliable 183 (RFC 3262) with a new RSeq and the same body as the previous one was dropped as a retransmission, so the TU never sent its PRACK; the UAS retransmitted it for 64*T1 and then failed the INVITE. The duplicate check now also compares RSeq. An exact retransmission keeps its RSeq and is still ignored. Fixes restsend#194.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #194.
Thanks to @nbasil for the report. The analysis and the proposed fix in the issue are exact; this PR implements them with the existing typed
rseq_value()accessor and adds the tests the issue describes.Problem
Transaction::on_received_responsedrops a response as a retransmission when the transaction stays in the same state and the previous response has the same status code and body (src/transaction/transaction.rs:1028at currentmain, 1f15ba9). #79 made a second 183 with a different body reach the TU, but a second reliable 183 (RFC 3262) with a newRSeqand the same SDP still matches and is dropped. The dialog never sends its PRACK, the UAS retransmits the 183 for 64*T1 and then fails the INVITE with a 5xx.Expected
RFC 3261 §17.1.1.2:
RFC 3262 §3:
RFC 3262 §4:
So two reliable 183s that differ only in
RSeqare two responses, and both must reach the TU.Fix
The duplicate check also compares
RSeq, through the existingHeadersExt::rseq_value()(src/sip/message.rs:328, the parsedu32the dialog's PRACK code already uses). +7 / -1 lines insrc/transaction/transaction.rs; the rest of the +128 / -2 is tests.Unchanged on purpose:
A response without
RSeqgivesNoneon both sides, so unreliable provisionals, retransmitted 3xx-6xx in Completed and non-INVITE client responses are deduplicated exactly as before.An exact retransmission of a reliable provisional keeps its
RSeqand is still dropped.The Accepted exemption for 2xx (RFC 6026) is untouched.
What to do with the
RSeqstays with the dialog:prepare_prack_request(src/dialog/dialog.rs:606) PRACKs a higherRSeq, re-sends the cached PRACK for the same one and ignores a lower one (lines 626-633), as before.The dialog's own RFC 3262 §4 ordering rule is not changed here. §4 says:
Today
prepare_prack_requestPRACKs any higherRSeq(also one that skips a value), andprocess_invitestill moves toEarlyfor a lower one without a PRACK (src/dialog/client_dialog.rs:755-756,src/dialog/invite_dialog.rs:292-293). With this PR a late copy of an older reliable 183 with the same body as the newer one also reaches the dialog (one moreEarlynotification, no PRACK), as one with a different body already did. A UAS may not send the next reliable provisional before the previous one is PRACKed (§3), so that only happens when a retransmission of the previous one is still in flight. Tightening the dialog to "exactly one higher" is a separate change; happy to send it as a follow-up.Contract / coverage
What the TU of a client INVITE transaction in Proceeding gets, when the new response has the same status and body as the last one:
RSeqRSeqRSeq: nRSeq: n(retransmission)RSeq: nRSeq: n+1RSeqRSeq: 1A different status or body was already passed to the TU and still is.
Tests
src/transaction/tests/test_provisional_responses.rs,test_multiple_provisional_responses: after the existing 183s, a reliable 183 with the same body andRSeq: 1, thenRSeq: 2, each sent twice. EachRSeqreaches the TU once; the copy is ignored. Onmainit fails with183 with RSeq 1 must reach the TU.src/dialog/tests/test_prack.rs,client_dialog_pracks_each_reliable_provisional: a client dialog (DialogLayer::do_invitewithsupport_prack: true, so the INVITE carriesSupported: 100rel) against a raw UDP peer that sends two reliable 183s with the same SDP andRSeq1 and 2. The UAC must send a PRACK withRAck: 1and then one withRAck: 2. Onmainit times out waiting for the second PRACK.Checks
cargo test --features bench: 419 lib tests passed, 0 failed (418 onmainplus the new one), and 65 doc tests passed. Plaincargo testpasses too.cargo check --no-default-features --features platform-embassy: no warnings, as onmain.cargo clippy --features bench --all-targets: the same output as onmain, none in the changed code. (Onmainit stops at aclippy::never_looperror insrc/dialog/tests/test_refer_notify.rs:98, unrelated to this PR; with-A clippy::never_loopthe warnings are the same as onmain.)cargo fmt --check: clean.