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 ¶
- func AckRequestURIFor2xx(resp *stack.Message, inviteRequestURI string) string
- func BranchParam(viaLine string) string
- func BuildAckForInvite(invite *stack.Message, final *stack.Message, requestURI string) (*stack.Message, error)
- func CSeqMethod(m *stack.Message) string
- func InviteTransactionKey(branch, callID string) string
- func IsAckCSeq(m *stack.Message) bool
- func IsCancelCSeq(m *stack.Message) bool
- func IsInviteCSeq(m *stack.Message) bool
- func NonInviteServerKey(req *stack.Message) string
- func RouteHeadersForDialog(resp *stack.Message) []string
- func SetTransactionTimeoutHook(fn func(method string))
- func TopBranch(m *stack.Message) string
- func TopVia(m *stack.Message) string
- type InviteClientResult
- type Manager
- func (m *Manager) BeginInviteServer(ctx context.Context, invite *stack.Message, remote *net.UDPAddr, ...) error
- func (m *Manager) BeginNonInviteServer(ctx context.Context, req *stack.Message, remote *net.UDPAddr, ...) error
- func (m *Manager) ClearPendingInviteServer(callID string)
- func (m *Manager) HandleAck(ack *stack.Message, _ *net.UDPAddr) bool
- func (m *Manager) HandleCancelRequest(cancel *stack.Message, addr *net.UDPAddr, send SendFunc) bool
- func (m *Manager) HandleInviteRequest(req *stack.Message, addr *net.UDPAddr) bool
- func (m *Manager) HandleNonInviteRequest(req *stack.Message, addr *net.UDPAddr) bool
- func (m *Manager) HandleResponse(resp *stack.Message, src *net.UDPAddr) bool
- func (m *Manager) RegisterPendingInviteServer(inv *stack.Message) error
- func (m *Manager) RunInviteClient(ctx context.Context, invite *stack.Message, remote *net.UDPAddr, send SendFunc, ...) (*InviteClientResult, error)
- func (m *Manager) RunNonInviteClient(ctx context.Context, req *stack.Message, remote *net.UDPAddr, send SendFunc) (*NonInviteClientResult, error)
- func (m *Manager) SetT1(d time.Duration)
- func (m *Manager) SetT2(d time.Duration)
- type NonInviteClientResult
- type SendFunc
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AckRequestURIFor2xx ¶
AckRequestURIFor2xx prefers Contact from a 2xx response (RFC 3261 dialog establishment).
func BranchParam ¶
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
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 ¶
InviteTransactionKey is the INVITE transaction map key (top Via branch + Call-ID).
func IsCancelCSeq ¶
IsCancelCSeq reports whether the CSeq method is CANCEL.
func IsInviteCSeq ¶
IsInviteCSeq reports whether the CSeq method is INVITE.
func NonInviteServerKey ¶
NonInviteServerKey builds a stable key for a non-INVITE server transaction (branch + Call-ID + method + CSeq number).
func RouteHeadersForDialog ¶
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.
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 ¶
ClearPendingInviteServer removes the pending INVITE record for Call-ID.
func (*Manager) HandleAck ¶
HandleAck matches an ACK to a pending INVITE server transaction and stops retransmissions.
func (*Manager) HandleCancelRequest ¶
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 ¶
HandleInviteRequest handles a retransmitted INVITE: resends the stored final to addr (fallback: tx.remote).
func (*Manager) HandleNonInviteRequest ¶
HandleNonInviteRequest handles a retransmitted non-INVITE request: resends the stored final if still in Timer J window.
func (*Manager) HandleResponse ¶
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 ¶
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.
type NonInviteClientResult ¶
NonInviteClientResult is the outcome of a completed non-INVITE client transaction (e.g. BYE).