From 656019e5b2e6bf46cf6a59ff0f5049d8c4da4904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Mill=C3=A1n?= Date: Wed, 21 Oct 2026 11:15:50 +0200 Subject: [PATCH] TS: sanityCheck --- src/UA.js | 2 +- src/{sanityCheck.js => sanityCheck.ts} | 186 +++++++++++++++---------- 2 files changed, 113 insertions(+), 75 deletions(-) rename src/{sanityCheck.js => sanityCheck.ts} (50%) diff --git a/src/UA.js b/src/UA.js index 571d6a94..d34284c4 100644 --- a/src/UA.js +++ b/src/UA.js @@ -16,7 +16,7 @@ const Exceptions = require('./Exceptions'); const URI = require('./URI'); const Parser = require('./Parser'); const SIPMessage = require('./SIPMessage'); -const sanityCheck = require('./sanityCheck'); +const { sanityCheck } = require('./sanityCheck'); const config = require('./Configuration'); const logger = new Logger('UA'); diff --git a/src/sanityCheck.js b/src/sanityCheck.ts similarity index 50% rename from src/sanityCheck.js rename to src/sanityCheck.ts index 5ff60ac5..2e5a9b81 100644 --- a/src/sanityCheck.js +++ b/src/sanityCheck.ts @@ -1,15 +1,38 @@ -const { Logger } = require('./Logger'); -const JsSIP_C = require('./Constants'); -const SIPMessage = require('./SIPMessage'); -const Utils = require('./Utils'); +import * as JsSIP_C from './Constants'; +import { Logger } from './Logger'; +import { IncomingRequest, IncomingResponse } from './SIPMessage'; +import type { IncomingMessage, UALike } from './SIPMessage'; +import type { TransactionRegistry } from './Transactions'; +import type { Transport } from './Transport'; +import * as Utils from './Utils'; const logger = new Logger('sanityCheck'); +/** + * The `UA` surface the sanity checks depend on. + * + * TODO: Replace with `import type { UA } from './UA'` once `UA` is ported to + * TypeScript. + */ +export interface SanityCheckUA extends UALike { + readonly _transactions: TransactionRegistry; +} + +/** + * A single check. Returns `false` to drop the message, `true` to let it + * through. + */ +type Check = ( + message: M, + transport: Transport, + ua: SanityCheckUA +) => boolean; + // Checks for requests and responses. -const all = [minimumHeaders]; +const all: Check[] = [minimumHeaders]; // Checks for requests. -const requests = [ +const requests: Check[] = [ rfc3261_8_2_2_1, rfc3261_16_3_4, rfc3261_18_3_request, @@ -17,33 +40,31 @@ const requests = [ ]; // Checks for responses. -const responses = [rfc3261_8_1_3_3, rfc3261_18_3_response]; - -// local variables. -let message; -let ua; -let transport; - -module.exports = (m, u, t) => { - message = m; - ua = u; - transport = t; +const responses: Check[] = [ + rfc3261_8_1_3_3, + rfc3261_18_3_response, +]; +export const sanityCheck = ( + message: IncomingRequest | IncomingResponse, + ua: SanityCheckUA, + transport: Transport +): boolean => { for (const check of all) { - if (check() === false) { + if (!check(message, transport, ua)) { return false; } } - if (message instanceof SIPMessage.IncomingRequest) { + if (message instanceof IncomingRequest) { for (const check of requests) { - if (check() === false) { + if (!check(message, transport, ua)) { return false; } } - } else if (message instanceof SIPMessage.IncomingResponse) { + } else if (message instanceof IncomingResponse) { for (const check of responses) { - if (check() === false) { + if (!check(message, transport, ua)) { return false; } } @@ -73,44 +94,63 @@ module.exports = (m, u, t) => { */ // Sanity Check functions for requests. -function rfc3261_8_2_2_1() { - if (message.s('to').uri.scheme !== 'sip') { - reply(416); +function rfc3261_8_2_2_1( + message: IncomingRequest, + transport: Transport +): boolean { + if (message.to.uri.scheme !== 'sip') { + reply(message, transport, 416); return false; } + + return true; } -function rfc3261_16_3_4() { +function rfc3261_16_3_4( + message: IncomingRequest, + transport: Transport, + ua: SanityCheckUA +): boolean { if (!message.to_tag) { - if (message.call_id.substr(0, 5) === ua.configuration.jssip_id) { - reply(482); + if (message.call_id?.substring(0, 5) === ua.configuration.jssip_id) { + reply(message, transport, 482); return false; } } + + return true; } -function rfc3261_18_3_request() { +function rfc3261_18_3_request( + message: IncomingRequest, + transport: Transport +): boolean { const len = Utils.str_utf8_length(message.body); - const contentLength = message.getHeader('content-length'); + const contentLength = Number(message.getHeader('content-length')); if (len < contentLength) { - reply(400); + reply(message, transport, 400); return false; } + + return true; } -function rfc3261_8_2_2_2() { +function rfc3261_8_2_2_2( + message: IncomingRequest, + transport: Transport, + ua: SanityCheckUA +): boolean { const fromTag = message.from_tag; const call_id = message.call_id; const cseq = message.cseq; - let tr; // Accept any in-dialog request. if (message.to_tag) { - return; + return true; } // INVITE request. @@ -118,28 +158,23 @@ function rfc3261_8_2_2_2() { // If the branch matches the key of any IST then assume it is a retransmission // and ignore the INVITE. // TODO: we should reply the last response. - if (ua._transactions.ist[message.via_branch]) { + // TODO: A Via with no `branch` parameter keys on the literal string + // 'undefined', so two such requests look like retransmissions of each + // other. Kept as is to preserve behaviour. + if (ua._transactions.ist[String(message.via_branch)]) { return false; } // Otherwise check whether it is a merged request. else { - for (const transaction in ua._transactions.ist) { + for (const tr of Object.values(ua._transactions.ist)) { if ( - Object.prototype.hasOwnProperty.call( - ua._transactions.ist, - transaction - ) + tr.request.from_tag === fromTag && + tr.request.call_id === call_id && + tr.request.cseq === cseq ) { - tr = ua._transactions.ist[transaction]; - if ( - tr.request.from_tag === fromTag && - tr.request.call_id === call_id && - tr.request.cseq === cseq - ) { - reply(482); - - return false; - } + reply(message, transport, 482); + + return false; } } } @@ -150,33 +185,30 @@ function rfc3261_8_2_2_2() { // If the branch matches the key of any NIST then assume it is a retransmission // and ignore the request. // TODO: we should reply the last response. - else if (ua._transactions.nist[message.via_branch]) { + else if (ua._transactions.nist[String(message.via_branch)]) { return false; } // Otherwise check whether it is a merged request. else { - for (const transaction in ua._transactions.nist) { + for (const tr of Object.values(ua._transactions.nist)) { if ( - Object.prototype.hasOwnProperty.call(ua._transactions.nist, transaction) + tr.request.from_tag === fromTag && + tr.request.call_id === call_id && + tr.request.cseq === cseq ) { - tr = ua._transactions.nist[transaction]; - if ( - tr.request.from_tag === fromTag && - tr.request.call_id === call_id && - tr.request.cseq === cseq - ) { - reply(482); + reply(message, transport, 482); - return false; - } + return false; } } } + + return true; } // Sanity Check functions for responses. -function rfc3261_8_1_3_3() { +function rfc3261_8_1_3_3(message: IncomingResponse): boolean { if (message.getHeaders('via').length > 1) { logger.debug( 'more than one Via header field present in the response, dropping the response' @@ -184,11 +216,13 @@ function rfc3261_8_1_3_3() { return false; } + + return true; } -function rfc3261_18_3_response() { +function rfc3261_18_3_response(message: IncomingResponse): boolean { const len = Utils.str_utf8_length(message.body), - contentLength = message.getHeader('content-length'); + contentLength = Number(message.getHeader('content-length')); if (len < contentLength) { logger.debug( @@ -197,10 +231,12 @@ function rfc3261_18_3_response() { return false; } + + return true; } // Sanity Check functions for requests and responses. -function minimumHeaders() { +function minimumHeaders(message: IncomingMessage): boolean { const mandatoryHeaders = ['from', 'to', 'call_id', 'cseq', 'via']; for (const header of mandatoryHeaders) { @@ -212,26 +248,28 @@ function minimumHeaders() { return false; } } + + return true; } // Reply. -function reply(status_code) { +function reply( + message: IncomingRequest, + transport: Transport, + status_code: number +): void { const vias = message.getHeaders('via'); - let to; let response = `SIP/2.0 ${status_code} ${JsSIP_C.REASON_PHRASE[status_code]}\r\n`; for (const via of vias) { response += `Via: ${via}\r\n`; } - to = message.getHeader('To'); - - if (!message.to_tag) { - to += `;tag=${Utils.newTag()}`; - } + const to = message.getHeader('To'); + const toTag = message.to_tag ? '' : `;tag=${Utils.newTag()}`; - response += `To: ${to}\r\n`; + response += `To: ${to}${toTag}\r\n`; response += `From: ${message.getHeader('From')}\r\n`; response += `Call-ID: ${message.call_id}\r\n`; response += `CSeq: ${message.cseq} ${message.method}\r\n`;