blob: 90952148601eae4edbd473d8b97bc79024c79c39 [file] [log] [blame]
Harald Welte067d66e2017-12-13 17:24:03 +01001module RTP_Emulation {
2
3/* Functionalities that we want this module to imeplement:
4 * * act as a RTP source that generates a RTP Stream
5 * * act asaa RTP sink that consumes a RTP Stream
6 *
Pau Espin Pedrol08005d72020-09-08 13:16:14 +02007 * for all of the above, we want to be able to
Harald Welte067d66e2017-12-13 17:24:03 +01008 * * specify the payload type
9 * * specify the interval / sample rate
10 * * create drop-outs in the stream
11 * * detect reordered or lost frames
12 * * validate if the size of the frames matches epectations
13 * * play back real audio (at least some tones?)
14 * * enable/disable generation/verification of RTCP
15 */
16
Harald Welte34b5a952019-05-27 11:54:11 +020017/* (C) 2017-2018 Harald Welte <laforge@gnumonks.org>
18 * (C) 2018-2019 sysmocom - s.f.m.c. GmbH
19 * All rights reserved.
20 *
21 * Released under the terms of GNU General Public License, Version 2 or
22 * (at your option) any later version.
23 *
24 * SPDX-License-Identifier: GPL-2.0-or-later
25 */
26
27
Harald Weltea4ca4462018-02-09 00:17:14 +010028/* Ideas:
29
30* each component consists of transmitter and receiver
31* transmitters and receivers can be operated as tuple?
32* high-level operation
33** set-up config at transmitter + receiver
34** transmit sequence of payloads
35** verify receiption of those payloads
36* can operate full-duplex/bi-directional as needed
37
38* transmitter
39** trigger transmission of n number of packets
40** transmit them at normal ptime interval
41** payload size configurable
42** payload contents PRBS or the like
43
44* receiver
45** count number of related packets at receiver
46** check received payload type
47** check received timestamp increments
48** check received seq_nr increments
49** (optionally) check for SSRC
50** (optionally) check for payload size
51** (optionally) check for payload contents
52
53* later
54** how to test transcoding?
55** how to test pure play-out endpoints (rx only)?
56** how to test "Rx from wrong IP/port" scenarios?
57** how to test RTCP?
58** maybe keep ports un-connected to show wrong src -lrt
59
60*/
61
62
63
64
Harald Welte067d66e2017-12-13 17:24:03 +010065import from General_Types all;
66import from Osmocom_Types all;
67import from IPL4asp_Types all;
68import from RTP_Types all;
69import from RTP_CodecPort all;
70import from RTP_CodecPort_CtrlFunct all;
71
Harald Welte80981642017-12-24 23:59:47 +010072import from IuUP_Types all;
73import from IuUP_Emulation all;
74
Harald Welte067d66e2017-12-13 17:24:03 +010075type component RTP_Emulation_CT {
76 /* down-facing ports for RTP and RTCP codec ports on top of IPL4asp */
77 port RTP_CODEC_PT RTP;
78 var integer g_rtp_conn_id := -1;
79 port RTP_CODEC_PT RTCP;
80 var integer g_rtcp_conn_id := -1;
81
82 /* user-facing port for controlling the binding */
83 port RTPEM_CTRL_PT CTRL;
Vadim Yanitskiyed5129f2021-07-04 03:42:31 +020084 /* user-facing port for sniffing RTP frames */
85 port RTPEM_DATA_PT DATA;
Harald Welte067d66e2017-12-13 17:24:03 +010086
87 /* configurable by user, should be fixed */
Harald Welte80981642017-12-24 23:59:47 +010088 var RtpemConfig g_cfg := c_RtpemDefaultCfg;
Harald Welte067d66e2017-12-13 17:24:03 +010089
Harald Weltecb8b4272018-03-28 19:57:58 +020090 /* statistics */
91 var RtpemStats g_stats_rtp := c_RtpemStatsReset;
92 var RtpemStats g_stats_rtcp := c_RtpemStatsReset;
93
Harald Welte067d66e2017-12-13 17:24:03 +010094 var HostName g_remote_host;
95 var PortNumber g_remote_port;
96 var HostName g_local_host;
97 var PortNumber g_local_port;
98
99 /* state variables, change over time */
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700100 var boolean g_loopback := false;
Harald Welte067d66e2017-12-13 17:24:03 +0100101 var boolean g_rx_enabled := false;
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200102 var boolean g_tx_connected := false; /* Set to true after connect() */
Harald Welte067d66e2017-12-13 17:24:03 +0100103 var LIN2_BO_LAST g_tx_next_seq := 0;
104 var uint32_t g_tx_next_ts := 0;
105
106 var INT7b g_rx_payload_type := 0;
107 var LIN2_BO_LAST g_rx_last_seq;
108 var uint32_t g_rx_last_ts;
Harald Welte80981642017-12-24 23:59:47 +0100109
110 var IuUP_Entity g_iuup_ent; // := valueof(t_IuUP_Entity(1));
Oliver Smith216019f2019-06-26 12:21:38 +0200111
112 var boolean g_conn_refuse_expect := false;
113 var boolean g_conn_refuse_received := false;
Harald Welte067d66e2017-12-13 17:24:03 +0100114}
115
116type enumerated RtpemMode {
117 RTPEM_MODE_NONE,
118 RTPEM_MODE_TXONLY,
119 RTPEM_MODE_RXONLY,
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700120 RTPEM_MODE_BIDIR,
121 RTPEM_MODE_LOOPBACK
Harald Welte067d66e2017-12-13 17:24:03 +0100122};
123
Harald Weltecb8b4272018-03-28 19:57:58 +0200124type record RtpemStats {
125 /* number of packets transmitted */
126 integer num_pkts_tx,
127 /* number of RTP payload bytes transmitted */
128 integer bytes_payload_tx,
129
130 /* number of packets received */
131 integer num_pkts_rx,
132 /* number of RTP payload bytes received */
133 integer bytes_payload_rx,
134 /* number of packets received out-of-sequence */
135 integer num_pkts_rx_err_seq,
136 /* number of packets received wrong timestamp */
137 integer num_pkts_rx_err_ts,
Philipp Maierc290d722018-07-24 18:51:36 +0200138 /* number of packets received wrong payload type */
139 integer num_pkts_rx_err_pt,
Harald Weltecb8b4272018-03-28 19:57:58 +0200140 /* number of packets received during Rx disable */
Philipp Maiera071ee42019-02-21 16:17:32 +0100141 integer num_pkts_rx_err_disabled,
142 /* number of packets received with mismatching payload */
143 integer num_pkts_rx_err_payload
Harald Weltecb8b4272018-03-28 19:57:58 +0200144}
145
146const RtpemStats c_RtpemStatsReset := {
147 num_pkts_tx := 0,
148 bytes_payload_tx := 0,
149 num_pkts_rx := 0,
150 bytes_payload_rx := 0,
151 num_pkts_rx_err_seq := 0,
152 num_pkts_rx_err_ts := 0,
Philipp Maierc290d722018-07-24 18:51:36 +0200153 num_pkts_rx_err_pt := 0,
Philipp Maiera071ee42019-02-21 16:17:32 +0100154 num_pkts_rx_err_disabled := 0,
155 num_pkts_rx_err_payload := 0
Harald Weltecb8b4272018-03-28 19:57:58 +0200156}
157
Philipp Maierbbe454d2023-03-28 15:31:57 +0200158type record RtpemConfigPayload {
159 INT7b payload_type,
160 octetstring fixed_payload optional
161};
162
Harald Welte3f6f48f2017-12-24 21:48:33 +0100163type record RtpemConfig {
Harald Welte3f6f48f2017-12-24 21:48:33 +0100164 integer tx_samplerate_hz,
165 integer tx_duration_ms,
166 BIT32_BO_LAST tx_ssrc,
Philipp Maierbbe454d2023-03-28 15:31:57 +0200167 record of RtpemConfigPayload tx_payloads,
168 record of RtpemConfigPayload rx_payloads,
Harald Welte80981642017-12-24 23:59:47 +0100169 boolean iuup_mode,
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200170 IuUP_Config iuup_cfg
Harald Welte3f6f48f2017-12-24 21:48:33 +0100171};
172
Harald Welte80981642017-12-24 23:59:47 +0100173const RtpemConfig c_RtpemDefaultCfg := {
Harald Welte3f6f48f2017-12-24 21:48:33 +0100174 tx_samplerate_hz := 8000,
175 tx_duration_ms := 20,
176 tx_ssrc := '11011110101011011011111011101111'B,
Philipp Maierbbe454d2023-03-28 15:31:57 +0200177 tx_payloads := {{0, '01020304'O}},
178 rx_payloads := {{0, '01020304'O}},
Harald Welte80981642017-12-24 23:59:47 +0100179 iuup_mode := false,
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200180 iuup_cfg := c_IuUP_Config_def
Harald Welte3f6f48f2017-12-24 21:48:33 +0100181}
182
Harald Welte067d66e2017-12-13 17:24:03 +0100183signature RTPEM_bind(in HostName local_host, inout PortNumber local_port);
184signature RTPEM_connect(in HostName remote_host, in PortNumber remote_port);
185signature RTPEM_mode(in RtpemMode mode);
Harald Welte3f6f48f2017-12-24 21:48:33 +0100186signature RTPEM_configure(in RtpemConfig cfg);
Harald Weltecb8b4272018-03-28 19:57:58 +0200187signature RTPEM_stats_get(out RtpemStats stats, in boolean rtcp);
Oliver Smith216019f2019-06-26 12:21:38 +0200188signature RTPEM_conn_refuse_expect(in boolean expect);
189signature RTPEM_conn_refuse_received(out boolean received);
Harald Welte067d66e2017-12-13 17:24:03 +0100190
191type port RTPEM_CTRL_PT procedure {
Oliver Smith216019f2019-06-26 12:21:38 +0200192 inout RTPEM_bind, RTPEM_connect, RTPEM_mode, RTPEM_configure, RTPEM_stats_get, RTPEM_conn_refuse_expect,
193 RTPEM_conn_refuse_received;
Harald Welte067d66e2017-12-13 17:24:03 +0100194} with { extension "internal" };
195
Vadim Yanitskiyed5129f2021-07-04 03:42:31 +0200196type port RTPEM_DATA_PT message {
197 inout PDU_RTP, PDU_RTCP;
198} with { extension "internal" };
199
Harald Welte1af40332018-03-29 08:50:33 +0200200function f_rtpem_bind(RTPEM_CTRL_PT pt, in HostName local_host, inout PortNumber local_port) {
201 pt.call(RTPEM_bind:{local_host, local_port}) {
202 [] pt.getreply(RTPEM_bind:{local_host, ?}) -> param (local_port) {};
203 }
204}
205function f_rtpem_connect(RTPEM_CTRL_PT pt, in HostName remote_host, in PortNumber remote_port) {
206 pt.call(RTPEM_connect:{remote_host, remote_port}) {
207 [] pt.getreply(RTPEM_connect:{remote_host, remote_port}) {};
208 }
209}
210function f_rtpem_mode(RTPEM_CTRL_PT pt, in RtpemMode mode) {
211 pt.call(RTPEM_mode:{mode}) {
212 [] pt.getreply(RTPEM_mode:{mode}) {};
213 }
214}
215function f_rtpem_configure(RTPEM_CTRL_PT pt, in RtpemConfig cfg) {
216 pt.call(RTPEM_configure:{cfg}) {
217 [] pt.getreply(RTPEM_configure:{cfg}) {};
218 }
219}
220function f_rtpem_stats_get(RTPEM_CTRL_PT pt, boolean rtcp := false) return RtpemStats {
221 var RtpemStats stats;
222 pt.call(RTPEM_stats_get:{-, rtcp}) {
223 [] pt.getreply(RTPEM_stats_get:{?, rtcp}) -> param(stats) {};
224 }
225 return stats;
226}
227
Philipp Maier2321ef92018-06-27 17:52:04 +0200228function f_rtpem_stats_compare_value(integer a, integer b, integer tolerance := 0) return boolean {
229 var integer temp;
Harald Welte5c49ba42018-03-29 08:51:20 +0200230
Philipp Maier2321ef92018-06-27 17:52:04 +0200231 temp := (a - b)
232 if (temp < 0) {
233 temp := -temp;
234 }
235
236 if (temp > tolerance) {
Harald Welte5c49ba42018-03-29 08:51:20 +0200237 return false;
238 }
Philipp Maier2321ef92018-06-27 17:52:04 +0200239
Harald Welte5c49ba42018-03-29 08:51:20 +0200240 return true;
241}
Harald Welte1af40332018-03-29 08:50:33 +0200242
Philipp Maier2321ef92018-06-27 17:52:04 +0200243/* Cross-compare two rtpem-statistics. The transmission statistics on the a side
244 * must match the reception statistics on the other side and vice versa. The
245 * user may also supply a tolerance value (number of packets) when deviations
246 * are acceptable */
247function f_rtpem_stats_compare(RtpemStats a, RtpemStats b, integer tolerance := 0) return boolean {
248 var integer plen;
249
250 log("stats A: ", a);
251 log("stats B: ", b);
252 log("tolerance: ", tolerance, " packets");
253
254 if (f_rtpem_stats_compare_value(a.num_pkts_tx, b.num_pkts_rx, tolerance) == false) {
255 return false;
256 }
257
258 if (f_rtpem_stats_compare_value(a.num_pkts_rx, b.num_pkts_tx, tolerance) == false) {
259 return false;
260 }
261
262 if(a.num_pkts_tx > 0) {
263 plen := a.bytes_payload_tx / a.num_pkts_tx;
264 } else {
265 plen := 0;
266 }
267
268 if (f_rtpem_stats_compare_value(a.bytes_payload_tx, b.bytes_payload_rx, tolerance * plen) == false) {
269 return false;
270 }
271
272 if (f_rtpem_stats_compare_value(a.bytes_payload_rx, b.bytes_payload_tx, tolerance * plen) == false) {
273 return false;
274 }
275
276 return true;
277}
Harald Welte1af40332018-03-29 08:50:33 +0200278
Philipp Maier36291392018-07-25 09:40:44 +0200279/* Check the statistics for general signs of errors. This is a basic general
280 * check that will fit most situations and is intended to be executed by
281 * the testcases as as needed. */
282function f_rtpem_stats_err_check(RtpemStats s) {
Pau Espin Pedrol64f20862023-09-27 18:14:27 +0200283 var boolean do_stop := false;
Philipp Maier36291392018-07-25 09:40:44 +0200284 log("stats: ", s);
285
286 /* Check if there was some activity at either on the RX or on the
287 * TX side, but complete silence would indicate some problem */
288 if (s.num_pkts_tx < 1 and s.num_pkts_rx < 1) {
289 setverdict(fail, "no RTP packet activity detected (packets)");
Pau Espin Pedrol64f20862023-09-27 18:14:27 +0200290 do_stop := true;
Philipp Maier36291392018-07-25 09:40:44 +0200291 }
292 if (s.bytes_payload_tx < 1 and s.bytes_payload_rx < 1) {
293 setverdict(fail, "no RTP packet activity detected (bytes)");
Pau Espin Pedrol64f20862023-09-27 18:14:27 +0200294 do_stop := true;
Philipp Maier36291392018-07-25 09:40:44 +0200295 }
296
297 /* Check error counters */
298 if (s.num_pkts_rx_err_seq != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200299 setverdict(fail, log2str(s.num_pkts_rx_err_seq, " RTP packet sequence number errors occurred"));
Pau Espin Pedrol64f20862023-09-27 18:14:27 +0200300 do_stop := true;
Philipp Maier36291392018-07-25 09:40:44 +0200301 }
302 if (s.num_pkts_rx_err_ts != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200303 setverdict(fail, log2str(s.num_pkts_rx_err_ts, " RTP packet timestamp errors occurred"));
Pau Espin Pedrol64f20862023-09-27 18:14:27 +0200304 do_stop := true;
Philipp Maier36291392018-07-25 09:40:44 +0200305 }
306 if (s.num_pkts_rx_err_pt != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200307 setverdict(fail, log2str(s.num_pkts_rx_err_pt, " RTP packet payload type errors occurred"));
Pau Espin Pedrol64f20862023-09-27 18:14:27 +0200308 do_stop := true;
Philipp Maier36291392018-07-25 09:40:44 +0200309 }
310 if (s.num_pkts_rx_err_disabled != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200311 setverdict(fail, log2str(s.num_pkts_rx_err_disabled, " RTP packets received while RX was disabled"));
Pau Espin Pedrol64f20862023-09-27 18:14:27 +0200312 do_stop := true;
Philipp Maier36291392018-07-25 09:40:44 +0200313 }
Philipp Maiera071ee42019-02-21 16:17:32 +0100314 if (s.num_pkts_rx_err_payload != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200315 setverdict(fail, log2str(s.num_pkts_rx_err_payload, " RTP packets with mismatching payload received"));
Pau Espin Pedrol64f20862023-09-27 18:14:27 +0200316 do_stop := true;
317 }
318
319 if (do_stop) {
320 if (self == mtc) {
321 /* Properly stop all ports before disconnecting them. This avoids
322 * running into the dynamic testcase error due to messages arriving on
323 * unconnected ports. */
324 all component.stop;
325 }
Philipp Maiera071ee42019-02-21 16:17:32 +0100326 mtc.stop;
327 }
Philipp Maier36291392018-07-25 09:40:44 +0200328}
329
Oliver Smith216019f2019-06-26 12:21:38 +0200330function f_rtpem_conn_refuse_expect(RTPEM_CTRL_PT pt) {
331 pt.call(RTPEM_conn_refuse_expect:{true}) {
332 [] pt.getreply(RTPEM_conn_refuse_expect:{true}) {};
333 }
334}
335
336function f_rtpem_conn_refuse_verify(RTPEM_CTRL_PT pt) {
337 pt.call(RTPEM_conn_refuse_received:{?}) {
338 [] pt.getreply(RTPEM_conn_refuse_received:{true}) {};
339 [] pt.getreply(RTPEM_conn_refuse_received:{false}) {
340 setverdict(fail, "Expected to receive connection refused");
341 };
342 }
343}
344
Harald Welte067d66e2017-12-13 17:24:03 +0100345template PDU_RTP ts_RTP(BIT32_BO_LAST ssrc, INT7b pt, LIN2_BO_LAST seq, uint32_t ts,
346 octetstring payload, BIT1 marker := '0'B) := {
347 version := 2,
348 padding_ind := '0'B,
349 extension_ind := '0'B,
350 CSRC_count := 0,
351 marker_bit := marker,
352 payload_type := pt,
353 sequence_number := seq,
Harald Welte8a4d3952017-12-24 21:30:23 +0100354 time_stamp := int2bit(ts, 32),
Harald Welte067d66e2017-12-13 17:24:03 +0100355 SSRC_id := ssrc,
356 CSRCs := omit,
357 ext_header := omit,
358 data := payload
359}
360
Philipp Maierbbe454d2023-03-28 15:31:57 +0200361private function f_tx_rtp(octetstring payload, INT7b rtp_payload_type, BIT1 marker := '0'B) runs on RTP_Emulation_CT {
Harald Welte80981642017-12-24 23:59:47 +0100362 if (g_cfg.iuup_mode) {
363 payload := f_IuUP_Em_tx_encap(g_iuup_ent, payload);
Pau Espin Pedrolb204a4e2021-12-24 14:18:39 +0100364 if (lengthof(payload) == 0) {
365 /* Nothing to transmit, waiting for INIT-ACK */
366 return;
367 }
Harald Welte80981642017-12-24 23:59:47 +0100368 }
Philipp Maierbbe454d2023-03-28 15:31:57 +0200369 var PDU_RTP rtp := valueof(ts_RTP(g_cfg.tx_ssrc, rtp_payload_type, g_tx_next_seq,
Harald Welte067d66e2017-12-13 17:24:03 +0100370 g_tx_next_ts, payload, marker));
371 RTP.send(t_RTP_Send(g_rtp_conn_id, RTP_messages_union:{rtp:=rtp}));
372 /* increment sequence + timestamp for next transmit */
373 g_tx_next_seq := g_tx_next_seq + 1;
Harald Welte3f6f48f2017-12-24 21:48:33 +0100374 g_tx_next_ts := g_tx_next_ts + (g_cfg.tx_samplerate_hz / (1000 / g_cfg.tx_duration_ms));
Harald Welte067d66e2017-12-13 17:24:03 +0100375}
376
Philipp Maierbbe454d2023-03-28 15:31:57 +0200377private function f_check_fixed_rx_payloads(INT7b rtp_payload_type, octetstring rtp_data) runs on RTP_Emulation_CT {
378 var boolean payload_type_match := false;
379
380 /* The API user has the option to define zero or multiple sets of rx_payloads. Each rx_payload set contains
381 the payload type number of the expected payload and an optional fixed_payload, which resembles the actual
382 payload.
383
384 In case zero rx_payloads are defined nothing is verified and no errors are counted. This is a corner case
385 and should be avoided since it would not yield any good test coverage.
386
387 During verification the payload type has the highest priority. It must match before the optional fixed
388 payload is checked. Since the fixed_payload is optional multiple error situations may apply:
389
390 | payload_type | fixed_payload | result
391 | match | match | full match => no error counter is incremented
392 | match | not present | counts as full match => no error counter is incremented
393 | match | mismatch | payload type match => only num_pkts_rx_err_payload is incremented
394 | | | unless something of the above is detected later.
395 | mismatch | (not checked) | no match => num_pkts_rx_err_payload and num_pkts_rx_err_pt
396 | | | are increment unless something of the above is
397 | | | detected later.
398 */
399
400 /* In case no rx payloads are defined any payload is accepted and no errors are counted. */
401 if (lengthof(g_cfg.rx_payloads) == 0) {
402 return;
403 }
404
405 /* Evaluate rtp_data and rtp_payload_type */
406 for (var integer i := 0; i < lengthof(g_cfg.rx_payloads); i := i + 1) {
407 if (rtp_payload_type == g_cfg.rx_payloads[i].payload_type) {
408 if (not ispresent(g_cfg.rx_payloads[i].fixed_payload)) {
409 /* full match */
410 return;
411 }
412 if (g_cfg.rx_payloads[i].fixed_payload == rtp_data) {
413 /* counts as full match */
414 return;
415 }
416
417 /* At least the payload type number did match
418 * (but we still may see a full match later) */
419 payload_type_match := true;
420 }
421 }
422
423 g_stats_rtp.num_pkts_rx_err_payload := g_stats_rtp.num_pkts_rx_err_payload + 1;
424 if (not payload_type_match) {
425 g_stats_rtp.num_pkts_rx_err_pt := g_stats_rtp.num_pkts_rx_err_pt + 1;
426 }
427}
428
Harald Welte067d66e2017-12-13 17:24:03 +0100429function f_main() runs on RTP_Emulation_CT
430{
431 var Result res;
Harald Weltecb8b4272018-03-28 19:57:58 +0200432 var boolean is_rtcp
Harald Welte067d66e2017-12-13 17:24:03 +0100433
Harald Welte3f6f48f2017-12-24 21:48:33 +0100434 timer T_transmit := int2float(g_cfg.tx_duration_ms)/1000.0;
Harald Welte067d66e2017-12-13 17:24:03 +0100435 var RTP_RecvFrom rx_rtp;
Harald Welte3f6f48f2017-12-24 21:48:33 +0100436 var RtpemConfig cfg;
Harald Welte067d66e2017-12-13 17:24:03 +0100437 var template RTP_RecvFrom tr := {
438 connId := ?,
439 remName := ?,
440 remPort := ?,
441 locName := ?,
442 locPort := ?,
443 msg := ?
444 };
445 var template RTP_RecvFrom tr_rtp := tr;
446 var template RTP_RecvFrom tr_rtcp := tr;
Harald Welte067d66e2017-12-13 17:24:03 +0100447 tr_rtp.msg := { rtp := ? };
Harald Welte067d66e2017-12-13 17:24:03 +0100448 tr_rtcp.msg := { rtcp := ? };
449
Oliver Smith216019f2019-06-26 12:21:38 +0200450 var template ASP_Event tr_conn_refuse := {result := { errorCode := ERROR_SOCKET,
451 connId := ?,
452 os_error_code := 111,
453 os_error_text := ? /* "Connection refused" */}};
454
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200455 g_iuup_ent := valueof(t_IuUP_Entity(g_cfg.iuup_cfg));
Harald Welte80981642017-12-24 23:59:47 +0100456
Harald Welte067d66e2017-12-13 17:24:03 +0100457 while (true) {
458 alt {
459 /* control procedures (calls) from the user */
460 [] CTRL.getcall(RTPEM_bind:{?,?}) -> param(g_local_host, g_local_port) {
461 if (g_local_port rem 2 == 1) {
462 //CTRL.raise(RTPEM_bind, "Local Port is not an even port number!");
463 log("Local Port is not an even port number!");
464 continue;
465 }
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200466
467 g_tx_connected := false; /* will set it back to true upon next connect() call */
Pau Espin Pedrol08005d72020-09-08 13:16:14 +0200468
469 if (g_rtp_conn_id != -1) {
470 res := RTP_CodecPort_CtrlFunct.f_IPL4_close(RTP, g_rtp_conn_id, {udp := {}});
471 g_rtp_conn_id := -1;
472 }
Harald Welte067d66e2017-12-13 17:24:03 +0100473 res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTP, g_local_host,
474 g_local_port, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200475 if (not ispresent(res.connId)) {
476 setverdict(fail, "Could not listen on RTP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200477 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200478 }
Harald Welte067d66e2017-12-13 17:24:03 +0100479 g_rtp_conn_id := res.connId;
Harald Welte9774e7a2018-03-29 08:49:38 +0200480 tr_rtp.connId := g_rtp_conn_id;
Pau Espin Pedrol08005d72020-09-08 13:16:14 +0200481
482 if (g_rtcp_conn_id != -1) {
483 res := RTP_CodecPort_CtrlFunct.f_IPL4_close(RTCP, g_rtcp_conn_id, {udp := {}});
484 g_rtcp_conn_id := -1;
485 }
Harald Welte98eb1bf2018-04-02 18:18:44 +0200486 res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTCP, g_local_host,
Harald Welte067d66e2017-12-13 17:24:03 +0100487 g_local_port+1, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200488 if (not ispresent(res.connId)) {
489 setverdict(fail, "Could not listen on RTCP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200490 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200491 }
Harald Welte067d66e2017-12-13 17:24:03 +0100492 g_rtcp_conn_id := res.connId;
Harald Welte9774e7a2018-03-29 08:49:38 +0200493 tr_rtcp.connId := g_rtcp_conn_id;
Harald Welte067d66e2017-12-13 17:24:03 +0100494 CTRL.reply(RTPEM_bind:{g_local_host, g_local_port});
495 }
496 [] CTRL.getcall(RTPEM_connect:{?,?}) -> param (g_remote_host, g_remote_port) {
497 if (g_remote_port rem 2 == 1) {
498 //CTRL.raise(RTPEM_connect, "Remote Port is not an even number!");
499 log("Remote Port is not an even number!");
500 continue;
501 }
502 res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTP, g_remote_host,
503 g_remote_port,
504 g_local_host, g_local_port,
505 g_rtp_conn_id, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200506 if (not ispresent(res.connId)) {
507 setverdict(fail, "Could not connect to RTP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200508 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200509 }
Harald Welte067d66e2017-12-13 17:24:03 +0100510 res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTCP, g_remote_host,
511 g_remote_port+1,
512 g_local_host, g_local_port+1,
513 g_rtcp_conn_id, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200514 if (not ispresent(res.connId)) {
515 setverdict(fail, "Could not connect to RTCP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200516 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200517 }
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200518 g_tx_connected := true;
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100519 /* Send any pending IuUP CTRL message whichwas delayed due to not being connected: */
520 if (isvalue(g_iuup_ent.pending_tx_pdu)) {
Philipp Maierbbe454d2023-03-28 15:31:57 +0200521 f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type);
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100522 }
Harald Welte067d66e2017-12-13 17:24:03 +0100523 CTRL.reply(RTPEM_connect:{g_remote_host, g_remote_port});
524 }
525 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_NONE}) {
526 T_transmit.stop;
527 g_rx_enabled := false;
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700528 g_loopback := false;
Harald Welte80981642017-12-24 23:59:47 +0100529 CTRL.reply(RTPEM_mode:{RTPEM_MODE_NONE});
Harald Welte067d66e2017-12-13 17:24:03 +0100530 }
531 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_TXONLY}) {
532 /* start transmit timer */
533 T_transmit.start;
534 g_rx_enabled := false;
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700535 g_loopback := false;
Harald Welte80981642017-12-24 23:59:47 +0100536 CTRL.reply(RTPEM_mode:{RTPEM_MODE_TXONLY});
Harald Welte067d66e2017-12-13 17:24:03 +0100537 }
538 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_RXONLY}) {
539
540 T_transmit.stop;
541 if (g_rx_enabled == false) {
542 /* flush queues */
543 RTP.clear;
544 RTCP.clear;
545 g_rx_enabled := true;
546 }
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700547 g_loopback := false;
Harald Welte80981642017-12-24 23:59:47 +0100548 CTRL.reply(RTPEM_mode:{RTPEM_MODE_RXONLY});
Harald Welte067d66e2017-12-13 17:24:03 +0100549 }
550 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_BIDIR}) {
551 T_transmit.start;
552 if (g_rx_enabled == false) {
553 /* flush queues */
554 RTP.clear;
555 RTCP.clear;
556 g_rx_enabled := true;
557 }
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700558 g_loopback := false;
Harald Welte80981642017-12-24 23:59:47 +0100559 CTRL.reply(RTPEM_mode:{RTPEM_MODE_BIDIR});
Harald Welte067d66e2017-12-13 17:24:03 +0100560 }
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700561 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_LOOPBACK}) {
562 T_transmit.stop;
563 if (g_rx_enabled == false) {
564 /* flush queues */
565 RTP.clear;
566 RTCP.clear;
567 g_rx_enabled := true;
568 }
569 g_loopback := true;
570 CTRL.reply(RTPEM_mode:{RTPEM_MODE_LOOPBACK});
571 }
Harald Welte3f6f48f2017-12-24 21:48:33 +0100572 [] CTRL.getcall(RTPEM_configure:{?}) -> param (cfg) {
573 g_cfg := cfg;
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200574 g_iuup_ent.cfg := g_cfg.iuup_cfg;
Harald Welte80981642017-12-24 23:59:47 +0100575 CTRL.reply(RTPEM_configure:{cfg});
Harald Welte3f6f48f2017-12-24 21:48:33 +0100576 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200577 [] CTRL.getcall(RTPEM_stats_get:{?, ?}) -> param (is_rtcp) {
578 if (is_rtcp) {
579 CTRL.reply(RTPEM_stats_get:{g_stats_rtcp, is_rtcp});
580 } else {
581 CTRL.reply(RTPEM_stats_get:{g_stats_rtp, is_rtcp});
582 }
583 }
Oliver Smith216019f2019-06-26 12:21:38 +0200584 [] CTRL.getcall(RTPEM_conn_refuse_expect:{?}) -> param(g_conn_refuse_expect) {
585 CTRL.reply(RTPEM_conn_refuse_expect:{g_conn_refuse_expect});
586 }
587 [] CTRL.getcall(RTPEM_conn_refuse_received:{?}) {
588 CTRL.reply(RTPEM_conn_refuse_received:{g_conn_refuse_received});
589 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200590
591
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100592 /* simply ignore any RTCP/RTP if receiver not enabled */
593 [not g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp {
594 /* In IuUP we need to decode possible Control packets, such as INIT-ACK: */
595 if (g_cfg.iuup_mode) {
596 /* In IuUP we need to decode possible Control packets, such as INIT-ACK: */
597 rx_rtp.msg.rtp.data := f_IuUP_Em_rx_decaps(g_iuup_ent, rx_rtp.msg.rtp.data);
598 if (lengthof(rx_rtp.msg.rtp.data) != 0) {
599 /* Unexpected RTP payload (user data) arrived: */
600 g_stats_rtp.num_pkts_rx_err_disabled := g_stats_rtp.num_pkts_rx_err_disabled+1;
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100601 } else if (g_tx_connected and isvalue(g_iuup_ent.pending_tx_pdu)) {
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100602 /* IuUP Control packet was received and requires sending back something: */
Philipp Maierbbe454d2023-03-28 15:31:57 +0200603 f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type);
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100604 }
605 } else {
606 g_stats_rtp.num_pkts_rx_err_disabled := g_stats_rtp.num_pkts_rx_err_disabled+1;
607 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200608 }
Vadim Yanitskiy8c978ec2021-07-04 03:13:36 +0200609 [not g_rx_enabled] RTCP.receive(tr_rtcp) {
Harald Weltecb8b4272018-03-28 19:57:58 +0200610 g_stats_rtcp.num_pkts_rx_err_disabled := g_stats_rtcp.num_pkts_rx_err_disabled+1;
611 }
Harald Welte067d66e2017-12-13 17:24:03 +0100612
613 /* process received RTCP/RTP if receiver enabled */
614 [g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp {
Harald Weltecb8b4272018-03-28 19:57:58 +0200615 /* increment counters */
616 g_stats_rtp.num_pkts_rx := g_stats_rtp.num_pkts_rx+1;
617 g_stats_rtp.bytes_payload_rx := g_stats_rtp.bytes_payload_rx +
618 lengthof(rx_rtp.msg.rtp.data);
Harald Welte80981642017-12-24 23:59:47 +0100619 if (g_cfg.iuup_mode) {
620 rx_rtp.msg.rtp.data := f_IuUP_Em_rx_decaps(g_iuup_ent, rx_rtp.msg.rtp.data);
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100621 /* IuUP Control packet was received which may require sending back something: */
622 if (lengthof(rx_rtp.msg.rtp.data) == 0) {
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100623 if (g_tx_connected and isvalue(g_iuup_ent.pending_tx_pdu)) {
Philipp Maierbbe454d2023-03-28 15:31:57 +0200624 f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type);
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100625 }
626 repeat;
627 }
628 }
Philipp Maierbbe454d2023-03-28 15:31:57 +0200629
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700630 if (g_loopback) {
631 f_tx_rtp(rx_rtp.msg.rtp.data, rx_rtp.msg.rtp.payload_type);
632 /* update counters */
633 g_stats_rtp.num_pkts_tx := g_stats_rtp.num_pkts_tx + 1;
634 g_stats_rtp.bytes_payload_tx := g_stats_rtp.bytes_payload_tx
635 + lengthof(rx_rtp.msg.rtp.data);
636 } else {
637 /* Match the received payload against any of the predefined fixed rx payloads */
638 f_check_fixed_rx_payloads(rx_rtp.msg.rtp.payload_type, rx_rtp.msg.rtp.data);
639 }
Philipp Maierbbe454d2023-03-28 15:31:57 +0200640
Vadim Yanitskiyed5129f2021-07-04 03:42:31 +0200641 if (DATA.checkstate("Connected")) {
642 DATA.send(rx_rtp.msg.rtp);
643 }
Harald Welte067d66e2017-12-13 17:24:03 +0100644 }
645 [g_rx_enabled] RTCP.receive(tr_rtcp) -> value rx_rtp {
Harald Welte8d3ea0e2018-03-29 08:50:18 +0200646 //log("RX RTCP: ", rx_rtp);
Harald Weltecb8b4272018-03-28 19:57:58 +0200647 g_stats_rtcp.num_pkts_rx := g_stats_rtcp.num_pkts_rx+1;
Vadim Yanitskiyed5129f2021-07-04 03:42:31 +0200648 if (DATA.checkstate("Connected")) {
649 DATA.send(rx_rtp.msg.rtcp);
650 }
Harald Welte067d66e2017-12-13 17:24:03 +0100651 }
652
653 /* transmit if timer has expired */
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200654 [g_tx_connected] T_transmit.timeout {
Philipp Maierbbe454d2023-03-28 15:31:57 +0200655 var octetstring payload := g_cfg.tx_payloads[g_tx_next_seq mod lengthof(g_cfg.tx_payloads)].fixed_payload;
656 var INT7b rtp_payload_type := g_cfg.tx_payloads[g_tx_next_seq mod lengthof(g_cfg.tx_payloads)].payload_type;
Harald Welte067d66e2017-12-13 17:24:03 +0100657 /* send one RTP frame, re-start timer */
Philipp Maierbbe454d2023-03-28 15:31:57 +0200658 f_tx_rtp(payload, rtp_payload_type);
Harald Welte067d66e2017-12-13 17:24:03 +0100659 T_transmit.start;
Harald Weltecb8b4272018-03-28 19:57:58 +0200660 /* update counters */
661 g_stats_rtp.num_pkts_tx := g_stats_rtp.num_pkts_tx+1;
Philipp Maierbbe454d2023-03-28 15:31:57 +0200662 g_stats_rtp.bytes_payload_tx := g_stats_rtp.bytes_payload_tx + lengthof(payload);
Harald Welte067d66e2017-12-13 17:24:03 +0100663 }
664
Oliver Smith216019f2019-06-26 12:21:38 +0200665 /* connection refused */
666 [g_conn_refuse_expect] RTP.receive(tr_conn_refuse) {
667 log("Connection refused (expected)");
668 g_conn_refuse_received := true;
669 }
670 [not g_conn_refuse_expect] RTP.receive(tr_conn_refuse) {
671 setverdict(fail, "Connection refused (unexpected)");
672 mtc.stop;
673 }
674
Harald Welte067d66e2017-12-13 17:24:03 +0100675 /* fail on any unexpected messages */
676 [] RTP.receive {
677 setverdict(fail, "Received unexpected type from RTP");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200678 mtc.stop;
Harald Welte067d66e2017-12-13 17:24:03 +0100679 }
680 [] RTCP.receive {
681 setverdict(fail, "Received unexpected type from RTCP");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200682 mtc.stop;
Harald Welte067d66e2017-12-13 17:24:03 +0100683 }
684 }
685 }
686}
687
688
689}