From 2bf7270295348d89ac2edc32bb7e6d56574855c3 Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Wed, 19 Feb 2025 05:20:25 -0800 Subject: [PATCH 1/9] Do not pass cluster ID to CommissionerControlServer (#37644) --- .../linux/include/CommissionerControlDelegate.h | 2 +- .../fabric-sync/bridge/include/CommissionerControlDelegate.h | 2 +- .../commissioner-control-server.cpp | 4 ++-- .../commissioner-control-server/commissioner-control-server.h | 3 +-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/fabric-bridge-app/linux/include/CommissionerControlDelegate.h b/examples/fabric-bridge-app/linux/include/CommissionerControlDelegate.h index a39f49066882ee..746fb9157057d8 100644 --- a/examples/fabric-bridge-app/linux/include/CommissionerControlDelegate.h +++ b/examples/fabric-bridge-app/linux/include/CommissionerControlDelegate.h @@ -31,7 +31,7 @@ inline constexpr EndpointId kAggregatorEndpointId = 1; class CommissionerControlDelegate : public Delegate { public: - CommissionerControlDelegate() : mCommissionerControlServer(this, kAggregatorEndpointId, CommissionerControl::Id) {} + CommissionerControlDelegate() : mCommissionerControlServer(this, kAggregatorEndpointId) {} CHIP_ERROR HandleCommissioningApprovalRequest(const CommissioningApprovalRequest & request) override; // TODO(#35627) clientNodeId should move towards ScopedNodeId. diff --git a/examples/fabric-sync/bridge/include/CommissionerControlDelegate.h b/examples/fabric-sync/bridge/include/CommissionerControlDelegate.h index 9dc4fac37423ae..95473935d40e6a 100644 --- a/examples/fabric-sync/bridge/include/CommissionerControlDelegate.h +++ b/examples/fabric-sync/bridge/include/CommissionerControlDelegate.h @@ -33,7 +33,7 @@ class CommissionerControlDelegate : public Delegate { public: CommissionerControlDelegate(bridge::FabricAdminDelegate * fabricAdmin) : - mFabricAdmin(fabricAdmin), mCommissionerControlServer(this, kAggregatorEndpointId, CommissionerControl::Id) + mFabricAdmin(fabricAdmin), mCommissionerControlServer(this, kAggregatorEndpointId) {} CHIP_ERROR HandleCommissioningApprovalRequest(const CommissioningApprovalRequest & request) override; diff --git a/src/app/clusters/commissioner-control-server/commissioner-control-server.cpp b/src/app/clusters/commissioner-control-server/commissioner-control-server.cpp index 9a0e91a2d0a3bd..36852514cb6a48 100644 --- a/src/app/clusters/commissioner-control-server/commissioner-control-server.cpp +++ b/src/app/clusters/commissioner-control-server/commissioner-control-server.cpp @@ -69,8 +69,8 @@ namespace app { namespace Clusters { namespace CommissionerControl { -CommissionerControlServer::CommissionerControlServer(Delegate * delegate, EndpointId endpointId, ClusterId clusterId) : - CommandHandlerInterface(MakeOptional(endpointId), clusterId) +CommissionerControlServer::CommissionerControlServer(Delegate * delegate, EndpointId endpointId) : + CommandHandlerInterface(MakeOptional(endpointId), Id) { mDelegate = delegate; } diff --git a/src/app/clusters/commissioner-control-server/commissioner-control-server.h b/src/app/clusters/commissioner-control-server/commissioner-control-server.h index 2319a71cc950a2..2fa73b102e6e4b 100644 --- a/src/app/clusters/commissioner-control-server/commissioner-control-server.h +++ b/src/app/clusters/commissioner-control-server/commissioner-control-server.h @@ -110,9 +110,8 @@ class CommissionerControlServer : public CommandHandlerInterface * @param delegate A pointer to the delegate to be used by this server. * Note: the caller must ensure that the delegate lives throughout the instance's lifetime. * @param endpointId The endpoint on which this cluster exists. This must match the zap configuration. - * @param clusterId The ID of the Microwave Oven Control cluster to be instantiated. */ - CommissionerControlServer(Delegate * delegate, EndpointId endpointId, ClusterId clusterId); + CommissionerControlServer(Delegate * delegate, EndpointId endpointId); ~CommissionerControlServer() override; From 00c6f4ee91b79da9905eb827b5ae7e3018e34783 Mon Sep 17 00:00:00 2001 From: Adrian Gielniewski Date: Wed, 19 Feb 2025 14:35:42 +0100 Subject: [PATCH 2/9] Add ICDConfigurationData to app sources when ICD management server cluster is included (#37653) * Fix ICD management server cluster dependecies Add ICDConfigurationData to app sources when ICD management server cluster is included in sample, but ICD support is disabled, e.g. lock-app on some platforms. Related to: #32321 Signed-off-by: Adrian Gielniewski * Add TODO comments for #32321 Add TODO comments for issue related to ICD management server. Signed-off-by: Adrian Gielniewski --------- Signed-off-by: Adrian Gielniewski --- src/app/chip_data_model.cmake | 10 ++++++++++ .../icd-management-server/icd-management-server.cpp | 1 + 2 files changed, 11 insertions(+) diff --git a/src/app/chip_data_model.cmake b/src/app/chip_data_model.cmake index b05fccc1cb1c01..d48c4c43f5db95 100644 --- a/src/app/chip_data_model.cmake +++ b/src/app/chip_data_model.cmake @@ -24,6 +24,16 @@ include("${CHIP_ROOT}/src/data-model-providers/codegen/model.cmake") function(chip_configure_cluster APP_TARGET CLUSTER) file(GLOB CLUSTER_SOURCES "${CHIP_APP_BASE_DIR}/clusters/${CLUSTER}/*.cpp") target_sources(${APP_TARGET} PRIVATE ${CLUSTER_SOURCES}) + + # Add clusters dependencies + if (CLUSTER STREQUAL "icd-management-server") + # TODO(#32321): Remove after issue is resolved + # Add ICDConfigurationData when ICD management server cluster is included, + # but ICD support is disabled, e.g. lock-app on some platforms + if(NOT CONFIG_CHIP_ENABLE_ICD_SUPPORT) + target_sources(${APP_TARGET} PRIVATE ${CHIP_APP_BASE_DIR}/icd/server/ICDConfigurationData.cpp) + endif() + endif() endfunction() # diff --git a/src/app/clusters/icd-management-server/icd-management-server.cpp b/src/app/clusters/icd-management-server/icd-management-server.cpp index cec4fbd54859ec..f99da627f1caf0 100644 --- a/src/app/clusters/icd-management-server/icd-management-server.cpp +++ b/src/app/clusters/icd-management-server/icd-management-server.cpp @@ -466,6 +466,7 @@ bool emberAfIcdManagementClusterUnregisterClientCallback(CommandHandler * comman bool emberAfIcdManagementClusterStayActiveRequestCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const Commands::StayActiveRequest::DecodableType & commandData) { +// TODO(#32321): Remove #if after issue is resolved // Note: We only need this #if statement for platform examples that enable the ICD management server without building the sample // as an ICD. Since this is not spec compliant, we should remove this #if statement once we stop compiling the ICD management // server in those examples. From 86e0b85b1a652a9cac20298a81488299d9997355 Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Wed, 19 Feb 2025 10:29:49 -0500 Subject: [PATCH 3/9] Adding MRP analytics delegate (#37439) This is a starting point for MRP events to be sent to some sort of delegate interested. The design is intentionally done in this way to reduce code size within the SDK and is meant for applications such as a controller to registers a delegate for MRP events allowing for it to construct analytics. --------- Co-authored-by: Restyled.io Co-authored-by: Boris Zbarsky --- src/lib/core/BUILD.gn | 1 + src/lib/core/CHIPConfig.h | 14 + src/lib/core/core.gni | 4 + src/messaging/BUILD.gn | 1 + .../ReliableMessageAnalyticsDelegate.h | 74 ++++ src/messaging/ReliableMessageMgr.cpp | 52 +++ src/messaging/ReliableMessageMgr.h | 18 + .../tests/TestReliableMessageProtocol.cpp | 367 ++++++++++++++++++ 8 files changed, 531 insertions(+) create mode 100644 src/messaging/ReliableMessageAnalyticsDelegate.h 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: From 38cc24bd33f738ef02e2a81d67046325098d2349 Mon Sep 17 00:00:00 2001 From: Adrian Gielniewski Date: Wed, 19 Feb 2025 17:19:45 +0100 Subject: [PATCH 4/9] Build time optimization (#37433) * Move some classes out of af-types.h Move AttributesChangedListener and MarkAttributeDirty out of af-types.h Signed-off-by: Adrian Gielniewski * Remove dependency to cluster-objects.h Signed-off-by: Adrian Gielniewski * Remove cluster-objects from chip_data_model.cmake Signed-off-by: Adrian Gielniewski * [Chef] Fix includes Fix missing includes after changes in Accessors.h Signed-off-by: Adrian Gielniewski * [Infineon] Fix includes Fix missing includes after changes in Accessors.h Signed-off-by: Adrian Gielniewski * [Silabs] Fix includes Fix missing includes after changes in Accessors.h Signed-off-by: Adrian Gielniewski --------- Signed-off-by: Adrian Gielniewski --- .../chef-dishwasher-mode-delegate-impl.cpp | 1 + .../chef/common/chef-rvc-mode-delegate.cpp | 1 + .../infineon/cyw30739/src/ZclCallbacks.cpp | 1 + .../infineon/cyw30739/src/ZclCallbacks.cpp | 1 + .../infineon/cyw30739/src/ZclCallbacks.cpp | 1 + .../cyw30739/src/TemperatureManager.cpp | 1 + .../silabs/src/TemperatureManager.cpp | 1 + src/app/chip_data_model.cmake | 8 ----- src/app/util/AttributesChangedListener.h | 35 +++++++++++++++++++ src/app/util/BUILD.gn | 6 +++- src/app/util/MarkAttributeDirty.h | 33 +++++++++++++++++ src/app/util/af-types.h | 29 ++------------- src/app/util/attribute-table.h | 3 +- .../app/attributes/Accessors-src.zapt | 1 + .../templates/app/attributes/Accessors.zapt | 7 ++-- .../zap-generated/attributes/Accessors.cpp | 1 + .../zap-generated/attributes/Accessors.h | 7 ++-- 17 files changed, 92 insertions(+), 45 deletions(-) create mode 100644 src/app/util/AttributesChangedListener.h create mode 100644 src/app/util/MarkAttributeDirty.h diff --git a/examples/chef/common/chef-dishwasher-mode-delegate-impl.cpp b/examples/chef/common/chef-dishwasher-mode-delegate-impl.cpp index b5206ef407dab6..397f13ad2b21a4 100644 --- a/examples/chef/common/chef-dishwasher-mode-delegate-impl.cpp +++ b/examples/chef/common/chef-dishwasher-mode-delegate-impl.cpp @@ -17,6 +17,7 @@ */ #include #include +#include using namespace chip; using namespace chip::app; diff --git a/examples/chef/common/chef-rvc-mode-delegate.cpp b/examples/chef/common/chef-rvc-mode-delegate.cpp index 9caf2ab94fd835..e5444e03737c32 100644 --- a/examples/chef/common/chef-rvc-mode-delegate.cpp +++ b/examples/chef/common/chef-rvc-mode-delegate.cpp @@ -17,6 +17,7 @@ */ #include #include +#include using namespace chip; using namespace chip::app; diff --git a/examples/light-switch-app/infineon/cyw30739/src/ZclCallbacks.cpp b/examples/light-switch-app/infineon/cyw30739/src/ZclCallbacks.cpp index bf7e0e9b2b35a7..089fe0ec633b94 100644 --- a/examples/light-switch-app/infineon/cyw30739/src/ZclCallbacks.cpp +++ b/examples/light-switch-app/infineon/cyw30739/src/ZclCallbacks.cpp @@ -17,6 +17,7 @@ */ #include +#include #include #include diff --git a/examples/lighting-app/infineon/cyw30739/src/ZclCallbacks.cpp b/examples/lighting-app/infineon/cyw30739/src/ZclCallbacks.cpp index 0b00eaec57687a..f643f244f869d8 100644 --- a/examples/lighting-app/infineon/cyw30739/src/ZclCallbacks.cpp +++ b/examples/lighting-app/infineon/cyw30739/src/ZclCallbacks.cpp @@ -19,6 +19,7 @@ #include "LightingManager.h" #include +#include #include #include diff --git a/examples/lock-app/infineon/cyw30739/src/ZclCallbacks.cpp b/examples/lock-app/infineon/cyw30739/src/ZclCallbacks.cpp index b8f581c21c9ead..272445fb30d635 100644 --- a/examples/lock-app/infineon/cyw30739/src/ZclCallbacks.cpp +++ b/examples/lock-app/infineon/cyw30739/src/ZclCallbacks.cpp @@ -19,6 +19,7 @@ #include "LockManager.h" #include +#include #include #include #include diff --git a/examples/thermostat/infineon/cyw30739/src/TemperatureManager.cpp b/examples/thermostat/infineon/cyw30739/src/TemperatureManager.cpp index 57d0e02d3d777c..a4b52d3873353f 100644 --- a/examples/thermostat/infineon/cyw30739/src/TemperatureManager.cpp +++ b/examples/thermostat/infineon/cyw30739/src/TemperatureManager.cpp @@ -24,6 +24,7 @@ #include "AppTask.h" #include "hdc2010.h" #include "platform/CHIPDeviceLayer.h" +#include /********************************************************** * Defines and Constants diff --git a/examples/thermostat/silabs/src/TemperatureManager.cpp b/examples/thermostat/silabs/src/TemperatureManager.cpp index d919d71ac12c7e..a16e4c3d63be58 100644 --- a/examples/thermostat/silabs/src/TemperatureManager.cpp +++ b/examples/thermostat/silabs/src/TemperatureManager.cpp @@ -25,6 +25,7 @@ #include "AppConfig.h" #include "AppEvent.h" #include "AppTask.h" +#include /********************************************************** * Defines and Constants diff --git a/src/app/chip_data_model.cmake b/src/app/chip_data_model.cmake index d48c4c43f5db95..33e3db23ca60b2 100644 --- a/src/app/chip_data_model.cmake +++ b/src/app/chip_data_model.cmake @@ -129,14 +129,6 @@ function(chip_configure_data_model APP_TARGET) set(APP_GEN_FILES) endif() - # This is: - # //src/app/common:cluster-objects - # - # TODO: ideally we would avoid duplication and would link gn-built items - target_sources(${APP_TARGET} ${SCOPE} - ${CHIP_APP_BASE_DIR}/../../zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp - ) - chip_zapgen(${APP_TARGET}-zapgen INPUT "${ARG_ZAP_FILE}" GENERATOR "app-templates" diff --git a/src/app/util/AttributesChangedListener.h b/src/app/util/AttributesChangedListener.h new file mode 100644 index 00000000000000..bd839dd99e15d1 --- /dev/null +++ b/src/app/util/AttributesChangedListener.h @@ -0,0 +1,35 @@ +/** + * + * Copyright (c) 2025 Project CHIP Authors + * + * 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. + */ +#pragma once + +#include + +namespace chip { +namespace app { + +/// Notification object of a specific path being changed +class AttributesChangedListener +{ +public: + virtual ~AttributesChangedListener() = default; + + /// Called when the set of attributes identified by AttributePathParams (which may contain wildcards) is to be considered dirty. + virtual void MarkDirty(const AttributePathParams & path) = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/util/BUILD.gn b/src/app/util/BUILD.gn index a31a28e565d9bb..d9ccc66655c3f4 100644 --- a/src/app/util/BUILD.gn +++ b/src/app/util/BUILD.gn @@ -53,7 +53,11 @@ source_set("types") { # This source set also depends on data-model source_set("af-types") { - sources = [ "af-types.h" ] + sources = [ + "AttributesChangedListener.h", + "MarkAttributeDirty.h", + "af-types.h", + ] deps = [ ":types", "${chip_root}/src/app:paths", diff --git a/src/app/util/MarkAttributeDirty.h b/src/app/util/MarkAttributeDirty.h new file mode 100644 index 00000000000000..d54fdedebfe787 --- /dev/null +++ b/src/app/util/MarkAttributeDirty.h @@ -0,0 +1,33 @@ +/** + * + * Copyright (c) 2025 Project CHIP Authors + * + * 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. + */ +#pragma once + +namespace chip { +namespace app { + +enum class MarkAttributeDirty +{ + kIfChanged, + kNo, + // kYes might need to be used if the attribute value was previously changed + // without reporting, and now is being set in a situation where we know + // reporting needs to be triggered (e.g. because QuieterReportingAttribute + // indicated that). + kYes, +}; +} +} // namespace chip diff --git a/src/app/util/af-types.h b/src/app/util/af-types.h index 010993f34785e2..5da5aeb7f5b703 100644 --- a/src/app/util/af-types.h +++ b/src/app/util/af-types.h @@ -26,6 +26,8 @@ #include // For bool #include // For various uint*_t types +#include +#include #include #include // For various types. @@ -280,30 +282,3 @@ typedef chip::Protocols::InteractionModel::Status (*EmberAfClusterPreAttributeCh #define MAX_INT16U_VALUE (0xFFFF) /** @} END addtogroup */ - -namespace chip { -namespace app { - -enum class MarkAttributeDirty -{ - kIfChanged, - kNo, - // kYes might need to be used if the attribute value was previously changed - // without reporting, and now is being set in a situation where we know - // reporting needs to be triggered (e.g. because QuieterReportingAttribute - // indicated that). - kYes, -}; - -/// Notification object of a specific path being changed -class AttributesChangedListener -{ -public: - virtual ~AttributesChangedListener() = default; - - /// Called when the set of attributes identified by AttributePathParams (which may contain wildcards) is to be considered dirty. - virtual void MarkDirty(const AttributePathParams & path) = 0; -}; - -} // namespace app -} // namespace chip diff --git a/src/app/util/attribute-table.h b/src/app/util/attribute-table.h index afa8bf88012f54..a60cde418b8427 100644 --- a/src/app/util/attribute-table.h +++ b/src/app/util/attribute-table.h @@ -18,7 +18,8 @@ #pragma once #include -#include +#include +#include #include #include #include diff --git a/src/app/zap-templates/templates/app/attributes/Accessors-src.zapt b/src/app/zap-templates/templates/app/attributes/Accessors-src.zapt index 7af9a625964690..da97c81c341fdd 100644 --- a/src/app/zap-templates/templates/app/attributes/Accessors-src.zapt +++ b/src/app/zap-templates/templates/app/attributes/Accessors-src.zapt @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/src/app/zap-templates/templates/app/attributes/Accessors.zapt b/src/app/zap-templates/templates/app/attributes/Accessors.zapt index 52e17cdf83a015..5092079754ad37 100644 --- a/src/app/zap-templates/templates/app/attributes/Accessors.zapt +++ b/src/app/zap-templates/templates/app/attributes/Accessors.zapt @@ -7,11 +7,10 @@ #pragma once +#include #include -#include -#include -#include -#include +#include +#include #include namespace chip { diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp index 611c529d924549..ad2100e2e80110 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h index 058caa58bda64b..3afe2b6276614c 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h @@ -24,11 +24,10 @@ #pragma once -#include +#include #include -#include -#include -#include +#include +#include #include namespace chip { From 1bd8e405cb62f1752456e70bbf3d467edf0b4ade Mon Sep 17 00:00:00 2001 From: Jeff Tung <100387939+jtung-apple@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:55:36 -0800 Subject: [PATCH 5/9] [Darwin] MTRDevice _delegateExists cannot be called without holding lock (#37662) --- src/darwin/Framework/CHIP/MTRDevice.mm | 6 ++++++ src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm | 2 +- src/darwin/Framework/CHIP/MTRDevice_Internal.h | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 7633d03ebd2d3b..1d09453c321d21 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -253,6 +253,12 @@ - (void)invalidate [self _cancelAllAttributeValueWaiters]; } +- (BOOL)delegateExists +{ + std::lock_guard lock(_lock); + return [self _delegateExists]; +} + - (BOOL)_delegateExists { os_unfair_lock_assert_owner(&self->_lock); diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm index 4871b1b129b699..97f447f1f81ac0 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm @@ -79,7 +79,7 @@ - (void)_updateRegistrationInfo for (NSNumber * nodeID in [self.nodeIDToDeviceMap keyEnumerator]) { MTRDevice * device = [self _deviceForNodeID:nodeID createIfNeeded:NO]; - if ([device _delegateExists]) { + if ([device delegateExists]) { NSMutableDictionary * nodeDictionary = [NSMutableDictionary dictionary]; MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationNodeIDKey, nodeID, nodeDictionary) diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h index 1887ddc3cb27b6..c7c4191b71b0d9 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h @@ -146,8 +146,12 @@ MTR_DIRECT_MEMBERS // Returns YES if any non-null delegates were found - (BOOL)_iterateDelegatesWithBlock:(void(NS_NOESCAPE ^ _Nullable)(MTRDeviceDelegateInfo * delegateInfo))block; +// For subclasses to call while holding lock - (BOOL)_delegateExists; +// For device controller or other objects to call +- (BOOL)delegateExists; + // Must be called by subclasses or MTRDevice implementation only. - (void)_delegateAdded:(id)delegate; - (void)_delegateRemoved:(id)delegate; From 0551d0a1cbc334ddeb8601c97ccb3e4515e2b2a8 Mon Sep 17 00:00:00 2001 From: Jeff Tung <100387939+jtung-apple@users.noreply.github.com> Date: Wed, 19 Feb 2025 10:05:48 -0800 Subject: [PATCH 6/9] [Darwin] MTRDeviceController_XPC _updateRegistrationInfo double lock fix (#37664) * [Darwin] MTRDeviceController_XPC _updateRegistrationInfo double lock fix * Make sure device map is accessed safely with lock --- .../Framework/CHIP/MTRDeviceController_XPC.mm | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm index 97f447f1f81ac0..4b7dc4e1de0fe8 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm @@ -72,24 +72,28 @@ @implementation MTRDeviceController_XPC - (void)_updateRegistrationInfo { - NSMutableDictionary * registrationInfo = [NSMutableDictionary dictionary]; + dispatch_async(self.workQueue, ^{ + std::lock_guard lock(*self.deviceMapLock); - NSMutableDictionary * controllerContext = [NSMutableDictionary dictionary]; - NSMutableArray * nodeIDs = [NSMutableArray array]; + NSMutableDictionary * registrationInfo = [NSMutableDictionary dictionary]; - for (NSNumber * nodeID in [self.nodeIDToDeviceMap keyEnumerator]) { - MTRDevice * device = [self _deviceForNodeID:nodeID createIfNeeded:NO]; - if ([device delegateExists]) { - NSMutableDictionary * nodeDictionary = [NSMutableDictionary dictionary]; - MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationNodeIDKey, nodeID, nodeDictionary) + NSMutableDictionary * controllerContext = [NSMutableDictionary dictionary]; + NSMutableArray * nodeIDs = [NSMutableArray array]; - [nodeIDs addObject:nodeDictionary]; + for (NSNumber * nodeID in [self.nodeIDToDeviceMap keyEnumerator]) { + MTRDevice * device = self.nodeIDToDeviceMap[nodeID]; + if ([device delegateExists]) { + NSMutableDictionary * nodeDictionary = [NSMutableDictionary dictionary]; + MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationNodeIDKey, nodeID, nodeDictionary) + + [nodeIDs addObject:nodeDictionary]; + } } - } - MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationNodeIDsKey, nodeIDs, registrationInfo) - MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationControllerContextKey, controllerContext, registrationInfo) + MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationNodeIDsKey, nodeIDs, registrationInfo) + MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationControllerContextKey, controllerContext, registrationInfo) - [self updateControllerConfiguration:registrationInfo]; + [self updateControllerConfiguration:registrationInfo]; + }); } - (void)_registerNodeID:(NSNumber *)nodeID From 0b67ce2f37e3c82823bb72ccec0dc576525835df Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Wed, 19 Feb 2025 19:39:41 +0100 Subject: [PATCH 7/9] Dft xpc2 (#36719) * [darwin-framework-tool][XPC] Add a XPC server registry. * [darwin-framework-tool][XPC] Add a XPC server for MTRDeviceControllerServerProtocol. * [darwin-framework-tool][XPC] Add a XPC server for MTRXPCServerProtocol --- examples/darwin-framework-tool/BUILD.gn | 3 + .../commands/common/CHIPCommandBridge.h | 2 + .../commands/common/CHIPCommandBridge.mm | 43 ++- .../common/xpc/DeviceControllerServer.mm | 349 ++++++++++++++++++ .../commands/common/xpc/XPCServer.mm | 259 +++++++++++++ .../commands/common/xpc/XPCServerProtocols.h | 44 +++ .../commands/common/xpc/XPCServerRegistry.h | 33 ++ .../commands/common/xpc/XPCServerRegistry.mm | 113 ++++++ .../Matter.xcodeproj/project.pbxproj | 28 ++ 9 files changed, 867 insertions(+), 7 deletions(-) create mode 100644 examples/darwin-framework-tool/commands/common/xpc/DeviceControllerServer.mm create mode 100644 examples/darwin-framework-tool/commands/common/xpc/XPCServer.mm create mode 100644 examples/darwin-framework-tool/commands/common/xpc/XPCServerProtocols.h create mode 100644 examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.h create mode 100644 examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.mm diff --git a/examples/darwin-framework-tool/BUILD.gn b/examples/darwin-framework-tool/BUILD.gn index e0224cc6865fbe..7532ed8dd99247 100644 --- a/examples/darwin-framework-tool/BUILD.gn +++ b/examples/darwin-framework-tool/BUILD.gn @@ -218,6 +218,9 @@ executable("darwin-framework-tool") { "commands/common/PreferencesStorage.mm", "commands/common/RemoteDataModelLogger.h", "commands/common/RemoteDataModelLogger.mm", + "commands/common/xpc/DeviceControllerServer.mm", + "commands/common/xpc/XPCServer.mm", + "commands/common/xpc/XPCServerRegistry.mm", "commands/configuration/Commands.h", "commands/configuration/ResetMRPParametersCommand.h", "commands/configuration/ResetMRPParametersCommand.mm", diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h index 7f9f3f9b6efc2c..e1cc97e20e8123 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h @@ -52,6 +52,7 @@ class CHIPCommandBridge : public Command { AddArgument("commissioner-vendor-id", 0, UINT16_MAX, &mCommissionerVendorId, "The vendor id to use for darwin-framework-tool. If not provided, chip::VendorId::TestVendor1 (65521, 0xFFF1) will be " "used."); + AddArgument("use-xpc", &mUseXPC, "Use a controller that will connect to an XPC endpoint instead of talking to devices directly. If a string argument is provided, it must identify a Mach service name that can be used to connect to a remote endpoint. If no argument is provided, a local endpoint will be used."); } /////////// Command Interface ///////// @@ -168,4 +169,5 @@ class CHIPCommandBridge : public Command { chip::Optional mPaaTrustStorePath; chip::Optional mCommissionerVendorId; std::string mCurrentIdentity; + chip::Optional> mUseXPC; }; diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm index 0eb7d60a89eb24..d4154de024a988 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm @@ -31,6 +31,8 @@ #import "DeviceDelegate.h" #include "MTRError_Utils.h" +#include "xpc/XPCServerRegistry.h" + #include #include @@ -45,6 +47,17 @@ bool CHIPCommandBridge::sUseSharedStorage = true; constexpr char kTrustStorePathVariable[] = "PAA_TRUST_STORE_PATH"; +namespace { +NSString * ToNSString(const chip::Optional> & string) +{ + if (!string.HasValue() && string.Value().IsNull()) { + return nil; + } + + return @(string.Value().Value()); +} +} + CHIP_ERROR CHIPCommandBridge::Run() { // In interactive mode, we want to avoid memory accumulating in the main autorelease pool, @@ -143,6 +156,8 @@ productAttestationAuthorityCertificates = nil; } + [[XPCServerRegistry sharedInstance] start]; + sUseSharedStorage = mCommissionerSharedStorage.ValueOr(false); if (sUseSharedStorage) { return SetUpStackWithSharedStorage(productAttestationAuthorityCertificates); @@ -202,7 +217,13 @@ params.productAttestationAuthorityCertificates = productAttestationAuthorityCertificates; - __auto_type * controller = [[MTRDeviceController alloc] initWithParameters:params error:&error]; + MTRDeviceController * controller = nil; + if (mUseXPC.HasValue()) { + __auto_type * identifier = uuidString; + controller = [[XPCServerRegistry sharedInstance] createController:identifier serviceName:ToNSString(mUseXPC) params:params error:&error]; + } else { + controller = [[MTRDeviceController alloc] initWithParameters:params error:&error]; + } VerifyOrReturnError(nil != controller, MTRErrorToCHIPErrorCode(error), ChipLogError(chipTool, "Controller startup failure: %@", error)); mControllers[identities[i]] = controller; } @@ -237,12 +258,18 @@ params.nodeId = @(mCommissionerNodeId.Value()); } - // We're not sure whether we're creating a new fabric or using an existing one, so just try both. - auto controller = [factory createControllerOnExistingFabric:params error:&error]; - if (controller == nil) { - // Maybe we didn't have this fabric yet. - params.vendorID = @(mCommissionerVendorId.ValueOr(chip::VendorId::TestVendor1)); - controller = [factory createControllerOnNewFabric:params error:&error]; + MTRDeviceController * controller = nil; + if (mUseXPC.HasValue()) { + __auto_type * identifier = @(identities[i]); + controller = [[XPCServerRegistry sharedInstance] createController:identifier serviceName:ToNSString(mUseXPC) params:params error:&error]; + } else { + // We're not sure whether we're creating a new fabric or using an existing one, so just try both. + controller = [factory createControllerOnExistingFabric:params error:&error]; + if (controller == nil) { + // Maybe we didn't have this fabric yet. + params.vendorID = @(mCommissionerVendorId.ValueOr(chip::VendorId::TestVendor1)); + controller = [factory createControllerOnNewFabric:params error:&error]; + } } VerifyOrReturnError(nil != controller, MTRErrorToCHIPErrorCode(error), ChipLogError(chipTool, "Controller startup failure: %@", error)); mControllers[identities[i]] = controller; @@ -256,6 +283,8 @@ if (IsInteractive()) { return; } + + [[XPCServerRegistry sharedInstance] stop]; ShutdownCommissioner(); } diff --git a/examples/darwin-framework-tool/commands/common/xpc/DeviceControllerServer.mm b/examples/darwin-framework-tool/commands/common/xpc/DeviceControllerServer.mm new file mode 100644 index 00000000000000..7b0f9ac99c1e79 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/DeviceControllerServer.mm @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2024 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. + * + */ + +#import "XPCServerProtocols.h" + +#import + +@interface DeviceControllerXPCServerImpl : NSObject +@property (nonatomic, strong) id clientProxy; +@property (nonatomic, strong) NSArray * controllers; +@property (nonatomic, strong) dispatch_queue_t callbackQueue; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithClientProxy:(id)proxy controllers:(NSArray *)controllers; +@end + +@implementation DeviceControllerXPCServerImpl +- (instancetype)initWithClientProxy:(id)proxy controllers:(NSArray *)controllers +{ + if ([super init]) { + _clientProxy = proxy; + _controllers = controllers; + _callbackQueue = dispatch_queue_create("com.chip.xpc.command", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + } + return self; +} + +- (MTRBaseDevice *)createDevice:(id)uniqueIdentifierString nodeID:(uint64_t)nodeID +{ + if (![uniqueIdentifierString isKindOfClass:[NSString class]]) { + ChipLogError(chipTool, "The controller identifier should be a NSString."); + return nil; + } + + __auto_type * uniqueIdentifier = [[NSUUID alloc] initWithUUIDString:uniqueIdentifierString]; + for (MTRDeviceController * controller in _controllers) { + if ([controller.uniqueIdentifier isEqual:uniqueIdentifier]) { + return [MTRBaseDevice deviceWithNodeID:@(nodeID) controller:controller]; + } + } + + ChipLogError(chipTool, "The controller '%s' could not be found.", [uniqueIdentifierString UTF8String]); + return nil; +} + +- (void)getAnyDeviceControllerWithCompletion:(MTRDeviceControllerGetterHandler)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +- (void)readAttributeWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber * _Nullable)endpointId + clusterId:(NSNumber * _Nullable)clusterId + attributeId:(NSNumber * _Nullable)attributeId + params:(NSDictionary * _Nullable)params + completion:(MTRValuesHandler)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + [device readAttributesWithEndpointID:endpointId + clusterID:clusterId + attributeID:attributeId + params:[MTRDeviceController decodeXPCReadParams:params] + queue:_callbackQueue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + completion([MTRDeviceController encodeXPCResponseValues:values], error); + }]; +} + +- (void)writeAttributeWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber *)endpointId + clusterId:(NSNumber *)clusterId + attributeId:(NSNumber *)attributeId + value:(id)value + timedWriteTimeout:(NSNumber * _Nullable)timeoutMs + completion:(MTRValuesHandler)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + [device + writeAttributeWithEndpointID:endpointId + clusterID:clusterId + attributeID:attributeId + value:value + timedWriteTimeout:timeoutMs + queue:_callbackQueue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + completion([MTRDeviceController encodeXPCResponseValues:values], error); + }]; +} + +- (void)invokeCommandWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber *)endpointId + clusterId:(NSNumber *)clusterId + commandId:(NSNumber *)commandId + fields:(id)fields + timedInvokeTimeout:(NSNumber * _Nullable)timeoutMs + completion:(MTRValuesHandler)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + [device + invokeCommandWithEndpointID:endpointId + clusterID:clusterId + commandID:commandId + commandFields:fields + timedInvokeTimeout:timeoutMs + queue:_callbackQueue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + completion([MTRDeviceController encodeXPCResponseValues:values], error); + }]; +} + +- (void)subscribeAttributeWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber * _Nullable)endpointId + clusterId:(NSNumber * _Nullable)clusterId + attributeId:(NSNumber * _Nullable)attributeId + minInterval:(NSNumber *)minInterval + maxInterval:(NSNumber *)maxInterval + params:(NSDictionary * _Nullable)params + establishedHandler:(dispatch_block_t)establishedHandler +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + if (nil == device) { + establishedHandler(); + // Send an error report so that the client knows of the failure + [self.clientProxy handleReportWithController:controller + nodeId:nodeId + values:nil + error:[NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]]; + return; + } + + auto subscriptionParams = [MTRDeviceController decodeXPCSubscribeParams:params]; + if (subscriptionParams == nil) { + subscriptionParams = [[MTRSubscribeParams alloc] initWithMinInterval:minInterval maxInterval:maxInterval]; + } else { + subscriptionParams.minInterval = minInterval; + subscriptionParams.maxInterval = maxInterval; + } + + [device subscribeToAttributesWithEndpointID:endpointId + clusterID:clusterId + attributeID:attributeId + params:subscriptionParams + queue:_callbackQueue + reportHandler:^( + NSArray *> * _Nullable values, NSError * _Nullable error) { + [self.clientProxy + handleReportWithController:controller + nodeId:nodeId + values:[MTRDeviceController encodeXPCResponseValues:values] + error:error]; + } + subscriptionEstablished:establishedHandler]; +} + +- (void)stopReportsWithController:(id _Nullable)controller nodeId:(uint64_t)nodeId completion:(dispatch_block_t)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + VerifyOrReturn(nil != device, completion()); + + [device deregisterReportHandlersWithQueue:_callbackQueue completion:completion]; +} + +- (void)subscribeWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + minInterval:(NSNumber *)minInterval + maxInterval:(NSNumber *)maxInterval + params:(NSDictionary * _Nullable)params + shouldCache:(BOOL)shouldCache + completion:(MTRStatusCompletion)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(error); +} + +- (void)readAttributeCacheWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber * _Nullable)endpointId + clusterId:(NSNumber * _Nullable)clusterId + attributeId:(NSNumber * _Nullable)attributeId + completion:(MTRValuesHandler)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +- (void)getDeviceControllerWithFabricId:(uint64_t)fabricId + completion:(MTRDeviceControllerGetterHandler)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +- (void)downloadLogWithController:(id _Nullable)controller + nodeId:(NSNumber *)nodeId + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + completion:(void (^)(NSString * _Nullable url, NSError * _Nullable error))completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +@end + +@interface DeviceControllerXPCServer : NSObject +@property (nonatomic, strong) NSXPCListener * listener; +@property (nonatomic, strong) NSMutableArray * controllers; +@end + +@implementation DeviceControllerXPCServer +- (instancetype)init +{ + if ((self = [super init])) { + _controllers = [NSMutableArray new]; + } + + return self; +} + +- (void)start +{ + _listener = [NSXPCListener anonymousListener]; + [_listener setDelegate:self]; + [_listener resume]; +} + +- (void)stop +{ + [_listener suspend]; + _listener = nil; + _controllers = nil; +} + +- (MTRDeviceController *)createController:(MTRDeviceControllerStartupParams *)params error:(NSError * __autoreleasing *)error +{ + __auto_type * factory = [MTRDeviceControllerFactory sharedInstance]; + __auto_type * local = [factory createControllerOnExistingFabric:params error:error]; + if (nil == local) { + return nil; + } + [_controllers addObject:local]; + + __auto_type connectBlock = ^NSXPCConnection * + { + return [[NSXPCConnection alloc] initWithListenerEndpoint:self.listener.endpoint]; + }; + return [MTRDeviceController sharedControllerWithID:[local.uniqueIdentifier UUIDString] xpcConnectBlock:connectBlock]; +} + +- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection +{ + newConnection.exportedInterface = [MTRDeviceController xpcInterfaceForServerProtocol]; + newConnection.remoteObjectInterface = [MTRDeviceController xpcInterfaceForClientProtocol]; + + auto server = [[DeviceControllerXPCServerImpl alloc] initWithClientProxy:[newConnection remoteObjectProxy] controllers:_controllers]; + newConnection.exportedObject = server; + + newConnection.interruptionHandler = ^{ + ChipLogProgress(chipTool, "XPC connection interrupted"); + }; + + newConnection.invalidationHandler = ^{ + ChipLogProgress(chipTool, "XPC connection invalidated"); + }; + + [newConnection resume]; + return YES; +} +@end + +@interface DeviceControllerXPCServerWithServiceName : NSObject +@end + +@implementation DeviceControllerXPCServerWithServiceName +- (void)start +{ +} + +- (void)stop +{ +} + +- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error +{ + __auto_type connectBlock = ^NSXPCConnection * + { +#if TARGET_OS_OSX + return [[NSXPCConnection alloc] initWithMachServiceName:serviceName options:0]; +#else + ChipLogError(chipTool, "NSXPCConnection::initWithMachServiceName is not supported on this platform."); + return nil; +#endif // TARGET_OS_OSX + }; + return [MTRDeviceController sharedControllerWithID:controllerID xpcConnectBlock:connectBlock]; +} +@end diff --git a/examples/darwin-framework-tool/commands/common/xpc/XPCServer.mm b/examples/darwin-framework-tool/commands/common/xpc/XPCServer.mm new file mode 100644 index 00000000000000..9aa7e77ec37177 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/XPCServer.mm @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2024 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. + * + */ + +#import "XPCServerProtocols.h" + +#import + +@interface XPCServerImpl : NSObject +@property (nonatomic, strong) id clientProxy; +@property (nonatomic, strong) NSArray * controllers; +@property (nonatomic, strong) dispatch_queue_t callbackQueue; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithClientProxy:(id)proxy controllers:(NSArray *)controllers; +@end + +@implementation XPCServerImpl +- (instancetype)initWithClientProxy:(id)proxy controllers:(NSArray *)controllers +{ + if ([super init]) { + _clientProxy = proxy; + _controllers = controllers; + _callbackQueue = dispatch_queue_create("com.chip.xpc.command", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + } + return self; +} + +- (MTRDevice *)createDevice:(NSUUID *)uniqueIdentifier nodeID:(NSNumber *)nodeID +{ + for (MTRDeviceController * controller in _controllers) { + if ([controller.uniqueIdentifier isEqual:uniqueIdentifier]) { + return [MTRDevice deviceWithNodeID:nodeID controller:controller]; + } + } + + ChipLogError(chipTool, "The controller '%s' could not be found.", [[uniqueIdentifier UUIDString] UTF8String]); + return nil; +} + +// TODO this is declared optional but the framework does not do any check on it, so it just crashes. +- (oneway void)deviceController:(NSUUID *)controllerUUID updateControllerConfiguration:(NSDictionary *)controllerState +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID getStateWithReply:(void (^)(MTRDeviceState state))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply(device.state); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID getDeviceCachePrimedWithReply:(void (^)(BOOL primed))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply(device.deviceCachePrimed); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID getEstimatedStartTimeWithReply:(void (^)(NSDate * _Nullable estimatedStartTime))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply(device.estimatedStartTime); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID getEstimatedSubscriptionLatencyWithReply:(void (^)(NSNumber * _Nullable estimatedSubscriptionLatency))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply(device.estimatedSubscriptionLatency); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID readAttributeWithEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID params:(MTRReadParams * _Nullable)params withReply:(void (^)(NSDictionary * _Nullable))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply([device readAttributeWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID params:params]); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID readAttributePaths:(NSArray *)attributePaths withReply:(void (^)(NSArray *> *))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply([device readAttributePaths:attributePaths]); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID writeAttributeWithEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID value:(id)value expectedValueInterval:(NSNumber * _Nullable)expectedValueInterval timedWriteTimeout:(NSNumber * _Nullable)timeout +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + [device writeAttributeWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID value:value expectedValueInterval:expectedValueInterval timedWriteTimeout:timeout]; +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID invokeCommandWithEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID commandID:(NSNumber *)commandID commandFields:(id)commandFields expectedValues:(NSArray *> * _Nullable)expectedValues expectedValueInterval:(NSNumber * _Nullable)expectedValueInterval timedInvokeTimeout:(NSNumber * _Nullable)timeout serverSideProcessingTimeout:(NSNumber * _Nullable)serverSideProcessingTimeout completion:(MTRDeviceResponseHandler)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + [device invokeCommandWithEndpointID:endpointID + clusterID:clusterID + commandID:commandID + commandFields:commandFields + expectedValues:expectedValues + expectedValueInterval:expectedValueInterval + timedInvokeTimeout:timeout + queue:_callbackQueue + completion:^(NSArray * values, NSError * _Nullable error) { + completion(values, error); + }]; +} + +- (oneway void)deviceController:(NSUUID *)controller nodeID:(NSNumber *)nodeID openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode discriminator:(NSNumber *)discriminator duration:(NSNumber *)duration completion:(MTRDeviceOpenCommissioningWindowHandler)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID downloadLogOfType:(MTRDiagnosticLogType)type timeout:(NSTimeInterval)timeout completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + [device downloadLogOfType:type timeout:timeout queue:_callbackQueue completion:completion]; +} + +- (oneway void)downloadLogOfType:(MTRDiagnosticLogType)type nodeID:(NSNumber *)nodeID timeout:(NSTimeInterval)timeout completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +@end + +@interface XPCServer : NSObject +@property (nonatomic, strong) NSXPCListener * listener; +@property (nonatomic, strong) NSMutableArray * controllers; +@end + +@implementation XPCServer +- (instancetype)init +{ + if ((self = [super init])) { + _controllers = [NSMutableArray new]; + } + + return self; +} + +- (void)start +{ + _listener = [NSXPCListener anonymousListener]; + [_listener setDelegate:self]; + [_listener resume]; +} + +- (void)stop +{ + [_listener suspend]; + _listener = nil; + _controllers = nil; +} + +- (MTRDeviceController *)createController:(MTRDeviceControllerExternalCertificateParameters *)params error:(NSError * __autoreleasing *)error +{ + __auto_type * local = [[MTRDeviceController alloc] initWithParameters:params error:error]; + if (nil == local) { + return nil; + } + [_controllers addObject:local]; + + __auto_type connectBlock = ^NSXPCConnection * + { + return [[NSXPCConnection alloc] initWithListenerEndpoint:self.listener.endpoint]; + }; + __auto_type * xpcParams = [[MTRXPCDeviceControllerParameters alloc] initWithXPConnectionBlock:connectBlock uniqueIdentifier:local.uniqueIdentifier]; + return [[MTRDeviceController alloc] initWithParameters:xpcParams error:error]; +} + +- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection +{ + newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MTRXPCServerProtocol)]; + newConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MTRXPCClientProtocol)]; + + auto server = [[XPCServerImpl alloc] initWithClientProxy:[newConnection remoteObjectProxy] controllers:_controllers]; + newConnection.exportedObject = server; + + newConnection.interruptionHandler = ^{ + ChipLogProgress(chipTool, "XPC connection interrupted"); + }; + + newConnection.invalidationHandler = ^{ + ChipLogProgress(chipTool, "XPC connection invalidated"); + }; + + [newConnection resume]; + return YES; +} +@end + +@interface XPCServerWithServiceName : NSObject +@end + +@implementation XPCServerWithServiceName +- (void)start +{ +} + +- (void)stop +{ +} + +- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error +{ + __auto_type connectBlock = ^NSXPCConnection * + { +#if TARGET_OS_OSX + return [[NSXPCConnection alloc] initWithMachServiceName:serviceName options:0]; +#else + ChipLogError(chipTool, "NSXPCConnection::initWithMachServiceName is not supported on this platform."); + return nil; +#endif // TARGET_OS_OSX + }; + __auto_type * uniqueIdentifier = [[NSUUID alloc] initWithUUIDString:controllerID]; + __auto_type * xpcParams = [[MTRXPCDeviceControllerParameters alloc] initWithXPConnectionBlock:connectBlock uniqueIdentifier:uniqueIdentifier]; + return [[MTRDeviceController alloc] initWithParameters:xpcParams error:error]; +} +@end diff --git a/examples/darwin-framework-tool/commands/common/xpc/XPCServerProtocols.h b/examples/darwin-framework-tool/commands/common/xpc/XPCServerProtocols.h new file mode 100644 index 00000000000000..e713bdb4804b69 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/XPCServerProtocols.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 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. + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol XPCServerBaseProtocol +- (void)start; +- (void)stop; +@end + +@protocol XPCServerExternalCertificateParametersProtocol +- (MTRDeviceController *)createController:(MTRDeviceControllerExternalCertificateParameters *)params error:(NSError * __autoreleasing *)error; +@end + +@protocol XPCServerStartupParametersProtocol +- (MTRDeviceController *)createController:(MTRDeviceControllerStartupParams *)params error:(NSError * __autoreleasing *)error; +@end + +@protocol XPCServerExternalCertificateParametersWithServiceNameProtocol +- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error; +@end + +@protocol XPCServerStartupParametersWithServiceNameProtocol +- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error; +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.h b/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.h new file mode 100644 index 00000000000000..1b77728836ff37 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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. + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface XPCServerRegistry : NSObject +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; ++ (XPCServerRegistry *)sharedInstance; + +- (void)start; +- (void)stop; +- (MTRDeviceController *)createController:(NSString * _Nullable)controllerId serviceName:(NSString * _Nullable)serviceName params:(id)params error:(NSError * __autoreleasing *)error; +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.mm b/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.mm new file mode 100644 index 00000000000000..3e873f3e5fffe6 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.mm @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024 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. + * + */ + +#import "XPCServerRegistry.h" + +#import "XPCServerProtocols.h" +#import + +@interface XPCServerRegistry () +@property (strong, nonatomic) NSMutableArray> * servers; +@end + +@implementation XPCServerRegistry +- (instancetype)init +{ + if ((self = [super init])) { + _servers = [NSMutableArray new]; + } + + return self; +} + ++ (XPCServerRegistry *)sharedInstance +{ + static XPCServerRegistry * registry = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + registry = [[XPCServerRegistry alloc] init]; + }); + return registry; +} + +- (void)start +{ + if (@available(macOS 13.0, *)) { + objc_enumerateClasses(nil, nil, @protocol(XPCServerBaseProtocol), nil, ^(Class cls, BOOL * stop) { + id server = [[cls alloc] init]; + [_servers addObject:server]; + }); + } else { + int numClasses = objc_getClassList(NULL, 0); + if (numClasses > 0) { + __auto_type * classes = (__unsafe_unretained Class *) malloc(sizeof(Class) * static_cast(numClasses)); + numClasses = objc_getClassList(classes, numClasses); + + for (int i = 0; i < numClasses; i++) { + id cls = classes[i]; + if (class_conformsToProtocol(cls, @protocol(XPCServerBaseProtocol))) { + id server = [[cls alloc] init]; + [_servers addObject:server]; + } + } + free(classes); // Free the allocated memory + } + } + + for (id server in _servers) { + [server start]; + } +} + +- (void)stop +{ + for (id server in _servers) { + [server stop]; + } +} + +- (MTRDeviceController *)createController:(NSString * _Nullable)controllerId serviceName:(NSString * _Nullable)serviceName params:(id)params error:(NSError * __autoreleasing *)error +{ + BOOL isExternalCertificateParameters = [params isKindOfClass:[MTRDeviceControllerExternalCertificateParameters class]]; + BOOL isStartupParameters = [params isKindOfClass:[MTRDeviceControllerStartupParams class]]; + for (id server in _servers) { + if (controllerId && serviceName) { + if ([server conformsToProtocol:@protocol(XPCServerExternalCertificateParametersWithServiceNameProtocol)] && isExternalCertificateParameters) { + return [server createController:controllerId serviceName:serviceName error:error]; + } + + if ([server conformsToProtocol:@protocol(XPCServerStartupParametersWithServiceNameProtocol)] && isStartupParameters) { + return [server createController:controllerId serviceName:serviceName error:error]; + } + } else { + if ([server conformsToProtocol:@protocol(XPCServerExternalCertificateParametersProtocol)] && isExternalCertificateParameters) { + return [server createController:params error:error]; + } + + if ([server conformsToProtocol:@protocol(XPCServerStartupParametersProtocol)] && isStartupParameters) { + return [server createController:params error:error]; + } + } + } + + __auto_type * userInfo = @{ NSLocalizedDescriptionKey : @"No XPC servers support this configuration." }; + *error = [NSError errorWithDomain:@"Error" code:0 userInfo:userInfo]; + return nil; +} + +@end diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 16ed12a0322224..702f853fdda5c1 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -436,12 +436,17 @@ B45374002A9FEC4F00807602 /* unix-init.c in Sources */ = {isa = PBXBuildFile; fileRef = B45373F92A9FEC4F00807602 /* unix-init.c */; settings = {COMPILER_FLAGS = "-Wno-error -Wno-unreachable-code -Wno-conversion -Wno-format-nonliteral"; }; }; B45374012A9FEC4F00807602 /* unix-sockets.c in Sources */ = {isa = PBXBuildFile; fileRef = B45373FA2A9FEC4F00807602 /* unix-sockets.c */; settings = {COMPILER_FLAGS = "-Wno-error -Wno-unreachable-code -Wno-conversion -Wno-format-nonliteral"; }; }; B4C8E6B72B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4C8E6B42B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm */; }; + B4D67A3B2D00DAB700C49965 /* XPCServerRegistry.h in Headers */ = {isa = PBXBuildFile; fileRef = B4D67A382D00DAB700C49965 /* XPCServerRegistry.h */; }; + B4D67A3C2D00DAB700C49965 /* XPCServerRegistry.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A392D00DAB700C49965 /* XPCServerRegistry.mm */; }; + B4D67A3D2D00DAB700C49965 /* DeviceControllerServer.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A362D00DAB700C49965 /* DeviceControllerServer.mm */; }; + B4D67A3E2D00DAB700C49965 /* XPCServer.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A372D00DAB700C49965 /* XPCServer.mm */; }; B4D67A412D00DD3D00C49965 /* DFTKeypair.h in Headers */ = {isa = PBXBuildFile; fileRef = B4D67A3F2D00DD3D00C49965 /* DFTKeypair.h */; }; B4D67A422D00DD3D00C49965 /* DFTKeypair.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A402D00DD3D00C49965 /* DFTKeypair.mm */; }; B4D67A922D527F4A00C49965 /* DCLClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A8E2D527F4A00C49965 /* DCLClient.cpp */; }; B4D67A932D527F4A00C49965 /* DisplayTermsAndConditions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A8F2D527F4A00C49965 /* DisplayTermsAndConditions.cpp */; }; B4D67A952D527F4A00C49965 /* JsonSchemaMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A912D527F4A00C49965 /* JsonSchemaMacros.cpp */; }; B4D67A9B2D538E9700C49965 /* HTTPSRequest.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A992D538E9700C49965 /* HTTPSRequest.mm */; }; + B4D67A462D07021700C49965 /* XPCServerProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = B4D67A452D07021700C49965 /* XPCServerProtocols.h */; }; B4E262162AA0CF1C00DBA5BC /* RemoteDataModelLogger.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E262122AA0C7A300DBA5BC /* RemoteDataModelLogger.mm */; }; B4E262172AA0CF2000DBA5BC /* RemoteDataModelLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = B4E262132AA0C7A300DBA5BC /* RemoteDataModelLogger.h */; }; B4E2621B2AA0D02000DBA5BC /* SleepCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */; }; @@ -998,12 +1003,17 @@ B45373FA2A9FEC4F00807602 /* unix-sockets.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "unix-sockets.c"; path = "repo/lib/plat/unix/unix-sockets.c"; sourceTree = ""; }; B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDiagnosticLogsDownloader.h; sourceTree = ""; }; B4C8E6B42B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDiagnosticLogsDownloader.mm; sourceTree = ""; }; + B4D67A362D00DAB700C49965 /* DeviceControllerServer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DeviceControllerServer.mm; sourceTree = ""; }; + B4D67A372D00DAB700C49965 /* XPCServer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = XPCServer.mm; sourceTree = ""; }; + B4D67A382D00DAB700C49965 /* XPCServerRegistry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPCServerRegistry.h; sourceTree = ""; }; + B4D67A392D00DAB700C49965 /* XPCServerRegistry.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = XPCServerRegistry.mm; sourceTree = ""; }; B4D67A3F2D00DD3D00C49965 /* DFTKeypair.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DFTKeypair.h; sourceTree = ""; }; B4D67A402D00DD3D00C49965 /* DFTKeypair.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DFTKeypair.mm; sourceTree = ""; }; B4D67A8E2D527F4A00C49965 /* DCLClient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DCLClient.cpp; path = commands/dcl/DCLClient.cpp; sourceTree = ""; }; B4D67A8F2D527F4A00C49965 /* DisplayTermsAndConditions.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DisplayTermsAndConditions.cpp; path = commands/dcl/DisplayTermsAndConditions.cpp; sourceTree = ""; }; B4D67A912D527F4A00C49965 /* JsonSchemaMacros.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = JsonSchemaMacros.cpp; path = commands/dcl/JsonSchemaMacros.cpp; sourceTree = ""; }; B4D67A992D538E9700C49965 /* HTTPSRequest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = HTTPSRequest.mm; sourceTree = ""; }; + B4D67A452D07021700C49965 /* XPCServerProtocols.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPCServerProtocols.h; sourceTree = ""; }; B4E262122AA0C7A300DBA5BC /* RemoteDataModelLogger.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RemoteDataModelLogger.mm; sourceTree = ""; }; B4E262132AA0C7A300DBA5BC /* RemoteDataModelLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteDataModelLogger.h; sourceTree = ""; }; B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SleepCommand.mm; sourceTree = ""; }; @@ -1171,6 +1181,7 @@ children = ( B4D67A3F2D00DD3D00C49965 /* DFTKeypair.h */, B4D67A402D00DD3D00C49965 /* DFTKeypair.mm */, + B4D67A3A2D00DAB700C49965 /* xpc */, B409D0AC2CCFB89600A7ED5A /* DeviceDelegate.h */, B409D0AD2CCFB89600A7ED5A /* DeviceDelegate.mm */, B43B39EF2CB99090006AA284 /* CertificateIssuer.h */, @@ -1889,6 +1900,18 @@ path = dcl; sourceTree = ""; }; + B4D67A3A2D00DAB700C49965 /* xpc */ = { + isa = PBXGroup; + children = ( + B4D67A452D07021700C49965 /* XPCServerProtocols.h */, + B4D67A362D00DAB700C49965 /* DeviceControllerServer.mm */, + B4D67A372D00DAB700C49965 /* XPCServer.mm */, + B4D67A382D00DAB700C49965 /* XPCServerRegistry.h */, + B4D67A392D00DAB700C49965 /* XPCServerRegistry.mm */, + ); + path = xpc; + sourceTree = ""; + }; B4E262182AA0CFFE00DBA5BC /* delay */ = { isa = PBXGroup; children = ( @@ -1976,6 +1999,7 @@ B43B39EC2CB859A5006AA284 /* DumpMemoryGraphCommand.h in Headers */, B43B39ED2CB859A5006AA284 /* Commands.h in Headers */, B43B39EE2CB859A5006AA284 /* LeaksTool.h in Headers */, + B4D67A462D07021700C49965 /* XPCServerProtocols.h in Headers */, 7534D17E2CF8CE2000F64654 /* DefaultAttributePersistenceProvider.h in Headers */, 037C3DBD2991BD5000B7EEE2 /* OTAProviderDelegate.h in Headers */, B4FCD5702B603A6300832859 /* Commands.h in Headers */, @@ -1985,6 +2009,7 @@ 037C3DCB2991BD5100B7EEE2 /* CHIPCommandStorageDelegate.h in Headers */, 037C3DD32991BD5200B7EEE2 /* logging.h in Headers */, 037C3DB72991BD5000B7EEE2 /* ModelCommandBridge.h in Headers */, + B4D67A3B2D00DAB700C49965 /* XPCServerRegistry.h in Headers */, 037C3DC52991BD5100B7EEE2 /* StorageManagementCommand.h in Headers */, 037C3DCC2991BD5100B7EEE2 /* MTRError_Utils.h in Headers */, 7592BD002CBEE98C00EB74A0 /* Instance.h in Headers */, @@ -2379,6 +2404,9 @@ 75A202E62BA8DBAC00A771DD /* reporting.cpp in Sources */, 039145E82993179300257B3E /* GetCommissionerNodeIdCommand.mm in Sources */, 0395469F2991DFC5006D42A8 /* json_reader.cpp in Sources */, + B4D67A3C2D00DAB700C49965 /* XPCServerRegistry.mm in Sources */, + B4D67A3D2D00DAB700C49965 /* DeviceControllerServer.mm in Sources */, + B4D67A3E2D00DAB700C49965 /* XPCServer.mm in Sources */, 514C79F42B62ED5500DD6D7B /* attribute-storage.cpp in Sources */, 0395469E2991DFC5006D42A8 /* json_writer.cpp in Sources */, B4D67A422D00DD3D00C49965 /* DFTKeypair.mm in Sources */, From 8464e5f7077d6cd222f9364115c7e355ef956274 Mon Sep 17 00:00:00 2001 From: Rohit Jadhav <69809379+jadhavrohit924@users.noreply.github.com> Date: Thu, 20 Feb 2025 00:20:36 +0530 Subject: [PATCH 8/9] Add OTA Requestor cluster to energy management app (#37614) * ESP32: Disable OTA requestor for energy management app. * Add OTA Requestor cluster to energy management app. --- .../energy-management-app.matter | 172 +++++++++++++ .../energy-management-app.zap | 237 ++++++++++++++++++ 2 files changed, 409 insertions(+) diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.matter b/examples/energy-management-app/energy-management-common/energy-management-app.matter index b2ffbc07f68271..19b1517be746c8 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.matter +++ b/examples/energy-management-app/energy-management-common/energy-management-app.matter @@ -546,6 +546,160 @@ cluster BasicInformation = 40 { command MfgSpecificPing(): DefaultSuccess = 0; } +/** Provides an interface for providing OTA software updates */ +cluster OtaSoftwareUpdateProvider = 41 { + revision 1; // NOTE: Default/not specifically set + + enum ApplyUpdateActionEnum : enum8 { + kProceed = 0; + kAwaitNextAction = 1; + kDiscontinue = 2; + } + + enum DownloadProtocolEnum : enum8 { + kBDXSynchronous = 0; + kBDXAsynchronous = 1; + kHTTPS = 2; + kVendorSpecific = 3; + } + + enum StatusEnum : enum8 { + kUpdateAvailable = 0; + kBusy = 1; + kNotAvailable = 2; + kDownloadProtocolNotSupported = 3; + } + + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct QueryImageRequest { + vendor_id vendorID = 0; + int16u productID = 1; + int32u softwareVersion = 2; + DownloadProtocolEnum protocolsSupported[] = 3; + optional int16u hardwareVersion = 4; + optional char_string<2> location = 5; + optional boolean requestorCanConsent = 6; + optional octet_string<512> metadataForProvider = 7; + } + + response struct QueryImageResponse = 1 { + StatusEnum status = 0; + optional int32u delayedActionTime = 1; + optional char_string<256> imageURI = 2; + optional int32u softwareVersion = 3; + optional char_string<64> softwareVersionString = 4; + optional octet_string<32> updateToken = 5; + optional boolean userConsentNeeded = 6; + optional octet_string<512> metadataForRequestor = 7; + } + + request struct ApplyUpdateRequestRequest { + octet_string<32> updateToken = 0; + int32u newVersion = 1; + } + + response struct ApplyUpdateResponse = 3 { + ApplyUpdateActionEnum action = 0; + int32u delayedActionTime = 1; + } + + request struct NotifyUpdateAppliedRequest { + octet_string<32> updateToken = 0; + int32u softwareVersion = 1; + } + + /** Determine availability of a new Software Image */ + command QueryImage(QueryImageRequest): QueryImageResponse = 0; + /** Determine next action to take for a downloaded Software Image */ + command ApplyUpdateRequest(ApplyUpdateRequestRequest): ApplyUpdateResponse = 2; + /** Notify OTA Provider that an update was applied */ + command NotifyUpdateApplied(NotifyUpdateAppliedRequest): DefaultSuccess = 4; +} + +/** Provides an interface for downloading and applying OTA software updates */ +cluster OtaSoftwareUpdateRequestor = 42 { + revision 1; // NOTE: Default/not specifically set + + enum AnnouncementReasonEnum : enum8 { + kSimpleAnnouncement = 0; + kUpdateAvailable = 1; + kUrgentUpdateAvailable = 2; + } + + enum ChangeReasonEnum : enum8 { + kUnknown = 0; + kSuccess = 1; + kFailure = 2; + kTimeOut = 3; + kDelayByProvider = 4; + } + + enum UpdateStateEnum : enum8 { + kUnknown = 0; + kIdle = 1; + kQuerying = 2; + kDelayedOnQuery = 3; + kDownloading = 4; + kApplying = 5; + kDelayedOnApply = 6; + kRollingBack = 7; + kDelayedOnUserConsent = 8; + } + + fabric_scoped struct ProviderLocation { + node_id providerNodeID = 1; + endpoint_no endpoint = 2; + fabric_idx fabricIndex = 254; + } + + info event StateTransition = 0 { + UpdateStateEnum previousState = 0; + UpdateStateEnum newState = 1; + ChangeReasonEnum reason = 2; + nullable int32u targetSoftwareVersion = 3; + } + + critical event VersionApplied = 1 { + int32u softwareVersion = 0; + int16u productID = 1; + } + + info event DownloadError = 2 { + int32u softwareVersion = 0; + int64u bytesDownloaded = 1; + nullable int8u progressPercent = 2; + nullable int64s platformCode = 3; + } + + attribute access(write: administer) ProviderLocation defaultOTAProviders[] = 0; + readonly attribute boolean updatePossible = 1; + readonly attribute UpdateStateEnum updateState = 2; + readonly attribute nullable int8u updateStateProgress = 3; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct AnnounceOTAProviderRequest { + node_id providerNodeID = 0; + vendor_id vendorID = 1; + AnnouncementReasonEnum announcementReason = 2; + optional octet_string<512> metadataForNode = 3; + endpoint_no endpoint = 4; + } + + /** Announce the presence of an OTA Provider */ + command AnnounceOTAProvider(AnnounceOTAProviderRequest): DefaultSuccess = 0; +} + /** Nodes should be expected to be deployed to any and all regions of the world. These global regions may have differing common languages, units of measurements, and numerical formatting standards. As such, Nodes that visually or audibly convey information need a mechanism by which @@ -2401,6 +2555,7 @@ provisional cluster DeviceEnergyManagementMode = 159 { endpoint 0 { device type ma_rootdevice = 22, version 1; + binding cluster OtaSoftwareUpdateProvider; server cluster Descriptor { callback attribute deviceTypeList; @@ -2450,6 +2605,23 @@ endpoint 0 { ram attribute clusterRevision default = 4; } + server cluster OtaSoftwareUpdateRequestor { + emits event StateTransition; + emits event VersionApplied; + emits event DownloadError; + callback attribute defaultOTAProviders; + ram attribute updatePossible default = true; + ram attribute updateState default = 0; + ram attribute updateStateProgress; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command AnnounceOTAProvider; + } + server cluster LocalizationConfiguration { ram attribute activeLocale; callback attribute supportedLocales; diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.zap b/examples/energy-management-app/energy-management-common/energy-management-app.zap index 9a1f4549b73051..d7c6303e0ac7ec 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.zap +++ b/examples/energy-management-app/energy-management-common/energy-management-app.zap @@ -717,6 +717,243 @@ } ] }, + { + "name": "OTA Software Update Provider", + "code": 41, + "mfgCode": null, + "define": "OTA_SOFTWARE_UPDATE_PROVIDER_CLUSTER", + "side": "client", + "enabled": 1, + "commands": [ + { + "name": "QueryImage", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "QueryImageResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ApplyUpdateRequest", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ApplyUpdateResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "NotifyUpdateApplied", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + } + ] + }, + { + "name": "OTA Software Update Requestor", + "code": 42, + "mfgCode": null, + "define": "OTA_SOFTWARE_UPDATE_REQUESTOR_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "AnnounceOTAProvider", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "DefaultOTAProviders", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "UpdatePossible", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "true", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "UpdateState", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "UpdateStateEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "UpdateStateProgress", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "StateTransition", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "VersionApplied", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "DownloadError", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, { "name": "Localization Configuration", "code": 43, From 40979b4028fb0d0c864887e5649e094f628473ca Mon Sep 17 00:00:00 2001 From: Jeff Tung <100387939+jtung-apple@users.noreply.github.com> Date: Wed, 19 Feb 2025 12:08:30 -0800 Subject: [PATCH 9/9] [Darwin] MTRDevice delegate callbacks should call out without holding lock (#37668) * [Darwin] MTRDevice delegate callbacks should call out without holding lock * Fixed compilation issues * Update src/darwin/Framework/CHIP/MTRDevice_XPC.mm Co-authored-by: Boris Zbarsky * Update src/darwin/Framework/CHIP/MTRDevice_XPC.mm Co-authored-by: Boris Zbarsky * Change back to lock_guard --------- Co-authored-by: Boris Zbarsky --- .../Framework/CHIP/MTRDeviceController_XPC.mm | 31 +++++++++---------- src/darwin/Framework/CHIP/MTRDevice_XPC.mm | 16 ++++++++-- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm index 4b7dc4e1de0fe8..3000c06ef775f1 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm @@ -23,6 +23,7 @@ #import "MTRDevice_XPC_Internal.h" #import "MTRError_Internal.h" #import "MTRLogging_Internal.h" +#import "MTRUnfairLock.h" #import "MTRXPCClientProtocol.h" #import "MTRXPCServerProtocol.h" @@ -72,28 +73,26 @@ @implementation MTRDeviceController_XPC - (void)_updateRegistrationInfo { - dispatch_async(self.workQueue, ^{ - std::lock_guard lock(*self.deviceMapLock); + std::lock_guard lock(*self.deviceMapLock); - NSMutableDictionary * registrationInfo = [NSMutableDictionary dictionary]; + NSMutableDictionary * registrationInfo = [NSMutableDictionary dictionary]; - NSMutableDictionary * controllerContext = [NSMutableDictionary dictionary]; - NSMutableArray * nodeIDs = [NSMutableArray array]; + NSMutableDictionary * controllerContext = [NSMutableDictionary dictionary]; + NSMutableArray * nodeIDs = [NSMutableArray array]; - for (NSNumber * nodeID in [self.nodeIDToDeviceMap keyEnumerator]) { - MTRDevice * device = self.nodeIDToDeviceMap[nodeID]; - if ([device delegateExists]) { - NSMutableDictionary * nodeDictionary = [NSMutableDictionary dictionary]; - MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationNodeIDKey, nodeID, nodeDictionary) + for (NSNumber * nodeID in [self.nodeIDToDeviceMap keyEnumerator]) { + MTRDevice * device = [self.nodeIDToDeviceMap objectForKey:nodeID]; + if ([device delegateExists]) { + NSMutableDictionary * nodeDictionary = [NSMutableDictionary dictionary]; + MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationNodeIDKey, nodeID, nodeDictionary) - [nodeIDs addObject:nodeDictionary]; - } + [nodeIDs addObject:nodeDictionary]; } - MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationNodeIDsKey, nodeIDs, registrationInfo) - MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationControllerContextKey, controllerContext, registrationInfo) + } + MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationNodeIDsKey, nodeIDs, registrationInfo) + MTR_REQUIRED_ATTRIBUTE(MTRDeviceControllerRegistrationControllerContextKey, controllerContext, registrationInfo) - [self updateControllerConfiguration:registrationInfo]; - }); + [self updateControllerConfiguration:registrationInfo]; } - (void)_registerNodeID:(NSNumber *)nodeID diff --git a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm index 77fa823a24eb5a..6ed4d17c1b5e19 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm @@ -151,16 +151,28 @@ - (MTRNetworkCommissioningFeature)networkCommissioningFeatures - (void)_delegateAdded:(id)delegate { + os_unfair_lock_assert_owner(&self->_lock); + [super _delegateAdded:delegate]; MTR_LOG("%@ delegate added: %@", self, delegate); - [(MTRDeviceController_XPC *) [self deviceController] _updateRegistrationInfo]; + + // dispatch to own queue so we're not holding our lock while calling out to the controller code. + dispatch_async(self.queue, ^{ + [(MTRDeviceController_XPC *) [self deviceController] _updateRegistrationInfo]; + }); } - (void)_delegateRemoved:(id)delegate { + os_unfair_lock_assert_owner(&self->_lock); + [super _delegateRemoved:delegate]; MTR_LOG("%@ delegate removed: %@", self, delegate); - [(MTRDeviceController_XPC *) [self deviceController] _updateRegistrationInfo]; + + // dispatch to own queue so we're not holding our lock while calling out to the controller code. + dispatch_async(self.queue, ^{ + [(MTRDeviceController_XPC *) [self deviceController] _updateRegistrationInfo]; + }); } #pragma mark - Client Callbacks (MTRDeviceDelegate)