/* * * Copyright (c) 2023-2024 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "simple-app-helper.h" #include "../tv-casting-common/core/ConnectionCallbacks.h" #include "clusters/Clusters.h" #include "app/clusters/bindings/BindingManager.h" #include #include #include #include #include #include #include #include #include #include // VendorId of the Endpoint on the CastingPlayer that the CastingApp desires to interact with after connection const uint16_t kDesiredEndpointVendorId = 65521; // EndpointId of the Endpoint on the CastingPlayer that the CastingApp desires to interact with after connection using the // Commissioner-Generated passcode commissioning flow const uint8_t kDesiredEndpointId = 1; // Indicates that the Commissioner-Generated passcode commissioning flow is in progress. bool gCommissionerGeneratedPasscodeFlowRunning = false; DiscoveryDelegateImpl * DiscoveryDelegateImpl::_discoveryDelegateImpl = nullptr; bool gAwaitingCommissionerPasscodeInput = false; LinuxCommissionableDataProvider gSimpleAppCommissionableDataProvider; std::shared_ptr targetCastingPlayer; DiscoveryDelegateImpl * DiscoveryDelegateImpl::GetInstance() { if (_discoveryDelegateImpl == nullptr) { _discoveryDelegateImpl = new DiscoveryDelegateImpl(); } return _discoveryDelegateImpl; } void DiscoveryDelegateImpl::HandleOnAdded(matter::casting::memory::Strong player) { ChipLogProgress(AppServer, "DiscoveryDelegateImpl::HandleOnAdded()"); if (commissionersCount == 0) { ChipLogProgress(AppServer, "---- Awaiting user input ----"); ChipLogProgress(AppServer, "Select a discovered Casting Player (start index = 0) to request commissioning."); ChipLogProgress( AppServer, "Include the commissioner-generated-passcode flag to attempt the Commissioner-Generated passcode commissioning flow."); ChipLogProgress(AppServer, "Example 1 Commissionee Passcode: cast request 0"); ChipLogProgress(AppServer, "Example 2 Commissioner Passcode: cast request 0 commissioner-generated-passcode"); ChipLogProgress(AppServer, "---- Awaiting user input ----"); } ChipLogProgress(AppServer, "Discovered CastingPlayer #%d", commissionersCount); ++commissionersCount; player->LogDetail(); } void DiscoveryDelegateImpl::HandleOnUpdated(matter::casting::memory::Strong player) { ChipLogProgress(AppServer, "DiscoveryDelegateImpl::HandleOnUpdated() Updated CastingPlayer with ID: %s", player->GetId()); } void InvokeContentLauncherLaunchURL(matter::casting::memory::Strong endpoint) { // get contentLauncherCluster from the endpoint matter::casting::memory::Strong contentLauncherCluster = endpoint->GetCluster(); VerifyOrReturn(contentLauncherCluster != nullptr); // get the launchURLCommand from the contentLauncherCluster matter::casting::core::Command * launchURLCommand = static_cast *>( contentLauncherCluster->GetCommand(chip::app::Clusters::ContentLauncher::Commands::LaunchURL::Id)); VerifyOrReturn(launchURLCommand != nullptr, ChipLogError(AppServer, "LaunchURL command not found on ContentLauncherCluster")); // create the LaunchURL request chip::app::Clusters::ContentLauncher::Commands::LaunchURL::Type request; request.contentURL = chip::CharSpan::fromCharString(kContentURL); request.displayString = chip::Optional(chip::CharSpan::fromCharString(kContentDisplayStr)); request.brandingInformation = chip::MakeOptional(chip::app::Clusters::ContentLauncher::Structs::BrandingInformationStruct::Type()); // call Invoke on launchURLCommand while passing in success/failure callbacks launchURLCommand->Invoke( request, nullptr, [](void * context, const chip::app::Clusters::ContentLauncher::Commands::LaunchURL::Type::ResponseType & response) { ChipLogProgress(AppServer, "LaunchURL Success with response.data: %.*s", static_cast(response.data.Value().size()), response.data.Value().data()); }, [](void * context, CHIP_ERROR error) { ChipLogError(AppServer, "LaunchURL Failure with err %" CHIP_ERROR_FORMAT, error.Format()); }, chip::MakeOptional(kTimedInvokeCommandTimeoutMs)); // time out after kTimedInvokeCommandTimeoutMs } void ReadApplicationBasicVendorID(matter::casting::memory::Strong endpoint) { // get applicationBasicCluster from the endpoint matter::casting::memory::Strong applicationBasicCluster = endpoint->GetCluster(); VerifyOrReturn(applicationBasicCluster != nullptr); // get the vendorIDAttribute from the applicationBasicCluster matter::casting::core::Attribute * vendorIDAttribute = static_cast *>( applicationBasicCluster->GetAttribute(chip::app::Clusters::ApplicationBasic::Attributes::VendorID::Id)); VerifyOrReturn(vendorIDAttribute != nullptr, ChipLogError(AppServer, "VendorID attribute not found on ApplicationBasicCluster")); // call Read on vendorIDAttribute while passing in success/failure callbacks vendorIDAttribute->Read( nullptr, [](void * context, chip::Optional before, chip::app::Clusters::ApplicationBasic::Attributes::VendorID::TypeInfo::DecodableArgType after) { if (before.HasValue()) { ChipLogProgress(AppServer, "Read VendorID value: %d [Before reading value: %d]", after, before.Value()); } else { ChipLogProgress(AppServer, "Read VendorID value: %d", after); } }, [](void * context, CHIP_ERROR error) { ChipLogError(AppServer, "VendorID Read failure with err %" CHIP_ERROR_FORMAT, error.Format()); }); } void SubscribeToMediaPlaybackCurrentState(matter::casting::memory::Strong endpoint) { // get mediaPlaybackCluster from the endpoint matter::casting::memory::Strong mediaPlaybackCluster = endpoint->GetCluster(); VerifyOrReturn(mediaPlaybackCluster != nullptr); // get the currentStateAttribute from the applicationBasicCluster matter::casting::core::Attribute * currentStateAttribute = static_cast *>( mediaPlaybackCluster->GetAttribute(chip::app::Clusters::MediaPlayback::Attributes::CurrentState::Id)); VerifyOrReturn(currentStateAttribute != nullptr, ChipLogError(AppServer, "CurrentState attribute not found on MediaPlaybackCluster")); // call Subscribe on currentStateAttribute while passing in success/failure callbacks currentStateAttribute->Subscribe( nullptr, [](void * context, chip::Optional before, chip::app::Clusters::MediaPlayback::Attributes::CurrentState::TypeInfo::DecodableArgType after) { if (before.HasValue()) { ChipLogProgress(AppServer, "Read CurrentState value: %d [Before reading value: %d]", static_cast(after), static_cast(before.Value())); } else { ChipLogProgress(AppServer, "Read CurrentState value: %d", static_cast(after)); } }, [](void * context, CHIP_ERROR error) { ChipLogError(AppServer, "CurrentState Read failure with err %" CHIP_ERROR_FORMAT, error.Format()); }, kMinIntervalFloorSeconds, kMaxIntervalCeilingSeconds); } CHIP_ERROR InitCommissionableDataProvider(LinuxCommissionableDataProvider & provider, LinuxDeviceOptions & options) { ChipLogProgress(Discovery, "InitCommissionableDataProvider()"); chip::Optional setupPasscode; if (options.payload.setUpPINCode != 0) { setupPasscode.SetValue(options.payload.setUpPINCode); ChipLogProgress(Discovery, "InitCommissionableDataProvider() using setupPasscode: %d", setupPasscode.Value()); } else if (!options.spake2pVerifier.HasValue()) { uint32_t defaultTestPasscode = 0; chip::DeviceLayer::TestOnlyCommissionableDataProvider TestOnlyCommissionableDataProvider; VerifyOrDie(TestOnlyCommissionableDataProvider.GetSetupPasscode(defaultTestPasscode) == CHIP_NO_ERROR); ChipLogError(Support, "InitCommissionableDataProvider() *** WARNING: Using temporary passcode %u due to no neither --passcode or " "--spake2p-verifier-base64 " "given on command line. This is temporary and will be deprecated. Please update your scripts " "to explicitly configure onboarding credentials. ***", static_cast(defaultTestPasscode)); setupPasscode.SetValue(defaultTestPasscode); options.payload.setUpPINCode = defaultTestPasscode; } else { ChipLogError(Support, "InitCommissionableDataProvider() *** WARNING: Passcode is 0, so will be ignored, and verifier will take " "over. Onboarding payload printed for debug will be invalid, but if the onboarding payload had been given " "properly to the commissioner later, PASE will succeed. ***"); } // Default to the minimum PBKDF iterations (1,000) for this example implementation. For TV devices and TV casting app production // implementations, you should use a higher number of PBKDF iterations to enhance security. The default minimum iterations are // not sufficient against brute-force and rainbow table attacks. Increasing the number of iterations will increase the // computational time required to derive the key. This can slow down the authentication process, especially on devices with // limited processing power like a Raspberry Pi 4. For a production implementation, you should measure the actual performance on // the target device. uint32_t spake2pIterationCount = chip::Crypto::kSpake2p_Min_PBKDF_Iterations; // 1,000 - Hypothetical key derivation time: ~20 milliseconds (ms). // uint32_t spake2pIterationCount = chip::Crypto::kSpake2p_Max_PBKDF_Iterations; // 100,000 - Hypothetical key derivation time: // ~2 seconds. if (options.spake2pIterations == 1000) { spake2pIterationCount = options.spake2pIterations; ChipLogError(Support, "InitCommissionableDataProvider() *** WARNING: PASE PBKDF iterations provided are the minimum allowable: %u. " "Increase for production use to enhance security. ***", static_cast(spake2pIterationCount)); } else if ((options.spake2pIterations > 1000)) { spake2pIterationCount = options.spake2pIterations; ChipLogProgress(Support, "InitCommissionableDataProvider() PASE PBKDF iterations set to: %u.", static_cast(spake2pIterationCount)); } else { ChipLogError(Support, "InitCommissionableDataProvider() *** WARNING: PASE PBKDF iterations set to the minimum allowable: %u. " "Increase for production use to enhance security. ***", static_cast(spake2pIterationCount)); } return provider.Init(options.spake2pVerifier, options.spake2pSalt, spake2pIterationCount, setupPasscode, options.payload.discriminator.GetLongValue()); } void LogEndpointsDetails(const std::vector> & endpoints) { ChipLogProgress(AppServer, "simple-app-helper.cpp::LogEndpointsDetails() Number of Endpoints: %d", static_cast(endpoints.size())); for (const auto & endpoint : endpoints) { endpoint->LogDetail(); } } void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) { ChipLogProgress(AppServer, "simple-app-helper.cpp::ConnectionHandler()"); // For a connection failure, called back with an error and nullptr. VerifyOrReturn( err == CHIP_NO_ERROR, ChipLogError( AppServer, "simple-app-helper.cpp::ConnectionHandler(): Failed to connect to CastingPlayer (ID: %s) with err %" CHIP_ERROR_FORMAT, targetCastingPlayer->GetId(), err.Format())); if (gCommissionerGeneratedPasscodeFlowRunning) { ChipLogProgress(AppServer, "simple-app-helper.cpp::ConnectionHandler(): Successfully connected to CastingPlayer (ID: %s) using " "Commissioner-Generated passcode", castingPlayer->GetId()); ChipLogProgress(AppServer, "simple-app-helper.cpp::ConnectionHandler(): Desired Endpoint ID for demo interactions: 1"); } else { ChipLogProgress(AppServer, "simple-app-helper.cpp::ConnectionHandler(): Successfully connected to CastingPlayer (ID: %s)", castingPlayer->GetId()); ChipLogProgress(AppServer, "simple-app-helper.cpp::ConnectionHandler(): Desired Endpoint Vendor ID for demo interactions: %d", kDesiredEndpointVendorId); } ChipLogProgress(AppServer, "simple-app-helper.cpp::ConnectionHandler(): Getting endpoints available for demo interactions"); std::vector> endpoints = castingPlayer->GetEndpoints(); LogEndpointsDetails(endpoints); // Find the desired Endpoint and auto-trigger some Matter Casting demo interactions auto it = std::find_if(endpoints.begin(), endpoints.end(), [](const matter::casting::memory::Strong & endpoint) { if (gCommissionerGeneratedPasscodeFlowRunning) { // For the example Commissioner-Generated passcode commissioning flow, run demo interactions with // the Endpoint with ID 1. For this flow, we commissioned with the Target Content Application // with Vendor ID 1111. Since this target content application does not report its Endpoint's // Vendor IDs, we find the desired endpoint based on the Endpoint ID. See // connectedhomeip/examples/tv-app/tv-common/include/AppTv.h. return endpoint->GetId() == kDesiredEndpointId; } return endpoint->GetVendorId() == kDesiredEndpointVendorId; }); if (it != endpoints.end()) { // The desired endpoint is endpoints[index] unsigned index = (unsigned int) std::distance(endpoints.begin(), it); ChipLogProgress( AppServer, "simple-app-helper.cpp::ConnectionHandler(): Triggering demo interactions with CastingPlayer (ID: %s). Endpoint ID: %d", castingPlayer->GetId(), endpoints[index]->GetId()); // demonstrate invoking a command InvokeContentLauncherLaunchURL(endpoints[index]); // demonstrate reading an attribute ReadApplicationBasicVendorID(endpoints[index]); // demonstrate subscribing to an attribute SubscribeToMediaPlaybackCurrentState(endpoints[index]); } else { ChipLogError( AppServer, "simple-app-helper.cpp::ConnectionHandler():Desired Endpoint Vendor ID not found on the CastingPlayer (ID: %s)", castingPlayer->GetId()); } } void CommissionerDeclarationCallback(const chip::Transport::PeerAddress & source, chip::Protocols::UserDirectedCommissioning::CommissionerDeclaration cd) { ChipLogProgress(AppServer, "simple-app-helper.cpp::CommissionerDeclarationCallback() called with CommissionerDeclaration message:"); cd.DebugLog(); if (cd.GetCommissionerPasscode()) { ChipLogProgress(AppServer, "---- Awaiting user input ----"); ChipLogProgress(AppServer, "Input the Commissioner-Generated passcode displayed on the CastingPlayer UX."); ChipLogProgress(AppServer, "Input 12345678 to use the default passcode."); ChipLogProgress(AppServer, "Example: cast setcommissionerpasscode 12345678"); ChipLogProgress(AppServer, "---- Awaiting user input ----"); gAwaitingCommissionerPasscodeInput = true; } } #if defined(ENABLE_CHIP_SHELL) void RegisterCommands() { static const chip::Shell::shell_command_t sDeviceComand = { &CommandHandler, "cast", "Casting commands. Usage: cast [command_name]" }; // Register the root `device` command with the top-level shell. chip::Shell::Engine::Root().RegisterCommands(&sDeviceComand, 1); } CHIP_ERROR CommandHandler(int argc, char ** argv) { if (argc == 0 || strcmp(argv[0], "help") == 0) { return PrintAllCommands(); } if (strcmp(argv[0], "discover") == 0) { ChipLogProgress(AppServer, "CommandHandler() discover"); return matter::casting::core::CastingPlayerDiscovery::GetInstance()->StartDiscovery(kTargetPlayerDeviceType); } if (strcmp(argv[0], "stop-discovery") == 0) { ChipLogProgress(AppServer, "CommandHandler() stop-discovery"); return matter::casting::core::CastingPlayerDiscovery::GetInstance()->StopDiscovery(); } if (strcmp(argv[0], "request") == 0) { ChipLogProgress(AppServer, "CommandHandler() request"); if (argc < 2) { return PrintAllCommands(); } char * eptr; unsigned long index = static_cast(strtol(argv[1], &eptr, 10)); std::vector> castingPlayers = matter::casting::core::CastingPlayerDiscovery::GetInstance()->GetCastingPlayers(); VerifyOrReturnValue(index < castingPlayers.size(), CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(AppServer, "Invalid casting player index provided: %lu", index)); targetCastingPlayer = castingPlayers.at(index); gCommissionerGeneratedPasscodeFlowRunning = false; // Specify the TargetApp that the client wants to interact with after commissioning. If this value is passed in, // VerifyOrEstablishConnection() will force UDC, in case the desired TargetApp is not found in the on-device // CastingStore matter::casting::core::IdentificationDeclarationOptions idOptions; chip::Protocols::UserDirectedCommissioning::TargetAppInfo targetAppInfo; targetAppInfo.vendorId = kDesiredEndpointVendorId; if (argc == 3) { if (strcmp(argv[2], "commissioner-generated-passcode") == 0) { // Attempt Commissioner-Generated Passcode (commissioner-generated-passcode) commissioning flow only if the // CastingPlayer indicates support for it. if (targetCastingPlayer->GetSupportsCommissionerGeneratedPasscode()) { ChipLogProgress(AppServer, "CommandHandler() request %lu commissioner-generated-passcode. Attempting the " "Commissioner-Generated Passcode commissioning flow", index); idOptions.mCommissionerPasscode = true; // For the example Commissioner-Generated passcode commissioning flow, override the default Target Content // Application Vendor ID, which is configured on the tv-app. This Target Content Application Vendor ID (1111), // does not implement the AccountLogin cluster, which would otherwise auto commission using the // Commissionee-Generated passcode upon recieving the IdentificationDeclaration Message. See // connectedhomeip/examples/tv-app/tv-common/include/AppTv.h. targetAppInfo.vendorId = 1111; gCommissionerGeneratedPasscodeFlowRunning = true; } else { ChipLogError(AppServer, "CommandHandler() request %lu commissioner-generated-passcode. Selected CastingPLayer does not " "support the Commissioner-Generated Passcode commissioning flow", index); } } } CHIP_ERROR result = idOptions.addTargetAppInfo(targetAppInfo); if (result != CHIP_NO_ERROR) { ChipLogError(AppServer, "CommandHandler() request, failed to add targetAppInfo: %" CHIP_ERROR_FORMAT, result.Format()); } matter::casting::core::ConnectionCallbacks connectionCallbacks; connectionCallbacks.mOnConnectionComplete = ConnectionHandler; // Provide an handler (Optional) for Commissioner's CommissionerDeclaration messages. The CommissionerDeclaration messages // provide information indicating the Commissioner's pre-commissioning state. connectionCallbacks.mCommissionerDeclarationCallback = CommissionerDeclarationCallback; targetCastingPlayer->VerifyOrEstablishConnection(connectionCallbacks, matter::casting::core::kCommissioningWindowTimeoutSec, idOptions); ChipLogProgress(AppServer, "CommandHandler() request, VerifyOrEstablishConnection() called, calling StopDiscovery()"); // Stop discovery since we have discovered, and are now connecting to the desired CastingPlayer. matter::casting::core::CastingPlayerDiscovery::GetInstance()->StopDiscovery(); return CHIP_NO_ERROR; } if (strcmp(argv[0], "setcommissionerpasscode") == 0) { ChipLogProgress(AppServer, "CommandHandler() setcommissionerpasscode"); if (argc < 2) { return PrintAllCommands(); } char * eptr; uint32_t userEnteredPasscode = (uint32_t) strtol(argv[1], &eptr, 10); if (gAwaitingCommissionerPasscodeInput) { ChipLogProgress(AppServer, "CommandHandler() setcommissionerpasscode user-entered passcode: %d", userEnteredPasscode); gAwaitingCommissionerPasscodeInput = false; // Per connectedhomeip/examples/platform/linux/LinuxCommissionableDataProvider.h: We don't support overriding the // passcode post-init (it is deprecated!). Therefore we need to initiate a new provider with the user-entered // Commissioner-generated passcode, and then update the CastigApp's AppParameters to update the commissioning session's // passcode. LinuxDeviceOptions::GetInstance().payload.setUpPINCode = userEnteredPasscode; CHIP_ERROR err = CHIP_NO_ERROR; err = InitCommissionableDataProvider(gSimpleAppCommissionableDataProvider, LinuxDeviceOptions::GetInstance()); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode InitCommissionableDataProvider() err %" CHIP_ERROR_FORMAT, err.Format()); return err; } // Update the CommissionableDataProvider stored in this CastingApp's AppParameters and the CommissionableDataProvider to // be used for the commissioning session. err = matter::casting::core::CastingApp::GetInstance()->UpdateCommissionableDataProvider( &gSimpleAppCommissionableDataProvider); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode InitCommissionableDataProvider() err %" CHIP_ERROR_FORMAT, err.Format()); return err; } // Continue Connecting to the target CastingPlayer with the user entered Commissioner-generated Passcode. err = targetCastingPlayer->ContinueConnecting(); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode ContinueConnecting() failed due to err %" CHIP_ERROR_FORMAT, err.Format()); // Since continueConnecting() failed, Attempt to cancel the connection attempt with // the CastingPlayer/Commissioner by calling StopConnecting(). err = targetCastingPlayer->StopConnecting(); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CommandHandler() setcommissionerpasscode, ContinueConnecting() failed and then StopConnecting " "failed due to err %" CHIP_ERROR_FORMAT, err.Format()); } return err; } } else { ChipLogError( AppServer, "CommandHandler() setcommissionerpasscode, no Commissioner-Generated passcode input expected at this time."); return CHIP_ERROR_INVALID_ARGUMENT; } } if (strcmp(argv[0], "stop-connecting") == 0) { ChipLogProgress(AppServer, "CommandHandler() stop-connecting"); CHIP_ERROR err = targetCastingPlayer->StopConnecting(); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CommandHandler() stop-connecting, err %" CHIP_ERROR_FORMAT, err.Format()); return err; } } if (strcmp(argv[0], "print-bindings") == 0) { 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; } return CHIP_ERROR_INVALID_ARGUMENT; } CHIP_ERROR PrintAllCommands() { chip::Shell::streamer_t * sout = chip::Shell::streamer_get(); 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, " discover Discover Casting Players. Usage: cast discover\r\n"); streamer_printf(sout, " stop-discovery Stop Discovery of Casting Players. Usage: cast stop-discovery\r\n"); streamer_printf(sout, " request Request connecting to discovered Casting Player with " "[index] using the Commissionee-Generated passcode commissioning flow. Usage: cast request 0\r\n"); streamer_printf(sout, " request commissioner-generated-passcode Request connecting to discovered Casting Player with " "[index] using the Commissioner-Generated passcode commissioning flow. Usage: cast request 0 cgp\r\n"); streamer_printf(sout, " setcommissionerpasscode Set the commissioning session's passcode to the " "Commissioner-Generated passcode. Used for the the Commissioner-Generated passcode commissioning flow. Usage: " "cast setcommissionerpasscode 12345678\r\n"); streamer_printf(sout, " stop-connecting Stop connecting to Casting Player upon " "Commissioner-Generated passcode commissioning flow passcode input request. Usage: cast stop-connecting\r\n"); streamer_printf(sout, "\r\n"); return CHIP_NO_ERROR; } void PrintBindings() { for (const auto & binding : chip::BindingTable::GetInstance()) { ChipLogProgress(AppServer, "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.value_or(0))); } } void PrintFabrics() { // set fabric to be the first in the list for (const auto & fb : chip::Server::GetInstance().GetFabricTable()) { chip::FabricIndex fabricIndex = fb.GetFabricIndex(); ChipLogError(AppServer, "Next Fabric index=%d", fabricIndex); if (!fb.IsInitialized()) { ChipLogError(AppServer, " -- Not initialized"); continue; } chip::NodeId myNodeId = fb.GetNodeId(); ChipLogProgress(AppServer, "---- Current Fabric nodeId=0x" ChipLogFormatX64 " fabricId=0x" ChipLogFormatX64 " fabricIndex=%d", ChipLogValueX64(myNodeId), ChipLogValueX64(fb.GetFabricId()), fabricIndex); } } #endif // ENABLE_CHIP_SHELL