feat: reinstate deferred call features on the engine - #42
Closed
wavekat-eason wants to merge 10 commits into
Closed
Conversation
Classify the features dropped by the rsipstack cutover into genuinely -missing engine capabilities (in scope: re-INVITE seam, User-Agent, DTMF INFO, hold/resume, session timers, inbound surfacing + CANCEL + provisional states) versus API-rename-only items (out of scope), and lay out a six-phase, one-commit-per-phase implementation grounded in the existing engine seams. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
Add the engine seam for in-dialog requests carrying a body and extra headers (Dialog::new_request_with, setting Content-Length from the body) and Ua::info, the non-INVITE in-dialog send used for DTMF. Restore dtmf_info as a pure module (build_info_body, content_type_header, InfoOutcome, classify) over the engine instead of rsipstack, and expose Call::send_dtmf_info for the application/dtmf-relay fallback used when the remote does not negotiate RFC 4733 telephone-event. Tested: dialog composer attaches body/headers/Content-Length; dtmf_info unit tests port unchanged; the loopback e2e call now also sends an INFO press and asserts the auto-answered 200 classifies as accepted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
Add Ua::reinvite, the in-dialog re-INVITE path that ACKs the 2xx per RFC 3261 §13.2.2.4 (the existing send_in_dialog never ACKs, so it cannot carry an INVITE transaction). Generalize the SDP builder into build_sdp_with(direction, version): a MediaDirection (sendrecv / sendonly / inactive) plus an o= version that increments on every re-offer per RFC 3264 §5. build_sdp keeps its signature (sendrecv, version 0). Expose Call::set_hold(on) / Call::is_held(): set_hold sends a fresh SDP re-offer via re-INVITE, only flips local hold state on a 2xx, and surfaces the server's reason on a non-2xx final. Tested: SDP direction/version unit tests; a stack::ua loopback test asserts the engine ACKs the re-INVITE 2xx with an advanced CSeq; the loopback e2e call now holds and resumes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
Carry an optional product token on the Ua and inject it as User-Agent at the single client-transaction chokepoint (start_client), so every outbound INVITE / REGISTER / BYE / re-INVITE / INFO advertises it; the header does not affect the transaction key. Restore the public entry point as SipEndpoint::new_with_app(account, product, cancel), with new() delegating with None (byte-identical when unset). Tested: the token rides an outbound request when configured, is not duplicated on re-application, and is absent by default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
Restore the session_timer module (Session-Expires/Min-SE parsing, Supported/Require: timer, UAC/UAS negotiation, the §10 interval math and the refresh/watchdog session_timer_loop with its full paused-clock test suite). The pure logic is unchanged; only the dialog seam is re-pointed off rsipstack onto the engine via the SessionDialogOps trait. Wire negotiation into the call path: Caller::dial advertises Supported: timer + Session-Expires (CallConfig gains extra_headers on the INVITE) and negotiates from the 2xx; IncomingCall::accept negotiates from the INVITE and echoes the agreed Session-Expires (+ Require: timer) in its 200 OK. Call now exposes session_timer() and a cloneable session_handle() (CallSession: SessionDialogOps) so the loop can refresh / BYE on the shared dialog (Arc<Mutex<Dialog>>) alongside the audio path. Tested: the full session_timer unit suite; build_invite carries extra headers; the loopback e2e asserts both sides negotiate the 1800 s timer with the caller as refresher and the callee as watchdog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
Move RFC 4028 (session timers) and RFC 6086 (SIP INFO DTMF) into the implemented sections, record §14 re-INVITE (hold/resume) and §20.41 User-Agent, and re-rank the remaining gaps so the docs/17 inbound surfacing + CANCEL + provisional work leads. Mark phases 1-5 done in the plan doc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
By default the endpoint still auto-answers every in-dialog request 200 OK. A Call can now opt in via Call::inbound_requests() to handle its dialog's peer re-INVITEs (e.g. answer an RFC 4028 session refresh or a peer hold with a fresh SDP answer) and INFOs (e.g. SIP-INFO DTMF) itself: the endpoint keys a dialog registry and routes those requests to an InboundRequests stream of InboundRequest (respond/ok), reverting to auto-answer when the stream is dropped. BYE / OPTIONS stay auto-answered. This completes the RFC 4028 watchdog path (the peer's refresh re-INVITE is now reachable so the consumer can reset the deadline) and enables inbound SIP-INFO DTMF. Tested: a loopback e2e where the caller opts in and answers a callee -initiated INFO and hold re-INVITE — with opt-in the endpoint no longer auto-answers, so the peer's 200 proves the stream delivered both, and the test asserts the received methods. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
Add build_cancel (RFC 3261 §9.1: same top Via branch, Request-URI, From/To/Call-ID, CSeq number with method CANCEL) and an engine call_cancellable path that watches a CancellationToken: once a provisional has arrived, firing the token sends the CANCEL as its own non-INVITE transaction and the INVITE resolves on the 487 Request Terminated. Expose Caller::dial_cancellable(target, cancel); dial() delegates with a token that never fires, so its behavior is unchanged. Tested: build_cancel reuses the INVITE branch + CSeq number; a stack::ua loopback test rings, cancels, observes the engine send CANCEL, replies 487, and asserts the call resolves Rejected(487) with the engine ACKing the 487. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
Forward each provisional response status from the in-flight INVITE to an optional progress channel and expose Caller::dial_with_progress(target, cancel, progress), so a consumer can drive a "ringing" UI. dial / dial_cancellable pass no channel (unchanged). Tested: the cancel-while-ringing loopback now also asserts the 180 Ringing arrives on the progress channel before the CANCEL/487. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
Record CANCEL (§9), provisional observation, and opt-in inbound in-dialog surfacing as implemented; note the RFC 4028 watchdog path is now complete; drop the closed items from the known-gaps ranking. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJyg8i9YjsgdEMp77PvRyq
Contributor
Author
|
Folded into #43, which now targets |
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.
Reinstates the call features the rsipstack cutover (#41) deferred, rebuilt on the in-house engine. Stacked on
feat/drop-rsipstack(#41) — review/merge that first; this PR's diff is just the reinstatement work (10 commits).Implements
docs/17, which first classifies every deferred item as an in-scope missing capability vs an out-of-scope API-rename only (the latter —Callee/PendingCall→IncomingCall,PendingDial→dial()->Call, the oldDialogState/TransactionHandlenames — are deliberately left for the consumer to adapt to).Capabilities restored
Call::send_dtmf_info,dtmf_info::{build_info_body, content_type_header, InfoOutcome}Call::set_hold/is_held,build_sdp_with,MediaDirectionSipEndpoint::new_with_appCall::session_timer/session_handle,session_timer_loop,negotiate_uac/negotiate_uas, …Call::inbound_requests→InboundRequests/InboundRequestCaller::dial_cancellableCaller::dial_with_progressFoundational engine seams added:
Ua::reinvite(ACKs the 2xx per §13.2.2.4),Ua::info,Dialog::new_request_with,build_cancel, a per-endpoint dialog registry.Notes
Call::inbound_requests(), so the auto-answer-200default is preserved and nothing changes for callers that don't opt in. This also completes the RFC 4028 watchdog path (the peer's refresh re-INVITE is now reachable).rsipstackstays absent.Tests & gates
201 unit tests + 2 loopback e2e + doctests. Every commit is green on
fmt,clippy -D warnings,test, anddoc. The public e2e exercises INFO, hold/resume, both-sides timer negotiation, opt-in surfacing, and cancel-while-ringing.🤖 Generated with Claude Code