From 31173b71afd0307babf26cbb552b6d49721a57c2 Mon Sep 17 00:00:00 2001
From: Andrea Gianarda <andrea.gianarda@belledonne-communications.com>
Date: Tue, 11 Jul 2023 07:41:42 +0200
Subject: [PATCH] - Prevent client that do not have appropriate capabilities to
 create chat rooms - Reply 488 Not Acceptable Here if a client without
 groupchat capabilities tries to create a chatroom - Do not send a SUBSCRIBE
 if the chatroom that has just been created has an handler in the remote
 conference list event handler - Reply 403 Forbidden if a client successfully
 creates a chatroom but he/she is not allowed in - Do not subscribe again to
 chatrooms when refreshing a register

Tester: Add devices information only if they have groupchat capabilities
when registering a new ClientConference
---
 coreapi/callbacks.c                           |  18 +-
 coreapi/proxy.c                               |   3 +
 coreapi/vtables.c                             |   5 +-
 include/linphone/api/c-call.h                 |   4 +-
 .../api/c-participant-device-identity.h       |  25 +-
 include/linphone/core.h                       |   2 +-
 include/linphone/friend.h                     |   2 +-
 include/linphone/types.h                      |  11 +-
 src/account/account.cpp                       |   6 +-
 src/account/account.h                         |   2 +
 .../api/c-participant-device-identity.cpp     |  15 +-
 src/chat/chat-room/client-group-chat-room.cpp |  14 +-
 src/chat/chat-room/server-group-chat-room-p.h |   9 +-
 src/chat/chat-room/server-group-chat-room.cpp |  83 ++-
 .../lime-x3dh-encryption-engine.cpp           |  12 +-
 .../encryption/lime-x3dh-encryption-engine.h  |   6 +-
 .../remote-conference-event-handler.cpp       |   6 +-
 .../remote-conference-event-handler.h         |   6 +-
 .../remote-conference-list-event-handler.cpp  |  23 +-
 .../remote-conference-list-event-handler.h    |   6 +-
 src/conference/participant.h                  |   5 -
 src/core/core-listener.h                      |   6 +
 src/core/core-p.h                             |   3 +
 src/core/core.cpp                             |  15 +-
 src/core/core.h                               |   7 +-
 tester/group_chat_tester.c                    |   4 +-
 tester/liblinphone_tester.h                   |   1 +
 tester/local_conference_tester.cpp            | 700 +++++++++++++++++-
 tester/remote-provisioning-tester.cpp         |   4 +-
 tester/tester.c                               |   3 +
 30 files changed, 894 insertions(+), 112 deletions(-)

diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c
index 67074709e9..d9621c0a65 100644
--- a/coreapi/callbacks.c
+++ b/coreapi/callbacks.c
@@ -509,8 +509,22 @@ static void register_success(SalOp *op, bool_t registered) {
 		ms_message("Registration success for deleted account, ignored");
 		return;
 	}
-	Account::toCpp(account)->setState(registered ? LinphoneRegistrationOk : LinphoneRegistrationCleared,
-	                                  registered ? "Registration successful" : "Unregistration done");
+
+	// If this register is a refresh sent by belle-sip, then move to the Refreshing register first
+	if (Account::toCpp(account)->getPreviousState() == LinphoneRegistrationOk) {
+		Account::toCpp(account)->setState(LinphoneRegistrationRefreshing, "Registration refreshing");
+	}
+
+	LinphoneRegistrationState state = LinphoneRegistrationNone;
+	std::string stateMessage;
+	if (registered) {
+		state = LinphoneRegistrationOk;
+		stateMessage = "Registration successful";
+	} else {
+		state = LinphoneRegistrationCleared;
+		stateMessage = "Unregistration done";
+	}
+	Account::toCpp(account)->setState(state, stateMessage);
 }
 
 static void register_failure(SalOp *op) {
diff --git a/coreapi/proxy.c b/coreapi/proxy.c
index afb378b0c4..d84d4c1018 100644
--- a/coreapi/proxy.c
+++ b/coreapi/proxy.c
@@ -1217,6 +1217,9 @@ const char *linphone_registration_state_to_string(LinphoneRegistrationState cs)
 		case LinphoneRegistrationProgress:
 			return "LinphoneRegistrationProgress";
 			break;
+		case LinphoneRegistrationRefreshing:
+			return "LinphoneRegistrationRefreshing";
+			break;
 		case LinphoneRegistrationOk:
 			return "LinphoneRegistrationOk";
 			break;
diff --git a/coreapi/vtables.c b/coreapi/vtables.c
index dac4acf6a8..e2423499c8 100644
--- a/coreapi/vtables.c
+++ b/coreapi/vtables.c
@@ -20,7 +20,6 @@
 
 #include "c-wrapper/c-wrapper.h"
 #include "core/core-p.h"
-
 #include "linphone/wrapper_utils.h"
 #include "private.h"
 
@@ -170,6 +169,8 @@ void linphone_core_notify_account_registration_state_changed(LinphoneCore *lc,
                                                              LinphoneAccount *account,
                                                              LinphoneRegistrationState state,
                                                              const char *message) {
+	L_GET_PRIVATE_FROM_C_OBJECT(lc)->notifyRegistrationStateChanged(
+	    LinphonePrivate::Account::toCpp(account)->getSharedFromThis(), state, message);
 	NOTIFY_IF_EXIST(account_registration_state_changed, lc, account, state, message);
 	cleanup_dead_vtable_refs(lc);
 }
@@ -582,4 +583,4 @@ void linphone_core_remove_callbacks(LinphoneCore *lc, const LinphoneCoreCbs *cbs
 void linphone_core_notify_alert(LinphoneCore *lc, LinphoneAlert *alert) {
 	NOTIFY_IF_EXIST(on_alert, lc, alert);
 	cleanup_dead_vtable_refs(lc);
-}
\ No newline at end of file
+}
diff --git a/include/linphone/api/c-call.h b/include/linphone/api/c-call.h
index c8c6957a33..40df58ef37 100644
--- a/include/linphone/api/c-call.h
+++ b/include/linphone/api/c-call.h
@@ -232,7 +232,7 @@ LINPHONE_PUBLIC bool_t linphone_call_camera_enabled(const LinphoneCall *call);
  *returns.
  * @param call #LinphoneCall object. @notnil
  * @param file_path a path where to write the jpeg content. @notnil
- * @return 0 if successfull, -1 otherwise (typically if jpeg format is not supported).
+ * @return 0 if successful, -1 otherwise (typically if jpeg format is not supported).
  **/
 LINPHONE_PUBLIC LinphoneStatus linphone_call_take_video_snapshot(LinphoneCall *call, const char *file_path);
 
@@ -242,7 +242,7 @@ LINPHONE_PUBLIC LinphoneStatus linphone_call_take_video_snapshot(LinphoneCall *c
  *returns.
  * @param call #LinphoneCall object. @notnil
  * @param file_path a path where to write the jpeg content. @notnil
- * @return 0 if successfull, -1 otherwise (typically if jpeg format is not supported).
+ * @return 0 if successful, -1 otherwise (typically if jpeg format is not supported).
  **/
 LINPHONE_PUBLIC LinphoneStatus linphone_call_take_preview_snapshot(LinphoneCall *call, const char *file_path);
 
diff --git a/include/linphone/api/c-participant-device-identity.h b/include/linphone/api/c-participant-device-identity.h
index 40520cf175..f73784dd8b 100644
--- a/include/linphone/api/c-participant-device-identity.h
+++ b/include/linphone/api/c-participant-device-identity.h
@@ -61,19 +61,40 @@ LINPHONE_PUBLIC void linphone_participant_device_identity_unref(LinphoneParticip
  * Set the capability descriptor (currently +org.linphone.specs value) for this participant device identity.
  * @param device_identity the #LinphoneParticipantDeviceIdentity object @notnil
  * @param capability_descriptor the capability descriptor string.
+ * @deprecated 12/06/2023 Use linphone_participant_device_identity_set_capability_descriptor_2() instead
  *
  **/
-LINPHONE_PUBLIC void
+LINPHONE_DEPRECATED LINPHONE_PUBLIC void
 linphone_participant_device_identity_set_capability_descriptor(LinphoneParticipantDeviceIdentity *device_identity,
                                                                const char *capability_descriptor);
 
+/**
+ * Set the capability descriptor (currently +org.linphone.specs value) for this participant device identity.
+ * @param device_identity the #LinphoneParticipantDeviceIdentity object @notnil
+ * @param capability_descriptor_list the capability descriptor list. \bctbx_list{const char *} @maybenil
+ *
+ **/
+LINPHONE_PUBLIC void
+linphone_participant_device_identity_set_capability_descriptor_2(LinphoneParticipantDeviceIdentity *device_identity,
+                                                                 const bctbx_list_t *capability_descriptor_list);
+
 /**
  * Get the capability descriptor (currently +org.linphone.specs value) for this participant device identity.
  * @param device_identity the #LinphoneParticipantDeviceIdentity object @notnil
  * @return the capability descriptor string.
+ * @deprecated 12/06/2023 Use linphone_participant_device_identity_get_capability_descriptor_list() instead
+ *
+ **/
+LINPHONE_DEPRECATED LINPHONE_PUBLIC const char *linphone_participant_device_identity_get_capability_descriptor(
+    const LinphoneParticipantDeviceIdentity *device_identity);
+
+/**
+ * Get the capability descriptor (currently +org.linphone.specs value) for this participant device identity.
+ * @param device_identity the #LinphoneParticipantDeviceIdentity object @notnil
+ * @return the capability descriptor list. \bctbx_list{const char *} @maybenil
  *
  **/
-LINPHONE_PUBLIC const char *linphone_participant_device_identity_get_capability_descriptor(
+LINPHONE_PUBLIC const bctbx_list_t *linphone_participant_device_identity_get_capability_descriptor_list(
     const LinphoneParticipantDeviceIdentity *device_identity);
 
 /**
diff --git a/include/linphone/core.h b/include/linphone/core.h
index 2088774cc0..b777babaed 100644
--- a/include/linphone/core.h
+++ b/include/linphone/core.h
@@ -3888,7 +3888,7 @@ LINPHONE_PUBLIC bool_t linphone_core_qrcode_video_preview_enabled(const Linphone
  * @ingroup misc
  * @param core the linphone core @notnil
  * @param file a path where to write the jpeg content. @notnil
- * @return 0 if successfull, -1 otherwise (typically if jpeg format is not supported).
+ * @return 0 if successful, -1 otherwise (typically if jpeg format is not supported).
  **/
 LINPHONE_PUBLIC LinphoneStatus linphone_core_take_preview_snapshot(LinphoneCore *core, const char *file);
 
diff --git a/include/linphone/friend.h b/include/linphone/friend.h
index 46802a424b..68fccb7a56 100644
--- a/include/linphone/friend.h
+++ b/include/linphone/friend.h
@@ -38,7 +38,7 @@ extern "C" {
  * Set #LinphoneAddress for this friend
  * @param linphone_friend #LinphoneFriend object @notnil
  * @param address the #LinphoneAddress to set @maybenil
- * return 0 if successfull, -1 otherwise
+ * return 0 if successful, -1 otherwise
  */
 LINPHONE_PUBLIC LinphoneStatus linphone_friend_set_address(LinphoneFriend *fr, const LinphoneAddress *address);
 
diff --git a/include/linphone/types.h b/include/linphone/types.h
index 9fe1307189..887a78e117 100644
--- a/include/linphone/types.h
+++ b/include/linphone/types.h
@@ -459,11 +459,12 @@ typedef enum _LinphoneGlobalState {
  * @ingroup proxies
  **/
 typedef enum _LinphoneRegistrationState {
-	LinphoneRegistrationNone = 0,     /**< Initial state for registrations */
-	LinphoneRegistrationProgress = 1, /**< Registration is in progress */
-	LinphoneRegistrationOk = 2,       /**< Registration is successful */
-	LinphoneRegistrationCleared = 3,  /**< Unregistration succeeded */
-	LinphoneRegistrationFailed = 4    /**< Registration failed */
+	LinphoneRegistrationNone = 0,      /**< Initial state for registrations */
+	LinphoneRegistrationProgress = 1,  /**< Registration is in progress */
+	LinphoneRegistrationOk = 2,        /**< Registration is successful */
+	LinphoneRegistrationCleared = 3,   /**< Unregistration succeeded */
+	LinphoneRegistrationFailed = 4,    /**< Registration failed */
+	LinphoneRegistrationRefreshing = 5 /**< Registration refreshing */
 } LinphoneRegistrationState;
 
 /**
diff --git a/src/account/account.cpp b/src/account/account.cpp
index 0dcde2ec0b..b2ab11d61a 100644
--- a/src/account/account.cpp
+++ b/src/account/account.cpp
@@ -466,6 +466,10 @@ const std::shared_ptr<Address> Account::getServiceRouteAddress() const {
 	return mServiceRouteAddress;
 }
 
+LinphoneRegistrationState Account::getPreviousState() const {
+	return mPreviousState;
+}
+
 LinphoneRegistrationState Account::getState() const {
 	return mState;
 }
@@ -663,7 +667,7 @@ void Account::refreshRegister() {
 
 	if (mParams->mRegisterEnabled && mOp && mState != LinphoneRegistrationProgress) {
 		if (mOp->refreshRegister(mParams->mExpires) == 0) {
-			setState(LinphoneRegistrationProgress, "Refresh registration");
+			setState(LinphoneRegistrationRefreshing, "Refresh registration");
 		}
 	}
 }
diff --git a/src/account/account.h b/src/account/account.h
index e43d381fad..b01575f195 100644
--- a/src/account/account.h
+++ b/src/account/account.h
@@ -88,6 +88,7 @@ public:
 	const std::shared_ptr<Address> &getPendingContactAddress() const;
 	const std::shared_ptr<Address> getServiceRouteAddress() const;
 	LinphoneRegistrationState getState() const;
+	LinphoneRegistrationState getPreviousState() const;
 	SalRegisterOp *getOp() const;
 	const char *getCustomHeader(const std::string &headerName) const;
 	std::shared_ptr<EventPublish> getPresencePublishEvent() const;
@@ -170,6 +171,7 @@ private:
 	mutable std::shared_ptr<Address> mServiceRouteAddress = nullptr;
 
 	LinphoneRegistrationState mState = LinphoneRegistrationNone;
+	LinphoneRegistrationState mPreviousState = LinphoneRegistrationNone;
 
 	SalRegisterOp *mOp = nullptr;
 	SalCustomHeader *mSentHeaders = nullptr;
diff --git a/src/c-wrapper/api/c-participant-device-identity.cpp b/src/c-wrapper/api/c-participant-device-identity.cpp
index b96e740661..63bb420cac 100644
--- a/src/c-wrapper/api/c-participant-device-identity.cpp
+++ b/src/c-wrapper/api/c-participant-device-identity.cpp
@@ -72,7 +72,13 @@ void linphone_participant_device_identity_set_capability_descriptor(LinphonePart
 	ParticipantDeviceIdentity::toCpp(deviceIdentity)->setCapabilityDescriptor(L_C_TO_STRING(descriptor));
 #endif
 }
-
+void linphone_participant_device_identity_set_capability_descriptor_2(LinphoneParticipantDeviceIdentity *deviceIdentity,
+                                                                      const bctbx_list_t *descriptor_list) {
+#ifdef HAVE_ADVANCED_IM
+	ParticipantDeviceIdentity::toCpp(deviceIdentity)
+	    ->setCapabilityDescriptor(L_GET_CPP_LIST_FROM_C_LIST(descriptor_list, const char *, string));
+#endif
+}
 const char *linphone_participant_device_identity_get_capability_descriptor(
     const LinphoneParticipantDeviceIdentity *deviceIdentity) {
 #ifdef HAVE_ADVANCED_IM
@@ -80,6 +86,13 @@ const char *linphone_participant_device_identity_get_capability_descriptor(
 #endif
 	return NULL;
 }
+const bctbx_list_t *linphone_participant_device_identity_get_capability_descriptor_list(
+    const LinphoneParticipantDeviceIdentity *deviceIdentity) {
+#ifdef HAVE_ADVANCED_IM
+	return L_GET_C_LIST_FROM_CPP_LIST(ParticipantDeviceIdentity::toCpp(deviceIdentity)->getCapabilityDescriptorList());
+#endif
+	return NULL;
+}
 
 const LinphoneAddress *
 linphone_participant_device_identity_get_address(const LinphoneParticipantDeviceIdentity *deviceIdentity) {
diff --git a/src/chat/chat-room/client-group-chat-room.cpp b/src/chat/chat-room/client-group-chat-room.cpp
index 70be78dc65..21ad93b098 100644
--- a/src/chat/chat-room/client-group-chat-room.cpp
+++ b/src/chat/chat-room/client-group-chat-room.cpp
@@ -328,7 +328,8 @@ void ClientGroupChatRoomPrivate::onCallSessionStateChanged(const shared_ptr<Call
 		}
 	} else if (newState == CallSession::State::Released) {
 		if (q->getState() == ConferenceInterface::State::TerminationPending) {
-			if (session->getReason() == LinphoneReasonNone || session->getReason() == LinphoneReasonDeclined) {
+			const auto &reason = session->getReason();
+			if ((reason == LinphoneReasonNone) || (reason == LinphoneReasonDeclined)) {
 				// Everything is fine, the chat room has been left on the server side.
 				// Or received 603 Declined, the chat room has been left on the server side but
 				// remains local.
@@ -340,6 +341,7 @@ void ClientGroupChatRoomPrivate::onCallSessionStateChanged(const shared_ptr<Call
 			}
 		}
 	} else if (newState == CallSession::State::Error) {
+		const auto &reason = session->getReason();
 		if (q->getState() == ConferenceInterface::State::CreationPending) {
 			q->setState(ConferenceInterface::State::CreationFailed);
 			// If there are chat message pending chat room creation, set state to NotDelivered and remove them from
@@ -348,8 +350,12 @@ void ClientGroupChatRoomPrivate::onCallSessionStateChanged(const shared_ptr<Call
 				message->getPrivate()->setState(ChatMessage::State::NotDelivered);
 			}
 			pendingCreationMessages.clear();
+			if (reason == LinphoneReasonForbidden) {
+				q->onConferenceTerminated(q->getConferenceAddress());
+				q->deleteFromDb();
+			}
 		} else if (q->getState() == ConferenceInterface::State::TerminationPending) {
-			if (session->getReason() == LinphoneReasonNotFound) {
+			if (reason == LinphoneReasonNotFound) {
 				// Somehow the chat room is no longer known on the server, so terminate it
 				q->onConferenceTerminated(q->getConferenceAddress());
 			} else {
@@ -375,9 +381,7 @@ void ClientGroupChatRoomPrivate::onChatRoomCreated(const std::shared_ptr<Address
 
 	q->onConferenceCreated(remoteContact);
 	if (remoteContact->hasParam("isfocus")) {
-		if (q->getCore()->getPrivate()->remoteListEventHandler->findHandler(q->getConferenceId())) {
-			q->getCore()->getPrivate()->remoteListEventHandler->subscribe();
-		} else {
+		if (!q->getCore()->getPrivate()->remoteListEventHandler->findHandler(q->getConferenceId())) {
 			bgTask.start(q->getCore(), 32); // It will be stopped when receiving the first notify
 			static_pointer_cast<RemoteConference>(q->getConference())->eventHandler->subscribe(q->getConferenceId());
 		}
diff --git a/src/chat/chat-room/server-group-chat-room-p.h b/src/chat/chat-room/server-group-chat-room-p.h
index 28568130bc..9c34f4c688 100644
--- a/src/chat/chat-room/server-group-chat-room-p.h
+++ b/src/chat/chat-room/server-group-chat-room-p.h
@@ -44,21 +44,22 @@ class ParticipantDeviceIdentity
 public:
 	ParticipantDeviceIdentity(const std::shared_ptr<Address> &address, const std::string &name);
 	void setCapabilityDescriptor(const std::string &capabilities);
+	void setCapabilityDescriptor(const std::list<std::string> &capabilities);
 	const std::shared_ptr<Address> &getAddress() const {
 		return mDeviceAddress;
 	}
 	const std::string &getName() const {
 		return mDeviceName;
 	}
-	const std::string &getCapabilityDescriptor() const {
-		return mCapabilityDescriptor;
-	}
+	const std::string &getCapabilityDescriptor() const;
+	const std::list<std::string> getCapabilityDescriptorList() const;
 	virtual ~ParticipantDeviceIdentity();
 
 private:
 	std::shared_ptr<Address> mDeviceAddress;
 	std::string mDeviceName;
-	std::string mCapabilityDescriptor; // +org.linphone.specs capability descriptor
+	mutable std::string mCapabilityDescriptorString;
+	std::map<std::string, std::string> mCapabilityDescriptor; // +org.linphone.specs capability descriptor
 };
 
 class ServerGroupChatRoomPrivate : public ChatRoomPrivate {
diff --git a/src/chat/chat-room/server-group-chat-room.cpp b/src/chat/chat-room/server-group-chat-room.cpp
index d930725eb9..2b7afd2150 100644
--- a/src/chat/chat-room/server-group-chat-room.cpp
+++ b/src/chat/chat-room/server-group-chat-room.cpp
@@ -57,7 +57,39 @@ ParticipantDeviceIdentity::ParticipantDeviceIdentity(const std::shared_ptr<Addre
 }
 
 void ParticipantDeviceIdentity::setCapabilityDescriptor(const string &capabilities) {
-	mCapabilityDescriptor = capabilities;
+	setCapabilityDescriptor(Utils::toList(bctoolbox::Utils::split(capabilities, ",")));
+}
+
+void ParticipantDeviceIdentity::setCapabilityDescriptor(const std::list<std::string> &capabilities) {
+	for (const auto &spec : capabilities) {
+		const auto nameVersion = Core::getSpecNameVersion(spec);
+		const auto &name = nameVersion.first;
+		const auto &version = nameVersion.second;
+		mCapabilityDescriptor[name] = version;
+	}
+}
+
+const std::string &ParticipantDeviceIdentity::getCapabilityDescriptor() const {
+	const std::list<std::string> capabilityDescriptor = getCapabilityDescriptorList();
+	mCapabilityDescriptorString = Utils::join(Utils::toVector(capabilityDescriptor), ",");
+	return mCapabilityDescriptorString;
+}
+
+const std::list<std::string> ParticipantDeviceIdentity::getCapabilityDescriptorList() const {
+	std::list<std::string> specsList;
+	for (const auto &nameVersion : mCapabilityDescriptor) {
+		const auto &name = nameVersion.first;
+		const auto &version = nameVersion.second;
+		std::string specNameVersion;
+		specNameVersion += name;
+		if (!version.empty()) {
+			specNameVersion += "/";
+			specNameVersion += version;
+		}
+		specsList.push_back(specNameVersion);
+	}
+
+	return specsList;
 }
 
 ParticipantDeviceIdentity::~ParticipantDeviceIdentity() {
@@ -159,6 +191,25 @@ void ServerGroupChatRoomPrivate::confirmCreation() {
 	shared_ptr<CallSession> session = me->getSession();
 	session->startIncomingNotification(false);
 
+	const auto &remoteContactAddress = session->getRemoteContactAddress();
+	if (remoteContactAddress->hasParam("+org.linphone.specs")) {
+		const auto linphoneSpecs = remoteContactAddress->getParamValue("+org.linphone.specs");
+		// The creator of the chatroom must have the capability "groupchat"
+		auto protocols = Utils::parseCapabilityDescriptor(linphoneSpecs.substr(1, linphoneSpecs.size() - 2));
+		auto groupchat = protocols.find("groupchat");
+		if (groupchat == protocols.end()) {
+			lError() << "Creator " << remoteContactAddress->asStringUriOnly()
+			         << " has no groupchat capability set: " << linphoneSpecs;
+			q->setState(ConferenceInterface::State::CreationFailed);
+			auto errorInfo = linphone_error_info_new();
+			linphone_error_info_set(errorInfo, nullptr, LinphoneReasonNotAcceptable, 488,
+			                        "\"groupchat\" capability has not been found in remote contact address", nullptr);
+			session->decline(errorInfo);
+			linphone_error_info_unref(errorInfo);
+			return;
+		}
+	}
+
 	/* Assign a random conference address to this new chatroom, with domain
 	 * set according to the proxy config used to receive the INVITE.
 	 */
@@ -727,19 +778,34 @@ void ServerGroupChatRoomPrivate::updateParticipantDevices(const std::shared_ptr<
 
 void ServerGroupChatRoomPrivate::conclude() {
 	L_Q();
-	lInfo() << q << "All devices are known, the chatroom creation can be concluded.";
+	lInfo() << q << " All devices are known, the chatroom creation can be concluded.";
 	shared_ptr<CallSession> session = mInitiatorDevice->getSession();
 
 	if (!session) {
-		lError() << "ServerGroupChatRoomPrivate::conclude(): initiator's session died.";
+		lError() << q << "ServerGroupChatRoomPrivate::conclude(): initiator's session died.";
 		requestDeletion();
 		return;
 	}
 
+	const auto device = q->getConference()->findParticipantDevice(session);
 	if (q->getParticipants().size() < 2) {
 		lError() << q << ": there are less than 2 participants in this chatroom, refusing creation.";
 		declineSession(session, LinphoneReasonNotAcceptable);
 		requestDeletion();
+	} else if (!device || (device->getState() != ParticipantDevice::State::Joining)) {
+		// We may end up here if a client successfully created a chat room but the conference server thinks it should be
+		// allowed to be part of a conference. A scenario where this branch is hit is the following. The client creating
+		// the chatroom registered into the server without the groupchat capability. Nonetheless, the capability is
+		// added in the INVITE creating the chatroom. Upon reception of the 302 Moved Temporarely, the client will dial
+		// the chatroom URI directly and the server will look for devices allowed to join the chatroom in method
+		// ServerGroupChatRoomPrivate::subscribeRegistrationForParticipants(). Since "groupchat" capability was not
+		// there, then the server doesn't allow to conclude the creation of the chatroom
+		// -
+		lError() << q
+		         << ": Declining session because it looks like the device creating the chatroom is not allowed to be "
+		            "part of this chatroom";
+		declineSession(session, LinphoneReasonForbidden);
+		requestDeletion();
 	} else {
 		/* Ok we are going to accept the session with 200Ok. However we want to wait for the ACK to be sure
 		 * that the initiator is aware that he's now part of the conference, before we invite the others.
@@ -1296,6 +1362,17 @@ void ServerGroupChatRoomPrivate::onCallSessionStateChanged(const shared_ptr<Call
 	auto device = q->findCachedParticipantDevice(session);
 	if (!device) {
 		lInfo() << q << " onCallSessionStateChanged on unknown device (maybe not yet).";
+		if ((newState == CallSession::State::Released) && (session->getReason() == LinphoneReasonNotAcceptable) &&
+		    (q->getState() == ConferenceInterface::State::CreationFailed)) {
+			// Delete the chat room from the main DB as its termination process started and it cannot be retrieved in
+			// the future
+			lInfo() << q << ": Delete chatroom from MainDB as its creation failed";
+			unique_ptr<MainDb> &mainDb = q->getCore()->getPrivate()->mainDb;
+			mainDb->deleteChatRoom(q->getConferenceId());
+			q->setState(ConferenceInterface::State::TerminationPending);
+			q->setState(ConferenceInterface::State::Terminated);
+			requestDeletion();
+		}
 		return;
 	}
 	switch (newState) {
diff --git a/src/chat/encryption/lime-x3dh-encryption-engine.cpp b/src/chat/encryption/lime-x3dh-encryption-engine.cpp
index f0262e33d4..1b1e794281 100644
--- a/src/chat/encryption/lime-x3dh-encryption-engine.cpp
+++ b/src/chat/encryption/lime-x3dh-encryption-engine.cpp
@@ -1014,12 +1014,11 @@ void LimeX3dhEncryptionEngine::onNetworkReachable(BCTBX_UNUSED(bool sipNetworkRe
                                                   BCTBX_UNUSED(bool mediaNetworkReachable)) {
 }
 
-void LimeX3dhEncryptionEngine::onRegistrationStateChanged(LinphoneProxyConfig *cfg,
-                                                          LinphoneRegistrationState state,
-                                                          BCTBX_UNUSED(const string &message)) {
+void LimeX3dhEncryptionEngine::onAccountRegistrationStateChanged(std::shared_ptr<Account> account,
+                                                                 LinphoneRegistrationState state,
+                                                                 BCTBX_UNUSED(const string &message)) {
 	if (state != LinphoneRegistrationState::LinphoneRegistrationOk) return;
 
-	auto account = Account::toCpp(cfg->account);
 	auto accountParams = account->getAccountParams();
 	// The LIME server URL set in the account parameters is preferred to that set in the core parameters
 	string accountLimeServerUrl = accountParams->getLimeServerUrl();
@@ -1034,11 +1033,10 @@ void LimeX3dhEncryptionEngine::onRegistrationStateChanged(LinphoneProxyConfig *c
 		return;
 	}
 
-	std::shared_ptr<Address> identityAddress =
-	    Address::toCpp(const_cast<LinphoneAddress *>(linphone_proxy_config_get_contact(cfg)))->getSharedFromThis();
+	const std::shared_ptr<Address> &identityAddress = account->getContactAddress();
 	string localDeviceId = identityAddress->asStringUriOnly();
 
-	LinphoneCore *lc = linphone_proxy_config_get_core(cfg);
+	LinphoneCore *lc = account->getCore();
 	lInfo() << "[LIME] Load lime user for device " << localDeviceId << " with server URL [" << accountLimeServerUrl
 	        << "]";
 
diff --git a/src/chat/encryption/lime-x3dh-encryption-engine.h b/src/chat/encryption/lime-x3dh-encryption-engine.h
index 7c8c80d2ad..e8f858c7bb 100644
--- a/src/chat/encryption/lime-x3dh-encryption-engine.h
+++ b/src/chat/encryption/lime-x3dh-encryption-engine.h
@@ -140,9 +140,9 @@ public:
 
 	void onNetworkReachable(bool sipNetworkReachable, bool mediaNetworkReachable) override;
 
-	void onRegistrationStateChanged(LinphoneProxyConfig *cfg,
-	                                LinphoneRegistrationState state,
-	                                const std::string &message) override;
+	void onAccountRegistrationStateChanged(std::shared_ptr<Account> account,
+	                                       LinphoneRegistrationState state,
+	                                       const std::string &message) override;
 
 	void onServerUrlChanged(const std::shared_ptr<Account> &account, const std::string &limeServerUrl) override;
 
diff --git a/src/conference/handlers/remote-conference-event-handler.cpp b/src/conference/handlers/remote-conference-event-handler.cpp
index e43cfbb2ac..35d87b250e 100644
--- a/src/conference/handlers/remote-conference-event-handler.cpp
+++ b/src/conference/handlers/remote-conference-event-handler.cpp
@@ -693,9 +693,9 @@ void RemoteConferenceEventHandler::onNetworkReachable(bool sipNetworkReachable,
 	}
 }
 
-void RemoteConferenceEventHandler::onRegistrationStateChanged(BCTBX_UNUSED(LinphoneProxyConfig *cfg),
-                                                              LinphoneRegistrationState state,
-                                                              BCTBX_UNUSED(const std::string &message)) {
+void RemoteConferenceEventHandler::onAccountRegistrationStateChanged(BCTBX_UNUSED(std::shared_ptr<Account> account),
+                                                                     LinphoneRegistrationState state,
+                                                                     BCTBX_UNUSED(const std::string &message)) {
 	if (state == LinphoneRegistrationOk) subscribe();
 }
 
diff --git a/src/conference/handlers/remote-conference-event-handler.h b/src/conference/handlers/remote-conference-event-handler.h
index 4d56588770..a10308733e 100644
--- a/src/conference/handlers/remote-conference-event-handler.h
+++ b/src/conference/handlers/remote-conference-event-handler.h
@@ -76,9 +76,9 @@ protected:
 
 	// CoreListener
 	void onNetworkReachable(bool sipNetworkReachable, bool mediaNetworkReachable) override;
-	void onRegistrationStateChanged(LinphoneProxyConfig *cfg,
-	                                LinphoneRegistrationState state,
-	                                const std::string &message) override;
+	void onAccountRegistrationStateChanged(std::shared_ptr<Account> account,
+	                                       LinphoneRegistrationState state,
+	                                       const std::string &message) override;
 	void onEnteringBackground() override;
 	void onEnteringForeground() override;
 
diff --git a/src/conference/handlers/remote-conference-list-event-handler.cpp b/src/conference/handlers/remote-conference-list-event-handler.cpp
index d1f0d66cb1..a6f6d40bf6 100644
--- a/src/conference/handlers/remote-conference-list-event-handler.cpp
+++ b/src/conference/handlers/remote-conference-list-event-handler.cpp
@@ -361,19 +361,20 @@ void RemoteConferenceListEventHandler::onNetworkReachable(bool sipNetworkReachab
 	}
 }
 
-void RemoteConferenceListEventHandler::onRegistrationStateChanged(LinphoneProxyConfig *cfg,
-                                                                  LinphoneRegistrationState state,
-                                                                  BCTBX_UNUSED(const std::string &message)) {
-	const auto &account = Account::toCpp(cfg->account)->getSharedFromThis();
-	if (state == LinphoneRegistrationOk) subscribe(account);
-	else if (state == LinphoneRegistrationCleared) { // On cleared, restart subscription if the cleared proxy config
-		                                             // is the current subscription
-		const LinphoneAddress *cfgAddress = linphone_proxy_config_get_identity_address(cfg);
-		auto it = std::find_if(levs.begin(), levs.end(), [&cfgAddress](const auto &evSub) {
-			const auto &currentAddress = evSub->getFrom();
-			return currentAddress->weakEqual(*Address::toCpp(cfgAddress));
+void RemoteConferenceListEventHandler::onAccountRegistrationStateChanged(std::shared_ptr<Account> account,
+                                                                         LinphoneRegistrationState state,
+                                                                         BCTBX_UNUSED(const std::string &message)) {
+	if (state == LinphoneRegistrationOk && (account->getPreviousState() != LinphoneRegistrationRefreshing))
+		subscribe(account);
+	else if (state == LinphoneRegistrationCleared) { // On cleared, restart subscription if the cleared proxy config is
+		                                             // the current subscription
+		const auto &accountParams = account->getAccountParams();
+		const auto &cfgAddress = accountParams->getIdentityAddress();
+		auto it = std::find_if(levs.begin(), levs.end(), [&cfgAddress](const auto &lev) {
+			return (*Address::create(lev->getOp()->getFrom()) == *cfgAddress);
 		});
 
+		// If no subscription is found, then unsubscribe the account
 		if (it != levs.end()) unsubscribe(account);
 	}
 }
diff --git a/src/conference/handlers/remote-conference-list-event-handler.h b/src/conference/handlers/remote-conference-list-event-handler.h
index a06a893341..e455f4c4bf 100644
--- a/src/conference/handlers/remote-conference-list-event-handler.h
+++ b/src/conference/handlers/remote-conference-list-event-handler.h
@@ -69,9 +69,9 @@ private:
 
 	// CoreListener
 	void onNetworkReachable(bool sipNetworkReachable, bool mediaNetworkReachable) override;
-	void onRegistrationStateChanged(LinphoneProxyConfig *cfg,
-	                                LinphoneRegistrationState state,
-	                                const std::string &message) override;
+	void onAccountRegistrationStateChanged(std::shared_ptr<Account> account,
+	                                       LinphoneRegistrationState state,
+	                                       BCTBX_UNUSED(const std::string &message)) override;
 	void onEnteringBackground() override;
 	void onEnteringForeground() override;
 };
diff --git a/src/conference/participant.h b/src/conference/participant.h
index c1df4a4fd2..0b8c351471 100644
--- a/src/conference/participant.h
+++ b/src/conference/participant.h
@@ -39,10 +39,6 @@
 
 class LocalConferenceTester;
 
-namespace LinphoneTest {
-class LocalConferenceTester;
-}
-
 LINPHONE_BEGIN_NAMESPACE
 
 namespace MediaConference {
@@ -78,7 +74,6 @@ class LINPHONE_PUBLIC Participant : public bellesip::HybridObject<LinphonePartic
 	friend class ServerGroupChatRoom;
 	friend class ServerGroupChatRoomPrivate;
 
-	friend class LinphoneTest::LocalConferenceTester;
 	friend class ::LocalConferenceTester;
 
 public:
diff --git a/src/core/core-listener.h b/src/core/core-listener.h
index e3cd374243..e34d7c1408 100644
--- a/src/core/core-listener.h
+++ b/src/core/core-listener.h
@@ -31,6 +31,8 @@
 
 LINPHONE_BEGIN_NAMESPACE
 
+class Account;
+
 class LINPHONE_PUBLIC CoreListener {
 public:
 	virtual ~CoreListener() = default;
@@ -43,6 +45,10 @@ public:
 	                                        BCTBX_UNUSED(LinphoneRegistrationState state),
 	                                        BCTBX_UNUSED(const std::string &message)) {
 	}
+	virtual void onAccountRegistrationStateChanged(BCTBX_UNUSED(std::shared_ptr<Account> account),
+	                                               BCTBX_UNUSED(LinphoneRegistrationState state),
+	                                               BCTBX_UNUSED(const std::string &message)) {
+	}
 	virtual void onCallStateChanged(BCTBX_UNUSED(LinphoneCall *call),
 	                                BCTBX_UNUSED(LinphoneCallState state),
 	                                BCTBX_UNUSED(const std::string &message)) {
diff --git a/src/core/core-p.h b/src/core/core-p.h
index d9a143a676..a01e30c248 100644
--- a/src/core/core-p.h
+++ b/src/core/core-p.h
@@ -65,6 +65,9 @@ public:
 	void notifyGlobalStateChanged(LinphoneGlobalState state);
 	void notifyNetworkReachable(bool sipNetworkReachable, bool mediaNetworkReachable);
 	void notifyCallStateChanged(LinphoneCall *cfg, LinphoneCallState state, const std::string &message);
+	void notifyRegistrationStateChanged(std::shared_ptr<Account> account,
+	                                    LinphoneRegistrationState state,
+	                                    const std::string &message);
 	void notifyRegistrationStateChanged(LinphoneProxyConfig *cfg,
 	                                    LinphoneRegistrationState state,
 	                                    const std::string &message);
diff --git a/src/core/core.cpp b/src/core/core.cpp
index a434a6eeca..2552cace21 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -430,6 +430,14 @@ void CorePrivate::notifyCallStateChanged(LinphoneCall *call, LinphoneCallState s
 		listener->onCallStateChanged(call, state, message);
 }
 
+void CorePrivate::notifyRegistrationStateChanged(std::shared_ptr<Account> account,
+                                                 LinphoneRegistrationState state,
+                                                 const string &message) {
+	auto listenersCopy = listeners; // Allow removal of a listener in its own call
+	for (const auto &listener : listenersCopy)
+		listener->onAccountRegistrationStateChanged(account, state, message);
+}
+
 void CorePrivate::notifyRegistrationStateChanged(LinphoneProxyConfig *cfg,
                                                  LinphoneRegistrationState state,
                                                  const string &message) {
@@ -834,13 +842,13 @@ void Core::setSpecs(const std::map<std::string, std::string> &specsMap) {
 void Core::setSpecs(const std::list<std::string> &specsList) {
 	std::map<std::string, std::string> specsMap;
 	for (const auto &spec : specsList) {
-		const auto [name, version] = getSpecNameVersion(spec);
+		const auto [name, version] = Core::getSpecNameVersion(spec);
 		specsMap[name] = version;
 	}
 	setSpecs(specsMap);
 }
 
-std::pair<std::string, std::string> Core::getSpecNameVersion(const std::string &spec) const {
+std::pair<std::string, std::string> Core::getSpecNameVersion(const std::string &spec) {
 	std::string specName;
 	std::string specVersion;
 	const auto slashPos = spec.find("/");
@@ -860,7 +868,7 @@ void Core::addSpec(const std::string &specName, const std::string &specVersion)
 }
 
 void Core::addSpec(const std::string &spec) {
-	const auto [name, version] = getSpecNameVersion(spec);
+	const auto [name, version] = Core::getSpecNameVersion(spec);
 	addSpec(name, version);
 }
 
@@ -934,7 +942,6 @@ const std::list<std::string> Core::getSpecsList() const {
 		}
 		specsList.push_back(specNameVersion);
 	}
-
 	return specsList;
 }
 
diff --git a/src/core/core.h b/src/core/core.h
index ad1fca9b37..9994f2d9fc 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -43,9 +43,7 @@ L_DECL_C_STRUCT(LinphoneCore);
 
 typedef struct belle_sip_source belle_sip_source_t;
 
-namespace LinphoneTest {
 class LocalConferenceTester;
-}
 
 LINPHONE_BEGIN_NAMESPACE
 
@@ -110,7 +108,7 @@ class LINPHONE_PUBLIC Core : public Object {
 	friend class MediaConference::RemoteConference;
 	friend class ConferenceScheduler;
 
-	friend class LinphoneTest::LocalConferenceTester;
+	friend class ::LocalConferenceTester;
 
 public:
 	L_OVERRIDE_SHARED_FROM_THIS(Core);
@@ -272,6 +270,7 @@ public:
 	std::string getSpecs() const;
 	const std::map<std::string, std::string> &getSpecsMap() const;
 	const std::list<std::string> getSpecsList() const;
+	static std::pair<std::string, std::string> getSpecNameVersion(const std::string &spec);
 
 	// ---------------------------------------------------------------------------
 	// Friends.
@@ -383,8 +382,6 @@ private:
 	std::shared_ptr<SignalInformation> mSignalInformation = nullptr;
 	const ConferenceId prepareConfereceIdForSearch(const ConferenceId &conferenceId) const;
 
-	std::pair<std::string, std::string> getSpecNameVersion(const std::string &spec) const;
-
 	std::list<std::string> plugins;
 #if defined(_WIN32) && !defined(_WIN32_WCE)
 	std::list<HINSTANCE> loadedPlugins;
diff --git a/tester/group_chat_tester.c b/tester/group_chat_tester.c
index df06d20bc7..00cc368c8f 100644
--- a/tester/group_chat_tester.c
+++ b/tester/group_chat_tester.c
@@ -8950,7 +8950,7 @@ test_t group_chat3_tests[] = {
     TEST_ONE_TAG("Check if participant device are removed",
                  group_chat_room_join_one_to_one_chat_room_with_a_new_device_not_notified,
                  "LeaksMemory" /*due to core restart*/),
-    TEST_ONE_TAG("Subscribe successfull after set chat database path",
+    TEST_ONE_TAG("Subscribe successful after set chat database path",
                  subscribe_test_after_set_chat_database_path,
                  "LeaksMemory" /*due to core restart*/),
     TEST_NO_TAG("Send forward message", one_to_one_chat_room_send_forward_message),
@@ -8964,7 +8964,7 @@ test_t group_chat3_tests[] = {
     TEST_ONE_TAG("Linphone core stop/start and chatroom ref",
                  core_stop_start_with_chat_room_ref,
                  "LeaksMemory" /*due to core restart*/),
-    TEST_ONE_TAG("Subscribe successfull after set chat database path",
+    TEST_ONE_TAG("Subscribe successful after set chat database path",
                  subscribe_test_after_set_chat_database_path,
                  "LeaksMemory" /*due to core restart*/),
     TEST_NO_TAG("Make sure device unregistration does not triger user to be removed from a group",
diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h
index 159e7af538..d490e4feaa 100644
--- a/tester/liblinphone_tester.h
+++ b/tester/liblinphone_tester.h
@@ -249,6 +249,7 @@ extern unsigned int liblinphone_tester_max_cpu_count;
 typedef struct _stats {
 	int number_of_LinphoneRegistrationNone;
 	int number_of_LinphoneRegistrationProgress;
+	int number_of_LinphoneRegistrationRefreshing;
 	int number_of_LinphoneRegistrationOk;
 	int number_of_LinphoneRegistrationCleared;
 	int number_of_LinphoneRegistrationFailed;
diff --git a/tester/local_conference_tester.cpp b/tester/local_conference_tester.cpp
index c97806b65c..965b80f60c 100644
--- a/tester/local_conference_tester.cpp
+++ b/tester/local_conference_tester.cpp
@@ -309,13 +309,21 @@ private:
 				auto participantRange = focus->mParticipantDevices.equal_range(participant);
 				for (auto participantIt = participantRange.first; participantIt != participantRange.second;
 				     participantIt++) {
-					LinphoneAddress *deviceAddr = linphone_address_new(participantIt->second.toString().c_str());
-					LinphoneParticipantDeviceIdentity *identity =
-					    linphone_factory_create_participant_device_identity(linphone_factory_get(), deviceAddr, "");
-					linphone_participant_device_identity_set_capability_descriptor(
-					    identity, linphone_core_get_linphone_specs(linphone_chat_room_get_core(cr)));
-					devices = bctbx_list_append(devices, identity);
-					linphone_address_unref(deviceAddr);
+					const bctbx_list_t *specs = linphone_core_get_linphone_specs_list(linphone_chat_room_get_core(cr));
+					bool groupchat_enabled = false;
+					for (const bctbx_list_t *specIt = specs; specIt != NULL; specIt = specIt->next) {
+						const char *spec = (const char *)bctbx_list_get_data(specIt);
+						// Search for "groupchat" string in the capability
+						groupchat_enabled |= (strstr(spec, "groupchat") != NULL);
+					}
+					if (groupchat_enabled) {
+						LinphoneAddress *deviceAddr = linphone_address_new(participantIt->second.toString().c_str());
+						LinphoneParticipantDeviceIdentity *identity =
+						    linphone_factory_create_participant_device_identity(linphone_factory_get(), deviceAddr, "");
+						linphone_participant_device_identity_set_capability_descriptor_2(identity, specs);
+						devices = bctbx_list_append(devices, identity);
+						linphone_address_unref(deviceAddr);
+					}
 				}
 				linphone_chat_room_set_participant_devices(cr, participant.toC(), devices);
 				bctbx_list_free_with_data(devices, (bctbx_list_free_func)belle_sip_object_unref);
@@ -1778,9 +1786,7 @@ static void group_chat_room_bulk_notify_to_participant(void) {
 		BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(paulineCr), 2, int, "%d");
 		BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(paulineCr), newSubject2);
 
-		CoreManagerAssert({focus, marie, pauline, michelle}).waitUntil(std::chrono::seconds(1), [] { return false; });
-
-		CoreManagerAssert({focus, marie}).waitUntil(std::chrono::seconds(2), [] { return false; });
+		CoreManagerAssert({focus, marie, pauline, michelle}).waitUntil(std::chrono::seconds(2), [] { return false; });
 
 		for (auto chatRoom : focus.getCore().getChatRooms()) {
 			for (auto participant : chatRoom->getParticipants()) {
@@ -1796,7 +1802,7 @@ static void group_chat_room_bulk_notify_to_participant(void) {
 		}));
 
 		// wait bit more to detect side effect if any
-		CoreManagerAssert({focus, marie, pauline}).waitUntil(chrono::seconds(2), [] { return false; });
+		CoreManagerAssert({focus, marie, pauline, michelle}).waitUntil(std::chrono::seconds(2), [] { return false; });
 
 		// to avoid creation attempt of a new chatroom
 		auto config = focus.getDefaultProxyConfig();
@@ -1939,14 +1945,14 @@ static void group_chat_room_with_client_restart_base(bool encrypted) {
 		const LinphoneAddress *deviceAddr = linphone_proxy_config_get_contact(michelle.getDefaultProxyConfig());
 		LinphoneParticipantDeviceIdentity *identity =
 		    linphone_factory_create_participant_device_identity(linphone_factory_get(), deviceAddr, "");
-		linphone_participant_device_identity_set_capability_descriptor(
-		    identity, linphone_core_get_linphone_specs(michelle.getLc()));
+		linphone_participant_device_identity_set_capability_descriptor_2(
+		    identity, linphone_core_get_linphone_specs_list(michelle.getLc()));
 		devices = bctbx_list_append(devices, identity);
 
 		deviceAddr = linphone_proxy_config_get_contact(michelle2.getDefaultProxyConfig());
 		identity = linphone_factory_create_participant_device_identity(linphone_factory_get(), deviceAddr, "");
-		linphone_participant_device_identity_set_capability_descriptor(
-		    identity, linphone_core_get_linphone_specs(michelle2.getLc()));
+		linphone_participant_device_identity_set_capability_descriptor_2(
+		    identity, linphone_core_get_linphone_specs_list(michelle2.getLc()));
 		devices = bctbx_list_append(devices, identity);
 
 		for (auto chatRoom : focus.getCore().getChatRooms()) {
@@ -2038,12 +2044,16 @@ static void group_chat_room_with_client_restart_base(bool encrypted) {
 		};
 
 		// wait bit more to detect side effect if any
-		CoreManagerAssert({focus, marie, michelle, michelle2}).waitUntil(chrono::seconds(1), [] { return false; });
+		CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).waitUntil(chrono::seconds(1), [] {
+			return false;
+		});
 
 		time_t participantAddedTime = ms_time(nullptr);
 
 		// wait bit more to detect side effect if any
-		CoreManagerAssert({focus, marie, michelle, michelle2}).waitUntil(chrono::seconds(10), [] { return false; });
+		CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).waitUntil(chrono::seconds(10), [] {
+			return false;
+		});
 
 		ms_message("%s is restarting its core", linphone_core_get_identity(focus.getLc()));
 		coresList = bctbx_list_remove(coresList, focus.getLc());
@@ -2078,10 +2088,10 @@ static void group_chat_room_with_client_restart_base(bool encrypted) {
 
 		LinphoneChatMessage *msg = linphone_chat_room_create_message_from_utf8(michelle2Cr, "back with you");
 		linphone_chat_message_send(msg);
-		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2}).wait([msg] {
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).wait([msg] {
 			return (linphone_chat_message_get_state(msg) == LinphoneChatMessageStateDelivered);
 		}));
-		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2}).wait([marieCr] {
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).wait([marieCr] {
 			return linphone_chat_room_get_unread_messages_count(marieCr) == 1;
 		}));
 		linphone_chat_message_unref(msg);
@@ -2089,13 +2099,13 @@ static void group_chat_room_with_client_restart_base(bool encrypted) {
 
 		msg = linphone_chat_room_create_message_from_utf8(marieCr, "welcome back");
 		linphone_chat_message_send(msg);
-		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2}).wait([msg] {
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).wait([msg] {
 			return (linphone_chat_message_get_state(msg) == LinphoneChatMessageStateDelivered);
 		}));
-		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2}).wait([michelleCr] {
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).wait([michelleCr] {
 			return linphone_chat_room_get_unread_messages_count(michelleCr) == 1;
 		}));
-		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2}).wait([michelle2Cr] {
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).wait([michelle2Cr] {
 			return linphone_chat_room_get_unread_messages_count(michelle2Cr) == 1;
 		}));
 		linphone_chat_message_unref(msg);
@@ -2103,17 +2113,17 @@ static void group_chat_room_with_client_restart_base(bool encrypted) {
 
 		msg = linphone_chat_room_create_message_from_utf8(michelleCr, "message blabla");
 		linphone_chat_message_send(msg);
-		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2}).wait([msg] {
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).wait([msg] {
 			return (linphone_chat_message_get_state(msg) == LinphoneChatMessageStateDelivered);
 		}));
-		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2}).wait([marieCr] {
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).wait([marieCr] {
 			return linphone_chat_room_get_unread_messages_count(marieCr) == 2;
 		}));
 		linphone_chat_message_unref(msg);
 
-		CoreManagerAssert({focus, marie}).waitUntil(std::chrono::seconds(1), [] { return false; });
-
-		CoreManagerAssert({focus, marie}).waitUntil(std::chrono::seconds(2), [] { return false; });
+		CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).waitUntil(std::chrono::seconds(2), [] {
+			return false;
+		});
 
 		for (auto chatRoom : focus.getCore().getChatRooms()) {
 			for (auto participant : chatRoom->getParticipants()) {
@@ -2124,12 +2134,14 @@ static void group_chat_room_with_client_restart_base(bool encrypted) {
 		}
 
 		// wait until chatroom is deleted server side
-		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2}).wait([&focus] {
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).wait([&focus] {
 			return focus.getCore().getChatRooms().size() == 0;
 		}));
 
 		// wait bit more to detect side effect if any
-		CoreManagerAssert({focus, marie, michelle, michelle2}).waitUntil(chrono::seconds(2), [] { return false; });
+		CoreManagerAssert({focus, marie, michelle, michelle2, laure, berthe}).waitUntil(chrono::seconds(2), [] {
+			return false;
+		});
 
 		// to avoid creation attempt of a new chatroom
 		auto config = focus.getDefaultProxyConfig();
@@ -2151,6 +2163,617 @@ static void secure_group_chat_room_with_client_restart(void) {
 	group_chat_room_with_client_restart_base(true);
 }
 
+static void group_chat_room_with_client_registering_with_short_register_expires(void) {
+	Focus focus("chloe_rc");
+	{ // to make sure focus is destroyed after clients.
+		bool_t encrypted = FALSE;
+		ClientConference marie("marie_rc", focus.getIdentity(), encrypted);
+		ClientConference michelle("michelle_rc", focus.getIdentity(), encrypted);
+		ClientConference berthe("berthe_rc", focus.getIdentity(), encrypted);
+
+		focus.registerAsParticipantDevice(marie);
+		focus.registerAsParticipantDevice(michelle);
+		focus.registerAsParticipantDevice(berthe);
+
+		bctbx_list_t *coresList = bctbx_list_append(NULL, focus.getLc());
+		coresList = bctbx_list_append(coresList, marie.getLc());
+		coresList = bctbx_list_append(coresList, michelle.getLc());
+		coresList = bctbx_list_append(coresList, berthe.getLc());
+		bctbx_list_t *participantsAddresses = NULL;
+		Address michelleAddr = michelle.getIdentity();
+		participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(michelleAddr.toC()));
+		Address bertheAddr = berthe.getIdentity();
+		participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(bertheAddr.toC()));
+
+		stats initialMarieStats = marie.getStats();
+		stats initialMichelleStats = michelle.getStats();
+		stats initialBertheStats = berthe.getStats();
+		//
+		// Marie creates a new group chat room
+		const char *initialSubject = "Colleagues (characters: $ £ çà)";
+		LinphoneChatRoom *marieCr = create_chat_room_client_side_with_expected_number_of_participants(
+		    coresList, marie.getCMgr(), &initialMarieStats, participantsAddresses, initialSubject, 2, encrypted,
+		    LinphoneChatRoomEphemeralModeDeviceManaged);
+		const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marieCr);
+
+		// Check that the chat room is correctly created on Michelle's side and that the participants are added
+		LinphoneChatRoom *michelleCr = check_creation_chat_room_client_side(
+		    coresList, michelle.getCMgr(), &initialMichelleStats, confAddr, initialSubject, 2, FALSE);
+
+		LinphoneChatRoom *bertheCr = check_creation_chat_room_client_side(
+		    coresList, berthe.getCMgr(), &initialBertheStats, confAddr, initialSubject, 2, FALSE);
+
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([&focus] {
+			for (const auto &chatRoom : focus.getCore().getChatRooms()) {
+				for (const auto &participant : chatRoom->getParticipants()) {
+					for (const auto &device : participant->getDevices()) {
+						if (device->getState() != ParticipantDevice::State::Present) {
+							return false;
+						}
+					}
+				}
+			}
+			return true;
+		}));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneConferenceStateCreated,
+		                             initialMichelleStats.number_of_LinphoneConferenceStateCreated + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &berthe.getStats().number_of_LinphoneConferenceStateCreated,
+		                             initialBertheStats.number_of_LinphoneConferenceStateCreated + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		// Marie now changes the subject
+		const char *newSubject = "Let's go drink a beer";
+		linphone_chat_room_set_subject(marieCr, newSubject);
+		BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_subject_changed,
+		                             initialMarieStats.number_of_subject_changed + 1, liblinphone_tester_sip_timeout));
+		BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_subject_changed,
+		                             initialMichelleStats.number_of_subject_changed + 1,
+		                             liblinphone_tester_sip_timeout));
+		BC_ASSERT_TRUE(wait_for_list(coresList, &berthe.getStats().number_of_subject_changed,
+		                             initialBertheStats.number_of_subject_changed + 1, liblinphone_tester_sip_timeout));
+		BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(marieCr), newSubject);
+		BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(michelleCr), newSubject);
+		BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(bertheCr), newSubject);
+
+		BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(marieCr), 2, int, "%d");
+		BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(michelleCr), 2, int, "%d");
+		BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(bertheCr), 2, int, "%d");
+
+		const std::initializer_list<std::reference_wrapper<ConfCoreManager>> cores2{focus, marie, michelle, berthe};
+		for (const ConfCoreManager &core : cores2) {
+			BC_ASSERT_TRUE(
+			    CoreManagerAssert({focus, marie, michelle, berthe}).waitUntil(chrono::seconds(10), [&focus, &core] {
+				    return checkChatroom(focus, core, -1);
+			    }));
+		};
+
+		initialMichelleStats = michelle.getStats();
+		stats initialFocusStats = focus.getStats();
+
+		int expires = 1;
+		LinphoneAddress *michelleContact = linphone_address_clone(
+		    linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(michelle.getLc())));
+		ms_message("%s is registering again with expires set to %0d seconds",
+		           linphone_address_as_string(michelleContact), expires);
+		linphone_core_set_network_reachable(michelle.getLc(), FALSE);
+		LinphoneAccount *account = linphone_core_get_default_account(michelle.getLc());
+		const LinphoneAccountParams *account_params = linphone_account_get_params(account);
+		LinphoneAccountParams *new_account_params = linphone_account_params_clone(account_params);
+		linphone_account_params_set_expires(new_account_params, expires);
+		linphone_account_set_params(account, new_account_params);
+		linphone_account_params_unref(new_account_params);
+		linphone_core_set_network_reachable(michelle.getLc(), TRUE);
+		BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneRegistrationOk,
+		                             initialMichelleStats.number_of_LinphoneRegistrationOk + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		// We expect that the client sends 2 subscriptions to the server:
+		// - one from the call onNetworkReacable which fails because the DNS resolution of sip.example.org has not been
+		// done yet
+		// - the second one upon reception of 200 Ok Registration Successful
+		BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneSubscriptionOutgoingProgress,
+		                             initialMichelleStats.number_of_LinphoneSubscriptionOutgoingProgress + 2,
+		                             liblinphone_tester_sip_timeout));
+		BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneSubscriptionTerminated,
+		                             initialMichelleStats.number_of_LinphoneSubscriptionTerminated + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		// Send many back to back registers to verify that the server is only sent one SUBSCRIBE
+		for (int cnt = 0; cnt < 10; cnt++) {
+			stats initialMichelleStats2 = michelle.getStats();
+			linphone_core_refresh_registers(michelle.getLc());
+
+			BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneRegistrationRefreshing,
+			                             initialMichelleStats2.number_of_LinphoneRegistrationRefreshing + 1,
+			                             liblinphone_tester_sip_timeout));
+			BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneRegistrationOk,
+			                             initialMichelleStats2.number_of_LinphoneRegistrationOk + 1,
+			                             liblinphone_tester_sip_timeout));
+		}
+
+		// Verify that only one subscription is sent out to the conference server
+		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneSubscriptionIncomingReceived,
+		                             initialFocusStats.number_of_LinphoneSubscriptionIncomingReceived + 1,
+		                             liblinphone_tester_sip_timeout));
+		BC_ASSERT_FALSE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneSubscriptionOutgoingProgress,
+		                              initialMichelleStats.number_of_LinphoneSubscriptionOutgoingProgress + 3, 5000));
+		BC_ASSERT_FALSE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneSubscriptionIncomingReceived,
+		                              initialFocusStats.number_of_LinphoneSubscriptionIncomingReceived + 2, 1000));
+
+		michelleCr = linphone_core_search_chat_room(michelle.getLc(), NULL, michelleContact, confAddr, NULL);
+		BC_ASSERT_PTR_NOT_NULL(michelleCr);
+
+		const std::initializer_list<std::reference_wrapper<ConfCoreManager>> cores{focus, marie, michelle, berthe};
+		for (const ConfCoreManager &core : cores) {
+			BC_ASSERT_TRUE(checkChatroom(focus, core, -1));
+			for (auto chatRoom : core.getCore().getChatRooms()) {
+				BC_ASSERT_EQUAL(chatRoom->getParticipants().size(), ((focus.getLc() == core.getLc())) ? 3 : 2, size_t,
+				                "%zu");
+				BC_ASSERT_STRING_EQUAL(chatRoom->getSubject().c_str(), newSubject);
+			}
+		};
+
+		LinphoneChatMessage *msg = linphone_chat_room_create_message_from_utf8(michelleCr, "back with you");
+		linphone_chat_message_send(msg);
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([msg] {
+			return (linphone_chat_message_get_state(msg) == LinphoneChatMessageStateDelivered);
+		}));
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([marieCr] {
+			return linphone_chat_room_get_unread_messages_count(marieCr) == 1;
+		}));
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([bertheCr] {
+			return linphone_chat_room_get_unread_messages_count(bertheCr) == 1;
+		}));
+		linphone_chat_message_unref(msg);
+		msg = NULL;
+
+		msg = linphone_chat_room_create_message_from_utf8(marieCr, "welcome back");
+		linphone_chat_message_send(msg);
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([msg] {
+			return (linphone_chat_message_get_state(msg) == LinphoneChatMessageStateDelivered);
+		}));
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([bertheCr] {
+			return linphone_chat_room_get_unread_messages_count(bertheCr) == 2;
+		}));
+		linphone_chat_message_unref(msg);
+		msg = NULL;
+
+		CoreManagerAssert({focus, marie, michelle, berthe}).waitUntil(std::chrono::seconds(2), [] { return false; });
+
+		for (auto chatRoom : focus.getCore().getChatRooms()) {
+			for (auto participant : chatRoom->getParticipants()) {
+				//  force deletion by removing devices
+				std::shared_ptr<Address> participantAddress = participant->getAddress();
+				linphone_chat_room_set_participant_devices(L_GET_C_BACK_PTR(chatRoom), participantAddress->toC(), NULL);
+			}
+		}
+
+		// wait until chatroom is deleted server side
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle}).wait([&focus] {
+			return focus.getCore().getChatRooms().size() == 0;
+		}));
+
+		// wait bit more to detect side effect if any
+		CoreManagerAssert({focus, marie, michelle, berthe}).waitUntil(chrono::seconds(2), [] { return false; });
+
+		// to avoid creation attempt of a new chatroom
+		LinphoneProxyConfig *config = linphone_core_get_default_proxy_config(focus.getLc());
+		linphone_proxy_config_edit(config);
+		linphone_proxy_config_set_conference_factory_uri(config, NULL);
+		linphone_proxy_config_done(config);
+
+		linphone_address_unref(michelleContact);
+		bctbx_list_free(coresList);
+	}
+}
+
+static void group_chat_room_with_client_restart_removed_from_server(void) {
+	Focus focus("chloe_rc");
+	{ // to make sure focus is destroyed after clients.
+		bool_t encrypted = FALSE;
+		ClientConference marie("marie_rc", focus.getIdentity(), encrypted);
+		ClientConference michelle("michelle_rc", focus.getIdentity(), encrypted);
+		ClientConference berthe("berthe_rc", focus.getIdentity(), encrypted);
+
+		focus.registerAsParticipantDevice(marie);
+		focus.registerAsParticipantDevice(michelle);
+		focus.registerAsParticipantDevice(berthe);
+
+		bctbx_list_t *coresList = bctbx_list_append(NULL, focus.getLc());
+		coresList = bctbx_list_append(coresList, marie.getLc());
+		coresList = bctbx_list_append(coresList, michelle.getLc());
+		coresList = bctbx_list_append(coresList, berthe.getLc());
+		bctbx_list_t *participantsAddresses = NULL;
+		Address michelleAddr = michelle.getIdentity();
+		participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(michelleAddr.toC()));
+		Address bertheAddr = berthe.getIdentity();
+		participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(bertheAddr.toC()));
+
+		stats initialMarieStats = marie.getStats();
+		stats initialMichelleStats = michelle.getStats();
+		stats initialBertheStats = berthe.getStats();
+		//
+		// Marie creates a new group chat room
+		const char *initialSubject = "Colleagues (characters: $ £ çà)";
+		LinphoneChatRoom *marieCr = create_chat_room_client_side_with_expected_number_of_participants(
+		    coresList, marie.getCMgr(), &initialMarieStats, participantsAddresses, initialSubject, 2, encrypted,
+		    LinphoneChatRoomEphemeralModeDeviceManaged);
+		const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marieCr);
+
+		// Check that the chat room is correctly created on Michelle's side and that the participants are added
+		LinphoneChatRoom *michelleCr = check_creation_chat_room_client_side(
+		    coresList, michelle.getCMgr(), &initialMichelleStats, confAddr, initialSubject, 2, FALSE);
+
+		LinphoneChatRoom *bertheCr = check_creation_chat_room_client_side(
+		    coresList, berthe.getCMgr(), &initialBertheStats, confAddr, initialSubject, 2, FALSE);
+
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([&focus] {
+			for (const auto &chatRoom : focus.getCore().getChatRooms()) {
+				for (const auto &participant : chatRoom->getParticipants()) {
+					for (const auto &device : participant->getDevices()) {
+						if (device->getState() != ParticipantDevice::State::Present) {
+							return false;
+						}
+					}
+				}
+			}
+			return true;
+		}));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneConferenceStateCreated,
+		                             initialMichelleStats.number_of_LinphoneConferenceStateCreated + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &berthe.getStats().number_of_LinphoneConferenceStateCreated,
+		                             initialBertheStats.number_of_LinphoneConferenceStateCreated + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		// Marie now changes the subject
+		const char *newSubject = "Let's go drink a beer";
+		linphone_chat_room_set_subject(marieCr, newSubject);
+		BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_subject_changed,
+		                             initialMarieStats.number_of_subject_changed + 1, liblinphone_tester_sip_timeout));
+		BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_subject_changed,
+		                             initialMichelleStats.number_of_subject_changed + 1,
+		                             liblinphone_tester_sip_timeout));
+		BC_ASSERT_TRUE(wait_for_list(coresList, &berthe.getStats().number_of_subject_changed,
+		                             initialBertheStats.number_of_subject_changed + 1, liblinphone_tester_sip_timeout));
+		BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(marieCr), newSubject);
+		BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(michelleCr), newSubject);
+		BC_ASSERT_STRING_EQUAL(linphone_chat_room_get_subject(bertheCr), newSubject);
+
+		BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(marieCr), 2, int, "%d");
+		BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(michelleCr), 2, int, "%d");
+		BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(bertheCr), 2, int, "%d");
+
+		const std::initializer_list<std::reference_wrapper<ConfCoreManager>> cores2{focus, marie, michelle, berthe};
+		for (const ConfCoreManager &core : cores2) {
+			BC_ASSERT_TRUE(
+			    CoreManagerAssert({focus, marie, michelle, berthe}).waitUntil(chrono::seconds(10), [&focus, &core] {
+				    return checkChatroom(focus, core, -1);
+			    }));
+		};
+
+		initialMichelleStats = michelle.getStats();
+
+		LinphoneAddress *michelleContact = linphone_address_clone(
+		    linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(michelle.getLc())));
+		ms_message("%s is restarting its core", linphone_address_as_string(michelleContact));
+		coresList = bctbx_list_remove(coresList, michelle.getLc());
+		// Restart michelle
+		michelle.reStart();
+
+		bctbx_list_t *devices = NULL;
+		LinphoneParticipantDeviceIdentity *identity =
+		    linphone_factory_create_participant_device_identity(linphone_factory_get(), michelleContact, "");
+		linphone_participant_device_identity_set_capability_descriptor_2(
+		    identity, linphone_core_get_linphone_specs_list(michelle.getLc()));
+		devices = bctbx_list_append(devices, identity);
+		// Remove and then add Michelle's device to simulate a new registration to the chat room
+		// The conference server will therefore send an INVITE to Michelle thinking that she is not yet part of the
+		// chatroom
+		for (auto chatRoom : focus.getCore().getChatRooms()) {
+			linphone_chat_room_set_participant_devices(L_GET_C_BACK_PTR(chatRoom), michelle.getCMgr()->identity, NULL);
+			linphone_chat_room_set_participant_devices(L_GET_C_BACK_PTR(chatRoom), michelle.getCMgr()->identity,
+			                                           devices);
+		}
+		bctbx_list_free_with_data(devices, (bctbx_list_free_func)belle_sip_object_unref);
+
+		setup_mgr_for_conference(michelle.getCMgr(), NULL);
+		coresList = bctbx_list_append(coresList, michelle.getLc());
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneConferenceStateCreated,
+		                             initialMichelleStats.number_of_LinphoneConferenceStateCreated + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		// Verify that only one subscription is sent out to the conference server
+		BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneSubscriptionOutgoingProgress, 1,
+		                             liblinphone_tester_sip_timeout));
+		BC_ASSERT_FALSE(
+		    wait_for_list(coresList, &michelle.getStats().number_of_LinphoneSubscriptionOutgoingProgress, 2, 5000));
+
+		michelleCr = linphone_core_search_chat_room(michelle.getLc(), NULL, michelleContact, confAddr, NULL);
+		BC_ASSERT_PTR_NOT_NULL(michelleCr);
+
+		const std::initializer_list<std::reference_wrapper<ConfCoreManager>> cores{focus, marie, michelle, berthe};
+		for (const ConfCoreManager &core : cores) {
+			BC_ASSERT_TRUE(checkChatroom(focus, core, -1));
+			for (auto chatRoom : core.getCore().getChatRooms()) {
+				BC_ASSERT_EQUAL(chatRoom->getParticipants().size(), ((focus.getLc() == core.getLc())) ? 3 : 2, size_t,
+				                "%zu");
+				BC_ASSERT_STRING_EQUAL(chatRoom->getSubject().c_str(), newSubject);
+			}
+		};
+
+		LinphoneChatMessage *msg = linphone_chat_room_create_message_from_utf8(michelleCr, "back with you");
+		linphone_chat_message_send(msg);
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([msg] {
+			return (linphone_chat_message_get_state(msg) == LinphoneChatMessageStateDelivered);
+		}));
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([marieCr] {
+			return linphone_chat_room_get_unread_messages_count(marieCr) == 1;
+		}));
+		linphone_chat_message_unref(msg);
+		msg = NULL;
+
+		msg = linphone_chat_room_create_message_from_utf8(marieCr, "welcome back");
+		linphone_chat_message_send(msg);
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([msg] {
+			return (linphone_chat_message_get_state(msg) == LinphoneChatMessageStateDelivered);
+		}));
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle, berthe}).wait([michelleCr] {
+			return linphone_chat_room_get_unread_messages_count(michelleCr) == 1;
+		}));
+		linphone_chat_message_unref(msg);
+		msg = NULL;
+
+		CoreManagerAssert({focus, marie, michelle, berthe}).waitUntil(std::chrono::seconds(2), [] { return false; });
+
+		for (auto chatRoom : focus.getCore().getChatRooms()) {
+			for (auto participant : chatRoom->getParticipants()) {
+				//  force deletion by removing devices
+				std::shared_ptr<Address> participantAddress = participant->getAddress();
+				linphone_chat_room_set_participant_devices(L_GET_C_BACK_PTR(chatRoom), participantAddress->toC(), NULL);
+			}
+		}
+
+		// wait until chatroom is deleted server side
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, michelle}).wait([&focus] {
+			return focus.getCore().getChatRooms().size() == 0;
+		}));
+
+		// wait bit more to detect side effect if any
+		CoreManagerAssert({focus, marie, michelle, berthe}).waitUntil(chrono::seconds(2), [] { return false; });
+
+		// to avoid creation attempt of a new chatroom
+		LinphoneProxyConfig *config = linphone_core_get_default_proxy_config(focus.getLc());
+		linphone_proxy_config_edit(config);
+		linphone_proxy_config_set_conference_factory_uri(config, NULL);
+		linphone_proxy_config_done(config);
+
+		linphone_address_unref(michelleContact);
+		bctbx_list_free(coresList);
+	}
+}
+
+static void group_chat_room_with_creator_without_groupchat_capability_in_register(void) {
+	Focus focus("chloe_rc");
+	{ // to make sure focus is destroyed after clients.
+		bool_t encrypted = FALSE;
+		ClientConference marie("marie_rc", focus.getIdentity(), encrypted);
+		ClientConference marie2("marie_rc", focus.getIdentity(), encrypted);
+		ClientConference michelle("michelle_rc", focus.getIdentity(), encrypted);
+		ClientConference berthe("berthe_rc", focus.getIdentity(), encrypted);
+
+		stats initialMarieStats = marie.getStats();
+		stats initialMarie2Stats = marie2.getStats();
+		stats initialMichelleStats = michelle.getStats();
+		stats initialBertheStats = berthe.getStats();
+		stats initialFocusStats = focus.getStats();
+
+		bctbx_list_t *coresList = bctbx_list_append(NULL, focus.getLc());
+		coresList = bctbx_list_append(coresList, marie.getLc());
+		coresList = bctbx_list_append(coresList, michelle.getLc());
+		coresList = bctbx_list_append(coresList, berthe.getLc());
+
+		focus.registerAsParticipantDevice(marie2);
+		focus.registerAsParticipantDevice(michelle);
+		focus.registerAsParticipantDevice(berthe);
+
+		bctbx_list_t *participantsAddresses = NULL;
+		Address michelleAddr = michelle.getIdentity();
+		participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(michelleAddr.toC()));
+		Address bertheAddr = berthe.getIdentity();
+		participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(bertheAddr.toC()));
+
+		// Marie creates a new group chat room
+		const char *initialSubject = "Colleagues (characters: $ £ çà)";
+
+		LinphoneChatRoomParams *params = linphone_core_create_default_chat_room_params(marie.getLc());
+		linphone_chat_room_params_enable_encryption(params, encrypted);
+		linphone_chat_room_params_set_ephemeral_mode(params, LinphoneChatRoomEphemeralModeDeviceManaged);
+		linphone_chat_room_params_set_backend(params, LinphoneChatRoomBackendFlexisipChat);
+		linphone_chat_room_params_enable_group(params, TRUE);
+		linphone_chat_room_params_set_subject(params, initialSubject);
+		LinphoneChatRoom *marieCr =
+		    linphone_core_create_chat_room_6(marie.getLc(), params, NULL, participantsAddresses);
+		linphone_chat_room_params_unref(params);
+		BC_ASSERT_PTR_NOT_NULL(marieCr);
+		// linphone_core_create_chat_room_6 takes a ref to the chatroom
+		if (marieCr) linphone_chat_room_unref(marieCr);
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateCreated,
+		                             initialFocusStats.number_of_LinphoneConferenceStateCreated + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		size_t numberFocusChatroom = focus.getCore().getChatRooms().size();
+		BC_ASSERT_GREATER_STRICT(numberFocusChatroom, 0, size_t, "%zu");
+		LinphoneAddress *confAddr = (numberFocusChatroom > 0)
+		                                ? linphone_address_clone(linphone_chat_room_get_conference_address(
+		                                      L_GET_C_BACK_PTR(focus.getCore().getChatRooms().front())))
+		                                : NULL;
+
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, marie2, michelle, berthe}).wait([&focus] {
+			for (const auto &chatRoom : focus.getCore().getChatRooms()) {
+				for (const auto &participant : chatRoom->getParticipants()) {
+					if (participant->getDevices().size() != 1) {
+						return false;
+					}
+					for (const auto &device : participant->getDevices()) {
+						if (device->getState() != ParticipantDevice::State::Present) {
+							return false;
+						}
+					}
+				}
+			}
+			return true;
+		}));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneConferenceStateCreationFailed,
+		                             initialMarieStats.number_of_LinphoneConferenceStateCreationFailed + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateDeleted,
+		                             initialFocusStats.number_of_LinphoneConferenceStateDeleted + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneChatRoomSessionError,
+		                             initialMarieStats.number_of_LinphoneChatRoomSessionError + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneConferenceStateDeleted,
+		                             initialMarieStats.number_of_LinphoneConferenceStateDeleted + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		BC_ASSERT_FALSE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneConferenceStateCreated,
+		                              initialMichelleStats.number_of_LinphoneConferenceStateCreated + 1, 2000));
+
+		BC_ASSERT_FALSE(wait_for_list(coresList, &berthe.getStats().number_of_LinphoneConferenceStateCreated,
+		                              initialBertheStats.number_of_LinphoneConferenceStateCreated + 1, 2000));
+
+		BC_ASSERT_FALSE(wait_for_list(coresList, &marie2.getStats().number_of_LinphoneConferenceStateCreated,
+		                              initialMarie2Stats.number_of_LinphoneConferenceStateCreated + 1, 2000));
+
+		const std::initializer_list<std::reference_wrapper<ConfCoreManager>> cores{focus, marie, marie2, michelle,
+		                                                                           berthe};
+		for (const ConfCoreManager &core : cores) {
+			const LinphoneAddress *deviceAddr = linphone_proxy_config_get_contact(core.getDefaultProxyConfig());
+			LinphoneChatRoom *cr = linphone_core_search_chat_room(core.getLc(), NULL, deviceAddr, confAddr, NULL);
+			BC_ASSERT_PTR_NULL(cr);
+		};
+
+		CoreManagerAssert({focus, marie, marie2, michelle, berthe}).waitUntil(std::chrono::seconds(2), [] {
+			return false;
+		});
+
+		for (auto chatRoom : focus.getCore().getChatRooms()) {
+			for (auto participant : chatRoom->getParticipants()) {
+				//  force deletion by removing devices
+				std::shared_ptr<Address> participantAddress = participant->getAddress();
+				linphone_chat_room_set_participant_devices(L_GET_C_BACK_PTR(chatRoom), participantAddress->toC(), NULL);
+			}
+		}
+
+		// wait until chatroom is deleted server side
+		BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, marie2, michelle, berthe}).wait([&focus] {
+			return focus.getCore().getChatRooms().size() == 0;
+		}));
+
+		// wait bit more to detect side effect if any
+		CoreManagerAssert({focus, marie, marie2, michelle, berthe}).waitUntil(chrono::seconds(2), [] { return false; });
+
+		// to avoid creation attempt of a new chatroom
+		LinphoneProxyConfig *config = linphone_core_get_default_proxy_config(focus.getLc());
+		linphone_proxy_config_edit(config);
+		linphone_proxy_config_set_conference_factory_uri(config, NULL);
+		linphone_proxy_config_done(config);
+
+		linphone_address_unref(confAddr);
+		bctbx_list_free(coresList);
+	}
+}
+
+static void group_chat_room_with_creator_without_groupchat_capability(void) {
+	Focus focus("chloe_rc");
+	{ // to make sure focus is destroyed after clients.
+		bool_t encrypted = FALSE;
+		ClientConference marie("marie_rc", focus.getIdentity(), encrypted);
+		ClientConference michelle("michelle_rc", focus.getIdentity(), encrypted);
+		ClientConference berthe("berthe_rc", focus.getIdentity(), encrypted);
+
+		bctbx_list_t *coresList = bctbx_list_append(NULL, focus.getLc());
+		coresList = bctbx_list_append(coresList, marie.getLc());
+		coresList = bctbx_list_append(coresList, michelle.getLc());
+		coresList = bctbx_list_append(coresList, berthe.getLc());
+
+		stats initialMarieStats = marie.getStats();
+		linphone_core_set_network_reachable(marie.getLc(), FALSE);
+		linphone_core_remove_linphone_spec(marie.getLc(), "groupchat");
+		linphone_core_remove_linphone_spec(marie.getLc(), "conference");
+		linphone_core_set_network_reachable(marie.getLc(), TRUE);
+		BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneRegistrationOk,
+		                             initialMarieStats.number_of_LinphoneRegistrationOk + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		focus.registerAsParticipantDevice(marie);
+		focus.registerAsParticipantDevice(michelle);
+		focus.registerAsParticipantDevice(berthe);
+
+		bctbx_list_t *participantsAddresses = NULL;
+		Address michelleAddr = michelle.getIdentity();
+		participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(michelleAddr.toC()));
+		Address bertheAddr = berthe.getIdentity();
+		participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(bertheAddr.toC()));
+
+		stats initialFocusStats = focus.getStats();
+
+		// Marie creates a new group chat room
+		const char *initialSubject = "Colleagues (characters: $ £ çà)";
+
+		LinphoneChatRoomParams *params = linphone_core_create_default_chat_room_params(marie.getLc());
+		linphone_chat_room_params_enable_encryption(params, encrypted);
+		linphone_chat_room_params_set_ephemeral_mode(params, LinphoneChatRoomEphemeralModeDeviceManaged);
+		linphone_chat_room_params_set_backend(params, LinphoneChatRoomBackendFlexisipChat);
+		linphone_chat_room_params_enable_group(params, TRUE);
+		linphone_chat_room_params_set_subject(params, initialSubject);
+		LinphoneChatRoom *chatRoom =
+		    linphone_core_create_chat_room_6(marie.getLc(), params, NULL, participantsAddresses);
+		linphone_chat_room_params_unref(params);
+		BC_ASSERT_PTR_NOT_NULL(chatRoom);
+
+		// linphone_core_create_chat_room_6 takes a ref to the chatroom
+		if (chatRoom) linphone_chat_room_unref(chatRoom);
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateCreationFailed,
+		                             initialFocusStats.number_of_LinphoneConferenceStateCreationFailed + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneConferenceStateCreationFailed,
+		                             initialMarieStats.number_of_LinphoneConferenceStateCreationFailed + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_LinphoneConferenceStateDeleted,
+		                             initialFocusStats.number_of_LinphoneConferenceStateDeleted + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneChatRoomSessionError,
+		                             initialMarieStats.number_of_LinphoneChatRoomSessionError + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		// Clean db from chat room
+		linphone_core_manager_delete_chat_room(marie.getCMgr(), chatRoom, coresList);
+
+		BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneConferenceStateDeleted,
+		                             initialMarieStats.number_of_LinphoneConferenceStateDeleted + 1,
+		                             liblinphone_tester_sip_timeout));
+
+		bctbx_list_free(coresList);
+	}
+}
+
 static void group_chat_room_with_client_removed_added(void) {
 	Focus focus("chloe_rc");
 	{ // to make sure focus is destroyed after clients.
@@ -2240,14 +2863,14 @@ static void group_chat_room_with_client_removed_added(void) {
 		const LinphoneAddress *deviceAddr = linphone_proxy_config_get_contact(michelle.getDefaultProxyConfig());
 		LinphoneParticipantDeviceIdentity *identity =
 		    linphone_factory_create_participant_device_identity(linphone_factory_get(), deviceAddr, "");
-		linphone_participant_device_identity_set_capability_descriptor(
-		    identity, linphone_core_get_linphone_specs(michelle.getLc()));
+		linphone_participant_device_identity_set_capability_descriptor_2(
+		    identity, linphone_core_get_linphone_specs_list(michelle.getLc()));
 		devices = bctbx_list_append(devices, identity);
 
 		deviceAddr = linphone_proxy_config_get_contact(michelle2.getDefaultProxyConfig());
 		identity = linphone_factory_create_participant_device_identity(linphone_factory_get(), deviceAddr, "");
-		linphone_participant_device_identity_set_capability_descriptor(
-		    identity, linphone_core_get_linphone_specs(michelle2.getLc()));
+		linphone_participant_device_identity_set_capability_descriptor_2(
+		    identity, linphone_core_get_linphone_specs_list(michelle2.getLc()));
 		devices = bctbx_list_append(devices, identity);
 
 		for (auto chatRoom : focus.getCore().getChatRooms()) {
@@ -2281,7 +2904,6 @@ static void group_chat_room_with_client_removed_added(void) {
 		ms_message("%s is restarting its core", linphone_address_as_string(michelle2Contact));
 		linphone_address_unref(michelle2Contact);
 		coresList = bctbx_list_remove(coresList, michelle2.getLc());
-		// Restart flexisip
 		michelle2.reStart();
 		michelle2.setupMgrForConference();
 		coresList = bctbx_list_append(coresList, michelle2.getLc());
@@ -6476,7 +7098,6 @@ static void create_conference_base(time_t start_time,
 		if (client_restart) {
 			ms_message("Marie restarts its core");
 			coresList = bctbx_list_remove(coresList, marie.getLc());
-			// Restart flexisip
 			marie.reStart();
 
 			if (enable_video) {
@@ -16646,6 +17267,15 @@ static test_t local_conference_chat_tests[] = {
     TEST_ONE_TAG("Group chat with client restart",
                  LinphoneTest::group_chat_room_with_client_restart,
                  "LeaksMemory"), /* beacause of coreMgr restart*/
+    TEST_NO_TAG("Group chat with client registering with a short REGISTER expires",
+                LinphoneTest::group_chat_room_with_client_registering_with_short_register_expires),
+    TEST_ONE_TAG("Group chat with client restart and removed from server",
+                 LinphoneTest::group_chat_room_with_client_restart_removed_from_server,
+                 "LeaksMemory"), /* beacause of coreMgr restart*/
+    TEST_NO_TAG("Group chat with creator without groupchat capability",
+                LinphoneTest::group_chat_room_with_creator_without_groupchat_capability),
+    TEST_NO_TAG("Group chat with creator without groupchat capability in register",
+                LinphoneTest::group_chat_room_with_creator_without_groupchat_capability_in_register),
     TEST_NO_TAG("Group chat room bulk notify to participant",
                 LinphoneTest::group_chat_room_bulk_notify_to_participant), /* because of network up and down*/
     TEST_ONE_TAG("One to one chatroom exhumed while participant is offline",
diff --git a/tester/remote-provisioning-tester.cpp b/tester/remote-provisioning-tester.cpp
index 4dd89abb9e..20c0110631 100644
--- a/tester/remote-provisioning-tester.cpp
+++ b/tester/remote-provisioning-tester.cpp
@@ -51,7 +51,7 @@ static void remote_provisioning_http(void) {
 	BC_ASSERT_TRUE(wait_for(marie->lc, NULL, &marie->stat.number_of_LinphoneConfiguringSuccessful, 1));
 	BC_ASSERT_TRUE(wait_for(marie->lc, NULL, &marie->stat.number_of_LinphoneRegistrationOk, 1));
 
-	/*make sure proxy config is not added in double, one time at core init, next time at configuring successfull*/
+	/*make sure proxy config is not added in double, one time at core init, next time at configuring successful*/
 	BC_ASSERT_EQUAL((int)bctbx_list_size(linphone_core_get_proxy_config_list(marie->lc)), 1, int, "%i");
 	BC_ASSERT_FALSE(linphone_friend_list_subscriptions_enabled(list));
 
@@ -429,7 +429,7 @@ static void remote_provisioning_check_push_params(void) {
 	BC_ASSERT_TRUE(wait_for(marie->lc, NULL, &marie->stat.number_of_LinphoneConfiguringSuccessful, 1));
 	BC_ASSERT_TRUE(wait_for(marie->lc, NULL, &marie->stat.number_of_LinphoneRegistrationOk, 1));
 
-	/*make sure proxy config is not added in double, one time at core init, next time at configuring successfull*/
+	/*make sure proxy config is not added in double, one time at core init, next time at configuring successful*/
 	BC_ASSERT_EQUAL((int)bctbx_list_size(linphone_core_get_proxy_config_list(marie->lc)), 1, int, "%i");
 
 	LinphoneAccount *marie_account = linphone_core_get_default_account(marie->lc);
diff --git a/tester/tester.c b/tester/tester.c
index 0b3355717a..5bfa13b4e7 100644
--- a/tester/tester.c
+++ b/tester/tester.c
@@ -3062,6 +3062,9 @@ void registration_state_changed(struct _LinphoneCore *lc,
 		case LinphoneRegistrationProgress:
 			counters->number_of_LinphoneRegistrationProgress++;
 			break;
+		case LinphoneRegistrationRefreshing:
+			counters->number_of_LinphoneRegistrationRefreshing++;
+			break;
 		case LinphoneRegistrationOk:
 			counters->number_of_LinphoneRegistrationOk++;
 			break;
-- 
GitLab