From 0d1beb029875e6446d3ddefbea412eedae032a69 Mon Sep 17 00:00:00 2001 From: chrisdecenzo <61757564+chrisdecenzo@users.noreply.github.com> Date: Mon, 2 May 2022 12:33:12 -0700 Subject: [PATCH] Add cluster commands to tv-casting-app (#17830) * Add cluster commands to tv-casting-app * Support looking up video player node id from bindings when nodeId=0 in command * refactor shell commands into tv-casting-common * refactor cleanup * add shell commands for print/delete fabrics * shell help * lint and restyled * lint and readme * restyled * spelling --- .github/.wordlist.txt | 1 + examples/tv-casting-app/linux/BUILD.gn | 1 + .../linux/CastingShellCommands.cpp | 46 +- .../tv-casting-app/linux/CastingUtils.cpp | 19 + examples/tv-casting-app/linux/CastingUtils.h | 4 + examples/tv-casting-app/linux/README.md | 105 ++- examples/tv-casting-app/linux/main.cpp | 117 +-- .../tv-casting-app/tv-casting-common/BUILD.gn | 23 + .../commands/clusters/ClusterCommand.h | 184 +++++ .../commands/clusters/ComplexArgument.h | 366 +++++++++ .../commands/clusters/CustomArgument.h | 266 +++++++ .../commands/clusters/DataModelLogger.h | 163 ++++ .../commands/clusters/ModelCommand.cpp | 88 +++ .../commands/clusters/ModelCommand.h | 63 ++ .../commands/clusters/ReportCommand.h | 575 ++++++++++++++ .../commands/clusters/SubscriptionsCommands.h | 78 ++ .../commands/clusters/WriteAttributeCommand.h | 151 ++++ .../commands/common/CHIPCommand.cpp | 85 ++ .../commands/common/CHIPCommand.h | 107 +++ .../commands/common/Command.cpp | 747 ++++++++++++++++++ .../commands/common/Command.h | 253 ++++++ .../commands/common/CommandInvoker.h | 241 ++++++ .../commands/common/Commands.cpp | 332 ++++++++ .../commands/common/Commands.h | 52 ++ .../common/CredentialIssuerCommands.h | 77 ++ .../example/ExampleCredentialIssuerCommands.h | 54 ++ .../tv-casting-common/include/CastingServer.h | 7 + .../tv-casting-common/src/CastingServer.cpp | 121 ++- .../src/TargetVideoPlayerInfo.cpp | 17 - 29 files changed, 4228 insertions(+), 115 deletions(-) create mode 100644 examples/tv-casting-app/tv-casting-common/commands/clusters/ClusterCommand.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/clusters/ComplexArgument.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/clusters/CustomArgument.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/clusters/DataModelLogger.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/clusters/ModelCommand.cpp create mode 100644 examples/tv-casting-app/tv-casting-common/commands/clusters/ModelCommand.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/clusters/ReportCommand.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/clusters/SubscriptionsCommands.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/clusters/WriteAttributeCommand.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.cpp create mode 100644 examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/common/Command.cpp create mode 100644 examples/tv-casting-app/tv-casting-common/commands/common/Command.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/common/CommandInvoker.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/common/Commands.cpp create mode 100644 examples/tv-casting-app/tv-casting-common/commands/common/Commands.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/common/CredentialIssuerCommands.h create mode 100644 examples/tv-casting-app/tv-casting-common/commands/example/ExampleCredentialIssuerCommands.h diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 5ac6294f7a8320..e735a504ed6661 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -1422,6 +1422,7 @@ xFF xFFF xFFFF xfffff +xFFFFFFEFFFFFFFFF xtensa xwayland xyz diff --git a/examples/tv-casting-app/linux/BUILD.gn b/examples/tv-casting-app/linux/BUILD.gn index 9f6bdb831c5a65..df8cac35cd5a3d 100644 --- a/examples/tv-casting-app/linux/BUILD.gn +++ b/examples/tv-casting-app/linux/BUILD.gn @@ -36,6 +36,7 @@ executable("chip-tv-casting-app") { "${chip_root}/src/credentials:default_attestation_verifier", "${chip_root}/src/lib", "${chip_root}/third_party/inipp", + "${chip_root}/third_party/jsoncpp", ] include_dirs = diff --git a/examples/tv-casting-app/linux/CastingShellCommands.cpp b/examples/tv-casting-app/linux/CastingShellCommands.cpp index b051c4be7a0339..9e79e1e964dfd2 100644 --- a/examples/tv-casting-app/linux/CastingShellCommands.cpp +++ b/examples/tv-casting-app/linux/CastingShellCommands.cpp @@ -38,7 +38,15 @@ namespace Shell { static CHIP_ERROR PrintAllCommands() { streamer_t * sout = streamer_get(); - streamer_printf(sout, " help Usage: app \r\n"); + streamer_printf(sout, " help Usage: cast \r\n"); + streamer_printf(sout, " print-bindings Usage: cast print-bindings\r\n"); + streamer_printf(sout, " print-fabrics Usage: cast print-fabrics\r\n"); + streamer_printf( + sout, + " delete-fabric Delete a fabric from the casting client's fabric store. Usage: cast delete-fabric 1\r\n"); + streamer_printf( + sout, + " set-fabric Set current fabric from the casting client's fabric store. Usage: cast set-fabric 1\r\n"); streamer_printf(sout, " init Initialize casting app using given nodeid and index from previous " "commissioning. Usage: init 18446744004990074879 2\r\n"); @@ -50,6 +58,9 @@ static CHIP_ERROR PrintAllCommands() sout, " access Read and display clusters on each endpoint for . Usage: cast access 0xFFFFFFEFFFFFFFFF\r\n"); streamer_printf(sout, " sendudc
Send UDC message to address. Usage: cast sendudc ::1 5543\r\n"); + streamer_printf( + sout, + " cluster [clustercommand] Send cluster command. Usage: cast cluster keypadinput send-key 1 18446744004990074879 1\r\n"); streamer_printf(sout, "\r\n"); return CHIP_NO_ERROR; @@ -122,6 +133,39 @@ static CHIP_ERROR CastingHandler(int argc, char ** argv) chip::Transport::PeerAddress::UDP(commissioner, port)); } #endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT + if (strcmp(argv[0], "print-bindings") == 0) + { + CastingServer::GetInstance()->PrintBindings(); + return CHIP_NO_ERROR; + } + if (strcmp(argv[0], "print-fabrics") == 0) + { + PrintFabrics(); + return CHIP_NO_ERROR; + } + if (strcmp(argv[0], "delete-fabric") == 0) + { + char * eptr; + chip::FabricIndex fabricIndex = (chip::FabricIndex) strtol(argv[1], &eptr, 10); + chip::Server::GetInstance().GetFabricTable().Delete(fabricIndex); + return CHIP_NO_ERROR; + } + if (strcmp(argv[0], "set-fabric") == 0) + { + char * eptr; + chip::FabricIndex fabricIndex = (chip::FabricIndex) strtol(argv[1], &eptr, 10); + chip::NodeId nodeId = CastingServer::GetInstance()->GetVideoPlayerNodeForFabricIndex(fabricIndex); + if (nodeId == kUndefinedFabricIndex) + { + streamer_printf(streamer_get(), "ERROR - invalid fabric or video player nodeId not found\r\n"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + return CastingServer::GetInstance()->TargetVideoPlayerInfoInit(nodeId, fabricIndex); + } + if (strcmp(argv[0], "cluster") == 0) + { + return ProcessClusterCommand(argc, argv); + } return CHIP_ERROR_INVALID_ARGUMENT; } diff --git a/examples/tv-casting-app/linux/CastingUtils.cpp b/examples/tv-casting-app/linux/CastingUtils.cpp index 61fafc850ba0eb..2edd67bc277edb 100644 --- a/examples/tv-casting-app/linux/CastingUtils.cpp +++ b/examples/tv-casting-app/linux/CastingUtils.cpp @@ -110,3 +110,22 @@ void HandleUDCSendExpiration(System::Layer * aSystemLayer, void * context) selectedCommissioner->ipAddress[0], selectedCommissioner->port, selectedCommissioner->interfaceId))); } #endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT + +void PrintFabrics() +{ + // set fabric to be the first in the list + for (const auto & fb : chip::Server::GetInstance().GetFabricTable()) + { + FabricIndex fabricIndex = fb.GetFabricIndex(); + ChipLogError(AppServer, "Next Fabric index=%d", fabricIndex); + if (!fb.IsInitialized()) + { + ChipLogError(AppServer, " -- Not initialized"); + continue; + } + NodeId myNodeId = fb.GetNodeId(); + ChipLogProgress(NotSpecified, + "---- Current Fabric nodeId=0x" ChipLogFormatX64 " fabricId=0x" ChipLogFormatX64 " fabricIndex=%d", + ChipLogValueX64(myNodeId), ChipLogValueX64(fb.GetFabricId()), fabricIndex); + } +} diff --git a/examples/tv-casting-app/linux/CastingUtils.h b/examples/tv-casting-app/linux/CastingUtils.h index 2491a1c0762631..f1cd6ab701abb7 100644 --- a/examples/tv-casting-app/linux/CastingUtils.h +++ b/examples/tv-casting-app/linux/CastingUtils.h @@ -28,6 +28,8 @@ constexpr uint32_t kCommissionerDiscoveryTimeoutInMs = 5 * 1000; +CHIP_ERROR ProcessClusterCommand(int argc, char ** argv); + CHIP_ERROR DiscoverCommissioners(); CHIP_ERROR RequestCommissioning(int index); @@ -39,3 +41,5 @@ void InitCommissioningFlow(intptr_t commandArg); #if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT void HandleUDCSendExpiration(System::Layer * aSystemLayer, void * context); #endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT + +void PrintFabrics(); diff --git a/examples/tv-casting-app/linux/README.md b/examples/tv-casting-app/linux/README.md index 6eba225d7c8c3e..065f6f27e758ff 100644 --- a/examples/tv-casting-app/linux/README.md +++ b/examples/tv-casting-app/linux/README.md @@ -4,8 +4,7 @@ This is a CHIP TV Casting app that can be used to cast content to a TV. This app discovers TVs on the local network that act as commissioners, lets the user select one, sends the TV a User Directed Commissioning request, enters commissioning mode, advertises itself as a Commissionable Node and gets -commissioned. Then it allows the user to send CHIP ContentLauncher commands to -the TV. +commissioned. Then it allows the user to send CHIP commands to the TV.
@@ -45,9 +44,109 @@ the TV. $ cd ~/connectedhomeip/examples/tv-app/linux $ out/debug/chip-tv-app -- Run the tv-casting-app +- Run the tv-casting-app (clean start) $ cd ~/connectedhomeip/examples/tv-casting-app/linux + (delete any stored fabrics from previous runs) + $ rm -rf /tmp/rm -rf /tmp/chip_casting_kvs* $ out/debug/chip-tv-casting-app Follow the on-screen prompts on the tv-casting-app console + +- Re-run the tv-casting-app (cached fabrics) + + $ cd ~/connectedhomeip/examples/tv-casting-app/linux + $ out/debug/chip-tv-casting-app + + Follow the on-screen prompts on the tv-casting-app console. NOTE: When there + are cached fabrics, re-commissioning onto the same fabric will fail. See + below for details. + +### Commissioning the tv-casting-app + +The tv-casting-app will automatically discover video players and print these out +upon startup. The user-directed-commissioning (UDC) process can be initiated +using the shell by specifying the index of the discovered video player in the +printed list. + +- Initiate UDC for the discovered video player with index 0 + + > cast request 0 + +- Re-run commissioner discovery + + > cast discover + +### Re-Running the Example on Linux with Cached Fabrics + +After successfully commissioning the tv-casting-app onto a fabric the app can be +re-run using a cached fabric so that the commissioning step does not need to be +repeated. This allows the app to be commissioned once and then re-run upon +demand. Shell commands allow viewing, selecting and deleting a cached fabric. If +no fabric is specified for a command that required a fabric, then the first +valid cached fabric will be used. + +- Print all shell commands + + > help + > cast help + +- Print all fabrics + + > cast print-fabrics + +- Delete fabric with index 1 + + > cast delete-fabric 1 + +- Switch to fabric index + + > cast set-fabric 1 + +### Sending Arbitrary Cluster commands + +The linux tv-casting-app can invoke any cluster command using the same syntax as +chip-tool. This can be done from the command line and from the shell. The +default (or first valid) cached fabric will be used unless a different fabric +has been set already using shell commands. + +Similar to chip-tool arguments, the target nodeId for the command is specified +along with the endpoint. For the casting app, the nodeId would be the nodeId for +the video player (TV). For convenience, when nodeId '0' is used, the +tv-casting-app will use the cached nodeId for the video player corresponding to +the current fabric. + +The tv-casting-app is able to determine the nodeId for the given fabric by +checking its binding table since the video player sets bindings on the +tv-casting-app for each endpoint to which the tv-casting-app is granted access +during commissioning. + +- Run the tv-casting-app and invoke a cluster command using default fabric, + target video player nodeId 18446744004990074879 + + $ out/debug/chip-tv-casting-app onoff read on-off 18446744004990074879 2 + +- Run the tv-casting-app and invoke a cluster command using default fabric, + target video player nodeId 0xFFFFFFEFFFFFFFFF (hex) + + $ out/debug/chip-tv-casting-app onoff read on-off 0xFFFFFFEFFFFFFFFF 2 + +- Run the tv-casting-app and invoke a cluster command using default fabric, + video player nodeId for that fabric + + $ out/debug/chip-tv-casting-app onoff read on-off 0 2 + +- Run the tv-casting-app and invoke a different cluster command using default + fabric, video player nodeId for that fabric + + $ out/debug/chip-tv-casting-app keypadinput send-key 1 0 1 + +- Run the tv-casting-app and invoke a cluster command from the shell using + default fabric, video player nodeId for that fabric + + > cast cluster keypadinput send-key 1 0 1 + +- Run the tv-casting-app and invoke a cluster command from the shell using + default fabric, target video player nodeId 0xFFFFFFEFFFFFFFFF + + > cast cluster keypadinput send-key 1 0xFFFFFFEFFFFFFFFF 1 diff --git a/examples/tv-casting-app/linux/main.cpp b/examples/tv-casting-app/linux/main.cpp index 683bb2c77026d8..753dd3637242d6 100644 --- a/examples/tv-casting-app/linux/main.cpp +++ b/examples/tv-casting-app/linux/main.cpp @@ -16,6 +16,11 @@ * limitations under the License. */ +#include "commands/clusters/SubscriptionsCommands.h" +#include "commands/common/Commands.h" +#include "commands/example/ExampleCredentialIssuerCommands.h" +#include + #include "CastingServer.h" #include "CastingUtils.h" #if defined(ENABLE_CHIP_SHELL) @@ -41,89 +46,6 @@ using namespace chip::app::Clusters::ContentLauncher::Commands; #if defined(ENABLE_CHIP_SHELL) using chip::Shell::Engine; #endif -struct TVExampleDeviceType -{ - const char * name; - uint16_t id; -}; - -Dnssd::DiscoveryFilter gDiscoveryFilter = Dnssd::DiscoveryFilter(); -constexpr TVExampleDeviceType kKnownDeviceTypes[] = { { "video-player", 35 }, { "dimmable-light", 257 } }; -constexpr int kKnownDeviceTypesCount = sizeof kKnownDeviceTypes / sizeof *kKnownDeviceTypes; -constexpr uint16_t kOptionDeviceType = 't'; - -// TODO: Accept these values over CLI -const char * kContentUrl = "https://www.test.com/videoid"; -const char * kContentDisplayStr = "Test video"; - -CommissionableNodeController gCommissionableNodeController; -chip::System::SocketWatchToken gToken; -bool gInited = false; - -bool HandleOptions(const char * aProgram, OptionSet * aOptions, int aIdentifier, const char * aName, const char * aValue) -{ - switch (aIdentifier) - { - case kOptionDeviceType: { - char * endPtr; - long deviceType = strtol(aValue, &endPtr, 10); - if (*endPtr == '\0' && deviceType > 0 && CanCastTo(deviceType)) - { - gDiscoveryFilter = Dnssd::DiscoveryFilter(Dnssd::DiscoveryFilterType::kDeviceType, static_cast(deviceType)); - return true; - } - - for (int i = 0; i < kKnownDeviceTypesCount; i++) - { - if (strcasecmp(aValue, kKnownDeviceTypes[i].name) == 0) - { - gDiscoveryFilter = Dnssd::DiscoveryFilter(Dnssd::DiscoveryFilterType::kDeviceType, kKnownDeviceTypes[i].id); - return true; - } - } - - ChipLogError(AppServer, "%s: INTERNAL ERROR: Unhandled option value: %s %s", aProgram, aName, aValue); - return false; - } - default: - ChipLogError(AppServer, "%s: INTERNAL ERROR: Unhandled option: %s", aProgram, aName); - return false; - } -} - -OptionDef cmdLineOptionsDef[] = { - { "device-type", chip::ArgParser::kArgumentRequired, kOptionDeviceType }, - {}, -}; - -OptionSet cmdLineOptions = { HandleOptions, cmdLineOptionsDef, "PROGRAM OPTIONS", - " -t \n" - " --device-type \n" - " Device type of the commissioner to discover and request commissioning from. Specify value as " - "a decimal integer or a known text representation. Defaults to all device types\n" }; - -HelpOptions helpOptions("tv-casting-app", "Usage: tv-casting-app [options]", "1.0"); - -OptionSet * allOptions[] = { &cmdLineOptions, &helpOptions, nullptr }; - -void DeviceEventCallback(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) -{ - if (event->Type == DeviceLayer::DeviceEventType::kBindingsChangedViaCluster) - { - if (CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->IsInitialized()) - { - CastingServer::GetInstance()->ReadServerClustersForNode( - CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->GetNodeId()); - } - } - else if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete) - { - ReturnOnFailure(CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->Initialize( - event->CommissioningComplete.PeerNodeId, event->CommissioningComplete.PeerFabricIndex)); - - CastingServer::GetInstance()->ContentLauncherLaunchURL(kContentUrl, kContentDisplayStr); - } -} CHIP_ERROR InitCommissionableDataProvider(LinuxCommissionableDataProvider & provider, LinuxDeviceOptions & options) { @@ -169,6 +91,20 @@ CHIP_ERROR InitCommissionableDataProvider(LinuxCommissionableDataProvider & prov // To hold SPAKE2+ verifier, discriminator, passcode LinuxCommissionableDataProvider gCommissionableDataProvider; +// For shell and command line processing of commands +ExampleCredentialIssuerCommands gCredIssuerCommands; +Commands gCommands; + +CHIP_ERROR ProcessClusterCommand(int argc, char ** argv) +{ + if (!CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->IsInitialized()) + { + CastingServer::GetInstance()->SetDefaultFabricIndex(); + } + gCommands.Run(argc, argv); + return CHIP_NO_ERROR; +} + int main(int argc, char * argv[]) { VerifyOrDie(CHIP_NO_ERROR == chip::Platform::MemoryInit()); @@ -197,11 +133,6 @@ int main(int argc, char * argv[]) SetDeviceAttestationVerifier(GetDefaultDACVerifier(testingRootStore)); } - if (!chip::ArgParser::ParseArgs(argv[0], argc, argv, allOptions)) - { - return 1; - } - // Send discover commissioners request SuccessOrExit(err = CastingServer::GetInstance()->DiscoverCommissioners()); @@ -210,8 +141,14 @@ int main(int argc, char * argv[]) chip::System::Clock::Milliseconds32(kCommissionerDiscoveryTimeoutInMs), [](System::Layer *, void *) { chip::DeviceLayer::PlatformMgr().ScheduleWork(InitCommissioningFlow); }, nullptr); - // Add callback to send Content casting commands after commissioning completes - chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(DeviceEventCallback, 0); + registerClusters(gCommands, &gCredIssuerCommands); + registerClusterSubscriptions(gCommands, &gCredIssuerCommands); + + if (argc > 1) + { + // if there are command-line arguments, then automatically start server + ProcessClusterCommand(argc, argv); + } DeviceLayer::PlatformMgr().RunEventLoop(); exit: diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index 28faac10bf99fc..109de607787fea 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -16,6 +16,17 @@ import("//build_overrides/chip.gni") import("${chip_root}/src/app/chip_data_model.gni") +config("config") { + include_dirs = [ + ".", + "${chip_root}/zzz_generated/chip-tool", + "${chip_root}/zzz_generated/tv-casting-app", + "${chip_root}/src/lib", + ] + + cflags = [ "-Wconversion" ] +} + chip_data_model("tv-casting-common") { zap_file = "tv-casting-app.zap" @@ -23,6 +34,15 @@ chip_data_model("tv-casting-common") { "${chip_root}/zzz_generated/tv-casting-app/zap-generated" sources = [ + "${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/common/CHIPCommand.cpp", + "commands/common/CHIPCommand.h", + "commands/common/Command.cpp", + "commands/common/CommandInvoker.h", + "commands/common/Commands.cpp", + "commands/common/CredentialIssuerCommands.h", "include/CastingServer.h", "include/TargetEndpointInfo.h", "include/TargetVideoPlayerInfo.h", @@ -36,8 +56,11 @@ chip_data_model("tv-casting-common") { "${chip_root}/src/credentials:default_attestation_verifier", "${chip_root}/src/lib", "${chip_root}/third_party/inipp", + "${chip_root}/third_party/jsoncpp", ] + public_configs = [ ":config" ] + include_dirs = [ "${chip_root}/examples/tv-casting-app/tv-casting-common/include" ] diff --git a/examples/tv-casting-app/tv-casting-common/commands/clusters/ClusterCommand.h b/examples/tv-casting-app/tv-casting-common/commands/clusters/ClusterCommand.h new file mode 100644 index 00000000000000..ca1738bc8b6715 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/clusters/ClusterCommand.h @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2022 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 "DataModelLogger.h" +#include "ModelCommand.h" + +class ClusterCommand : public ModelCommand, public chip::app::CommandSender::Callback +{ +public: + ClusterCommand(CredentialIssuerCommands * credsIssuerConfig) : ModelCommand("command-by-id", credsIssuerConfig) + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterId); + AddArgument("command-id", 0, UINT32_MAX, &mCommandId); + AddArgument("payload", &mPayload); + AddArgument("timedInteractionTimeoutMs", 0, UINT16_MAX, &mTimedInteractionTimeoutMs); + AddArgument("suppressResponse", 0, 1, &mSuppressResponse); + AddArgument("repeat-count", 1, UINT16_MAX, &mRepeatCount); + AddArgument("repeat-delay-ms", 0, UINT16_MAX, &mRepeatDelayInMs); + ModelCommand::AddArguments(); + } + + ClusterCommand(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + ModelCommand("command-by-id", credsIssuerConfig), mClusterId(clusterId) + { + AddArgument("command-id", 0, UINT32_MAX, &mCommandId); + AddArgument("payload", &mPayload); + AddArgument("timedInteractionTimeoutMs", 0, UINT16_MAX, &mTimedInteractionTimeoutMs); + AddArgument("suppressResponse", 0, 1, &mSuppressResponse); + AddArgument("repeat-count", 1, UINT16_MAX, &mRepeatCount); + AddArgument("repeat-delay-ms", 0, UINT16_MAX, &mRepeatDelayInMs); + ModelCommand::AddArguments(); + } + + ClusterCommand(const char * commandName, CredentialIssuerCommands * credsIssuerConfig) : + ModelCommand(commandName, credsIssuerConfig) + { + AddArgument("timedInteractionTimeoutMs", 0, UINT16_MAX, &mTimedInteractionTimeoutMs); + AddArgument("suppressResponse", 0, 1, &mSuppressResponse); + AddArgument("repeat-count", 1, UINT16_MAX, &mRepeatCount); + AddArgument("repeat-delay-ms", 0, UINT16_MAX, &mRepeatDelayInMs); + } + + ~ClusterCommand() {} + + CHIP_ERROR SendCommand(ChipDevice * device, std::vector endpointIds) override + { + return ClusterCommand::SendCommand(device, endpointIds.at(0), mClusterId, mCommandId, mPayload); + } + + CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex) override + { + return ClusterCommand::SendGroupCommand(groupId, fabricIndex, mClusterId, mCommandId, mPayload); + } + + /////////// 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) + { + ChipLogError(chipTool, "Response Failure: %s", chip::ErrorStr(error)); + mError = error; + return; + } + + if (data != nullptr) + { + error = DataModelLogger::LogCommand(path, data); + if (CHIP_NO_ERROR != error) + { + ChipLogError(chipTool, "Response Failure: Can not decode Data"); + mError = error; + return; + } + } + } + + virtual void OnError(const chip::app::CommandSender * client, CHIP_ERROR error) override + { + ChipLogProgress(chipTool, "Error: %s", chip::ErrorStr(error)); + mError = error; + } + + virtual void OnDone(chip::app::CommandSender * client) override + { + 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); + } + } + + template + CHIP_ERROR SendCommand(ChipDevice * device, chip::EndpointId endpointId, chip::ClusterId clusterId, chip::CommandId commandId, + const T & value) + { + uint16_t repeatCount = mRepeatCount.ValueOr(1); + while (repeatCount--) + { + chip::app::CommandPathParams commandPath = { endpointId, 0 /* groupId */, clusterId, commandId, + (chip::app::CommandPathFlags::kEndpointIdValid) }; + + auto commandSender = std::make_unique(this, device->GetExchangeManager(), + mTimedInteractionTimeoutMs.HasValue()); + VerifyOrReturnError(commandSender != nullptr, CHIP_ERROR_NO_MEMORY); + ReturnErrorOnFailure(commandSender->AddRequestDataNoTimedCheck(commandPath, value, mTimedInteractionTimeoutMs, + mSuppressResponse.ValueOr(false))); + + ReturnErrorOnFailure(commandSender->SendCommandRequest(device->GetSecureSession().Value())); + mCommandSender.push_back(std::move(commandSender)); + + if (mRepeatDelayInMs.HasValue()) + { + chip::test_utils::SleepMillis(mRepeatDelayInMs.Value()); + } + } + return CHIP_NO_ERROR; + } + + template + CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex, chip::ClusterId clusterId, + chip::CommandId commandId, const T & value) + { + chip::app::CommandPathParams commandPath = { 0 /* endpoint */, groupId, clusterId, commandId, + (chip::app::CommandPathFlags::kGroupIdValid) }; + + chip::Messaging::ExchangeManager * exchangeManager = chip::app::InteractionModelEngine::GetInstance()->GetExchangeManager(); + + auto commandSender = + chip::Platform::MakeUnique(this, exchangeManager, mTimedInteractionTimeoutMs.HasValue()); + VerifyOrReturnError(commandSender != nullptr, CHIP_ERROR_NO_MEMORY); + ReturnErrorOnFailure(commandSender->AddRequestDataNoTimedCheck(commandPath, value, mTimedInteractionTimeoutMs)); + + chip::Transport::OutgoingGroupSession session(groupId, fabricIndex); + ReturnErrorOnFailure(commandSender->SendGroupCommandRequest(chip::SessionHandle(session))); + commandSender.release(); + + return CHIP_NO_ERROR; + } + +private: + chip::ClusterId mClusterId; + chip::CommandId mCommandId; + chip::Optional mTimedInteractionTimeoutMs; + chip::Optional mSuppressResponse; + chip::Optional mRepeatCount; + chip::Optional mRepeatDelayInMs; + + CHIP_ERROR mError = CHIP_NO_ERROR; + CustomArgument mPayload; + std::vector> mCommandSender; +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/clusters/ComplexArgument.h b/examples/tv-casting-app/tv-casting-common/commands/clusters/ComplexArgument.h new file mode 100644 index 00000000000000..aafc18d68d7591 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/clusters/ComplexArgument.h @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2022 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 + +constexpr uint8_t kMaxLabelLength = 100; + +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(chipTool, "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(chipTool, "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::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(chipTool, "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))); + + Json::ArrayIndex size = value.size(); + for (Json::ArrayIndex i = 0; i < size; i++) + { + char labelWithIndex[kMaxLabelLength]; + snprintf(labelWithIndex, sizeof(labelWithIndex), "%s[%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(chipTool, "Error while encoding %s as an octet string: Not a string.", label); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (strlen(value.asCString()) % 2 != 0) + { + ChipLogError(chipTool, "Error while encoding %s as an octet string: Odd number of characters.", label); + return CHIP_ERROR_INVALID_STRING_LENGTH; + } + + size_t size = strlen(value.asCString()); + auto buffer = static_cast(chip::Platform::MemoryCalloc(size / 2, sizeof(uint8_t))); + size_t octetCount = chip::Encoding::HexToBytes(value.asCString(), size, buffer, size / 2); + + request = chip::ByteSpan(buffer, octetCount); + return CHIP_NO_ERROR; + } + + static CHIP_ERROR Setup(const char * label, chip::CharSpan & request, Json::Value & value) + { + if (!value.isString()) + { + ChipLogError(chipTool, "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))); + strncpy(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(chipTool, "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(chipTool, "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(chipTool, "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(chipTool, "%s is required. Should be provided as {\"%s\": value}", label, memberName); + 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; +}; + +template +class TypedComplexArgument : public ComplexArgument +{ +public: + TypedComplexArgument(T * request) : mRequest(request) {} + ~TypedComplexArgument() { ComplexArgumentParser::Finalize(*mRequest); } + + CHIP_ERROR Parse(const char * label, const char * json) + { + Json::Value value; + Json::Reader reader; + if (!reader.parse(json, value)) + { + std::vector errors = reader.getStructuredErrors(); + ChipLogError(chipTool, "Error parsing JSON for %s:", label); + for (auto & error : errors) + { + ChipLogError(chipTool, " %s", error.message.c_str()); + ptrdiff_t error_start = error.offset_start; + ptrdiff_t error_end = error.offset_limit; + const char * sourceText = json; + // 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 errorMsg; + if (error_start > kMaxContext) + { + sourceText += (error_start - kMaxContext); + error_end = kMaxContext + (error_end - error_start); + error_start = kMaxContext; + ChipLogError(chipTool, "... %s", sourceText); + // Add markers corresponding to the "... " above. + errorMsg += "----"; + } + else + { + ChipLogError(chipTool, "%s", sourceText); + } + for (ptrdiff_t i = 0; i < error_start; ++i) + { + errorMsg += "-"; + } + errorMsg += "^"; + if (error_start + 1 < error_end) + { + for (ptrdiff_t i = error_start + 1; i < error_end; ++i) + { + errorMsg += "-"; + } + errorMsg += "^"; + } + ChipLogError(chipTool, "%s", errorMsg.c_str()); + + if (error.message == "Missing ',' or '}' in object declaration" && error.offset_start > 0 && + json[error.offset_start - 1] == '0' && (json[error.offset_start] == 'x' || json[error.offset_start] == 'X')) + { + ChipLogError(chipTool, + "NOTE: JSON does not allow hex syntax beginning with 0x for numbers. Try putting the hex number " + "in quotes (like {\"name\": \"0x100\"})."); + } + } + return CHIP_ERROR_INVALID_ARGUMENT; + } + + return ComplexArgumentParser::Setup(label, *mRequest, value); + } + +private: + T * mRequest; +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/clusters/CustomArgument.h b/examples/tv-casting-app/tv-casting-common/commands/clusters/CustomArgument.h new file mode 100644 index 00000000000000..2362171aa06da9 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/clusters/CustomArgument.h @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2022 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 { +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) + { + size_t size = strlen(value.asCString()); + VerifyOrReturnError(size % 2 == 0, CHIP_ERROR_INVALID_STRING_LENGTH); + + chip::Platform::ScopedMemoryBuffer buffer; + VerifyOrReturnError(buffer.Calloc(size / 2), CHIP_ERROR_NO_MEMORY); + size_t octetCount = chip::Encoding::HexToBytes(value.asCString() + kPayloadHexPrefixLen, size - kPayloadHexPrefixLen, + buffer.Get(), (size - kPayloadHexPrefixLen) / 2); + VerifyOrReturnError(octetCount != 0, CHIP_ERROR_NO_MEMORY); + + 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::Reader reader; + Json::Value value; + reader.parse(json, value); + + 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); + 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/tv-casting-app/tv-casting-common/commands/clusters/DataModelLogger.h b/examples/tv-casting-app/tv-casting-common/commands/clusters/DataModelLogger.h new file mode 100644 index 00000000000000..7d725cf0a8b5ab --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/clusters/DataModelLogger.h @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2022 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 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) + { + char buffer[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE]; + if (CHIP_NO_ERROR == + chip::Encoding::BytesToUppercaseHexString(value.data(), value.size(), &buffer[0], CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE)) + { + DataModelLogger::LogString(label, indent, buffer); + } + else + { + DataModelLogger::LogString(label, indent, + std::string("Elided value too large of size ") + std::to_string(value.size())); + } + + 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) + { + DataModelLogger::LogValue(label, indent, chip::to_underlying(value)); + return CHIP_NO_ERROR; + } + + template + static CHIP_ERROR LogValue(const char * label, size_t indent, chip::BitFlags value) + { + DataModelLogger::LogValue(label, indent, value.Raw()); + return CHIP_NO_ERROR; + } + + template + static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::app::DataModel::DecodableList & value) + { + size_t count = 0; + CHIP_ERROR err = value.ComputeSize(&count); + if (err != CHIP_NO_ERROR) + { + return err; + } + 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"); + } + else + { + DataModelLogger::LogValue(label, indent, value.Value()); + } + + return CHIP_NO_ERROR; + } + + template + static CHIP_ERROR LogValue(const char * label, size_t indent, const chip::Optional & value) + { + if (value.HasValue()) + { + 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 indentation; + for (size_t i = 0; i < indent; ++i) + { + indentation.append(" "); + } + + ChipLogProgress(chipTool, "%s%s%s %s", indentation.c_str(), label.c_str(), label.size() ? ":" : "", string.c_str()); + } +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/clusters/ModelCommand.cpp b/examples/tv-casting-app/tv-casting-common/commands/clusters/ModelCommand.cpp new file mode 100644 index 00000000000000..4495b153011312 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/clusters/ModelCommand.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020 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() +{ + FabricIndex fabricIndex = CastingServer::GetInstance()->CurrentFabricIndex(); + + if (mNodeId == 0) + { + ChipLogProgress(chipTool, "nodeId set to 0, using default for fabric %d", fabricIndex); + mNodeId = CastingServer::GetInstance()->GetVideoPlayerNodeForFabricIndex(fabricIndex); + } + else + { + // potentially change fabric index if this is not the right one for the given nodeId + fabricIndex = CastingServer::GetInstance()->GetVideoPlayerFabricIndexForNode(mNodeId); + } + ChipLogProgress(chipTool, "Sending command to node 0x%" PRIx64, mNodeId); + + if (IsGroupId(mNodeId)) + { + ChipLogProgress(chipTool, "Sending command to group 0x%x", GroupIdFromNodeId(mNodeId)); + + return SendGroupCommand(GroupIdFromNodeId(mNodeId), fabricIndex); + } + + Server * server = &(chip::Server::GetInstance()); + chip::FabricInfo * fabric = server->GetFabricTable().FindFabricWithIndex(fabricIndex); + if (fabric == nullptr) + { + ChipLogError(AppServer, "Did not find fabric for index %d", fabricIndex); + return CHIP_ERROR_INVALID_FABRIC_ID; + } + + PeerId peerID = fabric->GetPeerIdForNode(mNodeId); + return server->GetCASESessionManager()->FindOrEstablishSession(peerID, &mOnDeviceConnectedCallback, + &mOnDeviceConnectionFailureCallback); +} + +void ModelCommand::OnDeviceConnectedFn(void * context, ChipDevice * device) +{ + ChipLogProgress(chipTool, "ModelCommand::OnDeviceConnectedFn"); + ModelCommand * command = reinterpret_cast(context); + VerifyOrReturn(command != nullptr, ChipLogError(chipTool, "OnDeviceConnectedFn: context is null")); + + CHIP_ERROR err = command->SendCommand(device, command->mEndPointId); + VerifyOrReturn(CHIP_NO_ERROR == err, command->SetCommandExitStatus(err)); +} + +void ModelCommand::OnDeviceConnectionFailureFn(void * context, PeerId peerId, CHIP_ERROR err) +{ + ChipLogProgress(chipTool, "ModelCommand::OnDeviceConnectionFailureFn"); + LogErrorOnFailure(err); + + ModelCommand * command = reinterpret_cast(context); + VerifyOrReturn(command != nullptr, ChipLogError(chipTool, "OnDeviceConnectionFailureFn: context is null")); + command->SetCommandExitStatus(err); +} + +void ModelCommand::Shutdown() +{ + ResetArguments(); + mOnDeviceConnectedCallback.Cancel(); + mOnDeviceConnectionFailureCallback.Cancel(); +} diff --git a/examples/tv-casting-app/tv-casting-common/commands/clusters/ModelCommand.h b/examples/tv-casting-app/tv-casting-common/commands/clusters/ModelCommand.h new file mode 100644 index 00000000000000..aa036a2cee18a9 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/clusters/ModelCommand.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 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 + +class ModelCommand : public CHIPCommand +{ +public: + using ChipDevice = ::chip::OperationalDeviceProxy; + + ModelCommand(const char * commandName, CredentialIssuerCommands * credsIssuerConfig) : + CHIPCommand(commandName, credsIssuerConfig), mOnDeviceConnectedCallback(OnDeviceConnectedFn, this), + mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this) + {} + + void AddArguments() + { + AddArgument("node-id/group-id", 0, UINT64_MAX, &mNodeId); + AddArgument("endpoint-id-ignored-for-group-commands", 0, UINT16_MAX, &mEndPointId); + 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(10)); } + + virtual CHIP_ERROR SendCommand(ChipDevice * device, std::vector endPointIds) = 0; + + virtual CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex) { return CHIP_ERROR_BAD_REQUEST; }; + + void Shutdown() override; + +protected: + chip::Optional mTimeout; + +private: + chip::NodeId mNodeId; + std::vector mEndPointId; + + static void OnDeviceConnectedFn(void * context, ChipDevice * device); + static void OnDeviceConnectionFailureFn(void * context, PeerId peerId, CHIP_ERROR error); + + chip::Callback::Callback mOnDeviceConnectedCallback; + chip::Callback::Callback mOnDeviceConnectionFailureCallback; +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/clusters/ReportCommand.h b/examples/tv-casting-app/tv-casting-common/commands/clusters/ReportCommand.h new file mode 100644 index 00000000000000..f2a8e62752e1bd --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/clusters/ReportCommand.h @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2020 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" + +constexpr uint8_t kMaxAllowedPaths = 10; + +class ReportCommand : public ModelCommand, public chip::app::ReadClient::Callback +{ +public: + ReportCommand(const char * commandName, CredentialIssuerCommands * credsIssuerConfig) : + ModelCommand(commandName, credsIssuerConfig), mBufferedReadAdapter(*this) + {} + + virtual void OnAttributeSubscription(){}; + virtual void OnEventSubscription(){}; + + /////////// 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) + { + ChipLogError(chipTool, "Response Failure: %s", chip::ErrorStr(error)); + mError = error; + return; + } + + if (data == nullptr) + { + ChipLogError(chipTool, "Response Failure: No Data"); + mError = CHIP_ERROR_INTERNAL; + return; + } + + error = DataModelLogger::LogAttribute(path, data); + if (CHIP_NO_ERROR != error) + { + ChipLogError(chipTool, "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) + { + ChipLogError(chipTool, "Response Failure: %s", chip::ErrorStr(error)); + mError = error; + return; + } + } + + if (data == nullptr) + { + ChipLogError(chipTool, "Response Failure: No Data"); + mError = CHIP_ERROR_INTERNAL; + return; + } + + CHIP_ERROR error = DataModelLogger::LogEvent(eventHeader, data); + if (CHIP_NO_ERROR != error) + { + ChipLogError(chipTool, "Response Failure: Can not decode Data"); + mError = error; + return; + } + } + + void OnError(CHIP_ERROR error) override + { + ChipLogProgress(chipTool, "Error: %s", chip::ErrorStr(error)); + mError = error; + } + + void OnDone() override + { + mReadClient.reset(); + SetCommandExitStatus(mError); + } + + void OnSubscriptionEstablished(uint64_t subscriptionId) override { OnAttributeSubscription(); } + +protected: + CHIP_ERROR ReportAttribute(ChipDevice * device, std::vector endpointIds, + std::vector clusterIds, std::vector attributeIds, + chip::app::ReadClient::InteractionType interactionType, uint16_t minInterval = 0, + uint16_t maxInterval = 0, + const chip::Optional> & dataVersions = chip::NullOptional) + { + const size_t clusterCount = clusterIds.size(); + const size_t attributeCount = attributeIds.size(); + const size_t endpointCount = endpointIds.size(); + const size_t dataVersionsCount = dataVersions.HasValue() ? dataVersions.Value().size() : 0; + + VerifyOrReturnError(clusterCount > 0 && clusterCount <= kMaxAllowedPaths, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(attributeCount > 0 && attributeCount <= kMaxAllowedPaths, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(endpointCount > 0 && endpointCount <= kMaxAllowedPaths, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(dataVersionsCount <= kMaxAllowedPaths, CHIP_ERROR_INVALID_ARGUMENT); + + const bool hasSameIdsCount = (clusterCount == attributeCount) && (clusterCount == endpointCount) && + (dataVersionsCount == 0 || clusterCount == dataVersionsCount); + const bool multipleClusters = + clusterCount > 1 && attributeCount == 1 && endpointCount == 1 && (dataVersionsCount == 0 || dataVersionsCount == 1); + const bool multipleAttributes = + attributeCount > 1 && clusterCount == 1 && endpointCount == 1 && (dataVersionsCount == 0 || dataVersionsCount == 1); + const bool multipleEndpoints = + endpointCount > 1 && clusterCount == 1 && attributeCount == 1 && (dataVersionsCount == 0 || dataVersionsCount == 1); + const bool multipleDataVersions = dataVersionsCount > 1 && clusterCount == 1 && attributeCount == 1 && endpointCount == 1; + + size_t pathsCount = 0; + if (hasSameIdsCount) + { + pathsCount = clusterCount; + } + else if (multipleClusters) + { + pathsCount = clusterCount; + } + else if (multipleAttributes) + { + pathsCount = attributeCount; + } + else if (multipleEndpoints) + { + pathsCount = endpointCount; + } + else if (multipleDataVersions) + { + pathsCount = dataVersionsCount; + } + else + { + ChipLogError( + chipTool, + "\n%sAttribute commands targeting multiple paths needs to have: \n \t * One element with multiple ids (for " + "example 1 cluster id, 1 attribute id, 2 endpoint ids)\n\t * Or the same " + "number of ids (for examples 2 cluster ids, 2 attribute ids and 2 endpoint ids).\n The current command has %u " + "cluster ids, %u attribute ids, %u endpoint ids.", + interactionType == chip::app::ReadClient::InteractionType::Subscribe ? "Subscribe" : "Read", + static_cast(clusterCount), static_cast(attributeCount), + static_cast(endpointCount)); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + ChipLogProgress(chipTool, "Sending %sAttribute to:", + interactionType == chip::app::ReadClient::InteractionType::Subscribe ? "Subscribe" : "Read"); + + chip::app::AttributePathParams attributePathParams[kMaxAllowedPaths]; + chip::app::DataVersionFilter dataVersionFilter[kMaxAllowedPaths]; + for (size_t i = 0; i < pathsCount; i++) + { + chip::ClusterId clusterId = clusterIds.at((hasSameIdsCount || multipleClusters) ? i : 0); + chip::AttributeId attributeId = attributeIds.at((hasSameIdsCount || multipleAttributes) ? i : 0); + chip::EndpointId endpointId = endpointIds.at((hasSameIdsCount || multipleEndpoints) ? i : 0); + + ChipLogProgress(chipTool, "\tcluster " ChipLogFormatMEI ", attribute: " ChipLogFormatMEI ", endpoint %u", + ChipLogValueMEI(clusterId), ChipLogValueMEI(attributeId), endpointId); + attributePathParams[i].mClusterId = clusterId; + attributePathParams[i].mAttributeId = attributeId; + attributePathParams[i].mEndpointId = endpointId; + + if (dataVersions.HasValue()) + { + chip::DataVersion dataVersion = dataVersions.Value().at((hasSameIdsCount || multipleDataVersions) ? i : 0); + dataVersionFilter[i].mEndpointId = endpointId; + dataVersionFilter[i].mClusterId = clusterId; + dataVersionFilter[i].mDataVersion.SetValue(dataVersion); + } + } + + chip::app::ReadPrepareParams params(device->GetSecureSession().Value()); + params.mpEventPathParamsList = nullptr; + params.mEventPathParamsListSize = 0; + params.mpAttributePathParamsList = attributePathParams; + params.mAttributePathParamsListSize = pathsCount; + + if (mFabricFiltered.HasValue()) + { + params.mIsFabricFiltered = mFabricFiltered.Value(); + } + + if (dataVersions.HasValue()) + { + params.mpDataVersionFilterList = dataVersionFilter; + params.mDataVersionFilterListSize = pathsCount; + } + + if (interactionType == chip::app::ReadClient::InteractionType::Subscribe) + { + params.mMinIntervalFloorSeconds = minInterval; + params.mMaxIntervalCeilingSeconds = maxInterval; + if (mKeepSubscriptions.HasValue()) + { + params.mKeepSubscriptions = mKeepSubscriptions.Value(); + } + } + + mReadClient = std::make_unique(chip::app::InteractionModelEngine::GetInstance(), + device->GetExchangeManager(), mBufferedReadAdapter, interactionType); + return mReadClient->SendRequest(params); + } + + CHIP_ERROR ReportEvent(ChipDevice * device, std::vector endpointIds, std::vector clusterIds, + std::vector eventIds, chip::app::ReadClient::InteractionType interactionType, + uint16_t minInterval = 0, uint16_t maxInterval = 0) + { + const size_t clusterCount = clusterIds.size(); + const size_t eventCount = eventIds.size(); + const size_t endpointCount = endpointIds.size(); + + VerifyOrReturnError(clusterCount > 0 && clusterCount <= kMaxAllowedPaths, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(eventCount > 0 && eventCount <= kMaxAllowedPaths, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(endpointCount > 0 && endpointCount <= kMaxAllowedPaths, CHIP_ERROR_INVALID_ARGUMENT); + + const bool hasSameIdsCount = (clusterCount == eventCount) && (clusterCount == endpointCount); + const bool multipleClusters = clusterCount > 1 && eventCount == 1 && endpointCount == 1; + const bool multipleEvents = eventCount > 1 && clusterCount == 1 && endpointCount == 1; + const bool multipleEndpoints = endpointCount > 1 && clusterCount == 1 && eventCount == 1; + + size_t pathsCount = 0; + if (hasSameIdsCount) + { + pathsCount = clusterCount; + } + else if (multipleClusters) + { + pathsCount = clusterCount; + } + else if (multipleEvents) + { + pathsCount = eventCount; + } + else if (multipleEndpoints) + { + pathsCount = endpointCount; + } + else + { + ChipLogError(chipTool, + "\n%sEvent command targeting multiple paths needs to have: \n \t * One element with multiple ids (for " + "example 1 cluster id, 1 event id, 2 endpoint ids)\n\t * Or the same " + "number of ids (for examples 2 cluster ids, 2 event ids and 2 endpoint ids).\n The current command has %u " + "cluster ids, %u event ids, %u endpoint ids.", + interactionType == chip::app::ReadClient::InteractionType::Subscribe ? "Subscribe" : "Read", + static_cast(clusterCount), static_cast(eventCount), + static_cast(endpointCount)); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + chip::app::EventPathParams eventPathParams[kMaxAllowedPaths]; + + ChipLogProgress(chipTool, "Sending %sEvent to:", + interactionType == chip::app::ReadClient::InteractionType::Subscribe ? "Subscribe" : "Read"); + for (size_t i = 0; i < pathsCount; i++) + { + chip::ClusterId clusterId = clusterIds.at((hasSameIdsCount || multipleClusters) ? i : 0); + chip::EventId eventId = eventIds.at((hasSameIdsCount || multipleEvents) ? i : 0); + chip::EndpointId endpointId = endpointIds.at((hasSameIdsCount || multipleEndpoints) ? i : 0); + + ChipLogProgress(chipTool, "\tcluster " ChipLogFormatMEI ", event: " ChipLogFormatMEI ", endpoint %u", + ChipLogValueMEI(clusterId), ChipLogValueMEI(eventId), endpointId); + eventPathParams[i].mClusterId = clusterId; + eventPathParams[i].mEventId = eventId; + eventPathParams[i].mEndpointId = endpointId; + } + + chip::app::ReadPrepareParams params(device->GetSecureSession().Value()); + params.mpEventPathParamsList = eventPathParams; + params.mEventPathParamsListSize = pathsCount; + params.mEventNumber = mEventNumber; + params.mpAttributePathParamsList = nullptr; + params.mAttributePathParamsListSize = 0; + + if (interactionType == chip::app::ReadClient::InteractionType::Subscribe) + { + params.mMinIntervalFloorSeconds = minInterval; + params.mMaxIntervalCeilingSeconds = maxInterval; + if (mKeepSubscriptions.HasValue()) + { + params.mKeepSubscriptions = mKeepSubscriptions.Value(); + } + } + + mReadClient = std::make_unique(chip::app::InteractionModelEngine::GetInstance(), + device->GetExchangeManager(), mBufferedReadAdapter, interactionType); + return mReadClient->SendRequest(params); + } + + // 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); + } + + std::unique_ptr mReadClient; + chip::app::BufferedReadCallback mBufferedReadAdapter; + + // mFabricFiltered is really only used by the attribute commands, but we end + // up needing it in our class's shared code. + chip::Optional mFabricFiltered; + + // mKeepSubscriptions is really only used by the subscribe commands, but we end + // up needing it in our class's shared code. + chip::Optional mKeepSubscriptions; + chip::Optional mEventNumber; + + CHIP_ERROR mError = CHIP_NO_ERROR; +}; + +class ReadAttribute : public ReportCommand +{ +public: + ReadAttribute(CredentialIssuerCommands * credsIssuerConfig) : ReportCommand("read-by-id", credsIssuerConfig) + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterIds); + AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeIds); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + ReportCommand::AddArguments(); + } + + ReadAttribute(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand("read-by-id", credsIssuerConfig), mClusterIds(1, clusterId) + { + AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeIds); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + ReportCommand::AddArguments(); + } + + ReadAttribute(chip::ClusterId clusterId, const char * attributeName, chip::AttributeId attributeId, + CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand("read", credsIssuerConfig), + mClusterIds(1, clusterId), mAttributeIds(1, attributeId) + { + AddArgument("attr-name", attributeName); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + ReportCommand::AddArguments(); + } + + ~ReadAttribute() {} + + CHIP_ERROR SendCommand(ChipDevice * device, std::vector endpointIds) override + { + return ReportCommand::ReportAttribute(device, endpointIds, mClusterIds, mAttributeIds, + chip::app::ReadClient::InteractionType::Read, 0, 0, mDataVersion); + } + +private: + std::vector mClusterIds; + std::vector mAttributeIds; + chip::Optional> mDataVersion; +}; + +class SubscribeAttribute : public ReportCommand +{ +public: + SubscribeAttribute(CredentialIssuerCommands * credsIssuerConfig) : ReportCommand("subscribe-by-id", credsIssuerConfig) + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterIds); + AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeIds); + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions); + ReportCommand::AddArguments(); + } + + SubscribeAttribute(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand("subscribe-by-id", credsIssuerConfig), mClusterIds(1, clusterId) + { + AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeIds); + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions); + ReportCommand::AddArguments(); + } + + SubscribeAttribute(chip::ClusterId clusterId, const char * attributeName, chip::AttributeId attributeId, + CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand("subscribe", credsIssuerConfig), + mClusterIds(1, clusterId), mAttributeIds(1, attributeId) + { + AddArgument("attr-name", attributeName); + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); + AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions); + ReportCommand::AddArguments(); + } + + ~SubscribeAttribute() {} + + CHIP_ERROR SendCommand(ChipDevice * device, std::vector endpointIds) override + { + return ReportCommand::ReportAttribute(device, endpointIds, mClusterIds, mAttributeIds, + chip::app::ReadClient::InteractionType::Subscribe, mMinInterval, mMaxInterval, + mDataVersion); + } + + chip::System::Clock::Timeout GetWaitDuration() const override { return ReportCommand::GetWaitDuration(); } + + void OnAttributeSubscription() override + { + // The ReadClient instance can not be released directly into the OnAttributeSubscription + // callback since it happens to be called by ReadClient itself which is doing additional + // work after that. + chip::DeviceLayer::PlatformMgr().ScheduleWork( + [](intptr_t arg) { + auto * command = reinterpret_cast(arg); + if (!command->IsInteractive()) + { + command->mReadClient.reset(); + } + command->SetCommandExitStatus(CHIP_NO_ERROR); + }, + reinterpret_cast(this)); + } + +private: + std::vector mClusterIds; + std::vector mAttributeIds; + + uint16_t mMinInterval; + uint16_t mMaxInterval; + chip::Optional> mDataVersion; +}; + +class ReadEvent : public ReportCommand +{ +public: + ReadEvent(CredentialIssuerCommands * credsIssuerConfig) : ReportCommand("read-event-by-id", credsIssuerConfig) + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterIds); + AddArgument("event-id", 0, UINT32_MAX, &mEventIds); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReportCommand::AddArguments(); + } + + ReadEvent(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand("read-event-by-id", credsIssuerConfig), mClusterIds(1, clusterId) + { + AddArgument("event-id", 0, UINT32_MAX, &mEventIds); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReportCommand::AddArguments(); + } + + ReadEvent(chip::ClusterId clusterId, const char * eventName, chip::EventId eventId, + CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand("read-event", credsIssuerConfig), + mClusterIds(1, clusterId), mEventIds(1, eventId) + { + AddArgument("event-name", eventName); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReportCommand::AddArguments(); + } + + ~ReadEvent() {} + + CHIP_ERROR SendCommand(ChipDevice * device, std::vector endpointIds) override + { + return ReportCommand::ReportEvent(device, endpointIds, mClusterIds, mEventIds, + chip::app::ReadClient::InteractionType::Read); + } + +private: + std::vector mClusterIds; + std::vector mEventIds; +}; + +class SubscribeEvent : public ReportCommand +{ +public: + SubscribeEvent(CredentialIssuerCommands * credsIssuerConfig) : ReportCommand("subscribe-event-by-id", credsIssuerConfig) + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterIds); + AddArgument("event-id", 0, UINT32_MAX, &mEventIds); + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReportCommand::AddArguments(); + } + + SubscribeEvent(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand("subscribe-event-by-id", credsIssuerConfig), mClusterIds(1, clusterId) + { + AddArgument("event-id", 0, UINT32_MAX, &mEventIds); + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReportCommand::AddArguments(); + } + + SubscribeEvent(chip::ClusterId clusterId, const char * eventName, chip::EventId eventId, + CredentialIssuerCommands * credsIssuerConfig) : + ReportCommand("subscribe-event", credsIssuerConfig), + mClusterIds(1, clusterId), mEventIds(1, eventId) + { + AddArgument("attr-name", eventName); + AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); + AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); + AddArgument("keepSubscriptions", 0, 1, &mKeepSubscriptions); + AddArgument("event-min", 0, UINT64_MAX, &mEventNumber); + ReportCommand::AddArguments(); + } + + ~SubscribeEvent() {} + + CHIP_ERROR SendCommand(ChipDevice * device, std::vector endpointIds) override + { + return ReportCommand::ReportEvent(device, endpointIds, mClusterIds, mEventIds, + chip::app::ReadClient::InteractionType::Subscribe, mMinInterval, mMaxInterval); + } + + chip::System::Clock::Timeout GetWaitDuration() const override { return ReportCommand::GetWaitDuration(); } + + void OnEventSubscription() override + { + // The ReadClient instance can not be released directly into the OnEventSubscription + // callback since it happens to be called by ReadClient itself which is doing additional + // work after that. + chip::DeviceLayer::PlatformMgr().ScheduleWork( + [](intptr_t arg) { + auto * command = reinterpret_cast(arg); + if (!command->IsInteractive()) + { + command->mReadClient.reset(); + } + command->SetCommandExitStatus(CHIP_NO_ERROR); + }, + reinterpret_cast(this)); + } + +private: + std::vector mClusterIds; + std::vector mEventIds; + + uint16_t mMinInterval; + uint16_t mMaxInterval; +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/clusters/SubscriptionsCommands.h b/examples/tv-casting-app/tv-casting-common/commands/clusters/SubscriptionsCommands.h new file mode 100644 index 00000000000000..2547b1257a3b95 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/clusters/SubscriptionsCommands.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 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/Commands.h" + +class ShutdownSubscription : public CHIPCommand +{ +public: + ShutdownSubscription(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("shutdown-subscription", credsIssuerConfig) + { + AddArgument("subscription-id", 0, UINT64_MAX, &mSubscriptionId); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + CHIP_ERROR err = chip::app::InteractionModelEngine::GetInstance()->ShutdownSubscription(mSubscriptionId); + SetCommandExitStatus(err); + return CHIP_NO_ERROR; + } + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } + +private: + uint64_t mSubscriptionId; +}; + +class ShutdownSubscriptions : public CHIPCommand +{ +public: + ShutdownSubscriptions(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("shutdown-subscriptions", credsIssuerConfig) + { + AddArgument("fabric-index", 0, UINT64_MAX, &mFabricIndex); + AddArgument("node-id", 0, UINT64_MAX, &mNodeId); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + CHIP_ERROR err = chip::app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(mFabricIndex, mNodeId); + SetCommandExitStatus(err); + return CHIP_NO_ERROR; + } + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } + +private: + chip::FabricIndex mFabricIndex; + chip::NodeId mNodeId; +}; + +void registerClusterSubscriptions(Commands & commands, CredentialIssuerCommands * credsIssuerConfig) +{ + const char * clusterName = "Subscriptions"; + + commands_list clusterCommands = { + make_unique(credsIssuerConfig), // + make_unique(credsIssuerConfig), // + }; + + commands.Register(clusterName, clusterCommands); +} diff --git a/examples/tv-casting-app/tv-casting-common/commands/clusters/WriteAttributeCommand.h b/examples/tv-casting-app/tv-casting-common/commands/clusters/WriteAttributeCommand.h new file mode 100644 index 00000000000000..43930a3fbecb05 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/clusters/WriteAttributeCommand.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2022 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 WriteAttribute : public ModelCommand, public chip::app::WriteClient::Callback +{ +public: + WriteAttribute(CredentialIssuerCommands * credsIssuerConfig) : ModelCommand("write-by-id", credsIssuerConfig) + { + AddArgument("cluster-id", 0, UINT32_MAX, &mClusterId); + AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeId); + AddArgument("attribute-value", &mAttributeValue); + AddArgument("timedInteractionTimeoutMs", 0, UINT16_MAX, &mTimedInteractionTimeoutMs); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); + AddArgument("suppressResponse", 0, 1, &mSuppressResponse); + ModelCommand::AddArguments(); + } + + WriteAttribute(chip::ClusterId clusterId, CredentialIssuerCommands * credsIssuerConfig) : + ModelCommand("write-by-id", credsIssuerConfig), mClusterId(clusterId) + { + AddArgument("attribute-id", 0, UINT32_MAX, &mAttributeId); + AddArgument("attribute-value", &mAttributeValue); + AddArgument("timedInteractionTimeoutMs", 0, UINT16_MAX, &mTimedInteractionTimeoutMs); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); + AddArgument("suppressResponse", 0, 1, &mSuppressResponse); + ModelCommand::AddArguments(); + } + + WriteAttribute(const char * attributeName, CredentialIssuerCommands * credsIssuerConfig) : + ModelCommand("write", credsIssuerConfig) + { + AddArgument("timedInteractionTimeoutMs", 0, UINT16_MAX, &mTimedInteractionTimeoutMs); + AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); + AddArgument("suppressResponse", 0, 1, &mSuppressResponse); + } + + ~WriteAttribute() {} + + CHIP_ERROR SendCommand(ChipDevice * device, std::vector endpointIds) override + { + return WriteAttribute::SendCommand(device, endpointIds.at(0), mClusterId, mAttributeId, mAttributeValue); + } + + CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex) override + { + return WriteAttribute::SendGroupCommand(groupId, fabricIndex, mClusterId, mAttributeId, mAttributeValue); + } + + /////////// 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) + { + ChipLogError(chipTool, "Response Failure: %s", chip::ErrorStr(error)); + mError = error; + } + } + + void OnError(const chip::app::WriteClient * client, CHIP_ERROR error) override + { + ChipLogProgress(chipTool, "Error: %s", chip::ErrorStr(error)); + mError = error; + } + + void OnDone(chip::app::WriteClient * client) override + { + mWriteClient.reset(); + SetCommandExitStatus(mError); + } + + template + CHIP_ERROR SendCommand(ChipDevice * device, chip::EndpointId endpointId, chip::ClusterId clusterId, + chip::AttributeId attributeId, const T & value) + { + ChipLogProgress(chipTool, "Sending WriteAttribute to cluster " ChipLogFormatMEI " on endpoint %u", + ChipLogValueMEI(clusterId), endpointId); + chip::app::AttributePathParams attributePathParams; + if (!device->GetSecureSession().Value()->IsGroupSession()) + { + attributePathParams.mEndpointId = endpointId; + } + attributePathParams.mClusterId = clusterId; + attributePathParams.mAttributeId = attributeId; + + mWriteClient = std::make_unique(device->GetExchangeManager(), this, mTimedInteractionTimeoutMs, + mSuppressResponse.ValueOr(false)); + + ReturnErrorOnFailure(mWriteClient->EncodeAttribute(attributePathParams, value, mDataVersion)); + + return mWriteClient->SendWriteRequest(device->GetSecureSession().Value()); + } + + template + CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex, chip::ClusterId clusterId, + chip::AttributeId attributeId, const T & value) + { + + chip::app::AttributePathParams attributePathParams; + attributePathParams.mClusterId = clusterId; + attributePathParams.mAttributeId = attributeId; + + chip::Messaging::ExchangeManager * exchangeManager = chip::app::InteractionModelEngine::GetInstance()->GetExchangeManager(); + + ChipLogDetail(chipTool, "Sending Write Attribute to Group %u, on Fabric %x, for cluster %u with attributeId %u", groupId, + fabricIndex, clusterId, attributeId); + + auto writeClient = chip::Platform::MakeUnique(exchangeManager, this, mTimedInteractionTimeoutMs); + VerifyOrReturnError(writeClient != nullptr, CHIP_ERROR_NO_MEMORY); + ReturnErrorOnFailure(writeClient->EncodeAttribute(attributePathParams, value, mDataVersion)); + + chip::Transport::OutgoingGroupSession session(groupId, fabricIndex); + ReturnErrorOnFailure(writeClient->SendWriteRequest(chip::SessionHandle(session))); + writeClient.release(); + + return CHIP_NO_ERROR; + } + +private: + chip::ClusterId mClusterId; + chip::AttributeId mAttributeId; + CHIP_ERROR mError = CHIP_NO_ERROR; + chip::Optional mTimedInteractionTimeoutMs; + chip::Optional mDataVersion = chip::NullOptional; + chip::Optional mSuppressResponse; + CustomArgument mAttributeValue; + std::unique_ptr mWriteClient; +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.cpp b/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.cpp new file mode 100644 index 00000000000000..78f6066ae3b68c --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021-2022 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 + +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +#include "TraceHandlers.h" +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + +CHIP_ERROR CHIPCommand::Run() +{ + CHIP_ERROR err = StartWaiting(GetWaitDuration()); + return err; +} + +void CHIPCommand::StartTracing() +{ +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + chip::trace::InitTrace(); + + if (mTraceFile.HasValue()) + { + chip::trace::SetTraceStream(new chip::trace::TraceStreamFile(mTraceFile.Value())); + } + else if (mTraceLog.HasValue() && mTraceLog.Value()) + { + chip::trace::SetTraceStream(new chip::trace::TraceStreamLog()); + } +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +} + +void CHIPCommand::StopTracing() +{ +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + chip::trace::DeInitTrace(); +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +} + +void CHIPCommand::RunQueuedCommand(intptr_t commandArg) +{ + auto * command = reinterpret_cast(commandArg); + CHIP_ERROR err = command->RunCommand(); + if (err != CHIP_NO_ERROR) + { + command->SetCommandExitStatus(err); + } +} + +static void OnResponseTimeout(chip::System::Layer *, void * appState) +{ + (reinterpret_cast(appState))->SetCommandExitStatus(CHIP_ERROR_TIMEOUT); +} + +CHIP_ERROR CHIPCommand::StartWaiting(chip::System::Clock::Timeout duration) +{ + chip::DeviceLayer::PlatformMgr().ScheduleWork(RunQueuedCommand, reinterpret_cast(this)); + ReturnLogErrorOnFailure(chip::DeviceLayer::SystemLayer().StartTimer(duration, OnResponseTimeout, this)); + return mCommandExitStatus; +} + +void CHIPCommand::StopWaiting() +{ + Shutdown(); +} diff --git a/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.h b/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.h new file mode 100644 index 00000000000000..53d6e0fb45f471 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/common/CHIPCommand.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2021 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 "Command.h" +#include +#include +#include + +#pragma once + +class PersistentStorage; + +constexpr const char kIdentityAlpha[] = "alpha"; +constexpr const char kIdentityBeta[] = "beta"; +constexpr const 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. +constexpr const char kIdentityNull[] = "null-fabric-commissioner"; + +class CHIPCommand : public Command +{ +public: + using ChipDevice = ::chip::DeviceProxy; + 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 = 5; + static constexpr uint16_t kMaxGroupKeysPerFabric = 8; + + CHIPCommand(const char * commandName, CredentialIssuerCommands * credIssuerCmds) : + Command(commandName) // , mCredIssuerCmds(credIssuerCmds) + { +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + AddArgument("trace_file", &mTraceFile); + AddArgument("trace_log", 0, 1, &mTraceLog); +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + } + + /////////// Command Interface ///////// + CHIP_ERROR Run() override; + + void SetCommandExitStatus(CHIP_ERROR status) + { + mCommandExitStatus = 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, in case any work needs to be done after the event + // loop has been stopped. + virtual void Shutdown() {} + +private: + static void RunQueuedCommand(intptr_t commandArg); + + CHIP_ERROR mCommandExitStatus = CHIP_ERROR_INTERNAL; + + CHIP_ERROR StartWaiting(chip::System::Clock::Timeout seconds); + void StopWaiting(); + + void StartTracing(); + void StopTracing(); + +#if CHIP_CONFIG_TRANSPORT_TRACE_ENABLED + chip::Optional mTraceFile; + chip::Optional mTraceLog; +#endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/common/Command.cpp b/examples/tv-casting-app/tv-casting-common/commands/common/Command.cpp new file mode 100644 index 00000000000000..b67880b2f2f38e --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/common/Command.cpp @@ -0,0 +1,747 @@ +/* + * Copyright (c) 2020 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 "platform/PlatformManager.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +constexpr const 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 (size_t i = 0; i < mArgs.size(); i++) + { + if (mArgs[i].isOptional()) + { + optionalArgsCount++; + } + else + { + mandatoryArgsCount++; + argvExtraArgsCount--; + } + } + + VerifyOrExit((size_t)(argc) >= mandatoryArgsCount && (argvExtraArgsCount == 0 || (argvExtraArgsCount && optionalArgsCount)), + ChipLogError(chipTool, "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(chipTool, "InitArgs: Optional argument %s missing value.", argv[i])); + if (!InitArgument(j, argv[i + 1])) + { + ExitNow(); + } + } + } + VerifyOrExit(found, ChipLogError(chipTool, "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(chipTool, "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(chipTool, "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 (strcmp(argValue, "null") == 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); + switch (arg.type) + { + case ArgumentType::Complex: { + 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::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::Attribute: { + if (arg.isOptional() || arg.isNullable()) + { + isValidArgument = false; + } + else + { + char * value = reinterpret_cast(arg.value); + isValidArgument = (strcmp(argValue, value) == 0); + } + break; + } + + 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 + // 0x followed by the hex-encoded bytes. + size_t argLen = strlen(argValue); + static constexpr char hexPrefix[] = "hex:"; + constexpr size_t prefixLen = ArraySize(hexPrefix) - 1; // Don't count the null + if (strncmp(argValue, hexPrefix, prefixLen) == 0) + { + // 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; + if (!buffer.Calloc(argLen)) // Bigger than needed, but it's fine. + { + return false; + } + + size_t octetCount = chip::Encoding::HexToBytes(argValue + prefixLen, argLen - prefixLen, buffer.Get(), argLen); + if (octetCount == 0) + { + 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. + static constexpr char strPrefix[] = "str:"; + constexpr size_t strPrefixLen = ArraySize(strPrefix) - 1; // Don't count the null + if (strncmp(argValue, strPrefix, strPrefixLen) == 0) + { + // Skip the prefix + argValue += strPrefixLen; + argLen -= strPrefixLen; + } + *value = chip::ByteSpan(chip::Uint8::from_char(argValue), argLen); + return true; + }); + break; + } + + case ArgumentType::Bool: + case ArgumentType::Number_uint8: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * 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; + }); + 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) { + std::stringstream ss; + ss << argValue; + ss >> *value; + return (!ss.fail() && ss.eof()); + }); + break; + } + + case ArgumentType::Double: { + isValidArgument = HandleNullableOptional(arg, argValue, [&](auto * value) { + 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(chipTool, "InitArgs: Invalid argument %s: %s", arg.name, argValue); + } + + return isValidArgument; +} + +size_t Command::AddArgument(const char * name, const char * value, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::Attribute; + arg.name = name; + arg.value = const_cast(reinterpret_cast(value)); + arg.flags = flags; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, char ** value, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::String; + arg.name = name; + arg.value = reinterpret_cast(value); + arg.flags = flags; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, chip::CharSpan * value, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::CharString; + arg.name = name; + arg.value = reinterpret_cast(value); + arg.flags = flags; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, chip::ByteSpan * value, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::OctetString; + arg.name = name; + arg.value = reinterpret_cast(value); + arg.flags = flags; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, AddressWithInterface * out, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::Address; + arg.name = name; + arg.value = reinterpret_cast(out); + arg.flags = flags; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, std::vector * value) +{ + Argument arg; + arg.type = ArgumentType::Vector16; + arg.name = name; + arg.value = static_cast(value); + arg.min = min; + arg.max = max; + arg.flags = 0; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, std::vector * value) +{ + Argument arg; + arg.type = ArgumentType::Vector32; + arg.name = name; + arg.value = static_cast(value); + arg.min = min; + arg.max = max; + arg.flags = 0; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional> * value) +{ + Argument arg; + arg.type = ArgumentType::Vector32; + arg.name = name; + arg.value = static_cast(value); + arg.min = min; + arg.max = max; + arg.flags = Argument::kOptional; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, ComplexArgument * value) +{ + Argument arg; + arg.type = ArgumentType::Complex; + arg.name = name; + arg.value = static_cast(value); + arg.flags = 0; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, CustomArgument * value) +{ + Argument arg; + arg.type = ArgumentType::Custom; + arg.name = name; + arg.value = const_cast(reinterpret_cast(value)); + arg.flags = 0; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, float min, float max, float * out, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::Float; + arg.name = name; + arg.value = reinterpret_cast(out); + arg.flags = flags; + // 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, uint8_t flags) +{ + Argument arg; + arg.type = ArgumentType::Double; + arg.name = name; + arg.value = reinterpret_cast(out); + arg.flags = flags; + // 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, uint8_t flags) +{ + Argument arg; + arg.type = type; + arg.name = name; + arg.value = out; + arg.min = min; + arg.max = max; + arg.flags = flags; + + return AddArgumentToList(std::move(arg)); +} + +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, void * out, 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; + + 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::GetAttribute(void) const +{ + size_t argsCount = mArgs.size(); + for (size_t i = 0; i < argsCount; i++) + { + Argument arg = mArgs.at(i); + if (arg.type == ArgumentType::Attribute) + { + return reinterpret_cast(arg.value); + } + } + + return nullptr; +} + +const char * Command::GetEvent(void) const +{ + size_t argsCount = mArgs.size(); + for (size_t i = 0; i < argsCount; i++) + { + Argument arg = mArgs.at(i); + if (arg.type == ArgumentType::Attribute) + { + return reinterpret_cast(arg.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; +} + +void Command::ResetArguments() +{ + ChipLogError(AppServer, " -- Command::ResetArguments"); + for (size_t i = 0; i < mArgs.size(); i++) + { + const Argument arg = mArgs[i]; + const ArgumentType type = arg.type; + const uint8_t flags = arg.flags; + if (type == ArgumentType::Vector16 && flags != Argument::kOptional) + { + auto vectorArgument = static_cast *>(arg.value); + vectorArgument->clear(); + } + else if (type == ArgumentType::Vector32 && flags != Argument::kOptional) + { + auto vectorArgument = static_cast *>(arg.value); + vectorArgument->clear(); + } + else if (type == ArgumentType::Vector32 && flags == Argument::kOptional) + { + auto optionalArgument = static_cast> *>(arg.value); + if (optionalArgument->HasValue()) + { + optionalArgument->Value().clear(); + } + } + } +} diff --git a/examples/tv-casting-app/tv-casting-common/commands/common/Command.h b/examples/tv-casting-app/tv-casting-common/commands/common/Command.h new file mode 100644 index 00000000000000..975c90297affc4 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/common/Command.h @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2020 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/ComplexArgument.h" +#include "../clusters/CustomArgument.h" +#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, + Attribute, + Address, + Complex, + Custom, + Vector16, + Vector32, +}; + +struct Argument +{ + const char * name; + ArgumentType type; + int64_t min; + uint64_t max; + void * value; + uint8_t flags; + + enum + { + kOptional = (1 << 0), + kNullable = (1 << 1), + }; + + bool isOptional() const { return flags & kOptional; } + bool isNullable() const { return flags & kNullable; } +}; + +class Command +{ +public: + struct AddressWithInterface + { + ::chip::Inet::IPAddress address; + ::chip::Inet::InterfaceId interfaceId; + }; + + Command(const char * commandName) : mName(commandName) {} + virtual ~Command() {} + + const char * GetName(void) const { return mName; } + const char * GetAttribute(void) const; + const char * GetEvent(void) const; + const char * GetArgumentName(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); + size_t AddArgument(const char * name, const char * value, uint8_t flags = 0); + /** + * @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 + * @returns The number of arguments currently added to the command + */ + size_t AddArgument(const char * name, char ** value, uint8_t flags = 0); + + /** + * Add an octet string command argument + */ + size_t AddArgument(const char * name, chip::ByteSpan * value, uint8_t flags = 0); + size_t AddArgument(const char * name, chip::Span * value, uint8_t flags = 0); + size_t AddArgument(const char * name, AddressWithInterface * out, uint8_t flags = 0); + size_t AddArgument(const char * name, ComplexArgument * value); + size_t AddArgument(const char * name, CustomArgument * value); + size_t AddArgument(const char * name, int64_t min, uint64_t max, bool * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Bool, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, int8_t * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_int8, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, int16_t * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_int16, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, int32_t * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_int32, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, int64_t * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_int64, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint8_t * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint8, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint16_t * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint16, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint32_t * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint32, flags); + } + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint64_t * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint64, flags); + } + + size_t AddArgument(const char * name, float min, float max, float * out, uint8_t flags = 0); + size_t AddArgument(const char * name, double min, double max, double * out, uint8_t flags = 0); + + size_t AddArgument(const char * name, int64_t min, uint64_t max, std::vector * value); + size_t AddArgument(const char * name, int64_t min, uint64_t max, std::vector * value); + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional> * value); + + template ::value>> + size_t AddArgument(const char * name, int64_t min, uint64_t max, T * out, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast *>(out), flags); + } + + template + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::BitFlags * out, 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), flags); + } + + template + size_t AddArgument(const char * name, chip::Optional * value) + { + return AddArgument(name, reinterpret_cast(value), Argument::kOptional); + } + + template + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional * value) + { + return AddArgument(name, min, max, reinterpret_cast(value), Argument::kOptional); + } + + template + size_t AddArgument(const char * name, chip::app::DataModel::Nullable * value, uint8_t flags = 0) + { + return AddArgument(name, reinterpret_cast(value), flags | Argument::kNullable); + } + + template + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::app::DataModel::Nullable * value, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(value), flags | Argument::kNullable); + } + + size_t AddArgument(const char * name, float min, float max, chip::app::DataModel::Nullable * value, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(value), flags | Argument::kNullable); + } + + size_t AddArgument(const char * name, double min, double max, chip::app::DataModel::Nullable * value, uint8_t flags = 0) + { + return AddArgument(name, min, max, reinterpret_cast(value), flags | Argument::kNullable); + } + + void ResetArguments(); + + virtual CHIP_ERROR Run() = 0; + + bool IsInteractive() { return mIsInteractive; } + + CHIP_ERROR RunAsInteractive() + { + mIsInteractive = true; + return Run(); + } + +private: + bool InitArgument(size_t argIndex, char * argValue); + size_t AddArgument(const char * name, int64_t min, uint64_t max, void * out, ArgumentType type, uint8_t flags); + size_t AddArgument(const char * name, int64_t min, uint64_t max, void * out, 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; + bool mIsInteractive = false; + std::vector mArgs; +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/common/CommandInvoker.h b/examples/tv-casting-app/tv-casting-common/commands/common/CommandInvoker.h new file mode 100644 index 00000000000000..74284f2515517a --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/common/CommandInvoker.h @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2021 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 { +namespace detail { +template +class ResponseReceiver : public app::CommandSender::Callback +{ +public: + using SuccessCallback = void (*)(void * context, const ResponseType & data); + using FailureCallback = void (*)(void * context, CHIP_ERROR err); + using DoneCallback = void (*)(void * context); + + virtual ~ResponseReceiver() {} + +protected: + ResponseReceiver(void * aContext, SuccessCallback aOnSuccess, FailureCallback aOnError, DoneCallback aOnDone) : + mContext(aContext), mOnSuccess(aOnSuccess), mOnError(aOnError), mOnDone(aOnDone) + {} + + inline void OnResponse(app::CommandSender * aCommandSender, const app::ConcreteCommandPath & aPath, + const app::StatusIB & aStatus, TLV::TLVReader * aData) override; + + void OnError(const app::CommandSender * aCommandSender, CHIP_ERROR aError) override { mOnError(mContext, aError); } + + void OnDone(app::CommandSender * aCommandSender) override + { + if (mOnDone != nullptr) + { + mOnDone(mContext); + } + + Platform::Delete(aCommandSender); + Platform::Delete(this); + } + +private: + void * mContext; + SuccessCallback mOnSuccess; + FailureCallback mOnError; + DoneCallback mOnDone = nullptr; +}; + +template +class CommandInvoker final : public ResponseReceiver +{ + using Super = ResponseReceiver; + +public: + CommandInvoker(void * aContext, typename Super::SuccessCallback aOnSuccess, typename Super::FailureCallback aOnError, + typename Super::DoneCallback aOnDone) : + Super(aContext, aOnSuccess, aOnError, aOnDone) + {} + + /** + * Use of CommandInvoker looks as follows: + * + * auto invoker = CommandInvoker::Alloc(args); + * VerifyOrReturnError(invoker != nullptr, CHIP_ERROR_NO_MEMORY); + * ReturnErrorOnFailure(invoker->InvokeCommand(args)); + * invoker.release(); // The invoker will deallocate itself now. + */ + static auto Alloc(void * aContext, typename Super::SuccessCallback aOnSuccess, typename Super::FailureCallback aOnError, + typename Super::DoneCallback aOnDone) + { + return Platform::MakeUnique(aContext, aOnSuccess, aOnError, aOnDone); + } + + CHIP_ERROR InvokeCommand(DeviceProxy * aDevice, EndpointId aEndpoint, const RequestType & aRequestData, + const Optional & aTimedInvokeTimeoutMs) + { + app::CommandPathParams commandPath = { aEndpoint, 0 /* groupId */, RequestType::GetClusterId(), RequestType::GetCommandId(), + (app::CommandPathFlags::kEndpointIdValid) }; + auto commandSender = + Platform::MakeUnique(this, aDevice->GetExchangeManager(), aTimedInvokeTimeoutMs.HasValue()); + VerifyOrReturnError(commandSender != nullptr, CHIP_ERROR_NO_MEMORY); + + ReturnErrorOnFailure(commandSender->AddRequestDataNoTimedCheck(commandPath, aRequestData, aTimedInvokeTimeoutMs)); + ReturnErrorOnFailure(commandSender->SendCommandRequest(aDevice->GetSecureSession().Value())); + commandSender.release(); + return CHIP_NO_ERROR; + } + + CHIP_ERROR InvokeGroupCommand(Messaging::ExchangeManager * exchangeManager, FabricIndex fabric, GroupId groupId, + const RequestType & aRequestData) + { + app::CommandPathParams commandPath = { 0 /* endpoint */, groupId, RequestType::GetClusterId(), RequestType::GetCommandId(), + (app::CommandPathFlags::kGroupIdValid) }; + + auto commandSender = Platform::MakeUnique(this, exchangeManager); + VerifyOrReturnError(commandSender != nullptr, CHIP_ERROR_NO_MEMORY); + + ReturnErrorOnFailure(commandSender->AddRequestData(commandPath, aRequestData)); + + Transport::OutgoingGroupSession session(groupId, fabric); + + // this (invoker) and commandSender will be deleted by the onDone call before the return of SendGroupCommandRequest + // this (invoker) should not be used after the SendGroupCommandRequest call + ReturnErrorOnFailure(commandSender->SendGroupCommandRequest(SessionHandle(session))); + + // this (invoker) and commandSender are already deleted and are not to be used + commandSender.release(); + + return CHIP_NO_ERROR; + } +}; + +template +void ResponseReceiver::OnResponse(app::CommandSender * aCommandSender, const app::ConcreteCommandPath & aPath, + const app::StatusIB & aStatus, TLV::TLVReader * aData) +{ + ResponseType response; + CHIP_ERROR err = CHIP_NO_ERROR; + + // + // We're expecting response data in this variant of OnResponse. Consequently, aReader should always be + // non-null. If it is, it means we received a success status code instead, which is not what was expected. + // + VerifyOrExit(aData != nullptr, err = CHIP_ERROR_SCHEMA_MISMATCH); + + // + // Validate that the data response we received matches what we expect in terms of its cluster and command IDs. + // + VerifyOrExit(aPath.mClusterId == ResponseType::GetClusterId() && aPath.mCommandId == ResponseType::GetCommandId(), + err = CHIP_ERROR_SCHEMA_MISMATCH); + + err = app::DataModel::Decode(*aData, response); + SuccessOrExit(err); + + mOnSuccess(mContext, response); + +exit: + if (err != CHIP_NO_ERROR) + { + mOnError(mContext, err); + } +} + +template <> +inline void ResponseReceiver::OnResponse(app::CommandSender * aCommandSender, + const app::ConcreteCommandPath & aPath, + const app::StatusIB & aStatus, TLV::TLVReader * aData) +{ + // + // If we got a valid reader, it means we received response data that we were not expecting to receive. + // + if (aData != nullptr) + { + mOnError(mContext, CHIP_ERROR_SCHEMA_MISMATCH); + return; + } + + app::DataModel::NullObjectType nullResp; + mOnSuccess(mContext, nullResp); +} + +} // namespace detail + +template +CHIP_ERROR InvokeCommand(DeviceProxy * aDevice, void * aContext, + typename detail::CommandInvoker::SuccessCallback aSuccessCallback, + typename detail::CommandInvoker::FailureCallback aFailureCallback, EndpointId aEndpoint, + const RequestType & aRequestData, const Optional & aTimedInvokeTimeoutMs) +{ + auto invoker = + detail::CommandInvoker::Alloc(aContext, aSuccessCallback, aFailureCallback, nullptr /* aDoneCallback */); + VerifyOrReturnError(invoker != nullptr, CHIP_ERROR_NO_MEMORY); + ReturnErrorOnFailure(invoker->InvokeCommand(aDevice, aEndpoint, aRequestData, aTimedInvokeTimeoutMs)); + invoker.release(); + return CHIP_NO_ERROR; +} + +template +CHIP_ERROR InvokeCommand(DeviceProxy * aDevice, void * aContext, + typename detail::CommandInvoker::SuccessCallback aSuccessCallback, + typename detail::CommandInvoker::FailureCallback aFailureCallback, EndpointId aEndpoint, + const RequestType & aRequestData, uint16_t aTimedInvokeTimeoutMs) +{ + return InvokeCommand(aDevice, aContext, aSuccessCallback, aFailureCallback, aEndpoint, aRequestData, + MakeOptional(aTimedInvokeTimeoutMs)); +} + +template = 0> +CHIP_ERROR InvokeCommand(DeviceProxy * aDevice, void * aContext, + typename detail::CommandInvoker::SuccessCallback aSuccessCallback, + typename detail::CommandInvoker::FailureCallback aFailureCallback, EndpointId aEndpoint, + const RequestType & aRequestData) +{ + return InvokeCommand(aDevice, aContext, aSuccessCallback, aFailureCallback, aEndpoint, aRequestData, NullOptional); +} + +// Group commands can't do timed invoke in a meaningful way. +template = 0> +CHIP_ERROR InvokeGroupCommand(DeviceProxy * aDevice, void * aContext, + typename detail::CommandInvoker::SuccessCallback aSuccessCallback, + typename detail::CommandInvoker::FailureCallback aFailureCallback, + typename detail::CommandInvoker::DoneCallback aDoneCallback, GroupId groupId, + const RequestType & aRequestData) +{ + auto invoker = detail::CommandInvoker::Alloc(aContext, aSuccessCallback, aFailureCallback, aDoneCallback); + VerifyOrReturnError(invoker != nullptr, CHIP_ERROR_NO_MEMORY); + + // invoker will be deleted by the onDone call before the return of InvokeGroupCommand + // invoker should not be used after the InvokeGroupCommand call + // + // We assume the aDevice already has a Case session which is way we can use he established Secure Session + ReturnErrorOnFailure(invoker->InvokeGroupCommand(aDevice->GetExchangeManager(), + aDevice->GetSecureSession().Value()->GetFabricIndex(), groupId, aRequestData)); + + // invoker is already deleted and is not to be used + invoker.release(); + return CHIP_NO_ERROR; +} + +} // namespace Controller +} // namespace chip diff --git a/examples/tv-casting-app/tv-casting-common/commands/common/Commands.cpp b/examples/tv-casting-app/tv-casting-common/commands/common/Commands.cpp new file mode 100644 index 00000000000000..0fbaeeefe7b199 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/common/Commands.cpp @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2020 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 + +void Commands::Register(const char * clusterName, commands_list commandsList) +{ + for (auto & command : commandsList) + { + mClusters[clusterName].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))); + + // err = mStorage.Init(); + // VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Init Storage failure: %s", chip::ErrorStr(err))); + + // chip::Logging::SetLogFilter(mStorage.GetLoggingLevel()); + + err = RunCommand(argc, argv); + VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(chipTool, "Run command failure: %s", chip::ErrorStr(err))); + +exit: + return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int Commands::RunInteractive(int argc, char ** argv) +{ + CHIP_ERROR err = RunCommand(argc, argv, true); + return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive) +{ + std::map::iterator cluster; + Command * command = nullptr; + + if (argc <= 1) + { + ChipLogError(chipTool, "Missing cluster name"); + ShowClusters(argv[0]); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + cluster = GetCluster(argv[1]); + if (cluster == mClusters.end()) + { + ChipLogError(chipTool, "Unknown cluster: %s", argv[1]); + ShowClusters(argv[0]); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (argc <= 2) + { + ChipLogError(chipTool, "Missing command name"); + ShowCluster(argv[0], argv[1], cluster->second); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (!IsGlobalCommand(argv[2])) + { + command = GetCommand(cluster->second, argv[2]); + if (command == nullptr) + { + ChipLogError(chipTool, "Unknown command: %s", argv[2]); + ShowCluster(argv[0], argv[1], cluster->second); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + else if (IsEventCommand(argv[2])) + { + if (argc <= 3) + { + ChipLogError(chipTool, "Missing event name"); + ShowClusterEvents(argv[0], argv[1], argv[2], cluster->second); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + command = GetGlobalCommand(cluster->second, argv[2], argv[3]); + if (command == nullptr) + { + ChipLogError(chipTool, "Unknown event: %s", argv[3]); + ShowClusterEvents(argv[0], argv[1], argv[2], cluster->second); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + if (argc <= 3) + { + ChipLogError(chipTool, "Missing attribute name"); + ShowClusterAttributes(argv[0], argv[1], argv[2], cluster->second); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + command = GetGlobalCommand(cluster->second, argv[2], argv[3]); + if (command == nullptr) + { + ChipLogError(chipTool, "Unknown attribute: %s", argv[3]); + ShowClusterAttributes(argv[0], argv[1], argv[2], cluster->second); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + if (!command->InitArguments(argc - 3, &argv[3])) + { + ShowCommand(argv[0], argv[1], command); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + return interactive ? command->RunAsInteractive() : command->Run(); +} + +std::map::iterator Commands::GetCluster(std::string clusterName) +{ + for (auto & cluster : mClusters) + { + std::string key(cluster.first); + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + if (key.compare(clusterName) == 0) + { + return mClusters.find(cluster.first); + } + } + + return mClusters.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("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::ShowClusters(std::string executable) +{ + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s cluster_name command_name [param1 param2 ...]\n", executable.c_str()); + fprintf(stderr, "\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + fprintf(stderr, " | Clusters: |\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + for (auto & cluster : mClusters) + { + std::string clusterName(cluster.first); + std::transform(clusterName.begin(), clusterName.end(), clusterName.begin(), + [](unsigned char c) { return std::tolower(c); }); + fprintf(stderr, " | * %-82s|\n", clusterName.c_str()); + } + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); +} + +void Commands::ShowCluster(std::string executable, std::string clusterName, CommandsVector & commands) +{ + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s %s command_name [param1 param2 ...]\n", executable.c_str(), clusterName.c_str()); + fprintf(stderr, "\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + fprintf(stderr, " | Commands: |\n"); + fprintf(stderr, " +-------------------------------------------------------------------------------------+\n"); + bool readCommand = false; + bool writeCommand = 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(), "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()); + } + } + 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; + arguments += command->GetName(); + + size_t argumentsCount = command->GetArgumentsCount(); + for (size_t i = 0; i < argumentsCount; i++) + { + arguments += " "; + bool isOptional = command->GetArgumentIsOptional(i); + if (isOptional) + { + arguments += "[--"; + } + arguments += command->GetArgumentName(i); + if (isOptional) + { + arguments += "]"; + } + } + fprintf(stderr, " %s %s %s\n", executable.c_str(), clusterName.c_str(), arguments.c_str()); +} diff --git a/examples/tv-casting-app/tv-casting-common/commands/common/Commands.h b/examples/tv-casting-app/tv-casting-common/commands/common/Commands.h new file mode 100644 index 00000000000000..a179bc33e53ff7 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/common/Commands.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 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 "../../config/PersistentStorage.h" +#include "Command.h" +#include + +class Commands +{ +public: + using CommandsVector = ::std::vector>; + + void Register(const char * clusterName, commands_list commandsList); + int Run(int argc, char ** argv); + int RunInteractive(int argc, char ** argv); + +private: + CHIP_ERROR RunCommand(int argc, char ** argv, bool interactive = false); + + std::map::iterator GetCluster(std::string clusterName); + 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 ShowClusters(std::string executable); + void ShowCluster(std::string executable, std::string clusterName, CommandsVector & commands); + 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); + + std::map mClusters; + // PersistentStorage mStorage; +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/common/CredentialIssuerCommands.h b/examples/tv-casting-app/tv-casting-common/commands/common/CredentialIssuerCommands.h new file mode 100644 index 00000000000000..951ef86efceb40 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/common/CredentialIssuerCommands.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021-2022 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 + +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; + + virtual chip::Controller::OperationalCredentialsDelegate * GetCredentialIssuer() = 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; +}; diff --git a/examples/tv-casting-app/tv-casting-common/commands/example/ExampleCredentialIssuerCommands.h b/examples/tv-casting-app/tv-casting-common/commands/example/ExampleCredentialIssuerCommands.h new file mode 100644 index 00000000000000..74646c8b5f10ba --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/commands/example/ExampleCredentialIssuerCommands.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021-2022 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 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()); + + setupParams.deviceAttestationVerifier = chip::Credentials::GetDefaultDACVerifier(trustStore); + + return CHIP_NO_ERROR; + } + chip::Controller::OperationalCredentialsDelegate * GetCredentialIssuer() override { return &mOpCredsIssuer; } + 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); + } + +private: + chip::Controller::ExampleOperationalCredentialsIssuer mOpCredsIssuer; +}; diff --git a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h index e046818be34868..2e317606fd79b9 100644 --- a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h +++ b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h @@ -64,6 +64,13 @@ class CastingServer CHIP_ERROR ContentLauncherLaunchURL(const char * contentUrl, const char * contentDisplayStr); static void OnContentLauncherSuccessResponse(void * context, const LaunchResponse::DecodableType & response); static void OnContentLauncherFailureResponse(void * context, CHIP_ERROR error); + static void DeviceEventCallback(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + + NodeId GetVideoPlayerNodeForFabricIndex(FabricIndex fabricIndex); + FabricIndex GetVideoPlayerFabricIndexForNode(NodeId nodeId); + void PrintBindings(); + FabricIndex CurrentFabricIndex() { return mTargetVideoPlayerInfo.GetFabricIndex(); } + void SetDefaultFabricIndex(); private: static CastingServer * castingServer_; diff --git a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp index 6555550999f1ab..9697a7f071cc56 100644 --- a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp +++ b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp @@ -19,7 +19,10 @@ #include "CastingServer.h" CastingServer * CastingServer::castingServer_ = nullptr; -; + +// TODO: Accept these values over CLI +const char * kContentUrl = "https://www.test.com/videoid"; +const char * kContentDisplayStr = "Test video"; CastingServer * CastingServer::GetInstance() { @@ -36,7 +39,6 @@ void CastingServer::InitServer() { return; } - // DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl().Init("/tmp/chip_tv_casting_kvs"); DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl().Init(CHIP_CONFIG_KVS_PATH); // Enter commissioning mode, open commissioning window @@ -47,6 +49,9 @@ void CastingServer::InitServer() // Initialize binding handlers ReturnOnFailure(InitBindingHandlers()); + // Add callback to send Content casting commands after commissioning completes + ReturnOnFailure(DeviceLayer::PlatformMgrImpl().AddEventHandler(DeviceEventCallback, 0)); + mInited = true; } @@ -67,12 +72,13 @@ CHIP_ERROR CastingServer::TargetVideoPlayerInfoInit(NodeId nodeId, FabricIndex f CHIP_ERROR CastingServer::DiscoverCommissioners() { // Send discover commissioners request - return mCommissionableNodeController.DiscoverCommissioners(Dnssd::DiscoveryFilter()); + return mCommissionableNodeController.DiscoverCommissioners( + Dnssd::DiscoveryFilter(Dnssd::DiscoveryFilterType::kDeviceType, static_cast(35))); } CHIP_ERROR CastingServer::OpenBasicCommissioningWindow() { - Server::GetInstance().GetFabricTable().DeleteAllFabrics(); + // Server::GetInstance().GetFabricTable().DeleteAllFabrics(); return Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow(kCommissioningWindowTimeout); } @@ -201,3 +207,110 @@ void CastingServer::OnContentLauncherFailureResponse(void * context, CHIP_ERROR { ChipLogError(AppServer, "ContentLauncher: Default Failure Response: %" CHIP_ERROR_FORMAT, error.Format()); } + +void CastingServer::DeviceEventCallback(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) +{ + if (event->Type == DeviceLayer::DeviceEventType::kBindingsChangedViaCluster) + { + if (CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->IsInitialized()) + { + CastingServer::GetInstance()->ReadServerClustersForNode( + CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->GetNodeId()); + } + } + else if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete) + { + ReturnOnFailure(CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->Initialize( + event->CommissioningComplete.PeerNodeId, event->CommissioningComplete.PeerFabricIndex)); + + CastingServer::GetInstance()->ContentLauncherLaunchURL(kContentUrl, kContentDisplayStr); + } +} + +// given a fabric index, try to determine the video-player nodeId by searching the binding table +NodeId CastingServer::GetVideoPlayerNodeForFabricIndex(FabricIndex fabricIndex) +{ + for (const auto & binding : BindingTable::GetInstance()) + { + ChipLogProgress(NotSpecified, + "Binding type=%d fab=%d nodeId=0x" ChipLogFormatX64 + " groupId=%d local endpoint=%d remote endpoint=%d cluster=" ChipLogFormatMEI, + binding.type, binding.fabricIndex, ChipLogValueX64(binding.nodeId), binding.groupId, binding.local, + binding.remote, ChipLogValueMEI(binding.clusterId.ValueOr(0))); + if (binding.type == EMBER_UNICAST_BINDING && fabricIndex == binding.fabricIndex) + { + ChipLogProgress(NotSpecified, "GetVideoPlayerNodeForFabricIndex nodeId=0x" ChipLogFormatX64, + ChipLogValueX64(binding.nodeId)); + return binding.nodeId; + } + } + ChipLogProgress(NotSpecified, "GetVideoPlayerNodeForFabricIndex no bindings found for fabricIndex=%d", fabricIndex); + return kUndefinedNodeId; +} + +// given a nodeId, try to determine the video-player fabric index by searching the binding table +FabricIndex CastingServer::GetVideoPlayerFabricIndexForNode(NodeId nodeId) +{ + for (const auto & binding : BindingTable::GetInstance()) + { + ChipLogProgress(NotSpecified, + "Binding type=%d fab=%d nodeId=0x" ChipLogFormatX64 + " groupId=%d local endpoint=%d remote endpoint=%d cluster=" ChipLogFormatMEI, + binding.type, binding.fabricIndex, ChipLogValueX64(binding.nodeId), binding.groupId, binding.local, + binding.remote, ChipLogValueMEI(binding.clusterId.ValueOr(0))); + if (binding.type == EMBER_UNICAST_BINDING && nodeId == binding.nodeId) + { + ChipLogProgress(NotSpecified, "GetVideoPlayerFabricIndexForNode fabricIndex=%d nodeId=0x" ChipLogFormatX64, + binding.fabricIndex, ChipLogValueX64(binding.nodeId)); + return binding.fabricIndex; + } + } + ChipLogProgress(NotSpecified, "GetVideoPlayerFabricIndexForNode no bindings found for nodeId=0x" ChipLogFormatX64, + ChipLogValueX64(nodeId)); + return kUndefinedFabricIndex; +} + +void CastingServer::PrintBindings() +{ + for (const auto & binding : BindingTable::GetInstance()) + { + ChipLogProgress(NotSpecified, + "Binding type=%d fab=%d nodeId=0x" ChipLogFormatX64 + " groupId=%d local endpoint=%d remote endpoint=%d cluster=" ChipLogFormatMEI, + binding.type, binding.fabricIndex, ChipLogValueX64(binding.nodeId), binding.groupId, binding.local, + binding.remote, ChipLogValueMEI(binding.clusterId.ValueOr(0))); + } + return; +} + +void CastingServer::SetDefaultFabricIndex() +{ + InitServer(); + + // set fabric to be the first in the list + for (const auto & fb : chip::Server::GetInstance().GetFabricTable()) + { + FabricIndex fabricIndex = fb.GetFabricIndex(); + ChipLogError(AppServer, "Next Fabric index=%d", fabricIndex); + if (!fb.IsInitialized()) + { + ChipLogError(AppServer, " -- Not initialized"); + continue; + } + NodeId myNodeId = fb.GetNodeId(); + ChipLogProgress(NotSpecified, + "---- Current Fabric nodeId=0x" ChipLogFormatX64 " fabricId=0x" ChipLogFormatX64 " fabricIndex=%d", + ChipLogValueX64(myNodeId), ChipLogValueX64(fb.GetFabricId()), fabricIndex); + + NodeId videoPlayerNodeId = GetVideoPlayerNodeForFabricIndex(fabricIndex); + if (videoPlayerNodeId == kUndefinedNodeId) + { + // could not determine video player nodeid for this fabric + continue; + } + + mTargetVideoPlayerInfo.Initialize(videoPlayerNodeId, fabricIndex); + return; + } + ChipLogError(AppServer, " -- No initialized fabrics with video players"); +} diff --git a/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp b/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp index 1e41faa0ccd93d..b155cb81ade32f 100644 --- a/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp +++ b/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp @@ -38,25 +38,8 @@ CHIP_ERROR TargetVideoPlayerInfo::Initialize(NodeId nodeId, FabricIndex fabricIn return CHIP_ERROR_INVALID_FABRIC_ID; } - chip::DeviceProxyInitParams initParams = { - .sessionManager = &(server->GetSecureSessionManager()), - .sessionResumptionStorage = server->GetSessionResumptionStorage(), - .exchangeMgr = &(server->GetExchangeManager()), - .fabricTable = &(server->GetFabricTable()), - .clientPool = &gCASEClientPool, - }; - PeerId peerID = fabric->GetPeerIdForNode(nodeId); - // - // TODO: The code here is assuming that we can create an OperationalDeviceProxy instance and attach it immediately - // to a CASE session that just got established to us by the tv-app. While this will work most of the time, - // this is a dangerous assumption to make since it is entirely possible for that secure session to have been - // evicted in the time since that session was established to the point here when we desire to interact back - // with that peer. If that is the case, our `OnConnected` callback will not get invoked syncronously and - // mOperationalDeviceProxy will still have a value of null, triggering the check below to fail. - // - mOperationalDeviceProxy = nullptr; CHIP_ERROR err = server->GetCASESessionManager()->FindOrEstablishSession(peerID, &mOnConnectedCallback, &mOnConnectionFailureCallback); if (err != CHIP_NO_ERROR)