diff options
Diffstat (limited to 'library/RTP_Emulation.ttcn')
-rw-r--r-- | library/RTP_Emulation.ttcn | 204 |
1 files changed, 167 insertions, 37 deletions
diff --git a/library/RTP_Emulation.ttcn b/library/RTP_Emulation.ttcn index 900a196a..90952148 100644 --- a/library/RTP_Emulation.ttcn +++ b/library/RTP_Emulation.ttcn @@ -81,6 +81,8 @@ type component RTP_Emulation_CT { /* user-facing port for controlling the binding */ port RTPEM_CTRL_PT CTRL; + /* user-facing port for sniffing RTP frames */ + port RTPEM_DATA_PT DATA; /* configurable by user, should be fixed */ var RtpemConfig g_cfg := c_RtpemDefaultCfg; @@ -95,6 +97,7 @@ type component RTP_Emulation_CT { var PortNumber g_local_port; /* state variables, change over time */ + var boolean g_loopback := false; var boolean g_rx_enabled := false; var boolean g_tx_connected := false; /* Set to true after connect() */ var LIN2_BO_LAST g_tx_next_seq := 0; @@ -114,7 +117,8 @@ type enumerated RtpemMode { RTPEM_MODE_NONE, RTPEM_MODE_TXONLY, RTPEM_MODE_RXONLY, - RTPEM_MODE_BIDIR + RTPEM_MODE_BIDIR, + RTPEM_MODE_LOOPBACK }; type record RtpemStats { @@ -151,26 +155,29 @@ const RtpemStats c_RtpemStatsReset := { num_pkts_rx_err_payload := 0 } +type record RtpemConfigPayload { + INT7b payload_type, + octetstring fixed_payload optional +}; + type record RtpemConfig { - INT7b tx_payload_type, integer tx_samplerate_hz, integer tx_duration_ms, BIT32_BO_LAST tx_ssrc, - octetstring tx_fixed_payload optional, - octetstring rx_fixed_payload optional, + record of RtpemConfigPayload tx_payloads, + record of RtpemConfigPayload rx_payloads, boolean iuup_mode, - boolean iuup_tx_init + IuUP_Config iuup_cfg }; const RtpemConfig c_RtpemDefaultCfg := { - tx_payload_type := 0, tx_samplerate_hz := 8000, tx_duration_ms := 20, tx_ssrc := '11011110101011011011111011101111'B, - tx_fixed_payload := '01020304'O, - rx_fixed_payload := '01020304'O, + tx_payloads := {{0, '01020304'O}}, + rx_payloads := {{0, '01020304'O}}, iuup_mode := false, - iuup_tx_init := true + iuup_cfg := c_IuUP_Config_def } signature RTPEM_bind(in HostName local_host, inout PortNumber local_port); @@ -186,6 +193,10 @@ type port RTPEM_CTRL_PT procedure { RTPEM_conn_refuse_received; } with { extension "internal" }; +type port RTPEM_DATA_PT message { + inout PDU_RTP, PDU_RTCP; +} with { extension "internal" }; + function f_rtpem_bind(RTPEM_CTRL_PT pt, in HostName local_host, inout PortNumber local_port) { pt.call(RTPEM_bind:{local_host, local_port}) { [] pt.getreply(RTPEM_bind:{local_host, ?}) -> param (local_port) {}; @@ -269,38 +280,49 @@ function f_rtpem_stats_compare(RtpemStats a, RtpemStats b, integer tolerance := * check that will fit most situations and is intended to be executed by * the testcases as as needed. */ function f_rtpem_stats_err_check(RtpemStats s) { + var boolean do_stop := false; log("stats: ", s); /* Check if there was some activity at either on the RX or on the * TX side, but complete silence would indicate some problem */ if (s.num_pkts_tx < 1 and s.num_pkts_rx < 1) { setverdict(fail, "no RTP packet activity detected (packets)"); - mtc.stop; + do_stop := true; } if (s.bytes_payload_tx < 1 and s.bytes_payload_rx < 1) { setverdict(fail, "no RTP packet activity detected (bytes)"); - mtc.stop; + do_stop := true; } /* Check error counters */ if (s.num_pkts_rx_err_seq != 0) { - setverdict(fail, "RTP packet sequence number errors occurred"); - mtc.stop; + setverdict(fail, log2str(s.num_pkts_rx_err_seq, " RTP packet sequence number errors occurred")); + do_stop := true; } if (s.num_pkts_rx_err_ts != 0) { - setverdict(fail, "RTP packet timestamp errors occurred"); - mtc.stop; + setverdict(fail, log2str(s.num_pkts_rx_err_ts, " RTP packet timestamp errors occurred")); + do_stop := true; } if (s.num_pkts_rx_err_pt != 0) { - setverdict(fail, "RTP packet payload type errors occurred"); - mtc.stop; + setverdict(fail, log2str(s.num_pkts_rx_err_pt, " RTP packet payload type errors occurred")); + do_stop := true; } if (s.num_pkts_rx_err_disabled != 0) { - setverdict(fail, "RTP packets received while RX was disabled"); - mtc.stop; + setverdict(fail, log2str(s.num_pkts_rx_err_disabled, " RTP packets received while RX was disabled")); + do_stop := true; } if (s.num_pkts_rx_err_payload != 0) { - setverdict(fail, "RTP packets with mismatching payload received"); + setverdict(fail, log2str(s.num_pkts_rx_err_payload, " RTP packets with mismatching payload received")); + do_stop := true; + } + + if (do_stop) { + if (self == mtc) { + /* Properly stop all ports before disconnecting them. This avoids + * running into the dynamic testcase error due to messages arriving on + * unconnected ports. */ + all component.stop; + } mtc.stop; } } @@ -336,11 +358,15 @@ template PDU_RTP ts_RTP(BIT32_BO_LAST ssrc, INT7b pt, LIN2_BO_LAST seq, uint32_t data := payload } -private function f_tx_rtp(octetstring payload, BIT1 marker := '0'B) runs on RTP_Emulation_CT { +private function f_tx_rtp(octetstring payload, INT7b rtp_payload_type, BIT1 marker := '0'B) runs on RTP_Emulation_CT { if (g_cfg.iuup_mode) { payload := f_IuUP_Em_tx_encap(g_iuup_ent, payload); + if (lengthof(payload) == 0) { + /* Nothing to transmit, waiting for INIT-ACK */ + return; + } } - var PDU_RTP rtp := valueof(ts_RTP(g_cfg.tx_ssrc, g_cfg.tx_payload_type, g_tx_next_seq, + var PDU_RTP rtp := valueof(ts_RTP(g_cfg.tx_ssrc, rtp_payload_type, g_tx_next_seq, g_tx_next_ts, payload, marker)); RTP.send(t_RTP_Send(g_rtp_conn_id, RTP_messages_union:{rtp:=rtp})); /* increment sequence + timestamp for next transmit */ @@ -348,6 +374,58 @@ private function f_tx_rtp(octetstring payload, BIT1 marker := '0'B) runs on RTP_ g_tx_next_ts := g_tx_next_ts + (g_cfg.tx_samplerate_hz / (1000 / g_cfg.tx_duration_ms)); } +private function f_check_fixed_rx_payloads(INT7b rtp_payload_type, octetstring rtp_data) runs on RTP_Emulation_CT { + var boolean payload_type_match := false; + + /* The API user has the option to define zero or multiple sets of rx_payloads. Each rx_payload set contains + the payload type number of the expected payload and an optional fixed_payload, which resembles the actual + payload. + + In case zero rx_payloads are defined nothing is verified and no errors are counted. This is a corner case + and should be avoided since it would not yield any good test coverage. + + During verification the payload type has the highest priority. It must match before the optional fixed + payload is checked. Since the fixed_payload is optional multiple error situations may apply: + + | payload_type | fixed_payload | result + | match | match | full match => no error counter is incremented + | match | not present | counts as full match => no error counter is incremented + | match | mismatch | payload type match => only num_pkts_rx_err_payload is incremented + | | | unless something of the above is detected later. + | mismatch | (not checked) | no match => num_pkts_rx_err_payload and num_pkts_rx_err_pt + | | | are increment unless something of the above is + | | | detected later. + */ + + /* In case no rx payloads are defined any payload is accepted and no errors are counted. */ + if (lengthof(g_cfg.rx_payloads) == 0) { + return; + } + + /* Evaluate rtp_data and rtp_payload_type */ + for (var integer i := 0; i < lengthof(g_cfg.rx_payloads); i := i + 1) { + if (rtp_payload_type == g_cfg.rx_payloads[i].payload_type) { + if (not ispresent(g_cfg.rx_payloads[i].fixed_payload)) { + /* full match */ + return; + } + if (g_cfg.rx_payloads[i].fixed_payload == rtp_data) { + /* counts as full match */ + return; + } + + /* At least the payload type number did match + * (but we still may see a full match later) */ + payload_type_match := true; + } + } + + g_stats_rtp.num_pkts_rx_err_payload := g_stats_rtp.num_pkts_rx_err_payload + 1; + if (not payload_type_match) { + g_stats_rtp.num_pkts_rx_err_pt := g_stats_rtp.num_pkts_rx_err_pt + 1; + } +} + function f_main() runs on RTP_Emulation_CT { var Result res; @@ -374,7 +452,7 @@ function f_main() runs on RTP_Emulation_CT os_error_code := 111, os_error_text := ? /* "Connection refused" */}}; - g_iuup_ent := valueof(t_IuUP_Entity(g_cfg.iuup_tx_init)); + g_iuup_ent := valueof(t_IuUP_Entity(g_cfg.iuup_cfg)); while (true) { alt { @@ -438,17 +516,23 @@ function f_main() runs on RTP_Emulation_CT mtc.stop; } g_tx_connected := true; + /* Send any pending IuUP CTRL message whichwas delayed due to not being connected: */ + if (isvalue(g_iuup_ent.pending_tx_pdu)) { + f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type); + } CTRL.reply(RTPEM_connect:{g_remote_host, g_remote_port}); } [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_NONE}) { T_transmit.stop; g_rx_enabled := false; + g_loopback := false; CTRL.reply(RTPEM_mode:{RTPEM_MODE_NONE}); } [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_TXONLY}) { /* start transmit timer */ T_transmit.start; g_rx_enabled := false; + g_loopback := false; CTRL.reply(RTPEM_mode:{RTPEM_MODE_TXONLY}); } [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_RXONLY}) { @@ -460,6 +544,7 @@ function f_main() runs on RTP_Emulation_CT RTCP.clear; g_rx_enabled := true; } + g_loopback := false; CTRL.reply(RTPEM_mode:{RTPEM_MODE_RXONLY}); } [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_BIDIR}) { @@ -470,11 +555,23 @@ function f_main() runs on RTP_Emulation_CT RTCP.clear; g_rx_enabled := true; } + g_loopback := false; CTRL.reply(RTPEM_mode:{RTPEM_MODE_BIDIR}); } + [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_LOOPBACK}) { + T_transmit.stop; + if (g_rx_enabled == false) { + /* flush queues */ + RTP.clear; + RTCP.clear; + g_rx_enabled := true; + } + g_loopback := true; + CTRL.reply(RTPEM_mode:{RTPEM_MODE_LOOPBACK}); + } [] CTRL.getcall(RTPEM_configure:{?}) -> param (cfg) { g_cfg := cfg; - g_iuup_ent.cfg.active_init := g_cfg.iuup_tx_init; + g_iuup_ent.cfg := g_cfg.iuup_cfg; CTRL.reply(RTPEM_configure:{cfg}); } [] CTRL.getcall(RTPEM_stats_get:{?, ?}) -> param (is_rtcp) { @@ -492,44 +589,77 @@ function f_main() runs on RTP_Emulation_CT } - /* simply ignore any RTTP/RTP if receiver not enabled */ - [g_rx_enabled==false] RTP.receive(tr_rtp) { - g_stats_rtp.num_pkts_rx_err_disabled := g_stats_rtp.num_pkts_rx_err_disabled+1; + /* simply ignore any RTCP/RTP if receiver not enabled */ + [not g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp { + /* In IuUP we need to decode possible Control packets, such as INIT-ACK: */ + if (g_cfg.iuup_mode) { + /* In IuUP we need to decode possible Control packets, such as INIT-ACK: */ + rx_rtp.msg.rtp.data := f_IuUP_Em_rx_decaps(g_iuup_ent, rx_rtp.msg.rtp.data); + if (lengthof(rx_rtp.msg.rtp.data) != 0) { + /* Unexpected RTP payload (user data) arrived: */ + g_stats_rtp.num_pkts_rx_err_disabled := g_stats_rtp.num_pkts_rx_err_disabled+1; + } else if (g_tx_connected and isvalue(g_iuup_ent.pending_tx_pdu)) { + /* IuUP Control packet was received and requires sending back something: */ + f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type); + } + } else { + g_stats_rtp.num_pkts_rx_err_disabled := g_stats_rtp.num_pkts_rx_err_disabled+1; } - [g_rx_enabled==false] RTCP.receive(tr_rtcp) { + } + [not g_rx_enabled] RTCP.receive(tr_rtcp) { g_stats_rtcp.num_pkts_rx_err_disabled := g_stats_rtcp.num_pkts_rx_err_disabled+1; } /* process received RTCP/RTP if receiver enabled */ [g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp { /* increment counters */ - if (rx_rtp.msg.rtp.payload_type != g_cfg.tx_payload_type) { - g_stats_rtp.num_pkts_rx_err_pt := g_stats_rtp.num_pkts_rx_err_pt+1; - } g_stats_rtp.num_pkts_rx := g_stats_rtp.num_pkts_rx+1; g_stats_rtp.bytes_payload_rx := g_stats_rtp.bytes_payload_rx + lengthof(rx_rtp.msg.rtp.data); - if (ispresent(g_cfg.rx_fixed_payload) and rx_rtp.msg.rtp.data != g_cfg.rx_fixed_payload) { - g_stats_rtp.num_pkts_rx_err_payload := g_stats_rtp.num_pkts_rx_err_payload + 1; - } if (g_cfg.iuup_mode) { rx_rtp.msg.rtp.data := f_IuUP_Em_rx_decaps(g_iuup_ent, rx_rtp.msg.rtp.data); + /* IuUP Control packet was received which may require sending back something: */ + if (lengthof(rx_rtp.msg.rtp.data) == 0) { + if (g_tx_connected and isvalue(g_iuup_ent.pending_tx_pdu)) { + f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type); + } + repeat; + } + } + + if (g_loopback) { + f_tx_rtp(rx_rtp.msg.rtp.data, rx_rtp.msg.rtp.payload_type); + /* update counters */ + g_stats_rtp.num_pkts_tx := g_stats_rtp.num_pkts_tx + 1; + g_stats_rtp.bytes_payload_tx := g_stats_rtp.bytes_payload_tx + + lengthof(rx_rtp.msg.rtp.data); + } else { + /* Match the received payload against any of the predefined fixed rx payloads */ + f_check_fixed_rx_payloads(rx_rtp.msg.rtp.payload_type, rx_rtp.msg.rtp.data); + } + + if (DATA.checkstate("Connected")) { + DATA.send(rx_rtp.msg.rtp); } } [g_rx_enabled] RTCP.receive(tr_rtcp) -> value rx_rtp { //log("RX RTCP: ", rx_rtp); g_stats_rtcp.num_pkts_rx := g_stats_rtcp.num_pkts_rx+1; + if (DATA.checkstate("Connected")) { + DATA.send(rx_rtp.msg.rtcp); + } } /* transmit if timer has expired */ [g_tx_connected] T_transmit.timeout { + var octetstring payload := g_cfg.tx_payloads[g_tx_next_seq mod lengthof(g_cfg.tx_payloads)].fixed_payload; + var INT7b rtp_payload_type := g_cfg.tx_payloads[g_tx_next_seq mod lengthof(g_cfg.tx_payloads)].payload_type; /* send one RTP frame, re-start timer */ - f_tx_rtp(g_cfg.tx_fixed_payload); + f_tx_rtp(payload, rtp_payload_type); T_transmit.start; /* update counters */ g_stats_rtp.num_pkts_tx := g_stats_rtp.num_pkts_tx+1; - g_stats_rtp.bytes_payload_tx := g_stats_rtp.bytes_payload_tx + - lengthof(g_cfg.tx_fixed_payload); + g_stats_rtp.bytes_payload_tx := g_stats_rtp.bytes_payload_tx + lengthof(payload); } /* connection refused */ |