diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7336213ba81a30..4685948fd7bd12 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -119,6 +119,10 @@ jobs: --known-failure app/util/DataModelHandler.h \ --known-failure app/util/ember-compatibility-functions.cpp \ --known-failure app/util/ember-compatibility-functions.h \ + --known-failure app/util/ember-global-attribute-access-interface.cpp \ + --known-failure app/util/ember-global-attribute-access-interface.h \ + --known-failure app/util/ember-io-storage.cpp \ + --known-failure app/util/ember-io-storage.h \ --known-failure app/util/endpoint-config-api.h \ --known-failure app/util/generic-callbacks.h \ --known-failure app/util/generic-callback-stubs.cpp \ diff --git a/.vscode/settings.json b/.vscode/settings.json index bfac435030bb4f..415d4980d00d24 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,18 @@ "${workspaceFolder}/src/lib/**", "${workspaceFolder}/src/system/**", "${workspaceFolder}/third_party/nlassert/repo/include/**", - "${workspaceFolder}/third_party/nlio/repo/include/**" + "${workspaceFolder}/third_party/nlio/repo/include/**", + "${workspaceFolder}/darwin/Framework/CHIP/**", + "${workspaceFolder}/src/messaging/**", + "${workspaceFolder}/src/protocols/**", + "${workspaceFolder}/src/tracing/**", + "${workspaceFolder}/src/transport/**", + "${workspaceFolder}/src/inet/**", + "${workspaceFolder}/src/credentials/**", + "${workspaceFolder}/src/data_model/**", + "${workspaceFolder}/src/app/**", + "${workspaceFolder}/src/crytpo/**", + "${workspaceFolder}/src/platform/**" ], "[cpp]": { "editor.defaultFormatter": "xaver.clang-format" @@ -35,6 +46,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "files.associations": { + "*.mm": "cpp", "iostream": "cpp", "array": "cpp", "atomic": "cpp", diff --git a/config/esp32/components/chip/Kconfig b/config/esp32/components/chip/Kconfig index 5998b220ba2fe5..e2f9dcc099eba6 100644 --- a/config/esp32/components/chip/Kconfig +++ b/config/esp32/components/chip/Kconfig @@ -1232,7 +1232,7 @@ menu "CHIP Device Layer" menu "Message Reliable Protocol Options" config MRP_LOCAL_ACTIVE_RETRY_INTERVAL_FOR_THREAD int "MRP local active retry interval for Thread network in milliseconds" - depends on OPENTHREAD_ENABLED + depends on ENABLE_MATTER_OVER_THREAD range 0 3600000 default 800 help @@ -1240,7 +1240,7 @@ menu "CHIP Device Layer" config MRP_LOCAL_ACTIVE_RETRY_INTERVAL_FOR_WIFI_ETHERNET int "MRP local active retry interval for WIFI or ETHERNET network in milliseconds" - depends on !OPENTHREAD_ENABLED + depends on !ENABLE_MATTER_OVER_THREAD range 0 3600000 default 300 help @@ -1248,7 +1248,7 @@ menu "CHIP Device Layer" config MRP_LOCAL_IDLE_RETRY_INTERVAL_FOR_THREAD int "MRP local idle retry interval for Thread network in milliseconds" - depends on OPENTHREAD_ENABLED + depends on ENABLE_MATTER_OVER_THREAD range 0 3600000 default 800 help @@ -1256,7 +1256,7 @@ menu "CHIP Device Layer" config MRP_LOCAL_IDLE_RETRY_INTERVAL_FOR_WIFI_ETHERNET int "MRP local idle retry interval for WIFI or ETHERNET network in milliseconds" - depends on !OPENTHREAD_ENABLED + depends on !ENABLE_MATTER_OVER_THREAD range 0 3600000 default 500 help @@ -1264,7 +1264,7 @@ menu "CHIP Device Layer" config MRP_RETRY_INTERVAL_SENDER_BOOST_FOR_THREAD int "MRP retransmission delta timeout for Thread network in milliseconds" - depends on OPENTHREAD_ENABLED + depends on ENABLE_MATTER_OVER_THREAD range 0 3600000 default 500 help @@ -1272,7 +1272,7 @@ menu "CHIP Device Layer" config MRP_RETRY_INTERVAL_SENDER_BOOST_FOR_WIFI_ETHERNET int "MRP retransmission delta timeout for WIFI or ETHERNET network in milliseconds" - depends on !OPENTHREAD_ENABLED + depends on !ENABLE_MATTER_OVER_THREAD range 0 3600000 default 0 help diff --git a/examples/chip-tool/commands/common/Command.cpp b/examples/chip-tool/commands/common/Command.cpp index 2c4f1eb3540e9f..1dff5e83f666b5 100644 --- a/examples/chip-tool/commands/common/Command.cpp +++ b/examples/chip-tool/commands/common/Command.cpp @@ -171,7 +171,7 @@ bool HandleNullableOptional(Argument & arg, char * argValue, std::function *>(arg.value); - if (strcmp(argValue, "null") == 0) + if (argValue != nullptr && strncmp(argValue, "null", 4) == 0) { nullable->SetNull(); return true; diff --git a/examples/fabric-admin/.gn b/examples/fabric-admin/.gn new file mode 100644 index 00000000000000..3b11e2ba2e62ee --- /dev/null +++ b/examples/fabric-admin/.gn @@ -0,0 +1,25 @@ +# 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("//build_overrides/build.gni") + +# The location of the build configuration file. +buildconfig = "${build_root}/config/BUILDCONFIG.gn" + +# CHIP uses angle bracket includes. +check_system_includes = true + +default_args = { + import("//args.gni") +} diff --git a/examples/fabric-admin/BUILD.gn b/examples/fabric-admin/BUILD.gn new file mode 100644 index 00000000000000..79e175fb4a70c1 --- /dev/null +++ b/examples/fabric-admin/BUILD.gn @@ -0,0 +1,120 @@ +# 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("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +import("//build_overrides/editline.gni") +import("${chip_root}/build/chip/tools.gni") +import("${chip_root}/examples/fabric-admin/fabric-admin.gni") +import("${chip_root}/src/lib/core/core.gni") + +assert(chip_build_tools) + +config("config") { + include_dirs = [ + ".", + "${chip_root}/examples/common", + "${chip_root}/zzz_generated/app-common/app-common", + "${chip_root}/zzz_generated/chip-tool", + "${chip_root}/src/lib", + ] + + defines = [ "CONFIG_USE_SEPARATE_EVENTLOOP=${config_use_separate_eventloop}" ] + + # Note: CONFIG_USE_LOCAL_STORAGE is tested for via #ifdef, not #if. + if (config_use_local_storage) { + defines += [ "CONFIG_USE_LOCAL_STORAGE" ] + } + + cflags = [ "-Wconversion" ] +} + +static_library("fabric-admin-utils") { + sources = [ + "${chip_root}/src/controller/ExamplePersistentStorage.cpp", + "${chip_root}/src/controller/ExamplePersistentStorage.h", + "${chip_root}/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp", + "${chip_root}/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp", + "commands/clusters/ModelCommand.cpp", + "commands/clusters/ModelCommand.h", + "commands/common/CHIPCommand.cpp", + "commands/common/CHIPCommand.h", + "commands/common/Command.cpp", + "commands/common/Command.h", + "commands/common/Commands.cpp", + "commands/common/Commands.h", + "commands/common/CredentialIssuerCommands.h", + "commands/common/HexConversion.h", + "commands/common/RemoteDataModelLogger.cpp", + "commands/common/RemoteDataModelLogger.h", + "commands/pairing/OpenCommissioningWindowCommand.cpp", + "commands/pairing/OpenCommissioningWindowCommand.h", + "commands/pairing/PairingCommand.cpp", + "commands/pairing/ToTLVCert.cpp", + ] + + deps = [ "${chip_root}/src/app:events" ] + + sources += [ "commands/interactive/InteractiveCommands.cpp" ] + deps += [ + "${chip_root}/examples/common/websocket-server", + "${chip_root}/src/platform/logging:headers", + "${editline_root}:editline", + ] + + if (chip_device_platform == "darwin") { + sources += [ "commands/common/DeviceScanner.cpp" ] + } + + public_deps = [ + "${chip_root}/examples/common/tracing:commandline", + "${chip_root}/src/app/icd/client:handler", + "${chip_root}/src/app/icd/client:manager", + "${chip_root}/src/app/server", + "${chip_root}/src/app/tests/suites/commands/interaction_model", + "${chip_root}/src/controller/data_model", + "${chip_root}/src/credentials:file_attestation_trust_store", + "${chip_root}/src/lib", + "${chip_root}/src/lib/core:types", + "${chip_root}/src/lib/support/jsontlv", + "${chip_root}/src/platform", + "${chip_root}/third_party/inipp", + "${chip_root}/third_party/jsoncpp", + ] + + public_configs = [ ":config" ] + + if (chip_enable_transport_trace) { + public_deps += + [ "${chip_root}/examples/common/tracing:trace_handlers_decoder" ] + } + + output_dir = root_out_dir +} + +executable("fabric-admin") { + sources = [ "main.cpp" ] + + deps = [ + ":fabric-admin-utils", + "${chip_root}/src/platform/logging:force_stdio", + ] + + output_dir = root_out_dir +} + +group("default") { + deps = [ ":fabric-admin" ] +} diff --git a/examples/fabric-admin/args.gni b/examples/fabric-admin/args.gni new file mode 100644 index 00000000000000..83300d797ed08a --- /dev/null +++ b/examples/fabric-admin/args.gni @@ -0,0 +1,34 @@ +# 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("//build_overrides/chip.gni") + +import("${chip_root}/config/standalone/args.gni") + +chip_device_project_config_include = "" +chip_project_config_include = "" +chip_system_project_config_include = "" + +chip_project_config_include_dirs = + [ "${chip_root}/examples/fabric-admin/include" ] +chip_project_config_include_dirs += [ "${chip_root}/config/standalone" ] + +matter_enable_tracing_support = true + +matter_log_json_payload_hex = true +matter_log_json_payload_decode_full = true + +# make fabric-admin very strict by default +chip_tlv_validate_char_string_on_read = true +chip_tlv_validate_char_string_on_write = true diff --git a/examples/fabric-admin/build_overrides b/examples/fabric-admin/build_overrides new file mode 120000 index 00000000000000..b430cf6a2e6391 --- /dev/null +++ b/examples/fabric-admin/build_overrides @@ -0,0 +1 @@ +../build_overrides \ No newline at end of file diff --git a/examples/fabric-admin/commands/clusters/ClusterCommand.h b/examples/fabric-admin/commands/clusters/ClusterCommand.h new file mode 100644 index 00000000000000..4865f056e135f4 --- /dev/null +++ b/examples/fabric-admin/commands/clusters/ClusterCommand.h @@ -0,0 +1,196 @@ +/* + * 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 "DataModelLogger.h" +#include "ModelCommand.h" + +class ClusterCommand : public InteractionModelCommands, public ModelCommand, public chip::app::CommandSender::Callback +{ +public: + ClusterCommand(CredentialIssuerCommands * credsIssuerConfig) : + InteractionModelCommands(this), ModelCommand("command-by-id", credsIssuerConfig) + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterId); + AddByIdArguments(); + AddArguments(); + } + + ClusterCommand(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + InteractionModelCommands(this), ModelCommand("command-by-id", credsIssuerConfig), mClusterId(clusterId) + { + AddByIdArguments(); + AddArguments(); + } + + ~ClusterCommand() {} + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + return InteractionModelCommands::SendCommand(device, endpointIds.at(0), mClusterId, mCommandId, mPayload); + } + + template + CHIP_ERROR SendCommand(chip::DeviceProxy * device, chip::EndpointId endpointId, chip::ClusterId clusterId, + chip::CommandId commandId, const T & value) + { + return InteractionModelCommands::SendCommand(device, endpointId, clusterId, commandId, value); + } + + CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex) override + { + return InteractionModelCommands::SendGroupCommand(groupId, fabricIndex, mClusterId, mCommandId, mPayload); + } + + template + CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex, chip::ClusterId clusterId, + chip::CommandId commandId, const T & value) + { + return InteractionModelCommands::SendGroupCommand(groupId, fabricIndex, clusterId, commandId, value); + } + + /////////// CommandSender Callback Interface ///////// + virtual void OnResponse(chip::app::CommandSender * client, const chip::app::ConcreteCommandPath & path, + const chip::app::StatusIB & status, chip::TLV::TLVReader * data) override + { + CHIP_ERROR error = status.ToChipError(); + if (CHIP_NO_ERROR != error) + { + LogErrorOnFailure(RemoteDataModelLogger::LogErrorAsJSON(path, status)); + + ChipLogError(NotSpecified, "Response Failure: %s", chip::ErrorStr(error)); + mError = error; + return; + } + + if (data != nullptr) + { + LogErrorOnFailure(RemoteDataModelLogger::LogCommandAsJSON(path, data)); + + error = DataModelLogger::LogCommand(path, data); + if (CHIP_NO_ERROR != error) + { + ChipLogError(NotSpecified, "Response Failure: Can not decode Data"); + mError = error; + return; + } + } + } + + virtual void OnError(const chip::app::CommandSender * client, CHIP_ERROR error) override + { + LogErrorOnFailure(RemoteDataModelLogger::LogErrorAsJSON(error)); + + ChipLogProgress(NotSpecified, "Error: %s", chip::ErrorStr(error)); + mError = error; + } + + virtual void OnDone(chip::app::CommandSender * client) override + { + if (mCommandSender.size()) + { + mCommandSender.front().reset(); + mCommandSender.erase(mCommandSender.begin()); + } + + // If the command is repeated N times, wait for all the responses to comes in + // before exiting. + bool shouldStop = true; + if (mRepeatCount.HasValue()) + { + mRepeatCount.SetValue(static_cast(mRepeatCount.Value() - 1)); + shouldStop = mRepeatCount.Value() == 0; + } + + if (shouldStop) + { + SetCommandExitStatus(mError); + } + } + + void Shutdown() override + { + mError = CHIP_NO_ERROR; + ModelCommand::Shutdown(); + } + +protected: + ClusterCommand(const char * commandName, CredentialIssuerCommands * credsIssuerConfig) : + InteractionModelCommands(this), ModelCommand(commandName, credsIssuerConfig) + { + // Subclasses are responsible for calling AddArguments. + } + + void AddByIdArguments() + { + AddArgument("command-id", 0, UINT32_MAX, &mCommandId); + AddArgument("payload", &mPayload, + "The command payload. This should be a JSON-encoded object, with string representations of field ids as keys. " + " The values for the keys are represented as follows, depending on the type:\n" + " * struct: a JSON-encoded object, with field ids as keys.\n" + " * list: a JSON-encoded array of values.\n" + " * null: A literal null.\n" + " * boolean: A literal true or false.\n" + " * unsigned integer: One of:\n" + " a) The number directly, as decimal.\n" + " b) A string starting with \"u:\" followed by decimal digits\n" + " * signed integer: One of:\n" + " a) The number directly, if it's negative.\n" + " b) A string starting with \"s:\" followed by decimal digits\n" + " * single-precision float: A string starting with \"f:\" followed by the number.\n" + " * double-precision float: One of:\n" + " a) The number directly, if it's not an integer.\n" + " b) A string starting with \"d:\" followed by the number.\n" + " * octet string: A string starting with \"hex:\" followed by the hex encoding of the bytes.\n" + " * string: A string with the characters.\n" + "\n" + " An example payload may look like this: '{ \"0x0\": { \"0\": null, \"1\": false }, \"1\": [17, \"u:17\"], " + "\"0x2\": [ -17, \"s:17\", \"s:-17\" ], \"0x3\": \"f:2\", \"0x4\": [ \"d:3\", 4.5 ], \"0x5\": \"hex:ab12\", " + "\"0x6\": \"ab12\" }' and represents:\n" + " Field 0: a struct with two fields, one with value null and one with value false.\n" + " Field 1: A list of unsigned integers.\n" + " Field 2: A list of signed integers.\n" + " Field 3: A single-precision float.\n" + " Field 4: A list of double-precision floats.\n" + " Field 5: A 2-byte octet string.\n" + " Field 6: A 4-char character string."); + } + + void AddArguments() + { + AddArgument("timedInteractionTimeoutMs", 0, UINT16_MAX, &mTimedInteractionTimeoutMs, + "If provided, do a timed invoke with the given timed interaction timeout. See \"7.6.10. Timed Interaction\" in " + "the Matter specification."); + AddArgument("busyWaitForMs", 0, UINT16_MAX, &mBusyWaitForMs, + "If provided, block the main thread processing for the given time right after sending a command."); + AddArgument("suppressResponse", 0, 1, &mSuppressResponse); + AddArgument("repeat-count", 1, UINT16_MAX, &mRepeatCount); + AddArgument("repeat-delay-ms", 0, UINT16_MAX, &mRepeatDelayInMs); + ModelCommand::AddArguments(); + } + +private: + chip::ClusterId mClusterId; + chip::CommandId mCommandId; + + CHIP_ERROR mError = CHIP_NO_ERROR; + CustomArgument mPayload; +}; diff --git a/examples/fabric-admin/commands/clusters/ComplexArgument.h b/examples/fabric-admin/commands/clusters/ComplexArgument.h new file mode 100644 index 00000000000000..954ea1db352b97 --- /dev/null +++ b/examples/fabric-admin/commands/clusters/ComplexArgument.h @@ -0,0 +1,422 @@ +/* + * 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. + * + */ + +/** + * This file allocate/free memory using the chip platform abstractions + * (Platform::MemoryCalloc and Platform::MemoryFree) for hosting a subset of the + * data model internal types until they are consumed by the DataModel::Encode machinery: + * - chip::app:DataModel::List + * - chip::ByteSpan + * - chip::CharSpan + * + * Memory allocation happens during the 'Setup' phase, while memory deallocation happens + * during the 'Finalize' phase. + * + * The 'Finalize' phase during the destructor phase, and if needed, 'Finalize' will call + * the 'Finalize' phase of its descendant. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "JsonParser.h" + +inline constexpr uint8_t kMaxLabelLength = UINT8_MAX; +inline constexpr char kNullString[] = "null"; + +class ComplexArgumentParser +{ +public: + ComplexArgumentParser() {} + + template ::value && !std::is_signed::value && + !std::is_same>, bool>::value, + int> = 0> + static CHIP_ERROR Setup(const char * label, T & request, Json::Value value) + { + if (value.isNumeric()) + { + if (chip::CanCastTo(value.asLargestUInt())) + { + request = static_cast(value.asLargestUInt()); + return CHIP_NO_ERROR; + } + } + else if (value.isString()) + { + // Check for a hex number; JSON does not support those as numbers, + // so they have to be done as strings. And we might as well support + // string-encoded unsigned numbers in general if we're doing that. + bool isHexNotation = strncmp(value.asCString(), "0x", 2) == 0 || strncmp(value.asCString(), "0X", 2) == 0; + + std::stringstream str; + isHexNotation ? str << std::hex << value.asCString() : str << value.asCString(); + uint64_t val; + str >> val; + if (!str.fail() && str.eof() && chip::CanCastTo(val)) + { + request = static_cast(val); + return CHIP_NO_ERROR; + } + } + + ChipLogError(NotSpecified, "Error while encoding %s as an unsigned integer.", label); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + template ::value, bool> = true> + static CHIP_ERROR Setup(const char * label, T & request, Json::Value value) + { + if (!value.isNumeric() || !chip::CanCastTo(value.asLargestInt())) + { + ChipLogError(NotSpecified, "Error while encoding %s as an unsigned integer.", label); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + request = static_cast(value.asLargestInt()); + return CHIP_NO_ERROR; + } + + template ::value, int> = 0> + static CHIP_ERROR Setup(const char * label, T & request, Json::Value value) + { + std::underlying_type_t requestValue; + ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); + + request = static_cast(requestValue); + return CHIP_NO_ERROR; + } + + template + static CHIP_ERROR Setup(const char * label, chip::BitFlags & request, Json::Value & value) + { + T requestValue; + ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); + + request = chip::BitFlags(requestValue); + return CHIP_NO_ERROR; + } + + template + static CHIP_ERROR Setup(const char * label, chip::BitMask & request, Json::Value & value) + { + T requestValue; + ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); + + request = chip::BitMask(requestValue); + return CHIP_NO_ERROR; + } + + template + static CHIP_ERROR Setup(const char * label, chip::Optional & request, Json::Value & value) + { + T requestValue; + ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); + + request = chip::Optional(requestValue); + return CHIP_NO_ERROR; + } + + template + static CHIP_ERROR Setup(const char * label, chip::app::DataModel::Nullable & request, Json::Value & value) + { + if (value.isNull()) + { + request.SetNull(); + return CHIP_NO_ERROR; + } + + T requestValue; + ReturnErrorOnFailure(ComplexArgumentParser::Setup(label, requestValue, value)); + + request = chip::app::DataModel::Nullable(requestValue); + return CHIP_NO_ERROR; + } + + template + static CHIP_ERROR Setup(const char * label, chip::app::DataModel::List & request, Json::Value & value) + { + if (!value.isArray()) + { + ChipLogError(NotSpecified, "Error while encoding %s as an array.", label); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + auto content = static_cast::type *>(chip::Platform::MemoryCalloc(value.size(), sizeof(T))); + VerifyOrReturnError(content != nullptr, CHIP_ERROR_NO_MEMORY); + + Json::ArrayIndex size = value.size(); + for (Json::ArrayIndex i = 0; i < size; i++) + { + char labelWithIndex[kMaxLabelLength]; + // GCC 7.0.1 has introduced some new warnings for snprintf (-Werror=format-truncation) by default. + // This is not particularly useful when using snprintf and especially in this context, so in order + // to disable the warning the %s is constrained to be of max length: (254 - 11 - 2) where: + // - 254 is kMaxLabelLength - 1 (for null) + // - 11 is the maximum length of a %d (-2147483648, 2147483647) + // - 2 is the length for the "[" and "]" characters. + snprintf(labelWithIndex, sizeof(labelWithIndex), "%.241s[%d]", label, i); + ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithIndex, content[i], value[i])); + } + + request = chip::app::DataModel::List(content, value.size()); + return CHIP_NO_ERROR; + } + + static CHIP_ERROR Setup(const char * label, chip::ByteSpan & request, Json::Value & value) + { + if (!value.isString()) + { + ChipLogError(NotSpecified, "Error while encoding %s as an octet string: Not a string.", label); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + auto str = value.asString(); + auto size = str.size(); + uint8_t * buffer = nullptr; + + if (IsStrString(str.c_str())) + { + // Skip the prefix + str.erase(0, kStrStringPrefixLen); + size = str.size(); + + buffer = static_cast(chip::Platform::MemoryCalloc(size, sizeof(uint8_t))); + VerifyOrReturnError(buffer != nullptr, CHIP_ERROR_NO_MEMORY); + + memcpy(buffer, str.c_str(), size); + } + else + { + if (IsHexString(str.c_str())) + { + // Skip the prefix + str.erase(0, kHexStringPrefixLen); + size = str.size(); + } + + CHIP_ERROR err = HexToBytes( + chip::CharSpan(str.c_str(), size), + [&buffer](size_t allocSize) { + buffer = static_cast(chip::Platform::MemoryCalloc(allocSize, sizeof(uint8_t))); + return buffer; + }, + &size); + + if (err != CHIP_NO_ERROR) + { + if (buffer != nullptr) + { + chip::Platform::MemoryFree(buffer); + } + + return err; + } + } + + request = chip::ByteSpan(buffer, size); + return CHIP_NO_ERROR; + } + + static CHIP_ERROR Setup(const char * label, chip::CharSpan & request, Json::Value & value) + { + if (!value.isString()) + { + ChipLogError(NotSpecified, "Error while encoding %s as a string: Not a string.", label); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + size_t size = strlen(value.asCString()); + auto buffer = static_cast(chip::Platform::MemoryCalloc(size, sizeof(char))); + VerifyOrReturnError(buffer != nullptr, CHIP_ERROR_NO_MEMORY); + + memcpy(buffer, value.asCString(), size); + + request = chip::CharSpan(buffer, size); + return CHIP_NO_ERROR; + } + + static CHIP_ERROR Setup(const char * label, float & request, Json::Value & value) + { + if (!value.isNumeric()) + { + ChipLogError(NotSpecified, "Error while encoding %s as a float: Not a number.", label); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + request = static_cast(value.asFloat()); + return CHIP_NO_ERROR; + } + + static CHIP_ERROR Setup(const char * label, double & request, Json::Value & value) + { + if (!value.isNumeric()) + { + ChipLogError(NotSpecified, "Error while encoding %s as a double: Not a number.", label); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + request = static_cast(value.asDouble()); + return CHIP_NO_ERROR; + } + + static CHIP_ERROR Setup(const char * label, bool & request, Json::Value & value) + { + if (!value.isBool()) + { + ChipLogError(NotSpecified, "Error while encoding %s as a boolean: Not a boolean.", label); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + request = value.asBool(); + return CHIP_NO_ERROR; + } + + static CHIP_ERROR EnsureMemberExist(const char * label, const char * memberName, bool hasMember) + { + if (hasMember) + { + return CHIP_NO_ERROR; + } + + ChipLogError(NotSpecified, "%s is required. Should be provided as {\"%s\": value}", label, memberName); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + static CHIP_ERROR EnsureNoMembersRemaining(const char * label, const Json::Value & value) + { + auto remainingFields = value.getMemberNames(); + if (remainingFields.size() == 0) + { + return CHIP_NO_ERROR; + } +#if CHIP_ERROR_LOGGING + for (auto & field : remainingFields) + { + ChipLogError(NotSpecified, "Unexpected field name: '%s.%s'", label, field.c_str()); + } +#endif // CHIP_ERROR_LOGGING + return CHIP_ERROR_INVALID_ARGUMENT; + } + + template + static void Finalize(T & request) + { + // Nothing to do + } + + template + static void Finalize(chip::Optional & request) + { + VerifyOrReturn(request.HasValue()); + ComplexArgumentParser::Finalize(request.Value()); + } + + template + static void Finalize(chip::app::DataModel::Nullable & request) + { + VerifyOrReturn(!request.IsNull()); + ComplexArgumentParser::Finalize(request.Value()); + } + + static void Finalize(chip::ByteSpan & request) + { + VerifyOrReturn(request.data() != nullptr); + chip::Platform::MemoryFree(reinterpret_cast(const_cast(request.data()))); + } + + static void Finalize(chip::CharSpan & request) + { + VerifyOrReturn(request.data() != nullptr); + chip::Platform::MemoryFree(reinterpret_cast(const_cast(request.data()))); + } + + template + static void Finalize(chip::app::DataModel::List & request) + { + VerifyOrReturn(request.data() != nullptr); + + size_t size = request.size(); + auto data = const_cast::type *>(request.data()); + for (size_t i = 0; i < size; i++) + { + Finalize(data[i]); + } + + chip::Platform::MemoryFree(reinterpret_cast(data)); + } + +#include +}; + +class ComplexArgument +{ +public: + virtual ~ComplexArgument() {} + + virtual CHIP_ERROR Parse(const char * label, const char * json) = 0; + + virtual void Reset() = 0; +}; + +template +class TypedComplexArgument : public ComplexArgument +{ +public: + TypedComplexArgument() {} + TypedComplexArgument(T * request) : mRequest(request) {} + ~TypedComplexArgument() + { + if (mRequest != nullptr) + { + ComplexArgumentParser::Finalize(*mRequest); + } + } + + void SetArgument(T * request) { mRequest = request; }; + + CHIP_ERROR Parse(const char * label, const char * json) + { + Json::Value value; + if (strcmp(kNullString, json) == 0) + { + value = Json::nullValue; + } + else if (!JsonParser::ParseComplexArgument(label, json, value)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + return ComplexArgumentParser::Setup(label, *mRequest, value); + } + + void Reset() { *mRequest = T(); } + +private: + T * mRequest; +}; diff --git a/examples/fabric-admin/commands/clusters/CustomArgument.h b/examples/fabric-admin/commands/clusters/CustomArgument.h new file mode 100644 index 00000000000000..3769c0052236cd --- /dev/null +++ b/examples/fabric-admin/commands/clusters/CustomArgument.h @@ -0,0 +1,297 @@ +/* + * 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 "JsonParser.h" + +namespace { +static constexpr char kPayloadHexPrefix[] = "hex:"; +static constexpr char kPayloadSignedPrefix[] = "s:"; +static constexpr char kPayloadUnsignedPrefix[] = "u:"; +static constexpr char kPayloadFloatPrefix[] = "f:"; +static constexpr char kPayloadDoublePrefix[] = "d:"; +static constexpr size_t kPayloadHexPrefixLen = ArraySize(kPayloadHexPrefix) - 1; // ignore null character +static constexpr size_t kPayloadSignedPrefixLen = ArraySize(kPayloadSignedPrefix) - 1; // ignore null character +static constexpr size_t kPayloadUnsignedPrefixLen = ArraySize(kPayloadUnsignedPrefix) - 1; // ignore null character +static constexpr size_t kPayloadFloatPrefixLen = ArraySize(kPayloadFloatPrefix) - 1; // ignore null character +static constexpr size_t kPayloadDoublePrefixLen = ArraySize(kPayloadDoublePrefix) - 1; // ignore null character +} // namespace + +class CustomArgumentParser +{ +public: + static CHIP_ERROR Put(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + if (value.isObject()) + { + return CustomArgumentParser::PutObject(writer, tag, value); + } + + if (value.isArray()) + { + return CustomArgumentParser::PutArray(writer, tag, value); + } + + if (value.isString()) + { + if (IsOctetString(value)) + { + return CustomArgumentParser::PutOctetString(writer, tag, value); + } + if (IsUnsignedNumberPrefix(value)) + { + return CustomArgumentParser::PutUnsignedFromString(writer, tag, value); + } + if (IsSignedNumberPrefix(value)) + { + return CustomArgumentParser::PutSignedFromString(writer, tag, value); + } + if (IsFloatNumberPrefix(value)) + { + return CustomArgumentParser::PutFloatFromString(writer, tag, value); + } + if (IsDoubleNumberPrefix(value)) + { + return CustomArgumentParser::PutDoubleFromString(writer, tag, value); + } + + return CustomArgumentParser::PutCharString(writer, tag, value); + } + + if (value.isNull()) + { + return chip::app::DataModel::Encode(*writer, tag, chip::app::DataModel::Nullable()); + } + + if (value.isBool()) + { + return chip::app::DataModel::Encode(*writer, tag, value.asBool()); + } + + if (value.isUInt()) + { + return chip::app::DataModel::Encode(*writer, tag, value.asLargestUInt()); + } + + if (value.isInt()) + { + return chip::app::DataModel::Encode(*writer, tag, value.asLargestInt()); + } + + if (value.isNumeric()) + { + return chip::app::DataModel::Encode(*writer, tag, value.asDouble()); + } + + return CHIP_ERROR_NOT_IMPLEMENTED; + } + +private: + static CHIP_ERROR PutArray(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + chip::TLV::TLVType outer; + ReturnErrorOnFailure(writer->StartContainer(tag, chip::TLV::kTLVType_Array, outer)); + + Json::ArrayIndex size = value.size(); + + for (Json::ArrayIndex i = 0; i < size; i++) + { + ReturnErrorOnFailure(CustomArgumentParser::Put(writer, chip::TLV::AnonymousTag(), value[i])); + } + + return writer->EndContainer(outer); + } + + static CHIP_ERROR PutObject(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + chip::TLV::TLVType outer; + ReturnErrorOnFailure(writer->StartContainer(tag, chip::TLV::kTLVType_Structure, outer)); + + for (auto const & id : value.getMemberNames()) + { + auto index = std::stoul(id, nullptr, 0); + VerifyOrReturnError(chip::CanCastTo(index), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(CustomArgumentParser::Put(writer, chip::TLV::ContextTag(static_cast(index)), value[id])); + } + + return writer->EndContainer(outer); + } + + static CHIP_ERROR PutOctetString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + const char * hexData = value.asCString() + kPayloadHexPrefixLen; + size_t hexDataLen = strlen(hexData); + chip::Platform::ScopedMemoryBuffer buffer; + + size_t octetCount; + ReturnErrorOnFailure(HexToBytes( + chip::CharSpan(hexData, hexDataLen), + [&buffer](size_t allocSize) { + buffer.Calloc(allocSize); + return buffer.Get(); + }, + &octetCount)); + + return chip::app::DataModel::Encode(*writer, tag, chip::ByteSpan(buffer.Get(), octetCount)); + } + + static CHIP_ERROR PutCharString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + size_t size = strlen(value.asCString()); + return chip::app::DataModel::Encode(*writer, tag, chip::CharSpan(value.asCString(), size)); + } + + static CHIP_ERROR PutUnsignedFromString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + char numberAsString[21]; + chip::Platform::CopyString(numberAsString, value.asCString() + kPayloadUnsignedPrefixLen); + + auto number = std::stoull(numberAsString, nullptr, 0); + return chip::app::DataModel::Encode(*writer, tag, static_cast(number)); + } + + static CHIP_ERROR PutSignedFromString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + char numberAsString[21]; + chip::Platform::CopyString(numberAsString, value.asCString() + kPayloadSignedPrefixLen); + + auto number = std::stoll(numberAsString, nullptr, 0); + return chip::app::DataModel::Encode(*writer, tag, static_cast(number)); + } + + static CHIP_ERROR PutFloatFromString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + char numberAsString[21]; + chip::Platform::CopyString(numberAsString, value.asCString() + kPayloadFloatPrefixLen); + + auto number = std::stof(numberAsString); + return chip::app::DataModel::Encode(*writer, tag, number); + } + + static CHIP_ERROR PutDoubleFromString(chip::TLV::TLVWriter * writer, chip::TLV::Tag tag, Json::Value & value) + { + char numberAsString[21]; + chip::Platform::CopyString(numberAsString, value.asCString() + kPayloadDoublePrefixLen); + + auto number = std::stod(numberAsString); + return chip::app::DataModel::Encode(*writer, tag, number); + } + + static bool IsOctetString(Json::Value & value) + { + return (strncmp(value.asCString(), kPayloadHexPrefix, kPayloadHexPrefixLen) == 0); + } + + static bool IsUnsignedNumberPrefix(Json::Value & value) + { + return (strncmp(value.asCString(), kPayloadUnsignedPrefix, kPayloadUnsignedPrefixLen) == 0); + } + + static bool IsSignedNumberPrefix(Json::Value & value) + { + return (strncmp(value.asCString(), kPayloadSignedPrefix, kPayloadSignedPrefixLen) == 0); + } + + static bool IsFloatNumberPrefix(Json::Value & value) + { + return (strncmp(value.asCString(), kPayloadFloatPrefix, kPayloadFloatPrefixLen) == 0); + } + + static bool IsDoubleNumberPrefix(Json::Value & value) + { + return (strncmp(value.asCString(), kPayloadDoublePrefix, kPayloadDoublePrefixLen) == 0); + } +}; + +class CustomArgument +{ +public: + ~CustomArgument() + { + if (mData != nullptr) + { + chip::Platform::MemoryFree(mData); + } + } + + CHIP_ERROR Parse(const char * label, const char * json) + { + Json::Value value; + static constexpr char kHexNumPrefix[] = "0x"; + constexpr size_t kHexNumPrefixLen = ArraySize(kHexNumPrefix) - 1; + if (strncmp(json, kPayloadHexPrefix, kPayloadHexPrefixLen) == 0 || + strncmp(json, kPayloadSignedPrefix, kPayloadSignedPrefixLen) == 0 || + strncmp(json, kPayloadUnsignedPrefix, kPayloadUnsignedPrefixLen) == 0 || + strncmp(json, kPayloadFloatPrefix, kPayloadFloatPrefixLen) == 0 || + strncmp(json, kPayloadDoublePrefix, kPayloadDoublePrefixLen) == 0) + { + value = Json::Value(json); + } + else if (strncmp(json, kHexNumPrefix, kHexNumPrefixLen) == 0) + { + // Assume that hex numbers are unsigned. Prepend + // kPayloadUnsignedPrefix and then let the rest of the logic handle + // things. + std::string str(kPayloadUnsignedPrefix); + str += json; + value = Json::Value(str); + } + else if (!JsonParser::ParseCustomArgument(label, json, value)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + mData = static_cast(chip::Platform::MemoryCalloc(sizeof(uint8_t), mDataMaxLen)); + VerifyOrReturnError(mData != nullptr, CHIP_ERROR_NO_MEMORY); + + chip::TLV::TLVWriter writer; + writer.Init(mData, mDataMaxLen); + + ReturnErrorOnFailure(CustomArgumentParser::Put(&writer, chip::TLV::AnonymousTag(), value)); + + mDataLen = writer.GetLengthWritten(); + return writer.Finalize(); + } + + CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, chip::TLV::Tag tag) const + { + chip::TLV::TLVReader reader; + reader.Init(mData, mDataLen); + ReturnErrorOnFailure(reader.Next()); + + return writer.CopyElement(tag, reader); + } + + // We trust our consumers to do the encoding of our data correctly, so don't + // need to know whether we are being encoded for a write. + static constexpr bool kIsFabricScoped = false; + +private: + uint8_t * mData = nullptr; + uint32_t mDataLen = 0; + static constexpr uint32_t mDataMaxLen = 4096; +}; diff --git a/examples/fabric-admin/commands/clusters/DataModelLogger.h b/examples/fabric-admin/commands/clusters/DataModelLogger.h new file mode 100644 index 00000000000000..ee649755014906 --- /dev/null +++ b/examples/fabric-admin/commands/clusters/DataModelLogger.h @@ -0,0 +1,190 @@ +/* + * 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 +#include +#include + +class DataModelLogger +{ +public: + static CHIP_ERROR LogAttribute(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data); + static CHIP_ERROR LogCommand(const chip::app::ConcreteCommandPath & path, chip::TLV::TLVReader * data); + static CHIP_ERROR LogEvent(const chip::app::EventHeader & header, chip::TLV::TLVReader * data); + +private: + static CHIP_ERROR LogValue(const char * label, size_t indent, bool value) + { + DataModelLogger::LogString(label, indent, value ? "TRUE" : "FALSE"); + return CHIP_NO_ERROR; + } + + static CHIP_ERROR LogValue(const char * label, size_t indent, chip::CharSpan value) + { + DataModelLogger::LogString(label, indent, std::string(value.data(), value.size())); + return CHIP_NO_ERROR; + } + + static CHIP_ERROR LogValue(const char * label, size_t indent, chip::ByteSpan value) + { + // CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE includes various prefixes we don't + // control (timestamps, process ids, etc). Let's assume (hope?) that + // those prefixes use up no more than half the total available space. + // Right now it looks like the prefixes are 45 chars out of a 255 char + // buffer. + char buffer[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE / 2]; + size_t prefixSize = ComputePrefixSize(label, indent); + if (prefixSize > ArraySize(buffer)) + { + DataModelLogger::LogString("", 0, "Prefix is too long to fit in buffer"); + return CHIP_ERROR_INTERNAL; + } + + const size_t availableSize = ArraySize(buffer) - prefixSize; + // Each byte ends up as two hex characters. + const size_t bytesPerLogCall = availableSize / 2; + std::string labelStr(label); + while (value.size() > bytesPerLogCall) + { + ReturnErrorOnFailure( + chip::Encoding::BytesToUppercaseHexString(value.data(), bytesPerLogCall, &buffer[0], ArraySize(buffer))); + LogString(labelStr, indent, buffer); + value = value.SubSpan(bytesPerLogCall); + // For the second and following lines, make it clear that they are + // continuation lines by replacing the label with "....". + labelStr.replace(labelStr.begin(), labelStr.end(), labelStr.size(), '.'); + } + ReturnErrorOnFailure(chip::Encoding::BytesToUppercaseHexString(value.data(), value.size(), &buffer[0], ArraySize(buffer))); + LogString(labelStr, indent, buffer); + + return CHIP_NO_ERROR; + } + + template ::value && !std::is_same>, bool>::value, int> = 0> + static CHIP_ERROR LogValue(const char * label, size_t indent, X value) + { + DataModelLogger::LogString(label, indent, std::to_string(value)); + return CHIP_NO_ERROR; + } + + template ::value, int> = 0> + static CHIP_ERROR LogValue(const char * label, size_t indent, X value) + { + DataModelLogger::LogString(label, indent, std::to_string(value)); + return CHIP_NO_ERROR; + } + + template ::value, int> = 0> + static CHIP_ERROR LogValue(const char * label, size_t indent, X value) + { + return DataModelLogger::LogValue(label, indent, chip::to_underlying(value)); + } + + template + static CHIP_ERROR LogValue(const char * label, size_t indent, chip::BitFlags value) + { + return DataModelLogger::LogValue(label, indent, value.Raw()); + } + + template + static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::app::DataModel::DecodableList & value) + { + size_t count = 0; + ReturnErrorOnFailure(value.ComputeSize(&count)); + DataModelLogger::LogString(label, indent, std::to_string(count) + " entries"); + + auto iter = value.begin(); + size_t i = 0; + while (iter.Next()) + { + ++i; + std::string itemLabel = std::string("[") + std::to_string(i) + "]"; + ReturnErrorOnFailure(DataModelLogger::LogValue(itemLabel.c_str(), indent + 1, iter.GetValue())); + } + if (iter.GetStatus() != CHIP_NO_ERROR) + { + DataModelLogger::LogString(indent + 1, "List truncated due to invalid value"); + } + return iter.GetStatus(); + } + + template + static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::app::DataModel::Nullable & value) + { + if (value.IsNull()) + { + DataModelLogger::LogString(label, indent, "null"); + return CHIP_NO_ERROR; + } + + return DataModelLogger::LogValue(label, indent, value.Value()); + } + + template + static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::Optional & value) + { + if (value.HasValue()) + { + return DataModelLogger::LogValue(label, indent, value.Value()); + } + + return CHIP_NO_ERROR; + } + +#include + + static void LogString(size_t indent, const std::string string) { LogString("", indent, string); } + + static void LogString(const std::string label, size_t indent, const std::string string) + { + std::string prefix = ComputePrefix(label, indent); + + ChipLogProgress(NotSpecified, "%s%s", prefix.c_str(), string.c_str()); + } + +private: + static std::string ComputePrefix(const std::string label, size_t indent) + { + std::string prefix; + for (size_t i = 0; i < indent; ++i) + { + prefix.append(" "); + } + if (label.size() > 0) + { + prefix.append(label); + prefix.append(":"); + } + prefix.append(" "); + + return prefix; + } + + static size_t ComputePrefixSize(const std::string label, size_t indent) { return ComputePrefix(label, indent).size(); } +}; diff --git a/examples/fabric-admin/commands/clusters/JsonParser.h b/examples/fabric-admin/commands/clusters/JsonParser.h new file mode 100644 index 00000000000000..0871e767c21bd6 --- /dev/null +++ b/examples/fabric-admin/commands/clusters/JsonParser.h @@ -0,0 +1,166 @@ +/* + * 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 "../common/CustomStringPrefix.h" + +#include +#include + +#include +#include +#include +#include + +class JsonParser +{ +public: + // Returns whether the parse succeeded. + static bool ParseComplexArgument(const char * label, const char * json, Json::Value & value) + { + return Parse(label, json, /* strictRoot = */ true, value); + } + + // Returns whether the parse succeeded. + static bool ParseCustomArgument(const char * label, const char * json, Json::Value & value) + { + return Parse(label, json, /* strictRoot = */ false, value); + } + +private: + static bool Parse(const char * label, const char * json, bool strictRoot, Json::Value & value) + { + Json::CharReaderBuilder readerBuilder; + readerBuilder.settings_["strictRoot"] = strictRoot; + readerBuilder.settings_["allowSingleQuotes"] = true; + readerBuilder.settings_["failIfExtra"] = true; + readerBuilder.settings_["rejectDupKeys"] = true; + + auto reader = std::unique_ptr(readerBuilder.newCharReader()); + std::string errors; + if (reader->parse(json, json + strlen(json), &value, &errors)) + { + return true; + } + + // The CharReader API allows us to set failIfExtra, unlike Reader, but does + // not allow us to get structured errors. We get to try to manually undo + // the work it did to create a string from the structured errors it had. + ChipLogError(NotSpecified, "Error parsing JSON for %s:", label); + + // For each error "errors" has the following: + // + // 1) A line starting with "* " that has line/column info + // 2) A line with the error message. + // 3) An optional line with some extra info. + // + // We keep track of the last error column, in case the error message + // reporting needs it. + std::istringstream stream(errors); + std::string error; + chip::Optional errorColumn; + while (getline(stream, error)) + { + if (error.rfind("* ", 0) == 0) + { + // Flush out any pending error location. + LogErrorLocation(errorColumn, json); + + // The format of this line is: + // + // * Line N, Column M + // + // Unfortunately it does not indicate end of error, so we can only + // show its start. + unsigned errorLine; // ignored in practice + if (sscanf(error.c_str(), "* Line %u, Column %u", &errorLine, &errorColumn.Emplace()) != 2) + { + ChipLogError(NotSpecified, "Unexpected location string: %s\n", error.c_str()); + // We don't know how to make sense of this thing anymore. + break; + } + if (errorColumn.Value() == 0) + { + ChipLogError(NotSpecified, "Expected error column to be at least 1"); + // We don't know how to make sense of this thing anymore. + break; + } + // We are using our column numbers as offsets, so want them to be + // 0-based. + --errorColumn.Value(); + } + else + { + ChipLogError(NotSpecified, " %s", error.c_str()); + if (error == " Missing ',' or '}' in object declaration" && errorColumn.HasValue() && errorColumn.Value() > 0 && + json[errorColumn.Value() - 1] == '0' && (json[errorColumn.Value()] == 'x' || json[errorColumn.Value()] == 'X')) + { + // Log the error location marker before showing the NOTE + // message. + LogErrorLocation(errorColumn, json); + ChipLogError(NotSpecified, + "NOTE: JSON does not allow hex syntax beginning with 0x for numbers. Try putting the hex number " + "in quotes (like {\"name\": \"0x100\"})."); + } + } + } + + // Write out the marker for our last error. + LogErrorLocation(errorColumn, json); + + return false; + } + +private: + static void LogErrorLocation(chip::Optional & errorColumn, const char * json) + { +#if CHIP_ERROR_LOGGING + if (!errorColumn.HasValue()) + { + return; + } + + const char * sourceText = json; + unsigned error_start = errorColumn.Value(); + // The whole JSON string might be too long to fit in our log + // messages. Just include 30 chars before the error. + constexpr ptrdiff_t kMaxContext = 30; + std::string errorMarker; + if (error_start > kMaxContext) + { + sourceText += (error_start - kMaxContext); + error_start = kMaxContext; + ChipLogError(NotSpecified, "... %s", sourceText); + // Add markers corresponding to the "... " above. + errorMarker += "----"; + } + else + { + ChipLogError(NotSpecified, "%s", sourceText); + } + for (unsigned i = 0; i < error_start; ++i) + { + errorMarker += "-"; + } + errorMarker += "^"; + ChipLogError(NotSpecified, "%s", errorMarker.c_str()); + errorColumn.ClearValue(); +#endif // CHIP_ERROR_LOGGING + } +}; diff --git a/examples/fabric-admin/commands/clusters/ModelCommand.cpp b/examples/fabric-admin/commands/clusters/ModelCommand.cpp new file mode 100644 index 00000000000000..8f379dbcc4ee06 --- /dev/null +++ b/examples/fabric-admin/commands/clusters/ModelCommand.cpp @@ -0,0 +1,105 @@ +/* + * 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. + * + */ + +#include "ModelCommand.h" + +#include +#include +#include + +using namespace ::chip; + +CHIP_ERROR ModelCommand::RunCommand() +{ + + if (IsGroupId(mDestinationId)) + { + FabricIndex fabricIndex = CurrentCommissioner().GetFabricIndex(); + ChipLogProgress(chipTool, "Sending command to group 0x%x", GroupIdFromNodeId(mDestinationId)); + + return SendGroupCommand(GroupIdFromNodeId(mDestinationId), fabricIndex); + } + + ChipLogProgress(NotSpecified, "Sending command to node " ChipLogFormatX64, ChipLogValueX64(mDestinationId)); + CheckPeerICDType(); + + CommissioneeDeviceProxy * commissioneeDeviceProxy = nullptr; + if (CHIP_NO_ERROR == CurrentCommissioner().GetDeviceBeingCommissioned(mDestinationId, &commissioneeDeviceProxy)) + { + return SendCommand(commissioneeDeviceProxy, mEndPointId); + } + + return CurrentCommissioner().GetConnectedDevice(mDestinationId, &mOnDeviceConnectedCallback, + &mOnDeviceConnectionFailureCallback); +} + +void ModelCommand::OnDeviceConnectedFn(void * context, chip::Messaging::ExchangeManager & exchangeMgr, + const chip::SessionHandle & sessionHandle) +{ + ModelCommand * command = reinterpret_cast(context); + VerifyOrReturn(command != nullptr, ChipLogError(NotSpecified, "OnDeviceConnectedFn: context is null")); + + chip::OperationalDeviceProxy device(&exchangeMgr, sessionHandle); + CHIP_ERROR err = command->SendCommand(&device, command->mEndPointId); + VerifyOrReturn(CHIP_NO_ERROR == err, command->SetCommandExitStatus(err)); +} + +void ModelCommand::OnDeviceConnectionFailureFn(void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR err) +{ + LogErrorOnFailure(err); + + ModelCommand * command = reinterpret_cast(context); + VerifyOrReturn(command != nullptr, ChipLogError(NotSpecified, "OnDeviceConnectionFailureFn: context is null")); + command->SetCommandExitStatus(err); +} + +void ModelCommand::Shutdown() +{ + mOnDeviceConnectedCallback.Cancel(); + mOnDeviceConnectionFailureCallback.Cancel(); + + CHIPCommand::Shutdown(); +} + +void ModelCommand::CheckPeerICDType() +{ + if (mIsPeerLIT.HasValue()) + { + ChipLogProgress(NotSpecified, "Peer ICD type is set to %s", mIsPeerLIT.Value() == 1 ? "LIT-ICD" : "non LIT-ICD"); + return; + } + + app::ICDClientInfo info; + auto destinationPeerId = chip::ScopedNodeId(mDestinationId, CurrentCommissioner().GetFabricIndex()); + auto iter = CHIPCommand::sICDClientStorage.IterateICDClientInfo(); + if (iter == nullptr) + { + return; + } + app::DefaultICDClientStorage::ICDClientInfoIteratorWrapper clientInfoIteratorWrapper(iter); + + while (iter->Next(info)) + { + if (ScopedNodeId(info.peer_node.GetNodeId(), info.peer_node.GetFabricIndex()) == destinationPeerId) + { + ChipLogProgress(NotSpecified, "Peer is a registered LIT ICD."); + mIsPeerLIT.SetValue(true); + return; + } + } +} diff --git a/examples/fabric-admin/commands/clusters/ModelCommand.h b/examples/fabric-admin/commands/clusters/ModelCommand.h new file mode 100644 index 00000000000000..c14d3c9952f3fc --- /dev/null +++ b/examples/fabric-admin/commands/clusters/ModelCommand.h @@ -0,0 +1,91 @@ +/* + * 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 + +#ifdef CONFIG_USE_LOCAL_STORAGE +#include +#endif // CONFIG_USE_LOCAL_STORAGE + +#include "../common/CHIPCommand.h" +#include + +class ModelCommand : public CHIPCommand +{ +public: + ModelCommand(const char * commandName, CredentialIssuerCommands * credsIssuerConfig, bool supportsMultipleEndpoints = false) : + CHIPCommand(commandName, credsIssuerConfig), mOnDeviceConnectedCallback(OnDeviceConnectedFn, this), + mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this), mSupportsMultipleEndpoints(supportsMultipleEndpoints) + {} + + void AddArguments(bool skipEndpoints = false) + { + AddArgument( + "destination-id", 0, UINT64_MAX, &mDestinationId, + "64-bit node or group identifier.\n Group identifiers are detected by being in the 0xFFFF'FFFF'FFFF'xxxx range."); + if (skipEndpoints == false) + { + if (mSupportsMultipleEndpoints) + { + AddArgument("endpoint-ids", 0, UINT16_MAX, &mEndPointId, + "Comma-separated list of endpoint ids (e.g. \"1\" or \"1,2,3\").\n Allowed to be 0xFFFF to indicate a " + "wildcard endpoint."); + } + else + { + AddArgument("endpoint-id-ignored-for-group-commands", 0, UINT16_MAX, &mEndPointId, + "Endpoint the command is targeted at."); + } + } + AddArgument( + "lit-icd-peer", 0, 1, &mIsPeerLIT, + "Whether to treat the peer as a LIT ICD. false: Always no, true: Always yes, (not set): Yes if the peer is registered " + "to this controller."); + AddArgument("timeout", 0, UINT16_MAX, &mTimeout); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override; + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(mTimeout.ValueOr(20)); } + + virtual CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endPointIds) = 0; + + virtual CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex) { return CHIP_ERROR_BAD_REQUEST; }; + + void Shutdown() override; + +protected: + bool IsPeerLIT() { return mIsPeerLIT.ValueOr(false); } + + chip::Optional mTimeout; + +private: + chip::NodeId mDestinationId; + std::vector mEndPointId; + chip::Optional mIsPeerLIT; + + void CheckPeerICDType(); + + static void OnDeviceConnectedFn(void * context, chip::Messaging::ExchangeManager & exchangeMgr, + const chip::SessionHandle & sessionHandle); + static void OnDeviceConnectionFailureFn(void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error); + + chip::Callback::Callback mOnDeviceConnectedCallback; + chip::Callback::Callback mOnDeviceConnectionFailureCallback; + const bool mSupportsMultipleEndpoints; +}; diff --git a/examples/fabric-admin/commands/clusters/ReportCommand.h b/examples/fabric-admin/commands/clusters/ReportCommand.h new file mode 100644 index 00000000000000..4e9dbd0f043931 --- /dev/null +++ b/examples/fabric-admin/commands/clusters/ReportCommand.h @@ -0,0 +1,551 @@ +/* + * 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 "DataModelLogger.h" +#include "ModelCommand.h" + +class ReportCommand : public InteractionModelReports, public ModelCommand, public chip::app::ReadClient::Callback +{ +public: + ReportCommand(const char * commandName, CredentialIssuerCommands * credsIssuerConfig) : + InteractionModelReports(this), ModelCommand(commandName, credsIssuerConfig, /* supportsMultipleEndpoints = */ true) + {} + + /////////// ReadClient Callback Interface ///////// + void OnAttributeData(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data, + const chip::app::StatusIB & status) override + { + CHIP_ERROR error = status.ToChipError(); + if (CHIP_NO_ERROR != error) + { + LogErrorOnFailure(RemoteDataModelLogger::LogErrorAsJSON(path, status)); + + ChipLogError(NotSpecified, "Response Failure: %s", chip::ErrorStr(error)); + mError = error; + return; + } + + if (data == nullptr) + { + ChipLogError(NotSpecified, "Response Failure: No Data"); + mError = CHIP_ERROR_INTERNAL; + return; + } + + LogErrorOnFailure(RemoteDataModelLogger::LogAttributeAsJSON(path, data)); + + error = DataModelLogger::LogAttribute(path, data); + if (CHIP_NO_ERROR != error) + { + ChipLogError(NotSpecified, "Response Failure: Can not decode Data"); + mError = error; + return; + } + } + + void OnEventData(const chip::app::EventHeader & eventHeader, chip::TLV::TLVReader * data, + const chip::app::StatusIB * status) override + { + if (status != nullptr) + { + CHIP_ERROR error = status->ToChipError(); + if (CHIP_NO_ERROR != error) + { + LogErrorOnFailure(RemoteDataModelLogger::LogErrorAsJSON(eventHeader, *status)); + + ChipLogError(NotSpecified, "Response Failure: %s", chip::ErrorStr(error)); + mError = error; + return; + } + } + + if (data == nullptr) + { + ChipLogError(NotSpecified, "Response Failure: No Data"); + mError = CHIP_ERROR_INTERNAL; + return; + } + + LogErrorOnFailure(RemoteDataModelLogger::LogEventAsJSON(eventHeader, data)); + + CHIP_ERROR error = DataModelLogger::LogEvent(eventHeader, data); + if (CHIP_NO_ERROR != error) + { + ChipLogError(NotSpecified, "Response Failure: Can not decode Data"); + mError = error; + return; + } + } + + void OnError(CHIP_ERROR error) override + { + LogErrorOnFailure(RemoteDataModelLogger::LogErrorAsJSON(error)); + + ChipLogProgress(NotSpecified, "Error: %s", chip::ErrorStr(error)); + mError = error; + } + + void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override + { + InteractionModelReports::OnDeallocatePaths(std::move(aReadPrepareParams)); + } + + void Shutdown() override + { + // We don't shut down InteractionModelReports here; we leave it for + // Cleanup to handle. + mError = CHIP_NO_ERROR; + ModelCommand::Shutdown(); + } + + void Cleanup() override { InteractionModelReports::Shutdown(); } + +protected: + // Use a 3x-longer-than-default timeout because wildcard reads can take a + // while. + chip::System::Clock::Timeout GetWaitDuration() const override + { + return mTimeout.HasValue() ? chip::System::Clock::Seconds16(mTimeout.Value()) : (ModelCommand::GetWaitDuration() * 3); + } + + CHIP_ERROR mError = CHIP_NO_ERROR; +}; + +class ReadCommand : public ReportCommand +{ +protected: + ReadCommand(const char * commandName, CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand(commandName, credsIssuerConfig) + {} + + void OnDone(chip::app::ReadClient * aReadClient) override + { + InteractionModelReports::CleanupReadClient(aReadClient); + SetCommandExitStatus(mError); + } +}; + +class SubscribeCommand : public ReportCommand +{ +protected: + SubscribeCommand(const char * commandName, CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand(commandName, credsIssuerConfig) + {} + + void OnSubscriptionEstablished(chip::SubscriptionId subscriptionId) override + { + mSubscriptionEstablished = true; + SetCommandExitStatus(CHIP_NO_ERROR); + } + + void OnDone(chip::app::ReadClient * aReadClient) override + { + InteractionModelReports::CleanupReadClient(aReadClient); + + if (!mSubscriptionEstablished) + { + SetCommandExitStatus(mError); + } + // else we must be getting here from Cleanup(), which means we have + // already done our exit status thing. + } + + void Shutdown() override + { + mSubscriptionEstablished = false; + ReportCommand::Shutdown(); + } + + // For subscriptions we always defer interactive cleanup. Either our + // ReadClients will terminate themselves (in which case they will be removed + // from our list anyway), or they should hang around until shutdown. + bool DeferInteractiveCleanup() override { return true; } + +private: + bool mSubscriptionEstablished = false; +}; + +class ReadAttribute : public ReadCommand +{ +public: + ReadAttribute(CredentialIssuerCommands * credsIssuerConfig) : ReadCommand("read-by-id", credsIssuerConfig) + { + AddArgument("cluster-ids", 0, UINT32_MAX, &mClusterIds, + "Comma-separated list of cluster ids to read from (e.g. \"6\" or \"8,0x201\").\n Allowed to be 0xFFFFFFFF to " + "indicate a wildcard cluster."); + AddAttributeIdArgument(); + AddCommonArguments(); + ReadCommand::AddArguments(); + } + + ReadAttribute(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + ReadCommand("read-by-id", credsIssuerConfig), mClusterIds(1, clusterId) + { + AddAttributeIdArgument(); + AddCommonArguments(); + ReadCommand::AddArguments(); + } + + ReadAttribute(chip::ClusterId clusterId, const char * attributeName, chip::AttributeId attributeId, + CredentialIssuerCommands * credsIssuerConfig) : + ReadCommand("read", credsIssuerConfig), + mClusterIds(1, clusterId), mAttributeIds(1, attributeId) + { + AddArgument("attr-name", attributeName); + AddCommonArguments(); + ReadCommand::AddArguments(); + } + + ~ReadAttribute() {} + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + return ReadCommand::ReadAttribute(device, endpointIds, mClusterIds, mAttributeIds); + } + +private: + void AddAttributeIdArgument() + { + AddArgument("attribute-ids", 0, UINT32_MAX, &mAttributeIds, + "Comma-separated list of attribute ids to read (e.g. \"0\" or \"1,0xFFFC,0xFFFD\").\n Allowed to be " + "0xFFFFFFFF to indicate a wildcard attribute."); + } + + void AddCommonArguments() + { + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered, + "Boolean indicating whether to do a fabric-filtered read. Defaults to true."); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersions, + "Comma-separated list of data versions for the clusters being read."); + } + + std::vector mClusterIds; + std::vector mAttributeIds; +}; + +class SubscribeAttribute : public SubscribeCommand +{ +public: + SubscribeAttribute(CredentialIssuerCommands * credsIssuerConfig) : SubscribeCommand("subscribe-by-id", credsIssuerConfig) + { + AddArgument("cluster-ids", 0, UINT32_MAX, &mClusterIds, + "Comma-separated list of cluster ids to subscribe to (e.g. \"6\" or \"8,0x201\").\n Allowed to be 0xFFFFFFFF " + "to indicate a wildcard cluster."); + AddAttributeIdArgument(); + AddCommonArguments(); + SubscribeCommand::AddArguments(); + } + + SubscribeAttribute(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + SubscribeCommand("subscribe-by-id", credsIssuerConfig), mClusterIds(1, clusterId) + { + AddAttributeIdArgument(); + AddCommonArguments(); + SubscribeCommand::AddArguments(); + } + + SubscribeAttribute(chip::ClusterId clusterId, const char * attributeName, chip::AttributeId attributeId, + CredentialIssuerCommands * credsIssuerConfig) : + SubscribeCommand("subscribe", credsIssuerConfig), + mClusterIds(1, clusterId), mAttributeIds(1, attributeId) + { + AddArgument("attr-name", attributeName); + AddCommonArguments(); + SubscribeCommand::AddArguments(); + } + + ~SubscribeAttribute() {} + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + SubscribeCommand::SetPeerLIT(IsPeerLIT()); + return SubscribeCommand::SubscribeAttribute(device, endpointIds, mClusterIds, mAttributeIds); + } + +private: + void AddAttributeIdArgument() + { + AddArgument("attribute-ids", 0, UINT32_MAX, &mAttributeIds, + "Comma-separated list of attribute ids to subscribe to (e.g. \"0\" or \"1,0xFFFC,0xFFFD\").\n Allowed to be " + "0xFFFFFFFF to indicate a wildcard attribute."); + } + + void AddCommonArguments() + { + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval, + "Server should not send a new report if less than this number of seconds has elapsed since the last report."); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval, + "Server must send a report if this number of seconds has elapsed since the last report."); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered, + "Boolean indicating whether to do a fabric-filtered subscription. Defaults to true."); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersions, + "Comma-separated list of data versions for the clusters being subscribed to."); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions, + "Boolean indicating whether to keep existing subscriptions when creating the new one. Defaults to false."); + AddArgument("auto-resubscribe", 0, 1, &mAutoResubscribe, + "Boolean indicating whether the subscription should auto-resubscribe. Defaults to false."); + } + + std::vector mClusterIds; + std::vector mAttributeIds; +}; + +class ReadEvent : public ReadCommand +{ +public: + ReadEvent(CredentialIssuerCommands * credsIssuerConfig) : ReadCommand("read-event-by-id", credsIssuerConfig) + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterIds); + AddArgument("event-id", 0, UINT32_MAX, &mEventIds); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReadCommand::AddArguments(); + } + + ReadEvent(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + ReadCommand("read-event-by-id", credsIssuerConfig), mClusterIds(1, clusterId) + { + AddArgument("event-id", 0, UINT32_MAX, &mEventIds); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReadCommand::AddArguments(); + } + + ReadEvent(chip::ClusterId clusterId, const char * eventName, chip::EventId eventId, + CredentialIssuerCommands * credsIssuerConfig) : + ReadCommand("read-event", credsIssuerConfig), + mClusterIds(1, clusterId), mEventIds(1, eventId) + { + AddArgument("event-name", eventName); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReadCommand::AddArguments(); + } + + ~ReadEvent() {} + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + return ReadCommand::ReadEvent(device, endpointIds, mClusterIds, mEventIds); + } + +private: + std::vector mClusterIds; + std::vector mEventIds; +}; + +class SubscribeEvent : public SubscribeCommand +{ +public: + SubscribeEvent(CredentialIssuerCommands * credsIssuerConfig) : SubscribeCommand("subscribe-event-by-id", credsIssuerConfig) + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterIds); + AddArgument("event-id", 0, UINT32_MAX, &mEventIds); + AddCommonArguments(); + SubscribeCommand::AddArguments(); + } + + SubscribeEvent(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + SubscribeCommand("subscribe-event-by-id", credsIssuerConfig), mClusterIds(1, clusterId) + { + AddArgument("event-id", 0, UINT32_MAX, &mEventIds); + AddCommonArguments(); + SubscribeCommand::AddArguments(); + } + + SubscribeEvent(chip::ClusterId clusterId, const char * eventName, chip::EventId eventId, + CredentialIssuerCommands * credsIssuerConfig) : + SubscribeCommand("subscribe-event", credsIssuerConfig), + mClusterIds(1, clusterId), mEventIds(1, eventId) + { + AddArgument("event-name", eventName, "Event name."); + AddCommonArguments(); + SubscribeCommand::AddArguments(); + } + + void AddCommonArguments() + { + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval, + "The requested minimum interval between reports. Sets MinIntervalFloor in the Subscribe Request."); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval, + "The requested maximum interval between reports. Sets MaxIntervalCeiling in the Subscribe Request."); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions, + "false - Terminate existing subscriptions from initiator.\n true - Leave existing subscriptions in place."); + AddArgument( + "is-urgent", 0, 1, &mIsUrgents, + "Sets isUrgent in the Subscribe Request.\n" + " The queueing of any urgent event SHALL force an immediate generation of reports containing all events queued " + "leading up to (and including) the urgent event in question.\n" + " This argument takes a comma separated list of true/false values.\n" + " If the number of paths exceeds the number of entries provided to is-urgent, then isUrgent will be false for the " + "extra paths."); + AddArgument("auto-resubscribe", 0, 1, &mAutoResubscribe, + "Boolean indicating whether the subscription should auto-resubscribe. Defaults to false."); + } + + ~SubscribeEvent() {} + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + SubscribeCommand::SetPeerLIT(IsPeerLIT()); + return SubscribeCommand::SubscribeEvent(device, endpointIds, mClusterIds, mEventIds); + } + +private: + std::vector mClusterIds; + std::vector mEventIds; +}; + +class ReadNone : public ReadCommand +{ +public: + ReadNone(CredentialIssuerCommands * credsIssuerConfig) : ReadCommand("read-none", credsIssuerConfig) + { + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered, + "Boolean indicating whether to do a fabric-filtered read. Defaults to true."); + AddArgument("data-versions", 0, UINT32_MAX, &mDataVersions, + "Comma-separated list of data versions for the clusters being read."); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReadCommand::AddArguments(true /* skipEndpoints */); + } + + ~ReadNone() {} + + void OnDone(chip::app::ReadClient * aReadClient) override + { + InteractionModelReports::CleanupReadClient(aReadClient); + SetCommandExitStatus(mError); + } + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + return ReadCommand::ReadNone(device); + } +}; + +class ReadAll : public ReadCommand +{ +public: + ReadAll(CredentialIssuerCommands * credsIssuerConfig) : ReadCommand("read-all", credsIssuerConfig) + { + AddArgument("cluster-ids", 0, UINT32_MAX, &mClusterIds, + "Comma-separated list of cluster ids to read from (e.g. \"6\" or \"8,0x201\").\n Allowed to be 0xFFFFFFFF to " + "indicate a wildcard cluster."); + AddArgument("attribute-ids", 0, UINT32_MAX, &mAttributeIds, + "Comma-separated list of attribute ids to read (e.g. \"0\" or \"1,0xFFFC,0xFFFD\").\n Allowed to be " + "0xFFFFFFFF to indicate a wildcard attribute."); + AddArgument("event-ids", 0, UINT32_MAX, &mEventIds, + "Comma-separated list of event ids to read (e.g. \"0\" or \"1,2,3\").\n Allowed to be " + "0xFFFFFFFF to indicate a wildcard event."); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered, + "Boolean indicating whether to do a fabric-filtered read. Defaults to true."); + AddArgument("data-versions", 0, UINT32_MAX, &mDataVersions, + "Comma-separated list of data versions for the clusters being read."); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReadCommand::AddArguments(); + } + + ~ReadAll() {} + + void OnDone(chip::app::ReadClient * aReadClient) override + { + InteractionModelReports::CleanupReadClient(aReadClient); + SetCommandExitStatus(mError); + } + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + return ReadCommand::ReadAll(device, endpointIds, mClusterIds, mAttributeIds, mEventIds); + } + +private: + std::vector mClusterIds; + std::vector mAttributeIds; + std::vector mEventIds; +}; + +class SubscribeNone : public SubscribeCommand +{ +public: + SubscribeNone(CredentialIssuerCommands * credsIssuerConfig) : SubscribeCommand("subscribe-none", credsIssuerConfig) + { + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval, + "The requested minimum interval between reports. Sets MinIntervalFloor in the Subscribe Request."); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval, + "The requested maximum interval between reports. Sets MaxIntervalCeiling in the Subscribe Request."); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered, + "Boolean indicating whether to do a fabric-filtered read. Defaults to true."); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions, + "false - Terminate existing subscriptions from initiator.\n true - Leave existing subscriptions in place."); + SubscribeCommand::AddArguments(true /* skipEndpoints */); + } + + ~SubscribeNone() {} + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + return SubscribeCommand::SubscribeNone(device); + } +}; + +class SubscribeAll : public SubscribeCommand +{ +public: + SubscribeAll(CredentialIssuerCommands * credsIssuerConfig) : SubscribeCommand("subscribe-all", credsIssuerConfig) + { + AddArgument("cluster-ids", 0, UINT32_MAX, &mClusterIds, + "Comma-separated list of cluster ids to read from (e.g. \"6\" or \"8,0x201\").\n Allowed to be 0xFFFFFFFF to " + "indicate a wildcard cluster."); + AddArgument("attribute-ids", 0, UINT32_MAX, &mAttributeIds, + "Comma-separated list of attribute ids to read (e.g. \"0\" or \"1,0xFFFC,0xFFFD\").\n Allowed to be " + "0xFFFFFFFF to indicate a wildcard attribute."); + AddArgument("event-ids", 0, UINT32_MAX, &mEventIds, + "Comma-separated list of event ids to read (e.g. \"0\" or \"1,2,3\").\n Allowed to be " + "0xFFFFFFFF to indicate a wildcard event."); + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval, + "The requested minimum interval between reports. Sets MinIntervalFloor in the Subscribe Request."); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval, + "The requested maximum interval between reports. Sets MaxIntervalCeiling in the Subscribe Request."); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered, + "Boolean indicating whether to do a fabric-filtered read. Defaults to true."); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions, + "false - Terminate existing subscriptions from initiator.\n true - Leave existing subscriptions in place."); + SubscribeCommand::AddArguments(); + } + + ~SubscribeAll() {} + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + SubscribeCommand::SetPeerLIT(IsPeerLIT()); + return SubscribeCommand::SubscribeAll(device, endpointIds, mClusterIds, mAttributeIds, mEventIds); + } + +private: + std::vector mClusterIds; + std::vector mAttributeIds; + std::vector mEventIds; +}; diff --git a/examples/fabric-admin/commands/clusters/SubscriptionsCommands.h b/examples/fabric-admin/commands/clusters/SubscriptionsCommands.h new file mode 100644 index 00000000000000..625e5a73245507 --- /dev/null +++ b/examples/fabric-admin/commands/clusters/SubscriptionsCommands.h @@ -0,0 +1,108 @@ +/* + * 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 + +class ShutdownSubscription : public CHIPCommand +{ +public: + ShutdownSubscription(CredentialIssuerCommands * credsIssuerConfig) : + CHIPCommand("shutdown-one", credsIssuerConfig, + "Shut down a single subscription, identified by its subscription id and target node id.") + { + AddArgument("subscription-id", 0, UINT32_MAX, &mSubscriptionId); + AddArgument("node-id", 0, UINT64_MAX, &mNodeId, + "The node id, scoped to the commissioner name the command is running under."); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + CHIP_ERROR err = chip::app::InteractionModelEngine::GetInstance()->ShutdownSubscription( + chip::ScopedNodeId(mNodeId, CurrentCommissioner().GetFabricIndex()), mSubscriptionId); + SetCommandExitStatus(err); + return CHIP_NO_ERROR; + } + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } + +private: + chip::SubscriptionId mSubscriptionId; + chip::NodeId mNodeId; +}; + +class ShutdownSubscriptionsForNode : public CHIPCommand +{ +public: + ShutdownSubscriptionsForNode(CredentialIssuerCommands * credsIssuerConfig) : + CHIPCommand("shutdown-all-for-node", credsIssuerConfig, "Shut down all subscriptions targeting a given node.") + { + AddArgument("node-id", 0, UINT64_MAX, &mNodeId, + "The node id, scoped to the commissioner name the command is running under."); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + chip::app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(CurrentCommissioner().GetFabricIndex(), mNodeId); + + SetCommandExitStatus(CHIP_NO_ERROR); + return CHIP_NO_ERROR; + } + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } + +private: + chip::NodeId mNodeId; +}; + +class ShutdownAllSubscriptions : public CHIPCommand +{ +public: + ShutdownAllSubscriptions(CredentialIssuerCommands * credsIssuerConfig) : + CHIPCommand("shutdown-all", credsIssuerConfig, "Shut down all subscriptions to all nodes.") + {} + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + chip::app::InteractionModelEngine::GetInstance()->ShutdownAllSubscriptions(); + + SetCommandExitStatus(CHIP_NO_ERROR); + return CHIP_NO_ERROR; + } + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } + +private: +}; + +void registerCommandsSubscriptions(Commands & commands, CredentialIssuerCommands * credsIssuerConfig) +{ + const char * clusterName = "Subscriptions"; + + commands_list clusterCommands = { + make_unique(credsIssuerConfig), // + make_unique(credsIssuerConfig), // + make_unique(credsIssuerConfig), // + }; + + commands.RegisterCommandSet(clusterName, clusterCommands, "Commands for shutting down subscriptions."); +} diff --git a/examples/fabric-admin/commands/clusters/WriteAttributeCommand.h b/examples/fabric-admin/commands/clusters/WriteAttributeCommand.h new file mode 100644 index 00000000000000..8424e95020f92e --- /dev/null +++ b/examples/fabric-admin/commands/clusters/WriteAttributeCommand.h @@ -0,0 +1,286 @@ +/* + * 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 "DataModelLogger.h" +#include "ModelCommand.h" + +inline constexpr char kWriteCommandKey[] = "write"; +inline constexpr char kWriteByIdCommandKey[] = "write-by-id"; +inline constexpr char kForceWriteCommandKey[] = "force-write"; + +enum class WriteCommandType +{ + kWrite, // regular, writable attributes + kForceWrite, // forced writes, send a write command on something expected to fail +}; + +template > +class WriteAttribute : public InteractionModelWriter, public ModelCommand, public chip::app::WriteClient::Callback +{ +public: + WriteAttribute(CredentialIssuerCommands * credsIssuerConfig) : + InteractionModelWriter(this), ModelCommand(kWriteByIdCommandKey, credsIssuerConfig) + { + AddArgumentClusterIds(); + AddArgumentAttributeIds(); + AddArgumentAttributeValues(); + AddArguments(); + } + + WriteAttribute(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + InteractionModelWriter(this), ModelCommand(kWriteByIdCommandKey, credsIssuerConfig), mClusterIds(1, clusterId) + { + AddArgumentAttributeIds(); + AddArgumentAttributeValues(); + AddArguments(); + } + + template + WriteAttribute(chip::ClusterId clusterId, const char * attributeName, minType minValue, maxType maxValue, + chip::AttributeId attributeId, WriteCommandType commandType, CredentialIssuerCommands * credsIssuerConfig) : + WriteAttribute(clusterId, attributeId, commandType, credsIssuerConfig) + { + AddArgumentAttributeName(attributeName); + AddArgumentAttributeValues(static_cast(minValue), static_cast(maxValue)); + AddArguments(); + } + + WriteAttribute(chip::ClusterId clusterId, const char * attributeName, float minValue, float maxValue, + chip::AttributeId attributeId, WriteCommandType commandType, CredentialIssuerCommands * credsIssuerConfig) : + WriteAttribute(clusterId, attributeId, commandType, credsIssuerConfig) + { + AddArgumentAttributeName(attributeName); + AddArgumentAttributeValues(minValue, maxValue); + AddArguments(); + } + + WriteAttribute(chip::ClusterId clusterId, const char * attributeName, double minValue, double maxValue, + chip::AttributeId attributeId, WriteCommandType commandType, CredentialIssuerCommands * credsIssuerConfig) : + WriteAttribute(clusterId, attributeId, commandType, credsIssuerConfig) + { + AddArgumentAttributeName(attributeName); + AddArgumentAttributeValues(minValue, maxValue); + AddArguments(); + } + + WriteAttribute(chip::ClusterId clusterId, const char * attributeName, chip::AttributeId attributeId, + WriteCommandType commandType, CredentialIssuerCommands * credsIssuerConfig) : + WriteAttribute(clusterId, attributeId, commandType, credsIssuerConfig) + { + AddArgumentAttributeName(attributeName); + AddArgumentAttributeValues(); + AddArguments(); + } + + WriteAttribute(chip::ClusterId clusterId, const char * attributeName, chip::AttributeId attributeId, + TypedComplexArgument & attributeParser, WriteCommandType commandType, + CredentialIssuerCommands * credsIssuerConfig) : + WriteAttribute(clusterId, attributeId, commandType, credsIssuerConfig) + { + AddArgumentAttributeName(attributeName); + AddArgumentAttributeValues(attributeParser); + AddArguments(); + } + + ~WriteAttribute() {} + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds) override + { + return WriteAttribute::SendCommand(device, endpointIds, mClusterIds, mAttributeIds, mAttributeValues); + } + + CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex) override + { + return WriteAttribute::SendGroupCommand(groupId, fabricIndex, mClusterIds, mAttributeIds, mAttributeValues); + } + + /////////// WriteClient Callback Interface ///////// + void OnResponse(const chip::app::WriteClient * client, const chip::app::ConcreteDataAttributePath & path, + chip::app::StatusIB status) override + { + CHIP_ERROR error = status.ToChipError(); + if (CHIP_NO_ERROR != error) + { + LogErrorOnFailure(RemoteDataModelLogger::LogErrorAsJSON(path, status)); + + ChipLogError(NotSpecified, "Response Failure: %s", chip::ErrorStr(error)); + mError = error; + } + } + + void OnError(const chip::app::WriteClient * client, CHIP_ERROR error) override + { + LogErrorOnFailure(RemoteDataModelLogger::LogErrorAsJSON(error)); + + ChipLogProgress(NotSpecified, "Error: %s", chip::ErrorStr(error)); + mError = error; + } + + void OnDone(chip::app::WriteClient * client) override + { + InteractionModelWriter::Shutdown(); + SetCommandExitStatus(mError); + } + + CHIP_ERROR SendCommand(chip::DeviceProxy * device, std::vector endpointIds, + std::vector clusterIds, std::vector attributeIds, const T & values) + { + return InteractionModelWriter::WriteAttribute(device, endpointIds, clusterIds, attributeIds, values); + } + + CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex, std::vector clusterIds, + std::vector attributeIds, const T & value) + { + ChipLogDetail(NotSpecified, "Sending Write Attribute to Group %u, on Fabric %x, for cluster %u with attributeId %u", + groupId, fabricIndex, clusterIds.at(0), attributeIds.at(0)); + chip::Optional dataVersion = chip::NullOptional; + if (mDataVersions.HasValue()) + { + dataVersion.SetValue(mDataVersions.Value().at(0)); + } + + return InteractionModelWriter::WriteGroupAttribute(groupId, fabricIndex, clusterIds.at(0), attributeIds.at(0), value, + dataVersion); + } + + void Shutdown() override + { + mError = CHIP_NO_ERROR; + ModelCommand::Shutdown(); + } + +protected: + WriteAttribute(const char * attributeName, CredentialIssuerCommands * credsIssuerConfig) : + InteractionModelWriter(this), ModelCommand(kWriteCommandKey, credsIssuerConfig) + { + // Subclasses are responsible for calling AddArguments. + } + + void AddArgumentClusterIds() + { + AddArgument("cluster-ids", 0, UINT32_MAX, &mClusterIds, + "Comma-separated list of cluster ids to write to (e.g. \"6\" or \"6,0x201\")."); + } + + void AddArgumentAttributeIds() + { + AddArgument("attribute-ids", 0, UINT32_MAX, &mAttributeIds, + "Comma-separated list of attribute ids to write (e.g. \"16385\" or \"16385,0x4002\")."); + } + + void AddArgumentAttributeName(const char * attributeName) + { + AddArgument("attribute-name", attributeName, "The attribute name to write."); + } + + template >::value, int> = 0> + static const char * GetAttributeValuesDescription() + { + return "Semicolon-separated list of attribute values to write. Each value is represented as follows, depending on the " + "type:\n" + " * struct: a JSON-encoded object, with field ids as keys.\n" + " * list: a JSON-encoded array of values.\n" + " * null: A literal null.\n" + " * boolean: A literal true or false.\n" + " * unsigned integer: One of:\n" + " a) The number directly, as decimal.\n" + " b) The number directly, as 0x followed by hex digits. (Only for the toplevel value, not inside structs or " + "lists.)\n" + " c) A string starting with \"u:\" followed by decimal digits\n" + " * signed integer: One of:\n" + " a) The number directly, if it's negative.\n" + " c) A string starting with \"s:\" followed by decimal digits\n" + " * single-precision float: A string starting with \"f:\" followed by the number.\n" + " * double-precision float: One of:\n" + " a) The number directly, if it's not an integer.\n" + " b) A string starting with \"d:\" followed by the number.\n" + " * octet string: A string starting with \"hex:\" followed by the hex encoding of the bytes.\n" + " * string: A string with the characters.\n" + "\n" + " Example values: '10;20', '10;\"u:20\"', '\"hex:aabbcc\";\"hello\"'."; + } + + static const char * GetTypedAttributeValuesDescription() { return "Comma-separated list of attribute values to write."; } + + template >::value, int> = 0> + static const char * GetAttributeValuesDescription() + { + return GetTypedAttributeValuesDescription(); + } + + template + void AddArgumentAttributeValues(minType minValue, maxType maxValue) + { + AddArgument("attribute-values", minValue, maxValue, &mAttributeValues, GetTypedAttributeValuesDescription()); + } + + void AddArgumentAttributeValues() { AddArgument("attribute-values", &mAttributeValues, GetAttributeValuesDescription()); } + + void AddArgumentAttributeValues(TypedComplexArgument & attributeParser) + { + attributeParser.SetArgument(&mAttributeValues); + AddArgument("attribute-values", &attributeParser, GetTypedAttributeValuesDescription()); + } + + void AddArguments() + { + AddArgument("timedInteractionTimeoutMs", 0, UINT16_MAX, &mTimedInteractionTimeoutMs, + "If provided, do a timed write with the given timed interaction timeout. See \"7.6.10. Timed Interaction\" in " + "the Matter specification."); + AddArgument("busyWaitForMs", 0, UINT16_MAX, &mBusyWaitForMs, + "If provided, block the main thread processing for the given time right after sending a command."); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersions, + "Comma-separated list of data versions for the clusters being written."); + AddArgument("suppressResponse", 0, 1, &mSuppressResponse); + AddArgument("repeat-count", 1, UINT16_MAX, &mRepeatCount); + AddArgument("repeat-delay-ms", 0, UINT16_MAX, &mRepeatDelayInMs); + ModelCommand::AddArguments(); + } + +private: + // This constructor is private as it is not intended to be used from outside the class. + WriteAttribute(chip::ClusterId clusterId, chip::AttributeId attributeId, WriteCommandType commandType, + CredentialIssuerCommands * credsIssuerConfig) : + InteractionModelWriter(this), + ModelCommand(commandType == WriteCommandType::kWrite ? kWriteCommandKey : kForceWriteCommandKey, credsIssuerConfig), + mClusterIds(1, clusterId), mAttributeIds(1, attributeId) + {} + + std::vector mClusterIds; + std::vector mAttributeIds; + + CHIP_ERROR mError = CHIP_NO_ERROR; + T mAttributeValues; +}; + +template +class WriteAttributeAsComplex : public WriteAttribute +{ +public: + WriteAttributeAsComplex(chip::ClusterId clusterId, const char * attributeName, chip::AttributeId attributeId, + WriteCommandType commandType, CredentialIssuerCommands * credsIssuerConfig) : + WriteAttribute(clusterId, attributeName, attributeId, mAttributeParser, commandType, credsIssuerConfig) + {} + +private: + TypedComplexArgument mAttributeParser; +}; diff --git a/examples/fabric-admin/commands/common/CHIPCommand.cpp b/examples/fabric-admin/commands/common/CHIPCommand.cpp new file mode 100644 index 00000000000000..982c857d206136 --- /dev/null +++ b/examples/fabric-admin/commands/common/CHIPCommand.cpp @@ -0,0 +1,651 @@ +/* + * 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. + * + */ + +#include "CHIPCommand.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +#include "TraceDecoder.h" +#include "TraceHandlers.h" +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + +std::map> CHIPCommand::mCommissioners; +std::set CHIPCommand::sDeferredCleanups; + +using DeviceControllerFactory = chip::Controller::DeviceControllerFactory; + +constexpr chip::FabricId kIdentityNullFabricId = chip::kUndefinedFabricId; +constexpr chip::FabricId kIdentityAlphaFabricId = 1; +constexpr chip::FabricId kIdentityBetaFabricId = 2; +constexpr chip::FabricId kIdentityGammaFabricId = 3; +constexpr chip::FabricId kIdentityOtherFabricId = 4; +constexpr char kPAATrustStorePathVariable[] = "FABRICSYNC_PAA_TRUST_STORE_PATH"; +constexpr char kCDTrustStorePathVariable[] = "FABRICSYNC_CD_TRUST_STORE_PATH"; + +const chip::Credentials::AttestationTrustStore * CHIPCommand::sTrustStore = nullptr; +chip::Credentials::GroupDataProviderImpl CHIPCommand::sGroupDataProvider{ kMaxGroupsPerFabric, kMaxGroupKeysPerFabric }; +// All fabrics share the same ICD client storage. +chip::app::DefaultICDClientStorage CHIPCommand::sICDClientStorage; +chip::Crypto::RawKeySessionKeystore CHIPCommand::sSessionKeystore; +chip::app::DefaultCheckInDelegate CHIPCommand::sCheckInDelegate; +chip::app::CheckInHandler CHIPCommand::sCheckInHandler; + +namespace { + +CHIP_ERROR GetAttestationTrustStore(const char * paaTrustStorePath, const chip::Credentials::AttestationTrustStore ** trustStore) +{ + if (paaTrustStorePath == nullptr) + { + paaTrustStorePath = getenv(kPAATrustStorePathVariable); + } + + if (paaTrustStorePath == nullptr) + { + *trustStore = chip::Credentials::GetTestAttestationTrustStore(); + return CHIP_NO_ERROR; + } + + static chip::Credentials::FileAttestationTrustStore attestationTrustStore{ paaTrustStorePath }; + + if (paaTrustStorePath != nullptr && attestationTrustStore.paaCount() == 0) + { + ChipLogError(NotSpecified, "No PAAs found in path: %s", paaTrustStorePath); + ChipLogError(NotSpecified, + "Please specify a valid path containing trusted PAA certificates using " + "the argument [--paa-trust-store-path paa/file/path] " + "or environment variable [%s=paa/file/path]", + kPAATrustStorePathVariable); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + *trustStore = &attestationTrustStore; + return CHIP_NO_ERROR; +} + +} // namespace + +CHIP_ERROR CHIPCommand::MaybeSetUpStack() +{ + if (IsInteractive()) + { + return CHIP_NO_ERROR; + } + + StartTracing(); + +#if (CHIP_DEVICE_LAYER_TARGET_LINUX || CHIP_DEVICE_LAYER_TARGET_TIZEN) && CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE + // By default, Linux device is configured as a BLE peripheral while the controller needs a BLE central. + ReturnLogErrorOnFailure(chip::DeviceLayer::Internal::BLEMgrImpl().ConfigureBle(mBleAdapterId.ValueOr(0), true)); +#endif + + ReturnLogErrorOnFailure(mDefaultStorage.Init(nullptr, GetStorageDirectory().ValueOr(nullptr))); + ReturnLogErrorOnFailure(mOperationalKeystore.Init(&mDefaultStorage)); + ReturnLogErrorOnFailure(mOpCertStore.Init(&mDefaultStorage)); + + // fabric-admin uses a non-persistent keystore. + // ICD storage lifetime is currently tied to the fabric-admin's lifetime. Since fabric-admin interactive mode is currently used + // for ICD commissioning and check-in validation, this temporary storage meets the test requirements. + // TODO: Implement persistent ICD storage for the fabric-admin. + ReturnLogErrorOnFailure(sICDClientStorage.Init(&mDefaultStorage, &sSessionKeystore)); + + chip::Controller::FactoryInitParams factoryInitParams; + + factoryInitParams.fabricIndependentStorage = &mDefaultStorage; + factoryInitParams.operationalKeystore = &mOperationalKeystore; + factoryInitParams.opCertStore = &mOpCertStore; + factoryInitParams.enableServerInteractions = NeedsOperationalAdvertising(); + factoryInitParams.sessionKeystore = &sSessionKeystore; + + // Init group data provider that will be used for all group keys and IPKs for the + // fabric-admin-configured fabrics. This is OK to do once since the fabric tables + // and the DeviceControllerFactory all "share" in the same underlying data. + // Different commissioner implementations may want to use alternate implementations + // of GroupDataProvider for injection through factoryInitParams. + sGroupDataProvider.SetStorageDelegate(&mDefaultStorage); + sGroupDataProvider.SetSessionKeystore(factoryInitParams.sessionKeystore); + ReturnLogErrorOnFailure(sGroupDataProvider.Init()); + chip::Credentials::SetGroupDataProvider(&sGroupDataProvider); + factoryInitParams.groupDataProvider = &sGroupDataProvider; + + uint16_t port = mDefaultStorage.GetListenPort(); + if (port != 0) + { + // Make sure different commissioners run on different ports. + port = static_cast(port + CurrentCommissionerId()); + } + factoryInitParams.listenPort = port; + ReturnLogErrorOnFailure(DeviceControllerFactory::GetInstance().Init(factoryInitParams)); + + auto systemState = chip::Controller::DeviceControllerFactory::GetInstance().GetSystemState(); + VerifyOrReturnError(nullptr != systemState, CHIP_ERROR_INCORRECT_STATE); + + ReturnErrorOnFailure(GetAttestationTrustStore(mPaaTrustStorePath.ValueOr(nullptr), &sTrustStore)); + + auto engine = chip::app::InteractionModelEngine::GetInstance(); + VerifyOrReturnError(engine != nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnLogErrorOnFailure(sCheckInDelegate.Init(&sICDClientStorage, engine)); + ReturnLogErrorOnFailure(sCheckInHandler.Init(DeviceControllerFactory::GetInstance().GetSystemState()->ExchangeMgr(), + &sICDClientStorage, &sCheckInDelegate, engine)); + + CommissionerIdentity nullIdentity{ kIdentityNull, chip::kUndefinedNodeId }; + ReturnLogErrorOnFailure(InitializeCommissioner(nullIdentity, kIdentityNullFabricId)); + + // After initializing first commissioner, add the additional CD certs once + { + const char * cdTrustStorePath = mCDTrustStorePath.ValueOr(nullptr); + if (cdTrustStorePath == nullptr) + { + cdTrustStorePath = getenv(kCDTrustStorePathVariable); + } + + auto additionalCdCerts = + chip::Credentials::LoadAllX509DerCerts(cdTrustStorePath, chip::Credentials::CertificateValidationMode::kPublicKeyOnly); + if (cdTrustStorePath != nullptr && additionalCdCerts.size() == 0) + { + ChipLogError(NotSpecified, "Warning: no CD signing certs found in path: %s, only defaults will be used", + cdTrustStorePath); + ChipLogError(NotSpecified, + "Please specify a path containing trusted CD verifying key certificates using " + "the argument [--cd-trust-store-path cd/file/path] " + "or environment variable [%s=cd/file/path]", + kCDTrustStorePathVariable); + } + ReturnErrorOnFailure(mCredIssuerCmds->AddAdditionalCDVerifyingCerts(additionalCdCerts)); + } + bool allowTestCdSigningKey = !mOnlyAllowTrustedCdKeys.ValueOr(false); + mCredIssuerCmds->SetCredentialIssuerOption(CredentialIssuerCommands::CredentialIssuerOptions::kAllowTestCdSigningKey, + allowTestCdSigningKey); + + return CHIP_NO_ERROR; +} + +void CHIPCommand::MaybeTearDownStack() +{ + if (IsInteractive()) + { + return; + } + + // + // We can call DeviceController::Shutdown() safely without grabbing the stack lock + // since the CHIP thread and event queue have been stopped, preventing any thread + // races. + // + for (auto & commissioner : mCommissioners) + { + ShutdownCommissioner(commissioner.first); + } + + StopTracing(); +} + +CHIP_ERROR CHIPCommand::EnsureCommissionerForIdentity(std::string identity) +{ + chip::NodeId nodeId; + ReturnErrorOnFailure(GetIdentityNodeId(identity, &nodeId)); + CommissionerIdentity lookupKey{ identity, nodeId }; + if (mCommissioners.find(lookupKey) != mCommissioners.end()) + { + return CHIP_NO_ERROR; + } + + // Need to initialize the commissioner. + chip::FabricId fabricId; + if (identity == kIdentityAlpha) + { + fabricId = kIdentityAlphaFabricId; + } + else if (identity == kIdentityBeta) + { + fabricId = kIdentityBetaFabricId; + } + else if (identity == kIdentityGamma) + { + fabricId = kIdentityGammaFabricId; + } + else + { + fabricId = strtoull(identity.c_str(), nullptr, 0); + if (fabricId < kIdentityOtherFabricId) + { + ChipLogError(NotSpecified, "Invalid identity: %s", identity.c_str()); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + return InitializeCommissioner(lookupKey, fabricId); +} + +CHIP_ERROR CHIPCommand::Run() +{ + ReturnErrorOnFailure(MaybeSetUpStack()); + + CHIP_ERROR err = StartWaiting(GetWaitDuration()); + + if (IsInteractive()) + { + bool timedOut; + // Give it 2 hours to run our cleanup; that should never get hit in practice. + CHIP_ERROR cleanupErr = RunOnMatterQueue(RunCommandCleanup, chip::System::Clock::Seconds16(7200), &timedOut); + VerifyOrDie(cleanupErr == CHIP_NO_ERROR); + VerifyOrDie(!timedOut); + } + else + { + CleanupAfterRun(); + } + + MaybeTearDownStack(); + + return err; +} + +void CHIPCommand::StartTracing() +{ + if (mTraceTo.HasValue()) + { + for (const auto & destination : mTraceTo.Value()) + { + mTracingSetup.EnableTracingFor(destination.c_str()); + } + } + +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + chip::trace::InitTrace(); + + if (mTraceFile.HasValue()) + { + chip::trace::AddTraceStream(new chip::trace::TraceStreamFile(mTraceFile.Value())); + } + else if (mTraceLog.HasValue() && mTraceLog.Value()) + { + chip::trace::AddTraceStream(new chip::trace::TraceStreamLog()); + } + + if (mTraceDecode.HasValue() && mTraceDecode.Value()) + { + chip::trace::TraceDecoderOptions options; + // The interaction model protocol is already logged, so just disable logging those. + options.mEnableProtocolInteractionModelResponse = false; + chip::trace::TraceDecoder * decoder = new chip::trace::TraceDecoder(); + decoder->SetOptions(options); + chip::trace::AddTraceStream(decoder); + } +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +} + +void CHIPCommand::StopTracing() +{ + mTracingSetup.StopTracing(); + +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + chip::trace::DeInitTrace(); +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +} + +void CHIPCommand::SetIdentity(const char * identity) +{ + std::string name = std::string(identity); + if (name.compare(kIdentityAlpha) != 0 && name.compare(kIdentityBeta) != 0 && name.compare(kIdentityGamma) != 0 && + name.compare(kIdentityNull) != 0 && strtoull(name.c_str(), nullptr, 0) < kIdentityOtherFabricId) + { + ChipLogError(NotSpecified, "Unknown commissioner name: %s. Supported names are [%s, %s, %s, 4, 5...]", name.c_str(), + kIdentityAlpha, kIdentityBeta, kIdentityGamma); + chipDie(); + } + + mCommissionerName.SetValue(const_cast(identity)); +} + +std::string CHIPCommand::GetIdentity() +{ + std::string name = mCommissionerName.HasValue() ? mCommissionerName.Value() : kIdentityAlpha; + if (name.compare(kIdentityAlpha) != 0 && name.compare(kIdentityBeta) != 0 && name.compare(kIdentityGamma) != 0 && + name.compare(kIdentityNull) != 0) + { + chip::FabricId fabricId = strtoull(name.c_str(), nullptr, 0); + if (fabricId >= kIdentityOtherFabricId) + { + // normalize name since it is used in persistent storage + + char s[24]; + sprintf(s, "%lx", fabricId); + + name = s; + } + else + { + ChipLogError(NotSpecified, "Unknown commissioner name: %s. Supported names are [%s, %s, %s, 4, 5...]", name.c_str(), + kIdentityAlpha, kIdentityBeta, kIdentityGamma); + chipDie(); + } + } + + return name; +} + +CHIP_ERROR CHIPCommand::GetIdentityNodeId(std::string identity, chip::NodeId * nodeId) +{ + if (mCommissionerNodeId.HasValue()) + { + *nodeId = mCommissionerNodeId.Value(); + return CHIP_NO_ERROR; + } + + if (identity == kIdentityNull) + { + *nodeId = chip::kUndefinedNodeId; + return CHIP_NO_ERROR; + } + + ReturnLogErrorOnFailure(mCommissionerStorage.Init(identity.c_str(), GetStorageDirectory().ValueOr(nullptr))); + + *nodeId = mCommissionerStorage.GetLocalNodeId(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR CHIPCommand::GetIdentityRootCertificate(std::string identity, chip::ByteSpan & span) +{ + if (identity == kIdentityNull) + { + return CHIP_ERROR_NOT_FOUND; + } + + chip::NodeId nodeId; + VerifyOrDie(GetIdentityNodeId(identity, &nodeId) == CHIP_NO_ERROR); + CommissionerIdentity lookupKey{ identity, nodeId }; + auto item = mCommissioners.find(lookupKey); + + span = chip::ByteSpan(item->first.mRCAC, item->first.mRCACLen); + return CHIP_NO_ERROR; +} + +chip::FabricId CHIPCommand::CurrentCommissionerId() +{ + chip::FabricId id; + + std::string name = GetIdentity(); + if (name.compare(kIdentityAlpha) == 0) + { + id = kIdentityAlphaFabricId; + } + else if (name.compare(kIdentityBeta) == 0) + { + id = kIdentityBetaFabricId; + } + else if (name.compare(kIdentityGamma) == 0) + { + id = kIdentityGammaFabricId; + } + else if (name.compare(kIdentityNull) == 0) + { + id = kIdentityNullFabricId; + } + else if ((id = strtoull(name.c_str(), nullptr, 0)) < kIdentityOtherFabricId) + { + VerifyOrDieWithMsg(false, NotSpecified, "Unknown commissioner name: %s. Supported names are [%s, %s, %s, 4, 5...]", + name.c_str(), kIdentityAlpha, kIdentityBeta, kIdentityGamma); + } + + return id; +} + +chip::Controller::DeviceCommissioner & CHIPCommand::CurrentCommissioner() +{ + return GetCommissioner(GetIdentity()); +} + +chip::Controller::DeviceCommissioner & CHIPCommand::GetCommissioner(std::string identity) +{ + // We don't have a great way to handle commissioner setup failures here. + // This only matters for commands (like TestCommand) that involve multiple + // identities. + VerifyOrDie(EnsureCommissionerForIdentity(identity) == CHIP_NO_ERROR); + + chip::NodeId nodeId; + VerifyOrDie(GetIdentityNodeId(identity, &nodeId) == CHIP_NO_ERROR); + CommissionerIdentity lookupKey{ identity, nodeId }; + auto item = mCommissioners.find(lookupKey); + VerifyOrDie(item != mCommissioners.end()); + return *item->second; +} + +void CHIPCommand::ShutdownCommissioner(const CommissionerIdentity & key) +{ + mCommissioners[key].get()->Shutdown(); +} + +CHIP_ERROR CHIPCommand::InitializeCommissioner(CommissionerIdentity & identity, chip::FabricId fabricId) +{ + std::unique_ptr commissioner = std::make_unique(); + chip::Controller::SetupParams commissionerParams; + + ReturnLogErrorOnFailure(mCredIssuerCmds->SetupDeviceAttestation(commissionerParams, sTrustStore)); + + chip::Crypto::P256Keypair ephemeralKey; + + if (fabricId != chip::kUndefinedFabricId) + { + + // TODO - OpCreds should only be generated for pairing command + // store the credentials in persistent storage, and + // generate when not available in the storage. + ReturnLogErrorOnFailure(mCommissionerStorage.Init(identity.mName.c_str(), GetStorageDirectory().ValueOr(nullptr))); + if (mUseMaxSizedCerts.HasValue()) + { + auto option = CredentialIssuerCommands::CredentialIssuerOptions::kMaximizeCertificateSizes; + mCredIssuerCmds->SetCredentialIssuerOption(option, mUseMaxSizedCerts.Value()); + } + + ReturnLogErrorOnFailure(mCredIssuerCmds->InitializeCredentialsIssuer(mCommissionerStorage)); + + chip::MutableByteSpan nocSpan(identity.mNOC); + chip::MutableByteSpan icacSpan(identity.mICAC); + chip::MutableByteSpan rcacSpan(identity.mRCAC); + + ReturnLogErrorOnFailure(ephemeralKey.Initialize(chip::Crypto::ECPKeyTarget::ECDSA)); + + ReturnLogErrorOnFailure(mCredIssuerCmds->GenerateControllerNOCChain(identity.mLocalNodeId, fabricId, + mCommissionerStorage.GetCommissionerCATs(), + ephemeralKey, rcacSpan, icacSpan, nocSpan)); + + identity.mRCACLen = rcacSpan.size(); + identity.mICACLen = icacSpan.size(); + identity.mNOCLen = nocSpan.size(); + + commissionerParams.operationalKeypair = &ephemeralKey; + commissionerParams.controllerRCAC = rcacSpan; + commissionerParams.controllerICAC = icacSpan; + commissionerParams.controllerNOC = nocSpan; + commissionerParams.permitMultiControllerFabrics = true; + commissionerParams.enableServerInteractions = NeedsOperationalAdvertising(); + } + + // TODO: Initialize IPK epoch key in ExampleOperationalCredentials issuer rather than relying on DefaultIpkValue + commissionerParams.operationalCredentialsDelegate = mCredIssuerCmds->GetCredentialIssuer(); + commissionerParams.controllerVendorId = mCommissionerVendorId.ValueOr(chip::VendorId::TestVendor1); + + ReturnLogErrorOnFailure(DeviceControllerFactory::GetInstance().SetupCommissioner(commissionerParams, *(commissioner.get()))); + + if (identity.mName != kIdentityNull) + { + // Initialize Group Data, including IPK + chip::FabricIndex fabricIndex = commissioner->GetFabricIndex(); + uint8_t compressed_fabric_id[sizeof(uint64_t)]; + chip::MutableByteSpan compressed_fabric_id_span(compressed_fabric_id); + ReturnLogErrorOnFailure(commissioner->GetCompressedFabricIdBytes(compressed_fabric_id_span)); + + ReturnLogErrorOnFailure(chip::GroupTesting::InitData(&sGroupDataProvider, fabricIndex, compressed_fabric_id_span)); + + // Configure the default IPK for all fabrics used by CHIP-tool. The epoch + // key is the same, but the derived keys will be different for each fabric. + chip::ByteSpan defaultIpk = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + ReturnLogErrorOnFailure( + chip::Credentials::SetSingleIpkEpochKey(&sGroupDataProvider, fabricIndex, defaultIpk, compressed_fabric_id_span)); + } + + CHIPCommand::sICDClientStorage.UpdateFabricList(commissioner->GetFabricIndex()); + + mCommissioners[identity] = std::move(commissioner); + + return CHIP_NO_ERROR; +} + +void CHIPCommand::RunQueuedCommand(intptr_t commandArg) +{ + auto * command = reinterpret_cast(commandArg); + CHIP_ERROR err = command->EnsureCommissionerForIdentity(command->GetIdentity()); + if (err == CHIP_NO_ERROR) + { + err = command->RunCommand(); + } + + if (err != CHIP_NO_ERROR) + { + command->SetCommandExitStatus(err); + } +} + +void CHIPCommand::RunCommandCleanup(intptr_t commandArg) +{ + auto * command = reinterpret_cast(commandArg); + command->CleanupAfterRun(); + command->StopWaiting(); +} + +void CHIPCommand::CleanupAfterRun() +{ + assertChipStackLockedByCurrentThread(); + bool deferCleanup = (IsInteractive() && DeferInteractiveCleanup()); + + Shutdown(); + + if (deferCleanup) + { + sDeferredCleanups.insert(this); + } + else + { + Cleanup(); + } +} + +CHIP_ERROR CHIPCommand::RunOnMatterQueue(MatterWorkCallback callback, chip::System::Clock::Timeout timeout, bool * timedOut) +{ + { + std::lock_guard lk(cvWaitingForResponseMutex); + mWaitingForResponse = true; + } + + auto err = chip::DeviceLayer::PlatformMgr().ScheduleWork(callback, reinterpret_cast(this)); + if (CHIP_NO_ERROR != err) + { + { + std::lock_guard lk(cvWaitingForResponseMutex); + mWaitingForResponse = false; + } + return err; + } + + auto waitingUntil = std::chrono::system_clock::now() + std::chrono::duration_cast(timeout); + { + std::unique_lock lk(cvWaitingForResponseMutex); + *timedOut = !cvWaitingForResponse.wait_until(lk, waitingUntil, [this]() { return !this->mWaitingForResponse; }); + } + + return CHIP_NO_ERROR; +} + +#if !CONFIG_USE_SEPARATE_EVENTLOOP +static void OnResponseTimeout(chip::System::Layer *, void * appState) +{ + (reinterpret_cast(appState))->SetCommandExitStatus(CHIP_ERROR_TIMEOUT); +} +#endif // !CONFIG_USE_SEPARATE_EVENTLOOP + +CHIP_ERROR CHIPCommand::StartWaiting(chip::System::Clock::Timeout duration) +{ +#if CONFIG_USE_SEPARATE_EVENTLOOP + // ServiceEvents() calls StartEventLoopTask(), which is paired with the StopEventLoopTask() below. + if (!IsInteractive()) + { + ReturnLogErrorOnFailure(DeviceControllerFactory::GetInstance().ServiceEvents()); + } + + if (duration.count() == 0) + { + mCommandExitStatus = RunCommand(); + } + else + { + bool timedOut; + CHIP_ERROR err = RunOnMatterQueue(RunQueuedCommand, duration, &timedOut); + if (CHIP_NO_ERROR != err) + { + return err; + } + if (timedOut) + { + mCommandExitStatus = CHIP_ERROR_TIMEOUT; + } + } + if (!IsInteractive()) + { + LogErrorOnFailure(chip::DeviceLayer::PlatformMgr().StopEventLoopTask()); + } +#else + chip::DeviceLayer::PlatformMgr().ScheduleWork(RunQueuedCommand, reinterpret_cast(this)); + ReturnLogErrorOnFailure(chip::DeviceLayer::SystemLayer().StartTimer(duration, OnResponseTimeout, this)); + chip::DeviceLayer::PlatformMgr().RunEventLoop(); +#endif // CONFIG_USE_SEPARATE_EVENTLOOP + + return mCommandExitStatus; +} + +void CHIPCommand::StopWaiting() +{ +#if CONFIG_USE_SEPARATE_EVENTLOOP + { + std::lock_guard lk(cvWaitingForResponseMutex); + mWaitingForResponse = false; + } + cvWaitingForResponse.notify_all(); +#else // CONFIG_USE_SEPARATE_EVENTLOOP + LogErrorOnFailure(chip::DeviceLayer::PlatformMgr().StopEventLoopTask()); +#endif // CONFIG_USE_SEPARATE_EVENTLOOP +} + +void CHIPCommand::ExecuteDeferredCleanups(intptr_t ignored) +{ + for (auto * cmd : sDeferredCleanups) + { + cmd->Cleanup(); + } + sDeferredCleanups.clear(); +} diff --git a/examples/fabric-admin/commands/common/CHIPCommand.h b/examples/fabric-admin/commands/common/CHIPCommand.h new file mode 100644 index 00000000000000..856a4daa48753d --- /dev/null +++ b/examples/fabric-admin/commands/common/CHIPCommand.h @@ -0,0 +1,264 @@ +/* + * 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 + +#ifdef CONFIG_USE_LOCAL_STORAGE +#include +#endif // CONFIG_USE_LOCAL_STORAGE + +#include "Command.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +inline constexpr char kIdentityAlpha[] = "alpha"; +inline constexpr char kIdentityBeta[] = "beta"; +inline constexpr char kIdentityGamma[] = "gamma"; +// The null fabric commissioner is a commissioner that isn't on a fabric. +// This is a legal configuration in which the commissioner delegates +// operational communication and invocation of the commssioning complete +// command to a separate on-fabric administrator node. +// +// The null-fabric-commissioner identity is provided here to demonstrate the +// commissioner portion of such an architecture. The null-fabric-commissioner +// can carry a commissioning flow up until the point of operational channel +// (CASE) communcation. +inline constexpr char kIdentityNull[] = "null-fabric-commissioner"; + +class CHIPCommand : public Command +{ +public: + using ChipDeviceCommissioner = ::chip::Controller::DeviceCommissioner; + using ChipDeviceController = ::chip::Controller::DeviceController; + using IPAddress = ::chip::Inet::IPAddress; + using NodeId = ::chip::NodeId; + using PeerId = ::chip::PeerId; + using PeerAddress = ::chip::Transport::PeerAddress; + + static constexpr uint16_t kMaxGroupsPerFabric = 50; + static constexpr uint16_t kMaxGroupKeysPerFabric = 25; + + CHIPCommand(const char * commandName, CredentialIssuerCommands * credIssuerCmds, const char * helpText = nullptr) : + Command(commandName, helpText), mCredIssuerCmds(credIssuerCmds) + { + AddArgument("paa-trust-store-path", &mPaaTrustStorePath, + "Path to directory holding PAA certificate information. Can be absolute or relative to the current working " + "directory."); + AddArgument("cd-trust-store-path", &mCDTrustStorePath, + "Path to directory holding CD certificate information. Can be absolute or relative to the current working " + "directory."); + AddArgument("commissioner-name", &mCommissionerName, + "Name of fabric to use. Valid values are \"alpha\", \"beta\", \"gamma\", and integers greater than or equal to " + "4. The default if not specified is \"alpha\"."); + AddArgument("commissioner-nodeid", 0, UINT64_MAX, &mCommissionerNodeId, + "The node id to use for fabric-admin. If not provided, kTestControllerNodeId (112233, 0x1B669) will be used."); + AddArgument("use-max-sized-certs", 0, 1, &mUseMaxSizedCerts, + "Maximize the size of operational certificates. If not provided or 0 (\"false\"), normally sized operational " + "certificates are generated."); + AddArgument("only-allow-trusted-cd-keys", 0, 1, &mOnlyAllowTrustedCdKeys, + "Only allow trusted CD verifying keys (disallow test keys). If not provided or 0 (\"false\"), untrusted CD " + "verifying keys are allowed. If 1 (\"true\"), test keys are disallowed."); +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + AddArgument("trace_file", &mTraceFile); + AddArgument("trace_log", 0, 1, &mTraceLog); + AddArgument("trace_decode", 0, 1, &mTraceDecode); +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + AddArgument("trace-to", &mTraceTo, "Trace destinations, comma-separated (" SUPPORTED_COMMAND_LINE_TRACING_TARGETS ")"); + AddArgument("ble-adapter", 0, UINT16_MAX, &mBleAdapterId); + AddArgument("storage-directory", &mStorageDirectory, + "Directory to place fabric-admin's storage files in. Defaults to $TMPDIR, with fallback to /tmp"); + AddArgument( + "commissioner-vendor-id", 0, UINT16_MAX, &mCommissionerVendorId, + "The vendor id to use for fabric-admin. If not provided, chip::VendorId::TestVendor1 (65521, 0xFFF1) will be used."); + } + + /////////// Command Interface ///////// + CHIP_ERROR Run() override; + + void SetCommandExitStatus(CHIP_ERROR status) + { + mCommandExitStatus = status; + // In interactive mode the stack is not shut down once a command is ended. + // That means calling `ErrorStr(err)` from the main thread when command + // completion is signaled may race since `ErrorStr` uses a static sErrorStr + // buffer for computing the error string. Call it here instead. + if (IsInteractive() && CHIP_NO_ERROR != status) + { + ChipLogError(NotSpecified, "Run command failure: %s", chip::ErrorStr(status)); + } + StopWaiting(); + } + +protected: + // Will be called in a setting in which it's safe to touch the CHIP + // stack. The rules for Run() are as follows: + // + // 1) If error is returned, Run() must not call SetCommandExitStatus. + // 2) If success is returned Run() must either have called + // SetCommandExitStatus() or scheduled async work that will do that. + virtual CHIP_ERROR RunCommand() = 0; + + // Get the wait duration, in seconds, before the command times out. + virtual chip::System::Clock::Timeout GetWaitDuration() const = 0; + + // Shut down the command. After a Shutdown call the command object is ready + // to be used for another command invocation. + virtual void Shutdown() { ResetArguments(); } + + // Clean up any resources allocated by the command. Some commands may hold + // on to resources after Shutdown(), but Cleanup() will guarantee those are + // cleaned up. + virtual void Cleanup() {} + + // If true, skip calling Cleanup() when in interactive mode, so the command + // can keep doing work as needed. Cleanup() will be called when quitting + // interactive mode. This method will be called before Shutdown, so it can + // use member values that Shutdown will normally reset. + virtual bool DeferInteractiveCleanup() { return false; } + + // If true, the controller will be created with server capabilities enabled, + // such as advertising operational nodes over DNS-SD and accepting incoming + // CASE sessions. + virtual bool NeedsOperationalAdvertising() { return mAdvertiseOperational; } + + // Execute any deferred cleanups. Used when exiting interactive mode. + static void ExecuteDeferredCleanups(intptr_t ignored); + +#ifdef CONFIG_USE_LOCAL_STORAGE + PersistentStorage mDefaultStorage; + // TODO: It's pretty weird that we re-init mCommissionerStorage for every + // identity without shutting it down or something in between... + PersistentStorage mCommissionerStorage; +#endif // CONFIG_USE_LOCAL_STORAGE + chip::PersistentStorageOperationalKeystore mOperationalKeystore; + chip::Credentials::PersistentStorageOpCertStore mOpCertStore; + static chip::Crypto::RawKeySessionKeystore sSessionKeystore; + + static chip::Credentials::GroupDataProviderImpl sGroupDataProvider; + static chip::app::DefaultICDClientStorage sICDClientStorage; + static chip::app::DefaultCheckInDelegate sCheckInDelegate; + static chip::app::CheckInHandler sCheckInHandler; + CredentialIssuerCommands * mCredIssuerCmds; + + std::string GetIdentity(); + CHIP_ERROR GetIdentityNodeId(std::string identity, chip::NodeId * nodeId); + CHIP_ERROR GetIdentityRootCertificate(std::string identity, chip::ByteSpan & span); + void SetIdentity(const char * name); + + // This method returns the commissioner instance to be used for running the command. + // The default commissioner instance name is "alpha", but it can be overridden by passing + // --identity "instance name" when running a command. + ChipDeviceCommissioner & CurrentCommissioner(); + + ChipDeviceCommissioner & GetCommissioner(std::string identity); + +private: + CHIP_ERROR MaybeSetUpStack(); + void MaybeTearDownStack(); + + CHIP_ERROR EnsureCommissionerForIdentity(std::string identity); + + // Commissioners are keyed by name and local node id. + struct CommissionerIdentity + { + bool operator<(const CommissionerIdentity & other) const + { + return mName < other.mName || (mName == other.mName && mLocalNodeId < other.mLocalNodeId); + } + std::string mName; + chip::NodeId mLocalNodeId; + uint8_t mRCAC[chip::Controller::kMaxCHIPDERCertLength] = {}; + uint8_t mICAC[chip::Controller::kMaxCHIPDERCertLength] = {}; + uint8_t mNOC[chip::Controller::kMaxCHIPDERCertLength] = {}; + + size_t mRCACLen; + size_t mICACLen; + size_t mNOCLen; + }; + + // InitializeCommissioner uses various members, so can't be static. This is + // obviously a little odd, since the commissioners are then shared across + // multiple commands in interactive mode... + CHIP_ERROR InitializeCommissioner(CommissionerIdentity & identity, chip::FabricId fabricId); + void ShutdownCommissioner(const CommissionerIdentity & key); + chip::FabricId CurrentCommissionerId(); + + static std::map> mCommissioners; + static std::set sDeferredCleanups; + + chip::Optional mCommissionerName; + chip::Optional mCommissionerNodeId; + chip::Optional mCommissionerVendorId; + chip::Optional mBleAdapterId; + chip::Optional mPaaTrustStorePath; + chip::Optional mCDTrustStorePath; + chip::Optional mUseMaxSizedCerts; + chip::Optional mOnlyAllowTrustedCdKeys; + + // Cached trust store so commands other than the original startup command + // can spin up commissioners as needed. + static const chip::Credentials::AttestationTrustStore * sTrustStore; + + static void RunQueuedCommand(intptr_t commandArg); + typedef decltype(RunQueuedCommand) MatterWorkCallback; + static void RunCommandCleanup(intptr_t commandArg); + + // Do cleanup after a commmand is done running. Must happen with the + // Matter stack locked. + void CleanupAfterRun(); + + // Run the given callback on the Matter thread. Return whether we managed + // to successfully dispatch it to the Matter thread. If we did, *timedOut + // will be set to whether we timed out or whether our mWaitingForResponse + // got set to false by the callback itself. + CHIP_ERROR RunOnMatterQueue(MatterWorkCallback callback, chip::System::Clock::Timeout timeout, bool * timedOut); + + CHIP_ERROR mCommandExitStatus = CHIP_ERROR_INTERNAL; + + CHIP_ERROR StartWaiting(chip::System::Clock::Timeout seconds); + void StopWaiting(); + +#if CONFIG_USE_SEPARATE_EVENTLOOP + std::condition_variable cvWaitingForResponse; + std::mutex cvWaitingForResponseMutex; + bool mWaitingForResponse{ true }; +#endif // CONFIG_USE_SEPARATE_EVENTLOOP + + void StartTracing(); + void StopTracing(); + +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + chip::Optional mTraceFile; + chip::Optional mTraceLog; + chip::Optional mTraceDecode; +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + + chip::CommandLineApp::TracingSetup mTracingSetup; + chip::Optional> mTraceTo; +}; diff --git a/examples/fabric-admin/commands/common/Command.cpp b/examples/fabric-admin/commands/common/Command.cpp new file mode 100644 index 00000000000000..7e5689130e3e97 --- /dev/null +++ b/examples/fabric-admin/commands/common/Command.cpp @@ -0,0 +1,1088 @@ +/* + * 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. + * + */ + +#include "Command.h" +#include "CustomStringPrefix.h" +#include "HexConversion.h" +#include "platform/PlatformManager.h" + +#include +#include +#include +#include +#include +#include + +#include // For INFINITY + +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr char kOptionalArgumentPrefix[] = "--"; +constexpr size_t kOptionalArgumentPrefixLength = 2; + +bool Command::InitArguments(int argc, char ** argv) +{ + bool isValidCommand = false; + + size_t argvExtraArgsCount = (size_t) argc; + size_t mandatoryArgsCount = 0; + size_t optionalArgsCount = 0; + for (auto & arg : mArgs) + { + if (arg.isOptional()) + { + optionalArgsCount++; + } + else + { + mandatoryArgsCount++; + argvExtraArgsCount--; + } + } + + VerifyOrExit((size_t) (argc) >= mandatoryArgsCount && (argvExtraArgsCount == 0 || (argvExtraArgsCount && optionalArgsCount)), + ChipLogError(NotSpecified, "InitArgs: Wrong arguments number: %d instead of %u", argc, + static_cast(mandatoryArgsCount))); + + // Initialize mandatory arguments + for (size_t i = 0; i < mandatoryArgsCount; i++) + { + char * arg = argv[i]; + if (!InitArgument(i, arg)) + { + ExitNow(); + } + } + + // Initialize optional arguments + // Optional arguments expect a name and a value, so i is increased by 2 on every step. + for (size_t i = mandatoryArgsCount; i < (size_t) argc; i += 2) + { + bool found = false; + for (size_t j = mandatoryArgsCount; j < mandatoryArgsCount + optionalArgsCount; j++) + { + // optional arguments starts with kOptionalArgumentPrefix + if (strlen(argv[i]) <= kOptionalArgumentPrefixLength && + strncmp(argv[i], kOptionalArgumentPrefix, kOptionalArgumentPrefixLength) != 0) + { + continue; + } + + if (strcmp(argv[i] + strlen(kOptionalArgumentPrefix), mArgs[j].name) == 0) + { + found = true; + + VerifyOrExit((size_t) argc > (i + 1), + ChipLogError(NotSpecified, "InitArgs: Optional argument %s missing value.", argv[i])); + if (!InitArgument(j, argv[i + 1])) + { + ExitNow(); + } + } + } + VerifyOrExit(found, ChipLogError(NotSpecified, "InitArgs: Optional argument %s does not exist.", argv[i])); + } + + isValidCommand = true; + +exit: + return isValidCommand; +} + +static bool ParseAddressWithInterface(const char * addressString, Command::AddressWithInterface * address) +{ + struct addrinfo hints; + struct addrinfo * result; + int ret; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + ret = getaddrinfo(addressString, nullptr, &hints, &result); + if (ret < 0) + { + ChipLogError(NotSpecified, "Invalid address: %s", addressString); + return false; + } + + if (result->ai_family == AF_INET6) + { + struct sockaddr_in6 * addr = reinterpret_cast(result->ai_addr); + address->address = ::chip::Inet::IPAddress::FromSockAddr(*addr); + address->interfaceId = ::chip::Inet::InterfaceId(addr->sin6_scope_id); + } +#if INET_CONFIG_ENABLE_IPV4 + else if (result->ai_family == AF_INET) + { + address->address = ::chip::Inet::IPAddress::FromSockAddr(*reinterpret_cast(result->ai_addr)); + address->interfaceId = chip::Inet::InterfaceId::Null(); + } +#endif // INET_CONFIG_ENABLE_IPV4 + else + { + ChipLogError(NotSpecified, "Unsupported address: %s", addressString); + return false; + } + + return true; +} + +// The callback should return whether the argument is valid, for the non-null +// case. It can't directly write to isValidArgument (by closing over it) +// because in the nullable-and-null case we need to do that from this function, +// via the return value. +template +bool HandleNullableOptional(Argument & arg, char * argValue, std::function callback) +{ + if (arg.isOptional()) + { + if (arg.isNullable()) + { + arg.value = &(reinterpret_cast> *>(arg.value)->Emplace()); + } + else + { + arg.value = &(reinterpret_cast *>(arg.value)->Emplace()); + } + } + + if (arg.isNullable()) + { + auto * nullable = reinterpret_cast *>(arg.value); + if (argValue != nullptr && strncmp(argValue, "null", 4) == 0) + { + nullable->SetNull(); + return true; + } + + arg.value = &(nullable->SetNonNull()); + } + + return callback(reinterpret_cast(arg.value)); +} + +bool Command::InitArgument(size_t argIndex, char * argValue) +{ + bool isValidArgument = false; + bool isHexNotation = strncmp(argValue, "0x", 2) == 0 || strncmp(argValue, "0X", 2) == 0; + + Argument arg = mArgs.at(argIndex); + + // We have two places where we handle uint8_t-typed args (actual int8u and + // bool args), so declare the handler function here so it can be reused. + auto uint8Handler = [&](uint8_t * value) { + // stringstream treats uint8_t as char, which is not what we want here. + uint16_t tmpValue; + std::stringstream ss; + isHexNotation ? (ss << std::hex << argValue) : (ss << argValue); + ss >> tmpValue; + if (chip::CanCastTo(tmpValue)) + { + *value = static_cast(tmpValue); + + uint64_t min = chip::CanCastTo(arg.min) ? static_cast(arg.min) : 0; + uint64_t max = arg.max; + return (!ss.fail() && ss.eof() && *value >= min && *value <= max); + } + + return false; + }; + + switch (arg.type) + { + case ArgumentType::Complex: { + // Complex arguments may be optional, but they are not currently supported via the class. + // Instead, they must be explicitly specified as optional using the kOptional flag, + // and the base TypedComplexArgument class is still referenced. + auto complexArgument = static_cast(arg.value); + return CHIP_NO_ERROR == complexArgument->Parse(arg.name, argValue); + } + + case ArgumentType::Custom: { + auto customArgument = static_cast(arg.value); + return CHIP_NO_ERROR == customArgument->Parse(arg.name, argValue); + } + + case ArgumentType::VectorString: { + std::vector vectorArgument; + + chip::StringSplitter splitter(argValue, ','); + chip::CharSpan value; + + while (splitter.Next(value)) + { + vectorArgument.push_back(std::string(value.data(), value.size())); + } + + if (arg.flags == Argument::kOptional) + { + auto argument = static_cast> *>(arg.value); + argument->SetValue(vectorArgument); + } + else + { + auto argument = static_cast *>(arg.value); + *argument = vectorArgument; + } + return true; + } + case ArgumentType::VectorBool: { + // Currently only chip::Optional> is supported. + if (arg.flags != Argument::kOptional) + { + return false; + } + + std::vector vectorArgument; + std::stringstream ss(argValue); + while (ss.good()) + { + std::string valueAsString; + getline(ss, valueAsString, ','); + + if (strcasecmp(valueAsString.c_str(), "true") == 0) + { + vectorArgument.push_back(true); + } + else if (strcasecmp(valueAsString.c_str(), "false") == 0) + { + vectorArgument.push_back(false); + } + else + { + return false; + } + } + + auto optionalArgument = static_cast> *>(arg.value); + optionalArgument->SetValue(vectorArgument); + return true; + } + + case ArgumentType::Vector16: + case ArgumentType::Vector32: { + std::vector values; + uint64_t min = chip::CanCastTo(arg.min) ? static_cast(arg.min) : 0; + uint64_t max = arg.max; + + std::stringstream ss(argValue); + while (ss.good()) + { + std::string valueAsString; + getline(ss, valueAsString, ','); + isHexNotation = strncmp(valueAsString.c_str(), "0x", 2) == 0 || strncmp(valueAsString.c_str(), "0X", 2) == 0; + + std::stringstream subss; + isHexNotation ? subss << std::hex << valueAsString : subss << valueAsString; + + uint64_t value; + subss >> value; + VerifyOrReturnError(!subss.fail() && subss.eof() && value >= min && value <= max, false); + values.push_back(value); + } + + if (arg.type == ArgumentType::Vector16) + { + auto vectorArgument = static_cast *>(arg.value); + for (uint64_t v : values) + { + vectorArgument->push_back(static_cast(v)); + } + } + else if (arg.type == ArgumentType::Vector32 && arg.flags != Argument::kOptional) + { + auto vectorArgument = static_cast *>(arg.value); + for (uint64_t v : values) + { + vectorArgument->push_back(static_cast(v)); + } + } + else if (arg.type == ArgumentType::Vector32 && arg.flags == Argument::kOptional) + { + std::vector vectorArgument; + for (uint64_t v : values) + { + vectorArgument.push_back(static_cast(v)); + } + + auto optionalArgument = static_cast> *>(arg.value); + optionalArgument->SetValue(vectorArgument); + } + else + { + return false; + } + + return true; + } + + case ArgumentType::VectorCustom: { + auto vectorArgument = static_cast *>(arg.value); + + std::stringstream ss(argValue); + while (ss.good()) + { + std::string valueAsString; + // By default the parameter separator is ";" in order to not collapse with the argument itself if it contains commas + // (e.g a struct argument with multiple fields). In case one needs to use ";" it can be overriden with the following + // environment variable. + static constexpr char kSeparatorVariable[] = "NotSpecified,_CUSTOM_ARGUMENTS_SEPARATOR"; + char * getenvSeparatorVariableResult = getenv(kSeparatorVariable); + getline(ss, valueAsString, getenvSeparatorVariableResult ? getenvSeparatorVariableResult[0] : ';'); + + CustomArgument * customArgument = new CustomArgument(); + vectorArgument->push_back(customArgument); + VerifyOrReturnError(CHIP_NO_ERROR == vectorArgument->back()->Parse(arg.name, valueAsString.c_str()), false); + } + + return true; + } + + case ArgumentType::String: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + *value = argValue; + return true; + }); + break; + } + + case ArgumentType::CharString: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + *value = chip::Span(argValue, strlen(argValue)); + return true; + }); + break; + } + + case ArgumentType::OctetString: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + // We support two ways to pass an octet string argument. If it happens + // to be all-ASCII, you can just pass it in. Otherwise you can pass in + // "hex:" followed by the hex-encoded bytes. + size_t argLen = strlen(argValue); + + if (IsHexString(argValue)) + { + // Hex-encoded. Decode it into a temporary buffer first, so if we + // run into errors we can do correct "argument is not valid" logging + // that actually shows the value that was passed in. After we + // determine it's valid, modify the passed-in value to hold the + // right bytes, so we don't need to worry about allocating storage + // for this somewhere else. This works because the hex + // representation is always longer than the octet string it encodes, + // so we have enough space in argValue for the decoded version. + chip::Platform::ScopedMemoryBuffer buffer; + + size_t octetCount; + CHIP_ERROR err = HexToBytes( + chip::CharSpan(argValue + kHexStringPrefixLen, argLen - kHexStringPrefixLen), + [&buffer](size_t allocSize) { + buffer.Calloc(allocSize); + return buffer.Get(); + }, + &octetCount); + if (err != CHIP_NO_ERROR) + { + return false; + } + + memcpy(argValue, buffer.Get(), octetCount); + *value = chip::ByteSpan(chip::Uint8::from_char(argValue), octetCount); + return true; + } + + // Just ASCII. Check for the "str:" prefix. + if (IsStrString(argValue)) + { + // Skip the prefix + argValue += kStrStringPrefixLen; + argLen -= kStrStringPrefixLen; + } + *value = chip::ByteSpan(chip::Uint8::from_char(argValue), argLen); + return true; + }); + break; + } + + case ArgumentType::Bool: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + // Start with checking for actual boolean values. + if (strcasecmp(argValue, "true") == 0) + { + *value = true; + return true; + } + + if (strcasecmp(argValue, "false") == 0) + { + *value = false; + return true; + } + + // For backwards compat, keep accepting 0 and 1 for now as synonyms + // for false and true. Since we set our min to 0 and max to 1 for + // booleans, calling uint8Handler does the right thing in terms of + // only allowing those two values. + uint8_t temp = 0; + if (!uint8Handler(&temp)) + { + return false; + } + *value = (temp == 1); + return true; + }); + break; + } + + case ArgumentType::Number_uint8: { + isValidArgument = HandleNullableOptional(arg, argValue, uint8Handler); + break; + } + + case ArgumentType::Number_uint16: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + std::stringstream ss; + isHexNotation ? ss << std::hex << argValue : ss << argValue; + ss >> *value; + + uint64_t min = chip::CanCastTo(arg.min) ? static_cast(arg.min) : 0; + uint64_t max = arg.max; + return (!ss.fail() && ss.eof() && *value >= min && *value <= max); + }); + break; + } + + case ArgumentType::Number_uint32: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + std::stringstream ss; + isHexNotation ? ss << std::hex << argValue : ss << argValue; + ss >> *value; + + uint64_t min = chip::CanCastTo(arg.min) ? static_cast(arg.min) : 0; + uint64_t max = arg.max; + return (!ss.fail() && ss.eof() && *value >= min && *value <= max); + }); + break; + } + + case ArgumentType::Number_uint64: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + std::stringstream ss; + isHexNotation ? ss << std::hex << argValue : ss << argValue; + ss >> *value; + + uint64_t min = chip::CanCastTo(arg.min) ? static_cast(arg.min) : 0; + uint64_t max = arg.max; + return (!ss.fail() && ss.eof() && *value >= min && *value <= max); + }); + break; + } + + case ArgumentType::Number_int8: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + // stringstream treats int8_t as char, which is not what we want here. + int16_t tmpValue; + std::stringstream ss; + isHexNotation ? ss << std::hex << argValue : ss << argValue; + ss >> tmpValue; + if (chip::CanCastTo(tmpValue)) + { + *value = static_cast(tmpValue); + + int64_t min = arg.min; + int64_t max = chip::CanCastTo(arg.max) ? static_cast(arg.max) : INT64_MAX; + return (!ss.fail() && ss.eof() && *value >= min && *value <= max); + } + + return false; + }); + break; + } + + case ArgumentType::Number_int16: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + std::stringstream ss; + isHexNotation ? ss << std::hex << argValue : ss << argValue; + ss >> *value; + + int64_t min = arg.min; + int64_t max = chip::CanCastTo(arg.max) ? static_cast(arg.max) : INT64_MAX; + return (!ss.fail() && ss.eof() && *value >= min && *value <= max); + }); + break; + } + + case ArgumentType::Number_int32: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + std::stringstream ss; + isHexNotation ? ss << std::hex << argValue : ss << argValue; + ss >> *value; + + int64_t min = arg.min; + int64_t max = chip::CanCastTo(arg.max) ? static_cast(arg.max) : INT64_MAX; + return (!ss.fail() && ss.eof() && *value >= min && *value <= max); + }); + break; + } + + case ArgumentType::Number_int64: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + std::stringstream ss; + isHexNotation ? ss << std::hex << argValue : ss << argValue; + ss >> *value; + + int64_t min = arg.min; + int64_t max = chip::CanCastTo(arg.max) ? static_cast(arg.max) : INT64_MAX; + return (!ss.fail() && ss.eof() && *value >= min && *value <= max); + }); + break; + } + + case ArgumentType::Float: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + if (strcmp(argValue, "Infinity") == 0) + { + *value = INFINITY; + return true; + } + + if (strcmp(argValue, "-Infinity") == 0) + { + *value = -INFINITY; + return true; + } + + std::stringstream ss; + ss << argValue; + ss >> *value; + return (!ss.fail() && ss.eof()); + }); + break; + } + + case ArgumentType::Double: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + if (strcmp(argValue, "Infinity") == 0) + { + *value = INFINITY; + return true; + } + + if (strcmp(argValue, "-Infinity") == 0) + { + *value = -INFINITY; + return true; + } + + std::stringstream ss; + ss << argValue; + ss >> *value; + return (!ss.fail() && ss.eof()); + }); + break; + } + + case ArgumentType::Address: { + isValidArgument = HandleNullableOptional( + arg, argValue, [&](auto * value) { return ParseAddressWithInterface(argValue, value); }); + break; + } + } + + if (!isValidArgument) + { + ChipLogError(NotSpecified, "InitArgs: Invalid argument %s: %s", arg.name, argValue); + } + + return isValidArgument; +} + +void Command::AddArgument(const char * name, const char * value, const char * desc) +{ + ReadOnlyGlobalCommandArgument arg; + arg.name = name; + arg.value = value; + arg.desc = desc; + + mReadOnlyGlobalCommandArgument.SetValue(arg); +} + +size_t Command::AddArgument(const char * name, char ** value, const char * desc, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::String; + arg.name = name; + arg.value = reinterpret_cast(value); + arg.flags = flags; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, chip::CharSpan * value, const char * desc, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::CharString; + arg.name = name; + arg.value = reinterpret_cast(value); + arg.flags = flags; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, chip::ByteSpan * value, const char * desc, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::OctetString; + arg.name = name; + arg.value = reinterpret_cast(value); + arg.flags = flags; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, AddressWithInterface * out, const char * desc, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::Address; + arg.name = name; + arg.value = reinterpret_cast(out); + arg.flags = flags; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, std::vector * value, const char * desc) +{ + Argument arg; + arg.type = ArgumentType::Vector16; + arg.name = name; + arg.value = static_cast(value); + arg.min = min; + arg.max = max; + arg.flags = 0; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, std::vector * value, const char * desc) +{ + Argument arg; + arg.type = ArgumentType::Vector32; + arg.name = name; + arg.value = static_cast(value); + arg.min = min; + arg.max = max; + arg.flags = 0; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional> * value, + const char * desc) +{ + Argument arg; + arg.type = ArgumentType::Vector32; + arg.name = name; + arg.value = static_cast(value); + arg.min = min; + arg.max = max; + arg.flags = Argument::kOptional; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional> * value, + const char * desc) +{ + Argument arg; + arg.type = ArgumentType::VectorBool; + arg.name = name; + arg.value = static_cast(value); + arg.min = min; + arg.max = max; + arg.flags = Argument::kOptional; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, ComplexArgument * value, const char * desc, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::Complex; + arg.name = name; + arg.value = static_cast(value); + arg.flags = flags; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, CustomArgument * value, const char * desc) +{ + Argument arg; + arg.type = ArgumentType::Custom; + arg.name = name; + arg.value = const_cast(reinterpret_cast(value)); + arg.flags = 0; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, std::vector * value, const char * desc) +{ + Argument arg; + arg.type = ArgumentType::VectorCustom; + arg.name = name; + arg.value = static_cast(value); + arg.flags = 0; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, float min, float max, float * out, const char * desc, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::Float; + arg.name = name; + arg.value = reinterpret_cast(out); + arg.flags = flags; + arg.desc = desc; + // Ignore min/max for now; they're always +-Infinity anyway. + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, double min, double max, double * out, const char * desc, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::Double; + arg.name = name; + arg.value = reinterpret_cast(out); + arg.flags = flags; + arg.desc = desc; + // Ignore min/max for now; they're always +-Infinity anyway. + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, void * out, ArgumentType type, const char * desc, + uint8_t flags) +{ + Argument arg; + arg.type = type; + arg.name = name; + arg.value = out; + arg.min = min; + arg.max = max; + arg.flags = flags; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, void * out, const char * desc, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::Number_uint8; + arg.name = name; + arg.value = out; + arg.min = min; + arg.max = max; + arg.flags = flags; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, std::vector * value, const char * desc) +{ + Argument arg; + arg.type = ArgumentType::VectorString; + arg.name = name; + arg.value = static_cast(value); + arg.flags = 0; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, chip::Optional> * value, const char * desc) +{ + Argument arg; + arg.type = ArgumentType::VectorString; + arg.name = name; + arg.value = static_cast(value); + arg.flags = Argument::kOptional; + arg.desc = desc; + + return AddArgumentToList(std::move(arg)); +} + +const char * Command::GetArgumentName(size_t index) const +{ + if (index < mArgs.size()) + { + return mArgs.at(index).name; + } + + return nullptr; +} + +const char * Command::GetArgumentDescription(size_t index) const +{ + if (index < mArgs.size()) + { + return mArgs.at(index).desc; + } + + return nullptr; +} + +const char * Command::GetReadOnlyGlobalCommandArgument() const +{ + if (GetAttribute()) + { + return GetAttribute(); + } + + if (GetEvent()) + { + return GetEvent(); + } + + return nullptr; +} + +const char * Command::GetAttribute() const +{ + if (mReadOnlyGlobalCommandArgument.HasValue()) + { + return mReadOnlyGlobalCommandArgument.Value().value; + } + + return nullptr; +} + +const char * Command::GetEvent() const +{ + if (mReadOnlyGlobalCommandArgument.HasValue()) + { + return mReadOnlyGlobalCommandArgument.Value().value; + } + + return nullptr; +} + +size_t Command::AddArgumentToList(Argument && argument) +{ + if (argument.isOptional() || mArgs.empty() || !mArgs.back().isOptional()) + { + // Safe to just append. + mArgs.emplace_back(std::move(argument)); + return mArgs.size(); + } + + // We're inserting a non-optional arg but we already have something optional + // in the list. Insert before the first optional arg. + for (auto cur = mArgs.cbegin(), end = mArgs.cend(); cur != end; ++cur) + { + if ((*cur).isOptional()) + { + mArgs.emplace(cur, std::move(argument)); + return mArgs.size(); + } + } + + // Never reached. + VerifyOrDie(false); + return 0; +} + +namespace { +template +void ResetOptionalArg(const Argument & arg) +{ + VerifyOrDie(arg.isOptional()); + + if (arg.isNullable()) + { + reinterpret_cast> *>(arg.value)->ClearValue(); + } + else + { + reinterpret_cast *>(arg.value)->ClearValue(); + } +} +} // anonymous namespace + +void Command::ResetArguments() +{ + for (const auto & arg : mArgs) + { + const ArgumentType type = arg.type; + if (arg.isOptional()) + { + // Must always clean these up so they don't carry over to the next + // command invocation in interactive mode. + switch (type) + { + case ArgumentType::Complex: { + // Optional Complex arguments are not currently supported via the class. + // Instead, they must be explicitly specified as optional using the kOptional flag, + // and the base TypedComplexArgument class is referenced. + auto argument = static_cast(arg.value); + argument->Reset(); + break; + } + case ArgumentType::Custom: { + // No optional custom arguments so far. + VerifyOrDie(false); + break; + } + case ArgumentType::VectorString: { + ResetOptionalArg>(arg); + break; + } + case ArgumentType::VectorBool: { + ResetOptionalArg>(arg); + break; + } + case ArgumentType::Vector16: { + // No optional Vector16 arguments so far. + VerifyOrDie(false); + break; + } + case ArgumentType::Vector32: { + ResetOptionalArg>(arg); + break; + } + case ArgumentType::VectorCustom: { + // No optional VectorCustom arguments so far. + VerifyOrDie(false); + break; + } + case ArgumentType::String: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::CharString: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::OctetString: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Bool: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Number_uint8: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Number_uint16: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Number_uint32: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Number_uint64: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Number_int8: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Number_int16: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Number_int32: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Number_int64: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Float: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Double: { + ResetOptionalArg(arg); + break; + } + case ArgumentType::Address: { + ResetOptionalArg(arg); + break; + } + } + } + else + { + // Some non-optional arguments have state that needs to be cleaned + // up too. + if (type == ArgumentType::Vector16) + { + auto vectorArgument = static_cast *>(arg.value); + vectorArgument->clear(); + } + else if (type == ArgumentType::Vector32) + { + auto vectorArgument = static_cast *>(arg.value); + vectorArgument->clear(); + } + else if (type == ArgumentType::VectorCustom) + { + auto vectorArgument = static_cast *>(arg.value); + for (auto & customArgument : *vectorArgument) + { + delete customArgument; + } + vectorArgument->clear(); + } + else if (type == ArgumentType::Complex) + { + auto argument = static_cast(arg.value); + argument->Reset(); + } + } + } +} diff --git a/examples/fabric-admin/commands/common/Command.h b/examples/fabric-admin/commands/common/Command.h new file mode 100644 index 00000000000000..ec8b51dd87b8f4 --- /dev/null +++ b/examples/fabric-admin/commands/common/Command.h @@ -0,0 +1,307 @@ +/* + * 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 + +#include +#include +#include +#include +#include +#include + +class Command; + +template +std::unique_ptr make_unique(Args &&... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +struct movable_initializer_list +{ + movable_initializer_list(std::unique_ptr && in) : item(std::move(in)) {} + operator std::unique_ptr() const && { return std::move(item); } + mutable std::unique_ptr item; +}; + +typedef std::initializer_list commands_list; + +enum ArgumentType +{ + Number_uint8, + Number_uint16, + Number_uint32, + Number_uint64, + Number_int8, + Number_int16, + Number_int32, + Number_int64, + Float, + Double, + Bool, + String, + CharString, + OctetString, + Address, + Complex, + Custom, + VectorBool, + Vector16, + Vector32, + VectorCustom, + VectorString, // comma separated string items +}; + +struct Argument +{ + const char * name; + ArgumentType type; + int64_t min; + uint64_t max; + void * value; + uint8_t flags; + const char * desc; + + enum + { + kOptional = (1 << 0), + kNullable = (1 << 1), + }; + + bool isOptional() const { return flags & kOptional; } + bool isNullable() const { return flags & kNullable; } +}; + +struct ReadOnlyGlobalCommandArgument +{ + const char * name; + const char * value; + const char * desc; +}; + +class Command +{ +public: + struct AddressWithInterface + { + ::chip::Inet::IPAddress address; + ::chip::Inet::InterfaceId interfaceId; + }; + + Command(const char * commandName, const char * helpText = nullptr) : mName(commandName), mHelpText(helpText) {} + virtual ~Command() {} + + const char * GetName(void) const { return mName; } + const char * GetHelpText() const { return mHelpText; } + const char * GetReadOnlyGlobalCommandArgument(void) const; + const char * GetAttribute(void) const; + const char * GetEvent(void) const; + const char * GetArgumentName(size_t index) const; + const char * GetArgumentDescription(size_t index) const; + bool GetArgumentIsOptional(size_t index) const { return mArgs[index].isOptional(); } + size_t GetArgumentsCount(void) const { return mArgs.size(); } + + bool InitArguments(int argc, char ** argv); + void AddArgument(const char * name, const char * value, const char * desc = ""); + /** + * @brief + * Add a char string command argument + * + * @param name The name that will be displayed in the command help + * @param value A pointer to a `char *` where the argv value will be stored + * @param flags + * @param desc The description of the argument that will be displayed in the command help + * @returns The number of arguments currently added to the command + */ + size_t AddArgument(const char * name, char ** value, const char * desc = "", uint8_t flags = 0); + + /** + * Add an octet string command argument + */ + size_t AddArgument(const char * name, chip::ByteSpan * value, const char * desc = "", uint8_t flags = 0); + size_t AddArgument(const char * name, chip::Span * value, const char * desc = "", uint8_t flags = 0); + size_t AddArgument(const char * name, AddressWithInterface * out, const char * desc = "", uint8_t flags = 0); + // Optional Complex arguments are not currently supported via the class. + // Instead, they must be explicitly specified as optional using kOptional in the flags parameter, + // and the base TypedComplexArgument class is referenced. + size_t AddArgument(const char * name, ComplexArgument * value, const char * desc = "", uint8_t flags = 0); + size_t AddArgument(const char * name, CustomArgument * value, const char * desc = ""); + size_t AddArgument(const char * name, int64_t min, uint64_t max, bool * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Bool, desc, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, int8_t * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_int8, desc, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, int16_t * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_int16, desc, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, int32_t * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_int32, desc, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, int64_t * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_int64, desc, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint8_t * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint8, desc, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint16_t * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint16, desc, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint32_t * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint32, desc, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint64_t * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint64, desc, flags); + } + + size_t AddArgument(const char * name, float min, float max, float * out, const char * desc = "", uint8_t flags = 0); + size_t AddArgument(const char * name, double min, double max, double * out, const char * desc = "", uint8_t flags = 0); + + size_t AddArgument(const char * name, int64_t min, uint64_t max, std::vector * value, const char * desc = ""); + size_t AddArgument(const char * name, int64_t min, uint64_t max, std::vector * value, const char * desc = ""); + size_t AddArgument(const char * name, std::vector * value, const char * desc = ""); + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional> * value, + const char * desc = ""); + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional> * value, + const char * desc = ""); + + template ::value>> + size_t AddArgument(const char * name, int64_t min, uint64_t max, T * out, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast *>(out), desc, flags); + } + + template + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::BitFlags * out, const char * desc = "", + uint8_t flags = 0) + { + // This is a terrible hack that relies on BitFlags only having the one + // mValue member. + return AddArgument(name, min, max, reinterpret_cast(out), desc, flags); + } + + template + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::BitMask * out, const char * desc = "", + uint8_t flags = 0) + { + // This is a terrible hack that relies on BitMask only having the one + // mValue member. + return AddArgument(name, min, max, reinterpret_cast(out), desc, flags); + } + + template + size_t AddArgument(const char * name, chip::Optional * value, const char * desc = "") + { + return AddArgument(name, reinterpret_cast(value), desc, Argument::kOptional); + } + + template + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional * value, const char * desc = "") + { + return AddArgument(name, min, max, reinterpret_cast(value), desc, Argument::kOptional); + } + + template + size_t AddArgument(const char * name, chip::app::DataModel::Nullable * value, const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, reinterpret_cast(value), desc, flags | Argument::kNullable); + } + + template + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::app::DataModel::Nullable * value, + const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(value), desc, flags | Argument::kNullable); + } + + size_t AddArgument(const char * name, float min, float max, chip::app::DataModel::Nullable * value, + const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(value), desc, flags | Argument::kNullable); + } + + size_t AddArgument(const char * name, double min, double max, chip::app::DataModel::Nullable * value, + const char * desc = "", uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(value), desc, flags | Argument::kNullable); + } + + size_t AddArgument(const char * name, std::vector * value, const char * desc); + size_t AddArgument(const char * name, chip::Optional> * value, const char * desc); + + void ResetArguments(); + + virtual CHIP_ERROR Run() = 0; + + bool IsInteractive() { return mIsInteractive; } + + CHIP_ERROR RunAsInteractive(const chip::Optional & interactiveStorageDirectory, bool advertiseOperational) + { + mStorageDirectory = interactiveStorageDirectory; + mIsInteractive = true; + mAdvertiseOperational = advertiseOperational; + return Run(); + } + + const chip::Optional & GetStorageDirectory() const { return mStorageDirectory; } + +protected: + // mStorageDirectory lives here so we can just set it in RunAsInteractive. + chip::Optional mStorageDirectory; + + // mAdvertiseOperational lives here so we can just set it in + // RunAsInteractive; it's only used by CHIPCommand. + bool mAdvertiseOperational = false; + +private: + bool InitArgument(size_t argIndex, char * argValue); + size_t AddArgument(const char * name, int64_t min, uint64_t max, void * out, ArgumentType type, const char * desc, + uint8_t flags); + size_t AddArgument(const char * name, int64_t min, uint64_t max, void * out, const char * desc, uint8_t flags); + + /** + * Add the Argument to our list. This preserves the property that all + * optional arguments come at the end of the list. + */ + size_t AddArgumentToList(Argument && argument); + + const char * mName = nullptr; + const char * mHelpText = nullptr; + bool mIsInteractive = false; + + chip::Optional mReadOnlyGlobalCommandArgument; + std::vector mArgs; +}; diff --git a/examples/fabric-admin/commands/common/Commands.cpp b/examples/fabric-admin/commands/common/Commands.cpp new file mode 100644 index 00000000000000..3742978443796a --- /dev/null +++ b/examples/fabric-admin/commands/common/Commands.cpp @@ -0,0 +1,703 @@ +/* + * 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. + * + */ + +#include "Commands.h" + +#include "Command.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../clusters/JsonParser.h" + +namespace { + +char kInteractiveModeName[] = ""; +constexpr size_t kInteractiveModeArgumentsMaxLength = 32; +constexpr char kOptionalArgumentPrefix[] = "--"; +constexpr char kJsonClusterKey[] = "cluster"; +constexpr char kJsonCommandKey[] = "command"; +constexpr char kJsonCommandSpecifierKey[] = "command_specifier"; +constexpr char kJsonArgumentsKey[] = "arguments"; + +#if !CHIP_DISABLE_PLATFORM_KVS +template +struct HasInitWithString +{ + template + static constexpr auto check(U *) -> typename std::is_same().Init("")), CHIP_ERROR>::type; + + template + static constexpr std::false_type check(...); + + typedef decltype(check>(nullptr)) type; + +public: + static constexpr bool value = type::value; +}; + +// Template so we can do conditional enabling +template ::value, int> = 0> +static void UseStorageDirectory(T & storageManagerImpl, const char * storageDirectory) +{ + std::string platformKVS = std::string(storageDirectory) + "/chip_tool_kvs"; + storageManagerImpl.Init(platformKVS.c_str()); +} + +template ::value, int> = 0> +static void UseStorageDirectory(T & storageManagerImpl, const char * storageDirectory) +{} +#endif // !CHIP_DISABLE_PLATFORM_KVS + +bool GetArgumentsFromJson(Command * command, Json::Value & value, bool optional, std::vector & outArgs) +{ + auto memberNames = value.getMemberNames(); + + std::vector args; + for (size_t i = 0; i < command->GetArgumentsCount(); i++) + { + auto argName = command->GetArgumentName(i); + auto memberNamesIterator = memberNames.begin(); + while (memberNamesIterator != memberNames.end()) + { + auto memberName = *memberNamesIterator; + if (strcasecmp(argName, memberName.c_str()) != 0) + { + memberNamesIterator++; + continue; + } + + if (command->GetArgumentIsOptional(i) != optional) + { + memberNamesIterator = memberNames.erase(memberNamesIterator); + continue; + } + + if (optional) + { + args.push_back(std::string(kOptionalArgumentPrefix) + argName); + } + + auto argValue = value[memberName].asString(); + args.push_back(std::move(argValue)); + memberNamesIterator = memberNames.erase(memberNamesIterator); + break; + } + } + + if (memberNames.size()) + { + auto memberName = memberNames.front(); + ChipLogError(NotSpecified, "The argument \"\%s\" is not supported.", memberName.c_str()); + return false; + } + + outArgs = args; + return true; +}; + +// Check for arguments with a starting '"' but no ending '"': those +// would indicate that people are using double-quoting, not single +// quoting, on arguments with spaces. +static void DetectAndLogMismatchedDoubleQuotes(int argc, char ** argv) +{ + for (int curArg = 0; curArg < argc; ++curArg) + { + char * arg = argv[curArg]; + if (!arg) + { + continue; + } + + auto len = strlen(arg); + if (len == 0) + { + continue; + } + + if (arg[0] == '"' && arg[len - 1] != '"') + { + ChipLogError(NotSpecified, + "Mismatched '\"' detected in argument: '%s'. Use single quotes to delimit arguments with spaces " + "in them: 'x y', not \"x y\".", + arg); + } + } +} + +} // namespace + +void Commands::Register(const char * commandSetName, commands_list commandsList, const char * helpText, bool isCluster) +{ + VerifyOrDieWithMsg(isCluster || helpText != nullptr, NotSpecified, "Non-cluster command sets must have help text"); + mCommandSets[commandSetName].isCluster = isCluster; + mCommandSets[commandSetName].helpText = helpText; + for (auto & command : commandsList) + { + mCommandSets[commandSetName].commands.push_back(std::move(command)); + } +} + +int Commands::Run(int argc, char ** argv) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + err = chip::Platform::MemoryInit(); + VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Init Memory failure: %s", chip::ErrorStr(err))); + +#ifdef CONFIG_USE_LOCAL_STORAGE + err = mStorage.Init(); + VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Init Storage failure: %s", chip::ErrorStr(err))); + + chip::Logging::SetLogFilter(mStorage.GetLoggingLevel()); +#endif // CONFIG_USE_LOCAL_STORAGE + + err = RunCommand(argc, argv); + VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(NotSpecified, "Run command failure: %s", chip::ErrorStr(err))); + +exit: + return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int Commands::RunInteractive(const char * command, const chip::Optional & storageDirectory, bool advertiseOperational) +{ + std::vector arguments; + VerifyOrReturnValue(DecodeArgumentsFromInteractiveMode(command, arguments), EXIT_FAILURE); + + if (arguments.size() > (kInteractiveModeArgumentsMaxLength - 1 /* for interactive mode name */)) + { + ChipLogError(NotSpecified, "Too many arguments. Ignoring."); + arguments.resize(kInteractiveModeArgumentsMaxLength - 1); + } + + int argc = 0; + char * argv[kInteractiveModeArgumentsMaxLength] = {}; + argv[argc++] = kInteractiveModeName; + + std::string commandStr; + for (auto & arg : arguments) + { + argv[argc] = new char[arg.size() + 1]; + strcpy(argv[argc++], arg.c_str()); + commandStr += arg; + commandStr += " "; + } + + ChipLogProgress(NotSpecified, "Command: %s", commandStr.c_str()); + auto err = RunCommand(argc, argv, true, storageDirectory, advertiseOperational); + + // Do not delete arg[0] + for (auto i = 1; i < argc; i++) + { + delete[] argv[i]; + } + + return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive, + const chip::Optional & interactiveStorageDirectory, bool interactiveAdvertiseOperational) +{ + Command * command = nullptr; + + if (argc <= 1) + { + ChipLogError(NotSpecified, "Missing cluster or command set name"); + ShowCommandSets(argv[0]); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + auto commandSetIter = GetCommandSet(argv[1]); + if (commandSetIter == mCommandSets.end()) + { + ChipLogError(NotSpecified, "Unknown cluster or command set: %s", argv[1]); + ShowCommandSets(argv[0]); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + auto & commandList = commandSetIter->second.commands; + auto * helpText = commandSetIter->second.helpText; + + if (argc <= 2) + { + ChipLogError(NotSpecified, "Missing command name"); + ShowCommandSet(argv[0], argv[1], commandList, helpText); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + bool isGlobalCommand = IsGlobalCommand(argv[2]); + if (!isGlobalCommand) + { + command = GetCommand(commandList, argv[2]); + if (command == nullptr) + { + ChipLogError(NotSpecified, "Unknown command: %s", argv[2]); + ShowCommandSet(argv[0], argv[1], commandList, helpText); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + else if (IsEventCommand(argv[2])) + { + if (argc <= 3) + { + ChipLogError(NotSpecified, "Missing event name"); + ShowClusterEvents(argv[0], argv[1], argv[2], commandList); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + command = GetGlobalCommand(commandList, argv[2], argv[3]); + if (command == nullptr) + { + ChipLogError(NotSpecified, "Unknown event: %s", argv[3]); + ShowClusterEvents(argv[0], argv[1], argv[2], commandList); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + if (argc <= 3) + { + ChipLogError(NotSpecified, "Missing attribute name"); + ShowClusterAttributes(argv[0], argv[1], argv[2], commandList); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + command = GetGlobalCommand(commandList, argv[2], argv[3]); + if (command == nullptr) + { + ChipLogError(NotSpecified, "Unknown attribute: %s", argv[3]); + ShowClusterAttributes(argv[0], argv[1], argv[2], commandList); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + int argumentsPosition = isGlobalCommand ? 4 : 3; + if (!command->InitArguments(argc - argumentsPosition, &argv[argumentsPosition])) + { + if (interactive) + { + DetectAndLogMismatchedDoubleQuotes(argc - argumentsPosition, &argv[argumentsPosition]); + } + ShowCommand(argv[0], argv[1], command); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (interactive) + { + return command->RunAsInteractive(interactiveStorageDirectory, interactiveAdvertiseOperational); + } + + // Now that the command is initialized, get our storage from it as needed + // and set up our loging level. +#ifdef CONFIG_USE_LOCAL_STORAGE + CHIP_ERROR err = mStorage.Init(nullptr, command->GetStorageDirectory().ValueOr(nullptr)); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Init Storage failure: %s", chip::ErrorStr(err)); + return err; + } + + chip::Logging::SetLogFilter(mStorage.GetLoggingLevel()); + +#if !CHIP_DISABLE_PLATFORM_KVS + UseStorageDirectory(chip::DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl(), mStorage.GetDirectory()); +#endif // !CHIP_DISABLE_PLATFORM_KVS + +#endif // CONFIG_USE_LOCAL_STORAGE + + return command->Run(); +} + +Commands::CommandSetMap::iterator Commands::GetCommandSet(std::string commandSetName) +{ + for (auto & commandSet : mCommandSets) + { + std::string key(commandSet.first); + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + if (key.compare(commandSetName) == 0) + { + return mCommandSets.find(commandSet.first); + } + } + + return mCommandSets.end(); +} + +Command * Commands::GetCommand(CommandsVector & commands, std::string commandName) +{ + for (auto & command : commands) + { + if (commandName.compare(command->GetName()) == 0) + { + return command.get(); + } + } + + return nullptr; +} + +Command * Commands::GetGlobalCommand(CommandsVector & commands, std::string commandName, std::string attributeName) +{ + for (auto & command : commands) + { + if (commandName.compare(command->GetName()) == 0 && attributeName.compare(command->GetAttribute()) == 0) + { + return command.get(); + } + } + + return nullptr; +} + +bool Commands::IsAttributeCommand(std::string commandName) const +{ + return commandName.compare("read") == 0 || commandName.compare("write") == 0 || commandName.compare("force-write") == 0 || + commandName.compare("subscribe") == 0; +} + +bool Commands::IsEventCommand(std::string commandName) const +{ + return commandName.compare("read-event") == 0 || commandName.compare("subscribe-event") == 0; +} + +bool Commands::IsGlobalCommand(std::string commandName) const +{ + return IsAttributeCommand(commandName) || IsEventCommand(commandName); +} + +void Commands::ShowCommandSetOverview(std::string commandSetName, const CommandSet & commandSet) +{ + std::transform(commandSetName.begin(), commandSetName.end(), commandSetName.begin(), + [](unsigned char c) { return std::tolower(c); }); + fprintf(stderr, " | * %-82s|\n", commandSetName.c_str()); + ShowHelpText(commandSet.helpText); +} + +void Commands::ShowCommandSets(std::string executable) +{ + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s cluster_name command_name [param1 param2 ...]\n", executable.c_str()); + fprintf(stderr, "or:\n"); + fprintf(stderr, " %s command_set_name command_name [param1 param2 ...]\n", executable.c_str()); + fprintf(stderr, "\n"); + // Table of clusters + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + fprintf(stderr, " | Clusters: |\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + for (auto & commandSet : mCommandSets) + { + if (commandSet.second.isCluster) + { + ShowCommandSetOverview(commandSet.first, commandSet.second); + } + } + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + fprintf(stderr, "\n"); + + // Table of command sets + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + fprintf(stderr, " | Command sets: |\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + for (auto & commandSet : mCommandSets) + { + if (!commandSet.second.isCluster) + { + ShowCommandSetOverview(commandSet.first, commandSet.second); + } + } + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); +} + +void Commands::ShowCommandSet(std::string executable, std::string commandSetName, CommandsVector & commands, const char * helpText) +{ + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s %s command_name [param1 param2 ...]\n", executable.c_str(), commandSetName.c_str()); + + if (helpText) + { + fprintf(stderr, "\n%s\n", helpText); + } + + fprintf(stderr, "\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + fprintf(stderr, " | Commands: |\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + bool readCommand = false; + bool writeCommand = false; + bool writeOverrideCommand = false; + bool subscribeCommand = false; + bool readEventCommand = false; + bool subscribeEventCommand = false; + for (auto & command : commands) + { + bool shouldPrint = true; + + if (IsGlobalCommand(command->GetName())) + { + if (strcmp(command->GetName(), "read") == 0 && !readCommand) + { + readCommand = true; + } + else if (strcmp(command->GetName(), "write") == 0 && !writeCommand) + { + writeCommand = true; + } + else if (strcmp(command->GetName(), "force-write") == 0 && !writeOverrideCommand) + { + writeOverrideCommand = true; + } + else if (strcmp(command->GetName(), "subscribe") == 0 && !subscribeCommand) + { + subscribeCommand = true; + } + else if (strcmp(command->GetName(), "read-event") == 0 && !readEventCommand) + { + readEventCommand = true; + } + else if (strcmp(command->GetName(), "subscribe-event") == 0 && !subscribeEventCommand) + { + subscribeEventCommand = true; + } + else + { + shouldPrint = false; + } + } + + if (shouldPrint) + { + fprintf(stderr, " | * %-82s|\n", command->GetName()); + ShowHelpText(command->GetHelpText()); + } + } + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); +} + +void Commands::ShowClusterAttributes(std::string executable, std::string clusterName, std::string commandName, + CommandsVector & commands) +{ + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s %s %s attribute-name [param1 param2 ...]\n", executable.c_str(), clusterName.c_str(), + commandName.c_str()); + fprintf(stderr, "\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + fprintf(stderr, " | Attributes: |\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + for (auto & command : commands) + { + if (commandName.compare(command->GetName()) == 0) + { + fprintf(stderr, " | * %-82s|\n", command->GetAttribute()); + } + } + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); +} + +void Commands::ShowClusterEvents(std::string executable, std::string clusterName, std::string commandName, + CommandsVector & commands) +{ + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s %s %s event-name [param1 param2 ...]\n", executable.c_str(), clusterName.c_str(), commandName.c_str()); + fprintf(stderr, "\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + fprintf(stderr, " | Events: |\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + for (auto & command : commands) + { + if (commandName.compare(command->GetName()) == 0) + { + fprintf(stderr, " | * %-82s|\n", command->GetEvent()); + } + } + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); +} + +void Commands::ShowCommand(std::string executable, std::string clusterName, Command * command) +{ + fprintf(stderr, "Usage:\n"); + + std::string arguments; + std::string description; + arguments += command->GetName(); + + if (command->GetReadOnlyGlobalCommandArgument()) + { + arguments += ' '; + arguments += command->GetReadOnlyGlobalCommandArgument(); + } + + size_t argumentsCount = command->GetArgumentsCount(); + for (size_t i = 0; i < argumentsCount; i++) + { + std::string arg; + bool isOptional = command->GetArgumentIsOptional(i); + if (isOptional) + { + arg += "[--"; + } + arg += command->GetArgumentName(i); + if (isOptional) + { + arg += "]"; + } + arguments += " "; + arguments += arg; + + const char * argDescription = command->GetArgumentDescription(i); + if ((argDescription != nullptr) && (strlen(argDescription) > 0)) + { + description += "\n"; + description += arg; + description += ":\n "; + description += argDescription; + description += "\n"; + } + } + fprintf(stderr, " %s %s %s\n", executable.c_str(), clusterName.c_str(), arguments.c_str()); + + if (command->GetHelpText()) + { + fprintf(stderr, "\n%s\n", command->GetHelpText()); + } + + if (description.size() > 0) + { + fprintf(stderr, "%s\n", description.c_str()); + } +} + +bool Commands::DecodeArgumentsFromInteractiveMode(const char * command, std::vector & args) +{ + // Remote clients may not know the ordering of arguments, so instead of a strict ordering arguments can + // be passed in as a json payload encoded in base64 and are reordered on the fly. + return IsJsonString(command) ? DecodeArgumentsFromBase64EncodedJson(command, args) + : DecodeArgumentsFromStringStream(command, args); +} + +bool Commands::DecodeArgumentsFromBase64EncodedJson(const char * json, std::vector & args) +{ + Json::Value jsonValue; + bool parsed = JsonParser::ParseCustomArgument(json, json + kJsonStringPrefixLen, jsonValue); + VerifyOrReturnValue(parsed, false, ChipLogError(NotSpecified, "Error while parsing json.")); + VerifyOrReturnValue(jsonValue.isObject(), false, ChipLogError(NotSpecified, "Unexpected json type.")); + VerifyOrReturnValue(jsonValue.isMember(kJsonClusterKey), false, + ChipLogError(NotSpecified, "'%s' key not found in json.", kJsonClusterKey)); + VerifyOrReturnValue(jsonValue.isMember(kJsonCommandKey), false, + ChipLogError(NotSpecified, "'%s' key not found in json.", kJsonCommandKey)); + VerifyOrReturnValue(jsonValue.isMember(kJsonArgumentsKey), false, + ChipLogError(NotSpecified, "'%s' key not found in json.", kJsonArgumentsKey)); + VerifyOrReturnValue(IsBase64String(jsonValue[kJsonArgumentsKey].asString().c_str()), false, + ChipLogError(NotSpecified, "'arguments' is not a base64 string.")); + + auto clusterName = jsonValue[kJsonClusterKey].asString(); + auto commandName = jsonValue[kJsonCommandKey].asString(); + auto arguments = jsonValue[kJsonArgumentsKey].asString(); + + auto clusterIter = GetCommandSet(clusterName); + VerifyOrReturnValue(clusterIter != mCommandSets.end(), false, + ChipLogError(NotSpecified, "Cluster '%s' is not supported.", clusterName.c_str())); + + auto & commandList = clusterIter->second.commands; + + auto command = GetCommand(commandList, commandName); + + if (jsonValue.isMember(kJsonCommandSpecifierKey) && IsGlobalCommand(commandName)) + { + auto commandSpecifierName = jsonValue[kJsonCommandSpecifierKey].asString(); + command = GetGlobalCommand(commandList, commandName, commandSpecifierName); + } + VerifyOrReturnValue(nullptr != command, false, ChipLogError(NotSpecified, "Unknown command.")); + + auto encodedData = arguments.c_str(); + encodedData += kBase64StringPrefixLen; + + size_t encodedDataSize = strlen(encodedData); + size_t expectedMaxDecodedSize = BASE64_MAX_DECODED_LEN(encodedDataSize); + + chip::Platform::ScopedMemoryBuffer decodedData; + VerifyOrReturnValue(decodedData.Calloc(expectedMaxDecodedSize + 1 /* for null */), false); + + size_t decodedDataSize = chip::Base64Decode(encodedData, static_cast(encodedDataSize), decodedData.Get()); + VerifyOrReturnValue(decodedDataSize != 0, false, ChipLogError(NotSpecified, "Error while decoding base64 data.")); + + decodedData.Get()[decodedDataSize] = '\0'; + + Json::Value jsonArguments; + bool parsedArguments = JsonParser::ParseCustomArgument(encodedData, chip::Uint8::to_char(decodedData.Get()), jsonArguments); + VerifyOrReturnValue(parsedArguments, false, ChipLogError(NotSpecified, "Error while parsing json.")); + VerifyOrReturnValue(jsonArguments.isObject(), false, ChipLogError(NotSpecified, "Unexpected json type, expects and object.")); + + std::vector mandatoryArguments; + std::vector optionalArguments; + VerifyOrReturnValue(GetArgumentsFromJson(command, jsonArguments, false /* addOptional */, mandatoryArguments), false); + VerifyOrReturnValue(GetArgumentsFromJson(command, jsonArguments, true /* addOptional */, optionalArguments), false); + + args.push_back(std::move(clusterName)); + args.push_back(std::move(commandName)); + if (jsonValue.isMember(kJsonCommandSpecifierKey)) + { + auto commandSpecifierName = jsonValue[kJsonCommandSpecifierKey].asString(); + args.push_back(std::move(commandSpecifierName)); + } + args.insert(args.end(), mandatoryArguments.begin(), mandatoryArguments.end()); + args.insert(args.end(), optionalArguments.begin(), optionalArguments.end()); + + return true; +} + +bool Commands::DecodeArgumentsFromStringStream(const char * command, std::vector & args) +{ + std::string arg; + std::stringstream ss(command); + while (ss >> std::quoted(arg, '\'')) + { + args.push_back(std::move(arg)); + } + + return true; +} + +void Commands::ShowHelpText(const char * helpText) +{ + if (helpText == nullptr) + { + return; + } + + // We leave 82 chars for command/cluster names. The help text starts + // two chars further to the right, so there are 80 chars left + // for it. + if (strlen(helpText) > 80) + { + // Add "..." at the end to indicate truncation, and only + // show the first 77 chars, since that's what will fit. + fprintf(stderr, " | - %.77s...|\n", helpText); + } + else + { + fprintf(stderr, " | - %-80s|\n", helpText); + } +} diff --git a/examples/fabric-admin/commands/common/Commands.h b/examples/fabric-admin/commands/common/Commands.h new file mode 100644 index 00000000000000..8638ededcb3b8c --- /dev/null +++ b/examples/fabric-admin/commands/common/Commands.h @@ -0,0 +1,90 @@ +/* + * 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 + +#ifdef CONFIG_USE_LOCAL_STORAGE +#include +#endif // CONFIG_USE_LOCAL_STORAGE + +#include "Command.h" +#include +#include + +class Commands +{ +public: + using CommandsVector = ::std::vector>; + + void RegisterCluster(const char * clusterName, commands_list commandsList) + { + Register(clusterName, commandsList, nullptr, true); + } + // Command sets represent fabric-admin functionality that is not actually + // XML-defined clusters. All command sets should have help text explaining + // what sort of commands one should expect to find in the set. + void RegisterCommandSet(const char * commandSetName, commands_list commandsList, const char * helpText) + { + Register(commandSetName, commandsList, helpText, false); + } + int Run(int argc, char ** argv); + int RunInteractive(const char * command, const chip::Optional & storageDirectory, bool advertiseOperational); + +private: + struct CommandSet + { + CommandsVector commands; + bool isCluster = false; + const char * helpText = nullptr; + }; + // The tuple contains the commands, whether it's a synthetic cluster, and + // the help text for the cluster (which may be null). + using CommandSetMap = std::map; + + CHIP_ERROR RunCommand(int argc, char ** argv, bool interactive = false, + const chip::Optional & interactiveStorageDirectory = chip::NullOptional, + bool interactiveAdvertiseOperational = false); + + CommandSetMap::iterator GetCommandSet(std::string commandSetName); + Command * GetCommand(CommandsVector & commands, std::string commandName); + Command * GetGlobalCommand(CommandsVector & commands, std::string commandName, std::string attributeName); + bool IsAttributeCommand(std::string commandName) const; + bool IsEventCommand(std::string commandName) const; + bool IsGlobalCommand(std::string commandName) const; + + void ShowCommandSets(std::string executable); + static void ShowCommandSetOverview(std::string commandSetName, const CommandSet & commandSet); + void ShowCommandSet(std::string executable, std::string commandSetName, CommandsVector & commands, const char * helpText); + void ShowClusterAttributes(std::string executable, std::string clusterName, std::string commandName, CommandsVector & commands); + void ShowClusterEvents(std::string executable, std::string clusterName, std::string commandName, CommandsVector & commands); + void ShowCommand(std::string executable, std::string clusterName, Command * command); + + bool DecodeArgumentsFromInteractiveMode(const char * command, std::vector & args); + bool DecodeArgumentsFromBase64EncodedJson(const char * encodedData, std::vector & args); + bool DecodeArgumentsFromStringStream(const char * command, std::vector & args); + + // helpText may be null, in which case it's not shown. + static void ShowHelpText(const char * helpText); + + void Register(const char * commandSetName, commands_list commandsList, const char * helpText, bool isCluster); + + CommandSetMap mCommandSets; +#ifdef CONFIG_USE_LOCAL_STORAGE + PersistentStorage mStorage; +#endif // CONFIG_USE_LOCAL_STORAGE +}; diff --git a/examples/fabric-admin/commands/common/CredentialIssuerCommands.h b/examples/fabric-admin/commands/common/CredentialIssuerCommands.h new file mode 100644 index 00000000000000..c36948fe69c992 --- /dev/null +++ b/examples/fabric-admin/commands/common/CredentialIssuerCommands.h @@ -0,0 +1,117 @@ +/* + * 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 + +namespace chip { +namespace Controller { +struct SetupParams; +class OperationalCredentialsDelegate; +} // namespace Controller +} // namespace chip + +class CredentialIssuerCommands +{ +public: + virtual ~CredentialIssuerCommands() {} + + /** + * @brief + * This function is used to initialize the Credentials Issuer, if needed. + * + * @param[in] storage A reference to the storage, where the Credentials Issuer can optionally use to access the keypair in + * storage. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code. + */ + virtual CHIP_ERROR InitializeCredentialsIssuer(chip::PersistentStorageDelegate & storage) = 0; + + /** + * @brief + * This function is used to setup Device Attestation Singletons and intialize Setup/Commissioning Parameters with a custom + * Device Attestation Verifier object. + * + * @param[in] setupParams A reference to the Setup/Commissioning Parameters, to be initialized with custom Device Attestation + * Verifier. + * @param[in] trustStore A pointer to the PAA trust store to use to find valid PAA roots. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code. + */ + virtual CHIP_ERROR SetupDeviceAttestation(chip::Controller::SetupParams & setupParams, + const chip::Credentials::AttestationTrustStore * trustStore) = 0; + + /** + * @brief Add a list of additional non-default CD verifying keys (by certificate) + * + * Must be called AFTER SetupDeviceAttestation. + * + * @param additionalCdCerts - vector of X.509 DER verifying cert bodies + * @return CHIP_NO_ERROR on succes, another CHIP_ERROR on internal failures. + */ + virtual CHIP_ERROR AddAdditionalCDVerifyingCerts(const std::vector> & additionalCdCerts) = 0; + + virtual chip::Controller::OperationalCredentialsDelegate * GetCredentialIssuer() = 0; + + virtual void SetCredentialIssuerCATValues(chip::CATValues cats) = 0; + + /** + * @brief + * This function is used to Generate NOC Chain for the Controller/Commissioner. Parameters follow the example implementation, + * so some parameters may not translate to the real remote Credentials Issuer policy. + * + * @param[in] nodeId The desired NodeId for the generated NOC Chain - May be optional/unused in some implementations. + * @param[in] fabricId The desired FabricId for the generated NOC Chain - May be optional/unused in some implementations. + * @param[in] cats The desired CATs for the generated NOC Chain - May be optional/unused in some implementations. + * @param[in] keypair The desired Keypair for the generated NOC Chain - May be optional/unused in some implementations. + * @param[in,out] rcac Buffer to hold the Root Certificate of the generated NOC Chain. + * @param[in,out] icac Buffer to hold the Intermediate Certificate of the generated NOC Chain. + * @param[in,out] noc Buffer to hold the Leaf Certificate of the generated NOC Chain. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code. + */ + virtual CHIP_ERROR GenerateControllerNOCChain(chip::NodeId nodeId, chip::FabricId fabricId, const chip::CATValues & cats, + chip::Crypto::P256Keypair & keypair, chip::MutableByteSpan & rcac, + chip::MutableByteSpan & icac, chip::MutableByteSpan & noc) = 0; + + // All options must start false + enum CredentialIssuerOptions : uint8_t + { + kMaximizeCertificateSizes = 0, // If set, certificate chains will be maximized for testing via padding + kAllowTestCdSigningKey = 1, // If set, allow development/test SDK CD verifying key to be used + }; + + virtual void SetCredentialIssuerOption(CredentialIssuerOptions option, bool isEnabled) + { + // Do nothing + (void) option; + (void) isEnabled; + } + + virtual bool GetCredentialIssuerOption(CredentialIssuerOptions option) + { + // All options always start false + return false; + } +}; diff --git a/examples/fabric-admin/commands/common/CustomStringPrefix.h b/examples/fabric-admin/commands/common/CustomStringPrefix.h new file mode 100644 index 00000000000000..be8442993d1bf2 --- /dev/null +++ b/examples/fabric-admin/commands/common/CustomStringPrefix.h @@ -0,0 +1,55 @@ +/* + * 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 + +static constexpr char kJsonStringPrefix[] = "json:"; +inline constexpr size_t kJsonStringPrefixLen = ArraySize(kJsonStringPrefix) - 1; // Don't count the null + +static constexpr char kBase64StringPrefix[] = "base64:"; +inline constexpr size_t kBase64StringPrefixLen = ArraySize(kBase64StringPrefix) - 1; // Don't count the null + +static constexpr char kHexStringPrefix[] = "hex:"; +inline constexpr size_t kHexStringPrefixLen = ArraySize(kHexStringPrefix) - 1; // Don't count the null + +static constexpr char kStrStringPrefix[] = "str:"; +inline constexpr size_t kStrStringPrefixLen = ArraySize(kStrStringPrefix) - 1; // Don't count the null + +inline bool IsJsonString(const char * str) +{ + return strncmp(str, kJsonStringPrefix, kJsonStringPrefixLen) == 0; +} + +inline bool IsBase64String(const char * str) +{ + return strncmp(str, kBase64StringPrefix, kBase64StringPrefixLen) == 0; +} + +inline bool IsHexString(const char * str) +{ + return strncmp(str, kHexStringPrefix, kHexStringPrefixLen) == 0; +} + +inline bool IsStrString(const char * str) +{ + return strncmp(str, kStrStringPrefix, kStrStringPrefixLen) == 0; +} diff --git a/examples/fabric-admin/commands/common/DeviceScanner.cpp b/examples/fabric-admin/commands/common/DeviceScanner.cpp new file mode 100644 index 00000000000000..e49eb852fe4808 --- /dev/null +++ b/examples/fabric-admin/commands/common/DeviceScanner.cpp @@ -0,0 +1,245 @@ +/* + * 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. + * + */ + +#include "DeviceScanner.h" + +using namespace chip; +using namespace chip::Dnssd; + +#if CONFIG_NETWORK_LAYER_BLE +using namespace chip::Ble; +constexpr char kBleKey[] = "BLE"; +#endif // CONFIG_NETWORK_LAYER_BLE + +CHIP_ERROR DeviceScanner::Start() +{ + mDiscoveredResults.clear(); + +#if CONFIG_NETWORK_LAYER_BLE + ReturnErrorOnFailure(DeviceLayer::PlatformMgrImpl().StartBleScan(this)); +#endif // CONFIG_NETWORK_LAYER_BLE + + ReturnErrorOnFailure(chip::Dnssd::Resolver::Instance().Init(DeviceLayer::UDPEndPointManager())); + + char serviceName[kMaxCommissionableServiceNameSize]; + auto filter = DiscoveryFilterType::kNone; + ReturnErrorOnFailure(MakeServiceTypeName(serviceName, sizeof(serviceName), filter, DiscoveryType::kCommissionableNode)); + + return ChipDnssdBrowse(serviceName, DnssdServiceProtocol::kDnssdProtocolUdp, Inet::IPAddressType::kAny, + Inet::InterfaceId::Null(), this); +} + +CHIP_ERROR DeviceScanner::Stop() +{ +#if CONFIG_NETWORK_LAYER_BLE + ReturnErrorOnFailure(DeviceLayer::PlatformMgrImpl().StopBleScan()); +#endif // CONFIG_NETWORK_LAYER_BLE + + return ChipDnssdStopBrowse(this); +} + +void DeviceScanner::OnNodeDiscovered(const DiscoveredNodeData & nodeData) +{ + VerifyOrReturn(nodeData.Is()); + auto & commissionData = nodeData.Get(); + + auto discriminator = commissionData.longDiscriminator; + auto vendorId = static_cast(commissionData.vendorId); + auto productId = commissionData.productId; + + ChipLogProgress(NotSpecified, "OnNodeDiscovered (MDNS): discriminator: %u, vendorId: %u, productId: %u", discriminator, + vendorId, productId); + + const CommonResolutionData & resolutionData = commissionData; + + auto & instanceData = mDiscoveredResults[commissionData.instanceName]; + auto & interfaceData = instanceData[resolutionData.interfaceId.GetPlatformInterface()]; + + for (size_t i = 0; i < resolutionData.numIPs; i++) + { + auto params = Controller::SetUpCodePairerParameters(resolutionData, i); + DeviceScannerResult result = { params, vendorId, productId, discriminator, chip::MakeOptional(resolutionData) }; + interfaceData.push_back(result); + } + + commissionData.LogDetail(); +} + +void DeviceScanner::OnBrowseAdd(chip::Dnssd::DnssdService service) +{ + ChipLogProgress(NotSpecified, "OnBrowseAdd: %s", service.mName); + LogErrorOnFailure(ChipDnssdResolve(&service, service.mInterface, this)); + + auto & instanceData = mDiscoveredResults[service.mName]; + auto & interfaceData = instanceData[service.mInterface.GetPlatformInterface()]; + (void) interfaceData; +} + +void DeviceScanner::OnBrowseRemove(chip::Dnssd::DnssdService service) +{ + ChipLogProgress(NotSpecified, "OnBrowseRemove: %s", service.mName); + auto & instanceData = mDiscoveredResults[service.mName]; + auto & interfaceData = instanceData[service.mInterface.GetPlatformInterface()]; + + // Check if the interface data has been resolved already, otherwise, just inform the + // back end that we may not need it anymore. + if (interfaceData.size() == 0) + { + ChipDnssdResolveNoLongerNeeded(service.mName); + } + + // Delete the interface placeholder. + instanceData.erase(service.mInterface.GetPlatformInterface()); + + // If there is nothing else to resolve for the given instance name, just remove it + // too. + if (instanceData.size() == 0) + { + mDiscoveredResults.erase(service.mName); + } +} + +void DeviceScanner::OnBrowseStop(CHIP_ERROR error) +{ + ChipLogProgress(NotSpecified, "OnBrowseStop: %" CHIP_ERROR_FORMAT, error.Format()); + + for (auto & instance : mDiscoveredResults) + { + for (auto & interface : instance.second) + { + if (interface.second.size() == 0) + { + ChipDnssdResolveNoLongerNeeded(instance.first.c_str()); + } + } + } +} + +#if CONFIG_NETWORK_LAYER_BLE +void DeviceScanner::OnBleScanAdd(BLE_CONNECTION_OBJECT connObj, const ChipBLEDeviceIdentificationInfo & info) +{ + auto discriminator = info.GetDeviceDiscriminator(); + auto vendorId = static_cast(info.GetVendorId()); + auto productId = info.GetProductId(); + + ChipLogProgress(NotSpecified, "OnBleScanAdd (BLE): %p, discriminator: %u, vendorId: %u, productId: %u", connObj, discriminator, + vendorId, productId); + + auto params = Controller::SetUpCodePairerParameters(connObj, false /* connected */); + DeviceScannerResult result = { params, vendorId, productId, discriminator }; + + auto & instanceData = mDiscoveredResults[kBleKey]; + auto & interfaceData = instanceData[chip::Inet::InterfaceId::Null().GetPlatformInterface()]; + interfaceData.push_back(result); +} + +void DeviceScanner::OnBleScanRemove(BLE_CONNECTION_OBJECT connObj) +{ + ChipLogProgress(NotSpecified, "OnBleScanRemove: %p", connObj); + + auto & instanceData = mDiscoveredResults[kBleKey]; + auto & interfaceData = instanceData[chip::Inet::InterfaceId::Null().GetPlatformInterface()]; + + interfaceData.erase(std::remove_if(interfaceData.begin(), interfaceData.end(), + [connObj](const DeviceScannerResult & result) { + return result.mParams.HasDiscoveredObject() && + result.mParams.GetDiscoveredObject() == connObj; + }), + interfaceData.end()); + + if (interfaceData.size() == 0) + { + instanceData.clear(); + mDiscoveredResults.erase(kBleKey); + } +} +#endif // CONFIG_NETWORK_LAYER_BLE + +CHIP_ERROR DeviceScanner::Get(uint16_t index, RendezvousParameters & params) +{ + uint16_t currentIndex = 0; + for (auto & instance : mDiscoveredResults) + { + for (auto & interface : instance.second) + { + for (auto & result : interface.second) + { + if (currentIndex == index) + { + params = result.mParams; + return CHIP_NO_ERROR; + } + currentIndex++; + } + } + } + + return CHIP_ERROR_NOT_FOUND; +} + +CHIP_ERROR DeviceScanner::Get(uint16_t index, Dnssd::CommonResolutionData & resolutionData) +{ + uint16_t currentIndex = 0; + for (auto & instance : mDiscoveredResults) + { + for (auto & interface : instance.second) + { + for (auto & result : interface.second) + { + if (currentIndex == index && result.mResolutionData.HasValue()) + { + resolutionData = result.mResolutionData.Value(); + return CHIP_NO_ERROR; + } + currentIndex++; + } + } + } + + return CHIP_ERROR_NOT_FOUND; +} + +void DeviceScanner::Log() const +{ + auto resultsCount = mDiscoveredResults.size(); + VerifyOrReturn(resultsCount > 0, ChipLogProgress(NotSpecified, "No device discovered.")); + + [[maybe_unused]] uint16_t index = 0; + for (auto & instance : mDiscoveredResults) + { + ChipLogProgress(NotSpecified, "Instance Name: %s ", instance.first.c_str()); + for (auto & interface : instance.second) + { + for (auto & result : interface.second) + { + char addr[Transport::PeerAddress::kMaxToStringSize]; + result.mParams.GetPeerAddress().ToString(addr); + + ChipLogProgress(NotSpecified, "\t %u - Discriminator: %u - Vendor: %u - Product: %u - %s", index, + result.mDiscriminator, result.mVendorId, result.mProductId, addr); + index++; + } + } + } +} + +DeviceScanner & GetDeviceScanner() +{ + static DeviceScanner scanner; + return scanner; +} diff --git a/examples/fabric-admin/commands/common/DeviceScanner.h b/examples/fabric-admin/commands/common/DeviceScanner.h new file mode 100644 index 00000000000000..c3e81a51c413cd --- /dev/null +++ b/examples/fabric-admin/commands/common/DeviceScanner.h @@ -0,0 +1,80 @@ +/* + * 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 + +#if CHIP_DEVICE_LAYER_TARGET_DARWIN + +#include +#include + +#include +#include +#include + +#if CONFIG_NETWORK_LAYER_BLE +#include +#endif // CONFIG_NETWORK_LAYER_BLE + +struct DeviceScannerResult +{ + chip::Controller::SetUpCodePairerParameters mParams; + chip::VendorId mVendorId; + uint16_t mProductId; + uint16_t mDiscriminator; + chip::Optional mResolutionData; +}; + +class DeviceScanner : public chip::Dnssd::DiscoverNodeDelegate, + public chip::Dnssd::DnssdBrowseDelegate +#if CONFIG_NETWORK_LAYER_BLE + , + public chip::DeviceLayer::BleScannerDelegate +#endif // CONFIG_NETWORK_LAYER_BLE +{ +public: + CHIP_ERROR Start(); + CHIP_ERROR Stop(); + CHIP_ERROR Get(uint16_t index, chip::RendezvousParameters & params); + CHIP_ERROR Get(uint16_t index, chip::Dnssd::CommonResolutionData & resolutionData); + void Log() const; + + /////////// DiscoverNodeDelegate Interface ///////// + void OnNodeDiscovered(const chip::Dnssd::DiscoveredNodeData & nodeData) override; + + /////////// DnssdBrowseDelegate Interface ///////// + void OnBrowseAdd(chip::Dnssd::DnssdService service) override; + void OnBrowseRemove(chip::Dnssd::DnssdService service) override; + void OnBrowseStop(CHIP_ERROR error) override; + +#if CONFIG_NETWORK_LAYER_BLE + /////////// BleScannerDelegate Interface ///////// + void OnBleScanAdd(BLE_CONNECTION_OBJECT connObj, const chip::Ble::ChipBLEDeviceIdentificationInfo & info) override; + void OnBleScanRemove(BLE_CONNECTION_OBJECT connObj) override; +#endif // CONFIG_NETWORK_LAYER_BLE + +private: + std::unordered_map>> + mDiscoveredResults; +}; + +DeviceScanner & GetDeviceScanner(); + +#endif // CHIP_DEVICE_LAYER_TARGET_DARWIN diff --git a/examples/fabric-admin/commands/common/HexConversion.h b/examples/fabric-admin/commands/common/HexConversion.h new file mode 100644 index 00000000000000..99b7f651734d12 --- /dev/null +++ b/examples/fabric-admin/commands/common/HexConversion.h @@ -0,0 +1,65 @@ +/* + * 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 + +/** + * Utility for converting a hex string to bytes, with the right error checking + * and allocation size computation. + * + * Takes a functor to allocate the buffer to use for the hex bytes. The functor + * is expected to return uint8_t *. The caller is responsible for cleaning up + * this buffer as needed. + * + * On success, *octetCount is filled with the number of octets placed in the + * buffer. On failure, the value of *octetCount is undefined. + */ +template +CHIP_ERROR HexToBytes(chip::CharSpan hex, F bufferAllocator, size_t * octetCount) +{ + *octetCount = 0; + + if (hex.size() % 2 != 0) + { + ChipLogError(NotSpecified, "Error while encoding '%.*s' as an octet string: Odd number of characters.", + static_cast(hex.size()), hex.data()); + return CHIP_ERROR_INVALID_STRING_LENGTH; + } + + const size_t bufferSize = hex.size() / 2; + uint8_t * buffer = bufferAllocator(bufferSize); + if (buffer == nullptr && bufferSize != 0) + { + ChipLogError(NotSpecified, "Failed to allocate buffer of size: %llu", static_cast(bufferSize)); + return CHIP_ERROR_NO_MEMORY; + } + + size_t byteCount = chip::Encoding::HexToBytes(hex.data(), hex.size(), buffer, bufferSize); + if (byteCount == 0 && hex.size() != 0) + { + ChipLogError(NotSpecified, "Error while encoding '%.*s' as an octet string.", static_cast(hex.size()), hex.data()); + return CHIP_ERROR_INTERNAL; + } + + *octetCount = byteCount; + return CHIP_NO_ERROR; +} diff --git a/examples/fabric-admin/commands/common/RemoteDataModelLogger.cpp b/examples/fabric-admin/commands/common/RemoteDataModelLogger.cpp new file mode 100644 index 00000000000000..c8e9d5a5450703 --- /dev/null +++ b/examples/fabric-admin/commands/common/RemoteDataModelLogger.cpp @@ -0,0 +1,279 @@ +/* + * 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. + * + */ + +#include "RemoteDataModelLogger.h" + +#include +#include + +constexpr char kEventNumberKey[] = "eventNumber"; +constexpr char kDataVersionKey[] = "dataVersion"; +constexpr char kClusterIdKey[] = "clusterId"; +constexpr char kEndpointIdKey[] = "endpointId"; +constexpr char kAttributeIdKey[] = "attributeId"; +constexpr char kEventIdKey[] = "eventId"; +constexpr char kCommandIdKey[] = "commandId"; +constexpr char kErrorIdKey[] = "error"; +constexpr char kClusterErrorIdKey[] = "clusterError"; +constexpr char kValueKey[] = "value"; +constexpr char kNodeIdKey[] = "nodeId"; +constexpr char kNOCKey[] = "NOC"; +constexpr char kICACKey[] = "ICAC"; +constexpr char kRCACKey[] = "RCAC"; +constexpr char kIPKKey[] = "IPK"; + +namespace { +RemoteDataModelLoggerDelegate * gDelegate; + +CHIP_ERROR LogError(Json::Value & value, const chip::app::StatusIB & status) +{ + if (status.mClusterStatus.HasValue()) + { + auto statusValue = status.mClusterStatus.Value(); + value[kClusterErrorIdKey] = statusValue; + } + +#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT + auto statusName = chip::Protocols::InteractionModel::StatusName(status.mStatus); + value[kErrorIdKey] = statusName; +#else + auto statusName = status.mStatus; + value[kErrorIdKey] = chip::to_underlying(statusName); +#endif // CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT + + auto valueStr = chip::JsonToString(value); + return gDelegate->LogJSON(valueStr.c_str()); +} + +} // namespace + +namespace RemoteDataModelLogger { +CHIP_ERROR LogAttributeAsJSON(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value value; + value[kClusterIdKey] = path.mClusterId; + value[kEndpointIdKey] = path.mEndpointId; + value[kAttributeIdKey] = path.mAttributeId; + if (path.mDataVersion.HasValue()) + { + value[kDataVersionKey] = path.mDataVersion.Value(); + } + + chip::TLV::TLVReader reader; + reader.Init(*data); + ReturnErrorOnFailure(chip::TlvToJson(reader, value)); + + auto valueStr = chip::JsonToString(value); + return gDelegate->LogJSON(valueStr.c_str()); +} + +CHIP_ERROR LogErrorAsJSON(const chip::app::ConcreteDataAttributePath & path, const chip::app::StatusIB & status) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value value; + value[kClusterIdKey] = path.mClusterId; + value[kEndpointIdKey] = path.mEndpointId; + value[kAttributeIdKey] = path.mAttributeId; + + return LogError(value, status); +} + +CHIP_ERROR LogCommandAsJSON(const chip::app::ConcreteCommandPath & path, chip::TLV::TLVReader * data) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value value; + value[kClusterIdKey] = path.mClusterId; + value[kEndpointIdKey] = path.mEndpointId; + value[kCommandIdKey] = path.mCommandId; + + chip::TLV::TLVReader reader; + reader.Init(*data); + ReturnErrorOnFailure(chip::TlvToJson(reader, value)); + + auto valueStr = chip::JsonToString(value); + return gDelegate->LogJSON(valueStr.c_str()); +} + +CHIP_ERROR LogErrorAsJSON(const chip::app::ConcreteCommandPath & path, const chip::app::StatusIB & status) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value value; + value[kClusterIdKey] = path.mClusterId; + value[kEndpointIdKey] = path.mEndpointId; + value[kCommandIdKey] = path.mCommandId; + + return LogError(value, status); +} + +CHIP_ERROR LogEventAsJSON(const chip::app::EventHeader & header, chip::TLV::TLVReader * data) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value value; + value[kClusterIdKey] = header.mPath.mClusterId; + value[kEndpointIdKey] = header.mPath.mEndpointId; + value[kEventIdKey] = header.mPath.mEventId; + value[kEventNumberKey] = header.mEventNumber; + + chip::TLV::TLVReader reader; + reader.Init(*data); + ReturnErrorOnFailure(chip::TlvToJson(reader, value)); + + auto valueStr = chip::JsonToString(value); + return gDelegate->LogJSON(valueStr.c_str()); +} + +CHIP_ERROR LogErrorAsJSON(const chip::app::EventHeader & header, const chip::app::StatusIB & status) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value value; + value[kClusterIdKey] = header.mPath.mClusterId; + value[kEndpointIdKey] = header.mPath.mEndpointId; + value[kEventIdKey] = header.mPath.mEventId; + + return LogError(value, status); +} + +CHIP_ERROR LogErrorAsJSON(const CHIP_ERROR & error) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value value; + chip::app::StatusIB status; + status.InitFromChipError(error); + return LogError(value, status); +} + +CHIP_ERROR LogGetCommissionerNodeId(chip::NodeId value) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value rootValue; + rootValue[kValueKey] = Json::Value(); + rootValue[kValueKey][kNodeIdKey] = value; + + auto valueStr = chip::JsonToString(rootValue); + return gDelegate->LogJSON(valueStr.c_str()); +} + +CHIP_ERROR LogGetCommissionerRootCertificate(const char * value) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value rootValue; + rootValue[kValueKey] = Json::Value(); + rootValue[kValueKey][kRCACKey] = value; + + auto valueStr = chip::JsonToString(rootValue); + return gDelegate->LogJSON(valueStr.c_str()); +} + +CHIP_ERROR LogIssueNOCChain(const char * noc, const char * icac, const char * rcac, const char * ipk) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + Json::Value rootValue; + rootValue[kValueKey] = Json::Value(); + rootValue[kValueKey][kNOCKey] = noc; + rootValue[kValueKey][kICACKey] = icac; + rootValue[kValueKey][kRCACKey] = rcac; + rootValue[kValueKey][kIPKKey] = ipk; + + auto valueStr = chip::JsonToString(rootValue); + return gDelegate->LogJSON(valueStr.c_str()); +} + +CHIP_ERROR LogDiscoveredNodeData(const chip::Dnssd::CommissionNodeData & nodeData) +{ + VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR); + + auto & commissionData = nodeData; + auto & resolutionData = commissionData; + + if (!chip::CanCastTo(resolutionData.numIPs)) + { + ChipLogError(NotSpecified, "Too many ips."); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (!chip::CanCastTo(commissionData.rotatingIdLen)) + { + ChipLogError(NotSpecified, "Can not convert rotatingId to json format."); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + char rotatingId[chip::Dnssd::kMaxRotatingIdLen * 2 + 1] = ""; + ReturnErrorOnFailure(chip::Encoding::BytesToUppercaseHexString(commissionData.rotatingId, commissionData.rotatingIdLen, + rotatingId, sizeof(rotatingId))); + + Json::Value value; + value["hostName"] = resolutionData.hostName; + value["instanceName"] = commissionData.instanceName; + value["longDiscriminator"] = commissionData.longDiscriminator; + value["shortDiscriminator"] = ((commissionData.longDiscriminator >> 8) & 0x0F); + value["vendorId"] = commissionData.vendorId; + value["productId"] = commissionData.productId; + value["commissioningMode"] = commissionData.commissioningMode; + value["deviceType"] = commissionData.deviceType; + value["deviceName"] = commissionData.deviceName; + value["rotatingId"] = rotatingId; + value["rotatingIdLen"] = static_cast(commissionData.rotatingIdLen); + value["pairingHint"] = commissionData.pairingHint; + value["pairingInstruction"] = commissionData.pairingInstruction; + value["supportsTcp"] = resolutionData.supportsTcp; + value["port"] = resolutionData.port; + value["numIPs"] = static_cast(resolutionData.numIPs); + + if (resolutionData.mrpRetryIntervalIdle.has_value()) + { + value["mrpRetryIntervalIdle"] = resolutionData.mrpRetryIntervalIdle->count(); + } + + if (resolutionData.mrpRetryIntervalActive.has_value()) + { + value["mrpRetryIntervalActive"] = resolutionData.mrpRetryIntervalActive->count(); + } + + if (resolutionData.mrpRetryActiveThreshold.has_value()) + { + value["mrpRetryActiveThreshold"] = resolutionData.mrpRetryActiveThreshold->count(); + } + + if (resolutionData.isICDOperatingAsLIT.has_value()) + { + value["isICDOperatingAsLIT"] = *(resolutionData.isICDOperatingAsLIT); + } + + Json::Value rootValue; + rootValue[kValueKey] = value; + + auto valueStr = chip::JsonToString(rootValue); + return gDelegate->LogJSON(valueStr.c_str()); +} + +void SetDelegate(RemoteDataModelLoggerDelegate * delegate) +{ + gDelegate = delegate; +} +}; // namespace RemoteDataModelLogger diff --git a/examples/fabric-admin/commands/common/RemoteDataModelLogger.h b/examples/fabric-admin/commands/common/RemoteDataModelLogger.h new file mode 100644 index 00000000000000..c31636ea1fd8a9 --- /dev/null +++ b/examples/fabric-admin/commands/common/RemoteDataModelLogger.h @@ -0,0 +1,48 @@ +/* + * 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 + +class RemoteDataModelLoggerDelegate +{ +public: + CHIP_ERROR virtual LogJSON(const char *) = 0; + virtual ~RemoteDataModelLoggerDelegate(){}; +}; + +namespace RemoteDataModelLogger { +CHIP_ERROR LogAttributeAsJSON(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data); +CHIP_ERROR LogErrorAsJSON(const chip::app::ConcreteDataAttributePath & path, const chip::app::StatusIB & status); +CHIP_ERROR LogCommandAsJSON(const chip::app::ConcreteCommandPath & path, chip::TLV::TLVReader * data); +CHIP_ERROR LogErrorAsJSON(const chip::app::ConcreteCommandPath & path, const chip::app::StatusIB & status); +CHIP_ERROR LogEventAsJSON(const chip::app::EventHeader & header, chip::TLV::TLVReader * data); +CHIP_ERROR LogErrorAsJSON(const chip::app::EventHeader & header, const chip::app::StatusIB & status); +CHIP_ERROR LogErrorAsJSON(const CHIP_ERROR & error); +CHIP_ERROR LogGetCommissionerNodeId(chip::NodeId value); +CHIP_ERROR LogGetCommissionerRootCertificate(const char * value); +CHIP_ERROR LogIssueNOCChain(const char * noc, const char * icac, const char * rcac, const char * ipk); +CHIP_ERROR LogDiscoveredNodeData(const chip::Dnssd::CommissionNodeData & nodeData); +void SetDelegate(RemoteDataModelLoggerDelegate * delegate); +}; // namespace RemoteDataModelLogger diff --git a/examples/fabric-admin/commands/example/ExampleCredentialIssuerCommands.h b/examples/fabric-admin/commands/example/ExampleCredentialIssuerCommands.h new file mode 100644 index 00000000000000..a739ac7dbfa9dd --- /dev/null +++ b/examples/fabric-admin/commands/example/ExampleCredentialIssuerCommands.h @@ -0,0 +1,111 @@ +/* + * 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 + +class ExampleCredentialIssuerCommands : public CredentialIssuerCommands +{ +public: + CHIP_ERROR InitializeCredentialsIssuer(chip::PersistentStorageDelegate & storage) override + { + return mOpCredsIssuer.Initialize(storage); + } + CHIP_ERROR SetupDeviceAttestation(chip::Controller::SetupParams & setupParams, + const chip::Credentials::AttestationTrustStore * trustStore) override + { + chip::Credentials::SetDeviceAttestationCredentialsProvider(chip::Credentials::Examples::GetExampleDACProvider()); + + mDacVerifier = chip::Credentials::GetDefaultDACVerifier(trustStore); + setupParams.deviceAttestationVerifier = mDacVerifier; + mDacVerifier->EnableCdTestKeySupport(mAllowTestCdSigningKey); + + return CHIP_NO_ERROR; + } + chip::Controller::OperationalCredentialsDelegate * GetCredentialIssuer() override { return &mOpCredsIssuer; } + void SetCredentialIssuerCATValues(chip::CATValues cats) override { mOpCredsIssuer.SetCATValuesForNextNOCRequest(cats); } + CHIP_ERROR GenerateControllerNOCChain(chip::NodeId nodeId, chip::FabricId fabricId, const chip::CATValues & cats, + chip::Crypto::P256Keypair & keypair, chip::MutableByteSpan & rcac, + chip::MutableByteSpan & icac, chip::MutableByteSpan & noc) override + { + return mOpCredsIssuer.GenerateNOCChainAfterValidation(nodeId, fabricId, cats, keypair.Pubkey(), rcac, icac, noc); + } + + CHIP_ERROR AddAdditionalCDVerifyingCerts(const std::vector> & additionalCdCerts) override + { + VerifyOrReturnError(mDacVerifier != nullptr, CHIP_ERROR_INCORRECT_STATE); + + for (const auto & cert : additionalCdCerts) + { + auto cdTrustStore = mDacVerifier->GetCertificationDeclarationTrustStore(); + VerifyOrReturnError(cdTrustStore != nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorOnFailure(cdTrustStore->AddTrustedKey(chip::ByteSpan(cert.data(), cert.size()))); + } + + return CHIP_NO_ERROR; + } + + void SetCredentialIssuerOption(CredentialIssuerOptions option, bool isEnabled) override + { + switch (option) + { + case CredentialIssuerOptions::kMaximizeCertificateSizes: + mUsesMaxSizedCerts = isEnabled; + mOpCredsIssuer.SetMaximallyLargeCertsUsed(mUsesMaxSizedCerts); + break; + case CredentialIssuerOptions::kAllowTestCdSigningKey: + mAllowTestCdSigningKey = isEnabled; + if (mDacVerifier != nullptr) + { + mDacVerifier->EnableCdTestKeySupport(isEnabled); + } + break; + default: + break; + } + } + + bool GetCredentialIssuerOption(CredentialIssuerOptions option) override + { + switch (option) + { + case CredentialIssuerOptions::kMaximizeCertificateSizes: + return mUsesMaxSizedCerts; + case CredentialIssuerOptions::kAllowTestCdSigningKey: + return mAllowTestCdSigningKey; + default: + return false; + } + } + +protected: + bool mUsesMaxSizedCerts = false; + // Starts true for legacy purposes + bool mAllowTestCdSigningKey = true; + +private: + chip::Controller::ExampleOperationalCredentialsIssuer mOpCredsIssuer; + chip::Credentials::DeviceAttestationVerifier * mDacVerifier; +}; diff --git a/examples/fabric-admin/commands/interactive/Commands.h b/examples/fabric-admin/commands/interactive/Commands.h new file mode 100644 index 00000000000000..e324ddae2680ae --- /dev/null +++ b/examples/fabric-admin/commands/interactive/Commands.h @@ -0,0 +1,34 @@ +/* + * 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 "commands/common/CHIPCommand.h" +#include "commands/common/Commands.h" +#include "commands/interactive/InteractiveCommands.h" + +void registerCommandsInteractive(Commands & commands, CredentialIssuerCommands * credsIssuerConfig) +{ + const char * clusterName = "interactive"; + + commands_list clusterCommands = { + make_unique(&commands, credsIssuerConfig), + }; + + commands.RegisterCommandSet(clusterName, clusterCommands, "Commands for starting long-lived interactive modes."); +} diff --git a/examples/fabric-admin/commands/interactive/InteractiveCommands.cpp b/examples/fabric-admin/commands/interactive/InteractiveCommands.cpp new file mode 100644 index 00000000000000..9ef07e79450300 --- /dev/null +++ b/examples/fabric-admin/commands/interactive/InteractiveCommands.cpp @@ -0,0 +1,139 @@ +/* + * 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. + * + */ + +#include "InteractiveCommands.h" + +#include + +#include + +#include +#include + +constexpr char kInteractiveModePrompt[] = ">>> "; +constexpr char kInteractiveModeHistoryFileName[] = "chip_tool_history"; +constexpr char kInteractiveModeStopCommand[] = "quit()"; + +namespace { + +void ClearLine() +{ + printf("\r\x1B[0J"); // Move cursor to the beginning of the line and clear from cursor to end of the screen +} + +void ENFORCE_FORMAT(3, 0) LoggingCallback(const char * module, uint8_t category, const char * msg, va_list args) +{ + ClearLine(); + chip::Logging::Platform::LogV(module, category, msg, args); + ClearLine(); +} + +} // namespace + +char * InteractiveStartCommand::GetCommand(char * command) +{ + if (command != nullptr) + { + free(command); + command = nullptr; + } + + command = readline(kInteractiveModePrompt); + + // Do not save empty lines + if (command != nullptr && *command) + { + add_history(command); + write_history(GetHistoryFilePath().c_str()); + } + + return command; +} + +std::string InteractiveStartCommand::GetHistoryFilePath() const +{ + std::string storageDir; + if (GetStorageDirectory().HasValue()) + { + storageDir = GetStorageDirectory().Value(); + } + else + { + // Match what GetFilename in ExamplePersistentStorage.cpp does. + const char * dir = getenv("TMPDIR"); + if (dir == nullptr) + { + dir = "/tmp"; + } + storageDir = dir; + } + + return storageDir + "/" + kInteractiveModeHistoryFileName; +} + +CHIP_ERROR InteractiveStartCommand::RunCommand() +{ + read_history(GetHistoryFilePath().c_str()); + + // Logs needs to be redirected in order to refresh the screen appropriately when something + // is dumped to stdout while the user is typing a command. + chip::Logging::SetLogRedirectCallback(LoggingCallback); + + char * command = nullptr; + int status; + while (true) + { + command = GetCommand(command); + if (command != nullptr && !ParseCommand(command, &status)) + { + break; + } + } + + if (command != nullptr) + { + free(command); + command = nullptr; + } + + SetCommandExitStatus(CHIP_NO_ERROR); + return CHIP_NO_ERROR; +} + +bool InteractiveCommand::ParseCommand(char * command, int * status) +{ + if (strcmp(command, kInteractiveModeStopCommand) == 0) + { + // If scheduling the cleanup fails, there is not much we can do. + // But if something went wrong while the application is leaving it could be because things have + // not been cleaned up properly, so it is still useful to log the failure. + LogErrorOnFailure(chip::DeviceLayer::PlatformMgr().ScheduleWork(ExecuteDeferredCleanups, 0)); + return false; + } + + ClearLine(); + + *status = mHandler->RunInteractive(command, GetStorageDirectory(), NeedsOperationalAdvertising()); + + return true; +} + +bool InteractiveCommand::NeedsOperationalAdvertising() +{ + return mAdvertiseOperational.ValueOr(true); +} diff --git a/examples/fabric-admin/commands/interactive/InteractiveCommands.h b/examples/fabric-admin/commands/interactive/InteractiveCommands.h new file mode 100644 index 00000000000000..21c14a7a4c95ce --- /dev/null +++ b/examples/fabric-admin/commands/interactive/InteractiveCommands.h @@ -0,0 +1,68 @@ +/* + * 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 "../clusters/DataModelLogger.h" +#include "../common/CHIPCommand.h" +#include "../common/Commands.h" + +#include + +#include + +class Commands; + +class InteractiveCommand : public CHIPCommand +{ +public: + InteractiveCommand(const char * name, Commands * commandsHandler, const char * helpText, + CredentialIssuerCommands * credsIssuerConfig) : + CHIPCommand(name, credsIssuerConfig, helpText), + mHandler(commandsHandler) + { + AddArgument("advertise-operational", 0, 1, &mAdvertiseOperational, + "Advertise operational node over DNS-SD and accept incoming CASE sessions."); + } + + /////////// CHIPCommand Interface ///////// + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(0); } + bool NeedsOperationalAdvertising() override; + + bool ParseCommand(char * command, int * status); + +private: + Commands * mHandler = nullptr; + chip::Optional mAdvertiseOperational; +}; + +class InteractiveStartCommand : public InteractiveCommand +{ +public: + InteractiveStartCommand(Commands * commandsHandler, CredentialIssuerCommands * credsIssuerConfig) : + InteractiveCommand("start", commandsHandler, "Start an interactive shell that can then run other commands.", + credsIssuerConfig) + {} + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override; + +private: + char * GetCommand(char * command); + std::string GetHistoryFilePath() const; +}; diff --git a/examples/fabric-admin/commands/pairing/Commands.h b/examples/fabric-admin/commands/pairing/Commands.h new file mode 100644 index 00000000000000..6fdfacef79e34f --- /dev/null +++ b/examples/fabric-admin/commands/pairing/Commands.h @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "commands/common/Commands.h" +#include "commands/pairing/GetCommissionerNodeIdCommand.h" +#include "commands/pairing/GetCommissionerRootCertificateCommand.h" +#include "commands/pairing/IssueNOCChainCommand.h" +#include "commands/pairing/OpenCommissioningWindowCommand.h" +#include "commands/pairing/PairingCommand.h" + +#include +#include +#include + +class Unpair : public PairingCommand +{ +public: + Unpair(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("unpair", PairingMode::None, PairingNetworkType::None, credsIssuerConfig) + {} +}; + +class PairCode : public PairingCommand +{ +public: + PairCode(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("code", PairingMode::Code, PairingNetworkType::None, credsIssuerConfig) + {} +}; + +class PairCodePase : public PairingCommand +{ +public: + PairCodePase(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("code-paseonly", PairingMode::CodePaseOnly, PairingNetworkType::None, credsIssuerConfig) + {} +}; + +class PairCodeWifi : public PairingCommand +{ +public: + PairCodeWifi(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("code-wifi", PairingMode::Code, PairingNetworkType::WiFi, credsIssuerConfig) + {} +}; + +class PairCodeThread : public PairingCommand +{ +public: + PairCodeThread(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("code-thread", PairingMode::Code, PairingNetworkType::Thread, credsIssuerConfig) + {} +}; + +class PairOnNetwork : public PairingCommand +{ +public: + PairOnNetwork(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("onnetwork", PairingMode::OnNetwork, PairingNetworkType::None, credsIssuerConfig) + {} +}; + +class PairOnNetworkShort : public PairingCommand +{ +public: + PairOnNetworkShort(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("onnetwork-short", PairingMode::OnNetwork, PairingNetworkType::None, credsIssuerConfig, + chip::Dnssd::DiscoveryFilterType::kShortDiscriminator) + {} +}; + +class PairOnNetworkLong : public PairingCommand +{ +public: + PairOnNetworkLong(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("onnetwork-long", PairingMode::OnNetwork, PairingNetworkType::None, credsIssuerConfig, + chip::Dnssd::DiscoveryFilterType::kLongDiscriminator) + {} +}; + +class PairOnNetworkVendor : public PairingCommand +{ +public: + PairOnNetworkVendor(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("onnetwork-vendor", PairingMode::OnNetwork, PairingNetworkType::None, credsIssuerConfig, + chip::Dnssd::DiscoveryFilterType::kVendorId) + {} +}; + +class PairOnNetworkFabric : public PairingCommand +{ +public: + PairOnNetworkFabric(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("onnetwork-fabric", PairingMode::OnNetwork, PairingNetworkType::None, credsIssuerConfig, + chip::Dnssd::DiscoveryFilterType::kCompressedFabricId) + {} +}; + +class PairOnNetworkCommissioningMode : public PairingCommand +{ +public: + PairOnNetworkCommissioningMode(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("onnetwork-commissioning-mode", PairingMode::OnNetwork, PairingNetworkType::None, credsIssuerConfig, + chip::Dnssd::DiscoveryFilterType::kCommissioningMode) + {} +}; + +class PairOnNetworkCommissioner : public PairingCommand +{ +public: + PairOnNetworkCommissioner(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("onnetwork-commissioner", PairingMode::OnNetwork, PairingNetworkType::None, credsIssuerConfig, + chip::Dnssd::DiscoveryFilterType::kCommissioner) + {} +}; + +class PairOnNetworkDeviceType : public PairingCommand +{ +public: + PairOnNetworkDeviceType(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("onnetwork-device-type", PairingMode::OnNetwork, PairingNetworkType::None, credsIssuerConfig, + chip::Dnssd::DiscoveryFilterType::kDeviceType) + {} +}; + +class PairOnNetworkInstanceName : public PairingCommand +{ +public: + PairOnNetworkInstanceName(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("onnetwork-instance-name", PairingMode::OnNetwork, PairingNetworkType::None, credsIssuerConfig, + chip::Dnssd::DiscoveryFilterType::kInstanceName) + {} +}; + +class PairBleWiFi : public PairingCommand +{ +public: + PairBleWiFi(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("ble-wifi", PairingMode::Ble, PairingNetworkType::WiFi, credsIssuerConfig) + {} +}; + +class PairBleThread : public PairingCommand +{ +public: + PairBleThread(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("ble-thread", PairingMode::Ble, PairingNetworkType::Thread, credsIssuerConfig) + {} +}; + +class PairSoftAP : public PairingCommand +{ +public: + PairSoftAP(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("softap", PairingMode::SoftAP, PairingNetworkType::WiFi, credsIssuerConfig) + {} +}; + +class PairAlreadyDiscovered : public PairingCommand +{ +public: + PairAlreadyDiscovered(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("already-discovered", PairingMode::AlreadyDiscovered, PairingNetworkType::None, credsIssuerConfig) + {} +}; + +class PairAlreadyDiscoveredByIndex : public PairingCommand +{ +public: + PairAlreadyDiscoveredByIndex(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("already-discovered-by-index", PairingMode::AlreadyDiscoveredByIndex, PairingNetworkType::None, + credsIssuerConfig) + {} +}; + +class PairAlreadyDiscoveredByIndexWithWiFi : public PairingCommand +{ +public: + PairAlreadyDiscoveredByIndexWithWiFi(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("already-discovered-by-index-with-wifi", PairingMode::AlreadyDiscoveredByIndex, PairingNetworkType::WiFi, + credsIssuerConfig) + {} +}; + +class PairAlreadyDiscoveredByIndexWithCode : public PairingCommand +{ +public: + PairAlreadyDiscoveredByIndexWithCode(CredentialIssuerCommands * credsIssuerConfig) : + PairingCommand("already-discovered-by-index-with-code", PairingMode::AlreadyDiscoveredByIndexWithCode, + PairingNetworkType::None, credsIssuerConfig) + {} +}; + +class StartUdcServerCommand : public CHIPCommand +{ +public: + StartUdcServerCommand(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("start-udc-server", credsIssuerConfig) {} + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(300); } + + CHIP_ERROR RunCommand() override + { + chip::app::DnssdServer::Instance().StartServer(chip::Dnssd::CommissioningMode::kDisabled); + return CHIP_NO_ERROR; + } +}; + +void registerCommandsPairing(Commands & commands, CredentialIssuerCommands * credsIssuerConfig) +{ + const char * clusterName = "Pairing"; + + commands_list clusterCommands = { + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + // TODO(#13973) - enable CommissionedListCommand once DNS Cache is implemented + // make_unique(), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + make_unique(credsIssuerConfig), + }; + + commands.RegisterCommandSet(clusterName, clusterCommands, "Commands for commissioning devices."); +} diff --git a/examples/fabric-admin/commands/pairing/GetCommissionerNodeIdCommand.h b/examples/fabric-admin/commands/pairing/GetCommissionerNodeIdCommand.h new file mode 100644 index 00000000000000..3234cfe456a956 --- /dev/null +++ b/examples/fabric-admin/commands/pairing/GetCommissionerNodeIdCommand.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "../common/CHIPCommand.h" +#include "../common/RemoteDataModelLogger.h" + +class GetCommissionerNodeIdCommand : public CHIPCommand +{ +public: + GetCommissionerNodeIdCommand(CredentialIssuerCommands * credIssuerCommands) : + CHIPCommand("get-commissioner-node-id", credIssuerCommands) + {} + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + chip::NodeId id; + ReturnErrorOnFailure(GetIdentityNodeId(GetIdentity(), &id)); + ChipLogProgress(NotSpecified, "Commissioner Node Id 0x:" ChipLogFormatX64, ChipLogValueX64(id)); + + ReturnErrorOnFailure(RemoteDataModelLogger::LogGetCommissionerNodeId(id)); + SetCommandExitStatus(CHIP_NO_ERROR); + return CHIP_NO_ERROR; + } + + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } +}; diff --git a/examples/fabric-admin/commands/pairing/GetCommissionerRootCertificateCommand.h b/examples/fabric-admin/commands/pairing/GetCommissionerRootCertificateCommand.h new file mode 100644 index 00000000000000..1d25efcc38224d --- /dev/null +++ b/examples/fabric-admin/commands/pairing/GetCommissionerRootCertificateCommand.h @@ -0,0 +1,53 @@ +/* + * 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 "../common/CHIPCommand.h" +#include "../common/RemoteDataModelLogger.h" + +#include "ToTLVCert.h" + +#include + +class GetCommissionerRootCertificateCommand : public CHIPCommand +{ +public: + GetCommissionerRootCertificateCommand(CredentialIssuerCommands * credIssuerCommands) : + CHIPCommand("get-commissioner-root-certificate", credIssuerCommands, + "Returns a base64-encoded RCAC prefixed with: 'base64:'") + {} + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + chip::ByteSpan span; + ReturnErrorOnFailure(GetIdentityRootCertificate(GetIdentity(), span)); + + std::string rcac; + ReturnErrorOnFailure(ToTLVCert(span, rcac)); + ChipLogProgress(NotSpecified, "RCAC: %s", rcac.c_str()); + + ReturnErrorOnFailure(RemoteDataModelLogger::LogGetCommissionerRootCertificate(rcac.c_str())); + + SetCommandExitStatus(CHIP_NO_ERROR); + return CHIP_NO_ERROR; + } + + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } +}; diff --git a/examples/fabric-admin/commands/pairing/IssueNOCChainCommand.h b/examples/fabric-admin/commands/pairing/IssueNOCChainCommand.h new file mode 100644 index 00000000000000..0103b26977136d --- /dev/null +++ b/examples/fabric-admin/commands/pairing/IssueNOCChainCommand.h @@ -0,0 +1,91 @@ +/* + * 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 "../common/CHIPCommand.h" +#include "../common/RemoteDataModelLogger.h" + +#include "ToTLVCert.h" + +#include + +class IssueNOCChainCommand : public CHIPCommand +{ +public: + IssueNOCChainCommand(CredentialIssuerCommands * credIssuerCommands) : + CHIPCommand("issue-noc-chain", credIssuerCommands, + "Returns a base64-encoded NOC, ICAC, RCAC, and IPK prefixed with: 'base64:'"), + mDeviceNOCChainCallback(OnDeviceNOCChainGeneration, this) + { + AddArgument("elements", &mNOCSRElements, "NOCSRElements encoded in hexadecimal"); + AddArgument("node-id", 0, UINT64_MAX, &mNodeId, "The target node id"); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + auto & commissioner = CurrentCommissioner(); + ReturnErrorOnFailure(commissioner.IssueNOCChain(mNOCSRElements, mNodeId, &mDeviceNOCChainCallback)); + return CHIP_NO_ERROR; + } + + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } + + static void OnDeviceNOCChainGeneration(void * context, CHIP_ERROR status, const chip::ByteSpan & noc, + const chip::ByteSpan & icac, const chip::ByteSpan & rcac, + chip::Optional ipk, + chip::Optional adminSubject) + { + auto command = static_cast(context); + + auto err = status; + VerifyOrReturn(CHIP_NO_ERROR == err, command->SetCommandExitStatus(err)); + + std::string nocStr; + err = ToTLVCert(noc, nocStr); + VerifyOrReturn(CHIP_NO_ERROR == err, command->SetCommandExitStatus(err)); + ChipLogProgress(NotSpecified, "NOC: %s", nocStr.c_str()); + + std::string icacStr; + err = ToTLVCert(icac, icacStr); + VerifyOrReturn(CHIP_NO_ERROR == err, command->SetCommandExitStatus(err)); + ChipLogProgress(NotSpecified, "ICAC: %s", icacStr.c_str()); + + std::string rcacStr; + err = ToTLVCert(rcac, rcacStr); + VerifyOrReturn(CHIP_NO_ERROR == err, command->SetCommandExitStatus(err)); + ChipLogProgress(NotSpecified, "RCAC: %s", rcacStr.c_str()); + + std::string ipkStr; + if (ipk.HasValue()) + { + err = ToBase64(ipk.Value(), ipkStr); + VerifyOrReturn(CHIP_NO_ERROR == err, command->SetCommandExitStatus(err)); + } + ChipLogProgress(NotSpecified, "IPK: %s", ipkStr.c_str()); + + err = RemoteDataModelLogger::LogIssueNOCChain(nocStr.c_str(), icacStr.c_str(), rcacStr.c_str(), ipkStr.c_str()); + command->SetCommandExitStatus(err); + } + +private: + chip::Callback::Callback mDeviceNOCChainCallback; + chip::ByteSpan mNOCSRElements; + chip::NodeId mNodeId; +}; diff --git a/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.cpp b/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.cpp new file mode 100644 index 00000000000000..bc4af6c4a51cec --- /dev/null +++ b/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.cpp @@ -0,0 +1,62 @@ +/* + * 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. + * + */ + +#include "OpenCommissioningWindowCommand.h" + +#include + +using namespace ::chip; + +CHIP_ERROR OpenCommissioningWindowCommand::RunCommand() +{ + mWindowOpener = Platform::MakeUnique(&CurrentCommissioner()); + if (mCommissioningWindowOption == Controller::CommissioningWindowOpener::CommissioningWindowOption::kOriginalSetupCode) + { + return mWindowOpener->OpenBasicCommissioningWindow(mNodeId, System::Clock::Seconds16(mCommissioningWindowTimeout), + &mOnOpenBasicCommissioningWindowCallback); + } + + if (mCommissioningWindowOption == Controller::CommissioningWindowOpener::CommissioningWindowOption::kTokenWithRandomPIN) + { + SetupPayload ignored; + return mWindowOpener->OpenCommissioningWindow(mNodeId, System::Clock::Seconds16(mCommissioningWindowTimeout), mIteration, + mDiscriminator, NullOptional, NullOptional, + &mOnOpenCommissioningWindowCallback, ignored, + /* readVIDPIDAttributes */ true); + } + + ChipLogError(NotSpecified, "Unknown commissioning window option: %d", to_underlying(mCommissioningWindowOption)); + return CHIP_ERROR_INVALID_ARGUMENT; +} + +void OpenCommissioningWindowCommand::OnOpenCommissioningWindowResponse(void * context, NodeId remoteId, CHIP_ERROR err, + chip::SetupPayload payload) +{ + LogErrorOnFailure(err); + + OnOpenBasicCommissioningWindowResponse(context, remoteId, err); +} + +void OpenCommissioningWindowCommand::OnOpenBasicCommissioningWindowResponse(void * context, NodeId remoteId, CHIP_ERROR err) +{ + LogErrorOnFailure(err); + + OpenCommissioningWindowCommand * command = reinterpret_cast(context); + VerifyOrReturn(command != nullptr, ChipLogError(NotSpecified, "OnOpenCommissioningWindowCommand: context is null")); + command->SetCommandExitStatus(err); +} diff --git a/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.h b/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.h new file mode 100644 index 00000000000000..99b179d8753125 --- /dev/null +++ b/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.h @@ -0,0 +1,67 @@ +/* + * 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 "../common/CHIPCommand.h" + +#include +#include + +class OpenCommissioningWindowCommand : public CHIPCommand +{ +public: + OpenCommissioningWindowCommand(CredentialIssuerCommands * credIssuerCommands) : + CHIPCommand("open-commissioning-window", credIssuerCommands), + mOnOpenCommissioningWindowCallback(OnOpenCommissioningWindowResponse, this), + mOnOpenBasicCommissioningWindowCallback(OnOpenBasicCommissioningWindowResponse, this) + { + AddArgument("node-id", 0, UINT64_MAX, &mNodeId, "Node to send command to."); + AddArgument("option", 0, 2, &mCommissioningWindowOption, + "1 to use Enhanced Commissioning Method.\n 0 to use Basic Commissioning Method."); + AddArgument("window-timeout", 0, UINT16_MAX, &mCommissioningWindowTimeout, + "Time, in seconds, before the commissioning window closes."); + AddArgument("iteration", chip::Crypto::kSpake2p_Min_PBKDF_Iterations, chip::Crypto::kSpake2p_Max_PBKDF_Iterations, + &mIteration, "Number of PBKDF iterations to use to derive the verifier. Ignored if 'option' is 0."); + AddArgument("discriminator", 0, 4096, &mDiscriminator, "Discriminator to use for advertising. Ignored if 'option' is 0."); + AddArgument("timeout", 0, UINT16_MAX, &mTimeout, "Time, in seconds, before this command is considered to have timed out."); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override; + // We issue multiple data model operations for this command, and the default + // timeout for those is 10 seconds, so default to 20 seconds. + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(mTimeout.ValueOr(20)); } + +private: + NodeId mNodeId; + chip::Controller::CommissioningWindowOpener::CommissioningWindowOption mCommissioningWindowOption; + uint16_t mCommissioningWindowTimeout; + uint32_t mIteration; + uint16_t mDiscriminator; + + chip::Optional mTimeout; + + chip::Platform::UniquePtr mWindowOpener; + + static void OnOpenCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status, chip::SetupPayload payload); + static void OnOpenBasicCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status); + + chip::Callback::Callback mOnOpenCommissioningWindowCallback; + chip::Callback::Callback mOnOpenBasicCommissioningWindowCallback; +}; diff --git a/examples/fabric-admin/commands/pairing/PairingCommand.cpp b/examples/fabric-admin/commands/pairing/PairingCommand.cpp new file mode 100644 index 00000000000000..80775f0853d110 --- /dev/null +++ b/examples/fabric-admin/commands/pairing/PairingCommand.cpp @@ -0,0 +1,551 @@ +/* + * 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. + * + */ + +#include "PairingCommand.h" +#include "platform/PlatformManager.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace ::chip; +using namespace ::chip::Controller; + +CHIP_ERROR PairingCommand::RunCommand() +{ + CurrentCommissioner().RegisterPairingDelegate(this); + // Clear the CATs in OperationalCredentialsIssuer + mCredIssuerCmds->SetCredentialIssuerCATValues(kUndefinedCATs); + + mDeviceIsICD = false; + + if (mCASEAuthTags.HasValue() && mCASEAuthTags.Value().size() <= kMaxSubjectCATAttributeCount) + { + CATValues cats = kUndefinedCATs; + for (size_t index = 0; index < mCASEAuthTags.Value().size(); ++index) + { + cats.values[index] = mCASEAuthTags.Value()[index]; + } + if (cats.AreValid()) + { + mCredIssuerCmds->SetCredentialIssuerCATValues(cats); + } + } + return RunInternal(mNodeId); +} + +CHIP_ERROR PairingCommand::RunInternal(NodeId remoteId) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + switch (mPairingMode) + { + case PairingMode::None: + err = Unpair(remoteId); + break; + case PairingMode::Code: + err = PairWithCode(remoteId); + break; + case PairingMode::CodePaseOnly: + err = PaseWithCode(remoteId); + break; + case PairingMode::Ble: + err = Pair(remoteId, PeerAddress::BLE()); + break; + case PairingMode::OnNetwork: + err = PairWithMdns(remoteId); + break; + case PairingMode::SoftAP: + err = Pair(remoteId, PeerAddress::UDP(mRemoteAddr.address, mRemotePort, mRemoteAddr.interfaceId)); + break; + case PairingMode::AlreadyDiscovered: + err = Pair(remoteId, PeerAddress::UDP(mRemoteAddr.address, mRemotePort, mRemoteAddr.interfaceId)); + break; + case PairingMode::AlreadyDiscoveredByIndex: + err = PairWithMdnsOrBleByIndex(remoteId, mIndex); + break; + case PairingMode::AlreadyDiscoveredByIndexWithCode: + err = PairWithMdnsOrBleByIndexWithCode(remoteId, mIndex); + break; + } + + return err; +} + +CommissioningParameters PairingCommand::GetCommissioningParameters() +{ + auto params = CommissioningParameters(); + params.SetSkipCommissioningComplete(mSkipCommissioningComplete.ValueOr(false)); + if (mBypassAttestationVerifier.ValueOr(false)) + { + params.SetDeviceAttestationDelegate(this); + } + + switch (mNetworkType) + { + case PairingNetworkType::WiFi: + params.SetWiFiCredentials(Controller::WiFiCredentials(mSSID, mPassword)); + break; + case PairingNetworkType::Thread: + params.SetThreadOperationalDataset(mOperationalDataset); + break; + case PairingNetworkType::None: + break; + } + + if (mCountryCode.HasValue()) + { + params.SetCountryCode(CharSpan::fromCharString(mCountryCode.Value())); + } + + // mTimeZoneList is an optional argument managed by TypedComplexArgument mComplex_TimeZones. + // Since optional Complex arguments are not currently supported via the class, + // we will use mTimeZoneList.data() value to determine if the argument was provided. + if (mTimeZoneList.data()) + { + params.SetTimeZone(mTimeZoneList); + } + + // miDSTOffsetList is an optional argument managed by TypedComplexArgument mComplex_DSTOffsets. + // Since optional Complex arguments are not currently supported via the class, + // we will use mTimeZoneList.data() value to determine if the argument was provided. + if (mDSTOffsetList.data()) + { + params.SetDSTOffsets(mDSTOffsetList); + } + + if (mICDRegistration.ValueOr(false)) + { + params.SetICDRegistrationStrategy(ICDRegistrationStrategy::kBeforeComplete); + + if (!mICDSymmetricKey.HasValue()) + { + chip::Crypto::DRBG_get_bytes(mRandomGeneratedICDSymmetricKey, sizeof(mRandomGeneratedICDSymmetricKey)); + mICDSymmetricKey.SetValue(ByteSpan(mRandomGeneratedICDSymmetricKey)); + } + if (!mICDCheckInNodeId.HasValue()) + { + mICDCheckInNodeId.SetValue(CurrentCommissioner().GetNodeId()); + } + if (!mICDMonitoredSubject.HasValue()) + { + mICDMonitoredSubject.SetValue(mICDCheckInNodeId.Value()); + } + // These Optionals must have values now. + // The commissioner will verify these values. + params.SetICDSymmetricKey(mICDSymmetricKey.Value()); + if (mICDStayActiveDurationMsec.HasValue()) + { + params.SetICDStayActiveDurationMsec(mICDStayActiveDurationMsec.Value()); + } + params.SetICDCheckInNodeId(mICDCheckInNodeId.Value()); + params.SetICDMonitoredSubject(mICDMonitoredSubject.Value()); + } + + return params; +} + +CHIP_ERROR PairingCommand::PaseWithCode(NodeId remoteId) +{ + auto discoveryType = DiscoveryType::kAll; + if (mUseOnlyOnNetworkDiscovery.ValueOr(false)) + { + discoveryType = DiscoveryType::kDiscoveryNetworkOnly; + } + + if (mDiscoverOnce.ValueOr(false)) + { + discoveryType = DiscoveryType::kDiscoveryNetworkOnlyWithoutPASEAutoRetry; + } + + return CurrentCommissioner().EstablishPASEConnection(remoteId, mOnboardingPayload, discoveryType); +} + +CHIP_ERROR PairingCommand::PairWithCode(NodeId remoteId) +{ + CommissioningParameters commissioningParams = GetCommissioningParameters(); + + // If no network discovery behavior and no network credentials are provided, assume that the pairing command is trying to pair + // with an on-network device. + if (!mUseOnlyOnNetworkDiscovery.HasValue()) + { + auto threadCredentials = commissioningParams.GetThreadOperationalDataset(); + auto wiFiCredentials = commissioningParams.GetWiFiCredentials(); + mUseOnlyOnNetworkDiscovery.SetValue(!threadCredentials.HasValue() && !wiFiCredentials.HasValue()); + } + + auto discoveryType = DiscoveryType::kAll; + if (mUseOnlyOnNetworkDiscovery.ValueOr(false)) + { + discoveryType = DiscoveryType::kDiscoveryNetworkOnly; + } + + if (mDiscoverOnce.ValueOr(false)) + { + discoveryType = DiscoveryType::kDiscoveryNetworkOnlyWithoutPASEAutoRetry; + } + + return CurrentCommissioner().PairDevice(remoteId, mOnboardingPayload, commissioningParams, discoveryType); +} + +CHIP_ERROR PairingCommand::Pair(NodeId remoteId, PeerAddress address) +{ + auto params = RendezvousParameters().SetSetupPINCode(mSetupPINCode).SetDiscriminator(mDiscriminator).SetPeerAddress(address); + + CHIP_ERROR err = CHIP_NO_ERROR; + if (mPaseOnly.ValueOr(false)) + { + err = CurrentCommissioner().EstablishPASEConnection(remoteId, params); + } + else + { + auto commissioningParams = GetCommissioningParameters(); + err = CurrentCommissioner().PairDevice(remoteId, params, commissioningParams); + } + return err; +} + +CHIP_ERROR PairingCommand::PairWithMdnsOrBleByIndex(NodeId remoteId, uint16_t index) +{ +#if CHIP_DEVICE_LAYER_TARGET_DARWIN + VerifyOrReturnError(IsInteractive(), CHIP_ERROR_INCORRECT_STATE); + + RendezvousParameters params; + ReturnErrorOnFailure(GetDeviceScanner().Get(index, params)); + params.SetSetupPINCode(mSetupPINCode); + + CHIP_ERROR err = CHIP_NO_ERROR; + if (mPaseOnly.ValueOr(false)) + { + err = CurrentCommissioner().EstablishPASEConnection(remoteId, params); + } + else + { + auto commissioningParams = GetCommissioningParameters(); + err = CurrentCommissioner().PairDevice(remoteId, params, commissioningParams); + } + return err; +#else + return CHIP_ERROR_NOT_IMPLEMENTED; +#endif // CHIP_DEVICE_LAYER_TARGET_DARWIN +} + +CHIP_ERROR PairingCommand::PairWithMdnsOrBleByIndexWithCode(NodeId remoteId, uint16_t index) +{ +#if CHIP_DEVICE_LAYER_TARGET_DARWIN + VerifyOrReturnError(IsInteractive(), CHIP_ERROR_INCORRECT_STATE); + + Dnssd::CommonResolutionData resolutionData; + auto err = GetDeviceScanner().Get(index, resolutionData); + if (CHIP_ERROR_NOT_FOUND == err) + { + // There is no device with this index that has some resolution data. This could simply + // be because the device is a ble device. In this case let's fall back to looking for + // a device with this index and some RendezvousParameters. + chip::SetupPayload payload; + bool isQRCode = strncmp(mOnboardingPayload, kQRCodePrefix, strlen(kQRCodePrefix)) == 0; + if (isQRCode) + { + ReturnErrorOnFailure(QRCodeSetupPayloadParser(mOnboardingPayload).populatePayload(payload)); + VerifyOrReturnError(payload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT); + } + else + { + ReturnErrorOnFailure(ManualSetupPayloadParser(mOnboardingPayload).populatePayload(payload)); + VerifyOrReturnError(payload.isValidManualCode(), CHIP_ERROR_INVALID_ARGUMENT); + } + + mSetupPINCode = payload.setUpPINCode; + return PairWithMdnsOrBleByIndex(remoteId, index); + } + + err = CHIP_NO_ERROR; + if (mPaseOnly.ValueOr(false)) + { + err = CurrentCommissioner().EstablishPASEConnection(remoteId, mOnboardingPayload, DiscoveryType::kDiscoveryNetworkOnly, + MakeOptional(resolutionData)); + } + else + { + auto commissioningParams = GetCommissioningParameters(); + err = CurrentCommissioner().PairDevice(remoteId, mOnboardingPayload, commissioningParams, + DiscoveryType::kDiscoveryNetworkOnly, MakeOptional(resolutionData)); + } + return err; +#else + return CHIP_ERROR_NOT_IMPLEMENTED; +#endif // CHIP_DEVICE_LAYER_TARGET_DARWIN +} + +CHIP_ERROR PairingCommand::PairWithMdns(NodeId remoteId) +{ + Dnssd::DiscoveryFilter filter(mFilterType); + switch (mFilterType) + { + case chip::Dnssd::DiscoveryFilterType::kNone: + break; + case chip::Dnssd::DiscoveryFilterType::kShortDiscriminator: + case chip::Dnssd::DiscoveryFilterType::kLongDiscriminator: + case chip::Dnssd::DiscoveryFilterType::kCompressedFabricId: + case chip::Dnssd::DiscoveryFilterType::kVendorId: + case chip::Dnssd::DiscoveryFilterType::kDeviceType: + filter.code = mDiscoveryFilterCode; + break; + case chip::Dnssd::DiscoveryFilterType::kCommissioningMode: + break; + case chip::Dnssd::DiscoveryFilterType::kCommissioner: + filter.code = 1; + break; + case chip::Dnssd::DiscoveryFilterType::kInstanceName: + filter.code = 0; + filter.instanceName = mDiscoveryFilterInstanceName; + break; + } + + CurrentCommissioner().RegisterDeviceDiscoveryDelegate(this); + return CurrentCommissioner().DiscoverCommissionableNodes(filter); +} + +CHIP_ERROR PairingCommand::Unpair(NodeId remoteId) +{ + mCurrentFabricRemover = Platform::MakeUnique(&CurrentCommissioner()); + return mCurrentFabricRemover->RemoveCurrentFabric(remoteId, &mCurrentFabricRemoveCallback); +} + +void PairingCommand::OnStatusUpdate(DevicePairingDelegate::Status status) +{ + switch (status) + { + case DevicePairingDelegate::Status::SecurePairingSuccess: + ChipLogProgress(NotSpecified, "Secure Pairing Success"); + ChipLogProgress(NotSpecified, "CASE establishment successful"); + break; + case DevicePairingDelegate::Status::SecurePairingFailed: + ChipLogError(NotSpecified, "Secure Pairing Failed"); + SetCommandExitStatus(CHIP_ERROR_INCORRECT_STATE); + break; + } +} + +void PairingCommand::OnPairingComplete(CHIP_ERROR err) +{ + if (err == CHIP_NO_ERROR) + { + ChipLogProgress(NotSpecified, "Pairing Success"); + ChipLogProgress(NotSpecified, "PASE establishment successful"); + if (mPairingMode == PairingMode::CodePaseOnly || mPaseOnly.ValueOr(false)) + { + SetCommandExitStatus(err); + } + } + else + { + ChipLogProgress(NotSpecified, "Pairing Failure: %s", ErrorStr(err)); + } + + if (err != CHIP_NO_ERROR) + { + SetCommandExitStatus(err); + } +} + +void PairingCommand::OnPairingDeleted(CHIP_ERROR err) +{ + if (err == CHIP_NO_ERROR) + { + ChipLogProgress(NotSpecified, "Pairing Deleted Success"); + } + else + { + ChipLogProgress(NotSpecified, "Pairing Deleted Failure: %s", ErrorStr(err)); + } + + SetCommandExitStatus(err); +} + +void PairingCommand::OnCommissioningComplete(NodeId nodeId, CHIP_ERROR err) +{ + if (err == CHIP_NO_ERROR) + { + ChipLogProgress(NotSpecified, "Device commissioning completed with success"); + } + else + { + // When ICD device commissioning fails, the ICDClientInfo stored in OnICDRegistrationComplete needs to be removed. + if (mDeviceIsICD) + { + CHIP_ERROR deleteEntryError = + CHIPCommand::sICDClientStorage.DeleteEntry(ScopedNodeId(mNodeId, CurrentCommissioner().GetFabricIndex())); + if (deleteEntryError != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to delete ICD entry: %s", ErrorStr(err)); + } + } + ChipLogProgress(NotSpecified, "Device commissioning Failure: %s", ErrorStr(err)); + } + + SetCommandExitStatus(err); +} + +void PairingCommand::OnReadCommissioningInfo(const Controller::ReadCommissioningInfo & info) +{ + ChipLogProgress(AppServer, "OnReadCommissioningInfo - vendorId=0x%04X productId=0x%04X", info.basic.vendorId, + info.basic.productId); + + // The string in CharSpan received from the device is not null-terminated, we use std::string here for coping and + // appending a numm-terminator at the end of the string. + std::string userActiveModeTriggerInstruction; + + // Note: the callback doesn't own the buffer, should make a copy if it will be used it later. + if (info.icd.userActiveModeTriggerInstruction.size() != 0) + { + userActiveModeTriggerInstruction = + std::string(info.icd.userActiveModeTriggerInstruction.data(), info.icd.userActiveModeTriggerInstruction.size()); + } + + if (info.icd.userActiveModeTriggerHint.HasAny()) + { + ChipLogProgress(AppServer, "OnReadCommissioningInfo - LIT UserActiveModeTriggerHint=0x%08x", + info.icd.userActiveModeTriggerHint.Raw()); + ChipLogProgress(AppServer, "OnReadCommissioningInfo - LIT UserActiveModeTriggerInstruction=%s", + userActiveModeTriggerInstruction.c_str()); + } + ChipLogProgress(AppServer, "OnReadCommissioningInfo ICD - IdleModeDuration=%u activeModeDuration=%u activeModeThreshold=%u", + info.icd.idleModeDuration, info.icd.activeModeDuration, info.icd.activeModeThreshold); +} + +void PairingCommand::OnICDRegistrationComplete(NodeId nodeId, uint32_t icdCounter) +{ + char icdSymmetricKeyHex[chip::Crypto::kAES_CCM128_Key_Length * 2 + 1]; + + chip::Encoding::BytesToHex(mICDSymmetricKey.Value().data(), mICDSymmetricKey.Value().size(), icdSymmetricKeyHex, + sizeof(icdSymmetricKeyHex), chip::Encoding::HexFlags::kNullTerminate); + + app::ICDClientInfo clientInfo; + clientInfo.peer_node = ScopedNodeId(nodeId, CurrentCommissioner().GetFabricIndex()); + clientInfo.monitored_subject = mICDMonitoredSubject.Value(); + clientInfo.start_icd_counter = icdCounter; + + CHIP_ERROR err = CHIPCommand::sICDClientStorage.SetKey(clientInfo, mICDSymmetricKey.Value()); + if (err == CHIP_NO_ERROR) + { + err = CHIPCommand::sICDClientStorage.StoreEntry(clientInfo); + } + + if (err != CHIP_NO_ERROR) + { + CHIPCommand::sICDClientStorage.RemoveKey(clientInfo); + ChipLogError(NotSpecified, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s", ChipLogValueX64(nodeId), + err.AsString()); + SetCommandExitStatus(err); + return; + } + + mDeviceIsICD = true; + + ChipLogProgress(NotSpecified, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(nodeId)); + ChipLogProgress(NotSpecified, + "ICD Registration Complete for device " ChipLogFormatX64 " / Check-In NodeID: " ChipLogFormatX64 + " / Monitored Subject: " ChipLogFormatX64 " / Symmetric Key: %s / ICDCounter %u", + ChipLogValueX64(nodeId), ChipLogValueX64(mICDCheckInNodeId.Value()), + ChipLogValueX64(mICDMonitoredSubject.Value()), icdSymmetricKeyHex, icdCounter); +} + +void PairingCommand::OnICDStayActiveComplete(NodeId deviceId, uint32_t promisedActiveDuration) +{ + ChipLogProgress(NotSpecified, "ICD Stay Active Complete for device " ChipLogFormatX64 " / promisedActiveDuration: %u", + ChipLogValueX64(deviceId), promisedActiveDuration); +} + +void PairingCommand::OnDiscoveredDevice(const chip::Dnssd::CommissionNodeData & nodeData) +{ + // Ignore nodes with closed commissioning window + VerifyOrReturn(nodeData.commissioningMode != 0); + + auto & resolutionData = nodeData; + + const uint16_t port = resolutionData.port; + char buf[chip::Inet::IPAddress::kMaxStringLength]; + resolutionData.ipAddress[0].ToString(buf); + ChipLogProgress(NotSpecified, "Discovered Device: %s:%u", buf, port); + + // Stop Mdns discovery. + auto err = CurrentCommissioner().StopCommissionableDiscovery(); + + // Some platforms does not implement a mechanism to stop mdns browse, so + // we just ignore CHIP_ERROR_NOT_IMPLEMENTED instead of bailing out. + if (CHIP_NO_ERROR != err && CHIP_ERROR_NOT_IMPLEMENTED != err) + { + SetCommandExitStatus(err); + return; + } + + CurrentCommissioner().RegisterDeviceDiscoveryDelegate(nullptr); + + auto interfaceId = resolutionData.ipAddress[0].IsIPv6LinkLocal() ? resolutionData.interfaceId : Inet::InterfaceId::Null(); + auto peerAddress = PeerAddress::UDP(resolutionData.ipAddress[0], port, interfaceId); + err = Pair(mNodeId, peerAddress); + if (CHIP_NO_ERROR != err) + { + SetCommandExitStatus(err); + } +} + +void PairingCommand::OnCurrentFabricRemove(void * context, NodeId nodeId, CHIP_ERROR err) +{ + PairingCommand * command = reinterpret_cast(context); + VerifyOrReturn(command != nullptr, ChipLogError(NotSpecified, "OnCurrentFabricRemove: context is null")); + + if (err == CHIP_NO_ERROR) + { + ChipLogProgress(NotSpecified, "Device unpair completed with success: " ChipLogFormatX64, ChipLogValueX64(nodeId)); + } + else + { + ChipLogProgress(NotSpecified, "Device unpair Failure: " ChipLogFormatX64 " %s", ChipLogValueX64(nodeId), ErrorStr(err)); + } + + command->SetCommandExitStatus(err); +} + +chip::Optional PairingCommand::FailSafeExpiryTimeoutSecs() const +{ + // We don't need to set additional failsafe timeout as we don't ask the final user if he wants to continue + return chip::Optional(); +} + +void PairingCommand::OnDeviceAttestationCompleted(chip::Controller::DeviceCommissioner * deviceCommissioner, + chip::DeviceProxy * device, + const chip::Credentials::DeviceAttestationVerifier::AttestationDeviceInfo & info, + chip::Credentials::AttestationVerificationResult attestationResult) +{ + // Bypass attestation verification, continue with success + auto err = deviceCommissioner->ContinueCommissioningAfterDeviceAttestation( + device, chip::Credentials::AttestationVerificationResult::kSuccess); + if (CHIP_NO_ERROR != err) + { + SetCommandExitStatus(err); + } +} diff --git a/examples/fabric-admin/commands/pairing/PairingCommand.h b/examples/fabric-admin/commands/pairing/PairingCommand.h new file mode 100644 index 00000000000000..4ff3903253be4e --- /dev/null +++ b/examples/fabric-admin/commands/pairing/PairingCommand.h @@ -0,0 +1,268 @@ +/* + * 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 "../common/CHIPCommand.h" +#include +#include + +#include +#include +#include + +enum class PairingMode +{ + None, + Code, + CodePaseOnly, + Ble, + SoftAP, + AlreadyDiscovered, + AlreadyDiscoveredByIndex, + AlreadyDiscoveredByIndexWithCode, + OnNetwork, +}; + +enum class PairingNetworkType +{ + None, + WiFi, + Thread, +}; + +class PairingCommand : public CHIPCommand, + public chip::Controller::DevicePairingDelegate, + public chip::Controller::DeviceDiscoveryDelegate, + public chip::Credentials::DeviceAttestationDelegate +{ +public: + PairingCommand(const char * commandName, PairingMode mode, PairingNetworkType networkType, + CredentialIssuerCommands * credIssuerCmds, + chip::Dnssd::DiscoveryFilterType filterType = chip::Dnssd::DiscoveryFilterType::kNone) : + CHIPCommand(commandName, credIssuerCmds), + mPairingMode(mode), mNetworkType(networkType), mFilterType(filterType), + mRemoteAddr{ IPAddress::Any, chip::Inet::InterfaceId::Null() }, mComplex_TimeZones(&mTimeZoneList), + mComplex_DSTOffsets(&mDSTOffsetList), mCurrentFabricRemoveCallback(OnCurrentFabricRemove, this) + { + AddArgument("node-id", 0, UINT64_MAX, &mNodeId); + AddArgument("bypass-attestation-verifier", 0, 1, &mBypassAttestationVerifier, + "Bypass the attestation verifier. If not provided or false, the attestation verifier is not bypassed." + " If true, the commissioning will continue in case of attestation verification failure."); + AddArgument("case-auth-tags", 1, UINT32_MAX, &mCASEAuthTags, "The CATs to be encoded in the NOC sent to the commissionee"); + AddArgument("icd-registration", 0, 1, &mICDRegistration, + "Whether to register for check-ins from ICDs during commissioning. Default: false"); + AddArgument("icd-check-in-nodeid", 0, UINT64_MAX, &mICDCheckInNodeId, + "The check-in node id for the ICD, default: node id of the commissioner."); + AddArgument("icd-monitored-subject", 0, UINT64_MAX, &mICDMonitoredSubject, + "The monitored subject of the ICD, default: The node id used for icd-check-in-nodeid."); + AddArgument("icd-symmetric-key", &mICDSymmetricKey, "The 16 bytes ICD symmetric key, default: randomly generated."); + AddArgument("icd-stay-active-duration", 0, UINT32_MAX, &mICDStayActiveDurationMsec, + "If set, a LIT ICD that is commissioned will be requested to stay active for this many milliseconds"); + switch (networkType) + { + case PairingNetworkType::None: + break; + case PairingNetworkType::WiFi: + AddArgument("ssid", &mSSID); + AddArgument("password", &mPassword); + break; + case PairingNetworkType::Thread: + AddArgument("operationalDataset", &mOperationalDataset); + break; + } + + switch (mode) + { + case PairingMode::None: + break; + case PairingMode::Code: + AddArgument("skip-commissioning-complete", 0, 1, &mSkipCommissioningComplete); + FALLTHROUGH; + case PairingMode::CodePaseOnly: + AddArgument("payload", &mOnboardingPayload); + AddArgument("discover-once", 0, 1, &mDiscoverOnce); + AddArgument("use-only-onnetwork-discovery", 0, 1, &mUseOnlyOnNetworkDiscovery); + break; + case PairingMode::Ble: + AddArgument("skip-commissioning-complete", 0, 1, &mSkipCommissioningComplete); + AddArgument("setup-pin-code", 0, 134217727, &mSetupPINCode); + AddArgument("discriminator", 0, 4096, &mDiscriminator); + break; + case PairingMode::OnNetwork: + AddArgument("skip-commissioning-complete", 0, 1, &mSkipCommissioningComplete); + AddArgument("setup-pin-code", 0, 134217727, &mSetupPINCode); + AddArgument("pase-only", 0, 1, &mPaseOnly); + break; + case PairingMode::SoftAP: + AddArgument("skip-commissioning-complete", 0, 1, &mSkipCommissioningComplete); + AddArgument("setup-pin-code", 0, 134217727, &mSetupPINCode); + AddArgument("discriminator", 0, 4096, &mDiscriminator); + AddArgument("device-remote-ip", &mRemoteAddr); + AddArgument("device-remote-port", 0, UINT16_MAX, &mRemotePort); + AddArgument("pase-only", 0, 1, &mPaseOnly); + break; + case PairingMode::AlreadyDiscovered: + AddArgument("skip-commissioning-complete", 0, 1, &mSkipCommissioningComplete); + AddArgument("setup-pin-code", 0, 134217727, &mSetupPINCode); + AddArgument("device-remote-ip", &mRemoteAddr); + AddArgument("device-remote-port", 0, UINT16_MAX, &mRemotePort); + AddArgument("pase-only", 0, 1, &mPaseOnly); + break; + case PairingMode::AlreadyDiscoveredByIndex: + AddArgument("skip-commissioning-complete", 0, 1, &mSkipCommissioningComplete); + AddArgument("setup-pin-code", 0, 134217727, &mSetupPINCode); + AddArgument("index", 0, UINT16_MAX, &mIndex); + AddArgument("pase-only", 0, 1, &mPaseOnly); + break; + case PairingMode::AlreadyDiscoveredByIndexWithCode: + AddArgument("skip-commissioning-complete", 0, 1, &mSkipCommissioningComplete); + AddArgument("payload", &mOnboardingPayload); + AddArgument("index", 0, UINT16_MAX, &mIndex); + AddArgument("pase-only", 0, 1, &mPaseOnly); + break; + } + + switch (filterType) + { + case chip::Dnssd::DiscoveryFilterType::kNone: + break; + case chip::Dnssd::DiscoveryFilterType::kShortDiscriminator: + AddArgument("discriminator", 0, 15, &mDiscoveryFilterCode); + break; + case chip::Dnssd::DiscoveryFilterType::kLongDiscriminator: + AddArgument("discriminator", 0, 4096, &mDiscoveryFilterCode); + break; + case chip::Dnssd::DiscoveryFilterType::kVendorId: + AddArgument("vendor-id", 0, UINT16_MAX, &mDiscoveryFilterCode); + break; + case chip::Dnssd::DiscoveryFilterType::kCompressedFabricId: + AddArgument("fabric-id", 0, UINT64_MAX, &mDiscoveryFilterCode); + break; + case chip::Dnssd::DiscoveryFilterType::kCommissioningMode: + case chip::Dnssd::DiscoveryFilterType::kCommissioner: + break; + case chip::Dnssd::DiscoveryFilterType::kDeviceType: + AddArgument("device-type", 0, UINT16_MAX, &mDiscoveryFilterCode); + break; + case chip::Dnssd::DiscoveryFilterType::kInstanceName: + AddArgument("name", &mDiscoveryFilterInstanceName); + break; + } + + if (mode != PairingMode::None) + { + AddArgument("country-code", &mCountryCode, + "Country code to use to set the Basic Information cluster's Location attribute"); + + // mTimeZoneList is an optional argument managed by TypedComplexArgument mComplex_TimeZones. + // Since optional Complex arguments are not currently supported via the class, + // we explicitly set the kOptional flag. + AddArgument("time-zone", &mComplex_TimeZones, + "TimeZone list to use when setting Time Synchronization cluster's TimeZone attribute", Argument::kOptional); + + // mDSTOffsetList is an optional argument managed by TypedComplexArgument mComplex_DSTOffsets. + // Since optional Complex arguments are not currently supported via the class, + // we explicitly set the kOptional flag. + AddArgument("dst-offset", &mComplex_DSTOffsets, + "DSTOffset list to use when setting Time Synchronization cluster's DSTOffset attribute", + Argument::kOptional); + } + + AddArgument("timeout", 0, UINT16_MAX, &mTimeout); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override; + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(mTimeout.ValueOr(120)); } + + /////////// DevicePairingDelegate Interface ///////// + void OnStatusUpdate(chip::Controller::DevicePairingDelegate::Status status) override; + void OnPairingComplete(CHIP_ERROR error) override; + void OnPairingDeleted(CHIP_ERROR error) override; + void OnReadCommissioningInfo(const chip::Controller::ReadCommissioningInfo & info) override; + void OnCommissioningComplete(NodeId deviceId, CHIP_ERROR error) override; + void OnICDRegistrationComplete(NodeId deviceId, uint32_t icdCounter) override; + void OnICDStayActiveComplete(NodeId deviceId, uint32_t promisedActiveDuration) override; + + /////////// DeviceDiscoveryDelegate Interface ///////// + void OnDiscoveredDevice(const chip::Dnssd::CommissionNodeData & nodeData) override; + + /////////// DeviceAttestationDelegate ///////// + chip::Optional FailSafeExpiryTimeoutSecs() const override; + void OnDeviceAttestationCompleted(chip::Controller::DeviceCommissioner * deviceCommissioner, chip::DeviceProxy * device, + const chip::Credentials::DeviceAttestationVerifier::AttestationDeviceInfo & info, + chip::Credentials::AttestationVerificationResult attestationResult) override; + +private: + CHIP_ERROR RunInternal(NodeId remoteId); + CHIP_ERROR Pair(NodeId remoteId, PeerAddress address); + CHIP_ERROR PairWithMdns(NodeId remoteId); + CHIP_ERROR PairWithCode(NodeId remoteId); + CHIP_ERROR PaseWithCode(NodeId remoteId); + CHIP_ERROR PairWithMdnsOrBleByIndex(NodeId remoteId, uint16_t index); + CHIP_ERROR PairWithMdnsOrBleByIndexWithCode(NodeId remoteId, uint16_t index); + CHIP_ERROR Unpair(NodeId remoteId); + chip::Controller::CommissioningParameters GetCommissioningParameters(); + + const PairingMode mPairingMode; + const PairingNetworkType mNetworkType; + const chip::Dnssd::DiscoveryFilterType mFilterType; + Command::AddressWithInterface mRemoteAddr; + NodeId mNodeId; + chip::Optional mTimeout; + chip::Optional mDiscoverOnce; + chip::Optional mUseOnlyOnNetworkDiscovery; + chip::Optional mPaseOnly; + chip::Optional mSkipCommissioningComplete; + chip::Optional mBypassAttestationVerifier; + chip::Optional> mCASEAuthTags; + chip::Optional mCountryCode; + chip::Optional mICDRegistration; + chip::Optional mICDCheckInNodeId; + chip::Optional mICDSymmetricKey; + chip::Optional mICDMonitoredSubject; + chip::Optional mICDStayActiveDurationMsec; + chip::app::DataModel::List mTimeZoneList; + TypedComplexArgument> + mComplex_TimeZones; + chip::app::DataModel::List mDSTOffsetList; + TypedComplexArgument> + mComplex_DSTOffsets; + + uint16_t mRemotePort; + uint16_t mDiscriminator; + uint32_t mSetupPINCode; + uint16_t mIndex; + chip::ByteSpan mOperationalDataset; + chip::ByteSpan mSSID; + chip::ByteSpan mPassword; + char * mOnboardingPayload; + uint64_t mDiscoveryFilterCode; + char * mDiscoveryFilterInstanceName; + + bool mDeviceIsICD; + uint8_t mRandomGeneratedICDSymmetricKey[chip::Crypto::kAES_CCM128_Key_Length]; + + // For unpair + chip::Platform::UniquePtr mCurrentFabricRemover; + chip::Callback::Callback mCurrentFabricRemoveCallback; + + static void OnCurrentFabricRemove(void * context, NodeId remoteNodeId, CHIP_ERROR status); + void PersistIcdInfo(); +}; diff --git a/examples/fabric-admin/commands/pairing/ToTLVCert.cpp b/examples/fabric-admin/commands/pairing/ToTLVCert.cpp new file mode 100644 index 00000000000000..01f9156f744593 --- /dev/null +++ b/examples/fabric-admin/commands/pairing/ToTLVCert.cpp @@ -0,0 +1,54 @@ +/* + * 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. + * + */ + +#include "ToTLVCert.h" + +#include +#include + +#include + +constexpr char kBase64Header[] = "base64:"; +constexpr size_t kBase64HeaderLen = ArraySize(kBase64Header) - 1; + +CHIP_ERROR ToBase64(const chip::ByteSpan & input, std::string & outputAsPrefixedBase64) +{ + chip::Platform::ScopedMemoryBuffer base64String; + base64String.Alloc(kBase64HeaderLen + BASE64_ENCODED_LEN(input.size()) + 1); + VerifyOrReturnError(base64String.Get() != nullptr, CHIP_ERROR_NO_MEMORY); + + auto encodedLen = chip::Base64Encode(input.data(), static_cast(input.size()), base64String.Get() + kBase64HeaderLen); + if (encodedLen) + { + memcpy(base64String.Get(), kBase64Header, kBase64HeaderLen); + encodedLen = static_cast(encodedLen + kBase64HeaderLen); + } + base64String.Get()[encodedLen] = '\0'; + outputAsPrefixedBase64 = std::string(base64String.Get(), encodedLen); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ToTLVCert(const chip::ByteSpan & derEncodedCertificate, std::string & tlvCertAsPrefixedBase64) +{ + uint8_t chipCertBuffer[chip::Credentials::kMaxCHIPCertLength]; + chip::MutableByteSpan chipCertBytes(chipCertBuffer); + ReturnErrorOnFailure(chip::Credentials::ConvertX509CertToChipCert(derEncodedCertificate, chipCertBytes)); + ReturnErrorOnFailure(ToBase64(chipCertBytes, tlvCertAsPrefixedBase64)); + return CHIP_NO_ERROR; +} diff --git a/examples/fabric-admin/commands/pairing/ToTLVCert.h b/examples/fabric-admin/commands/pairing/ToTLVCert.h new file mode 100644 index 00000000000000..29956470b529ce --- /dev/null +++ b/examples/fabric-admin/commands/pairing/ToTLVCert.h @@ -0,0 +1,25 @@ +/* + * 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 + +CHIP_ERROR ToBase64(const chip::ByteSpan & input, std::string & outputAsPrefixedBase64); +CHIP_ERROR ToTLVCert(const chip::ByteSpan & derEncodedCertificate, std::string & tlvCertAsPrefixedBase64); diff --git a/examples/fabric-admin/fabric-admin.gni b/examples/fabric-admin/fabric-admin.gni new file mode 100644 index 00000000000000..021ab7792458d9 --- /dev/null +++ b/examples/fabric-admin/fabric-admin.gni @@ -0,0 +1,22 @@ +# 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("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +declare_args() { + # Use a separate eventloop for CHIP tasks + config_use_separate_eventloop = true + config_use_local_storage = true +} diff --git a/examples/fabric-admin/include/CHIPProjectAppConfig.h b/examples/fabric-admin/include/CHIPProjectAppConfig.h new file mode 100644 index 00000000000000..b3f85d69359e87 --- /dev/null +++ b/examples/fabric-admin/include/CHIPProjectAppConfig.h @@ -0,0 +1,67 @@ +/* + * + * 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 + * Project configuration for Fabric Admin. + * + */ +#ifndef CHIPPROJECTCONFIG_H +#define CHIPPROJECTCONFIG_H + +#define CHIP_CONFIG_MAX_FABRICS 17 + +#define CHIP_CONFIG_EVENT_LOGGING_NUM_EXTERNAL_CALLBACKS 2 + +// Uncomment this for a large Tunnel MTU. +// #define CHIP_CONFIG_TUNNEL_INTERFACE_MTU (9000) + +// Enable support functions for parsing command-line arguments +#define CHIP_CONFIG_ENABLE_ARG_PARSER 1 + +// Use a default pairing code if one hasn't been provisioned in flash. +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 + +// Enable reading DRBG seed data from /dev/(u)random. +// This is needed for test applications and the CHIP device manager to function +// properly when CHIP_CONFIG_RNG_IMPLEMENTATION_CHIPDRBG is enabled. +#define CHIP_CONFIG_DEV_RANDOM_DRBG_SEED 1 + +// For convenience, Chip Security Test Mode can be enabled and the +// requirement for authentication in various protocols can be disabled. +// +// WARNING: These options make it possible to circumvent basic Chip security functionality, +// including message encryption. Because of this they MUST NEVER BE ENABLED IN PRODUCTION BUILDS. +// +#define CHIP_CONFIG_SECURITY_TEST_MODE 0 + +#define CHIP_CONFIG_ENABLE_UPDATE 1 + +#define CHIP_SYSTEM_CONFIG_PACKETBUFFER_POOL_SIZE 0 + +#define CHIP_CONFIG_DATA_MANAGEMENT_CLIENT_EXPERIMENTAL 1 + +#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY 1 + +// Enable some test-only interaction model APIs. +#define CONFIG_BUILD_FOR_HOST_UNIT_TEST 1 + +// Allow us, for test purposes, to encode invalid enum values. +#define CHIP_CONFIG_IM_ENABLE_ENCODING_SENTINEL_ENUM_VALUES 1 + +#endif /* CHIPPROJECTCONFIG_H */ diff --git a/examples/fabric-admin/main.cpp b/examples/fabric-admin/main.cpp new file mode 100644 index 00000000000000..e517c67f6b403f --- /dev/null +++ b/examples/fabric-admin/main.cpp @@ -0,0 +1,40 @@ +/* + * 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. + * + */ + +#include "commands/common/Commands.h" + +#include "commands/clusters/SubscriptionsCommands.h" +#include "commands/interactive/Commands.h" +#include "commands/pairing/Commands.h" +#include + +// ================================================================================ +// Main Code +// ================================================================================ +int main(int argc, char * argv[]) +{ + ExampleCredentialIssuerCommands credIssuerCommands; + Commands commands; + + registerCommandsInteractive(commands, &credIssuerCommands); + registerCommandsPairing(commands, &credIssuerCommands); + registerClusters(commands, &credIssuerCommands); + registerCommandsSubscriptions(commands, &credIssuerCommands); + + return commands.Run(argc, argv); +} diff --git a/examples/fabric-admin/third_party/connectedhomeip b/examples/fabric-admin/third_party/connectedhomeip new file mode 120000 index 00000000000000..1b20c9fb816b63 --- /dev/null +++ b/examples/fabric-admin/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../ \ No newline at end of file diff --git a/examples/platform/nxp/common/app_task/source/AppTaskBase.cpp b/examples/platform/nxp/common/app_task/source/AppTaskBase.cpp index 2f0bbcbf85b653..2b70fcb4c070c7 100644 --- a/examples/platform/nxp/common/app_task/source/AppTaskBase.cpp +++ b/examples/platform/nxp/common/app_task/source/AppTaskBase.cpp @@ -210,7 +210,7 @@ CHIP_ERROR chip::NXP::App::AppTaskBase::Init() Shell::SetWiFiDriver(chip::NXP::App::GetAppTask().GetWifiDriverInstance()); #endif #endif -#if CONFIG_CHIP_OTA_REQUESTOR +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR if (err == CHIP_NO_ERROR) { /* If an update is under test make it permanent */ diff --git a/examples/platform/silabs/FreeRTOSConfig.h b/examples/platform/silabs/FreeRTOSConfig.h index ec926f4bbbeb94..5e257c4caf5b16 100644 --- a/examples/platform/silabs/FreeRTOSConfig.h +++ b/examples/platform/silabs/FreeRTOSConfig.h @@ -197,7 +197,7 @@ See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY 48 #endif // SLI_SI91X_MCU_INTERFACE -#define configENABLE_FPU 0 +#define configENABLE_FPU 1 #define configENABLE_MPU 0 /* FreeRTOS Secure Side Only and TrustZone Security Extension */ #define configRUN_FREERTOS_SECURE_ONLY 1 diff --git a/integrations/docker/images/chip-cert-bins/Dockerfile b/integrations/docker/images/chip-cert-bins/Dockerfile index b04679dd3a1ebe..ef26e30945c943 100644 --- a/integrations/docker/images/chip-cert-bins/Dockerfile +++ b/integrations/docker/images/chip-cert-bins/Dockerfile @@ -89,7 +89,6 @@ RUN set -x \ wget \ git-lfs \ zlib1g-dev \ - && rm -rf /var/lib/apt/lists/ \ && git lfs install \ && : # last line @@ -119,13 +118,12 @@ RUN case ${TARGETPLATFORM} in \ # Python 3 and PIP RUN set -x \ - && DEBIAN_FRONTEND=noninteractive apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y libgirepository1.0-dev \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + libgirepository1.0-dev \ + software-properties-common \ && add-apt-repository universe \ && curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py \ && python3 get-pip.py \ - && rm -rf /var/lib/apt/lists/ \ && : # last line RUN set -x \ @@ -148,9 +146,9 @@ RUN set -x \ && git clone https://github.com/google/bloaty.git \ && mkdir -p bloaty/build \ && cd bloaty/build \ - && cmake ../ \ - && make -j8 \ - && make install \ + && cmake -G Ninja ../ \ + && ninja \ + && ninja install \ && cd ../.. \ && rm -rf bloaty \ && : # last line @@ -194,7 +192,6 @@ RUN case ${TARGETPLATFORM} in \ --target linux-x64-lit-icd-ipv6only \ --target linux-x64-energy-management-ipv6only \ --target linux-x64-microwave-oven-ipv6only \ - --target linux-x64-rvc-ipv6only \ build \ && mv out/linux-x64-chip-tool-ipv6only-platform-mdns/chip-tool out/chip-tool \ && mv out/linux-x64-shell-ipv6only-platform-mdns/chip-shell out/chip-shell \ @@ -214,7 +211,6 @@ RUN case ${TARGETPLATFORM} in \ && mv out/linux-x64-lit-icd-ipv6only/lit-icd-app out/lit-icd-app \ && mv out/linux-x64-energy-management-ipv6only/chip-energy-management-app out/chip-energy-management-app \ && mv out/linux-x64-microwave-oven-ipv6only/chip-microwave-oven-app out/chip-microwave-oven-app \ - && mv out/linux-x64-rvc-ipv6only/chip-rvc-app out/chip-rvc-app \ ;; \ "linux/arm64")\ set -x \ @@ -257,7 +253,6 @@ RUN case ${TARGETPLATFORM} in \ && mv out/linux-arm64-lit-icd-ipv6only/lit-icd-app out/lit-icd-app \ && mv out/linux-arm64-energy-management-ipv6only/chip-energy-management-app out/chip-energy-management-app \ && mv out/linux-arm64-microwave-oven-ipv6only/chip-microwave-oven-app out/chip-microwave-oven-app \ - && mv out/linux-arm64-rvc-ipv6only/chip-rvc-app out/chip-rvc-app \ ;; \ *) ;; \ esac @@ -290,13 +285,11 @@ COPY --from=chip-build-cert-bins /root/connectedhomeip/out/chip-app1 chip-app1 COPY --from=chip-build-cert-bins /root/connectedhomeip/out/lit-icd-app lit-icd-app COPY --from=chip-build-cert-bins /root/connectedhomeip/out/chip-energy-management-app chip-energy-management-app COPY --from=chip-build-cert-bins /root/connectedhomeip/out/chip-microwave-oven-app chip-microwave-oven-app -COPY --from=chip-build-cert-bins /root/connectedhomeip/out/chip-rvc-app chip-rvc-app # Stage 3.1: Setup the Matter Python environment COPY --from=chip-build-cert-bins /root/connectedhomeip/out/python_lib python_lib COPY --from=chip-build-cert-bins /root/connectedhomeip/out/python_env python_env -COPY --from=chip-build-cert-bins /root/connectedhomeip/src/python_testing python_testing/scripts/sdk -COPY --from=chip-build-cert-bins /root/connectedhomeip/data_model python_testing/data_model +COPY --from=chip-build-cert-bins /root/connectedhomeip/src/python_testing python_testing COPY --from=chip-build-cert-bins /root/connectedhomeip/scripts/tests/requirements.txt /tmp/requirements.txt RUN pip install -r /tmp/requirements.txt && rm /tmp/requirements.txt @@ -304,4 +297,7 @@ RUN pip install -r /tmp/requirements.txt && rm /tmp/requirements.txt COPY --from=chip-build-cert-bins /root/connectedhomeip/src/python_testing/requirements.txt /tmp/requirements.txt RUN pip install -r /tmp/requirements.txt && rm /tmp/requirements.txt +# PIP requires MASON package compilation, which seems to require a JDK +RUN set -x && DEBIAN_FRONTEND=noninteractive apt-get install -fy openjdk-8-jdk + RUN pip install --no-cache-dir python_lib/controller/python/chip*.whl diff --git a/src/BUILD.gn b/src/BUILD.gn index 01aae8abc3fbab..cf15f40ec00629 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -92,9 +92,9 @@ if (chip_build_tests) { ] } + # Skip on efr32 due to flash and/or ram limitations. if (chip_device_platform != "efr32") { tests += [ - # TODO(#10447): App test has HF on EFR32. "${chip_root}/src/app/tests", "${chip_root}/src/credentials/tests", "${chip_root}/src/lib/format/tests", @@ -128,7 +128,7 @@ if (chip_build_tests) { # https://github.com/project-chip/connectedhomeip/issues/9630 if (chip_device_platform != "nrfconnect" && chip_device_platform != "efr32") { - # TODO(#10447): Controller test has HF on EFR32. + # Doesn't compile on ef32. Multiple definitions issues with attribute storage and overflows flash memory. tests += [ "${chip_root}/src/controller/tests/data_model" ] # Skip controller test for Open IoT SDK diff --git a/src/app/chip_data_model.cmake b/src/app/chip_data_model.cmake index 5573eeb275acd7..2d4149b66f6a8a 100644 --- a/src/app/chip_data_model.cmake +++ b/src/app/chip_data_model.cmake @@ -144,6 +144,8 @@ function(chip_configure_data_model APP_TARGET) ${CHIP_APP_BASE_DIR}/icd/server/ICDConfigurationData.cpp ${CHIP_APP_BASE_DIR}/util/DataModelHandler.cpp ${CHIP_APP_BASE_DIR}/util/ember-compatibility-functions.cpp + ${CHIP_APP_BASE_DIR}/util/ember-global-attribute-access-interface.cpp + ${CHIP_APP_BASE_DIR}/util/ember-io-storage.cpp ${CHIP_APP_BASE_DIR}/util/generic-callback-stubs.cpp ${CHIP_APP_BASE_DIR}/util/privilege-storage.cpp ${CHIP_APP_BASE_DIR}/util/util.cpp diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index c8a30b1b5bd36a..a68d193d241541 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -209,6 +209,8 @@ template("chip_data_model") { "${_app_root}/util/attribute-storage.cpp", "${_app_root}/util/attribute-table.cpp", "${_app_root}/util/ember-compatibility-functions.cpp", + "${_app_root}/util/ember-global-attribute-access-interface.cpp", + "${_app_root}/util/ember-io-storage.cpp", "${_app_root}/util/util.cpp", ] } diff --git a/src/app/icd/server/tests/TestICDManager.cpp b/src/app/icd/server/tests/TestICDManager.cpp index 6cba8c308b4e06..45e781685e6d31 100644 --- a/src/app/icd/server/tests/TestICDManager.cpp +++ b/src/app/icd/server/tests/TestICDManager.cpp @@ -1061,10 +1061,15 @@ TEST_F(TestICDManager, TestICDStateObserverOnTransitionToIdleModeEqualActiveMode // Expire IdleMode timer AdvanceClockAndRunEventLoop(1_s); - EXPECT_FALSE(mICDStateObserver.mOnTransitionToIdleCalled); + // In this scenario, The ICD state machine kicked a OnTransitionToIdle timer with a duration of 0 seconds. + // The freeRTOS systemlayer timer calls a 0s timer's callback instantly while on posix it take and 1 addition event loop. + // Thefore, the expect result diverges here based on the systemlayer implementation. Skip this check. + // https://github.com/project-chip/connectedhomeip/issues/33441 + // EXPECT_FALSE(mICDStateObserver.mOnTransitionToIdleCalled); // Expire OnTransitionToIdleMode AdvanceClockAndRunEventLoop(1_ms32); + // All systems should have called the OnTransitionToIdle callback by now. EXPECT_TRUE(mICDStateObserver.mOnTransitionToIdleCalled); // Reset Old durations diff --git a/src/app/tests/AppTestContext.h b/src/app/tests/AppTestContext.h index b9945fecd3a90a..a2fe3387d2d7aa 100644 --- a/src/app/tests/AppTestContext.h +++ b/src/app/tests/AppTestContext.h @@ -28,13 +28,13 @@ class AppContext : public LoopbackMessagingContext { public: // Performs shared setup for all tests in the test suite - void SetUpTestSuite() override; + static void SetUpTestSuite(); // Performs shared teardown for all tests in the test suite - void TearDownTestSuite() override; + static void TearDownTestSuite(); // Performs setup for each individual test in the test suite - void SetUp() override; + void SetUp(); // Performs teardown for each individual test in the test suite - void TearDown() override; + void TearDown(); }; } // namespace Test diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 49196f2fdaaf8f..df6737a713e6e4 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -29,7 +29,6 @@ static_library("helpers") { "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "AppTestContext.cpp", "AppTestContext.h", - "integration/RequiredPrivilegeStubs.cpp", ] cflags = [ "-Wconversion" ] diff --git a/src/app/tests/TestAclAttribute.cpp b/src/app/tests/TestAclAttribute.cpp index c1bfd8acf68a73..427b257f9efd7c 100644 --- a/src/app/tests/TestAclAttribute.cpp +++ b/src/app/tests/TestAclAttribute.cpp @@ -259,10 +259,10 @@ const nlTest sTests[] = { nlTestSuite sSuite = { "TestAclAttribute", &sTests[0], - TestAccessContext::nlTestSetUpTestSuite, - TestAccessContext::nlTestTearDownTestSuite, - TestAccessContext::nlTestSetUp, - TestAccessContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestAccessContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestAccessContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestAccessContext, SetUp), + NL_TEST_WRAP_METHOD(TestAccessContext, TearDown), }; } // namespace diff --git a/src/app/tests/TestAclEvent.cpp b/src/app/tests/TestAclEvent.cpp index f7c85e0bccaed8..8f377cc263e9bd 100644 --- a/src/app/tests/TestAclEvent.cpp +++ b/src/app/tests/TestAclEvent.cpp @@ -377,10 +377,10 @@ nlTestSuite sSuite = { "TestAclEvent", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/app/tests/TestBufferedReadCallback.cpp b/src/app/tests/TestBufferedReadCallback.cpp index 93eca2ad5044f4..6079c21b50b49f 100644 --- a/src/app/tests/TestBufferedReadCallback.cpp +++ b/src/app/tests/TestBufferedReadCallback.cpp @@ -610,10 +610,10 @@ nlTestSuite theSuite = { "TestBufferedReadCallback", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; } diff --git a/src/app/tests/TestClusterStateCache.cpp b/src/app/tests/TestClusterStateCache.cpp index a871f3dd1a434e..3247af55bccd03 100644 --- a/src/app/tests/TestClusterStateCache.cpp +++ b/src/app/tests/TestClusterStateCache.cpp @@ -695,10 +695,10 @@ nlTestSuite theSuite = { "TestClusterStateCache", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; } diff --git a/src/app/tests/TestCommandInteraction.cpp b/src/app/tests/TestCommandInteraction.cpp index 87296580fa695f..195580ce8702a5 100644 --- a/src/app/tests/TestCommandInteraction.cpp +++ b/src/app/tests/TestCommandInteraction.cpp @@ -2120,10 +2120,10 @@ nlTestSuite sSuite = { "TestCommandInteraction", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/app/tests/TestEventLogging.cpp b/src/app/tests/TestEventLogging.cpp index 41ae31bd4295ff..854247240ac701 100644 --- a/src/app/tests/TestEventLogging.cpp +++ b/src/app/tests/TestEventLogging.cpp @@ -318,10 +318,10 @@ const nlTest sTests[] = { nlTestSuite sSuite = { "EventLogging", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; } // namespace diff --git a/src/app/tests/TestEventLoggingNoUTCTime.cpp b/src/app/tests/TestEventLoggingNoUTCTime.cpp index 77a8463a5f9718..c6dc4182b79e0e 100644 --- a/src/app/tests/TestEventLoggingNoUTCTime.cpp +++ b/src/app/tests/TestEventLoggingNoUTCTime.cpp @@ -84,16 +84,16 @@ class TestContext : public chip::Test::AppContext { public: // Performs shared setup for all tests in the test suite - void SetUpTestSuite() override + static void SetUpTestSuite() { chip::Test::AppContext::SetUpTestSuite(); - mClock.Emplace(chip::System::SystemClock()); + sClock.Emplace(chip::System::SystemClock()); } // Performs shared teardown for all tests in the test suite - void TearDownTestSuite() override + static void TearDownTestSuite() { - mClock.ClearValue(); + sClock.ClearValue(); chip::Test::AppContext::TearDownTestSuite(); } @@ -125,9 +125,11 @@ class TestContext : public chip::Test::AppContext private: chip::MonotonicallyIncreasingCounter mEventCounter; - chip::Optional mClock; + static chip::Optional sClock; }; +chip::Optional TestContext::sClock; + void ENFORCE_FORMAT(1, 2) SimpleDumpWriter(const char * aFormat, ...) { va_list args; @@ -370,10 +372,10 @@ const nlTest sTests[] = { nlTestSuite sSuite = { "EventLogging", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; } // namespace diff --git a/src/app/tests/TestEventOverflow.cpp b/src/app/tests/TestEventOverflow.cpp index a328bec9783ef2..23615f401d6f75 100644 --- a/src/app/tests/TestEventOverflow.cpp +++ b/src/app/tests/TestEventOverflow.cpp @@ -169,10 +169,10 @@ nlTestSuite sSuite = { "TestEventOverflow", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/app/tests/TestFabricScopedEventLogging.cpp b/src/app/tests/TestFabricScopedEventLogging.cpp index 082aca0a2ecb7d..2ed758573aa5b6 100644 --- a/src/app/tests/TestFabricScopedEventLogging.cpp +++ b/src/app/tests/TestFabricScopedEventLogging.cpp @@ -278,10 +278,10 @@ nlTestSuite sSuite = { "TestFabricScopedEventLogging", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/app/tests/TestInteractionModelEngine.cpp b/src/app/tests/TestInteractionModelEngine.cpp index 4f0b75315355ad..3d6abd9e651d49 100644 --- a/src/app/tests/TestInteractionModelEngine.cpp +++ b/src/app/tests/TestInteractionModelEngine.cpp @@ -773,10 +773,10 @@ nlTestSuite sSuite = { "TestInteractionModelEngine", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp index 7e18cca7883c8f..e06721806732f6 100644 --- a/src/app/tests/TestReadInteraction.cpp +++ b/src/app/tests/TestReadInteraction.cpp @@ -79,13 +79,13 @@ class TestContext : public chip::Test::AppContext { public: // Performs shared setup for all tests in the test suite - void SetUpTestSuite() override + static void SetUpTestSuite() { chip::Test::AppContext::SetUpTestSuite(); gRealClock = &chip::System::SystemClock(); chip::System::Clock::Internal::SetSystemClockForTesting(&gMockClock); - if (mSyncScheduler) + if (sSyncScheduler) { gReportScheduler = chip::app::reporting::GetSynchronizedReportScheduler(); sUsingSubSync = true; @@ -96,14 +96,8 @@ class TestContext : public chip::Test::AppContext } } - static int nlTestSetUpTestSuite_Sync(void * context) - { - static_cast(context)->mSyncScheduler = true; - return nlTestSetUpTestSuite(context); - } - // Performs shared teardown for all tests in the test suite - void TearDownTestSuite() override + static void TearDownTestSuite() { chip::System::Clock::Internal::SetSystemClockForTesting(gRealClock); chip::Test::AppContext::TearDownTestSuite(); @@ -133,9 +127,21 @@ class TestContext : public chip::Test::AppContext chip::Test::AppContext::TearDown(); } -private: +protected: chip::MonotonicallyIncreasingCounter mEventCounter; - bool mSyncScheduler = false; + static bool sSyncScheduler; +}; + +bool TestContext::sSyncScheduler = false; + +class TestSyncContext : public TestContext +{ +public: + static void SetUpTestSuite() + { + sSyncScheduler = true; + TestContext::SetUpTestSuite(); + } }; class TestEventGenerator : public chip::app::EventLoggingDelegate @@ -5146,19 +5152,19 @@ const nlTest sTests[] = { nlTestSuite sSuite = { "TestReadInteraction", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; nlTestSuite sSyncSuite = { "TestSyncReadInteraction", &sTests[0], - TestContext::nlTestSetUpTestSuite_Sync, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestSyncContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestSyncContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestSyncContext, SetUp), + NL_TEST_WRAP_METHOD(TestSyncContext, TearDown), }; } // namespace diff --git a/src/app/tests/TestReportScheduler.cpp b/src/app/tests/TestReportScheduler.cpp index 5219943136847d..b7289f5f2daa90 100644 --- a/src/app/tests/TestReportScheduler.cpp +++ b/src/app/tests/TestReportScheduler.cpp @@ -821,10 +821,10 @@ static nlTest sTests[] = { nlTestSuite sSuite = { "TestReportScheduler", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; } // namespace diff --git a/src/app/tests/TestReportingEngine.cpp b/src/app/tests/TestReportingEngine.cpp index 618a2e892e789b..459c9752016e48 100644 --- a/src/app/tests/TestReportingEngine.cpp +++ b/src/app/tests/TestReportingEngine.cpp @@ -350,10 +350,10 @@ nlTestSuite sSuite = { "TestReportingEngine", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/app/tests/TestTimedHandler.cpp b/src/app/tests/TestTimedHandler.cpp index 29e8aac71d35a7..64e174a2b9e7d7 100644 --- a/src/app/tests/TestTimedHandler.cpp +++ b/src/app/tests/TestTimedHandler.cpp @@ -264,10 +264,10 @@ nlTestSuite sSuite = { "TestTimedHandler", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/app/tests/TestWriteInteraction.cpp b/src/app/tests/TestWriteInteraction.cpp index 16647ca523829e..25c9106f2986a0 100644 --- a/src/app/tests/TestWriteInteraction.cpp +++ b/src/app/tests/TestWriteInteraction.cpp @@ -1063,10 +1063,10 @@ const nlTest sTests[] = nlTestSuite sSuite = { "TestWriteInteraction", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; } // namespace diff --git a/src/app/tests/integration/BUILD.gn b/src/app/tests/integration/BUILD.gn index da0db88682f180..49aea9034e9766 100644 --- a/src/app/tests/integration/BUILD.gn +++ b/src/app/tests/integration/BUILD.gn @@ -38,7 +38,6 @@ source_set("common") { executable("chip-im-initiator") { sources = [ "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", - "RequiredPrivilegeStubs.cpp", "chip_im_initiator.cpp", ] @@ -61,7 +60,6 @@ executable("chip-im-responder") { "${chip_root}/src/app/reporting/tests/MockReportScheduler.cpp", "MockEvents.cpp", "MockEvents.h", - "RequiredPrivilegeStubs.cpp", "chip_im_responder.cpp", ] diff --git a/src/app/tests/suites/certification/Test_TC_ICDM_2_2.yaml b/src/app/tests/suites/certification/Test_TC_ICDM_2_2.yaml deleted file mode 100644 index c23b7fae1ea6c1..00000000000000 --- a/src/app/tests/suites/certification/Test_TC_ICDM_2_2.yaml +++ /dev/null @@ -1,277 +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 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. -# Auto-generated scripts for harness use only, please review before automation. The endpoints and cluster names are currently set to default - -name: 214.2.2. [TC-ICDM-2.2] Primary functionality with DUT as Server - -PICS: - - ICDM.S - - ICDM.S.C00.Rsp - - ICDM.S.C02.Rsp - -config: - nodeId: 0x12344321 - cluster: "Basic Information" - endpoint: 0 - -tests: - - label: "Precondition" - verification: | - Commission DUT to TH (can be skipped if done in a preceding test). - TH reads from the DUT the RegisteredClients attribute. - If list of registered clients is not empty, unregister existing clients - TH reads from the DUT the RegisteredClients attribute. Verify that the DUT response contains empty list of registered clients. - disabled: true - - - label: - "Step 1: TH sends RegisterClient command. - CheckInNodeID: registering - clients node ID - MonitoredSubject: monitored subject ID - Key: shared - secret between the client and the ICD used in the encryption of the - check-in message payload." - PICS: ICDM.S.C00.Rsp - verification: | - ./chip-tool icdmanagement register-client 1 1 1234567890abcdef 1 0 - On TH(chip-tool) verify that DUT responds with status code as success - - [1687534883748] [49948:406768] [DMG] Received Command Response Data, Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0001 - [1687534883748] [49948:406768] [TOO] Endpoint: 0 Cluster: 0x0000_0046 Command 0x0000_0001 - [1687534883748] [49948:406768] [TOO] RegisterClientResponse: { - [1687534883748] [49948:406768] [TOO] ICDCounter: 0 - [1687534883748] [49948:406768] [TOO] } - disabled: true - - - label: "Step 2: TH reads from the DUT the RegisteredClients attribute." - PICS: ICDM.S.A0003 - verification: | - ./chip-tool icdmanagement read registered-clients 1 0 - - On TH(Chip-tool), Verify that the DUT response contains a list of 1 registered client of given CheckInNodeID, MonitoredSubject, and Key - - [1689676816.943056][18202:18204] CHIP:DMG: } - [1689676816.943185][18202:18204] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0046 Attribute 0x0000_0003 DataVersion: 1824957093 - [1689676816.943237][18202:18204] CHIP:TOO: RegisteredClients: 2 entries - [1689676816.943266][18202:18204] CHIP:TOO: [1]: { - [1689676816.943273][18202:18204] CHIP:TOO: CheckInNodeID: 1 - [1689676816.943279][18202:18204] CHIP:TOO: MonitoredSubject: 1 - [1689676816.943288][18202:18204] CHIP:TOO: Key: 31323334353637383930616263646566 - [1689676816.943302][18202:18204] CHIP:TOO: FabricIndex: 1 - [1689676816.943309][18202:18204] CHIP:TOO: } - [1689676816.943411][18202:18204] CHIP:EM: <<< [E:57729i S:18360 M:263136445 (Ack:51562449)] (S) Msg TX to 1:0000000000000001 [BFDE] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1689676816.943425][18202:18204] CHIP:IN: (S) Sending msg 263136445 on secure session with LSID: 18360 - disabled: true - - - label: - "Step 3a: TH reads from the DUT the - ICDM.S.A0005(ClientsSupportedPerFabric) attribute." - PICS: ICDM.S.A0005 - verification: | - ./chip-tool icdmanagement read clients-supported-per-fabric 1 0 - - [1689676851.025335][18210:18212] CHIP:DMG: } - [1689676851.025406][18210:18212] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0046 Attribute 0x0000_0005 DataVersion: 1824957093 - [1689676851.025433][18210:18212] CHIP:TOO: ClientsSupportedPerFabric: 2 - [1689676851.025500][18210:18212] CHIP:EM: <<< [E:302i S:21595 M:237990169 (Ack:268232015)] (S) Msg TX to 1:0000000000000001 [BFDE] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1689676851.025513][18210:18212] CHIP:IN: (S) Sending msg 237990169 on secure session with LSID: 21595 - [1689676851.025540][18210:18212] CHIP:EM: Flushed pending ack for MessageCounter:268232015 on exchange 302i - disabled: true - - - label: - "Step 3b: If RegisteredClients is less than ClientsSupportedPerFabric, - TH repeats RegisterClient command with a different CheckInNodeID until - the number of RegisteredClients equals ClientsSupportedPerFabric." - PICS: ICDM.S.C00.Rsp - verification: | - ./chip-tool icdmanagement register-client 2 2 2234567890abcdef 1 0 - - On TH(chip-tool) verify that DUT responds with status code as success - - [1687535275413] [50580:413095] [DMG] Received Command Response Data, Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0001 - [1687535275413] [50580:413095] [TOO] Endpoint: 0 Cluster: 0x0000_0046 Command 0x0000_0001 - [1687535275413] [50580:413095] [TOO] RegisterClientResponse: { - [1687535275413] [50580:413095] [TOO] ICDCounter: 0 - [1687535275413] [50580:413095] [TOO] } - disabled: true - - - label: - "Step 3c: TH sends RegisterClient command with a different - CheckInNodeID." - PICS: ICDM.S.C00.Rsp - verification: | - ./chip-tool icdmanagement register-client 10 20 abcdef1234567890 1 0 - - On TH(chip-tool) verify that DUT responds with status as RESOURCE_EXHAUSTED(0x89) - - [1687535364276] [50673:414417] [DMG] Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0000 Status=0x89 - [1687535364276] [50673:414417] [TOO] Error: IM Error 0x00000589: General error: 0x89 (RESOURCE_EXHAUSTED) - disabled: true - - - label: "Step 4: TH reads from the DUT the RegisteredClients attribute." - PICS: ICDM.S.A0003 - verification: | - ./chip-tool icdmanagement read registered-clients 1 0 - On TH(chip-tool), Verify that the DUT response contains a list of registered clients from Step 1 and 3b; each entry contains CheckInNodeID, MonitoredSubject, and Key. - - [1689676880.681118][18221:18223] CHIP:DMG: } - [1689676880.681246][18221:18223] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0046 Attribute 0x0000_0003 DataVersion: 1824957093 - [1689676880.681292][18221:18223] CHIP:TOO: RegisteredClients: 2 entries - [1689676880.681320][18221:18223] CHIP:TOO: [1]: { - [1689676880.681334][18221:18223] CHIP:TOO: CheckInNodeID: 1 - [1689676880.681341][18221:18223] CHIP:TOO: MonitoredSubject: 1 - [1689676880.681349][18221:18223] CHIP:TOO: Key: 31323334353637383930616263646566 - [1689676880.681357][18221:18223] CHIP:TOO: FabricIndex: 1 - [1689676880.681363][18221:18223] CHIP:TOO: } - [1689676880.681373][18221:18223] CHIP:TOO: [2]: { - [1689676880.681378][18221:18223] CHIP:TOO: CheckInNodeID: 2 - [1689676880.681384][18221:18223] CHIP:TOO: MonitoredSubject: 2 - [1689676880.681390][18221:18223] CHIP:TOO: Key: 32323334353637383930616263646566 - [1689676880.681396][18221:18223] CHIP:TOO: FabricIndex: 1 - [1689676880.681401][18221:18223] CHIP:TOO: } - [1689676880.681461][18221:18223] CHIP:EM: <<< [E:58454i S:61329 M:203963160 (Ack:146764558)] (S) Msg TX to 1:0000000000000001 [BFDE] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1689676880.681475][18221:18223] CHIP:IN: (S) Sending msg 203963160 on secure session with LSID: 61329 - [1689676880.681506][18221:18223] CHIP:EM: Flushed pending ack for MessageCounter:146764558 on exchange 58454i - [1689676880.681697][18221:18221] CHIP:CTL: Shutting down the commissioner - disabled: true - - - label: - "Step 5a: TH sends UnregisterClient command with a CheckInNodeID which - is not in the list of RegisteredClients from Step 4." - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 10 1 0 - On TH(chip-tool) verify that DUT responds with status as NOT_FOUND(0x8b). - [1687535473821] [50706:415574] [DMG] Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x8b - [1687535473821] [50706:415574] [TOO] Error: IM Error 0x0000058B: General error: 0x8b (NOT_FOUND) - disabled: true - - - label: - "Step 5b: TH sends UnregisterClient command with the CheckInNodeID - from Step 1." - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 1 1 0 - On TH(chip-tool) verify that DUT responds with status as SUCCESS(0x00). - [1689764286.310839][26236:26241] CHIP:DMG: InvokeResponseMessage = - [1689764286.310843][26236:26241] CHIP:DMG: { - [1689764286.310848][26236:26241] CHIP:DMG: suppressResponse = false, - [1689764286.310852][26236:26241] CHIP:DMG: InvokeResponseIBs = - [1689764286.310859][26236:26241] CHIP:DMG: [ - [1689764286.310863][26236:26241] CHIP:DMG: InvokeResponseIB = - [1689764286.310871][26236:26241] CHIP:DMG: { - [1689764286.310875][26236:26241] CHIP:DMG: CommandStatusIB = - [1689764286.310880][26236:26241] CHIP:DMG: { - [1689764286.310885][26236:26241] CHIP:DMG: CommandPathIB = - [1689764286.310891][26236:26241] CHIP:DMG: { - [1689764286.310896][26236:26241] CHIP:DMG: EndpointId = 0x0, - [1689764286.310901][26236:26241] CHIP:DMG: ClusterId = 0x46, - [1689764286.310906][26236:26241] CHIP:DMG: CommandId = 0x2, - [1689764286.310911][26236:26241] CHIP:DMG: }, - [1689764286.310919][26236:26241] CHIP:DMG: - [1689764286.310922][26236:26241] CHIP:DMG: StatusIB = - [1689764286.310928][26236:26241] CHIP:DMG: { - [1689764286.310932][26236:26241] CHIP:DMG: status = 0x00 (SUCCESS), - [1689764286.310936][26236:26241] CHIP:DMG: }, - [1689764286.310941][26236:26241] CHIP:DMG: - [1689764286.310945][26236:26241] CHIP:DMG: }, - [1689764286.310951][26236:26241] CHIP:DMG: - [1689764286.310955][26236:26241] CHIP:DMG: }, - [1689764286.310962][26236:26241] CHIP:DMG: - [1689764286.310966][26236:26241] CHIP:DMG: ], - [1689764286.310972][26236:26241] CHIP:DMG: - [1689764286.310976][26236:26241] CHIP:DMG: InteractionModelRevision = 1 - [1689764286.310980][26236:26241] CHIP:DMG: }, - [1689764286.311002][26236:26241] CHIP:DMG: Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x0 - [1689764286.311020][26236:26241] CHIP:DMG: ICR moving to [AwaitingDe] - [1689764286.311060][26236:26241] CHIP:EM: <<< [E:63413i S:31018 M:161228496 (Ack:98843769)] (S) Msg TX to 1:0000000000000001 [D397] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1689764286.311069][26236:26241] CHIP:IN: (S) Sending msg 161228496 on secure session with LSID: 31018 - [1689764286.311093][26236:26241] CHIP:EM: Flushed pending ack for MessageCounter:98843769 on exchange 63413i - disabled: true - - - label: - "Step 5c: Repeat Step 5b with the rest of CheckInNodeIDs from the list - of RegisteredClients from Step 4, if any." - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 2 1 0 - On TH(chip-tool) verify that DUT responds with status as SUCCESS(0x00). - - [1689764308.632783][26647:26650] CHIP:DMG: ICR moving to [ResponseRe] - [1689764308.632806][26647:26650] CHIP:DMG: InvokeResponseMessage = - [1689764308.632816][26647:26650] CHIP:DMG: { - [1689764308.632826][26647:26650] CHIP:DMG: suppressResponse = false, - [1689764308.632842][26647:26650] CHIP:DMG: InvokeResponseIBs = - [1689764308.632864][26647:26650] CHIP:DMG: [ - [1689764308.632878][26647:26650] CHIP:DMG: InvokeResponseIB = - [1689764308.632899][26647:26650] CHIP:DMG: { - [1689764308.632918][26647:26650] CHIP:DMG: CommandStatusIB = - [1689764308.632941][26647:26650] CHIP:DMG: { - [1689764308.632982][26647:26650] CHIP:DMG: CommandPathIB = - [1689764308.633008][26647:26650] CHIP:DMG: { - [1689764308.633028][26647:26650] CHIP:DMG: EndpointId = 0x0, - [1689764308.633056][26647:26650] CHIP:DMG: ClusterId = 0x46, - [1689764308.633080][26647:26650] CHIP:DMG: CommandId = 0x2, - [1689764308.633103][26647:26650] CHIP:DMG: }, - [1689764308.633129][26647:26650] CHIP:DMG: - [1689764308.633151][26647:26650] CHIP:DMG: StatusIB = - [1689764308.633176][26647:26650] CHIP:DMG: { - [1689764308.633201][26647:26650] CHIP:DMG: status = 0x00 (SUCCESS), - [1689764308.633228][26647:26650] CHIP:DMG: }, - [1689764308.633252][26647:26650] CHIP:DMG: - [1689764308.633273][26647:26650] CHIP:DMG: }, - [1689764308.633295][26647:26650] CHIP:DMG: - [1689764308.633311][26647:26650] CHIP:DMG: }, - [1689764308.633331][26647:26650] CHIP:DMG: - [1689764308.633345][26647:26650] CHIP:DMG: ], - [1689764308.633363][26647:26650] CHIP:DMG: - [1689764308.633383][26647:26650] CHIP:DMG: InteractionModelRevision = 1 - [1689764308.633413][26647:26650] CHIP:DMG: }, - [1689764308.633514][26647:26650] CHIP:DMG: Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x0 - [1689764308.633536][26647:26650] CHIP:DMG: ICR moving to [AwaitingDe] - disabled: true - - - label: "Step 6: TH reads from the DUT the RegisteredClients attribute." - PICS: ICDM.S.A0003 - verification: | - ./chip-tool icdmanagement read registered-clients 1 0 - On TH(chip-tool) Verify that the DUT response contains empty list of registered clients. - - [1689764322.761414][26808:26810] CHIP:DMG: } - [1689764322.761506][26808:26810] CHIP:TOO: Endpoint: 0 Cluster: 0x0000_0046 Attribute 0x0000_0003 DataVersion: 1750818309 - [1689764322.761629][26808:26810] CHIP:TOO: RegisteredClients: 0 entries - [1689764322.761723][26808:26810] CHIP:EM: <<< [E:36669i S:5946 M:161762028 (Ack:88110606)] (S) Msg TX to 1:0000000000000001 [D397] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1689764322.761742][26808:26810] CHIP:IN: (S) Sending msg 161762028 on secure session with LSID: 5946 - [1689764322.761775][26808:26810] CHIP:EM: Flushed pending ack for MessageCounter:88110606 on exchange 36669i - disabled: true - - - label: - "Step 7: TH sends UnregisterClient command with the CheckInNodeID from - Step 1." - PICS: ICDM.S.C02.Rsp - verification: | - ./chip-tool icdmanagement unregister-client 1 1 0 - On TH(chip-tool) verify that DUT responds with status as NOT_FOUND(0x8b) - - [1689764335.957449][27077:27079] CHIP:DMG: }, - [1689764335.957456][27077:27079] CHIP:DMG: - [1689764335.957460][27077:27079] CHIP:DMG: ], - [1689764335.957468][27077:27079] CHIP:DMG: - [1689764335.957472][27077:27079] CHIP:DMG: InteractionModelRevision = 1 - [1689764335.957476][27077:27079] CHIP:DMG: }, - [1689764335.957497][27077:27079] CHIP:DMG: Received Command Response Status for Endpoint=0 Cluster=0x0000_0046 Command=0x0000_0002 Status=0x8b - [1689764335.957513][27077:27079] CHIP:TOO: Error: IM Error 0x0000058B: General error: 0x8b (NOT_FOUND) - [1689764335.957525][27077:27079] CHIP:DMG: ICR moving to [AwaitingDe] - [1689764335.957567][27077:27079] CHIP:EM: <<< [E:17566i S:36201 M:3097260 (Ack:169314533)] (S) Msg TX to 1:0000000000000001 [D397] --- Type 0000:10 (SecureChannel:StandaloneAck) - [1689764335.957578][27077:27079] CHIP:IN: (S) Sending msg 3097260 on secure session with LSID: 36201 - [1689764335.957604][27077:27079] CHIP:EM: Flushed pending ack for MessageCounter:169314533 on exchange 17566i - [1689764335.957677][27077:27077] CHIP:CTL: Shutting down the commissioner - disabled: true diff --git a/src/app/tests/suites/manualTests.json b/src/app/tests/suites/manualTests.json index d199a682492150..7472f56588b61d 100644 --- a/src/app/tests/suites/manualTests.json +++ b/src/app/tests/suites/manualTests.json @@ -120,7 +120,6 @@ "GeneralDiagnostics": ["Test_TC_DGGEN_2_2"], "Identify": ["Test_TC_I_3_2"], "IcdManagement": [ - "Test_TC_ICDM_2_2", "Test_TC_ICDM_3_1", "Test_TC_ICDM_3_2", "Test_TC_ICDM_3_3", diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index e87e0f564a5ff7..cc3185f78a5df6 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -54,118 +56,18 @@ using chip::Protocols::InteractionModel::Status; using namespace chip; using namespace chip::app; using namespace chip::Access; +using namespace chip::app::Compatibility; +using namespace chip::app::Compatibility::Internal; namespace chip { namespace app { -namespace Compatibility { -namespace { -// On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold -// uint64_t. Make kAttributeReadBufferSize at least 8 so it can fit all basic types. -constexpr size_t kAttributeReadBufferSize = (ATTRIBUTE_LARGEST >= 8 ? ATTRIBUTE_LARGEST : 8); - -// BasicType maps the type to basic int(8|16|32|64)(s|u) types. -EmberAfAttributeType BaseType(EmberAfAttributeType type) -{ - switch (type) - { - case ZCL_ACTION_ID_ATTRIBUTE_TYPE: // Action Id - case ZCL_FABRIC_IDX_ATTRIBUTE_TYPE: // Fabric Index - case ZCL_BITMAP8_ATTRIBUTE_TYPE: // 8-bit bitmap - case ZCL_ENUM8_ATTRIBUTE_TYPE: // 8-bit enumeration - case ZCL_STATUS_ATTRIBUTE_TYPE: // Status Code - case ZCL_PERCENT_ATTRIBUTE_TYPE: // Percentage - static_assert(std::is_same::value, - "chip::Percent is expected to be uint8_t, change this when necessary"); - return ZCL_INT8U_ATTRIBUTE_TYPE; - - case ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE: // Endpoint Number - case ZCL_GROUP_ID_ATTRIBUTE_TYPE: // Group Id - case ZCL_VENDOR_ID_ATTRIBUTE_TYPE: // Vendor Id - case ZCL_ENUM16_ATTRIBUTE_TYPE: // 16-bit enumeration - case ZCL_BITMAP16_ATTRIBUTE_TYPE: // 16-bit bitmap - case ZCL_PERCENT100THS_ATTRIBUTE_TYPE: // 100ths of a percent - static_assert(std::is_same::value, - "chip::EndpointId is expected to be uint16_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::GroupId is expected to be uint16_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::Percent100ths is expected to be uint16_t, change this when necessary"); - return ZCL_INT16U_ATTRIBUTE_TYPE; - - case ZCL_CLUSTER_ID_ATTRIBUTE_TYPE: // Cluster Id - case ZCL_ATTRIB_ID_ATTRIBUTE_TYPE: // Attribute Id - case ZCL_FIELD_ID_ATTRIBUTE_TYPE: // Field Id - case ZCL_EVENT_ID_ATTRIBUTE_TYPE: // Event Id - case ZCL_COMMAND_ID_ATTRIBUTE_TYPE: // Command Id - case ZCL_TRANS_ID_ATTRIBUTE_TYPE: // Transaction Id - case ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE: // Device Type Id - case ZCL_DATA_VER_ATTRIBUTE_TYPE: // Data Version - case ZCL_BITMAP32_ATTRIBUTE_TYPE: // 32-bit bitmap - case ZCL_EPOCH_S_ATTRIBUTE_TYPE: // Epoch Seconds - case ZCL_ELAPSED_S_ATTRIBUTE_TYPE: // Elapsed Seconds - static_assert(std::is_same::value, - "chip::Cluster is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::AttributeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::AttributeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::EventId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::CommandId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::TransactionId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::DeviceTypeId is expected to be uint32_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::DataVersion is expected to be uint32_t, change this when necessary"); - return ZCL_INT32U_ATTRIBUTE_TYPE; - - case ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE: // Amperage milliamps - case ZCL_ENERGY_MWH_ATTRIBUTE_TYPE: // Energy milliwatt-hours - case ZCL_POWER_MW_ATTRIBUTE_TYPE: // Power milliwatts - case ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE: // Voltage millivolts - return ZCL_INT64S_ATTRIBUTE_TYPE; - - case ZCL_EVENT_NO_ATTRIBUTE_TYPE: // Event Number - case ZCL_FABRIC_ID_ATTRIBUTE_TYPE: // Fabric Id - case ZCL_NODE_ID_ATTRIBUTE_TYPE: // Node Id - case ZCL_BITMAP64_ATTRIBUTE_TYPE: // 64-bit bitmap - case ZCL_EPOCH_US_ATTRIBUTE_TYPE: // Epoch Microseconds - case ZCL_POSIX_MS_ATTRIBUTE_TYPE: // POSIX Milliseconds - case ZCL_SYSTIME_MS_ATTRIBUTE_TYPE: // System time Milliseconds - case ZCL_SYSTIME_US_ATTRIBUTE_TYPE: // System time Microseconds - static_assert(std::is_same::value, - "chip::EventNumber is expected to be uint64_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::FabricId is expected to be uint64_t, change this when necessary"); - static_assert(std::is_same::value, - "chip::NodeId is expected to be uint64_t, change this when necessary"); - return ZCL_INT64U_ATTRIBUTE_TYPE; - - case ZCL_TEMPERATURE_ATTRIBUTE_TYPE: // Temperature - return ZCL_INT16S_ATTRIBUTE_TYPE; - - default: - return type; - } -} - -} // namespace - -} // namespace Compatibility - -using namespace chip::app::Compatibility; - namespace { -// Common buffer for ReadSingleClusterData & WriteSingleClusterData -uint8_t attributeData[kAttributeReadBufferSize]; template CHIP_ERROR attributeBufferToNumericTlvData(TLV::TLVWriter & writer, bool isNullable) { typename NumericAttributeTraits::StorageType value; - memcpy(&value, attributeData, sizeof(value)); + memcpy(&value, gEmberAttributeIOBufferSpan.data(), sizeof(value)); TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); if (isNullable && NumericAttributeTraits::IsNullValue(value)) { @@ -285,143 +187,6 @@ CHIP_ERROR SendFailureStatus(const ConcreteAttributePath & aPath, AttributeRepor return aAttributeReports.EncodeAttributeStatus(aPath, StatusIB(aStatus)); } -// This reader should never actually be registered; we do manual dispatch to it -// for the one attribute it handles. -class MandatoryGlobalAttributeReader : public AttributeAccessInterface -{ -public: - MandatoryGlobalAttributeReader(const EmberAfCluster * aCluster) : - AttributeAccessInterface(MakeOptional(kInvalidEndpointId), kInvalidClusterId), mCluster(aCluster) - {} - -protected: - const EmberAfCluster * mCluster; -}; - -class GlobalAttributeReader : public MandatoryGlobalAttributeReader -{ -public: - GlobalAttributeReader(const EmberAfCluster * aCluster) : MandatoryGlobalAttributeReader(aCluster) {} - - CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; - -private: - typedef CHIP_ERROR (CommandHandlerInterface::*CommandListEnumerator)(const ConcreteClusterPath & cluster, - CommandHandlerInterface::CommandIdCallback callback, - void * context); - static CHIP_ERROR EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, - CommandListEnumerator aEnumerator, const CommandId * aClusterCommandList); -}; - -CHIP_ERROR GlobalAttributeReader::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) -{ - using namespace Clusters::Globals::Attributes; - switch (aPath.mAttributeId) - { - case AttributeList::Id: - return aEncoder.EncodeList([this](const auto & encoder) { - const size_t count = mCluster->attributeCount; - bool addedExtraGlobals = false; - for (size_t i = 0; i < count; ++i) - { - AttributeId id = mCluster->attributes[i].attributeId; - constexpr auto lastGlobalId = GlobalAttributesNotInMetadata[ArraySize(GlobalAttributesNotInMetadata) - 1]; -#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - // The GlobalAttributesNotInMetadata shouldn't have any gaps in their ids here. - static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata) - 1, - "Ids in GlobalAttributesNotInMetadata not consecutive"); -#else - // If EventList is not supported. The GlobalAttributesNotInMetadata is missing one id here. - static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata), - "Ids in GlobalAttributesNotInMetadata not consecutive (except EventList)"); -#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - if (!addedExtraGlobals && id > lastGlobalId) - { - for (const auto & globalId : GlobalAttributesNotInMetadata) - { - ReturnErrorOnFailure(encoder.Encode(globalId)); - } - addedExtraGlobals = true; - } - ReturnErrorOnFailure(encoder.Encode(id)); - } - if (!addedExtraGlobals) - { - for (const auto & globalId : GlobalAttributesNotInMetadata) - { - ReturnErrorOnFailure(encoder.Encode(globalId)); - } - } - return CHIP_NO_ERROR; - }); -#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - case EventList::Id: - return aEncoder.EncodeList([this](const auto & encoder) { - for (size_t i = 0; i < mCluster->eventCount; ++i) - { - ReturnErrorOnFailure(encoder.Encode(mCluster->eventList[i])); - } - return CHIP_NO_ERROR; - }); -#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE - case AcceptedCommandList::Id: - return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateAcceptedCommands, - mCluster->acceptedCommandList); - case GeneratedCommandList::Id: - return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateGeneratedCommands, - mCluster->generatedCommandList); - default: - // This function is only called if attributeCluster is non-null in - // ReadSingleClusterData, which only happens for attributes listed in - // GlobalAttributesNotInMetadata. If we reach this code, someone added - // a global attribute to that list but not the above switch. - VerifyOrDieWithMsg(false, DataManagement, "Unexpected global attribute: " ChipLogFormatMEI, - ChipLogValueMEI(aPath.mAttributeId)); - return CHIP_NO_ERROR; - } -} - -CHIP_ERROR GlobalAttributeReader::EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, - GlobalAttributeReader::CommandListEnumerator aEnumerator, - const CommandId * aClusterCommandList) -{ - return aEncoder.EncodeList([&](const auto & encoder) { - auto * commandHandler = - InteractionModelEngine::GetInstance()->FindCommandHandler(aClusterPath.mEndpointId, aClusterPath.mClusterId); - if (commandHandler) - { - struct Context - { - decltype(encoder) & commandIdEncoder; - CHIP_ERROR err; - } context{ encoder, CHIP_NO_ERROR }; - CHIP_ERROR err = (commandHandler->*aEnumerator)( - aClusterPath, - [](CommandId command, void * closure) -> Loop { - auto * ctx = static_cast(closure); - ctx->err = ctx->commandIdEncoder.Encode(command); - if (ctx->err != CHIP_NO_ERROR) - { - return Loop::Break; - } - return Loop::Continue; - }, - &context); - if (err != CHIP_ERROR_NOT_IMPLEMENTED) - { - return context.err; - } - // Else fall through to the list in aClusterCommandList. - } - - for (const CommandId * cmd = aClusterCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++) - { - ReturnErrorOnFailure(encoder.Encode(*cmd)); - } - return CHIP_NO_ERROR; - }); -} - // Helper function for trying to read an attribute value via an // AttributeAccessInterface. On failure, the read has failed. On success, the // aTriedEncode outparam is set to whether the AttributeAccessInterface tried to encode a value. @@ -603,7 +368,8 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b record.endpoint = aPath.mEndpointId; record.clusterId = aPath.mClusterId; record.attributeId = aPath.mAttributeId; - Status status = emAfReadOrWriteAttribute(&record, &attributeMetadata, attributeData, sizeof(attributeData), + Status status = emAfReadOrWriteAttribute(&record, &attributeMetadata, gEmberAttributeIOBufferSpan.data(), + static_cast(gEmberAttributeIOBufferSpan.size()), /* write = */ false); if (status == Status::Success) @@ -613,7 +379,7 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b TLV::TLVWriter * writer = attributeDataIBBuilder.GetWriter(); VerifyOrReturnError(writer != nullptr, CHIP_NO_ERROR); TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); - switch (BaseType(attributeType)) + switch (AttributeBaseType(attributeType)) { case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data ReturnErrorOnFailure(writer->PutNull(tag)); @@ -719,8 +485,8 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b } case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string { - char * actualData = reinterpret_cast(attributeData + 1); - uint8_t dataLength = attributeData[0]; + char * actualData = reinterpret_cast(gEmberAttributeIOBufferSpan.data() + 1); + uint8_t dataLength = gEmberAttributeIOBufferSpan[0]; if (dataLength == 0xFF) { if (isNullable) @@ -739,9 +505,10 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b break; } case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: { - char * actualData = reinterpret_cast(attributeData + 2); // The pascal string contains 2 bytes length + char * actualData = + reinterpret_cast(gEmberAttributeIOBufferSpan.data() + 2); // The pascal string contains 2 bytes length uint16_t dataLength; - memcpy(&dataLength, attributeData, sizeof(dataLength)); + memcpy(&dataLength, gEmberAttributeIOBufferSpan.data(), sizeof(dataLength)); if (dataLength == 0xFFFF) { if (isNullable) @@ -761,8 +528,8 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b } case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string { - uint8_t * actualData = attributeData + 1; - uint8_t dataLength = attributeData[0]; + uint8_t * actualData = gEmberAttributeIOBufferSpan.data() + 1; + uint8_t dataLength = gEmberAttributeIOBufferSpan[0]; if (dataLength == 0xFF) { if (isNullable) @@ -781,9 +548,9 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b break; } case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: { - uint8_t * actualData = attributeData + 2; // The pascal string contains 2 bytes length + uint8_t * actualData = gEmberAttributeIOBufferSpan.data() + 2; // The pascal string contains 2 bytes length uint16_t dataLength; - memcpy(&dataLength, attributeData, sizeof(dataLength)); + memcpy(&dataLength, gEmberAttributeIOBufferSpan.data(), sizeof(dataLength)); if (dataLength == 0xFFFF) { if (isNullable) @@ -821,7 +588,8 @@ template CHIP_ERROR numericTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isNullable, uint16_t & dataLen) { typename NumericAttributeTraits::StorageType value; - static_assert(sizeof(value) <= sizeof(attributeData), "Value cannot fit into attribute data"); + VerifyOrDie(sizeof(value) <= gEmberAttributeIOBufferSpan.size()); + if (isNullable && aReader.GetType() == TLV::kTLVType_Null) { NumericAttributeTraits::SetNull(value); @@ -834,7 +602,7 @@ CHIP_ERROR numericTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isNull NumericAttributeTraits::WorkingToStorage(val, value); } dataLen = sizeof(value); - memcpy(attributeData, &value, sizeof(value)); + memcpy(gEmberAttributeIOBufferSpan.data(), &value, sizeof(value)); return CHIP_NO_ERROR; } @@ -847,7 +615,7 @@ CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctet { // Null is represented by an 0xFF or 0xFFFF length, respectively. len = std::numeric_limits::max(); - memcpy(&attributeData[0], &len, sizeof(len)); + memcpy(gEmberAttributeIOBufferSpan.data(), &len, sizeof(len)); dataLen = sizeof(len); } else @@ -859,10 +627,10 @@ CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctet ReturnErrorOnFailure(aReader.GetDataPtr(data)); len = static_cast(aReader.GetLength()); VerifyOrReturnError(len != std::numeric_limits::max(), CHIP_ERROR_MESSAGE_TOO_LONG); - VerifyOrReturnError(len + sizeof(len) /* length at the beginning of data */ <= sizeof(attributeData), + VerifyOrReturnError(len + sizeof(len) /* length at the beginning of data */ <= gEmberAttributeIOBufferSpan.size(), CHIP_ERROR_MESSAGE_TOO_LONG); - memcpy(&attributeData[0], &len, sizeof(len)); - memcpy(&attributeData[sizeof(len)], data, len); + memcpy(gEmberAttributeIOBufferSpan.data(), &len, sizeof(len)); + memcpy(gEmberAttributeIOBufferSpan.data() + sizeof(len), data, len); dataLen = static_cast(len + sizeof(len)); } return CHIP_NO_ERROR; @@ -870,7 +638,7 @@ CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctet CHIP_ERROR prepareWriteData(const EmberAfAttributeMetadata * attributeMetadata, TLV::TLVReader & aReader, uint16_t & dataLen) { - EmberAfAttributeType expectedType = BaseType(attributeMetadata->attributeType); + EmberAfAttributeType expectedType = AttributeBaseType(attributeMetadata->attributeType); bool isNullable = attributeMetadata->IsNullable(); switch (expectedType) { @@ -1031,8 +799,8 @@ CHIP_ERROR WriteSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, return apWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::InvalidValue); } - auto status = emAfWriteAttributeExternal(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, attributeData, - attributeMetadata->attributeType); + auto status = emAfWriteAttributeExternal(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, + gEmberAttributeIOBufferSpan.data(), attributeMetadata->attributeType); return apWriteHandler->AddStatus(aPath, status); } diff --git a/src/app/util/ember-global-attribute-access-interface.cpp b/src/app/util/ember-global-attribute-access-interface.cpp new file mode 100644 index 00000000000000..327ab09c479512 --- /dev/null +++ b/src/app/util/ember-global-attribute-access-interface.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021-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. + */ +#include + +#include +#include + +namespace chip { +namespace app { +namespace Compatibility { + +CHIP_ERROR GlobalAttributeReader::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + using namespace Clusters::Globals::Attributes; + switch (aPath.mAttributeId) + { + case AttributeList::Id: + return aEncoder.EncodeList([this](const auto & encoder) { + const size_t count = mCluster->attributeCount; + bool addedExtraGlobals = false; + for (size_t i = 0; i < count; ++i) + { + AttributeId id = mCluster->attributes[i].attributeId; + constexpr auto lastGlobalId = GlobalAttributesNotInMetadata[ArraySize(GlobalAttributesNotInMetadata) - 1]; +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + // The GlobalAttributesNotInMetadata shouldn't have any gaps in their ids here. + static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata) - 1, + "Ids in GlobalAttributesNotInMetadata not consecutive"); +#else + // If EventList is not supported. The GlobalAttributesNotInMetadata is missing one id here. + static_assert(lastGlobalId - GlobalAttributesNotInMetadata[0] == ArraySize(GlobalAttributesNotInMetadata), + "Ids in GlobalAttributesNotInMetadata not consecutive (except EventList)"); +#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + if (!addedExtraGlobals && id > lastGlobalId) + { + for (const auto & globalId : GlobalAttributesNotInMetadata) + { + ReturnErrorOnFailure(encoder.Encode(globalId)); + } + addedExtraGlobals = true; + } + ReturnErrorOnFailure(encoder.Encode(id)); + } + if (!addedExtraGlobals) + { + for (const auto & globalId : GlobalAttributesNotInMetadata) + { + ReturnErrorOnFailure(encoder.Encode(globalId)); + } + } + return CHIP_NO_ERROR; + }); +#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + case EventList::Id: + return aEncoder.EncodeList([this](const auto & encoder) { + for (size_t i = 0; i < mCluster->eventCount; ++i) + { + ReturnErrorOnFailure(encoder.Encode(mCluster->eventList[i])); + } + return CHIP_NO_ERROR; + }); +#endif // CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE + case AcceptedCommandList::Id: + return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateAcceptedCommands, + mCluster->acceptedCommandList); + case GeneratedCommandList::Id: + return EncodeCommandList(aPath, aEncoder, &CommandHandlerInterface::EnumerateGeneratedCommands, + mCluster->generatedCommandList); + default: + // This function is only called if attributeCluster is non-null in + // ReadSingleClusterData, which only happens for attributes listed in + // GlobalAttributesNotInMetadata. If we reach this code, someone added + // a global attribute to that list but not the above switch. + VerifyOrDieWithMsg(false, DataManagement, "Unexpected global attribute: " ChipLogFormatMEI, + ChipLogValueMEI(aPath.mAttributeId)); + return CHIP_NO_ERROR; + } +} + +CHIP_ERROR GlobalAttributeReader::EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, + GlobalAttributeReader::CommandListEnumerator aEnumerator, + const CommandId * aClusterCommandList) +{ + return aEncoder.EncodeList([&](const auto & encoder) { + auto * commandHandler = + InteractionModelEngine::GetInstance()->FindCommandHandler(aClusterPath.mEndpointId, aClusterPath.mClusterId); + if (commandHandler) + { + struct Context + { + decltype(encoder) & commandIdEncoder; + CHIP_ERROR err; + } context{ encoder, CHIP_NO_ERROR }; + CHIP_ERROR err = (commandHandler->*aEnumerator)( + aClusterPath, + [](CommandId command, void * closure) -> Loop { + auto * ctx = static_cast(closure); + ctx->err = ctx->commandIdEncoder.Encode(command); + if (ctx->err != CHIP_NO_ERROR) + { + return Loop::Break; + } + return Loop::Continue; + }, + &context); + if (err != CHIP_ERROR_NOT_IMPLEMENTED) + { + return context.err; + } + // Else fall through to the list in aClusterCommandList. + } + + for (const CommandId * cmd = aClusterCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++) + { + ReturnErrorOnFailure(encoder.Encode(*cmd)); + } + return CHIP_NO_ERROR; + }); +} + +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/ember-global-attribute-access-interface.h b/src/app/util/ember-global-attribute-access-interface.h new file mode 100644 index 00000000000000..d17e1b286dbd81 --- /dev/null +++ b/src/app/util/ember-global-attribute-access-interface.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021-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. + */ +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { +namespace Compatibility { + +// This reader should never actually be registered; we do manual dispatch to it +// for the one attribute it handles. +class MandatoryGlobalAttributeReader : public AttributeAccessInterface +{ +public: + MandatoryGlobalAttributeReader(const EmberAfCluster * aCluster) : + AttributeAccessInterface(MakeOptional(kInvalidEndpointId), kInvalidClusterId), mCluster(aCluster) + {} + +protected: + const EmberAfCluster * mCluster; +}; + +class GlobalAttributeReader : public MandatoryGlobalAttributeReader +{ +public: + GlobalAttributeReader(const EmberAfCluster * aCluster) : MandatoryGlobalAttributeReader(aCluster) {} + + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + +private: + typedef CHIP_ERROR (CommandHandlerInterface::*CommandListEnumerator)(const ConcreteClusterPath & cluster, + CommandHandlerInterface::CommandIdCallback callback, + void * context); + static CHIP_ERROR EncodeCommandList(const ConcreteClusterPath & aClusterPath, AttributeValueEncoder & aEncoder, + CommandListEnumerator aEnumerator, const CommandId * aClusterCommandList); +}; + +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/ember-io-storage.cpp b/src/app/util/ember-io-storage.cpp new file mode 100644 index 00000000000000..cc5eacf733480c --- /dev/null +++ b/src/app/util/ember-io-storage.cpp @@ -0,0 +1,126 @@ +/* + * 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. + */ +#include + +#include +#include + +#include + +namespace chip { +namespace app { +namespace Compatibility { +namespace Internal { + +// On some apps, ATTRIBUTE_LARGEST can as small as 3, making compiler unhappy since data[kAttributeReadBufferSize] cannot hold +// uint64_t. Make kAttributeReadBufferSize at least 8 so it can fit all basic types. +constexpr size_t kAttributeReadBufferSize = (ATTRIBUTE_LARGEST >= 8 ? ATTRIBUTE_LARGEST : 8); +uint8_t attributeIOBuffer[kAttributeReadBufferSize]; + +MutableByteSpan gEmberAttributeIOBufferSpan(attributeIOBuffer); + +EmberAfAttributeType AttributeBaseType(EmberAfAttributeType type) +{ + switch (type) + { + case ZCL_ACTION_ID_ATTRIBUTE_TYPE: // Action Id + case ZCL_FABRIC_IDX_ATTRIBUTE_TYPE: // Fabric Index + case ZCL_BITMAP8_ATTRIBUTE_TYPE: // 8-bit bitmap + case ZCL_ENUM8_ATTRIBUTE_TYPE: // 8-bit enumeration + case ZCL_STATUS_ATTRIBUTE_TYPE: // Status Code + case ZCL_PERCENT_ATTRIBUTE_TYPE: // Percentage + static_assert(std::is_same::value, + "chip::Percent is expected to be uint8_t, change this when necessary"); + return ZCL_INT8U_ATTRIBUTE_TYPE; + + case ZCL_ENDPOINT_NO_ATTRIBUTE_TYPE: // Endpoint Number + case ZCL_GROUP_ID_ATTRIBUTE_TYPE: // Group Id + case ZCL_VENDOR_ID_ATTRIBUTE_TYPE: // Vendor Id + case ZCL_ENUM16_ATTRIBUTE_TYPE: // 16-bit enumeration + case ZCL_BITMAP16_ATTRIBUTE_TYPE: // 16-bit bitmap + case ZCL_PERCENT100THS_ATTRIBUTE_TYPE: // 100ths of a percent + static_assert(std::is_same::value, + "chip::EndpointId is expected to be uint16_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::GroupId is expected to be uint16_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::Percent100ths is expected to be uint16_t, change this when necessary"); + return ZCL_INT16U_ATTRIBUTE_TYPE; + + case ZCL_CLUSTER_ID_ATTRIBUTE_TYPE: // Cluster Id + case ZCL_ATTRIB_ID_ATTRIBUTE_TYPE: // Attribute Id + case ZCL_FIELD_ID_ATTRIBUTE_TYPE: // Field Id + case ZCL_EVENT_ID_ATTRIBUTE_TYPE: // Event Id + case ZCL_COMMAND_ID_ATTRIBUTE_TYPE: // Command Id + case ZCL_TRANS_ID_ATTRIBUTE_TYPE: // Transaction Id + case ZCL_DEVTYPE_ID_ATTRIBUTE_TYPE: // Device Type Id + case ZCL_DATA_VER_ATTRIBUTE_TYPE: // Data Version + case ZCL_BITMAP32_ATTRIBUTE_TYPE: // 32-bit bitmap + case ZCL_EPOCH_S_ATTRIBUTE_TYPE: // Epoch Seconds + case ZCL_ELAPSED_S_ATTRIBUTE_TYPE: // Elapsed Seconds + static_assert(std::is_same::value, + "chip::Cluster is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::AttributeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::AttributeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::EventId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::CommandId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::TransactionId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::DeviceTypeId is expected to be uint32_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::DataVersion is expected to be uint32_t, change this when necessary"); + return ZCL_INT32U_ATTRIBUTE_TYPE; + + case ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE: // Amperage milliamps + case ZCL_ENERGY_MWH_ATTRIBUTE_TYPE: // Energy milliwatt-hours + case ZCL_POWER_MW_ATTRIBUTE_TYPE: // Power milliwatts + case ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE: // Voltage millivolts + return ZCL_INT64S_ATTRIBUTE_TYPE; + + case ZCL_EVENT_NO_ATTRIBUTE_TYPE: // Event Number + case ZCL_FABRIC_ID_ATTRIBUTE_TYPE: // Fabric Id + case ZCL_NODE_ID_ATTRIBUTE_TYPE: // Node Id + case ZCL_BITMAP64_ATTRIBUTE_TYPE: // 64-bit bitmap + case ZCL_EPOCH_US_ATTRIBUTE_TYPE: // Epoch Microseconds + case ZCL_POSIX_MS_ATTRIBUTE_TYPE: // POSIX Milliseconds + case ZCL_SYSTIME_MS_ATTRIBUTE_TYPE: // System time Milliseconds + case ZCL_SYSTIME_US_ATTRIBUTE_TYPE: // System time Microseconds + static_assert(std::is_same::value, + "chip::EventNumber is expected to be uint64_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::FabricId is expected to be uint64_t, change this when necessary"); + static_assert(std::is_same::value, + "chip::NodeId is expected to be uint64_t, change this when necessary"); + return ZCL_INT64U_ATTRIBUTE_TYPE; + + case ZCL_TEMPERATURE_ATTRIBUTE_TYPE: // Temperature + return ZCL_INT16S_ATTRIBUTE_TYPE; + + default: + return type; + } +} + +} // namespace Internal +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/ember-io-storage.h b/src/app/util/ember-io-storage.h new file mode 100644 index 00000000000000..4297bc73d0c176 --- /dev/null +++ b/src/app/util/ember-io-storage.h @@ -0,0 +1,55 @@ +/* + * 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 + +namespace chip { +namespace app { +namespace Compatibility { +namespace Internal { + +/// A buffer guaranteed to be sized sufficiently large to contain any individual value from +/// the ember attribute/data store (i.e. a buffer that can be used to read ember data into +/// or as a temporary buffer to place data before asking ember to store it). +/// +/// This buffer is intended to be used for calls to `emAfReadOrWriteAttribute` and +/// `emAfWriteAttributeExternal`: it is sufficiently sized to be able to handle any +/// max-sized data that ember is aware of. +extern MutableByteSpan gEmberAttributeIOBufferSpan; + +/// Maps an attribute type that is not an integer but can be represented as an integer to the +/// corresponding basic int(8|16|32|64)(s|u) type +/// +/// For example: +/// ZCL_ENUM8_ATTRIBUTE_TYPE maps to ZCL_INT8U_ATTRIBUTE_TYPE +/// ZCL_VENDOR_ID_ATTRIBUTE_TYPE maps to ZCL_INT16U_ATTRIBUTE_TYPE +/// ZCL_BITMAP32_ATTRIBUTE_TYPE maps to ZCL_INT32U_ATTRIBUTE_TYPE +/// ZCL_VOLTAGE_MV_ATTRIBUTE_TYPE maps to ZCL_INT64S_ATTRIBUTE_TYPE +/// ... +/// +/// If the `type` cannot be mapped to a basic type (or is already a basic type) its value +/// is returned unchanged. +EmberAfAttributeType AttributeBaseType(EmberAfAttributeType type); + +} // namespace Internal +} // namespace Compatibility +} // namespace app +} // namespace chip diff --git a/src/app/util/mock/BUILD.gn b/src/app/util/mock/BUILD.gn index da44fb84e5a809..b63c49e68f2494 100644 --- a/src/app/util/mock/BUILD.gn +++ b/src/app/util/mock/BUILD.gn @@ -25,6 +25,7 @@ source_set("mock_ember") { "MockNodeConfig.cpp", "MockNodeConfig.h", "attribute-storage.cpp", + "privilege-storage.cpp", ] public_deps = [ diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index 2293a48a8403e4..e4c965e98905f4 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -152,6 +152,50 @@ uint8_t emberAfGetClusterCountForEndpoint(EndpointId endpointId) return static_cast(endpoint->clusters.size()); } +const EmberAfAttributeMetadata * emberAfLocateAttributeMetadata(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId) +{ + auto ep = GetMockNodeConfig().endpointById(endpointId); + VerifyOrReturnValue(ep != nullptr, nullptr); + + auto cluster = ep->clusterById(clusterId); + VerifyOrReturnValue(cluster != nullptr, nullptr); + + auto attr = cluster->attributeById(attributeId); + VerifyOrReturnValue(attr != nullptr, nullptr); + + return &attr->attributeMetaData; +} + +const EmberAfCluster * emberAfFindClusterInType(const EmberAfEndpointType * endpointType, ClusterId clusterId, + EmberAfClusterMask mask, uint8_t * index) +{ + // This is a copy & paste implementation from ember attribute storage + // TODO: this hard-codes ember logic and is duplicated code. + uint8_t scopedIndex = 0; + + for (uint8_t i = 0; i < endpointType->clusterCount; i++) + { + const EmberAfCluster * cluster = &(endpointType->cluster[i]); + + if (mask == 0 || ((cluster->mask & mask) != 0)) + { + if (cluster->clusterId == clusterId) + { + if (index) + { + *index = scopedIndex; + } + + return cluster; + } + + scopedIndex++; + } + } + + return nullptr; +} + uint8_t emberAfClusterCount(chip::EndpointId endpoint, bool server) { return (server) ? emberAfGetClusterCountForEndpoint(endpoint) : 0; @@ -414,6 +458,7 @@ void SetMockNodeConfig(const MockNodeConfig & config) mockConfig = &config; } +/// Resets the mock attribute storage to the default configuration. void ResetMockNodeConfig() { mockConfig = nullptr; diff --git a/src/app/util/mock/include/zap-generated/endpoint_config.h b/src/app/util/mock/include/zap-generated/endpoint_config.h index 33d1e87e320896..620e6e29880f36 100644 --- a/src/app/util/mock/include/zap-generated/endpoint_config.h +++ b/src/app/util/mock/include/zap-generated/endpoint_config.h @@ -1,2 +1,4 @@ // Number of fixed endpoints #define FIXED_ENDPOINT_COUNT (3) + +#define ATTRIBUTE_LARGEST (1003) diff --git a/src/app/tests/integration/RequiredPrivilegeStubs.cpp b/src/app/util/mock/privilege-storage.cpp similarity index 82% rename from src/app/tests/integration/RequiredPrivilegeStubs.cpp rename to src/app/util/mock/privilege-storage.cpp index 2cc23056e5d34a..26ff8967944e12 100644 --- a/src/app/tests/integration/RequiredPrivilegeStubs.cpp +++ b/src/app/util/mock/privilege-storage.cpp @@ -1,6 +1,5 @@ -/* - * - * Copyright (c) 2022 Project CHIP Authors +/** + * 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. @@ -14,8 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include +#include -#include +// Privilege mocks here are MUCH more strict so that +// testing code can generally validatate access without something +// being permissive like kView. chip::Access::Privilege MatterGetAccessPrivilegeForReadAttribute(chip::ClusterId cluster, chip::AttributeId attribute) { diff --git a/src/ble/BtpEngine.cpp b/src/ble/BtpEngine.cpp index 59cc73222e5722..75958169a785d5 100644 --- a/src/ble/BtpEngine.cpp +++ b/src/ble/BtpEngine.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -45,8 +46,10 @@ #ifdef CHIP_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED #define ChipLogDebugBtpEngine(MOD, MSG, ...) ChipLogError(MOD, MSG, ##__VA_ARGS__) +#define ChipLogDebugBufferBtpEngine(MOD, BUF) ChipLogByteSpan(MOD, ByteSpan((BUF)->Start(), (BUF)->DataLength())) #else #define ChipLogDebugBtpEngine(MOD, MSG, ...) +#define ChipLogDebugBufferBtpEngine(MOD, BUF) #endif namespace chip { @@ -63,18 +66,6 @@ static inline bool DidReceiveData(BitFlags rx_flags) BtpEngine::HeaderFlags::kEndMessage); } -static void PrintBufDebug(const System::PacketBufferHandle & buf) -{ -#ifdef CHIP_BTP_PROTOCOL_ENGINE_DEBUG_LOGGING_ENABLED - uint8_t * b = buf->Start(); - - for (int i = 0; i < buf->DataLength(); i++) - { - ChipLogError(Ble, "\t%02x", b[i]); - } -#endif -} - const uint16_t BtpEngine::sDefaultFragmentSize = 20; // 23-byte minimum ATT_MTU - 3 bytes for ATT operation header const uint16_t BtpEngine::sMaxFragmentSize = 244; // Maximum size of BTP segment @@ -289,7 +280,7 @@ CHIP_ERROR BtpEngine::HandleCharacteristicReceived(System::PacketBufferHandle && data->ConsumeHead(static_cast(reader.OctetsRead())); ChipLogDebugBtpEngine(Ble, ">>> BTP reassembler received data:"); - PrintBufDebug(data); + ChipLogDebugBufferBtpEngine(Ble, data); } if (mRxState == kState_Idle) @@ -434,7 +425,7 @@ bool BtpEngine::HandleCharacteristicSend(System::PacketBufferHandle data, bool s mTxLength = static_cast(mTxBuf->DataLength()); ChipLogDebugBtpEngine(Ble, ">>> CHIPoBle preparing to send whole message:"); - PrintBufDebug(mTxBuf); + ChipLogDebugBufferBtpEngine(Ble, mTxBuf); // Determine fragment header size. uint8_t header_size = @@ -485,7 +476,7 @@ bool BtpEngine::HandleCharacteristicSend(System::PacketBufferHandle data, bool s characteristic[0] = headerFlags.Raw(); ChipLogDebugBtpEngine(Ble, ">>> CHIPoBle preparing to send first fragment:"); - PrintBufDebug(mTxBuf); + ChipLogDebugBufferBtpEngine(Ble, mTxBuf); } else if (mTxState == kState_InProgress) { @@ -531,7 +522,7 @@ bool BtpEngine::HandleCharacteristicSend(System::PacketBufferHandle data, bool s characteristic[0] = headerFlags.Raw(); ChipLogDebugBtpEngine(Ble, ">>> CHIPoBle preparing to send additional fragment:"); - PrintBufDebug(mTxBuf); + ChipLogDebugBufferBtpEngine(Ble, mTxBuf); } else { diff --git a/src/darwin/CHIPTool/README.md b/src/darwin/CHIPTool/README.md index d31ab129972ffb..bf74d6bb14e5c2 100644 --- a/src/darwin/CHIPTool/README.md +++ b/src/darwin/CHIPTool/README.md @@ -6,9 +6,11 @@ control. --- - [CHIP Tool iOS Sample Commissioner App](#chip-tool-ios-sample-commissioner-app) + - [Prerequisites](#prerequisites) - [Building the Application](#building-the-application) - [Compilation Fixes](#compilation-fixes) - [Installing the Application](#installing-the-application) + - [Setting up the iPhone](#setting-up-the-iphone) - [Pairing an Accessory](#pairing-an-accessory) --- @@ -80,6 +82,15 @@ run. Now you can launch the application from the Home screen or from Xcode by hitting the run button once more. +## Setting up the iPhone + +To use CHIP Tool on iOS or macOS, enable Developer Mode during the development +phase of your app by following the steps at +[Enabling Developer Mode on a device](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device). +On the iOS or macOS device that initiates the pairing, +[download](https://developer.apple.com/services-account/download?path=/iOS/iOS_Logs/EnableBluetoothCentralMatterClientDeveloperMode.mobileconfig) +the developer profile, then install it. + ## Pairing an Accessory Once you have CHIPTool up and running, to pair an accessory simply: diff --git a/src/darwin/Framework/CHIP/MTRMetricsCollector.mm b/src/darwin/Framework/CHIP/MTRMetricsCollector.mm index 3bde24fa52a527..76176c58df5c94 100644 --- a/src/darwin/Framework/CHIP/MTRMetricsCollector.mm +++ b/src/darwin/Framework/CHIP/MTRMetricsCollector.mm @@ -25,6 +25,11 @@ #include #include +/* + * Set this to MTR_LOG_DEBUG(__VA_ARGS__) to enable logging noisy debug logging for metrics events processing + */ +#define MTR_METRICS_LOG_DEBUG(...) + using MetricEvent = chip::Tracing::MetricEvent; @implementation MTRMetricData { @@ -74,7 +79,8 @@ - (instancetype)initWithMetricEvent:(const MetricEvent &)event case ValueType::kUndefined: break; } - MTR_LOG_DEBUG("Initializing metric event data %s, type: %d, with time point %llu", event.key(), _type, _timePoint.count()); + + MTR_METRICS_LOG_DEBUG("Initializing metric event data %s, type: %d, with time point %llu", event.key(), _type, _timePoint.count()); return self; } @@ -83,7 +89,7 @@ - (void)setDurationFromMetricData:(MTRMetricData *)fromData auto duration = _timePoint - fromData->_timePoint; _duration = [NSNumber numberWithDouble:double(duration.count()) / USEC_PER_SEC]; - MTR_LOG_DEBUG("Calculating duration for Matter metric with type %d, from type %d, (%llu - %llu) = %llu us (%llu s)", + MTR_METRICS_LOG_DEBUG("Calculating duration for Matter metric with type %d, from type %d, (%llu - %llu) = %llu us (%llu s)", _type, fromData->_type, _timePoint.count(), fromData->_timePoint.count(), duration.count(), [_duration unsignedLongLongValue]); } @@ -201,19 +207,19 @@ - (void)handleMetricEvent:(MetricEvent)event using ValueType = MetricEvent::Value::Type; switch (event.ValueType()) { case ValueType::kInt32: - MTR_LOG_DEBUG("Received metric event, key: %s, type: %d, value: %d", event.key(), static_cast(event.type()), event.ValueInt32()); + MTR_METRICS_LOG_DEBUG("Received metric event, key: %s, type: %d, value: %d", event.key(), static_cast(event.type()), event.ValueInt32()); break; case ValueType::kUInt32: - MTR_LOG_DEBUG("Received metric event, key: %s, type: %d, value: %u", event.key(), static_cast(event.type()), event.ValueUInt32()); + MTR_METRICS_LOG_DEBUG("Received metric event, key: %s, type: %d, value: %u", event.key(), static_cast(event.type()), event.ValueUInt32()); break; case ValueType::kChipErrorCode: - MTR_LOG_DEBUG("Received metric event, key: %s, type: %d, error value: %u", event.key(), static_cast(event.type()), event.ValueErrorCode()); + MTR_METRICS_LOG_DEBUG("Received metric event, key: %s, type: %d, error value: %u", event.key(), static_cast(event.type()), event.ValueErrorCode()); break; case ValueType::kUndefined: - MTR_LOG_DEBUG("Received metric event, key: %s, type: %d, value: nil", event.key(), static_cast(event.type())); + MTR_METRICS_LOG_DEBUG("Received metric event, key: %s, type: %d, value: nil", event.key(), static_cast(event.type())); break; default: - MTR_LOG_DEBUG("Received metric event, key: %s, type: %d, unknown value", event.key(), static_cast(event.type())); + MTR_METRICS_LOG_DEBUG("Received metric event, key: %s, type: %d, unknown value", event.key(), static_cast(event.type())); return; } diff --git a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m index 2874e6cfff509e..39f6a499312088 100644 --- a/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m +++ b/src/darwin/Framework/CHIPTests/MTROTAProviderTests.m @@ -722,11 +722,11 @@ + (void)shutdownStack */ - (NSString *)absolutePathFor:(NSString *)matterRootRelativePath { - // Find the right absolute path to our file. PWD should - // point to our src/darwin/Framework. - NSString * pwd = [[NSProcessInfo processInfo] environment][@"PWD"]; + // Start with the absolute path to our file, then remove the suffix that + // comes after the path to the Matter SDK root. + NSString * pathToTest = [NSString stringWithUTF8String:__FILE__]; NSMutableArray * pathComponents = [[NSMutableArray alloc] init]; - [pathComponents addObject:[pwd substringToIndex:(pwd.length - @"src/darwin/Framework".length)]]; + [pathComponents addObject:[pathToTest substringToIndex:(pathToTest.length - @"src/darwin/Framework/CHIPTests/MTROTAProviderTests.m".length)]]; [pathComponents addObjectsFromArray:[matterRootRelativePath pathComponents]]; return [NSString pathWithComponents:pathComponents]; } diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 4cb4edf4a8edd3..36aa65dfaa2752 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -364,6 +364,10 @@ B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */; }; BA09EB43247477BA00605257 /* libCHIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA09EB3F2474762900605257 /* libCHIP.a */; }; D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D4772A45285AE98300383630 /* MTRClusterConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E04AC67D2BEEA17F00BA409B /* ember-io-storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E04AC67B2BEEA17F00BA409B /* ember-io-storage.cpp */; }; + E04AC67E2BEEA17F00BA409B /* ember-io-storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E04AC67B2BEEA17F00BA409B /* ember-io-storage.cpp */; }; + E04AC67F2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E04AC67C2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp */; }; + E04AC6802BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E04AC67C2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -788,6 +792,8 @@ D437613F285BDC0D0051FEA2 /* MTRTestKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestKeys.h; sourceTree = ""; }; D4376140285BDC0D0051FEA2 /* MTRTestStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestStorage.h; sourceTree = ""; }; D4772A45285AE98300383630 /* MTRClusterConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRClusterConstants.h; sourceTree = ""; }; + E04AC67B2BEEA17F00BA409B /* ember-io-storage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "ember-io-storage.cpp"; path = "util/ember-io-storage.cpp"; sourceTree = ""; }; + E04AC67C2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "ember-global-attribute-access-interface.cpp"; path = "util/ember-global-attribute-access-interface.cpp"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1033,6 +1039,8 @@ 514C79F22B62ED5500DD6D7B /* attribute-storage.cpp */, 514C79EF2B62ADDA00DD6D7B /* descriptor.cpp */, 514C79EC2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp */, + E04AC67C2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp */, + E04AC67B2BEEA17F00BA409B /* ember-io-storage.cpp */, 516415FE2B6B132200D5CE11 /* DataModelHandler.cpp */, 514C79F52B62F0B900DD6D7B /* util.cpp */, 514C79FB2B62F94C00DD6D7B /* ota-provider.cpp */, @@ -1845,6 +1853,7 @@ B45373FE2A9FEC4F00807602 /* unix-fds.c in Sources */, B45374002A9FEC4F00807602 /* unix-init.c in Sources */, B45373FF2A9FEC4F00807602 /* unix-misc.c in Sources */, + E04AC67E2BEEA17F00BA409B /* ember-io-storage.cpp in Sources */, B45373FD2A9FEC4F00807602 /* unix-pipe.c in Sources */, B45373FB2A9FEC4F00807602 /* unix-service.c in Sources */, B45374012A9FEC4F00807602 /* unix-sockets.c in Sources */, @@ -1891,6 +1900,7 @@ 037C3DB62991BD5000B7EEE2 /* ModelCommandBridge.mm in Sources */, 516411322B6BF75700E67C05 /* MTRIMDispatch.mm in Sources */, 037C3DB42991BD5000B7EEE2 /* DeviceControllerDelegateBridge.mm in Sources */, + E04AC6802BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp in Sources */, 039547012992D461006D42A8 /* generic-callback-stubs.cpp in Sources */, 514C79F12B62ADDA00DD6D7B /* descriptor.cpp in Sources */, ); @@ -1926,6 +1936,7 @@ 7534F12828BFF20300390851 /* MTRDeviceAttestationDelegate.mm in Sources */, B4C8E6B72B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm in Sources */, 2C5EEEF7268A85C400CAE3D3 /* MTRDeviceConnectionBridge.mm in Sources */, + E04AC67D2BEEA17F00BA409B /* ember-io-storage.cpp in Sources */, 51B22C262740CB32008D5055 /* MTRStructsObjc.mm in Sources */, 2C222AD1255C620600E446B9 /* MTRBaseDevice.mm in Sources */, 1EC3238D271999E2002A8BF0 /* cluster-objects.cpp in Sources */, @@ -1967,6 +1978,7 @@ 5178E67E2AE098210069DF72 /* MTRCommandTimedCheck.mm in Sources */, 7596A84928762783004DAE0E /* MTRAsyncCallbackWorkQueue.mm in Sources */, B2E0D7B9245B0B5C003C5B48 /* MTRSetupPayload.mm in Sources */, + E04AC67F2BEEA17F00BA409B /* ember-global-attribute-access-interface.cpp in Sources */, B2E0D7B6245B0B5C003C5B48 /* MTRManualSetupPayloadParser.mm in Sources */, 7596A85528788557004DAE0E /* MTRClusters.mm in Sources */, 88EBF8CF27FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.mm in Sources */, diff --git a/src/inet/tests/TestInetCommonOptions.cpp b/src/inet/tests/TestInetCommonOptions.cpp index fdc79b11954a27..7e4648d7466b1b 100644 --- a/src/inet/tests/TestInetCommonOptions.cpp +++ b/src/inet/tests/TestInetCommonOptions.cpp @@ -51,7 +51,7 @@ NetworkOptions::NetworkOptions() static OptionDef optionDefs[] = { { "local-addr", kArgumentRequired, 'a' }, { "node-addr", kArgumentRequired, kToolCommonOpt_NodeAddr }, /* alias for local-addr */ -#if CHIP_SYSTEM_CONFIG_USE_LWIP +#if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT { "tap-device", kArgumentRequired, kToolCommonOpt_TapDevice }, { "ipv4-gateway", kArgumentRequired, kToolCommonOpt_IPv4GatewayAddr }, { "ipv6-gateway", kArgumentRequired, kToolCommonOpt_IPv6GatewayAddr }, @@ -59,7 +59,7 @@ NetworkOptions::NetworkOptions() { "debug-lwip", kNoArgument, kToolCommonOpt_DebugLwIP }, { "event-delay", kArgumentRequired, kToolCommonOpt_EventDelay }, { "tap-system-config", kNoArgument, kToolCommonOpt_TapInterfaceConfig }, -#endif +#endif // CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT {} }; OptionDefs = optionDefs; @@ -69,7 +69,7 @@ NetworkOptions::NetworkOptions() OptionHelp = " -a, --local-addr, --node-addr \n" " Local address for the node.\n" "\n" -#if CHIP_SYSTEM_CONFIG_USE_LWIP +#if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT " --tap-device \n" " TAP device name for LwIP hosted OS usage. Defaults to chip-dev-.\n" "\n" @@ -91,14 +91,14 @@ NetworkOptions::NetworkOptions() " --tap-system-config\n" " Use configuration on each of the Linux TAP interfaces to configure LwIP's interfaces.\n" "\n" -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP +#endif // CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT ; // Defaults. LocalIPv4Addr.clear(); LocalIPv6Addr.clear(); -#if CHIP_SYSTEM_CONFIG_USE_LWIP +#if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT TapDeviceName.clear(); LwIPDebugFlags = 0; EventDelay = 0; @@ -106,7 +106,7 @@ NetworkOptions::NetworkOptions() IPv6GatewayAddr.clear(); DNSServerAddr = Inet::IPAddress::Any; TapUseSystemConfig = false; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP +#endif // CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT } bool NetworkOptions::HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) diff --git a/src/inet/tests/TestInetCommonOptions.h b/src/inet/tests/TestInetCommonOptions.h index 9b3cd2e59b4f3b..a67536dda1b266 100644 --- a/src/inet/tests/TestInetCommonOptions.h +++ b/src/inet/tests/TestInetCommonOptions.h @@ -59,7 +59,7 @@ class NetworkOptions : public chip::ArgParser::OptionSetBase std::vector LocalIPv4Addr; std::vector LocalIPv6Addr; -#if CHIP_SYSTEM_CONFIG_USE_LWIP +#if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT std::vector IPv4GatewayAddr; std::vector IPv6GatewayAddr; chip::Inet::IPAddress DNSServerAddr; @@ -67,7 +67,7 @@ class NetworkOptions : public chip::ArgParser::OptionSetBase uint8_t LwIPDebugFlags; uint32_t EventDelay; bool TapUseSystemConfig; -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP +#endif // CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT NetworkOptions(); diff --git a/src/inet/tests/TestInetCommonPosix.cpp b/src/inet/tests/TestInetCommonPosix.cpp index 9d5e4588979d1a..6fc53a15fbb980 100644 --- a/src/inet/tests/TestInetCommonPosix.cpp +++ b/src/inet/tests/TestInetCommonPosix.cpp @@ -337,7 +337,7 @@ void InitNetwork() #if INET_CONFIG_ENABLE_TCP_ENDPOINT gTCP.Init(gSystemLayer); #endif -#if INET_CONFIG_ENABLE_TCP_ENDPOINT +#if INET_CONFIG_ENABLE_UDP_ENDPOINT gUDP.Init(gSystemLayer); #endif } @@ -368,14 +368,14 @@ void ServiceEvents(uint32_t aSleepTimeMilliseconds) gSystemLayer.HandleEvents(); #endif -#if CHIP_SYSTEM_CONFIG_USE_LWIP +#if CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT if (gSystemLayer.IsInitialized()) { static uint32_t sRemainingSystemLayerEventDelay = 0; if (sRemainingSystemLayerEventDelay == 0) { -#if CHIP_DEVICE_LAYER_TARGET_OPEN_IOT_SDK +#if CHIP_DEVICE_LAYER_TARGET_OPEN_IOT_SDK || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT // We need to terminate event loop after performance single step. // Event loop processing work items until StopEventLoopTask is called. // Scheduling StopEventLoop task guarantees correct operation of the loop. @@ -390,7 +390,7 @@ void ServiceEvents(uint32_t aSleepTimeMilliseconds) gSystemLayer.HandlePlatformTimer(); } -#endif // CHIP_SYSTEM_CONFIG_USE_LWIP +#endif // CHIP_SYSTEM_CONFIG_USE_LWIP || CHIP_SYSTEM_CONFIG_USE_OPEN_THREAD_ENDPOINT } #if CHIP_SYSTEM_CONFIG_USE_LWIP && !(CHIP_SYSTEM_CONFIG_LWIP_SKIP_INIT) diff --git a/src/lib/core/tests/BUILD.gn b/src/lib/core/tests/BUILD.gn index 30d50ee9493699..001078019a7e83 100644 --- a/src/lib/core/tests/BUILD.gn +++ b/src/lib/core/tests/BUILD.gn @@ -30,9 +30,14 @@ chip_test_suite("tests") { "TestOptional.cpp", "TestReferenceCounted.cpp", "TestTLV.cpp", - "TestTLVVectorWriter.cpp", ] + # requires large amount of heap for multiple unfragmented 10k buffers + # skip for efr32 to allow flash space for other tests + if (chip_device_platform != "efr32") { + test_sources += [ "TestTLVVectorWriter.cpp" ] + } + cflags = [ "-Wconversion" ] public_deps = [ diff --git a/src/lib/support/UnitTestRegistration.h b/src/lib/support/UnitTestRegistration.h index 791a62be855a36..be7cc710a9278d 100644 --- a/src/lib/support/UnitTestRegistration.h +++ b/src/lib/support/UnitTestRegistration.h @@ -48,6 +48,20 @@ VerifyOrDie(chip::RegisterUnitTests(&FUNCTION) == CHIP_NO_ERROR); \ } +// TODO: remove these once transition to pw_unit_test is completed +#define NL_TEST_WRAP_FUNCTION(FUNCTION) \ + [](void * _context) -> int { \ + FUNCTION(); \ + return SUCCESS; \ + } + +#define NL_TEST_WRAP_METHOD(CLASS, METHOD) \ + [](void * context) -> int { \ + auto ctx = static_cast(context); \ + ctx->METHOD(); \ + return SUCCESS; \ + } + namespace chip { typedef int (*UnitTestTriggerFunction)(); diff --git a/src/lib/support/UnitTestUtils.cpp b/src/lib/support/UnitTestUtils.cpp index 84976405a20940..0d3acae6487106 100644 --- a/src/lib/support/UnitTestUtils.cpp +++ b/src/lib/support/UnitTestUtils.cpp @@ -51,7 +51,7 @@ uint64_t TimeMonotonicMillis() void SleepMillis(uint64_t millisecs) { - uint32_t ticks = static_cast(millisecs / portTICK_PERIOD_MS); + uint32_t ticks = pdMS_TO_TICKS(millisecs); vTaskDelay(ticks == 0 ? 1 : ticks); // delay at least 1 tick } diff --git a/src/messaging/ReliableMessageMgr.cpp b/src/messaging/ReliableMessageMgr.cpp index 4bc196c45510ea..c9178a03d1822d 100644 --- a/src/messaging/ReliableMessageMgr.cpp +++ b/src/messaging/ReliableMessageMgr.cpp @@ -199,7 +199,7 @@ void ReliableMessageMgr::Timeout(System::Layer * aSystemLayer, void * aAppState) CHIP_ERROR ReliableMessageMgr::AddToRetransTable(ReliableMessageContext * rc, RetransTableEntry ** rEntry) { - VerifyOrDie(!rc->IsWaitingForAck()); + VerifyOrReturnError(!rc->IsWaitingForAck(), CHIP_ERROR_INCORRECT_STATE); *rEntry = mRetransTable.CreateObject(rc); if (*rEntry == nullptr) diff --git a/src/messaging/tests/BUILD.gn b/src/messaging/tests/BUILD.gn index a78dcce953756b..89f0934486b9c1 100644 --- a/src/messaging/tests/BUILD.gn +++ b/src/messaging/tests/BUILD.gn @@ -49,27 +49,20 @@ static_library("helpers") { chip_test_suite_using_nltest("tests") { output_name = "libMessagingLayerTests" - test_sources = [] - - if (chip_device_platform != "efr32") { - # TODO(#10447): ReliableMessage Test has HF, and ExchangeMgr hangs on EFR32. - # And TestAbortExchangesForFabric does not link on EFR32 for some reason. - # TODO #33372: TestExchange.cpp asserts in ExchangeContext::SendMessage - test_sources += [ - "TestAbortExchangesForFabric.cpp", - "TestExchange.cpp", - "TestExchangeMgr.cpp", - "TestReliableMessageProtocol.cpp", - ] + test_sources = [ + "TestAbortExchangesForFabric.cpp", + "TestExchange.cpp", + "TestExchangeMgr.cpp", + "TestReliableMessageProtocol.cpp", + ] - if (chip_device_platform != "esp32" && chip_device_platform != "mbed" && - chip_device_platform != "nrfconnect" && chip_device_platform != "nxp") { - test_sources += [ "TestExchangeHolder.cpp" ] - } + if (chip_device_platform != "esp32" && chip_device_platform != "mbed" && + chip_device_platform != "nrfconnect" && chip_device_platform != "nxp") { + test_sources += [ "TestExchangeHolder.cpp" ] + } - if (chip_device_platform == "linux") { - test_sources += [ "TestMessagingLayer.cpp" ] - } + if (chip_device_platform == "linux") { + test_sources += [ "TestMessagingLayer.cpp" ] } cflags = [ "-Wconversion" ] diff --git a/src/messaging/tests/MessagingContext.cpp b/src/messaging/tests/MessagingContext.cpp index 72ab1650da552c..61565da1cd4ac0 100644 --- a/src/messaging/tests/MessagingContext.cpp +++ b/src/messaging/tests/MessagingContext.cpp @@ -58,7 +58,7 @@ CHIP_ERROR MessagingContext::Init(TransportMgrBase * transport, IOContext * ioCo ReturnErrorOnFailure(mExchangeManager.Init(&mSessionManager)); ReturnErrorOnFailure(mMessageCounterManager.Init(&mExchangeManager)); - if (mInitializeNodes) + if (sInitializeNodes) { ReturnErrorOnFailure(CreateAliceFabric()); ReturnErrorOnFailure(CreateBobFabric()); @@ -112,6 +112,8 @@ using namespace System::Clock::Literals; constexpr chip::System::Clock::Timeout MessagingContext::kResponsiveIdleRetransTimeout; constexpr chip::System::Clock::Timeout MessagingContext::kResponsiveActiveRetransTimeout; +bool MessagingContext::sInitializeNodes = true; + void MessagingContext::SetMRPMode(MRPMode mode) { if (mode == MRPMode::kDefault) @@ -302,6 +304,10 @@ Messaging::ExchangeContext * MessagingContext::NewExchangeToBob(Messaging::Excha return mExchangeManager.NewContext(GetSessionAliceToBob(), delegate, isInitiator); } +LoopbackTransportManager LoopbackMessagingContext::sLoopbackTransportManager; + +UDPTransportManager UDPMessagingContext::sUDPTransportManager; + void MessageCapturer::OnMessageReceived(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, const SessionHandle & session, DuplicateMessage isDuplicate, System::PacketBufferHandle && msgBuf) diff --git a/src/messaging/tests/MessagingContext.h b/src/messaging/tests/MessagingContext.h index 9ed82de60e4d5d..ab9912924e713c 100644 --- a/src/messaging/tests/MessagingContext.h +++ b/src/messaging/tests/MessagingContext.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -100,7 +101,7 @@ class MessagingContext : public PlatformMemoryUser ~MessagingContext() { VerifyOrDie(mInitialized == false); } // Whether Alice and Bob are initialized, must be called before Init - void ConfigInitializeNodes(bool initializeNodes) { mInitializeNodes = initializeNodes; } + static void ConfigInitializeNodes(bool initializeNodes) { sInitializeNodes = initializeNodes; } /// Initialize the underlying layers and test suite pointer CHIP_ERROR Init(TransportMgrBase * transport, IOContext * io); @@ -177,7 +178,7 @@ class MessagingContext : public PlatformMemoryUser System::Layer & GetSystemLayer() { return mIOContext->GetSystemLayer(); } private: - bool mInitializeNodes = true; + static bool sInitializeNodes; bool mInitialized; FabricTable mFabricTable; @@ -206,26 +207,38 @@ class MessagingContext : public PlatformMemoryUser }; // LoopbackMessagingContext enriches MessagingContext with an async loopback transport -class LoopbackMessagingContext : public LoopbackTransportManager, public MessagingContext +class LoopbackMessagingContext : public MessagingContext { public: virtual ~LoopbackMessagingContext() {} + // These functions wrap sLoopbackTransportManager methods + static auto & GetSystemLayer() { return sLoopbackTransportManager.GetSystemLayer(); } + static auto & GetLoopback() { return sLoopbackTransportManager.GetLoopback(); } + static auto & GetTransportMgr() { return sLoopbackTransportManager.GetTransportMgr(); } + static auto & GetIOContext() { return sLoopbackTransportManager.GetIOContext(); } + + template + static void DrainAndServiceIO(Ts... args) + { + return sLoopbackTransportManager.DrainAndServiceIO(args...); + } + // Performs shared setup for all tests in the test suite - virtual void SetUpTestSuite() + static void SetUpTestSuite() { CHIP_ERROR err = CHIP_NO_ERROR; // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete VerifyOrDieWithMsg((err = chip::Platform::MemoryInit()) == CHIP_NO_ERROR, AppServer, "Init CHIP memory failed: %" CHIP_ERROR_FORMAT, err.Format()); - VerifyOrDieWithMsg((err = LoopbackTransportManager::Init()) == CHIP_NO_ERROR, AppServer, + VerifyOrDieWithMsg((err = sLoopbackTransportManager.Init()) == CHIP_NO_ERROR, AppServer, "Init LoopbackTransportManager failed: %" CHIP_ERROR_FORMAT, err.Format()); } // Performs shared teardown for all tests in the test suite - virtual void TearDownTestSuite() + static void TearDownTestSuite() { - LoopbackTransportManager::Shutdown(); + sLoopbackTransportManager.Shutdown(); chip::Platform::MemoryShutdown(); } @@ -240,100 +253,48 @@ class LoopbackMessagingContext : public LoopbackTransportManager, public Messagi // Performs teardown for each individual test in the test suite virtual void TearDown() { MessagingContext::Shutdown(); } - // Helpers that can be used directly by the nlTestSuite - - static int nlTestSetUpTestSuite(void * context) - { - static_cast(context)->SetUpTestSuite(); - return SUCCESS; - } - - static int nlTestTearDownTestSuite(void * context) - { - static_cast(context)->TearDownTestSuite(); - return SUCCESS; - } - - static int nlTestSetUp(void * context) - { - static_cast(context)->SetUp(); - return SUCCESS; - } - - static int nlTestTearDown(void * context) - { - static_cast(context)->TearDown(); - return SUCCESS; - } - - using LoopbackTransportManager::GetSystemLayer; + static LoopbackTransportManager sLoopbackTransportManager; }; // UDPMessagingContext enriches MessagingContext with an UDP transport -class UDPMessagingContext : public UDPTransportManager, public MessagingContext +class UDPMessagingContext : public MessagingContext { public: virtual ~UDPMessagingContext() {} + static auto & GetSystemLayer() { return sUDPTransportManager.GetSystemLayer(); } + static auto & GetTransportMgr() { return sUDPTransportManager.GetTransportMgr(); } + static auto & GetIOContext() { return sUDPTransportManager.GetIOContext(); } + // Performs shared setup for all tests in the test suite - virtual CHIP_ERROR SetUpTestSuite() + static void SetUpTestSuite() { CHIP_ERROR err = CHIP_NO_ERROR; - VerifyOrExit((err = chip::Platform::MemoryInit()) == CHIP_NO_ERROR, - ChipLogError(AppServer, "Init CHIP memory failed: %" CHIP_ERROR_FORMAT, err.Format())); - VerifyOrExit((err = UDPTransportManager::Init()) == CHIP_NO_ERROR, - ChipLogError(AppServer, "Init UDPTransportManager failed: %" CHIP_ERROR_FORMAT, err.Format())); - exit: - return err; + VerifyOrDieWithMsg((err = chip::Platform::MemoryInit()) == CHIP_NO_ERROR, AppServer, + "Init CHIP memory failed: %" CHIP_ERROR_FORMAT, err.Format()); + VerifyOrDieWithMsg((err = sUDPTransportManager.Init()) == CHIP_NO_ERROR, AppServer, + "Init UDPTransportManager failed: %" CHIP_ERROR_FORMAT, err.Format()); } // Performs shared teardown for all tests in the test suite - virtual void TearDownTestSuite() + static void TearDownTestSuite() { - UDPTransportManager::Shutdown(); + sUDPTransportManager.Shutdown(); chip::Platform::MemoryShutdown(); } // Performs setup for each individual test in the test suite - virtual CHIP_ERROR SetUp() + virtual void SetUp() { CHIP_ERROR err = CHIP_NO_ERROR; - VerifyOrExit((err = MessagingContext::Init(&GetTransportMgr(), &GetIOContext())) == CHIP_NO_ERROR, - ChipLogError(AppServer, "Init MessagingContext failed: %" CHIP_ERROR_FORMAT, err.Format())); - exit: - return err; + VerifyOrDieWithMsg((err = MessagingContext::Init(&GetTransportMgr(), &GetIOContext())) == CHIP_NO_ERROR, AppServer, + "Init MessagingContext failed: %" CHIP_ERROR_FORMAT, err.Format()); } // Performs teardown for each individual test in the test suite virtual void TearDown() { MessagingContext::Shutdown(); } - // Helpers that can be used directly by the nlTestSuite - - static int nlTestSetUpTestSuite(void * context) - { - auto err = static_cast(context)->SetUpTestSuite(); - return err == CHIP_NO_ERROR ? SUCCESS : FAILURE; - } - - static int nlTestTearDownTestSuite(void * context) - { - static_cast(context)->TearDownTestSuite(); - return SUCCESS; - } - - static int nlTestSetUp(void * context) - { - auto err = static_cast(context)->SetUp(); - return err == CHIP_NO_ERROR ? SUCCESS : FAILURE; - } - - static int nlTestTearDown(void * context) - { - static_cast(context)->TearDown(); - return SUCCESS; - } - - using UDPTransportManager::GetSystemLayer; + static UDPTransportManager sUDPTransportManager; }; // Class that can be used to capture decrypted message traffic in tests using diff --git a/src/messaging/tests/TestAbortExchangesForFabric.cpp b/src/messaging/tests/TestAbortExchangesForFabric.cpp index 1540b91725a3aa..d5c945c848a9aa 100644 --- a/src/messaging/tests/TestAbortExchangesForFabric.cpp +++ b/src/messaging/tests/TestAbortExchangesForFabric.cpp @@ -273,10 +273,10 @@ const nlTest sTests[] = { nlTestSuite sSuite = { "Test-AbortExchangesForFabric", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/messaging/tests/TestExchange.cpp b/src/messaging/tests/TestExchange.cpp index 67b9ab6d16d0d3..fbc1c9e505204d 100644 --- a/src/messaging/tests/TestExchange.cpp +++ b/src/messaging/tests/TestExchange.cpp @@ -241,10 +241,10 @@ nlTestSuite sSuite = { "Test-Exchange", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/messaging/tests/TestExchangeHolder.cpp b/src/messaging/tests/TestExchangeHolder.cpp index 9325350d262e1b..849c1856d9b013 100644 --- a/src/messaging/tests/TestExchangeHolder.cpp +++ b/src/messaging/tests/TestExchangeHolder.cpp @@ -821,10 +821,10 @@ nlTestSuite sSuite = { "Test-TestExchangeHolder", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/messaging/tests/TestExchangeMgr.cpp b/src/messaging/tests/TestExchangeMgr.cpp index cc4dbf10e5a4f4..efca83e6d1a92a 100644 --- a/src/messaging/tests/TestExchangeMgr.cpp +++ b/src/messaging/tests/TestExchangeMgr.cpp @@ -301,10 +301,10 @@ nlTestSuite sSuite = { "Test-CHIP-ExchangeManager", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/messaging/tests/TestMessagingLayer.cpp b/src/messaging/tests/TestMessagingLayer.cpp index 2c05c067989e42..00dca89f2b6639 100644 --- a/src/messaging/tests/TestMessagingLayer.cpp +++ b/src/messaging/tests/TestMessagingLayer.cpp @@ -162,10 +162,10 @@ nlTestSuite sSuite = { "Test-CHIP-MessagingLayer", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/messaging/tests/TestReliableMessageProtocol.cpp b/src/messaging/tests/TestReliableMessageProtocol.cpp index 057aa599726df3..68342838a735b1 100644 --- a/src/messaging/tests/TestReliableMessageProtocol.cpp +++ b/src/messaging/tests/TestReliableMessageProtocol.cpp @@ -2229,10 +2229,10 @@ const nlTest sTests[] = { nlTestSuite sSuite = { "Test-CHIP-ReliableMessageProtocol", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp index c28cb86edf0085..12f9af57a44342 100644 --- a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp +++ b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp @@ -227,18 +227,22 @@ void GenericThreadStackManagerImpl_OpenThread::_OnPlatformEvent(const ThreadDiagnosticsDelegate * delegate = GetDiagnosticDataProvider().GetThreadDiagnosticsDelegate(); - if (mIsAttached) + if (delegate) { - delegate->OnConnectionStatusChanged(app::Clusters::ThreadNetworkDiagnostics::ConnectionStatusEnum::kConnected); - } - else - { - delegate->OnConnectionStatusChanged(app::Clusters::ThreadNetworkDiagnostics::ConnectionStatusEnum::kNotConnected); + if (mIsAttached) + { + delegate->OnConnectionStatusChanged(app::Clusters::ThreadNetworkDiagnostics::ConnectionStatusEnum::kConnected); + } + else + { + delegate->OnConnectionStatusChanged( + app::Clusters::ThreadNetworkDiagnostics::ConnectionStatusEnum::kNotConnected); - GeneralFaults current; - current.add(to_underlying(chip::app::Clusters::ThreadNetworkDiagnostics::NetworkFaultEnum::kLinkDown)); - delegate->OnNetworkFaultChanged(mNetworkFaults, current); - mNetworkFaults = current; + GeneralFaults current; + current.add(to_underlying(chip::app::Clusters::ThreadNetworkDiagnostics::NetworkFaultEnum::kLinkDown)); + delegate->OnNetworkFaultChanged(mNetworkFaults, current); + mNetworkFaults = current; + } } } diff --git a/src/platform/silabs/CHIPDevicePlatformConfig.h b/src/platform/silabs/CHIPDevicePlatformConfig.h index e8601e5feedaf9..feabf306ab0772 100644 --- a/src/platform/silabs/CHIPDevicePlatformConfig.h +++ b/src/platform/silabs/CHIPDevicePlatformConfig.h @@ -96,7 +96,9 @@ #endif /* CHIP_ENABLE_OPENTHREAD */ #endif /* defined(SL_WIFI) */ +#ifndef CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE #define CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE 1 +#endif #if defined(SL_WIFI) diff --git a/src/protocols/secure_channel/tests/TestCASESession.cpp b/src/protocols/secure_channel/tests/TestCASESession.cpp index 1ef821f30c8b50..5fc6a37f72f4ab 100644 --- a/src/protocols/secure_channel/tests/TestCASESession.cpp +++ b/src/protocols/secure_channel/tests/TestCASESession.cpp @@ -61,9 +61,9 @@ class TestContext : public Test::LoopbackMessagingContext { public: // Performs shared setup for all tests in the test suite - void SetUpTestSuite() override; + static void SetUpTestSuite(); // Performs shared teardown for all tests in the test suite - void TearDownTestSuite() override; + static void TearDownTestSuite(); }; void ServiceEvents(TestContext & ctx) @@ -1240,10 +1240,10 @@ static nlTestSuite sSuite = { "Test-CHIP-SecurePairing-CASE", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/protocols/secure_channel/tests/TestPASESession.cpp b/src/protocols/secure_channel/tests/TestPASESession.cpp index 368811a273ff66..d610b304cec481 100644 --- a/src/protocols/secure_channel/tests/TestPASESession.cpp +++ b/src/protocols/secure_channel/tests/TestPASESession.cpp @@ -89,7 +89,7 @@ class TestContext : public chip::Test::LoopbackMessagingContext { public: // Performs shared setup for all tests in the test suite - void SetUpTestSuite() override + static void SetUpTestSuite() { ConfigInitializeNodes(false); chip::Test::LoopbackMessagingContext::SetUpTestSuite(); @@ -541,10 +541,10 @@ static nlTestSuite sSuite = { "Test-CHIP-SecurePairing-PASE", &sTests[0], - TestContext::nlTestSetUpTestSuite, - TestContext::nlTestTearDownTestSuite, - TestContext::nlTestSetUp, - TestContext::nlTestTearDown, + NL_TEST_WRAP_FUNCTION(TestContext::SetUpTestSuite), + NL_TEST_WRAP_FUNCTION(TestContext::TearDownTestSuite), + NL_TEST_WRAP_METHOD(TestContext, SetUp), + NL_TEST_WRAP_METHOD(TestContext, TearDown), }; // clang-format on diff --git a/src/system/tests/TestSystemTimer.cpp b/src/system/tests/TestSystemTimer.cpp index 6c065b96a01f9a..6271836238b55c 100644 --- a/src/system/tests/TestSystemTimer.cpp +++ b/src/system/tests/TestSystemTimer.cpp @@ -70,7 +70,7 @@ class LayerEvents class LayerEvents::value>::type> @@ -87,7 +87,7 @@ class LayerEvents #include #include +#include #include +#include #include #include +#include #include #include #include @@ -37,6 +40,8 @@ #include #include +#include "SilabsDeviceDataProvider.h" + extern "C" int printf(const char * format, ...) { va_list args; @@ -56,8 +61,11 @@ class NlTest : public pw_rpc::nanopb::NlTest::Service stream_writer = &writer; nlTestSetLogger(&nl_test_logger); - RunRegisteredUnitTests(); - + printf("--- Running nltest ---"); + int status = RunRegisteredUnitTests(); + printf("--- Running gtest ---"); + status += chip::test::RunAllTests(); + printf("Test status: %d", status); stream_writer = nullptr; writer.Finish(); } @@ -194,6 +202,9 @@ int main(void) chip::Platform::MemoryInit(); chip::DeviceLayer::PlatformMgr().InitChipStack(); + // required for inits tied to the event loop + chip::DeviceLayer::SetDeviceInstanceInfoProvider(&chip::DeviceLayer::Silabs::SilabsDeviceDataProvider::GetDeviceDataProvider()); + chip::DeviceLayer::SetCommissionableDataProvider(&chip::DeviceLayer::Silabs::SilabsDeviceDataProvider::GetDeviceDataProvider()); SILABS_LOG("***** CHIP EFR32 device tests *****\r\n"); diff --git a/src/transport/raw/tests/NetworkTestHelpers.h b/src/transport/raw/tests/NetworkTestHelpers.h index bf1e812a87fe02..49890406bc6afb 100644 --- a/src/transport/raw/tests/NetworkTestHelpers.h +++ b/src/transport/raw/tests/NetworkTestHelpers.h @@ -72,7 +72,12 @@ class LoopbackTransportDelegate class LoopbackTransport : public Transport::Base { public: - void InitLoopbackTransport(System::Layer * systemLayer) { mSystemLayer = systemLayer; } + void InitLoopbackTransport(System::Layer * systemLayer) + { + Reset(); + mSystemLayer = systemLayer; + } + void ShutdownLoopbackTransport() { // Make sure no one left packets hanging out that they thought got