From fdac2d1b8686d5df84d7e4bf090abedb624838aa Mon Sep 17 00:00:00 2001
From: Dragos Oancea <dragos@signalwire.com>
Date: Wed, 15 Feb 2023 13:07:26 +0200
Subject: [PATCH] [unit-tests] media timeout: improve existing unit-test and
 add new media timeout test for call-on-hold.

---
 tests/unit/switch_rtp_pcap.c | 167 ++++++++++++++++++++++++++++++++++-
 1 file changed, 163 insertions(+), 4 deletions(-)

diff --git a/tests/unit/switch_rtp_pcap.c b/tests/unit/switch_rtp_pcap.c
index 647e481c8e..17903be8bc 100644
--- a/tests/unit/switch_rtp_pcap.c
+++ b/tests/unit/switch_rtp_pcap.c
@@ -50,6 +50,8 @@ switch_payload_t read_pt;
 static switch_port_t audio_rx_port = 1234;
 
 static int got_media_timeout = 0;
+switch_time_t timeout_time;
+switch_mutex_t *timeout_mutex;
 
 //#define USE_RTCP_PCAP 
 
@@ -276,8 +278,11 @@ static void event_handler(switch_event_t *event)
 	const char *new_ev = switch_event_get_header(event, "Event-Name");
 
 	if (new_ev && !strcmp(new_ev, "CHANNEL_HANGUP")) {
-		if (!strcmp(switch_event_get_header(event, "Hangup-Cause"), "MEDIA_TIMEOUT")) {
+		if (new_ev && (!strcmp(new_ev, "CHANNEL_HANGUP") || !strcmp(new_ev, "CHANNEL_HANGUP_COMPLETE"))) {
+			switch_mutex_lock(timeout_mutex);
 			got_media_timeout = 1;
+			timeout_time = switch_time_now();
+			switch_mutex_unlock(timeout_mutex);
 		}
 	}
 
@@ -470,6 +475,7 @@ FST_TEARDOWN_END()
 		switch_socket_t *sock_rtp = NULL;
 		switch_sockaddr_t *sock_addr = NULL;
 		const char *str_err;
+		switch_time_t time_of_last_packet_rcvd;
 
 		status = rtp_test_start_call(&session);
 		fst_requires(status == SWITCH_STATUS_SUCCESS);
@@ -480,7 +486,7 @@ FST_TEARDOWN_END()
 		pcap = pcap_open_offline_with_tstamp_precision("pcap/milliwatt.pcmu.rtp.pcap", PCAP_TSTAMP_PRECISION_MICRO, errbuf);
 		fst_requires(pcap);
 
-		switch_core_media_set_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_ENABLE_RTCP);
+		switch_core_media_clear_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_ENABLE_RTCP);
 
 		rtp_session = switch_core_media_get_rtp_session(session, SWITCH_MEDIA_TYPE_AUDIO);
 		fst_requires(rtp_session);
@@ -498,6 +504,7 @@ FST_TEARDOWN_END()
 
 		switch_rtp_set_remote_address(rtp_session, tx_host, switch_sockaddr_get_port(sock_addr), 0, SWITCH_FALSE, &str_err);
 		switch_rtp_reset(rtp_session);
+		switch_mutex_init(&timeout_mutex, SWITCH_MUTEX_NESTED, fst_pool);
 
 		/* send 3 packets then wait and expect RTP timeout */
 		while ((packet = pcap_next(pcap, &pcap_header)) && x < 3) {
@@ -548,9 +555,13 @@ FST_TEARDOWN_END()
 			fst_requires(rcvd_datalen == plen - SWITCH_RTP_HEADER_LEN);
 		}
 
+		time_of_last_packet_rcvd = switch_time_now();
 		x = 150; /* 3 seconds max */
-		while (x || !got_media_timeout) {
-			uint32_t rcvd_datalen; 
+		switch_mutex_lock(timeout_mutex);
+		while (x > 0 || !got_media_timeout) {
+			uint32_t rcvd_datalen;
+
+			switch_mutex_unlock(timeout_mutex);
 			status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags);
 			if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) {
 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG, skip\n");
@@ -558,7 +569,9 @@ FST_TEARDOWN_END()
 			switch_yield(20 * 1000);
 			fst_requires(status == SWITCH_STATUS_SUCCESS);
 			x--;
+			switch_mutex_lock(timeout_mutex);
 		}
+		switch_mutex_unlock(timeout_mutex);
 
 		if (write_frame) switch_frame_free(&write_frame);
 
@@ -570,9 +583,155 @@ FST_TEARDOWN_END()
 
 		pcap_close(pcap);
 
+		switch_event_unbind_callback(event_handler);
+
 		fst_check(got_media_timeout);
+
+		fst_check(timeout_time - time_of_last_packet_rcvd > 3 * 1000); /* consider 3 seconds extra for the test - CI delay, load, etc */
 	}
 	FST_TEST_END()
+	FST_TEST_BEGIN(test_rtp_media_timeout_hold)
+	{
+		switch_core_session_t *session = NULL;
+		switch_status_t status;
+		uint32_t plen = SWITCH_RTP_HEADER_LEN;
+		char rpacket[SWITCH_RECOMMENDED_BUFFER_SIZE];
+		switch_payload_t pt = { 0 };
+		switch_frame_flag_t frameflags = { 0 };
+		int x = 0;
+		switch_frame_t *write_frame;
+		pcap_t *pcap;
+		const unsigned char *packet;
+		char errbuf[PCAP_ERRBUF_SIZE];
+		struct pcap_pkthdr pcap_header;
+		const struct sniff_ip *ip; /* The IP header */
+		int size_ip, jump_over;
+		struct timeval prev_ts = { 0 };
+		switch_socket_t *sock_rtp = NULL;
+		switch_sockaddr_t *sock_addr = NULL;
+		const char *str_err;
+		switch_time_t time_of_last_packet_rcvd;
+		switch_channel_t *channel = NULL;
+
+		got_media_timeout = 0;
+		status = rtp_test_start_call(&session);
+		fst_requires(status == SWITCH_STATUS_SUCCESS);
+		fst_requires(session);
+
+		switch_event_bind("", SWITCH_EVENT_ALL, SWITCH_EVENT_SUBCLASS_ANY, event_handler, NULL);
+
+		pcap = pcap_open_offline_with_tstamp_precision("pcap/milliwatt.pcmu.rtp.pcap", PCAP_TSTAMP_PRECISION_MICRO, errbuf);
+		fst_requires(pcap);
+
+		switch_core_media_clear_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_ENABLE_RTCP);
+
+		rtp_session = switch_core_media_get_rtp_session(session, SWITCH_MEDIA_TYPE_AUDIO);
+		fst_requires(rtp_session);
+
+		rtp_test_init_frame(&write_frame, &session);
+
+		switch_rtp_clear_flag(rtp_session, SWITCH_RTP_FLAG_PAUSE);
+
+		if (switch_socket_create(&sock_rtp, AF_INET, SOCK_DGRAM, 0, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
+			fst_requires(0); /*exit*/ 
+		}
+
+		switch_sockaddr_new(&sock_addr, rx_host, audio_rx_port, switch_core_session_get_pool(session));
+		fst_requires(sock_addr);
+
+		switch_rtp_set_remote_address(rtp_session, tx_host, switch_sockaddr_get_port(sock_addr), 0, SWITCH_FALSE, &str_err);
+		switch_rtp_reset(rtp_session);
+		switch_mutex_init(&timeout_mutex, SWITCH_MUTEX_NESTED, fst_pool);
+
+		/* send 3 packets then wait and expect RTP timeout */
+		while ((packet = pcap_next(pcap, &pcap_header)) && x < 3) {
+			/*assume only UDP/RTP packets in the pcap*/
+			uint32_t rcvd_datalen = pcap_header.caplen;
+			size_t len;
+			switch_size_t tmp_len;
+
+			int diff_us = (pcap_header.ts.tv_sec-prev_ts.tv_sec)*1000000+(pcap_header.ts.tv_usec-prev_ts.tv_usec);
+			if (diff_us > 0) {
+				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "SENT pkt diff: %d us\n", diff_us);
+				usleep(diff_us);
+			}
+
+			x++;
+
+			prev_ts = pcap_header.ts;
+
+			len = pcap_header.caplen;
+
+			if (len <= 42) {
+				continue;
+			} 
+
+			ip = (struct sniff_ip*)(packet + 14);
+			size_ip = IP_HL(ip) * 4;
+
+			jump_over = 14 /*SIZE_ETHERNET*/ + size_ip /*IP HDR size*/ + 8 /* UDP HDR SIZE */; /* jump 42 bytes over network layers/headers */
+			packet += jump_over;
+			
+			if (packet[0] == 0x80 && packet[1] == 0 /*PCMU*/) {
+				int16_t *seq = (int16_t *)packet + 1;
+				plen = len - jump_over;
+				tmp_len = plen;
+				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Sent RTP. Packet size = [%u] seq = [%d]\n", plen, htons(*seq));
+				if (switch_socket_sendto(sock_rtp, sock_addr, MSG_CONFIRM, (const char*)packet, &tmp_len) != SWITCH_STATUS_SUCCESS) {
+					fst_requires(0);
+				}
+			}
+
+			status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags);
+			if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) {
+				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG, skip\n");
+				continue;
+			}
+
+			fst_requires(status == SWITCH_STATUS_SUCCESS);
+			fst_requires(rcvd_datalen == plen - SWITCH_RTP_HEADER_LEN);
+		}
+
+		time_of_last_packet_rcvd = switch_time_now();
+		channel = switch_core_session_get_channel(session);
+		switch_channel_set_flag(channel, CF_PROTO_HOLD);
+		switch_core_media_toggle_hold(session, 0);
+		
+		x = 600; /* 12 seconds max (hold) */
+		switch_mutex_lock(timeout_mutex);
+		while (x > 0 || !got_media_timeout) {
+			uint32_t rcvd_datalen;
+
+			switch_mutex_unlock(timeout_mutex);
+			status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags);
+			if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) {
+				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG, skip\n");
+			}
+			switch_yield(20 * 1000);
+			fst_requires(status == SWITCH_STATUS_SUCCESS);
+			x--;
+			switch_mutex_lock(timeout_mutex);
+		}
+		switch_mutex_unlock(timeout_mutex);
+
+		if (write_frame) switch_frame_free(&write_frame);
+
+		switch_rtp_destroy(&rtp_session);
+
+		rtp_test_end_call(&session);
+
+		switch_socket_close(sock_rtp);
+
+		pcap_close(pcap);
+
+		switch_event_unbind_callback(event_handler);
+
+		fst_check(got_media_timeout);
+
+		fst_check(timeout_time - time_of_last_packet_rcvd > 13000); /* consider 3 extra seconds for the tets - CI delay, load, etc */
+	}
+	FST_TEST_END()
+
 }
 FST_SUITE_END()
 }