stack

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: 10 Imported by: 0

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

View Source
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).

View Source
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.

View Source
const DefaultMaxForwards = "70"

DefaultMaxForwards is the usual Max-Forwards value for new requests.

View Source
const SIPVersion = "SIP/2.0"

SIPVersion is the protocol version on the wire (RFC 3261).

Variables

View Source
var CapabilityHeaders = []string{HeaderRequire, HeaderSupported}

CapabilityHeaders lists headers that advertise or require SIP extensions (Require, Supported).

CorrelationHeaders are copied from an inbound request when building a correlated SIP response.

Functions

func BodyBytesLen

func BodyBytesLen(body string) int

BodyBytesLen returns the byte length of the body after CRLF normalization.

func IsSignalingNoiseDatagram

func IsSignalingNoiseDatagram(b []byte) bool

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

func ParseCSeqNum(cseq string) (int, bool)

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

func ParseRAck(h string) (rseq uint32, cseqNum int, method string, err error)

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

func WithCSeqACK(inviteCSeq int) string

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

func (e *Endpoint) AppendOnResponseSent(fn func(*Message, *Message, *net.UDPAddr))

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

func (e *Endpoint) Close() error

Close closes the listen socket, waits for in-flight async handlers, and unblocks Serve.

func (*Endpoint) DispatchRequest

func (e *Endpoint) DispatchRequest(req *Message, addr *net.UDPAddr) *Message

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

func (e *Endpoint) InvokeOnSIPResponse(resp *Message, addr *net.UDPAddr)

InvokeOnSIPResponse calls the configured OnSIPResponse hook (e.g. for responses received on TCP).

func (*Endpoint) ListenAddr

func (e *Endpoint) ListenAddr() net.Addr

ListenAddr returns the bound UDP address after Open, or nil.

func (*Endpoint) NotifyResponseDelivered added in v1.4.3

func (e *Endpoint) NotifyResponseDelivered(req, resp *Message, addr *net.UDPAddr)

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) Open

func (e *Endpoint) Open() error

Open binds the UDP listen socket.

func (*Endpoint) RegisterHandler

func (e *Endpoint) RegisterHandler(method string, h HandlerFunc)

RegisterHandler registers a SIP method handler (method is case-insensitive).

func (*Endpoint) Send

func (e *Endpoint) Send(msg *Message, addr *net.UDPAddr) error

Send writes a SIP message to addr using the bound UDP socket.

func (*Endpoint) Serve

func (e *Endpoint) Serve(ctx context.Context) error

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

func (e Event) RequestMethod() string

RequestMethod returns e.Request.Method for request-bearing events, or "".

func (Event) ResponseStatus

func (e Event) ResponseStatus() int

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

type HandlerFunc func(msg *Message, addr *net.UDPAddr) *Message

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

func DefaultNoRouteResponse(req *Message) *Message

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

func Parse(raw string) (*Message, error)

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

func ReadMessage(r *bufio.Reader) (*Message, error)

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) AddHeader

func (m *Message) AddHeader(name, value string)

AddHeader appends a header value (multi-value headers such as Via).

func (*Message) GetHeader

func (m *Message) GetHeader(name string) string

GetHeader returns the first header value for name (case-insensitive).

func (*Message) GetHeaders

func (m *Message) GetHeaders(name string) []string

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) SetHeader

func (m *Message) SetHeader(name, value string)

SetHeader replaces a header with a single value.

func (*Message) String

func (m *Message) String() 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

func (t *UDPTransport) ReadFrom(ctx context.Context, buf []byte) (int, *net.UDPAddr, error)

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

func (*UDPTransport) WriteTo

func (t *UDPTransport) WriteTo(ctx context.Context, p []byte, addr *net.UDPAddr) (int, error)

Jump to

Keyboard shortcuts

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