drachtio-srf 4.5.0 → 4.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/dialog.js CHANGED
@@ -198,21 +198,24 @@ class Dialog extends Emitter {
198
198
 
199
199
  const __x = (callback) => {
200
200
  if (this.dialogType === 'INVITE') {
201
- this.agent.request({
202
- method: 'BYE',
203
- headers: opts.headers || {},
204
- stackDialogId: this.id,
205
- auth: opts.auth || this.auth,
206
- _socket: this.socket
207
- }, (err, bye) => {
208
- if (err || !bye) {
209
- return removeDialog(err, bye, callback);
210
- }
211
-
212
- bye.on('response', () => {
213
- removeDialog(err, bye, callback);
201
+ try {
202
+ this.agent.request({
203
+ method: 'BYE',
204
+ headers: opts.headers || {},
205
+ stackDialogId: this.id,
206
+ auth: opts.auth || this.auth,
207
+ _socket: this.socket
208
+ }, (err, bye) => {
209
+ if (err || !bye) {
210
+ return removeDialog(err, bye, callback);
211
+ }
212
+ bye.on('response', () => {
213
+ removeDialog(err, bye, callback);
214
+ });
214
215
  });
215
- }) ;
216
+ } catch (err) {
217
+ removeDialog(err, null, callback);
218
+ }
216
219
  if (this._emitter) {
217
220
  Object.assign(this._state, {state: 'terminated'});
218
221
  this._emitter.emit('stateChange', this._state);
@@ -223,14 +226,18 @@ class Dialog extends Emitter {
223
226
  opts.headers = opts.headers || {} ;
224
227
  opts.headers['subscription-state'] = 'terminated';
225
228
  opts.headers['event'] = this.subscribeEvent ;
226
- this.agent.request({
227
- method: 'NOTIFY',
228
- headers: opts.headers || {},
229
- stackDialogId: this.id,
230
- _socket: this.socket
231
- }, (err, notify) => {
232
- removeDialog(err, notify, callback);
233
- }) ;
229
+ try {
230
+ this.agent.request({
231
+ method: 'NOTIFY',
232
+ headers: opts.headers || {},
233
+ stackDialogId: this.id,
234
+ _socket: this.socket
235
+ }, (err, notify) => {
236
+ removeDialog(err, notify, callback);
237
+ }) ;
238
+ } catch (err) {
239
+ removeDialog(err, null, callback);
240
+ }
234
241
  }
235
242
  };
236
243
 
@@ -318,43 +325,48 @@ class Dialog extends Emitter {
318
325
  if (opts.headers && !opts.headers['Contact']) {
319
326
  opts.headers['Contact'] = this.local.contact;
320
327
  }
321
- this.agent.request({
322
- method: 'INVITE',
323
- stackDialogId: this.id,
324
- body: this.local.sdp,
325
- _socket: this.socket,
326
- headers:
327
- opts.headers ? opts.headers : {'Contact': this.local.contact}
328
- }, (err, req) => {
329
- if (err) {
330
- this._reinvitesInProgress.count--;
331
- const admitOne = this._reinvitesInProgress.admitOne.shift();
332
- if (admitOne) setImmediate(admitOne);
333
- return callback(err);
334
- }
335
- req.on('response', (res, ack) => {
336
- debug(`Dialog#modifySession: received response to reINVITE with status ${res.status}`) ;
337
- if (res.status >= 200) {
338
- if (200 === res.status) {
339
- this.remote.sdp = res.body ;
340
- if (this.local.sdp || opts.noAck !== true) {
341
- ack() ;
342
- onReInviteComplete(this);
343
- return callback(null, res.body);
344
- }
345
- else {
346
- debug(`opts: ${JSON.stringify(opts)}`);
347
- debug('SipDialog#modify: go 200 OK to 3pcc INVITE with no sdp; ack is application responsibility');
348
- return callback(null, res.body, (sdp) => {
349
- ack({body: sdp});
328
+ try {
329
+ this.agent.request({
330
+ method: 'INVITE',
331
+ stackDialogId: this.id,
332
+ body: this.local.sdp,
333
+ _socket: this.socket,
334
+ auth: opts.auth || this.auth,
335
+ headers:
336
+ opts.headers ? opts.headers : {'Contact': this.local.contact}
337
+ }, (err, req) => {
338
+ if (err) {
339
+ this._reinvitesInProgress.count--;
340
+ const admitOne = this._reinvitesInProgress.admitOne.shift();
341
+ if (admitOne) setImmediate(admitOne);
342
+ return callback(err);
343
+ }
344
+ req.on('response', (res, ack) => {
345
+ debug(`Dialog#modifySession: received response to reINVITE with status ${res.status}`) ;
346
+ if (res.status >= 200) {
347
+ if (200 === res.status) {
348
+ this.remote.sdp = res.body ;
349
+ if (this.local.sdp || opts.noAck !== true) {
350
+ ack() ;
350
351
  onReInviteComplete(this);
351
- });
352
+ return callback(null, res.body);
353
+ }
354
+ else {
355
+ debug(`opts: ${JSON.stringify(opts)}`);
356
+ debug('SipDialog#modify: go 200 OK to 3pcc INVITE with no sdp; ack is application responsibility');
357
+ return callback(null, res.body, (sdp) => {
358
+ ack({body: sdp});
359
+ onReInviteComplete(this);
360
+ });
361
+ }
352
362
  }
363
+ callback(new SipError(res.status, res.reason)) ;
353
364
  }
354
- callback(new SipError(res.status, res.reason)) ;
355
- }
356
- }) ;
357
- });
365
+ }) ;
366
+ });
367
+ } catch (err) {
368
+ callback(err);
369
+ }
358
370
  });
359
371
  };
360
372
 
@@ -398,45 +410,48 @@ class Dialog extends Emitter {
398
410
  const __x = (callback) => {
399
411
  const method = opts.method.toUpperCase() ;
400
412
 
401
- this.agent.request({
402
- method: method,
403
- stackDialogId: this.id,
404
- headers: opts.headers || {},
405
- auth: opts.auth || this.auth,
406
- _socket: this.socket,
407
- body: opts.body
408
- }, (err, req) => {
409
- if (err) {
410
- return callback(err) ;
411
- }
412
-
413
- req.on('response', (res, ack) => {
414
- if ('BYE' === method) {
415
- this.srf.removeDialog(this) ;
413
+ try {
414
+ this.agent.request({
415
+ method: method,
416
+ stackDialogId: this.id,
417
+ headers: opts.headers || {},
418
+ auth: opts.auth || this.auth,
419
+ _socket: this.socket,
420
+ body: opts.body
421
+ }, (err, req) => {
422
+ if (err) {
423
+ return callback(err) ;
416
424
  }
417
- if ('INVITE' === method && res.status >= 200) {
418
- if (res.status > 200 || opts.body || opts.noAck !== true) ack() ;
419
- else {
420
- debug('SipDialog#request: received 200 OK to 3pcc INVITE sent with no sdp; deferring ack to application');
421
- return callback(null, res, ack) ;
425
+
426
+ req.on('response', (res, ack) => {
427
+ if ('BYE' === method) {
428
+ this.srf.removeDialog(this) ;
429
+ }
430
+ if ('INVITE' === method && res.status >= 200) {
431
+ if (res.status > 200 || opts.body || opts.noAck !== true) ack() ;
432
+ else {
433
+ return callback(null, res, ack) ;
434
+ }
422
435
  }
423
- }
424
436
 
425
- if (this.dialogType === 'SUBSCRIBE' && 'NOTIFY' === method &&
426
- /terminated/.test(req.get('Subscription-State'))) {
427
- debug('received response to a NOTIFY we sent terminating final subscription; dialog is ended') ;
437
+ if (this.dialogType === 'SUBSCRIBE' && 'NOTIFY' === method &&
438
+ /terminated/.test(req.get('Subscription-State'))) {
439
+ debug('received response to a NOTIFY we sent terminating final subscription; dialog is ended') ;
428
440
 
429
- const from = req.getParsedHeader('From');
430
- if (this.removeSubscription(from.uri, req.get('Event')) === 0) {
431
- this.connected = false ;
432
- this.srf.removeDialog(this) ;
433
- this.emit('destroy', req) ;
434
- this.removeAllListeners();
441
+ const from = req.getParsedHeader('From');
442
+ if (this.removeSubscription(from.uri, req.get('Event')) === 0) {
443
+ this.connected = false ;
444
+ this.srf.removeDialog(this) ;
445
+ this.emit('destroy', req) ;
446
+ this.removeAllListeners();
447
+ }
435
448
  }
436
- }
437
- callback(null, res) ;
449
+ callback(null, res) ;
450
+ }) ;
438
451
  }) ;
439
- }) ;
452
+ } catch (err) {
453
+ callback(err);
454
+ }
440
455
  } ;
441
456
 
442
457
  if (callback) {
package/lib/srf.js CHANGED
@@ -25,6 +25,8 @@ DialogState.Cancelled = 'cancelled';
25
25
  DialogDirection.Initiator = 'initiator';
26
26
  DialogDirection.Recipient = 'recipient';
27
27
 
28
+ const sleepFor = async(ms) => await new Promise((resolve) => setTimeout(resolve, ms));
29
+
28
30
  const noncopyableHdrs = ['via', 'from', 'to', 'call-id', 'cseq', 'contact', 'content-length', 'content-type'];
29
31
  function copyAllHeaders(msg, obj) {
30
32
  if (msg) Object.keys(msg.headers).forEach((h) => {
@@ -835,7 +837,15 @@ class Srf extends Emitter {
835
837
 
836
838
  if (remoteSdpB && remoteSdpB === sdpB) {
837
839
  // called again with same remote SDP, return previous result
838
- return translatedRemoteSdpB ;
840
+ if (translatedRemoteSdpB) return translatedRemoteSdpB;
841
+
842
+ /* race condition: we are still producing the translatedSdp from 183 */
843
+ await sleepFor(100);
844
+ if (translatedRemoteSdpB) return translatedRemoteSdpB;
845
+ await sleepFor(500);
846
+ if (translatedRemoteSdpB) return translatedRemoteSdpB;
847
+ await sleepFor(1000);
848
+ return translatedRemoteSdpB;
839
849
  }
840
850
 
841
851
  remoteSdpB = sdpB ;
@@ -872,7 +882,11 @@ class Srf extends Emitter {
872
882
 
873
883
  /* get headers from response on uac (B) leg and ready them for inclusion on our response on uas (A) leg */
874
884
  function copyUACHeadersToUAS(uacRes) {
875
- const headers = {} ;
885
+ const headers = {};
886
+ if (!uacRes) {
887
+ return headers;
888
+ }
889
+
876
890
  if (proxyResponseHeaders[0] === 'all') {
877
891
  copyAllHeaders(uacRes, headers);
878
892
  possiblyRemoveHeaders(proxyResponseHeaders.slice(1), headers);
@@ -908,7 +922,7 @@ class Srf extends Emitter {
908
922
  try {
909
923
  const sdpA = await generateSdpA(provisionalRes);
910
924
  opts.body = sdpA ;
911
- return res.send(provisionalRes.status, provisionalRes.reason, opts) ;
925
+ return res.send(provisionalRes.status, provisionalRes.reason, opts);
912
926
  } catch (err) {
913
927
  debug(`Srf#createB2BUA: failed in call to produceSdpForALeg: ${err.message}`);
914
928
  res.send(500) ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drachtio-srf",
3
- "version": "4.5.0",
3
+ "version": "4.5.13",
4
4
  "description": "drachtio signaling resource framework",
5
5
  "main": "lib/srf.js",
6
6
  "scripts": {
@@ -161,3 +161,14 @@ services:
161
161
  networks:
162
162
  testbed:
163
163
  ipv4_address: 172.29.0.24
164
+
165
+ sipp-uas-reinvite-with-auth:
166
+ image: drachtio/sipp:latest
167
+ command: sipp -sf /tmp/uas-reinvite-with-auth.xml
168
+ container_name: sipp-uas-reinvite-with-auth
169
+ volumes:
170
+ - ./scenarios:/tmp
171
+ tty: true
172
+ networks:
173
+ testbed:
174
+ ipv4_address: 172.29.0.25
@@ -0,0 +1,213 @@
1
+ <?xml version="1.0" encoding="ISO-8859-1" ?>
2
+ <!DOCTYPE scenario SYSTEM "sipp.dtd">
3
+
4
+ <scenario name="Basic UAS responder">
5
+ <recv request="INVITE" crlf="true">
6
+ <action>
7
+ <ereg regexp="<sip:(.*)>" search_in="hdr" header="Contact:" assign_to="3"/>
8
+ <log message="Contact hostport is [$3]"/>
9
+ </action>
10
+
11
+ </recv>
12
+
13
+ <send>
14
+ <![CDATA[
15
+
16
+ SIP/2.0 100 Trying
17
+ [last_Via:]
18
+ [last_From:]
19
+ [last_To:];tag=[pid]SIPpTag01[call_number]
20
+ [last_Call-ID:]
21
+ [last_CSeq:]
22
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
23
+ Content-Length: 0
24
+
25
+ ]]>
26
+ </send>
27
+
28
+ <send retrans="500">
29
+ <![CDATA[
30
+
31
+ SIP/2.0 407 Proxy Authentication Required
32
+ [last_Via:]
33
+ [last_From:]
34
+ [last_To:];tag=[pid]SIPpTag01[call_number]
35
+ [last_Call-ID:]
36
+ [last_CSeq:]
37
+ Proxy-Authenticate: Digest realm="local", nonce="4cdbb733645816512687270b83d2ae5d11e4d9d8"
38
+ Content-Length: 0
39
+
40
+ ]]>
41
+ </send>
42
+
43
+ <recv request="ACK"
44
+ rtd="true"
45
+ crlf="true">
46
+ <action>
47
+ <ereg regexp=".*" search_in="hdr" header="From:" assign_to="1"/>
48
+ <ereg regexp=".*" search_in="hdr" header="To:" assign_to="2"/>
49
+ </action>
50
+ </recv>
51
+
52
+
53
+ <recv request="INVITE" crlf="true">
54
+ <action>
55
+ <ereg regexp="<sip:(.*)>" search_in="hdr" header="Contact:" assign_to="3"/>
56
+ <log message="Contact hostport is [$3]"/>
57
+ </action>
58
+
59
+ </recv>
60
+
61
+ <send>
62
+ <![CDATA[
63
+
64
+ SIP/2.0 100 Trying
65
+ [last_Via:]
66
+ [last_From:]
67
+ [last_To:];tag=[pid]SIPpTag01[call_number]
68
+ [last_Call-ID:]
69
+ [last_CSeq:]
70
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
71
+ Content-Length: 0
72
+
73
+ ]]>
74
+ </send>
75
+
76
+ <send retrans="500">
77
+ <![CDATA[
78
+
79
+ SIP/2.0 200 OK
80
+ [last_Via:]
81
+ [last_From:]
82
+ [last_To:];tag=[pid]SIPpTag01[call_number]
83
+ [last_Call-ID:]
84
+ [last_CSeq:]
85
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
86
+ Content-Type: application/sdp
87
+ Content-Length: [len]
88
+
89
+ v=0
90
+ o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
91
+ s=-
92
+ c=IN IP[media_ip_type] [media_ip]
93
+ t=0 0
94
+ m=audio [media_port] RTP/AVP 0
95
+ a=rtpmap:0 PCMU/8000
96
+
97
+ ]]>
98
+ </send>
99
+
100
+ <recv request="ACK"
101
+ rtd="true"
102
+ crlf="true">
103
+ <action>
104
+ <ereg regexp=".*" search_in="hdr" header="From:" assign_to="1"/>
105
+ <ereg regexp=".*" search_in="hdr" header="To:" assign_to="2"/>
106
+ </action>
107
+ </recv>
108
+
109
+ <recv request="INVITE" crlf="true">
110
+ <action>
111
+ <ereg regexp=".*" search_in="hdr" header="From:" assign_to="1"/>
112
+ <ereg regexp=".*" search_in="hdr" header="To:" assign_to="2"/>
113
+ </action>
114
+ </recv>
115
+
116
+ <send retrans="500">
117
+ <![CDATA[
118
+
119
+ SIP/2.0 407 Proxy Authentication Required
120
+ [last_Via:]
121
+ [last_From:]
122
+ [last_To:];tag=[pid]SIPpTag01[call_number]
123
+ [last_Call-ID:]
124
+ [last_CSeq:]
125
+ Proxy-Authenticate: Digest realm="local", nonce="4cdbb733645816512687270b83d2ae5d11e4d9d8"
126
+ Content-Length: 0
127
+
128
+ ]]>
129
+ </send>
130
+
131
+ <recv request="ACK"
132
+ rtd="true"
133
+ crlf="true">
134
+ <action>
135
+ <ereg regexp=".*" search_in="hdr" header="From:" assign_to="1"/>
136
+ <ereg regexp=".*" search_in="hdr" header="To:" assign_to="2"/>
137
+ </action>
138
+ </recv>
139
+
140
+ <recv request="INVITE" crlf="true">
141
+ <action>
142
+ <ereg regexp=".*" search_in="hdr" header="From:" assign_to="1"/>
143
+ <ereg regexp=".*" search_in="hdr" header="To:" assign_to="2"/>
144
+ </action>
145
+
146
+ </recv>
147
+
148
+ <send>
149
+ <![CDATA[
150
+
151
+ SIP/2.0 200 OK
152
+ [last_Via:]
153
+ [last_From:]
154
+ [last_To:];tag=[pid]SIPpTag01[call_number]
155
+ [last_Call-ID:]
156
+ [last_CSeq:]
157
+ [last_Record-Route:]
158
+ Subject:[$1]
159
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
160
+ Content-Type: application/sdp
161
+ Content-Length: [len]
162
+
163
+ v=0
164
+ o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
165
+ s=-
166
+ c=IN IP[media_ip_type] [media_ip]
167
+ t=0 0
168
+ m=audio [media_port] RTP/AVP 0
169
+ a=rtpmap:0 PCMU/8000
170
+
171
+ ]]>
172
+ </send>
173
+
174
+ <recv request="ACK"
175
+ rtd="true"
176
+ crlf="true">
177
+ <action>
178
+ <ereg regexp=".*" search_in="hdr" header="From:" assign_to="1"/>
179
+ <ereg regexp=".*" search_in="hdr" header="To:" assign_to="2"/>
180
+ </action>
181
+ </recv>
182
+
183
+ <recv request="BYE">
184
+ </recv>
185
+
186
+ <send>
187
+ <![CDATA[
188
+
189
+ SIP/2.0 200 OK
190
+ [last_Via:]
191
+ [last_From:]
192
+ [last_To:]
193
+ [last_Call-ID:]
194
+ [last_CSeq:]
195
+ Contact: <sip:[local_ip]:[local_port];transport=[transport]>
196
+ Content-Length: 0
197
+
198
+ ]]>
199
+ </send>
200
+
201
+ <!-- Keep the call open for a while in case the 200 is lost to be -->
202
+ <!-- able to retransmit it if we receive the BYE again. -->
203
+ <timewait milliseconds="4000"/>
204
+
205
+
206
+ <!-- definition of the response time repartition table (unit is ms) -->
207
+ <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
208
+
209
+ <!-- definition of the call length repartition table (unit is ms) -->
210
+ <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
211
+
212
+ </scenario>
213
+
package/test/uac.js CHANGED
@@ -176,6 +176,32 @@ test('UAC', (t) => {
176
176
  srf = new Srf();
177
177
  return connect(srf);
178
178
  })
179
+ .then(() => {
180
+ return srf.createUAC('sip:172.29.0.25', {
181
+ method: 'INVITE',
182
+ headers: {
183
+ To: 'sip:dhorton@sip.drachtio.org',
184
+ From: 'sip:dhorton@sip.drachtio.org'
185
+ },
186
+ auth: {
187
+ username: 'foo',
188
+ password: 'bar'
189
+ }
190
+ });
191
+ })
192
+ .then((uac) => {
193
+ uacOverlap = uac;
194
+ return uac.modify('hold');
195
+ })
196
+ .then(() => uacOverlap.destroy())
197
+ .then(() => {
198
+ srf.disconnect();
199
+ return t.pass('SipDialog will handle authentication on re-invites');
200
+ })
201
+ .then(() => {
202
+ srf = new Srf();
203
+ return connect(srf);
204
+ })
179
205
  .then(() => {
180
206
  return srf.createUAC('sip:sipp-uas-auth', {
181
207
  method: 'INVITE',