Files
node-sbc/routes/indialog.js
2025-10-09 16:37:27 +02:00

248 lines
7.4 KiB
JavaScript

const logger = require('../utils/logger');
/**
* In-Dialog Handler - Equivalent to Kamailio's WITHINDLG route
* Handles SIP requests within established dialogs (BYE, re-INVITE, etc.)
*/
class InDialogHandler {
constructor(srf) {
this.srf = srf;
}
/**
* Handle media teardown for BYE/CANCEL (equivalent to kamailio.cfg:494-497)
*/
handleMediaTeardown(req) {
if (req.method === 'BYE' || req.method === 'CANCEL') {
logger.info('[WITHINDLG] Media teardown for %s | Call-ID: %s', req.method, req.get('Call-ID'));
// RTP proxy cleanup would go here
}
}
/**
* Handle media setup for ACK (equivalent to kamailio.cfg:500-502)
*/
handleMediaSetup(req) {
if (req.method === 'ACK') {
logger.info('[WITHINDLG] Media setup for ACK | Call-ID: %s', req.get('Call-ID'));
// RTP proxy setup would go here
}
}
/**
* Process loose route headers (equivalent to kamailio.cfg:506-561)
*/
processLooseRoute(req, res) {
const routeHeaders = req.get('Route');
if (!routeHeaders) {
return false;
}
logger.info('[WITHINDLG] Route headers processed | Call-ID: %s', req.get('Call-ID'));
// Process Route headers to determine next hop
const routes = Array.isArray(routeHeaders) ? routeHeaders : [routeHeaders];
const nextRoute = routes[0];
if (nextRoute.includes(this.srf.localAddress)) {
// Route points back to us - check if user is registered locally
const aor = `${req.uri.user}@${req.uri.host}`;
const registration = this.srf.registrationHandler?.lookup(aor);
if (registration) {
logger.info('[WITHINDLG] Loose route resolved to local user, forwarding | Call-ID: %s', req.get('Call-ID'));
return this.forwardToUser(req, res, registration);
} else {
logger.warn('[WITHINDLG] Loose route points to us but user not found locally | Call-ID: %s', req.get('Call-ID'));
res.send(404, 'Not here');
return true;
}
} else {
// Route is to a different destination, process normally
logger.info('[WITHINDLG] Forwarding to different destination | Call-ID: %s', req.get('Call-ID'));
return this.forwardToDestination(req, res, nextRoute);
}
}
/**
* Forward request to registered user
*/
async forwardToUser(req, res, registration) {
try {
const dlg = await this.srf.createRequest(req.method, registration.contact, {
headers: {
'From': req.get('From'),
'To': req.get('To'),
'Call-ID': req.get('Call-ID'),
'CSeq': req.get('CSeq'),
'Contact': req.get('Contact'),
'Content-Type': req.get('Content-Type'),
'Via': req.get('Via'),
'Route': req.get('Route'),
'Max-Forwards': req.get('Max-Forwards')
},
body: req.body
});
logger.info('[WITHINDLG] Successfully forwarded to user | Call-ID: %s', req.get('Call-ID'));
// Send response back to originator
res.send(200, 'OK');
// Relay responses
this.relayResponses(dlg, req, res);
} catch (error) {
logger.error('[WITHINDLG] Failed to forward to user: %s | Call-ID: %s', error.message, req.get('Call-ID'));
res.send(500, 'Server Error');
}
}
/**
* Forward request to destination
*/
async forwardToDestination(req, res, destination) {
try {
const dlg = await this.srf.createRequest(req.method, destination, {
headers: {
'From': req.get('From'),
'To': req.get('To'),
'Call-ID': req.get('Call-ID'),
'CSeq': req.get('CSeq'),
'Contact': req.get('Contact'),
'Content-Type': req.get('Content-Type'),
'Via': req.get('Via'),
'Route': req.get('Route'),
'Max-Forwards': req.get('Max-Forwards')
},
body: req.body
});
logger.info('[WITHINDLG] Successfully forwarded to destination | Call-ID: %s', req.get('Call-ID'));
// Send response back to originator
res.send(200, 'OK');
// Relay responses
this.relayResponses(dlg, req, res);
} catch (error) {
logger.error('[WITHINDLG] Failed to forward to destination: %s | Call-ID: %s', error.message, req.get('Call-ID'));
res.send(500, 'Server Error');
}
}
/**
* Relay responses between dialogs
*/
relayResponses(uacDialog, uasReq, uasRes) {
// Relay UAC responses back to UAS
uacDialog.on('response', (response) => {
uasRes.send(response.status, response.reason, response.headers);
});
// Handle UAS responses to UAC
uasRes.on('response', (response) => {
uacDialog.send(response.status, response.reason, response.headers);
});
}
/**
* Handle BYE request (equivalent to kamailio.cfg:508-511)
*/
handleBye(req, res) {
logger.info('[WITHINDLG] BYE request | Call-ID: %s', req.get('Call-ID'));
// Handle media teardown
this.handleMediaTeardown(req);
// Process routing
if (this.processLooseRoute(req, res)) {
return;
}
// If no loose route, try to find the target user
const aor = `${req.uri.user}@${req.uri.host}`;
const registration = this.srf.registrationHandler?.lookup(aor);
if (registration) {
logger.info('[WITHINDLG] BYE: Target user found locally, forwarding | Call-ID: %s', req.get('Call-ID'));
this.forwardToUser(req, res, registration);
} else {
logger.warn('[WITHINDLG] BYE: Target user not found locally | Call-ID: %s', req.get('Call-ID'));
res.send(404, 'Not here');
}
}
/**
* Handle ACK request (equivalent to kamailio.cfg:611-621)
*/
handleAck(req, res) {
logger.info('[WITHINDLG] ACK request | Call-ID: %s', req.get('Call-ID'));
// Handle media setup
this.handleMediaSetup(req);
// ACK is typically just absorbed
res.send(200, 'OK');
}
/**
* Handle in-dialog SUBSCRIBE (equivalent to kamailio.cfg:606-610)
*/
handleSubscribe(req, res) {
logger.info('[WITHINDLG] SUBSCRIBE request | Call-ID: %s', req.get('Call-ID'));
res.send(404, 'Not here');
}
/**
* Main in-dialog request handler (equivalent to kamailio.cfg:489-627)
*/
async handleInDialog(req, res) {
const method = req.method;
const callId = req.get('Call-ID');
logger.info('[WITHINDLG] %s request | Call-ID: %s', method, callId);
// Handle media-related operations
if (method === 'BYE' || method === 'CANCEL') {
this.handleMediaTeardown(req);
} else if (method === 'ACK') {
this.handleMediaSetup(req);
}
// Process based on method
switch (method) {
case 'BYE':
this.handleBye(req, res);
break;
case 'ACK':
this.handleAck(req, res);
break;
case 'SUBSCRIBE':
this.handleSubscribe(req, res);
break;
default:
// For other methods, try to process Route headers
if (!this.processLooseRoute(req, res)) {
// No route found, try direct forwarding
logger.info('[WITHINDLG] No route headers, forwarding to destination | Call-ID: %s', callId);
const aor = `${req.uri.user}@${req.uri.host}`;
const registration = this.srf.registrationHandler?.lookup(aor);
if (registration) {
this.forwardToUser(req, res, registration);
} else {
logger.warn('[WITHINDLG] No route found for %s | Call-ID: %s', method, callId);
res.send(404, 'Not here');
}
}
break;
}
}
}
module.exports = InDialogHandler;