Commit 6408b7a for jssip.net
commit 6408b7a3bb82f3ae352621af1b4dbf6abd09c0a0
Author: José Luis Millán <jmillan@aliax.net>
Date: Wed Oct 21 11:21:41 2026 +0200
TS: sanityCheck (#990)
diff --git a/src/UA.js b/src/UA.js
index 571d6a9..d34284c 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 5ff60ac..2e5a9b8 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<M extends IncomingMessage> = (
+ message: M,
+ transport: Transport,
+ ua: SanityCheckUA
+) => boolean;
+
// Checks for requests and responses.
-const all = [minimumHeaders];
+const all: Check<IncomingMessage>[] = [minimumHeaders];
// Checks for requests.
-const requests = [
+const requests: Check<IncomingRequest>[] = [
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<IncomingResponse>[] = [
+ 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`;