# 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 ```ts import { SipMessage, SipDialog, buildSdp, parseSdpEndpoint, rewriteSipUri, rewriteSdp, generateCallId, generateTag, generateBranch, } from './sip/index.ts'; ``` ## SipMessage ### Parsing ```ts 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: ;tag=abc\r\n' + 'To: \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: ```ts const buf = SipMessage.parse(raw)! .setHeader('Contact', '') .prependHeader('Record-Route', '') .updateContentLength() .serialize(); ``` ### Building requests from scratch ```ts 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: '', body: sdpBody, contentType: 'application/sdp', }); // Call-ID, From tag, Via branch are auto-generated if not provided. ``` ### Building responses ```ts const ok = SipMessage.createResponse(200, 'OK', incomingInvite, { toTag: generateTag(), contact: '', 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 ```ts SipMessage.extractTag(';tag=abc') // → "abc" SipMessage.extractUri('"Alice" ') // → "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 ```ts // 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 ```ts const dialog = SipDialog.fromUasInvite(incomingInvite, generateTag(), localHost, localPort); ``` ### CANCEL (before answer) ```ts const cancel = dialog.createCancel(originalInvite); ``` ### Dialog states `'early'` → `'confirmed'` → `'terminated'` ## Helpers ### ID generation ```ts generateCallId() // → "a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5" generateCallId('example.com') // → "a3f8b2c1...@example.com" generateTag() // → "1a2b3c4d5e6f7a8b" generateBranch() // → "z9hG4bK-1a2b3c4d5e6f7a8b" ``` ### SDP builder ```ts 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 ```ts const ep = parseSdpEndpoint(sdpBody); // → { address: '10.0.0.1', port: 20000 } or null ``` ### Codec names ```ts 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: ```ts rewriteSipUri('', '203.0.113.1', 5070) // → '' ``` ### SDP body Rewrites the connection address and audio media port, returning the original endpoint that was replaced: ```ts 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.