UNPKG

@babblevoice/projectrtp

Version:
1,484 lines 56 kB
const expect = require( "chai" ).expect
const projectrtp = require( "../../index" ).projectrtp
const dgram = require( "dgram" )

const pcap = require( "./pcap" )

const pkcountperdtmfdigit = 14 /* defined in projectrtpchannel::senddtmf */
const pausebetweendtmfdigits = 200 /* mS 10 pks ref projectrtpchannel::senddtmf */
const timeperdtmfdigit = ( pkcountperdtmfdigit * 20 /* mS */ ) + pausebetweendtmfdigits
const dtmfeventduration = ( ( pkcountperdtmfdigit - 2)  * 160 )

/*
i.e. the RTP payload
str = "80 e5 03 b5 00 02 44 a0 1e e3 61 fb 03 0a 00 a0 4c d1"
returns
Buffer.from( [ 0x80, ... ] )
*/
function fromstr( str ) {
  const retval = []
  str.split( " " ).forEach( v => retval.push ( parseInt( v, 16 ) ) )
  return Buffer.from( retval )
}

function sendpayload( sendtime, pk, dstport, server ) {
  return setTimeout( () => {
    /* Pcap replays schedule the full capture up front and then close
       the channel at a fixed deadline. Any packet whose sendtime lands
       after that deadline fires on a closed socket; without a guard
       the synchronous ERR_SOCKET_DGRAM_NOT_RUNNING bleeds into the
       NEXT test as an uncaught error. Silently drop. */
    try {
      server.send( pk, dstport, "localhost" )
    } catch ( e ) {
      if( "ERR_SOCKET_DGRAM_NOT_RUNNING" !== e.code ) throw e
    }
  }, sendtime )
}

/**
 * Limitation of not parsing ccrc.
 * @param { Buffer } packet
 * @return { object }
 */
function parsepk( packet ) {
  return {
    sn: packet.readUInt16BE( 2 ),
    ts: packet.readUInt32BE( 4 ),
    pt: packet.readUInt8( 1 ) & 0x7f,
    ssrc: packet.readUInt32BE( 8 ),
    payload: new Uint8Array( packet.slice( 12 ) )
  }
}

/**
 * Limitation of not parsing ccrc.
 * @param { Buffer } packet
 * @return { object }
 */
function parse2833pk( packet ) {
  return {
    sn: packet.readUInt16BE( 2 ),
    ts: packet.readUInt32BE( 4 ),
    pt: packet.readUInt8( 1 ) & 0x7f,
    ssrc: packet.readUInt32BE( 8 ),
    event: {
      id: packet.readUInt8( 12 ),
      eoe: ( packet.readUInt8( 13 ) & 0x80 ) == 0x80,
      volume: packet.readUInt8( 13 ) & 0x7f,
      duration: packet.readUInt16BE( 14 ),
    }
  }
}

/* helper functions */
function sendpk( sn, ts, sendtime, dstport, server, pt = 0, ssrc ) {

  if( !ssrc ) ssrc = 25
  const pklength = 172

  return setTimeout( () => {

    const payload = Buffer.alloc( pklength - 12 ).fill( projectrtp.codecx.linear162pcmu( sn ) & 0xff )
    const subheader = Buffer.alloc( 10 )

    subheader.writeUInt8( pt, 1 ) // payload type
    subheader.writeUInt16BE( ( sn ) % ( 2**16 ) )
    subheader.writeUInt32BE( ts, 2 )
    subheader.writeUInt32BE( ssrc, 6 )

    const rtppacket = Buffer.concat( [
      Buffer.from( [ 0x80, 0x00 ] ),
      subheader,
      payload ] )

    server.send( rtppacket, dstport, "localhost" )
  }, sendtime )
}

/*

*/
function senddtmf( sn, ts, sendtime, dstport, server, endofevent, ev, pt = 101 ) {

  return setTimeout( () => {
    const ssrc = 25
    const pklength = 16

    const header = Buffer.alloc( pklength )

    header.writeUInt8( 0x80 )
    header.writeUInt8( pt, 1 ) // payload type
    header.writeUInt16BE( ( sn ) % ( 2**16 ), 2 )
    header.writeUInt32BE( ts, 4 )
    header.writeUInt32BE( ssrc, 8 )

    /* DTMF data */
    header.writeUInt8( ev, 12 )

    let eoerv = 10
    if( endofevent ) {
      eoerv = eoerv | 0x80
    }
    header.writeUInt8( eoerv, 13 ) /* End of Event, Reserved, Volume */
    header.writeUInt16BE( 160, 14 ) /* Duration */

    server.send( header, dstport, "localhost" )
  }, sendtime )
}

/* Tests */
describe( "dtmf", function() {

  it( "Send 2833 DTMF and check event", function( done ) {

    /* create our RTP/UDP endpoint */
    const server = dgram.createSocket( "udp4" )
    server.on( "message", function() {} )

    this.timeout( 3000 )
    this.slow( 2500 )

    server.bind()
    server.on( "listening", async function() {

      const ourport = server.address().port

      let expectedmessagecount = 0
      const expectedmessages = [
        { action: "telephone-event", event: "4" },
        { action: "close" }
      ]

      const channel = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourport, "codec": 0 } }, function( d ) {

        expect( d ).to.deep.include( expectedmessages[ expectedmessagecount ] )
        expectedmessagecount++

        if( "close" === d.action ) {
          server.close()
          done()
        }
      } )

      expect( channel.echo() ).to.be.true

      /* send a packet every 20mS x 50 */
      for( let i = 0;  23 > i; i ++ ) {
        sendpk( i, i*160, i*20, channel.local.port, server )
      }

      senddtmf( 23, 22*160, 23*20, channel.local.port, server, false, "4" )
      senddtmf( 24, 22*160, 24*20, channel.local.port, server, false, "4" )
      senddtmf( 25, 22*160, 25*20, channel.local.port, server, true, "4" )

      for( let i = 26;  40 > i; i ++ ) {
        sendpk( i, i*160, (i-3)*20, channel.local.port, server )
      }

      setTimeout( () => channel.close(), 1000 )
    } )
  } )


  it( "single channel and request rtp server to send 2833", async function() {
    /* TODO - check tre fuller structure of an RTP event (duration, volume, etc) */
    /* create our RTP/UDP endpoint */

    let done
    const finished = new Promise( resolve => done = resolve )

    const server = dgram.createSocket( "udp4" )
    let dtmfpkcount = 0
    server.on( "message", function( msg ) {
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        dtmfpkcount++
      } else {
        expect( msg.length ).to.equal( 172 )
        expect( 0x7f & msg [ 1 ] ).to.equal( 0 )
      }
    } )

    this.timeout( 5000 )
    this.slow( 4000 )

    server.bind()
    await new Promise( resolve => server.on( "listening", resolve ) ) 

    const ourport = server.address().port

    let expectedmessagecount = 0
    const expectedmessages = [
      { action: "close" }
    ]

    const channel = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourport, "codec": 0 } }, function( d ) {

      expect( d ).to.deep.include( expectedmessages[ expectedmessagecount ] )
      expectedmessagecount++

      if( "close" === d.action ) {
        server.close()
        done()
      }
    } )

    expect( channel.echo() ).to.be.true

    /* send a packet every 20mS x 50 */
    for( let i = 0;  50 > i; i ++ ) {
      sendpk( i, i*160, i*20, channel.local.port, server )
    }

    await new Promise( resolve => setTimeout( resolve, 400 ) )
    channel.dtmf( "#1" )

    await new Promise( resolve => setTimeout( resolve, 1000 ) )
    channel.close()
    await finished

    expect( dtmfpkcount ).to.equal( 2*pkcountperdtmfdigit )

  } )


  it( "2 channels mixing and request rtp server to send 2833 to one", async function() {

    /* create our RTP/UDP endpoint */
    const clienta = dgram.createSocket( "udp4" )
    const clientb = dgram.createSocket( "udp4" )

    const dtmfpks = []
    clienta.on( "message", function( msg ) {
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        dtmfpks.push( parse2833pk( msg ) )
      } else {
        expect( msg.length ).to.equal( 172 )
        expect( 0x7f & msg [ 1 ] ).to.equal( 0 )
      }
    } )

    clientb.on( "message", function( msg ) {
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        expect( true ).to.equal( false ) //here = bad
      }
      clientb.send( msg, channelb.local.port, "localhost" )
    } )

    this.timeout( 3000 )
    this.slow( 2500 )

    clienta.bind()
    await new Promise( ( resolve ) => { clienta.on( "listening", () => resolve()  ) } )
    clientb.bind()
    await new Promise( ( resolve ) => { clientb.on( "listening", () => resolve()  ) } )

    const ouraport = clienta.address().port
    const ourbport = clientb.address().port

    let done
    const finished = new Promise( ( r ) => { done = r } )

    const channela = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ouraport, "codec": 0 } }, function( d ) {
      if( "close" === d.action ) channelb.close()
    } )

    const channelb = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourbport, "codec": 0 } }, function( d ) {
      if( "close" === d.action ) done()
    } )

    expect( channela.mix( channelb ) ).to.be.true

    /* send a packet every 20mS x 70 */
    for( let i = 0;  50 > i; i ++ ) {
      sendpk( i, i*160, i*20, channela.local.port, clienta )
    }

    await new Promise( ( resolve ) => { setTimeout( () => resolve(), 400 ) } )
    channela.dtmf( "*9F" )
    await new Promise( ( resolve ) => { setTimeout( () => resolve(), 3 * timeperdtmfdigit ) } )
    channela.close()

    await finished

    clienta.close()
    clientb.close()

    expect( dtmfpks.length ).to.equal( 3*pkcountperdtmfdigit )
    expect( dtmfpks[ 0 ].event.id ).to.equal( 10 )
    expect( dtmfpks[ 0 ].event.duration ).to.equal( 160 )
    expect( dtmfpks[ 0 ].event.eoe ).to.be.false

    expect( dtmfpks[ pkcountperdtmfdigit - 3 ].event.id ).to.equal( 10 )
    expect( dtmfpks[ pkcountperdtmfdigit - 3 ].event.duration ).to.equal( dtmfeventduration )
    expect( dtmfpks[ pkcountperdtmfdigit - 3 ].event.eoe ).to.be.true

  } )

  it( "2 channels mixing and request rtp server to send 2833 to one with dynamic payloadtype", async function() {

    /* create our RTP/UDP endpoint */
    const clienta = dgram.createSocket( "udp4" )
    const clientb = dgram.createSocket( "udp4" )

    const rfc2833pt = 44

    let dtmfpkcount = 0
    clienta.on( "message", function( msg ) {
      if( rfc2833pt == ( 0x7f & msg [ 1 ] ) ) {
        dtmfpkcount++
      } else {
        expect( msg.length ).to.equal( 172 )
        expect( 0x7f & msg [ 1 ] ).to.equal( 0 )
      }
    } )

    clientb.on( "message", function( msg ) {
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        expect( true ).to.equal( false ) //here = bad
        dtmfpkcount++
      }
      clientb.send( msg, channelb.local.port, "localhost" )
    } )

    this.timeout( 3000 )
    this.slow( 2500 )

    clienta.bind()
    await new Promise( ( resolve ) => { clienta.on( "listening", () => resolve()  ) } )
    clientb.bind()
    await new Promise( ( resolve ) => { clientb.on( "listening", () => resolve()  ) } )

    const ouraport = clienta.address().port
    const ourbport = clientb.address().port

    let done
    const finished = new Promise( ( r ) => { done = r } )

    const channela = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ouraport, "codec": 0, rfc2833pt } }, function( d ) {
      if( "close" === d.action ) channelb.close()
    } )

    const channelb = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourbport, "codec": 0 } }, function( d ) {
      if( "close" === d.action ) done()
    } )

    expect( channela.mix( channelb ) ).to.be.true

    /* send a packet every 20mS x 70 */
    for( let i = 0;  50 > i; i ++ ) {
      sendpk( i, i*160, i*20, channela.local.port, clienta )
    }

    await new Promise( ( resolve ) => { setTimeout( () => resolve(), 400 ) } )
    channela.dtmf( "*9F" )
    await new Promise( ( resolve ) => { setTimeout( () => resolve(), 3 * timeperdtmfdigit ) } )
    channela.close()

    await finished

    clienta.close()
    clientb.close()

    expect( dtmfpkcount ).to.equal( 3*pkcountperdtmfdigit )
  } )

  it( "3 channels mixing and request rtp server to send 2833 to one", async function() {

    /* create our RTP/UDP endpoint */
    const clienta = dgram.createSocket( "udp4" )
    const clientb = dgram.createSocket( "udp4" )
    const clientc = dgram.createSocket( "udp4" )

    let dtmfpks = []
    clienta.on( "message", function( msg ) {
      const pk = parsepk( msg )
      if( 101 == pk.pt ) {
        dtmfpks.push( parse2833pk( msg ) )
      } else {
        expect( msg.length ).to.equal( 172 )
        expect( pk.pt ).to.equal( 0 )
      }
    } )

    clientb.on( "message", function( msg ) {
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        expect( true ).to.equal( false ) //here = bad
      }
      clientb.send( msg, channelb.local.port, "localhost" )
    } )

    clientc.on( "message", function( msg ) {
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        expect( true ).to.equal( false ) //here = bad
      }
      clientb.send( msg, channelb.local.port, "localhost" )
    } )

    this.timeout( 3000 )
    this.slow( 2500 )

    clienta.bind()
    await new Promise( ( resolve ) => { clienta.on( "listening", () => resolve()  ) } )
    clientb.bind()
    await new Promise( ( resolve ) => { clientb.on( "listening", () => resolve()  ) } )
    clientc.bind()
    await new Promise( ( resolve ) => { clientc.on( "listening", () => resolve()  ) } )

    const ouraport = clienta.address().port
    const ourbport = clientb.address().port
    const ourcport = clientc.address().port

    let done
    const finished = new Promise( ( r ) => { done = r } )

    const channela = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ouraport, "codec": 0 } }, function( d ) {
      if( "close" === d.action ) channelb.close()
    } )

    const channelb = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourbport, "codec": 0 } }, function( d ) {
      if( "close" === d.action ) channelc.close()
    } )

    const channelc = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourcport, "codec": 0 } }, function( d ) {
      if( "close" === d.action ) done()
    } )

    expect( channela.mix( channelb ) ).to.be.true
    expect( channela.mix( channelc ) ).to.be.true

    /* send a packet every 20mS x 50 */
    for( let i = 0;  50 > i; i ++ ) {
      sendpk( i, i*160, i*20, channela.local.port, clienta )
    }

    await new Promise( ( resolve ) => { setTimeout( () => resolve(), 100 ) } )
    channela.dtmf( "*9ABD" )
    await new Promise( ( resolve ) => { setTimeout( () => resolve(), 5 * timeperdtmfdigit ) } )
    channela.close()

    clienta.close()
    clientb.close()
    clientc.close()

    expect( dtmfpks.length ).to.equal( 5*pkcountperdtmfdigit )

    await finished
  } )


  it( "Send multiple 2833 DTMF and check event", async function() {

    /* create our RTP/UDP endpoint */
    const server = dgram.createSocket( "udp4" )
    server.on( "message", function() {} )

    this.timeout( 3000 )
    this.slow( 2500 )

    server.bind()
    await new Promise( ( resolve ) => { server.on( "listening", () => resolve() ) } )

    const ourport = server.address().port

    const receivedmessages = []
    const channel = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourport, "codec": 0 } }, function( d ) {
      receivedmessages.push( d )
      if( "close" === d.action ) {
        server.close()
      }
    } )

    expect( channel.echo() ).to.be.true

    /* send a packet every 20mS x 50 */
    for( let i = 0;  13 > i; i ++ ) {
      sendpk( i, i*160, i*20, channel.local.port, server )
    }

    /* DTMF every 50mS */
    senddtmf( 13, 12*160, 13*20, channel.local.port, server, false, "4" )
    sendpk( 14, 13*160, 13*20, channel.local.port, server )
    sendpk( 15, 14*160, 14*20, channel.local.port, server )
    senddtmf( 16, 14*160, (13*20)+50, channel.local.port, server, false, "4" )
    // Packet loss
    // senddtmf( 15, 12 * 160, 15*20, channel.port, server, true, "4" )

    for( let i = 17;  30 > i; i ++ ) {
      sendpk( i, (i-2)*160, (i-2)*20, channel.local.port, server )
    }

    senddtmf( 31, 29*160, 29*20, channel.local.port, server, false, "5" )
    sendpk( 32, 29*160, 29*20, channel.local.port, server )
    sendpk( 33, 30*160, 30*20, channel.local.port, server )
    senddtmf( 34, 30*160, (29*20)+50, channel.local.port, server, false, "5" )
    sendpk( 35, 31*160, 31*20, channel.local.port, server )
    sendpk( 36, 32*160, 32*20, channel.local.port, server )
    senddtmf( 37, 32*160, (29*20)+100, channel.local.port, server, true, "5" )

    for( let i = 37; 45 > i; i ++ ) {
      sendpk( i, i*160, (i-6)*20, channel.local.port, server )
    }

    setTimeout( () => channel.close(), 1100 )
    await new Promise( resolve => { server.on( "close", resolve ) } )

    expect( receivedmessages[ 0 ].action ).to.equal( "telephone-event" )
    expect( receivedmessages[ 0 ].event ).to.equal( "4" )
    expect( receivedmessages[ 1 ].action ).to.equal( "telephone-event" )
    expect( receivedmessages[ 1 ].event ).to.equal( "5" )
    expect( receivedmessages[ 2 ].action ).to.equal( "close" )

  } )

  it( "Lose end packet", async function() {

    /* create our RTP/UDP endpoint */
    const server = dgram.createSocket( "udp4" )
    const receivedmessages = []

    let receviedpkcount = 0
    server.on( "message", function() {
      receviedpkcount++
    } )

    let done
    const finished = new Promise( r => done = r )

    this.timeout( 3000 )
    this.slow( 2500 )

    server.bind()
    server.on( "listening", async function() {

      const ourport = server.address().port
      const channel = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourport, "codec": 0 } }, function( d ) {
        receivedmessages.push( d )

        if( "close" === d.action ) {
          server.close()
          done()
        }
      } )

      expect( channel.echo() ).to.be.true

      /* send a packet every 20mS x 50 */
      sendpk( 0, 160, 0, channel.local.port, server )
      sendpk( 1, 1*160, 1*20, channel.local.port, server )
      sendpk( 2, 2*160, 2*20, channel.local.port, server )
      sendpk( 3, 3*160, 3*20, channel.local.port, server )
      sendpk( 4, 4*160, 4*20, channel.local.port, server )
      sendpk( 5, 5*160, 5*20, channel.local.port, server )
      sendpk( 6, 6*160, 6*20, channel.local.port, server )
      sendpk( 7, 7*160, 7*20, channel.local.port, server )
      sendpk( 8, 8*160, 8*20, channel.local.port, server )
      sendpk( 9, 9*160, 9*20, channel.local.port, server )
      sendpk( 10, 10*160, 10*20, channel.local.port, server )
      sendpk( 11, 11*160, 11*20, channel.local.port, server )
      sendpk( 12, 12*160, 12*20, channel.local.port, server )

      senddtmf( 13, 13*160, 13*20, channel.local.port, server, false, "4" )
      sendpk( 14, 13*160, 13*20, channel.local.port, server, 0 )
      sendpk( 15, 14*160, 14*20, channel.local.port, server, 0 )

      senddtmf( 16, (15*160)+10, (15*20)+10, channel.local.port, server, false, "4" )
      sendpk( 17, 15*160, 15*20, channel.local.port, server, 0 )
      sendpk( 18, 16*160, 16*20, channel.local.port, server, 0 )
      // Packet loss
      // senddtmf( 19, (16*160)+20, (16*20)+20, channel.local.port, server, true, "4" )

      sendpk( 20, 17*160, 17*20, channel.local.port, server, 0 )
      sendpk( 21, 18*160, 18*20, channel.local.port, server, 0 )
      sendpk( 22, 19*160, 19*20, channel.local.port, server, 0 )
      sendpk( 23, 20*160, 20*20, channel.local.port, server, 0 )
      sendpk( 24, 21*160, 21*20, channel.local.port, server, 0 )
      sendpk( 25, 22*160, 22*20, channel.local.port, server, 0 )
      sendpk( 26, 23*160, 23*20, channel.local.port, server, 0 )
      sendpk( 27, 24*160, 24*20, channel.local.port, server, 0 )
      sendpk( 28, 25*160, 25*20, channel.local.port, server, 0 )
      sendpk( 29, 26*160, 26*20, channel.local.port, server, 0 )
      sendpk( 30, 27*160, 27*20, channel.local.port, server, 0 )

      setTimeout( () => channel.close(), 1000 )

    } )

    await finished

    const expectedmessages = [
      { action: "telephone-event", event: "4" },
      { action: "close" }
    ]

    expect( receviedpkcount ).to.be.above( 15 )
    expect( receivedmessages.length ).to.equal( 2 )
    expect( receivedmessages[ 0 ] ).to.deep.include( expectedmessages[ 0 ] )
    expect( receivedmessages[ 1 ] ).to.deep.include( expectedmessages[ 1 ] )
  } )


  it( "mix 2 channels - pcmu <-> pcma and send DTMF", async function() {

    /*
      When mixing 2 channels, we expect the second leg to receive the 2833 packets
      and our server to emit events indicating the DTMF on the first channel.

      The timing was tight against the original C++ addon: 50 packets
      emitted over 1 second, 1400 ms settle after the last send, total
      ~2450 ms. The Rust jitter buffer primes ~200 ms/hop and the mix is
      a 2-hop round trip (endpointa → channela → mix → endpointb echo →
      channelb → mix → endpointa), so the last couple of packets can
      miss the close window. Widening the settle gives every packet
      time to complete the round trip without changing what's asserted.
    */
    this.timeout( 5000 )
    this.slow( 3500 )

    const endpointa = dgram.createSocket( "udp4" )
    const endpointb = dgram.createSocket( "udp4" )

    const receivedmessages = []

    let endpointapkcount = 0
    let endpointbpkcount = 0
    let dtmfpkcount = 0

    endpointa.on( "message", function( msg ) {
      endpointapkcount++
      expect( msg.length ).to.equal( 172 )
      expect( 0x7f & msg [ 1 ] ).to.equal( 0 )
    } )

    endpointb.on( "message", function( msg ) {
      endpointbpkcount++
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        dtmfpkcount++
      } else {
        expect( msg.length ).to.equal( 172 )
        expect( 0x7f & msg [ 1 ] ).to.equal( 8 )
        endpointb.send( msg, channelb.local.port, "localhost" )
      }
    } )

    endpointa.bind()
    await new Promise( ( resolve ) => { endpointa.on( "listening", function() { resolve() } ) } )

    endpointb.bind()
    await new Promise( ( resolve ) => { endpointb.on( "listening", function() { resolve() } ) } )

    let done
    const finished = new Promise( ( r ) => { done = r } )

    const channela = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": endpointa.address().port, "codec": 0 } }, function( d ) {
      receivedmessages.push( d )

      if( "close" === d.action ) channelb.close()
    } )

    const channelb = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": endpointb.address().port, "codec": 8 } }, function( d ) {
      if( "close" === d.action ) done()
    } )

    /* mix */
    expect( channela.mix( channelb ) ).to.be.true

    /* send a packet every 20mS x 50 */
    sendpk( 0, 0, 0, channela.local.port, endpointa )
    sendpk( 1, 1*160, 1*20, channela.local.port, endpointa )
    sendpk( 2, 2*160, 2*20, channela.local.port, endpointa )
    sendpk( 3, 3*160, 3*20, channela.local.port, endpointa )
    sendpk( 4, 4*160, 4*20, channela.local.port, endpointa )
    sendpk( 5, 5*160, 5*20, channela.local.port, endpointa )
    sendpk( 6, 6*160, 6*20, channela.local.port, endpointa )
    sendpk( 7, 7*160, 7*20, channela.local.port, endpointa )
    sendpk( 8, 8*160, 8*20, channela.local.port, endpointa )
    sendpk( 9, 9*160, 9*20, channela.local.port, endpointa )
    sendpk( 10, 10*160, 10*20, channela.local.port, endpointa )
    sendpk( 11, 11*160, 11*20, channela.local.port, endpointa )
    sendpk( 12, 12*160, 12*20, channela.local.port, endpointa )

    senddtmf( 13, 13*160, 13*20, channela.local.port, endpointa, false, "4" )
    sendpk( 14, 13*160, 13*20, channela.local.port, endpointa, 0 )
    sendpk( 15, 14*160, 14*20, channela.local.port, endpointa, 0 )
    senddtmf( 16, (15*160)+10, (15*20)+10, channela.local.port, endpointa, false, "4" )
    sendpk( 17, 15*160, 15*20, channela.local.port, endpointa, 0 )
    sendpk( 18, 16*160, 16*20, channela.local.port, endpointa, 0 )
    senddtmf( 19, (17*160)+20, (17*20)+20, channela.local.port, endpointa, true, "4" )
    sendpk( 20, 17*160, 17*20, channela.local.port, endpointa, 0 )
    sendpk( 21, 18*160, 18*20, channela.local.port, endpointa, 0 )
    senddtmf( 22, (18*160)+30, (18*20)+30, channela.local.port, endpointa, true, "4" )
    sendpk( 23, 19*160, 19*20, channela.local.port, endpointa, 0 )
    sendpk( 24, 20*160, 20*20, channela.local.port, endpointa, 0 )
    sendpk( 25, 21*160, 21*20, channela.local.port, endpointa, 0 )
    sendpk( 26, 22*160, 22*20, channela.local.port, endpointa, 0 )
    sendpk( 27, 23*160, 23*20, channela.local.port, endpointa, 0 )
    sendpk( 28, 24*160, 24*20, channela.local.port, endpointa, 0 )
    sendpk( 29, 25*160, 25*20, channela.local.port, endpointa, 0 )

    senddtmf( 30, 26*160, 26*20, channela.local.port, endpointa, false, "5" )
    sendpk( 31, 26*160, 26*20, channela.local.port, endpointa, 0 )
    sendpk( 32, 27*160, 27*20, channela.local.port, endpointa, 0 )
    senddtmf( 33, (27*160)+10, (27*20)+10, channela.local.port, endpointa, false, "5" )
    sendpk( 34, 28*160, 28*20, channela.local.port, endpointa, 0 )
    sendpk( 35, 29*160, 28*20, channela.local.port, endpointa, 0 )
    senddtmf( 36, (28*160)+20, (28*20)+20, channela.local.port, endpointa, true, "5" )
    sendpk( 37, 30*160, 29*20, channela.local.port, endpointa, 0 )
    sendpk( 38, 31*160, 30*20, channela.local.port, endpointa, 0 )

    senddtmf( 39, (38*160)+30, (30*20)+30, channela.local.port, endpointa, true, "5" )
    sendpk( 40, 32*160, 31*20, channela.local.port, endpointa, 0 )
    sendpk( 41, 33*160, 32*20, channela.local.port, endpointa, 0 )

    sendpk( 42, 34*160, 33*20, channela.local.port, endpointa, 0 )
    sendpk( 43, 35*160, 34*20, channela.local.port, endpointa, 0 )
    sendpk( 44, 36*160, 35*20, channela.local.port, endpointa, 0 )
    sendpk( 45, 37*160, 36*20, channela.local.port, endpointa, 0 )
    sendpk( 46, 38*160, 37*20, channela.local.port, endpointa, 0 )
    sendpk( 47, 39*160, 38*20, channela.local.port, endpointa, 0 )
    sendpk( 48, 40*160, 39*20, channela.local.port, endpointa, 0 )
    sendpk( 49, 51*160, 40*20, channela.local.port, endpointa, 0 )

    await new Promise( ( r ) => { setTimeout( () => r(), 2500 ) } )

    channela.close()
    endpointa.close()
    endpointb.close()

    await finished

    expect( dtmfpkcount ).to.be.within( ( pkcountperdtmfdigit * 2 ) - 4 , pkcountperdtmfdigit * 2 ) /* allow loss */
    expect( endpointapkcount ).to.be.within( 30, 51 )
    expect( endpointbpkcount - dtmfpkcount ).to.be.within( 30, 51 )

    expect( receivedmessages.length ).to.equal( 5 )

    expect( receivedmessages[ 0 ].action ).to.equal( "mix" )
    expect( receivedmessages[ 1 ].action ).to.equal( "telephone-event" )
    expect( receivedmessages[ 2 ].action ).to.equal( "telephone-event" )
    expect( receivedmessages[ 3 ].action ).to.equal( "mix" )
    expect( receivedmessages[ 3 ].event ).to.equal( "finished" )
    expect( receivedmessages[ 4 ].action ).to.equal( "close" )

    expect( receivedmessages[ 0 ].event ).to.equal( "start" )
    expect( receivedmessages[ 1 ].event ).to.equal( "4" )
    expect( receivedmessages[ 2 ].event ).to.equal( "5" )
    expect( receivedmessages[ 3 ].event ).to.equal( "finished" )

  } )


  it( "wefwef mix 2 channels - pcmu <-> pcma and send DTMF different 2833 pt", async function() {

    /*
      When mixing 2 channels, we expect the second leg to receive the 2833 packets
      and our server to emit events indicating the DTMF on the first channel.

      Timing rationale: identical to the sibling test above — widened to
      accommodate the ~400 ms two-hop jitter-prime latency so the last
      packets complete the round trip before close.
    */
    this.timeout( 5000 )
    this.slow( 3500 )

    const endpointa = dgram.createSocket( "udp4" )
    const endpointb = dgram.createSocket( "udp4" )

    const receivedmessages = []

    let endpointapkcount = 0
    let endpointbpkcount = 0
    let dtmfpkcount = 0

    endpointa.on( "message", function( msg ) {
      endpointapkcount++
      expect( msg.length ).to.equal( 172 )
      expect( 0x7f & msg [ 1 ] ).to.equal( 0 )
    } )

    endpointb.on( "message", function( msg ) {
      endpointbpkcount++
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        dtmfpkcount++
      } else {
        expect( msg.length ).to.equal( 172 )
        expect( 0x7f & msg [ 1 ] ).to.equal( 8 )
        endpointb.send( msg, channelb.local.port, "localhost" )
      }
    } )

    endpointa.bind()
    await new Promise( ( resolve ) => { endpointa.on( "listening", function() { resolve() } ) } )

    endpointb.bind()
    await new Promise( ( resolve ) => { endpointb.on( "listening", function() { resolve() } ) } )

    let done
    const finished = new Promise( ( r ) => { done = r } )

    const channela = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": endpointa.address().port, "codec": 0, "rfc2833pt": 127 } }, function( d ) {
      receivedmessages.push( d )

      if( "close" === d.action ) channelb.close()
    } )

    const channelb = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": endpointb.address().port, "codec": 8, "rfc2833pt": 101 } }, function( d ) {
      if( "close" === d.action ) done()
    } )

    /* mix */
    expect( channela.mix( channelb ) ).to.be.true

    /* send a packet every 20mS x 50 */
    sendpk( 0, 0, 0, channela.local.port, endpointa )
    sendpk( 1, 1*160, 1*20, channela.local.port, endpointa )
    sendpk( 2, 2*160, 2*20, channela.local.port, endpointa )
    sendpk( 3, 3*160, 3*20, channela.local.port, endpointa )
    sendpk( 4, 4*160, 4*20, channela.local.port, endpointa )
    sendpk( 5, 5*160, 5*20, channela.local.port, endpointa )
    sendpk( 6, 6*160, 6*20, channela.local.port, endpointa )
    sendpk( 7, 7*160, 7*20, channela.local.port, endpointa )
    sendpk( 8, 8*160, 8*20, channela.local.port, endpointa )
    sendpk( 9, 9*160, 9*20, channela.local.port, endpointa )
    sendpk( 10, 10*160, 10*20, channela.local.port, endpointa )
    sendpk( 11, 11*160, 11*20, channela.local.port, endpointa )
    sendpk( 12, 12*160, 12*20, channela.local.port, endpointa )

    senddtmf( 13, 13*160, 13*20, channela.local.port, endpointa, false, "4", 127 )
    sendpk( 14, 13*160, 13*20, channela.local.port, endpointa, 0 )
    sendpk( 15, 14*160, 14*20, channela.local.port, endpointa, 0 )
    senddtmf( 16, (15*160)+10, (15*20)+10, channela.local.port, endpointa, false, "4", 127 )
    sendpk( 17, 15*160, 15*20, channela.local.port, endpointa, 0 )
    sendpk( 18, 16*160, 16*20, channela.local.port, endpointa, 0 )
    senddtmf( 19, (17*160)+20, (17*20)+20, channela.local.port, endpointa, true, "4", 127 )
    sendpk( 20, 17*160, 17*20, channela.local.port, endpointa, 0 )
    sendpk( 21, 18*160, 18*20, channela.local.port, endpointa, 0 )
    senddtmf( 22, (18*160)+30, (18*20)+30, channela.local.port, endpointa, true, "4", 127 )
    sendpk( 23, 19*160, 19*20, channela.local.port, endpointa, 0 )
    sendpk( 24, 20*160, 20*20, channela.local.port, endpointa, 0 )
    sendpk( 25, 21*160, 21*20, channela.local.port, endpointa, 0 )
    sendpk( 26, 22*160, 22*20, channela.local.port, endpointa, 0 )
    sendpk( 27, 23*160, 23*20, channela.local.port, endpointa, 0 )
    sendpk( 28, 24*160, 24*20, channela.local.port, endpointa, 0 )
    sendpk( 29, 25*160, 25*20, channela.local.port, endpointa, 0 )

    senddtmf( 30, 26*160, 26*20, channela.local.port, endpointa, false, "5", 127 )
    sendpk( 31, 26*160, 26*20, channela.local.port, endpointa, 0 )
    sendpk( 32, 27*160, 27*20, channela.local.port, endpointa, 0 )
    senddtmf( 33, (27*160)+10, (27*20)+10, channela.local.port, endpointa, false, "5", 127 )
    sendpk( 34, 28*160, 28*20, channela.local.port, endpointa, 0 )
    sendpk( 35, 29*160, 28*20, channela.local.port, endpointa, 0 )
    senddtmf( 36, (28*160)+20, (28*20)+20, channela.local.port, endpointa, true, "5", 127 )
    sendpk( 37, 30*160, 29*20, channela.local.port, endpointa, 0 )
    sendpk( 38, 31*160, 30*20, channela.local.port, endpointa, 0 )

    senddtmf( 39, (38*160)+30, (30*20)+30, channela.local.port, endpointa, true, "5", 127 )
    sendpk( 40, 32*160, 31*20, channela.local.port, endpointa, 0 )
    sendpk( 41, 33*160, 32*20, channela.local.port, endpointa, 0 )

    sendpk( 42, 34*160, 33*20, channela.local.port, endpointa, 0 )
    sendpk( 43, 35*160, 34*20, channela.local.port, endpointa, 0 )
    sendpk( 44, 36*160, 35*20, channela.local.port, endpointa, 0 )
    sendpk( 45, 37*160, 36*20, channela.local.port, endpointa, 0 )
    sendpk( 46, 38*160, 37*20, channela.local.port, endpointa, 0 )
    sendpk( 47, 39*160, 38*20, channela.local.port, endpointa, 0 )
    sendpk( 48, 40*160, 39*20, channela.local.port, endpointa, 0 )
    sendpk( 49, 51*160, 40*20, channela.local.port, endpointa, 0 )

    await new Promise( ( r ) => { setTimeout( () => r(), 2500 ) } )

    channela.close()
    endpointa.close()
    endpointb.close()

    await finished

    expect( endpointapkcount ).to.be.within( 30, 51 )
    expect( endpointbpkcount - dtmfpkcount ).to.be.within( 30, 51 )

    expect( receivedmessages.length ).to.equal( 5 )

    expect( dtmfpkcount ).to.be.within( ( pkcountperdtmfdigit * 2 ) - 4 , pkcountperdtmfdigit * 2 ) /* allow loss */

    expect( receivedmessages[ 0 ].action ).to.equal( "mix" )
    expect( receivedmessages[ 1 ].action ).to.equal( "telephone-event" )
    expect( receivedmessages[ 2 ].action ).to.equal( "telephone-event" )
    expect( receivedmessages[ 3 ].action ).to.equal( "mix" )
    expect( receivedmessages[ 3 ].event ).to.equal( "finished" )
    expect( receivedmessages[ 4 ].action ).to.equal( "close" )

    expect( receivedmessages[ 0 ].event ).to.equal( "start" )
    expect( receivedmessages[ 1 ].event ).to.equal( "4" )
    expect( receivedmessages[ 2 ].event ).to.equal( "5" )
    expect( receivedmessages[ 3 ].event ).to.equal( "finished" )

  } )


  it( "mix 3 channels - pcmu <-> pcma and ilbc and send DTMF", async function() {

    this.timeout( 3000 )
    this.slow( 2000 )

    const endpointa = dgram.createSocket( "udp4" )
    const endpointb = dgram.createSocket( "udp4" )
    const endpointc = dgram.createSocket( "udp4" )

    let endpointapkcount = 0
    let endpointbpkcount = 0
    let endpointcpkcount = 0

    let dtmfapkcount = 0
    let dtmfbpkcount = 0
    let dtmfcpkcount = 0

    endpointa.on( "message", function( msg ) {
      endpointapkcount++
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        dtmfapkcount++
      } else {
        expect( msg.length ).to.equal( 172 )
        expect( 0x7f & msg [ 1 ] ).to.equal( 0 )
        endpointb.send( msg, channelb.local.port, "localhost" )
      }
    } )

    endpointb.on( "message", function( msg ) {
      endpointbpkcount++
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        dtmfbpkcount++
      } else {
        expect( msg.length ).to.equal( 172 )
        expect( 0x7f & msg [ 1 ] ).to.equal( 8 )
        endpointb.send( msg, channelb.local.port, "localhost" )
      }
    } )

    endpointc.on( "message", function( msg ) {
      endpointcpkcount++
      if( 101 == ( 0x7f & msg [ 1 ] ) ) {
        dtmfcpkcount++
      } else {
        expect( msg.length ).to.equal( 50 )
        expect( 0x7f & msg [ 1 ] ).to.equal( 97 )
        endpointb.send( msg, channelb.local.port, "localhost" )
      }
    } )

    endpointa.bind()
    await new Promise( ( resolve ) => { endpointa.on( "listening", function() { resolve() } ) } )

    endpointb.bind()
    await new Promise( ( resolve ) => { endpointb.on( "listening", function() { resolve() } ) } )

    endpointc.bind()
    await new Promise( ( resolve ) => { endpointc.on( "listening", function() { resolve() } ) } )

    const receveiedmessages = []

    const channela = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": endpointa.address().port, "codec": 0 } }, function( d ) {
      receveiedmessages.push( d )

      if( "close" === d.action ) channelb.close()
    } )

    const channelb = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": endpointb.address().port, "codec": 8 } }, function( d ) {
      expect( d.action).to.not.equal( "telephone-event" )
      if( "close" === d.action ) channelc.close()
    } )

    const channelc = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": endpointc.address().port, "codec": 97 } }, function( d ) {
      expect( d.action).to.not.equal( "telephone-event" )
      if( "close" === d.action ) {
        endpointa.close()
        endpointb.close()
        endpointc.close()
      }
    } )

    /* mix */
    expect( channela.mix( channelb ) ).to.be.true
    expect( channela.mix( channelc ) ).to.be.true

    /* send a packet every 20mS x 50 */
    /* NO FOR LOOPS for explicit readablity of the test */
    sendpk( 0, 0, 0, channela.local.port, endpointa )
    sendpk( 1, 1*160, 20, channela.local.port, endpointa )
    sendpk( 2, 2*160, 2*20, channela.local.port, endpointa )
    sendpk( 3, 3*160, 3*20, channela.local.port, endpointa )
    sendpk( 4, 4*160, 4*20, channela.local.port, endpointa )
    sendpk( 5, 5*160, 5*20, channela.local.port, endpointa )
    sendpk( 6, 6*160, 6*20, channela.local.port, endpointa )
    sendpk( 7, 7*160, 7*20, channela.local.port, endpointa )
    sendpk( 8, 8*160, 8*20, channela.local.port, endpointa )
    sendpk( 9, 9*160, 9*20, channela.local.port, endpointa )
    sendpk( 10, 10*160, 10*20, channela.local.port, endpointa )
    sendpk( 11, 11*160, 11*20, channela.local.port, endpointa )
    sendpk( 12, 12*160, 12*20, channela.local.port, endpointa )

    /* rfc2833 - 3.6: An audio source SHOULD start transmitting event packets as soon as it
       recognizes an event and every 50 ms thereafter or the packet interval
       for the audio codec used for this session, if known. 
        This means our ts will not stay in sync with our sequence number - which increments 
        with every packet.
        senddtmf( sn, ts, sendtime, port, socket, endofevent, event )
        sendpk( sn, sendtime, port, socket, pt, ts, ssrc ) */
    senddtmf( 13, 13*160, 13*20, channela.local.port, endpointa, false, "4" )
    sendpk( 14, 13*160, 13*20, channela.local.port, endpointa, 0 )
    sendpk( 15, 14*160, 14*20, channela.local.port, endpointa, 0 )
    sendpk( 16, 15*160, 15*20, channela.local.port, endpointa, 0 )
    senddtmf( 17, (15*160), (13*20)+50, channela.local.port, endpointa, false, "4" )
    sendpk( 18, 16*160, 16*20, channela.local.port, endpointa, 0 )
    sendpk( 19, 17*160, 17*20, channela.local.port, endpointa, 0 )
    senddtmf( 20, (17*160), (13*20)+100, channela.local.port, endpointa, true, "4" )
    sendpk( 21, 18*160, 18*20, channela.local.port, endpointa, 0 )
    sendpk( 22, 19*160, 19*20, channela.local.port, endpointa, 0 )
    sendpk( 23, 20*160, 20*20, channela.local.port, endpointa, 0 )
    senddtmf( 24, (20*160), (13*20)+150, channela.local.port, endpointa, true, "4" )
    sendpk( 25, 21*160, 21*20, channela.local.port, endpointa, 0 )
    sendpk( 26, 22*160, 22*20, channela.local.port, endpointa, 0 )
    sendpk( 27, 23*160, 23*20, channela.local.port, endpointa, 0 )
    sendpk( 28, 24*160, 24*20, channela.local.port, endpointa, 0 )
    sendpk( 29, 25*160, 25*20, channela.local.port, endpointa, 0 )

    senddtmf( 30, 25*160, 25*20, channela.local.port, endpointa, false, "5" )
    sendpk( 31, 26*160, 26*20, channela.local.port, endpointa, 0 )
    sendpk( 32, 27*160, 27*20, channela.local.port, endpointa, 0 )
    senddtmf( 33, (27*160), (25*20)+50, channela.local.port, endpointa, false, "5" )
    sendpk( 34, 28*160, 28*20, channela.local.port, endpointa, 0 )
    sendpk( 35, 29*160, 29*20, channela.local.port, endpointa, 0 )
    senddtmf( 36, (29*160), (25*20)+100, channela.local.port, endpointa, true, "5" )
    sendpk( 37, 30*160, 30*20, channela.local.port, endpointa, 0 )
    sendpk( 38, 31*160, 31*20, channela.local.port, endpointa, 0 )
    sendpk( 39, 32*160, 32*20, channela.local.port, endpointa, 0 )
    senddtmf( 40, (32*160), (25*20)+150, channela.local.port, endpointa, true, "5" )
    sendpk( 41, 33*160, 33*20, channela.local.port, endpointa, 0 )
    sendpk( 42, 34*160, 34*20, channela.local.port, endpointa, 0 )
    sendpk( 43, 35*160, 35*20, channela.local.port, endpointa, 0 )
    sendpk( 44, 36*160, 36*20, channela.local.port, endpointa, 0 )
    sendpk( 45, 37*160, 37*20, channela.local.port, endpointa, 0 )
    sendpk( 46, 38*160, 38*20, channela.local.port, endpointa, 0 )
    sendpk( 47, 39*160, 39*20, channela.local.port, endpointa, 0 )
    sendpk( 48, 40*160, 40*20, channela.local.port, endpointa, 0 )
    sendpk( 49, 41*160, 41*20, channela.local.port, endpointa, 0 )
    sendpk( 50, 42*160, 42*20, channela.local.port, endpointa, 0 )
    sendpk( 51, 43*160, 43*20, channela.local.port, endpointa, 0 )
    sendpk( 52, 44*160, 44*20, channela.local.port, endpointa, 0 )
    sendpk( 53, 45*160, 45*20, channela.local.port, endpointa, 0 )
    sendpk( 54, 46*160, 46*20, channela.local.port, endpointa, 0 )
    sendpk( 55, 47*160, 47*20, channela.local.port, endpointa, 0 )
    sendpk( 56, 48*160, 48*20, channela.local.port, endpointa, 0 )
    sendpk( 57, 49*160, 49*20, channela.local.port, endpointa, 0 )
    sendpk( 58, 50*160, 50*20, channela.local.port, endpointa, 0 )
    sendpk( 59, 52*160, 51*20, channela.local.port, endpointa, 0 )

    await new Promise( ( resolve ) => { setTimeout( () => resolve(), 1200 ) } )

    channela.close()

    await Promise.all( [
      new Promise( resolve => { endpointa.on( "close", resolve ) } ),
      new Promise( resolve => { endpointb.on( "close", resolve ) } ),
      new Promise( resolve => { endpointc.on( "close", resolve ) } )
    ] )

    expect( endpointapkcount ).to.be.within( 59, 70 )
    expect( endpointbpkcount - dtmfbpkcount ).to.be.within( 59, 70 )
    expect( endpointcpkcount - dtmfcpkcount ).to.be.within( 59, 70 )

    // 3 after we return to the event loop and enter the callback with close event.
    expect( dtmfapkcount ).to.equal( 0 )
    expect( dtmfbpkcount ).to.be.within( ( pkcountperdtmfdigit * 2 ) - 4, pkcountperdtmfdigit * 2 )
    expect( dtmfcpkcount ).to.be.within( ( pkcountperdtmfdigit * 2 ) - 4, pkcountperdtmfdigit * 2 )

    expect( receveiedmessages[ 0 ].action ).to.equal( "mix" )
    expect( receveiedmessages[ 1 ].action ).to.equal( "mix" )
    expect( receveiedmessages[ 2 ].action ).to.equal( "telephone-event" )
    expect( receveiedmessages[ 3 ].action ).to.equal( "telephone-event" )
    expect( receveiedmessages[ 4 ].action ).to.equal( "mix" )
    expect( receveiedmessages[ 5 ].action ).to.equal( "close" )

    expect( receveiedmessages[ 0 ].event ).to.equal( "start" )
    expect( receveiedmessages[ 1 ].event ).to.equal( "start" )
    expect( receveiedmessages[ 2 ].event ).to.equal( "4" )
    expect( receveiedmessages[ 3 ].event ).to.equal( "5" )
    expect( receveiedmessages[ 4 ].event ).to.equal( "finished" )

  } )

  it( "DTMF captured not working", async function() {

    /* create our RTP/UDP endpoint */
    const server = dgram.createSocket( "udp4" )
    server.on( "message", function() {} )

    this.timeout( 3000 )
    this.slow( 2500 )

    let done
    const finished = new Promise( ( r ) => { done = r } )

    server.bind()
    server.on( "listening", async function() {

      const ourport = server.address().port

      let expectedmessagecount = 0
      const expectedmessages = [
        { action: "telephone-event", event: "3" },
        { action: "close" }
      ]

      const channel = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourport, "codec": 0 } }, function( d ) {

        expect( d ).to.deep.include( expectedmessages[ expectedmessagecount ] )
        expectedmessagecount++

        if( "close" === d.action ) {
          server.close()
          done()
        }
      } )

      expect( channel.echo() ).to.be.true

      // Event "3"
      const dstport = channel.local.port
      sendpk( 948, 148480, 0, dstport, server, 0, 518218235 )
      sendpayload( 20, fromstr( "80 e5 03 b5 00 02 44 a0 1e e3 61 fb 03 0a 00 a0 4c d1" ), dstport, server )
      sendpk( 950, 148800, 40, dstport, server, 0, 518218235 )
      sendpayload( 60, fromstr( "80 65 03 b7 00 02 44 a0 1e e3 61 fb 03 0a 01 40 25 b8" ), dstport, server )
      sendpk( 952, 149120, 80, dstport, server, 0, 518218235 )
      sendpayload( 100, fromstr( "80 65 03 b9 00 02 44 a0 1e e3 61 fb 03 0a 01 e0 e8 81" ), dstport, server )
      sendpk( 954, 149440,120, dstport, server, 0, 518218235 )
      sendpayload( 140, fromstr( "80 65 03 bb 00 02 44 a0 1e e3 61 fb 03 0a 02 80 e2 74" ), dstport, server )
      sendpk( 956, 149760, 160, dstport, server, 0, 518218235 )
      sendpayload( 180, fromstr( "80 65 03 bd 00 02 44 a0 1e e3 61 fb 03 0a 03 20 1f bb" ), dstport, server )
      sendpk( 958, 150080, 200, dstport, server, 0, 518218235 )
      sendpayload( 220, fromstr( "80 65 03 bf 00 02 44 a0 1e e3 61 fb 03 0a 03 c0 13 0c" ), dstport, server )
      sendpk( 960, 150400,240, dstport, server, 0, 518218235 )
      sendpayload( 260, fromstr( "80 65 03 c1 00 02 44 a0 1e e3 61 fb 03 0a 04 60 2e bf" ), dstport, server )
      sendpk( 962, 150720, 280, dstport, server, 0, 518218235 )
      sendpayload( 300, fromstr( "80 65 03 c3 00 02 44 a0 1e e3 61 fb 03 8a 04 60 05 c1" ), dstport, server )
      sendpayload( 320 ,fromstr( "80 65 03 c4 00 02 44 a0 1e e3 61 fb 03 8a 04 60 bb 69" ), dstport, server )
      sendpk( 965, 151200, 340, dstport, server, 0, 518218235 )
      sendpayload( 360, fromstr( "80 65 03 c6 00 02 44 a0 1e e3 61 fb 03 8a 04 60 1e 27" ), dstport, server )
      sendpk( 967, 151520, 380, dstport, server, 0, 518218235 )
      sendpk( 968, 151680, 400, dstport, server, 0, 518218235 )
      sendpk( 969, 151840, 420, dstport, server, 0, 518218235 )
      sendpk( 970, 152000, 440, dstport, server, 0, 518218235 )

      setTimeout( () => channel.close(), 25*20 )
    } )

    await finished
  } )

  it( "DTMF PCAP playback test 1", async function() {

    this.timeout( 21000 )
    this.slow( 20000 )

    let done
    const finished = new Promise( ( r ) => { done = r } )

    const receivedmessages = []

    /* create our RTP/UDP endpoint */
    const server = dgram.createSocket( "udp4" )
    server.on( "message", function() {} )

    server.bind()
    server.on( "listening", async function() {

      const ourport = server.address().port
      const channel = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourport, "codec": 0 } }, function( d ) {
        receivedmessages.push( d )

        if( "close" === d.action ) {
          server.close()
          done()
        }
      } )
      setTimeout( () => channel.close(), 1000 * 17 )
      const dstport = channel.local.port

      channel.play( { "interupt":true, "files": [ { "wav": "test/interface/pcaps/180fa1ac-08e5-11ed-bd4d-02dba5b5aad6.wav" } ] } )

      const ourpcap = await pcap.readpcap( "test/interface/pcaps/dtmfcapture1.pcap" )

      const offset = 4700

      ourpcap.forEach( ( packet ) => {
        if( packet.ipv4 && packet.ipv4.udp && 10230 == packet.ipv4.udp.dstport ) {
          //console.dir( packet, { depth: null } )
          //console.log(packet.ipv4.udp.data.readUInt16BE( 2 ) )

          //const sn = packet.ipv4.udp.data.readUInt16BE( 2 )

          sendpayload( ( 1000 * packet.ts_sec_offset ) - offset, packet.ipv4.udp.data, dstport, server )
        }
      } )
    } )

    await finished

    const expectedmessages = [
      { action: "play", event: "start", reason: "new" },
      { action: "play", event: "end", reason: "telephone-event" },
      { action: "telephone-event", event: "2" },
      { action: "close" }
    ]

    expect( receivedmessages.length ).to.equal( 4 )
    expect( receivedmessages[ 0 ] ).to.deep.include( expectedmessages[ 0 ] )
    expect( receivedmessages[ 1 ] ).to.deep.include( expectedmessages[ 1 ] )
    expect( receivedmessages[ 2 ] ).to.deep.include( expectedmessages[ 2 ] )
    expect( receivedmessages[ 3 ] ).to.deep.include( expectedmessages[ 3 ] )

  } )

  it( "DTMF PCAP playback test 2", async function() {

    this.timeout( 21000 )
    this.slow( 20000 )

    let done
    const finished = new Promise( ( r ) => { done = r } )

    const receivedmessages = []

    /* create our RTP/UDP endpoint */
    const server = dgram.createSocket( "udp4" )
    server.on( "message", function() {} )

    server.bind()
    server.on( "listening", async function() {

      const ourport = server.address().port
      const channel = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourport, "codec": 0 } }, function( d ) {
        receivedmessages.push( d )

        if( "close" === d.action ) {
          server.close()
          done()
        }
      } )
      setTimeout( () => channel.close(), 1000 * 17 )
      const dstport = channel.local.port

      channel.play( { "interupt":true, "files": [ { "wav": "test/interface/pcaps/180fa1ac-08e5-11ed-bd4d-02dba5b5aad6.wav" } ] } )

      const ourpcap = await pcap.readpcap( "test/interface/pcaps/dtmf3presses.pcap" )

      const offset = 17 * 1000

      ourpcap.forEach( ( packet ) => {
        
        if( packet.ipv4 && packet.ipv4.udp && 10298 == packet.ipv4.udp.dstport ) {
          //console.dir( packet, { depth: null } )
          //console.log(packet.ipv4.udp.data.readUInt16BE( 2 ) )
          //const sn = packet.ipv4.udp.data.readUInt16BE( 2 )

          const sendat = ( 1000 * packet.ts_sec_offset ) - offset
          if ( 0 < sendat ) {
            //console.log(sn, sendat)
            sendpayload( sendat, packet.ipv4.udp.data, dstport, server )
          }
        }
      } )
    } )

    await finished

    const expectedmessages = [
      { action: "play", event: "start", reason: "new" },
      { action: "play", event: "end", reason: "telephone-event" },
      { action: "telephone-event", event: "1" },
      { action: "telephone-event", event: "1" },
      { action: "telephone-event", event: "1" },
      { action: "close" }
    ]

    expect( receivedmessages.length ).to.equal( 6 )
    expect( receivedmessages[ 0 ] ).to.deep.include( expectedmessages[ 0 ] )
    expect( receivedmessages[ 1 ] ).to.deep.include( expectedmessages[ 1 ] )
    expect( receivedmessages[ 2 ] ).to.deep.include( expectedmessages[ 2 ] )
    expect( receivedmessages[ 3 ] ).to.deep.include( expectedmessages[ 3 ] )
    expect( receivedmessages[ 4 ] ).to.deep.include( expectedmessages[ 4 ] )
    expect( receivedmessages[ 5 ] ).to.deep.include( expectedmessages[ 5 ] )

  } )

  /*
    Regression for the IVR bug captured in
    test/interface/pcaps/ivr_dtmf_issue_9_1_9_9_3.pcap.

    The caller dialed 9, then 1-9-9-3 (which should have triggered an IVR
    branch), then retried with 3 and a few more digits. C++ produced the
    full digit stream; Rust dropped or merged something so the IVR never
    matched. This test replays the pcap exactly as it arrived on the wire
    and asserts every digit press surfaces as one telephone-event.
  */
  it( "DTMF PCAP IVR 9-1-9-9-3 sequence", async function() {

    this.timeout( 50000 )
    this.slow( 45000 )

    let done
    const finished = new Promise( ( r ) => { done = r } )

    const receivedmessages = []

    const server = dgram.createSocket( "udp4" )
    server.on( "message", function() {} )

    server.bind()
    server.on( "listening", async function() {

      const ourport = server.address().port
      const channel = await projectrtp.openchannel( { "remote": { "address": "127.0.0.1", "port": ourport, "codec": 0 } }, function( d ) {
        receivedmessages.push( d )

        if( "close" === d.action ) {
          server.close()
          done()
        }
      } )

      // Pcap last DTMF lands at 41.2 s; offset 4 s of audio so the first
      // press fires almost immediately. Close shortly after the last
      // expected digit so the channel posts its close event.
      const offset = 4000
      setTimeout( () => channel.close(), 1000 * 40 )
      const dstport = channel.local.port

      const ourpcap = await pcap.readpcap( "test/interface/pcaps/ivr_dtmf_issue_9_1_9_9_3.pcap" )

      ourpcap.forEach( ( packet ) => {
        if( packet.ipv4 && packet.ipv4.udp && 11408 == packet.ipv4.udp.dstport ) {
          const sendat = ( 1000 * packet.ts_sec_offset ) - offset
          if ( 0 < sendat ) {
            sendpayload( sendat, packet.ipv4.udp.data, dstport, server )
          }
        }
      } )
    } )

    await finished

    const dtmf = receivedmessages
      .filter( m => "telephone-event" === m.action )
      .map( m => m.event )

    expect( dtmf ).to.deep.equal(
      [ "9", "1", "9", "9", "3", "3", "1", "1", "1", "1", "2" ],
      `expected full IVR digit sequence; got ${ JSON.stringify( dtmf ) }`
    )

  } )

  /*
    Regression for the 3CX-bridged-call DTMF drop captured in
    test/interface/pcaps/sinora.pcap.

    The 3CX client (31.15.105.43) was on a bridged call through projectrtp
    with two legs:
      leg A: 9492 <-> 11634 (audio only)
      leg B: 9494 <-> 13018 (audio + RFC 2833 DTMF)

    The wire shows five distinct RFC 2833 bursts of digit "2" (the caller
    re-pressed because nothing was getting through). On the C++ build the
    digits forwarded out leg A; on Rust they didn't.
    This test mixes two channels, replays both legs of the wire-captured
    audio into them, and asserts both that the DTMF is detected on the
    receiving leg AND that the digits are forwarded out the peer leg as
    RFC 2833 packets.
  */
  it( "DTMF PCAP sinora 3CX bridged DTMF forwarding", async function() {

    this.timeout( 30000 )
    this.slow( 25000 )

    let done
    const finished = new Promise( ( r ) => { done = r } )

    const channelbreceivedmessages = []

    const endpointa = dgram.createSocket( "udp4" )
    const endpointb = dgram.createSocket( "udp4" )

    let endpointadtmfpkcount = 0
    endpointa.on( "message", function( msg ) {
      if( 101 == ( 0x7f & msg[ 1 ] ) ) endpointadtmfpkcount++
    } )
    endpointb.on( "message", function() {} )

    endpointa.bind()
    await new Promise( ( resolve ) => { endpointa.on( "listening", () => resolve() ) } )
    endpointb.bind()
    await new Promise( ( resolve ) => { endpointb.on( "listening", () => resolve() ) } )

    const channela = await projectrtp.openchannel(
      { "remote": { "address": "127.0.0.1", "port": endpointa.address().port, "codec": 0 } },
      function( d ) {
        if( "close" === d.action ) channelb.close()
      }
    )
    const channelb = await projectrtp.openchannel(
      { "remote": { "address": "127.0.0.1", "port": endpointb.address().port, "codec": 0 } },
      function( d ) {
        channelbreceivedmessages.push( d )
        if( "close" === d.action ) {
          endpointa.close()
          endpointb.close()
          done()
        }
      }
    )

    expect( channela.mix( channelb ) ).to.be.true

    const adstport = channela.local.port
    const bdstport = channelb.local.port

    const ourpcap = await pcap.readpcap( "test/interface/pcaps/sinora.pcap", 10000 )

    ourpcap.forEach( ( packet ) => {
      if( !packet.ipv4 || !packet.ipv4.udp ) return
      const u = packet.ipv4.udp
      const sendat = 1000 * packet.ts_sec_offset
      if( sendat < 0 ) return
      /* leg A audio: 3CX:9492 -> projectrtp:11634 */
      if( 9492 === u.srcport && 11634 === u.dstport ) {
        sendpayload( sendat, u.data, adstport, endpointa )
      }
      /* leg B audio + DTMF: 3CX:9494 -> projectrtp:13018 */
      if( 9494 === u.srcport && 13018 === u.dstport ) {
        sendpayload( sendat, u.data, bdstport, endpointb )
      }
    } )

    /* trimmed pcap is ~17s; last DTMF burst ends ~14.9s — close after settle */
    setTimeout( () => channela.close(), 1000 * 18 )

    await finished

    const events = channelbreceivedmessages
      .filter( m => "telephone-event" === m.action )
      .map( m => m.event )

    expect( events ).to.deep.equal(
      [ "2", "2", "2", "2", "2" ],
      `expected 5 presses of '2' detected on leg B; got ${ JSON.stringify( events ) }`
    )

    /* forwarded DTMF: 5 bursts × 14 packets per digit; allow some packet loss */
    expect( endpointadtmfpkcount ).to.be.at.least(
      5 * ( pkcountperdtmfdigit - 2 ),
      `expected DTMF forwarded to leg A; got ${ endpointadtmfpkcount } packets`
    )
  } )
} )