const logger = require('../utils/logger'); const RequestValidator = require('./validation'); const UserAgentFilter = require('./userAgentFilter'); /** * Request Processing Pipeline - Equivalent to Kamailio's main request_route * This is the primary entry point for all SIP requests received by Drachtio */ class RequestProcessor { constructor(srf) { this.srf = srf; this.validator = new RequestValidator(); this.uaFilter = new UserAgentFilter(); } /** * Handle OPTIONS requests (equivalent to kamailio.cfg:297-301) */ handleOptions(req, res) { logger.info('[OPTIONS] Options request from %s:%s | Call-ID: %s', req.source_address, req.source_port, req.get('Call-ID')); res.send(200, 'OK'); } /** * Handle CANCEL requests (equivalent to kamailio.cfg:196-201) */ handleCancel(req, res) { const callId = req.get('Call-ID'); logger.info('[CANCEL] CANCEL: Call-ID: %s', callId); // Check if transaction exists if (this.srf.hasUacTransaction(callId)) { // Relay the CANCEL to the appropriate destination this.srf.createUacRequest('CANCEL', req.uri, { headers: { 'Call-ID': callId, 'CSeq': req.get('CSeq'), 'From': req.get('From'), 'To': req.get('To'), 'Via': req.get('Via'), 'Route': req.get('Route'), 'Max-Forwards': req.get('Max-Forwards') } }) .then(() => { res.send(200, 'OK'); }) .catch((err) => { logger.error('[CANCEL] Failed to relay CANCEL: %s', err.message); res.send(500, 'Server Error'); }); } else { res.send(481, 'Transaction Does Not Exist'); } } /** * Handle in-dialog requests (equivalent to kamailio.cfg:239-241) */ handleInDialog(req, res, next) { if (req.has('To') && req.getParsedHeader('to').params.tag && req.method !== 'INVITE') { logger.info('[WITHINDLG] %s request | Call-ID: %s', req.method, req.get('Call-ID')); // Pass to in-dialog handler next(); } else { next(); } } /** * Handle presence-related requests (equivalent to kamailio.cfg:304) */ handlePresence(req, res) { if (req.method === 'PUBLISH' || req.method === 'SUBSCRIBE') { logger.info('[PRESENCE] %s request from %s:%s | Call-ID: %s', req.method, req.source_address, req.source_port, req.get('Call-ID')); res.send(404, 'Not here'); return true; } return false; } /** * Handle requests that don't match specific routes (equivalent to kamailio.cfg:312-315) */ handleUnmatchedRequest(req, res) { logger.warn('[MAIN_ROUTE] No specific route found for method: %s to %s | Call-ID: %s', req.method, req.uri.user, req.get('Call-ID')); res.send(501, 'Not Implemented - No action found for destination or method'); } /** * Main request processing pipeline */ processRequest() { return (req, res, next) => { logger.debug('[REQUEST_PROCESSOR] Processing %s request from %s:%s', req.method, req.source_address, req.source_port); // Apply validation middleware this.validator.middleware()(req, res, () => { // Apply User-Agent filtering this.uaFilter.middleware()(req, res, () => { // Handle specific method types switch (req.method) { case 'OPTIONS': this.handleOptions(req, res); break; case 'CANCEL': this.handleCancel(req, res); break; case 'REGISTER': // Pass to registration handler next(); break; case 'INVITE': // Pass to INVITE handler next(); break; default: // Handle in-dialog requests this.handleInDialog(req, res, () => { // Check for presence requests if (!this.handlePresence(req, res)) { // If no specific handler, return error this.handleUnmatchedRequest(req, res); } }); break; } }); }); }; } } module.exports = RequestProcessor;