Repository navigation
fix(transaction): Timer F ends a non-INVITE client transaction in Proceeding - #189
Merged
Merged
Conversation
…ceeding A non-INVITE client transaction runs Timer F as Timer B, and on_timer handled Timer B only in Calling and Trying. After a provisional other than 100 (e.g. 180 to a BYE) the transaction is in Proceeding, Timer F was ignored there, and a request answered with one 1xx and then nothing never ended. RFC 3261 section 17.1.2.2: if Timer F fires in Proceeding, the TU must be informed of a timeout and the transaction terminated. Handle Timer B for a non-INVITE client in Proceeding like Timer C: a local 408 to the TU, which terminates the transaction.
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 #188.
Problem
A non-INVITE client transaction runs Timer F as
TransactionTimer::TimerB(armed inCalling,transaction.rs:1284).on_timerhandles Timer B only in theCalling | Tryingarm (line 1153). After a provisional other than 100 the transaction is inProceeding, whose arm (line 1163) handles only Timer C, so Timer F is ignored: no 408 reaches the TU, the transaction never terminates, and a BYE answered with one 1xx and then nothing hangs.RFC 3261 §17.1.2.2:
Fix
In the
Proceedingarm, handle Timer B for a non-INVITE client the same way as Timer C: a local 408 to the TU throughinform_tu_response, which terminates the transaction like any final response. +6 / -1 lines insrc/transaction/transaction.rs.Unchanged on purpose:
Calling(line 1294), and a Timer B still in flight is ignored inProceeding, as before (RFC 3261 §17.1.1.2: "If the client transaction is still in the "Calling" state when timer B fires, the client transaction SHOULD inform the TU that a timeout has occurred."). Timer C is handled as before.Calling | Tryingarm are untouched; a non-INVITE that got a 100 already timed out there.TryingorProceeding(line 1291), so a non-INVITE over UDP is not retransmitted after any 1xx, and before one its interval doubles up to 64*T1 rather than T2 (line 1147). That is a separate change and is left for a follow-up.Contract / coverage
What the TU of a non-INVITE client transaction gets when the only response is a provisional:
The 408 is the same locally generated response the
Tryingarm already sends. Callers that already handle that 408 now get it in this case too:send_byeends the dialog on it (dialog.rs:1335), other in-dialog requests and REGISTER return it as a final response.Tests
src/transaction/tests/test_client.rs:test_non_invite_timer_f_after_provisional. T1 = 20 ms; a raw peer answers a BYE with one provisional and then stays silent (over TCP it keeps the connection open). For each row, the TU must see[provisional, 408]andreceive()must returnNone(terminated) within 3 * 64*T1:Tryingarm);On
main(92cec74) it fails:Row by row (assertion turned into a print),
mainthen this PR:Checks
cargo test --features bench: 407 lib tests passed, 0 failed (406 onmainplus the new one), and 65 doc tests passed. Plaincargo testpasses too. The new test passed 20 runs in a row.cargo check --no-default-features --features platform-embassy: the same output 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.)rustfmt --checkon the changed files: clean.