blob: 539ea45dd99d6f99a84d130c80545e8cec53c7c0 [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 */
100 var boolean g_rx_enabled := false;
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200101 var boolean g_tx_connected := false; /* Set to true after connect() */
Harald Welte067d66e2017-12-13 17:24:03 +0100102 var LIN2_BO_LAST g_tx_next_seq := 0;
103 var uint32_t g_tx_next_ts := 0;
104
105 var INT7b g_rx_payload_type := 0;
106 var LIN2_BO_LAST g_rx_last_seq;
107 var uint32_t g_rx_last_ts;
Harald Welte80981642017-12-24 23:59:47 +0100108
109 var IuUP_Entity g_iuup_ent; // := valueof(t_IuUP_Entity(1));
Oliver Smith216019f2019-06-26 12:21:38 +0200110
111 var boolean g_conn_refuse_expect := false;
112 var boolean g_conn_refuse_received := false;
Harald Welte067d66e2017-12-13 17:24:03 +0100113}
114
115type enumerated RtpemMode {
116 RTPEM_MODE_NONE,
117 RTPEM_MODE_TXONLY,
118 RTPEM_MODE_RXONLY,
119 RTPEM_MODE_BIDIR
120};
121
Harald Weltecb8b4272018-03-28 19:57:58 +0200122type record RtpemStats {
123 /* number of packets transmitted */
124 integer num_pkts_tx,
125 /* number of RTP payload bytes transmitted */
126 integer bytes_payload_tx,
127
128 /* number of packets received */
129 integer num_pkts_rx,
130 /* number of RTP payload bytes received */
131 integer bytes_payload_rx,
132 /* number of packets received out-of-sequence */
133 integer num_pkts_rx_err_seq,
134 /* number of packets received wrong timestamp */
135 integer num_pkts_rx_err_ts,
Philipp Maierc290d722018-07-24 18:51:36 +0200136 /* number of packets received wrong payload type */
137 integer num_pkts_rx_err_pt,
Harald Weltecb8b4272018-03-28 19:57:58 +0200138 /* number of packets received during Rx disable */
Philipp Maiera071ee42019-02-21 16:17:32 +0100139 integer num_pkts_rx_err_disabled,
140 /* number of packets received with mismatching payload */
141 integer num_pkts_rx_err_payload
Harald Weltecb8b4272018-03-28 19:57:58 +0200142}
143
144const RtpemStats c_RtpemStatsReset := {
145 num_pkts_tx := 0,
146 bytes_payload_tx := 0,
147 num_pkts_rx := 0,
148 bytes_payload_rx := 0,
149 num_pkts_rx_err_seq := 0,
150 num_pkts_rx_err_ts := 0,
Philipp Maierc290d722018-07-24 18:51:36 +0200151 num_pkts_rx_err_pt := 0,
Philipp Maiera071ee42019-02-21 16:17:32 +0100152 num_pkts_rx_err_disabled := 0,
153 num_pkts_rx_err_payload := 0
Harald Weltecb8b4272018-03-28 19:57:58 +0200154}
155
Philipp Maierbbe454d2023-03-28 15:31:57 +0200156type record RtpemConfigPayload {
157 INT7b payload_type,
158 octetstring fixed_payload optional
159};
160
Harald Welte3f6f48f2017-12-24 21:48:33 +0100161type record RtpemConfig {
Harald Welte3f6f48f2017-12-24 21:48:33 +0100162 integer tx_samplerate_hz,
163 integer tx_duration_ms,
164 BIT32_BO_LAST tx_ssrc,
Philipp Maierbbe454d2023-03-28 15:31:57 +0200165 record of RtpemConfigPayload tx_payloads,
166 record of RtpemConfigPayload rx_payloads,
Harald Welte80981642017-12-24 23:59:47 +0100167 boolean iuup_mode,
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200168 IuUP_Config iuup_cfg
Harald Welte3f6f48f2017-12-24 21:48:33 +0100169};
170
Harald Welte80981642017-12-24 23:59:47 +0100171const RtpemConfig c_RtpemDefaultCfg := {
Harald Welte3f6f48f2017-12-24 21:48:33 +0100172 tx_samplerate_hz := 8000,
173 tx_duration_ms := 20,
174 tx_ssrc := '11011110101011011011111011101111'B,
Philipp Maierbbe454d2023-03-28 15:31:57 +0200175 tx_payloads := {{0, '01020304'O}},
176 rx_payloads := {{0, '01020304'O}},
Harald Welte80981642017-12-24 23:59:47 +0100177 iuup_mode := false,
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200178 iuup_cfg := c_IuUP_Config_def
Harald Welte3f6f48f2017-12-24 21:48:33 +0100179}
180
Harald Welte067d66e2017-12-13 17:24:03 +0100181signature RTPEM_bind(in HostName local_host, inout PortNumber local_port);
182signature RTPEM_connect(in HostName remote_host, in PortNumber remote_port);
183signature RTPEM_mode(in RtpemMode mode);
Harald Welte3f6f48f2017-12-24 21:48:33 +0100184signature RTPEM_configure(in RtpemConfig cfg);
Harald Weltecb8b4272018-03-28 19:57:58 +0200185signature RTPEM_stats_get(out RtpemStats stats, in boolean rtcp);
Oliver Smith216019f2019-06-26 12:21:38 +0200186signature RTPEM_conn_refuse_expect(in boolean expect);
187signature RTPEM_conn_refuse_received(out boolean received);
Harald Welte067d66e2017-12-13 17:24:03 +0100188
189type port RTPEM_CTRL_PT procedure {
Oliver Smith216019f2019-06-26 12:21:38 +0200190 inout RTPEM_bind, RTPEM_connect, RTPEM_mode, RTPEM_configure, RTPEM_stats_get, RTPEM_conn_refuse_expect,
191 RTPEM_conn_refuse_received;
Harald Welte067d66e2017-12-13 17:24:03 +0100192} with { extension "internal" };
193
Vadim Yanitskiyed5129f2021-07-04 03:42:31 +0200194type port RTPEM_DATA_PT message {
195 inout PDU_RTP, PDU_RTCP;
196} with { extension "internal" };
197
Harald Welte1af40332018-03-29 08:50:33 +0200198function f_rtpem_bind(RTPEM_CTRL_PT pt, in HostName local_host, inout PortNumber local_port) {
199 pt.call(RTPEM_bind:{local_host, local_port}) {
200 [] pt.getreply(RTPEM_bind:{local_host, ?}) -> param (local_port) {};
201 }
202}
203function f_rtpem_connect(RTPEM_CTRL_PT pt, in HostName remote_host, in PortNumber remote_port) {
204 pt.call(RTPEM_connect:{remote_host, remote_port}) {
205 [] pt.getreply(RTPEM_connect:{remote_host, remote_port}) {};
206 }
207}
208function f_rtpem_mode(RTPEM_CTRL_PT pt, in RtpemMode mode) {
209 pt.call(RTPEM_mode:{mode}) {
210 [] pt.getreply(RTPEM_mode:{mode}) {};
211 }
212}
213function f_rtpem_configure(RTPEM_CTRL_PT pt, in RtpemConfig cfg) {
214 pt.call(RTPEM_configure:{cfg}) {
215 [] pt.getreply(RTPEM_configure:{cfg}) {};
216 }
217}
218function f_rtpem_stats_get(RTPEM_CTRL_PT pt, boolean rtcp := false) return RtpemStats {
219 var RtpemStats stats;
220 pt.call(RTPEM_stats_get:{-, rtcp}) {
221 [] pt.getreply(RTPEM_stats_get:{?, rtcp}) -> param(stats) {};
222 }
223 return stats;
224}
225
Philipp Maier2321ef92018-06-27 17:52:04 +0200226function f_rtpem_stats_compare_value(integer a, integer b, integer tolerance := 0) return boolean {
227 var integer temp;
Harald Welte5c49ba42018-03-29 08:51:20 +0200228
Philipp Maier2321ef92018-06-27 17:52:04 +0200229 temp := (a - b)
230 if (temp < 0) {
231 temp := -temp;
232 }
233
234 if (temp > tolerance) {
Harald Welte5c49ba42018-03-29 08:51:20 +0200235 return false;
236 }
Philipp Maier2321ef92018-06-27 17:52:04 +0200237
Harald Welte5c49ba42018-03-29 08:51:20 +0200238 return true;
239}
Harald Welte1af40332018-03-29 08:50:33 +0200240
Philipp Maier2321ef92018-06-27 17:52:04 +0200241/* Cross-compare two rtpem-statistics. The transmission statistics on the a side
242 * must match the reception statistics on the other side and vice versa. The
243 * user may also supply a tolerance value (number of packets) when deviations
244 * are acceptable */
245function f_rtpem_stats_compare(RtpemStats a, RtpemStats b, integer tolerance := 0) return boolean {
246 var integer plen;
247
248 log("stats A: ", a);
249 log("stats B: ", b);
250 log("tolerance: ", tolerance, " packets");
251
252 if (f_rtpem_stats_compare_value(a.num_pkts_tx, b.num_pkts_rx, tolerance) == false) {
253 return false;
254 }
255
256 if (f_rtpem_stats_compare_value(a.num_pkts_rx, b.num_pkts_tx, tolerance) == false) {
257 return false;
258 }
259
260 if(a.num_pkts_tx > 0) {
261 plen := a.bytes_payload_tx / a.num_pkts_tx;
262 } else {
263 plen := 0;
264 }
265
266 if (f_rtpem_stats_compare_value(a.bytes_payload_tx, b.bytes_payload_rx, tolerance * plen) == false) {
267 return false;
268 }
269
270 if (f_rtpem_stats_compare_value(a.bytes_payload_rx, b.bytes_payload_tx, tolerance * plen) == false) {
271 return false;
272 }
273
274 return true;
275}
Harald Welte1af40332018-03-29 08:50:33 +0200276
Philipp Maier36291392018-07-25 09:40:44 +0200277/* Check the statistics for general signs of errors. This is a basic general
278 * check that will fit most situations and is intended to be executed by
279 * the testcases as as needed. */
280function f_rtpem_stats_err_check(RtpemStats s) {
281 log("stats: ", s);
282
283 /* Check if there was some activity at either on the RX or on the
284 * TX side, but complete silence would indicate some problem */
285 if (s.num_pkts_tx < 1 and s.num_pkts_rx < 1) {
286 setverdict(fail, "no RTP packet activity detected (packets)");
287 mtc.stop;
288 }
289 if (s.bytes_payload_tx < 1 and s.bytes_payload_rx < 1) {
290 setverdict(fail, "no RTP packet activity detected (bytes)");
291 mtc.stop;
292 }
293
294 /* Check error counters */
295 if (s.num_pkts_rx_err_seq != 0) {
296 setverdict(fail, "RTP packet sequence number errors occurred");
297 mtc.stop;
298 }
299 if (s.num_pkts_rx_err_ts != 0) {
300 setverdict(fail, "RTP packet timestamp errors occurred");
301 mtc.stop;
302 }
303 if (s.num_pkts_rx_err_pt != 0) {
304 setverdict(fail, "RTP packet payload type errors occurred");
305 mtc.stop;
306 }
307 if (s.num_pkts_rx_err_disabled != 0) {
308 setverdict(fail, "RTP packets received while RX was disabled");
309 mtc.stop;
310 }
Philipp Maiera071ee42019-02-21 16:17:32 +0100311 if (s.num_pkts_rx_err_payload != 0) {
312 setverdict(fail, "RTP packets with mismatching payload received");
313 mtc.stop;
314 }
Philipp Maier36291392018-07-25 09:40:44 +0200315}
316
Oliver Smith216019f2019-06-26 12:21:38 +0200317function f_rtpem_conn_refuse_expect(RTPEM_CTRL_PT pt) {
318 pt.call(RTPEM_conn_refuse_expect:{true}) {
319 [] pt.getreply(RTPEM_conn_refuse_expect:{true}) {};
320 }
321}
322
323function f_rtpem_conn_refuse_verify(RTPEM_CTRL_PT pt) {
324 pt.call(RTPEM_conn_refuse_received:{?}) {
325 [] pt.getreply(RTPEM_conn_refuse_received:{true}) {};
326 [] pt.getreply(RTPEM_conn_refuse_received:{false}) {
327 setverdict(fail, "Expected to receive connection refused");
328 };
329 }
330}
331
Harald Welte067d66e2017-12-13 17:24:03 +0100332template PDU_RTP ts_RTP(BIT32_BO_LAST ssrc, INT7b pt, LIN2_BO_LAST seq, uint32_t ts,
333 octetstring payload, BIT1 marker := '0'B) := {
334 version := 2,
335 padding_ind := '0'B,
336 extension_ind := '0'B,
337 CSRC_count := 0,
338 marker_bit := marker,
339 payload_type := pt,
340 sequence_number := seq,
Harald Welte8a4d3952017-12-24 21:30:23 +0100341 time_stamp := int2bit(ts, 32),
Harald Welte067d66e2017-12-13 17:24:03 +0100342 SSRC_id := ssrc,
343 CSRCs := omit,
344 ext_header := omit,
345 data := payload
346}
347
Philipp Maierbbe454d2023-03-28 15:31:57 +0200348private 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 +0100349 if (g_cfg.iuup_mode) {
350 payload := f_IuUP_Em_tx_encap(g_iuup_ent, payload);
Pau Espin Pedrolb204a4e2021-12-24 14:18:39 +0100351 if (lengthof(payload) == 0) {
352 /* Nothing to transmit, waiting for INIT-ACK */
353 return;
354 }
Harald Welte80981642017-12-24 23:59:47 +0100355 }
Philipp Maierbbe454d2023-03-28 15:31:57 +0200356 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 +0100357 g_tx_next_ts, payload, marker));
358 RTP.send(t_RTP_Send(g_rtp_conn_id, RTP_messages_union:{rtp:=rtp}));
359 /* increment sequence + timestamp for next transmit */
360 g_tx_next_seq := g_tx_next_seq + 1;
Harald Welte3f6f48f2017-12-24 21:48:33 +0100361 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 +0100362}
363
Philipp Maierbbe454d2023-03-28 15:31:57 +0200364private function f_check_fixed_rx_payloads(INT7b rtp_payload_type, octetstring rtp_data) runs on RTP_Emulation_CT {
365 var boolean payload_type_match := false;
366
367 /* The API user has the option to define zero or multiple sets of rx_payloads. Each rx_payload set contains
368 the payload type number of the expected payload and an optional fixed_payload, which resembles the actual
369 payload.
370
371 In case zero rx_payloads are defined nothing is verified and no errors are counted. This is a corner case
372 and should be avoided since it would not yield any good test coverage.
373
374 During verification the payload type has the highest priority. It must match before the optional fixed
375 payload is checked. Since the fixed_payload is optional multiple error situations may apply:
376
377 | payload_type | fixed_payload | result
378 | match | match | full match => no error counter is incremented
379 | match | not present | counts as full match => no error counter is incremented
380 | match | mismatch | payload type match => only num_pkts_rx_err_payload is incremented
381 | | | unless something of the above is detected later.
382 | mismatch | (not checked) | no match => num_pkts_rx_err_payload and num_pkts_rx_err_pt
383 | | | are increment unless something of the above is
384 | | | detected later.
385 */
386
387 /* In case no rx payloads are defined any payload is accepted and no errors are counted. */
388 if (lengthof(g_cfg.rx_payloads) == 0) {
389 return;
390 }
391
392 /* Evaluate rtp_data and rtp_payload_type */
393 for (var integer i := 0; i < lengthof(g_cfg.rx_payloads); i := i + 1) {
394 if (rtp_payload_type == g_cfg.rx_payloads[i].payload_type) {
395 if (not ispresent(g_cfg.rx_payloads[i].fixed_payload)) {
396 /* full match */
397 return;
398 }
399 if (g_cfg.rx_payloads[i].fixed_payload == rtp_data) {
400 /* counts as full match */
401 return;
402 }
403
404 /* At least the payload type number did match
405 * (but we still may see a full match later) */
406 payload_type_match := true;
407 }
408 }
409
410 g_stats_rtp.num_pkts_rx_err_payload := g_stats_rtp.num_pkts_rx_err_payload + 1;
411 if (not payload_type_match) {
412 g_stats_rtp.num_pkts_rx_err_pt := g_stats_rtp.num_pkts_rx_err_pt + 1;
413 }
414}
415
Harald Welte067d66e2017-12-13 17:24:03 +0100416function f_main() runs on RTP_Emulation_CT
417{
418 var Result res;
Harald Weltecb8b4272018-03-28 19:57:58 +0200419 var boolean is_rtcp
Harald Welte067d66e2017-12-13 17:24:03 +0100420
Harald Welte3f6f48f2017-12-24 21:48:33 +0100421 timer T_transmit := int2float(g_cfg.tx_duration_ms)/1000.0;
Harald Welte067d66e2017-12-13 17:24:03 +0100422 var RTP_RecvFrom rx_rtp;
Harald Welte3f6f48f2017-12-24 21:48:33 +0100423 var RtpemConfig cfg;
Harald Welte067d66e2017-12-13 17:24:03 +0100424 var template RTP_RecvFrom tr := {
425 connId := ?,
426 remName := ?,
427 remPort := ?,
428 locName := ?,
429 locPort := ?,
430 msg := ?
431 };
432 var template RTP_RecvFrom tr_rtp := tr;
433 var template RTP_RecvFrom tr_rtcp := tr;
Harald Welte067d66e2017-12-13 17:24:03 +0100434 tr_rtp.msg := { rtp := ? };
Harald Welte067d66e2017-12-13 17:24:03 +0100435 tr_rtcp.msg := { rtcp := ? };
436
Oliver Smith216019f2019-06-26 12:21:38 +0200437 var template ASP_Event tr_conn_refuse := {result := { errorCode := ERROR_SOCKET,
438 connId := ?,
439 os_error_code := 111,
440 os_error_text := ? /* "Connection refused" */}};
441
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200442 g_iuup_ent := valueof(t_IuUP_Entity(g_cfg.iuup_cfg));
Harald Welte80981642017-12-24 23:59:47 +0100443
Harald Welte067d66e2017-12-13 17:24:03 +0100444 while (true) {
445 alt {
446 /* control procedures (calls) from the user */
447 [] CTRL.getcall(RTPEM_bind:{?,?}) -> param(g_local_host, g_local_port) {
448 if (g_local_port rem 2 == 1) {
449 //CTRL.raise(RTPEM_bind, "Local Port is not an even port number!");
450 log("Local Port is not an even port number!");
451 continue;
452 }
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200453
454 g_tx_connected := false; /* will set it back to true upon next connect() call */
Pau Espin Pedrol08005d72020-09-08 13:16:14 +0200455
456 if (g_rtp_conn_id != -1) {
457 res := RTP_CodecPort_CtrlFunct.f_IPL4_close(RTP, g_rtp_conn_id, {udp := {}});
458 g_rtp_conn_id := -1;
459 }
Harald Welte067d66e2017-12-13 17:24:03 +0100460 res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTP, g_local_host,
461 g_local_port, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200462 if (not ispresent(res.connId)) {
463 setverdict(fail, "Could not listen on RTP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200464 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200465 }
Harald Welte067d66e2017-12-13 17:24:03 +0100466 g_rtp_conn_id := res.connId;
Harald Welte9774e7a2018-03-29 08:49:38 +0200467 tr_rtp.connId := g_rtp_conn_id;
Pau Espin Pedrol08005d72020-09-08 13:16:14 +0200468
469 if (g_rtcp_conn_id != -1) {
470 res := RTP_CodecPort_CtrlFunct.f_IPL4_close(RTCP, g_rtcp_conn_id, {udp := {}});
471 g_rtcp_conn_id := -1;
472 }
Harald Welte98eb1bf2018-04-02 18:18:44 +0200473 res := RTP_CodecPort_CtrlFunct.f_IPL4_listen(RTCP, g_local_host,
Harald Welte067d66e2017-12-13 17:24:03 +0100474 g_local_port+1, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200475 if (not ispresent(res.connId)) {
476 setverdict(fail, "Could not listen on RTCP 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_rtcp_conn_id := res.connId;
Harald Welte9774e7a2018-03-29 08:49:38 +0200480 tr_rtcp.connId := g_rtcp_conn_id;
Harald Welte067d66e2017-12-13 17:24:03 +0100481 CTRL.reply(RTPEM_bind:{g_local_host, g_local_port});
482 }
483 [] CTRL.getcall(RTPEM_connect:{?,?}) -> param (g_remote_host, g_remote_port) {
484 if (g_remote_port rem 2 == 1) {
485 //CTRL.raise(RTPEM_connect, "Remote Port is not an even number!");
486 log("Remote Port is not an even number!");
487 continue;
488 }
489 res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTP, g_remote_host,
490 g_remote_port,
491 g_local_host, g_local_port,
492 g_rtp_conn_id, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200493 if (not ispresent(res.connId)) {
494 setverdict(fail, "Could not connect to RTP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200495 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200496 }
Harald Welte067d66e2017-12-13 17:24:03 +0100497 res := RTP_CodecPort_CtrlFunct.f_IPL4_connect(RTCP, g_remote_host,
498 g_remote_port+1,
499 g_local_host, g_local_port+1,
500 g_rtcp_conn_id, {udp:={}});
Harald Welte9220f632018-05-23 20:27:02 +0200501 if (not ispresent(res.connId)) {
502 setverdict(fail, "Could not connect to RTCP socket, check your configuration");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200503 mtc.stop;
Harald Welte9220f632018-05-23 20:27:02 +0200504 }
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200505 g_tx_connected := true;
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100506 /* Send any pending IuUP CTRL message whichwas delayed due to not being connected: */
507 if (isvalue(g_iuup_ent.pending_tx_pdu)) {
Philipp Maierbbe454d2023-03-28 15:31:57 +0200508 f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type);
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100509 }
Harald Welte067d66e2017-12-13 17:24:03 +0100510 CTRL.reply(RTPEM_connect:{g_remote_host, g_remote_port});
511 }
512 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_NONE}) {
513 T_transmit.stop;
514 g_rx_enabled := false;
Harald Welte80981642017-12-24 23:59:47 +0100515 CTRL.reply(RTPEM_mode:{RTPEM_MODE_NONE});
Harald Welte067d66e2017-12-13 17:24:03 +0100516 }
517 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_TXONLY}) {
518 /* start transmit timer */
519 T_transmit.start;
520 g_rx_enabled := false;
Harald Welte80981642017-12-24 23:59:47 +0100521 CTRL.reply(RTPEM_mode:{RTPEM_MODE_TXONLY});
Harald Welte067d66e2017-12-13 17:24:03 +0100522 }
523 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_RXONLY}) {
524
525 T_transmit.stop;
526 if (g_rx_enabled == false) {
527 /* flush queues */
528 RTP.clear;
529 RTCP.clear;
530 g_rx_enabled := true;
531 }
Harald Welte80981642017-12-24 23:59:47 +0100532 CTRL.reply(RTPEM_mode:{RTPEM_MODE_RXONLY});
Harald Welte067d66e2017-12-13 17:24:03 +0100533 }
534 [] CTRL.getcall(RTPEM_mode:{RTPEM_MODE_BIDIR}) {
535 T_transmit.start;
536 if (g_rx_enabled == false) {
537 /* flush queues */
538 RTP.clear;
539 RTCP.clear;
540 g_rx_enabled := true;
541 }
Harald Welte80981642017-12-24 23:59:47 +0100542 CTRL.reply(RTPEM_mode:{RTPEM_MODE_BIDIR});
Harald Welte067d66e2017-12-13 17:24:03 +0100543 }
Harald Welte3f6f48f2017-12-24 21:48:33 +0100544 [] CTRL.getcall(RTPEM_configure:{?}) -> param (cfg) {
545 g_cfg := cfg;
Pau Espin Pedrol6ed76302022-05-25 18:09:37 +0200546 g_iuup_ent.cfg := g_cfg.iuup_cfg;
Harald Welte80981642017-12-24 23:59:47 +0100547 CTRL.reply(RTPEM_configure:{cfg});
Harald Welte3f6f48f2017-12-24 21:48:33 +0100548 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200549 [] CTRL.getcall(RTPEM_stats_get:{?, ?}) -> param (is_rtcp) {
550 if (is_rtcp) {
551 CTRL.reply(RTPEM_stats_get:{g_stats_rtcp, is_rtcp});
552 } else {
553 CTRL.reply(RTPEM_stats_get:{g_stats_rtp, is_rtcp});
554 }
555 }
Oliver Smith216019f2019-06-26 12:21:38 +0200556 [] CTRL.getcall(RTPEM_conn_refuse_expect:{?}) -> param(g_conn_refuse_expect) {
557 CTRL.reply(RTPEM_conn_refuse_expect:{g_conn_refuse_expect});
558 }
559 [] CTRL.getcall(RTPEM_conn_refuse_received:{?}) {
560 CTRL.reply(RTPEM_conn_refuse_received:{g_conn_refuse_received});
561 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200562
563
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100564 /* simply ignore any RTCP/RTP if receiver not enabled */
565 [not g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp {
566 /* In IuUP we need to decode possible Control packets, such as INIT-ACK: */
567 if (g_cfg.iuup_mode) {
568 /* In IuUP we need to decode possible Control packets, such as INIT-ACK: */
569 rx_rtp.msg.rtp.data := f_IuUP_Em_rx_decaps(g_iuup_ent, rx_rtp.msg.rtp.data);
570 if (lengthof(rx_rtp.msg.rtp.data) != 0) {
571 /* Unexpected RTP payload (user data) arrived: */
572 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 +0100573 } else if (g_tx_connected and isvalue(g_iuup_ent.pending_tx_pdu)) {
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100574 /* IuUP Control packet was received and requires sending back something: */
Philipp Maierbbe454d2023-03-28 15:31:57 +0200575 f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type);
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100576 }
577 } else {
578 g_stats_rtp.num_pkts_rx_err_disabled := g_stats_rtp.num_pkts_rx_err_disabled+1;
579 }
Harald Weltecb8b4272018-03-28 19:57:58 +0200580 }
Vadim Yanitskiy8c978ec2021-07-04 03:13:36 +0200581 [not g_rx_enabled] RTCP.receive(tr_rtcp) {
Harald Weltecb8b4272018-03-28 19:57:58 +0200582 g_stats_rtcp.num_pkts_rx_err_disabled := g_stats_rtcp.num_pkts_rx_err_disabled+1;
583 }
Harald Welte067d66e2017-12-13 17:24:03 +0100584
585 /* process received RTCP/RTP if receiver enabled */
586 [g_rx_enabled] RTP.receive(tr_rtp) -> value rx_rtp {
Harald Weltecb8b4272018-03-28 19:57:58 +0200587 /* increment counters */
588 g_stats_rtp.num_pkts_rx := g_stats_rtp.num_pkts_rx+1;
589 g_stats_rtp.bytes_payload_rx := g_stats_rtp.bytes_payload_rx +
590 lengthof(rx_rtp.msg.rtp.data);
Harald Welte80981642017-12-24 23:59:47 +0100591 if (g_cfg.iuup_mode) {
592 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 +0100593 /* IuUP Control packet was received which may require sending back something: */
594 if (lengthof(rx_rtp.msg.rtp.data) == 0) {
Pau Espin Pedrol5c505032022-01-07 14:36:25 +0100595 if (g_tx_connected and isvalue(g_iuup_ent.pending_tx_pdu)) {
Philipp Maierbbe454d2023-03-28 15:31:57 +0200596 f_tx_rtp(''O, g_cfg.tx_payloads[0].payload_type);
Pau Espin Pedrol735ba7a2022-01-03 18:38:41 +0100597 }
598 repeat;
599 }
600 }
Philipp Maierbbe454d2023-03-28 15:31:57 +0200601
602 /* Match the received payload against any of the predefined fixed rx payloads */
603 f_check_fixed_rx_payloads(rx_rtp.msg.rtp.payload_type, rx_rtp.msg.rtp.data);
604
Vadim Yanitskiyed5129f2021-07-04 03:42:31 +0200605 if (DATA.checkstate("Connected")) {
606 DATA.send(rx_rtp.msg.rtp);
607 }
Harald Welte067d66e2017-12-13 17:24:03 +0100608 }
609 [g_rx_enabled] RTCP.receive(tr_rtcp) -> value rx_rtp {
Harald Welte8d3ea0e2018-03-29 08:50:18 +0200610 //log("RX RTCP: ", rx_rtp);
Harald Weltecb8b4272018-03-28 19:57:58 +0200611 g_stats_rtcp.num_pkts_rx := g_stats_rtcp.num_pkts_rx+1;
Vadim Yanitskiyed5129f2021-07-04 03:42:31 +0200612 if (DATA.checkstate("Connected")) {
613 DATA.send(rx_rtp.msg.rtcp);
614 }
Harald Welte067d66e2017-12-13 17:24:03 +0100615 }
616
617 /* transmit if timer has expired */
Pau Espin Pedrole38bfe02019-05-17 18:40:43 +0200618 [g_tx_connected] T_transmit.timeout {
Philipp Maierbbe454d2023-03-28 15:31:57 +0200619 var octetstring payload := g_cfg.tx_payloads[g_tx_next_seq mod lengthof(g_cfg.tx_payloads)].fixed_payload;
620 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 +0100621 /* send one RTP frame, re-start timer */
Philipp Maierbbe454d2023-03-28 15:31:57 +0200622 f_tx_rtp(payload, rtp_payload_type);
Harald Welte067d66e2017-12-13 17:24:03 +0100623 T_transmit.start;
Harald Weltecb8b4272018-03-28 19:57:58 +0200624 /* update counters */
625 g_stats_rtp.num_pkts_tx := g_stats_rtp.num_pkts_tx+1;
Philipp Maierbbe454d2023-03-28 15:31:57 +0200626 g_stats_rtp.bytes_payload_tx := g_stats_rtp.bytes_payload_tx + lengthof(payload);
Harald Welte067d66e2017-12-13 17:24:03 +0100627 }
628
Oliver Smith216019f2019-06-26 12:21:38 +0200629 /* connection refused */
630 [g_conn_refuse_expect] RTP.receive(tr_conn_refuse) {
631 log("Connection refused (expected)");
632 g_conn_refuse_received := true;
633 }
634 [not g_conn_refuse_expect] RTP.receive(tr_conn_refuse) {
635 setverdict(fail, "Connection refused (unexpected)");
636 mtc.stop;
637 }
638
Harald Welte067d66e2017-12-13 17:24:03 +0100639 /* fail on any unexpected messages */
640 [] RTP.receive {
641 setverdict(fail, "Received unexpected type from RTP");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200642 mtc.stop;
Harald Welte067d66e2017-12-13 17:24:03 +0100643 }
644 [] RTCP.receive {
645 setverdict(fail, "Received unexpected type from RTCP");
Daniel Willmanne4ff5372018-07-05 17:35:03 +0200646 mtc.stop;
Harald Welte067d66e2017-12-13 17:24:03 +0100647 }
648 }
649 }
650}
651
652
653}