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

164 lines
4.5 KiB
JavaScript

const logger = require('../utils/logger');
const config = require('config');
/**
* Registration Handler - Equivalent to Kamailio's REGISTRAR route
* Handles SIP REGISTER requests and manages user location database
*/
class RegistrationHandler {
constructor(srf) {
this.srf = srf;
this.registry = new Map(); // In-memory registry (equivalent to Kamailio usrloc)
this.config = config.get('registry');
}
/**
* Validate From header before processing (equivalent to kamailio.cfg:635-639)
*/
validateFromHeader(req, res) {
const from = req.getParsedHeader('from');
if (!from || !from.uri || !from.uri.user || !from.uri.host) {
logger.error('[REGISTER] Cannot register user - From header is invalid | Call-ID: %s', req.get('Call-ID'));
res.send(400, 'Bad Request - Invalid From header');
return false;
}
return true;
}
/**
* Get registration expiry from Contact header (equivalent to kamailio.cfg:647-657)
*/
getRegistrationExpiry(req) {
let expires = req.get('Expires');
if (!expires || expires === '') {
expires = this.config.defaultExpires.toString();
}
// Validate expires is a number
if (expires !== '0' && !/^[0-9]+$/.test(expires)) {
expires = this.config.defaultExpires.toString();
}
const expiresNum = parseInt(expires);
// Validate expiry range
if (expiresNum > this.config.maxExpires) {
return this.config.maxExpires;
}
if (expiresNum < this.config.minExpires && expiresNum !== 0) {
return this.config.minExpires;
}
return expiresNum;
}
/**
* Save registration to registry (equivalent to kamailio.cfg:644)
*/
saveToRegistry(from, contact, expires, sourceIp, sourcePort) {
const key = `${from.uri.user}@${from.uri.host}`;
const registration = {
aor: key,
contact: contact,
expires: expires,
registeredAt: Date.now(),
sourceIp: sourceIp,
sourcePort: sourcePort,
callId: from.params.tag || 'unknown'
};
if (expires === 0) {
// Remove registration (unregister)
this.registry.delete(key);
logger.info('[REGISTER] User %s unregistered | From: %s:%s', key, sourceIp, sourcePort);
} else {
// Add/update registration
this.registry.set(key, registration);
logger.info('[REGISTER] User %s registered | Contact: %s | Expires: %ds | From: %s:%s',
key, contact, expires, sourceIp, sourcePort);
}
}
/**
* Handle REGISTER request (equivalent to kamailio.cfg:630-676)
*/
handleRegister(req, res) {
const callId = req.get('Call-ID');
const sourceIp = req.source_address;
const sourcePort = req.source_port;
logger.info('[REGISTER] Registration request from %s:%s | Call-ID: %s', sourceIp, sourcePort, callId);
// Validate From header
if (!this.validateFromHeader(req, res)) {
return;
}
// Get registration details
const from = req.getParsedHeader('from');
const contact = req.get('Contact');
const expires = this.getRegistrationExpiry(req);
// Save to registry
this.saveToRegistry(from, contact, expires, sourceIp, sourcePort);
// Send response
if (expires === 0) {
res.send(200, 'OK - Unregistered from Cyanet VoIP system.');
} else {
res.send(200, 'OK - Welcome to Cyanet VoIP system.');
}
}
/**
* Lookup user in registry (equivalent to Kamailio's lookup() function)
*/
lookup(aor) {
return this.registry.get(aor);
}
/**
* Get all registered users
*/
getAllRegistrations() {
return Array.from(this.registry.values());
}
/**
* Clean expired registrations
*/
cleanExpiredRegistrations() {
const now = Date.now();
const expiredKeys = [];
for (const [key, registration] of this.registry.entries()) {
const expiryTime = registration.registeredAt + (registration.expires * 1000);
if (registration.expires > 0 && expiryTime <= now) {
expiredKeys.push(key);
}
}
for (const key of expiredKeys) {
this.registry.delete(key);
logger.info('[REGISTER] Expired registration removed: %s', key);
}
return expiredKeys.length;
}
/**
* Start registration cleanup timer
*/
startCleanupTimer() {
// Clean expired registrations every 60 seconds (equivalent to Kamailio timer_interval)
setInterval(() => {
const cleaned = this.cleanExpiredRegistrations();
if (cleaned > 0) {
logger.info('[REGISTER] Cleaned %d expired registrations', cleaned);
}
}, 60000);
}
}
module.exports = RegistrationHandler;