diff --git a/.gitmodules b/.gitmodules index 824bd9954e506c..4eca51b5cf03f9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -332,6 +332,7 @@ [submodule "third_party/lwip/repo"] path = third_party/lwip/repo url = https://github.com/lwip-tcpip/lwip.git + excluded-platforms = darwin [submodule "third_party/abseil-cpp/src"] path = third_party/abseil-cpp/src url = https://github.com/abseil/abseil-cpp.git diff --git a/examples/common/pigweed/rpc_services/Attributes.h b/examples/common/pigweed/rpc_services/Attributes.h index e4ced64a51d9f2..d34d7e5789c3cd 100644 --- a/examples/common/pigweed/rpc_services/Attributes.h +++ b/examples/common/pigweed/rpc_services/Attributes.h @@ -224,7 +224,7 @@ class Attributes : public pw_rpc::nanopb::Attributes::Service app::DataModel::ReadAttributeRequest request; request.path = path; request.operationFlags.Set(app::DataModel::OperationFlags::kInternal); - request.subjectDescriptor = subjectDescriptor; + request.subjectDescriptor = &subjectDescriptor; std::optional info = provider->GetClusterInfo(path); if (!info.has_value()) diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h index 3466972f1f6f25..477e1ed8105101 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h @@ -42,7 +42,7 @@ class CHIPCommandBridge : public Command { "commissioner-name. Interactive mode will only set a single commissioner on the inital command. " "The commissioner node ID will be persisted until a different one is specified."); AddArgument("commissioner-shared-storage", 0, 1, &mCommissionerSharedStorage, - "Use a shared storage instance instead of individual storage for each commissioner. Default is true."); + "Use a shared storage instance instead of individual storage for each commissioner. Default is false."); AddArgument("paa-trust-store-path", &mPaaTrustStorePath, "Path to directory holding PAA certificate information. Can be absolute or relative to the current working " "directory."); diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm index 85ce8d8935bec0..55df92f8b98a11 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm @@ -139,7 +139,7 @@ productAttestationAuthorityCertificates = nil; } - sUseSharedStorage = mCommissionerSharedStorage.ValueOr(true); + sUseSharedStorage = mCommissionerSharedStorage.ValueOr(false); if (sUseSharedStorage) { return SetUpStackWithSharedStorage(productAttestationAuthorityCertificates); } @@ -185,6 +185,10 @@ intermediateCertificate:nil rootCertificate:certificateIssuer.rootCertificate]; [params setOperationalCertificateIssuer:certificateIssuer queue:controllerStorageQueue]; + + __auto_type * otaDelegateQueue = dispatch_queue_create("com.chip.ota", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + [params setOTAProviderDelegate:mOTADelegate queue:otaDelegateQueue]; + params.productAttestationAuthorityCertificates = productAttestationAuthorityCertificates; __auto_type * controller = [[MTRDeviceController alloc] initWithParameters:params error:&error]; diff --git a/examples/darwin-framework-tool/commands/common/CertificateIssuer.mm b/examples/darwin-framework-tool/commands/common/CertificateIssuer.mm index bd12878c6addd7..c86a41490b08c5 100644 --- a/examples/darwin-framework-tool/commands/common/CertificateIssuer.mm +++ b/examples/darwin-framework-tool/commands/common/CertificateIssuer.mm @@ -21,6 +21,8 @@ #include +constexpr const uint32_t kIssuerId = 12345678; + @interface CertificateIssuer () - (MTRCertificateDERBytes _Nullable)issueOperationalCertificateForNodeID:(NSNumber *)nodeID fabricID:(NSNumber *)fabricID @@ -67,7 +69,7 @@ - (void)startWithStorage:(id)storage return; } - __auto_type * rootCertificate = [MTRCertificates createRootCertificate:signingKey issuerID:nil fabricID:nil error:error]; + __auto_type * rootCertificate = [MTRCertificates createRootCertificate:signingKey issuerID:@(kIssuerId) fabricID:nil error:error]; if (nil == rootCertificate) { *error = [NSError errorWithDomain:@"Error" code:0 userInfo:@{ @"reason" : @"Error creating root certificate" }]; return; diff --git a/examples/fabric-admin/README.md b/examples/fabric-admin/README.md index c6b4ba7eb3577f..bdfb82d5ac5d8d 100644 --- a/examples/fabric-admin/README.md +++ b/examples/fabric-admin/README.md @@ -23,13 +23,13 @@ For Raspberry Pi 4 example: ### Pull Docker Images ``` -docker pull connectedhomeip/chip-build-vscode:latest +docker pull ghcr.io/project-chip/chip-build-crosscompile:81 ``` ### Run docker ``` -docker run -it -v ~/connectedhomeip:/var/connectedhomeip connectedhomeip/chip-build-vscode:latest /bin/bash +docker run -it -v ~/connectedhomeip:/var/connectedhomeip ghcr.io/project-chip/chip-build-crosscompile:81 /bin/bash ``` ### Build @@ -38,8 +38,6 @@ docker run -it -v ~/connectedhomeip:/var/connectedhomeip connectedhomeip/chip-bu cd /var/connectedhomeip git config --global --add safe.directory /var/connectedhomeip -git config --global --add safe.directory /var/connectedhomeip/third_party/pigweed/repo -git config --global --add safe.directory /var/connectedhomeip/examples/common/QRCode/repo ./scripts/run_in_build_env.sh \ "./scripts/build/build_examples.py \ diff --git a/examples/fabric-bridge-app/linux/README.md b/examples/fabric-bridge-app/linux/README.md index ede6ce26399062..fdb466147e3cf4 100644 --- a/examples/fabric-bridge-app/linux/README.md +++ b/examples/fabric-bridge-app/linux/README.md @@ -100,13 +100,13 @@ defined: Pull Docker Images ``` - docker pull connectedhomeip/chip-build-vscode:latest + docker pull ghcr.io/project-chip/chip-build-crosscompile:81 ``` Run docker ``` - docker run -it -v ~/connectedhomeip:/var/connectedhomeip connectedhomeip/chip-build-vscode:latest /bin/bash + docker run -it -v ~/connectedhomeip:/var/connectedhomeip ghcr.io/project-chip/chip-build-crosscompile:81 /bin/bash ``` Build @@ -115,8 +115,6 @@ defined: cd /var/connectedhomeip git config --global --add safe.directory /var/connectedhomeip - git config --global --add safe.directory /var/connectedhomeip/third_party/pigweed/repo - git config --global --add safe.directory /var/connectedhomeip/examples/common/QRCode/repo ./scripts/run_in_build_env.sh \ "./scripts/build/build_examples.py \ diff --git a/examples/fabric-sync/README.md b/examples/fabric-sync/README.md index 6cbe9da9e9428d..0309218725f02b 100644 --- a/examples/fabric-sync/README.md +++ b/examples/fabric-sync/README.md @@ -92,13 +92,13 @@ defined: Pull Docker Images ``` - docker pull connectedhomeip/chip-build-vscode:latest + docker pull ghcr.io/project-chip/chip-build-crosscompile:81 ``` Run docker ``` - docker run -it -v ~/connectedhomeip:/var/connectedhomeip connectedhomeip/chip-build-vscode:latest /bin/bash + docker run -it -v ~/connectedhomeip:/var/connectedhomeip ghcr.io/project-chip/chip-build-crosscompile:81 /bin/bash ``` Build @@ -107,8 +107,6 @@ defined: cd /var/connectedhomeip git config --global --add safe.directory /var/connectedhomeip - git config --global --add safe.directory /var/connectedhomeip/third_party/pigweed/repo - git config --global --add safe.directory /var/connectedhomeip/examples/common/QRCode/repo ./scripts/run_in_build_env.sh \ "./scripts/build/build_examples.py \ diff --git a/examples/fabric-sync/main.cpp b/examples/fabric-sync/main.cpp index 84c014dfcbf010..66541b32de91f9 100644 --- a/examples/fabric-sync/main.cpp +++ b/examples/fabric-sync/main.cpp @@ -20,14 +20,69 @@ using namespace chip; +namespace { + +constexpr char kFabricSyncLogFilePath[] = "/tmp/fabric_sync.log"; + +// File pointer for the log file +FILE * sLogFile = nullptr; + +void OpenLogFile(const char * filePath) +{ + sLogFile = fopen(filePath, "a"); + if (sLogFile == nullptr) + { + perror("Failed to open log file"); + } +} + +void CloseLogFile() +{ + if (sLogFile != nullptr) + { + fclose(sLogFile); + sLogFile = nullptr; + } +} + +void ENFORCE_FORMAT(3, 0) LoggingCallback(const char * module, uint8_t category, const char * msg, va_list args) +{ + if (sLogFile == nullptr) + { + return; + } + + uint64_t timeMs = System::SystemClock().GetMonotonicMilliseconds64().count(); + uint64_t seconds = timeMs / 1000; + uint64_t milliseconds = timeMs % 1000; + + flockfile(sLogFile); + + fprintf(sLogFile, "[%llu.%06llu] CHIP:%s: ", static_cast(seconds), + static_cast(milliseconds), module); + vfprintf(sLogFile, msg, args); + fprintf(sLogFile, "\n"); + fflush(sLogFile); + + funlockfile(sLogFile); +} + +} // namespace + void ApplicationInit() { ChipLogProgress(NotSpecified, "Fabric-Sync: ApplicationInit()"); + + OpenLogFile(kFabricSyncLogFilePath); + + // Redirect logs to the custom logging callback + Logging::SetLogRedirectCallback(LoggingCallback); } void ApplicationShutdown() { ChipLogDetail(NotSpecified, "Fabric-Sync: ApplicationShutdown()"); + CloseLogFile(); } int main(int argc, char * argv[]) diff --git a/examples/platform/silabs/provision/ProvisionStorageDefault.cpp b/examples/platform/silabs/provision/ProvisionStorageDefault.cpp index 6a889b5bffeb06..5180e031f07238 100644 --- a/examples/platform/silabs/provision/ProvisionStorageDefault.cpp +++ b/examples/platform/silabs/provision/ProvisionStorageDefault.cpp @@ -29,6 +29,11 @@ #include #include #include +#ifndef NDEBUG +#if defined(SL_MATTER_TEST_EVENT_TRIGGER_ENABLED) && (SL_MATTER_GN_BUILD == 0) +#include +#endif // defined(SL_MATTER_TEST_EVENT_TRIGGER_ENABLED) && (SL_MATTER_GN_BUILD == 0) +#endif // NDEBUG #ifdef OTA_ENCRYPTION_ENABLE #include #endif // OTA_ENCRYPTION_ENABLE diff --git a/scripts/checkout_submodules.py b/scripts/checkout_submodules.py index 0290182b5b4bff..4d527148c7d9b0 100755 --- a/scripts/checkout_submodules.py +++ b/scripts/checkout_submodules.py @@ -66,6 +66,18 @@ def load_module_info() -> None: platforms = set(filter(None, platforms)) assert not ( platforms - ALL_PLATFORMS), "Submodule's platform not contained in ALL_PLATFORMS" + + # Check for explicitly excluded platforms + excluded_platforms = module.get('excluded-platforms', '').split(',') + excluded_platforms = set(filter(None, excluded_platforms)) + assert not ( + excluded_platforms - ALL_PLATFORMS), "Submodule excluded on platforms not contained in ALL_PLATFORMS" + + if len(excluded_platforms) != 0: + if len(platforms) == 0: + platforms = ALL_PLATFORMS + platforms = platforms - excluded_platforms + recursive = module.getboolean('recursive', False) name = name.replace('submodule "', '').replace('"', '') yield Module(name=name, path=module['path'], platforms=platforms, recursive=recursive) diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index f3565cf5d9a5e1..13a15f2d7c87be 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -76,6 +76,7 @@ buildconfig_header("app_buildconfig") { "CHIP_DEVICE_CONFIG_DYNAMIC_SERVER=${chip_build_controller_dynamic_server}", "CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP=${chip_enable_busy_handling_for_operational_session_setup}", "CHIP_CONFIG_DATA_MODEL_CHECK_DIE_ON_FAILURE=${chip_data_model_check_die_on_failure}", + "CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING=${chip_data_model_extra_logging}", ] if (chip_use_data_model_interface == "disabled") { diff --git a/src/app/CommandHandlerImpl.cpp b/src/app/CommandHandlerImpl.cpp index 2142af63494348..80373dc32ad755 100644 --- a/src/app/CommandHandlerImpl.cpp +++ b/src/app/CommandHandlerImpl.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -391,10 +392,11 @@ Status CommandHandlerImpl::ProcessCommandDataIB(CommandDataIB::Parser & aCommand VerifyOrReturnError(err == CHIP_NO_ERROR, Status::InvalidAction); { + Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor(); DataModel::InvokeRequest request; request.path = concretePath; - request.subjectDescriptor = GetSubjectDescriptor(); + request.subjectDescriptor = &subjectDescriptor; request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, IsTimedInvoke()); Status preCheckStatus = mpCallback->ValidateCommandCanBeDispatched(request); @@ -513,10 +515,11 @@ Status CommandHandlerImpl::ProcessGroupCommandDataIB(CommandDataIB::Parser & aCo const ConcreteCommandPath concretePath(mapping.endpoint_id, clusterId, commandId); { + Access::SubjectDescriptor subjectDescriptor = GetSubjectDescriptor(); DataModel::InvokeRequest request; request.path = concretePath; - request.subjectDescriptor = GetSubjectDescriptor(); + request.subjectDescriptor = &subjectDescriptor; request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, IsTimedInvoke()); Status preCheckStatus = mpCallback->ValidateCommandCanBeDispatched(request); diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index d88299c29da412..40ec6e71b3210b 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -1646,10 +1646,12 @@ void InteractionModelEngine::DispatchCommand(CommandHandlerImpl & apCommandObj, { #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE + Access::SubjectDescriptor subjectDescriptor = apCommandObj.GetSubjectDescriptor(); + DataModel::InvokeRequest request; request.path = aCommandPath; request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, apCommandObj.IsTimedInvoke()); - request.subjectDescriptor = apCommandObj.GetSubjectDescriptor(); + request.subjectDescriptor = &subjectDescriptor; std::optional status = GetDataModelProvider()->Invoke(request, apPayload, &apCommandObj); @@ -1702,7 +1704,7 @@ Protocols::InteractionModel::Status InteractionModelEngine::ValidateCommandCanBe Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandAccess(const DataModel::InvokeRequest & aRequest) { - if (!aRequest.subjectDescriptor.has_value()) + if (aRequest.subjectDescriptor == nullptr) { return Status::UnsupportedAccess; // we require a subject for invoke } diff --git a/src/app/WriteHandler.cpp b/src/app/WriteHandler.cpp index 67a5c7a3ac99d9..7a1bbc5d57c668 100644 --- a/src/app/WriteHandler.cpp +++ b/src/app/WriteHandler.cpp @@ -108,8 +108,13 @@ void WriteHandler::Close() std::optional WriteHandler::IsListAttributePath(const ConcreteAttributePath & path) { #if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE - VerifyOrReturnValue(mDataModelProvider != nullptr, std::nullopt, - ChipLogError(DataManagement, "Null data model while checking attribute properties.")); + if (mDataModelProvider == nullptr) + { +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING + ChipLogError(DataManagement, "Null data model while checking attribute properties."); +#endif + return std::nullopt; + } auto info = mDataModelProvider->GetAttributeInfo(path); if (!info.has_value()) @@ -774,7 +779,7 @@ CHIP_ERROR WriteHandler::WriteClusterData(const Access::SubjectDescriptor & aSub DataModel::WriteAttributeRequest request; request.path = aPath; - request.subjectDescriptor = aSubject; + request.subjectDescriptor = &aSubject; request.previousSuccessPath = mLastSuccessfullyWrittenPath; request.writeFlags.Set(DataModel::WriteFlags::kTimed, IsTimedWrite()); diff --git a/src/app/clusters/chime-server/chime-server.cpp b/src/app/clusters/chime-server/chime-server.cpp new file mode 100644 index 00000000000000..b2b2367f9b826f --- /dev/null +++ b/src/app/clusters/chime-server/chime-server.cpp @@ -0,0 +1,279 @@ +/* + * + * Copyright (c) 2024 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. + */ + +/****************************************************************************' + * @file + * @brief Implementation for the Chime Server Cluster + ***************************************************************************/ + +#include "chime-server.h" + +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters::Chime; +using namespace chip::app::Clusters::Chime::Attributes; +using chip::Protocols::InteractionModel::Status; +using ChimeSoundStructType = Structs::ChimeSoundStruct::Type; + +namespace chip { +namespace app { +namespace Clusters { + +ChimeServer::ChimeServer(EndpointId endpointId, ChimeDelegate & delegate) : + AttributeAccessInterface(MakeOptional(endpointId), Chime::Id), CommandHandlerInterface(MakeOptional(endpointId), Chime::Id), + mDelegate(delegate), mActiveChimeID(0), mEnabled(true) +{ + mDelegate.SetChimeServer(this); +} + +ChimeServer::~ChimeServer() +{ + // null out the ref to us on the delegate + mDelegate.SetChimeServer(nullptr); + + // unregister + CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this); + AttributeAccessInterfaceRegistry::Instance().Unregister(this); +} + +CHIP_ERROR ChimeServer::Init() +{ + LoadPersistentAttributes(); + + VerifyOrReturnError(AttributeAccessInterfaceRegistry::Instance().Register(this), CHIP_ERROR_INTERNAL); + ReturnErrorOnFailure(CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this)); + return CHIP_NO_ERROR; +} + +void ChimeServer::LoadPersistentAttributes() +{ + // Load Active Chime ID + uint8_t storedActiveChimeID; + CHIP_ERROR err = GetSafeAttributePersistenceProvider()->ReadScalarValue( + ConcreteAttributePath(GetEndpointId(), Chime::Id, ActiveChimeID::Id), storedActiveChimeID); + if (err == CHIP_NO_ERROR) + { + mActiveChimeID = storedActiveChimeID; + } + else + { + // otherwise defaults + ChipLogDetail(Zcl, "Chime: Unable to load the ActiveChimeID attribute from the KVS. Defaulting to %u", mActiveChimeID); + } + + // Load Enabled + bool storedEnabled; + err = GetSafeAttributePersistenceProvider()->ReadScalarValue(ConcreteAttributePath(GetEndpointId(), Chime::Id, Enabled::Id), + storedEnabled); + if (err == CHIP_NO_ERROR) + { + mEnabled = storedEnabled; + } + else + { + // otherwise take the default + ChipLogDetail(Zcl, "Chime: Unable to load the Enabled attribute from the KVS. Defaulting to %u", mEnabled); + } +} + +// AttributeAccessInterface +CHIP_ERROR ChimeServer::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + VerifyOrDie(aPath.mClusterId == Chime::Id); + + switch (aPath.mAttributeId) + { + case ActiveChimeID::Id: + ReturnErrorOnFailure(aEncoder.Encode(mActiveChimeID)); + break; + case Enabled::Id: + ReturnErrorOnFailure(aEncoder.Encode(mEnabled)); + break; + case InstalledChimeSounds::Id: + ChimeServer * cs = this; + CHIP_ERROR err = + aEncoder.EncodeList([cs](const auto & encoder) -> CHIP_ERROR { return cs->EncodeSupportedChimeSounds(encoder); }); + return err; + } + + return CHIP_NO_ERROR; +} + +uint8_t ChimeServer::GetActiveChimeID() const +{ + return mActiveChimeID; +} + +bool ChimeServer::GetEnabled() const +{ + return mEnabled; +} + +// helper method to get the Chime Sounds one by one and encode into a list +CHIP_ERROR ChimeServer::EncodeSupportedChimeSounds(const AttributeValueEncoder::ListEncodeHelper & encoder) +{ + + for (uint8_t i = 0; true; i++) + { + ChimeSoundStructType chimeSound; + + // Get the chime sound + // We pass in a MutableCharSpan to avoid any ownership issues - Delegate needs to use + // CopyCharSpanToMutableCharSpan to copy data in + char buffer[kMaxChimeSoundNameSize]; + MutableCharSpan name(buffer); + auto err = mDelegate.GetChimeSoundByIndex(i, chimeSound.chimeID, name); + + // return if we've run off the end of the Chime Sound List on the delegate + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(err); + + // set the name on the struct + chimeSound.name = name; + + // and now encode the struct + ReturnErrorOnFailure(encoder.Encode(chimeSound)); + } + return CHIP_NO_ERROR; +} + +// helper method to check if the chimeID param is supported by the delegate +bool ChimeServer::IsSupportedChimeID(uint8_t chimeID) +{ + uint8_t supportedChimeID; + for (uint8_t i = 0; mDelegate.GetChimeIDByIndex(i, supportedChimeID) != CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; i++) + { + if (supportedChimeID == chimeID) + { + return true; + } + } + + ChipLogDetail(Zcl, "Cannot find a supported ChimeID with value %u", chimeID); + return false; +} + +CHIP_ERROR ChimeServer::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) +{ + VerifyOrDie(aPath.mClusterId == Chime::Id); + Status status; + + switch (aPath.mAttributeId) + { + case ActiveChimeID::Id: { + uint8_t newValue; + ReturnErrorOnFailure(aDecoder.Decode(newValue)); + status = SetActiveChimeID(newValue); + return StatusIB(status).ToChipError(); + } + case Enabled::Id: { + bool newValue; + ReturnErrorOnFailure(aDecoder.Decode(newValue)); + status = SetEnabled(newValue); + return StatusIB(status).ToChipError(); + } + + default: + // Unknown attribute + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } +} + +Status ChimeServer::SetActiveChimeID(uint8_t chimeID) +{ + if (!IsSupportedChimeID(chimeID)) + { + return Protocols::InteractionModel::Status::ConstraintError; + } + + bool activeIDChanged = !(mActiveChimeID == chimeID); + if (activeIDChanged) + { + mActiveChimeID = chimeID; + + // Write new value to persistent storage. + auto endpointId = GetEndpointId(); + ConcreteAttributePath path = ConcreteAttributePath(endpointId, Chime::Id, ActiveChimeID::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mActiveChimeID); + + // and mark as dirty + MatterReportingAttributeChangeCallback(path); + } + return Protocols::InteractionModel::Status::Success; +} + +Status ChimeServer::SetEnabled(bool Enabled) +{ + bool enableChanged = !(mEnabled == Enabled); + + if (enableChanged) + { + mEnabled = Enabled; + + // Write new value to persistent storage. + auto endpointId = GetEndpointId(); + ConcreteAttributePath path = ConcreteAttributePath(endpointId, Chime::Id, Enabled::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mEnabled); + + // and mark as dirty + MatterReportingAttributeChangeCallback(path); + } + + return Protocols::InteractionModel::Status::Success; +} + +void ChimeServer::InvokeCommand(HandlerContext & ctx) +{ + switch (ctx.mRequestPath.mCommandId) + { + case Commands::PlayChimeSound::Id: + CommandHandlerInterface::HandleCommand( + ctx, [this](HandlerContext & ctx, const auto & req) { HandlePlayChimeSound(ctx, req); }); + break; + } +} + +void ChimeServer::HandlePlayChimeSound(HandlerContext & ctx, const Commands::PlayChimeSound::DecodableType & req) +{ + + ChipLogDetail(Zcl, "Chime: PlayChimeSound"); + + // call the delegate to play the chime + Status status = mDelegate.PlayChimeSound(); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); +} + +} // namespace Clusters +} // namespace app +} // namespace chip + +/** @brief Chime Cluster Server Init + * + * Server Init + * + */ +void MatterChimePluginServerInitCallback() {} diff --git a/src/app/clusters/chime-server/chime-server.h b/src/app/clusters/chime-server/chime-server.h new file mode 100644 index 00000000000000..7e98f741987219 --- /dev/null +++ b/src/app/clusters/chime-server/chime-server.h @@ -0,0 +1,178 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { + +class ChimeDelegate; + +class ChimeServer : private AttributeAccessInterface, private CommandHandlerInterface +{ +public: + /** + * Creates a chime server instance. The Init() function needs to be called for this instance to be registered and + * called by the interaction model at the appropriate times. + * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration. + * @param aDelegate A reference to the delegate to be used by this server. + * Note: the caller must ensure that the delegate lives throughout the instance's lifetime. + */ + ChimeServer(EndpointId endpointId, ChimeDelegate & delegate); + ~ChimeServer(); + + /** + * Initialise the chime server instance. + * @return Returns an error if the CommandHandler or AttributeHandler registration fails. + */ + CHIP_ERROR Init(); + + // Attribute Setters + /** + * Sets the ActiveChimeID attribute. Note, this also handles writing the new value into non-volatile storage. + * @param chimeSoundID The value to which the ActiveChimeID is to be set. + * @return Returns a ConstraintError if the chimeSoundID value is not valid. Returns Success otherwise. + */ + Protocols::InteractionModel::Status SetActiveChimeID(uint8_t chimeSoundID); + + /** + * Sets the Enabled attribute. Note, this also handles writing the new value into non-volatile storage. + * @param Enabled The value to which the Enabled is to be set. + */ + Protocols::InteractionModel::Status SetEnabled(bool Enabled); + + // Attribute Getters + /** + * @return The Current ActiveChimeID. + */ + uint8_t GetActiveChimeID() const; + + /** + * @return The Enabled attribute.. + */ + bool GetEnabled() const; + + /** + * @return The endpoint ID. + */ + EndpointId GetEndpointId() { return AttributeAccessInterface::GetEndpointId().Value(); } + + // Cluster constants from the spec + static constexpr uint8_t kMaxChimeSoundNameSize = 48; + + // List Change Reporting + /** + * Reports that the contents of the InstalledChimeSounds attribute have changed. + * The device SHALL call this method whenever it changes the list of installed chime sounds. + */ + void ReportInstalledChimeSoundsChange(); + +private: + ChimeDelegate & mDelegate; + + // Attribute local storage + uint8_t mActiveChimeID; + bool mEnabled; + + // AttributeAccessInterface + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override; + + // CommandHandlerInterface + void InvokeCommand(HandlerContext & ctx) override; + void HandlePlayChimeSound(HandlerContext & ctx, const Chime::Commands::PlayChimeSound::DecodableType & req); + + // Helpers + // Loads all the persistent attributes from the KVS. + void LoadPersistentAttributes(); + + // Checks if the chimeID is supported by the delegate + bool IsSupportedChimeID(uint8_t chimeID); + + // Encodes all Installed Chime Sounds + CHIP_ERROR EncodeSupportedChimeSounds(const AttributeValueEncoder::ListEncodeHelper & encoder); +}; + +/** @brief + * Defines methods for implementing application-specific logic for the Chime Cluster. + */ +class ChimeDelegate +{ +public: + ChimeDelegate() = default; + + virtual ~ChimeDelegate() = default; + + /** + * Get the installed chime sounds. + * @param index The index of the chime sound to be returned. It is assumed that chime sounds are indexable from 0 and with no + * gaps. + * @param chimeID a reference to the uint8_t variable that is to contain the ChimeID value. + * @param name A reference to the mutable char span which will be mutated to receive the chime sound name on success. Use + * CopyCharSpanToMutableCharSpan to copy into the MutableCharSpan. + * @return Returns a CHIP_NO_ERROR if there was no error and the chime sound details were returned successfully, + * CHIP_ERROR_NOT_FOUND if the index in beyond the list of available chime sounds. + * + * Note: This is used by the SDK to populate the InstalledChimeSounds attribute. If the contents of this list change, + * the device SHALL call the Instance's ReportInstalledChimeSoundsChange method to report that this attribute has changed. + */ + virtual CHIP_ERROR GetChimeSoundByIndex(uint8_t chimeIndex, uint8_t & chimeID, MutableCharSpan & name) = 0; + + /** + * Get the installed chime sound IDs + * @param index The index of the chime ID to be returned. It is assumed that chime sounds are indexable from 0 and with no + * gaps. + * @param chimeID a reference to the uint8_t variable that is to contain the ChimeID value. + * @return Returns a CHIP_NO_ERROR if there was no error and the ChimeID was returned successfully, + * CHIP_ERROR_NOT_FOUND if the index in beyond the list of available chime sounds. + * + * Note: This is used by the SDK to help populate the InstalledChimeSounds attribute. If the contents of this list change, + * the device SHALL call the Instance's ReportInstalledChimeSoundsChange method to report that this attribute has changed. + */ + virtual CHIP_ERROR GetChimeIDByIndex(uint8_t chimeIndex, uint8_t & chimeID) = 0; + + // Commands + /** + * @brief Delegate should implement a handler to play the currently active chime sound. + * It should report Status::Success if successful and may + * return other Status codes if it fails + */ + virtual Protocols::InteractionModel::Status PlayChimeSound() = 0; + +protected: + friend class ChimeServer; + + ChimeServer * mChimeServer = nullptr; + + // sets the Chime Server pointer + void SetChimeServer(ChimeServer * chimeServer) { mChimeServer = chimeServer; } + ChimeServer * GetChimeServer() const { return mChimeServer; } +}; + +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp index 2fb542c768aac0..dd320e5d03780a 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp @@ -31,6 +31,7 @@ #include #include #include +#include // separated out for code-reuse #include @@ -40,84 +41,32 @@ namespace chip { namespace app { -namespace { +namespace detail { -/// Handles going through callback-based enumeration of generated/accepted commands -/// for CommandHandlerInterface based items. -/// -/// Offers the ability to focus on some operation for finding a given -/// command id: -/// - FindFirst will return the first found element -/// - FindExact finds the element with the given id -/// - FindNext finds the element following the given id -class EnumeratorCommandFinder -{ -public: - using HandlerCallbackFunction = CHIP_ERROR (CommandHandlerInterface::*)(const ConcreteClusterPath &, - CommandHandlerInterface::CommandIdCallback, void *); - - enum class Operation - { - kFindFirst, // Find the first value in the list - kFindExact, // Find the given value - kFindNext // Find the value AFTER this value - }; - - EnumeratorCommandFinder(HandlerCallbackFunction callback) : - mCallback(callback), mOperation(Operation::kFindFirst), mTarget(kInvalidCommandId) - {} - - /// Find the given command ID that matches the given operation/path. - /// - /// If operation is kFindFirst, then path commandID is ignored. Otherwise it is used as a key to - /// kFindExact or kFindNext. - /// - /// Returns: - /// - std::nullopt if no command found using the command handler interface - /// - kInvalidCommandId if the find failed (but command handler interface does provide a list) - /// - valid id if command handler interface usage succeeds - std::optional FindCommandId(Operation operation, const ConcreteCommandPath & path); - - /// Uses FindCommandId to find the given command and loads the command entry data - std::optional FindCommandEntry(Operation operation, const ConcreteCommandPath & path); - -private: - HandlerCallbackFunction mCallback; - Operation mOperation; - CommandId mTarget; - std::optional mFound = std::nullopt; - - Loop HandlerCallback(CommandId id) - { - switch (mOperation) +Loop EnumeratorCommandFinder::HandlerCallback(CommandId id) +{ + switch (mOperation) + { + case Operation::kFindFirst: + mFound = id; + return Loop::Break; + case Operation::kFindExact: + if (mTarget == id) { - case Operation::kFindFirst: - mFound = id; + mFound = id; // found it return Loop::Break; - case Operation::kFindExact: - if (mTarget == id) - { - mFound = id; // found it - return Loop::Break; - } - break; - case Operation::kFindNext: - if (mTarget == id) - { - // Once we found the ID, get the first - mOperation = Operation::kFindFirst; - } - break; } - return Loop::Continue; // keep searching - } - - static Loop HandlerCallbackFn(CommandId id, void * context) - { - auto self = static_cast(context); - return self->HandlerCallback(id); + break; + case Operation::kFindNext: + if (mTarget == id) + { + // Once we found the ID, get the first + mOperation = Operation::kFindFirst; + } + break; } -}; + return Loop::Continue; // keep searching +} std::optional EnumeratorCommandFinder::FindCommandId(Operation operation, const ConcreteCommandPath & path) { @@ -140,23 +89,43 @@ std::optional EnumeratorCommandFinder::FindCommandId(Operation operat if (err != CHIP_NO_ERROR) { +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING // Report the error here since we lose actual error. This generally should NOT be possible as CommandHandlerInterface // usually returns unimplemented or should just work for our use case (our callback never fails) ChipLogError(DataManagement, "Enumerate error: %" CHIP_ERROR_FORMAT, err.Format()); +#endif return kInvalidCommandId; } return mFound.value_or(kInvalidCommandId); } +} // namespace detail + +using detail::EnumeratorCommandFinder; + +namespace { + +const chip::CommandId * AcceptedCommands(const EmberAfCluster & cluster) +{ + return cluster.acceptedCommandList; +} + +const chip::CommandId * GeneratedCommands(const EmberAfCluster & cluster) +{ + return cluster.generatedCommandList; +} + /// Load the cluster information into the specified destination std::variant LoadClusterInfo(const ConcreteClusterPath & path, const EmberAfCluster & cluster) { DataVersion * versionPtr = emberAfDataVersionStorage(path); if (versionPtr == nullptr) { +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING ChipLogError(AppServer, "Failed to get data version for %d/" ChipLogFormatMEI, static_cast(path.mEndpointId), ChipLogValueMEI(cluster.clusterId)); +#endif return CHIP_ERROR_NOT_FOUND; } @@ -211,7 +180,7 @@ DataModel::ClusterEntry FirstServerClusterEntry(EndpointId endpointId, const Emb return *entryValue; } -#if CHIP_ERROR_LOGGING +#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING if (CHIP_ERROR * errValue = std::get_if(&entry)) { ChipLogError(AppServer, "Failed to load cluster entry: %" CHIP_ERROR_FORMAT, errValue->Format()); @@ -278,20 +247,6 @@ DataModel::CommandEntry CommandEntryFrom(const ConcreteClusterPath & clusterPath return entry; } -std::optional EnumeratorCommandFinder::FindCommandEntry(Operation operation, - const ConcreteCommandPath & path) -{ - - std::optional id = FindCommandId(operation, path); - - if (!id.has_value()) - { - return std::nullopt; - } - - return (*id == kInvalidCommandId) ? DataModel::CommandEntry::kInvalid : CommandEntryFrom(path, *id); -} - // TODO: DeviceTypeEntry content is IDENTICAL to EmberAfDeviceType, so centralizing // to a common type is probably better. Need to figure out dependencies since // this would make ember return datamodel-provider types. @@ -571,7 +526,7 @@ std::optional CodegenDataModelProvider::GetClusterInfo(c if (CHIP_ERROR * err = std::get_if(&info)) { -#if CHIP_ERROR_LOGGING +#if CHIP_ERROR_LOGGING && CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING ChipLogError(AppServer, "Failed to load cluster info: %" CHIP_ERROR_FORMAT, err->Format()); #else (void) err->Format(); @@ -635,6 +590,35 @@ const EmberAfCluster * CodegenDataModelProvider::FindServerCluster(const Concret return cluster; } +CommandId CodegenDataModelProvider::FindCommand(const ConcreteCommandPath & path, detail::EnumeratorCommandFinder & handlerFinder, + detail::EnumeratorCommandFinder::Operation operation, + CodegenDataModelProvider::EmberCommandListIterator & emberIterator, + CommandListGetter commandListGetter) +{ + + std::optional handlerCommandId = handlerFinder.FindCommandId(operation, path); + if (handlerCommandId.has_value()) + { + return *handlerCommandId; + } + + const EmberAfCluster * cluster = FindServerCluster(path); + VerifyOrReturnValue(cluster != nullptr, kInvalidCommandId); + + const CommandId * commandList = commandListGetter(*cluster); + + switch (operation) + { + case EnumeratorCommandFinder::Operation::kFindFirst: + return emberIterator.First(commandList).value_or(kInvalidCommandId); + case EnumeratorCommandFinder::Operation::kFindNext: + return emberIterator.Next(commandList, path.mCommandId).value_or(kInvalidCommandId); + case EnumeratorCommandFinder::Operation::kFindExact: + default: + return emberIterator.Exists(commandList, path.mCommandId) ? path.mCommandId : kInvalidCommandId; + } +} + DataModel::AttributeEntry CodegenDataModelProvider::NextAttribute(const ConcreteAttributePath & before) { const EmberAfCluster * cluster = FindServerCluster(before); @@ -682,106 +666,58 @@ std::optional CodegenDataModelProvider::GetAttributeIn DataModel::CommandEntry CodegenDataModelProvider::FirstAcceptedCommand(const ConcreteClusterPath & path) { - auto handlerInterfaceValue = EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateAcceptedCommands) - .FindCommandEntry(EnumeratorCommandFinder::Operation::kFindFirst, - ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId)); - - if (handlerInterfaceValue.has_value()) - { - return *handlerInterfaceValue; - } - - const EmberAfCluster * cluster = FindServerCluster(path); + EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands); - VerifyOrReturnValue(cluster != nullptr, DataModel::CommandEntry::kInvalid); + CommandId commandId = + FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder, + detail::EnumeratorCommandFinder::Operation::kFindFirst, mAcceptedCommandsIterator, AcceptedCommands); - std::optional commandId = mAcceptedCommandsIterator.First(cluster->acceptedCommandList); - VerifyOrReturnValue(commandId.has_value(), DataModel::CommandEntry::kInvalid); - - return CommandEntryFrom(path, *commandId); + VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid); + return CommandEntryFrom(path, commandId); } DataModel::CommandEntry CodegenDataModelProvider::NextAcceptedCommand(const ConcreteCommandPath & before) { - // TODO: `Next` redirecting to a callback is slow O(n^2). - // see https://github.com/project-chip/connectedhomeip/issues/35790 - auto handlerInterfaceValue = EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateAcceptedCommands) - .FindCommandEntry(EnumeratorCommandFinder::Operation::kFindNext, before); - - if (handlerInterfaceValue.has_value()) - { - return *handlerInterfaceValue; - } - - const EmberAfCluster * cluster = FindServerCluster(before); - VerifyOrReturnValue(cluster != nullptr, DataModel::CommandEntry::kInvalid); + EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands); + CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext, + mAcceptedCommandsIterator, AcceptedCommands); - std::optional commandId = mAcceptedCommandsIterator.Next(cluster->acceptedCommandList, before.mCommandId); - VerifyOrReturnValue(commandId.has_value(), DataModel::CommandEntry::kInvalid); - - return CommandEntryFrom(before, *commandId); + VerifyOrReturnValue(commandId != kInvalidCommandId, DataModel::CommandEntry::kInvalid); + return CommandEntryFrom(before, commandId); } std::optional CodegenDataModelProvider::GetAcceptedCommandInfo(const ConcreteCommandPath & path) { - auto handlerInterfaceValue = EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateAcceptedCommands) - .FindCommandEntry(EnumeratorCommandFinder::Operation::kFindExact, path); - if (handlerInterfaceValue.has_value()) - { - return handlerInterfaceValue->IsValid() ? std::make_optional(handlerInterfaceValue->info) : std::nullopt; - } - - const EmberAfCluster * cluster = FindServerCluster(path); + EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateAcceptedCommands); + CommandId commandId = FindCommand(path, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindExact, + mAcceptedCommandsIterator, AcceptedCommands); - VerifyOrReturnValue(cluster != nullptr, std::nullopt); - VerifyOrReturnValue(mAcceptedCommandsIterator.Exists(cluster->acceptedCommandList, path.mCommandId), std::nullopt); - - return CommandEntryFrom(path, path.mCommandId).info; + VerifyOrReturnValue(commandId != kInvalidCommandId, std::nullopt); + return CommandEntryFrom(path, commandId).info; } ConcreteCommandPath CodegenDataModelProvider::FirstGeneratedCommand(const ConcreteClusterPath & path) { - std::optional commandId = - EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateGeneratedCommands) - .FindCommandId(EnumeratorCommandFinder::Operation::kFindFirst, - ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId)); - if (commandId.has_value()) - { - return *commandId == kInvalidCommandId ? kInvalidCommandPath - : ConcreteCommandPath(path.mEndpointId, path.mClusterId, *commandId); - } + EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands); + CommandId commandId = + FindCommand(ConcreteCommandPath(path.mEndpointId, path.mClusterId, kInvalidCommandId), handlerFinder, + detail::EnumeratorCommandFinder::Operation::kFindFirst, mGeneratedCommandsIterator, GeneratedCommands); - const EmberAfCluster * cluster = FindServerCluster(path); - VerifyOrReturnValue(cluster != nullptr, kInvalidCommandPath); - - commandId = mGeneratedCommandsIterator.First(cluster->generatedCommandList); - VerifyOrReturnValue(commandId.has_value(), kInvalidCommandPath); - return ConcreteCommandPath(path.mEndpointId, path.mClusterId, *commandId); + VerifyOrReturnValue(commandId != kInvalidCommandId, kInvalidCommandPath); + return ConcreteCommandPath(path.mEndpointId, path.mClusterId, commandId); } ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand(const ConcreteCommandPath & before) { - // TODO: `Next` redirecting to a callback is slow O(n^2). - // see https://github.com/project-chip/connectedhomeip/issues/35790 - auto nextId = EnumeratorCommandFinder(&CommandHandlerInterface::EnumerateGeneratedCommands) - .FindCommandId(EnumeratorCommandFinder::Operation::kFindNext, before); - - if (nextId.has_value()) - { - return (*nextId == kInvalidCommandId) ? kInvalidCommandPath - : ConcreteCommandPath(before.mEndpointId, before.mClusterId, *nextId); - } - - const EmberAfCluster * cluster = FindServerCluster(before); - - VerifyOrReturnValue(cluster != nullptr, kInvalidCommandPath); + EnumeratorCommandFinder handlerFinder(&CommandHandlerInterface::EnumerateGeneratedCommands); - std::optional commandId = mGeneratedCommandsIterator.Next(cluster->generatedCommandList, before.mCommandId); - VerifyOrReturnValue(commandId.has_value(), kInvalidCommandPath); + CommandId commandId = FindCommand(before, handlerFinder, detail::EnumeratorCommandFinder::Operation::kFindNext, + mGeneratedCommandsIterator, GeneratedCommands); - return ConcreteCommandPath(before.mEndpointId, before.mClusterId, *commandId); + VerifyOrReturnValue(commandId != kInvalidCommandId, kInvalidCommandPath); + return ConcreteCommandPath(before.mEndpointId, before.mClusterId, commandId); } std::optional CodegenDataModelProvider::FirstDeviceType(EndpointId endpoint) diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider.h b/src/app/codegen-data-model-provider/CodegenDataModelProvider.h index 085ae67ec392ab..5c87e264fd2aea 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider.h +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider.h @@ -16,14 +16,71 @@ */ #pragma once -#include "app/data-model-provider/ActionReturnStatus.h" +#include "app/ConcreteCommandPath.h" #include +#include +#include #include namespace chip { namespace app { +namespace detail { + +/// Handles going through callback-based enumeration of generated/accepted commands +/// for CommandHandlerInterface based items. +/// +/// Offers the ability to focus on some operation for finding a given +/// command id: +/// - FindFirst will return the first found element +/// - FindExact finds the element with the given id +/// - FindNext finds the element following the given id +class EnumeratorCommandFinder +{ +public: + using HandlerCallbackFunction = CHIP_ERROR (CommandHandlerInterface::*)(const ConcreteClusterPath &, + CommandHandlerInterface::CommandIdCallback, void *); + + enum class Operation + { + kFindFirst, // Find the first value in the list + kFindExact, // Find the given value + kFindNext // Find the value AFTER this value + }; + + EnumeratorCommandFinder(HandlerCallbackFunction callback) : + mCallback(callback), mOperation(Operation::kFindFirst), mTarget(kInvalidCommandId) + {} + + /// Find the given command ID that matches the given operation/path. + /// + /// If operation is kFindFirst, then path commandID is ignored. Otherwise it is used as a key to + /// kFindExact or kFindNext. + /// + /// Returns: + /// - std::nullopt if no command found using the command handler interface + /// - kInvalidCommandId if the find failed (but command handler interface does provide a list) + /// - valid id if command handler interface usage succeeds + std::optional FindCommandId(Operation operation, const ConcreteCommandPath & path); + +private: + HandlerCallbackFunction mCallback; + Operation mOperation; + CommandId mTarget; + std::optional mFound = std::nullopt; + + Loop HandlerCallback(CommandId id); + + static Loop HandlerCallbackFn(CommandId id, void * context) + { + auto self = static_cast(context); + return self->HandlerCallback(id); + } +}; + +} // namespace detail + /// An implementation of `InteractionModel::Model` that relies on code-generation /// via zap/ember. /// @@ -152,6 +209,12 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider /// Find the index of the given endpoint id std::optional TryFindEndpointIndex(chip::EndpointId id) const; + + using CommandListGetter = const chip::CommandId *(const EmberAfCluster &); + + CommandId FindCommand(const ConcreteCommandPath & path, detail::EnumeratorCommandFinder & handlerFinder, + detail::EnumeratorCommandFinder::Operation operation, + CodegenDataModelProvider::EmberCommandListIterator & emberIterator, CommandListGetter commandListGetter); }; } // namespace app diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp index ea35356391d63d..aa357ce4dfb5cb 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Read.cpp @@ -106,7 +106,7 @@ DataModel::ActionReturnStatus CodegenDataModelProvider::ReadAttribute(const Data // ACL check for non-internal requests if (!request.operationFlags.Has(DataModel::OperationFlags::kInternal)) { - VerifyOrReturnError(request.subjectDescriptor.has_value(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(request.subjectDescriptor != nullptr, CHIP_ERROR_INVALID_ARGUMENT); Access::RequestPath requestPath{ .cluster = request.path.mClusterId, .endpoint = request.path.mEndpointId, diff --git a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp index 51807fe98cf47d..de2f8886476707 100644 --- a/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp +++ b/src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp @@ -159,7 +159,7 @@ DataModel::ActionReturnStatus CodegenDataModelProvider::WriteAttribute(const Dat if (checkAcl) { - VerifyOrReturnError(request.subjectDescriptor.has_value(), Status::UnsupportedAccess); + VerifyOrReturnError(request.subjectDescriptor != nullptr, Status::UnsupportedAccess); Access::RequestPath requestPath{ .cluster = request.path.mClusterId, .endpoint = request.path.mEndpointId, diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml index 61e7c45582268a..f5259748d9421c 100644 --- a/src/app/common/templates/config-data.yaml +++ b/src/app/common/templates/config-data.yaml @@ -36,6 +36,7 @@ CommandHandlerInterfaceOnlyClusters: - RVC Operational State - Sample MEI - Microwave Oven Control + - Chime - Energy EVSE - Energy EVSE Mode - Device Energy Management diff --git a/src/app/common_flags.gni b/src/app/common_flags.gni index d3e7ce34bf0338..e5d5748153d09c 100644 --- a/src/app/common_flags.gni +++ b/src/app/common_flags.gni @@ -40,4 +40,11 @@ declare_args() { # If/once the chip_use_data_model_interface flag is removed or does not support # a `check` option, this should alwo be removed chip_data_model_check_die_on_failure = false + + # This controls if more logging is supposed to be enabled into the data models. + # This is an optimization for small-flash size devices as extra logging requires + # additional flash for messages & code for formatting. + chip_data_model_extra_logging = + current_os == "linux" || current_os == "ios" || current_os == "mac" || + current_os == "android" } diff --git a/src/app/data-model-provider/OperationTypes.h b/src/app/data-model-provider/OperationTypes.h index 6bfffccc655f00..294bf2616435b4 100644 --- a/src/app/data-model-provider/OperationTypes.h +++ b/src/app/data-model-provider/OperationTypes.h @@ -55,7 +55,7 @@ struct OperationRequest /// - operationFlags.Has(OperationFlags::kInternal) MUST NOT have this set /// /// NOTE: once kInternal flag is removed, this will become non-optional - std::optional subjectDescriptor; + const chip::Access::SubjectDescriptor * subjectDescriptor = nullptr; /// Accessing fabric index is the subjectDescriptor fabric index (if any). /// This is a readability convenience function. @@ -63,7 +63,8 @@ struct OperationRequest /// Returns kUndefinedFabricIndex if no subject descriptor is available FabricIndex GetAccessingFabricIndex() const { - return subjectDescriptor.has_value() ? subjectDescriptor->fabricIndex : kUndefinedFabricIndex; + VerifyOrReturnValue(subjectDescriptor != nullptr, kUndefinedFabricIndex); + return subjectDescriptor->fabricIndex; } }; diff --git a/src/app/data-model-provider/tests/ReadTesting.h b/src/app/data-model-provider/tests/ReadTesting.h index f7cee20c27123b..e34a83377313ab 100644 --- a/src/app/data-model-provider/tests/ReadTesting.h +++ b/src/app/data-model-provider/tests/ReadTesting.h @@ -136,7 +136,7 @@ class ReadOperation ReadOperation(const ConcreteAttributePath & path) { mRequest.path = path; - mRequest.subjectDescriptor = kDenySubjectDescriptor; + mRequest.subjectDescriptor = &kDenySubjectDescriptor; } ReadOperation(EndpointId endpoint, ClusterId cluster, AttributeId attribute) : @@ -146,7 +146,7 @@ class ReadOperation ReadOperation & SetSubjectDescriptor(const chip::Access::SubjectDescriptor & descriptor) { VerifyOrDie(mState == State::kInitializing); - mRequest.subjectDescriptor = descriptor; + mRequest.subjectDescriptor = &descriptor; return *this; } diff --git a/src/app/data-model-provider/tests/WriteTesting.h b/src/app/data-model-provider/tests/WriteTesting.h index d18fb93fdd70cc..7651ffe37940f3 100644 --- a/src/app/data-model-provider/tests/WriteTesting.h +++ b/src/app/data-model-provider/tests/WriteTesting.h @@ -47,7 +47,7 @@ class WriteOperation WriteOperation(const ConcreteDataAttributePath & path) { mRequest.path = path; - mRequest.subjectDescriptor = kDenySubjectDescriptor; + mRequest.subjectDescriptor = &kDenySubjectDescriptor; } WriteOperation(EndpointId endpoint, ClusterId cluster, AttributeId attribute) : @@ -56,7 +56,7 @@ class WriteOperation WriteOperation & SetSubjectDescriptor(const chip::Access::SubjectDescriptor & descriptor) { - mRequest.subjectDescriptor = descriptor; + mRequest.subjectDescriptor = &descriptor; return *this; } @@ -123,7 +123,11 @@ class WriteOperation AttributeValueDecoder DecoderFor(const T & value) { mTLVReader = ReadEncodedValue(value); - return AttributeValueDecoder(mTLVReader, mRequest.subjectDescriptor.value_or(kDenySubjectDescriptor)); + if (mRequest.subjectDescriptor == nullptr) + { + AttributeValueDecoder(mTLVReader, kDenySubjectDescriptor); + } + return AttributeValueDecoder(mTLVReader, *mRequest.subjectDescriptor); } private: diff --git a/src/app/data-model/DecodableList.h b/src/app/data-model/DecodableList.h index bff54ca532bbde..b05db43c1522bf 100644 --- a/src/app/data-model/DecodableList.h +++ b/src/app/data-model/DecodableList.h @@ -27,6 +27,68 @@ namespace chip { namespace app { namespace DataModel { +namespace detail { + +/* + * Base class of DecodableList to minimize template usage + */ +class DecodableListBase +{ +public: + DecodableListBase() { ClearReader(); } + + /* + * @brief + * + * This call stores a TLV reader positioned on the list this class is to manage. + * + * Specifically, the passed-in reader should be pointing into the list just after + * having called `OpenContainer` on the list element. + */ + void SetReader(const TLV::TLVReader & reader) { mReader = reader; } + + /* + * @brief + * + * This call clears the TLV reader managed by this class, so it can be reused. + */ + void ClearReader() { mReader.Init(nullptr, 0); } + + /* + * Compute the size of the list. This can fail if the TLV is malformed. If + * this succeeds, that does not guarantee that the individual items can be + * successfully decoded; consumers should check Iterator::GetStatus() when + * actually decoding them. If there is no list then the size is considered + * to be zero. + */ + CHIP_ERROR ComputeSize(size_t * size) const + { + if (mReader.GetContainerType() == TLV::kTLVType_NotSpecified) + { + *size = 0; + return CHIP_NO_ERROR; + } + + return mReader.CountRemainingInContainer(size); + } + + CHIP_ERROR Decode(TLV::TLVReader & reader) + { + VerifyOrReturnError(reader.GetType() == TLV::kTLVType_Array, CHIP_ERROR_SCHEMA_MISMATCH); + TLV::TLVType type; + ReturnErrorOnFailure(reader.EnterContainer(type)); + SetReader(reader); + ReturnErrorOnFailure(reader.ExitContainer(type)); + return CHIP_NO_ERROR; + } + +protected: + TLV::TLVReader mReader; + chip::Optional mFabricIndex; +}; + +} // namespace detail + /* * @brief * @@ -47,30 +109,13 @@ namespace DataModel { * */ template -class DecodableList +class DecodableList : public detail::DecodableListBase { public: - DecodableList() { ClearReader(); } + DecodableList() {} static constexpr bool kIsFabricScoped = DataModel::IsFabricScoped::value; - /* - * @brief - * - * This call stores a TLV reader positioned on the list this class is to manage. - * - * Specifically, the passed-in reader should be pointing into the list just after - * having called `OpenContainer` on the list element. - */ - void SetReader(const TLV::TLVReader & reader) { mReader = reader; } - - /* - * @brief - * - * This call clears the TLV reader managed by this class, so it can be reused. - */ - void ClearReader() { mReader.Init(nullptr, 0); } - template ::value, bool> = true> void SetFabricIndex(FabricIndex fabricIndex) { @@ -189,38 +234,6 @@ class DecodableList }; Iterator begin() const { return Iterator(mReader, mFabricIndex); } - - /* - * Compute the size of the list. This can fail if the TLV is malformed. If - * this succeeds, that does not guarantee that the individual items can be - * successfully decoded; consumers should check Iterator::GetStatus() when - * actually decoding them. If there is no list then the size is considered - * to be zero. - */ - CHIP_ERROR ComputeSize(size_t * size) const - { - if (mReader.GetContainerType() == TLV::kTLVType_NotSpecified) - { - *size = 0; - return CHIP_NO_ERROR; - } - - return mReader.CountRemainingInContainer(size); - } - - CHIP_ERROR Decode(TLV::TLVReader & reader) - { - VerifyOrReturnError(reader.GetType() == TLV::kTLVType_Array, CHIP_ERROR_SCHEMA_MISMATCH); - TLV::TLVType type; - ReturnErrorOnFailure(reader.EnterContainer(type)); - SetReader(reader); - ReturnErrorOnFailure(reader.ExitContainer(type)); - return CHIP_NO_ERROR; - } - -private: - TLV::TLVReader mReader; - chip::Optional mFabricIndex; }; } // namespace DataModel diff --git a/src/app/reporting/Read-DataModel.cpp b/src/app/reporting/Read-DataModel.cpp index 9342961cefdc78..584536bdeb9606 100644 --- a/src/app/reporting/Read-DataModel.cpp +++ b/src/app/reporting/Read-DataModel.cpp @@ -47,7 +47,7 @@ DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataMode { readRequest.readFlags.Set(DataModel::ReadFlags::kFabricFiltered); } - readRequest.subjectDescriptor = subjectDescriptor; + readRequest.subjectDescriptor = &subjectDescriptor; readRequest.path = path; DataVersion version = 0; @@ -97,7 +97,9 @@ DataModel::ActionReturnStatus RetrieveClusterData(DataModel::Provider * dataMode if (!status.IsOutOfSpaceEncodingResponse()) { DataModel::ActionReturnStatus::StringStorage storage; +#if CHIP_CONFIG_DATA_MODEL_EXTRA_LOGGING ChipLogError(DataManagement, "Failed to read attribute: %s", status.c_str(storage)); +#endif } return status; } diff --git a/src/app/tests/TestCommissioningWindowManager.cpp b/src/app/tests/TestCommissioningWindowManager.cpp index 5df7a4b6ce861d..297a95e299a30b 100644 --- a/src/app/tests/TestCommissioningWindowManager.cpp +++ b/src/app/tests/TestCommissioningWindowManager.cpp @@ -113,9 +113,8 @@ class TestCommissioningWindowManager : public ::testing::Test static chip::SimpleTestEventTriggerDelegate sSimpleTestEventTriggerDelegate; initParams.testEventTriggerDelegate = &sSimpleTestEventTriggerDelegate; (void) initParams.InitializeStaticResourcesBeforeServerInit(); - // Set a randomized server port(slightly shifted from CHIP_PORT) for testing - initParams.operationalServicePort = - static_cast(initParams.operationalServicePort + chip::Crypto::GetRandU16() % 20); + // Use whatever server port the kernel decides to give us. + initParams.operationalServicePort = 0; ASSERT_EQ(chip::Server::GetInstance().Init(initParams), CHIP_NO_ERROR); diff --git a/src/app/tests/suites/certification/Test_TC_ACE_1_5.yaml b/src/app/tests/suites/certification/Test_TC_ACE_1_5.yaml deleted file mode 100644 index 34faee27709d22..00000000000000 --- a/src/app/tests/suites/certification/Test_TC_ACE_1_5.yaml +++ /dev/null @@ -1,399 +0,0 @@ -# Copyright (c) 2021 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. - -name: 42.1.5. [TC-ACE-1.5] Multi-fabric - -PICS: - - MCORE.ROLE.COMMISSIONEE - - APPDEVICE.S - -config: - nodeId: 0x12344321 - cluster: "Access Control" - endpoint: 0 - payload: - type: char_string - defaultValue: "MT:-24J0AFN00KA0648G00" - discriminator: - type: int16u - defaultValue: 3840 - waitAfterCommissioning: - type: int16u - defaultValue: 5000 - PakeVerifier: - type: octet_string - defaultValue: "hex:b96170aae803346884724fe9a3b287c30330c2a660375d17bb205a8cf1aecb350457f8ab79ee253ab6a8e46bb09e543ae422736de501e3db37d441fe344920d09548e4c18240630c4ff4913c53513839b7c07fcc0627a1b8573a149fcd1fa466cf" - -tests: - - label: "Step 1: TH1 commissions DUT using admin node ID N1" - cluster: "DelayCommands" - command: "WaitForCommissionee" - arguments: - values: - - name: "nodeId" - value: nodeId - - - label: "TH1 reads the fabric index" - cluster: "Operational Credentials" - command: "readAttribute" - attribute: "CurrentFabricIndex" - response: - saveAs: th1FabricIndex - - - label: - "Step 2 & 3: TH1 puts DUT into commissioning mode, TH2 commissions DUT - using admin node ID N2" - verification: | - ./chip-tool pairing open-commissioning-window 1 1 400 2000 3841 - - On TH1(chip-tool) note the manual pairing code for commissioning the TH2 - - [1684416077.831754][118314:118316] CHIP:CTL: Successfully opened pairing window on the device - [1684416077.831763][118314:118316] CHIP:CTL: Manual pairing code: [36283142515] - [1684416077.831771][118314:118316] CHIP:CTL: SetupQRCode: [MT:-24J0IRV010UJE7ZH10] - [1684416077.831791][118314:118316] CHIP:DMG: ICR moving to [AwaitingDe] - - ./chip-tool pairing code 2 36283142515 --commissioner-name beta - - On TH2 (chip-tool) verify the commissioning completed with success - - [1684416247.482777][118352:118354] CHIP:CTL: Successfully finished commissioning step 'Cleanup' - [1684416247.482789][118352:118354] CHIP:TOO: Device commissioning completed with success - [1684416247.482823][118352:118354] CHIP:DMG: ICR moving to [AwaitingDe] - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP - arguments: - values: - - name: "message" - value: "Enter 'y' after success" - - name: "expectedValue" - value: "y" - - - label: "Step 2: TH1 opens the commissioning window on the DUT" - cluster: "Administrator Commissioning" - command: "OpenCommissioningWindow" - timedInteractionTimeoutMs: 10000 - PICS: PICS_SDK_CI_ONLY - arguments: - values: - - name: "CommissioningTimeout" - value: 180 - - name: "PAKEPasscodeVerifier" - value: PakeVerifier - - name: "Discriminator" - value: discriminator - - name: "Iterations" - value: 1000 - - name: "Salt" - value: "SPAKE2P Key Salt" - - - label: "Waiting after opening commissioning window" - cluster: "DelayCommands" - command: "WaitForMs" - PICS: PICS_SDK_CI_ONLY - arguments: - values: - - name: "ms" - value: waitAfterCommissioning - - - label: "Step 3: TH2 commissions DUT using admin node ID N2" - identity: "beta" - cluster: "CommissionerCommands" - command: "PairWithCode" - PICS: PICS_SDK_CI_ONLY - arguments: - values: - - name: "nodeId" - value: nodeId - - name: "payload" - value: payload - - - label: "Wait for the commissioned device to be retrieved for TH2" - identity: beta - cluster: "DelayCommands" - command: "WaitForCommissionee" - PICS: PICS_SDK_CI_ONLY - arguments: - values: - - name: "nodeId" - value: nodeId - - - label: - "Step 4: TH2 reads its fabric index from the Operational Credentials - cluster CurrentFabricIndex attribute" - identity: "beta" - PICS: PICS_SDK_CI_ONLY - cluster: "Operational Credentials" - command: "readAttribute" - attribute: "CurrentFabricIndex" - response: - saveAs: th2FabricIndex - - #Issue https://github.com/CHIP-Specifications/chip-certification-tool/issues/768 - - label: "Step 4: TH2 reads the fabric index" - verification: | - ./chip-tool operationalcredentials read current-fabric-index 2 0 --commissioner-name beta - - On TH2 (chip-tool) note the CurrentFabricIndex for the further use - - [1684416368.885484][118383:118385] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_003E Attribute 0x0000_0005 DataVersion: 3654336520 - [1684416368.885511][118383:118385] CHIP:TOO: CurrentFabricIndex: 2 - [1684416368.885577][118383:118385] CHIP:EM: <<< [E:65212i S:18077 M:83303022 (Ack:184536262)] (S) Msg TX to 2:0000000000000002 [C33E] --- Type 0000:10 (SecureChannel:StandaloneAck) - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP - arguments: - values: - - name: "message" - value: "Enter 'y' after success" - - name: "expectedValue" - value: "y" - - - label: "Read the commissioner node ID from the alpha fabric" - identity: "alpha" - cluster: "CommissionerCommands" - command: "GetCommissionerNodeId" - response: - values: - - name: "nodeId" - saveAs: commissionerNodeIdAlpha - - - label: - "Step 5: TH1 writes DUT Endpoint 0 AccessControl cluster ACL - attribute,value is list of AccessControlEntryStruct containing 2 - elements 1.Struct : a)Fabric Index: 1 b)Privilege field: Administer - (5) c)AuthMode field: CASE (2) d)Subjects field: [N1] e)Targets - field:[{Cluster: AccessControl (0x001f), Endpoint: 0}] 2.struct : - a)Fabric Index: 1 b)Privilege field: View (1) c)AuthMode field: CASE - (2) d)Subjects field: null e)Targets field: [{Cluster: Descriptor - (0x001d), Endpoint: 0}]" - command: "writeAttribute" - attribute: "ACL" - arguments: - value: [ - { - FabricIndex: th1FabricIndex, - Privilege: 5, # administer - AuthMode: 2, # case - Subjects: [commissionerNodeIdAlpha], - Targets: - [{ Cluster: 0x001f, Endpoint: 0, DeviceType: null }], - }, - { - FabricIndex: th1FabricIndex, - Privilege: 1, # view - AuthMode: 2, # case - Subjects: null, - Targets: - [{ Cluster: 0x001d, Endpoint: 0, DeviceType: null }], - }, - ] - - - label: "Read the commissioner node ID from the beta fabric" - PICS: PICS_SDK_CI_ONLY - identity: "beta" - cluster: "CommissionerCommands" - command: "GetCommissionerNodeId" - response: - values: - - name: "nodeId" - saveAs: commissionerNodeIdBeta - - - label: - "Step 6: TH2 writes DUT Endpoint 0 AccessControl cluster ACL - attribute,value is list of AccessControlEntryStruct containing 2 - elements 1.Struct : a)Fabric Index: th2FabricIndex b)Privilege field: - Administer (5) c)AuthMode field: CASE (2) d)Subjects field: [N2] - e)Targets field: [{Cluster: AccessControl (0x001f), Endpoint: 0}]" - identity: beta - PICS: PICS_SDK_CI_ONLY - command: "writeAttribute" - attribute: "ACL" - arguments: - value: [ - { - FabricIndex: th2FabricIndex, - Privilege: 5, # administer - AuthMode: 2, # case - Subjects: [commissionerNodeIdBeta], - Targets: - [{ Cluster: 0x001f, Endpoint: 0, DeviceType: null }], - }, - { - FabricIndex: th2FabricIndex, - Privilege: 1, # view - AuthMode: 2, # case - Subjects: null, - Targets: - [{ Cluster: 0x0028, Endpoint: 0, DeviceType: null }], - }, - ] - - #Issue https://github.com/CHIP-Specifications/chip-certification-tool/issues/768 - - label: "Step 6: TH2 writes ACL giving view privilge for basic cluster" - verification: | - ./chip-tool accesscontrol write acl '[{"fabricIndex": 2, "privilege": 5, "authMode": 2, "subjects": [223344], "targets": [{ "cluster": 31, "endpoint": 0, "deviceType": null }]}, {"fabricIndex": 2, "privilege": 1, "authMode": 2, "subjects": null, "targets": [{ "cluster": 40, "endpoint": 0, "deviceType": null }]}]' 2 0 --commissioner-name beta - - On TH2 (chip-tool) verify the success response for the write function - - [1684416510.660175][118418:118420] CHIP:DMG: StatusIB = - [1684416510.660184][118418:118420] CHIP:DMG: { - [1684416510.660192][118418:118420] CHIP:DMG: status = 0x00 (SUCCESS), - [1684416510.660201][118418:118420] CHIP:DMG: }, - [1684416510.660211][118418:118420] CHIP:DMG: - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP - arguments: - values: - - name: "message" - value: "Enter 'y' after success" - - name: "expectedValue" - value: "y" - - - label: - "Step 7: TH1 reads DUT Endpoint 0 Descriptor cluster DeviceTypeList - attribute" - command: "readAttribute" - cluster: "Descriptor" - attribute: "DeviceTypeList" - - - label: - "Step 8: TH1 reads DUT Endpoint 0 Basic Information cluster VendorID - attribute" - command: "readAttribute" - cluster: "Basic Information" - attribute: "VendorID" - response: - error: UNSUPPORTED_ACCESS - - - label: - "Step 9: TH2 reads DUT Endpoint 0 Descriptor cluster DeviceTypeList - attribute" - identity: "beta" - PICS: PICS_SDK_CI_ONLY - command: "readAttribute" - cluster: "Descriptor" - attribute: "DeviceTypeList" - response: - error: UNSUPPORTED_ACCESS - - #Issue https://github.com/CHIP-Specifications/chip-certification-tool/issues/768 - - label: "Step 9: TH2 reads descriptor cluster - expect UNSUPPORTED_ACCESS" - verification: | - ./chip-tool descriptor read device-type-list 2 0 --commissioner-name beta - - On TH2(chip-tool) verify the UNSUPPORTED_ACCESS (0x7e) response - - [1684416700.274460][118482:118484] CHIP:DMG: StatusIB = - [1684416700.274467][118482:118484] CHIP:DMG: { - [1684416700.274475][118482:118484] CHIP:DMG: status = 0x7e (UNSUPPORTED_ACCESS), - [1684416700.274482][118482:118484] CHIP:DMG: }, - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP - arguments: - values: - - name: "message" - value: "Enter 'y' after success" - - name: "expectedValue" - value: "y" - - - label: - "Step 10: TH2 reads DUT Endpoint 0 Basic Information cluster VendorID - attribute" - identity: "beta" - PICS: PICS_SDK_CI_ONLY - command: "readAttribute" - cluster: "Basic Information" - attribute: "VendorID" - - #Issue https://github.com/CHIP-Specifications/chip-certification-tool/issues/768 - - label: - "Step 10: TH2 reads DUT Endpoint 0 Basic Information cluster VendorID - attribute - expect SUCCESS" - verification: | - ./chip-tool basicinformation read vendor-id 2 0 --commissioner-name beta - - On TH2(chip-tool) verify the success with the Vendor-id - - [1684416789.682243][118505:118507] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0028 Attribute 0x0000_0002 DataVersion: 2033462723 - [1684416789.682271][118505:118507] CHIP:TOO: VendorID: 65521 - [1684416789.682327][118505:118507] CHIP:EM: <<< [E:11340i S:29188 M:208193949 (Ack:232576417)] (S) Msg TX to 2:0000000000000002 [C33E] --- Type 0000:10 (SecureChannel:StandaloneAck) - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP - arguments: - values: - - name: "message" - value: "Enter 'y' after success" - - name: "expectedValue" - value: "y" - - - label: - "Step 11: TH1 resets the ACLs to the default value bywriting DUT - Endpoint 0 AccessControl cluster ACL attribute,value is list of - AccessControlEntryStruct containing 1 elements 1.Struct : a)Fabric - Index: 1 b)Privilege field: Administer (5) c)AuthMode field: CASE (2) - d)Subjects field: [N1] e)Targets field: null" - command: "writeAttribute" - attribute: "ACL" - arguments: - value: [ - { - FabricIndex: 1, - Privilege: 5, # administer - AuthMode: 2, # case - Subjects: [commissionerNodeIdAlpha], - Targets: null, - }, - ] - - - label: - "Step 12: TH1 removes the TH2 fabric by sending the RemoveFabric - command to the DUT with the FabricIndex set to th2FabricIndex" - cluster: "Operational Credentials" - PICS: PICS_SDK_CI_ONLY - command: "RemoveFabric" - arguments: - values: - - name: "FabricIndex" - value: th2FabricIndex - - #Issue https://github.com/CHIP-Specifications/chip-certification-tool/issues/768 - - label: - "Step 12: TH1 removes the TH2 fabric by sending the RemoveFabric - commandto the DUT with the FabricIndex set to th2FabricIndex" - verification: | - ./chip-tool operationalcredentials remove-fabric 2 1 0 - - On TH1(chip-tool) verify the success with the nocresponse with statuscode is success(0) - - [1684416866.004187][118527:118529] CHIP:DMG: Received Command Response Data, Endpoint=0 Cluster=0x0000_003E Command=0x0000_0008 - [1684416866.004214][118527:118529] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_003E Command 0x0000_0008 - [1684416866.004236][118527:118529] CHIP:TOO: NOCResponse: { - [1684416866.004250][118527:118529] CHIP:TOO: statusCode: 0 - [1684416866.004255][118527:118529] CHIP:TOO: fabricIndex: 2 - [1684416866.004259][118527:118529] CHIP:TOO: } - [1684416866.004270][118527:118529] CHIP:DMG: ICR moving to [AwaitingDe] - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_SKIP_SAMPLE_APP - arguments: - values: - - name: "message" - value: "Enter 'y' after success" - - name: "expectedValue" - value: "y" diff --git a/src/app/tests/suites/certification/Test_TC_CNET_4_4.yaml b/src/app/tests/suites/certification/Test_TC_CNET_4_4.yaml deleted file mode 100644 index f020b75bee18dd..00000000000000 --- a/src/app/tests/suites/certification/Test_TC_CNET_4_4.yaml +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright (c) 2023 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 atour sweet -# -# 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. - -name: - 12.4.4. [TC-CNET-4.4] [WiFi] Verification for ScanNetworks command - [DUT-Server] - -PICS: - - CNET.S.F00 - -config: - nodeId: 0x12344321 - cluster: "Network Commissioning" - #PIXIT.CNET.ENDPOINT_WIFI - endpoint: 0 - PIXIT.CNET.WIFI_1ST_ACCESSPOINT_SSID: - type: octet_string - defaultValue: "hex:47524C50726976617465" - -tests: - - label: "Precondition: TH reads FeatureMap attribute from the DUT" - command: "readAttribute" - attribute: "FeatureMap" - response: - value: 1 - constraints: - type: bitmap32 - - - label: - "Step 1a: TH sends ScanNetworks command to the DUT with the SSID field - set to 'null' and Breadcrumb field set to 1" - PICS: CNET.S.C00.Rsp && CNET.S.C01.Tx - command: "ScanNetworks" - arguments: - values: - - name: "SSID" - value: null - - name: "Breadcrumb" - value: 1 - response: - values: - - name: "NetworkingStatus" - constraints: - anyOf: [0, 1, 5, 6, 12] - - name: "DebugText" - constraints: - maxLength: 512 - - name: "WiFiScanResults" - constraints: - type: list - - - label: - "Step 1a: Verify each element in the WiFiScanResults list will have - the following fields:" - verification: | - Via the TH (chip-tool), verify: - -the Security value is in the type of map8 with length range 0 to 254. - -the SSID value is in the ype of octstr with length range 1 to 32. - -the BSSID value is in the type of octstr with length range of 6. - -the Channel is in the type of uint16 with range 0 to 65,535. - -the WiFi Band is in the of type enum8 with a range of -128 to 127. - -the RSSI is in the of type int8 with a range of -120 to 0. - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_USER_PROMPT && CNET.S.C00.Rsp && CNET.S.C01.Tx - arguments: - values: - - name: "message" - value: "Please enter 'y' for success" - - name: "expectedValue" - value: "y" - - - label: - "Step 1b: TH reads Breadcrumb attribute from the General Commissioning - Cluster" - cluster: "General Commissioning" - command: "readAttribute" - attribute: "Breadcrumb" - response: - value: 1 - constraints: - type: int64u - - - label: - "Step 2a: TH sends ScanNetworks Command to the DUT with SSID field set - to PIXIT.CNET.WIFI_1ST_ACCESSPOINT_SSID and Breadcrumb field set to 2" - PICS: CNET.S.C00.Rsp && CNET.S.C01.Tx - command: "ScanNetworks" - arguments: - values: - - name: "SSID" - value: PIXIT.CNET.WIFI_1ST_ACCESSPOINT_SSID - - name: "Breadcrumb" - value: 2 - response: - values: - - name: "NetworkingStatus" - value: 0 - - name: "DebugText" - constraints: - maxLength: 512 - - name: "WiFiScanResults" - constraints: - type: list - - - label: - "Step 2a: Verify each element in the WiFiScanResults list will have - the following fields: " - verification: | - Via the TH (chip-tool), verify: - -the Security value is in the type of map8 with length range 0 to 254. - -the SSID value is in the ype of octstr with length range 1 to 32. - -the BSSID value is in the type of octstr with length range of 6. - -the Channel is in the type of uint16 with range 0 to 65,535. - -the WiFi Band is in the of type enum8 with a range of -128 to 127. - -the RSSI is in the of type int8 with a range of -120 to 0. - cluster: "LogCommands" - command: "UserPrompt" - PICS: PICS_USER_PROMPT && CNET.S.C00.Rsp && CNET.S.C01.Tx - arguments: - values: - - name: "message" - value: "Please enter 'y' for success" - - name: "expectedValue" - value: "y" - - - label: - "Step 2b: TH reads Breadcrumb attribute from the General Commissioning - Cluster" - cluster: "General Commissioning" - command: "readAttribute" - attribute: "Breadcrumb" - response: - value: 2 - constraints: - type: int64u diff --git a/src/app/tests/suites/manualTests.json b/src/app/tests/suites/manualTests.json index 5f7aa99f9b2952..bee33964dfac50 100644 --- a/src/app/tests/suites/manualTests.json +++ b/src/app/tests/suites/manualTests.json @@ -89,7 +89,6 @@ "Test_TC_CNET_4_1", "Test_TC_CNET_4_2", "Test_TC_CNET_4_3", - "Test_TC_CNET_4_4", "Test_TC_CNET_4_5", "Test_TC_CNET_4_6", "Test_TC_CNET_4_9", diff --git a/src/app/tests/test-interaction-model-api.cpp b/src/app/tests/test-interaction-model-api.cpp index b69c4234273cb9..33097d320bc880 100644 --- a/src/app/tests/test-interaction-model-api.cpp +++ b/src/app/tests/test-interaction-model-api.cpp @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "access/SubjectDescriptor.h" #include #include @@ -171,8 +172,13 @@ ActionReturnStatus TestImCustomDataModel::ReadAttribute(const ReadAttributeReque { AttributeEncodeState mutableState(&encoder.GetState()); // provide a state copy to start. - CHIP_ERROR err = ReadSingleClusterData(request.subjectDescriptor.value_or(Access::SubjectDescriptor()), - request.readFlags.Has(ReadFlags::kFabricFiltered), request.path, + Access::SubjectDescriptor subjectDescriptor; + if (request.subjectDescriptor != nullptr) + { + subjectDescriptor = *request.subjectDescriptor; + } + + CHIP_ERROR err = ReadSingleClusterData(subjectDescriptor, request.readFlags.Has(ReadFlags::kFabricFiltered), request.path, TestOnlyAttributeValueEncoderAccessor(encoder).Builder(), &mutableState); // state must survive CHIP_ERRORs as it is used for chunking diff --git a/src/app/zap-templates/zcl/zcl-with-test-extensions.json b/src/app/zap-templates/zcl/zcl-with-test-extensions.json index 0eba8fc692d6d8..39301cdc30cecc 100644 --- a/src/app/zap-templates/zcl/zcl-with-test-extensions.json +++ b/src/app/zap-templates/zcl/zcl-with-test-extensions.json @@ -196,6 +196,7 @@ "MaxPathsPerInvoke" ], "Bridged Device Basic Information": ["ProductAppearance"], + "Chime": ["ActiveChimeID", "Enabled"], "Descriptor": ["ClusterRevision", "FeatureMap"], "Device Energy Management": [ "ESAType", diff --git a/src/app/zap-templates/zcl/zcl.json b/src/app/zap-templates/zcl/zcl.json index 6907cfea0eb19f..c2de6acabf2951 100644 --- a/src/app/zap-templates/zcl/zcl.json +++ b/src/app/zap-templates/zcl/zcl.json @@ -190,6 +190,7 @@ "MaxPathsPerInvoke" ], "Bridged Device Basic Information": ["ProductAppearance"], + "Chime": ["ActiveChimeID", "Enabled"], "Descriptor": ["ClusterRevision", "FeatureMap"], "Device Energy Management": [ "ESAType", diff --git a/src/app/zap_cluster_list.json b/src/app/zap_cluster_list.json index e38e8809616f45..4de99f1b7aaf3d 100644 --- a/src/app/zap_cluster_list.json +++ b/src/app/zap_cluster_list.json @@ -171,7 +171,7 @@ "concentration-measurement-server" ], "CHANNEL_CLUSTER": ["channel-server"], - "CHIME_CLUSTER": [], + "CHIME_CLUSTER": ["chime-server"], "COLOR_CONTROL_CLUSTER": ["color-control-server"], "COMMISSIONER_CONTROL_CLUSTER": ["commissioner-control-server"], "COMMISSIONING_CLUSTER": [], diff --git a/src/controller/data_model/controller-clusters.zap b/src/controller/data_model/controller-clusters.zap index 1a1f8539267101..8c1eba99077039 100644 --- a/src/controller/data_model/controller-clusters.zap +++ b/src/controller/data_model/controller-clusters.zap @@ -2955,6 +2955,25 @@ } ] }, + { + "name": "Chime", + "code": 1366, + "mfgCode": null, + "define": "CHIME_CLUSTER", + "side": "client", + "enabled": 1, + "apiMaturity": "provisional", + "commands": [ + { + "name": "PlayChimeSound", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + } + ] + }, { "name": "Unit Testing", "code": 4294048773, diff --git a/src/controller/tests/data_model/DataModelFixtures.cpp b/src/controller/tests/data_model/DataModelFixtures.cpp index a5533dc51de57c..f007275c9b3bc2 100644 --- a/src/controller/tests/data_model/DataModelFixtures.cpp +++ b/src/controller/tests/data_model/DataModelFixtures.cpp @@ -18,6 +18,7 @@ #include "DataModelFixtures.h" +#include #include #include #include @@ -522,8 +523,13 @@ ActionReturnStatus CustomDataModel::ReadAttribute(const ReadAttributeRequest & r } #endif // CHIP_CONFIG_USE_EMBER_DATA_MODEL && CHIP_CONFIG_USE_DATA_MODEL_INTERFACE - CHIP_ERROR err = ReadSingleClusterData(request.subjectDescriptor.value_or(Access::SubjectDescriptor()), - request.readFlags.Has(ReadFlags::kFabricFiltered), request.path, + Access::SubjectDescriptor subjectDescriptor; + if (request.subjectDescriptor != nullptr) + { + subjectDescriptor = *request.subjectDescriptor; + } + + CHIP_ERROR err = ReadSingleClusterData(subjectDescriptor, request.readFlags.Has(ReadFlags::kFabricFiltered), request.path, TestOnlyAttributeValueEncoderAccessor(encoder).Builder(), &mutableState); // state must survive CHIP_ERRORs as it is used for chunking diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm index 2f75038eda97a2..3868060175bb1b 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm @@ -23,6 +23,7 @@ #import "MTRCluster.h" #import "MTRClusterStateCacheContainer_Internal.h" #import "MTRCluster_Internal.h" +#import "MTRDeviceDataValidation.h" #import "MTRDevice_Internal.h" #import "MTRError_Internal.h" #import "MTREventTLVValueDecoder_Internal.h" diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h index 075ee99da20990..fa766463e3e025 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h @@ -18,7 +18,7 @@ #import "MTRBaseDevice.h" #import -#import "MTRDeviceDataValueDictionary.h" +#import "MTRDefines_Internal.h" #include #include diff --git a/src/darwin/Framework/CHIP/MTRDefines_Internal.h b/src/darwin/Framework/CHIP/MTRDefines_Internal.h index 71a03aa09184c5..ba7d6be51d61f5 100644 --- a/src/darwin/Framework/CHIP/MTRDefines_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDefines_Internal.h @@ -65,6 +65,11 @@ typedef struct {} variable_hidden_by_mtr_hide; // Default timed interaction timeout, in ms, if another one is not provided. #define MTR_DEFAULT_TIMED_INTERACTION_TIMEOUT_MS 10000 +// Useful building block for type-checking machinery. Uses C-style cast so it +// can be used in .m files as well. +#define MTR_SAFE_CAST(object, classname) \ + ([object isKindOfClass:[classname class]] ? (classname *) (object) : nil) + #pragma mark - XPC Defines #define MTR_SIMPLE_REMOTE_XPC_GETTER(XPC_CONNECTION, NAME, TYPE, DEFAULT_VALUE, GETTER_NAME, PREFIX) \ @@ -179,3 +184,15 @@ typedef struct {} variable_hidden_by_mtr_hide; #define MTR_ABSTRACT_METHOD() \ _MTR_ABSTRACT_METHOD_IMPL("%@ or some ancestor must implement %@", self.class, NSStringFromSelector(_cmd)) + +#pragma mark - Typedefs for some commonly used types. + +/** + * A data-value as defined in MTRBaseDevice.h. + */ +typedef NSDictionary * MTRDeviceDataValueDictionary; + +/** + * A response-value as defined in MTRBaseDevice.h. + */ +typedef NSDictionary * MTRDeviceResponseValueDictionary; diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 8e379980578f58..2337d0eae625f5 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -25,7 +25,6 @@ #import "MTRConversion.h" #import "MTRDefines_Internal.h" #import "MTRDeviceController_Internal.h" -#import "MTRDeviceDataValueDictionary.h" #import "MTRDevice_Internal.h" #import "MTRError_Internal.h" #import "MTRLogging_Internal.h" diff --git a/src/darwin/Framework/CHIP/MTRDeviceClusterData.h b/src/darwin/Framework/CHIP/MTRDeviceClusterData.h index d34ada90e9a7f2..2abdc6113b44af 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceClusterData.h +++ b/src/darwin/Framework/CHIP/MTRDeviceClusterData.h @@ -17,7 +17,6 @@ #import #import "MTRDefines_Internal.h" -#import "MTRDeviceDataValueDictionary.h" NS_ASSUME_NONNULL_BEGIN diff --git a/src/darwin/Framework/CHIP/MTRDeviceDataValidation.h b/src/darwin/Framework/CHIP/MTRDeviceDataValidation.h new file mode 100644 index 00000000000000..5d448d8aa4fcba --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceDataValidation.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2024 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. + */ + +#import + +#import "MTRDefines_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +// Returns whether a data-value dictionary is well-formed (in the sense that all +// the types of the objects inside are as expected, so it's actually a valid +// representation of some TLV). Implemented in MTRBaseDevice.mm because that's +// where the pieces needed to implement it are, but declared here so our tests +// can see it. +MTR_EXTERN MTR_TESTABLE BOOL MTRDataValueDictionaryIsWellFormed(MTRDeviceDataValueDictionary value); + +// Returns whether the provided attribute report actually has the right sorts of +// objects in the right places. +MTR_EXTERN MTR_TESTABLE BOOL MTRAttributeReportIsWellFormed(NSArray * report); + +// Returns whether the provided event report actually has the right sorts of +// objects in the right places. +MTR_EXTERN MTR_TESTABLE BOOL MTREventReportIsWellFormed(NSArray * report); + +// Returns whether the provided invoke response actually has the right sorts of +// objects in the right places. +MTR_EXTERN MTR_TESTABLE BOOL MTRInvokeResponseIsWellFormed(NSArray * response); + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDeviceDataValidation.mm b/src/darwin/Framework/CHIP/MTRDeviceDataValidation.mm new file mode 100644 index 00000000000000..55014b453132a3 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceDataValidation.mm @@ -0,0 +1,216 @@ +/** + * Copyright (c) 2024 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. + */ + +#import "MTRDeviceDataValidation.h" + +#import "MTRBaseDevice.h" + +#import "MTRLogging_Internal.h" + +// MTRDataValueDictionaryIsWellFormed lives in MTRBaseDevice.mm, because it uses +// static functions defined in that file. + +#pragma mark - Helpers used by multiple validators + +#define MTR_CHECK_CLASS(className) \ + ^(className * arg) { return MTR_SAFE_CAST(arg, className) != nil; } + +// input is not known to be an NSDictionary yet on entry. +// +// expectedShape maps keys to value validator blocks. +static BOOL MTRDictionaryHasExpectedShape(NSDictionary * input, NSDictionary * expectedShape) +{ + if (!MTR_SAFE_CAST(input, NSDictionary)) { + return NO; + } + + for (id key in expectedShape) { + id value = input[key]; + if (!value) { + return NO; + } + auto validator = static_cast(expectedShape[key]); + if (!validator(value)) { + return NO; + } + } + + return YES; +} + +#pragma mark - Attribute report validation + +static const auto sAttributeDataShape = @{ + MTRAttributePathKey : MTR_CHECK_CLASS(MTRAttributePath), + MTRDataKey : (^(MTRDeviceDataValueDictionary arg) { + return MTRDataValueDictionaryIsWellFormed(arg); + }), +}; + +static const auto sAttributeErrorShape = @{ + MTRAttributePathKey : MTR_CHECK_CLASS(MTRAttributePath), + MTRErrorKey : MTR_CHECK_CLASS(NSError), +}; + +BOOL MTRAttributeReportIsWellFormed(NSArray * report) +{ + if (!MTR_SAFE_CAST(report, NSArray)) { + MTR_LOG_ERROR("Attribute report is not an array: %@", report); + return NO; + } + + for (MTRDeviceResponseValueDictionary item in report) { + // item can be a value report or an error report. + if (!MTRDictionaryHasExpectedShape(item, sAttributeDataShape) && !MTRDictionaryHasExpectedShape(item, sAttributeErrorShape)) { + MTR_LOG_ERROR("Attribute report contains a weird entry: %@", item); + return NO; + } + + // Now we know item is in fact a dictionary, and it has at least one of MTRDataKey and MTRErrorKey. Make sure it's + // not claiming both, which could confuse code that examines it. + if (item[MTRDataKey] != nil && item[MTRErrorKey] != nil) { + MTR_LOG_ERROR("Attribute report contains an entry that claims to be both data and error: %@", item); + return NO; + } + } + + return YES; +} + +#pragma mark - Event report validation + +// MTREventIsHistoricalKey is claimed to be present no matter what, as +// long as MTREventPathKey is present. +static const auto sEventDataShape = @{ + MTREventPathKey : MTR_CHECK_CLASS(MTREventPath), + MTRDataKey : (^(MTRDeviceDataValueDictionary arg) { + return MTRDataValueDictionaryIsWellFormed(arg); + }), + MTREventIsHistoricalKey : MTR_CHECK_CLASS(NSNumber), + MTREventNumberKey : MTR_CHECK_CLASS(NSNumber), + MTREventPriorityKey : MTR_CHECK_CLASS(NSNumber), + MTREventTimeTypeKey : MTR_CHECK_CLASS(NSNumber), +}; + +static const auto sEventErrorShape = @{ + MTREventPathKey : MTR_CHECK_CLASS(MTREventPath), + MTRErrorKey : MTR_CHECK_CLASS(NSError), + MTREventIsHistoricalKey : MTR_CHECK_CLASS(NSNumber), +}; + +BOOL MTREventReportIsWellFormed(NSArray * report) +{ + if (!MTR_SAFE_CAST(report, NSArray)) { + MTR_LOG_ERROR("Event report is not an array: %@", report); + return NO; + } + + for (MTRDeviceResponseValueDictionary item in report) { + // item can be a value report or an error report. + if (!MTRDictionaryHasExpectedShape(item, sEventDataShape) && !MTRDictionaryHasExpectedShape(item, sEventErrorShape)) { + MTR_LOG_ERROR("Event report contains a weird entry: %@", item); + return NO; + } + + // Now we know item is in fact a dictionary, and it has at least one of MTRDataKey and MTRErrorKey. Make sure it's + // not claiming both, which could confuse code that examines it. + if (item[MTRDataKey] != nil && item[MTRErrorKey] != nil) { + MTR_LOG_ERROR("Event report contains an entry that claims to be both data and error: %@", item); + return NO; + } + + if (item[MTRDataKey]) { + // Check well-formedness of our timestamps. Note that we have + // already validated the type of item[MTREventTimeTypeKey]. + uint64_t eventTimeType = [item[MTREventTimeTypeKey] unsignedLongLongValue]; + switch (eventTimeType) { + case MTREventTimeTypeSystemUpTime: { + if (!MTR_SAFE_CAST(item[MTREventSystemUpTimeKey], NSNumber)) { + MTR_LOG_ERROR("Event report claims system uptime timing but does not have the time: %@", item); + return NO; + } + break; + } + case MTREventTimeTypeTimestampDate: { + if (!MTR_SAFE_CAST(item[MTREventTimestampDateKey], NSDate)) { + MTR_LOG_ERROR("Event report claims epoch timing but does not have the time: %@", item); + return NO; + } + break; + } + default: + MTR_LOG_ERROR("Uknown time type for event report: %@", item); + return NO; + } + } + } + + return YES; +} + +#pragma mark - Invoke response validation + +BOOL MTRInvokeResponseIsWellFormed(NSArray * response) +{ + if (!MTR_SAFE_CAST(response, NSArray)) { + MTR_LOG_ERROR("Invoke response is not an array: %@", response); + return NO; + } + + // Input is an array with a single value that must have MTRCommandPathKey. + if (response.count != 1) { + MTR_LOG_ERROR("Invoke response is not an array with exactly one entry: %@", response); + return NO; + } + + MTRDeviceResponseValueDictionary responseValue = response[0]; + + if (!MTR_SAFE_CAST(responseValue, NSDictionary) || !MTR_SAFE_CAST(responseValue[MTRCommandPathKey], MTRCommandPath)) { + MTR_LOG_ERROR("Invoke response is not an array with the right things in it: %@", response); + return NO; + } + + MTRDeviceDataValueDictionary _Nullable data = responseValue[MTRDataKey]; + NSError * _Nullable error = responseValue[MTRErrorKey]; + + if (data != nil && error != nil) { + MTR_LOG_ERROR("Invoke response claims to have both data and error: %@", responseValue); + return NO; + } + + if (error != nil) { + return MTR_SAFE_CAST(error, NSError) != nil; + } + + if (data == nil) { + // This is valid: indicates a success status response. + return YES; + } + + if (!MTRDataValueDictionaryIsWellFormed(data)) { + MTR_LOG_ERROR("Invoke response claims to have data that is not a data-value: %@", data); + return NO; + } + + // Now we know data is a dictionary (in fact a data-value). The only thing + // we promise about it is that it has type MTRStructureValueType. + if (data[MTRTypeKey] != MTRStructureValueType) { + MTR_LOG_ERROR("Invoke response data is not of structure type: %@", data); + return NO; + } + + return YES; +} diff --git a/src/darwin/Framework/CHIP/MTRDeviceDataValueDictionary.h b/src/darwin/Framework/CHIP/MTRDeviceDataValueDictionary.h deleted file mode 100644 index 53a6b2e6f914b7..00000000000000 --- a/src/darwin/Framework/CHIP/MTRDeviceDataValueDictionary.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2024 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. - */ - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * A data-value as defined in MTRBaseDevice.h. - */ -typedef NSDictionary * MTRDeviceDataValueDictionary; - -NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm index f8319f2636351e..12b6250e0bcadb 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm +++ b/src/darwin/Framework/CHIP/MTRDevice_Concrete.mm @@ -31,7 +31,7 @@ #import "MTRDeviceConnectivityMonitor.h" #import "MTRDeviceControllerOverXPC.h" #import "MTRDeviceController_Internal.h" -#import "MTRDeviceDataValueDictionary.h" +#import "MTRDeviceDataValidation.h" #import "MTRDevice_Concrete.h" #import "MTRDevice_Internal.h" #import "MTRError_Internal.h" @@ -1899,8 +1899,13 @@ - (void)_handleAttributeReport:(NSArray *> *)attrib } // BEGIN DRAGON: This is used by the XPC Server to inject reports into local cache and broadcast them -- (void)_injectAttributeReport:(NSArray *> *)attributeReport fromSubscription:(BOOL)isFromSubscription +- (void)_injectAttributeReport:(NSArray *)attributeReport fromSubscription:(BOOL)isFromSubscription { + if (!MTRAttributeReportIsWellFormed(attributeReport)) { + MTR_LOG_ERROR("%@ injected attribute report is not well-formed: %@", self, attributeReport); + return; + } + [_deviceController asyncDispatchToMatterQueue:^{ MTR_LOG("%@ injected attribute report (%p) %@", self, attributeReport, attributeReport); [self _handleReportBegin]; @@ -1911,8 +1916,13 @@ - (void)_injectAttributeReport:(NSArray *> *)attrib } errorHandler:nil]; } -- (void)_injectEventReport:(NSArray *> *)eventReport +- (void)_injectEventReport:(NSArray *)eventReport { + if (!MTREventReportIsWellFormed(eventReport)) { + MTR_LOG_ERROR("%@ injected event report is not well-formed: %@", self, eventReport); + return; + } + // [_deviceController asyncDispatchToMatterQueue:^{ // TODO: This wasn't used previously, not sure why, so keeping it here for thought, but preserving existing behavior dispatch_async(self.queue, ^{ [self _handleEventReport:eventReport]; diff --git a/src/darwin/Framework/CHIP/MTRDevice_Internal.h b/src/darwin/Framework/CHIP/MTRDevice_Internal.h index 4414b3c6133b07..35cc25e26949da 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDevice_Internal.h @@ -22,7 +22,6 @@ #import "MTRAsyncWorkQueue.h" #import "MTRDefines_Internal.h" -#import "MTRDeviceDataValueDictionary.h" #import "MTRDeviceStorageBehaviorConfiguration_Internal.h" NS_ASSUME_NONNULL_BEGIN @@ -176,13 +175,6 @@ MTR_DIRECT_MEMBERS - (void)devicePrivateInternalStateChanged:(MTRDevice *)device internalState:(NSDictionary *)state; @end -// Returns whether a data-value dictionary is well-formed (in the sense that all -// the types of the objects inside are as expected, so it's actually a valid -// representation of some TLV). Implemented in MTRBaseDevice.mm because that's -// where the pieces needed to implement it are, but declared here so our tests -// can see it. -MTR_EXTERN MTR_TESTABLE BOOL MTRDataValueDictionaryIsWellFormed(MTRDeviceDataValueDictionary value); - #pragma mark - Constants static NSString * const kDefaultSubscriptionPoolSizeOverrideKey = @"subscriptionPoolSizeOverride"; diff --git a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm index cee041afbd52cf..8a5f745a1dfc70 100644 --- a/src/darwin/Framework/CHIP/MTRDevice_XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDevice_XPC.mm @@ -41,7 +41,7 @@ #import "MTRDeviceControllerStartupParams_Internal.h" #import "MTRDeviceController_Concrete.h" #import "MTRDeviceController_XPC.h" -#import "MTRDeviceDataValueDictionary.h" +#import "MTRDeviceDataValidation.h" #import "MTRDevice_Concrete.h" #import "MTRDevice_Internal.h" #import "MTRDevice_XPC_Internal.h" @@ -146,22 +146,47 @@ - (nullable NSNumber *)productID // required methods for MTRDeviceDelegates - (oneway void)device:(NSNumber *)nodeID stateChanged:(MTRDeviceState)state { + if (!MTR_SAFE_CAST(nodeID, NSNumber)) { + MTR_LOG_ERROR("%@ invalid device:stateChanged: nodeID: %@", self, nodeID); + return; + } + MTR_LOG("%s", __PRETTY_FUNCTION__); [self _lockAndCallDelegatesWithBlock:^(id delegate) { [delegate device:self stateChanged:state]; }]; } -- (oneway void)device:(NSNumber *)nodeID receivedAttributeReport:(NSArray *> *)attributeReport +- (oneway void)device:(NSNumber *)nodeID receivedAttributeReport:(NSArray *)attributeReport { + if (!MTR_SAFE_CAST(nodeID, NSNumber)) { + MTR_LOG_ERROR("%@ invalid device:receivedAttributeReport: nodeID: %@", self, nodeID); + return; + } + + if (!MTRAttributeReportIsWellFormed(attributeReport)) { + MTR_LOG_ERROR("%@ invalid device:receivedAttributeReport: attributeReport: %@", self, attributeReport); + return; + } + MTR_LOG("%s", __PRETTY_FUNCTION__); [self _lockAndCallDelegatesWithBlock:^(id delegate) { [delegate device:self receivedAttributeReport:attributeReport]; }]; } -- (oneway void)device:(NSNumber *)nodeID receivedEventReport:(NSArray *> *)eventReport +- (oneway void)device:(NSNumber *)nodeID receivedEventReport:(NSArray *)eventReport { + if (!MTR_SAFE_CAST(nodeID, NSNumber)) { + MTR_LOG_ERROR("%@ invalid device:receivedEventReport: nodeID: %@", self, nodeID); + return; + } + + if (!MTREventReportIsWellFormed(eventReport)) { + MTR_LOG_ERROR("%@ invalid device:receivedEventReport: eventReport: %@", self, eventReport); + return; + } + MTR_LOG("%s", __PRETTY_FUNCTION__); [self _lockAndCallDelegatesWithBlock:^(id delegate) { [delegate device:self receivedEventReport:eventReport]; @@ -171,6 +196,11 @@ - (oneway void)device:(NSNumber *)nodeID receivedEventReport:(NSArray delegate) { if ([delegate respondsToSelector:@selector(deviceBecameActive:)]) { @@ -181,6 +211,11 @@ - (oneway void)deviceBecameActive:(NSNumber *)nodeID - (oneway void)deviceCachePrimed:(NSNumber *)nodeID { + if (!MTR_SAFE_CAST(nodeID, NSNumber)) { + MTR_LOG_ERROR("%@ invalid deviceCachePrimed: nodeID: %@", self, nodeID); + return; + } + [self _lockAndCallDelegatesWithBlock:^(id delegate) { if ([delegate respondsToSelector:@selector(deviceCachePrimed:)]) { [delegate deviceCachePrimed:self]; @@ -190,6 +225,11 @@ - (oneway void)deviceCachePrimed:(NSNumber *)nodeID - (oneway void)deviceConfigurationChanged:(NSNumber *)nodeID { + if (!MTR_SAFE_CAST(nodeID, NSNumber)) { + MTR_LOG_ERROR("%@ invalid deviceConfigurationChanged: nodeID: %@", self, nodeID); + return; + } + [self _lockAndCallDelegatesWithBlock:^(id delegate) { if ([delegate respondsToSelector:@selector(deviceConfigurationChanged:)]) { [delegate deviceConfigurationChanged:self]; @@ -197,14 +237,55 @@ - (oneway void)deviceConfigurationChanged:(NSNumber *)nodeID }]; } +static const auto * requiredInternalStateKeys = @[ kMTRDeviceInternalPropertyDeviceState, kMTRDeviceInternalPropertyLastSubscriptionAttemptWait ]; +static const auto * optionalInternalStateKeys = @[ kMTRDeviceInternalPropertyKeyVendorID, kMTRDeviceInternalPropertyKeyProductID, kMTRDeviceInternalPropertyNetworkFeatures, kMTRDeviceInternalPropertyMostRecentReportTime, kMTRDeviceInternalPropertyLastSubscriptionFailureTime ]; + +- (BOOL)_internalState:(NSDictionary *)dictionary hasValidValuesForKeys:(const NSArray *)keys valueRequired:(BOOL)required +{ + // All the keys are NSNumber-valued. + for (NSString * key in keys) { + id value = dictionary[key]; + if (!value) { + if (required) { + MTR_LOG_ERROR("%@ device:internalStateUpdated: handed state with no value for \"%@\": %@", self, key, value); + return NO; + } + + continue; + } + if (!MTR_SAFE_CAST(value, NSNumber)) { + MTR_LOG_ERROR("%@ device:internalStateUpdated: handed state with invalid value for \"%@\": %@", self, key, value); + return NO; + } + } + + return YES; +} + - (oneway void)device:(NSNumber *)nodeID internalStateUpdated:(NSDictionary *)dictionary { + if (!MTR_SAFE_CAST(nodeID, NSNumber)) { + MTR_LOG_ERROR("%@ invalid device:internalStateUpdated: nodeID: %@", self, nodeID); + return; + } + + if (!MTR_SAFE_CAST(dictionary, NSDictionary)) { + MTR_LOG_ERROR("%@ invalid device:internalStateUpdated dictionary: %@", self, dictionary); + return; + } + + VerifyOrReturn([self _internalState:dictionary hasValidValuesForKeys:requiredInternalStateKeys valueRequired:YES]); + VerifyOrReturn([self _internalState:dictionary hasValidValuesForKeys:optionalInternalStateKeys valueRequired:NO]); + [self _setInternalState:dictionary]; MTR_LOG("%@ internal state updated", self); } #pragma mark - Remote Commands +// TODO: Figure out how to validate the return values for the various +// MTR_DEVICE_*_XPC macros below. + MTR_DEVICE_SIMPLE_REMOTE_XPC_GETTER(state, MTRDeviceState, MTRDeviceStateUnknown, getStateWithReply) MTR_DEVICE_SIMPLE_REMOTE_XPC_GETTER(deviceCachePrimed, BOOL, NO, getDeviceCachePrimedWithReply) MTR_DEVICE_SIMPLE_REMOTE_XPC_GETTER(estimatedStartTime, NSDate * _Nullable, nil, getEstimatedStartTimeWithReply) @@ -273,7 +354,34 @@ - (void)_invokeCommandWithEndpointID:(NSNumber *)endpointID expectedValueInterval:expectedValueInterval timedInvokeTimeout:timeout serverSideProcessingTimeout:serverSideProcessingTimeout - completion:completion]; + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + if (values == nil && error == nil) { + MTR_LOG_ERROR("%@ got invoke response for (%@, %@, %@) without values or error", self, endpointID, clusterID, commandID); + completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT]); + return; + } + + if (error != nil && !MTR_SAFE_CAST(error, NSError)) { + MTR_LOG_ERROR("%@ got invoke response for (%@, %@, %@) that has invalid error object: %@", self, endpointID, clusterID, commandID, error); + completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT]); + return; + } + + if (values != nil && !MTRInvokeResponseIsWellFormed(values)) { + MTR_LOG_ERROR("%@ got invoke response for (%@, %@, %@) that has invalid data: %@", self, clusterID, commandID, values, values); + completion(nil, [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT]); + return; + } + + if (values != nil && error != nil) { + MTR_LOG_ERROR("%@ got invoke response for (%@, %@, %@) with both values and error: %@, %@", self, endpointID, clusterID, commandID, values, error); + // Just propagate through the error. + completion(nil, error); + return; + } + + completion(values, error); + }]; } @catch (NSException * exception) { MTR_LOG_ERROR("Exception sending XPC message: %@", exception); completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m index 3fa4ff45449f89..d5faaec24b63b3 100644 --- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m +++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m @@ -27,6 +27,7 @@ #import "MTRCommandPayloadExtensions_Internal.h" #import "MTRDeviceClusterData.h" #import "MTRDeviceControllerLocalTestStorage.h" +#import "MTRDeviceDataValidation.h" #import "MTRDeviceStorageBehaviorConfiguration.h" #import "MTRDeviceTestDelegate.h" #import "MTRDevice_Internal.h" @@ -1505,7 +1506,14 @@ - (void)test017_TestMTRDeviceBasics [device unitTestInjectEventReport:@[ @{ MTREventPathKey : [MTREventPath eventPathWithEndpointID:@(1) clusterID:@(1) eventID:@(1)], MTREventTimeTypeKey : @(MTREventTimeTypeTimestampDate), - MTREventTimestampDateKey : [NSDate date] + MTREventTimestampDateKey : [NSDate date], + MTREventIsHistoricalKey : @(NO), + MTREventPriorityKey : @(MTREventPriorityInfo), + MTREventNumberKey : @(1), // Doesn't matter, in practice + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], + }, } ]]; #endif }; @@ -4139,7 +4147,14 @@ - (void)test037_MTRDeviceMultipleDelegatesGetReports MTREventPathKey : [MTREventPath eventPathWithEndpointID:endpointID clusterID:clusterID eventID:eventID], MTREventTimeTypeKey : @(MTREventTimeTypeTimestampDate), MTREventTimestampDateKey : [NSDate date], - // For unit test no real data is needed, but timestamp is required + MTREventIsHistoricalKey : @(NO), + MTREventPriorityKey : @(MTREventPriorityInfo), + MTREventNumberKey : @(1), // Doesn't matter, in practice + // Empty payload. + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], + }, }; } @@ -5187,6 +5202,306 @@ - (void)test041_AttributeDataValueValidation } } +- (void)test042_AttributeReportWellFormedness +{ + __auto_type * testData = @[ + @{ + @"input" : @[], + @"valid" : @(YES), + }, + @{ + @"input" : @[ + @{ + MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(0) clusterID:@(6) attributeID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRBooleanValueType, + MTRValueKey : @(YES), + }, + }, + ], + @"valid" : @(YES), + }, + @{ + @"input" : @[ + @{ + MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(0) clusterID:@(6) attributeID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRBooleanValueType, + MTRValueKey : @(YES), + }, + }, + @{ + MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(0) clusterID:@(6) attributeID:@(1)], + MTRErrorKey : [NSError errorWithDomain:MTRErrorDomain code:0 userInfo:nil], + }, + ], + @"valid" : @(YES), + }, + @{ + @"input" : @[ + @{ + MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(0) clusterID:@(6) attributeID:@(0)], + }, + @{ + MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(0) clusterID:@(6) attributeID:@(1)], + MTRErrorKey : [NSError errorWithDomain:MTRErrorDomain code:0 userInfo:nil], + }, + ], + // Missing both error and data + @"valid" : @(NO), + }, + @{ + @"input" : @[ + @{ + MTRAttributePathKey : [MTRAttributePath attributePathWithEndpointID:@(0) clusterID:@(6) attributeID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRBooleanValueType, + MTRValueKey : @("abc"), + }, + }, + ], + // Data dictionary is broken. + @"valid" : @(NO), + }, + @{ + @"input" : @ {}, + // Input is not an array. + @"valid" : @(NO), + }, + ]; + + for (NSDictionary * test in testData) { + XCTAssertEqual(MTRAttributeReportIsWellFormed(test[@"input"]), [test[@"valid"] boolValue], + "input: %@", test[@"input"]); + } +} + +- (void)test043_EventReportWellFormedness +{ + __auto_type * testData = @[ + @{ + @"input" : @[ + @{ + MTREventPathKey : [MTREventPath eventPathWithEndpointID:@(0) clusterID:@(6) eventID:@(0)], + MTRErrorKey : [NSError errorWithDomain:MTRErrorDomain code:0 userInfo:nil], + MTREventIsHistoricalKey : @(NO), + }, + ], + @"valid" : @(YES), + }, + @{ + @"input" : @[ + @{ + MTREventPathKey : [MTREventPath eventPathWithEndpointID:@(0) clusterID:@(6) eventID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], // No fields + }, + MTREventNumberKey : @(5), + MTREventPriorityKey : @(MTREventPriorityInfo), + MTREventTimeTypeKey : @(MTREventTimeTypeTimestampDate), + MTREventTimestampDateKey : [NSDate now], + MTREventIsHistoricalKey : @(NO), + }, + ], + @"valid" : @(YES), + }, + @{ + @"input" : @[ + @{ + MTREventPathKey : [MTREventPath eventPathWithEndpointID:@(0) clusterID:@(6) eventID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], // No fields + }, + MTREventNumberKey : @(5), + MTREventPriorityKey : @(MTREventPriorityInfo), + MTREventTimeTypeKey : @(MTREventTimeTypeSystemUpTime), + MTREventSystemUpTimeKey : @(5), + MTREventIsHistoricalKey : @(NO), + }, + ], + @"valid" : @(YES), + }, + @{ + @"input" : @[ + @{ + MTREventPathKey : [MTREventPath eventPathWithEndpointID:@(0) clusterID:@(6) eventID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], // No fields + }, + MTREventNumberKey : @(5), + MTREventPriorityKey : @(MTREventPriorityInfo), + MTREventTimeTypeKey : @(MTREventTimeTypeTimestampDate), + MTREventTimestampDateKey : @(5), + MTREventIsHistoricalKey : @(NO), + }, + ], + // Wrong date type + @"valid" : @(NO), + }, + @{ + @"input" : @[ + @{ + MTREventPathKey : [MTREventPath eventPathWithEndpointID:@(0) clusterID:@(6) eventID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], // No fields + }, + MTREventNumberKey : @("abc"), + MTREventPriorityKey : @(MTREventPriorityInfo), + MTREventTimeTypeKey : @(MTREventTimeTypeSystemUpTime), + MTREventSystemUpTimeKey : @(5), + MTREventIsHistoricalKey : @(NO), + }, + ], + // Wrong type of EventNumber + @"valid" : @(NO), + }, + @{ + @"input" : @[ + @{ + MTREventPathKey : [MTREventPath eventPathWithEndpointID:@(0) clusterID:@(6) eventID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], // No fields + }, + MTREventNumberKey : @(5), + MTREventPriorityKey : @("abc"), + MTREventTimeTypeKey : @(MTREventTimeTypeSystemUpTime), + MTREventSystemUpTimeKey : @(5), + MTREventIsHistoricalKey : @(NO), + }, + ], + // Wrong type of EventPriority + @"valid" : @(NO), + }, + @{ + @"input" : @[ + @{ + MTREventPathKey : [MTREventPath eventPathWithEndpointID:@(0) clusterID:@(6) eventID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], // No fields + }, + MTREventNumberKey : @(5), + MTREventPriorityKey : @(MTREventPriorityInfo), + MTREventTimeTypeKey : @("abc"), + MTREventSystemUpTimeKey : @(5), + MTREventIsHistoricalKey : @(NO), + }, + ], + // Wrong type of EventTimeType + @"valid" : @(NO), + }, + @{ + @"input" : @[ @(5) ], + // Wrong type of data entirely. + @"valid" : @(NO), + }, + @{ + @"input" : @ {}, + // Not even an array. + @"valid" : @(NO), + }, + ]; + + for (NSDictionary * test in testData) { + XCTAssertEqual(MTREventReportIsWellFormed(test[@"input"]), [test[@"valid"] boolValue], + "input: %@", test[@"input"]); + } +} + +- (void)test044_InvokeResponseWellFormedness +{ + __auto_type * testData = @[ + @{ + @"input" : @[ + @{ + MTRCommandPathKey : [MTRCommandPath commandPathWithEndpointID:@(0) clusterID:@(6) commandID:@(0)], + }, + ], + @"valid" : @(YES), + }, + @{ + @"input" : @[ + @{ + MTRCommandPathKey : [MTRCommandPath commandPathWithEndpointID:@(0) clusterID:@(6) commandID:@(0)], + }, + @{ + MTRCommandPathKey : [MTRCommandPath commandPathWithEndpointID:@(0) clusterID:@(6) commandID:@(0)], + }, + ], + // Multiple responses + @"valid" : @(NO), + }, + @{ + @"input" : @[ + @{ + MTRCommandPathKey : [MTRCommandPath commandPathWithEndpointID:@(0) clusterID:@(6) commandID:@(0)], + MTRErrorKey : [NSError errorWithDomain:MTRErrorDomain code:0 userInfo:nil], + }, + ], + @"valid" : @(YES), + }, + @{ + @"input" : @[ + @{ + MTRCommandPathKey : [MTRCommandPath commandPathWithEndpointID:@(0) clusterID:@(6) commandID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], // Empty structure, valid + }, + }, + ], + @"valid" : @(YES), + }, + @{ + @"input" : @[ + @{ + MTRCommandPathKey : [MTRCommandPath commandPathWithEndpointID:@(0) clusterID:@(6) commandID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRStructureValueType, + MTRValueKey : @[], // Empty structure, valid + }, + MTRErrorKey : [NSError errorWithDomain:MTRErrorDomain code:0 userInfo:nil], + }, + ], + // Having both data and error not valid. + @"valid" : @(NO), + }, + @{ + @"input" : @[ + @{ + MTRCommandPathKey : [MTRCommandPath commandPathWithEndpointID:@(0) clusterID:@(6) commandID:@(0)], + MTRDataKey : @ { + MTRTypeKey : MTRUnsignedIntegerValueType, + MTRValueKey : @(5), + }, + }, + ], + // Data is not a struct. + @"valid" : @(NO), + }, + @{ + @"input" : @[ + @{ + MTRCommandPathKey : [MTRCommandPath commandPathWithEndpointID:@(0) clusterID:@(6) commandID:@(0)], + MTRDataKey : @(6), + }, + ], + // Data is not a data-value at all.. + @"valid" : @(NO), + }, + ]; + + for (NSDictionary * test in testData) { + XCTAssertEqual(MTRInvokeResponseIsWellFormed(test[@"input"]), [test[@"valid"] boolValue], + "input: %@", test[@"input"]); + } +} + @end @interface MTRDeviceEncoderTests : XCTestCase diff --git a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m index 721cb09b994e25..071566381cc69c 100644 --- a/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPerControllerStorageTests.m @@ -19,9 +19,9 @@ #import #import +#import "MTRDefines_Internal.h" #import "MTRDeviceClusterData.h" #import "MTRDeviceControllerLocalTestStorage.h" -#import "MTRDeviceDataValueDictionary.h" #import "MTRDeviceStorageBehaviorConfiguration.h" #import "MTRDeviceTestDelegate.h" #import "MTRDevice_Internal.h" diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h index b00507d665a9f0..591110b34ea712 100644 --- a/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h +++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRTestDeclarations.h @@ -18,8 +18,8 @@ #import #import +#import "MTRDefines_Internal.h" #import "MTRDeviceClusterData.h" -#import "MTRDeviceDataValueDictionary.h" #import "MTRDevice_Internal.h" NS_ASSUME_NONNULL_BEGIN diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index a08a406778458e..6a802900d8d30c 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -134,7 +134,8 @@ 5109E9B72CB8B83D0006884B /* MTRDeviceTypeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5109E9B62CB8B83D0006884B /* MTRDeviceTypeTests.m */; }; 5109E9BA2CC1F23E0006884B /* MTRDeviceClusterData.h in Headers */ = {isa = PBXBuildFile; fileRef = 5109E9B82CC1F23E0006884B /* MTRDeviceClusterData.h */; }; 5109E9BB2CC1F23E0006884B /* MTRDeviceClusterData.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5109E9B92CC1F23E0006884B /* MTRDeviceClusterData.mm */; }; - 5109E9BD2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 5109E9BC2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h */; }; + 5109E9C02CCAD64F0006884B /* MTRDeviceDataValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5109E9BE2CCAD64F0006884B /* MTRDeviceDataValidation.h */; }; + 5109E9C12CCAD64F0006884B /* MTRDeviceDataValidation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5109E9BF2CCAD64F0006884B /* MTRDeviceDataValidation.mm */; }; 510A07492A685D3900A9241C /* Matter.apinotes in Headers */ = {isa = PBXBuildFile; fileRef = 510A07482A685D3900A9241C /* Matter.apinotes */; settings = {ATTRIBUTES = (Public, ); }; }; 510CECA8297F72970064E0B3 /* MTROperationalCertificateIssuerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 510CECA6297F72470064E0B3 /* MTROperationalCertificateIssuerTests.m */; }; 5117DD3829A931AE00FFA1AA /* MTROperationalBrowser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */; }; @@ -589,7 +590,8 @@ 5109E9B62CB8B83D0006884B /* MTRDeviceTypeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRDeviceTypeTests.m; sourceTree = ""; }; 5109E9B82CC1F23E0006884B /* MTRDeviceClusterData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceClusterData.h; sourceTree = ""; }; 5109E9B92CC1F23E0006884B /* MTRDeviceClusterData.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceClusterData.mm; sourceTree = ""; }; - 5109E9BC2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceDataValueDictionary.h; sourceTree = ""; }; + 5109E9BE2CCAD64F0006884B /* MTRDeviceDataValidation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceDataValidation.h; sourceTree = ""; }; + 5109E9BF2CCAD64F0006884B /* MTRDeviceDataValidation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceDataValidation.mm; sourceTree = ""; }; 510A07482A685D3900A9241C /* Matter.apinotes */ = {isa = PBXFileReference; lastKnownFileType = text.apinotes; name = Matter.apinotes; path = CHIP/Matter.apinotes; sourceTree = ""; }; 510CECA6297F72470064E0B3 /* MTROperationalCertificateIssuerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTROperationalCertificateIssuerTests.m; sourceTree = ""; }; 5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROperationalBrowser.mm; sourceTree = ""; }; @@ -1425,7 +1427,8 @@ 51565CB32A7AD78D00469F18 /* MTRDeviceControllerStorageDelegate.h */, 5A6FEC9427B5976200F25F42 /* MTRDeviceControllerXPCConnection.h */, 5A6FEC9527B5983000F25F42 /* MTRDeviceControllerXPCConnection.mm */, - 5109E9BC2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h */, + 5109E9BE2CCAD64F0006884B /* MTRDeviceDataValidation.h */, + 5109E9BF2CCAD64F0006884B /* MTRDeviceDataValidation.mm */, 5A6FEC8B27B5609C00F25F42 /* MTRDeviceOverXPC.h */, 5A6FEC9727B5C6AF00F25F42 /* MTRDeviceOverXPC.mm */, 754784632BFE65B70089C372 /* MTRDeviceStorageBehaviorConfiguration.h */, @@ -1707,7 +1710,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 5109E9BD2CC1F25C0006884B /* MTRDeviceDataValueDictionary.h in Headers */, 51D0B1282B617246006E3511 /* MTRServerEndpoint.h in Headers */, 51565CB62A7B0D6600469F18 /* MTRDeviceControllerParameters.h in Headers */, 51565CB42A7AD78D00469F18 /* MTRDeviceControllerStorageDelegate.h in Headers */, @@ -1767,6 +1769,7 @@ 516415FD2B6ACA8300D5CE11 /* MTRServerAccessControl.h in Headers */, 3CF134AB289D8DF70017A19E /* MTRDeviceAttestationInfo.h in Headers */, B2E0D7B2245B0B5C003C5B48 /* MTRManualSetupPayloadParser.h in Headers */, + 5109E9C02CCAD64F0006884B /* MTRDeviceDataValidation.h in Headers */, 3CF134A7289D8ADA0017A19E /* MTRCSRInfo.h in Headers */, 88E07D612B9A89A4005FD53E /* MTRMetricKeys.h in Headers */, 3D4733B32BE2D1DA003DC19B /* MTRUtilities.h in Headers */, @@ -2157,6 +2160,7 @@ 7596A85528788557004DAE0E /* MTRClusters.mm in Sources */, 88EBF8CF27FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.mm in Sources */, 5A6FEC9827B5C6AF00F25F42 /* MTRDeviceOverXPC.mm in Sources */, + 5109E9C12CCAD64F0006884B /* MTRDeviceDataValidation.mm in Sources */, 9BDA2A062C5D9AF800A32BDD /* MTRDevice_Concrete.mm in Sources */, 514654492A72F9DF00904E61 /* MTRDemuxingStorage.mm in Sources */, 1E4D655229C30A8700BC3478 /* MTRCommissionableBrowser.mm in Sources */, diff --git a/src/lwip/lwip.gni b/src/lwip/lwip.gni index 89b4808af8fa5a..cbbf0a4f74c901 100644 --- a/src/lwip/lwip.gni +++ b/src/lwip/lwip.gni @@ -14,7 +14,8 @@ declare_args() { # Have the lwIP library available. - chip_with_lwip = current_os != "zephyr" && current_os != "mbed" + chip_with_lwip = current_os != "zephyr" && current_os != "mbed" && + current_os != "mac" && current_os != "ios" # lwIP platform: standalone, freertos. lwip_platform = "" 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 c23e0e252bbb99..a1caa8052ae71e 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 @@ -38730,98 +38730,6 @@ Protocols::InteractionModel::Status Set(EndpointId endpoint, uint16_t value) namespace Chime { namespace Attributes { -namespace ActiveChimeID { - -Protocols::InteractionModel::Status Get(EndpointId endpoint, uint8_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - Protocols::InteractionModel::Status status = emberAfReadAttribute(endpoint, Clusters::Chime::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(Protocols::InteractionModel::Status::Success == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - *value = Traits::StorageToWorking(temp); - return status; -} - -Protocols::InteractionModel::Status Set(EndpointId endpoint, uint8_t value, MarkAttributeDirty markDirty) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(ConcreteAttributePath(endpoint, Clusters::Chime::Id, Id), - EmberAfWriteDataInput(writable, ZCL_INT8U_ATTRIBUTE_TYPE).SetMarkDirty(markDirty)); -} - -Protocols::InteractionModel::Status Set(EndpointId endpoint, uint8_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::Chime::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); -} - -} // namespace ActiveChimeID - -namespace Enabled { - -Protocols::InteractionModel::Status Get(EndpointId endpoint, bool * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - Protocols::InteractionModel::Status status = emberAfReadAttribute(endpoint, Clusters::Chime::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(Protocols::InteractionModel::Status::Success == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - *value = Traits::StorageToWorking(temp); - return status; -} - -Protocols::InteractionModel::Status Set(EndpointId endpoint, bool value, MarkAttributeDirty markDirty) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(ConcreteAttributePath(endpoint, Clusters::Chime::Id, Id), - EmberAfWriteDataInput(writable, ZCL_BOOLEAN_ATTRIBUTE_TYPE).SetMarkDirty(markDirty)); -} - -Protocols::InteractionModel::Status Set(EndpointId endpoint, bool value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return Protocols::InteractionModel::Status::ConstraintError; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::Chime::Id, Id, writable, ZCL_BOOLEAN_ATTRIBUTE_TYPE); -} - -} // namespace Enabled - namespace FeatureMap { Protocols::InteractionModel::Status Get(EndpointId endpoint, uint32_t * value) 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 11e2228564e6ab..bb11fdf51607d1 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 @@ -5861,18 +5861,6 @@ Protocols::InteractionModel::Status Set(EndpointId endpoint, uint16_t value, Mar namespace Chime { namespace Attributes { -namespace ActiveChimeID { -Protocols::InteractionModel::Status Get(EndpointId endpoint, uint8_t * value); // int8u -Protocols::InteractionModel::Status Set(EndpointId endpoint, uint8_t value); -Protocols::InteractionModel::Status Set(EndpointId endpoint, uint8_t value, MarkAttributeDirty markDirty); -} // namespace ActiveChimeID - -namespace Enabled { -Protocols::InteractionModel::Status Get(EndpointId endpoint, bool * value); // boolean -Protocols::InteractionModel::Status Set(EndpointId endpoint, bool value); -Protocols::InteractionModel::Status Set(EndpointId endpoint, bool value, MarkAttributeDirty markDirty); -} // namespace Enabled - namespace FeatureMap { Protocols::InteractionModel::Status Get(EndpointId endpoint, uint32_t * value); // bitmap32 Protocols::InteractionModel::Status Set(EndpointId endpoint, uint32_t value); diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h index 90be2632818072..430eb0bd3821fd 100644 --- a/zzz_generated/app-common/app-common/zap-generated/callback.h +++ b/zzz_generated/app-common/app-common/zap-generated/callback.h @@ -6930,12 +6930,6 @@ bool emberAfWebRTCTransportRequestorClusterICECandidateCallback( bool emberAfWebRTCTransportRequestorClusterEndCallback( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const chip::app::Clusters::WebRTCTransportRequestor::Commands::End::DecodableType & commandData); -/** - * @brief Chime Cluster PlayChimeSound Command callback (from client) - */ -bool emberAfChimeClusterPlayChimeSoundCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::Chime::Commands::PlayChimeSound::DecodableType & commandData); /** * @brief Commissioner Control Cluster RequestCommissioningApproval Command callback (from client) */