sip

package
v0.9.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const (
	// DefaultMinSE is the minimum session interval we accept (RFC 4028 §4).
	DefaultMinSE = 90

	// DefaultSessionExpires is the default session interval when the remote
	// requests timers but doesn't specify an interval.
	DefaultSessionExpires = 1800
)
View Source
const (
	WhatsAppOutboundHost = "wa.meta.vc"
)

Variables

View Source
var ErrNotRTP = errors.New("not an RTP packet")

ErrNotRTP is returned by ReadRTP when a received UDP packet is not valid RTP (e.g. RTCP, STUN). Callers should continue reading on this error.

Functions

func AMRNBModeSet added in v0.9.0

func AMRNBModeSet(fmtp string) []int

AMRNBModeSet parses an AMR-NB "mode-set=a,b,c" fmtp parameter into the set of allowed speech modes (0..7). Returns nil when no valid mode-set is present (RFC 4867: absence means all modes are permitted). Modes outside 0..7 are dropped.

func AMRNBOctetAligned added in v0.9.0

func AMRNBOctetAligned(fmtp string) bool

AMRNBOctetAligned reports whether the AMR-NB fmtp params select octet-aligned framing. Per RFC 4867 the default (no octet-align) is bandwidth-efficient, so an absent or "octet-align=0" parameter means bandwidth-efficient.

func AMRWBModeSet added in v0.9.0

func AMRWBModeSet(fmtp string) []int

AMRWBModeSet parses an AMR-WB "mode-set=a,b,c" fmtp parameter into the set of allowed speech modes (0..8). Returns nil when no valid mode-set is present (RFC 4867: absence means all modes are permitted). Modes outside 0..8 are dropped.

func AMRWBOctetAligned added in v0.9.0

func AMRWBOctetAligned(fmtp string) bool

AMRWBOctetAligned reports whether the AMR-WB fmtp params select octet-aligned framing. Per RFC 4867 the default (no octet-align) is bandwidth-efficient, so an absent or "octet-align=0" parameter means bandwidth-efficient.

func AddressFamily added in v0.6.0

func AddressFamily(ip string) string

AddressFamily returns the SDP address-type token ("IP4" or "IP6") for an IP literal, or "" when the input is empty / not a literal IP.

func CanonicalizeAOR added in v0.8.0

func CanonicalizeAOR(u sip.Uri) string

CanonicalizeAOR returns the registry key for an AOR URI. The transformation is: lower-case scheme, strip user-info parameters, lower-case host, keep port only when explicit, drop URI params. Returns "" for malformed input.

func ClampAMRNBMode added in v0.9.0

func ClampAMRNBMode(ceiling int, modeSet []int) int

ClampAMRNBMode constrains a desired ceiling mode to the peer's negotiated mode-set. Behaviour mirrors ClampAMRWBMode but ranges over AMR-NB modes 0..7.

func ClampAMRWBMode added in v0.9.0

func ClampAMRWBMode(ceiling int, modeSet []int) int

ClampAMRWBMode constrains a desired ceiling mode to the peer's negotiated mode-set: it returns the highest set member <= ceiling, or — when the ceiling is below every member — the lowest member (so the result always stays inside the set). A nil/empty set means no restriction, so the ceiling is returned.

func DTMFDigitToEvent

func DTMFDigitToEvent(digit rune) (uint8, bool)

DTMFDigitToEvent converts a DTMF digit character to its RFC 4733 event code.

func DTMFEventToDigit

func DTMFEventToDigit(event uint8) (rune, bool)

DTMFEventToDigit converts an RFC 4733 event code to a digit character.

func EncodeDTMFEvent

func EncodeDTMFEvent(ev DTMFEvent) []byte

EncodeDTMFEvent encodes a DTMFEvent into a 4-byte RFC 4733 payload.

func FormatAMRNBModeSet added in v0.9.0

func FormatAMRNBModeSet(modes []int) string

FormatAMRNBModeSet renders modes as a "0,4,7" mode-set value (the inverse of AMRNBModeSet); returns "" for an empty set.

func FormatAMRWBModeSet added in v0.9.0

func FormatAMRWBModeSet(modes []int) string

FormatAMRWBModeSet renders modes as a "0,1,2" mode-set value (the inverse of AMRWBModeSet); returns "" for an empty set.

func FormatSessionExpires

func FormatSessionExpires(interval uint32, refresher string) string

FormatSessionExpires formats a Session-Expires header value.

func GenerateAnswer

func GenerateAnswer(cfg SDPConfig, selected codec.CodecType, selectedPT uint8, textRejected bool) []byte

GenerateAnswer builds an SDP answer with a single selected codec. selectedPT echoes the remote offer's PT for dynamic codecs. When cfg.TextRTPPort != 0 the answer accepts RTT; when textRejected is true the answer includes a port=0 m=text section per RFC 3264.

func GenerateDTMFPackets

func GenerateDTMFPackets(digit rune, pt uint8, ssrc uint32, baseSeq uint16, baseTS uint32, samplesPerPkt uint16) []*rtp.Packet

GenerateDTMFPackets builds the sequence of RTP packets for one DTMF digit. Returns 7 packets: 4 normal (80ms) + 3 end-of-event, each 20ms apart. The timestamp remains fixed for the entire event per RFC 4733. samplesPerPkt is the telephone-event clock rate per 20ms (e.g. 160 at 8kHz, 320 at AMR-WB's 16kHz); it sets the units of the encoded event duration and must match the negotiated telephone-event clock rate or strict peers drop the digit as too short.

func GenerateOffer

func GenerateOffer(cfg SDPConfig) []byte

GenerateOffer builds an SDP offer with all configured codecs.

func GenerateReInviteSDP

func GenerateReInviteSDP(cfg SDPConfig, selected codec.CodecType, selectedPT uint8, direction string) []byte

GenerateReInviteSDP builds an SDP body for a re-INVITE (hold/unhold). It is similar to GenerateAnswer but uses the specified direction attribute.

func IsWhatsAppInvite added in v0.5.0

func IsWhatsAppInvite(call *InboundCall) bool

IsWhatsAppInvite reports whether an inbound INVITE comes from a Meta WhatsApp gateway (meta.vc or any subdomain).

func JoinHostPort added in v0.6.0

func JoinHostPort(host string, port int) string

JoinHostPort wraps net.JoinHostPort with an int port — bracket-safe for IPv6 literals and a no-op for IPv4 / hostnames.

func NegotiateCodec

func NegotiateCodec(remote *SDPMedia, supported []codec.CodecType) (codec.CodecType, uint8, bool)

NegotiateCodec finds the first codec in the remote SDP that is also in the supported list. Returns the codec type, the payload type from the remote SDP, and whether negotiation succeeded.

func NegotiateCodecPreferred added in v0.6.0

func NegotiateCodecPreferred(remote *SDPMedia, supported []codec.CodecType, preferred codec.CodecType) (codec.CodecType, uint8, bool)

NegotiateCodecPreferred is like NegotiateCodec but biases the choice toward preferred when it is non-zero. The preferred codec must appear in both the remote offer and the supported list; otherwise selection falls back to the regular preference order.

func ParseMinSE

func ParseMinSE(value string) uint32

ParseMinSE parses a Min-SE header value, e.g. "90" → 90.

func ParseSessionExpires

func ParseSessionExpires(value string) (interval uint32, refresher string)

ParseSessionExpires parses a Session-Expires header value, e.g. "1800;refresher=uac" → interval=1800, refresher="uac".

func ParseSipfrag added in v0.3.0

func ParseSipfrag(body []byte) (int, string)

ParseSipfrag returns (statusCode, reason) from a sipfrag status line, or (0,"").

func TelephoneEventClockRate added in v0.9.0

func TelephoneEventClockRate(c codec.CodecType) int

TelephoneEventClockRate returns the RTP clock rate to pair with the telephone-event (RFC 4733) format for codec c. RFC 4733 requires the telephone-event clock rate to match the audio codec's RTP clock rate, so AMR-WB uses 16 kHz; all other codecs use the conventional 8 kHz.

func UDPNetwork added in v0.6.0

func UDPNetwork(listenIP string) string

UDPNetwork returns the UDP network string ("udp", "udp4", or "udp6") to use for a given listen IP literal. An empty string or "::" yields "udp" so the OS can give us a dual-stack socket on Linux when bindv6only=0.

func WhatsAppRecipientURI added in v0.5.0

func WhatsAppRecipientURI(toUser string) sip.Uri

WhatsAppRecipientURI builds the Request-URI for an outbound call. Meta uses "sip:+E164@wa.meta.vc;transport=tls" — using "sips:" returns 404 because their internal routing isn't strict-TLS end-to-end.

Types

type Binding added in v0.8.0

type Binding struct {
	AOR            string
	Contact        string
	Socket         string
	Transport      string
	UserAgent      string
	CallID         string
	AppID          string
	CreatedAt      time.Time
	LastRefresh    time.Time
	ExpiresAt      time.Time
	GrantedExpires int // seconds granted at bind time (constant for the binding)
}

Binding represents one Contact registered under an AOR.

type DTMFEvent

type DTMFEvent struct {
	Event      uint8 // 0-9, 10=*, 11=#, 12-15=A-D
	EndOfEvent bool
	Volume     uint8  // 0-63
	Duration   uint16 // in timestamp units
}

DTMFEvent represents an RFC 4733 telephone-event payload.

func DecodeDTMFEvent

func DecodeDTMFEvent(payload []byte) (DTMFEvent, error)

DecodeDTMFEvent decodes a 4-byte RFC 4733 payload into a DTMFEvent.

type Engine

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

Engine wraps sipgo server/client + dialog caches for SIP signaling.

func NewEngine

func NewEngine(cfg EngineConfig) (*Engine, error)

NewEngine creates a SIP engine with the given configuration.

func (*Engine) AMRNBMode added in v0.9.0

func (e *Engine) AMRNBMode() int

AMRNBMode returns the configured AMR-NB encoder speech mode (0..7).

func (*Engine) AMRNBOctetAligned added in v0.9.0

func (e *Engine) AMRNBOctetAligned() bool

AMRNBOctetAligned reports whether AMR-NB offers advertise octet-aligned framing.

func (*Engine) AMRWBMode added in v0.9.0

func (e *Engine) AMRWBMode() int

AMRWBMode returns the configured AMR-WB encoder speech mode (0..8).

func (*Engine) AMRWBOctetAligned added in v0.9.0

func (e *Engine) AMRWBOctetAligned() bool

AMRWBOctetAligned reports whether AMR-WB offers advertise octet-aligned framing.

func (*Engine) AdvertisedIPForFamily added in v0.6.0

func (e *Engine) AdvertisedIPForFamily(family string) string

AdvertisedIPForFamily returns the configured advertised IP for the given SDP address family token ("IP4" or "IP6"). Falls back to the other family when the requested one is unconfigured. Returns empty when neither is configured (caller should reject the call).

func (*Engine) AllowHeader added in v0.9.0

func (e *Engine) AllowHeader() sip.Header

AllowHeader returns an Allow header listing every SIP method this UA answers (RFC 3261 §20.5). The list comes from sipgo's registered request handlers so it can never drift from the methods we actually accept.

func (*Engine) BindIP

func (e *Engine) BindIP() string

BindIP returns the engine's IPv4 advertised address. May be empty in IPv6-only deployments.

func (*Engine) BindIPV6 added in v0.6.0

func (e *Engine) BindIPV6() string

BindIPV6 returns the engine's IPv6 advertised address. Empty when no v6 is configured.

func (*Engine) Codecs

func (e *Engine) Codecs() []codec.CodecType

Codecs returns the engine's supported codecs.

func (*Engine) ContactForInvite added in v0.6.0

func (e *Engine) ContactForInvite(req *sip.Request) *sip.ContactHeader

ContactForInvite is the public form of contactForInvite, used by callers outside this package that need to attach a transport-appropriate Contact header to a dialog response.

func (*Engine) DestinationsPinned added in v0.6.0

func (e *Engine) DestinationsPinned() uint64

DestinationsPinned returns the cumulative count of responses whose destination has been overridden via SIP_USE_SOURCE_SOCKET. Tests use this to assert the flag actually fired rather than silently no-op'd.

func (*Engine) DialogRespond added in v0.6.0

func (e *Engine) DialogRespond(d *sipgo.DialogServerSession, statusCode int, reason string, body []byte, headers ...sip.Header) error

DialogRespond sends a response on a UAS dialog, pinning the destination to the original INVITE source when SIP_USE_SOURCE_SOCKET is enabled. Replaces direct calls to dialog.Respond / dialog.RespondSDP — those reach tx.Respond internally without giving us a hook to override Destination.

func (*Engine) Invite

func (e *Engine) Invite(ctx context.Context, recipient sip.Uri, opts InviteOptions) (*OutboundCall, error)

Invite sends an outbound INVITE and returns an OutboundCall on success. When opts.ForkTargets has more than one entry the INVITE is parallel-forked (RFC 3261 §16); the first branch to reach a 2xx response wins and the others are CANCELled.

func (*Engine) InviteWhatsApp added in v0.5.0

func (e *Engine) InviteWhatsApp(ctx context.Context, recipient sip.Uri, opts WhatsAppInviteOptions) (*WhatsAppOutboundCall, error)

InviteWhatsApp sends an outbound INVITE over SIP/TLS with a pre-built WebRTC SDP offer and digest auth credentials.

func (*Engine) LogSyntheticResponse added in v0.5.0

func (e *Engine) LogSyntheticResponse(req *sip.Request, statusCode int, reason string, body []byte, headers ...sip.Header)

LogSyntheticResponse constructs a response from a request (mirroring what sipgo would build internally) purely for SIP_DEBUG logging. The actual response still goes out through dialog.Respond / dialog.RespondSDP; this is a best-effort wire-form dump so the body and headers we ask sipgo to include are visible on the debug channel.

func (*Engine) OnInvite

func (e *Engine) OnInvite(handler func(*InboundCall))

OnInvite registers a handler for inbound INVITE requests.

func (*Engine) OnNotify added in v0.3.0

func (e *Engine) OnNotify(handler func(callID string, statusCode int, reason string, terminated bool))

OnNotify registers a handler for in-dialog NOTIFY requests carrying a "refer" subscription (RFC 3515 sipfrag). It is invoked once per NOTIFY with the subscription's terminal/transient SIP status parsed from the sipfrag body.

func (*Engine) OnReInvite

func (e *Engine) OnReInvite(handler func(callID string, direction string) []byte)

OnReInvite registers a handler for in-dialog re-INVITE requests (hold/unhold). The handler receives the SIP Call-ID and the SDP direction attribute, and returns the SDP body to include in the 200 OK response (nil = no SDP).

func (*Engine) OnRefer added in v0.3.0

func (e *Engine) OnRefer(handler func(callID string, target string, replaces *ReplacesParams, req *sip.Request, tx sip.ServerTransaction))

OnRefer registers a handler for in-dialog REFER requests (transfer). The handler is responsible for sending the SIP response (typically 202 Accepted, or 603 Decline when transfers are disabled). req is provided so the handler can pass it to sip.NewResponseFromRequest.

func (*Engine) OnUpdate added in v0.9.0

func (e *Engine) OnUpdate(handler func(callID string, direction string, hasSDP bool) []byte)

OnUpdate registers a handler for in-dialog UPDATE requests (RFC 3311), used for session-timer refresh (RFC 4028) and mid-dialog media changes. hasSDP indicates whether the UPDATE carried an SDP offer; when true, direction is the parsed a=sendrecv/sendonly/recvonly/inactive attribute and the returned []byte is the SDP answer for the 200 OK. When false, direction is "" and the handler should only refresh session-timer state.

func (*Engine) PortAllocator

func (e *Engine) PortAllocator() *PortAllocator

PortAllocator returns the engine's port allocator (nil if OS-assigned).

func (*Engine) PublicHost added in v0.5.0

func (e *Engine) PublicHost() string

PublicHost returns the canonical signalling hostname (SIP_DOMAIN when set, otherwise the advertised IP).

func (*Engine) Registrar added in v0.8.0

func (e *Engine) Registrar() *Registrar

Registrar returns the engine's AOR registry (may be nil when the engine was created without one).

func (*Engine) RespondFromSource added in v0.3.0

func (e *Engine) RespondFromSource(tx sip.ServerTransaction, req *sip.Request, statusCode int, reason string) error

RespondFromSource pins the response destination to the request's UDP source so peers with unroutable Via headers still get our reply.

func (*Engine) RespondInviteSDP added in v0.5.0

func (e *Engine) RespondInviteSDP(dialog *sipgo.DialogServerSession, sdp []byte) error

RespondInviteSDP sends a 2xx response to an inbound INVITE with a transport-appropriate Contact header. This is required for WhatsApp inbound calls, which arrive over TLS and need a sips: Contact pointing at our TLS port — otherwise the remote's ACK is routed to the wrong scheme/port, the dialog stays in Early state, and retransmits eventually kill the transaction.

func (*Engine) SIPDebug added in v0.5.0

func (e *Engine) SIPDebug() bool

SIPDebug reports whether SIP_DEBUG is enabled. Consumers that send responses via sipgo (dialog.Respond / RespondSDP) use this to gate LogSyntheticResponse calls.

func (*Engine) SIPHost added in v0.4.0

func (e *Engine) SIPHost() string

func (*Engine) SendNotifySipfrag added in v0.3.0

func (e *Engine) SendNotifySipfrag(ctx context.Context, dialog interface{}, statusCode int, reason string, terminated bool) error

SendNotifySipfrag sends a "refer" subscription NOTIFY with a sipfrag body.

func (*Engine) SendReInvite

func (e *Engine) SendReInvite(ctx context.Context, dialog interface{}, sdpBody []byte) error

SendReInvite sends a re-INVITE within an existing dialog for hold/unhold. dialog must be either *sipgo.DialogServerSession or *sipgo.DialogClientSession.

func (*Engine) SendRefer added in v0.3.0

func (e *Engine) SendRefer(ctx context.Context, dialog interface{}, referTo string, replaces *ReplacesParams) error

SendRefer sends an in-dialog REFER, returning nil on 202 Accepted.

func (*Engine) Serve

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

Serve starts the SIP server and blocks until ctx is cancelled. When TLSBindPort is configured it runs UDP and TLS listeners concurrently; if either fails the other is torn down via ctx cancellation. A secondary UDP listener is started when listenIPV6 is set to a literal distinct from listenIP (rare dual-bind case for hosts with bindv6only=1).

func (*Engine) ServerHeader added in v0.4.0

func (e *Engine) ServerHeader() sip.Header

ServerHeader returns a SIP Server header for UAS responses.

func (*Engine) TLSPort added in v0.5.0

func (e *Engine) TLSPort() int

TLSPort returns the configured SIP TLS port (0 = disabled).

func (*Engine) Trunks added in v0.9.0

func (e *Engine) Trunks() *TrunkManager

Trunks returns the engine's outbound SIP trunk manager. Always non-nil.

func (*Engine) UserAgentHeader added in v0.9.0

func (e *Engine) UserAgentHeader() sip.Header

UserAgentHeader returns a SIP User-Agent header for UAC requests. Mirrors the Server header so peers see the same identity in both directions.

type EngineConfig

type EngineConfig struct {
	BindIP      string // IPv4 advertised address for SDP c= line / Contact (when v4 is in use)
	BindIPV6    string // IPv6 advertised address; empty = v6 not advertised
	ListenIP    string // IPv4 socket bind (default: same as BindIP). Special values: "0.0.0.0", "::" (dual-stack)
	ListenIPV6  string // IPv6 socket bind (default: same as BindIPV6). Used when configured separately from ListenIP.
	ExternalIP  string // IPv4 public IP override for NAT/Docker (v6 has no equivalent — set BindIPV6 directly)
	PublicHost  string // FQDN advertised in From/Contact/Via signaling headers; falls back to ExternalIP/BindIP when empty
	BindPort    int
	TLSBindPort int    // 0 = TLS disabled
	TLSCertPath string // CA-signed cert (fullchain.pem) — required when TLSBindPort > 0
	TLSKeyPath  string // private key (privkey.pem) — required when TLSBindPort > 0
	SIPDebug    bool   // dump full SIP request/response bodies on the debug channel
	SIPHost     string
	// UseSourceSocket forces SIP responses and in-dialog requests to be
	// routed back to the request's source socket (req.Source()) instead of
	// the peer's Contact URI / Via sent-by. Required when peers are behind
	// NAT and advertise unroutable addresses.
	UseSourceSocket bool
	Codecs          []codec.CodecType
	// AMRWBMode is the AMR-WB encoder speech mode (0..8) offered/transmitted.
	AMRWBMode int
	// AMRWBOctetAligned selects octet-aligned (true) vs bandwidth-efficient
	// (false) AMR-WB framing in generated offers.
	AMRWBOctetAligned bool
	// AMRNBMode is the AMR-NB encoder speech mode (0..7) offered/transmitted.
	AMRNBMode int
	// AMRNBOctetAligned selects octet-aligned (true) vs bandwidth-efficient
	// (false) AMR-NB framing in generated offers.
	AMRNBOctetAligned bool
	Log               *slog.Logger
	PortAllocator     *PortAllocator // nil = OS-assigned ports

	// Registrar, when non-nil, enables inbound REGISTER handling and AOR
	// resolution for outbound INVITEs.
	Registrar *Registrar
}

EngineConfig holds configuration for the SIP engine.

type ForkTarget added in v0.8.0

type ForkTarget struct {
	Socket    string // "ip:port"
	Transport string // "udp" | "tcp" | "tls"
}

ForkTarget identifies one branch of a parallel-forked INVITE: an explicit transport-layer destination (ip:port) that overrides the routing implied by the Request-URI's host. Used when dialing a registered AOR with one or more bound contacts; one ForkTarget per bound contact.

type IPIPTrunkView added in v0.9.0

type IPIPTrunkView struct {
	PeerURI string `json:"peer_uri,omitempty"`
}

IPIPTrunkView is the placeholder shape for the unimplemented ip_ip type.

type InboundCall

type InboundCall struct {
	Dialog    *sipgo.DialogServerSession
	From      string    // caller URI user part
	To        string    // callee URI user part
	RemoteSDP *SDPMedia // parsed offer SDP
	Request   *sip.Request

	// Session timer (RFC 4028) — populated when remote requests timers.
	SessionTimer *SessionTimerParams // nil when remote didn't request timers
}

InboundCall wraps a sipgo DialogServerSession with parsed SDP.

type InviteOptions

type InviteOptions struct {
	Codecs       []codec.CodecType                              // Override engine codecs for this call; nil = use engine default
	Headers      []sip.Header                                   // Extra SIP headers to include in the INVITE
	FromUser     string                                         // Override the user part of the From header (caller ID)
	OnEarlyMedia func(remoteSDP *SDPMedia, rtpSess *RTPSession) // Called on first 183 with SDP
	AuthUsername string                                         // SIP digest auth username (optional)
	AuthPassword string                                         // SIP digest auth password (optional)

	// ForkTargets, when non-empty, routes the INVITE to the listed transport
	// addresses. A single target overrides the destination for a single
	// outbound dialog; multiple targets parallel-fork the INVITE and race
	// for the first 2xx (RFC 3261 §16).
	ForkTargets []ForkTarget

	// RouteURI, when set, adds a single loose-route header pointing at the
	// given URI (typically a trunk's upstream proxy/registrar). The ";lr"
	// param is appended if not already present.
	RouteURI *sip.Uri

	// RTT (T.140 / RFC 4103) parameters. RTTEnabled offers m=text alongside
	// audio in the INVITE. RTTRedundancy controls the RFC 2198 RED depth
	// (0 = plain T.140, no RED).
	RTTEnabled    bool
	RTTRedundancy int
}

InviteOptions holds optional parameters for outbound INVITE.

type OutboundCall

type OutboundCall struct {
	Dialog    *sipgo.DialogClientSession
	RemoteSDP *SDPMedia
	RTPSess   *RTPSession

	// Optional RTT (T.140 / RFC 4103) media. Populated when the remote's
	// answer accepts the offered m=text section. Nil otherwise.
	TextRTPSess *RTPSession

	// Session timer (RFC 4028) — populated when remote's 200 OK includes timers.
	SessionTimer *SessionTimerParams // nil when remote didn't include timers
}

OutboundCall wraps a sipgo DialogClientSession with parsed answer SDP.

type OutboundRegistration added in v0.9.0

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

OutboundRegistration is the sip_register Trunk implementation. One instance per trunk; safe for concurrent access.

func NewOutboundRegistration added in v0.9.0

func NewOutboundRegistration(engine *Engine, bus *events.Bus, log *slog.Logger, cfg OutboundRegistrationConfig, p OutboundRegistrationParams) *OutboundRegistration

NewOutboundRegistration constructs a trunk in the registering state. Call Start to launch the background lifecycle.

func (*OutboundRegistration) AOR added in v0.9.0

func (r *OutboundRegistration) AOR() string

func (*OutboundRegistration) AppID added in v0.9.0

func (r *OutboundRegistration) AppID() string

func (*OutboundRegistration) Credentials added in v0.9.0

func (r *OutboundRegistration) Credentials() (string, string)

Credentials returns the trunk's digest username/password. Used by the outbound INVITE path; never exposed over the API.

func (*OutboundRegistration) ID added in v0.9.0

func (r *OutboundRegistration) ID() string

func (*OutboundRegistration) PeerSocket added in v0.9.0

func (r *OutboundRegistration) PeerSocket() (host string, port int, transport string)

func (*OutboundRegistration) RegistrarURI added in v0.9.0

func (r *OutboundRegistration) RegistrarURI() sip.Uri

RegistrarURI exposes the configured upstream registrar URI; used by the outbound INVITE path to attach a Route header.

func (*OutboundRegistration) Snapshot added in v0.9.0

func (r *OutboundRegistration) Snapshot() TrunkView

Snapshot returns the current TrunkView. Safe to call concurrently.

func (*OutboundRegistration) Start added in v0.9.0

func (r *OutboundRegistration) Start(ctx context.Context)

Start launches the background register-and-refresh loop. Calling Start more than once is a no-op.

func (*OutboundRegistration) Stop added in v0.9.0

Stop cancels the refresh loop and sends a final REGISTER with Expires: 0. Best-effort; honours ctx.

func (*OutboundRegistration) Type added in v0.9.0

func (r *OutboundRegistration) Type() TrunkType

type OutboundRegistrationConfig added in v0.9.0

type OutboundRegistrationConfig struct {
	DefaultExpiresSeconds int
	MinExpiresSeconds     int
	MaxExpiresSeconds     int
	RefreshRatio          float64
	FailureBackoffMax     time.Duration
}

OutboundRegistrationConfig holds tunables shared across every sip_register trunk.

type OutboundRegistrationParams added in v0.9.0

type OutboundRegistrationParams struct {
	ID                      string
	AppID                   string
	RegistrarURI            sip.Uri
	AOR                     sip.Uri
	Username                string
	Password                string
	ContactUser             string
	RequestedExpiresSeconds int
}

OutboundRegistrationParams is the per-trunk creation payload (the values that survive validation of a CreateTrunkRequest of type sip_register).

type PortAllocator

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

PortAllocator manages a pool of UDP ports within a configured range. It is safe for concurrent use.

func NewPortAllocator

func NewPortAllocator(min, max int) (*PortAllocator, error)

NewPortAllocator creates a port allocator for the given range [min, max]. Returns nil if min and max are both 0 (use OS-assigned ports).

func (*PortAllocator) Allocate

func (pa *PortAllocator) Allocate() (int, error)

Allocate returns the next available port from the pool.

func (*PortAllocator) Range

func (pa *PortAllocator) Range() (int, int)

Range returns the configured min and max ports.

func (*PortAllocator) Release

func (pa *PortAllocator) Release(port int)

Release returns a port back to the pool.

type RTPSession

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

RTPSession manages a UDP socket for RTP send/receive. It implements symmetric RTP (RFC 4961): once an RTP packet is received, the remote address is latched to the source IP:port of that packet, overriding the SDP-provided address. This is essential for NAT traversal.

func NewRTPSession

func NewRTPSession() (*RTPSession, error)

NewRTPSession creates a new RTP session listening on a random UDP port. The socket is dual-stack on Linux (accepts both v4 and v6 when /proc/sys/net/ipv6/bindv6only is 0, which is the default).

func NewRTPSessionFromAllocator

func NewRTPSessionFromAllocator(alloc *PortAllocator) (*RTPSession, error)

NewRTPSessionFromAllocator creates an RTP session using a port from the allocator's pool. If alloc is nil, behaves like NewRTPSession (OS-assigned).

func NewRTPSessionOnPort

func NewRTPSessionOnPort(port int) (*RTPSession, error)

NewRTPSessionOnPort creates a new RTP session on a specific local port. Same dual-stack semantics as NewRTPSession.

func (*RTPSession) Close

func (s *RTPSession) Close() error

Close closes the UDP connection and releases the port back to the allocator. Idempotent: subsequent calls are no-ops. Multiple rollback paths in the engine ladder can each call Close on the same session.

func (*RTPSession) LocalPort

func (s *RTPSession) LocalPort() int

LocalPort returns the local UDP port this session is listening on.

func (*RTPSession) ReadRTP

func (s *RTPSession) ReadRTP() (*rtp.Packet, error)

ReadRTP reads and unmarshals an RTP packet from the UDP socket. Blocks until data arrives. Implements symmetric RTP: the remote address is latched to the source IP:port of each incoming RTP packet.

func (*RTPSession) SendKeepalive

func (s *RTPSession) SendKeepalive(payloadType uint8, count int)

SendKeepalive sends a small burst of silence RTP packets to the remote address. This is used immediately after SetRemote on outbound calls to punch through NAT devices (port-latching) before the leg's full media pipeline starts.

func (*RTPSession) SetReadDeadline

func (s *RTPSession) SetReadDeadline(t time.Time) error

SetReadDeadline sets a deadline on the underlying UDP socket for reads.

func (*RTPSession) SetRemote

func (s *RTPSession) SetRemote(ip string, port int) error

SetRemote sets the remote address for sending RTP packets. ip may be either an IPv4 or IPv6 literal (or a hostname); IPv6 literals are bracketed via net.JoinHostPort.

func (*RTPSession) WriteRTP

func (s *RTPSession) WriteRTP(pkt *rtp.Packet) error

WriteRTP marshals and sends an RTP packet to the remote address.

type Registrar added in v0.8.0

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

Registrar is the in-memory AOR registry. Safe for concurrent use.

func NewRegistrar added in v0.8.0

func NewRegistrar(bus *events.Bus, log *slog.Logger, cfg RegistrarConfig) *Registrar

NewRegistrar constructs a registrar. bus may be nil (events are dropped).

func (*Registrar) Bind added in v0.8.0

func (r *Registrar) Bind(b Binding)

Bind inserts a new binding or refreshes an existing one (same AOR + Contact identity). When AllowMultipleContacts is false, any pre-existing bindings for the AOR are removed first (one event each, reason "replaced"). Emits sip.registration_active.

func (*Registrar) ClampExpires added in v0.8.0

func (r *Registrar) ClampExpires(requested int) int

ClampExpires clamps a requested expiry to [60, MaxExpiresSeconds]. A non-positive input is treated as the default.

func (*Registrar) Config added in v0.8.0

func (r *Registrar) Config() RegistrarConfig

Config returns the effective configuration (with defaults applied).

func (*Registrar) List added in v0.8.0

func (r *Registrar) List() []Binding

List returns every binding across all AORs. Order is not specified.

func (*Registrar) Lookup added in v0.8.0

func (r *Registrar) Lookup(aor string) (Binding, bool)

Lookup returns the most-recently-refreshed binding for an AOR. The second return value is false when no binding exists.

func (*Registrar) LookupAll added in v0.8.0

func (r *Registrar) LookupAll(aor string) []Binding

LookupAll returns every binding for an AOR, sorted most-recently-refreshed first.

func (*Registrar) Start added in v0.8.0

func (r *Registrar) Start(ctx context.Context)

Start launches the expiry sweeper. Returns immediately; the sweeper runs until ctx is cancelled.

func (*Registrar) UnbindAll added in v0.8.0

func (r *Registrar) UnbindAll(aor, reason string) int

UnbindAll removes every binding under the AOR. One event per binding. Returns the number of removed bindings.

func (*Registrar) UnbindContact added in v0.8.0

func (r *Registrar) UnbindContact(aor, contactURI, reason string) bool

UnbindContact removes a single contact under an AOR. Returns true if a binding was removed.

type RegistrarConfig added in v0.8.0

type RegistrarConfig struct {
	DefaultExpiresSeconds int
	MaxExpiresSeconds     int
	SweepInterval         time.Duration
	AllowMultipleContacts bool
}

RegistrarConfig holds tunables for the registrar.

type ReplacesParams added in v0.3.0

type ReplacesParams struct {
	CallID  string
	ToTag   string
	FromTag string
}

ReplacesParams carries the Replaces header dialog identity (RFC 3891).

func ParseReferTo added in v0.3.0

func ParseReferTo(value string) (string, *ReplacesParams, error)

ParseReferTo extracts the bare URI and (optional) Replaces from a Refer-To.

func (*ReplacesParams) String added in v0.3.0

func (p *ReplacesParams) String() string

String formats as the Replaces value embedded in a Refer-To URI.

type SDPConfig

type SDPConfig struct {
	LocalIP string
	RTPPort int
	Codecs  []codec.CodecType // Offered/supported codecs in preference order

	// Optional RTT (T.140 / RFC 4103) section. Set TextRTPPort != 0 to emit
	// an m=text line in offers/answers/re-INVITEs. TextT140PT and TextREDPT
	// are dynamic payload types; TextREDPT == 0 disables RFC 2198 redundancy.
	// RTTRedundancy is the number of t140/t140/.../t140 generations declared
	// in the RED fmtp.
	TextRTPPort   int
	TextT140PT    uint8
	TextREDPT     uint8
	RTTRedundancy int

	// AMRWBOctetAligned controls the AMR-WB fmtp emitted for an offer/answer:
	// true emits "octet-align=1", false emits no octet-align (RFC 4867 default,
	// bandwidth-efficient). On answers it must echo the peer's negotiated format.
	AMRWBOctetAligned bool

	// AMRWBModeSet, when non-empty (e.g. "0,1,2"), adds a "mode-set=..." AMR-WB
	// fmtp param. Used on answers to echo the peer's negotiated mode-set per
	// RFC 4867; left empty on offers (we accept all modes on receive).
	AMRWBModeSet string

	// AMRNBOctetAligned controls the AMR-NB fmtp emitted for an offer/answer:
	// true emits "octet-align=1", false emits no octet-align (RFC 4867 default,
	// bandwidth-efficient). On answers it must echo the peer's negotiated format.
	AMRNBOctetAligned bool

	// AMRNBModeSet, when non-empty (e.g. "0,4,7"), adds a "mode-set=..." AMR-NB
	// fmtp param. Used on answers to echo the peer's negotiated mode-set per
	// RFC 4867; left empty on offers (we accept all modes on receive).
	AMRNBModeSet string

	// DTMFPT, when non-zero, is the telephone-event (RFC 4733) PT to advertise
	// in the generated SDP. Zero defaults to 101. In answers callers should set
	// this from the remote offer to mirror the offerer's choice.
	DTMFPT uint8

	// DTMFClockRate, when non-zero, is the telephone-event clock rate (Hz) to
	// advertise. Zero defaults to TelephoneEventClockRate(selected codec). In
	// answers callers must set this from the remote offer so RFC 3264
	// offer/answer semantics hold — phones like Fanvil pin telephone-event at
	// 8 kHz regardless of audio codec, and unilaterally upgrading to 16 kHz
	// breaks their DTMF.
	DTMFClockRate int
}

SDPConfig holds local media parameters for SDP generation.

type SDPMedia

type SDPMedia struct {
	RemoteIP      string
	RemotePort    int
	AddressFamily string                     // "IP4" or "IP6" (from c= line); empty if not present
	Codecs        []codec.CodecType          // Codecs from m= line, in offer order
	CodecPTs      map[codec.CodecType]uint8  // Actual PT for each codec from remote SDP
	CodecRates    map[codec.CodecType]int    // Clock rate (Hz) for each codec, from a=rtpmap; falls back to codec default
	CodecFmtp     map[codec.CodecType]string // Raw a=fmtp params for each codec (e.g. AMR-WB "octet-align=1; mode-set=...")
	Ptime         int                        // ms, default 20
	Direction     string                     // "sendrecv", "sendonly", "recvonly", "inactive"; empty = sendrecv
	DTMFEventPTs  map[uint8]int              // telephone-event (RFC 4733) PT -> clock rate, as advertised by the remote

	// Text (RTT, T.140 / RFC 4103). Non-nil when the remote SDP carried an
	// m=text line with a non-zero port. A port of zero (peer rejecting the
	// text section per RFC 3264) leaves this field nil.
	Text *SDPTextMedia
}

SDPMedia holds parsed remote media parameters.

func ParseSDP

func ParseSDP(raw []byte) (*SDPMedia, error)

ParseSDP parses a remote SDP body and extracts media parameters.

func (*SDPMedia) DTMFPTForRate added in v0.9.0

func (m *SDPMedia) DTMFPTForRate(rate int) (uint8, bool)

DTMFPTForRate returns the remote telephone-event payload type advertised at the given clock rate, if any.

func (*SDPMedia) PreferredDTMFEvent added in v0.9.0

func (m *SDPMedia) PreferredDTMFEvent() (pt uint8, rate int, ok bool)

PreferredDTMFEvent returns the telephone-event PT and clock rate to mirror in an answer (RFC 3264 offer/answer). When the remote advertised multiple telephone-event lines, the lowest PT wins for determinism. Returns (0, 0, false) when no telephone-event was offered.

type SDPTextMedia added in v0.6.0

type SDPTextMedia struct {
	RemoteIP   string
	RemotePort int
	T140PT     uint8 // 0 if no t140/1000 advertised
	REDPT      uint8 // 0 if no red/1000 advertised
	Direction  string
}

SDPTextMedia holds parsed remote RTT parameters.

type SIPRegisterTrunkView added in v0.9.0

type SIPRegisterTrunkView struct {
	RegistrarURI            string `json:"registrar_uri"`
	AOR                     string `json:"aor"`
	Username                string `json:"username,omitempty"`
	ContactURI              string `json:"contact_uri,omitempty"`
	RequestedExpiresSeconds int    `json:"requested_expires_seconds"`
	GrantedExpiresSeconds   int    `json:"granted_expires_seconds,omitempty"`
	LastRegisteredAt        string `json:"last_registered_at,omitempty"`
	NextRefreshAt           string `json:"next_refresh_at,omitempty"`
	CallID                  string `json:"call_id,omitempty"`
	CSeq                    uint32 `json:"cseq,omitempty"`
	// SourceAddress is the host:port the registrar's most recent response
	// actually came from. Initially set from the configured RegistrarURI;
	// updated to the real transport address (which may differ from the URI
	// when DNS / load-balancing fronts the registrar) on each 2xx response.
	// Used as the key for tagging inbound INVITEs back to this trunk.
	SourceAddress string `json:"source_address,omitempty"`
}

SIPRegisterTrunkView holds the sip_register-specific runtime fields. Credentials (password) are never exposed.

type SessionTimerParams

type SessionTimerParams struct {
	Interval  uint32 // Session-Expires delta-seconds
	Refresher string // "uac" or "uas"
	MinSE     uint32 // Min-SE value (0 = not present)
}

SessionTimerParams holds parsed Session-Expires / Min-SE values from a SIP request or response.

type Trunk added in v0.9.0

type Trunk interface {
	ID() string
	Type() TrunkType
	// AOR returns the canonical identity used to match outbound POST /v1/legs
	// `from` against this trunk. Empty when the trunk type has no AOR concept.
	AOR() string
	// PeerSocket returns the upstream peer's transport address used to tag
	// inbound INVITEs delivered by this trunk. Returns empty host with port 0
	// when not yet known.
	PeerSocket() (host string, port int, transport string)
	AppID() string
	Snapshot() TrunkView
	// Start launches the background lifecycle (REGISTER + refresh for
	// sip_register). Returns immediately.
	Start(ctx context.Context)
	// Stop tears the trunk down (de-register for sip_register). Best-effort;
	// honours ctx for timeout.
	Stop(ctx context.Context) error
}

Trunk is the abstract resource managed by the TrunkManager. Each concrete type (sip_register, future ip_ip, ...) implements this interface; lookups over the manager are type-agnostic.

type TrunkManager added in v0.9.0

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

TrunkManager is the concurrent registry of all SIP trunks (sip_register today; ip_ip etc. in the future). Lookups are type-agnostic and indexed by id, canonical AOR, and upstream peer socket.

func NewTrunkManager added in v0.9.0

func NewTrunkManager() *TrunkManager

func (*TrunkManager) Add added in v0.9.0

func (m *TrunkManager) Add(t Trunk)

Add inserts a trunk, indexing it by id, AOR (when non-empty), and peer socket (when known).

func (*TrunkManager) Get added in v0.9.0

func (m *TrunkManager) Get(id string) Trunk

Get returns the trunk with the given id, or nil.

func (*TrunkManager) List added in v0.9.0

func (m *TrunkManager) List() []Trunk

List returns every registered trunk in indeterminate order.

func (*TrunkManager) LookupByAORUser added in v0.9.0

func (m *TrunkManager) LookupByAORUser(user string) Trunk

LookupByAORUser returns a trunk whose AOR user-part matches the given user string. Falls back when the caller only supplied a bare user on `from` (e.g. POST /v1/legs with `from: "alice"`) — the engine's publicHost won't match the trunk's upstream domain. Returns nil if no match; if multiple trunks have the same user, returns one arbitrarily.

func (*TrunkManager) LookupByFromAOR added in v0.9.0

func (m *TrunkManager) LookupByFromAOR(aor string) Trunk

LookupByFromAOR returns the trunk whose canonical AOR matches the given URI (after canonicalisation), or nil. The input may be a raw SIP URI string ("sip:alice@host") or already-canonical form.

func (*TrunkManager) LookupByPeerSocket added in v0.9.0

func (m *TrunkManager) LookupByPeerSocket(host string, port int) Trunk

LookupByPeerSocket returns the trunk whose upstream peer matches the given transport address, or nil. Matches first on full host:port, then on host alone (for cases where the peer's ephemeral source port differs from the configured registrar port).

func (*TrunkManager) RefreshIndex added in v0.9.0

func (m *TrunkManager) RefreshIndex(id string)

RefreshIndex re-indexes the trunk under its current AOR and peer socket. Call this after a trunk's PeerSocket() value becomes known (e.g. after the first successful REGISTER reveals the registrar's actual transport addr).

func (*TrunkManager) Remove added in v0.9.0

func (m *TrunkManager) Remove(id string) Trunk

Remove removes a trunk by id (does not Stop it). Returns the removed trunk, or nil if absent.

func (*TrunkManager) Shutdown added in v0.9.0

func (m *TrunkManager) Shutdown(ctx context.Context)

Shutdown stops every trunk in parallel, honouring ctx. After return the manager is empty.

type TrunkStatus added in v0.9.0

type TrunkStatus string

TrunkStatus is the runtime state of a trunk's upstream connection.

const (
	TrunkStatusRegistering   TrunkStatus = "registering"
	TrunkStatusActive        TrunkStatus = "active"
	TrunkStatusFailed        TrunkStatus = "failed"
	TrunkStatusUnregistering TrunkStatus = "unregistering"
	TrunkStatusExpired       TrunkStatus = "expired"
)

type TrunkType added in v0.9.0

type TrunkType string

TrunkType discriminates the upstream connection style of a SIP trunk. Only TrunkTypeSIPRegister is implemented today; other values are reserved for the OpenAPI/AsyncAPI contract and rejected at request time.

const (
	// TrunkTypeSIPRegister: VoiceBlender acts as a UAC and REGISTERs to an
	// upstream registrar, refreshing periodically; calls in either direction
	// flow through that registered identity.
	TrunkTypeSIPRegister TrunkType = "sip_register"

	// TrunkTypeIPIP: reserved for static-IP peering (no REGISTER). Not yet
	// implemented; the trunks handler returns 501 when requested.
	TrunkTypeIPIP TrunkType = "ip_ip"
)

type TrunkView added in v0.9.0

type TrunkView struct {
	ID        string      `json:"id"`
	Type      TrunkType   `json:"type"`
	AppID     string      `json:"app_id,omitempty"`
	Status    TrunkStatus `json:"status"`
	LastError string      `json:"last_error,omitempty"`
	CreatedAt string      `json:"created_at"`

	SIPRegister *SIPRegisterTrunkView `json:"sip_register,omitempty"`
	IPIP        *IPIPTrunkView        `json:"ip_ip,omitempty"`
}

TrunkView is the JSON-friendly snapshot of a trunk's current state. Each type populates its own sub-struct; only one of `SIPRegister` / `IPIP` is non-nil per snapshot.

type WhatsAppInviteOptions added in v0.5.0

type WhatsAppInviteOptions struct {
	// FromNumber is the business phone number in E.164. Used as the From
	// URI user (with leading '+'). When DigestUsername is empty it also
	// serves as the digest auth username (with the '+' stripped, per Meta).
	FromNumber     string
	DigestUsername string // optional override; defaults to FromNumber without '+'
	Password       string
	SDPOffer       []byte
	Headers        []sip.Header
}

type WhatsAppOutboundCall added in v0.5.0

type WhatsAppOutboundCall struct {
	Dialog    *sipgo.DialogClientSession
	AnswerSDP []byte
}

Jump to

Keyboard shortcuts

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