Documentation
¶
Overview ¶
Package stack is the lowest SIP/2.0 signaling layer in lingllm.
It owns wire-format messages and UDP I/O. Higher packages build on it:
protocol/sip/transaction — RFC 3261 client/server transaction state machines protocol/sip/dialog — Call-ID / tag dialog registry protocol/sip/uas — method handlers wired to stack.Endpoint protocol/sip/gateway — combined UAS listen socket + optional TCP/TLS
SIP message shape (RFC 3261) RFC 3261)" aria-label="Go to SIP message shape (RFC 3261)">¶
Every SIP message is ASCII text with CRLF line endings:
start-line CRLF *(message-header CRLF) CRLF [message-body]
A request start-line has three tokens:
METHOD Request-URI SIP-Version
Example INVITE (headers abbreviated):
INVITE sip:alice@example.com SIP/2.0 Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bK776asdhds Max-Forwards: 70 From: Bob <sip:bob@example.com>;tag=a6c85cf To: Alice <sip:alice@example.com> Call-ID: a84b4c76e66710@192.0.2.1 CSeq: 314159 INVITE Contact: <sip:bob@192.0.2.1> Content-Type: application/sdp Content-Length: 142 v=0 o=bob 2890844526 2890844526 IN IP4 192.0.2.1 ...
A response start-line has three tokens:
SIP-Version Status-Code Reason-Phrase
Example 200 OK to the INVITE above:
SIP/2.0 200 OK Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bK776asdhds From: Bob <sip:bob@example.com>;tag=a6c85cf To: Alice <sip:alice@example.com>;tag=1928301774 Call-ID: a84b4c76e66710@192.0.2.1 CSeq: 314159 INVITE Contact: <sip:alice@pc33.example.com> Content-Type: application/sdp Content-Length: 131 v=0 ...
Key headers used throughout the stack:
- Via: routing; each hop adds a Via with a unique branch parameter
- Call-ID: dialog identifier (must be globally unique)
- CSeq: "<number> <METHOD>" — pairs requests with responses
- From / To: dialog parties; To gains a ;tag= on the first provisional/final response
- Contact: direct URI for subsequent in-dialog requests
- Content-Length: byte length of the body (mandatory when a body is present on TCP/TLS)
What this package provides ¶
- Message — parsed request/response with header maps and body
- Parse / Message.String — text serialization (CRLF)
- ReadMessage — stream-oriented read using Content-Length (for TCP/TLS gateways)
- Endpoint — UDP listen loop: parse datagram → dispatch by method → optional auto-reply
- DatagramTransport / UDPTransport — thin UDP adapter
- Method name constants and small helpers (ParseCSeqNum, ParseRAck, …)
What this package deliberately does NOT do ¶
- Transaction timers, retransmission, or CANCEL matching (see transaction package)
- Digest authentication, Record-Route routing, or dialog state
- TCP/TLS listeners (gateway package; it reuses Message + ReadMessage)
- RTP or codec handling (see protocol/sipmedia)
UDP Endpoint lifecycle ¶
ep := stack.NewEndpoint(stack.EndpointConfig{Host: "0.0.0.0", Port: 5060})
ep.RegisterHandler(stack.MethodInvite, myInviteHandler)
ep.Open()
go ep.Serve(ctx) // blocks until ctx cancelled, Close(), or fatal read error
Handlers return a response Message; Endpoint sends it on the same UDP socket. Responses received on the socket are forwarded to EndpointConfig.OnSIPResponse (used by the outbound Manager and transaction layer).
Known limitations ¶
- Malformed header lines without ":" are still skipped rather than rejected.
- Very large SIP bodies over UDP may exceed datagram MTU (no automatic fragmentation).
- Set EndpointConfig.SyncHandlers to force synchronous dispatch (default is async).
Index ¶
- Constants
- Variables
- func BodyBytesLen(body string) int
- func IsSignalingNoiseDatagram(b []byte) bool
- func ParseCSeqNum(cseq string) (int, bool)
- func ParseRAck(h string) (rseq uint32, cseqNum int, method string, err error)
- func WithCSeqACK(inviteCSeq int) string
- type DatagramTransport
- type Endpoint
- func (e *Endpoint) AppendOnResponseSent(fn func(*Message, *Message, *net.UDPAddr))
- func (e *Endpoint) Close() error
- func (e *Endpoint) DispatchRequest(req *Message, addr *net.UDPAddr) *Message
- func (e *Endpoint) InvokeOnSIPResponse(resp *Message, addr *net.UDPAddr)
- func (e *Endpoint) ListenAddr() net.Addr
- func (e *Endpoint) NotifyResponseDelivered(req, resp *Message, addr *net.UDPAddr)
- func (e *Endpoint) Open() error
- func (e *Endpoint) RegisterHandler(method string, h HandlerFunc)
- func (e *Endpoint) Send(msg *Message, addr *net.UDPAddr) error
- func (e *Endpoint) Serve(ctx context.Context) error
- func (e *Endpoint) SetNoRouteHandler(h HandlerFunc)
- func (e *Endpoint) Transport() DatagramTransport
- type EndpointConfig
- type Event
- type EventType
- type HandlerFunc
- type Message
- type UDPTransport
- func (t *UDPTransport) Close() error
- func (t *UDPTransport) LocalAddr() net.Addr
- func (t *UDPTransport) ReadFrom(ctx context.Context, buf []byte) (int, *net.UDPAddr, error)
- func (t *UDPTransport) String() string
- func (t *UDPTransport) WriteTo(ctx context.Context, p []byte, addr *net.UDPAddr) (int, error)
Constants ¶
const ( HeaderVia = "Via" HeaderMaxForwards = "Max-Forwards" HeaderFrom = "From" HeaderTo = "To" HeaderCallID = "Call-ID" HeaderCSeq = "CSeq" HeaderContact = "Contact" HeaderAllow = "Allow" HeaderSupported = "Supported" HeaderUserAgent = "User-Agent" HeaderContentType = "Content-Type" HeaderContentLength = "Content-Length" HeaderRecordRoute = "Record-Route" HeaderRoute = "Route" HeaderRAck = "RAck" HeaderRSeq = "RSeq" HeaderRequire = "Require" HeaderSessionExpires = "Session-Expires" HeaderMinSE = "Min-SE" HeaderReferTo = "Refer-To" HeaderReferredBy = "Referred-By" HeaderReplaces = "Replaces" HeaderPrivacy = "Privacy" HeaderPAssertedIdentity = "P-Asserted-Identity" HeaderHistoryInfo = "History-Info" HeaderDiversion = "Diversion" HeaderExpires = "Expires" HeaderSubscriptionState = "Subscription-State" HeaderEvent = "Event" HeaderReason = "Reason" HeaderAuthorization = "Authorization" HeaderProxyAuthorization = "Proxy-Authorization" HeaderWWWAuthenticate = "WWW-Authenticate" HeaderSubject = "Subject" HeaderAccept = "Accept" HeaderIdentity = "Identity" // STIR/SHAKEN )
SIP header field names in on-the-wire form (RFC 3261 and common extensions). Use with Message.GetHeader / SetHeader / AddHeader / GetHeaders (case-insensitive).
const ( // RFC 3261 core MethodInvite = "INVITE" MethodAck = "ACK" MethodBye = "BYE" MethodCancel = "CANCEL" MethodOptions = "OPTIONS" MethodRegister = "REGISTER" // RFC 3262 reliable provisional responses MethodPrack = "PRACK" // RFC 3265 / 6665 event state; RFC 3856 presence MethodSubscribe = "SUBSCRIBE" MethodNotify = "NOTIFY" MethodPublish = "PUBLISH" // RFC 6086 session-info; RFC 3515 transfer; RFC 3428 instant messages MethodInfo = "INFO" MethodRefer = "REFER" MethodMessage = "MESSAGE" // RFC 3311 mid-dialog parameter refresh MethodUpdate = "UPDATE" )
SIP method names (RFC 3261 and common extensions), upper-case as on the wire.
IANA registry: https://www.iana.org/assignments/sip-methods/sip-methods.xhtml Not every deployment uses all of these; stack only needs constants for registration and builders.
const DefaultMaxForwards = "70"
DefaultMaxForwards is the usual Max-Forwards value for new requests.
const SIPVersion = "SIP/2.0"
SIPVersion is the protocol version on the wire (RFC 3261).
Variables ¶
var CapabilityHeaders = []string{HeaderRequire, HeaderSupported}
CapabilityHeaders lists headers that advertise or require SIP extensions (Require, Supported).
var CorrelationHeaders = []string{ HeaderVia, HeaderFrom, HeaderTo, HeaderCallID, HeaderCSeq, }
CorrelationHeaders are copied from an inbound request when building a correlated SIP response.
Functions ¶
func BodyBytesLen ¶
BodyBytesLen returns the byte length of the body after CRLF normalization.
func IsSignalingNoiseDatagram ¶
IsSignalingNoiseDatagram reports tiny payloads that are not SIP but commonly arrive on port 5060: CRLF keepalives (RFC 5626 double-CRLF), NAT binding refreshes, or whitespace pings. Endpoint skips them before Parse to avoid log spam. Payloads longer than 64 bytes are never classified as noise.
func ParseCSeqNum ¶
ParseCSeqNum extracts the numeric prefix from a CSeq header value. Example: "314159 INVITE" -> (314159, true). The second return is false when the header is empty or malformed (distinguishes failure from a valid zero).
func ParseRAck ¶
ParseRAck parses the RAck header from RFC 3262 (reliable provisional responses). Wire format: "<response-num> <cseq-num> <method>", e.g. "1 314159 INVITE". PRACK requests carry RAck so the UAS can match them to a specific 1xx response.
func WithCSeqACK ¶
WithCSeqACK builds the CSeq header value for an ACK to a 2xx INVITE response. The sequence number must match the original INVITE; only the method changes to ACK.
Types ¶
type DatagramTransport ¶
type DatagramTransport interface {
ReadFrom(ctx context.Context, buf []byte) (n int, addr *net.UDPAddr, err error)
WriteTo(ctx context.Context, p []byte, addr *net.UDPAddr) (n int, err error)
Close() error
LocalAddr() net.Addr
String() string
}
DatagramTransport abstracts connectionless SIP I/O (typically one UDP socket shared by UAS and UAC on the same host). One goroutine should call ReadFrom; multiple goroutines may call WriteTo concurrently.
type Endpoint ¶
type Endpoint struct {
// contains filtered or unexported fields
}
Endpoint is a minimal SIP UAS over UDP: one socket, parse, dispatch by method.
It does not implement transactions. For production INVITE handling, register handlers that delegate to protocol/sip/transaction and return nil or 100 Trying synchronously while the transaction layer sends further responses.
func NewEndpoint ¶
func NewEndpoint(cfg EndpointConfig) *Endpoint
NewEndpoint constructs an endpoint. Call Open then Serve.
func (*Endpoint) AppendOnResponseSent ¶
AppendOnResponseSent chains fn after the existing OnResponseSent callback. Call before Serve starts, or only from the setup goroutine (not concurrently with the read loop).
func (*Endpoint) Close ¶
Close closes the listen socket, waits for in-flight async handlers, and unblocks Serve.
func (*Endpoint) DispatchRequest ¶
DispatchRequest runs the registered method handler (or NoRouteHandler) without sending on UDP. Used by alternate transports (e.g. TCP/TLS) that write responses themselves.
func (*Endpoint) InvokeOnSIPResponse ¶
InvokeOnSIPResponse calls the configured OnSIPResponse hook (e.g. for responses received on TCP).
func (*Endpoint) ListenAddr ¶
ListenAddr returns the bound UDP address after Open, or nil.
func (*Endpoint) NotifyResponseDelivered ¶ added in v1.4.3
NotifyResponseDelivered runs the same post-send hooks as UDP after a response is written on an alternate transport (TCP/TLS). Call after the response bytes are on the wire.
func (*Endpoint) RegisterHandler ¶
func (e *Endpoint) RegisterHandler(method string, h HandlerFunc)
RegisterHandler registers a SIP method handler (method is case-insensitive).
func (*Endpoint) Serve ¶
Serve runs the read/dispatch loop until ctx is cancelled, Close is called, or a non-timeout read error occurs (then returns a wrapped error after optional OnReadError). It returns ctx.Err() when the context was cancelled before a read completes.
func (*Endpoint) SetNoRouteHandler ¶
func (e *Endpoint) SetNoRouteHandler(h HandlerFunc)
SetNoRouteHandler sets the fallback handler for unknown methods (same as EndpointConfig.NoRouteHandler).
func (*Endpoint) Transport ¶
func (e *Endpoint) Transport() DatagramTransport
Transport returns the datagram transport after Open, or nil.
type EndpointConfig ¶
type EndpointConfig struct {
// Host is the local address to bind (empty or "0.0.0.0" / "::" = all interfaces).
Host string
// Port is the UDP port (typically 5060).
Port int
// Network selects the UDP socket family: "udp4", "udp6", or "udp" (dual-stack).
// Default "udp4".
Network string
// ReadBufSize is the UDP receive buffer (default 65535, max single datagram size).
ReadBufSize int
// ReadDeadline is applied before each read so Serve can poll ctx.Done()
// and shut down promptly (default 1s). Timeout reads are retried, not fatal.
ReadDeadline time.Duration
// SyncHandlers when true runs method handlers in the read loop; default is false
// (each request is handled in its own goroutine so slow work does not block I/O).
SyncHandlers bool
// OnReadError is called once for non-timeout read errors before Serve returns.
OnReadError func(err error)
// OnDatagram receives every non-noise UDP payload before parsing (wire tap).
OnDatagram func(raw []byte, addr *net.UDPAddr)
// OnParseErr receives datagrams that failed Parse (malformed SIP).
OnParseErr func(raw []byte, addr *net.UDPAddr, err error)
// OnRequest is notified after a request is parsed, before the method handler runs.
OnRequest func(req *Message, addr *net.UDPAddr)
// OnResponse is notified when a handler returns a response, before UDP send.
OnResponse func(req *Message, resp *Message, addr *net.UDPAddr)
// OnResponseSent runs after the response bytes were written successfully.
OnResponseSent func(req *Message, resp *Message, addr *net.UDPAddr)
// OnSIPResponse receives every SIP response datagram (outbound transaction matching).
OnSIPResponse func(resp *Message, addr *net.UDPAddr)
// OnMessageSent runs after any outbound write via Endpoint.Send (UAC or UAS).
OnMessageSent func(msg *Message, addr *net.UDPAddr)
// OnEvent is a unified hook for metrics; see EventType constants.
OnEvent func(e Event)
// NoRouteHandler handles methods with no RegisterHandler entry.
// When nil, DefaultNoRouteResponse (501 Not Implemented) is used.
NoRouteHandler HandlerFunc
}
EndpointConfig configures a UDP signaling Endpoint.
type Event ¶
type Event struct {
Type EventType
Addr *net.UDPAddr
Raw []byte
Request *Message
Response *Message
Err error
}
Event is a lightweight observation from the read loop for metrics/tracing.
func (Event) RequestMethod ¶
RequestMethod returns e.Request.Method for request-bearing events, or "".
func (Event) ResponseStatus ¶
ResponseStatus returns e.Response.StatusCode for response-bearing events, or 0.
type EventType ¶
type EventType int
EventType identifies telemetry emitted from the UDP read loop.
const ( // EventDatagramReceived — raw bytes received before parse (after noise filter). EventDatagramReceived EventType = iota // EventParseError — datagram was not valid SIP text. EventParseError // EventRequestReceived — a SIP request was parsed and will be dispatched. EventRequestReceived // EventResponseReceived — a SIP response arrived (forwarded to OnSIPResponse). EventResponseReceived // EventResponseSent — a handler response was written to the socket successfully. EventResponseSent )
type HandlerFunc ¶
HandlerFunc is a UAS-style SIP method handler. It receives the parsed request and the source UDP address. Returning nil means "do not send anything" (used when the handler will respond asynchronously via transaction layer or multiple provisional responses).
type Message ¶
type Message struct {
Method string
RequestURI string
StatusCode int // 200
StatusText string // "OK"
Version string // "SIP/2.0"
// Headers = map[string]string{
// "Via": "SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK12345",
// "To": "Bob <sip:bob@example.com>;tag=as12345",
// "From": "Alice <sip:alice@example.com>;tag=1928301774",
// "Call-ID": "a84b4c76e66710@pc33.atlanta.com",
// "CSeq": "314159 INVITE",
// "Contact": "<sip:bob@192.168.1.100:5060>",
// "Content-Type": "application/sdp",
// "Content-Length": "158",
//}
Headers map[string]string // first value per canonical header name
// HeadersMulti = map[string][]string{
// "Via": {"SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK12345"},
// "To": {"Bob <sip:bob@example.com>;tag=as12345"},
// "From": {"Alice <sip:alice@example.com>;tag=1928301774"},
// "Call-ID": {"a84b4c76e66710@pc33.atlanta.com"},
// "CSeq": {"314159 INVITE"},
// "Contact": {"<sip:bob@192.168.1.100:5060>"},
// "Content-Type": {"application/sdp"},
// "Content-Length": {"158"},
//}
HeadersMulti map[string][]string // all values per canonical header name (e.g. Via)
// Body input SDP 会话描述协议
// v=0 => SDP Version
// o=user2 2890844526 2890844526 IN IP4 192.168.1.100 会话所有者 / 会话 ID
// 用户名 会话ID 版本号 网络类型 地址类型 主机地址
// s=Session SDP 会话名称
// c=IN IP4 192.168.1.100 连接信息-指定媒体流收发地址
// t=0 0 会话时长
// m=audio 3456 RTP/AVP 0 媒体类型 端口 传输协议 编码格式
// a=rtpmap:0 PCMU/8000
// rtpmap:RTP 映射说明
// 0:对应上面 m 行的负载类型 ID
// PCMU:编码格式(G.711 μ 律,传统电话语音编码)
// 8000:采样率 8000Hz(标准电话音质)
Body string
IsRequest bool
}
Message is an in-memory representation of one SIP/2.0 request or response.
Requests set IsRequest=true and populate Method, RequestURI, and Version (typically "SIP/2.0"). Responses set IsRequest=false and populate StatusCode and StatusText (e.g. 200, "OK").
Headers are stored under lowercase canonical keys (see canonicalHeaderKey). HeadersMulti preserves every value for multi-value headers such as Via, Record-Route, and Contact. Headers holds only the first value per name for quick lookup via GetHeader.
Body holds the raw message body (usually SDP for INVITE/200, or empty). Call PrepareForSend before transmission to set Content-Length from Body.
func DefaultNoRouteResponse ¶ added in v1.4.3
DefaultNoRouteResponse builds a 501 Not Implemented for an unregistered SIP method. It copies Via, From, To, Call-ID, and CSeq from the request per RFC 3261.
func Parse ¶
Parse decodes a complete SIP message from its on-the-wire text form.
The parser accepts CRLF or bare LF, unfolds RFC 3261 header continuation lines, and trims the body to Content-Length when that header is present. Malformed header lines without ":" are silently skipped.
func ReadMessage ¶
ReadMessage reads exactly one SIP message from a byte stream (TCP/TLS).
It reads header lines until a blank line (unfolding continuation lines), validates duplicate Content-Length headers, reads exactly Content-Length body octets, then parses. When Content-Length is absent the body is empty.
func (*Message) GetHeaders ¶
GetHeaders returns all values for a header name (case-insensitive).
func (*Message) PrepareForSend ¶ added in v1.4.3
func (m *Message) PrepareForSend()
PrepareForSend sets Content-Length from the normalized body byte length. Call before String() or Endpoint.Send when the body may have changed.
func (*Message) String ¶
String encodes the message for transmission. Line endings are CRLF. Well-known headers are emitted in a stable, SIP-friendly order (Via, From, To, Call-ID, CSeq, …) followed by remaining headers sorted lexicographically. Endpoint.Send calls PrepareForSend automatically; use PrepareForSend yourself when serializing outside Endpoint.
type UDPTransport ¶
type UDPTransport struct {
// contains filtered or unexported fields
}
UDPTransport adapts *net.UDPConn to DatagramTransport.
func NewUDPTransport ¶
func NewUDPTransport(conn *net.UDPConn) *UDPTransport
NewUDPTransport wraps an existing UDP connection.
func (*UDPTransport) Close ¶
func (t *UDPTransport) Close() error
func (*UDPTransport) LocalAddr ¶
func (t *UDPTransport) LocalAddr() net.Addr
func (*UDPTransport) ReadFrom ¶
ReadFrom reads a datagram. If ctx is cancelled, returns ctx.Err() without blocking indefinitely (requires SetReadDeadline on the conn by the caller, or Endpoint sets deadlines each iteration).
func (*UDPTransport) String ¶
func (t *UDPTransport) String() string