Skip to content

fix(cli): M6 CP6 — single-UDP-transport startup guard - #153

Merged
ryanmurf merged 2 commits into
masterfrom
fix/m6-cp6-single-udp-transport-guard
Jul 17, 2026
Merged

fix(cli): M6 CP6 — single-UDP-transport startup guard#153
ryanmurf merged 2 commits into
masterfrom
fix/m6-cp6-single-udp-transport-guard

Conversation

@ryanmurf

Copy link
Copy Markdown
Owner

M6 CP6 — single-UDP-transport startup guard

FINDING. rustisk wires the SIP transport through two passes that DISAGREE if more than one runs:
the channel driver keeps the LAST transport it is handed (set_transport is last-wins) while the
global event handler keeps the FIRST (set_global_event_handler is a first-wins OnceLock). With
two configured UDP transports the startup loop runs twice, so inbound would be handled on the first
socket while outbound is sent on the second — a silent split-brain / race.

FIX. A startup guard, single_udp_transport(&[SocketAddr]) -> Result<SocketAddr, String>,
asserts exactly ONE UDP bind after de-dup and fails startup fast (Err propagated from
startup_sequence) rather than binding two. The transport loop now iterates over the single guarded
bind. Two identical binds (deduped to one) still pass; two DISTINCT binds are rejected with a message
explaining the single-transport rule.

PROOF + RED. Unit tests: accepts exactly one (returns the bind unchanged); rejects two distinct
(the guard's whole purpose); rejects empty. RED — make the guard return Ok(many[0]) for >1:

thread 'tests::single_udp_transport_rejects_two' panicked:
two distinct UDP transports must be rejected: 0.0.0.0:5060

(the guard silently accepts two → the reject-two assertion fails).

Ordering note. CP6 is independent of CP5 (it only touches crates/rustisk-cli/src/main.rs),
landed ahead of CP5 to bank its value; CP5 branches cleanly on top.

GATES. cargo test --workspace --exclude pjsip-shim green; cargo clippy --workspace --exclude pjsip-shim -- -D warnings clean. Rust 1.97.0.

Repin Agent added 2 commits July 17, 2026 04:07
Two transport-wiring passes disagree if more than one runs: the channel driver
keeps the LAST transport (set_transport is last-wins) while the global event
handler keeps the FIRST (set_global_event_handler is a first-wins OnceLock), so
two UDP transports would bind different sockets and race (inbound handled on one,
outbound sent on the other). Add a startup guard, single_udp_transport(), that
asserts exactly one UDP bind and fails startup fast (Err from startup_sequence)
rather than binding two. Two identical binds deduped to one still pass; two
distinct binds are rejected.

Tests: single_udp_transport accepts exactly one, rejects two distinct, rejects
empty. RED: make the guard return Ok for >1 -> the reject-two test fails
(accepts 0.0.0.0:5060).
Extract udp_transport_binds (collect + de-dup) and resolve_single_udp_bind
(binds + guard), and route startup through resolve_single_udp_bind — the single
path startup uses. Config-level tests now exercise that exact entry point: two
DISTINCT udp transports are rejected, two IDENTICAL dedup to one and are
accepted (pinning dedup-before-guard), and no-config falls back to the default.
So the tests fail RED if the guard is removed from the startup path or dedup is
reordered after it, not only if the helper itself is changed.
@ryanmurf

Copy link
Copy Markdown
Owner Author

Codex review disposition (gpt-5.5, xhigh)

Codex verified placement (guard after de-dup, before any SipStack::new/set_transport/set_global_event_handler), cardinality, loop semantics (from_ref preserves the item type + single iteration), fail-fast appropriateness, and UDP+TLS still accepted (only udp binds enter the guarded set). One finding:

  • P2 (test) — FIXED (4ba563b): the tests pinned the helper but not the startup wiring / dedup ordering (removing the production call, or moving dedup after the guard, left them green). Extracted udp_transport_binds (collect + de-dup) and resolve_single_udp_bind (binds + guard), and routed startup through resolve_single_udp_bind — the single path startup uses. New config-level tests exercise that exact entry point: two DISTINCT udp transports rejected, two IDENTICAL dedup-to-one accepted (pinning dedup-before-guard), no-config → default. These fail RED if the guard is removed from the startup path or dedup is reordered.

CI green; workspace test + clippy green locally.

@ryanmurf
ryanmurf merged commit a468686 into master Jul 17, 2026
3 checks passed
@ryanmurf
ryanmurf deleted the fix/m6-cp6-single-udp-transport-guard branch July 17, 2026 10:18
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.

1 participant