From a60dcc803b2efc869befe21a34c2c97f4e72bc3d Mon Sep 17 00:00:00 2001 From: Andrea Gianarda <andrea.gianarda@belledonne-communications.com> Date: Wed, 18 Oct 2023 16:54:36 +0200 Subject: [PATCH] Recover from chat room duplication in the database. When starting the core, make sure that there is not other chatroom already retrieved that has the same conference id when the gr parameter is ignored on the peer and local address. If so, then keep the one that has the oldest creation time and replace it by the value stored in the other. All events linked to the to-be-deleted chat room generated before chat room creation time are moved as they now belong to the kept chat room. The other events are simply deleted because they are linked ot both chatrooms. Split suite "Local conference tester (Chat)" into "Local conference tester (Chat Basic)" and "Local conference tester (Chat Advanced)" --- src/chat/chat-room/server-group-chat-room.cpp | 2 +- .../local-conference-event-handler.cpp | 14 +- src/core/core-chat-room.cpp | 15 +- src/db/main-db.cpp | 110 ++++- src/db/main-db.h | 2 + tester/dtmf_tester.c | 5 + tester/liblinphone_tester.c | 3 +- tester/liblinphone_tester.h | 3 +- tester/local_chat_tester.cpp | 448 +++++++++++++++++- tester/tester.c | 2 +- 10 files changed, 556 insertions(+), 48 deletions(-) diff --git a/src/chat/chat-room/server-group-chat-room.cpp b/src/chat/chat-room/server-group-chat-room.cpp index 2ebfa75e70..d63e2957c3 100644 --- a/src/chat/chat-room/server-group-chat-room.cpp +++ b/src/chat/chat-room/server-group-chat-room.cpp @@ -769,7 +769,7 @@ void ServerGroupChatRoomPrivate::updateParticipantDevices(const std::shared_ptr< lError() << q << " participant devices updated for unknown participant, ignored."; return; } - lInfo() << q << ": Setting " << devices.size() << " participant device(s) for " << participantAddress->toString(); + lInfo() << q << ": Setting " << devices.size() << " participant device(s) for " << *participantAddress; // Remove devices that are in the chatroom but no longer in the given list list<shared_ptr<ParticipantDevice>> devicesToRemove; diff --git a/src/conference/handlers/local-conference-event-handler.cpp b/src/conference/handlers/local-conference-event-handler.cpp index 303534eb0b..ee1b5aa102 100644 --- a/src/conference/handlers/local-conference-event-handler.cpp +++ b/src/conference/handlers/local-conference-event-handler.cpp @@ -1023,9 +1023,9 @@ LinphoneStatus LocalConferenceEventHandler::subscribeReceived(const shared_ptr<E } if ((evLastNotify == 0) || (deviceState == ParticipantDevice::State::Joining)) { lInfo() << "Sending initial notify of conference [" << conf->getConferenceAddress() - << "] to: " << device->getAddress() << " with last notif set to " << conf->getLastNotify(); + << "] to: " << *device->getAddress() << " with last notif set to " << conf->getLastNotify(); if (deviceState == ParticipantDevice::State::Present) { - lInfo() << "Participant " << device->getAddress() << " is already part of conference [" + lInfo() << "Device " << *device->getAddress() << " is already part of conference [" << conf->getConferenceAddress() << "] hence send full state to be sure the client and the server are on the same page"; } else { @@ -1043,7 +1043,7 @@ LinphoneStatus LocalConferenceEventHandler::subscribeReceived(const shared_ptr<E } } else if (evLastNotify < lastNotify) { lInfo() << "Sending all missed notify [" << evLastNotify << "-" << lastNotify << "] for conference [" - << conf->getConferenceAddress() << "] to: " << participant->getAddress(); + << conf->getConferenceAddress() << "] to: " << *participant->getAddress(); const int fullStateTrigger = linphone_config_get_int(linphone_core_get_config(conf->getCore()->getCCore()), "misc", @@ -1081,7 +1081,7 @@ void LocalConferenceEventHandler::subscriptionStateChanged(const shared_ptr<Even shared_ptr<ParticipantDevice> device = participant->findDevice(contactAddr); if (!device) return; if (ev == device->getConferenceSubscribeEvent()) { - lInfo() << "End of subscription for device [" << device->getAddress() << "] of conference [" + lInfo() << "End of subscription for device [" << *device->getAddress() << "] of conference [" << conf->getConferenceAddress() << "]"; device->setConferenceSubscribeEvent(nullptr); } @@ -1142,7 +1142,7 @@ void LocalConferenceEventHandler::onParticipantAdded(const std::shared_ptr<Confe } } } else { - lWarning() << __func__ << ": Not sending notification of participant " << participant->getAddress() + lWarning() << __func__ << ": Not sending notification of participant " << *participant->getAddress() << " being added because pointer to conference is null"; } } @@ -1165,7 +1165,7 @@ void LocalConferenceEventHandler::onParticipantRemoved(const std::shared_ptr<Con } } } else { - lWarning() << __func__ << ": Not sending notification of participant " << participant->getAddress() + lWarning() << __func__ << ": Not sending notification of participant " << *participant->getAddress() << " being removed because pointer to conference is null"; } } @@ -1189,7 +1189,7 @@ void LocalConferenceEventHandler::onParticipantSetAdmin(const std::shared_ptr<Co } } } else { - lWarning() << __func__ << ": Not sending notification of participant " << participant->getAddress() + lWarning() << __func__ << ": Not sending notification of participant " << *participant->getAddress() << " admin status changed because pointer to conference is null"; } } diff --git a/src/core/core-chat-room.cpp b/src/core/core-chat-room.cpp index b1a02e01da..8a0f780095 100644 --- a/src/core/core-chat-room.cpp +++ b/src/core/core-chat-room.cpp @@ -242,9 +242,6 @@ CorePrivate::searchChatRoom(const shared_ptr<ChatRoomParams> ¶ms, const std::list<std::shared_ptr<Address>> &participants) const { for (auto it = chatRoomsById.begin(); it != chatRoomsById.end(); it++) { const auto &chatRoom = it->second; - std::shared_ptr<Address> curLocalAddress = chatRoom->getLocalAddress(); - std::shared_ptr<Address> curRemoteAddress = chatRoom->getPeerAddress(); - if (params) { ChatRoom::CapabilitiesMask capabilities = chatRoom->getCapabilities(); if (params->getChatRoomBackend() != chatRoom->getCurrentParams()->getChatRoomBackend()) continue; @@ -261,9 +258,17 @@ CorePrivate::searchChatRoom(const shared_ptr<ChatRoomParams> ¶ms, continue; } - if (localAddress && localAddress->isValid() && (!localAddress->weakEqual(*curLocalAddress))) continue; + std::shared_ptr<Address> curLocalAddress = chatRoom->getLocalAddress(); + const auto localAddressWithoutGruu = + (localAddress && localAddress->isValid()) ? localAddress->getUriWithoutGruu() : Address(); + const auto curLocalAddressWithoutGruu = curLocalAddress->getUriWithoutGruu(); + if (localAddressWithoutGruu.isValid() && (localAddressWithoutGruu != curLocalAddressWithoutGruu)) continue; - if (remoteAddress && remoteAddress->isValid() && (!remoteAddress->weakEqual(*curRemoteAddress))) continue; + std::shared_ptr<Address> curRemoteAddress = chatRoom->getPeerAddress(); + const auto remoteAddressWithoutGruu = + (remoteAddress && remoteAddress->isValid()) ? remoteAddress->getUriWithoutGruu() : Address(); + const auto curRemoteAddressWithoutGruu = curRemoteAddress->getUriWithoutGruu(); + if (remoteAddressWithoutGruu.isValid() && (remoteAddressWithoutGruu != curRemoteAddressWithoutGruu)) continue; bool allFound = true; for (const auto &participant : participants) { diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp index 03068228fc..740dc9962e 100644 --- a/src/db/main-db.cpp +++ b/src/db/main-db.cpp @@ -2896,18 +2896,17 @@ void MainDb::init() { auto timestampType = bind(&DbSession::timestampType, &d->dbSession); auto varcharPrimaryKeyStr = bind(&DbSession::varcharPrimaryKeyStr, &d->dbSession, _1); - /* Enable secure delete - so that erased chat messages are really erased and not just marked as unused. - * See https://sqlite.org/pragma.html#pragma_secure_delete - * This setting is global for the database. - * It is enabled only for sqlite3 backend, which is the one used for liblinphone clients. - * The mysql backend (used server-side) doesn't support this PRAGMA. - */ - initCleanup(); session->begin(); try { + /* Enable secure delete - so that erased chat messages are really erased and not just marked as unused. + * See https://sqlite.org/pragma.html#pragma_secure_delete + * This setting is global for the database. + * It is enabled only for sqlite3 backend, which is the one used for liblinphone clients. + * The mysql backend (used server-side) doesn't support this PRAGMA. + */ if (backend == Sqlite3) *session << string("PRAGMA secure_delete = ON"); // Charset set to ascii for mysql/mariadb to allow creation of indexed collumns of size > 191. We assume that @@ -4867,6 +4866,101 @@ void MainDb::disableDisplayNotificationRequired(const std::shared_ptr<const Even // ----------------------------------------------------------------------------- +// Add a chatroom to the list passed as first argument if it is not a duplicate. +// In case a chatroom with the same conference id (where the comparison doesn't take into account the gr parameters) is +// already found, then a merge is executed: +// - keep the chatroom with the oldest creation time +// - set the creation time to the earliest one +// - assign all events preceeding the latest creation time to the kept chatroom +// - destroy the deleted chatroom from DB +void MainDb::addChatroomToList(list<shared_ptr<AbstractChatRoom>> &chatRooms, + const shared_ptr<AbstractChatRoom> chatRoom) const { +#ifdef HAVE_DB_STORAGE + L_D(); + const auto chatRoomConferenceId = chatRoom->getConferenceId(); + const auto chatRoomIt = + std::find_if(chatRooms.cbegin(), chatRooms.cend(), [&chatRoomConferenceId](const auto &storedChatRoom) { + const auto &storedChatRoomConferenceId = storedChatRoom->getConferenceId(); + + const auto storedChatRoomConferenceIdPeerAddress = storedChatRoomConferenceId.getPeerAddress(); + const auto storedChatRoomConferenceIdPeerAddressWithoutGruu = + (storedChatRoomConferenceIdPeerAddress && storedChatRoomConferenceIdPeerAddress->isValid()) + ? storedChatRoomConferenceId.getPeerAddress()->getUriWithoutGruu() + : Address(); + const auto chatRoomConferenceIdPeerAddress = chatRoomConferenceId.getPeerAddress(); + const auto chatRoomConferenceIdPeerAddressWithoutGruu = + (chatRoomConferenceIdPeerAddress && chatRoomConferenceIdPeerAddress->isValid()) + ? chatRoomConferenceId.getPeerAddress()->getUriWithoutGruu() + : Address(); + + const auto storedChatRoomConferenceIdLocalAddress = storedChatRoomConferenceId.getLocalAddress(); + const auto storedChatRoomConferenceIdLocalAddressWithoutGruu = + (storedChatRoomConferenceIdLocalAddress && storedChatRoomConferenceIdLocalAddress->isValid()) + ? storedChatRoomConferenceId.getLocalAddress()->getUriWithoutGruu() + : Address(); + const auto chatRoomConferenceIdLocalAddress = chatRoomConferenceId.getLocalAddress(); + const auto chatRoomConferenceIdLocalAddressWithoutGruu = + (chatRoomConferenceIdLocalAddress && chatRoomConferenceIdLocalAddress->isValid()) + ? chatRoomConferenceId.getLocalAddress()->getUriWithoutGruu() + : Address(); + + return (storedChatRoomConferenceIdLocalAddressWithoutGruu == chatRoomConferenceIdLocalAddressWithoutGruu) && + (storedChatRoomConferenceIdPeerAddressWithoutGruu == chatRoomConferenceIdPeerAddressWithoutGruu); + }); + shared_ptr<AbstractChatRoom> chatRoomToAdd = nullptr; + if (chatRoomIt == chatRooms.cend()) { + chatRoomToAdd = chatRoom; + } else { + auto storedChatRoom = (*chatRoomIt); + const auto storedChatRoomConferenceId = storedChatRoom->getConferenceId(); + const auto storedChatRoomCreationTime = storedChatRoom->getCreationTime(); + const auto chatRoomCreationTime = chatRoom->getCreationTime(); + lInfo() << "Chat rooms with conference id " << chatRoomConferenceId << " and " << storedChatRoomConferenceId + << " will be merged as they have the same peer address"; + chatRooms.erase(chatRoomIt); + ConferenceId conferenceIdToAdd; + ConferenceId conferenceIdToRemove; + time_t creationTime = 0; + time_t creationTimeToDelete = 0; + // Update chatroom with the largest creation time and update its creation time with the lowest value. + if (storedChatRoomCreationTime < chatRoomCreationTime) { + chatRoomToAdd = chatRoom; + creationTime = storedChatRoomCreationTime; + creationTimeToDelete = chatRoomCreationTime; + conferenceIdToAdd = chatRoomConferenceId; + conferenceIdToRemove = storedChatRoomConferenceId; + } else { + chatRoomToAdd = storedChatRoom; + creationTime = chatRoomCreationTime; + creationTimeToDelete = storedChatRoomCreationTime; + conferenceIdToAdd = storedChatRoomConferenceId; + conferenceIdToRemove = chatRoomConferenceId; + } + chatRoomToAdd->getPrivate()->setCreationTime(creationTime); + d->unreadChatMessageCountCache.insert(conferenceIdToAdd, + *d->unreadChatMessageCountCache[conferenceIdToAdd] + + *d->unreadChatMessageCountCache[conferenceIdToRemove]); + d->unreadChatMessageCountCache.insert(conferenceIdToRemove, 0); + + const long long &dbChatRoomToAddId = d->selectChatRoomId(conferenceIdToAdd); + const long long &dbChatRoomToRemoveId = d->selectChatRoomId(conferenceIdToRemove); + + auto creationTimeToDeleteSoci = d->dbSession.getTimeWithSociIndicator(creationTimeToDelete); + soci::session *session = d->dbSession.getBackendSession(); + // Move conference event that occurred before the latest chatroom was created. + // Events such as chat messages are already stored in both chat rooms + *session << "UPDATE conference_event SET chat_room_id = :newChatRoomid WHERE event_id IN (SELECT " + "conference_event.event_id FROM conference_event, event WHERE event.id = conference_event.event_id " + "AND conference_event.chat_room_id = :chatRoomId AND event.creation_time < :creationTime)", + soci::use(dbChatRoomToAddId), soci::use(dbChatRoomToRemoveId), + soci::use(creationTimeToDeleteSoci.first, creationTimeToDeleteSoci.second); + *session << "DELETE FROM chat_room WHERE id = :chatRoomId", soci::use(dbChatRoomToRemoveId); + } + + chatRooms.push_back(chatRoomToAdd); +#endif +} + list<shared_ptr<AbstractChatRoom>> MainDb::getChatRooms() const { #ifdef HAVE_DB_STORAGE static const string query = @@ -5051,7 +5145,7 @@ list<shared_ptr<AbstractChatRoom>> MainDb::getChatRooms() const { lDebug() << "Found chat room in DB: (peer=" << conferenceId.getPeerAddress()->toStringUriOnlyOrdered() << ", local=" << conferenceId.getLocalAddress()->toStringUriOnlyOrdered() << ")."; - chatRooms.push_back(chatRoom); + addChatroomToList(chatRooms, chatRoom); } tr.commit(); diff --git a/src/db/main-db.h b/src/db/main-db.h index da0ac5d4f4..4006a1acb6 100644 --- a/src/db/main-db.h +++ b/src/db/main-db.h @@ -251,6 +251,8 @@ private: L_DISABLE_COPY(MainDb); void initCleanup(); + void addChatroomToList(std::list<std::shared_ptr<AbstractChatRoom>> &chatRooms, + const std::shared_ptr<AbstractChatRoom> chatRoom) const; }; LINPHONE_END_NAMESPACE diff --git a/tester/dtmf_tester.c b/tester/dtmf_tester.c index 48d7f769f2..d2d9c7a376 100644 --- a/tester/dtmf_tester.c +++ b/tester/dtmf_tester.c @@ -109,6 +109,11 @@ void send_dtmf_cleanup(LinphoneCoreManager *marie, LinphoneCoreManager *pauline) BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneCallReleased, 1)); BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneCallReleased, 1)); } + + linphone_core_play_dtmf(marie->lc, '#', 900); + /*wait a few time to ensure that DTMF is sent*/ + wait_for_until(marie->lc, pauline->lc, NULL, 0, 500); + linphone_core_manager_destroy(marie); linphone_core_manager_destroy(pauline); } diff --git a/tester/liblinphone_tester.c b/tester/liblinphone_tester.c index 014d9d2d0b..ae7c0e35f7 100644 --- a/tester/liblinphone_tester.c +++ b/tester/liblinphone_tester.c @@ -509,7 +509,8 @@ void liblinphone_tester_add_suites(void) { liblinphone_tester_add_suite_with_default_time(&local_conference_test_suite_scheduled_ice_conference, 371); liblinphone_tester_add_suite_with_default_time(&local_conference_test_suite_inpromptu_conference, 351); liblinphone_tester_add_suite_with_default_time(&local_conference_test_suite_inpromptu_mismatch_conference, 198); - liblinphone_tester_add_suite_with_default_time(&local_conference_test_suite_chat, 448); + liblinphone_tester_add_suite_with_default_time(&local_conference_test_suite_chat_basic, 300); + liblinphone_tester_add_suite_with_default_time(&local_conference_test_suite_chat_advanced, 300); liblinphone_tester_add_suite_with_default_time(&local_conference_test_suite_chat_error, 246); liblinphone_tester_add_suite_with_default_time(&local_conference_test_suite_chat_imdn, 215); liblinphone_tester_add_suite_with_default_time(&local_conference_test_suite_ephemeral_chat, 201); diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h index fb134d7cdb..eb9def6ebf 100644 --- a/tester/liblinphone_tester.h +++ b/tester/liblinphone_tester.h @@ -135,7 +135,8 @@ extern test_suite_t call_with_rtp_bundle_test_suite; extern test_suite_t shared_core_test_suite; extern test_suite_t lime_server_auth_test_suite; extern test_suite_t vfs_encryption_test_suite; -extern test_suite_t local_conference_test_suite_chat; +extern test_suite_t local_conference_test_suite_chat_basic; +extern test_suite_t local_conference_test_suite_chat_advanced; extern test_suite_t local_conference_test_suite_chat_error; extern test_suite_t local_conference_test_suite_chat_imdn; extern test_suite_t local_conference_test_suite_ephemeral_chat; diff --git a/tester/local_chat_tester.cpp b/tester/local_chat_tester.cpp index e90bac0435..9239f51aa0 100644 --- a/tester/local_chat_tester.cpp +++ b/tester/local_chat_tester.cpp @@ -103,7 +103,6 @@ static void group_chat_room_creation_server(void) { coresList = bctbx_list_append(coresList, focus.getLc()); for (auto chatRoom : focus.getCore().getChatRooms()) { - BC_ASSERT_EQUAL(linphone_chat_room_get_nb_participants(L_GET_C_BACK_PTR(chatRoom)), 3, int, "%d"); } @@ -196,10 +195,10 @@ static void group_chat_room_server_deletion(void) { })); BC_ASSERT_FALSE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneMessageReceived, - initialMarieStats.number_of_LinphoneMessageReceived + 1, 3000)); + initialMarieStats.number_of_LinphoneMessageReceived + 1, 3000)); BC_ASSERT_FALSE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneAggregatedMessagesReceived, - initialMarieStats.number_of_LinphoneAggregatedMessagesReceived + 1, 3000)); + initialMarieStats.number_of_LinphoneAggregatedMessagesReceived + 1, 3000)); for (auto chatRoom : focus.getCore().getChatRooms()) { for (auto participant : chatRoom->getParticipants()) { @@ -493,10 +492,10 @@ static void group_chat_room_with_client_removed_added(void) { initialMichelle2Stats.number_of_LinphoneConferenceStateDeleted + 1, liblinphone_tester_sip_timeout)); BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_participants_removed, - initialMarieStats.number_of_participant_devices_removed + 1, + initialMarieStats.number_of_participants_removed + 1, liblinphone_tester_sip_timeout)); BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_participants_removed, - initialPaulineStats.number_of_participant_devices_removed + 1, + initialPaulineStats.number_of_participants_removed + 1, liblinphone_tester_sip_timeout)); initialMarieStats = marie.getStats(); @@ -1328,6 +1327,7 @@ static void group_chat_room_with_client_removed_while_stopped_base(bool_t use_re if (uuid) { bctbx_free(uuid); } + setup_mgr_for_conference(michelle.getCMgr(), NULL); coresList = bctbx_list_append(coresList, michelle.getLc()); if (use_remote_event_list_handler) { @@ -2083,7 +2083,7 @@ static void one_to_one_chatroom_exhumed_while_offline(void) { char *paulineDeviceIdentity = linphone_core_get_device_identity(pauline.getLc()); LinphoneAddress *paulineDeviceAddr = linphone_address_new(paulineDeviceIdentity); bctbx_free(paulineDeviceIdentity); - auto newPaulineCr = pauline.searchChatRoom(paulineDeviceAddr, confAddr); + auto newPaulineCr = pauline.searchChatRoom(paulineDeviceAddr, exhumedConfAddr); linphone_address_unref(paulineDeviceAddr); BC_ASSERT_PTR_NOT_NULL(newPaulineCr); BC_ASSERT_PTR_EQUAL(newPaulineCr, paulineCr); @@ -2597,19 +2597,417 @@ static void group_chat_room_with_only_participant_with_invalid_address(void) { } } +static void group_chat_room_with_duplications(void) { + Focus focus("chloe_rc"); + { // to make sure focus is destroyed after clients. + bool encrypted = false; + ClientConference marie("marie_rc", focus.getConferenceFactoryAddress(), encrypted); + ClientConference michelle("michelle_rc", focus.getConferenceFactoryAddress(), encrypted); + ClientConference pauline("pauline_rc", focus.getConferenceFactoryAddress(), encrypted); + ClientConference laure("laure_tcp_rc", focus.getConferenceFactoryAddress(), encrypted); + + LinphoneAccount *account = linphone_core_get_default_account(laure.getLc()); + const LinphoneAccountParams *account_params = linphone_account_get_params(account); + LinphoneAccountParams *new_account_params = linphone_account_params_clone(account_params); + linphone_account_params_set_conference_factory_address(new_account_params, + focus.getConferenceFactoryAddress().toC()); + linphone_account_set_params(account, new_account_params); + linphone_account_params_unref(new_account_params); + + focus.registerAsParticipantDevice(marie); + focus.registerAsParticipantDevice(michelle); + focus.registerAsParticipantDevice(laure); + focus.registerAsParticipantDevice(pauline); + + stats marie_stat = marie.getStats(); + stats pauline_stat = pauline.getStats(); + stats laure_stat = laure.getStats(); + stats michelle_stat = michelle.getStats(); + bctbx_list_t *coresList = bctbx_list_append(NULL, focus.getLc()); + coresList = bctbx_list_append(coresList, marie.getLc()); + coresList = bctbx_list_append(coresList, pauline.getLc()); + coresList = bctbx_list_append(coresList, laure.getLc()); + coresList = bctbx_list_append(coresList, michelle.getLc()); + + int nbChatrooms = 10; + for (int idx = 0; idx < nbChatrooms; idx++) { + marie_stat = marie.getStats(); + pauline_stat = pauline.getStats(); + laure_stat = laure.getStats(); + michelle_stat = michelle.getStats(); + + // Marie creates a new group chat room + char *initialSubject = bctbx_strdup_printf("test subject for chatroom idx %d", idx); + bctbx_list_t *participantsAddresses = NULL; + Address michelleAddr = michelle.getIdentity(); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(michelleAddr.toC())); + Address laureAddr = laure.getIdentity(); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(laureAddr.toC())); + Address paulineAddr = pauline.getIdentity(); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(paulineAddr.toC())); + + LinphoneChatRoom *marieCr = create_chat_room_client_side_with_expected_number_of_participants( + coresList, marie.getCMgr(), &marie_stat, participantsAddresses, initialSubject, 3, encrypted, + LinphoneChatRoomEphemeralModeDeviceManaged); + BC_ASSERT_PTR_NOT_NULL(marieCr); + const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marieCr); + BC_ASSERT_PTR_NOT_NULL(confAddr); + + // 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(), &michelle_stat, confAddr, initialSubject, 3, FALSE); + BC_ASSERT_PTR_NOT_NULL(michelleCr); + + // Check that the chat room is correctly created on Pauline's side and that the participants are added + LinphoneChatRoom *paulineCr = check_creation_chat_room_client_side( + coresList, pauline.getCMgr(), &pauline_stat, confAddr, initialSubject, 3, FALSE); + BC_ASSERT_PTR_NOT_NULL(paulineCr); + + // Check that the chat room is correctly created on Laure's side and that the participants are added + LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure.getCMgr(), &laure_stat, + confAddr, initialSubject, 3, FALSE); + BC_ASSERT_PTR_NOT_NULL(laureCr); + ms_free(initialSubject); + } + + BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, pauline, michelle, laure}).wait([&focus] { + for (auto chatRoom : focus.getCore().getChatRooms()) { + for (auto participant : chatRoom->getParticipants()) { + for (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, + nbChatrooms, liblinphone_tester_sip_timeout)); + + BC_ASSERT_TRUE(wait_for_list(coresList, &laure.getStats().number_of_LinphoneConferenceStateCreated, nbChatrooms, + liblinphone_tester_sip_timeout)); + + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_LinphoneConferenceStateCreated, + nbChatrooms, liblinphone_tester_sip_timeout)); + + marie_stat = marie.getStats(); + pauline_stat = pauline.getStats(); + laure_stat = laure.getStats(); + michelle_stat = michelle.getStats(); + + std::list<ConferenceId> oldConferenceIds; + + for (auto chatRoom : laure.getCore().getChatRooms()) { + // Store conference ID to verify that no events are left matching it after restarting the core + oldConferenceIds.push_back(chatRoom->getConferenceId()); + std::string msg_text = + std::string("Welcome to all to chatroom ") + chatRoom->getConferenceAddress()->toString(); + LinphoneChatMessage *msg = ClientConference::sendTextMsg(L_GET_C_BACK_PTR(chatRoom), msg_text); + + BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, pauline, michelle, laure}).wait([&msg] { + return (linphone_chat_message_get_state(msg) == LinphoneChatMessageStateDelivered); + })); + linphone_chat_message_unref(msg); + } + + BC_ASSERT_TRUE(wait_for_list(coresList, &laure.getStats().number_of_LinphoneMessageSent, + laure_stat.number_of_LinphoneMessageSent + nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_LinphoneMessageReceived, + pauline_stat.number_of_LinphoneMessageReceived + nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneMessageReceived, + marie_stat.number_of_LinphoneMessageReceived + nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneMessageReceived, + michelle_stat.number_of_LinphoneMessageReceived + nbChatrooms, + liblinphone_tester_sip_timeout)); + + // wait bit more to detect side effect if any + CoreManagerAssert({focus, marie, pauline, michelle, laure}).waitUntil(chrono::seconds(5), [] { return false; }); + + account = linphone_core_get_default_account(laure.getLc()); + LinphoneAddress *oldLaureDeviceAddress = linphone_address_clone(linphone_account_get_contact_address(account)); + + ms_message("%s reinitializes its core", linphone_core_get_identity(laure.getLc())); + coresList = bctbx_list_remove(coresList, laure.getLc()); + linphone_core_manager_reinit(laure.getCMgr()); + linphone_config_set_string(linphone_core_get_config(laure.getLc()), "misc", "uuid", NULL); + linphone_core_remove_linphone_spec(laure.getLc(), "groupchat"); + const char *spec = "groupchat/1.2"; + linphone_core_add_linphone_spec(laure.getLc(), spec); + + account = linphone_core_get_default_account(laure.getLc()); + account_params = linphone_account_get_params(account); + new_account_params = linphone_account_params_clone(account_params); + linphone_account_params_set_conference_factory_address(new_account_params, + focus.getConferenceFactoryAddress().toC()); + linphone_account_set_params(account, new_account_params); + linphone_account_params_unref(new_account_params); + + laure_stat = laure.getStats(); + + ms_message("%s starts again its core", linphone_core_get_identity(laure.getLc())); + linphone_core_manager_start(laure.getCMgr(), TRUE); + focus.registerAsParticipantDevice(laure); + setup_mgr_for_conference(laure.getCMgr(), NULL); + coresList = bctbx_list_append(coresList, laure.getLc()); + + BC_ASSERT_TRUE(wait_for_list(coresList, &laure.getStats().number_of_LinphoneRegistrationOk, + laure_stat.number_of_LinphoneRegistrationOk + 1, liblinphone_tester_sip_timeout)); + + LinphoneAddress *laureDeviceAddress = linphone_account_get_contact_address(account); + // Notify chat room that a participant has registered + bctbx_list_t *devices = NULL; + bctbx_list_t *specs = linphone_core_get_linphone_specs_list(laure.getLc()); + LinphoneParticipantDeviceIdentity *identity = + linphone_factory_create_participant_device_identity(linphone_factory_get(), oldLaureDeviceAddress, ""); + linphone_participant_device_identity_set_capability_descriptor_2(identity, specs); + devices = bctbx_list_append(devices, identity); + + identity = linphone_factory_create_participant_device_identity(linphone_factory_get(), laureDeviceAddress, ""); + linphone_participant_device_identity_set_capability_descriptor_2(identity, specs); + devices = bctbx_list_append(devices, identity); + bctbx_list_free_with_data(specs, ms_free); + + for (auto chatRoom : focus.getCore().getChatRooms()) { + linphone_chat_room_set_participant_devices(L_GET_C_BACK_PTR(chatRoom), laure.getCMgr()->identity, devices); + } + bctbx_list_free_with_data(devices, (bctbx_list_free_func)belle_sip_object_unref); + + BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, pauline, michelle, laure}).wait([&laure, &nbChatrooms] { + if (laure.getCore().getChatRooms().size() != static_cast<size_t>(2 * nbChatrooms)) { + return false; + } + for (auto chatRoom : laure.getCore().getChatRooms()) { + if (chatRoom->getState() != ChatRoom::State::Created) { + return false; + } + } + return true; + })); + + BC_ASSERT_EQUAL(laure.getCore().getChatRooms().size(), 2 * nbChatrooms, size_t, "%zu"); + + marie_stat = marie.getStats(); + pauline_stat = pauline.getStats(); + michelle_stat = michelle.getStats(); + + for (auto chatRoom : laure.getCore().getChatRooms()) { + LinphoneChatRoom *cr = L_GET_C_BACK_PTR(chatRoom); + LinphoneChatRoomCbs *cbs = linphone_factory_create_chat_room_cbs(linphone_factory_get()); + setup_chat_room_callbacks(cbs); + linphone_chat_room_add_callbacks(cr, cbs); + linphone_chat_room_cbs_unref(cbs); + + std::string msg_text = std::string("I am back in chatroom ") + chatRoom->getConferenceAddress()->toString(); + LinphoneChatMessage *msg = ClientConference::sendTextMsg(cr, msg_text); + + BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, pauline, michelle, laure}).wait([&msg] { + return (linphone_chat_message_get_state(msg) == LinphoneChatMessageStateDelivered); + })); + linphone_chat_message_unref(msg); + } + + BC_ASSERT_TRUE(wait_for_list(coresList, &laure.getStats().number_of_LinphoneMessageSent, + laure_stat.number_of_LinphoneMessageSent + 2 * nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_LinphoneMessageReceived, + pauline_stat.number_of_LinphoneMessageReceived + 2 * nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneMessageReceived, + marie_stat.number_of_LinphoneMessageReceived + 2 * nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneMessageReceived, + michelle_stat.number_of_LinphoneMessageReceived + 2 * nbChatrooms, + liblinphone_tester_sip_timeout)); + + for (auto chatRoom : michelle.getCore().getChatRooms()) { + ms_message("%s is deleting chatroom %s", linphone_core_get_identity(michelle.getLc()), + chatRoom->getConferenceAddress()->toString().c_str()); + linphone_core_delete_chat_room(michelle.getLc(), L_GET_C_BACK_PTR(chatRoom)); + } + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneConferenceStateDeleted, + michelle_stat.number_of_LinphoneConferenceStateDeleted + nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_participants_removed, + marie_stat.number_of_participants_removed + nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_participants_removed, + pauline_stat.number_of_participants_removed + nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &laure.getStats().number_of_participants_removed, + laure_stat.number_of_participants_removed + nbChatrooms, + liblinphone_tester_sip_timeout)); + + char *uuid = NULL; + if (linphone_config_get_string(linphone_core_get_config(laure.getLc()), "misc", "uuid", NULL)) { + uuid = + bctbx_strdup(linphone_config_get_string(linphone_core_get_config(laure.getLc()), "misc", "uuid", NULL)); + } + + ms_message("%s reinitializes one last time its core", linphone_core_get_identity(laure.getLc())); + coresList = bctbx_list_remove(coresList, laure.getLc()); + linphone_core_manager_reinit(laure.getCMgr()); + // Keep the same uuid + linphone_config_set_string(linphone_core_get_config(laure.getLc()), "misc", "uuid", uuid); + if (uuid) { + bctbx_free(uuid); + } + + linphone_core_remove_linphone_spec(laure.getLc(), "groupchat"); + linphone_core_add_linphone_spec(laure.getLc(), spec); + + account = linphone_core_get_default_account(laure.getLc()); + account_params = linphone_account_get_params(account); + new_account_params = linphone_account_params_clone(account_params); + linphone_account_params_set_conference_factory_address(new_account_params, + focus.getConferenceFactoryAddress().toC()); + linphone_account_set_params(account, new_account_params); + linphone_account_params_unref(new_account_params); + + marie_stat = marie.getStats(); + pauline_stat = pauline.getStats(); + michelle_stat = michelle.getStats(); + + Address michelleAddr = michelle.getIdentity(); + for (auto chatRoom : marie.getCore().getChatRooms()) { + stats michelle_stat2 = michelle.getStats(); + LinphoneChatRoom *cChatRoom = L_GET_C_BACK_PTR(chatRoom); + linphone_chat_room_add_participant(cChatRoom, michelleAddr.toC()); + BC_ASSERT_PTR_NOT_NULL(check_creation_chat_room_client_side( + coresList, michelle.getCMgr(), &michelle_stat2, linphone_chat_room_get_conference_address(cChatRoom), + linphone_chat_room_get_subject(cChatRoom), 3, FALSE)); + } + + BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_participant_devices_added, + marie_stat.number_of_participant_devices_added + nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_participant_devices_added, + pauline_stat.number_of_participant_devices_added + nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_participants_added, + marie_stat.number_of_participants_added + nbChatrooms, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_participants_added, + pauline_stat.number_of_participants_added + nbChatrooms, + liblinphone_tester_sip_timeout)); + + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneConferenceStateCreated, + michelle_stat.number_of_LinphoneConferenceStateCreated + nbChatrooms, + liblinphone_tester_sip_timeout)); + + laure_stat = laure.getStats(); + + ms_message("%s starts one last time its core", linphone_core_get_identity(laure.getLc())); + linphone_core_manager_start(laure.getCMgr(), TRUE); + focus.registerAsParticipantDevice(laure); + setup_mgr_for_conference(laure.getCMgr(), NULL); + coresList = bctbx_list_append(coresList, laure.getLc()); + + BC_ASSERT_TRUE(wait_for_list(coresList, &laure.getStats().number_of_LinphoneRegistrationOk, + laure_stat.number_of_LinphoneRegistrationOk + 1, liblinphone_tester_sip_timeout)); + + BC_ASSERT_EQUAL(laure.getCore().getChatRooms().size(), nbChatrooms, size_t, "%zu"); + BC_ASSERT_EQUAL(L_GET_PRIVATE_FROM_C_OBJECT(laure.getLc())->mainDb->getChatRooms().size(), nbChatrooms, size_t, + "%zu"); + + BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, pauline, michelle, laure}).wait([&laure, &nbChatrooms] { + if (laure.getCore().getChatRooms().size() < static_cast<size_t>(nbChatrooms)) { + return false; + } + for (auto chatRoom : laure.getCore().getChatRooms()) { + if (chatRoom->getState() != ChatRoom::State::Created) { + return false; + } + if (chatRoom->getMessageHistorySize() != 3) { + return false; + } + if (L_GET_PRIVATE_FROM_C_OBJECT(laure.getLc()) + ->mainDb->getChatMessageCount(chatRoom->getConferenceId()) != 3) { + return false; + } + } + return true; + })); + + for (const auto &conferenceId : oldConferenceIds) { + BC_ASSERT_EQUAL( + L_GET_PRIVATE_FROM_C_OBJECT(laure.getLc())->mainDb->getConferenceNotifiedEvents(conferenceId, 0).size(), + 0, size_t, "%zu"); + } + + BC_ASSERT_EQUAL(marie.getCore().getChatRooms().size(), nbChatrooms, size_t, "%zu"); + + // Delete Laure's chatrooms by retrieving their conference address from Marie's ones. + // This will allow to verify that there is no duplicate chatroom in stored by Laure's core + for (auto chatRoom : marie.getCore().getChatRooms()) { + const auto &conferenceAddress = chatRoom->getConferenceAddress(); + LinphoneAddress *laureLocalAddress = + linphone_account_get_contact_address(linphone_core_get_default_account(laure.getLc())); + LinphoneChatRoom *laureCr = laure.searchChatRoom(laureLocalAddress, conferenceAddress->toC()); + LinphoneChatRoomCbs *cbs = linphone_factory_create_chat_room_cbs(linphone_factory_get()); + setup_chat_room_callbacks(cbs); + linphone_chat_room_add_callbacks(laureCr, cbs); + linphone_chat_room_cbs_unref(cbs); + linphone_core_manager_delete_chat_room(laure.getCMgr(), laureCr, coresList); + } + + BC_ASSERT_EQUAL(laure.getCore().getChatRooms().size(), 0, size_t, "%zu"); + 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, pauline, michelle, laure}).waitUntil(chrono::seconds(90), [&focus] { + return focus.getCore().getChatRooms().size() == 0; + })); + + // wait bit more to detect side effect if any + CoreManagerAssert({focus, marie, pauline, michelle, laure}).waitUntil(chrono::seconds(2), [] { return false; }); + + // to avoid creation attempt of a new chatroom + auto config = focus.getDefaultProxyConfig(); + linphone_proxy_config_edit(config); + linphone_proxy_config_set_conference_factory_uri(config, NULL); + linphone_proxy_config_done(config); + + bctbx_list_free(coresList); + } +} } // namespace LinphoneTest -static test_t local_conference_chat_tests[] = { +static test_t local_conference_chat_basic_tests[] = { TEST_ONE_TAG("Group chat room creation local server", LinphoneTest::group_chat_room_creation_server, "LeaksMemory"), /* beacause of coreMgr restart*/ TEST_NO_TAG("Group chat Server chat room deletion", LinphoneTest::group_chat_room_server_deletion), + TEST_ONE_TAG("Group chat with duplications", + LinphoneTest::group_chat_room_with_duplications, + "LeaksMemory"), /* beacause of coreMgr restart*/ TEST_ONE_TAG("Group chat with client removed added", LinphoneTest::group_chat_room_with_client_removed_added, "LeaksMemory"), /* beacause of coreMgr restart*/ 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 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", + LinphoneTest::one_to_one_chatroom_exhumed_while_offline, + "LeaksMemory"), /* because of network up and down*/ + TEST_ONE_TAG("Group chat Server chat room deletion with remote list event handler", + LinphoneTest::group_chat_room_server_deletion_with_rmt_lst_event_handler, + "LeaksMemory") /* because of coreMgr restart*/ +}; + +static test_t local_conference_chat_advanced_tests[] = { 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", @@ -2625,14 +3023,6 @@ static test_t local_conference_chat_tests[] = { 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", - LinphoneTest::one_to_one_chatroom_exhumed_while_offline, - "LeaksMemory"), /* because of network up and down*/ - TEST_ONE_TAG("Group chat Server chat room deletion with remote list event handler", - LinphoneTest::group_chat_room_server_deletion_with_rmt_lst_event_handler, - "LeaksMemory"), /* because of coreMgr restart*/ TEST_ONE_TAG("One to one group chat deletion initiated by server and client", LinphoneTest::one_to_one_group_chat_room_deletion_by_server_client, "LeaksMemory"), /* because of network up and down */ @@ -2654,15 +3044,25 @@ static test_t local_conference_chat_error_tests[] = { }; -test_suite_t local_conference_test_suite_chat = {"Local conference tester (Chat)", - NULL, - NULL, - liblinphone_tester_before_each, - liblinphone_tester_after_each, - sizeof(local_conference_chat_tests) / - sizeof(local_conference_chat_tests[0]), - local_conference_chat_tests, - 0}; +test_suite_t local_conference_test_suite_chat_basic = {"Local conference tester (Chat Basic)", + NULL, + NULL, + liblinphone_tester_before_each, + liblinphone_tester_after_each, + sizeof(local_conference_chat_basic_tests) / + sizeof(local_conference_chat_basic_tests[0]), + local_conference_chat_basic_tests, + 0}; + +test_suite_t local_conference_test_suite_chat_advanced = {"Local conference tester (Chat Advanced)", + NULL, + NULL, + liblinphone_tester_before_each, + liblinphone_tester_after_each, + sizeof(local_conference_chat_advanced_tests) / + sizeof(local_conference_chat_advanced_tests[0]), + local_conference_chat_advanced_tests, + 0}; test_suite_t local_conference_test_suite_chat_error = {"Local conference tester (Chat error)", NULL, diff --git a/tester/tester.c b/tester/tester.c index 54271a3fec..e74f6dbf62 100644 --- a/tester/tester.c +++ b/tester/tester.c @@ -2900,8 +2900,8 @@ void linphone_core_manager_destroy_after_stop_async(LinphoneCoreManager *mgr) { } void linphone_core_manager_delete_chat_room(LinphoneCoreManager *mgr, LinphoneChatRoom *cr, bctbx_list_t *coresList) { - stats mgrStats = mgr->stat; if (cr) { + stats mgrStats = mgr->stat; linphone_core_delete_chat_room(mgr->lc, cr); BC_ASSERT_TRUE(wait_for_list(coresList, &mgr->stat.number_of_LinphoneConferenceStateDeleted, mgrStats.number_of_LinphoneConferenceStateDeleted + 1, 10000)); -- GitLab