Files
siprouter/ts/sip/readme.md
Juergen Kunz f3e1c96872 initial commit — SIP B2BUA + WebRTC bridge with Rust codec engine
Full-featured SIP router with multi-provider trunking, browser softphone
via WebRTC, real-time Opus/G.722/PCM transcoding in Rust, RNNoise ML
noise suppression, Kokoro neural TTS announcements, and a Lit-based
web dashboard with live call monitoring and REST API.
2026-04-09 23:03:55 +00:00

6.0 KiB

ts/sip — SIP Protocol Library

A zero-dependency SIP (Session Initiation Protocol) library for Deno / Node. Provides parsing, construction, mutation, and dialog management for SIP messages, plus helpers for SDP bodies and URI rewriting.

Modules

File Purpose
message.ts SipMessage class — parse, inspect, mutate, serialize
dialog.ts SipDialog class — track dialog state, build in-dialog requests
helpers.ts ID generators, codec registry, SDP builder/parser
rewrite.ts SIP URI and SDP body rewriting
types.ts Shared types (IEndpoint)
index.ts Barrel re-export

Quick Start

import {
  SipMessage,
  SipDialog,
  buildSdp,
  parseSdpEndpoint,
  rewriteSipUri,
  rewriteSdp,
  generateCallId,
  generateTag,
  generateBranch,
} from './sip/index.ts';

SipMessage

Parsing

import { Buffer } from 'node:buffer';

const raw = Buffer.from(
  'INVITE sip:user@example.com SIP/2.0\r\n' +
  'Via: SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776\r\n' +
  'From: <sip:alice@example.com>;tag=abc\r\n' +
  'To: <sip:bob@example.com>\r\n' +
  'Call-ID: a84b4c76e66710@10.0.0.1\r\n' +
  'CSeq: 1 INVITE\r\n' +
  'Content-Length: 0\r\n\r\n'
);

const msg = SipMessage.parse(raw);
// msg.method        → "INVITE"
// msg.isRequest     → true
// msg.callId        → "a84b4c76e66710@10.0.0.1"
// msg.cseqMethod    → "INVITE"
// msg.isDialogEstablishing → true

Fluent mutation

All setter methods return this for chaining:

const buf = SipMessage.parse(raw)!
  .setHeader('Contact', '<sip:proxy@192.168.1.1:5070>')
  .prependHeader('Record-Route', '<sip:192.168.1.1:5070;lr>')
  .updateContentLength()
  .serialize();

Building requests from scratch

const invite = SipMessage.createRequest('INVITE', 'sip:+4930123@voip.example.com', {
  via: { host: '192.168.5.66', port: 5070 },
  from: { uri: 'sip:alice@example.com', displayName: 'Alice' },
  to: { uri: 'sip:+4930123@voip.example.com' },
  contact: '<sip:192.168.5.66:5070>',
  body: sdpBody,
  contentType: 'application/sdp',
});
// Call-ID, From tag, Via branch are auto-generated if not provided.

Building responses

const ok = SipMessage.createResponse(200, 'OK', incomingInvite, {
  toTag: generateTag(),
  contact: '<sip:192.168.5.66:5070>',
  body: answerSdp,
  contentType: 'application/sdp',
});

Inspectors

Property Type Description
isRequest boolean True for requests (INVITE, BYE, ...)
isResponse boolean True for responses (SIP/2.0 200 OK, ...)
method string | null Request method or null
statusCode number | null Response status code or null
callId string Call-ID header value
cseqMethod string | null Method from CSeq header
requestUri string | null Request-URI (second token of start line)
isDialogEstablishing boolean INVITE, SUBSCRIBE, REFER, NOTIFY, UPDATE
hasSdpBody boolean Body present with Content-Type: application/sdp

Static helpers

SipMessage.extractTag('<sip:alice@x.com>;tag=abc')   // → "abc"
SipMessage.extractUri('"Alice" <sip:alice@x.com>')   // → "sip:alice@x.com"

SipDialog

Tracks dialog state per RFC 3261 §12. A dialog is created from a dialog-establishing request and updated as responses arrive.

UAC (caller) side

// 1. Build and send INVITE
const invite = SipMessage.createRequest('INVITE', destUri, { ... });
const dialog = SipDialog.fromUacInvite(invite, '192.168.5.66', 5070);

// 2. Process responses as they arrive
dialog.processResponse(trying100);   // state stays 'early'
dialog.processResponse(ringing180);  // state stays 'early', remoteTag learned
dialog.processResponse(ok200);       // state → 'confirmed'

// 3. ACK the 200
const ack = dialog.createAck();

// 4. In-dialog requests
const bye = dialog.createRequest('BYE');
dialog.terminate();

UAS (callee) side

const dialog = SipDialog.fromUasInvite(incomingInvite, generateTag(), localHost, localPort);

CANCEL (before answer)

const cancel = dialog.createCancel(originalInvite);

Dialog states

'early''confirmed''terminated'

Helpers

ID generation

generateCallId()               // → "a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5"
generateCallId('example.com')  // → "a3f8b2c1...@example.com"
generateTag()                  // → "1a2b3c4d5e6f7a8b"
generateBranch()               // → "z9hG4bK-1a2b3c4d5e6f7a8b"

SDP builder

const sdp = buildSdp({
  ip: '192.168.5.66',
  port: 20000,
  payloadTypes: [9, 0, 8, 101],  // G.722, PCMU, PCMA, telephone-event
  direction: 'sendrecv',
});

SDP parser

const ep = parseSdpEndpoint(sdpBody);
// → { address: '10.0.0.1', port: 20000 } or null

Codec names

codecName(9)   // → "G722/8000"
codecName(0)   // → "PCMU/8000"
codecName(101) // → "telephone-event/8000"

Rewriting

SIP URI

Replaces the host:port in all sip: / sips: URIs found in a header value:

rewriteSipUri('<sip:user@10.0.0.1:5060>', '203.0.113.1', 5070)
// → '<sip:user@203.0.113.1:5070>'

SDP body

Rewrites the connection address and audio media port, returning the original endpoint that was replaced:

const { body, original } = rewriteSdp(sdpBody, '203.0.113.1', 20000);
// original → { address: '10.0.0.1', port: 8000 }

Architecture Notes

This library is intentionally low-level — it operates on individual messages and dialogs rather than providing a full SIP stack with transport and transaction layers. This makes it suitable for building:

  • SIP proxies — parse, rewrite headers/SDP, serialize, forward
  • B2BUA (back-to-back user agents) — manage two dialogs, bridge media
  • SIP testing tools — craft and send arbitrary messages
  • Protocol analyzers — parse and inspect SIP traffic

The library does not manage sockets, timers, or retransmissions — those concerns belong to the application layer.