merge of features from hosted fork (#9)

major merge of features from the hosted branch that was created temporarily during the initial launch of jambonz.org
This commit is contained in:
Dave Horton
2021-06-17 16:49:37 -04:00
committed by GitHub
parent e3963bf057
commit c324f0425b
17 changed files with 1293 additions and 1029 deletions

124
app.js
View File

@@ -5,28 +5,38 @@ assert.ok(process.env.JAMBONES_MYSQL_HOST &&
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars'); process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env var'); assert.ok(process.env.DRACHTIO_PORT || process.env.DRACHTIO_HOST, 'missing DRACHTIO_PORT env var');
assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var'); assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
assert.ok(process.env.JAMBONES_RTPENGINES, 'missing DRACHTIO_SECRET env var'); assert.ok(process.env.JAMBONES_TIME_SERIES_HOST, 'missing JAMBONES_TIME_SERIES_HOST env var');
const Srf = require('drachtio-srf'); const Srf = require('drachtio-srf');
const srf = new Srf('sbc-inbound'); const srf = new Srf('sbc-inbound');
const opts = Object.assign({ const opts = Object.assign({
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;} timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}
}, {level: process.env.JAMBONES_LOGLEVEL || 'info'}); }, {level: process.env.JAMBONES_LOGLEVEL || 'info'});
const logger = require('pino')(opts); const logger = require('pino')(opts);
const StatsCollector = require('@jambonz/stats-collector'); const {
const stats = srf.locals.stats = new StatsCollector(logger); queryCdrs,
srf.locals.getFeatureServer = require('./lib/fs-tracking')(srf, logger); writeCdrs,
const {getRtpEngine} = require('@jambonz/rtpengine-utils')(process.env.JAMBONES_RTPENGINES.split(','), logger, { writeAlerts,
emitter: srf.locals.stats AlertType
} = require('@jambonz/time-series')(logger, {
host: process.env.JAMBONES_TIME_SERIES_HOST,
commitSize: 50,
commitInterval: 'test' === process.env.NODE_ENV ? 7 : 20
}); });
srf.locals.getRtpEngine = getRtpEngine; const StatsCollector = require('@jambonz/stats-collector');
const activeCallIds = srf.locals.activeCallIds = new Map(); const stats = new StatsCollector(logger);
logger.info('starting..'); const setNameRtp = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-rtp`;
const rtpServers = [];
const { const {
pool,
lookupAuthHook, lookupAuthHook,
lookupSipGatewayBySignalingAddress, lookupSipGatewayBySignalingAddress,
addSbcAddress addSbcAddress,
lookupAccountByPhoneNumber,
lookupAppByTeamsTenant,
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid
} = require('@jambonz/db-helpers')({ } = require('@jambonz/db-helpers')({
host: process.env.JAMBONES_MYSQL_HOST, host: process.env.JAMBONES_MYSQL_HOST,
user: process.env.JAMBONES_MYSQL_USER, user: process.env.JAMBONES_MYSQL_USER,
@@ -34,12 +44,46 @@ const {
database: process.env.JAMBONES_MYSQL_DATABASE, database: process.env.JAMBONES_MYSQL_DATABASE,
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10 connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
}, logger); }, logger);
const {createSet, retrieveSet, incrKey, decrKey} = require('@jambonz/realtimedb-helpers')({
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
srf.locals.dbHelpers = { const {getRtpEngine, setRtpEngines} = require('@jambonz/rtpengine-utils')([], logger, {emitter: stats});
lookupAuthHook, srf.locals = {...srf.locals,
lookupSipGatewayBySignalingAddress stats,
queryCdrs,
writeCdrs,
writeAlerts,
AlertType,
activeCallIds: new Map(),
getRtpEngine,
dbHelpers: {
pool,
lookupAuthHook,
lookupSipGatewayBySignalingAddress,
lookupAccountByPhoneNumber,
lookupAppByTeamsTenant,
lookupAccountBySid,
lookupAccountBySipRealm,
lookupAccountCapacitiesBySid
},
realtimeDbHelpers: {
createSet,
incrKey,
decrKey,
retrieveSet
}
}; };
const {challengeDeviceCalls, initLocals} = require('./lib/middleware')(srf, logger); srf.locals.getFeatureServer = require('./lib/fs-tracking')(srf, logger);
const activeCallIds = srf.locals.activeCallIds;
const {
initLocals,
identifyAccount,
checkLimits,
challengeDeviceCalls
} = require('./lib/middleware')(srf, logger);
const CallSession = require('./lib/call-session'); const CallSession = require('./lib/call-session');
if (process.env.DRACHTIO_HOST) { if (process.env.DRACHTIO_HOST) {
@@ -48,7 +92,12 @@ if (process.env.DRACHTIO_HOST) {
const last = hp.split(',').pop(); const last = hp.split(',').pop();
const arr = /^(.*)\/(.*):(\d+)$/.exec(last); const arr = /^(.*)\/(.*):(\d+)$/.exec(last);
logger.info(`connected to drachtio listening on ${hp}: adding ${arr[2]} to sbc_addresses table`); logger.info(`connected to drachtio listening on ${hp}: adding ${arr[2]} to sbc_addresses table`);
addSbcAddress(arr[2]); srf.locals.sipAddress = arr[2];
/* don't add IP to the general SBC table if this is a static IP for a single account */
if (!process.env.SBC_ACCOUNT_SID) {
addSbcAddress(arr[2]);
}
}); });
} }
else { else {
@@ -60,8 +109,8 @@ if (process.env.NODE_ENV === 'test') {
}); });
} }
// challenge calls from devices, let calls from sip gateways through /* install middleware */
srf.use('invite', [initLocals, challengeDeviceCalls]); srf.use('invite', [initLocals, identifyAccount, checkLimits, challengeDeviceCalls]);
srf.invite((req, res) => { srf.invite((req, res) => {
if (req.has('Replaces')) { if (req.has('Replaces')) {
@@ -84,8 +133,47 @@ srf.use((req, res, next, err) => {
res.send(500); res.send(500);
}); });
/* update call stats periodically */
setInterval(() => { setInterval(() => {
stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:inbound']); stats.gauge('sbc.sip.calls.count', activeCallIds.size, ['direction:inbound']);
}, 20000); }, 20000);
const arrayCompare = (a, b) => {
if (a.length !== b.length) return false;
const uniqueValues = new Set([...a, ...b]);
for (const v of uniqueValues) {
const aCount = a.filter((e) => e === v).length;
const bCount = b.filter((e) => e === v).length;
if (aCount !== bCount) return false;
}
return true;
};
/* update rtpengines periodically */
if (process.env.JAMBONES_RTPENGINES) {
setRtpEngines([process.env.JAMBONES_RTPENGINES]);
}
else {
const getActiveRtpServers = async() => {
try {
const set = await retrieveSet(setNameRtp);
const newArray = Array.from(set);
logger.debug({newArray, rtpServers}, 'getActiveRtpServers');
if (!arrayCompare(newArray, rtpServers)) {
logger.info({newArray}, 'resetting active rtpengines');
setRtpEngines(newArray.map((a) => `${a}:${process.env.RTPENGINE_PORT || 22222}`));
rtpServers.length = 0;
Array.prototype.push.apply(rtpServers, newArray);
}
} catch (err) {
logger.error({err}, 'Error setting new rtpengines');
}
};
setInterval(() => {
getActiveRtpServers();
}, 30000);
getActiveRtpServers();
}
module.exports = {srf, logger}; module.exports = {srf, logger};

View File

@@ -1,5 +1,5 @@
const Emitter = require('events'); const Emitter = require('events');
const {makeRtpEngineOpts, SdpWantsSrtp} = require('./utils'); const {makeRtpEngineOpts, SdpWantsSrtp, makeCallCountKey} = require('./utils');
const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar'); const {forwardInDialogRequests} = require('drachtio-fn-b2b-sugar');
const {parseUri, SipError} = require('drachtio-srf'); const {parseUri, SipError} = require('drachtio-srf');
const debug = require('debug')('jambonz:sbc-inbound'); const debug = require('debug')('jambonz:sbc-inbound');
@@ -28,7 +28,11 @@ class CallSession extends Emitter {
this.getRtpEngine = req.srf.locals.getRtpEngine; this.getRtpEngine = req.srf.locals.getRtpEngine;
this.getFeatureServer = req.srf.locals.getFeatureServer; this.getFeatureServer = req.srf.locals.getFeatureServer;
this.stats = this.srf.locals.stats; this.stats = this.srf.locals.stats;
this.writeCdrs = this.srf.locals.writeCdrs;
this.activeCallIds = this.srf.locals.activeCallIds; this.activeCallIds = this.srf.locals.activeCallIds;
this.decrKey = req.srf.locals.realtimeDbHelpers.decrKey;
this.callCountKey = makeCallCountKey(req.locals.account_sid);
} }
get isFromMSTeams() { get isFromMSTeams() {
@@ -50,14 +54,14 @@ class CallSession extends Emitter {
this.answer = answer; this.answer = answer;
this.del = del; this.del = del;
const featureServer = this.getFeatureServer(); const featureServer = await this.getFeatureServer();
if (!featureServer) { if (!featureServer) {
this.logger.info('No available feature servers, rejecting call!'); this.logger.info('No available feature servers, rejecting call!');
const tags = ['accepted:no', 'sipStatus:480', `originator:${this.req.locals.originator}`]; const tags = ['accepted:no', 'sipStatus:480', `originator:${this.req.locals.originator}`];
this.stats.increment('sbc.terminations', tags); this.stats.increment('sbc.terminations', tags);
return this.res.send(480); return this.res.send(480);
} }
debug(`using feature server ${featureServer}`); this.logger.debug(`using feature server ${featureServer}`);
this.rtpEngineOpts = makeRtpEngineOpts(this.req, SdpWantsSrtp(this.req.body), false, this.isFromMSTeams); this.rtpEngineOpts = makeRtpEngineOpts(this.req, SdpWantsSrtp(this.req.body), false, this.isFromMSTeams);
this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)}; this.rtpEngineResource = {destroy: this.del.bind(null, this.rtpEngineOpts.common)};
@@ -97,6 +101,7 @@ class CallSession extends Emitter {
const headers = { const headers = {
'From': createBLegFromHeader(this.req), 'From': createBLegFromHeader(this.req),
'To': this.req.get('To'), 'To': this.req.get('To'),
'X-Account-Sid': this.req.locals.account_sid,
'X-CID': this.req.get('Call-ID'), 'X-CID': this.req.get('Call-ID'),
'X-Forwarded-For': `${this.req.source_address}:${this.req.source_port}` 'X-Forwarded-For': `${this.req.source_address}:${this.req.source_port}`
}; };
@@ -154,6 +159,7 @@ class CallSession extends Emitter {
// successfully connected // successfully connected
this.logger.info('call connected successfully to feature server'); this.logger.info('call connected successfully to feature server');
debug('call connected successfully to feature server');
this._setHandlers({uas, uac}); this._setHandlers({uas, uac});
return; return;
} catch (err) { } catch (err) {
@@ -175,30 +181,60 @@ class CallSession extends Emitter {
_setDlgHandlers(dlg) { _setDlgHandlers(dlg) {
this.activeCallIds.set(this.req.get('Call-ID'), this); this.activeCallIds.set(this.req.get('Call-ID'), this);
dlg.on('destroy', () => { dlg.on('destroy', () => {
debug('call ended with normal termination');
this.logger.info('call ended with normal termination'); this.logger.info('call ended with normal termination');
this.rtpEngineResource.destroy().catch((err) => {}); this.rtpEngineResource.destroy().catch((err) => {});
this.activeCallIds.delete(this.req.get('Call-ID')); this.activeCallIds.delete(this.req.get('Call-ID'));
if (dlg.other && dlg.other.connected) dlg.other.destroy().catch((e) => {}); if (dlg.other && dlg.other.connected) dlg.other.destroy().catch((e) => {});
}); });
//re-invite //re-invite
dlg.on('modify', this._onReinvite.bind(this, dlg)); dlg.on('modify', this._onReinvite.bind(this, dlg));
} }
_setHandlers({uas, uac}) { _setHandlers({uas, uac}) {
this.emit('connected'); this.emit('connected');
const callStart = Date.now();
const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`]; const tags = ['accepted:yes', 'sipStatus:200', `originator:${this.req.locals.originator}`];
this.stats.increment('sbc.terminations', tags); this.stats.increment('sbc.terminations', tags);
this.activeCallIds.set(this.req.get('Call-ID'), this); this.activeCallIds.set(this.req.get('Call-ID'), this);
if (this.req.locals.cdr) {
this.req.locals.cdr = {
...this.req.locals.cdr,
answered: true,
answered_at: callStart
};
}
this.uas = uas; this.uas = uas;
this.uac = uac; this.uac = uac;
[uas, uac].forEach((dlg) => { [uas, uac].forEach((dlg) => {
//hangup
dlg.on('destroy', () => { dlg.on('destroy', () => {
this.logger.info('call ended with normal termination'); this.logger.info('call ended with normal termination');
this.rtpEngineResource.destroy().catch((err) => {}); this.rtpEngineResource.destroy().catch((err) => {});
this.activeCallIds.delete(this.req.get('Call-ID')); this.activeCallIds.delete(this.req.get('Call-ID'));
dlg.other.destroy().catch((e) => {}); dlg.other.destroy().catch((e) => {});
if (process.env.JAMBONES_HOSTING) {
this.decrKey(this.callCountKey)
.then((count) => this.logger.debug({key: this.callCountKey},
`after hangup there are ${count} active calls for this account`))
.catch((err) => this.logger.error({err}, 'Error decrementing call count'));
}
/* write cdr for connected call */
if (this.req.locals.cdr) {
const now = Date.now();
const trunk = ['trunk', 'teams'].includes(this.req.locals.originator) ?
this.req.locals.carrier :
this.req.locals.originator;
this.writeCdrs({...this.req.locals.cdr,
terminated_at: now,
termination_reason: dlg.type === 'uas' ? 'caller hungup' : 'called party hungup',
sip_status: 200,
duration: Math.floor((now - callStart) / 1000),
trunk
}).catch((err) => this.logger.error({err}, 'Error writing cdr for completed call'));
}
}); });
}); });
uas.on('modify', this._onReinvite.bind(this, uas)); uas.on('modify', this._onReinvite.bind(this, uas));

130
lib/db-utils.js Normal file
View File

@@ -0,0 +1,130 @@
const assert = require('assert');
const CIDRMatcher = require('cidr-matcher');
const {parseUri} = require('drachtio-srf');
const {normalizeDID} = require('./utils');
const sqlSelectAllCarriersForAccountByRealm =
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
FROM sip_gateways sg, voip_carriers vc, accounts acc
WHERE acc.sip_realm = ?
AND vc.account_sid = acc.account_sid
AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
const sqlSelectAllGatewaysForSP =
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid,
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
FROM sip_gateways sg, voip_carriers vc
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
AND vc.service_provider_sid IS NOT NULL
AND vc.is_active = 1`;
const sqlCarriersForAccountBySid =
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
FROM sip_gateways sg, voip_carriers vc, accounts acc
WHERE acc.account_sid = ?
AND vc.account_sid = acc.account_sid
AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
const sqlAccountByRealm = 'SELECT * from accounts WHERE sip_realm = ?';
const sqlAccountBySid = 'SELECT * from accounts WHERE account_sid = ?';
const sqlQueryApplicationByDid = `
SELECT * FROM phone_numbers
WHERE number = ?
AND voip_carrier_sid = ?`;
const gatewayMatchesSourceAddress = (source_address, gw) => {
if (32 === gw.netmask && gw.ipv4 === source_address) return true;
if (gw.netmask < 32) {
const matcher = new CIDRMatcher([`${gw.ipv4}/${gw.netmask}`]);
return matcher.contains(source_address);
}
return false;
};
module.exports = (srf, logger) => {
const {pool} = srf.locals.dbHelpers;
const pp = pool.promise();
const getApplicationForDidAndCarrier = async(req, voip_carrier_sid) => {
const did = normalizeDID(req.calledNumber);
try {
const [r] = await pp.query(sqlQueryApplicationByDid, [did, voip_carrier_sid]);
if (0 === r.length) return null;
return r[0].application_sid;
} catch (err) {
logger.error({err}, 'getApplicationForDidAndCarrier');
}
};
const wasOriginatedFromCarrier = async(req) => {
const failure = {fromCarrier: false};
const uri = parseUri(req.uri);
const isDotDecimal = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(uri.host);
if (isDotDecimal) {
if (process.env.JAMBONES_HOSTING) {
if (!process.env.SBC_ACCOUNT_SID) return failure;
/* look for carrier only within that account */
const [r] = await pp.query(sqlCarriersForAccountBySid,
[process.env.SBC_ACCOUNT_SID, req.source_address, req.source_port]);
if (0 === r.length) return failure;
return {fromCarrier: true, gateway: r[0], account_sid: process.env.SBC_ACCOUNT_SID};
}
else {
/* we may have a carrier at the service provider level */
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
if (matches.length) {
/* we have one or more carriers that match. Now we need to find one with a provisioned phone number */
const vc_sids = matches.map((m) => `'${m.voip_carrier_sid}'`).join(',');
const did = normalizeDID(req.calledNumber);
const sql = `SELECT * FROM phone_numbers WHERE number = ${did} AND voip_carrier_sid IN (${vc_sids})`;
logger.debug({matches, sql, did, vc_sids}, 'looking up DID');
const [r] = await pp.query(sql);
if (0 === r.length) {
/* came from a carrier, but number is not provisioned */
return {fromCarrier: true};
}
const gateway = matches.find((m) => m.voip_carrier_sid === r[0].voip_carrier_sid);
const [accounts] = await pp.query(sqlAccountBySid, r[0].account_sid);
assert(accounts.length);
return {
fromCarrier: true,
gateway,
account_sid: r[0].account_sid,
application_sid: r[0].application_sid,
account: accounts[0]
};
}
return failure;
}
}
/* get all the carriers and gateways for the account owning this sip realm */
const [gw] = await pp.query(sqlSelectAllCarriersForAccountByRealm, uri.host);
const selected = gw.find(gatewayMatchesSourceAddress.bind(null, req.source_address));
if (selected) {
const [a] = await pp.query(sqlAccountByRealm, uri.host);
if (0 === a.length) return failure;
return {
fromCarrier: true,
gateway: selected,
account_sid: a[0].account_sid,
application_sid: selected.application_sid,
account: a[0]
};
}
return failure;
};
return {
wasOriginatedFromCarrier,
getApplicationForDidAndCarrier
};
};

View File

@@ -1,100 +1,24 @@
const contacts = new Map(); const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
const noopLogger = {info: () => {}, error: () => {}};
const debug = require('debug')('jambonz:sbc-inbound');
module.exports = (srf, logger) => { module.exports = (srf, logger) => {
logger = logger || noopLogger; const {retrieveSet, createSet} = srf.locals.realtimeDbHelpers;
let dynamic = true;
let idx = 0; let idx = 0;
const stats = srf.locals.stats;
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-fs`;
const {createSet} = require('@jambonz/realtimedb-helpers')({ if ('test' === process.env.NODE_ENV) {
host: process.env.JAMBONES_REDIS_HOST || 'localhost', createSet(setName, [process.env.JAMBONES_FEATURE_SERVERS]);
port: process.env.JAMBONES_REDIS_PORT || 6379
}, logger);
srf.options((req, res) => {
res.send(200);
if (req.has('X-FS-Status')) {
const uri = `${req.source_address}:${req.source_port}`;
const status = req.get('X-FS-Status');
const calls = req.has('X-FS-Calls') ? parseInt(req.get('X-FS-Calls')) : 0;
if (status === 'open') {
const adding = !contacts.has(uri);
debug(`Feature server at ${uri} has ${calls} calls`);
contacts.set(uri, {pingTime: new Date(), calls: calls});
if (adding) {
logger.info(`adding feature server at ${uri}: ${[...contacts.keys()]}`);
stats.gauge('sbc.featureservers.count', contacts.size);
const featureServerIps = [...contacts.keys()].map((u) => {
const arr = /^(.*):\d+$/.exec(u);
return arr[1];
});
createSet(setName, new Set(featureServerIps));
}
}
else {
if (contacts.has(uri)) {
contacts.delete(uri);
logger.info(`removing feature server at ${uri}, leaving ${[...contacts.keys()]}`);
stats.gauge('sbc.featureservers.count', contacts.size);
const featureServerIps = [...contacts.keys()].map((u) => {
const arr = /^(.*):\d+$/.exec(u);
return arr[1];
});
createSet(setName, new Set(featureServerIps));
}
}
}
});
// NB: this should only be used for test suite at this point
// in actual scenario OPTIONS pings from feature servers are used
if (process.env.NODE_ENV === 'test' && process.env.JAMBONES_FEATURE_SERVERS) {
dynamic = false;
process.env.JAMBONES_FEATURE_SERVERS
.split(',')
.map((hp) => hp.trim())
.forEach((uri) => contacts.set(uri, {active: 0, calls: 0}));
debug(`using static list of feature servers: ${[ ...contacts]}`);
} }
if (dynamic) { return async() => {
const CHECK_INTERVAL = 35; try {
setInterval(() => { const fs = await retrieveSet(setName);
const dead = []; if (0 === fs.length) {
const deadline = Date.now() - 90000; logger.info('No available feature servers to handle incoming call');
for (const obj of contacts) { return;
if (obj[1].pingTime.getTime() < deadline) dead.push(obj[0]);
} }
dead.forEach((uri) => { logger.debug({fs}, `retrieved ${setName}`);
logger.info(`removing feature server at ${uri} due to lack of OPTIONS ping`); return fs[idx++ % fs.length];
contacts.delete(uri); } catch (err) {
}); logger.error({err}, `Error retrieving ${setName}`);
const keys = [ ...contacts.keys() ];
debug({keys}, `there are ${keys.length} feature servers online`);
stats.gauge('sbc.featureservers.count', contacts.size);
}, CHECK_INTERVAL * 1000);
}
return () => {
let selectedUri;
if (dynamic) {
const featureServers = [ ...contacts].map((o) => Object.assign({}, {uri: o[0]}, o[1]));
debug({featureServers}, 'selecting feature servers with least calls');
const fs = featureServers.sort((a, b) => (a.calls - b.calls)).shift();
if (!fs) logger.info('No available feature servers!');
else selectedUri = fs.uri;
} }
else {
const featureServers = [ ...contacts].map((o) => o[0]);
debug({featureServers}, 'selecting feature servers from static list');
selectedUri = featureServers[idx++ % featureServers.length];
debug(`selected ${selectedUri}`);
}
return selectedUri;
}; };
}; };

View File

@@ -1,29 +1,79 @@
const debug = require('debug')('jambonz:sbc-inbound'); const debug = require('debug')('jambonz:sbc-inbound');
const assert = require('assert');
const Emitter = require('events'); const Emitter = require('events');
const parseUri = require('drachtio-srf').parseUri; const parseUri = require('drachtio-srf').parseUri;
const {makeCallCountKey} = require('./utils');
const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ? const msProxyIps = process.env.MS_TEAMS_SIP_PROXY_IPS ?
process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) : process.env.MS_TEAMS_SIP_PROXY_IPS.split(',').map((i) => i.trim()) :
[]; [];
class AuthOutcomeReporter extends Emitter { const initCdr = (req) => {
constructor(stats) { return {
super(); from: req.callingNumber,
this.on('regHookOutcome', ({rtt, status}) => { to: req.calledNumber,
stats.histogram('app.hook.response_time', rtt, ['hook_type:auth', `status:${status}`]); sip_callid: req.get('Call-ID'),
}); duration: 0,
} attempted_at: Date.now(),
} direction: 'inbound',
host: req.srf.locals.sipAddress,
remote_host: req.source_address,
answered: false
};
};
module.exports = function(srf, logger) { module.exports = function(srf, logger) {
const {lookupSipGatewayBySignalingAddress, lookupAuthHook} = srf.locals.dbHelpers; class AuthOutcomeReporter extends Emitter {
const {stats} = srf.locals; constructor(stats) {
super();
this
.on('regHookOutcome', ({rtt, status}) => {
stats.histogram('app.hook.response_time', rtt, ['hook_type:auth', `status:${status}`]);
})
.on('error', async(err, req) => {
const {account_sid} = req.locals;
const {writeAlerts, AlertType} = req.srf.locals;
if (account_sid) {
let opts = {account_sid};
if (err.code === 'ECONNREFUSED') {
opts = {...opts, alert_type: AlertType.WEBHOOK_CONNECTION_FAILURE, url: err.hook};
}
else if (err.code === 'ENOTFOUND') {
opts = {...opts, alert_type: AlertType.WEBHOOK_CONNECTION_FAILURE, url: err.hook};
}
else if (err.name === 'StatusError') {
opts = {...opts, alert_type: AlertType.WEBHOOK_STATUS_FAILURE, url: err.hook, status: err.statusCode};
}
if (opts.alert_type) {
try {
await writeAlerts(opts);
} catch (err) {
logger.error({err, opts}, 'Error writing alert');
}
}
}
});
}
}
const {
lookupAuthHook,
lookupAppByTeamsTenant,
lookupAccountBySipRealm,
lookupAccountBySid,
lookupAccountCapacitiesBySid
} = srf.locals.dbHelpers;
const {stats, writeCdrs} = srf.locals;
const authenticator = require('@jambonz/http-authenticator')(lookupAuthHook, logger, { const authenticator = require('@jambonz/http-authenticator')(lookupAuthHook, logger, {
blacklistUnknownRealms: true, blacklistUnknownRealms: true,
emitter: new AuthOutcomeReporter(stats) emitter: new AuthOutcomeReporter(stats)
}); });
const {wasOriginatedFromCarrier, getApplicationForDidAndCarrier} = require('./db-utils')(srf, logger);
function initLocals(req, res, next) {
const initLocals = (req, res, next) => {
req.locals = req.locals || {}; req.locals = req.locals || {};
req.locals.cdr = initCdr(req);
const callId = req.get('Call-ID'); const callId = req.get('Call-ID');
req.on('cancel', () => { req.on('cancel', () => {
logger.info({callId}, 'caller hungup before connecting to feature server'); logger.info({callId}, 'caller hungup before connecting to feature server');
@@ -33,38 +83,193 @@ module.exports = function(srf, logger) {
stats.increment('sbc.terminations', tags); stats.increment('sbc.terminations', tags);
}); });
stats.increment('sbc.invites', ['direction:inbound']); stats.increment('sbc.invites', ['direction:inbound']);
next();
}
async function challengeDeviceCalls(req, res, next) { /* write cdr for non-success response here */
res.once('end', ({status}) => {
if (req.locals.cdr && req.locals.cdr.account_sid && status > 200 && 401 !== status) {
const trunk = ['trunk', 'teams'].includes(req.locals.originator) ? req.locals.carrier : req.locals.originator;
writeCdrs({...req.locals.cdr,
terminated_at: Date.now(),
termination_reason: status === 487 === status ? 'caller abandoned' : 'failed',
sip_status: status,
trunk
}).catch((err) => logger.error({err}, 'Error writing cdr for call failure'));
}
});
next();
};
const identifyAccount = async(req, res, next) => {
try { try {
const gateway = await lookupSipGatewayBySignalingAddress(req.source_address, req.source_port);
if (gateway) { const {fromCarrier, gateway, account_sid, application_sid, account} = await wasOriginatedFromCarrier(req);
debug(`challengeDeviceCalls: call came from gateway: ${JSON.stringify(gateway)}`); /**
req.locals.originator = 'trunk'; * calls come from 3 sources:
req.locals.carrier = gateway.name; * (1) A carrier
if (gateway.application_sid) req.locals.application_sid = gateway.application_sid; * (2) Microsoft Teams
return next(); * (3) A SIP user
*/
if (fromCarrier) {
if (!gateway) {
logger.info('identifyAccount: rejecting call from carrier because DID has not been provisioned');
return res.send(404, 'Number Not Provisioned');
}
logger.debug({gateway}, 'identifyAccount: incoming call from gateway');
/* check for phone number level routing */
const sid = application_sid || await getApplicationForDidAndCarrier(req, gateway.voip_carrier_sid);
req.locals = {
originator: 'trunk',
carrier: gateway.name,
application_sid: sid || gateway.application_sid,
account_sid,
account,
...req.locals
};
} }
if (msProxyIps.includes(req.source_address)) { else if (msProxyIps.includes(req.source_address)) {
logger.debug({source_address: req.source_address}, 'challengeDeviceCalls: incoming call from Microsoft Teams'); logger.debug({source_address: req.source_address}, 'identifyAccount: incoming call from Microsoft Teams');
const uri = parseUri(req.uri); const uri = parseUri(req.uri);
req.locals.originator = 'teams';
req.locals.carrier = 'Microsoft Teams'; const app = await lookupAppByTeamsTenant(uri.host);
req.locals.msTeamsTenantFqdn = uri.host; if (!app) {
return next(); stats.increment('sbc.terminations', ['sipStatus:404']);
return res.send(404, {headers: {'X-Reason': 'no configured application'}});
}
req.locals = {
originator: 'teams',
carrier: 'Microsoft Teams',
msTeamsTenantFqdn: uri.host,
account_sid: app.account_sid,
...req.locals
};
} }
req.locals.originator = 'device'; else {
req.locals.originator = 'user';
const uri = parseUri(req.uri);
logger.debug({source_address: req.source_address, realm: uri.host},
'identifyAccount: incoming user call');
const account = await lookupAccountBySipRealm(uri.host);
if (!account) {
stats.increment('sbc.terminations', ['sipStatus:404']);
return res.send(404);
}
/* if this is a dedicated SBC (static IP) only take calls for that account's sip realm */
if (process.env.SBC_ACCOUNT_SID && account.account_sid !== process.env.SBC_ACCOUNT_SID) {
logger.info(
`identifyAccount: static IP for ${process.env.SBC_ACCOUNT_SID} but call for ${account.account_sid}`);
stats.increment('sbc.terminations', ['sipStatus:404']);
delete req.locals.cdr;
return res.send(404);
}
req.locals = {
account_sid: account.account_sid,
account,
webhook_secret: account.webhook_secret,
...req.locals
};
}
assert(req.locals.account_sid);
req.locals.cdr.account_sid = req.locals.account_sid;
if (!req.locals.account) {
req.locals.account = await lookupAccountBySid(req.locals.account_sid);
}
if (!req.locals.account.is_active) {
stats.increment('sbc.terminations', ['sipStatus:503']);
return res.send(503, {headers: {'X-Reason': 'Account exists but is inactive'}});
}
if (req.locals.account.disable_cdrs) {
logger.info({account_sid: req.locals.account_sid}, 'Not writing CDRs for this account');
delete req.locals.cdr;
}
req.locals.logger = logger.child({callId: req.get('Call-ID'), account_sid: req.locals.account_sid});
next();
} catch (err) {
stats.increment('sbc.terminations', ['sipStatus:500']);
logger.error(err, `${req.get('Call-ID')} database error for inbound call`);
res.send(500);
}
};
const checkLimits = async(req, res, next) => {
if (!process.env.JAMBONES_HOSTING) return next(); // skip
const {incrKey, decrKey} = req.srf.locals.realtimeDbHelpers;
const {logger, account_sid} = req.locals;
const {writeAlerts, AlertType} = req.srf.locals;
assert(account_sid);
const key = makeCallCountKey(account_sid);
/* decrement count if INVITE is later rejected */
res.once('end', ({status}) => {
if (status > 200) {
decrKey(key)
.then((count) => {
logger.debug({key}, `after rejection there are ${count} active calls for this account`);
debug({key}, `after rejection there are ${count} active calls for this account`);
return;
})
.catch((err) => logger.error({err}, 'checkLimits: decrKey err'));
}
});
try {
/* increment the call count */
const calls = await incrKey(key);
/* compare to account's limit, though avoid db hit when call count is low */
const minLimit = process.env.MIN_CALL_LIMIT ?
parseInt(process.env.MIN_CALL_LIMIT) :
0;
logger.debug(`checkLimits: call count is now ${calls}, limit is ${minLimit}`);
if (calls <= minLimit) return next();
const capacities = await lookupAccountCapacitiesBySid(account_sid);
const limit = capacities.find((c) => c.category == 'voice_call_session');
if (!limit) throw new Error('no account_capacities found');
const limit_sessions = limit.quantity;
if (calls > limit_sessions) {
debug(`checkLimits: limits exceeded: call count ${calls}, limit ${limit_sessions}`);
logger.info({calls, limit_sessions}, 'checkLimits: limits exceeded');
writeAlerts({
alert_type: AlertType.CALL_LIMIT,
account_sid,
count: limit_sessions
}).catch((err) => logger.info({err}, 'checkLimits: error writing alert'));
return res.send(503, 'Maximum Calls In Progress');
}
next();
} catch (err) {
stats.increment('sbc.terminations', ['sipStatus:500']);
logger.error({err}, 'error checking limits error for inbound call');
res.send(500);
}
};
const challengeDeviceCalls = async(req, res, next) => {
try {
/* TODO: check if this is a gateway that we have an ACL for */
if (req.locals.originator !== 'user') return next();
return authenticator(req, res, next); return authenticator(req, res, next);
} catch (err) { } catch (err) {
stats.increment('sbc.terminations', ['sipStatus:500']); stats.increment('sbc.terminations', ['sipStatus:500']);
logger.error(err, `${req.get('Call-ID')} Error looking up related info for inbound call`); logger.error(err, `${req.get('Call-ID')} Error looking up related info for inbound call`);
res.send(500); res.send(500);
} }
} };
return { return {
initLocals, initLocals,
challengeDeviceCalls challengeDeviceCalls,
identifyAccount,
checkLimits
}; };
}; };

View File

@@ -2,14 +2,14 @@ const rtpCharacteristics = require('../data/rtp-transcoding');
const srtpCharacteristics = require('../data/srtp-transcoding'); const srtpCharacteristics = require('../data/srtp-transcoding');
let idx = 0; let idx = 0;
function isWSS(req) { const isWSS = (req) => {
return req.getParsedHeader('Via')[0].protocol.toLowerCase().startsWith('ws'); return req.getParsedHeader('Via')[0].protocol.toLowerCase().startsWith('ws');
} };
function getAppserver(srf) { const getAppserver = (srf) => {
const len = srf.locals.featureServers.length; const len = srf.locals.featureServers.length;
return srf.locals.featureServers[ idx++ % len]; return srf.locals.featureServers[ idx++ % len];
} };
function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) { function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
const from = req.getParsedHeader('from'); const from = req.getParsedHeader('from');
@@ -33,13 +33,23 @@ function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp, teams = false) {
}; };
} }
function SdpWantsSrtp(sdp) { const SdpWantsSrtp = (sdp) => {
return /m=audio.*SAVP/.test(sdp); return /m=audio.*SAVP/.test(sdp);
} };
const makeCallCountKey = (sid) => `${sid}:incalls`;
const normalizeDID = (tel) => {
const regex = /^\+(\d+)$/;
const arr = regex.exec(tel);
return arr ? arr[1] : tel;
};
module.exports = { module.exports = {
isWSS, isWSS,
SdpWantsSrtp, SdpWantsSrtp,
getAppserver, getAppserver,
makeRtpEngineOpts makeRtpEngineOpts,
makeCallCountKey,
normalizeDID
}; };

963
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,28 +20,29 @@
}, },
"scripts": { "scripts": {
"start": "node app", "start": "node app",
"test": "NODE_ENV=test JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ | ./node_modules/.bin/tap-spec", "test": "NODE_ENV=test JAMBONES_HOSTING=1 SBC_ACCOUNT_SID=ed649e33-e771-403a-8c99-1780eabbc803 JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=localhost JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug DRACHTIO_SECRET=cymru DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 JAMBONES_RTPENGINES=127.0.0.1:12222 JAMBONES_FEATURE_SERVERS=172.38.0.11 node test/ ",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test", "coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js lib" "jslint": "eslint app.js lib"
}, },
"dependencies": { "dependencies": {
"@jambonz/db-helpers": "^0.5.12", "@jambonz/db-helpers": "^0.6.12",
"@jambonz/http-authenticator": "^0.1.6", "@jambonz/http-authenticator": "^0.2.0",
"@jambonz/realtimedb-helpers": "^0.2.20", "@jambonz/realtimedb-helpers": "^0.4.3",
"@jambonz/rtpengine-utils": "^0.1.7", "@jambonz/rtpengine-utils": "^0.1.12",
"@jambonz/stats-collector": "^0.1.4", "@jambonz/stats-collector": "^0.1.5",
"@jambonz/time-series": "^0.1.5",
"cidr-matcher": "^2.1.1",
"debug": "^4.3.1", "debug": "^4.3.1",
"drachtio-fn-b2b-sugar": "0.0.12", "drachtio-fn-b2b-sugar": "0.0.12",
"drachtio-srf": "^4.4.44", "drachtio-srf": "^4.4.49",
"pino": "^6.8.0", "pino": "^6.8.0",
"rtpengine-client": "^0.1.1" "rtpengine-client": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"blue-tape": "^1.0.0",
"clear-module": "^4.1.1", "clear-module": "^4.1.1",
"eslint": "^7.15.0", "eslint": "^7.15.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"tap-spec": "^5.0.0" "tape": "^4.13.3"
} }
} }

View File

@@ -1,4 +1,4 @@
const test = require('tape').test ; const test = require('tape') ;
const exec = require('child_process').exec ; const exec = require('child_process').exec ;
test('creating jambones_test database', (t) => { test('creating jambones_test database', (t) => {

View File

@@ -2,20 +2,46 @@
SET FOREIGN_KEY_CHECKS=0; SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS account_static_ips;
DROP TABLE IF EXISTS account_products;
DROP TABLE IF EXISTS account_subscriptions;
DROP TABLE IF EXISTS beta_invite_codes;
DROP TABLE IF EXISTS call_routes; DROP TABLE IF EXISTS call_routes;
DROP TABLE IF EXISTS dns_records;
DROP TABLE IF EXISTS lcr_carrier_set_entry; DROP TABLE IF EXISTS lcr_carrier_set_entry;
DROP TABLE IF EXISTS lcr_routes; DROP TABLE IF EXISTS lcr_routes;
DROP TABLE IF EXISTS api_keys; DROP TABLE IF EXISTS predefined_sip_gateways;
DROP TABLE IF EXISTS ms_teams_tenants; DROP TABLE IF EXISTS predefined_carriers;
DROP TABLE IF EXISTS account_offers;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS api_keys;
DROP TABLE IF EXISTS sbc_addresses; DROP TABLE IF EXISTS sbc_addresses;
DROP TABLE IF EXISTS ms_teams_tenants;
DROP TABLE IF EXISTS signup_history;
DROP TABLE IF EXISTS smpp_addresses;
DROP TABLE IF EXISTS speech_credentials;
DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS smpp_gateways;
DROP TABLE IF EXISTS phone_numbers; DROP TABLE IF EXISTS phone_numbers;
DROP TABLE IF EXISTS sip_gateways; DROP TABLE IF EXISTS sip_gateways;
@@ -30,6 +56,41 @@ DROP TABLE IF EXISTS service_providers;
DROP TABLE IF EXISTS webhooks; DROP TABLE IF EXISTS webhooks;
CREATE TABLE account_static_ips
(
account_static_ip_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
public_ipv4 VARCHAR(16) NOT NULL UNIQUE ,
private_ipv4 VARBINARY(16) NOT NULL UNIQUE ,
PRIMARY KEY (account_static_ip_sid)
);
CREATE TABLE account_subscriptions
(
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
pending BOOLEAN NOT NULL DEFAULT false,
effective_start_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
effective_end_date DATETIME,
change_reason VARCHAR(255),
stripe_subscription_id VARCHAR(56),
stripe_payment_method_id VARCHAR(56),
stripe_statement_descriptor VARCHAR(255),
last4 VARCHAR(512),
exp_month INTEGER,
exp_year INTEGER,
card_type VARCHAR(16),
pending_reason VARBINARY(52),
PRIMARY KEY (account_subscription_sid)
);
CREATE TABLE beta_invite_codes
(
invite_code CHAR(6) NOT NULL UNIQUE ,
in_use BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (invite_code)
);
CREATE TABLE call_routes CREATE TABLE call_routes
( (
call_route_sid CHAR(36) NOT NULL UNIQUE , call_route_sid CHAR(36) NOT NULL UNIQUE ,
@@ -38,7 +99,16 @@ account_sid CHAR(36) NOT NULL,
regex VARCHAR(255) NOT NULL, regex VARCHAR(255) NOT NULL,
application_sid CHAR(36) NOT NULL, application_sid CHAR(36) NOT NULL,
PRIMARY KEY (call_route_sid) PRIMARY KEY (call_route_sid)
) ENGINE=InnoDB COMMENT='a regex-based pattern match for call routing'; ) COMMENT='a regex-based pattern match for call routing';
CREATE TABLE dns_records
(
dns_record_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
record_type VARCHAR(6) NOT NULL,
record_id INTEGER NOT NULL,
PRIMARY KEY (dns_record_sid)
);
CREATE TABLE lcr_routes CREATE TABLE lcr_routes
( (
@@ -49,6 +119,61 @@ priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted f
PRIMARY KEY (lcr_route_sid) PRIMARY KEY (lcr_route_sid)
) COMMENT='Least cost routing table'; ) COMMENT='Least cost routing table';
CREATE TABLE predefined_carriers
(
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL,
requires_static_ip BOOLEAN NOT NULL DEFAULT false,
e164_leading_plus BOOLEAN NOT NULL DEFAULT false COMMENT 'if true, a leading plus should be prepended to outbound phone numbers',
requires_register BOOLEAN NOT NULL DEFAULT false,
register_username VARCHAR(64),
register_sip_realm VARCHAR(64),
register_password VARCHAR(64),
tech_prefix VARCHAR(16) COMMENT 'tech prefix to prepend to outbound calls to this carrier',
inbound_auth_username VARCHAR(64),
inbound_auth_password VARCHAR(64),
diversion VARCHAR(32),
PRIMARY KEY (predefined_carrier_sid)
);
CREATE TABLE predefined_sip_gateways
(
predefined_sip_gateway_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
netmask INTEGER NOT NULL DEFAULT 32,
predefined_carrier_sid CHAR(36) NOT NULL,
PRIMARY KEY (predefined_sip_gateway_sid)
);
CREATE TABLE products
(
product_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(32) NOT NULL,
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
PRIMARY KEY (product_sid)
);
CREATE TABLE account_products
(
account_product_sid CHAR(36) NOT NULL UNIQUE ,
account_subscription_sid CHAR(36) NOT NULL,
product_sid CHAR(36) NOT NULL,
quantity INTEGER NOT NULL,
PRIMARY KEY (account_product_sid)
);
CREATE TABLE account_offers
(
account_offer_sid CHAR(36) NOT NULL UNIQUE ,
account_sid CHAR(36) NOT NULL,
product_sid CHAR(36) NOT NULL,
stripe_product_id VARCHAR(56) NOT NULL,
PRIMARY KEY (account_offer_sid)
);
CREATE TABLE api_keys CREATE TABLE api_keys
( (
api_key_sid CHAR(36) NOT NULL UNIQUE , api_key_sid CHAR(36) NOT NULL UNIQUE ,
@@ -59,7 +184,16 @@ expires_at TIMESTAMP NULL DEFAULT NULL,
last_used TIMESTAMP NULL DEFAULT NULL, last_used TIMESTAMP NULL DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (api_key_sid) PRIMARY KEY (api_key_sid)
) ENGINE=InnoDB COMMENT='An authorization token that is used to access the REST api'; ) COMMENT='An authorization token that is used to access the REST api';
CREATE TABLE sbc_addresses
(
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(255) NOT NULL,
port INTEGER NOT NULL DEFAULT 5060,
service_provider_sid CHAR(36),
PRIMARY KEY (sbc_address_sid)
);
CREATE TABLE ms_teams_tenants CREATE TABLE ms_teams_tenants
( (
@@ -71,64 +205,121 @@ tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
PRIMARY KEY (ms_teams_tenant_sid) PRIMARY KEY (ms_teams_tenant_sid)
) COMMENT='A Microsoft Teams customer tenant'; ) COMMENT='A Microsoft Teams customer tenant';
CREATE TABLE sbc_addresses CREATE TABLE signup_history
( (
sbc_address_sid CHAR(36) NOT NULL UNIQUE , email VARCHAR(255) NOT NULL,
name VARCHAR(255),
signed_up_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (email)
);
CREATE TABLE smpp_addresses
(
smpp_address_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(255) NOT NULL, ipv4 VARCHAR(255) NOT NULL,
port INTEGER NOT NULL DEFAULT 5060, port INTEGER NOT NULL DEFAULT 5060,
use_tls BOOLEAN NOT NULL DEFAULT 0,
is_primary BOOLEAN NOT NULL DEFAULT 1,
service_provider_sid CHAR(36), service_provider_sid CHAR(36),
PRIMARY KEY (sbc_address_sid) PRIMARY KEY (smpp_address_sid)
);
CREATE TABLE speech_credentials
(
speech_credential_sid CHAR(36) NOT NULL UNIQUE ,
service_provider_sid CHAR(36),
account_sid CHAR(36),
vendor VARCHAR(32) NOT NULL,
credential VARCHAR(8192) NOT NULL,
use_for_tts BOOLEAN DEFAULT true,
use_for_stt BOOLEAN DEFAULT true,
last_used DATETIME,
last_tested DATETIME,
tts_tested_ok BOOLEAN,
stt_tested_ok BOOLEAN,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (speech_credential_sid)
); );
CREATE TABLE users CREATE TABLE users
( (
user_sid CHAR(36) NOT NULL UNIQUE , user_sid CHAR(36) NOT NULL UNIQUE ,
name CHAR(36) NOT NULL UNIQUE , name VARCHAR(255) NOT NULL,
hashed_password VARCHAR(1024) NOT NULL, email VARCHAR(255) NOT NULL,
salt CHAR(16) NOT NULL, pending_email VARCHAR(255),
force_change BOOLEAN NOT NULL DEFAULT TRUE, phone VARCHAR(20) UNIQUE ,
hashed_password VARCHAR(1024),
account_sid CHAR(36),
service_provider_sid CHAR(36),
force_change BOOLEAN NOT NULL DEFAULT FALSE,
provider VARCHAR(255) NOT NULL,
provider_userid VARCHAR(255),
scope VARCHAR(16) NOT NULL DEFAULT 'read-write',
phone_activation_code VARCHAR(16),
email_activation_code VARCHAR(16),
email_validated BOOLEAN NOT NULL DEFAULT false,
phone_validated BOOLEAN NOT NULL DEFAULT false,
email_content_opt_out BOOLEAN NOT NULL DEFAULT false,
PRIMARY KEY (user_sid) PRIMARY KEY (user_sid)
); );
CREATE TABLE voip_carriers CREATE TABLE voip_carriers
( (
voip_carrier_sid CHAR(36) NOT NULL UNIQUE , voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL UNIQUE , name VARCHAR(64) NOT NULL,
description VARCHAR(255), description VARCHAR(255),
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a customer PBX that is associated with a specific account', account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a sip trunk that is associated with a specific account',
service_provider_sid CHAR(36),
application_sid CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application', application_sid CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
e164_leading_plus BOOLEAN NOT NULL DEFAULT false, e164_leading_plus BOOLEAN NOT NULL DEFAULT false COMMENT 'if true, a leading plus should be prepended to outbound phone numbers',
requires_register BOOLEAN NOT NULL DEFAULT false, requires_register BOOLEAN NOT NULL DEFAULT false,
register_username VARCHAR(64), register_username VARCHAR(64),
register_sip_realm VARCHAR(64), register_sip_realm VARCHAR(64),
register_password VARCHAR(64), register_password VARCHAR(64),
tech_prefix VARCHAR(16) COMMENT 'tech prefix to prepend to outbound calls to this carrier',
inbound_auth_username VARCHAR(64),
inbound_auth_password VARCHAR(64),
diversion VARCHAR(32),
is_active BOOLEAN NOT NULL DEFAULT true,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
smpp_system_id VARCHAR(255),
smpp_password VARCHAR(64),
smpp_enquire_link_interval INTEGER DEFAULT 0,
smpp_inbound_system_id VARCHAR(255),
smpp_inbound_password VARCHAR(64),
PRIMARY KEY (voip_carrier_sid) PRIMARY KEY (voip_carrier_sid)
) ENGINE=InnoDB COMMENT='A Carrier or customer PBX that can send or receive calls'; ) COMMENT='A Carrier or customer PBX that can send or receive calls';
CREATE TABLE smpp_gateways
(
smpp_gateway_sid CHAR(36) NOT NULL UNIQUE ,
ipv4 VARCHAR(128) NOT NULL,
port INTEGER NOT NULL DEFAULT 2775,
netmask INTEGER NOT NULL DEFAULT 32,
is_primary BOOLEAN NOT NULL DEFAULT 1,
inbound BOOLEAN NOT NULL DEFAULT 0 COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
outbound BOOLEAN NOT NULL DEFAULT 1 COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
use_tls BOOLEAN DEFAULT 0,
voip_carrier_sid CHAR(36) NOT NULL,
PRIMARY KEY (smpp_gateway_sid)
);
CREATE TABLE phone_numbers CREATE TABLE phone_numbers
( (
phone_number_sid CHAR(36) UNIQUE , phone_number_sid CHAR(36) UNIQUE ,
number VARCHAR(32) NOT NULL UNIQUE , number VARCHAR(32) NOT NULL UNIQUE ,
voip_carrier_sid CHAR(36) NOT NULL, voip_carrier_sid CHAR(36),
account_sid CHAR(36), account_sid CHAR(36),
application_sid CHAR(36), application_sid CHAR(36),
service_provider_sid CHAR(36) COMMENT 'if not null, this number is a test number for the associated service provider',
PRIMARY KEY (phone_number_sid) PRIMARY KEY (phone_number_sid)
) ENGINE=InnoDB COMMENT='A phone number that has been assigned to an account'; ) COMMENT='A phone number that has been assigned to an account';
CREATE TABLE webhooks
(
webhook_sid CHAR(36) NOT NULL UNIQUE ,
url VARCHAR(1024) NOT NULL,
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
username VARCHAR(255),
password VARCHAR(255),
PRIMARY KEY (webhook_sid)
) COMMENT='An HTTP callback';
CREATE TABLE sip_gateways CREATE TABLE sip_gateways
( (
sip_gateway_sid CHAR(36), sip_gateway_sid CHAR(36),
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.', ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
netmask INTEGER NOT NULL DEFAULT 32,
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port', port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway', inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN', outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
@@ -147,11 +338,22 @@ priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempt
PRIMARY KEY (lcr_carrier_set_entry_sid) PRIMARY KEY (lcr_carrier_set_entry_sid)
) COMMENT='An entry in the LCR routing list'; ) COMMENT='An entry in the LCR routing list';
CREATE TABLE webhooks
(
webhook_sid CHAR(36) NOT NULL UNIQUE ,
url VARCHAR(1024) NOT NULL,
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
username VARCHAR(255),
password VARCHAR(255),
PRIMARY KEY (webhook_sid)
) COMMENT='An HTTP callback';
CREATE TABLE applications CREATE TABLE applications
( (
application_sid CHAR(36) NOT NULL UNIQUE , application_sid CHAR(36) NOT NULL UNIQUE ,
name VARCHAR(64) NOT NULL, name VARCHAR(64) NOT NULL,
account_sid CHAR(36) NOT NULL COMMENT 'account that this application belongs to', service_provider_sid CHAR(36) COMMENT 'if non-null, this application is a test application that can be used by any account under the associated service provider',
account_sid CHAR(36) COMMENT 'account that this application belongs to (if null, this is a service provider test application)',
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ', call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ',
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events', call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ', messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
@@ -160,8 +362,9 @@ speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
speech_synthesis_voice VARCHAR(64), speech_synthesis_voice VARCHAR(64),
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google', speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US', speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (application_sid) PRIMARY KEY (application_sid)
) ENGINE=InnoDB COMMENT='A defined set of behaviors to be applied to phone calls '; ) COMMENT='A defined set of behaviors to be applied to phone calls ';
CREATE TABLE service_providers CREATE TABLE service_providers
( (
@@ -172,7 +375,7 @@ root_domain VARCHAR(128) UNIQUE ,
registration_hook_sid CHAR(36), registration_hook_sid CHAR(36),
ms_teams_fqdn VARCHAR(255), ms_teams_fqdn VARCHAR(255),
PRIMARY KEY (service_provider_sid) PRIMARY KEY (service_provider_sid)
) ENGINE=InnoDB COMMENT='A partition of the platform used by one service provider'; ) COMMENT='A partition of the platform used by one service provider';
CREATE TABLE accounts CREATE TABLE accounts
( (
@@ -181,67 +384,144 @@ name VARCHAR(64) NOT NULL,
sip_realm VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account', sip_realm VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
service_provider_sid CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account', service_provider_sid CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
registration_hook_sid CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register', registration_hook_sid CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
queue_event_hook_sid CHAR(36),
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account', device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
is_active BOOLEAN NOT NULL DEFAULT true, is_active BOOLEAN NOT NULL DEFAULT true,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
plan_type ENUM('trial','free','paid') NOT NULL DEFAULT 'trial',
stripe_customer_id VARCHAR(56),
webhook_secret VARCHAR(36) NOT NULL,
disable_cdrs BOOLEAN NOT NULL DEFAULT 0,
trial_end_date DATETIME,
deactivated_reason VARCHAR(255),
device_to_call_ratio INTEGER NOT NULL DEFAULT 5,
PRIMARY KEY (account_sid) PRIMARY KEY (account_sid)
) ENGINE=InnoDB COMMENT='An enterprise that uses the platform for comm services'; ) COMMENT='An enterprise that uses the platform for comm services';
CREATE INDEX account_static_ip_sid_idx ON account_static_ips (account_static_ip_sid);
CREATE INDEX account_sid_idx ON account_static_ips (account_sid);
ALTER TABLE account_static_ips ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subscription_sid);
CREATE INDEX account_sid_idx ON account_subscriptions (account_sid);
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX invite_code_idx ON beta_invite_codes (invite_code);
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid); CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid); ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid); ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
ALTER TABLE predefined_sip_gateways ADD FOREIGN KEY predefined_carrier_sid_idxfk (predefined_carrier_sid) REFERENCES predefined_carriers (predefined_carrier_sid);
CREATE INDEX product_sid_idx ON products (product_sid);
CREATE INDEX account_product_sid_idx ON account_products (account_product_sid);
CREATE INDEX account_subscription_sid_idx ON account_products (account_subscription_sid);
ALTER TABLE account_products ADD FOREIGN KEY account_subscription_sid_idxfk (account_subscription_sid) REFERENCES account_subscriptions (account_subscription_sid);
ALTER TABLE account_products ADD FOREIGN KEY product_sid_idxfk (product_sid) REFERENCES products (product_sid);
CREATE INDEX account_offer_sid_idx ON account_offers (account_offer_sid);
CREATE INDEX account_sid_idx ON account_offers (account_sid);
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX product_sid_idx ON account_offers (product_sid);
ALTER TABLE account_offers ADD FOREIGN KEY product_sid_idxfk_1 (product_sid) REFERENCES products (product_sid);
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid); CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
CREATE INDEX account_sid_idx ON api_keys (account_sid); CREATE INDEX account_sid_idx ON api_keys (account_sid);
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid); ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid); CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid); ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
CREATE INDEX sbc_addresses_idx_host_port ON sbc_addresses (ipv4,port); CREATE INDEX sbc_addresses_idx_host_port ON sbc_addresses (ipv4,port);
CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid); CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid);
CREATE INDEX service_provider_sid_idx ON sbc_addresses (service_provider_sid); CREATE INDEX service_provider_sid_idx ON sbc_addresses (service_provider_sid);
ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid); ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
CREATE INDEX email_idx ON signup_history (email);
CREATE INDEX smpp_address_sid_idx ON smpp_addresses (smpp_address_sid);
CREATE INDEX service_provider_sid_idx ON smpp_addresses (service_provider_sid);
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX user_sid_idx ON users (user_sid); CREATE INDEX user_sid_idx ON users (user_sid);
CREATE INDEX name_idx ON users (name); CREATE INDEX email_idx ON users (email);
CREATE INDEX phone_idx ON users (phone);
CREATE INDEX account_sid_idx ON users (account_sid);
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX service_provider_sid_idx ON users (service_provider_sid);
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX email_activation_code_idx ON users (email_activation_code);
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid); CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
CREATE INDEX name_idx ON voip_carriers (name); CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid); ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
CREATE INDEX service_provider_sid_idx ON voip_carriers (service_provider_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid); ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid); CREATE INDEX smpp_gateway_sid_idx ON smpp_gateways (smpp_gateway_sid);
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid); CREATE INDEX voip_carrier_sid_idx ON smpp_gateways (voip_carrier_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid); ALTER TABLE smpp_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid); CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
CREATE INDEX number_idx ON phone_numbers (number);
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid); ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid); CREATE INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
CREATE UNIQUE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port); ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid); CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
CREATE INDEX voip_carrier_sid_idx ON sip_gateways (voip_carrier_sid);
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid); ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid);
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid); ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_3 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name); CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
CREATE INDEX application_sid_idx ON applications (application_sid); CREATE INDEX application_sid_idx ON applications (application_sid);
CREATE INDEX service_provider_sid_idx ON applications (service_provider_sid);
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
CREATE INDEX account_sid_idx ON applications (account_sid); CREATE INDEX account_sid_idx ON applications (account_sid);
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid); ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid); ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
@@ -257,10 +537,12 @@ ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (regis
CREATE INDEX account_sid_idx ON accounts (account_sid); CREATE INDEX account_sid_idx ON accounts (account_sid);
CREATE INDEX sip_realm_idx ON accounts (sip_realm); CREATE INDEX sip_realm_idx ON accounts (sip_realm);
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid); CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid); ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_9 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid); ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE accounts ADD FOREIGN KEY queue_event_hook_sid_idxfk (queue_event_hook_sid) REFERENCES webhooks (webhook_sid);
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid); ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
SET FOREIGN_KEY_CHECKS=1; SET FOREIGN_KEY_CHECKS=1;

View File

@@ -1,22 +1,60 @@
-- create our products
insert into products (product_sid, name, category)
values
('c4403cdb-8e75-4b27-9726-7d8315e3216d', 'concurrent call session', 'voice_call_session'),
('2c815913-5c26-4004-b748-183b459329df', 'registered device', 'device'),
('35a9fb10-233d-4eb9-aada-78de5814d680', 'api call', 'api_rate');
insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2-47d1-8164-5bd49003476c', 'http://127.0.0.1:4000/auth', 'foo', 'bar'); insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2-47d1-8164-5bd49003476c', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid) insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c'); values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'sip.example.com', '90dda62e-0ea2-47d1-8164-5bd49003476c');
insert into voip_carriers (voip_carrier_sid, name) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco'); insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret)
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar');
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
values ('f4e1848d-3ff8-40eb-b9c1-30e1ef053f94','ed649e33-e771-403a-8c99-1780eabbc803',0);
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
values ('f23ff996-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 10);
insert into voip_carriers (voip_carrier_sid, name, account_sid) values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', 'ed649e33-e771-403a-8c99-1780eabbc803');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound) insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('124a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.38.0.20/32', true, true); values ('124a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.38.0.20', true, true);
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, port, inbound, outbound) insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, port, inbound, outbound)
values ('efbc4830-57cd-4c78-a56f-d64fdf210fe8', '287c1452-620d-4195-9f19-c9814ef90d78', '3.3.3.3', 5062, false, true); values ('efbc4830-57cd-4c78-a56f-d64fdf210fe8', '287c1452-620d-4195-9f19-c9814ef90d78', '3.3.3.3', 5062, false, true);
insert into webhooks(webhook_sid, url) values('4d7ce0aa-5ead-4e61-9a6b-3daa732218b1', 'http://example.com/status'); insert into webhooks(webhook_sid, url) values('4d7ce0aa-5ead-4e61-9a6b-3daa732218b1', 'http://example.com/status');
insert into accounts (account_sid, name, service_provider_sid)
values ('ee9d7d49-b3e4-4fdb-9d66-661149f717e8', 'Account A1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0'); insert into accounts (account_sid, name, service_provider_sid, webhook_secret, sip_realm)
values ('ee9d7d49-b3e4-4fdb-9d66-661149f717e8', 'Account A1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'foobar', 'delta.sip.jambonz.org');
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
values ('4f3853e6-d8b0-43de-ba62-d9279801695d','ee9d7d49-b3e4-4fdb-9d66-661149f717e8',0);
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
values ('61840638-ccde-4bc5-b645-5d32718c68a5', '4f3853e6-d8b0-43de-ba62-d9279801695d', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 10);
insert into accounts (account_sid, name, service_provider_sid, webhook_secret, sip_realm)
values ('d7cc37cb-d152-49ef-a51b-485f6e917089', 'Account A1', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'foobar', 'echo.sip.jambonz.org');
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
values ('73bbcc5d-512f-4cea-8535-9a6e3d2bd19d','d7cc37cb-d152-49ef-a51b-485f6e917089',0);
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
values ('92f137f7-4bc3-4157-b096-6817e54b1874', '73bbcc5d-512f-4cea-8535-9a6e3d2bd19d', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 0);
insert into voip_carriers (voip_carrier_sid, name, account_sid) values ('9b1abdc7-0220-4964-bc66-32b5c70cd9ab', 'westco', 'd7cc37cb-d152-49ef-a51b-485f6e917089');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('12f401d9-cbb1-49e5-bd33-cefbca0badc3', '9b1abdc7-0220-4964-bc66-32b5c70cd9ab', '172.38.0.20', true, true);
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, port, inbound, outbound)
values ('1401eb72-0daf-4471-aba6-038a0a2587b3', '9b1abdc7-0220-4964-bc66-32b5c70cd9ab', '3.3.3.3', 5062, false, true);
insert into applications (application_sid, name, account_sid, call_hook_sid, call_status_hook_sid) insert into applications (application_sid, name, account_sid, call_hook_sid, call_status_hook_sid)
values ('3b43e39f-4346-4218-8434-a53130e8be49', 'test', 'ee9d7d49-b3e4-4fdb-9d66-661149f717e8', '90dda62e-0ea2-47d1-8164-5bd49003476c', '4d7ce0aa-5ead-4e61-9a6b-3daa732218b1'); values ('3b43e39f-4346-4218-8434-a53130e8be49', 'test', 'ee9d7d49-b3e4-4fdb-9d66-661149f717e8', '90dda62e-0ea2-47d1-8164-5bd49003476c', '4d7ce0aa-5ead-4e61-9a6b-3daa732218b1');
insert into voip_carriers (voip_carrier_sid, name, account_sid, application_sid) values ('999c1452-620d-4195-9f19-c9814ef90d78', 'customer PBX', 'ee9d7d49-b3e4-4fdb-9d66-661149f717e8', '3b43e39f-4346-4218-8434-a53130e8be49'); insert into voip_carriers (voip_carrier_sid, name, account_sid, application_sid) values ('999c1452-620d-4195-9f19-c9814ef90d78', 'customer PBX', 'ee9d7d49-b3e4-4fdb-9d66-661149f717e8', '3b43e39f-4346-4218-8434-a53130e8be49');
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound) insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
values ('888a5339-c62c-4075-9e19-f4de70a96597', '999c1452-620d-4195-9f19-c9814ef90d78', '172.38.0.21', true, false); values ('888a5339-c62c-4075-9e19-f4de70a96597', '999c1452-620d-4195-9f19-c9814ef90d78', '172.38.0.21', true, false);
insert into phone_numbers (phone_number_sid, number, voip_carrier_sid, account_sid)
values ('999a5339-c62c-4075-9e19-f4de70a96597', '16173333456', '287c1452-620d-4195-9f19-c9814ef90d78', 'ed649e33-e771-403a-8c99-1780eabbc803');
insert into phone_numbers (phone_number_sid, number, voip_carrier_sid, account_sid)
values ('f7ad205d-b92f-4363-8160-f8b5216b40d3', '15083871234', '287c1452-620d-4195-9f19-c9814ef90d78', 'd7cc37cb-d152-49ef-a51b-485f6e917089');

View File

@@ -17,7 +17,7 @@ services:
healthcheck: healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "--protocol", "tcp"] test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "--protocol", "tcp"]
timeout: 5s timeout: 5s
retries: 10 retries: 15
networks: networks:
sbc-inbound: sbc-inbound:
ipv4_address: 172.38.0.2 ipv4_address: 172.38.0.2
@@ -70,3 +70,10 @@ services:
sbc-inbound: sbc-inbound:
ipv4_address: 172.38.0.14 ipv4_address: 172.38.0.14
influxdb:
image: influxdb:1.8-alpine
ports:
- "8086:8086"
networks:
sbc-inbound:
ipv4_address: 172.38.0.90

View File

@@ -1,9 +1,9 @@
const test = require('tape').test ; const test = require('tape').test ;
const exec = require('child_process').exec ; const exec = require('child_process').exec ;
const async = require('async');
test('starting docker network..', (t) => { test('starting docker network..', (t) => {
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => { exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => {
t.pass('docker is up');
t.end(err); t.end(err);
}); });

View File

@@ -53,7 +53,7 @@
<recv response="100" optional="true"> <recv response="100" optional="true">
</recv> </recv>
<recv response="403"> <recv response="404">
</recv> </recv>
<send> <send>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="UAC with media">
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
<!-- generated by sipp. To do so, use [call_id] keyword. -->
<send retrans="500">
<![CDATA[
INVITE sip:+15083871234@echo.sip.jambonz.org SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:15083871234@echo.sip.jambonz.org>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-pcap-carrier-max-call-limit
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[local_ip_type] [local_ip]
t=0 0
m=audio [auto_media_port] RTP/AVP 8 101
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-11,16
]]>
</send>
<recv response="100" optional="true">
</recv>
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
<!-- are saved and used for following messages sent. Useful to test -->
<!-- against stateful SIP proxies/B2BUAs. -->
<recv response="503" rtd="true" crlf="true">
</recv>
<!-- Packet lost can be simulated in any send/recv message by -->
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
<send>
<![CDATA[
ACK sip:15083871234@echo.sip.jambonz.org SIP/2.0
[last_Via]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: <sip:15083871234@echo.sip.jambonz.org>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Max-Forwards: 70
Subject: uac-pcap-carrier-max-call-limit
Content-Length: 0
]]>
</send>
<!-- definition of the response time repartition table (unit is ms) -->
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
<!-- definition of the call length repartition table (unit is ms) -->
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
</scenario>

View File

@@ -25,10 +25,10 @@
<send retrans="500"> <send retrans="500">
<![CDATA[ <![CDATA[
INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 INVITE sip:+16173333456@jambonz.org SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number] From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]> To: <sip:16173333456@jambonz.org>
Call-ID: [call_id] Call-ID: [call_id]
CSeq: 1 INVITE CSeq: 1 INVITE
Contact: sip:sipp@[local_ip]:[local_port] Contact: sip:sipp@[local_ip]:[local_port]
@@ -67,10 +67,10 @@
<send> <send>
<![CDATA[ <![CDATA[
ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0 ACK sip:16173333456@jambonz.org SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number] From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] To: <sip:16173333456@jambonz.org>[peer_tag_param]
Call-ID: [call_id] Call-ID: [call_id]
CSeq: 1 ACK CSeq: 1 ACK
Max-Forwards: 70 Max-Forwards: 70
@@ -94,10 +94,10 @@
<send retrans="500"> <send retrans="500">
<![CDATA[ <![CDATA[
BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 BYE sip:16173333456@jambonz.org SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number] From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag09[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] To: <sip:16173333456@jambonz.org>[peer_tag_param]
Call-ID: [call_id] Call-ID: [call_id]
CSeq: 2 BYE CSeq: 2 BYE
Subject: uac-pcap-carrier-success Subject: uac-pcap-carrier-success

View File

@@ -1,7 +1,8 @@
const test = require('blue-tape'); const test = require('tape');
const { output, sippUac } = require('./sipp')('test_sbc-inbound'); const { output, sippUac } = require('./sipp')('test_sbc-inbound');
const debug = require('debug')('drachtio:sbc-inbound'); const debug = require('debug')('drachtio:sbc-inbound');
const clearModule = require('clear-module'); const clearModule = require('clear-module');
const consoleLogger = {error: console.error, info: console.log, debug: console.log};
process.on('unhandledRejection', (reason, p) => { process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
@@ -15,60 +16,52 @@ function connect(connectable) {
}); });
} }
test('incoming call tests', (t) => { function waitFor(ms) {
const {srf} = require('../app'); return new Promise((resolve) => {
setTimeout(resolve, ms * 1000);
});
}
connect(srf) test('incoming call tests', async(t) => {
.then(() => { const {srf} = require('../app');
return sippUac('uac-pcap-carrier-success.xml', '172.38.0.20'); const { queryCdrs } = srf.locals;
})
.then(() => { try {
return t.pass('incoming call from carrier completed successfully'); await connect(srf);
}) await sippUac('uac-pcap-carrier-success.xml', '172.38.0.20');
.then(() => { t.pass('incoming call from carrier completed successfully');
return sippUac('uac-pcap-pbx-success.xml', '172.38.0.21');
}) await sippUac('uac-pcap-pbx-success.xml', '172.38.0.21');
.then(() => { t.pass('incoming call from account-level carrier completed successfully');
return t.pass('incoming call from customer PBX completed successfully');
}) await sippUac('uac-pcap-device-success.xml', '172.38.0.30');
.then(() => { t.pass('incoming call from authenticated device completed successfully');
return sippUac('uac-pcap-device-success.xml', '172.38.0.30');
}) await sippUac('uac-device-unknown-user.xml', '172.38.0.30');
.then(() => { t.pass('unknown user is rejected with a 403');
return t.pass('incoming call from authenticated device completed successfully');
}) await sippUac('uac-device-unknown-realm.xml', '172.38.0.30');
.then(() => { t.pass('unknown realm is rejected with a 404');
return sippUac('uac-device-unknown-user.xml', '172.38.0.30');
}) await sippUac('uac-device-invalid-password.xml', '172.38.0.30');
.then(() => { t.pass('invalid password for valid user is rejected with a 403');
return t.pass('unknown user is rejected with a 403');
}) await sippUac('uac-pcap-device-success-in-dialog-request.xml', '172.38.0.30');
.then(() => { t.pass('handles in-dialog requests');
return sippUac('uac-device-unknown-realm.xml', '172.38.0.30');
}) await sippUac('uac-pcap-carrier-max-call-limit.xml', '172.38.0.20');
.then(() => { t.pass('rejects incoming call with 503 when max calls reached')
return t.pass('unknown realm is rejected with a 403');
}) await waitFor(10);
.then(() => { const res = await queryCdrs({account_sid: 'ed649e33-e771-403a-8c99-1780eabbc803'});
return sippUac('uac-device-invalid-password.xml', '172.38.0.30'); console.log(`cdrs: ${JSON.stringify(res)}`);
}) t.ok(6 === res.total, 'successfully wrote 6 cdrs for calls');
.then(() => {
return t.pass('invalid password for valid user is rejected with a 403'); srf.disconnect();
}) t.end();
.then(() => { } catch (err) {
return sippUac('uac-pcap-device-success-in-dialog-request.xml', '172.38.0.30'); console.log(`error received: ${err}`);
}) if (srf) srf.disconnect();
.then(() => { t.error(err);
return t.pass('handles in-dialog requests'); }
})
.then(() => {
srf.disconnect();
t.end();
return;
})
.catch((err) => {
console.log(`error received: ${err}`);
if (srf) srf.disconnect();
t.error(err);
});
}); });