@babblevoice/projectrtp
Version:
A scalable Node addon RTP server
1,484 lines • 56 kB
JavaScript
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`
)
} )
} )