diff --git a/src/lib/core/BUILD.gn b/src/lib/core/BUILD.gn index 906b8a4988aee0..89f85d5015b3df 100644 --- a/src/lib/core/BUILD.gn +++ b/src/lib/core/BUILD.gn @@ -71,6 +71,7 @@ buildconfig_header("chip_buildconfig") { "CHIP_CONFIG_TLV_VALIDATE_CHAR_STRING_ON_READ=${chip_tlv_validate_char_string_on_read}", "CHIP_CONFIG_COMMAND_SENDER_BUILTIN_SUPPORT_FOR_BATCHED_COMMANDS=${chip_enable_sending_batch_commands}", "CHIP_CONFIG_TEST_GOOGLETEST=${chip_build_tests_googletest}", + "CHIP_CONFIG_MRP_ANALYTICS_ENABLED=${chip_enable_mrp_analytics}", ] visibility = [ ":chip_config_header" ] diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 8731bc4b3019a7..221f9b3751bcf6 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1867,6 +1867,20 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_TEST_GOOGLETEST 0 #endif // CHIP_CONFIG_TEST_GOOGLETEST +/** + * @def CHIP_CONFIG_MRP_ANALYTICS_ENABLED + * + * @brief + * Enables code for collecting and sending analytic related events for MRP + * + * The purpose of this macro is to prevent compiling code related to MRP analytics + * for devices that are not interested interested to save on flash. + */ + +#ifndef CHIP_CONFIG_MRP_ANALYTICS_ENABLED +#define CHIP_CONFIG_MRP_ANALYTICS_ENABLED 0 +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED + /** * @} */ diff --git a/src/lib/core/core.gni b/src/lib/core/core.gni index 6dd71cf64f5a57..1f41ed8e6f053e 100644 --- a/src/lib/core/core.gni +++ b/src/lib/core/core.gni @@ -126,6 +126,10 @@ declare_args() { chip_enable_sending_batch_commands = current_os == "linux" || current_os == "mac" || current_os == "ios" || current_os == "android" + + chip_enable_mrp_analytics = + current_os == "linux" || current_os == "android" || current_os == "mac" || + current_os == "ios" } if (chip_target_style == "") { diff --git a/src/messaging/BUILD.gn b/src/messaging/BUILD.gn index a170d625111a06..8e94a2b0d0b5e7 100644 --- a/src/messaging/BUILD.gn +++ b/src/messaging/BUILD.gn @@ -63,6 +63,7 @@ static_library("messaging") { "ExchangeMgr.cpp", "ExchangeMgr.h", "Flags.h", + "ReliableMessageAnalyticsDelegate.h", "ReliableMessageContext.cpp", "ReliableMessageContext.h", "ReliableMessageMgr.cpp", diff --git a/src/messaging/ReliableMessageAnalyticsDelegate.h b/src/messaging/ReliableMessageAnalyticsDelegate.h new file mode 100644 index 00000000000000..4eeea4f23b7e5f --- /dev/null +++ b/src/messaging/ReliableMessageAnalyticsDelegate.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file defines an interface for objects interested in MRP events for analytics + */ + +#pragma once + +#include +#include + +namespace chip { +namespace Messaging { + +class ReliableMessageAnalyticsDelegate +{ +public: + virtual ~ReliableMessageAnalyticsDelegate() = default; + + enum class SessionType + { + kEstablishedCase, + // Initially, we are starting with only one session type, but we are considering the future when we expand to allow + // other session types, such as establishing a CASE session. + }; + + enum class EventType + { + // Event associated with first time this specific message is sent. + kInitialSend, + // Event associated with re-transmitting a message that was previously sent but not acknowledged. + kRetransmission, + // Event associated with receiving an acknowledgement of a previously sent message. + kAcknowledged, + // Event associated with transmission of a message that failed to be acknowledged. + kFailed, + }; + + struct TransmitEvent + { + // When the session has a peer node ID, this will be a value other than kUndefinedNodeId. + NodeId nodeId = kUndefinedNodeId; + // When the session has a fabric index, this will be a value other than kUndefinedFabricIndex. + FabricIndex fabricIndex = kUndefinedFabricIndex; + // Session type of session the message involved is being sent on. + SessionType sessionType = SessionType::kEstablishedCase; + // The transmit event type. + EventType eventType = EventType::kInitialSend; + // The outgoing message counter associated with the event. If there is no outgoing message counter + // this value will be 0. + uint32_t messageCounter = 0; + }; + + virtual void OnTransmitEvent(const TransmitEvent & event) = 0; +}; + +} // namespace Messaging +} // namespace chip diff --git a/src/messaging/ReliableMessageMgr.cpp b/src/messaging/ReliableMessageMgr.cpp index f6af9df2627307..9a2bb31777c7e6 100644 --- a/src/messaging/ReliableMessageMgr.cpp +++ b/src/messaging/ReliableMessageMgr.cpp @@ -101,6 +101,35 @@ void ReliableMessageMgr::TicklessDebugDumpRetransTable(const char * log) #endif } +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED +void ReliableMessageMgr::NotifyMessageSendAnalytics(const RetransTableEntry & entry, const SessionHandle & sessionHandle, + const ReliableMessageAnalyticsDelegate::EventType & eventType) +{ + // For now we only support sending analytics for messages being sent over an established CASE session. + if (!mAnalyticsDelegate || !sessionHandle->IsSecureSession()) + { + return; + } + + auto secureSession = sessionHandle->AsSecureSession(); + if (!secureSession->IsCASESession()) + { + return; + } + + uint32_t messageCounter = entry.retainedBuf.GetMessageCounter(); + auto fabricIndex = sessionHandle->GetFabricIndex(); + auto destination = secureSession->GetPeerNodeId(); + ReliableMessageAnalyticsDelegate::TransmitEvent event = { .nodeId = destination, + .fabricIndex = fabricIndex, + .sessionType = + ReliableMessageAnalyticsDelegate::SessionType::kEstablishedCase, + .eventType = eventType, + .messageCounter = messageCounter }; + mAnalyticsDelegate->OnTransmitEvent(event); +} +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED + void ReliableMessageMgr::ExecuteActions() { System::Clock::Timestamp now = System::SystemClock().GetMonotonicTimestamp(); @@ -155,6 +184,10 @@ void ReliableMessageMgr::ExecuteActions() Transport::GetSessionTypeString(session), fabricIndex, ChipLogValueX64(destination), CHIP_CONFIG_RMP_DEFAULT_MAX_RETRANS); +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED + NotifyMessageSendAnalytics(*entry, session, ReliableMessageAnalyticsDelegate::EventType::kFailed); +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED + // If the exchange is expecting a response, it will handle sending // this notification once it detects that it has not gotten a // response. Otherwise, we need to do it. @@ -286,6 +319,9 @@ System::Clock::Timeout ReliableMessageMgr::GetBackoff(System::Clock::Timeout bas void ReliableMessageMgr::StartRetransmision(RetransTableEntry * entry) { CalculateNextRetransTime(*entry); +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED + NotifyMessageSendAnalytics(*entry, entry->ec->GetSessionHandle(), ReliableMessageAnalyticsDelegate::EventType::kInitialSend); +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED StartTimer(); } @@ -295,6 +331,11 @@ bool ReliableMessageMgr::CheckAndRemRetransTable(ReliableMessageContext * rc, ui mRetransTable.ForEachActiveObject([&](auto * entry) { if (entry->ec->GetReliableMessageContext() == rc && entry->retainedBuf.GetMessageCounter() == ackMessageCounter) { +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED + auto session = entry->ec->GetSessionHandle(); + NotifyMessageSendAnalytics(*entry, session, ReliableMessageAnalyticsDelegate::EventType::kAcknowledged); +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED + // Clear the entry from the retransmision table. ClearRetransTable(*entry); @@ -334,6 +375,10 @@ CHIP_ERROR ReliableMessageMgr::SendFromRetransTable(RetransTableEntry * entry) #if CHIP_CONFIG_ENABLE_ICD_SERVER app::ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); #endif // CHIP_CONFIG_ENABLE_ICD_SERVER +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED + NotifyMessageSendAnalytics(*entry, entry->ec->GetSessionHandle(), + ReliableMessageAnalyticsDelegate::EventType::kRetransmission); +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED #if CHIP_CONFIG_RESOLVE_PEER_ON_FIRST_TRANSMIT_FAILURE const ExchangeManager * exchangeMgr = entry->ec->GetExchangeMgr(); // TODO: investigate why in ReliableMessageMgr::CheckResendApplicationMessageWithPeerExchange unit test released exchange @@ -440,6 +485,13 @@ void ReliableMessageMgr::RegisterSessionUpdateDelegate(SessionUpdateDelegate * s mSessionUpdateDelegate = sessionUpdateDelegate; } +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED +void ReliableMessageMgr::RegisterAnalyticsDelegate(ReliableMessageAnalyticsDelegate * analyticsDelegate) +{ + mAnalyticsDelegate = analyticsDelegate; +} +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED + CHIP_ERROR ReliableMessageMgr::MapSendError(CHIP_ERROR error, uint16_t exchangeId, bool isInitiator) { if ( diff --git a/src/messaging/ReliableMessageMgr.h b/src/messaging/ReliableMessageMgr.h index 5036b832108443..9c27186032bb3c 100644 --- a/src/messaging/ReliableMessageMgr.h +++ b/src/messaging/ReliableMessageMgr.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -185,6 +186,15 @@ class ReliableMessageMgr */ void RegisterSessionUpdateDelegate(SessionUpdateDelegate * sessionUpdateDelegate); +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED + /** + * Registers a delegate interested in analytic information + * + * @param[in] analyticsDelegate - Pointer to delegate for reporting analytic + */ + void RegisterAnalyticsDelegate(ReliableMessageAnalyticsDelegate * analyticsDelegate); +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED + /** * Map a send error code to the error code we should actually use for * success checks. This maps some error codes to CHIP_NO_ERROR as @@ -245,10 +255,18 @@ class ReliableMessageMgr void TicklessDebugDumpRetransTable(const char * log); +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED + void NotifyMessageSendAnalytics(const RetransTableEntry & entry, const SessionHandle & sessionHandle, + const ReliableMessageAnalyticsDelegate::EventType & eventType); +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED + // ReliableMessageProtocol Global tables for timer context ObjectPool mRetransTable; SessionUpdateDelegate * mSessionUpdateDelegate = nullptr; +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED + ReliableMessageAnalyticsDelegate * mAnalyticsDelegate = nullptr; +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED static System::Clock::Timeout sAdditionalMRPBackoffTime; }; diff --git a/src/messaging/tests/TestReliableMessageProtocol.cpp b/src/messaging/tests/TestReliableMessageProtocol.cpp index 6390e4eca1920e..f30e763c50cd1b 100644 --- a/src/messaging/tests/TestReliableMessageProtocol.cpp +++ b/src/messaging/tests/TestReliableMessageProtocol.cpp @@ -21,6 +21,8 @@ * This file implements unit tests for the ReliableMessageProtocol * implementation. */ +#include + #include #include @@ -61,6 +63,13 @@ using namespace chip::System::Clock::Literals; const char PAYLOAD[] = "Hello!"; +class TestReliablityAnalyticDelegate : public ReliableMessageAnalyticsDelegate +{ +public: + virtual void OnTransmitEvent(const TransmitEvent & event) override { mTransmitEvents.push(event); } + std::queue mTransmitEvents; +}; + class TestReliableMessageProtocol : public chip::Test::LoopbackMessagingContext { public: @@ -2043,6 +2052,364 @@ TEST_F(TestReliableMessageProtocol, CheckApplicationResponseNeverComes) EXPECT_EQ(err, CHIP_NO_ERROR); } +#if CHIP_CONFIG_MRP_ANALYTICS_ENABLED +TEST_F(TestReliableMessageProtocol, CheckReliableMessageAnalyticsForTransmitEventualSuccessForEsablishedCase) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + // Make sure we are using CASE sessions, because there is no defunct-marking for PASE. + ExpireSessionBobToAlice(); + ExpireSessionAliceToBob(); + err = CreateCASESessionBobToAlice(); + EXPECT_EQ(err, CHIP_NO_ERROR); + err = CreateCASESessionAliceToBob(); + EXPECT_EQ(err, CHIP_NO_ERROR); + + chip::System::PacketBufferHandle buffer = chip::MessagePacketBuffer::NewWithData(PAYLOAD, sizeof(PAYLOAD)); + EXPECT_FALSE(buffer.IsNull()); + + MockAppDelegate mockSender(*this); + ExchangeContext * exchange = NewExchangeToAlice(&mockSender); + ASSERT_NE(exchange, nullptr); + + ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); + ASSERT_NE(rm, nullptr); + TestReliablityAnalyticDelegate testAnalyticsDelegate; + rm->RegisterAnalyticsDelegate(&testAnalyticsDelegate); + + exchange->GetSessionHandle()->AsSecureSession()->SetRemoteSessionParameters(ReliableMessageProtocolConfig({ + 30_ms32, // CHIP_CONFIG_MRP_LOCAL_IDLE_RETRY_INTERVAL + 30_ms32, // CHIP_CONFIG_MRP_LOCAL_ACTIVE_RETRY_INTERVAL + })); + + const auto expectedFabricIndex = exchange->GetSessionHandle()->GetFabricIndex(); + const auto expectedNodeId = exchange->GetSessionHandle()->AsSecureSession()->GetPeerNodeId(); + + // Let's drop the initial message + auto & loopback = GetLoopback(); + loopback.mSentMessageCount = 0; + loopback.mNumMessagesToDrop = 4; + loopback.mDroppedMessageCount = 0; + + // Ensure the retransmit table is empty right now + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); + + err = exchange->SendMessage(Echo::MsgType::EchoRequest, std::move(buffer)); + EXPECT_EQ(err, CHIP_NO_ERROR); + DrainAndServiceIO(); + + // Ensure the initial message was dropped and was added to retransmit table + EXPECT_EQ(loopback.mNumMessagesToDrop, 3u); + EXPECT_EQ(loopback.mDroppedMessageCount, 1u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 1); + + // Wait for the initial message to fail (should take 330-413ms) + GetIOContext().DriveIOUntil(1000_ms32, [&] { return loopback.mSentMessageCount >= 2; }); + DrainAndServiceIO(); + + // Ensure the 1st retry was dropped, and is still there in the retransmit table + EXPECT_EQ(loopback.mSentMessageCount, 2u); + EXPECT_EQ(loopback.mNumMessagesToDrop, 2u); + EXPECT_EQ(loopback.mDroppedMessageCount, 2u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 1); + + GetIOContext().DriveIOUntil(1000_ms32, [&] { return loopback.mSentMessageCount >= 3; }); + DrainAndServiceIO(); + + // Ensure the 2nd retry was dropped, and is still there in the retransmit table + EXPECT_EQ(loopback.mSentMessageCount, 3u); + EXPECT_EQ(loopback.mNumMessagesToDrop, 1u); + EXPECT_EQ(loopback.mDroppedMessageCount, 3u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 1); + + GetIOContext().DriveIOUntil(1000_ms32, [&] { return loopback.mSentMessageCount >= 4; }); + DrainAndServiceIO(); + + // Ensure the 3rd retry was dropped, and is still there in the retransmit table + EXPECT_EQ(loopback.mSentMessageCount, 4u); + EXPECT_EQ(loopback.mNumMessagesToDrop, 0u); + EXPECT_EQ(loopback.mDroppedMessageCount, 4u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 1); + + // Trigger final transmission + GetIOContext().DriveIOUntil(1500_ms32, [&] { return loopback.mSentMessageCount >= 5; }); + DrainAndServiceIO(); + + // Ensure the last retransmission was NOT dropped, and the retransmit table is empty, as we should have gotten an ack + EXPECT_GE(loopback.mSentMessageCount, 5u); + EXPECT_EQ(loopback.mDroppedMessageCount, 4u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); + + ASSERT_EQ(testAnalyticsDelegate.mTransmitEvents.size(), 6u); + auto firstTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(firstTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(firstTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(firstTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kInitialSend); + // We have no way of validating the first messageCounter since this is a randomly generated value, but it should + // remain constant for all subsequent transmit events in this test. + const uint32_t messageCounter = firstTransmitEvent.messageCounter; + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto secondTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(secondTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(secondTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(secondTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kRetransmission); + EXPECT_EQ(messageCounter, secondTransmitEvent.messageCounter); + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto thirdTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(thirdTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(thirdTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(thirdTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kRetransmission); + EXPECT_EQ(messageCounter, thirdTransmitEvent.messageCounter); + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto forthTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(forthTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(forthTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(forthTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kRetransmission); + EXPECT_EQ(messageCounter, forthTransmitEvent.messageCounter); + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto fifthTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(fifthTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(fifthTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(fifthTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kRetransmission); + EXPECT_EQ(messageCounter, fifthTransmitEvent.messageCounter); + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto sixthTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(sixthTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(sixthTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(sixthTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kAcknowledged); + EXPECT_EQ(messageCounter, sixthTransmitEvent.messageCounter); +} + +TEST_F(TestReliableMessageProtocol, CheckReliableMessageAnalyticsForTransmitFailureForEsablishedCase) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + // Make sure we are using CASE sessions, because there is no defunct-marking for PASE. + ExpireSessionBobToAlice(); + ExpireSessionAliceToBob(); + err = CreateCASESessionBobToAlice(); + EXPECT_EQ(err, CHIP_NO_ERROR); + err = CreateCASESessionAliceToBob(); + EXPECT_EQ(err, CHIP_NO_ERROR); + + chip::System::PacketBufferHandle buffer = chip::MessagePacketBuffer::NewWithData(PAYLOAD, sizeof(PAYLOAD)); + EXPECT_FALSE(buffer.IsNull()); + + MockAppDelegate mockSender(*this); + ExchangeContext * exchange = NewExchangeToAlice(&mockSender); + ASSERT_NE(exchange, nullptr); + + ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); + ASSERT_NE(rm, nullptr); + TestReliablityAnalyticDelegate testAnalyticsDelegate; + rm->RegisterAnalyticsDelegate(&testAnalyticsDelegate); + + exchange->GetSessionHandle()->AsSecureSession()->SetRemoteSessionParameters(ReliableMessageProtocolConfig({ + 30_ms32, // CHIP_CONFIG_MRP_LOCAL_IDLE_RETRY_INTERVAL + 30_ms32, // CHIP_CONFIG_MRP_LOCAL_ACTIVE_RETRY_INTERVAL + })); + + const auto expectedFabricIndex = exchange->GetSessionHandle()->GetFabricIndex(); + const auto expectedNodeId = exchange->GetSessionHandle()->AsSecureSession()->GetPeerNodeId(); + + // Let's drop the initial message + auto & loopback = GetLoopback(); + loopback.mSentMessageCount = 0; + loopback.mNumMessagesToDrop = 5; + loopback.mDroppedMessageCount = 0; + + // Ensure the retransmit table is empty right now + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); + + err = exchange->SendMessage(Echo::MsgType::EchoRequest, std::move(buffer)); + EXPECT_EQ(err, CHIP_NO_ERROR); + DrainAndServiceIO(); + + // Ensure the initial message was dropped and was added to retransmit table + EXPECT_EQ(loopback.mNumMessagesToDrop, 4u); + EXPECT_EQ(loopback.mDroppedMessageCount, 1u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 1); + + // Wait for the initial message to fail (should take 330-413ms) + GetIOContext().DriveIOUntil(1000_ms32, [&] { return loopback.mSentMessageCount >= 2; }); + DrainAndServiceIO(); + + // Ensure the 1st retry was dropped, and is still there in the retransmit table + EXPECT_EQ(loopback.mSentMessageCount, 2u); + EXPECT_EQ(loopback.mNumMessagesToDrop, 3u); + EXPECT_EQ(loopback.mDroppedMessageCount, 2u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 1); + + GetIOContext().DriveIOUntil(1000_ms32, [&] { return loopback.mSentMessageCount >= 3; }); + DrainAndServiceIO(); + + // Ensure the 2nd retry was dropped, and is still there in the retransmit table + EXPECT_EQ(loopback.mSentMessageCount, 3u); + EXPECT_EQ(loopback.mNumMessagesToDrop, 2u); + EXPECT_EQ(loopback.mDroppedMessageCount, 3u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 1); + + GetIOContext().DriveIOUntil(1000_ms32, [&] { return loopback.mSentMessageCount >= 4; }); + DrainAndServiceIO(); + + // Ensure the 3rd retry was dropped, and is still there in the retransmit table + EXPECT_EQ(loopback.mSentMessageCount, 4u); + EXPECT_EQ(loopback.mNumMessagesToDrop, 1u); + EXPECT_EQ(loopback.mDroppedMessageCount, 4u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 1); + + // Trigger final transmission + GetIOContext().DriveIOUntil(1500_ms32, [&] { return loopback.mSentMessageCount >= 5; }); + DrainAndServiceIO(); + + // Ensure the last retransmission was NOT dropped, and the retransmit table is empty, as we should have gotten an ack + EXPECT_GE(loopback.mSentMessageCount, 5u); + EXPECT_EQ(rm->TestGetCountRetransTable(), 1); + EXPECT_EQ(loopback.mDroppedMessageCount, 5u); + + // Now wait for our exchange to time out. + GetIOContext().DriveIOUntil(3000_ms32, [&] { return rm->TestGetCountRetransTable() == 0; }); + DrainAndServiceIO(); + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); + + ASSERT_EQ(testAnalyticsDelegate.mTransmitEvents.size(), 6u); + auto firstTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(firstTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(firstTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(firstTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kInitialSend); + // We have no way of validating the first messageCounter since this is a randomly generated value, but it should + // remain constant for all subsequent transmit events in this test. + const uint32_t messageCounter = firstTransmitEvent.messageCounter; + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto secondTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(secondTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(secondTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(secondTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kRetransmission); + EXPECT_EQ(messageCounter, secondTransmitEvent.messageCounter); + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto thirdTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(thirdTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(thirdTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(thirdTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kRetransmission); + EXPECT_EQ(messageCounter, thirdTransmitEvent.messageCounter); + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto forthTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(forthTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(forthTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(forthTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kRetransmission); + EXPECT_EQ(messageCounter, forthTransmitEvent.messageCounter); + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto fifthTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(fifthTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(fifthTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(fifthTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kRetransmission); + EXPECT_EQ(messageCounter, fifthTransmitEvent.messageCounter); + + testAnalyticsDelegate.mTransmitEvents.pop(); + auto sixthTransmitEvent = testAnalyticsDelegate.mTransmitEvents.front(); + EXPECT_EQ(sixthTransmitEvent.nodeId, expectedNodeId); + EXPECT_EQ(sixthTransmitEvent.fabricIndex, expectedFabricIndex); + EXPECT_EQ(sixthTransmitEvent.eventType, ReliableMessageAnalyticsDelegate::EventType::kFailed); + EXPECT_EQ(messageCounter, sixthTransmitEvent.messageCounter); +} + +TEST_F(TestReliableMessageProtocol, CheckReliableMessageAnalyticsForTransmitEsablishedPase) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + chip::System::PacketBufferHandle buffer = chip::MessagePacketBuffer::NewWithData(PAYLOAD, sizeof(PAYLOAD)); + EXPECT_FALSE(buffer.IsNull()); + + MockAppDelegate mockSender(*this); + ExchangeContext * exchange = NewExchangeToAlice(&mockSender); + ASSERT_NE(exchange, nullptr); + + ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); + ASSERT_NE(rm, nullptr); + + TestReliablityAnalyticDelegate testAnalyticsDelegate; + rm->RegisterAnalyticsDelegate(&testAnalyticsDelegate); + + exchange->GetSessionHandle()->AsSecureSession()->SetRemoteSessionParameters(ReliableMessageProtocolConfig({ + 30_ms32, // CHIP_CONFIG_MRP_LOCAL_IDLE_RETRY_INTERVAL + 30_ms32, // CHIP_CONFIG_MRP_LOCAL_ACTIVE_RETRY_INTERVAL + })); + + ASSERT_TRUE(exchange->GetSessionHandle()->AsSecureSession()->IsPASESession()); + + auto & loopback = GetLoopback(); + loopback.mSentMessageCount = 0; + loopback.mNumMessagesToDrop = 0; + loopback.mDroppedMessageCount = 0; + + // Ensure the retransmit table is empty right now + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); + + err = exchange->SendMessage(Echo::MsgType::EchoRequest, std::move(buffer)); + EXPECT_EQ(err, CHIP_NO_ERROR); + DrainAndServiceIO(); + + // Test that the message was actually sent (and not dropped) + EXPECT_EQ(loopback.mSentMessageCount, 2u); + EXPECT_EQ(loopback.mDroppedMessageCount, 0u); + + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); + + ASSERT_EQ(testAnalyticsDelegate.mTransmitEvents.size(), 0u); +} + +TEST_F(TestReliableMessageProtocol, CheckReliableMessageAnalyticsForTransmitUnauthenticatedExchange) +{ + chip::System::PacketBufferHandle buffer = chip::MessagePacketBuffer::NewWithData(PAYLOAD, sizeof(PAYLOAD)); + EXPECT_FALSE(buffer.IsNull()); + + MockSessionEstablishmentDelegate mockReceiver; + CHIP_ERROR err = GetExchangeManager().RegisterUnsolicitedMessageHandlerForType(Echo::MsgType::EchoRequest, &mockReceiver); + EXPECT_EQ(err, CHIP_NO_ERROR); + + MockSessionEstablishmentDelegate mockSender; + ExchangeContext * exchange = NewUnauthenticatedExchangeToAlice(&mockSender); + ASSERT_NE(exchange, nullptr); + + ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr(); + ASSERT_NE(rm, nullptr); + + TestReliablityAnalyticDelegate testAnalyticsDelegate; + rm->RegisterAnalyticsDelegate(&testAnalyticsDelegate); + + exchange->GetSessionHandle()->AsUnauthenticatedSession()->SetRemoteSessionParameters(ReliableMessageProtocolConfig({ + 64_ms32, // CHIP_CONFIG_MRP_LOCAL_IDLE_RETRY_INTERVAL + 64_ms32, // CHIP_CONFIG_MRP_LOCAL_ACTIVE_RETRY_INTERVAL + })); + + auto & loopback = GetLoopback(); + loopback.mSentMessageCount = 0; + loopback.mNumMessagesToDrop = 0; + loopback.mDroppedMessageCount = 0; + + // Ensure the retransmit table is empty right now + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); + + err = exchange->SendMessage(Echo::MsgType::EchoRequest, std::move(buffer)); + EXPECT_EQ(err, CHIP_NO_ERROR); + DrainAndServiceIO(); + + // Test that the message was actually sent (and not dropped) + EXPECT_EQ(loopback.mSentMessageCount, 2u); + EXPECT_EQ(loopback.mDroppedMessageCount, 0u); + + EXPECT_EQ(rm->TestGetCountRetransTable(), 0); + + ASSERT_EQ(testAnalyticsDelegate.mTransmitEvents.size(), 0u); +} +#endif // CHIP_CONFIG_MRP_ANALYTICS_ENABLED + /** * TODO: A test that we should have but can't write with the existing * infrastructure we have: