diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index f2b56c39f29d2fa84dcbc4385e2e216586eacf31..d4da2af8692145445748705dab8c95baa065427f 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 92b706f94f051fdaeed74c73f81dcd1e1061f205..24d881219f6d9e3da043f3d42ba9f89073a7a986 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 51a2a3dfc4ff553cc4ea15850eaa5dff3de13b48..a8f66f7e944ae51ae5e1f04058cd2928f66da09a 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 d13f92711d1c54dcadffbb6719179a85c5dd5023..28b519649e66584c034d1caef1ab3cabed7fd315 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 fd566c4b6355a8221586be544ac04e146760d29c..88db4eb22759b99a723011dd3286506fbe525c7f 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 5c52cc648b71fca100997c3eb74a8a12ee97cf40..f3a6995d1f8fb914c8b0cdd71a940570ebefc2f0 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 2cb00c390c0d421ac1dbaafa8d527746348cec60..514bcce45888214bc16c8583fa8de2a93edb861f 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 59dcc1b874c21b9eb66503b421b821dfdc946d10..c0da0b68c67f69d05d47c12147e556c5899fff5b 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 199decae08c5bde245e365ad7dbdbcf51dc257f0..b1676d21d0f316efb7af39341bb80a44ae567427 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 b0d7f29f8b126ef3287cb69d89950ff87fc9bdc4..e5b3f58888ba8876dcc77b5d6641477f47f02d51 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 9517d84703572641f3890c3e64032a65723e35a4..1e579155ffc8eb9c0103823e804591f099721fe0 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 9dddc2d16955f829dcdfae440604e91ad4290c97..8afe5dab1a44c0d83bcdf74ba9f82dff26db1977 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 5ff42f5e411ae6b1e9b3831a38539732550150fb..2a1ff017b0eee4719888e332cc4498729872010d 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 76e2b2186411da9563ce557027ed9be857ebceba..7cb2bcbf74478fc4962eaa4327503b6e9386cfcf 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 50821523be219a1e2017ecc70ae90974a8a900f0..1e17d89792df643a714eb4b8f84abeff45a787bd 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 5fa3cad6179eafe1578f9bc39ecc4f229046e996..786b5bf2d22bd9127efbaae7045b442c1b75c6eb 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");