diff --git a/src/chat/chat-room/server-group-chat-room.cpp b/src/chat/chat-room/server-group-chat-room.cpp index 2ebfa75e7097c95ecb143f94f08ac94912f61b8f..d63e2957c32918c3b7a559d7b9f646b8d73d63f6 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 303534eb0b9d686dc2d59a82168fb124a42f6f2a..ee1b5aa102a3b536b942fe042369f825c9fb232b 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 b1a02e01da0e3960370bf28e0f2ccdd3172d0b5c..8a0f78009518e6bc84129d1aef276533fafa553e 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 03068228fc25e80323c4d3fb5f7b55a9c2d0c7a9..740dc9962e22de188de2ca3fe69bd8d27a52a018 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 da0ac5d4f47454c69b0ae08ede1125bac6c95cd7..4006a1acb6e4581f094044d244dd9ff1163aa402 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 48d7f769f2ee71efef547911e04486dc3de8bb32..d2d9c7a376e2ee40c85357e81597b26fce8c42b1 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 014d9d2d0b2c8f7ee8b31efd7b1ed8241dac22e1..ae7c0e35f748f6f63913cebf381aa0a868d33524 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 fb134d7cdb91dcaa0dfa62b9fa1bda9decfd3db6..eb9def6ebfec1025e8b518a1d8158a3b12e4fc15 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 e90bac043560edd078a4f1f4e113aaa5e72ed9e8..9239f51aa000354b89d77cd79ebbfe98cafee807 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 54271a3fecfe06f57e41bab8d8aa95e6f02a0fe4..e74f6dbf62c4b8a1cf06ccd5e48832c0c5973a54 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));