UNPKG

@babblevoice/projectrtp

Version:
608 lines (458 loc) 17.3 kB
const expect = require( "chai" ).expect const dgram = require( "dgram" ) const projectrtp = require( "../../index" ).projectrtp const node = require( "../../lib/node" ).interface const server = require( "../../lib/server" ).interface const rtputil = require( "../util/rtp" ) /** * Common channel tester * @param { object } channel * @param { string } id */ function exepectchannel( channel, id ) { expect( channel ).to.be.an( "object" ) expect( channel.close ).to.be.an( "function" ) expect( channel.mix ).to.be.an( "function" ) expect( channel.unmix ).to.be.an( "function" ) expect( channel.echo ).to.be.an( "function" ) expect( channel.play ).to.be.an( "function" ) expect( channel.record ).to.be.an( "function" ) expect( channel.direction ).to.be.an( "function" ) expect( channel.dtmf ).to.be.an( "function" ) expect( channel.remote ).to.be.an( "function" ) expect( channel.local ).to.have.property( "port" ).that.is.a( "number" ) expect( channel.local ).to.have.property( "address" ).that.is.a( "string" ) expect( channel.local ).to.have.property( "icepwd" ).that.is.a( "string" ).to.have.lengthOf.above( 20 ) expect( channel.uuid ).that.is.a( "string" ) expect( channel.id ).that.is.a( "string" ) expect( channel.id ).to.equal( id ) } /** * @ignore * @param { number } sn * @param { number } sendtime * @param { number } dstport * @param { object } server * @param { number } ssrc * @param { number } pklength */ function sendpk( sn, sendtime, dstport, server, ssrc = 25, pklength = 172 ) { setTimeout( () => { const payload = Buffer.alloc( pklength - 12 ).fill( sn & 0xff ) const ts = sn * 160 const tsparts = [] /* portability? */ tsparts[ 3 ] = ( ts & 0xff000000 ) >> 24 tsparts[ 2 ] = ( ts & 0xff0000 ) >> 16 tsparts[ 1 ] = ( ts & 0xff00 ) >> 8 tsparts[ 0 ] = ( ts & 0xff ) const snparts = [] sn = ( sn + 100 ) % ( 2**16 ) /* just some offset */ snparts[ 0 ] = sn & 0xff snparts[ 1 ] = sn >> 8 const ssrcparts = [] ssrcparts[ 3 ] = ( ssrc & 0xff000000 ) >> 24 ssrcparts[ 2 ] = ( ssrc & 0xff0000 ) >> 16 ssrcparts[ 1 ] = ( ssrc & 0xff00 ) >> 8 ssrcparts[ 0 ] = ( ssrc & 0xff ) const rtppacket = Buffer.concat( [ Buffer.from( [ 0x80, 0x00, snparts[ 1 ], snparts[ 0 ], tsparts[ 3 ], tsparts[ 2 ], tsparts[ 1 ], tsparts[ 0 ], ssrcparts[ 3 ], ssrcparts[ 2 ], ssrcparts[ 1 ], ssrcparts[ 0 ] ] ), payload ] ) server.send( rtppacket, dstport, "localhost" ) }, sendtime * 20 ) } /* Tests */ describe( "rtpchannel", function() { it( "call create channel and check the structure of the returned object", async function() { let done const finished = new Promise( ( r ) => { done = r } ) const channel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": 20000, "codec": 0 } }, function( d ) { if( "close" === d.action ) done() } ) exepectchannel( channel, "4" ) await new Promise( ( resolve ) => { setTimeout( () => resolve(), 100 ) } ) channel.close() await finished } ) it( "call create channel and check the structure of the returned object - server as listener", async function() { const ourport = 45433 await projectrtp.server.listen( ourport, "127.0.0.1" ) const ournode = await projectrtp.node.connect( ourport, "127.0.0.1" ) let done const finished = new Promise( ( r ) => { done = r } ) const channel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": 20000, "codec": 0 } }, function( d ) { if( "close" === d.action ) done() } ) exepectchannel( channel, "4" ) await new Promise( ( resolve ) => { setTimeout( () => resolve(), 100 ) } ) channel.close() await finished ournode.destroy() await server.destroy() } ) it( "call create channel and check the structure of the returned object - node as listener", async function() { const ourport = 45432 projectrtp.server.clearnodes() projectrtp.server.addnode( { host: "127.0.0.1", port: ourport } ) const ournode = node.create( projectrtp ) const n = await ournode.listen( "127.0.0.1", ourport ) let done const finished = new Promise( ( r ) => { done = r } ) const channel = await projectrtp.openchannel( { "id": "4", "remote": { "address": "localhost", "port": 20000, "codec": 0 } }, function( d ) { if( "close" === d.action ) done() } ) exepectchannel( channel, "4" ) await new Promise( ( resolve ) => { setTimeout( () => resolve(), 100 ) } ) channel.close() await finished projectrtp.server.clearnodes() n.destroy() } ) it( "call create channel echo", function( done ) { /* create our RTP/UDP endpoint */ const server = dgram.createSocket( "udp4" ) let receviedpkcount = 0 server.on( "message", function() { receviedpkcount++ } ) 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": "localhost", "port": ourport, "codec": 0 } }, function( d ) { if( "close" === d.action ) { expect( d.reason ).to.equal( "requested" ) expect( receviedpkcount ).to.equal( 50 ) expect( d.stats.in.count ).to.equal( 50 ) expect( d.stats.in.mos ).to.equal( 4.5 ) expect( d.stats.in.dropped ).to.equal( 0 ) expect( d.stats.in.skip ).to.equal( 0 ) expect( d.stats.out.count ).to.equal( 50 ) server.close() done() } } ) expect( channel ).to.be.an( "object" ) expect( channel.close ).to.be.an( "function" ) expect( channel.local ).to.have.property( "port" ).that.is.a( "number" ) expect( channel.echo() ).to.be.true /* send a packet every 20mS x 50 */ for( let i = 0; 50 > i; i ++ ) { sendpk( i, i, channel.local.port, server ) } setTimeout( () => channel.close(), 2000 ) } ) } ) it( "create channel echo and skip some packets", function( done ) { const server = dgram.createSocket( "udp4" ) let receviedpkcount = 0 server.on( "message", function() { receviedpkcount++ } ) 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": "localhost", "port": ourport, "codec": 0 } }, function( d ) { if( "close" === d.action ) { expect( receviedpkcount ).to.equal( 50 - 6 ) expect( d.stats.in.count ).to.equal( 50 - 6 ) expect( d.stats.out.count ).to.equal( 50 - 6 ) server.close() done() } } ) expect( channel.echo() ).to.be.true /* send a packet every 20mS x 50 */ for( let i = 0; 50 > i; i ++ ) { if( i in { 3:0, 13:0, 23:0, 24:0, 30:0, 49:0 } ) continue sendpk( i, i, channel.local.port, server ) } setTimeout( () => channel.close(), 2000 ) } ) } ) it( "create channel echo and send out of order packets", function( done ) { const server = dgram.createSocket( "udp4" ) let receviedpkcount = 0 let lastsn = -1 let lastts = -1 let totalsndiff = 0 let totaltsdiff = 0 server.on( "message", function( msg ) { let sn = 0 sn = msg[ 2 ] << 8 sn = sn | msg[ 3 ] let ts = 0 ts = msg[ 4 ] << 24 ts = ts | ( msg[ 5 ] << 16 ) ts = ts | ( msg[ 6 ] << 8 ) ts = ts | msg[ 7 ] if( -1 !== lastsn ) { totalsndiff += sn - lastsn - 1 totaltsdiff += ts - lastts - 160 } lastsn = sn lastts = ts receviedpkcount++ } ) 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": "localhost", "port": ourport, "codec": 0 } }, function( d ) { if( "close" === d.action ) { expect( receviedpkcount ).to.equal( 50 ) expect( d.stats.in.count ).to.equal( 50 ) expect( d.stats.out.count ).to.equal( 50 ) expect( totalsndiff ).to.equal( 0 ) // received should be reordered expect( totaltsdiff ).to.equal( 0 ) server.close() done() } } ) expect( channel.echo() ).to.be.true /* send a packet every 20mS x 50 */ const sns = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 12, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 24, 26, 27, 28, 29, 30, 31, 37, 33, 34, 36, 35, 32, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49 ] sns.forEach( function( e, i ) { sendpk( e, i, channel.local.port, server ) } ) setTimeout( () => channel.close(), 2000 ) } ) } ) it( "create channel echo and send packets outside of window", function( done ) { const server = dgram.createSocket( "udp4" ) let receviedpkcount = 0 server.on( "message", function() { receviedpkcount++ } ) 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": "localhost", "port": ourport, "codec": 0 } }, function( d ) { if( "close" === d.action ) { expect( receviedpkcount ).to.equal( 47 ) expect( d.stats.in.count ).to.equal( 50 ) expect( d.stats.in.dropped ).to.equal( 3 ) expect( d.stats.out.count ).to.equal( 47 ) server.close() done() } } ) expect( channel.echo() ).to.be.true /* send a packet every 20mS x 50 */ const sns = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 12, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 24, 26, 27, 28, 29, 100, 31, 37, 33, 34, 36, 35, 32, 38, 39, 400, 41, 42, 43, 44, 45, 46, 47, 48, 2 ] sns.forEach( function( e, i ) { sendpk( e, i, channel.local.port, server ) } ) setTimeout( () => channel.close(), 2000 ) } ) } ) it( "create channel echo and simulate a stalled connection", async function() { const server = dgram.createSocket( "udp4" ) let receviedpkcount = 0 let channel let firstsn = 0 let lastsn = -1 let lastts = -1 let totalsndiff = 0 let totaltsdiff = 0 server.on( "message", function( msg ) { const pk = rtputil.parsepk( msg ) const sn = pk.sn const ts = pk.ts if( -1 !== lastsn ) { totalsndiff += sn - lastsn - 1 totaltsdiff += ts - lastts - 160 } else { firstsn = sn } lastsn = sn lastts = ts receviedpkcount++ } ) this.timeout( 15000 ) this.slow( 8000 ) server.bind() let closedstats = {} server.on( "listening", async function() { const ourport = server.address().port channel = await projectrtp.openchannel( { "remote": { "address": "localhost", "port": ourport, "codec": 0 } }, function( d ) { if( "close" === d.action ) { closedstats = d server.close() } } ) expect( channel.echo() ).to.be.true /* send a packet every 20mS x 50 */ let i for( i = 0; 120 > i; i++ ) { sendpk( i, i, channel.local.port, server ) } /* pause then catchup */ for( ; 150 > i; i++ ) { sendpk( i, 150, channel.local.port, server ) } /* resume */ for( ; 300 > i; i++ ) { sendpk( i, i, channel.local.port, server ) } } ) await new Promise( resolve => setTimeout( resolve, 6500 ) ) // @ts-ignore channel.close() await new Promise( resolve => setTimeout( resolve, 50 ) ) /* We should receive 50 from the first batch 30 from the catchup as some will be dropped 100 from the final batch as it will take some to get going again */ expect( receviedpkcount ).to.equal( closedstats.stats.out.count ) expect( closedstats.stats.in.count ).to.equal( 300 ) expect( closedstats.stats.out.count ).to.be.above( 250 ) expect( totalsndiff ).to.equal( 0 ) // received should be reordered expect( totaltsdiff ).to.be.within( 5000, 18400 ) // Allow some loss in test expect( lastsn - firstsn ).to.be.within( 250, 300 ) } ) it( "create channel echo whilst wrapping the sn", function( done ) { /* create our RTP/UDP endpoint */ const server = dgram.createSocket( "udp4" ) let receviedpkcount = 0 server.on( "message", function() { receviedpkcount++ } ) 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": "localhost", "port": ourport, "codec": 0 } }, function( d ) { if( "close" === d.action ) { expect( receviedpkcount ).to.equal( 50 ) expect( d.stats.in.count ).to.equal( 50 ) expect( d.stats.out.count ).to.equal( 50 ) server.close() done() } } ) expect( channel.echo() ).to.be.true /* send a packet every 20mS x 50 */ for( let i = 0 ; 50 > i; i ++ ) { const sn = i + ( 2**16 ) - 25 sendpk( sn, i, channel.local.port, server ) } setTimeout( () => channel.close(), 2000 ) } ) } ) it( "create channel echo and incorrectly change the ssrc", function( done ) { /* This needs further work so make work for now. Remove tests which check for ignored packets. */ /* create our RTP/UDP endpoint */ const server = dgram.createSocket( "udp4" ) let receviedpkcount = 0 server.on( "message", function() { receviedpkcount++ } ) 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": "localhost", "port": ourport, "codec": 0 } }, function( d ) { if( "close" === d.action ) { expect( d.stats.in.count ).to.equal( 100 ) expect( d.stats.out.count ).to.equal( receviedpkcount ) server.close() done() } } ) expect( channel.echo() ).to.be.true /* send a packet every 20mS x 50 */ let i for( i = 0 ; 50 > i; i ++ ) { sendpk( i, i, channel.local.port, server, 25 ) } for( ; 100 > i; i ++ ) { sendpk( i, i, channel.local.port, server, 77 ) } setTimeout( () => channel.close(), 2100 ) } ) } ) it( "send oversized rtp packet", function( done ) { /* This test has been adjusted as we now handle large packets - but we should crash! */ /* 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 const channel = await projectrtp.openchannel( { "remote": { "address": "localhost", "port": ourport, "codec": 0 } }, function( d ) { if( "close" === d.action ) { expect( d.stats.in.count ).to.equal( 50 ) expect( d.stats.out.count ).to.equal( 50 ) server.close() done() } } ) expect( channel.echo() ).to.be.true /* send a packet every 20mS x 50 */ for( let i = 0 ; 50 > i; i ++ ) { if( 40 == i ) { /* an oversized packet */ sendpk( i, i, channel.local.port, server, 25, 1200 ) } else { sendpk( i, i, channel.local.port, server, 25 ) } } setTimeout( () => channel.close(), 2100 ) } ) } ) it( "create channel echo and close on timeout", function( done ) { this.timeout( 21000 ) this.slow( 20000 ) projectrtp.openchannel( { "remote": { "address": "localhost", "port": 20765, "codec": 0 } }, function( d ) { if( "close" === d.action ) { expect( d.stats.in.count ).to.equal( 0 ) expect( d.stats.in.skip ).to.equal( 0 ) expect( d.stats.out.count ).to.equal( 0 ) done() } } ) } ) it( "create channel and check event emitter", async () => { this.timeout( 2000 ) this.slow( 2000 ) let done const waituntildone = new Promise( ( r ) => done = r ) const chan = await projectrtp.openchannel( { "remote": { "address": "localhost", "port": 20765, "codec": 0 } } ) chan.em.on( "all", ( d ) => { if( "close" === d.action ) { expect( d.stats.in.count ).to.equal( 0 ) expect( d.stats.in.skip ).to.equal( 0 ) expect( d.stats.out.count ).to.equal( 0 ) done() } } ) chan.close() await waituntildone } ) } )