diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index 67074709e971d9a274a9fbd0d413012448e53b24..d9621c0a65193cb499e94f2ec994d7f2f7921967 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 afb378b0c4d1cea717cd5d820c8925f9b737ef83..d84d4c1018e6d01edd487261068782cf2cf672c3 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 dac4acf6a886a9f8fdfe6ffa1db648d9d8031c8c..e2423499c8fc3d59f73ddf3ffa72af3ce5cf185c 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 c8c6957a33cc6515cbdb75f1a1c859d1f63cffa4..40df58ef375e2e0d7871bb4a9fc711cc505ef3a2 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 40520cf175c00ac085d580514240d6b65e668a31..f73784dd8bcef1d5e00c976c1ea0a812167e4ffd 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 2088774cc0cf7b77897bea225360972cdb58e033..b777babaedffe6368b3729cef98c69025244a73d 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 46802a424b3528e97cbb5f702b65f522f7688a2b..68fccb7a563f0c07d8c5779b1755317f2bcfbfac 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 9fe13071893b8ca72f4af824b3db9a01b36a93ed..887a78e1175e247454ab88a2c61f25df7473ec05 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 0dcde2ec0bfbd3ad3816d6efe9699f1b58bded92..b2ab11d61a2476539bdd4831ccfa95804357d55c 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 e43d381fad6ab465c679a1ea1dc093e16f75f416..b01575f1957a65a9dd291445f46094d3bd437347 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 b96e74066131f2db01449db6049013697777ea2c..63bb420caca3dda67b5d14e65ad5359106fe324c 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 70be78dc6580991d84747a386ba6c06ca5a4e6cb..21ad93b09877668a3745f4070161b0cfab5c4cd2 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 28568130bc1226867eef3b2806cd3b9e99ed7aac..9c34f4c6880566dbc779f19fe325699b02796ef3 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 d930725eb9714552671b80ebd7d59fe284a5b0de..2b7afd21501e3152a4ff762d37d05ebf5e308239 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 f0262e33d49483c8342937db8a0e4ce15b751a0e..1b1e794281d98daf9ea8f83a75fcd4441f6a892f 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 7c8c80d2ad9977bd20019f7a87dde70ab5ca88ca..e8f858c7bbdbee08a08cbeca796020a0d5de6bc4 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 e43cfbb2acc3d5be3c110d540bc22691388220a8..35d87b250ee874dbc096ef0b4115ce956358e1e8 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 4d565887700fde23bde8deb084718f5fa61e52bc..a10308733e6eba7ac1f4027e5191248c61d11ba8 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 d1f0d66cb1d3e91f14c9efba8cf46e1783dd380d..a6f6d40bf65fdd69b881a30df6e90adee9355225 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 ¤tAddress = 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 a06a893341bc5358d083e8ef677fe5a06c76d6a7..e455f4c4bffc051e94ba52ff3ddeb1dc8a8569dc 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 c1df4a4fd25f6d6305c3dde5305354bab9c832e9..0b8c3514719feb6d860356859129fea682fcff44 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 e3cd3742430316c5b227be9e664a212430f79250..e34d7c14089aa3ce8249d2d668fa05c1de2c89ef 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 d9a143a676490ad52336f0ab222b22ebd3d8f30f..a01e30c24865cd3b3c32e9f624cc472646b26457 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 a434a6eeca43d1a5df2469dd331ce08371a2ea96..2552cace21fdc5ff061538183bcfd8047d0fdfd9 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 ad1fca9b37dacf2271f6c66ef6c212ea9a1b170b..9994f2d9fc5b611de52f483c6cf8fb54cda8f495 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 df06d20bc75fed4e713f06451c1fb6c05c2c15ee..00cc368c8fff1b987ebfd4e1f9e71ee1978eb238 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 159e7af5382237a1670e730bd6c6a54aea62646a..d490e4feaa989f0565ffbdb2e31da4f8ca0f835d 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 c97806b65caad578e436f800691b54e37b7e0d58..965b80f60ca1c6622b2caf4c932a1ca71e7c54ab 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 4dd89abb9ef26077b312e536077b4f651b88b42e..20c0110631cfab540e4b550330255a8b40800423 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 0b3355717a71172a8b444455fea0208b2dcee00e..5bfa13b4e7f1d7744837fc6a85aa4ff6bb54ef00 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;