#!/usr/bin/perl use strict; use warnings; use NGCP::Rtpengine::Test; use NGCP::Rtpclient::SRTP; use Test::More; use File::Temp; use IPC::Open3; use Time::HiRes; use POSIX ":sys_wait_h"; use IO::Socket; like $ENV{LD_PRELOAD}, qr/tests-preload/, 'LD_PRELOAD present'; is $ENV{RTPE_PRELOAD_TEST_ACTIVE}, '1', 'preload library is active'; SKIP: { skip 'daemon is running externally', 1 if $ENV{RTPE_TEST_NO_LAUNCH}; ok -x $ENV{RTPE_BIN}, 'RTPE_BIN points to executable'; } my $rtpe_stdout = File::Temp::tempfile() or die; my $rtpe_stderr = File::Temp::tempfile() or die; my $rtpe_pid; SKIP: { skip 'daemon is running externally', 1 if $ENV{RTPE_TEST_NO_LAUNCH}; $rtpe_pid = open3(undef, '>&'.fileno($rtpe_stdout), '>&'.fileno($rtpe_stderr), $ENV{RTPE_BIN}, qw(--config-file=none -t -1 -i 203.0.113.1 -i 2001:db8:4321::1 -n 2223 -c 12345 -f -L 7 -E -u 2222)); ok $rtpe_pid, 'daemon launched in background'; } # keep trying to connect to the control socket while daemon is starting up my $c; for (1 .. 300) { $c = NGCP::Rtpengine->new($ENV{RTPENGINE_HOST} // 'localhost', $ENV{RTPENGINE_PORT} // 2223); last if $c->{socket}; Time::HiRes::usleep(100000); # 100 ms x 300 = 30 sec } 1; $c->{socket} or die; my ($cid, $ft, $tt, @sockets); my ($tag_iter) = (0); sub new_call { my @ports = @_; for my $s (@sockets) { $s->close(); } @sockets = (); $cid = $tag_iter++ . "-test-callID"; $ft = $tag_iter++ . "-test-fromtag"; $tt = $tag_iter++ . "-test-totag"; for my $p (@ports) { my ($addr, $port) = @{$p}; my $s = IO::Socket::IP->new(Type => &SOCK_DGRAM, Proto => 'udp', LocalHost => $addr, LocalPort => $port) or die; push(@sockets, $s); } return @sockets; } sub crlf { my ($s) = @_; $s =~ s/\r\n/\n/gs; return $s; } sub sdp_split { my ($s) = @_; return split(/--------*\n/, $s); } sub rtpe_req { my ($cmd, $name, $req) = @_; $req->{command} = $cmd; $req->{'call-id'} = $cid; my $resp = $c->req($req); is $resp->{result}, 'ok', "$name - '$cmd' status"; return $resp; } sub offer_answer { my ($cmd, $name, $req, $sdps) = @_; my ($sdp_in, $exp_sdp_out) = sdp_split($sdps); $req->{'from-tag'} = $ft; $req->{sdp} = $sdp_in; my $resp = rtpe_req($cmd, $name, $req); my $regexp = "^\Q$exp_sdp_out\E\$"; $regexp =~ s/\\\?/./gs; $regexp =~ s/PORT/(\\d{1,5})/gs; $regexp =~ s/ICEBASE/([0-9a-zA-Z]{16})/gs; $regexp =~ s/ICEUFRAG/([0-9a-zA-Z]{8})/gs; $regexp =~ s/ICEPWD/([0-9a-zA-Z]{26})/gs; $regexp =~ s/CRYPTO128/([0-9a-zA-Z\/+]{40})/gs; $regexp =~ s/CRYPTO192/([0-9a-zA-Z\/+]{51})/gs; $regexp =~ s/CRYPTO256/([0-9a-zA-Z\/+]{62})/gs; $regexp =~ s/LOOPER/([0-9a-f]{12})/gs; my $crlf = crlf($resp->{sdp}); like $crlf, qr/$regexp/s, "$name - output '$cmd' SDP"; my @matches = $crlf =~ qr/$regexp/s; return @matches; } sub offer { return offer_answer('offer', @_); } sub answer { my ($name, $req, $sdps) = @_; $req->{'to-tag'} = $tt; return offer_answer('answer', $name, $req, $sdps); } sub snd { my ($sock, $dest, $packet) = @_; $sock->send($packet, 0, pack_sockaddr_in($dest, inet_aton('203.0.113.1'))) or die; } sub rtp { my ($pt, $seq, $ts, $ssrc, $payload) = @_; print("rtp in $pt $seq $ts $ssrc\n"); return pack('CCnNN a*', 0x80, $pt, $seq, $ts, $ssrc, $payload); } sub rcv { my ($sock, $port, $match, $cb, $cb_arg) = @_; my $p = ''; alarm(1); my $addr = $sock->recv($p, 65535, 0) or die; alarm(0); my ($hdr_mark, $pt, $seq, $ts, $ssrc, $payload) = unpack('CCnNN a*', $p); if ($payload) { print("rtp recv $pt $seq $ts $ssrc " . unpack('H*', $payload) . "\n"); } if ($cb) { $p = $cb->($hdr_mark, $pt, $seq, $ts, $ssrc, $payload, $p, $cb_arg); } like $p, $match, 'received packet matches'; my @matches = $p =~ $match; for my $m (@matches) { if (length($m) == 2) { ($m) = unpack('n', $m); } elsif (length($m) == 4) { ($m) = unpack('N', $m); } } return @matches; } sub srtp_rcv { my ($sock, $port, $match, $srtp_ctx) = @_; return rcv($sock, $port, $match, \&srtp_dec, $srtp_ctx); } sub srtp_dec { my ($hdr_mark, $pt, $seq, $ts, $ssrc, $payload, $pack, $srtp_ctx) = @_; if (!$srtp_ctx->{skey}) { my ($key, $salt) = NGCP::Rtpclient::SRTP::decode_inline_base64($srtp_ctx->{key}, $srtp_ctx->{cs}); @$srtp_ctx{qw(skey sauth ssalt)} = NGCP::Rtpclient::SRTP::gen_rtp_session_keys($key, $salt); } my ($dec, $out_roc, $tag, $hmac) = NGCP::Rtpclient::SRTP::decrypt_rtp(@$srtp_ctx{qw(cs skey ssalt sauth roc)}, $pack); $srtp_ctx->{roc} = $out_roc; is $tag, substr($hmac, 0, length($tag)), 'SRTP auth tag matches'; return $dec; } sub escape { return "\Q$_[0]\E"; } sub rtpm { my ($pt, $seq, $ts, $ssrc, $payload) = @_; print("rtp matcher $pt $seq $ts $ssrc " . unpack('H*', $payload) . "\n"); my $re = ''; $re .= escape(pack('C', 0x80)); $re .= escape(pack('C', $pt)); $re .= $seq >= 0 ? escape(pack('n', $seq)) : '(..)'; $re .= $ts >= 0 ? escape(pack('N', $ts)) : '(....)'; $re .= $ssrc >= 0 ? escape(pack('N', $ssrc)) : '(....)'; $re .= escape($payload); return qr/^$re$/s; } { my $r = $c->req({command => 'ping'}); ok $r->{result} eq 'pong', 'ping works, daemon operational'; } # SDP in/out tests, various ICE options new_call; offer('plain SDP, no ICE', { ICE => 'remove' }, < 'remove' }, < 'remove' }, < 'remove' }, < 'force' }, < 'force' }, < 'force' }, < 'force' }, < 'remove' }, < 'remove', DTLS => 'off' }, < 'remove' }, < 'remove', DTLS => 'off', SDES => [ 'no-F8_128_HMAC_SHA1_80' ] }, < 'remove' }, < 'remove', DTLS => 'off', SDES => [ 'no-AES_CM_128_HMAC_SHA1_32' ] }, < 'remove' }, < 'remove', DTLS => 'off', SDES => [ 'no-AES_CM_128_HMAC_SHA1_80' ] }, < 'remove' }, < 'remove', DTLS => 'off', 'transport protocol' => 'RTP/AVP' }, < 'remove' }, < 'remove', DTLS => 'off', 'transport protocol' => 'RTP/AVP', SDES => [ 'no-AES_CM_128_HMAC_SHA1_32' ] }, < 'remove' }, < 'remove', DTLS => 'off', 'transport protocol' => 'RTP/AVP', SDES => [ 'no-AES_CM_128_HMAC_SHA1_80' ] }, < 'remove' }, < 'remove', DTLS => 'off', 'transport protocol' => 'RTP/SAVP' }, < 'remove' }, < 'remove', DTLS => 'off', 'transport protocol' => 'RTP/SAVP', SDES => [ 'no-F8_128_HMAC_SHA1_80' ] }, < 'remove' }, < 'remove', DTLS => 'off', 'transport protocol' => 'RTP/SAVP', SDES => [ 'no-AES_CM_128_HMAC_SHA1_80' ] }, < 'remove' }, < 'remove', replace => [qw(origin session-connection)], flags => [qw(codec-mask-opus codec-mask-G722 codec-strip-G7221)] }, < 'remove', replace => [qw(origin session-connection)] }, < 'remove', replace => [qw(origin session-connection)], flags => [qw(codec-mask-opus codec-mask-G722 codec-strip-G7221 always-transcode)] }, < 'remove', replace => [qw(origin session-connection)] }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'], codec => { transcode => ['PCMA'] }}, < ['origin'] }, < 'remove', replace => ['origin'] }, < $ft, blob => $wav_file }); is $resp->{duration}, 100, 'media duration'; my ($ts, $seq); ($seq, $ts, $ssrc) = rcv($sock_a, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); rcv($sock_a, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); rcv($sock_a, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); rcv($sock_a, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); rcv($sock_a, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 2020)], [qw(198.51.100.3 2022)]); offer('media playback, side A', { ICE => 'remove', replace => ['origin'] }, < ['origin'] }, < $ft, blob => $wav_file }); is $resp->{duration}, 100, 'media duration'; ($seq, $ts, $ssrc) = rcv($sock_a, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); rcv($sock_a, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); rcv($sock_a, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); rcv($sock_a, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); rcv($sock_a, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 2030)], [qw(198.51.100.3 2032)]); offer('media playback, side B', { ICE => 'remove', replace => ['origin'] }, < ['origin'] }, < $tt, blob => $wav_file }); is $resp->{duration}, 100, 'media duration'; ($seq, $ts, $ssrc) = rcv($sock_b, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); rcv($sock_b, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); rcv($sock_b, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); rcv($sock_b, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); rcv($sock_b, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); $resp = rtpe_req('play media', 'restart media playback', { 'from-tag' => $tt, blob => $wav_file }); is $resp->{duration}, 100, 'media duration'; $ts += 160 * 5; my $old_ts = $ts; ($ts) = rcv($sock_b, -1, rtpm(8 | 0x80, $seq + 5, -1, $ssrc, $pcma_1)); print("ts $ts old $old_ts\n"); SKIP: { skip 'random timestamp too close to margin', 2 if $old_ts < 500 or $old_ts > 4294966795; cmp_ok($ts, '<', $old_ts + 500, 'ts within < range'); cmp_ok($ts, '>', $old_ts - 500, 'ts within > range'); } rcv($sock_b, -1, rtpm(8, $seq + 6, $ts + 160 * 1, $ssrc, $pcma_2)); rcv($sock_b, -1, rtpm(8, $seq + 7, $ts + 160 * 2, $ssrc, $pcma_3)); rcv($sock_b, -1, rtpm(8, $seq + 8, $ts + 160 * 3, $ssrc, $pcma_4)); rcv($sock_b, -1, rtpm(8, $seq + 9, $ts + 160 * 4, $ssrc, $pcma_5)); ($sock_a, $sock_b) = new_call([qw(198.51.100.9 2020)], [qw(198.51.100.9 2022)]); offer('media playback, side A, select by label', { ICE => 'remove', replace => ['origin'], label => 'foobar' }, < ['origin'], label => 'blah' }, < 'foobar', blob => $wav_file }); is $resp->{duration}, 100, 'media duration'; ($seq, $ts, $ssrc) = rcv($sock_a, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); rcv($sock_a, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); rcv($sock_a, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); rcv($sock_a, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); rcv($sock_a, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); ($sock_a, $sock_b) = new_call([qw(198.51.100.9 2030)], [qw(198.51.100.9 2032)]); offer('media playback, side B, select by label', { ICE => 'remove', replace => ['origin'], label => 'quux' }, < ['origin'], label => 'meh' }, < 'meh', blob => $wav_file }); is $resp->{duration}, 100, 'media duration'; ($seq, $ts, $ssrc) = rcv($sock_b, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1)); rcv($sock_b, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2)); rcv($sock_b, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3)); rcv($sock_b, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4)); rcv($sock_b, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 2050)], [qw(198.51.100.3 2052)]); offer('media playback, SRTP', { ICE => 'remove', replace => ['origin'], DTLS => 'off' }, < ['origin'] }, < $ft, blob => $wav_file }); is $resp->{duration}, 100, 'media duration'; my $srtp_ctx = { cs => $NGCP::Rtpclient::SRTP::crypto_suites{AES_CM_128_HMAC_SHA1_80}, key => 'DVM+BTeYX2UI1LaA9bgXrcBEDBxoItA9/39fSoRF', }; ($seq, $ts, $ssrc) = srtp_rcv($sock_a, -1, rtpm(8 | 0x80, -1, -1, -1, $pcma_1), $srtp_ctx); srtp_rcv($sock_a, -1, rtpm(8, $seq + 1, $ts + 160 * 1, $ssrc, $pcma_2), $srtp_ctx); srtp_rcv($sock_a, -1, rtpm(8, $seq + 2, $ts + 160 * 2, $ssrc, $pcma_3), $srtp_ctx); srtp_rcv($sock_a, -1, rtpm(8, $seq + 3, $ts + 160 * 3, $ssrc, $pcma_4), $srtp_ctx); srtp_rcv($sock_a, -1, rtpm(8, $seq + 4, $ts + 160 * 4, $ssrc, $pcma_5), $srtp_ctx); # ptime tests ($sock_a, $sock_b) = new_call([qw(198.51.100.1 3000)], [qw(198.51.100.3 3002)]); ($port_a) = offer('default ptime in/out', { ICE => 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'], ptime => 30 }, < 'remove', replace => ['origin'] }, <B: 5x 20 ms packets -> 3x 30 ms snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160)); Time::HiRes::usleep(20000); # 20 ms, needed to ensure that packet 1000 is received first snd($sock_a, $port_b, rtp(0, 1001, 3160, 0x1234, "\x00" x 160)); ($ssrc) = rcv($sock_b, $port_a, rtpm(0, 1000, 3000, -1, "\x00" x 240)); snd($sock_a, $port_b, rtp(0, 1002, 3320, 0x1234, "\x00" x 160)); rcv($sock_b, $port_a, rtpm(0, 1001, 3240, $ssrc, "\x00" x 240)); snd($sock_a, $port_b, rtp(0, 1003, 3480, 0x1234, "\x00" x 160)); snd($sock_a, $port_b, rtp(0, 1004, 3640, 0x1234, "\x00" x 160)); rcv($sock_b, $port_a, rtpm(0, 1002, 3480, $ssrc, "\x00" x 240)); # A->B: 60 ms packet -> 2x 30 ms # also perform TS and seq reset snd($sock_a, $port_b, rtp(0, 8000, 500000, 0x1234, "\x00" x 480)); rcv($sock_b, $port_a, rtpm(0, 1003, 3720, $ssrc, "\x00" x 240)); rcv($sock_b, $port_a, rtpm(0, 1004, 3960, $ssrc, "\x00" x 240)); # B->A: 2x 60 ms packet -> 6x 20 ms snd($sock_b, $port_a, rtp(0, 4000, 5000, 0x4567, "\x88" x 480)); ($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 5000, -1, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4001, 5160, $ssrc, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4002, 5320, $ssrc, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4001, 5480, 0x4567, "\x88" x 480)); rcv($sock_a, $port_b, rtpm(0, 4003, 5480, $ssrc, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4004, 5640, $ssrc, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4005, 5800, $ssrc, "\x88" x 160)); # B->A: 4x 10 ms packet -> 2x 20 ms # out of order packet input snd($sock_b, $port_a, rtp(0, 4003, 6040, 0x4567, "\x88" x 80)); Time::HiRes::usleep(10000); snd($sock_b, $port_a, rtp(0, 4002, 5960, 0x4567, "\x88" x 80)); rcv($sock_a, $port_b, rtpm(0, 4006, 5960, $ssrc, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4004, 6120, 0x4567, "\x88" x 80)); snd($sock_b, $port_a, rtp(0, 4005, 6200, 0x4567, "\x88" x 80)); rcv($sock_a, $port_b, rtpm(0, 4007, 6120, $ssrc, "\x88" x 160)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 3008)], [qw(198.51.100.3 3010)]); ($port_a) = offer('default ptime in, no change, ptime=30 response', { ICE => 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'] }, <B: 20 ms unchanged snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 160)); ($ssrc) = rcv($sock_b, $port_a, rtpm(0, 1000, 3000, -1, "\x00" x 160)); # A->B: 30 ms unchanged snd($sock_a, $port_b, rtp(0, 1001, 3160, 0x1234, "\x00" x 240)); rcv($sock_b, $port_a, rtpm(0, 1001, 3160, $ssrc, "\x00" x 240)); # B->A: 20 ms unchanged snd($sock_b, $port_a, rtp(0, 4000, 5000, 0x4567, "\x88" x 160)); ($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 5000, -1, "\x88" x 160)); # B->A: 30 ms unchanged snd($sock_b, $port_a, rtp(0, 4001, 5160, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4001, 5160, $ssrc, "\x88" x 240)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 3012)], [qw(198.51.100.3 3014)]); ($port_a) = offer('ptime=50 in, change to 30, default response', { ICE => 'remove', replace => ['origin'], ptime => 30 }, < 'remove', replace => ['origin'] }, <B: 2x 50 ms -> 3x 30 ms (plus 10 ms left) snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 400)); ($ssrc) = rcv($sock_b, $port_a, rtpm(0, 1000, 3000, -1, "\x00" x 240)); snd($sock_a, $port_b, rtp(0, 1001, 3400, 0x1234, "\x00" x 400)); rcv($sock_b, $port_a, rtpm(0, 1001, 3240, $ssrc, "\x00" x 240)); rcv($sock_b, $port_a, rtpm(0, 1002, 3480, $ssrc, "\x00" x 240)); # A->B: add another 20 ms for another full 30 ms snd($sock_a, $port_b, rtp(0, 1002, 3800, 0x1234, "\x00" x 160)); rcv($sock_b, $port_a, rtpm(0, 1003, 3720, $ssrc, "\x00" x 240)); # B->A: 4x 30 ms -> 2x 50 ms (plus 20 ms left) snd($sock_b, $port_a, rtp(0, 4000, 5000, 0x4567, "\x88" x 240)); Time::HiRes::usleep(20000); # 20 ms, needed to ensure that packet 1000 is received first snd($sock_b, $port_a, rtp(0, 4001, 5240, 0x4567, "\x88" x 240)); ($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 5000, -1, "\x88" x 400)); snd($sock_b, $port_a, rtp(0, 4002, 5480, 0x4567, "\x88" x 240)); snd($sock_b, $port_a, rtp(0, 4003, 5720, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4001, 5400, $ssrc, "\x88" x 400)); # B->A: add another 30 ms for another full 50 ms snd($sock_b, $port_a, rtp(0, 4004, 5960, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4002, 5800, $ssrc, "\x88" x 400)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 3016)], [qw(198.51.100.3 3018)]); ($port_a) = offer('ptime=50 in, change to 30, reverse to 50, response 30', { ICE => 'remove', replace => ['origin'], ptime => 30, 'ptime-reverse' => 50 }, < 'remove', replace => ['origin'] }, <B: 2x 50 ms -> 3x 30 ms (plus 10 ms left) snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 400)); ($ssrc) = rcv($sock_b, $port_a, rtpm(0, 1000, 3000, -1, "\x00" x 240)); snd($sock_a, $port_b, rtp(0, 1001, 3400, 0x1234, "\x00" x 400)); rcv($sock_b, $port_a, rtpm(0, 1001, 3240, $ssrc, "\x00" x 240)); rcv($sock_b, $port_a, rtpm(0, 1002, 3480, $ssrc, "\x00" x 240)); # A->B: add another 20 ms for another full 30 ms snd($sock_a, $port_b, rtp(0, 1002, 3800, 0x1234, "\x00" x 160)); rcv($sock_b, $port_a, rtpm(0, 1003, 3720, $ssrc, "\x00" x 240)); # B->A: 4x 30 ms -> 2x 50 ms (plus 20 ms left) snd($sock_b, $port_a, rtp(0, 4000, 5000, 0x4567, "\x88" x 240)); Time::HiRes::usleep(20000); # 20 ms, needed to ensure that packet 1000 is received first snd($sock_b, $port_a, rtp(0, 4001, 5240, 0x4567, "\x88" x 240)); ($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 5000, -1, "\x88" x 400)); snd($sock_b, $port_a, rtp(0, 4002, 5480, 0x4567, "\x88" x 240)); snd($sock_b, $port_a, rtp(0, 4003, 5720, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4001, 5400, $ssrc, "\x88" x 400)); # B->A: add another 30 ms for another full 50 ms snd($sock_b, $port_a, rtp(0, 4004, 5960, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4002, 5800, $ssrc, "\x88" x 400)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 3012)], [qw(198.51.100.3 3014)]); ($port_a) = offer('ptime=50 in, change to 30, response 30', { ICE => 'remove', replace => ['origin'], ptime => 30 }, < 'remove', replace => ['origin'] }, <B: 2x 50 ms -> 3x 30 ms (plus 10 ms left) snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 400)); ($ssrc) = rcv($sock_b, $port_a, rtpm(0, 1000, 3000, -1, "\x00" x 240)); snd($sock_a, $port_b, rtp(0, 1001, 3400, 0x1234, "\x00" x 400)); rcv($sock_b, $port_a, rtpm(0, 1001, 3240, $ssrc, "\x00" x 240)); rcv($sock_b, $port_a, rtpm(0, 1002, 3480, $ssrc, "\x00" x 240)); # A->B: add another 20 ms for another full 30 ms snd($sock_a, $port_b, rtp(0, 1002, 3800, 0x1234, "\x00" x 160)); rcv($sock_b, $port_a, rtpm(0, 1003, 3720, $ssrc, "\x00" x 240)); # B->A: 4x 30 ms -> 2x 50 ms (plus 20 ms left) snd($sock_b, $port_a, rtp(0, 4000, 5000, 0x4567, "\x88" x 240)); Time::HiRes::usleep(20000); # 20 ms, needed to ensure that packet 1000 is received first snd($sock_b, $port_a, rtp(0, 4001, 5240, 0x4567, "\x88" x 240)); ($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 5000, -1, "\x88" x 400)); snd($sock_b, $port_a, rtp(0, 4002, 5480, 0x4567, "\x88" x 240)); snd($sock_b, $port_a, rtp(0, 4003, 5720, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4001, 5400, $ssrc, "\x88" x 400)); # B->A: add another 30 ms for another full 50 ms snd($sock_b, $port_a, rtp(0, 4004, 5960, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4002, 5800, $ssrc, "\x88" x 400)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 3016)], [qw(198.51.100.3 3018)]); ($port_a) = offer('ptime=50 in, change to 30, reverse to 50, default response', { ICE => 'remove', replace => ['origin'], ptime => 30, 'ptime-reverse' => 50 }, < 'remove', replace => ['origin'] }, <B: 2x 50 ms -> 3x 30 ms (plus 10 ms left) snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 400)); ($ssrc) = rcv($sock_b, $port_a, rtpm(0, 1000, 3000, -1, "\x00" x 240)); snd($sock_a, $port_b, rtp(0, 1001, 3400, 0x1234, "\x00" x 400)); rcv($sock_b, $port_a, rtpm(0, 1001, 3240, $ssrc, "\x00" x 240)); rcv($sock_b, $port_a, rtpm(0, 1002, 3480, $ssrc, "\x00" x 240)); # A->B: add another 20 ms for another full 30 ms snd($sock_a, $port_b, rtp(0, 1002, 3800, 0x1234, "\x00" x 160)); rcv($sock_b, $port_a, rtpm(0, 1003, 3720, $ssrc, "\x00" x 240)); # B->A: 4x 30 ms -> 2x 50 ms (plus 20 ms left) snd($sock_b, $port_a, rtp(0, 4000, 5000, 0x4567, "\x88" x 240)); Time::HiRes::usleep(20000); # 20 ms, needed to ensure that packet 1000 is received first snd($sock_b, $port_a, rtp(0, 4001, 5240, 0x4567, "\x88" x 240)); ($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 5000, -1, "\x88" x 400)); snd($sock_b, $port_a, rtp(0, 4002, 5480, 0x4567, "\x88" x 240)); snd($sock_b, $port_a, rtp(0, 4003, 5720, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4001, 5400, $ssrc, "\x88" x 400)); # B->A: add another 30 ms for another full 50 ms snd($sock_b, $port_a, rtp(0, 4004, 5960, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4002, 5800, $ssrc, "\x88" x 400)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 3016)], [qw(198.51.100.3 3018)]); ($port_a) = offer('ptime=50 in, change to 30, reverse to 20, default response', { ICE => 'remove', replace => ['origin'], ptime => 30, 'ptime-reverse' => 20 }, < 'remove', replace => ['origin'] }, <B: 2x 50 ms -> 3x 30 ms (plus 10 ms left) snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 400)); ($ssrc) = rcv($sock_b, $port_a, rtpm(0, 1000, 3000, -1, "\x00" x 240)); snd($sock_a, $port_b, rtp(0, 1001, 3400, 0x1234, "\x00" x 400)); rcv($sock_b, $port_a, rtpm(0, 1001, 3240, $ssrc, "\x00" x 240)); rcv($sock_b, $port_a, rtpm(0, 1002, 3480, $ssrc, "\x00" x 240)); # A->B: add another 20 ms for another full 30 ms snd($sock_a, $port_b, rtp(0, 1002, 3800, 0x1234, "\x00" x 160)); rcv($sock_b, $port_a, rtpm(0, 1003, 3720, $ssrc, "\x00" x 240)); # B->A: 4x 30 ms -> 6x 20 ms snd($sock_b, $port_a, rtp(0, 4000, 5000, 0x4567, "\x88" x 240)); ($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 5000, -1, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4001, 5240, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4001, 5160, $ssrc, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4002, 5320, $ssrc, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4002, 5480, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4003, 5480, $ssrc, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4003, 5720, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4004, 5640, $ssrc, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4005, 5800, $ssrc, "\x88" x 160)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 3016)], [qw(198.51.100.3 3018)]); ($port_a) = offer('ptime=50 in, change to 30, reverse to 20, response 40', { ICE => 'remove', replace => ['origin'], ptime => 30, 'ptime-reverse' => 20 }, < 'remove', replace => ['origin'] }, <B: 2x 50 ms -> 3x 30 ms (plus 10 ms left) snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 400)); ($ssrc) = rcv($sock_b, $port_a, rtpm(0, 1000, 3000, -1, "\x00" x 240)); snd($sock_a, $port_b, rtp(0, 1001, 3400, 0x1234, "\x00" x 400)); rcv($sock_b, $port_a, rtpm(0, 1001, 3240, $ssrc, "\x00" x 240)); rcv($sock_b, $port_a, rtpm(0, 1002, 3480, $ssrc, "\x00" x 240)); # A->B: add another 20 ms for another full 30 ms snd($sock_a, $port_b, rtp(0, 1002, 3800, 0x1234, "\x00" x 160)); rcv($sock_b, $port_a, rtpm(0, 1003, 3720, $ssrc, "\x00" x 240)); # B->A: 4x 30 ms -> 6x 20 ms snd($sock_b, $port_a, rtp(0, 4000, 5000, 0x4567, "\x88" x 240)); ($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 5000, -1, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4001, 5240, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4001, 5160, $ssrc, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4002, 5320, $ssrc, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4002, 5480, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4003, 5480, $ssrc, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4003, 5720, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4004, 5640, $ssrc, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4005, 5800, $ssrc, "\x88" x 160)); ($sock_a, $sock_b) = new_call([qw(198.51.100.1 3016)], [qw(198.51.100.3 3018)]); ($port_a) = offer('ptime=30 in, no change, reverse to 20, response 40', { ICE => 'remove', replace => ['origin'], 'ptime-reverse' => 20 }, < 'remove', replace => ['origin'] }, <B: 2x 50 ms -> 3x 30 ms (plus 10 ms left) snd($sock_a, $port_b, rtp(0, 1000, 3000, 0x1234, "\x00" x 400)); ($ssrc) = rcv($sock_b, $port_a, rtpm(0, 1000, 3000, -1, "\x00" x 240)); snd($sock_a, $port_b, rtp(0, 1001, 3400, 0x1234, "\x00" x 400)); rcv($sock_b, $port_a, rtpm(0, 1001, 3240, $ssrc, "\x00" x 240)); rcv($sock_b, $port_a, rtpm(0, 1002, 3480, $ssrc, "\x00" x 240)); # A->B: add another 20 ms for another full 30 ms snd($sock_a, $port_b, rtp(0, 1002, 3800, 0x1234, "\x00" x 160)); rcv($sock_b, $port_a, rtpm(0, 1003, 3720, $ssrc, "\x00" x 240)); # B->A: 4x 30 ms -> 6x 20 ms snd($sock_b, $port_a, rtp(0, 4000, 5000, 0x4567, "\x88" x 240)); ($ssrc) = rcv($sock_a, $port_b, rtpm(0, 4000, 5000, -1, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4001, 5240, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4001, 5160, $ssrc, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4002, 5320, $ssrc, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4002, 5480, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4003, 5480, $ssrc, "\x88" x 160)); snd($sock_b, $port_a, rtp(0, 4003, 5720, 0x4567, "\x88" x 240)); rcv($sock_a, $port_b, rtpm(0, 4004, 5640, $ssrc, "\x88" x 160)); rcv($sock_a, $port_b, rtpm(0, 4005, 5800, $ssrc, "\x88" x 160)); # gh #730 ($sock_a, $sock_b) = new_call([qw(198.51.100.1 7300)], [qw(198.51.100.3 7302)]); ($port_a) = offer('gh 730', { ICE => 'remove', replace => ['origin'] }, < 'remove', replace => ['origin'] }, < 'remove', replace => ['origin', 'session-connection'], flags => [ "loop-protect", "asymmetric" ] }, < 'remove', replace => ['origin', 'session-connection'], flags => [ "loop-protect", "asymmetric" ] }, < $tt, ICE => 'remove', replace => ['origin', 'session-connection'], flags => [ "loop-protect", "asymmetric" ] }, < 'remove', replace => ['origin', 'session-connection'], flags => [ "loop-protect", "asymmetric" ] }, <