Skip to content

Attended transfer Replaces INVITE needs RFC 3891 compliant 481 dispatch #1525

@CraziestPower

Description

@CraziestPower

Problem

PR #1520 fixed a race condition where multiple SIPUserAgent instances sharing a SIPTransport would race to respond to a Replaces INVITE — the non-matching agent would send 400 Bad Request before the matching agent could process it.

The fix in #1520 (having non-matching agents silently ignore Replaces INVITEs) is correct for the race condition, but it leaves a gap: no RFC 3891 Section 3 compliant 481 Call/Transaction Does Not Exist response is ever sent when a Replaces INVITE targets a dialog that genuinely doesn't exist.

Per RFC 3891 Section 3:

If the Replaces header field matches more than one dialog, the UAS MUST act as if no match was found.
If no match is found, the UAS rejects the INVITE and returns a 481 Call/Transaction Does Not Exist response.

With the current multicast delegate architecture (SIPTransportRequestReceived), there's no single point of truth that can determine whether any agent matched the Replaces Call-ID, making it impossible to send the RFC-required 481.

Proposed Solution

Add a dialog owner registry to SIPTransport — a ConcurrentDictionary<string, ISIPDialogOwner> keyed by Call-ID. This follows the same pattern as the existing CANCEL interception via the transaction engine.

Key design points:

  1. New ISIPDialogOwner interface — exposes DialogCallID, DialogLocalTag, DialogRemoteTag, and OnDialogRequestReceived() for direct dispatch
  2. Registry in SIPTransportRegisterDialogOwner() / UnregisterDialogOwner() with 1:1 Call-ID to owner mapping
  3. Dispatch intercept inserted between CANCEL interception and the broadcast, handling:
    • Multiple Replaces headers → 400 Bad Request (RFC 3891)
    • Replaces INVITE with matching dialog → direct dispatch to owner
    • Replaces INVITE with no match or tag mismatch → 481 response
    • In-dialog requests (both tags present, Call-ID in registry) → direct dispatch
    • Everything else → existing broadcast (unchanged)
  4. SIPUserAgent and SIPNotifierClient implement ISIPDialogOwner, register on dialog establishment, unregister on dialog teardown

Benefits over #1520:

  • RFC 3891 compliant 481 responses for non-matching Replaces INVITEs
  • Direct in-dialog request dispatch (avoids O(n) broadcast fan-out)
  • Eliminates the race condition at the transport level rather than working around it at the agent level
  • Clean separation of concerns — transport routes, agents handle

Backwards compatibility:

  • New INVITEs and out-of-dialog requests still broadcast via SIPTransportRequestReceived (unchanged)
  • External API consumers not using ISIPDialogOwner are unaffected
  • The registry is additive — unregistered dialogs fall through to the existing broadcast path

Related: #1459, #1520

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions