Skip to content

fix(dialog): end the session on a never-ACKed re-INVITE 2xx in ClientInviteDialog - #185

Merged
shenjinti merged 1 commit into
restsend:mainfrom
tgeorge06:fix/legacy-client-dialog-unacked-reinvite
Oct 8, 2026
Merged

shenjinti merged 1 commit into
restsend:mainfrom
tgeorge06:fix/legacy-client-dialog-unacked-reinvite

Conversation

@tgeorge06

Copy link
Copy Markdown
Contributor

Fixes #184.

Problem

#149 added the never-ACKed 2xx teardown to InviteDialog::handle_reinvite and ServerInviteDialog::handle_reinvite, and #169 added the acked guard to both, but the deprecated ClientInviteDialog::handle_reinvite (src/dialog/client_dialog.rs:664-684) has its own copy and got neither. A UAC dialog handled through ClientInviteDialog::handle whose re-INVITE 2xx is never ACKed retransmits the 2xx until 64*T1 and then stays Confirmed, with no Terminated and no BYE.

Spec

  • RFC 3261 §13.3.1.4: "If the server retransmits the 2xx response for 64*T1 seconds without receiving an ACK, the dialog is confirmed, but the session SHOULD be terminated. This is accomplished with a BYE, as described in Section 15."

Fix

ClientInviteDialog::handle_reinvite now does what the other two handlers do (invite_dialog.rs:899-923, server_dialog.rs:852-876): after the application's final response it records whether it was a 2xx, sets acked on the matching ACK, and calls self.inner.end_session_without_ack(tx, answered_2xx && !acked). The helper is the existing one from #149: it does nothing unless a 2xx was sent, the transaction ended and the dialog is not already terminated; otherwise it notifies Terminated(Timeout) and sends a BYE. +9 lines in src/dialog/client_dialog.rs, no other src change.

Unchanged on purpose:

  • A non-2xx final answer, or a handle dropped without an answer, sends no BYE (answered_2xx is false), as in the other two handlers.
  • An ACKed re-INVITE leaves the session up.
  • Unlike InviteDialog and ServerInviteDialog, the wrapper does not notify Confirmed when the re-INVITE's ACK arrives (its stored state stays Confirmed, since Updated is event-only). That predates this teardown and is not changed here.

Contract / coverage

What a re-INVITE from the peer, handled through ClientInviteDialog::handle, ends in:

application answer peer ACK before after
2xx ACKed session up, no BYE same
2xx never 2xx retransmitted until 64*T1, then stays Confirmed, no BYE Terminated(Timeout) + BYE after 64*T1, as InviteDialog
non-2xx, or handle dropped n/a no BYE same

Tests

src/dialog/tests/test_uas_ack_timeout.rs: a helper legacy_client_dialog_reinvite(ack) sets up a UAC dialog with DialogLayer::do_invite against a raw UDP peer (the file's short timers: T1 20 ms, 64*T1 1.28 s), with an incoming loop that converts each matched dialog with ClientInviteDialog::try_from and calls its handle. The peer sends a re-INVITE, the application answers 200, and the peer either never ACKs or ACKs once the 2xx arrived.

  • test_unacked_reinvite_2xx_ends_the_session_on_a_legacy_client_dialog: the 2xx is retransmitted, a BYE from the dialog's local tag comes no earlier than 64*T1 - T1 after the answer, and the dialog notifies Terminated(Timeout).
  • test_acked_reinvite_keeps_a_legacy_client_dialog: no BYE, no Terminated, the dialog is still confirmed.

On main (92cec74) the first fails and the second passes:

test dialog::tests::test_uas_ack_timeout::test_acked_reinvite_keeps_a_legacy_client_dialog ... ok
test dialog::tests::test_uas_ack_timeout::test_unacked_reinvite_2xx_ends_the_session_on_a_legacy_client_dialog ... FAILED
panicked at src/dialog/tests/test_uas_ack_timeout.rs:194:10:
the UAS must send a BYE when the ACK never arrives

Checks

  • cargo test --features bench: 408 lib tests passed, 0 failed (406 on main plus the two new ones), and 65 doc tests passed. Plain cargo test passes too. The new tests passed 5 runs in a row.
  • cargo check --no-default-features --features platform-embassy: no warnings, the same 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 in src/dialog/tests/test_refer_notify.rs:98, unrelated to this PR; with -A clippy::never_loop the warnings are the same as on main.)
  • cargo fmt --check: clean.

…InviteDialog

The deprecated ClientInviteDialog has its own handle_reinvite, which
missed the teardown that InviteDialog and ServerInviteDialog apply
(RFC 3261 13.3.1.4): when the callee re-INVITEs a UAC dialog handled
through the wrapper and never ACKs the 2xx, the 2xx was retransmitted
until 64*T1 and then nothing happened. The dialog stayed Confirmed,
with no event and no BYE.

Track the 2xx and the ACK as the other two handlers do and call
end_session_without_ack, so the dialog ends with
TerminatedReason::Timeout and a BYE.
@shenjinti
shenjinti merged commit 7978f2c 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.

Deprecated ClientInviteDialog: a re-INVITE 2xx that is never ACKed does not end the session (follow-up to #149)

2 participants