import { setTimeout } from "timers/promises" ; import { Candidate } from "../../src/candidate" ; import { CandidatePair , CandidatePairState , Connection } from "../../src/ice" ; import { classes , methods } from "../../src/stun/const" ; import { Message } from "../../src/stun/message" ; import { Address , Protocol } from "../../src/types/model" ; import { assertCandidateTypes , inviteAccept } from "../utils" ; class ProtocolMock implements Protocol { type = "mock" ; responseAddr ? : Address ; responseMessage ? : string ; localCandidate = new Candidate ( "some-foundation" , 1 , "udp" , 1234 , "1.2.3.4" , 1234 , "host" ) ; sentMessage ? : Message ; request = async ( ) => { return null as any ; } ; sendStun = ( message ) => { this . sentMessage = message ; } ; async connectionMade ( ) { } async sendData ( ) { } } describe ( "ice" , ( ) => { test ( "test_peer_reflexive" , async ( ) => { const connection = new Connection ( true ) ; connection . remotePassword = "remote-password" ; connection . remoteUsername = "remote-username" ; const protocol = new ProtocolMock ( ) as any ; const request = new Message ( methods . BINDING , classes . REQUEST ) ; request . attributes [ "PRIORITY" ] = 456789 ; connection . checkIncoming ( request , [ "2.3.4.5" , 2345 ] , protocol ) ; expect ( protocol . sentMessage ) . not . toBeNull ( ) ; expect ( connection . remoteCandidates . length ) . toBe ( 1 ) ; const candidate = connection . remoteCandidates [ 0 ] ; expect ( candidate . component ) . toBe ( 1 ) ; expect ( candidate . transport ) . toBe ( "udp" ) ; expect ( candidate . priority ) . toBe ( 456789 ) ; expect ( candidate . host ) . toBe ( "2.3.4.5" ) ; expect ( candidate . type ) . toBe ( "prflx" ) ; expect ( candidate . generation ) . toBe ( undefined ) ; expect ( connection . checkList . length ) . toBe ( 1 ) ; const pair = connection . checkList [ 0 ] ; expect ( pair . protocol ) . toBe ( protocol ) ; expect ( pair . remoteCandidate ) . toBe ( candidate ) ; expect ( pair . handle ) . not . toBeNull ( ) ; protocol . responseAddr = [ "2.3.4.5" , 2345 ] ; protocol . responseMessage = "bad" ; await pair . handle ?. promise ; } ) ; test ( "test_request_with_invalid_method" , ( ) => { const connection = new Connection ( true ) ; const protocol = new ProtocolMock ( ) ; const request = new Message ( methods . ALLOCATE , classes . REQUEST ) ; connection . requestReceived ( request , [ "2.3.4.5" , 2345 ] , protocol , request . bytes ) ; expect ( protocol . sentMessage ) . not . toBeNull ( ) ; expect ( protocol . sentMessage ?. messageMethod ) . toBe ( methods . ALLOCATE ) ; expect ( protocol . sentMessage ?. messageClass ) . toBe ( classes . ERROR ) ; expect ( protocol . sentMessage ?. attributes [ "ERROR-CODE" ] ) . toEqual ( [ 400 , "Bad Request" , ] ) ; } ) ; test ( "test_response_with_invalid_address" , async ( ) => { const connection = new Connection ( true ) ; connection . remotePassword = "remote-password" ; connection . remoteUsername = "remote-username" ; const protocol = new ProtocolMock ( ) ; protocol . responseAddr = [ "3.4.5.6" , 3456 ] ; protocol . responseMessage = "bad" ; const pair = new CandidatePair ( protocol , new Candidate ( "some-foundation" , 1 , "udp" , 2345 , "2.3.4.5" , 2345 , "host" ) ) ; await connection . checkStart ( pair ) ; expect ( pair . state ) . toBe ( CandidatePairState . FAILED ) ; } ) ; test ( "test_connect" , async ( ) => { const a = new Connection ( true , { } ) ; const b = new Connection ( false , { } ) ; await inviteAccept ( a , b ) ; assertCandidateTypes ( a , [ "host" ] ) ; assertCandidateTypes ( b , [ "host" ] ) ; let candidate = a . getDefaultCandidate ( 1 ) ; expect ( candidate ) . not . toBeUndefined ( ) ; expect ( candidate ?. type ) . toBe ( "host" ) ; candidate = a . getDefaultCandidate ( 2 ) ; expect ( candidate ) . toBeUndefined ( ) ; await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; await a . send ( Buffer . from ( "howdee" ) ) ; let [ data ] = await b . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "howdee" ) ; await b . send ( Buffer . from ( "gotcha" ) ) ; [ data ] = await a . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "gotcha" ) ; await a . close ( ) ; await b . close ( ) ; } ) ; test ( "test_connect_close" , async ( ) => new Promise < void > ( async ( done ) => { const a = new Connection ( true , { } ) ; const b = new Connection ( false , { } ) ; await inviteAccept ( a , b ) ; await b . close ( ) ; try { await Promise . all ( [ a . connect ( ) , async ( ) => { await setTimeout ( 1000 ) ; await a . close ( ) ; } , ] ) ; } catch ( error ) { expect ( true ) . toBe ( true ) ; done ( ) ; } } ) , 1000 * 10 ) ; test ( "test_connect_ipv6" , async ( ) => { const a = new Connection ( true , { useIpv4 : false , useIpv6 : true } ) ; const b = new Connection ( false , { useIpv4 : false , useIpv6 : true } ) ; await inviteAccept ( a , b ) ; assertCandidateTypes ( a , [ "host" ] ) ; await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; await a . send ( Buffer . from ( "howdee" ) ) ; let [ data ] = await b . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "howdee" ) ; await b . send ( Buffer . from ( "gotcha" ) ) ; [ data ] = await a . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "gotcha" ) ; await a . close ( ) ; await b . close ( ) ; } ) ; test ( "test_connect_reverse_order" , async ( ) => { const a = new Connection ( true ) ; const b = new Connection ( false ) ; await inviteAccept ( a , b ) ; await Promise . all ( [ new Promise < void > ( ( r ) => setTimeout ( 1000 ) . then ( async ( ) => { await a . connect ( ) ; r ( ) ; } ) ) , b . connect ( ) , ] ) ; await a . send ( Buffer . from ( "howdee" ) ) ; let [ data ] = await b . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "howdee" ) ; await b . send ( Buffer . from ( "gotcha" ) ) ; [ data ] = await a . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "gotcha" ) ; await a . close ( ) ; await b . close ( ) ; } ) ; test ( "test_connect_invalid_password" , async ( ) => new Promise < void > ( async ( done ) => { const a = new Connection ( true ) ; const b = new Connection ( false ) ; await a . gatherCandidates ( ) ; b . remoteCandidates = a . localCandidates ; b . remoteUsername = a . localUserName ; b . remotePassword = a . remotePassword ; await b . gatherCandidates ( ) ; a . remoteCandidates = b . localCandidates ; a . remoteUsername = b . localUserName ; a . remotePassword = "wrong-password" ; try { await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; } catch ( error ) { expect ( error . message ) . toBe ( "Remote username or password is missing" ) ; await a . close ( ) ; await b . close ( ) ; done ( ) ; } } ) ) ; test ( "test_connect_invalid_username" , async ( ) => new Promise < void > ( async ( done ) => { const a = new Connection ( true ) ; const b = new Connection ( false ) ; await a . gatherCandidates ( ) ; b . remoteCandidates = a . localCandidates ; b . remoteUsername = a . localUserName ; b . remotePassword = a . remotePassword ; await b . gatherCandidates ( ) ; a . remoteCandidates = b . localCandidates ; a . remoteUsername = "wrong-username" ; a . remotePassword = b . localPassword ; try { await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; } catch ( error ) { expect ( error . message ) . toBe ( "Remote username or password is missing" ) ; await a . close ( ) ; await b . close ( ) ; done ( ) ; } } ) ) ; test ( "test_connect_no_gather" , async ( ) => new Promise < void > ( async ( done ) => { const conn = new Connection ( true ) ; conn . remoteCandidates = [ Candidate . fromSdp ( "6815297761 1 udp 659136 1.2.3.4 31102 typ host generation 0" ) , ] ; conn . remoteUsername = "foo" ; conn . remotePassword = "bar" ; try { await conn . connect ( ) ; } catch ( error ) { expect ( error . message ) . toBe ( "Local candidates gathering was not performed" ) ; await conn . close ( ) ; done ( ) ; } } ) ) ; test ( "test_connect_no_local_candidates" , async ( ) => new Promise < void > ( async ( done ) => { const conn = new Connection ( true ) ; conn . _localCandidatesEnd = true ; conn . remoteCandidates = [ Candidate . fromSdp ( "6815297761 1 udp 659136 1.2.3.4 31102 typ host generation 0" ) , ] ; conn . remoteUsername = "foo" ; conn . remotePassword = "bar" ; try { await conn . connect ( ) ; } catch ( error ) { expect ( error . message ) . toBe ( "ICE negotiation failed" ) ; await conn . close ( ) ; done ( ) ; } } ) ) ; test ( "test_connect_no_remote_candidates" , async ( ) => new Promise < void > ( async ( done ) => { const conn = new Connection ( true ) ; await conn . gatherCandidates ( ) ; conn . remoteCandidates = [ ] ; conn . remoteUsername = "foo" ; conn . remotePassword = "bar" ; try { await conn . connect ( ) ; } catch ( error ) { expect ( error . message ) . toBe ( "ICE negotiation failed" ) ; await conn . close ( ) ; done ( ) ; } } ) ) ; test ( "test_connect_no_remote_credentials" , async ( ) => new Promise < void > ( async ( done ) => { const conn = new Connection ( true ) ; await conn . gatherCandidates ( ) ; conn . remoteCandidates = [ Candidate . fromSdp ( "6815297761 1 udp 659136 1.2.3.4 31102 typ host generation 0" ) , ] ; try { await conn . connect ( ) ; } catch ( error ) { expect ( error . message ) . toBe ( "Remote username or password is missing" ) ; await conn . close ( ) ; done ( ) ; } } ) ) ; test ( "test_connect_role_conflict_both_controlling" , async ( ) => { const a = new Connection ( true ) ; const b = new Connection ( true ) ; a . _tieBreaker = BigInt ( 1 ) ; b . _tieBreaker = BigInt ( 2 ) ; await inviteAccept ( a , b ) ; try { await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; } catch ( error ) { } expect ( a . iceControlling ) . toBe ( false ) ; expect ( b . iceControlling ) . toBe ( true ) ; await a . close ( ) ; await b . close ( ) ; } , 1000 * 60 * 60 ) ; test ( "test_connect_role_conflict_both_controlled" , async ( ) => { const a = new Connection ( false ) ; const b = new Connection ( false ) ; a . _tieBreaker = BigInt ( 1 ) ; b . _tieBreaker = BigInt ( 2 ) ; await inviteAccept ( a , b ) ; await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; expect ( a . iceControlling ) . toBe ( false ) ; expect ( b . iceControlling ) . toBe ( true ) ; await a . close ( ) ; await b . close ( ) ; } , 1000 * 60 * 60 ) ; test ( "test_connect_with_stun_server" , async ( ) => { const a = new Connection ( true , { stunServer : [ "stun.l.google.com" , 19302 ] , } ) ; const b = new Connection ( false , { stunServer : [ "stun.l.google.com" , 19302 ] , } ) ; await inviteAccept ( a , b ) ; assertCandidateTypes ( a , [ "host" , "srflx" ] ) ; assertCandidateTypes ( b , [ "host" , "srflx" ] ) ; const candidate = a . getDefaultCandidate ( 1 ) ! ; expect ( candidate ) . not . toBeUndefined ( ) ; expect ( candidate . type ) . toBe ( "srflx" ) ; expect ( candidate . relatedAddress ) . not . toBeUndefined ( ) ; expect ( candidate . relatedPort ) . not . toBeUndefined ( ) ; await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; await a . send ( Buffer . from ( "howdee" ) ) ; let [ data ] = await b . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "howdee" ) ; await b . send ( Buffer . from ( "gotcha" ) ) ; [ data ] = await a . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "gotcha" ) ; await a . close ( ) ; await b . close ( ) ; } ) ; test ( "test_connect_with_stun_server_dns_lookup_error" , async ( ) => { const a = new Connection ( true , { stunServer : [ "invalid" , 19302 ] , } ) ; const b = new Connection ( false , { } ) ; await inviteAccept ( a , b ) ; assertCandidateTypes ( a , [ "host" ] ) ; assertCandidateTypes ( b , [ "host" ] ) ; await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; await a . send ( Buffer . from ( "howdee" ) ) ; let [ data ] = await b . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "howdee" ) ; await b . send ( Buffer . from ( "gotcha" ) ) ; [ data ] = await a . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "gotcha" ) ; await a . close ( ) ; await b . close ( ) ; } , 1000 * 60 ) ; test ( "test_connect_with_stun_server_ipv6" , async ( ) => { const a = new Connection ( true , { stunServer : [ "stun.l.google.com" , 19302 ] , useIpv4 : false , useIpv6 : true , } ) ; const b = new Connection ( false , { stunServer : [ "stun.l.google.com" , 19302 ] , useIpv4 : false , useIpv6 : true , } ) ; await inviteAccept ( a , b ) ; expect ( a . localCandidates . length > 0 ) . toBeTruthy ( ) ; a . localCandidates . forEach ( ( v ) => expect ( v . type ) . toBe ( "host" ) ) ; await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; await a . send ( Buffer . from ( "howdee" ) ) ; let [ data ] = await b . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "howdee" ) ; await b . send ( Buffer . from ( "gotcha" ) ) ; [ data ] = await a . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "gotcha" ) ; await a . close ( ) ; await b . close ( ) ; } , 60 * 1000 ) ; test ( "test_connect_to_ice_lite" , async ( ) => { const a = new Connection ( true , { } ) ; a . remoteIsLite = true ; const b = new Connection ( false , { } ) ; await inviteAccept ( a , b ) ; assertCandidateTypes ( a , [ "host" ] ) ; assertCandidateTypes ( b , [ "host" ] ) ; const candidate = a . getDefaultCandidate ( 1 ) ! ; expect ( candidate ) . not . toBeUndefined ( ) ; expect ( candidate . type ) . toBe ( "host" ) ; expect ( a . getDefaultCandidate ( 2 ) ) . toBeUndefined ( ) ; await Promise . all ( [ a . connect ( ) , b . connect ( ) ] ) ; await a . send ( Buffer . from ( "howdee" ) ) ; let [ data ] = await b . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "howdee" ) ; await b . send ( Buffer . from ( "gotcha" ) ) ; [ data ] = await a . onData . asPromise ( ) ; expect ( data . toString ( ) ) . toBe ( "gotcha" ) ; await a . close ( ) ; await b . close ( ) ; } ) ; } ) ;