@babblevoice/projectrtp 2.4.8 → 2.4.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babblevoice/projectrtp",
3
- "version": "2.4.8",
3
+ "version": "2.4.10",
4
4
  "description": "A scalable Node addon RTP server",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -75,7 +75,7 @@ void projectchannelmux::mixall( void ) {
75
75
  this->postrtpdata( chan, dtmfchan, src );
76
76
  }
77
77
  }
78
-
78
+ /* remove the DTMF packet */
79
79
  AQUIRESPINLOCK( chan->rtpbufferlock );
80
80
  chan->inbuff->poppeeked();
81
81
  RELEASESPINLOCK( chan->rtpbufferlock );
@@ -94,7 +94,7 @@ void projectchannelmux::mixall( void ) {
94
94
 
95
95
  rtppacket *dst = chan->gettempoutbuf();
96
96
 
97
- this->subtracted.zero();
97
+ /* start with a direct copy */
98
98
  this->subtracted.copy( this->added );
99
99
 
100
100
  if( chan->recv ) {
@@ -141,8 +141,7 @@ void codecx::reset()
141
141
  ## restart
142
142
  Do enough to manage missing packets.
143
143
  */
144
- void codecx::restart( void )
145
- {
144
+ void codecx::restart( void ) {
146
145
  this->lpfilter.reset();
147
146
  this->dcpowerfilter.reset();
148
147
  this->resamplelastsample = 0;
@@ -154,8 +153,7 @@ void codecx::restart( void )
154
153
 
155
154
  From whichever PCM encoding (u or a) encode to the other without having to do intermediate l16.
156
155
  */
157
- bool codecx::alaw2ulaw( void )
158
- {
156
+ bool codecx::alaw2ulaw( void ) {
159
157
  if( 0 == this->pcmaref.size() ) return false;
160
158
  if( this->pcmaref.isdirty() ) return false;
161
159
 
@@ -167,23 +165,22 @@ bool codecx::alaw2ulaw( void )
167
165
  outbufptr = this->pcmuref.c_str();
168
166
  this->pcmuref.size( insize );
169
167
 
170
- if( nullptr == outbufptr || nullptr == inbufptr )
171
- {
168
+ if( nullptr == outbufptr || nullptr == inbufptr ) {
172
169
  std::cerr << "PCMA NULLPTR shouldn't happen (" << (void*)outbufptr << ", " << (void*)inbufptr << ")" << std::endl;
173
170
  return false;
174
171
  }
175
172
 
176
- for( size_t i = 0; i < insize; i++ )
177
- {
178
- *outbufptr++ = alaw_to_ulaw_table[ *inbufptr++ ];
173
+ for( size_t i = 0; i < insize; i++ ) {
174
+ *outbufptr = alaw_to_ulaw_table[ *inbufptr ];
175
+ inbufptr++;
176
+ outbufptr++;
179
177
  }
180
178
 
181
179
  this->pcmuref.dirty( false );
182
180
  return true;
183
181
  }
184
182
 
185
- bool codecx::ulaw2alaw( void )
186
- {
183
+ bool codecx::ulaw2alaw( void ) {
187
184
  if( 0 == this->pcmuref.size() ) return false;
188
185
  if( this->pcmuref.isdirty() ) return false;
189
186
 
@@ -195,15 +192,15 @@ bool codecx::ulaw2alaw( void )
195
192
  outbufptr = this->pcmaref.c_str();
196
193
  this->pcmaref.size( insize );
197
194
 
198
- if( nullptr == outbufptr || nullptr == inbufptr )
199
- {
195
+ if( nullptr == outbufptr || nullptr == inbufptr ) {
200
196
  std::cerr << "PCMU NULLPTR shouldn't happen(" << (void*)outbufptr << ", " << (void*)inbufptr << ")" << std::endl;
201
197
  return false;
202
198
  }
203
199
 
204
- for( size_t i = 0; i < insize; i++ )
205
- {
206
- *outbufptr++ = ulaw_to_alaw_table[ *inbufptr++ ];
200
+ for( size_t i = 0; i < insize; i++ ) {
201
+ *outbufptr = ulaw_to_alaw_table[ *inbufptr ];
202
+ inbufptr++;
203
+ outbufptr++;
207
204
  }
208
205
 
209
206
  this->pcmaref.dirty( false );
@@ -220,20 +217,15 @@ bool codecx::g711tol16( void )
220
217
  int16_t *convert;
221
218
  size_t insize;
222
219
 
223
- if( this->pcmaref.size() > 0 && !this->pcmaref.isdirty() )
224
- {
220
+ if( this->pcmaref.size() > 0 && !this->pcmaref.isdirty() ) {
225
221
  in = this->pcmaref.c_str();
226
222
  convert = _pcmatol16;
227
223
  insize = this->pcmaref.size();
228
- }
229
- else if ( this->pcmuref.size() > 0 && !this->pcmuref.isdirty() )
230
- {
224
+ } else if ( this->pcmuref.size() > 0 && !this->pcmuref.isdirty() ) {
231
225
  in = this->pcmuref.c_str();
232
226
  convert = _pcmutol16;
233
227
  insize = this->pcmuref.size();
234
- }
235
- else
236
- {
228
+ } else {
237
229
  return false;
238
230
  }
239
231
 
@@ -241,9 +233,10 @@ bool codecx::g711tol16( void )
241
233
 
242
234
  int16_t *out = ( int16_t * ) this->l168kref.c_str();
243
235
 
244
- for( size_t i = 0; i < insize; i++ )
245
- {
246
- *out++ = convert[ *in++ ];
236
+ for( size_t i = 0; i < insize; i++ ) {
237
+ *out = convert[ *in ];
238
+ in++;
239
+ out++;
247
240
  }
248
241
 
249
242
  this->l168kref.dirty( false );
@@ -265,9 +258,12 @@ bool codecx::l16topcma( void )
265
258
  size_t l168klength = this->l168kref.size();
266
259
  in = ( int16_t * ) this->l168kref.c_str();
267
260
 
268
- for( size_t i = 0; i < l168klength; i++ )
269
- {
270
- *out++ = _l16topcma[ ( *in++ ) + 32768 ];
261
+ uint16_t index;
262
+ for( size_t i = 0; i < l168klength; i++ ) {
263
+ index = *in + 32768;
264
+ *out = _l16topcma[ index ];
265
+ in++;
266
+ out++;
271
267
  }
272
268
  this->pcmaref.dirty( false );
273
269
  return true;
@@ -276,8 +272,7 @@ bool codecx::l16topcma( void )
276
272
  /*!md
277
273
  ## l16topcmu
278
274
  */
279
- bool codecx::l16topcmu( void )
280
- {
275
+ bool codecx::l16topcmu( void ) {
281
276
  if( 0 == this->l168kref.size() ) return false;
282
277
  if( this->l168kref.isdirty() ) return false;
283
278
 
@@ -287,9 +282,12 @@ bool codecx::l16topcmu( void )
287
282
  size_t l168klength = this->l168kref.size();
288
283
  in = ( int16_t * ) this->l168kref.c_str();
289
284
 
290
- for( size_t i = 0; i < l168klength; i++ )
291
- {
292
- *out++ = _l16topcmu[ ( *in++ ) + 32768 ];
285
+ uint16_t index;
286
+ for( size_t i = 0; i < l168klength; i++ ) {
287
+ index = *in + 32768;
288
+ *out = _l16topcmu[ index ];
289
+ in++;
290
+ out++;
293
291
  }
294
292
  this->pcmuref.dirty( false );
295
293
  return true;
@@ -299,8 +297,7 @@ bool codecx::l16topcmu( void )
299
297
  ## ilbctol16
300
298
  As it says.
301
299
  */
302
- bool codecx::ilbctol16( void )
303
- {
300
+ bool codecx::ilbctol16( void ) {
304
301
  if( 0 == this->ilbcref.size() ) return false;
305
302
  if( this->ilbcref.isdirty() ) return false;
306
303
 
@@ -335,25 +332,20 @@ bool codecx::ilbctol16( void )
335
332
  ## l16tog722
336
333
  As it says.
337
334
  */
338
- bool codecx::l16tog722( void )
339
- {
335
+ bool codecx::l16tog722( void ) {
340
336
  if( 0 == this->l1616kref.size() ) return false;
341
337
  if( this->l1616kref.isdirty() ) return false;
342
338
 
343
- if( nullptr == this->g722encoder )
344
- {
339
+ if( nullptr == this->g722encoder ) {
345
340
  this->g722encoder = g722_encode_init( NULL, 64000, G722_PACKED );
346
341
  }
347
342
 
348
343
  int len = g722_encode( this->g722encoder, this->g722ref.c_str(), ( int16_t * ) this->l1616kref.c_str(), this->g722ref.size() * 2 );
349
344
 
350
- if( len > 0 )
351
- {
345
+ if( len > 0 ) {
352
346
  this->g722ref.size( len );
353
347
  this->g722ref.dirty( false );
354
- }
355
- else
356
- {
348
+ } else {
357
349
  std::cerr << "g722_encode didn't encode any data" << std::endl;
358
350
  this->g722ref.size( 0 );
359
351
  }
@@ -400,19 +392,16 @@ bool codecx::l16toilbc( void ) {
400
392
  ## g722tol16
401
393
  As it says.
402
394
  */
403
- bool codecx::g722tol16( void )
404
- {
395
+ bool codecx::g722tol16( void ) {
405
396
  if( 0 == this->g722ref.size() ) return false;
406
397
  if( this->g722ref.isdirty() ) return false;
407
398
 
408
399
  /* x 2 for 16 bit instead of 8 and then x 2 sample rate */
409
400
  this->l1616kref.malloc( this->g722ref.size(), sizeof( int16_t ), L1616KPAYLOADTYPE );
410
401
 
411
- if( nullptr == this->g722decoder )
412
- {
402
+ if( nullptr == this->g722decoder ) {
413
403
  this->g722decoder = g722_decode_init( NULL, 64000, G722_PACKED );
414
- if( nullptr == this->g722decoder )
415
- {
404
+ if( nullptr == this->g722decoder ) {
416
405
  std::cerr << "Failed to init G722 decoder" << std::endl;
417
406
  }
418
407
  }
@@ -431,8 +420,7 @@ bool codecx::g722tol16( void )
431
420
  ## l16lowtowideband
432
421
  Upsample from narrow to wideband. Take each point and interpolate between them. We require the final sample from the last packet to continue the interpolating.
433
422
  */
434
- bool codecx::l16lowtowideband( void )
435
- {
423
+ bool codecx::l16lowtowideband( void ) {
436
424
  size_t l168klength = this->l168kref.size();
437
425
 
438
426
  if( 0 == l168klength ) return false;
@@ -443,8 +431,7 @@ bool codecx::l16lowtowideband( void )
443
431
  int16_t *in = ( int16_t * ) this->l168kref.c_str();
444
432
  int16_t *out = ( int16_t * ) this->l1616kref.c_str();
445
433
 
446
- for( size_t i = 0; i < l168klength; i++ )
447
- {
434
+ for( size_t i = 0; i < l168klength; i++ ) {
448
435
  *out = ( ( *in - this->resamplelastsample ) / 2 ) + this->resamplelastsample;
449
436
  this->resamplelastsample = *in;
450
437
  out++;
@@ -463,8 +450,7 @@ bool codecx::l16lowtowideband( void )
463
450
  ## requirewideband
464
451
  Search for the relevent data and convert as necessary.
465
452
  */
466
- bool codecx::requirewideband( void )
467
- {
453
+ bool codecx::requirewideband( void ) {
468
454
  if( 0 != this->l1616kref.size() && !this->l1616kref.isdirty() ) return true;
469
455
  if( this->g722tol16() ) return true;
470
456
  if( !this->g711tol16() )
@@ -491,10 +477,12 @@ bool codecx::l16widetonarrowband( void )
491
477
  int16_t *out = ( int16_t * ) this->l168kref.c_str();
492
478
  int16_t *in = ( int16_t * ) this->l1616kref.c_str();
493
479
 
494
- for( size_t i = 0; i < l1616klength / 2; i++ )
495
- {
496
- lpfilter.execute( *in++ );
497
- *out++ = lpfilter.execute( *in++ );
480
+ for( size_t i = 0; i < l1616klength / 2; i++ ) {
481
+ lpfilter.execute( *in );
482
+ in++;
483
+ *out = lpfilter.execute( *in );
484
+ in++;
485
+ out++;
498
486
  }
499
487
 
500
488
  this->l168kref.dirty( false );
@@ -820,6 +808,18 @@ void codectests( void )
820
808
 
821
809
  #ifdef NODE_MODULE
822
810
 
811
+ static napi_value codectest( napi_env env, napi_callback_info info ) {
812
+ napi_value result;
813
+ if( napi_ok != napi_create_object( env, &result ) ) return NULL;
814
+
815
+ codectests();
816
+
817
+ napi_create_uint32( env, 1, &result );
818
+ napi_coerce_to_bool( env, result, &result );
819
+
820
+ return result;
821
+ }
822
+
823
823
  /*
824
824
  Support single number just for now - but TODO detect Buffer input to convert whole bufffer.
825
825
  */
@@ -929,6 +929,9 @@ void initrtpcodecx( napi_env env, napi_value &result ) {
929
929
 
930
930
  if( napi_ok != napi_create_function( env, "exports", NAPI_AUTO_LENGTH, pcmu2linear, nullptr, &funct ) ) return;
931
931
  if( napi_ok != napi_set_named_property( env, codecx, "pcmu2linear16", funct ) ) return;
932
+
933
+ if( napi_ok != napi_create_function( env, "exports", NAPI_AUTO_LENGTH, codectest, nullptr, &funct ) ) return;
934
+ if( napi_ok != napi_set_named_property( env, codecx, "codectests", funct ) ) return;
932
935
  }
933
936
 
934
937
  #endif /* NODE_MODULE */
@@ -298,36 +298,24 @@ describe( "dtls", function() {
298
298
  it( "Connect then remote to another session", async function() {
299
299
 
300
300
  /*
301
- | internal projectrtp |
302
- Clienta | channela channelb | clientb
303
- | RTP (DTLS) | MIX | RTP |
304
- |<--------------->|<--------------->|<--------------->| Step 1.
305
- |<-(close)-(rem)->|<--------------->|<--------------->| Step 2.
306
- Clientc
307
- |<--------------->|<--------------->|<--------------->|Step 3.
308
- play echo
309
-
301
+ Play, then encrypt at one end, then unencrypt in projectrtp and send onto d
302
+ Create an e channel so that we mix 3
303
+
304
+ | internal |
305
+ channel channel channel channel
306
+ a RTP over DTLS -----------b c RTP d
307
+ play b mix c echo
308
+ mix e f
309
+ echo
310
310
 
311
311
  */
312
312
 
313
313
  this.timeout( 6000 )
314
314
  this.slow( 2500 )
315
315
 
316
- projectrtp.tone.generate( "400+450*0.5/0/400+450*0.5/0:400/200/400/2000", "/tmp/ukringing.wav" )
316
+ projectrtp.tone.generate( "400+450*0.25/0/400+450*0.25/0:400/200/400/2000", "/tmp/ukringing.wav" )
317
317
 
318
- const channeltargeta = {
319
- "address": "localhost",
320
- "port": 0,
321
- "codec": 0,
322
- "dtls": {
323
- "fingerprint": {
324
- "hash": ""
325
- },
326
- "mode": "active" // - is this in the right place and the right way round!
327
- }
328
- }
329
-
330
- const channeltargetc = {
318
+ const targeta = {
331
319
  "address": "localhost",
332
320
  "port": 0,
333
321
  "codec": 0,
@@ -335,36 +323,23 @@ describe( "dtls", function() {
335
323
  "fingerprint": {
336
324
  "hash": ""
337
325
  },
338
- "mode": "active" // - is this in the right place and the right way round!
326
+ "mode": "active"
339
327
  }
340
328
  }
341
329
 
342
- const clienttargeta = {
343
- "address": "localhost",
344
- "port": 12008,
345
- "codec": 0,
346
- "dtls": {
347
- "fingerprint": {
348
- "hash": ""
349
- },
350
- "mode": "passive"
351
- }
352
- }
330
+ const targetb = JSON.parse( JSON.stringify( targeta ) )
353
331
 
354
- const channeltargetb = {
332
+ const targetc = {
355
333
  "address": "localhost",
356
334
  "port": 0,
357
335
  "codec": 0
358
336
  }
359
-
360
- const clienttargetb = {
361
- "address": "localhost",
362
- "port": 12010,
363
- "codec": 0
364
- }
337
+ const targetd = JSON.parse( JSON.stringify( targetc ) )
338
+ const targete = JSON.parse( JSON.stringify( targetc ) )
339
+ const targetf = JSON.parse( JSON.stringify( targetc ) )
365
340
 
366
341
  let done
367
- const finished = new Promise( ( r ) => { done = r } )
342
+ const finished = new Promise( ( resolve ) => { done = resolve } )
368
343
 
369
344
  const channela = await projectrtp.openchannel( {}, function( d ) {
370
345
  if( "close" === d.action ) {
@@ -372,57 +347,66 @@ describe( "dtls", function() {
372
347
  }
373
348
  } )
374
349
 
375
- const clienta = await projectrtp.openchannel( {}, function() {} )
376
-
377
- channeltargeta.dtls.fingerprint.hash = clienta.local.dtls.fingerprint
378
- channeltargeta.port = clienta.local.port
379
- expect( channela.remote( channeltargeta ) ).to.be.true
380
- clienttargeta.port = channela.local.port
381
- clienttargeta.dtls.fingerprint.hash = channela.local.dtls.fingerprint
382
- expect( clienta.remote( clienttargeta ) ).to.be.true
383
-
384
- clienta.play( { "loop": true, "files": [
385
- { "wav": "/tmp/ukringing.wav" } ] } )
350
+ const channelb = await projectrtp.openchannel( {}, function( d ) {
351
+ if( "close" === d.action ) {
352
+ channelc.close()
353
+ }
354
+ } )
386
355
 
356
+ const channelc = await projectrtp.openchannel( {}, function( d ) {
357
+ if( "close" === d.action ) {
358
+ channeld.close()
359
+ }
360
+ } )
387
361
 
388
- const channelb = await projectrtp.openchannel( {}, function( d ) {
362
+ const channeld = await projectrtp.openchannel( {}, function( d ) {
389
363
  if( "close" === d.action ) {
390
- clientb.close()
364
+ channele.close()
391
365
  }
392
366
  } )
393
367
 
394
- const clientb = await projectrtp.openchannel( {}, function( d ) {
368
+ const channele = await projectrtp.openchannel( {}, function( d ) {
395
369
  if( "close" === d.action ) {
396
- clientc.close()
370
+ channelf.close()
397
371
  }
398
372
  } )
399
373
 
400
- let clientcclose = {}
401
- const clientc = await projectrtp.openchannel( {}, function( d ) {
374
+ let channelclose = {}
375
+ const channelf = await projectrtp.openchannel( {}, function( d ) {
402
376
  if( "close" === d.action ) {
403
- clientcclose = d
377
+ channelclose = d
404
378
  done()
405
379
  }
406
380
  } )
407
381
 
408
- channeltargetb.port = clientb.local.port
409
- expect( channelb.remote( channeltargetb ) ).to.be.true
410
- clienttargetb.port = channelb.local.port
411
- expect( clientb.remote( clienttargetb ) ).to.be.true
412
- expect( clientb.echo() ).to.be.true
413
-
414
- channela.mix( channelb )
382
+ targeta.dtls.fingerprint.hash = channelb.local.dtls.fingerprint
383
+ targeta.port = channelb.local.port
384
+ expect( channela.remote( targeta ) ).to.be.true
385
+ targetb.port = channela.local.port
386
+ targetb.dtls.fingerprint.hash = channela.local.dtls.fingerprint
387
+ targetb.dtls.mode = "passive"
388
+ expect( channelb.remote( targetb ) ).to.be.true
415
389
 
416
- await new Promise( ( r ) => { setTimeout( () => r(), 500 ) } )
390
+ targetc.port = channeld.local.port
391
+ expect( channelc.remote( targetc ) ).to.be.true
392
+ targetd.port = channelc.local.port
393
+ expect( channeld.remote( targetd ) ).to.be.true
417
394
 
418
- channeltargetc.dtls.fingerprint.hash = clientc.local.dtls.fingerprint
419
- channeltargetc.port = clientc.local.port
420
- expect( channela.remote( channeltargetc ) ).to.be.true
421
- expect( clientc.remote( clienttargeta ) ).to.be.true
395
+ targete.port = channelf.local.port
396
+ expect( channele.remote( targete ) ).to.be.true
397
+ targetf.port = channele.local.port
398
+ expect( channelf.remote( targetf ) ).to.be.true
422
399
 
423
- clientc.play( { "loop": true, "files": [
400
+ /* play on one end */
401
+ channela.play( { "loop": true, "files": [
424
402
  { "wav": "/tmp/ukringing.wav" } ] } )
425
- clienta.close()
403
+
404
+ /* mix in the middle */
405
+ channelb.mix( channelc )
406
+ channelb.mix( channele )
407
+
408
+ /* echo at the other end */
409
+ expect( channeld.echo() ).to.be.true
426
410
 
427
411
  await new Promise( ( r ) => { setTimeout( () => r(), 1500 ) } )
428
412
 
@@ -431,8 +415,8 @@ describe( "dtls", function() {
431
415
  await fs.promises.unlink( "/tmp/ukringing.wav" ).catch( () => {} )
432
416
  await finished
433
417
 
434
- expect( clientcclose.stats.in.count ).to.be.above( 10 )
435
- expect( clientcclose.stats.in.skip ).to.be.below( 2 ) // allow a little loss in test
418
+ expect( channelclose.stats.in.count ).to.be.above( 30 )
419
+ expect( channelclose.stats.in.skip ).to.be.below( 2 ) // allow a little loss in test
436
420
 
437
421
  } )
438
422
  } )
@@ -432,6 +432,69 @@ describe( "channel mix", function() {
432
432
 
433
433
  } )
434
434
 
435
+ it( "mix 2 channels - pcmu <-> g722 with recording", async function() {
436
+
437
+ this.timeout( 3000 )
438
+ this.slow( 2000 )
439
+
440
+ const endpointa = dgram.createSocket( "udp4" )
441
+ const endpointb = dgram.createSocket( "udp4" )
442
+
443
+ let endpointapkcount = 0
444
+ let endpointbpkcount = 0
445
+
446
+ endpointa.on( "message", function( msg ) {
447
+ endpointapkcount++
448
+ expect( 0x7f & msg [ 1 ] ).to.equal( 0 )
449
+ } )
450
+
451
+ endpointb.on( "message", function( msg ) {
452
+
453
+ endpointbpkcount++
454
+ expect( 0x7f & msg [ 1 ] ).to.equal( 9 )
455
+ endpointb.send( msg, channelb.local.port, "localhost" )
456
+ } )
457
+
458
+ endpointa.bind()
459
+ await new Promise( ( resolve ) => { endpointa.on( "listening", function() { resolve() } ) } )
460
+
461
+ endpointb.bind()
462
+ await new Promise( ( resolve ) => { endpointb.on( "listening", function() { resolve() } ) } )
463
+
464
+ let done
465
+ const finished = new Promise( ( r ) => { done = r } )
466
+
467
+ const channela = await projectrtp.openchannel( { "remote": { "address": "localhost", "port": endpointa.address().port, "codec": 0 } }, function( d ) {
468
+ if( "close" === d.action ) channelb.close()
469
+ } )
470
+
471
+ const channelb = await projectrtp.openchannel( { "remote": { "address": "localhost", "port": endpointb.address().port, "codec": 9 } }, function( d ) {
472
+ if( "close" === d.action ) done()
473
+ } )
474
+
475
+ /* mix */
476
+ expect( channela.mix( channelb ) ).to.be.true
477
+
478
+ channela.record( { "file": "/tmp/g722mix2recording.wav" } )
479
+
480
+ /* Now, when we send UDP on endpointb it passes through our mix then arrives at endpointa */
481
+ for( let i = 0; 50 > i; i ++ ) {
482
+ sendpk( i, i, channela.local.port, endpointa )
483
+ }
484
+
485
+ await new Promise( ( resolve ) => { setTimeout( () => resolve(), 1500 ) } )
486
+
487
+ channela.close()
488
+ endpointa.close()
489
+ endpointb.close()
490
+
491
+ expect( endpointapkcount ).to.be.above( 30 )
492
+ expect( endpointbpkcount ).to.be.above( 30 )
493
+
494
+ await finished
495
+
496
+ } )
497
+
435
498
  it( "mix 3 channels - 1 writer 3 readers", async function() {
436
499
 
437
500
  this.timeout( 3000 )