From ee0a12f26b6636a3ef17975b46007b5980d2bdfb Mon Sep 17 00:00:00 2001
From: Andrea Gianarda <andrea.gianarda@belledonne-communications.com>
Date: Thu, 25 May 2023 11:19:13 +0200
Subject: [PATCH] Add test to verify behaviour when multiple INFO messages are
 sent back to back by both parties almost at the same time.

---
 src/conference/session/call-session.cpp  | 11 +++-
 src/conference/session/media-session.cpp | 11 ++++
 tester/call_race_conditions.c            | 78 ++++++++++++++++++++----
 tester/call_single_tester.c              | 12 ++--
 tester/call_video_tester.cpp             |  2 +-
 tester/liblinphone_tester.h              |  2 +-
 tester/message_tester.c                  |  2 +-
 tester/tester.c                          |  2 +-
 tools/python/unittests/linphonetester.py |  2 +-
 9 files changed, 96 insertions(+), 26 deletions(-)

diff --git a/src/conference/session/call-session.cpp b/src/conference/session/call-session.cpp
index 158232011b..125438d44f 100644
--- a/src/conference/session/call-session.cpp
+++ b/src/conference/session/call-session.cpp
@@ -635,15 +635,22 @@ void CallSessionPrivate::updated(bool isUpdate) {
 			         "Call updated by remote while in transcient state (Pausing/Updating/Resuming)");
 			acceptUpdate(nullptr, localState, Utils::toString(localState));
 			break;
+		case CallSession::State::End:
+		case CallSession::State::Released:
+			lWarning() << "Session [" << q
+			           << "] is going to reject the reINVITE or UPDATE because it is already in state ["
+			           << Utils::toString(state) << "]";
+			sal_error_info_set(&sei, SalReasonNoMatch, "SIP", 0, "Incompatible SDP", nullptr);
+			op->declineWithErrorInfo(&sei, nullptr);
+			sal_error_info_reset(&sei);
+			break;
 		case CallSession::State::Idle:
 		case CallSession::State::OutgoingInit:
-		case CallSession::State::End:
 		case CallSession::State::IncomingReceived:
 		case CallSession::State::PushIncomingReceived:
 		case CallSession::State::OutgoingProgress:
 		case CallSession::State::Referred:
 		case CallSession::State::Error:
-		case CallSession::State::Released:
 		case CallSession::State::EarlyUpdatedByRemote:
 		case CallSession::State::EarlyUpdating:
 			lWarning() << "Receiving reINVITE or UPDATE while in state [" << Utils::toString(state)
diff --git a/src/conference/session/media-session.cpp b/src/conference/session/media-session.cpp
index e71bd7d687..29ced9f0fb 100644
--- a/src/conference/session/media-session.cpp
+++ b/src/conference/session/media-session.cpp
@@ -696,6 +696,17 @@ bool MediaSessionPrivate::incompatibleSecurity(const std::shared_ptr<SalMediaDes
 
 void MediaSessionPrivate::updating(bool isUpdate) {
 	L_Q();
+	if ((state == CallSession::State::End) || (state == CallSession::State::Released)) {
+		lWarning() << "Session [" << q << "] is going to reject the reINVITE or UPDATE because it is already in state ["
+		           << Utils::toString(state) << "]";
+		SalErrorInfo sei;
+		memset(&sei, 0, sizeof(sei));
+		sal_error_info_set(&sei, SalReasonNoMatch, "SIP", 0, "Incompatible SDP", nullptr);
+		op->declineWithErrorInfo(&sei, nullptr);
+		sal_error_info_reset(&sei);
+		return;
+	}
+
 	std::shared_ptr<SalMediaDescription> rmd = op->getRemoteMediaDescription();
 	// Fix local parameter before creating new local media description in order to have it consistent with the offer.
 	// Note that in some case such as if we are the offerer or transition from the state UpdateByRemote to
diff --git a/tester/call_race_conditions.c b/tester/call_race_conditions.c
index daa487b5a9..4cc44081d9 100644
--- a/tester/call_race_conditions.c
+++ b/tester/call_race_conditions.c
@@ -65,19 +65,24 @@ static void call_with_video_added_by_both_parties(void) {
 	linphone_call_params_unref(pauline_params);
 	linphone_call_params_unref(marie_params);
 
-	BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallUpdating, 1, 10000));
-	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallUpdating, 1, 10000));
+	BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallUpdating, 1, liblinphone_tester_sip_timeout));
+	BC_ASSERT_TRUE(
+	    wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallUpdating, 1, liblinphone_tester_sip_timeout));
 
 	/* Marie shall transition to UpdatedByRemote (Pauline has priority per RFC3261 since she is the call-id owner)*/
-	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallUpdatedByRemote, 1, 10000));
+	BC_ASSERT_TRUE(
+	    wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallUpdatedByRemote, 1, liblinphone_tester_sip_timeout));
 
 	/* Marie will succeed with its re INVITE transaction */
-	BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallStreamsRunning, 2, 10000));
+	BC_ASSERT_TRUE(
+	    wait_for_list(lcs, &marie->stat.number_of_LinphoneCallStreamsRunning, 2, liblinphone_tester_sip_timeout));
 
 	/* Pauline shall return to Updating state*/
-	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallUpdating, 2, 10000));
+	BC_ASSERT_TRUE(
+	    wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallUpdating, 2, liblinphone_tester_sip_timeout));
 	/* And finally succeed too */
-	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallStreamsRunning, 2, 10000));
+	BC_ASSERT_TRUE(
+	    wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallStreamsRunning, 2, liblinphone_tester_sip_timeout));
 
 	ei = linphone_call_get_error_info(marie_call);
 	BC_ASSERT_TRUE(linphone_error_info_get_protocol_code(ei) == 0);
@@ -93,6 +98,51 @@ end:
 	linphone_core_manager_destroy(pauline);
 }
 
+static void call_with_info_sent_by_both_parties(void) {
+	LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc");
+	LinphoneCoreManager *pauline =
+	    linphone_core_manager_new(transport_supported(LinphoneTransportTls) ? "pauline_rc" : "pauline_tcp_rc");
+	LinphoneCall *marie_call, *pauline_call;
+	LinphoneInfoMessage *marie_info, *pauline_info;
+	bctbx_list_t *lcs = NULL;
+	bool_t call_ok;
+	int number_of_infos = 4;
+
+	lcs = bctbx_list_append(lcs, marie->lc);
+	lcs = bctbx_list_append(lcs, pauline->lc);
+
+	BC_ASSERT_TRUE(call_ok = call(pauline, marie));
+	if (!call_ok) goto end;
+
+	marie_call = linphone_core_get_current_call(marie->lc);
+	BC_ASSERT_PTR_NOT_NULL(marie_call);
+	pauline_call = linphone_core_get_current_call(pauline->lc);
+	BC_ASSERT_PTR_NOT_NULL(pauline_call);
+
+	// Send multiple INFO messages in order to be sure to have at least one 491 request pending
+	for (int i = 0; i < number_of_infos; i++) {
+		marie_info = linphone_core_create_info_message(marie->lc);
+		pauline_info = linphone_core_create_info_message(pauline->lc);
+
+		linphone_call_send_info_message(pauline_call, pauline_info);
+		linphone_call_send_info_message(marie_call, marie_info);
+
+		linphone_info_message_unref(marie_info);
+		linphone_info_message_unref(pauline_info);
+	}
+
+	BC_ASSERT_TRUE(
+	    wait_for_list(lcs, &pauline->stat.number_of_InfoReceived, number_of_infos, liblinphone_tester_sip_timeout));
+	BC_ASSERT_TRUE(
+	    wait_for_list(lcs, &marie->stat.number_of_InfoReceived, number_of_infos, liblinphone_tester_sip_timeout));
+
+	end_call(pauline, marie);
+end:
+	bctbx_list_free(lcs);
+	linphone_core_manager_destroy(marie);
+	linphone_core_manager_destroy(pauline);
+}
+
 static void call_paused_by_both_parties(void) {
 	LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc");
 	LinphoneCoreManager *pauline =
@@ -113,11 +163,11 @@ static void call_paused_by_both_parties(void) {
 	linphone_call_pause(marie_call);
 	linphone_call_pause(pauline_call);
 
-	BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallPausing, 1, 10000));
-	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallPausing, 1, 10000));
+	BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallPausing, 1, liblinphone_tester_sip_timeout));
+	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallPausing, 1, liblinphone_tester_sip_timeout));
 
-	BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallPaused, 1, 10000));
-	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallPaused, 1, 10000));
+	BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallPaused, 1, liblinphone_tester_sip_timeout));
+	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallPaused, 1, liblinphone_tester_sip_timeout));
 
 	BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallUpdatedByRemote, 1, int, "%d");
 	BC_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallPausing, 2, int, "%d");
@@ -128,8 +178,10 @@ static void call_paused_by_both_parties(void) {
 	/* both resume the call at the same time */
 	linphone_call_resume(marie_call);
 	linphone_call_resume(pauline_call);
-	BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallStreamsRunning, 2, 10000));
-	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallStreamsRunning, 2, 10000));
+	BC_ASSERT_TRUE(
+	    wait_for_list(lcs, &marie->stat.number_of_LinphoneCallStreamsRunning, 2, liblinphone_tester_sip_timeout));
+	BC_ASSERT_TRUE(
+	    wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallStreamsRunning, 2, liblinphone_tester_sip_timeout));
 	liblinphone_tester_check_rtcp(pauline, marie);
 
 	end_call(pauline, marie);
@@ -168,7 +220,6 @@ static void call_end_and_reinvite(void) {
 	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallEnd, 1, 10000));
 	BC_ASSERT_TRUE(wait_for_list(lcs, &marie->stat.number_of_LinphoneCallReleased, 1, 10000));
 	BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_LinphoneCallReleased, 1, 10000));
-
 end:
 	bctbx_list_free(lcs);
 	linphone_core_manager_destroy(marie);
@@ -177,6 +228,7 @@ end:
 
 static test_t call_race_conditions_tests[] = {
     TEST_NO_TAG("Call with video added by both parties", call_with_video_added_by_both_parties),
+    TEST_NO_TAG("Call with INFO sent by both parties", call_with_info_sent_by_both_parties),
     TEST_NO_TAG("Call paused by both parties", call_paused_by_both_parties),
     TEST_NO_TAG("Call ended and re-invited at the same time", call_end_and_reinvite)};
 
diff --git a/tester/call_single_tester.c b/tester/call_single_tester.c
index 44d73626a4..308d8f8c06 100644
--- a/tester/call_single_tester.c
+++ b/tester/call_single_tester.c
@@ -4314,8 +4314,8 @@ static void call_established_with_rejected_info(void) {
 
 		im2 = linphone_core_create_info_message(pauline->lc);
 		linphone_call_send_info_message(linphone_core_get_current_call(pauline->lc), im2);
-		BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_inforeceived, 1));
-		BC_ASSERT_EQUAL(marie->stat.number_of_inforeceived, 1, int, "%d");
+		BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_InfoReceived, 1));
+		BC_ASSERT_EQUAL(marie->stat.number_of_InfoReceived, 1, int, "%d");
 		linphone_info_message_unref(im2);
 
 		check_call_state(pauline, LinphoneCallStreamsRunning);
@@ -4354,8 +4354,8 @@ static void call_established_with_complex_rejected_operation(void) {
 		info = linphone_core_create_info_message(marie->lc);
 		linphone_call_send_info_message(linphone_core_get_current_call(marie->lc), info);
 		linphone_info_message_unref(info);
-		BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_inforeceived, 1));
-		BC_ASSERT_EQUAL(pauline->stat.number_of_inforeceived, 1, int, "%d");
+		BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_InfoReceived, 1));
+		BC_ASSERT_EQUAL(pauline->stat.number_of_InfoReceived, 1, int, "%d");
 		/*to give time for 200ok to arrive*/
 		wait_for_until(marie->lc, pauline->lc, NULL, 0, 1000);
 
@@ -4425,8 +4425,8 @@ static void call_established_with_rejected_info_during_reinvite(void) {
 		info = linphone_core_create_info_message(marie->lc);
 		linphone_call_send_info_message(linphone_core_get_current_call(marie->lc), info);
 		linphone_info_message_unref(info);
-		BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_inforeceived, 1));
-		BC_ASSERT_EQUAL(pauline->stat.number_of_inforeceived, 1, int, "%d");
+		BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_InfoReceived, 1));
+		BC_ASSERT_EQUAL(pauline->stat.number_of_InfoReceived, 1, int, "%d");
 		/*to give time for 200ok to arrive*/
 		wait_for_until(marie->lc, pauline->lc, NULL, 0, 1000);
 
diff --git a/tester/call_video_tester.cpp b/tester/call_video_tester.cpp
index c28114e83d..5df9ee23a4 100644
--- a/tester/call_video_tester.cpp
+++ b/tester/call_video_tester.cpp
@@ -1789,7 +1789,7 @@ static void multiple_early_media(void) {
 		info = linphone_core_create_info_message(marie1->lc);
 		linphone_call_send_info_message(marie1_call, info);
 		linphone_info_message_unref(info);
-		BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_inforeceived, 1, 3000));
+		BC_ASSERT_TRUE(wait_for_list(lcs, &pauline->stat.number_of_InfoReceived, 1, 3000));
 	}
 
 	end_call(pauline, marie1);
diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h
index 224722283b..dac5b88d40 100644
--- a/tester/liblinphone_tester.h
+++ b/tester/liblinphone_tester.h
@@ -380,7 +380,7 @@ typedef struct _stats {
 	int number_of_LinphoneConsolidatedPresenceDoNotDisturb;
 	int number_of_LinphoneConsolidatedPresenceOffline;
 
-	int number_of_inforeceived;
+	int number_of_InfoReceived;
 	LinphoneInfoMessage *last_received_info_message;
 
 	int number_of_LinphoneSubscriptionIncomingReceived;
diff --git a/tester/message_tester.c b/tester/message_tester.c
index 487b775fd6..548eb48742 100644
--- a/tester/message_tester.c
+++ b/tester/message_tester.c
@@ -2347,7 +2347,7 @@ void info_message_base(bool_t with_content) {
 		linphone_call_send_info_message(linphone_core_get_current_call(marie->lc), info);
 		linphone_info_message_unref(info);
 
-		BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_inforeceived, 1));
+		BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_InfoReceived, 1));
 
 		BC_ASSERT_PTR_NOT_NULL(pauline->stat.last_received_info_message);
 		hvalue = linphone_info_message_get_header(pauline->stat.last_received_info_message, "Weather");
diff --git a/tester/tester.c b/tester/tester.c
index ca36c07dd6..97d8bb1683 100644
--- a/tester/tester.c
+++ b/tester/tester.c
@@ -3517,7 +3517,7 @@ void info_message_received(LinphoneCore *lc, BCTBX_UNUSED(LinphoneCall *call), c
 		linphone_info_message_unref(counters->last_received_info_message);
 	}
 	counters->last_received_info_message = linphone_info_message_copy(msg);
-	counters->number_of_inforeceived++;
+	counters->number_of_InfoReceived++;
 }
 
 void linphone_subscription_state_change(LinphoneCore *lc, LinphoneEvent *lev, LinphoneSubscriptionState state) {
diff --git a/tools/python/unittests/linphonetester.py b/tools/python/unittests/linphonetester.py
index 15eb9ab4e1..2430823311 100644
--- a/tools/python/unittests/linphonetester.py
+++ b/tools/python/unittests/linphonetester.py
@@ -281,7 +281,7 @@ class CoreManagerStats:
         self.number_of_LinphonePresenceActivityWorship = 0
         self.last_received_presence = None
 
-        self.number_of_inforeceived = 0
+        self.number_of_InfoReceived = 0
 
         self.number_of_LinphoneSubscriptionIncomingReceived = 0
         self.number_of_LinphoneSubscriptionOutgoingInit = 0
-- 
GitLab