blob: 5e10b3fea58dadc8e12ea5d1e8be4553a35bb5dc [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) {
283 log("stats: ", s);
284
285 /* Check if there was some activity at either on the RX or on the
286 * TX side, but complete silence would indicate some problem */
287 if (s.num_pkts_tx < 1 and s.num_pkts_rx < 1) {
288 setverdict(fail, "no RTP packet activity detected (packets)");
289 mtc.stop;
290 }
291 if (s.bytes_payload_tx < 1 and s.bytes_payload_rx < 1) {
292 setverdict(fail, "no RTP packet activity detected (bytes)");
293 mtc.stop;
294 }
295
296 /* Check error counters */
297 if (s.num_pkts_rx_err_seq != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200298 setverdict(fail, log2str(s.num_pkts_rx_err_seq, " RTP packet sequence number errors occurred"));
Philipp Maier36291392018-07-25 09:40:44 +0200299 mtc.stop;
300 }
301 if (s.num_pkts_rx_err_ts != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200302 setverdict(fail, log2str(s.num_pkts_rx_err_ts, " RTP packet timestamp errors occurred"));
Philipp Maier36291392018-07-25 09:40:44 +0200303 mtc.stop;
304 }
305 if (s.num_pkts_rx_err_pt != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200306 setverdict(fail, log2str(s.num_pkts_rx_err_pt, " RTP packet payload type errors occurred"));
Philipp Maier36291392018-07-25 09:40:44 +0200307 mtc.stop;
308 }
309 if (s.num_pkts_rx_err_disabled != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200310 setverdict(fail, log2str(s.num_pkts_rx_err_disabled, " RTP packets received while RX was disabled"));
Philipp Maier36291392018-07-25 09:40:44 +0200311 mtc.stop;
312 }
Philipp Maiera071ee42019-02-21 16:17:32 +0100313 if (s.num_pkts_rx_err_payload != 0) {
Pau Espin Pedrol60c69a12023-09-27 18:10:13 +0200314 setverdict(fail, log2str(s.num_pkts_rx_err_payload, " RTP packets with mismatching payload received"));
Philipp Maiera071ee42019-02-21 16:17:32 +0100315 mtc.stop;
316 }
Philipp Maier36291392018-07-25 09:40:44 +0200317}
318
Oliver Smith216019f2019-06-26 12:21:38 +0200319function f_rtpem_conn_refuse_expect(RTPEM_CTRL_PT pt) {
320 pt.call(RTPEM_conn_refuse_expect:{true}) {
321 [] pt.getreply(RTPEM_conn_refuse_expect:{true}) {};
322 }
323}
324
325function f_rtpem_conn_refuse_verify(RTPEM_CTRL_PT pt) {
326 pt.call(RTPEM_conn_refuse_received:{?}) {
327 [] pt.getreply(RTPEM_conn_refuse_received:{true}) {};
328 [] pt.getreply(RTPEM_conn_refuse_received:{false}) {
329 setverdict(fail, "Expected to receive connection refused");
330 };
331 }
332}
333
Harald Welte067d66e2017-12-13 17:24:03 +0100334template PDU_RTP ts_RTP(BIT32_BO_LAST ssrc, INT7b pt, LIN2_BO_LAST seq, uint32_t ts,
335 octetstring payload, BIT1 marker := '0'B) := {
336 version := 2,
337 padding_ind := '0'B,
338 extension_ind := '0'B,
339 CSRC_count := 0,
340 marker_bit := marker,
341 payload_type := pt,
342 sequence_number := seq,
Harald Welte8a4d3952017-12-24 21:30:23 +0100343 time_stamp := int2bit(ts, 32),
Harald Welte067d66e2017-12-13 17:24:03 +0100344 SSRC_id := ssrc,
345 CSRCs := omit,
346 ext_header := omit,
347 data := payload
348}
349
Philipp Maierbbe454d2023-03-28 15:31:57 +0200350private 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 +0100351 if (g_cfg.iuup_mode) {
352 payload := f_IuUP_Em_tx_encap(g_iuup_ent, payload);
Pau Espin Pedrolb204a4e2021-12-24 14:18:39 +0100353 if (lengthof(payload) == 0) {
354 /* Nothing to transmit, waiting for INIT-ACK */
355 return;
356 }
Harald Welte80981642017-12-24 23:59:47 +0100357 }
Philipp Maierbbe454d2023-03-28 15:31:57 +0200358 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 +0100359 g_tx_next_ts, payload, marker));
360 RTP.send(t_RTP_Send(g_rtp_conn_id, RTP_messages_union:{rtp:=rtp}));
361 /* increment sequence + timestamp for next transmit */
362 g_tx_next_seq := g_tx_next_seq + 1;
Harald Welte3f6f48f2017-12-24 21:48:33 +0100363 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 +0100364}
365
Philipp Maierbbe454d2023-03-28 15:31:57 +0200366private function f_check_fixed_rx_payloads(INT7b rtp_payload_type, octetstring rtp_data) runs on RTP_Emulation_CT {
367 var boolean payload_type_match := false;
368
369 /* The API user has the option to define zero or multiple sets of rx_payloads. Each rx_payload set contains
370 the payload type number of the expected payload and an optional fixed_payload, which resembles the actual
371 payload.
372
373 In case zero rx_payloads are defined nothing is verified and no errors are counted. This is a corner case
374 and should be avoided since it would not yield any good test coverage.
375
376 During verification the payload type has the highest priority. It must match before the optional fixed
377 payload is checked. Since the fixed_payload is optional multiple error situations may apply:
378
379 | payload_type | fixed_payload | result
380 | match | match | full match => no error counter is incremented
381 | match | not present | counts as full match => no error counter is incremented
382 | match | mismatch | payload type match => only num_pkts_rx_err_payload is incremented
383 | | | unless something of the above is detected later.
384 | mismatch | (not checked) | no match => num_pkts_rx_err_payload and num_pkts_rx_err_pt
385 | | | are increment unless something of the above is
386 | | | detected later.
387 */
388
389 /* In case no rx payloads are defined any payload is accepted and no errors are counted. */
390 if (lengthof(g_cfg.rx_payloads) == 0) {
391 return;
392 }
393
394 /* Evaluate rtp_data and rtp_payload_type */
395 for (var integer i := 0; i < lengthof(g_cfg.rx_payloads); i := i + 1) {
396 if (rtp_payload_type == g_cfg.rx_payloads[i].payload_type) {
397 if (not ispresent(g_cfg.rx_payloads[i].fixed_payload)) {
398 /* full match */
399 return;
400 }
401 if (g_cfg.rx_payloads[i].fixed_payload == rtp_data) {
402 /* counts as full match */
403 return;
404 }
405
406 /* At least the payload type number did match
407 * (but we still may see a full match later) */
408 payload_type_match := true;
409 }
410 }
411
412 g_stats_rtp.num_pkts_rx_err_payload := g_stats_rtp.num_pkts_rx_err_payload + 1;
413 if (not payload_type_match) {
414 g_stats_rtp.num_pkts_rx_err_pt := g_stats_rtp.num_pkts_rx_err_pt + 1;
415 }
416}
417
Harald Welte067d66e2017-12-13 17:24:03 +0100418function f_main() runs on RTP_Emulation_CT
419{
420 var Result res;
Harald Weltecb8b4272018-03-28 19:57:58 +0200421 var boolean is_rtcp
Harald Welte067d66e2017-12-13 17:24:03 +0100422
Harald Welte3f6f48f2017-12-24 21:48:33 +0100423 timer T_transmit := int2float(g_cfg.tx_duration_ms)/1000.0;
Harald Welte067d66e2017-12-13 17:24:03 +0100424 var RTP_RecvFrom rx_rtp;
Harald Welte3f6f48f2017-12-24 21:48:33 +0100425 var RtpemConfig cfg;
Harald Welte067d66e2017-12-13 17:24:03 +0100426 var template RTP_RecvFrom tr := {
427 connId := ?,
428 remName := ?,
429 remPort := ?,
430 locName := ?,
431 locPort := ?,
432 msg := ?
433 };
434 var template RTP_RecvFrom tr_rtp := tr;
435 var template RTP_RecvFrom tr_rtcp := tr;
Harald Welte067d66e2017-12-13 17:24:03 +0100436 tr_rtp.msg := { rtp := ? };
Harald Welte067d66e2017-12-13 17:24:03 +0100437 tr_rtcp.msg := { rtcp := ? };
438
Oliver Smith216019f2019-06-26 12:21:38 +0200439 var template ASP_Event tr_conn_refuse := {result := { errorCode := ERROR_SOCKET,
440 connId := ?,
441 os_error_code := 111,
442 os_error_text := ? /* "Connection refused" */}};
443
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200444 g_iuup_ent := valueof(t_IuUP_Entity(g_cfg.iuup_cfg));
Harald Welte80981642017-12-24 23:59:47 +0100445
Harald Welte067d66e2017-12-13 17:24:03 +0100446 while (true) {
447 alt {
448 /* control procedures (calls) from the user */
449 [] CTRL.getcall(RTPEM_bind:{?,?}) -> param(g_local_host, g_local_port) {
450 if (g_local_port rem 2 == 1) {
451 //CTRL.raise(RTPEM_bind, "Local Port is not an even port number!");
452 log("Local Port is not an even port number!");
453 continue;
454 }
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200455
456 g_tx_connected := false; /* will set it back to true upon next connect() call */
Pau Espin Pedrol08005d72020-09-08 13:16:14 +0200457
458 if (g_rtp_conn_id != -1) {
459 res := RTP_CodecPort_CtrlFunct.f_IPL4_close(RTP, g_rtp_conn_id, {udp := {}});
460 g_rtp_conn_id := -1;
461 }
Harald Welte067d66e2017-12-13 17:24:03 +0100462 res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTP, g_local_host,
463 g_local_port, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200464 if (not ispresent(res.connId)) {
465 setverdict(fail, "Could not listen on RTP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200466 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200467 }
Harald Welte067d66e2017-12-13 17:24:03 +0100468 g_rtp_conn_id := res.connId;
Harald Welte9774e7a2018-03-29 08:49:38 +0200469 tr_rtp.connId := g_rtp_conn_id;
Pau Espin Pedrol08005d72020-09-08 13:16:14 +0200470
471 if (g_rtcp_conn_id != -1) {
472 res := RTP_CodecPort_CtrlFunct.f_IPL4_close(RTCP, g_rtcp_conn_id, {udp := {}});
473 g_rtcp_conn_id := -1;
474 }
Harald Welte98eb1bf2018-04-02 18:18:44 +0200475 res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTCP, g_local_host,
Harald Welte067d66e2017-12-13 17:24:03 +0100476 g_local_port+1, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200477 if (not ispresent(res.connId)) {
478 setverdict(fail, "Could not listen on RTCP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200479 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200480 }
Harald Welte067d66e2017-12-13 17:24:03 +0100481 g_rtcp_conn_id := res.connId;
Harald Welte9774e7a2018-03-29 08:49:38 +0200482 tr_rtcp.connId := g_rtcp_conn_id;
Harald Welte067d66e2017-12-13 17:24:03 +0100483 CTRL.reply(RTPEM_bind:{g_local_host, g_local_port});
484 }
485 [] CTRL.getcall(RTPEM_connect:{?,?}) -> param (g_remote_host, g_remote_port) {
486 if (g_remote_port rem 2 == 1) {
487 //CTRL.raise(RTPEM_connect, "Remote Port is not an even number!");
488 log("Remote Port is not an even number!");
489 continue;
490 }
491 res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTP, g_remote_host,
492 g_remote_port,
493 g_local_host, g_local_port,
494 g_rtp_conn_id, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200495 if (not ispresent(res.connId)) {
496 setverdict(fail, "Could not connect to RTP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200497 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200498 }
Harald Welte067d66e2017-12-13 17:24:03 +0100499 res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTCP, g_remote_host,
500 g_remote_port+1,
501 g_local_host, g_local_port+1,
502 g_rtcp_conn_id, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200503 if (not ispresent(res.connId)) {
504 setverdict(fail, "Could not connect to RTCP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200505 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200506 }
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200507 g_tx_connected := true;
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100508 /* Send any pending IuUP CTRL message whichwas delayed due to not being connected: */
509 if (isvalue(g_iuup_ent.pending_tx_pdu)) {
Philipp Maierbbe454d2023-03-28 15:31:57 +0200510 f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type);
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100511 }
Harald Welte067d66e2017-12-13 17:24:03 +0100512 CTRL.reply(RTPEM_connect:{g_remote_host, g_remote_port});
513 }
514 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_NONE}) {
515 T_transmit.stop;
516 g_rx_enabled := false;
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700517 g_loopback := false;
Harald Welte80981642017-12-24 23:59:47 +0100518 CTRL.reply(RTPEM_mode:{RTPEM_MODE_NONE});
Harald Welte067d66e2017-12-13 17:24:03 +0100519 }
520 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_TXONLY}) {
521 /* start transmit timer */
522 T_transmit.start;
523 g_rx_enabled := false;
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700524 g_loopback := false;
Harald Welte80981642017-12-24 23:59:47 +0100525 CTRL.reply(RTPEM_mode:{RTPEM_MODE_TXONLY});
Harald Welte067d66e2017-12-13 17:24:03 +0100526 }
527 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_RXONLY}) {
528
529 T_transmit.stop;
530 if (g_rx_enabled == false) {
531 /* flush queues */
532 RTP.clear;
533 RTCP.clear;
534 g_rx_enabled := true;
535 }
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700536 g_loopback := false;
Harald Welte80981642017-12-24 23:59:47 +0100537 CTRL.reply(RTPEM_mode:{RTPEM_MODE_RXONLY});
Harald Welte067d66e2017-12-13 17:24:03 +0100538 }
539 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_BIDIR}) {
540 T_transmit.start;
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_BIDIR});
Harald Welte067d66e2017-12-13 17:24:03 +0100549 }
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700550 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_LOOPBACK}) {
551 T_transmit.stop;
552 if (g_rx_enabled == false) {
553 /* flush queues */
554 RTP.clear;
555 RTCP.clear;
556 g_rx_enabled := true;
557 }
558 g_loopback := true;
559 CTRL.reply(RTPEM_mode:{RTPEM_MODE_LOOPBACK});
560 }
Harald Welte3f6f48f2017-12-24 21:48:33 +0100561 [] CTRL.getcall(RTPEM_configure:{?}) -> param (cfg) {
562 g_cfg := cfg;
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200563 g_iuup_ent.cfg := g_cfg.iuup_cfg;
Harald Welte80981642017-12-24 23:59:47 +0100564 CTRL.reply(RTPEM_configure:{cfg});
Harald Welte3f6f48f2017-12-24 21:48:33 +0100565 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200566 [] CTRL.getcall(RTPEM_stats_get:{?, ?}) -> param (is_rtcp) {
567 if (is_rtcp) {
568 CTRL.reply(RTPEM_stats_get:{g_stats_rtcp, is_rtcp});
569 } else {
570 CTRL.reply(RTPEM_stats_get:{g_stats_rtp, is_rtcp});
571 }
572 }
Oliver Smith216019f2019-06-26 12:21:38 +0200573 [] CTRL.getcall(RTPEM_conn_refuse_expect:{?}) -> param(g_conn_refuse_expect) {
574 CTRL.reply(RTPEM_conn_refuse_expect:{g_conn_refuse_expect});
575 }
576 [] CTRL.getcall(RTPEM_conn_refuse_received:{?}) {
577 CTRL.reply(RTPEM_conn_refuse_received:{g_conn_refuse_received});
578 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200579
580
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100581 /* simply ignore any RTCP/RTP if receiver not enabled */
582 [not g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp {
583 /* In IuUP we need to decode possible Control packets, such as INIT-ACK: */
584 if (g_cfg.iuup_mode) {
585 /* In IuUP we need to decode possible Control packets, such as INIT-ACK: */
586 rx_rtp.msg.rtp.data := f_IuUP_Em_rx_decaps(g_iuup_ent, rx_rtp.msg.rtp.data);
587 if (lengthof(rx_rtp.msg.rtp.data) != 0) {
588 /* Unexpected RTP payload (user data) arrived: */
589 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 +0100590 } else if (g_tx_connected and isvalue(g_iuup_ent.pending_tx_pdu)) {
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100591 /* IuUP Control packet was received and requires sending back something: */
Philipp Maierbbe454d2023-03-28 15:31:57 +0200592 f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type);
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100593 }
594 } else {
595 g_stats_rtp.num_pkts_rx_err_disabled := g_stats_rtp.num_pkts_rx_err_disabled+1;
596 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200597 }
Vadim Yanitskiy8c978ec2021-07-04 03:13:36 +0200598 [not g_rx_enabled] RTCP.receive(tr_rtcp) {
Harald Weltecb8b4272018-03-28 19:57:58 +0200599 g_stats_rtcp.num_pkts_rx_err_disabled := g_stats_rtcp.num_pkts_rx_err_disabled+1;
600 }
Harald Welte067d66e2017-12-13 17:24:03 +0100601
602 /* process received RTCP/RTP if receiver enabled */
603 [g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp {
Harald Weltecb8b4272018-03-28 19:57:58 +0200604 /* increment counters */
605 g_stats_rtp.num_pkts_rx := g_stats_rtp.num_pkts_rx+1;
606 g_stats_rtp.bytes_payload_rx := g_stats_rtp.bytes_payload_rx +
607 lengthof(rx_rtp.msg.rtp.data);
Harald Welte80981642017-12-24 23:59:47 +0100608 if (g_cfg.iuup_mode) {
609 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 +0100610 /* IuUP Control packet was received which may require sending back something: */
611 if (lengthof(rx_rtp.msg.rtp.data) == 0) {
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100612 if (g_tx_connected and isvalue(g_iuup_ent.pending_tx_pdu)) {
Philipp Maierbbe454d2023-03-28 15:31:57 +0200613 f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type);
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100614 }
615 repeat;
616 }
617 }
Philipp Maierbbe454d2023-03-28 15:31:57 +0200618
Vadim Yanitskiy15034cb2023-09-01 05:39:48 +0700619 if (g_loopback) {
620 f_tx_rtp(rx_rtp.msg.rtp.data, rx_rtp.msg.rtp.payload_type);
621 /* update counters */
622 g_stats_rtp.num_pkts_tx := g_stats_rtp.num_pkts_tx + 1;
623 g_stats_rtp.bytes_payload_tx := g_stats_rtp.bytes_payload_tx
624 + lengthof(rx_rtp.msg.rtp.data);
625 } else {
626 /* Match the received payload against any of the predefined fixed rx payloads */
627 f_check_fixed_rx_payloads(rx_rtp.msg.rtp.payload_type, rx_rtp.msg.rtp.data);
628 }
Philipp Maierbbe454d2023-03-28 15:31:57 +0200629
Vadim Yanitskiyed5129f2021-07-04 03:42:31 +0200630 if (DATA.checkstate("Connected")) {
631 DATA.send(rx_rtp.msg.rtp);
632 }
Harald Welte067d66e2017-12-13 17:24:03 +0100633 }
634 [g_rx_enabled] RTCP.receive(tr_rtcp) -> value rx_rtp {
Harald Welte8d3ea0e2018-03-29 08:50:18 +0200635 //log("RX RTCP: ", rx_rtp);
Harald Weltecb8b4272018-03-28 19:57:58 +0200636 g_stats_rtcp.num_pkts_rx := g_stats_rtcp.num_pkts_rx+1;
Vadim Yanitskiyed5129f2021-07-04 03:42:31 +0200637 if (DATA.checkstate("Connected")) {
638 DATA.send(rx_rtp.msg.rtcp);
639 }
Harald Welte067d66e2017-12-13 17:24:03 +0100640 }
641
642 /* transmit if timer has expired */
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200643 [g_tx_connected] T_transmit.timeout {
Philipp Maierbbe454d2023-03-28 15:31:57 +0200644 var octetstring payload := g_cfg.tx_payloads[g_tx_next_seq mod lengthof(g_cfg.tx_payloads)].fixed_payload;
645 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 +0100646 /* send one RTP frame, re-start timer */
Philipp Maierbbe454d2023-03-28 15:31:57 +0200647 f_tx_rtp(payload, rtp_payload_type);
Harald Welte067d66e2017-12-13 17:24:03 +0100648 T_transmit.start;
Harald Weltecb8b4272018-03-28 19:57:58 +0200649 /* update counters */
650 g_stats_rtp.num_pkts_tx := g_stats_rtp.num_pkts_tx+1;
Philipp Maierbbe454d2023-03-28 15:31:57 +0200651 g_stats_rtp.bytes_payload_tx := g_stats_rtp.bytes_payload_tx + lengthof(payload);
Harald Welte067d66e2017-12-13 17:24:03 +0100652 }
653
Oliver Smith216019f2019-06-26 12:21:38 +0200654 /* connection refused */
655 [g_conn_refuse_expect] RTP.receive(tr_conn_refuse) {
656 log("Connection refused (expected)");
657 g_conn_refuse_received := true;
658 }
659 [not g_conn_refuse_expect] RTP.receive(tr_conn_refuse) {
660 setverdict(fail, "Connection refused (unexpected)");
661 mtc.stop;
662 }
663
Harald Welte067d66e2017-12-13 17:24:03 +0100664 /* fail on any unexpected messages */
665 [] RTP.receive {
666 setverdict(fail, "Received unexpected type from RTP");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200667 mtc.stop;
Harald Welte067d66e2017-12-13 17:24:03 +0100668 }
669 [] RTCP.receive {
670 setverdict(fail, "Received unexpected type from RTCP");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200671 mtc.stop;
Harald Welte067d66e2017-12-13 17:24:03 +0100672 }
673 }
674 }
675}
676
677
678}