From 32eb8387af8911d9befb044d0ac8ded717fe87be Mon Sep 17 00:00:00 2001 From: Andrea Gianarda <andrea.gianarda@belledonne-communications.com> Date: Fri, 8 Sep 2023 16:16:38 +0200 Subject: [PATCH] Rework of message state to resolve a circular dependency between ChatMessagePrivate::setParticipantState and ChatMessagePrivate::setState. All event regarding a chat message will now chat the state of a participant and then if neecesary the state of the chat message will follow. For IMDN controlled stats (i.e. DeliveredToUser, NotDelivered and Displayed), the state of the chat message will be deduced based on the states of all participants For non-IMDN controlled states, the state of the chat message will follow the state of the me of the chat room the message it associated with --- coreapi/callbacks.c | 9 +- src/c-wrapper/api/c-chat-room.cpp | 3 +- src/chat/chat-message/chat-message-p.h | 16 +- src/chat/chat-message/chat-message.cpp | 245 ++++++++++-------- src/chat/chat-room/chat-room.cpp | 12 +- src/chat/chat-room/client-group-chat-room.cpp | 3 +- src/chat/chat-room/server-group-chat-room.cpp | 4 +- .../lime-x3dh-encryption-engine.cpp | 3 +- .../file-transfer-chat-message-modifier.cpp | 40 ++- src/db/main-db.cpp | 30 ++- .../conference-participant-event.cpp | 4 +- tester/local_chat_imdn_tester.cpp | 206 +++++++++++++++ tester/local_chat_tester.cpp | 9 +- tester/local_conference_tester_functions.cpp | 90 +++---- tester/local_secure_chat_tester.cpp | 9 +- tester/message_tester.c | 8 +- 16 files changed, 489 insertions(+), 202 deletions(-) diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index f2b56c39f2..d4da2af869 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -808,9 +808,16 @@ static void message_delivery_update(SalOp *op, SalMessageDeliveryStatus status) LinphonePrivate::ChatMessage *msg = static_cast<LinphonePrivate::ChatMessage *>(op->getUserPointer()); if (!msg) return; // Do not handle delivery status for isComposing messages. + auto chatRoom = msg->getChatRoom(); // Check that the message does not belong to an already destroyed chat room - if so, do not invoke callbacks - if (msg->getChatRoom()) + if (chatRoom) { + // It would be better to call setParticipantState but doing so, it causes a memory leak L_GET_PRIVATE(msg)->setState((LinphonePrivate::ChatMessage::State)chatStatusSal2Linphone(status)); + /*L_GET_PRIVATE(msg)->setParticipantState(chatRoom->getMe()->getAddress(), + (LinphonePrivate::ChatMessage::State)chatStatusSal2Linphone(status), + ::ms_time(NULL)); + */ + } } static void info_received(SalOp *op, SalBodyHandler *body_handler) { diff --git a/src/c-wrapper/api/c-chat-room.cpp b/src/c-wrapper/api/c-chat-room.cpp index 92b706f94f..24d881219f 100644 --- a/src/c-wrapper/api/c-chat-room.cpp +++ b/src/c-wrapper/api/c-chat-room.cpp @@ -168,7 +168,8 @@ LinphoneChatMessage *linphone_chat_room_create_message_2(LinphoneChatRoom *cr, LinphonePrivate::ChatMessagePrivate *dMsg = L_GET_PRIVATE_FROM_C_OBJECT(msg); dMsg->setTime(time); - dMsg->setState(static_cast<LinphonePrivate::ChatMessage::State>(state)); + dMsg->setParticipantState(L_GET_CPP_PTR_FROM_C_OBJECT(cr)->getMe()->getAddress(), + static_cast<LinphonePrivate::ChatMessage::State>(state), ::ms_time(NULL)); return msg; } diff --git a/src/chat/chat-message/chat-message-p.h b/src/chat/chat-message/chat-message-p.h index 51a2a3dfc4..a8f66f7e94 100644 --- a/src/chat/chat-message/chat-message-p.h +++ b/src/chat/chat-message/chat-message-p.h @@ -70,9 +70,9 @@ public: void setParticipantState(const std::shared_ptr<Address> &participantAddress, ChatMessage::State newState, - time_t stateChangeTime); + time_t stateChangeTime, + LinphoneReason reason = LinphoneReasonNone); - virtual void setState(ChatMessage::State newState); void forceState(ChatMessage::State newState) { state = newState; } @@ -244,16 +244,15 @@ public: void updateInDb(); static bool isValidStateTransition(ChatMessage::State currentState, ChatMessage::State newState); + static bool isImdnControlledState(ChatMessage::State state); void restoreFileTransferContentAsFileContent(); -private: - ChatMessagePrivate(const std::shared_ptr<AbstractChatRoom> &cr, ChatMessage::Direction dir); - virtual ~ChatMessagePrivate(); - -public: long long storageId = -1; + // It should be private if we do so, but it must be called in message_delivery_update to avoid a memory leak + virtual void setState(ChatMessage::State newState); + protected: bool displayNotificationRequired = true; bool negativeDeliveryNotificationRequired = true; @@ -262,6 +261,9 @@ protected: std::string contentEncoding; private: + ChatMessagePrivate(const std::shared_ptr<AbstractChatRoom> &cr, ChatMessage::Direction dir); + virtual ~ChatMessagePrivate(); + // TODO: Clean attributes. time_t time = ::ms_time(0); // TODO: Change me in all files. std::string imdnId; diff --git a/src/chat/chat-message/chat-message.cpp b/src/chat/chat-message/chat-message.cpp index d13f92711d..28b519649e 100644 --- a/src/chat/chat-message/chat-message.cpp +++ b/src/chat/chat-message/chat-message.cpp @@ -121,39 +121,80 @@ bool ChatMessagePrivate::isMarkedAsRead() const { void ChatMessagePrivate::setParticipantState(const std::shared_ptr<Address> &participantAddress, ChatMessage::State newState, - time_t stateChangeTime) { + time_t stateChangeTime, + LinphoneReason reason) { L_Q(); const auto &chatRoom = q->getChatRoom(); - if (!q->isValid() || !chatRoom) return; - - if (chatRoom->getCapabilities().isSet(ChatRoom::Capabilities::Basic)) { - // Basic Chat Room doesn't support participant state - setState(newState); - return; - } + if (!chatRoom) return; + const shared_ptr<ChatMessage> &sharedMessage = q->getSharedFromThis(); + const bool isBasicChatRoom = chatRoom->getCapabilities().isSet(ChatRoom::Capabilities::Basic); unique_ptr<MainDb> &mainDb = chatRoom->getCore()->getPrivate()->mainDb; shared_ptr<EventLog> eventLog = mainDb->getEvent(mainDb, q->getStorageId()); - ChatMessage::State currentState = mainDb->getChatMessageParticipantState(eventLog, participantAddress); + ChatMessage::State currentState = ChatMessage::State::Idle; + if (isBasicChatRoom) { + currentState = q->getState(); + } else if (eventLog) { + currentState = mainDb->getChatMessageParticipantState(eventLog, participantAddress); + } if (!isValidStateTransition(currentState, newState)) { - lWarning() << "Chat message " << q->getSharedFromThis() << ": Invalid transaction of participant " - << *participantAddress << " from state " << Utils::toString(currentState) << " to state " - << Utils::toString(newState); + if (isBasicChatRoom) { + const auto &conferenceAddress = chatRoom->getConferenceAddress(); + const auto conferenceAddressStr = + conferenceAddress ? conferenceAddress->toString() : std::string("<unknown-conference-address>"); + lWarning() << "Chat message " << sharedMessage << ": Invalid transaction of basic chat room " + << conferenceAddressStr << " from state " << Utils::toString(currentState) << " to state " + << Utils::toString(newState); + } else { + lWarning() << "Chat message " << sharedMessage << ": Invalid transaction of participant " + << *participantAddress << " from state " << Utils::toString(currentState) << " to state " + << Utils::toString(newState); + } return; } - lInfo() << "Chat message " << q->getSharedFromThis() << ": moving participant '" << *participantAddress - << "' state to " << Utils::toString(newState); - mainDb->setChatMessageParticipantState(eventLog, participantAddress, newState, stateChangeTime); + auto me = chatRoom->getMe(); + const auto isMe = participantAddress->weakEqual(*me->getAddress()); + + // Send IMDN if the participant whose state changes is me + if (isMe) { + switch (newState) { + case ChatMessage::State::Displayed: + static_cast<ChatRoomPrivate *>(chatRoom->getPrivate())->sendDisplayNotification(sharedMessage); + break; + case ChatMessage::State::DeliveredToUser: + static_cast<ChatRoomPrivate *>(chatRoom->getPrivate())->sendDeliveryNotification(sharedMessage); + break; + case ChatMessage::State::NotDelivered: + if (reason != LinphoneReasonNone) { + static_cast<ChatRoomPrivate *>(chatRoom->getPrivate()) + ->sendDeliveryErrorNotification(sharedMessage, reason); + } + break; + default: + break; + } + } + + if (!q->isValid()) { + if (newState == ChatMessage::State::NotDelivered) { + setState(newState); + } + return; + } + + // Participant states are not supported + if (isBasicChatRoom) { + setState(newState); + return; + } LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q); LinphoneChatRoom *cr = L_GET_C_BACK_PTR(chatRoom); - auto me = chatRoom->getMe(); - auto participant = - participantAddress->weakEqual(*me->getAddress()) ? me : chatRoom->findParticipant(participantAddress); + auto participant = isMe ? me : chatRoom->findParticipant(participantAddress); ParticipantImdnState imdnState(participant, newState, stateChangeTime); // Legacy callbacks, deprecated ! @@ -173,40 +214,64 @@ void ChatMessagePrivate::setParticipantState(const std::shared_ptr<Address> &par return; } - const auto states = q->getParticipantsState(); + lInfo() << "Chat message " << sharedMessage << ": moving participant '" << *participantAddress << "' state to " + << Utils::toString(newState); + mainDb->setChatMessageParticipantState(eventLog, participantAddress, newState, stateChangeTime); + + // Update chat message state if it doesn't depend on IMDN + if (isMe && !isImdnControlledState(newState)) { + setState(newState); + } + + const auto imdnStates = q->getParticipantsState(); + size_t nbRecipients = 0; size_t nbDisplayedStates = 0; size_t nbDeliveredToUserStates = 0; size_t nbNotDeliveredStates = 0; - for (const auto &state : states) { - switch (state.getState()) { - case ChatMessage::State::Displayed: - nbDisplayedStates++; - break; - case ChatMessage::State::DeliveredToUser: - nbDeliveredToUserStates++; - break; - case ChatMessage::State::NotDelivered: + for (const auto &imdnState : imdnStates) { + const auto &participantState = imdnState.getState(); + const auto &imdnParticipant = imdnState.getParticipant(); + if (fromAddress->weakEqual(*(imdnParticipant->getAddress()))) { + if (participantState == ChatMessage::State::NotDelivered) { nbNotDeliveredStates++; - break; - default: - break; + } + } else { + nbRecipients++; + switch (participantState) { + case ChatMessage::State::Displayed: + nbDisplayedStates++; + break; + case ChatMessage::State::DeliveredToUser: + nbDeliveredToUserStates++; + break; + case ChatMessage::State::NotDelivered: + nbNotDeliveredStates++; + break; + default: + break; + } } } if (nbNotDeliveredStates > 0) { setState(ChatMessage::State::NotDelivered); - } else if ((states.size() > 0) && (nbDisplayedStates == states.size())) { + } else if ((nbRecipients > 0) && (nbDisplayedStates == nbRecipients)) { setState(ChatMessage::State::Displayed); - } else if ((states.size() > 0) && ((nbDisplayedStates + nbDeliveredToUserStates) == states.size())) { + } else if ((nbRecipients > 0) && ((nbDisplayedStates + nbDeliveredToUserStates) == nbRecipients)) { setState(ChatMessage::State::DeliveredToUser); } // When we already marked an incoming message as displayed, start ephemeral countdown when all other recipients have // displayed it as well - if (isEphemeral && state == ChatMessage::State::Displayed) { - if (direction == ChatMessage::Direction::Incoming && - nbDisplayedStates == states.size()) { // -1 is for ourselves, our own display state isn't stored in db - startEphemeralCountDown(); + if (isEphemeral && state == ChatMessage::State::Displayed && direction == ChatMessage::Direction::Incoming) { + startEphemeralCountDown(); + } + + if (isMe) { + // Set me participant state to displayed if we are the sender, set the message as Displayed as soon as we + // received the 202 Accepted response + if (fromAddress->weakEqual(*participantAddress) && (newState == ChatMessage::State::DeliveredToUser)) { + setParticipantState(participantAddress, ChatMessage::State::Displayed, ::ms_time(nullptr)); } } } @@ -226,7 +291,6 @@ void ChatMessagePrivate::setState(ChatMessage::State newState) { << " to " << Utils::toString(newState); ChatMessage::State oldState = state; state = newState; - setParticipantState(chatRoom->getMe()->getAddress(), state, ::ms_time(nullptr)); if (state == ChatMessage::State::NotDelivered) { if (salOp) { @@ -239,7 +303,10 @@ void ChatMessagePrivate::setState(ChatMessage::State newState) { if (direction == ChatMessage::Direction::Outgoing) { // Delivered state isn't triggered by IMDN, so participants state won't be set unless we manually do so here if (state == ChatMessage::State::Delivered) { - for (auto participant : chatRoom->getParticipants()) { + // Use list of participants the client is sure have received the message and not the actual list of + // participants being part of the chatroom + for (const auto &imdnState : q->getParticipantsState()) { + const auto &participant = imdnState.getParticipant(); setParticipantState(participant->getAddress(), state, q->getTime()); } } @@ -268,16 +335,14 @@ void ChatMessagePrivate::setState(ChatMessage::State newState) { listeners.clear(); } - // 3. Specific case, change to displayed once all file transfers haven been downloaded, and only if chat message has - // been marked as read. + // 3. Specific case, upon reception do not attempt to store in db before asking the user if he wants to do so or not if (state == ChatMessage::State::FileTransferDone && direction == ChatMessage::Direction::Incoming) { if (!hasFileTransferContent() && isMarkedAsRead()) { - setState(ChatMessage::State::Displayed); + setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::Displayed, ::ms_time(nullptr)); return; } } - // 4. Specific case, upon reception do not attempt to store in db before asking the user if he wants to do so or not if (state == ChatMessage::State::Delivered && oldState == ChatMessage::State::Idle && direction == ChatMessage::Direction::Incoming && !q->isValid()) { // If we're here it's because message is because we're in the middle of the receive() method and @@ -287,47 +352,13 @@ void ChatMessagePrivate::setState(ChatMessage::State newState) { return; } - // 5. Send notification - if ((state == ChatMessage::State::Displayed) && direction == ChatMessage::Direction::Incoming) { - // Wait until all files are downloaded before sending displayed IMDN - static_cast<ChatRoomPrivate *>(chatRoom->getPrivate())->sendDisplayNotification(sharedMessage); - } - - // 6. update in database for ephemeral message if necessary. + // 4. update in database for ephemeral message if necessary. if (isEphemeral && state == ChatMessage::State::Displayed) { - bool allParticipantsAreInDisplayedState = false; - if (chatRoom->getCapabilities().isSet(ChatRoom::Capabilities::OneToOne)) { - allParticipantsAreInDisplayedState = true; - } else { - if (direction == ChatMessage::Direction::Incoming) { - const auto states = q->getParticipantsState(); - size_t nbDisplayedStates = 0; - for (const auto &state : states) { - switch (state.getState()) { - case ChatMessage::State::Displayed: - nbDisplayedStates++; - break; - default: - break; - } - } - - allParticipantsAreInDisplayedState = - nbDisplayedStates == - states.size() - 1; // -1 is for ourselves, our own display state isn't stored in db - } else { - // For outgoing messages state is never displayed until all participants are in display state - allParticipantsAreInDisplayedState = true; - } - } - - if (allParticipantsAreInDisplayedState) { - lInfo() << "All participants are in displayed state, starting ephemeral countdown"; - startEphemeralCountDown(); - } + lInfo() << "All participants are in displayed state, starting ephemeral countdown"; + startEphemeralCountDown(); } - // 7. Update in database if necessary. + // 5. Update in database if necessary. if (state != ChatMessage::State::InProgress && state != ChatMessage::State::FileTransferError && state != ChatMessage::State::FileTransferInProgress) { updateInDb(); @@ -754,8 +785,8 @@ LinphoneReason ChatMessagePrivate::receive() { chatRoom->getPrivate()->notifyUndecryptableChatMessageReceived(q->getSharedFromThis()); reason = linphone_error_code_to_reason(errorCode); if (!chatRoom) return reason; - static_cast<ChatRoomPrivate *>(chatRoom->getPrivate()) - ->sendDeliveryErrorNotification(q->getSharedFromThis(), reason); + setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::NotDelivered, ::ms_time(nullptr), + reason); return reason; } @@ -844,7 +875,7 @@ LinphoneReason ChatMessagePrivate::receive() { // a modifier) currentRecvStep = ChatMessagePrivate::Step::None; - setState(ChatMessage::State::Delivered); + setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::Delivered, ::ms_time(nullptr)); // Check if this is in fact an outgoing message (case where this is a message sent by us from an other device). if (chatRoom->getCapabilities() & ChatRoom::Capabilities::Conference && @@ -880,8 +911,8 @@ LinphoneReason ChatMessagePrivate::receive() { if (chatRoom && (errorCode > 0)) { reason = linphone_error_code_to_reason(errorCode); - static_cast<ChatRoomPrivate *>(q->getChatRoom()->getPrivate()) - ->sendDeliveryErrorNotification(q->getSharedFromThis(), reason); + setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::NotDelivered, ::ms_time(nullptr), + reason); return reason; } @@ -1007,7 +1038,10 @@ void ChatMessagePrivate::handleAutoDownload() { chatRoom->getPrivate()->removeTransientChatMessage(q->getSharedFromThis()); setAutoFileTransferDownloadInProgress(false); - setState(ChatMessage::State::Delivered); + // The message is set to delivered here because this code is hit if the attachment cannot be downloaded or the + // download is aborted The delivered state will be set again message_delivery_update upon reception of 200 Ok or 202 + // Accepted when a message is sent + setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::Delivered, ::ms_time(NULL)); chatRoom->getPrivate()->onChatMessageReceived(q->getSharedFromThis()); for (Content *c : contents) { @@ -1113,14 +1147,16 @@ void ChatMessagePrivate::send() { } else { ChatMessageModifier::Result result = fileTransferChatMessageModifier.encode(q->getSharedFromThis(), errorCode); if (result == ChatMessageModifier::Result::Error) { - setState(ChatMessage::State::NotDelivered); + setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::NotDelivered, ::ms_time(nullptr), + linphone_error_code_to_reason(errorCode)); // Remove current step so we go through all modifiers if message is re-sent currentSendStep = ChatMessagePrivate::Step::None; return; } if (result == ChatMessageModifier::Result::Suspended) { - setState(ChatMessage::State::FileTransferInProgress); + setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::FileTransferInProgress, + ::ms_time(nullptr)); return; } currentSendStep |= ChatMessagePrivate::Step::FileUpload; @@ -1213,8 +1249,8 @@ void ChatMessagePrivate::send() { // Remove current step so we go through all modifiers if message is re-sent currentSendStep = ChatMessagePrivate::Step::None; restoreFileTransferContentAsFileContent(); - setState( - ChatMessage::State::NotDelivered); // Do it after the restore to have the correct message in db + setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::NotDelivered, + ::ms_time(nullptr), linphone_error_code_to_reason(errorCode)); chatRoom->getPrivate()->removeTransientChatMessage(q->getSharedFromThis()); return; } else if (result == ChatMessageModifier::Result::Suspended) { @@ -1289,9 +1325,14 @@ void ChatMessagePrivate::send() { // If it is a resend, reset participant states to Idle. // Not doing so, it will lead to the message being incorrectly marked as not delivered when at least one // participant hasn't received it yet. - for (auto participant : chatRoom->getParticipants()) { + // Use list of participants the client is sure have received the message and not the actual list of participants + // being part of the chatroom + for (const auto &imdnState : q->getParticipantsState()) { + const auto &participant = imdnState.getParticipant(); setParticipantState(participant->getAddress(), ChatMessage::State::Idle, q->getTime()); } + // Update message in DB to store the new IMDN message ID + updateInDb(); } else if (toBeStored) { // Composing messages and IMDN aren't stored in DB so do not try, it will log an error message Invalid db key // for nothing. @@ -1307,7 +1348,7 @@ void ChatMessagePrivate::send() { /* If operation failed, we should not change message state */ if (direction == ChatMessage::Direction::Outgoing) { setIsReadOnly(true); - setState(ChatMessage::State::InProgress); + setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::InProgress, ::ms_time(nullptr)); } if (q->isReaction()) { @@ -1343,8 +1384,6 @@ void ChatMessagePrivate::storeInDb() { dChatRoom->addEvent(eventLog); // From this point forward the chat message will have a valid dbKey if (!chatRoom->getCapabilities().isSet(ChatRoom::Capabilities::Basic)) { setParticipantState(chatRoom->getMe()->getAddress(), state, ::ms_time(nullptr)); - setParticipantState(Address::create(q->getFromAddress()->getUriWithoutGruu()), ChatMessage::State::Displayed, - ::ms_time(nullptr)); } if (direction == ChatMessage::Direction::Incoming) { @@ -1397,6 +1436,11 @@ void ChatMessagePrivate::updateInDb() { // ----------------------------------------------------------------------------- +bool ChatMessagePrivate::isImdnControlledState(ChatMessage::State state) { + return (state == ChatMessage::State::Displayed || state == ChatMessage::State::DeliveredToUser || + state == ChatMessage::State::NotDelivered); +} + bool ChatMessagePrivate::isValidStateTransition(ChatMessage::State currentState, ChatMessage::State newState) { if (newState == currentState) return false; @@ -1659,13 +1703,11 @@ list<ParticipantImdnState> ChatMessage::getParticipantsState() const { unique_ptr<MainDb> &mainDb = chatRoom->getCore()->getPrivate()->mainDb; shared_ptr<EventLog> eventLog = mainDb->getEvent(mainDb, getStorageId()); list<MainDb::ParticipantState> dbResults = mainDb->getChatMessageParticipantStates(eventLog); - auto sender = chatRoom->findParticipant(getFromAddress()); - auto meIsSender = chatRoom->isMe(getFromAddress()); for (const auto &dbResult : dbResults) { auto isMe = chatRoom->isMe(dbResult.address); auto participant = isMe ? chatRoom->getMe() : chatRoom->findParticipant(dbResult.address); // Do not add myself to the result list if I am the sender. - if (participant && ((participant != sender) || !meIsSender)) { + if (participant) { result.emplace_back(participant, dbResult.state, dbResult.timestamp); } } @@ -1769,13 +1811,14 @@ void ChatMessage::cancelFileTransfer() { lInfo() << "File transfer on message [" << getSharedFromThis() << "] has been cancelled"; if (d->state == State::FileTransferInProgress) { + auto chatRoom = getChatRoom(); lInfo() << "File transfer on message [" << getSharedFromThis() << "] was in progress, updating state"; // For auto download messages, set the state back to Delivered if (d->isAutoFileTransferDownloadInProgress()) { - d->setState(State::Delivered); - getChatRoom()->getPrivate()->removeTransientChatMessage(getSharedFromThis()); + d->setParticipantState(chatRoom->getMe()->getAddress(), State::Delivered, ::ms_time(nullptr)); + chatRoom->getPrivate()->removeTransientChatMessage(getSharedFromThis()); } else { - d->setState(State::NotDelivered); + d->setParticipantState(chatRoom->getMe()->getAddress(), State::NotDelivered, ::ms_time(nullptr)); } } } else { diff --git a/src/chat/chat-room/chat-room.cpp b/src/chat/chat-room/chat-room.cpp index fd566c4b63..88db4eb227 100644 --- a/src/chat/chat-room/chat-room.cpp +++ b/src/chat/chat-room/chat-room.cpp @@ -169,7 +169,8 @@ void ChatRoomPrivate::realtimeTextReceived(uint32_t character, const shared_ptr< pendingMessage->addContent(content); bctbx_debug("New line received, forge a message with content [%s]", content->getBodyAsString().c_str()); - pendingMessage->getPrivate()->setState(ChatMessage::State::Delivered); + pendingMessage->getPrivate()->setParticipantState(q->getMe()->getAddress(), ChatMessage::State::Delivered, + ::ms_time(nullptr)); pendingMessage->getPrivate()->setTime(::ms_time(0)); if (linphone_config_get_int(linphone_core_get_config(cCore), "misc", "store_rtt_messages", 1) == 1) { @@ -474,7 +475,8 @@ void ChatRoomPrivate::notifyAggregatedChatMessages() { // Notify delivery for (auto &chatMessage : aggregatedMessages) { - sendDeliveryNotification(chatMessage); + chatMessage->getPrivate()->setParticipantState(q->getMe()->getAddress(), ChatMessage::State::DeliveredToUser, + ::ms_time(nullptr)); } bctbx_list_free_with_data(cMessages, (bctbx_list_free_func)linphone_chat_message_unref); @@ -771,7 +773,8 @@ void ChatRoom::markAsRead() { chatMessage->getPrivate()->markAsRead(); // Do not set the message state has displayed if it contains a file transfer (to prevent imdn sending) if (!chatMessage->getPrivate()->hasFileTransferContent()) { - chatMessage->getPrivate()->setState(ChatMessage::State::Displayed); + chatMessage->getPrivate()->setParticipantState(getMe()->getAddress(), ChatMessage::State::Displayed, + ::ms_time(nullptr)); } } @@ -780,7 +783,8 @@ void ChatRoom::markAsRead() { chatMessage->getPrivate()->markAsRead(); // Do not set the message state has displayed if it contains a file transfer (to prevent imdn sending) if (!chatMessage->getPrivate()->hasFileTransferContent()) { - chatMessage->getPrivate()->setState(ChatMessage::State::Displayed); + chatMessage->getPrivate()->setParticipantState(getMe()->getAddress(), ChatMessage::State::Displayed, + ::ms_time(nullptr)); } } diff --git a/src/chat/chat-room/client-group-chat-room.cpp b/src/chat/chat-room/client-group-chat-room.cpp index 5c52cc648b..f3a6995d1f 100644 --- a/src/chat/chat-room/client-group-chat-room.cpp +++ b/src/chat/chat-room/client-group-chat-room.cpp @@ -349,7 +349,8 @@ void ClientGroupChatRoomPrivate::onCallSessionStateChanged(const shared_ptr<Call // If there are chat message pending chat room creation, set state to NotDelivered and remove them from // queue. for (const auto &message : pendingCreationMessages) { - message->getPrivate()->setState(ChatMessage::State::NotDelivered); + message->getPrivate()->setParticipantState(q->getMe()->getAddress(), ChatMessage::State::NotDelivered, + ::ms_time(nullptr)); } pendingCreationMessages.clear(); if (reason == LinphoneReasonForbidden) { diff --git a/src/chat/chat-room/server-group-chat-room.cpp b/src/chat/chat-room/server-group-chat-room.cpp index 2cb00c390c..514bcce458 100644 --- a/src/chat/chat-room/server-group-chat-room.cpp +++ b/src/chat/chat-room/server-group-chat-room.cpp @@ -1070,7 +1070,7 @@ shared_ptr<CallSession> ServerGroupChatRoomPrivate::makeSession(const std::share void ServerGroupChatRoomPrivate::inviteDevice(const shared_ptr<ParticipantDevice> &device) { L_Q(); - lInfo() << q << ": Inviting device '" << device->getAddress()->toString() << "'"; + lInfo() << q << ": Inviting device '" << *device->getAddress() << "'"; shared_ptr<Participant> participant = const_pointer_cast<Participant>(device->getParticipant()->getSharedFromThis()); shared_ptr<CallSession> session = makeSession(device); @@ -1157,7 +1157,7 @@ void ServerGroupChatRoomPrivate::queueMessage(const shared_ptr<Message> &msg) { for (const auto &participant : q->getParticipants()) { for (const auto &device : participant->getDevices()) { // Queue the message for all devices except the one that sent it - if (msg->fromAddr != device->getAddress()) { + if (*msg->fromAddr != *device->getAddress()) { queueMessage(msg, device->getAddress()); } } diff --git a/src/chat/encryption/lime-x3dh-encryption-engine.cpp b/src/chat/encryption/lime-x3dh-encryption-engine.cpp index 59dcc1b874..c0da0b68c6 100644 --- a/src/chat/encryption/lime-x3dh-encryption-engine.cpp +++ b/src/chat/encryption/lime-x3dh-encryption-engine.cpp @@ -423,7 +423,8 @@ ChatMessageModifier::Result LimeX3dhEncryptionEngine::processOutgoingMessage(con } } else { lError() << "[LIME] operation failed: " << errorMessage; - message->getPrivate()->setState(ChatMessage::State::NotDelivered); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::NotDelivered, ::ms_time(nullptr)); *result = ChatMessageModifier::Result::Error; } }, diff --git a/src/chat/modifier/file-transfer-chat-message-modifier.cpp b/src/chat/modifier/file-transfer-chat-message-modifier.cpp index 199decae08..b1676d21d0 100644 --- a/src/chat/modifier/file-transfer-chat-message-modifier.cpp +++ b/src/chat/modifier/file-transfer-chat-message-modifier.cpp @@ -30,6 +30,7 @@ #include "chat/chat-message/chat-message-p.h" #include "chat/chat-room/chat-room-p.h" #include "chat/encryption/encryption-engine.h" +#include "conference/participant.h" #include "content/content-type.h" #include "content/content.h" #include "core/core.h" @@ -359,7 +360,8 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht delete currentFileTransferContent; currentFileTransferContent = nullptr; - message->getPrivate()->setState(ChatMessage::State::NotDelivered); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::NotDelivered, ::ms_time(nullptr)); releaseHttpRequest(); fileUploadEndBackgroundTask(); @@ -389,7 +391,8 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht currentFileTransferContent->setBodyFromUtf8(xml_body.c_str()); currentFileTransferContent = nullptr; - message->getPrivate()->setState(ChatMessage::State::FileTransferDone); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::FileTransferDone, ::ms_time(nullptr)); releaseHttpRequest(); message->getPrivate()->send(); fileUploadEndBackgroundTask(); @@ -399,7 +402,8 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht delete currentFileTransferContent; currentFileTransferContent = nullptr; - message->getPrivate()->setState(ChatMessage::State::NotDelivered); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::NotDelivered, ::ms_time(nullptr)); releaseHttpRequest(); fileUploadEndBackgroundTask(); } @@ -410,7 +414,8 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht delete currentFileTransferContent; currentFileTransferContent = nullptr; - message->getPrivate()->setState(ChatMessage::State::FileTransferError); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::FileTransferError, ::ms_time(nullptr)); releaseHttpRequest(); fileUploadEndBackgroundTask(); } else if (code == 401) { @@ -420,7 +425,8 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht delete currentFileTransferContent; currentFileTransferContent = nullptr; - message->getPrivate()->setState(ChatMessage::State::FileTransferError); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::FileTransferError, ::ms_time(nullptr)); releaseHttpRequest(); fileUploadEndBackgroundTask(); } else { @@ -429,7 +435,8 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht delete currentFileTransferContent; currentFileTransferContent = nullptr; - message->getPrivate()->setState(ChatMessage::State::NotDelivered); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::NotDelivered, ::ms_time(nullptr)); releaseHttpRequest(); fileUploadEndBackgroundTask(); } @@ -445,7 +452,8 @@ void FileTransferChatMessageModifier::processIoErrorUpload(BCTBX_UNUSED(const be shared_ptr<ChatMessage> message = chatMessage.lock(); lError() << "I/O Error during file upload of message [" << message << "]"; if (!message) return; - message->getPrivate()->setState(ChatMessage::State::NotDelivered); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::NotDelivered, ::ms_time(nullptr)); releaseHttpRequest(); } @@ -665,7 +673,8 @@ void FileTransferChatMessageModifier::onRecvBody(BCTBX_UNUSED(belle_sip_user_bod } } else { lWarning() << "File transfer decrypt failed with code -" << hex << (int)(-retval); - message->getPrivate()->setState(ChatMessage::State::FileTransferError); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::FileTransferError, ::ms_time(nullptr)); } } @@ -751,7 +760,8 @@ void FileTransferChatMessageModifier::onRecvEnd(BCTBX_UNUSED(belle_sip_user_body } releaseHttpRequest(); - message->getPrivate()->setState(ChatMessage::State::FileTransferDone); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::FileTransferDone, ::ms_time(nullptr)); if (message->getPrivate()->isAutoFileTransferDownloadInProgress()) { renameFileAfterAutoDownload(core, fileContent); message->getPrivate()->handleAutoDownload(); @@ -759,7 +769,8 @@ void FileTransferChatMessageModifier::onRecvEnd(BCTBX_UNUSED(belle_sip_user_body } } else { lWarning() << "File transfer decrypt failed with code " << (int)retval; - message->getPrivate()->setState(ChatMessage::State::FileTransferError); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::FileTransferError, ::ms_time(nullptr)); releaseHttpRequest(); currentFileTransferContent = nullptr; } @@ -802,7 +813,8 @@ void FileTransferChatMessageModifier::processResponseHeadersFromGetFile(const be if (code >= 400 && code < 500) { lWarning() << "File transfer failed with code " << code; - message->getPrivate()->setState(ChatMessage::State::FileTransferError); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::FileTransferDone, ::ms_time(nullptr)); releaseHttpRequest(); currentFileTransferContent = nullptr; return; @@ -864,7 +876,8 @@ void FileTransferChatMessageModifier::onDownloadFailed() { releaseHttpRequest(); message->getPrivate()->handleAutoDownload(); } else { - message->getPrivate()->setState(ChatMessage::State::FileTransferError); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::FileTransferError, ::ms_time(nullptr)); releaseHttpRequest(); currentFileTransferContent = nullptr; } @@ -983,7 +996,8 @@ bool FileTransferChatMessageModifier::downloadFile(const shared_ptr<ChatMessage> int err = startHttpTransfer(url, "GET", nullptr, &cbs); if (err == -1) return false; // start the download, status is In Progress - message->getPrivate()->setState(ChatMessage::State::FileTransferInProgress); + message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(), + ChatMessage::State::FileTransferInProgress, ::ms_time(nullptr)); return true; } diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp index b0d7f29f8b..e5b3f58888 100644 --- a/src/db/main-db.cpp +++ b/src/db/main-db.cpp @@ -1532,6 +1532,7 @@ void MainDbPrivate::updateConferenceChatMessageEvent(const shared_ptr<EventLog> const EventLogPrivate *dEventLog = eventLog->getPrivate(); MainDbKeyPrivate *dEventKey = static_cast<MainDbKey &>(dEventLog->dbKey).getPrivate(); const long long &eventId = dEventKey->storageId; + soci::session *session = dbSession.getBackendSession(); // 1. Get current chat message state and database state. const ChatMessage::State state = chatMessage->getState(); @@ -1540,8 +1541,7 @@ void MainDbPrivate::updateConferenceChatMessageEvent(const shared_ptr<EventLog> { int intState; int intMarkedAsRead; - *dbSession.getBackendSession() - << "SELECT state, marked_as_read FROM conference_chat_message_event WHERE event_id = :eventId", + *session << "SELECT state, marked_as_read FROM conference_chat_message_event WHERE event_id = :eventId", soci::into(intState), soci::into(intMarkedAsRead), soci::use(eventId); dbState = ChatMessage::State(intState); dbMarkedAsRead = intMarkedAsRead == 1; @@ -1550,7 +1550,7 @@ void MainDbPrivate::updateConferenceChatMessageEvent(const shared_ptr<EventLog> // 2. Update unread chat message count if necessary. const bool isOutgoing = chatMessage->getDirection() == ChatMessage::Direction::Outgoing; - shared_ptr<AbstractChatRoom> chatRoom(chatMessage->getChatRoom()); + shared_ptr<AbstractChatRoom> chatRoom = chatMessage->getChatRoom(); if (!isOutgoing && markedAsRead) { int *count = unreadChatMessageCountCache[chatRoom->getConferenceId()]; if (count && !dbMarkedAsRead) { @@ -1570,9 +1570,9 @@ void MainDbPrivate::updateConferenceChatMessageEvent(const shared_ptr<EventLog> ? dbState : state); const int markedAsReadInt = markedAsRead ? 1 : 0; - *dbSession.getBackendSession() << "UPDATE conference_chat_message_event SET state = :state, imdn_message_id = " - ":imdnMessageId, marked_as_read = :markedAsRead" - " WHERE event_id = :eventId", + *session << "UPDATE conference_chat_message_event SET state = :state, imdn_message_id = " + ":imdnMessageId, marked_as_read = :markedAsRead" + " WHERE event_id = :eventId", soci::use(stateInt), soci::use(imdnMessageId), soci::use(markedAsReadInt), soci::use(eventId); } @@ -1582,9 +1582,21 @@ void MainDbPrivate::updateConferenceChatMessageEvent(const shared_ptr<EventLog> insertContent(eventId, *content); // 5. Update participants. - if (isOutgoing && (state == ChatMessage::State::Delivered || state == ChatMessage::State::NotDelivered)) - for (const auto &participant : chatRoom->getParticipants()) - setChatMessageParticipantState(eventLog, participant->getAddress(), state, std::time(nullptr)); + if (isOutgoing && (state == ChatMessage::State::Delivered || state == ChatMessage::State::NotDelivered) && + (chatRoom->getCapabilities() & AbstractChatRoom::Capabilities::Conference) && chatMessage->isValid()) { + static const string query = "SELECT sip_address.value" + " FROM sip_address" + " WHERE event_id = :eventId" + " AND sip_address.id = chat_message_participant.participant_sip_address_id"; + soci::rowset<soci::row> rows = (session->prepare << query, soci::use(chatMessage->getStorageId())); + + // Use list of participants the client is sure have received the message and not the actual list of participants + // being part of the chatroom + for (const auto &row : rows) { + const auto address = Address::create(row.get<string>(0)); + setChatMessageParticipantState(eventLog, address, state, std::time(nullptr)); + } + } #endif } diff --git a/src/event-log/conference/conference-participant-event.cpp b/src/event-log/conference/conference-participant-event.cpp index 9517d84703..1e579155ff 100644 --- a/src/event-log/conference/conference-participant-event.cpp +++ b/src/event-log/conference/conference-participant-event.cpp @@ -36,7 +36,9 @@ ConferenceParticipantEvent::ConferenceParticipantEvent(Type type, : ConferenceNotifiedEvent(*new ConferenceParticipantEventPrivate, type, creationTime, conferenceId) { L_D(); L_ASSERT(type == Type::ConferenceParticipantAdded || type == Type::ConferenceParticipantRemoved || - type == Type::ConferenceParticipantSetAdmin || type == Type::ConferenceParticipantUnsetAdmin); + type == Type::ConferenceParticipantSetAdmin || type == Type::ConferenceParticipantUnsetAdmin || + type == Type::ConferenceParticipantRoleUnknown || type == Type::ConferenceParticipantRoleListener || + type == Type::ConferenceParticipantRoleSpeaker); d->participantAddress = participantAddress; } diff --git a/tester/local_chat_imdn_tester.cpp b/tester/local_chat_imdn_tester.cpp index 9dddc2d169..8afe5dab1a 100644 --- a/tester/local_chat_imdn_tester.cpp +++ b/tester/local_chat_imdn_tester.cpp @@ -24,6 +24,211 @@ namespace LinphoneTest { +static void group_chat_room_with_imdn(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 michelle2("michelle_rc", focus.getIdentity(), encrypted); + ClientConference pauline("pauline_rc", focus.getIdentity(), encrypted); + ClientConference pauline2("pauline_rc", focus.getIdentity(), encrypted); + + focus.registerAsParticipantDevice(marie); + focus.registerAsParticipantDevice(marie2); + focus.registerAsParticipantDevice(michelle); + focus.registerAsParticipantDevice(michelle2); + focus.registerAsParticipantDevice(pauline); + focus.registerAsParticipantDevice(pauline2); + + linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(marie.getLc())); + linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(marie2.getLc())); + linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(michelle.getLc())); + linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(michelle2.getLc())); + linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(pauline.getLc())); + linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(pauline2.getLc())); + + stats marie_stat = marie.getStats(); + stats marie2_stat = marie.getStats(); + stats michelle_stat = michelle.getStats(); + stats michelle2_stat = michelle2.getStats(); + stats pauline_stat = pauline.getStats(); + stats pauline2_stat = pauline2.getStats(); + bctbx_list_t *coresList = bctbx_list_append(NULL, focus.getLc()); + coresList = bctbx_list_append(coresList, marie.getLc()); + coresList = bctbx_list_append(coresList, marie2.getLc()); + coresList = bctbx_list_append(coresList, michelle.getLc()); + coresList = bctbx_list_append(coresList, michelle2.getLc()); + coresList = bctbx_list_append(coresList, pauline.getLc()); + coresList = bctbx_list_append(coresList, pauline2.getLc()); + + if (encrypted) { + BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_X3dhUserCreationSuccess, + marie_stat.number_of_X3dhUserCreationSuccess + 1, x3dhServer_creationTimeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie2.getStats().number_of_X3dhUserCreationSuccess, + marie2_stat.number_of_X3dhUserCreationSuccess + 1, + x3dhServer_creationTimeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_X3dhUserCreationSuccess, + michelle_stat.number_of_X3dhUserCreationSuccess + 1, + x3dhServer_creationTimeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle2.getStats().number_of_X3dhUserCreationSuccess, + michelle2_stat.number_of_X3dhUserCreationSuccess + 1, + x3dhServer_creationTimeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_X3dhUserCreationSuccess, + pauline_stat.number_of_X3dhUserCreationSuccess + 1, + x3dhServer_creationTimeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline2.getStats().number_of_X3dhUserCreationSuccess, + pauline2_stat.number_of_X3dhUserCreationSuccess + 1, + x3dhServer_creationTimeout)); + + BC_ASSERT_TRUE(linphone_core_lime_x3dh_enabled(marie.getLc())); + BC_ASSERT_TRUE(linphone_core_lime_x3dh_enabled(marie2.getLc())); + BC_ASSERT_TRUE(linphone_core_lime_x3dh_enabled(michelle.getLc())); + BC_ASSERT_TRUE(linphone_core_lime_x3dh_enabled(michelle2.getLc())); + BC_ASSERT_TRUE(linphone_core_lime_x3dh_enabled(pauline.getLc())); + BC_ASSERT_TRUE(linphone_core_lime_x3dh_enabled(pauline2.getLc())); + } + + bctbx_list_t *participantsAddresses = NULL; + Address michelleAddr = michelle.getIdentity(); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(michelleAddr.toC())); + Address michelle2Addr = michelle2.getIdentity(); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(michelle2Addr.toC())); + Address paulineAddr = pauline.getIdentity(); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(paulineAddr.toC())); + Address pauline2Addr = pauline2.getIdentity(); + participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_ref(pauline2Addr.toC())); + + // 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(), &marie_stat, participantsAddresses, initialSubject, 2, encrypted, + LinphoneChatRoomEphemeralModeDeviceManaged); + BC_ASSERT_PTR_NOT_NULL(marieCr); + const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marieCr); + + LinphoneChatRoom *marie2Cr = check_creation_chat_room_client_side(coresList, marie.getCMgr(), &marie_stat, + confAddr, initialSubject, 2, TRUE); + BC_ASSERT_PTR_NOT_NULL(marie2Cr); + + // 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, 2, FALSE); + BC_ASSERT_PTR_NOT_NULL(michelleCr); + LinphoneChatRoom *michelle2Cr = check_creation_chat_room_client_side( + coresList, michelle2.getCMgr(), &michelle2_stat, confAddr, initialSubject, 2, FALSE); + BC_ASSERT_PTR_NOT_NULL(michelle2Cr); + + // 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, 2, FALSE); + BC_ASSERT_PTR_NOT_NULL(paulineCr); + LinphoneChatRoom *pauline2Cr = check_creation_chat_room_client_side( + coresList, pauline2.getCMgr(), &pauline2_stat, confAddr, initialSubject, 2, FALSE); + BC_ASSERT_PTR_NOT_NULL(pauline2Cr); + + // Marie sends the message + const char *marieMessage = "Hey ! What's up ?"; + LinphoneChatMessage *msg = ClientConference::sendTextMsg(marieCr, marieMessage); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie2.getStats().number_of_LinphoneMessageReceived, + marie2_stat.number_of_LinphoneMessageReceived + 1, + liblinphone_tester_sip_timeout)); + LinphoneChatMessage *marie2LastMsg = marie2.getStats().last_received_chat_message; + BC_ASSERT_PTR_NOT_NULL(marie2LastMsg); + if (marie2LastMsg) { + LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(marie2LastMsg); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + } + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneMessageReceived, + michelle_stat.number_of_LinphoneMessageReceived + 1, + liblinphone_tester_sip_timeout)); + LinphoneChatMessage *michelleLastMsg = michelle.getStats().last_received_chat_message; + BC_ASSERT_PTR_NOT_NULL(michelleLastMsg); + if (michelleLastMsg) { + LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(michelleLastMsg); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + } + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle2.getStats().number_of_LinphoneMessageReceived, + michelle2_stat.number_of_LinphoneMessageReceived + 1, + liblinphone_tester_sip_timeout)); + LinphoneChatMessage *michelle2LastMsg = michelle2.getStats().last_received_chat_message; + BC_ASSERT_PTR_NOT_NULL(michelle2LastMsg); + if (michelle2LastMsg) { + LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(michelle2LastMsg); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + } + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_LinphoneMessageReceived, + pauline_stat.number_of_LinphoneMessageReceived + 1, + liblinphone_tester_sip_timeout)); + LinphoneChatMessage *paulineLastMsg = pauline.getStats().last_received_chat_message; + BC_ASSERT_PTR_NOT_NULL(paulineLastMsg); + if (paulineLastMsg) { + LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(paulineLastMsg); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + } + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline2.getStats().number_of_LinphoneMessageReceived, + pauline2_stat.number_of_LinphoneMessageReceived + 1, + liblinphone_tester_sip_timeout)); + LinphoneChatMessage *pauline2LastMsg = pauline2.getStats().last_received_chat_message; + BC_ASSERT_PTR_NOT_NULL(pauline2LastMsg); + if (pauline2LastMsg) { + LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(pauline2LastMsg); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + } + + linphone_chat_room_mark_as_read(michelleCr); + linphone_chat_room_mark_as_read(paulineCr); + + BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneMessageDisplayed, + marie_stat.number_of_LinphoneMessageDisplayed + 1, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &marie2.getStats().number_of_LinphoneMessageDisplayed, + marie2_stat.number_of_LinphoneMessageDisplayed + 1, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle.getStats().number_of_LinphoneMessageDisplayed, + michelle_stat.number_of_LinphoneMessageDisplayed + 1, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &michelle2.getStats().number_of_LinphoneMessageDisplayed, + michelle2_stat.number_of_LinphoneMessageDisplayed + 1, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline.getStats().number_of_LinphoneMessageDisplayed, + pauline_stat.number_of_LinphoneMessageDisplayed + 1, + liblinphone_tester_sip_timeout)); + BC_ASSERT_TRUE(wait_for_list(coresList, &pauline2.getStats().number_of_LinphoneMessageDisplayed, + pauline2_stat.number_of_LinphoneMessageDisplayed + 1, + liblinphone_tester_sip_timeout)); + + linphone_chat_message_unref(msg); + msg = nullptr; + + 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, michelle2, pauline, pauline2}).wait([&focus] { + return focus.getCore().getChatRooms().size() == 0; + })); + + // wait bit more to detect side effect if any + CoreManagerAssert({focus, marie, marie2, michelle, michelle2, pauline, pauline2}) + .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); + } +} + static void group_chat_room_with_client_idmn_after_restart_base(bool_t encrypted, bool_t add_participant, bool_t stop_core) { Focus focus("chloe_rc"); @@ -674,6 +879,7 @@ static test_t local_conference_chat_imdn_tests[] = { TEST_ONE_TAG("Secure group chat with client IMDN sent after restart and participant added", LinphoneTest::secure_group_chat_room_with_client_idmn_sent_after_restart_and_participant_added, "LeaksMemory"), /* because of network up and down */ + TEST_NO_TAG("Secure Group chat with IMDN", LinphoneTest::group_chat_room_with_imdn), TEST_ONE_TAG( "Secure group chat with client IMDN sent after restart and participant added and core stopped before sending " "IMDN", diff --git a/tester/local_chat_tester.cpp b/tester/local_chat_tester.cpp index 5ff42f5e41..2a1ff017b0 100644 --- a/tester/local_chat_tester.cpp +++ b/tester/local_chat_tester.cpp @@ -1653,7 +1653,6 @@ static void group_chat_room_with_creator_without_groupchat_capability(void) { 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); @@ -2624,12 +2623,8 @@ static test_t local_conference_chat_tests[] = { }; static test_t local_conference_chat_error_tests[] = { - TEST_ONE_TAG("Group chat with INVITE session error", - LinphoneTest::group_chat_room_with_invite_error, - "LeaksMemory"), /* because of network up and down */ - TEST_ONE_TAG("Group chat with SUBSCRIBE session error", - LinphoneTest::group_chat_room_with_subscribe_error, - "LeaksMemory"), /* because of network up and down */ + TEST_NO_TAG("Group chat with INVITE session error", LinphoneTest::group_chat_room_with_invite_error), + TEST_NO_TAG("Group chat with SUBSCRIBE session error", LinphoneTest::group_chat_room_with_subscribe_error), TEST_NO_TAG("Group chat Add participant with invalid address", LinphoneTest::group_chat_room_add_participant_with_invalid_address), TEST_NO_TAG("Group chat Only participant with invalid address", diff --git a/tester/local_conference_tester_functions.cpp b/tester/local_conference_tester_functions.cpp index 76e2b21864..7cb2bcbf74 100644 --- a/tester/local_conference_tester_functions.cpp +++ b/tester/local_conference_tester_functions.cpp @@ -617,11 +617,15 @@ void group_chat_room_with_client_restart_base(bool encrypted) { static void chat_room_participant_added_sip_error(LinphoneChatRoom *cr, BCTBX_UNUSED(const LinphoneEventLog *event_log)) { - if (bctbx_list_size(linphone_chat_room_get_participants(cr)) == 2) { + bctbx_list_t *participants = linphone_chat_room_get_participants(cr); + if (bctbx_list_size(participants) == 2) { LinphoneCoreManager *initiator = (LinphoneCoreManager *)linphone_chat_room_get_user_data(cr); ms_message("Turning off network for core %s", linphone_core_get_identity(initiator->lc)); linphone_core_set_network_reachable(initiator->lc, FALSE); } + if (participants) { + bctbx_list_free_with_data(participants, (bctbx_list_free_func)linphone_participant_unref); + } } static void @@ -717,6 +721,22 @@ void group_chat_room_with_sip_errors_base(bool invite_error, bool subscribe_erro initialMarieStats = marie.getStats(); initialBertheStats = berthe.getStats(); + std::list<LinphoneCoreManager *> shutdownNetworkClients; + std::list<stats> initialStatsList; + if (invite_error) { + shutdownNetworkClients.push_back(michelle2.getCMgr()); + initialStatsList.push_back(michelle2.getStats()); + shutdownNetworkClients.push_back(berthe.getCMgr()); + initialStatsList.push_back(berthe.getStats()); + } else if (subscribe_error) { + shutdownNetworkClients.push_back(marie.getCMgr()); + initialStatsList.push_back(marie.getStats()); + shutdownNetworkClients.push_back(michelle2.getCMgr()); + initialStatsList.push_back(michelle2.getStats()); + shutdownNetworkClients.push_back(berthe.getCMgr()); + initialStatsList.push_back(berthe.getStats()); + } + focus.registerAsParticipantDevice(marie); focus.registerAsParticipantDevice(michelle); focus.registerAsParticipantDevice(michelle2); @@ -727,6 +747,7 @@ void group_chat_room_with_sip_errors_base(bool invite_error, bool subscribe_erro LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get()); linphone_core_cbs_set_chat_room_state_changed(cbs, server_core_chat_room_state_changed_sip_error); linphone_core_add_callbacks(focus.getLc(), cbs); + linphone_core_cbs_unref(cbs); } linphone_im_notif_policy_enable_all(linphone_core_get_im_notif_policy(marie.getLc())); @@ -758,6 +779,7 @@ void group_chat_room_with_sip_errors_base(bool invite_error, bool subscribe_erro LinphoneChatRoom *marieCr = linphone_core_create_chat_room_2(marie.getLc(), params, initialSubject, participantsAddresses); linphone_chat_room_params_unref(params); + if (marieCr) linphone_chat_room_unref(marieCr); BC_ASSERT_TRUE(CoreManagerAssert({focus, marie, pauline, michelle, michelle2, laure, berthe}).wait([&focus] { return focus.getCore().getChatRooms().size() == 1; @@ -767,43 +789,18 @@ void group_chat_room_with_sip_errors_base(bool invite_error, bool subscribe_erro linphone_chat_room_set_user_data(L_GET_C_BACK_PTR(chatRoom), marie.getCMgr()); } - if (invite_error) { - BC_ASSERT_TRUE(wait_for_list(coresList, &michelle2.getStats().number_of_LinphoneConferenceStateCreated, - initialMichelle2Stats.number_of_LinphoneConferenceStateCreated + 1, - liblinphone_tester_sip_timeout)); - ms_message("Disabling network of core %s (contact %s)", linphone_core_get_identity(michelle2.getLc()), - linphone_address_as_string(linphone_proxy_config_get_contact( - linphone_core_get_default_proxy_config(michelle2.getLc())))); - linphone_core_set_network_reachable(michelle2.getLc(), FALSE); - BC_ASSERT_TRUE(wait_for_list(coresList, &berthe.getStats().number_of_LinphoneConferenceStateCreated, - initialBertheStats.number_of_LinphoneConferenceStateCreated + 1, + for (const auto &client : shutdownNetworkClients) { + stats &initialStats = initialStatsList.front(); + BC_ASSERT_TRUE(wait_for_list(coresList, &client->stat.number_of_LinphoneConferenceStateCreated, + initialStats.number_of_LinphoneConferenceStateCreated + 1, liblinphone_tester_sip_timeout)); - ms_message("Disabling network of core %s (contact %s)", linphone_core_get_identity(berthe.getLc()), - linphone_address_as_string( - linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(berthe.getLc())))); - linphone_core_set_network_reachable(berthe.getLc(), FALSE); - } else if (subscribe_error) { - BC_ASSERT_TRUE(wait_for_list(coresList, &marie.getStats().number_of_LinphoneSubscriptionActive, - initialMarieStats.number_of_LinphoneSubscriptionActive + 1, - liblinphone_tester_sip_timeout)); - ms_message("Disabling network of core %s (contact %s)", linphone_core_get_identity(marie.getLc()), - linphone_address_as_string( - linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(marie.getLc())))); - linphone_core_set_network_reachable(marie.getLc(), FALSE); - BC_ASSERT_TRUE(wait_for_list(coresList, &michelle2.getStats().number_of_LinphoneSubscriptionActive, - initialMichelle2Stats.number_of_LinphoneSubscriptionActive + 1, - liblinphone_tester_sip_timeout)); - ms_message("Disabling network of core %s (contact %s)", linphone_core_get_identity(michelle2.getLc()), - linphone_address_as_string(linphone_proxy_config_get_contact( - linphone_core_get_default_proxy_config(michelle2.getLc())))); - linphone_core_set_network_reachable(michelle2.getLc(), FALSE); - BC_ASSERT_TRUE(wait_for_list(coresList, &berthe.getStats().number_of_LinphoneSubscriptionActive, - initialBertheStats.number_of_LinphoneSubscriptionActive + 1, - liblinphone_tester_sip_timeout)); - ms_message("Disabling network of core %s (contact %s)", linphone_core_get_identity(berthe.getLc()), - linphone_address_as_string( - linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(berthe.getLc())))); - linphone_core_set_network_reachable(berthe.getLc(), FALSE); + char *proxy_contact_str = linphone_address_as_string( + linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(client->lc))); + ms_message("Disabling network of core %s (contact %s)", linphone_core_get_identity(client->lc), + proxy_contact_str); + ms_free(proxy_contact_str); + linphone_core_set_network_reachable(client->lc, FALSE); + initialStatsList.pop_front(); } BC_ASSERT_TRUE(wait_for_list(coresList, &focus.getStats().number_of_participants_added, @@ -862,9 +859,11 @@ void group_chat_room_with_sip_errors_base(bool invite_error, bool subscribe_erro initialMichelleStats.number_of_LinphoneMessageDisplayed + 1, 3000)); if (invite_error || subscribe_error) { + char *marie_proxy_contact_str = linphone_address_as_string( + linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(marie.getLc()))); ms_message("Enabling network of core %s (contact %s)", linphone_core_get_identity(marie.getLc()), - linphone_address_as_string( - linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(marie.getLc())))); + marie_proxy_contact_str); + ms_free(marie_proxy_contact_str); 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, @@ -934,10 +933,11 @@ void group_chat_room_with_sip_errors_base(bool invite_error, bool subscribe_erro initialLaureStats.number_of_LinphoneMessageDisplayed + 1, 3000)); if (invite_error || subscribe_error) { + char *michelle2_proxy_contact_str = linphone_address_as_string( + linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(michelle2.getLc()))); ms_message("Enabling network of core %s (contact %s)", linphone_core_get_identity(michelle2.getLc()), - linphone_address_as_string(linphone_proxy_config_get_contact( - linphone_core_get_default_proxy_config(michelle2.getLc())))); - + michelle2_proxy_contact_str); + ms_free(michelle2_proxy_contact_str); linphone_core_set_network_reachable(michelle2.getLc(), TRUE); BC_ASSERT_TRUE(wait_for_list(coresList, &michelle2.getStats().number_of_LinphoneRegistrationOk, initialMichelle2Stats.number_of_LinphoneRegistrationOk + 1, @@ -945,9 +945,11 @@ void group_chat_room_with_sip_errors_base(bool invite_error, bool subscribe_erro michelle2Cr = check_creation_chat_room_client_side(coresList, michelle2.getCMgr(), &initialMichelle2Stats, confAddr, initialSubject, 4, FALSE); + char *berthe_proxy_contact_str = linphone_address_as_string( + linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(berthe.getLc()))); ms_message("Enabling network of core %s (contact %s)", linphone_core_get_identity(berthe.getLc()), - linphone_address_as_string( - linphone_proxy_config_get_contact(linphone_core_get_default_proxy_config(berthe.getLc())))); + berthe_proxy_contact_str); + ms_free(berthe_proxy_contact_str); linphone_core_set_network_reachable(berthe.getLc(), TRUE); BC_ASSERT_TRUE(wait_for_list(coresList, &berthe.getStats().number_of_LinphoneRegistrationOk, initialBertheStats.number_of_LinphoneRegistrationOk + 1, diff --git a/tester/local_secure_chat_tester.cpp b/tester/local_secure_chat_tester.cpp index 50821523be..1e17d89792 100644 --- a/tester/local_secure_chat_tester.cpp +++ b/tester/local_secure_chat_tester.cpp @@ -362,12 +362,9 @@ static test_t local_conference_secure_chat_tests[] = { TEST_ONE_TAG("Secure Group chat with client restart", LinphoneTest::secure_group_chat_room_with_client_restart, "LeaksMemory"), /* beacause of coreMgr restart*/ - TEST_ONE_TAG("Secure group chat with INVITE session error", - LinphoneTest::secure_group_chat_room_with_invite_error, - "LeaksMemory"), /* because of network up and down */ - TEST_ONE_TAG("Secure group chat with SUBSCRIBE session error", - LinphoneTest::secure_group_chat_room_with_subscribe_error, - "LeaksMemory"), /* because of network up and down */ + TEST_NO_TAG("Secure group chat with INVITE session error", LinphoneTest::secure_group_chat_room_with_invite_error), + TEST_NO_TAG("Secure group chat with SUBSCRIBE session error", + LinphoneTest::secure_group_chat_room_with_subscribe_error), TEST_ONE_TAG("Secure group chat with chat room deleted before server restart", LinphoneTest::secure_group_chat_room_with_chat_room_deleted_before_server_restart, "LeaksMemory"), /* because of network up and down */ diff --git a/tester/message_tester.c b/tester/message_tester.c index 5fa3cad617..786b5bf2d2 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -1685,11 +1685,11 @@ static void message_with_voice_recording_base(bool_t create_message_from_recorde BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneMessageDelivered, 1)); BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageReceived, 1)); - BC_ASSERT_PTR_NOT_NULL(marie->stat.last_received_chat_message); + LinphoneChatMessage *marie_msg = marie->stat.last_received_chat_message; + BC_ASSERT_PTR_NOT_NULL(marie_msg); - if (marie->stat.last_received_chat_message != NULL) { - LinphoneContent *content = - (LinphoneContent *)(linphone_chat_message_get_contents(marie->stat.last_received_chat_message)->data); + if (marie_msg != NULL) { + LinphoneContent *content = (LinphoneContent *)(linphone_chat_message_get_contents(marie_msg)->data); BC_ASSERT_EQUAL(linphone_content_get_file_duration(content), duration, int, "%d"); BC_ASSERT_STRING_EQUAL(linphone_content_get_type(content), "audio"); BC_ASSERT_STRING_EQUAL(linphone_content_get_subtype(content), "wav"); -- GitLab