transaction

package
v1.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package transaction implements RFC 3261 SIP transaction-layer behavior over UDP.

It sits between stack.Endpoint (wire I/O) and the TU (call logic in uas/gateway/outbound). TCP/TLS uses the same message matching rules; retransmission timers apply only where the transport is unreliable (UDP).

Transaction identity

SIP matches requests and responses by the top Via branch parameter plus dialog headers:

  • INVITE client/server: key = Via.branch + Call-ID
  • Non-INVITE client: key = Via.branch + Call-ID + CSeq number
  • Non-INVITE server: key = Via.branch + Call-ID + method + CSeq number

Example INVITE (client transaction):

INVITE sip:bob@example.com SIP/2.0
Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bK776asdhds
From: Alice <sip:alice@example.com>;tag=1928301774
To: Bob <sip:bob@example.com>
Call-ID: a84b4c76e66710@192.0.2.1
CSeq: 314159 INVITE

Matching 180 Ringing (same Via branch, Call-ID, CSeq method INVITE):

SIP/2.0 180 Ringing
Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bK776asdhds
From: ...
To: ...;tag=a6c85cf
Call-ID: a84b4c76e66710@192.0.2.1
CSeq: 314159 INVITE

UAC (client) flows

INVITE:

result, err := mgr.RunInviteClient(ctx, invite, remote, send, onProvisional)
// Wire mgr.HandleResponse from stack.Endpoint.OnSIPResponse
ack, _ := transaction.BuildAckForInvite(invite, result.Final, transaction.AckRequestURIFor2xx(result.Final, invite.RequestURI))
_ = send(ack, result.Remote)

Non-INVITE (BYE, OPTIONS as UAC, …):

result, err := mgr.RunNonInviteClient(ctx, bye, remote, send)

Timers (UDP):

  • INVITE client: Timer A (retransmit INVITE, exponential to T2) until 1xx or 2xx–6xx
  • Non-INVITE client: Timer E (same pattern, capped at T2)

UAS (server) flows

INVITE before final:

_ = mgr.RegisterPendingInviteServer(invite)   // enables CANCEL matching
// TU sends 100 Trying / 180 / … then final 200/487 via stack.Endpoint.Send
// After final is on the wire: uas.AfterResponseSentBeginServerTx → BeginInviteServer

Duplicate INVITE retransmissions:

mgr.HandleInviteRequest(invite, addr)  // resends stored final (uas.ChainInviteServerTx)

CANCEL (same Call-ID + CSeq number as pending INVITE):

mgr.HandleCancelRequest(cancel, addr, send)  // 200 OK to CANCEL; TU still sends 487 on INVITE

ACK after 2xx:

mgr.HandleAck(ack, addr)  // stops Timer G (2xx retransmissions)

Non-INVITE server (OPTIONS, REGISTER, BYE as UAS):

// After final on wire: BeginNonInviteServer
mgr.HandleNonInviteRequest(req, addr)  // Timer J window: resend final to retransmissions

Server timers (UDP):

  • 2xx INVITE: Timer G (retransmit final until ACK)
  • 3xx–6xx INVITE: Timer I (64×T1 absorb window, resend final on duplicate INVITE)
  • Non-INVITE: Timer J (64×T1)

Integration with uas package

handlers.AttachWithTransaction(ep, uas.TransactionBinding{
    Mgr: mgr, Send: ep.Send, Ctx: srvCtx,
})

This chains HandleInviteRequest, HandleNonInviteRequest, HandleCancelRequest, HandleAck, and registers BeginInviteServer/BeginNonInviteServer on OnResponseSent.

Helpers

  • TopVia / TopBranch / BranchParam — parse routing keys from messages
  • CSeqMethod / IsInviteCSeq / IsAckCSeq / IsCancelCSeq — CSeq inspection
  • BuildAckForInvite / AckRequestURIFor2xx — UAC ACK after INVITE completes
  • RouteHeadersForDialog — Record-Route → Route for in-dialog requests

Known limitations

  • UDP only for retransmission timers (TCP relies on stack/gateway framing).
  • INVITE client onProvisional fires at most once (first 1xx only).
  • No forked INVITE handling (multiple 2xx from parallel branches).
  • No full PRACK / re-INVITE transaction state machines (see session_timer / invite_rfc3262 hooks elsewhere).
  • CANCEL matching uses Call-ID + CSeq number; Via branch is stored but not verified on CANCEL.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AckRequestURIFor2xx

func AckRequestURIFor2xx(resp *stack.Message, inviteRequestURI string) string

AckRequestURIFor2xx prefers Contact from a 2xx response (RFC 3261 dialog establishment).

func BranchParam

func BranchParam(viaLine string) string

BranchParam extracts the branch parameter from one Via field-value (case-insensitive "branch=").

func BuildAckForInvite

func BuildAckForInvite(invite *stack.Message, final *stack.Message, requestURI string) (*stack.Message, error)

BuildAckForInvite builds an ACK for the completed INVITE transaction. For status 200–299, requestURI should usually be AckRequestURIFor2xx(resp, invite.RequestURI). For 300–699, requestURI should be the same as the INVITE Request-URI (RFC 3261 §17.1.1.2).

func CSeqMethod added in v1.4.3

func CSeqMethod(m *stack.Message) string

CSeqMethod returns the method token from a CSeq header (e.g. "314159 INVITE" → "INVITE"). Empty string when the header is missing or malformed.

func InviteTransactionKey

func InviteTransactionKey(branch, callID string) string

InviteTransactionKey is the INVITE transaction map key (top Via branch + Call-ID).

func IsAckCSeq

func IsAckCSeq(m *stack.Message) bool

IsAckCSeq reports whether the CSeq method is ACK.

func IsCancelCSeq

func IsCancelCSeq(m *stack.Message) bool

IsCancelCSeq reports whether the CSeq method is CANCEL.

func IsInviteCSeq

func IsInviteCSeq(m *stack.Message) bool

IsInviteCSeq reports whether the CSeq method is INVITE.

func NonInviteServerKey

func NonInviteServerKey(req *stack.Message) string

NonInviteServerKey builds a stable key for a non-INVITE server transaction (branch + Call-ID + method + CSeq number).

func RouteHeadersForDialog

func RouteHeadersForDialog(resp *stack.Message) []string

RouteHeadersForDialog returns Route header field-values for a subsequent in-dialog request, derived from Record-Route on a dialog-creating 2xx (reverse of Record-Route appearance order).

func SetTransactionTimeoutHook

func SetTransactionTimeoutHook(fn func(method string))

SetTransactionTimeoutHook lets tests intercept timer-B/F firings without touching the metrics registry. Pass nil to reset to the default sipMetrics-backed emitter.

func TopBranch

func TopBranch(m *stack.Message) string

TopBranch returns BranchParam(TopVia(m)).

func TopVia

func TopVia(m *stack.Message) string

TopVia returns the first Via header field-value (top-most in SIP message order).

Types

type InviteClientResult

type InviteClientResult struct {
	Final  *stack.Message
	Remote *net.UDPAddr // UDP source of the last response (use for ACK / subsequent in-dialog routing).
}

InviteClientResult is the outcome of a completed INVITE client transaction over UDP.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager is the central registry for SIP client and server transactions on one UDP socket.

Client side: RunInviteClient / RunNonInviteClient register txs; HandleResponse dispatches inbound responses from stack.Endpoint.OnSIPResponse.

Server side: RegisterPendingInviteServer + HandleCancelRequest; BeginInviteServer / BeginNonInviteServer after finals; HandleInviteRequest / HandleNonInviteRequest / HandleAck for retransmissions and ACK absorption.

func NewManager

func NewManager() *Manager

NewManager creates a manager with RFC default T1 = 500ms and T2 = 4s.

func (*Manager) BeginInviteServer

func (m *Manager) BeginInviteServer(ctx context.Context, invite *stack.Message, remote *net.UDPAddr, final *stack.Message, send SendFunc) error

BeginInviteServer registers a UAS INVITE transaction after the TU has sent a final response (2xx–6xx). For UDP, duplicate INVITE retransmissions must receive the same final (use HandleInviteRequest). For 2xx, Timer G retransmits until HandleAck sees the matching ACK.

func (*Manager) BeginNonInviteServer

func (m *Manager) BeginNonInviteServer(ctx context.Context, req *stack.Message, remote *net.UDPAddr, final *stack.Message, send SendFunc) error

BeginNonInviteServer registers a UAS non-INVITE transaction after the TU sent a final response (e.g. 200 to OPTIONS/REGISTER).

func (*Manager) ClearPendingInviteServer

func (m *Manager) ClearPendingInviteServer(callID string)

ClearPendingInviteServer removes the pending INVITE record for Call-ID.

func (*Manager) HandleAck

func (m *Manager) HandleAck(ack *stack.Message, _ *net.UDPAddr) bool

HandleAck matches an ACK to a pending INVITE server transaction and stops retransmissions.

func (*Manager) HandleCancelRequest

func (m *Manager) HandleCancelRequest(cancel *stack.Message, addr *net.UDPAddr, send SendFunc) bool

HandleCancelRequest handles an inbound CANCEL matching a pending INVITE (same Call-ID and CSeq number). It sends 200 OK to CANCEL via send, clears the pending record, and returns true. The TU should still send a final response to the INVITE (e.g. 487).

func (*Manager) HandleInviteRequest

func (m *Manager) HandleInviteRequest(req *stack.Message, addr *net.UDPAddr) bool

HandleInviteRequest handles a retransmitted INVITE: resends the stored final to addr (fallback: tx.remote).

func (*Manager) HandleNonInviteRequest

func (m *Manager) HandleNonInviteRequest(req *stack.Message, addr *net.UDPAddr) bool

HandleNonInviteRequest handles a retransmitted non-INVITE request: resends the stored final if still in Timer J window.

func (*Manager) HandleResponse

func (m *Manager) HandleResponse(resp *stack.Message, src *net.UDPAddr) bool

HandleResponse dispatches a SIP response to a matching client transaction (INVITE or non-INVITE such as BYE). Returns true if the message was consumed by a transaction (including duplicate finals). src is the UDP source of the datagram (used for symmetric routing of ACK/subsequent requests).

func (*Manager) RegisterPendingInviteServer

func (m *Manager) RegisterPendingInviteServer(inv *stack.Message) error

RegisterPendingInviteServer records an inbound INVITE before a final response is sent, so CANCEL with the same Call-ID and CSeq number can be matched (RFC 3261). Clear with ClearPendingInviteServer or automatically when BeginInviteServer runs.

func (*Manager) RunInviteClient

func (m *Manager) RunInviteClient(ctx context.Context, invite *stack.Message, remote *net.UDPAddr, send SendFunc, onProvisional func(*stack.Message)) (*InviteClientResult, error)

RunInviteClient registers an INVITE client transaction, sends the INVITE (and UDP retransmits until a provisional or final response), then blocks until ctx is done or a final (2xx–6xx) arrives.

onProvisional is optional; it is invoked at most once for the first 1xx response. Wire HandleResponse on the same Manager from stack.Endpoint.OnSIPResponse.

func (*Manager) RunNonInviteClient

func (m *Manager) RunNonInviteClient(ctx context.Context, req *stack.Message, remote *net.UDPAddr, send SendFunc) (*NonInviteClientResult, error)

RunNonInviteClient runs a non-INVITE client transaction (UDP retransmits until a final response). Wire HandleResponse on the same Manager from stack.Endpoint.OnSIPResponse.

func (*Manager) SetT1

func (m *Manager) SetT1(d time.Duration)

SetT1 sets the initial INVITE retransmission interval (must be > 0). Intended for tests.

func (*Manager) SetT2

func (m *Manager) SetT2(d time.Duration)

SetT2 sets the Timer G maximum interval for 2xx retransmissions (must be > 0). Default 4s.

type NonInviteClientResult

type NonInviteClientResult struct {
	Final  *stack.Message
	Remote *net.UDPAddr
}

NonInviteClientResult is the outcome of a completed non-INVITE client transaction (e.g. BYE).

type SendFunc

type SendFunc func(msg *stack.Message, addr *net.UDPAddr) error

SendFunc sends a SIP request datagram.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL