From ae33af40ef0717256c6b8d5e9a3f697cc38aadc6 Mon Sep 17 00:00:00 2001
From: Andrea Gianarda <andrea.gianarda@belledonne-communications.com>
Date: Mon, 25 Nov 2024 16:05:10 +0100
Subject: [PATCH] Verify behaviour when remote puts down all inactive streams
 and disable all capabilities in the current params in such a scenario

---
 coreapi/local_conference.cpp             |   6 +-
 src/conference/session/media-session.cpp |  44 ++++++---
 tester/call_single_tester.c              | 114 +++++++++++++++++++++--
 tester/capability_negotiation_tester.cpp |  12 ++-
 4 files changed, 148 insertions(+), 28 deletions(-)

diff --git a/coreapi/local_conference.cpp b/coreapi/local_conference.cpp
index 0d8ceb718c..50377bd042 100644
--- a/coreapi/local_conference.cpp
+++ b/coreapi/local_conference.cpp
@@ -1839,9 +1839,9 @@ bool LocalConference::validateNewParameters(const LinphonePrivate::ConferencePar
 		return false;
 	}
 
-	if (confParams->getConferenceAddress() != newConfParams.getConferenceAddress()) {
-		lError() << "Conference address change is not allowed: actual " << confParams->getConferenceAddress()
-		         << " new value " << newConfParams.getConferenceAddress();
+	if (*confParams->getConferenceAddress() != *newConfParams.getConferenceAddress()) {
+		lError() << "Conference address change is not allowed: actual " << *confParams->getConferenceAddress()
+		         << " new value " << *newConfParams.getConferenceAddress();
 		return false;
 	}
 
diff --git a/src/conference/session/media-session.cpp b/src/conference/session/media-session.cpp
index bfeda2e8ae..6c8caf3311 100644
--- a/src/conference/session/media-session.cpp
+++ b/src/conference/session/media-session.cpp
@@ -275,7 +275,8 @@ void MediaSessionPrivate::accepted() {
 	/* Reset the internal call update flag, so it doesn't risk to be copied and used in further re-INVITEs */
 	getParams()->getPrivate()->setInternalCallUpdate(false);
 	std::shared_ptr<SalMediaDescription> rmd = op->getRemoteMediaDescription();
-	std::shared_ptr<SalMediaDescription> &md = op->getFinalMediaDescription();
+	std::shared_ptr<SalMediaDescription> cmd = op->getFinalMediaDescription();
+	std::shared_ptr<SalMediaDescription> md = cmd;
 	if (!md && (prevState == CallSession::State::OutgoingEarlyMedia) && resultDesc) {
 		lInfo() << "Using early media SDP since none was received with the 200 OK";
 		md = resultDesc;
@@ -285,7 +286,7 @@ void MediaSessionPrivate::accepted() {
 	bool updatingConference = conferenceInfo && (conferenceInfo->getState() == ConferenceInfo::State::Updated);
 	// Do not reject media session if the client is trying to update a conference
 	if (rejectMediaSession(rmd, md) && !updatingConference) {
-		lInfo() << "Rejecting media session";
+		lInfo() << "Rejecting media session [" << this << "]";
 		md = nullptr;
 	}
 
@@ -437,6 +438,7 @@ void MediaSessionPrivate::accepted() {
 					default:
 						lInfo() << "Incompatible SDP answer received, restoring previous state ["
 						        << Utils::toString(prevState) << "]";
+						resultDesc = cmd;
 						setState(prevState, "Incompatible media parameters.");
 						break;
 				}
@@ -797,8 +799,8 @@ void MediaSessionPrivate::updating(bool isUpdate) {
 		SalErrorInfo sei;
 		memset(&sei, 0, sizeof(sei));
 		expectMediaInAck = false;
-		std::shared_ptr<SalMediaDescription> &md = op->getFinalMediaDescription();
-		if (rejectMediaSession(rmd, md)) {
+		std::shared_ptr<SalMediaDescription> &cmd = op->getFinalMediaDescription();
+		if (rejectMediaSession(rmd, cmd)) {
 			lWarning() << "Session [" << q << "] is going to be rejected because of an incompatible negotiated SDP";
 			sal_error_info_set(&sei, SalReasonNotAcceptable, "SIP", 0, "Incompatible SDP", nullptr);
 			op->declineWithErrorInfo(&sei, nullptr);
@@ -806,8 +808,8 @@ void MediaSessionPrivate::updating(bool isUpdate) {
 			return;
 		}
 		std::shared_ptr<SalMediaDescription> &prevResultDesc = resultDesc;
-		if (isUpdate && prevResultDesc && md) {
-			int diff = md->equal(*prevResultDesc);
+		if (isUpdate && prevResultDesc && cmd) {
+			int diff = cmd->equal(*prevResultDesc);
 			if (diff & (SAL_MEDIA_DESCRIPTION_CRYPTO_POLICY_CHANGED | SAL_MEDIA_DESCRIPTION_STREAMS_CHANGED)) {
 				lWarning() << "Cannot accept this update, it is changing parameters that require user approval";
 				sal_error_info_set(&sei, SalReasonUnknown, "SIP", 504,
@@ -3504,8 +3506,14 @@ void MediaSessionPrivate::setTerminated() {
 
 LinphoneStatus MediaSessionPrivate::startAcceptUpdate(CallSession::State nextState, const string &stateInfo) {
 	op->accept();
-	std::shared_ptr<SalMediaDescription> &md = op->getFinalMediaDescription();
-	if (md && !md->isEmpty()) updateStreams(md, nextState);
+	std::shared_ptr<SalMediaDescription> &cmd = op->getFinalMediaDescription();
+	if (cmd) {
+		if (cmd->isEmpty()) {
+			resultDesc = cmd;
+		} else {
+			updateStreams(cmd, nextState);
+		}
+	}
 	setState(nextState, stateInfo);
 	getCurrentParams()->getPrivate()->setInConference(getParams()->getPrivate()->getInConference());
 
@@ -3818,6 +3826,10 @@ void MediaSessionPrivate::updateCurrentParams() const {
 		} else {
 			getCurrentParams()->enableRealtimeText(false);
 		}
+	} else {
+		getCurrentParams()->enableAudio(false);
+		getCurrentParams()->enableVideo(false);
+		getCurrentParams()->enableRealtimeText(false);
 	}
 	getCurrentParams()->getPrivate()->setUpdateCallWhenIceCompleted(isUpdateSentWhenIceCompleted());
 }
@@ -3895,7 +3907,9 @@ LinphoneStatus MediaSessionPrivate::startAccept() {
 		}
 		updateStreams(newMd, CallSession::State::StreamsRunning);
 		setState(CallSession::State::StreamsRunning, "Connected (streams running)");
-	} else expectMediaInAck = true;
+	} else {
+		expectMediaInAck = true;
+	}
 
 	return 0;
 }
@@ -4835,7 +4849,7 @@ LinphoneStatus MediaSession::update(const MediaSessionParams *msp,
 		                             isCapabilityNegotiationReInvite);
 		const auto &localDesc = d->localDesc;
 
-		auto updateCompletionTask = [this, method, subject, localDesc]() -> LinphoneStatus {
+		auto updateCompletionTask = [this, method, subject, localDesc, isOfferer]() -> LinphoneStatus {
 			L_D();
 
 			CallSession::State previousState = d->state;
@@ -4855,17 +4869,17 @@ LinphoneStatus MediaSession::update(const MediaSessionParams *msp,
 			// therefore the local description must be updated to include ICE candidates for every stream
 			const auto currentLocalDesc = d->localDesc;
 			d->localDesc = localDesc;
-			d->updateLocalMediaDescriptionFromIce(!getCore()->getCCore()->sip_conf.sdp_200_ack);
+			d->updateLocalMediaDescriptionFromIce(isOfferer);
 
-			if (getCore()->getCCore()->sip_conf.sdp_200_ack) {
-				d->op->setLocalMediaDescription(nullptr);
-			} else {
+			if (isOfferer) {
 				d->op->setLocalMediaDescription(d->localDesc);
+			} else {
+				d->op->setLocalMediaDescription(nullptr);
 			}
 			LinphoneStatus res = d->startUpdate(method, subject);
 
 			d->localDesc = currentLocalDesc;
-			if (getCore()->getCCore()->sip_conf.sdp_200_ack) {
+			if (!isOfferer) {
 				/* We are NOT offering, set local media description after sending the call so that we are ready to
 				 * process the remote offer when it will arrive. */
 				d->op->setLocalMediaDescription(d->localDesc);
diff --git a/tester/call_single_tester.c b/tester/call_single_tester.c
index c5064e4684..d7e4d539d2 100644
--- a/tester/call_single_tester.c
+++ b/tester/call_single_tester.c
@@ -382,8 +382,8 @@ static void simple_call_with_video_declined(void) {
 		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_lparams));
 		const LinphoneCallParams *call_rparams = linphone_call_get_remote_params(marie_call);
 		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_rparams));
-		const LinphoneCallParams *call_params = linphone_call_get_current_params(marie_call);
-		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_params));
+		const LinphoneCallParams *call_cparams = linphone_call_get_current_params(marie_call);
+		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_cparams));
 	}
 	LinphoneCall *pauline_call = linphone_core_get_call_by_remote_address2(pauline->lc, marie->identity);
 	BC_ASSERT_PTR_NOT_NULL(pauline_call);
@@ -427,8 +427,8 @@ static void simple_call_with_video_declined(void) {
 	if (marie_call) {
 		const LinphoneCallParams *call_rparams = linphone_call_get_remote_params(marie_call);
 		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_rparams));
-		const LinphoneCallParams *call_params = linphone_call_get_current_params(marie_call);
-		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_params));
+		const LinphoneCallParams *call_cparams = linphone_call_get_current_params(marie_call);
+		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_cparams));
 	}
 	if (pauline_call) {
 		const LinphoneCallParams *call_lparams = linphone_call_get_params(pauline_call);
@@ -1241,6 +1241,101 @@ static void call_with_no_audio_codec(void) {
 	linphone_core_manager_destroy(caller);
 }
 
+static void call_with_no_active_stream_on_reinvite(void) {
+	LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc");
+	LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_tcp_rc");
+
+	LinphoneVideoActivationPolicy *vpol = linphone_factory_create_video_activation_policy(linphone_factory_get());
+	linphone_video_activation_policy_set_automatically_initiate(vpol, TRUE);
+	linphone_video_activation_policy_set_automatically_accept(vpol, TRUE);
+	linphone_core_set_video_activation_policy(marie->lc, vpol);
+	linphone_core_set_video_activation_policy(pauline->lc, vpol);
+	linphone_video_activation_policy_unref(vpol);
+
+	linphone_core_enable_video_capture(marie->lc, TRUE);
+	linphone_core_enable_video_display(marie->lc, TRUE);
+	linphone_core_set_video_device(marie->lc, liblinphone_tester_mire_id);
+
+	linphone_core_enable_video_capture(pauline->lc, TRUE);
+	linphone_core_enable_video_display(pauline->lc, TRUE);
+	linphone_core_set_video_device(pauline->lc, liblinphone_tester_mire_id);
+
+	BC_ASSERT_TRUE(call(marie, pauline));
+
+	LinphoneCall *marie_call = linphone_core_get_call_by_remote_address2(marie->lc, pauline->identity);
+	BC_ASSERT_PTR_NOT_NULL(marie_call);
+	if (marie_call) {
+		const LinphoneCallParams *call_lparams = linphone_call_get_params(marie_call);
+		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_lparams));
+		const LinphoneCallParams *call_rparams = linphone_call_get_remote_params(marie_call);
+		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
+		const LinphoneCallParams *call_cparams = linphone_call_get_current_params(marie_call);
+		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_cparams));
+	}
+	LinphoneCall *pauline_call = linphone_core_get_call_by_remote_address2(pauline->lc, marie->identity);
+	BC_ASSERT_PTR_NOT_NULL(pauline_call);
+	if (pauline_call) {
+		const LinphoneCallParams *call_lparams = linphone_call_get_params(pauline_call);
+		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_lparams));
+		const LinphoneCallParams *call_rparams = linphone_call_get_remote_params(pauline_call);
+		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
+		const LinphoneCallParams *call_cparams = linphone_call_get_current_params(pauline_call);
+		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_cparams));
+	}
+
+	linphone_config_set_int(linphone_core_get_config(pauline->lc), "sip", "defer_update_default", TRUE);
+
+	stats initial_pauline_stat = pauline->stat;
+	stats initial_marie_stat = marie->stat;
+	LinphoneCallParams *marie_params = linphone_core_create_call_params(marie->lc, marie_call);
+	linphone_call_update(marie_call, marie_params);
+	linphone_call_params_unref(marie_params);
+
+	BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallUpdatedByRemote,
+	                        initial_pauline_stat.number_of_LinphoneCallUpdatedByRemote + 1));
+
+	int pauline_defer_update =
+	    !!linphone_config_get_int(linphone_core_get_config(pauline->lc), "sip", "defer_update_default", FALSE);
+	BC_ASSERT_TRUE(pauline_defer_update);
+	if (pauline_defer_update == TRUE) {
+		LinphoneCallParams *pauline_params = linphone_core_create_call_params(pauline->lc, pauline_call);
+		linphone_call_params_enable_audio(pauline_params, FALSE);
+		linphone_call_params_enable_video(pauline_params, FALSE);
+		linphone_call_accept_update(pauline_call, pauline_params);
+		linphone_call_params_unref(pauline_params);
+	}
+	BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallUpdating,
+	                        initial_marie_stat.number_of_LinphoneCallUpdating + 1));
+	BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &pauline->stat.number_of_LinphoneCallStreamsRunning,
+	                        initial_pauline_stat.number_of_LinphoneCallStreamsRunning + 1));
+	BC_ASSERT_TRUE(wait_for(marie->lc, pauline->lc, &marie->stat.number_of_LinphoneCallStreamsRunning,
+	                        initial_marie_stat.number_of_LinphoneCallStreamsRunning + 1));
+
+	if (marie_call) {
+		const LinphoneCallParams *call_rparams = linphone_call_get_remote_params(marie_call);
+		BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_rparams));
+		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_rparams));
+		const LinphoneCallParams *call_cparams = linphone_call_get_current_params(marie_call);
+		BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_cparams));
+		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_cparams));
+	}
+	if (pauline_call) {
+		const LinphoneCallParams *call_lparams = linphone_call_get_params(pauline_call);
+		BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_lparams));
+		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_lparams));
+		const LinphoneCallParams *call_rparams = linphone_call_get_remote_params(pauline_call);
+		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
+		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
+		const LinphoneCallParams *call_cparams = linphone_call_get_current_params(pauline_call);
+		BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_cparams));
+		BC_ASSERT_FALSE(linphone_call_params_video_enabled(call_cparams));
+	}
+
+	end_call(marie, pauline);
+	linphone_core_manager_destroy(marie);
+	linphone_core_manager_destroy(pauline);
+}
+
 static void simple_call_compatibility_mode(void) {
 	char route[256];
 	LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc");
@@ -7296,9 +7391,9 @@ static void call_with_audio_stream_added_later_on(void) {
 		const LinphoneCallParams *call_rparams = linphone_call_get_remote_params(marie_call);
 		BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_rparams));
 		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
-		const LinphoneCallParams *call_params = linphone_call_get_current_params(marie_call);
-		BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_params));
-		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
+		const LinphoneCallParams *call_cparams = linphone_call_get_current_params(marie_call);
+		BC_ASSERT_FALSE(linphone_call_params_audio_enabled(call_cparams));
+		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_cparams));
 	}
 	if (pauline_call) {
 		const LinphoneCallParams *call_lparams = linphone_call_get_params(pauline_call);
@@ -7332,8 +7427,8 @@ static void call_with_audio_stream_added_later_on(void) {
 		const LinphoneCallParams *call_rparams = linphone_call_get_remote_params(marie_call);
 		BC_ASSERT_TRUE(linphone_call_params_audio_enabled(call_rparams));
 		BC_ASSERT_TRUE(linphone_call_params_video_enabled(call_rparams));
-		const LinphoneCallParams *call_params = linphone_call_get_current_params(marie_call);
-		BC_ASSERT_TRUE(linphone_call_params_audio_enabled(call_params));
+		const LinphoneCallParams *call_cparams = linphone_call_get_current_params(marie_call);
+		BC_ASSERT_TRUE(linphone_call_params_audio_enabled(call_cparams));
 	}
 	if (pauline_call) {
 		const LinphoneCallParams *call_lparams = linphone_call_get_params(pauline_call);
@@ -7806,6 +7901,7 @@ static test_t call2_tests[] = {
     TEST_NO_TAG("Call with specified codec bitrate", call_with_specified_codec_bitrate),
     TEST_NO_TAG("Call with maxptime", call_with_maxptime),
     TEST_NO_TAG("Call with no audio codec", call_with_no_audio_codec),
+    TEST_NO_TAG("Call with no active stream on reINVITE", call_with_no_active_stream_on_reinvite),
     TEST_NO_TAG("Call with in-dialog UPDATE request", call_with_in_dialog_update),
     TEST_NO_TAG("Call with in-dialog very early call request", call_with_very_early_call_update),
     TEST_NO_TAG("Call with in-dialog codec change", call_with_in_dialog_codec_change),
diff --git a/tester/capability_negotiation_tester.cpp b/tester/capability_negotiation_tester.cpp
index 99467e0d14..aac09f155a 100644
--- a/tester/capability_negotiation_tester.cpp
+++ b/tester/capability_negotiation_tester.cpp
@@ -195,8 +195,10 @@ LinphoneCoreManager *create_core_mgr_with_capability_negotiation_setup(const cha
 #ifdef VIDEO_ENABLED
 		// important: VP8 has really poor performances with the mire camera, at least
 		// on iOS - so when ever h264 is available, let's use it instead
-		if (linphone_core_get_payload_type(mgr->lc, "h264", -1, -1) != NULL) {
+		LinphonePayloadType *h264_payload = linphone_core_get_payload_type(mgr->lc, "h264", -1, -1);
+		if (h264_payload != NULL) {
 			disable_all_video_codecs_except_one(mgr->lc, "h264");
+			linphone_payload_type_unref(h264_payload);
 		}
 
 		linphone_core_set_video_device(mgr->lc, liblinphone_tester_mire_id);
@@ -483,6 +485,8 @@ void pause_resume_calls(LinphoneCoreManager *caller, LinphoneCoreManager *callee
 		LinphoneMediaEncryption callerEncryption =
 		    linphone_call_params_get_media_encryption(linphone_call_get_current_params(callerCall));
 
+		ms_message("%s pauses call with %s", linphone_core_get_identity(callee->lc),
+		           linphone_core_get_identity(caller->lc));
 		// Pause callee call
 		BC_ASSERT_TRUE(pause_call_1(callee, calleeCall, caller, callerCall));
 		wait_for_until(callee->lc, caller->lc, NULL, 5, 10000);
@@ -493,6 +497,8 @@ void pause_resume_calls(LinphoneCoreManager *caller, LinphoneCoreManager *callee
 		stats caller_stat = caller->stat;
 		stats callee_stat = callee->stat;
 
+		ms_message("%s resumes call with %s", linphone_core_get_identity(callee->lc),
+		           linphone_core_get_identity(caller->lc));
 		linphone_call_resume(calleeCall);
 		BC_ASSERT_TRUE(wait_for(callee->lc, caller->lc, &callee->stat.number_of_LinphoneCallStreamsRunning,
 		                        callee_stat.number_of_LinphoneCallStreamsRunning + 1));
@@ -520,6 +526,8 @@ void pause_resume_calls(LinphoneCoreManager *caller, LinphoneCoreManager *callee
 		check_stream_encryption(calleeCall);
 
 		// Pause caller call
+		ms_message("%s pauses call with %s", linphone_core_get_identity(caller->lc),
+		           linphone_core_get_identity(callee->lc));
 		BC_ASSERT_TRUE(pause_call_1(caller, callerCall, callee, calleeCall));
 		wait_for_until(callee->lc, caller->lc, NULL, 5, 10000);
 
@@ -529,6 +537,8 @@ void pause_resume_calls(LinphoneCoreManager *caller, LinphoneCoreManager *callee
 		caller_stat = caller->stat;
 		callee_stat = callee->stat;
 
+		ms_message("%s resumes call with %s", linphone_core_get_identity(caller->lc),
+		           linphone_core_get_identity(callee->lc));
 		linphone_call_resume(callerCall);
 		BC_ASSERT_TRUE(wait_for(callee->lc, caller->lc, &callee->stat.number_of_LinphoneCallStreamsRunning,
 		                        callee_stat.number_of_LinphoneCallStreamsRunning + 1));
-- 
GitLab