Skip to content

Commit

Permalink
feature request #99: support for generating dialoginfo events (RFC 4235)
Browse files Browse the repository at this point in the history
  • Loading branch information
davehorton committed Dec 11, 2020
1 parent c240d54 commit cf80ed9
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 2 deletions.
16 changes: 16 additions & 0 deletions lib/dialog.js
Expand Up @@ -111,6 +111,11 @@ class Dialog extends Emitter {
return this.req.socket;
}

set stateEmitter({emitter, state}) {
this._emitter = emitter;
this._state = state;
}

set queueRequests(enqueue) {
debug(`dialog ${this.id}: queueing requests: ${enqueue ? 'ON' : 'OFF'}`);
this._queueRequests = enqueue;
Expand Down Expand Up @@ -191,6 +196,11 @@ class Dialog extends Emitter {
callback(err, bye) ;
this.removeAllListeners();
}) ;
if (this._emitter) {
Object.assign(this._state, {state: 'terminated'});
this._emitter.emit('stateChange', this._state);
this._emitter = null;
}
}
else if (this.dialogType === 'SUBSCRIBE') {
opts.headers = opts.headers || {} ;
Expand Down Expand Up @@ -400,6 +410,12 @@ class Dialog extends Emitter {
const eventName = req.method.toLowerCase() ;
switch (req.method) {
case 'BYE':
if (this._emitter) {
Object.assign(this._state, {state: 'terminated'});
this._emitter.emit('stateChange', this._state);
this._emitter = null;
}

let reason = 'normal release';
if (req.meta.source === 'application') {
if (req.has('Reason')) {
Expand Down
133 changes: 132 additions & 1 deletion lib/srf.js
Expand Up @@ -11,6 +11,21 @@ const deprecate = require('deprecate');
const debug = require('debug')('drachtio:srf') ;
const Socket = require('net').Socket;
const noop = () => {};
const idgen = require('short-uuid')();

class DialogState {}
class DialogDirection {}

DialogState.Trying = 'trying';
DialogState.Proceeding = 'proceeding';
DialogState.Early = 'early';
DialogState.Confirmed = 'confirmed',
DialogState.Terminated = 'terminated';
DialogState.Rejected = 'rejected';
DialogState.Cancelled = 'cancelled';

DialogDirection.Initiator = 'initiator';
DialogDirection.Recipient = 'recipient';

const noncopyableHdrs = ['via', 'from', 'to', 'call-id', 'cseq', 'contact', 'content-length', 'content-type'];
function copyAllHeaders(msg, obj) {
Expand Down Expand Up @@ -202,12 +217,35 @@ class Srf extends Emitter {
callback(err);
};

if (req.method === 'INVITE'
&& opts.dialogStateEmitter && opts.dialogStateEmitter.listenerCount('stateChange') > 0) {
if (!req._dialogState) {
const from = req.getParsedHeader('from');
const uri = Srf.parseUri(from.uri);
if (uri.user && uri.host) {
req._dialogState = {
state: DialogState.Trying,
direction: DialogDirection.Initiator,
aor: `${uri.user || 'unknown'}@${uri.host || 'unknown'}`,
callId: req.get('Call-ID'),
localTag: from.params.tag,
id: idgen.new()
};
opts.dialogStateEmitter.emit('stateChange', req._dialogState);
}
}
}

const __send = (callback, content) => {
let called = false;
debug('createUAS sending');

req.on('cancel', () => {
req.canceled = called = true ;
if (req._dialogState) {
Object.assign(req._dialogState, {state: DialogState.Cancelled});
opts.dialogStateEmitter.emit('stateChange', req._dialogState);
}
callback(new SipError(487, 'Request Terminated')) ;
}) ;

Expand All @@ -217,17 +255,40 @@ class Srf extends Emitter {
}, (err, response) => {
if (err) {
debug(`createUAS: send failed with ${err}`);
if (req._dialogState) {
Object.assign(req._dialogState, {
state: DialogState.Rejected
});
opts.dialogStateEmitter.emit('stateChange', req._dialogState);
}
if (!called) {
called = true;
callback(err);
}
return;
}

if (req._dialogState) {
const to = response.getParsedHeader('to');
Object.assign(req._dialogState, {
state: DialogState.Confirmed,
localTag: to.params.tag
});
opts.dialogStateEmitter.emit('stateChange', req._dialogState);
}


// note: we used to invoke callback after ACK was received
// now we send it at the time we send the 200 OK
// this is in keeping with the RFC 3261 spec
const dialog = new Dialog(this, 'uas', {req: req, res: res, sent: response}) ;
if (req._dialogState) {
dialog.stateEmitter = {
emitter: opts.dialogStateEmitter,
state: req._dialogState
};
}

this.addDialog(dialog);
callback(null, dialog);

Expand Down Expand Up @@ -412,16 +473,55 @@ class Srf extends Emitter {
cbRequest(err);
return callback(err) ;
}
if ('INVITE' === method &&
opts.dialogStateEmitter && opts.dialogStateEmitter.listenerCount('stateChange') > 0) {

const from = req.getParsedHeader('from');
const to = req.getParsedHeader('to');
const uri = Srf.parseUri(to.uri);
if (uri.user && uri.host) {
req._dialogState = {
state: DialogState.Trying,
direction: DialogDirection.Recipient,
aor: `${uri.user || 'unknown'}@${uri.host || 'unknown'}`,
callId: req.get('Call-ID'),
localTag: from.params.tag,
id: idgen.new()
};
}
opts.dialogStateEmitter.emit('stateChange', req._dialogState);
}
cbRequest(null, req) ;

req.on('response', (res, ack) => {
if (res.status < 200) {
if (req._dialogState && req._dialogState.state !== DialogState.Early) {
const to = res.getParsedHeader('to');
if (to.params.tag) {
Object.assign(req._dialogState, {remoteTag: to.params.tag, state: DialogState.Early});
opts.dialogStateEmitter.emit('stateChange', req._dialogState);
}
else if (req._dialogState.state === DialogState.Trying) {
Object.assign(req._dialogState, {state: DialogState.Proceeding});
opts.dialogStateEmitter.emit('stateChange', req._dialogState);
}
}
cbProvisional(res) ;
if (res.has('RSeq')) {
ack() ; // send PRACK
}
}
else {
if (req._dialogState) {
const to = res.getParsedHeader('to');
const state = (200 === res.status ?
DialogState.Confirmed :
(487 === res.status ? DialogState.Cancelled : DialogState.Rejected));
Object.assign(req._dialogState, {
remoteTag: to.params.tag,
state});
opts.dialogStateEmitter.emit('stateChange', req._dialogState);
}
if (is3pcc && 200 === res.status && !!res.body) {

if (opts.noAck === true) {
Expand Down Expand Up @@ -458,6 +558,12 @@ class Srf extends Emitter {
if ((200 === res.status && method === 'INVITE') ||
((202 === res.status || 200 === res.status) && method === 'SUBSCRIBE')) {
const dialog = new Dialog(this, 'uac', {req: req, res: res}) ;
if (req._dialogState) {
dialog.stateEmitter = {
emitter: opts.dialogStateEmitter,
state: req._dialogState
};
}
this.addDialog(dialog) ;
return callback(null, dialog) ;
}
Expand Down Expand Up @@ -808,6 +914,23 @@ class Srf extends Emitter {

opts._socket = req.socket ;

// emit dialog events, per https://tools.ietf.org/html/rfc4235#section-3.7.1
if (opts.dialogStateEmitter && opts.dialogStateEmitter.listenerCount('stateChange') > 0) {
const from = req.getParsedHeader('from');
const uri = Srf.parseUri(from.uri);
if (uri.user && uri.host) {
req._dialogState = {
state: DialogState.Trying,
direction: DialogDirection.Initiator,
aor: `${uri.user || 'unknown'}@${uri.host || 'unknown'}`,
callId: req.get('Call-ID'),
remoteTag: from.params.tag,
id: idgen.new()
};
opts.dialogStateEmitter.emit('stateChange', req._dialogState);
}
}

this.createUAC(opts, {cbRequest: handleUACSent, cbProvisional: handleUACProvisionalResponse})
.then((uac) => {

Expand All @@ -821,7 +944,8 @@ class Srf extends Emitter {

return this.createUAS(req, res, {
headers: copyUACHeadersToUAS(uac.res),
localSdp: generateSdpA.bind(null, uac.res)
localSdp: generateSdpA.bind(null, uac.res),
dialogStateEmitter: opts.dialogStateEmitter
})
.then((uas) => {
debug('createB2BUA: successfully created UAS..done!');
Expand Down Expand Up @@ -1497,6 +1621,13 @@ class Srf extends Emitter {
static get SipResponse() {
return require('./response');
}

static get DialogState() {
return DialogState;
}
static get DialogDirection() {
return DialogDirection;
}
}

module.exports = exports = Srf ;
Expand Down
3 changes: 2 additions & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "drachtio-srf",
"version": "4.4.43",
"version": "4.4.44",
"description": "drachtio signaling resource framework",
"main": "lib/srf.js",
"scripts": {
Expand Down Expand Up @@ -32,6 +32,7 @@
"node-noop": "0.0.1",
"only": "0.0.2",
"sdp-transform": "^2.14.1",
"short-uuid": "^4.1.0",
"sip-methods": "^0.3.0",
"utils-merge": "1.0.0",
"uuid": "^3.4.0"
Expand Down

0 comments on commit cf80ed9

Please sign in to comment.