From 6ae595c914a072feeeb50a8af75d98a29cfcfca8 Mon Sep 17 00:00:00 2001
From: Andrea Gianarda <andrea.gianarda@belledonne-communications.com>
Date: Mon, 10 Mar 2025 11:25:34 +0100
Subject: [PATCH] Do not add any attribute such as content, label or rtpmap if
 a stream is inactive If the offerer doesn't put down a content of the main
 stream when the call is in a conference, then append the main stream instead
 of searching for the best one in the offer as this search might hit a
 participant stream Improve layout deduction logic: if the client SDP has no
 stream with main content but at least one recvonly stream without mlabel,
 then guess the client layout to be Grid Rework thumbnail stream index search
 by looking only at the client SDP Add function that verify conference info
 deletion on the server

---
 coreapi/callbacks.c                           |   2 +
 src/conference/participant-device.cpp         |   7 +-
 src/conference/session/media-session.cpp      | 397 ++++++++++--------
 src/conference/session/media-session.h        |   6 +-
 src/conference/session/video-stream.cpp       |   6 +-
 src/core/core.cpp                             |   2 +-
 src/event/event-publish.cpp                   |   5 +-
 src/event/event.cpp                           |  16 +-
 src/sal/sal_stream_description.cpp            |   3 +-
 tester/local-conference-tester-functions.cpp  | 200 ++++-----
 tester/local-conference-tester-functions.h    |   5 +
 tester/local-scheduled-conference-tester.cpp  |  54 +--
 .../local-transferred-conference-tester.cpp   |  12 +-
 tester/shared_tester_functions.cpp            |  10 +-
 14 files changed, 345 insertions(+), 380 deletions(-)

diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c
index dd4cd5470f..51e4041405 100644
--- a/coreapi/callbacks.c
+++ b/coreapi/callbacks.c
@@ -1144,6 +1144,8 @@ static void on_publish_response(SalOp *op) {
 	const SalErrorInfo *ei = op->getErrorInfo();
 
 	if (lev == NULL) return;
+	LinphoneCore *lc = static_cast<LinphoneCore *>(op->getSal()->getUserPointer());
+	if (linphone_core_get_global_state(lc) != LinphoneGlobalOn) return;
 	if (ei->reason == SalReasonNone) {
 		if (linphone_event_get_publish_state(lev) != LinphonePublishTerminating) {
 			SalPublishOp *publishOp = static_cast<SalPublishOp *>(op);
diff --git a/src/conference/participant-device.cpp b/src/conference/participant-device.cpp
index 7dc17a40b6..5b903424e8 100644
--- a/src/conference/participant-device.cpp
+++ b/src/conference/participant-device.cpp
@@ -220,7 +220,6 @@ bool ParticipantDevice::setThumbnailStreamSsrc(uint32_t newSsrc) {
 			lInfo() << "Setting thumbnail stream ssrc of " << *this << " to " << newSsrc;
 		}
 	}
-
 	return changed;
 }
 
@@ -606,8 +605,8 @@ std::set<LinphoneStreamType> ParticipantDevice::updateMediaCapabilities() {
 					audioDir = getStreamDirectionFromSession(LinphoneStreamTypeAudio);
 					videoDir = getStreamDirectionFromSession(LinphoneStreamTypeVideo);
 					textDir = getStreamDirectionFromSession(LinphoneStreamTypeText);
-					thumbnailDir =
-					    mMediaSession->getDirectionOfStream(MediaSessionPrivate::ThumbnailVideoContentAttribute);
+					thumbnailDir = mMediaSession->getDirectionOfStream(
+					    MediaSessionPrivate::ThumbnailVideoContentAttribute, getThumbnailStreamLabel());
 				}
 
 				updateSsrc = isInConference;
@@ -649,7 +648,7 @@ std::set<LinphoneStreamType> ParticipantDevice::updateMediaCapabilities() {
 			mediaCapabilityChanged.insert(LinphoneStreamTypeVideo);
 		}
 
-		bool updateThumbnailSsrc = ((resultThumbnailDir != LinphoneMediaDirectionInactive) || (thumbnailSsrc == 0));
+		bool updateThumbnailSsrc = (resultThumbnailDir != LinphoneMediaDirectionInactive);
 		if (setThumbnailStreamSsrc(thumbnailSsrc) && updateThumbnailSsrc) {
 			mediaCapabilityChanged.insert(LinphoneStreamTypeVideo);
 		}
diff --git a/src/conference/session/media-session.cpp b/src/conference/session/media-session.cpp
index 6c20061c6f..e0c7f8025c 100644
--- a/src/conference/session/media-session.cpp
+++ b/src/conference/session/media-session.cpp
@@ -1791,31 +1791,36 @@ void MediaSessionPrivate::fillConferenceParticipantStream(SalStreamDescription &
 					}
 				}
 			}
-			if (dir == SalStreamInactive) {
-				lWarning() << *q << "Setting " << std::string(sal_stream_type_to_string(type))
-				           << " stream of participant device " << dev->getAddress() << " to inactive (label " << label
-				           << " and content " << content
-				           << ") because he or she doesn't have the send component in its stream capabilities";
+			bool isInactive = (dir == SalStreamInactive);
+			if (isInactive) {
+				lWarning() << *q << ": Setting " << std::string(sal_stream_type_to_string(type)) << " stream of "
+				           << *dev << " to inactive (label " << label << " and content " << content
+				           << ") because the send component is disabled in its stream capabilities";
 			}
 			cfg.dir = dir;
 			if (isVideoStream) {
 				validateVideoStreamDirection(cfg);
 			}
-			if (getParams()->rtpBundleEnabled() && (dir != SalStreamInactive))
-				addStreamToBundle(md, newStream, cfg, mid);
+			if (!isInactive) {
+				if (getParams()->rtpBundleEnabled()) {
+					addStreamToBundle(md, newStream, cfg, mid);
+				}
+				newStream.setSupportedEncryptions(encs);
+			}
 			cfg.replacePayloads(l);
 			newStream.addActualConfiguration(cfg);
-			newStream.setSupportedEncryptions(encs);
 			fillRtpParameters(newStream);
+			lInfo() << "Add stream of type " << sal_stream_type_to_string(type) << " for " << *dev << " (label "
+			        << label << " and content " << content << ") on local offer of " << *q;
 			success = true;
 		}
 		PayloadTypeHandler::clearPayloadList(l);
 	}
 
 	if (!success) {
-		lInfo() << "Don't put video stream for device in conference with address "
-		        << (dev ? dev->getAddress()->toString() : "sip:") << " on local offer for CallSession [" << q
-		        << "] because no valid payload has been found or device is not valid (pointer " << dev << ")";
+		lInfo() << "Don't put stream of type " << sal_stream_type_to_string(type) << " of device with address "
+		        << (dev ? dev->getAddress()->toString() : "sip:") << " on local offer of " << *q
+		        << " because no valid payload has been found or device is not valid (pointer " << dev << ")";
 		cfg.dir = SalStreamInactive;
 		newStream.disable();
 		newStream.type = type;
@@ -1872,7 +1877,12 @@ void MediaSessionPrivate::fillLocalStreamDescription(SalStreamDescription &strea
 				cfg.frame_marking_extension_id = RTP_EXTENSION_FRAME_MARKING;
 			}
 		}
-		if (getParams()->rtpBundleEnabled()) addStreamToBundle(md, stream, cfg, mid);
+		bool isInactive = (dir == SalStreamInactive);
+		if (!isInactive) {
+			if (getParams()->rtpBundleEnabled()) {
+				addStreamToBundle(md, stream, cfg, mid);
+			}
+		}
 
 		// Only used for testing
 		if (q->getCore()->getCCore()->goog_remb_enabled) cfg.rtcp_fb.goog_remb_enabled = TRUE;
@@ -1977,21 +1987,16 @@ SalStreamDescription &MediaSessionPrivate::addStreamToMd(std::shared_ptr<SalMedi
 		}
 	}
 
-	if (streamIdx < 0) {
-		if (freeSlot < 0) {
-			md->streams.resize(currentMdSize + 1);
-			return md->streams[currentMdSize];
-		} else {
-			return md->streams[static_cast<size_t>(freeSlot)];
-		}
-	} else {
+	// Stream position preference
+	if (streamIdx >= 0) {
 		const auto &idx = static_cast<decltype(md->streams)::size_type>(streamIdx);
 		try {
 			auto stream = md->streams.at(idx);
-			// If a stream at the index requested in the the function argument has already been allocated and it is
+			// If a stream at the index requested in the function argument has already been allocated and it is
 			// active, then it must be replaced.
 			if ((stream.getDirection() != SalStreamInactive) && oldMd) {
 				const auto &currentStreamLabel = stream.getLabel();
+				std::string oldStreamLabel;
 				bool currentStreamLabelEmpty = currentStreamLabel.empty();
 				const auto oldMdSize = oldMd->streams.size();
 				int idxOldMd = -1;
@@ -2001,7 +2006,7 @@ SalStreamDescription &MediaSessionPrivate::addStreamToMd(std::shared_ptr<SalMedi
 					    (std::find(protectedStreamNumbersOldMd.cbegin(), protectedStreamNumbersOldMd.cend(),
 					               mdStreamIdx) != protectedStreamNumbersOldMd.cend());
 					auto oldStream = oldMd->getStreamAtIdx(static_cast<unsigned int>(mdStreamIdx));
-					const auto &oldStreamLabel = oldStream.getLabel();
+					oldStreamLabel = oldStream.getLabel();
 					bool oldStreamLabelEmpty = oldStreamLabel.empty();
 					// Select index if either the labels match or the new and old stream have no labels and the index is
 					// not the same. In fact it may happen that a faulty core sends an SDP with multiple streams without
@@ -2024,30 +2029,35 @@ SalStreamDescription &MediaSessionPrivate::addStreamToMd(std::shared_ptr<SalMedi
 					}
 				} else {
 					if (idxOldMd == streamIdx) {
-						lFatal() << "Unable to find available free stream description index:\n- Index in previous SDP: "
-						         << idxOldMd << "\n- Guessed index: " << streamIdx;
+						lFatal()
+						    << *q
+						    << ": Unable to find available free stream description index:\n- Index in previous SDP: "
+						    << idxOldMd << " - label: " << oldStreamLabel << "\n- Guessed index: " << streamIdx
+						    << " - label: " << currentStreamLabel;
 					}
 					auto &streamToFill = addStreamToMd(md, idxOldMd, oldMd);
 					streamToFill = stream;
 				}
 			}
+			lInfo() << *q << ": Add or replace stream at index " << idx;
 			return md->streams.at(idx);
 		} catch (std::out_of_range &) {
-			// If a stream at the index requested in the the function argument has not already been allocated, resize
+			// If a stream at the index requested in the function argument has not already been allocated, resize
 			// the vector list
-			lWarning() << "The current media description has only " << currentMdSize
+			lWarning() << *q << ": The current media description has only " << currentMdSize
 			           << " streams and it has been requested to allocate a stream at index " << idx;
 			md->streams.resize(idx + 1);
 			if (oldMd) {
 				const auto oldMdSize = oldMd->streams.size();
-				lWarning() << "Keep the same type as in the previous media description for all newly allocate streams";
+				lWarning()
+				    << *q << ": Keep the same type as in the previous media description for all newly allocate streams";
 				for (decltype(md->streams)::size_type i = currentMdSize; i < idx; i++) {
 					auto &c = md->streams[i];
 					if (i < oldMdSize) {
 						const auto &s = oldMd->streams[i];
 						c.type = s.type;
 					}
-					lWarning() << "Setting " << std::string(sal_stream_type_to_string(c.type))
+					lWarning() << *q << ": Setting " << std::string(sal_stream_type_to_string(c.type))
 					           << " stream inactive at index " << i << " because of std::out_of_range.";
 					c.setDirection(SalStreamInactive);
 				}
@@ -2055,6 +2065,19 @@ SalStreamDescription &MediaSessionPrivate::addStreamToMd(std::shared_ptr<SalMedi
 			return md->streams.at(idx);
 		}
 	}
+
+	// Free slot
+	if (freeSlot >= 0) {
+		// No preferred stream position has been given but a free slot has been found
+		lInfo() << *q << ": Use free slot at index " << freeSlot;
+		return md->streams[static_cast<size_t>(freeSlot)];
+	}
+
+	// No preference has been given regarding the stream position and no free slots are available, therefore extend the
+	// media description by appending a stream at the end
+	md->streams.resize(currentMdSize + 1);
+	lInfo() << *q << ": Extend local media description to allocate stream at index " << currentMdSize;
+	return md->streams[currentMdSize];
 }
 
 void MediaSessionPrivate::addConferenceLocalParticipantStreams(bool add,
@@ -2107,15 +2130,9 @@ void MediaSessionPrivate::addConferenceLocalParticipantStreams(bool add,
 				     (deviceState == ParticipantDevice::State::OnHold));
 				if (addStream) {
 					SalStreamDescription &newStream = addStreamToMd(md, foundStreamIdx, oldMd);
-					SalStreamConfiguration cfg;
-
 					newStream.type = type;
-					newStream.setContent(content);
-
-					if (!deviceLabel.empty()) {
-						newStream.setLabel(deviceLabel);
-					}
 
+					SalStreamConfiguration cfg;
 					cfg.proto = getParams()->getMediaProto();
 
 					bool bundle_enabled = getParams()->rtpBundleEnabled();
@@ -2127,7 +2144,6 @@ void MediaSessionPrivate::addConferenceLocalParticipantStreams(bool add,
 					         : emptyList),
 					    bundle_enabled);
 					if (!l.empty()) {
-						cfg.replacePayloads(l);
 						newStream.name = "Thumbnail " + std::string(sal_stream_type_to_string(type)) + " " +
 						                 participantDevice->getAddress()->toString();
 						const auto mediaDirection =
@@ -2153,15 +2169,24 @@ void MediaSessionPrivate::addConferenceLocalParticipantStreams(bool add,
 							}
 						}
 						cfg.dir = dir;
-						if (type == SalVideo) {
-							validateVideoStreamDirection(cfg);
-
-							if (!isInLocalConference) cfg.frame_marking_extension_id = RTP_EXTENSION_FRAME_MARKING;
-						}
-						if (bundle_enabled) {
-							const std::string bundleNamePrefix((type == SalVideo) ? "vs" : "as");
-							const std::string bundleName(bundleNamePrefix + std::string("Me") + content);
-							addStreamToBundle(md, newStream, cfg, bundleName);
+						if (dir != SalStreamInactive) {
+							cfg.replacePayloads(l);
+							if (type == SalVideo) {
+								validateVideoStreamDirection(cfg);
+								if (!isInLocalConference) {
+									cfg.frame_marking_extension_id = RTP_EXTENSION_FRAME_MARKING;
+								}
+							}
+							newStream.setSupportedEncryptions(encs);
+							newStream.setContent(content);
+							if (!deviceLabel.empty()) {
+								newStream.setLabel(deviceLabel);
+							}
+							if (bundle_enabled) {
+								const std::string bundleNamePrefix((type == SalVideo) ? "vs" : "as");
+								const std::string bundleName(bundleNamePrefix + std::string("Me") + content);
+								addStreamToBundle(md, newStream, cfg, bundleName);
+							}
 						}
 					} else {
 						lInfo() << "Don't put " << std::string(sal_stream_type_to_string(type))
@@ -2171,7 +2196,6 @@ void MediaSessionPrivate::addConferenceLocalParticipantStreams(bool add,
 					}
 					PayloadTypeHandler::clearPayloadList(l);
 					newStream.addActualConfiguration(cfg);
-					newStream.setSupportedEncryptions(encs);
 					fillRtpParameters(newStream);
 				} else {
 					lWarning() << "Do not add thumbnail stream for the local participant "
@@ -2248,8 +2272,7 @@ void MediaSessionPrivate::addConferenceParticipantStreams(std::shared_ptr<SalMed
 								                           : dev->getStreamLabel(sal_stream_type_to_linphone(type));
 								// main stream has the same label as one of the thumbnail streams
 								const auto &foundStreamIdx =
-								    devLabel.empty() ? -1
-								                     : oldMd->findIdxStreamWithContent(participantContent, devLabel);
+								    devLabel.empty() ? -1 : oldMd->findIdxStreamWithLabel(type, devLabel);
 								lInfo() << "MediaSession [" << q << "] (local address " << *q->getLocalAddress()
 								        << " remote address " << *q->getRemoteAddress() << "] in " << *conference
 								        << " is adding a stream of type "
@@ -2339,9 +2362,8 @@ void MediaSessionPrivate::addConferenceParticipantStreams(std::shared_ptr<SalMed
 							newStream.rtp_port = 0;
 							newStream.rtcp_port = 0;
 							newStream.addActualConfiguration(cfg);
-							lWarning() << *q
-							           << ": New stream added as disabled and inactive because no device has been "
-							              "found with label "
+							lWarning() << *q << ": New stream added at index " << idx
+							           << " as disabled and inactive because no device has been found with label "
 							           << participantsAttrValue << " in " << *conference;
 						}
 					}
@@ -2706,9 +2728,9 @@ void MediaSessionPrivate::makeLocalMediaDescription(bool localIsOfferer,
 					videoStreamIdx = gridStreamIdxWithContent;
 				} else if (activeSpeakerStreamIdxWithContent > -1) {
 					videoStreamIdx = activeSpeakerStreamIdxWithContent;
-				} else {
-					videoStreamIdx = refMd->findIdxBestStream(SalVideo);
 				}
+				// If no stream with content has been found, then let's append the stream. There is no way for the SDK
+				// to guess where the main stream should be
 			} else {
 				videoStreamIdx = refMd->findIdxBestStream(SalVideo);
 			}
@@ -2718,8 +2740,7 @@ void MediaSessionPrivate::makeLocalMediaDescription(bool localIsOfferer,
 		fillLocalStreamDescription(videoStream, md, enableVideoStream, "Video", SalVideo, proto, videoDir, videoCodecs,
 		                           "vs",
 		                           getParams()->getPrivate()->getCustomSdpMediaAttributes(LinphoneStreamTypeVideo));
-
-		if (conference) {
+		if (conference && videoStream.enabled()) {
 			if (participantDevice &&
 			    (isInLocalConference || (!isInLocalConference && remoteContactAddress &&
 			                             remoteContactAddress->hasParam(Conference::IsFocusParameter)))) {
@@ -3667,7 +3688,7 @@ LinphoneStatus MediaSessionPrivate::pause() {
 		q->updateContactAddressInOp();
 
 		if (conference) {
-			lInfo() << "Removing participant with session " << q << " (local addres " << *q->getLocalAddress()
+			lInfo() << "Removing participant with session " << q << " (local address " << *q->getLocalAddress()
 			        << " remote address " << *q->getRemoteAddress() << ")  from conference "
 			        << *conference->getConferenceAddress();
 			// Do not preserve conference after removing the participant
@@ -3949,32 +3970,47 @@ void MediaSessionPrivate::updateCurrentParams() const {
 			getCurrentParams()->enableAudio(false);
 		}
 
-		const auto streamIdx = q->getThumbnailStreamIdx(md);
-		const auto &videoStream =
-		    (streamIdx == -1) ? md->findBestStream(SalVideo) : md->getStreamAtIdx(static_cast<unsigned int>(streamIdx));
-		if (videoStream != Utils::getEmptyConstRefObject<SalStreamDescription>()) {
-			getCurrentParams()->getPrivate()->enableImplicitRtcpFb(videoStream.hasImplicitAvpf());
-
-			const auto videoDirection = getDirFromMd(md, SalVideo);
-			getCurrentParams()->setVideoDirection(videoDirection);
+		const auto mainStreamIdx = q->getMainVideoStreamIdx(md);
+		bool mainStreamEnabled = false;
+		if (mainStreamIdx != -1) {
+			const auto &mainVideoStream = md->getStreamAtIdx(static_cast<unsigned int>(mainStreamIdx));
+			mainStreamEnabled = mainVideoStream.enabled();
+			getCurrentParams()->getPrivate()->enableImplicitRtcpFb(mainVideoStream.hasImplicitAvpf());
 
 			if (getCurrentParams()->getVideoDirection() != LinphoneMediaDirectionInactive) {
 				const std::string &rtpAddr =
-				    (videoStream.getRtpAddress().empty() == false) ? videoStream.getRtpAddress() : md->addr;
+				    (mainVideoStream.getRtpAddress().empty() == false) ? mainVideoStream.getRtpAddress() : md->addr;
 				getCurrentParams()->enableVideoMulticast(!!ms_is_multicast(rtpAddr.c_str()));
 			} else {
 				getCurrentParams()->enableVideoMulticast(false);
 			}
-			const auto enable =
-			    (conference) ? (videoDirection != LinphoneMediaDirectionInactive) : videoStream.enabled();
-			getCurrentParams()->enableVideo(enable);
 		} else {
 			getCurrentParams()->getPrivate()->enableImplicitRtcpFb(false);
-			getCurrentParams()->setVideoDirection(LinphoneMediaDirectionInactive);
 			getCurrentParams()->enableVideoMulticast(false);
-			getCurrentParams()->enableVideo(false);
 		}
 
+		const auto videoDirection = getDirFromMd(md, SalVideo);
+		getCurrentParams()->setVideoDirection(videoDirection);
+		getCurrentParams()->enableVideo((conference) ? (videoDirection != LinphoneMediaDirectionInactive)
+		                                             : mainStreamEnabled);
+
+		SalStreamDir thumbnailStreamDirection = SalStreamInactive;
+		if (conference) {
+			const auto thumbnailStreamIdx = q->getThumbnailStreamIdx();
+			if (thumbnailStreamIdx != -1) {
+				const auto &thumbnailVideoStream = md->getStreamAtIdx(static_cast<unsigned int>(thumbnailStreamIdx));
+				thumbnailStreamDirection = thumbnailVideoStream.getDirection();
+			}
+		} else {
+			thumbnailStreamDirection = SalStreamSendRecv;
+		}
+		// The camera is enabled if:
+		// - the thumbnail stream is enabled
+		// - the thumbnail stream's direction is sendrecv or sendonly
+		getCurrentParams()->enableCamera(
+		    ((thumbnailStreamDirection == SalStreamSendRecv) || (thumbnailStreamDirection == SalStreamSendOnly)) &&
+		    getCurrentParams()->videoEnabled());
+
 		const SalStreamDescription &textStream = md->findBestStream(SalText);
 		if (textStream != Utils::getEmptyConstRefObject<SalStreamDescription>()) {
 			// Direction and multicast are not supported for real-time text.
@@ -4029,7 +4065,7 @@ LinphoneStatus MediaSessionPrivate::startAccept() {
 	if (isThisNotCurrentConference || isThisNotCurrentMediaSession) {
 		if ((linphone_core_get_media_resource_mode(q->getCore()->getCCore()) == LinphoneExclusiveMediaResources) &&
 		    linphone_core_preempt_sound_resources(q->getCore()->getCCore()) != 0) {
-			lInfo() << "Delaying call to " << __func__ << " for media session (local addres " << *q->getLocalAddress()
+			lInfo() << "Delaying call to " << __func__ << " for media session (local address " << *q->getLocalAddress()
 			        << " remote address " << *q->getRemoteAddress() << ") in state " << Utils::toString(state)
 			        << " because sound resources cannot be preempted";
 			q->addPendingAction([this] {
@@ -4363,9 +4399,20 @@ ConferenceLayout MediaSession::computeConferenceLayout(const std::shared_ptr<Sal
 			const auto &params = (isInLocalConference) ? d->getRemoteParams() : d->getParams();
 			layout = params->getConferenceVideoLayout();
 		} else {
-			layout = ConferenceLayout::ActiveSpeaker;
-			lDebug() << "Unable to deduce layout from media description " << md
-			         << " - Default it to: " << Utils::toString(layout);
+			const auto &content = MediaSessionPrivate::ThumbnailVideoContentAttribute;
+			const auto direction = (isInLocalConference) ? SalStreamSendOnly : SalStreamRecvOnly;
+			if ((md->nbActiveStreamsOfType(SalVideo) > 0) && (md->findIdxStreamWithContent(content, direction) == -1)) {
+				layout = ConferenceLayout::Grid;
+				lWarning() << "No stream with a main stream content attribute has been found; nonetheless the media "
+				              "description has at least one stream active video stream and none with content "
+				           << content << " and direction " << sal_stream_dir_to_string(direction)
+				           << ". It means that the remote party is likely to be on a " << Utils::toString(layout)
+				           << " layout and is not wishing to send its camera's video stream";
+			} else {
+				layout = ConferenceLayout::ActiveSpeaker;
+				lDebug() << "Unable to deduce layout from media description " << md
+				         << " - Default it to: " << Utils::toString(layout);
+			}
 		}
 	}
 	return layout;
@@ -5183,7 +5230,7 @@ bool MediaSession::cameraEnabled() const {
 	if (iface) {
 		auto vs = iface->getVideoStream();
 		if (vs && video_stream_local_screen_sharing_enabled(vs)) {
-			auto streamIdx = getLocalThumbnailStreamIdx();
+			auto streamIdx = getThumbnailStreamIdx();
 			if (streamIdx >= 0) iface = dynamic_cast<MS2VideoControl *>(d->getStreamsGroup().getStream(streamIdx));
 		}
 	}
@@ -5200,7 +5247,7 @@ void MediaSession::enableCamera(BCTBX_UNUSED(bool value)) {
 	if (iface) {
 		auto vs = iface->getVideoStream();
 		if (vs && video_stream_local_screen_sharing_enabled(vs)) {
-			auto streamIdx = getLocalThumbnailStreamIdx();
+			auto streamIdx = getThumbnailStreamIdx();
 			if (streamIdx >= 0) iface = dynamic_cast<MS2VideoControl *>(d->getStreamsGroup().getStream(streamIdx));
 		}
 	}
@@ -5426,7 +5473,7 @@ void MediaSession::setNativePreviewWindowId(BCTBX_UNUSED(void *id)) {
 	if (iface) {
 		auto vs = iface->getVideoStream();
 		if (vs && video_stream_local_screen_sharing_enabled(vs)) {
-			auto streamIdx = getLocalThumbnailStreamIdx();
+			auto streamIdx = getThumbnailStreamIdx();
 			if (streamIdx < 0) return;
 			auto videostream = dynamic_cast<VideoControlInterface *>(d->getStreamsGroup().getStream(streamIdx));
 			videostream->setNativePreviewWindowId(id);
@@ -5442,7 +5489,7 @@ void *MediaSession::getNativePreviewVideoWindowId() const {
 	if (iface) {
 		auto vs = iface->getVideoStream();
 		if (vs && video_stream_local_screen_sharing_enabled(vs)) {
-			auto streamIdx = getLocalThumbnailStreamIdx();
+			auto streamIdx = getThumbnailStreamIdx();
 			if (streamIdx < 0) return nullptr;
 			auto videostream = dynamic_cast<VideoControlInterface *>(d->getStreamsGroup().getStream(streamIdx));
 			return videostream->getNativePreviewWindowId();
@@ -5459,7 +5506,7 @@ void *MediaSession::createNativePreviewVideoWindowId() const {
 	if (iface) {
 		auto vs = iface->getVideoStream();
 		if (vs && video_stream_local_screen_sharing_enabled(vs)) {
-			auto streamIdx = getLocalThumbnailStreamIdx();
+			auto streamIdx = getThumbnailStreamIdx();
 			if (streamIdx < 0) return nullptr;
 			auto videostream = dynamic_cast<MS2VideoControl *>(d->getStreamsGroup().getStream(streamIdx));
 			return videostream->createNativePreviewWindowId();
@@ -5533,29 +5580,41 @@ const MediaSessionParams *MediaSession::getRemoteParams() const {
 				                                                  audioStream.custom_sdp_attributes);
 			} else params->enableAudio(false);
 
-			const auto streamIdx = getThumbnailStreamIdx(md);
-			const auto &videoStream = (streamIdx == -1) ? md->findBestStream(SalVideo)
-			                                            : md->getStreamAtIdx(static_cast<unsigned int>(streamIdx));
-			if (videoStream != Utils::getEmptyConstRefObject<SalStreamDescription>()) {
-				const auto videoDir = d->getDirFromMd(md, SalVideo);
-				const auto &videoEnabled = videoStream.enabled();
-				params->enableVideo(videoEnabled || (videoDir != LinphoneMediaDirectionInactive));
-				params->setVideoDirection(videoDir);
-				params->setMediaEncryption(videoStream.hasSrtp() ? LinphoneMediaEncryptionSRTP
-				                                                 : LinphoneMediaEncryptionNone);
+			bool mainStreamEnabled = false;
+			const auto mainStreamIdx = getMainVideoStreamIdx(md);
+			SalStreamDir mainStreamDirection = SalStreamInactive;
+			if (mainStreamIdx != -1) {
+				const auto &mainVideoStream = md->getStreamAtIdx(static_cast<unsigned int>(mainStreamIdx));
+				mainStreamEnabled = mainVideoStream.enabled();
+				mainStreamDirection = mainVideoStream.getDirection();
+				params->setMediaEncryption(mainVideoStream.hasSrtp() ? LinphoneMediaEncryptionSRTP
+				                                                     : LinphoneMediaEncryptionNone);
 				params->getPrivate()->setCustomSdpMediaAttributes(LinphoneStreamTypeVideo,
-				                                                  videoStream.custom_sdp_attributes);
-				// The camera is enabled if:
-				// - the thumbnail stream is enabled
-				// - the thumbnail stream's direction is sendrecv or sendonly
-				const auto &thumbnailDirection = videoStream.getDirection();
-				params->enableCamera(
-				    ((thumbnailDirection == SalStreamSendRecv) || (thumbnailDirection == SalStreamSendOnly)) &&
-				    videoEnabled);
+				                                                  mainVideoStream.custom_sdp_attributes);
+			}
+
+			const auto videoDir = d->getDirFromMd(md, SalVideo);
+			params->setVideoDirection(videoDir);
+			params->enableVideo(mainStreamEnabled || (videoDir != LinphoneMediaDirectionInactive));
+
+			const auto conference = getCore()->findConference(getSharedFromThis(), false);
+			SalStreamDir thumbnailStreamDirection = SalStreamInactive;
+			if (conference) {
+				const auto thumbnailStreamIdx = getThumbnailStreamIdx();
+				if (thumbnailStreamIdx != -1) {
+					const auto &thumbnailVideoStream =
+					    md->getStreamAtIdx(static_cast<unsigned int>(thumbnailStreamIdx));
+					thumbnailStreamDirection = thumbnailVideoStream.getDirection();
+				}
 			} else {
-				params->enableVideo(false);
-				params->enableCamera(false);
+				thumbnailStreamDirection = mainStreamDirection;
 			}
+			// The camera is enabled if:
+			// - the thumbnail stream is enabled
+			// - the thumbnail stream's direction is sendrecv or sendonly
+			params->enableCamera(
+			    ((thumbnailStreamDirection == SalStreamSendRecv) || (thumbnailStreamDirection == SalStreamSendOnly)) &&
+			    params->videoEnabled());
 
 			const SalStreamDescription &textStream = md->findBestStream(SalText);
 			if (textStream != Utils::getEmptyConstRefObject<SalStreamDescription>()) {
@@ -5938,92 +5997,68 @@ int MediaSession::getMainVideoStreamIdx(const std::shared_ptr<SalMediaDescriptio
 	// client is more difficult as the NOTIFY message may have not come or been processed. The algorithm below searches
 	// for the label in the main stream and then reuses the label to look for the desired thumbnail stream
 	auto streamIdx = -1;
-	if (md) {
-		const auto conference = getCore()->findConference(getSharedFromThis(), false);
-		if (conference && d->op) {
-			const bool isInLocalConference = d->getParams()->getPrivate()->getInConference();
-			const auto &confLayout = computeConferenceLayout(isInLocalConference ? d->op->getRemoteMediaDescription()
-			                                                                     : d->op->getLocalMediaDescription());
-			const auto isConferenceLayoutActiveSpeaker = (confLayout == ConferenceLayout::ActiveSpeaker);
-			const auto isConferenceLayoutGrid = (confLayout == ConferenceLayout::Grid);
-			const auto &participantDevice = (isInLocalConference)
-			                                    ? conference->findParticipantDevice(getSharedFromThis())
-			                                    : conference->getMe()->findDevice(getSharedFromThis());
-
-			// Try to find a stream with the screen sharing content attribute as it will be for sure the main video
-			// stream. Indeed, screen sharing can be enabled regardless of the conference layout, it is needed to always
-			// make this try.
-			std::string mainStreamAttrValue = MediaSessionPrivate::ScreenSharingContentAttribute;
-			streamIdx = md->findIdxStreamWithContent(mainStreamAttrValue);
-			if (streamIdx == -1) {
-				// If no stream with the screen sharing content is found, then try with regular attributes for each of
-				// the different layouts
-				if (isConferenceLayoutActiveSpeaker) {
-					mainStreamAttrValue = MediaSessionPrivate::ActiveSpeakerVideoContentAttribute;
-				} else if (isConferenceLayoutGrid) {
-					mainStreamAttrValue = MediaSessionPrivate::GridVideoContentAttribute;
-				} else {
-					lError() << "Unable to determine attribute of main video stream of session " << this
-					         << " (local addres " << *getLocalAddress() << " remote address " << *getRemoteAddress()
-					         << ") in conference " << *conference->getConferenceAddress() << ":";
-					lError() << " - grid layout: " << isConferenceLayoutGrid;
-					lError() << " - active speaker layout: " << isConferenceLayoutActiveSpeaker;
-					lError() << " - device is screen sharing: "
-					         << (participantDevice && participantDevice->screenSharingEnabled());
-				}
-				streamIdx = md->findIdxStreamWithContent(mainStreamAttrValue);
-			}
-			if (streamIdx == -1) {
-				// The stream index was not found despite all efforts
-				lDebug() << "Unable to find main video stream of session " << this << " (local addres "
-				         << *getLocalAddress() << " remote address " << *getRemoteAddress() << "):";
-				lDebug() << " - no stream with content \"" << MediaSessionPrivate::ScreenSharingContentAttribute
-				         << "\" is found";
-				lDebug() << " - grid layout: " << isConferenceLayoutGrid;
-				lDebug() << " - active speaker layout: " << isConferenceLayoutActiveSpeaker;
-				lDebug() << " - device is screen sharing: "
+	const auto conference = getCore()->findConference(getSharedFromThis(), false);
+	if (conference && d->op) {
+		const bool isInLocalConference = d->getParams()->getPrivate()->getInConference();
+		const auto &refMd =
+		    isInLocalConference ? d->op->getRemoteMediaDescription() : d->op->getLocalMediaDescription();
+		const auto &confLayout = computeConferenceLayout(refMd);
+		const auto isConferenceLayoutActiveSpeaker = (confLayout == ConferenceLayout::ActiveSpeaker);
+		const auto isConferenceLayoutGrid = (confLayout == ConferenceLayout::Grid);
+		const auto &participantDevice = (isInLocalConference) ? conference->findParticipantDevice(getSharedFromThis())
+		                                                      : conference->getMe()->findDevice(getSharedFromThis());
+
+		// Try to find a stream with the screen sharing content attribute as it will be for sure the main video
+		// stream. Indeed, screen sharing can be enabled regardless of the conference layout, it is needed to always
+		// make this try.
+		std::string mainStreamAttrValue = MediaSessionPrivate::ScreenSharingContentAttribute;
+		streamIdx = refMd->findIdxStreamWithContent(mainStreamAttrValue);
+		if (streamIdx == -1) {
+			// If no stream with the screen sharing content is found, then try with regular attributes for each of
+			// the different layouts
+			if (isConferenceLayoutActiveSpeaker) {
+				mainStreamAttrValue = MediaSessionPrivate::ActiveSpeakerVideoContentAttribute;
+			} else if (isConferenceLayoutGrid) {
+				mainStreamAttrValue = MediaSessionPrivate::GridVideoContentAttribute;
+			} else {
+				lError() << "Unable to determine attribute of main video stream of " << *this << " (local address "
+				         << *getLocalAddress() << " remote address " << *getRemoteAddress() << ") in " << *conference
+				         << ":";
+				lError() << " - grid layout: " << isConferenceLayoutGrid;
+				lError() << " - active speaker layout: " << isConferenceLayoutActiveSpeaker;
+				lError() << " - device is screen sharing: "
 				         << (participantDevice && participantDevice->screenSharingEnabled());
 			}
+			streamIdx = refMd->findIdxStreamWithContent(mainStreamAttrValue);
 		}
 		if (streamIdx == -1) {
-			streamIdx = md->findIdxBestStream(SalVideo);
-		}
+			// The stream index was not found despite all efforts
+			lDebug() << "Unable to find main video stream of " << *this << " (local address " << *getLocalAddress()
+			         << " remote address " << *getRemoteAddress() << ") in " << *conference << ":";
+			lDebug() << " - no stream with content \"" << MediaSessionPrivate::ScreenSharingContentAttribute
+			         << "\" is found";
+			lDebug() << " - grid layout: " << isConferenceLayoutGrid;
+			lDebug() << " - active speaker layout: " << isConferenceLayoutActiveSpeaker;
+			lDebug() << " - device is screen sharing: "
+			         << (participantDevice && participantDevice->screenSharingEnabled());
+		}
+	} else if (md) {
+		streamIdx = md->findIdxBestStream(SalVideo);
 	}
-
 	return streamIdx;
 }
 
-int MediaSession::getLocalThumbnailStreamIdx() const {
+int MediaSession::getThumbnailStreamIdx() const {
 	L_D();
-	return getThumbnailStreamIdx(d->op ? d->op->getLocalMediaDescription() : nullptr);
-}
-
-int MediaSession::getThumbnailStreamIdx(const std::shared_ptr<SalMediaDescription> &md) const {
-	L_D();
-	// In order to set properly the negotiated parameters, we must know if the client is sending video to the
-	// conference, i.e. look at the thumbnail stream direction. In order to do so, we must know the label of the
-	// searched thumbnail stream. The local case is quite straightforward because all labels are known, but for the
-	// client is more difficult as the NOTIFY message may have not come or been processed. The algorithm below searches
-	// for the label in the main stream and then reuses the label to look for the desired thumbnail stream
 	auto streamIdx = -1;
-	if (md) {
-		const auto conference = getCore()->findConference(getSharedFromThis(), false);
-		if (conference) {
-			const auto content = MediaSessionPrivate::ThumbnailVideoContentAttribute;
-			const bool isInLocalConference = d->getParams()->getPrivate()->getInConference();
-			std::string label;
-			if (isInLocalConference) {
-				auto device = conference->findParticipantDevice(getSharedFromThis());
-				if (device) {
-					label = device->getThumbnailStreamLabel();
-				}
-			} else {
-				auto device = conference->getMe()->findDevice(getSharedFromThis());
-				if (device) {
-					label = device->getThumbnailStreamLabel();
-				}
-			}
-			streamIdx = md->findIdxStreamWithContent(content, label);
+	const auto conference = getCore()->findConference(getSharedFromThis(), false);
+	if (conference) {
+		const bool isInLocalConference = d->getParams()->getPrivate()->getInConference();
+		const auto &refMd =
+		    isInLocalConference ? d->op->getRemoteMediaDescription() : d->op->getLocalMediaDescription();
+		if (refMd) {
+			streamIdx =
+			    refMd->findIdxStreamWithContent(MediaSessionPrivate::ThumbnailVideoContentAttribute, SalStreamSendOnly);
 		}
 	}
 	return streamIdx;
@@ -6033,20 +6068,19 @@ void MediaSession::setEkt(const MSEKTParametersSet *ekt_params) const {
 	getStreamsGroup().setEkt(ekt_params);
 }
 
-LinphoneMediaDirection MediaSession::getDirectionOfStream(BCTBX_UNUSED(const std::string content)) const {
-	auto direction = LinphoneMediaDirectionInactive;
-#ifdef VIDEO_ENABLED
+LinphoneMediaDirection MediaSession::getDirectionOfStream(const std::string &content, const std::string &label) const {
 	L_D();
+	auto direction = LinphoneMediaDirectionInactive;
 	// If we are a conference server, we must look at the incoming INVITE as this SDP has the content attribute.
 	// Look at the local description in all the other scenarions (remote conferece on a server or call)
 	const auto &op = d->getOp();
 	if (op) {
 		bool isServer = linphone_core_conference_server_enabled(getCore()->getCCore());
-		std::shared_ptr<SalMediaDescription> offer =
-		    (isServer) ? op->getRemoteMediaDescription() : op->getLocalMediaDescription();
+		const auto &offer = (isServer) ? op->getLocalMediaDescription() : op->getRemoteMediaDescription();
 		if (offer) {
 			// Look for the index of the stream containing the requested attribute in the offered SDP
-			const auto idx = offer->findIdxStreamWithContent(content);
+			const auto idx = label.empty() ? offer->findIdxStreamWithContent(content)
+			                               : offer->findIdxStreamWithContent(content, label);
 			if (idx != -1) {
 				std::shared_ptr<SalMediaDescription> &md = op->getFinalMediaDescription();
 				try {
@@ -6066,7 +6100,6 @@ LinphoneMediaDirection MediaSession::getDirectionOfStream(BCTBX_UNUSED(const std
 			}
 		}
 	}
-#endif
 	return direction;
 }
 
diff --git a/src/conference/session/media-session.h b/src/conference/session/media-session.h
index 9573630291..58b4ae4b55 100644
--- a/src/conference/session/media-session.h
+++ b/src/conference/session/media-session.h
@@ -187,8 +187,7 @@ public:
 	uint32_t getSsrc(LinphoneStreamType type) const;
 	uint32_t getSsrc(std::string content) const;
 
-	int getLocalThumbnailStreamIdx() const;
-	int getThumbnailStreamIdx(const std::shared_ptr<SalMediaDescription> &md) const;
+	int getThumbnailStreamIdx() const;
 	int getMainVideoStreamIdx(const std::shared_ptr<SalMediaDescription> &md) const;
 
 	/**
@@ -199,7 +198,8 @@ public:
 	void setEkt(const MSEKTParametersSet *ekt_params) const;
 	bool dtmfSendingAllowed() const;
 
-	LinphoneMediaDirection getDirectionOfStream(const std::string content) const;
+	LinphoneMediaDirection getDirectionOfStream(const std::string &content,
+	                                            const std::string &label = std::string()) const;
 	bool isScreenSharingNegotiated() const;
 	const std::shared_ptr<const VideoSourceDescriptor> getVideoSourceDescriptor() const;
 
diff --git a/src/conference/session/video-stream.cpp b/src/conference/session/video-stream.cpp
index 00299e1abc..3dc136a627 100644
--- a/src/conference/session/video-stream.cpp
+++ b/src/conference/session/video-stream.cpp
@@ -398,7 +398,7 @@ void MS2VideoStream::render(const OfferAnswerContext &ctx, CallSession::State ta
 	bool localScreenSharingChanged = false, displayModeChanged = false;
 	auto participantDevice = getMediaSession().getParticipantDevice(LinphoneStreamTypeVideo, label);
 	if (!participantDevice) {
-		if (conference) {
+		if (conference && conference->getMe()) {
 			participantDevice = conference->getMe()->findDevice(LinphoneStreamTypeVideo, label, false);
 			// is Me. Q : Me is always local? (multi account)
 			isScreenSharing = (participantDevice && participantDevice->screenSharingEnabled());
@@ -418,7 +418,7 @@ void MS2VideoStream::render(const OfferAnswerContext &ctx, CallSession::State ta
 						}
 					}
 				} else { // Get Thumbnail Stream.
-					int idx = getMediaSession().getLocalThumbnailStreamIdx();
+					int idx = getMediaSession().getThumbnailStreamIdx();
 					if (idx >= 0) auxStream = dynamic_cast<MS2VideoStream *>(getGroup().getStream(idx));
 					localScreenSharingChanged = enableLocalScreenSharing(isScreenSharing);
 				}
@@ -747,7 +747,7 @@ void MS2VideoStream::render(const OfferAnswerContext &ctx, CallSession::State ta
 						link_video_stream_with_itc_sink(mStream);
 						// Current stream is Main, search for the thumbnail to connect with ITC.
 						MS2VideoStream *vs = nullptr;
-						int idx = getMediaSession().getLocalThumbnailStreamIdx();
+						int idx = getMediaSession().getThumbnailStreamIdx();
 						if (idx >= 0) vs = dynamic_cast<MS2VideoStream *>(getGroup().getStream(idx));
 						if (vs) {
 							VideoStream *itcStream = vs->getVideoStream();
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 65e7351463..aebb09694d 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -740,7 +740,7 @@ void CorePrivate::updateVideoDevice() {
 			auto vs = i->getVideoStream();
 			if (vs && video_stream_local_screen_sharing_enabled(vs)) {
 				auto &group = ms->getStreamsGroup();
-				int idx = ms->getLocalThumbnailStreamIdx();
+				int idx = ms->getThumbnailStreamIdx();
 				if (idx >= 0) i = dynamic_cast<MS2VideoControl *>(group.getStream(idx));
 			}
 			if (i) i->parametersChanged();
diff --git a/src/event/event-publish.cpp b/src/event/event-publish.cpp
index 89f54493d2..641aaea843 100644
--- a/src/event/event-publish.cpp
+++ b/src/event/event-publish.cpp
@@ -224,7 +224,7 @@ void EventPublish::terminate() {
 	}
 
 	if (mPublishState != LinphonePublishNone) {
-		if (mPublishState == LinphonePublishOk && mExpires != -1) {
+		if (mOp && (mPublishState == LinphonePublishOk) && (mExpires != -1)) {
 			auto op = dynamic_cast<SalPublishOp *>(mOp);
 			op->unpublish();
 		}
@@ -237,7 +237,7 @@ void EventPublish::terminate() {
 
 void EventPublish::startTimeoutHandling() {
 	stopTimeoutHandling();
-	if (mExpires > 0)
+	if (mExpires > 0) {
 		mTimer = getCore()->createTimer(
 		    [this]() {
 			    lInfo() << "Publish event [" << this << "] has expired";
@@ -245,6 +245,7 @@ void EventPublish::startTimeoutHandling() {
 			    return true;
 		    },
 		    static_cast<unsigned int>(mExpires) * 1000, "Publish timer");
+	}
 }
 
 void EventPublish::stopTimeoutHandling() {
diff --git a/src/event/event.cpp b/src/event/event.cpp
index 08a85e38fa..47638a56e8 100644
--- a/src/event/event.cpp
+++ b/src/event/event.cpp
@@ -151,7 +151,9 @@ std::shared_ptr<Address> Event::getRemoteContact() const {
 	if (!mRemoteContactAddress) {
 		mRemoteContactAddress = Address::create();
 	}
-	mRemoteContactAddress->setImpl(mOp->getRemoteContactAddress());
+	if (mOp) {
+		mRemoteContactAddress->setImpl(mOp->getRemoteContactAddress());
+	}
 	return mRemoteContactAddress;
 }
 
@@ -159,7 +161,9 @@ std::shared_ptr<Address> Event::cacheFrom() const {
 	if (!mFromAddress) {
 		mFromAddress = Address::create();
 	}
-	mFromAddress->setImpl(mOp->getFromAddress());
+	if (mOp) {
+		mFromAddress->setImpl(mOp->getFromAddress());
+	}
 	return mFromAddress;
 }
 
@@ -167,7 +171,9 @@ std::shared_ptr<Address> Event::cacheTo() const {
 	if (!mToAddress) {
 		mToAddress = Address::create();
 	}
-	mToAddress->setImpl(mOp->getToAddress());
+	if (mOp) {
+		mToAddress->setImpl(mOp->getToAddress());
+	}
 	return mToAddress;
 }
 
@@ -175,7 +181,9 @@ std::shared_ptr<Address> Event::cacheRequestAddress() const {
 	if (!mRequestAddress) {
 		mRequestAddress = Address::create();
 	}
-	mRequestAddress->setImpl(mOp->getRequestAddress());
+	if (mOp) {
+		mRequestAddress->setImpl(mOp->getRequestAddress());
+	}
 	return mRequestAddress;
 }
 
diff --git a/src/sal/sal_stream_description.cpp b/src/sal/sal_stream_description.cpp
index 9f4ee3b500..586dd67dc4 100644
--- a/src/sal/sal_stream_description.cpp
+++ b/src/sal/sal_stream_description.cpp
@@ -524,7 +524,6 @@ SalStreamDescription::addAcapsToConfiguration(const SalStreamConfiguration &base
 				cfgList.push_back(cfg);
 				cfg = baseCfg;
 			}
-
 		} else if (enc == LinphoneMediaEncryptionZRTP) {
 			for (const auto &attr : attrs) {
 				const auto &capNameValue = attr.second;
@@ -1217,7 +1216,7 @@ SalStreamDescription::toSdpMediaDescription(const SalMediaDescription *salMediaD
 		bctbx_free(value);
 	}
 
-	if (actualCfg.conference_ssrc) {
+	if ((actualCfg.conference_ssrc != 0) && !actualCfg.rtcp_cname.empty()) {
 		char *ssrc_attribute = ms_strdup_printf("%u cname:%s", actualCfg.conference_ssrc, actualCfg.rtcp_cname.c_str());
 		belle_sdp_media_description_add_attribute(media_desc, belle_sdp_attribute_create("ssrc", ssrc_attribute));
 		ms_free(ssrc_attribute);
diff --git a/tester/local-conference-tester-functions.cpp b/tester/local-conference-tester-functions.cpp
index 39785a865e..91bd36c29e 100644
--- a/tester/local-conference-tester-functions.cpp
+++ b/tester/local-conference-tester-functions.cpp
@@ -181,6 +181,50 @@ void check_conference_me(LinphoneConference *conference, bool_t is_admin) {
 	}
 }
 
+void check_delete_focus_conference_info(std::initializer_list<std::reference_wrapper<CoreManager>> coreMgrs,
+                                        std::list<LinphoneCoreManager *> conferenceMgrs,
+                                        LinphoneCoreManager *focus,
+                                        LinphoneAddress *confAddr,
+                                        time_t end_time) {
+	if (end_time > 0) {
+		long focus_cleanup_window = linphone_core_get_conference_cleanup_period(focus->lc);
+		time_t now = ms_time(NULL);
+		time_t time_left = end_time - now;
+		if (focus_cleanup_window > 0) {
+			time_left += focus_cleanup_window;
+			// The conference information is only deleted by the cleanup timer. Hence even if the end time went by, the
+			// conference information might not be deleted
+			CoreManagerAssert(coreMgrs).waitUntil(chrono::seconds(focus_cleanup_window), [] { return false; });
+		}
+
+		for (const auto &mgr : conferenceMgrs) {
+			LinphoneConferenceInfo *info = linphone_core_find_conference_information_from_uri(mgr->lc, confAddr);
+			if ((mgr == focus) && (time_left <= 0) && (focus_cleanup_window > 0)) {
+				BC_ASSERT_PTR_NULL(info);
+			} else {
+				BC_ASSERT_PTR_NOT_NULL(info);
+			}
+			if (info) {
+				linphone_conference_info_unref(info);
+			}
+		}
+
+		if (time_left > 0) {
+			// wait for the conference to end
+			CoreManagerAssert(coreMgrs).waitUntil(chrono::seconds((time_left + 1)), [] { return false; });
+			LinphoneConferenceInfo *focus_info =
+			    linphone_core_find_conference_information_from_uri(focus->lc, confAddr);
+			if (focus_cleanup_window > 0) {
+				BC_ASSERT_PTR_NULL(focus_info);
+			} else {
+				if (BC_ASSERT_PTR_NOT_NULL(focus_info)) {
+					linphone_conference_info_unref(focus_info);
+				}
+			}
+		}
+	}
+}
+
 LinphoneAddress *
 create_conference_on_server(Focus &focus,
                             ClientConference &organizer,
@@ -257,7 +301,7 @@ create_conference_on_server(Focus &focus,
 		BC_ASSERT_PTR_NOT_NULL(conference);
 		if (conference) {
 			bctbx_list_t *participant_addresses = NULL;
-			for (auto &mgr : participants) {
+			for (const auto &mgr : participants) {
 				participant_addresses = bctbx_list_append(participant_addresses, mgr->identity);
 			}
 			LinphoneCallParams *call_params = linphone_core_create_call_params(organizer.getLc(), NULL);
@@ -303,7 +347,7 @@ create_conference_on_server(Focus &focus,
 
 		int call_ok_cnt = 0;
 		// Conference server dials out participants
-		for (auto &mgr : participants) {
+		for (const auto &mgr : participants) {
 			auto old_stats = participant_stats[idx];
 			if (have_common_audio_payload(mgr, focus.getCMgr()) && !previous_calls[mgr]) {
 				BC_ASSERT_TRUE(wait_for_list(coresList, &mgr->stat.number_of_LinphoneCallIncomingReceived,
@@ -337,7 +381,7 @@ create_conference_on_server(Focus &focus,
 			duration = static_cast<int>((end_time - start_time) / 60); // duration is expected to be set in minutes
 		}
 
-		for (auto &mgr : participants) {
+		for (const auto &mgr : participants) {
 			previous_calls[mgr] = linphone_core_get_call_by_remote_address2(mgr->lc, focus.getCMgr()->identity);
 		}
 
@@ -443,7 +487,7 @@ create_conference_on_server(Focus &focus,
 	}
 
 	idx = 0;
-	for (auto &mgr : participants) {
+	for (const auto &mgr : participants) {
 		if (!is_dialout || !previous_calls[mgr]) {
 			auto old_stats = participant_stats[idx];
 			if (will_send_ics) {
@@ -745,24 +789,34 @@ void does_all_participants_have_matching_ekt(LinphoneCoreManager *focus,
 			if (security_level == LinphoneConferenceSecurityLevelEndToEnd) {
 				auto firstClientConf = dynamic_cast<const LinphonePrivate::ClientConference *>(
 				    Conference::toCpp(linphone_core_search_conference_2(members.begin()->first->lc, confAddr)));
-				shared_ptr<ClientEktManager::EktContext> firstClientEktCtx;
 				BC_ASSERT_PTR_NOT_NULL(firstClientConf);
 				if (firstClientConf) {
-					firstClientEktCtx = firstClientConf->getClientEktManager()->getEktCtx();
-					BC_ASSERT_PTR_NOT_NULL(firstClientEktCtx);
-					for (auto member : members) {
-						auto conf = linphone_core_search_conference_2(member.first->lc, confAddr);
-						BC_ASSERT_PTR_NOT_NULL(conf);
-						if (conf) {
-							auto rcConf =
-							    dynamic_cast<const LinphonePrivate::ClientConference *>(Conference::toCpp(conf));
-							BC_ASSERT_PTR_NOT_NULL(rcConf);
-							if (rcConf) {
-								auto rcEktCtx = rcConf->getClientEktManager()->getEktCtx();
-								BC_ASSERT_PTR_NOT_NULL(rcEktCtx);
-								BC_ASSERT_EQUAL(firstClientEktCtx->getSSpi(), rcEktCtx->getSSpi(), uint16_t, "%d");
-								BC_ASSERT_TRUE(firstClientEktCtx->getCSpi() == rcEktCtx->getCSpi());
-								BC_ASSERT_TRUE(firstClientEktCtx->getEkt() == rcEktCtx->getEkt());
+					const auto &firstClientEktManager = firstClientConf->getClientEktManager();
+					BC_ASSERT_PTR_NOT_NULL(firstClientEktManager);
+					if (firstClientEktManager) {
+						const auto &firstClientEktCtx = firstClientEktManager->getEktCtx();
+						BC_ASSERT_PTR_NOT_NULL(firstClientEktCtx);
+						for (auto member : members) {
+							auto conf = linphone_core_search_conference_2(member.first->lc, confAddr);
+							BC_ASSERT_PTR_NOT_NULL(conf);
+							if (conf) {
+								auto rcConf =
+								    dynamic_cast<const LinphonePrivate::ClientConference *>(Conference::toCpp(conf));
+								BC_ASSERT_PTR_NOT_NULL(rcConf);
+								if (rcConf) {
+									const auto &clientEktManager = rcConf->getClientEktManager();
+									BC_ASSERT_PTR_NOT_NULL(clientEktManager);
+									if (clientEktManager) {
+										const auto &rcEktCtx = clientEktManager->getEktCtx();
+										BC_ASSERT_PTR_NOT_NULL(rcEktCtx);
+										if (rcEktCtx && firstClientEktCtx) {
+											BC_ASSERT_EQUAL(firstClientEktCtx->getSSpi(), rcEktCtx->getSSpi(), uint16_t,
+											                "%d");
+											BC_ASSERT_TRUE(firstClientEktCtx->getCSpi() == rcEktCtx->getCSpi());
+											BC_ASSERT_TRUE(firstClientEktCtx->getEkt() == rcEktCtx->getEkt());
+										}
+									}
+								}
 							}
 						}
 					}
@@ -4087,25 +4141,8 @@ void create_conference_base(time_t start_time,
 			                             liblinphone_tester_sip_timeout));
 		}
 
-		if (end_time > 0) {
-			time_t now = ms_time(NULL);
-			time_t time_left = end_time - now + linphone_core_get_conference_cleanup_period(focus.getLc());
-			if (time_left < 0) {
-				time_left = 0;
-			}
-			for (auto mgr : allConferenceMgrs) {
-				LinphoneConferenceInfo *info = linphone_core_find_conference_information_from_uri(mgr->lc, confAddr);
-				if (BC_ASSERT_PTR_NOT_NULL(info)) {
-					linphone_conference_info_unref(info);
-				}
-			}
-			// wait for the conference to end
-			CoreManagerAssert({focus, marie, pauline, laure, michelle, berthe})
-			    .waitUntil(chrono::seconds((time_left + 1)), [] { return false; });
-			LinphoneConferenceInfo *focus_info =
-			    linphone_core_find_conference_information_from_uri(focus.getLc(), confAddr);
-			BC_ASSERT_PTR_NULL(focus_info);
-		}
+		check_delete_focus_conference_info({focus, marie, pauline, laure, michelle, berthe}, allConferenceMgrs,
+		                                   focus.getCMgr(), confAddr, end_time);
 
 		std::list<LinphoneCoreManager *> allMembers{marie.getCMgr(), pauline.getCMgr()};
 		if (!version_mismatch) allMembers.push_back(laure.getCMgr());
@@ -4682,7 +4719,6 @@ void create_conference_with_screen_sharing_base(time_t start_time,
 			BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneCallStreamsRunning,
 			                             focus_stat.number_of_LinphoneCallStreamsRunning + 1,
 			                             liblinphone_tester_sip_timeout));
-			BC_ASSERT_TRUE(check_screen_sharing_sdp(focus.getCMgr(), laure.getCMgr(), FALSE));
 		}
 
 		BC_ASSERT_FALSE(wait_for_list(coresList, &focus.getStats().number_of_participant_devices_screen_sharing_enabled,
@@ -6129,19 +6165,8 @@ void create_conference_with_screen_sharing_chat_base(time_t start_time,
 		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateTerminated, 1,
 		                             liblinphone_tester_sip_timeout));
 
-		long focus_cleanup_window = linphone_core_get_conference_cleanup_period(focus.getLc());
-		if (end_time > 0) {
-			time_t now = ms_time(NULL);
-			time_t time_left = end_time - now;
-			if (focus_cleanup_window > 0) {
-				time_left += focus_cleanup_window;
-			}
-			if (time_left > 0) {
-				// wait for the conference to end
-				CoreManagerAssert({focus, marie, pauline, michelle, laure, berthe})
-				    .waitUntil(chrono::seconds((time_left + 1)), [] { return false; });
-			}
-		}
+		check_delete_focus_conference_info({focus, marie, pauline, laure, michelle, berthe}, conferenceMgrs,
+		                                   focus.getCMgr(), confAddr, end_time);
 
 		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateDeleted, 1,
 		                             liblinphone_tester_sip_timeout));
@@ -8099,35 +8124,8 @@ void create_conference_with_chat_base(LinphoneConferenceSecurityLevel security_l
 		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateDeleted, 1,
 		                             liblinphone_tester_sip_timeout));
 
-		long focus_cleanup_window = linphone_core_get_conference_cleanup_period(focus.getLc());
-		if (end_time > 0) {
-			time_t now = ms_time(NULL);
-			time_t time_left = end_time - now;
-			if (focus_cleanup_window > 0) {
-				time_left += focus_cleanup_window;
-			}
-			if (time_left > 0) {
-				for (auto mgr : conferenceMgrs) {
-					LinphoneConferenceInfo *info =
-					    linphone_core_find_conference_information_from_uri(mgr->lc, confAddr);
-					if (BC_ASSERT_PTR_NOT_NULL(info)) {
-						linphone_conference_info_unref(info);
-					}
-				}
-				// wait for the conference to end
-				CoreManagerAssert({focus, marie, pauline, michelle, laure, berthe})
-				    .waitUntil(chrono::seconds((time_left + 1)), [] { return false; });
-				LinphoneConferenceInfo *focus_info =
-				    linphone_core_find_conference_information_from_uri(focus.getLc(), confAddr);
-				if (focus_cleanup_window > 0) {
-					BC_ASSERT_PTR_NULL(focus_info);
-				} else {
-					if (BC_ASSERT_PTR_NOT_NULL(focus_info)) {
-						linphone_conference_info_unref(focus_info);
-					}
-				}
-			}
-		}
+		check_delete_focus_conference_info({focus, marie, pauline, michelle, laure, berthe}, conferenceMgrs,
+		                                   focus.getCMgr(), confAddr, end_time);
 
 		if (!!server_restart) {
 			focus_stat = focus.getStats();
@@ -8304,6 +8302,7 @@ void create_conference_with_chat_base(LinphoneConferenceSecurityLevel security_l
 					const LinphoneErrorInfo *error_info = linphone_call_log_get_error_info(call_log);
 					BC_ASSERT_PTR_NOT_NULL(error_info);
 					if (error_info) {
+						long focus_cleanup_window = linphone_core_get_conference_cleanup_period(focus.getLc());
 						LinphoneReason reason =
 						    (focus_cleanup_window <= 0) ? LinphoneReasonForbidden : LinphoneReasonNotFound;
 						BC_ASSERT_EQUAL(linphone_error_info_get_reason(error_info), reason, int, "%d");
@@ -8824,26 +8823,8 @@ void conference_joined_multiple_times(LinphoneConferenceSecurityLevel security_l
 			});
 		}
 
-		if ((focus_cleanup_window > 0) && (end_time > 0)) {
-			time_t now = ms_time(NULL);
-			time_t time_left = end_time - now + focus_cleanup_window;
-			if (time_left < 0) {
-				time_left = 0;
-			}
-			// wait for the conference to end
-			CoreManagerAssert({focus, marie, pauline, michelle, laure, berthe})
-			    .waitUntil(chrono::seconds((time_left + 1)), [] { return false; });
-		}
-
-		LinphoneConferenceInfo *focus_info =
-		    linphone_core_find_conference_information_from_uri(focus.getLc(), confAddr);
-		if (focus_cleanup_window > 0) {
-			BC_ASSERT_PTR_NULL(focus_info);
-		} else {
-			if (BC_ASSERT_PTR_NOT_NULL(focus_info)) {
-				linphone_conference_info_unref(focus_info);
-			}
-		}
+		check_delete_focus_conference_info({focus, marie, pauline, michelle, laure, berthe}, conferenceMgrs,
+		                                   focus.getCMgr(), confAddr, end_time);
 
 		ms_free(conference_address_str);
 		bctbx_list_free_with_data(participants_info, (bctbx_list_free_func)linphone_participant_info_unref);
@@ -13211,15 +13192,8 @@ void create_conference_with_audio_only_participants_base(LinphoneConferenceSecur
 		BC_ASSERT_TRUE(
 		    wait_for_list(coresList, &focus.getStats().number_of_LinphoneCallReleased, total_focus_calls, 40000));
 
-		if (end_time > 0) {
-			time_t now = ms_time(NULL);
-			time_t time_left = end_time - now + linphone_core_get_conference_cleanup_period(focus.getLc());
-			if (time_left > 0) {
-				// wait for the conference to end
-				CoreManagerAssert({focus, marie, pauline, laure, berthe})
-				    .waitUntil(chrono::seconds((time_left + 1)), [] { return false; });
-			}
-		}
+		check_delete_focus_conference_info({focus, marie, pauline, laure, berthe}, conferenceMgrs, focus.getCMgr(),
+		                                   confAddr, end_time);
 
 		for (auto mgr : {focus.getCMgr(), marie.getCMgr(), pauline.getCMgr(), laure.getCMgr(), berthe.getCMgr()}) {
 
@@ -13676,10 +13650,6 @@ void create_simple_conference_dial_out_with_some_calls_declined_base(LinphoneRea
 					LinphoneParticipantDevice *d = (LinphoneParticipantDevice *)bctbx_list_get_data(itd);
 					bool video_available =
 					    !!linphone_participant_device_get_stream_availability(d, LinphoneStreamTypeVideo);
-					//								if (linphone_conference_is_me(conference,
-					// linphone_participant_device_get_address(d))) {
-					// BC_ASSERT_TRUE(video_available ==
-					// video_enabled); 								} else {
 					LinphoneMediaDirection video_direction =
 					    linphone_participant_device_get_stream_capability(d, LinphoneStreamTypeVideo);
 					BC_ASSERT_TRUE(video_available == (((video_direction == LinphoneMediaDirectionSendOnly) ||
diff --git a/tester/local-conference-tester-functions.h b/tester/local-conference-tester-functions.h
index aeae4f68bb..123f1e0937 100644
--- a/tester/local-conference-tester-functions.h
+++ b/tester/local-conference-tester-functions.h
@@ -409,6 +409,11 @@ void create_one_participant_conference_toggle_video_base(LinphoneConferenceLayou
 void create_conference_with_active_call_base(bool_t is_dialout);
 
 void check_conference_me(LinphoneConference *conference, bool_t is_me);
+void check_delete_focus_conference_info(std::initializer_list<std::reference_wrapper<CoreManager>> coreMgrs,
+                                        std::list<LinphoneCoreManager *> conferenceMgrs,
+                                        LinphoneCoreManager *focus,
+                                        LinphoneAddress *confAddr,
+                                        time_t end_time);
 
 LinphoneAddress *
 create_conference_on_server(Focus &focus,
diff --git a/tester/local-scheduled-conference-tester.cpp b/tester/local-scheduled-conference-tester.cpp
index a18edb287c..ad60bb627b 100644
--- a/tester/local-scheduled-conference-tester.cpp
+++ b/tester/local-scheduled-conference-tester.cpp
@@ -666,19 +666,8 @@ static void alone_in_conference_with_chat_exits_enter(void) {
 		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateTerminated, 1,
 		                             liblinphone_tester_sip_timeout));
 
-		long focus_cleanup_window = linphone_core_get_conference_cleanup_period(focus.getLc());
-		if (end_time > 0) {
-			time_t now = ms_time(NULL);
-			time_t time_left = end_time - now;
-			if (focus_cleanup_window > 0) {
-				time_left += focus_cleanup_window;
-			}
-			if (time_left > 0) {
-				// wait for the conference to end
-				CoreManagerAssert({focus, marie, pauline, michelle, laure, berthe})
-				    .waitUntil(chrono::seconds((time_left + 1)), [] { return false; });
-			}
-		}
+		check_delete_focus_conference_info({focus, marie, pauline, michelle, laure, berthe}, conferenceMgrs,
+		                                   focus.getCMgr(), confAddr, end_time);
 
 		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateDeleted, 1,
 		                             liblinphone_tester_sip_timeout));
@@ -1049,19 +1038,8 @@ static void conference_with_participants_late_except_one(void) {
 		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateTerminated, 1,
 		                             liblinphone_tester_sip_timeout));
 
-		long focus_cleanup_window = linphone_core_get_conference_cleanup_period(focus.getLc());
-		if (end_time > 0) {
-			time_t now = ms_time(NULL);
-			time_t time_left = end_time - now;
-			if (focus_cleanup_window > 0) {
-				time_left += focus_cleanup_window;
-			}
-			if (time_left > 0) {
-				// wait for the conference to end
-				CoreManagerAssert({focus, marie, pauline, michelle, laure, berthe})
-				    .waitUntil(chrono::seconds((time_left + 1)), [] { return false; });
-			}
-		}
+		check_delete_focus_conference_info({focus, marie, pauline, michelle, laure, berthe}, conferenceMgrs,
+		                                   focus.getCMgr(), confAddr, end_time);
 
 		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateDeleted, 1,
 		                             liblinphone_tester_sip_timeout));
@@ -6068,28 +6046,8 @@ static void create_simple_conference_in_sfu_payload_mode(void) {
 			}
 		}
 
-		if (end_time > 0) {
-			LinphoneConferenceInfo *info = linphone_core_find_conference_information_from_uri(focus.getLc(), confAddr);
-			BC_ASSERT_PTR_NOT_NULL(info);
-			if (info) {
-				linphone_conference_info_unref(info);
-				info = NULL;
-			}
-			time_t now = ms_time(NULL);
-			time_t time_left = end_time - now + linphone_core_get_conference_cleanup_period(focus.getLc());
-			if (time_left > 0) {
-				// wait for the conference to end
-				CoreManagerAssert({focus, marie, pauline}).waitUntil(chrono::seconds((time_left + 1)), [] {
-					return false;
-				});
-			}
-			info = linphone_core_find_conference_information_from_uri(focus.getLc(), confAddr);
-			BC_ASSERT_PTR_NULL(info);
-			if (info) {
-				linphone_conference_info_unref(info);
-				info = NULL;
-			}
-		}
+		check_delete_focus_conference_info({focus, marie, pauline}, conferenceMgrs, focus.getCMgr(), confAddr,
+		                                   end_time);
 
 		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateTerminationPending,
 		                             1, liblinphone_tester_sip_timeout));
diff --git a/tester/local-transferred-conference-tester.cpp b/tester/local-transferred-conference-tester.cpp
index 28779a0aba..521123dc5a 100644
--- a/tester/local-transferred-conference-tester.cpp
+++ b/tester/local-transferred-conference-tester.cpp
@@ -427,16 +427,8 @@ void create_transfer_conference_base(time_t start_time,
 			}
 		}
 
-		if (end_time > 0) {
-			time_t now = ms_time(NULL);
-			time_t time_left = end_time - now + linphone_core_get_conference_cleanup_period(focus.getLc());
-			if (time_left > 0) {
-				// wait for the conference to end
-				CoreManagerAssert({focus, marie, pauline}).waitUntil(chrono::seconds((time_left + 1)), [] {
-					return false;
-				});
-			}
-		}
+		check_delete_focus_conference_info({focus, marie, pauline, laure}, conferenceMgrs, focus.getCMgr(), confAddr,
+		                                   end_time);
 
 		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateTerminationPending,
 		                             1, liblinphone_tester_sip_timeout));
diff --git a/tester/shared_tester_functions.cpp b/tester/shared_tester_functions.cpp
index 610fad1f23..9a9fd1bc28 100644
--- a/tester/shared_tester_functions.cpp
+++ b/tester/shared_tester_functions.cpp
@@ -956,12 +956,13 @@ bool check_conference_ssrc(LinphoneConference *local_conference, LinphoneConfere
 								// The thumbnail video stream can only be sendonly (or recvonly from the server
 								// standpoint) or inactive. Henceherefore a client that can only receive video, will
 								// have to set it to inactive
+								uint32_t stream_ssrc = linphone_participant_device_get_ssrc(device, type);
 								if (media_direction == LinphoneMediaDirectionInactive) {
-									if (linphone_participant_device_get_ssrc(device, type) != 0) {
+									if (stream_ssrc != 0) {
 										ret = false;
 									}
 								} else {
-									if (linphone_participant_device_get_ssrc(device, type) == 0) {
+									if (stream_ssrc == 0) {
 										ret = false;
 									}
 								}
@@ -972,17 +973,14 @@ bool check_conference_ssrc(LinphoneConference *local_conference, LinphoneConfere
 									if (!stream_available) {
 										continue;
 									}
-									uint32_t video_ssrc = linphone_participant_device_get_ssrc(device, type);
-
 									auto cppDevice = ParticipantDevice::toCpp(device)->getSharedFromThis();
 									bool thumbnail_available = cppDevice->getThumbnailStreamAvailability();
 									uint32_t thumbnail_ssrc = cppDevice->getThumbnailStreamSsrc();
-
 									if (thumbnail_available) {
 										if (thumbnail_ssrc == 0) {
 											ret = false;
 										}
-										if (thumbnail_ssrc == video_ssrc) {
+										if (thumbnail_ssrc == stream_ssrc) {
 											ret = false;
 										}
 									}
-- 
GitLab