From c89f8081505cef3ad953916a50c5014f2712747d Mon Sep 17 00:00:00 2001 From: mthiesc Date: Fri, 23 Aug 2024 00:59:53 +0200 Subject: [PATCH] AccountLogin Login/Logout command support (#34162) * AccountLogin Login/Logout command support [Problem] * ContentAppPlatform should send AccountLogin::Login command after successfull commissioning if user was shown setupPIN prompt. * Handling Login/Logout commands is currently not implemented in the Android. [Solution] * Call AccountLoginManager::HandleLogin if commissioning succeeded successfully with setupPIN prompt flow. * Implement HandleLogin (and HandleLogout) for Android AccountLoginManager using the ContentAppCommandDelegate. [Testing] WIP * HandleLogin in ContentAppPlatform::ManageClientAccess * Cache rotating ID in OnUserDirectedCommissioningRequest * Restyled by google-java-format * Restyled by clang-format * Update content app * Restyled by whitespace * Restyled by clang-format * Update response * Update method name * Restyled by google-java-format * Update src/app/app-platform/ContentApp.h Co-authored-by: chrisdecenzo <61757564+chrisdecenzo@users.noreply.github.com> * Update content app platform validation * Update validation logic * Update logic * Update responses * Restyled by clang-format * Simplify logic * Restyled by whitespace * clean up * Update code * Update content app with dynamic pin code * Restyled by google-java-format * Remove getRotatingIdSpan class methods * Restyled by clang-format --------- Co-authored-by: Restyled.io Co-authored-by: Lazar Kovacic Co-authored-by: chrisdecenzo <61757564+chrisdecenzo@users.noreply.github.com> --- .../contentapp/CommandResponseHolder.java | 10 +++ .../receiver/MatterCommandReceiver.java | 17 ++++ .../account-login/AccountLoginManager.cpp | 87 ++++++++++++++++--- examples/tv-app/android/java/AppImpl.cpp | 2 +- .../java/ContentAppCommandDelegate.cpp | 66 +++++++++----- .../android/java/ContentAppCommandDelegate.h | 2 + examples/tv-app/android/java/TVApp-JNI.cpp | 35 +++++--- examples/tv-app/tv-common/src/AppTv.cpp | 37 +++++--- src/app/app-platform/ContentApp.cpp | 13 ++- src/app/app-platform/ContentApp.h | 3 +- src/app/app-platform/ContentAppPlatform.cpp | 26 +++++- src/app/app-platform/ContentAppPlatform.h | 6 +- .../CommissionerDiscoveryController.cpp | 11 ++- .../CommissionerDiscoveryController.h | 7 +- 14 files changed, 254 insertions(+), 68 deletions(-) diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/CommandResponseHolder.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/CommandResponseHolder.java index 61ee303b408587..fa49afc312d383 100644 --- a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/CommandResponseHolder.java +++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/CommandResponseHolder.java @@ -31,6 +31,16 @@ private CommandResponseHolder() { Clusters.AccountLogin.Id, Clusters.AccountLogin.Commands.GetSetupPIN.ID, "{\"0\":\"20202021\"}"); + setResponseValue( + Clusters.AccountLogin.Id, + Clusters.AccountLogin.Commands.Login.ID, + // 0 is for success, you can return 1 for failure + "{\"Status\":0}"); + setResponseValue( + Clusters.AccountLogin.Id, + Clusters.AccountLogin.Commands.Logout.ID, + // 0 is for success, you can return 1 for failure + "{\"Status\":0}"); }; public static CommandResponseHolder getInstance() { diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java index 6a4eb77cb4be3b..7134691392e6aa 100644 --- a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java +++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/receiver/MatterCommandReceiver.java @@ -4,11 +4,13 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.provider.Settings; import android.util.Log; import com.example.contentapp.AttributeHolder; import com.example.contentapp.CommandResponseHolder; import com.example.contentapp.MainActivity; import com.example.contentapp.matter.MatterAgentClient; +import com.matter.tv.app.api.Clusters; import com.matter.tv.app.api.MatterIntentConstants; public class MatterCommandReceiver extends BroadcastReceiver { @@ -44,6 +46,21 @@ public void onReceive(Context context, Intent intent) { .append(command) .toString(); Log.d(TAG, message); + + int pinCode = + Settings.Secure.getInt(context.getContentResolver(), "matter_pin_code", 20202021); + Log.d(TAG, "Retrieved pin code:" + pinCode); + + CommandResponseHolder.getInstance() + .setResponseValue( + Clusters.AccountLogin.Id, + Clusters.AccountLogin.Commands.GetSetupPIN.ID, + "{\"" + + Clusters.AccountLogin.Commands.GetSetupPINResponse.Fields.SetupPIN + + "\":\"" + + pinCode + + "\"}"); + String response = CommandResponseHolder.getInstance().getCommandResponse(clusterId, commandId); diff --git a/examples/tv-app/android/include/account-login/AccountLoginManager.cpp b/examples/tv-app/android/include/account-login/AccountLoginManager.cpp index 12e6ba44dfb220..2aa0b313b0eda1 100644 --- a/examples/tv-app/android/include/account-login/AccountLoginManager.cpp +++ b/examples/tv-app/android/include/account-login/AccountLoginManager.cpp @@ -24,38 +24,105 @@ #include using namespace std; +using namespace chip::app::Clusters; using namespace chip::app::Clusters::AccountLogin; using Status = chip::Protocols::InteractionModel::Status; +namespace { + +const auto loginTempAccountIdentifierFieldId = + to_string(chip::to_underlying(AccountLogin::Commands::Login::Fields::kTempAccountIdentifier)); +const auto loginSetupPINFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Login::Fields::kSetupPIN)); +const auto loginNodeFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Login::Fields::kNode)); +const auto logoutNodeFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Logout::Fields::kNode)); + +string charSpanToString(const CharSpan & charSpan) +{ + return { charSpan.data(), charSpan.size() }; +} + +std::string serializeLoginCommand(AccountLogin::Commands::Login::Type cmd) +{ + return R"({")" + loginTempAccountIdentifierFieldId + R"(":")" + charSpanToString(cmd.tempAccountIdentifier) + R"(",)" + R"(")" + + loginSetupPINFieldId + R"(":")" + charSpanToString(cmd.setupPIN) + R"(",)" + R"(")" + loginNodeFieldId + R"(":")" + + to_string(cmd.node.Value()) + R"("})"; +} + +std::string serializeLogoutCommand(AccountLogin::Commands::Logout::Type cmd) +{ + return R"({")" + logoutNodeFieldId + R"(":")" + to_string(cmd.node.Value()) + R"("})"; +} + +} // namespace + AccountLoginManager::AccountLoginManager(ContentAppCommandDelegate * commandDelegate, const char * setupPin) : mCommandDelegate(commandDelegate) { CopyString(mSetupPin, sizeof(mSetupPin), setupPin); } -bool AccountLoginManager::HandleLogin(const CharSpan & tempAccountIdentifier, const CharSpan & setupPin, +bool AccountLoginManager::HandleLogin(const CharSpan & tempAccountIdentifier, const CharSpan & setupPIN, const chip::Optional & nodeId) { ChipLogProgress(DeviceLayer, "AccountLoginManager::HandleLogin called for endpoint %d", mEndpointId); - string tempAccountIdentifierString(tempAccountIdentifier.data(), tempAccountIdentifier.size()); - string setupPinString(setupPin.data(), setupPin.size()); - if (strcmp(mSetupPin, setupPinString.c_str()) == 0) + if (mCommandDelegate == nullptr) { - ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin success"); - return true; + ChipLogError(Zcl, "CommandDelegate not found"); + return false; } - else + + if (tempAccountIdentifier.empty() || setupPIN.empty() || !nodeId.HasValue()) { - ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin failed expected pin %s", mSetupPin); + ChipLogError(Zcl, "Invalid parameters"); return false; } + + Json::Value response; + bool commandHandled = true; + AccountLogin::Commands::Login::Type cmd = { tempAccountIdentifier, setupPIN, nodeId }; + + auto status = mCommandDelegate->InvokeCommand(mEndpointId, AccountLogin::Id, AccountLogin::Commands::Login::Id, + serializeLoginCommand(cmd), commandHandled, response); + if (status == Status::Success) + { + // Format status response to verify that response is non-failure. + status = mCommandDelegate->FormatStatusResponse(response); + } + ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin command returned with status: %d", chip::to_underlying(status)); + return status == chip::Protocols::InteractionModel::Status::Success; } bool AccountLoginManager::HandleLogout(const chip::Optional & nodeId) { - // TODO: Insert your code here to send logout request - return true; + ChipLogProgress(DeviceLayer, "AccountLoginManager::HandleLogout called for endpoint %d", mEndpointId); + + if (mCommandDelegate == nullptr) + { + ChipLogError(Zcl, "CommandDelegate not found"); + return false; + } + + if (!nodeId.HasValue()) + { + ChipLogError(Zcl, "Invalid parameters"); + return false; + } + + Json::Value response; + bool commandHandled = true; + AccountLogin::Commands::Logout::Type cmd = { nodeId }; + + auto status = mCommandDelegate->InvokeCommand(mEndpointId, AccountLogin::Id, AccountLogin::Commands::Logout::Id, + serializeLogoutCommand(cmd), commandHandled, response); + + if (status == Status::Success) + { + // Format status response to verify that response is non-failure. + status = mCommandDelegate->FormatStatusResponse(response); + } + ChipLogProgress(Zcl, "AccountLoginManager::HandleLogout command returned with status: %d", chip::to_underlying(status)); + return status == chip::Protocols::InteractionModel::Status::Success; } void AccountLoginManager::HandleGetSetupPin(CommandResponseHelper & helper, diff --git a/examples/tv-app/android/java/AppImpl.cpp b/examples/tv-app/android/java/AppImpl.cpp index d7da8ab6661a46..d33949a5d382d9 100644 --- a/examples/tv-app/android/java/AppImpl.cpp +++ b/examples/tv-app/android/java/AppImpl.cpp @@ -417,7 +417,7 @@ void refreshConnectedClientsAcl(uint16_t vendorId, uint16_t productId, ContentAp for (const auto & allowedVendor : app->GetApplicationBasicDelegate()->GetAllowedVendorList()) { - std::set tempNodeIds = ContentAppPlatform::GetInstance().GetNodeIdsForAllowVendorId(allowedVendor); + std::set tempNodeIds = ContentAppPlatform::GetInstance().GetNodeIdsForAllowedVendorId(allowedVendor); nodeIds.insert(tempNodeIds.begin(), tempNodeIds.end()); } diff --git a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp index 02b4a7e806fefb..caf2d665b8522e 100644 --- a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp +++ b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp @@ -39,15 +39,9 @@ namespace chip { namespace AppPlatform { -using CommandHandlerInterface = chip::app::CommandHandlerInterface; -using LaunchResponseType = chip::app::Clusters::ContentLauncher::Commands::LauncherResponse::Type; -using PlaybackResponseType = chip::app::Clusters::MediaPlayback::Commands::PlaybackResponse::Type; -using NavigateTargetResponseType = chip::app::Clusters::TargetNavigator::Commands::NavigateTargetResponse::Type; -using GetSetupPINResponseType = chip::app::Clusters::AccountLogin::Commands::GetSetupPINResponse::Type; -using Status = chip::Protocols::InteractionModel::Status; - -const std::string FAILURE_KEY = "PlatformError"; -const std::string FAILURE_STATUS_KEY = "Status"; +const std::string FAILURE_KEY = "PlatformError"; +const std::string FAILURE_STATUS_KEY = "Status"; +const std::string RESPONSE_STATUS_KEY = "Status"; bool isValidJson(const char * response) { @@ -166,6 +160,7 @@ Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clust ChipLogError(Zcl, "Java exception in ContentAppCommandDelegate::sendCommand"); env->ExceptionDescribe(); env->ExceptionClear(); + return chip::Protocols::InteractionModel::Status::Failure; } else { @@ -198,7 +193,8 @@ Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clust } env->DeleteLocalRef(resp); - // handle errors from platform-app + // Parse response here in case there is failure response. + // Return non-success error code to indicate to caller it should not parse response. if (!value[FAILURE_KEY].empty()) { value = value[FAILURE_KEY]; @@ -209,7 +205,9 @@ Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clust return chip::Protocols::InteractionModel::Status::Failure; } - return chip::Protocols::InteractionModel::Status::UnsupportedEndpoint; + // Return success to indicate command has been sent, response returned and parsed successfully. + // Caller has to manually parse value input/output parameter to get response status/object. + return chip::Protocols::InteractionModel::Status::Success; } else { @@ -282,20 +280,31 @@ void ContentAppCommandDelegate::FormatResponseData(CommandHandlerInterface::Hand } case app::Clusters::AccountLogin::Id: { - if (app::Clusters::AccountLogin::Commands::GetSetupPIN::Id != handlerContext.mRequestPath.mCommandId) + switch (handlerContext.mRequestPath.mCommandId) { - // No response for other commands in this cluster + case app::Clusters::AccountLogin::Commands::GetSetupPIN::Id: { + Status status; + GetSetupPINResponseType getSetupPINresponse = FormatGetSetupPINResponse(value, status); + if (status != chip::Protocols::InteractionModel::Status::Success) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, status); + } + else + { + handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, getSetupPINresponse); + } break; } - Status status; - GetSetupPINResponseType getSetupPINresponse = FormatGetSetupPINResponse(value, status); - if (status != chip::Protocols::InteractionModel::Status::Success) - { - handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, status); + case app::Clusters::AccountLogin::Commands::Login::Id: { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, FormatStatusResponse(value)); + break; } - else - { - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, getSetupPINresponse); + case app::Clusters::AccountLogin::Commands::Logout::Id: { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, FormatStatusResponse(value)); + break; + } + default: + break; } break; } @@ -388,10 +397,23 @@ GetSetupPINResponseType ContentAppCommandDelegate::FormatGetSetupPINResponse(Jso } else { - getSetupPINresponse.setupPIN = ""; + status = chip::Protocols::InteractionModel::Status::Failure; } return getSetupPINresponse; } +Status ContentAppCommandDelegate::FormatStatusResponse(Json::Value value) +{ + // check if JSON has "Status" key + if (!value[RESPONSE_STATUS_KEY].empty() && !value[RESPONSE_STATUS_KEY].isUInt()) + { + return static_cast(value.asUInt()); + } + else + { + return chip::Protocols::InteractionModel::Status::Failure; + } +} + } // namespace AppPlatform } // namespace chip diff --git a/examples/tv-app/android/java/ContentAppCommandDelegate.h b/examples/tv-app/android/java/ContentAppCommandDelegate.h index f033b1afa79189..49d3e6fda87610 100644 --- a/examples/tv-app/android/java/ContentAppCommandDelegate.h +++ b/examples/tv-app/android/java/ContentAppCommandDelegate.h @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -75,6 +76,7 @@ class ContentAppCommandDelegate : public CommandHandlerInterface LaunchResponseType FormatContentLauncherResponse(Json::Value value, Status & status); NavigateTargetResponseType FormatNavigateTargetResponse(Json::Value value, Status & status); PlaybackResponseType FormatMediaPlaybackResponse(Json::Value value, Status & status); + Status FormatStatusResponse(Json::Value value); private: void InitializeJNIObjects(jobject manager) diff --git a/examples/tv-app/android/java/TVApp-JNI.cpp b/examples/tv-app/android/java/TVApp-JNI.cpp index 0fe1bdef939ca8..228cab60e03748 100644 --- a/examples/tv-app/android/java/TVApp-JNI.cpp +++ b/examples/tv-app/android/java/TVApp-JNI.cpp @@ -258,15 +258,15 @@ SampleTvAppInstallationService gSampleTvAppInstallationService; class MyPostCommissioningListener : public PostCommissioningListener { - void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr, - const SessionHandle & sessionHandle) override + void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, CharSpan rotatingId, uint32_t passcode, + Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) override { // read current binding list chip::Controller::ClusterBase cluster(exchangeMgr, sessionHandle, kTargetBindingClusterEndpointId); ContentAppPlatform::GetInstance().StoreNodeIdForContentApp(vendorId, productId, nodeId); - cacheContext(vendorId, productId, nodeId, exchangeMgr, sessionHandle); + cacheContext(vendorId, productId, nodeId, rotatingId, passcode, exchangeMgr, sessionHandle); CHIP_ERROR err = cluster.ReadAttribute(this, OnReadSuccessResponse, OnReadFailureResponse); @@ -350,17 +350,23 @@ class MyPostCommissioningListener : public PostCommissioningListener Optional opt = mSecureSession.Get(); SessionHandle & sessionHandle = opt.Value(); + auto rotatingIdSpan = CharSpan{ mRotatingId.data(), mRotatingId.size() }; ContentAppPlatform::GetInstance().ManageClientAccess(*mExchangeMgr, sessionHandle, mVendorId, mProductId, localNodeId, - bindings, OnSuccessResponse, OnFailureResponse); + rotatingIdSpan, mPasscode, bindings, OnSuccessResponse, + OnFailureResponse); clearContext(); } - void cacheContext(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr, - const SessionHandle & sessionHandle) + void cacheContext(uint16_t vendorId, uint16_t productId, NodeId nodeId, CharSpan rotatingId, uint32_t passcode, + Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) { - mVendorId = vendorId; - mProductId = productId; - mNodeId = nodeId; + mVendorId = vendorId; + mProductId = productId; + mNodeId = nodeId; + mRotatingId = std::string{ + rotatingId.data(), rotatingId.size() + }; // Allocates and copies to string instead of storing span to make sure lifetime is valid. + mPasscode = passcode; mExchangeMgr = &exchangeMgr; mSecureSession.ShiftToSession(sessionHandle); } @@ -370,12 +376,17 @@ class MyPostCommissioningListener : public PostCommissioningListener mVendorId = 0; mProductId = 0; mNodeId = 0; + mRotatingId = {}; + mPasscode = 0; mExchangeMgr = nullptr; mSecureSession.SessionReleased(); } - uint16_t mVendorId = 0; - uint16_t mProductId = 0; - NodeId mNodeId = 0; + + uint16_t mVendorId = 0; + uint16_t mProductId = 0; + NodeId mNodeId = 0; + std::string mRotatingId; + uint32_t mPasscode = 0; Messaging::ExchangeManager * mExchangeMgr = nullptr; SessionHolder mSecureSession; }; diff --git a/examples/tv-app/tv-common/src/AppTv.cpp b/examples/tv-app/tv-common/src/AppTv.cpp index 8d81d9cf6c2c5c..794c879b4c4da1 100644 --- a/examples/tv-app/tv-common/src/AppTv.cpp +++ b/examples/tv-app/tv-common/src/AppTv.cpp @@ -158,15 +158,15 @@ MyAppInstallationService gMyAppInstallationService; class MyPostCommissioningListener : public PostCommissioningListener { - void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr, - const SessionHandle & sessionHandle) override + void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, CharSpan rotatingId, uint32_t passcode, + Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) override { // read current binding list chip::Controller::ClusterBase cluster(exchangeMgr, sessionHandle, kTargetBindingClusterEndpointId); ContentAppPlatform::GetInstance().StoreNodeIdForContentApp(vendorId, productId, nodeId); - cacheContext(vendorId, productId, nodeId, exchangeMgr, sessionHandle); + cacheContext(vendorId, productId, nodeId, rotatingId, passcode, exchangeMgr, sessionHandle); CHIP_ERROR err = cluster.ReadAttribute(this, OnReadSuccessResponse, OnReadFailureResponse); @@ -250,17 +250,23 @@ class MyPostCommissioningListener : public PostCommissioningListener Optional opt = mSecureSession.Get(); SessionHandle & sessionHandle = opt.Value(); + auto rotatingIdSpan = CharSpan{ mRotatingId.data(), mRotatingId.size() }; ContentAppPlatform::GetInstance().ManageClientAccess(*mExchangeMgr, sessionHandle, mVendorId, mProductId, localNodeId, - bindings, OnSuccessResponse, OnFailureResponse); + rotatingIdSpan, mPasscode, bindings, OnSuccessResponse, + OnFailureResponse); clearContext(); } - void cacheContext(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr, - const SessionHandle & sessionHandle) + void cacheContext(uint16_t vendorId, uint16_t productId, NodeId nodeId, CharSpan rotatingId, uint32_t passcode, + Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) { - mVendorId = vendorId; - mProductId = productId; - mNodeId = nodeId; + mVendorId = vendorId; + mProductId = productId; + mNodeId = nodeId; + mRotatingId = std::string{ + rotatingId.data(), rotatingId.size() + }; // Allocates and copies to string instead of storing span to make sure lifetime is valid. + mPasscode = passcode; mExchangeMgr = &exchangeMgr; mSecureSession.ShiftToSession(sessionHandle); } @@ -270,12 +276,17 @@ class MyPostCommissioningListener : public PostCommissioningListener mVendorId = 0; mProductId = 0; mNodeId = 0; + mRotatingId = {}; + mPasscode = 0; mExchangeMgr = nullptr; mSecureSession.SessionReleased(); } - uint16_t mVendorId = 0; - uint16_t mProductId = 0; - NodeId mNodeId = 0; + + uint16_t mVendorId = 0; + uint16_t mProductId = 0; + NodeId mNodeId = 0; + std::string mRotatingId; + uint32_t mPasscode = 0; Messaging::ExchangeManager * mExchangeMgr = nullptr; SessionHolder mSecureSession; }; @@ -684,7 +695,7 @@ void ContentAppFactoryImpl::InstallContentApp(uint16_t vendorId, uint16_t produc // update the list of node ids with content apps allowed vendor list for (const auto & allowedVendor : app->GetApplicationBasicDelegate()->GetAllowedVendorList()) { - std::set tempNodeIds = ContentAppPlatform::GetInstance().GetNodeIdsForAllowVendorId(allowedVendor); + std::set tempNodeIds = ContentAppPlatform::GetInstance().GetNodeIdsForAllowedVendorId(allowedVendor); nodeIds.insert(tempNodeIds.begin(), tempNodeIds.end()); } diff --git a/src/app/app-platform/ContentApp.cpp b/src/app/app-platform/ContentApp.cpp index a70965c5f2cd5e..4d9268e586dc2c 100644 --- a/src/app/app-platform/ContentApp.cpp +++ b/src/app/app-platform/ContentApp.cpp @@ -64,8 +64,17 @@ Status ContentApp::HandleWriteAttribute(ClusterId clusterId, AttributeId attribu return Status::Failure; } -void ContentApp::AddClientNode(NodeId subjectNodeId) +bool ContentApp::AddClientNode(NodeId subjectNodeId) { + for (int i = 0; i < kMaxClientNodes; ++i) + { + if (mClientNodes[i] == subjectNodeId) + { + // avoid storing duplicate nodes + return false; + } + } + mClientNodes[mNextClientNodeIndex++] = subjectNodeId; if (mClientNodeCount < kMaxClientNodes) { @@ -76,6 +85,8 @@ void ContentApp::AddClientNode(NodeId subjectNodeId) // if we exceed the max number, then overwrite the oldest entry mNextClientNodeIndex = 0; } + + return true; } void ContentApp::SendAppObserverCommand(chip::Controller::DeviceCommissioner * commissioner, NodeId clientNodeId, char * data, diff --git a/src/app/app-platform/ContentApp.h b/src/app/app-platform/ContentApp.h index 79c59e45dfd5bf..ee272c314d01f0 100644 --- a/src/app/app-platform/ContentApp.h +++ b/src/app/app-platform/ContentApp.h @@ -135,7 +135,8 @@ class DLL_EXPORT ContentApp uint16_t maxReadLength); Protocols::InteractionModel::Status HandleWriteAttribute(ClusterId clusterId, AttributeId attributeId, uint8_t * buffer); - void AddClientNode(NodeId clientNodeId); + // returns true only if new node is added. If node was added previously, then false is returned. + bool AddClientNode(NodeId clientNodeId); uint8_t GetClientNodeCount() const { return mClientNodeCount; } NodeId GetClientNode(uint8_t index) const { return mClientNodes[index]; } diff --git a/src/app/app-platform/ContentAppPlatform.cpp b/src/app/app-platform/ContentAppPlatform.cpp index 214af261e26e54..aa615aaae8be54 100644 --- a/src/app/app-platform/ContentAppPlatform.cpp +++ b/src/app/app-platform/ContentAppPlatform.cpp @@ -420,7 +420,7 @@ std::set ContentAppPlatform::GetNodeIdsForContentApp(uint16_t vendorId, return {}; } -std::set ContentAppPlatform::GetNodeIdsForAllowVendorId(uint16_t vendorId) +std::set ContentAppPlatform::GetNodeIdsForAllowedVendorId(uint16_t vendorId) { std::set result; std::string vendorPrefix = std::to_string(vendorId) + ":"; @@ -660,6 +660,7 @@ CHIP_ERROR ContentAppPlatform::GetACLEntryIndex(size_t * foundIndex, FabricIndex // and create bindings on the given client so that it knows what it has access to. CHIP_ERROR ContentAppPlatform::ManageClientAccess(Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle, uint16_t targetVendorId, uint16_t targetProductId, NodeId localNodeId, + CharSpan rotatingId, uint32_t passcode, std::vector bindings, Controller::WriteResponseSuccessCallback successCb, Controller::WriteResponseFailureCallback failureCb) @@ -799,13 +800,32 @@ CHIP_ERROR ContentAppPlatform::ManageClientAccess(Messaging::ExchangeManager & e .cluster = NullOptional, .fabricIndex = kUndefinedFabricIndex, }); + + accessAllowed = true; } - accessAllowed = true; } if (accessAllowed) { // notify content app about this nodeId - app->AddClientNode(subjectNodeId); + bool isNodeAdded = app->AddClientNode(subjectNodeId); + + if (isNodeAdded && rotatingId.size() != 0) + { + // handle login + auto setupPIN = std::to_string(passcode); + auto accountLoginDelegate = app->GetAccountLoginDelegate(); + if (accountLoginDelegate != nullptr) + { + bool condition = accountLoginDelegate->HandleLogin(rotatingId, { setupPIN.data(), setupPIN.size() }, + MakeOptional(subjectNodeId)); + ChipLogProgress(Controller, "AccountLogin::Login command sent and returned: %s", + condition ? "success" : "failure"); + } + else + { + ChipLogError(Controller, "AccountLoginDelegate not found for app"); + } + } } } } diff --git a/src/app/app-platform/ContentAppPlatform.h b/src/app/app-platform/ContentAppPlatform.h index 45615d09ed8ddf..b00f9f1bf7a1ab 100644 --- a/src/app/app-platform/ContentAppPlatform.h +++ b/src/app/app-platform/ContentAppPlatform.h @@ -165,7 +165,7 @@ class DLL_EXPORT ContentAppPlatform std::set GetNodeIdsForContentApp(uint16_t vendorId, uint16_t productId); // returns set of connected nodes for a given allowed vendor id - std::set GetNodeIdsForAllowVendorId(uint16_t vendorId); + std::set GetNodeIdsForAllowedVendorId(uint16_t vendorId); // store node id for content app after commissioning // node id can be used later on to update ACL @@ -188,6 +188,8 @@ class DLL_EXPORT ContentAppPlatform * @param[in] targetVendorId Vendor ID for the target device. * @param[in] targetProductId Product ID for the target device. * @param[in] localNodeId The NodeId for the local device. + * @param[in] rotatingId The rotating account ID to handle account login. + * @param[in] passcode The passcode to handle account login. * @param[in] bindings Any additional bindings to include. This may include current bindings. * @param[in] successCb The function to be called on success of adding the binding. * @param[in] failureCb The function to be called on failure of adding the binding. @@ -195,7 +197,7 @@ class DLL_EXPORT ContentAppPlatform * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error */ CHIP_ERROR ManageClientAccess(Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle, uint16_t targetVendorId, - uint16_t targetProductId, NodeId localNodeId, + uint16_t targetProductId, NodeId localNodeId, chip::CharSpan rotatingId, uint32_t passcode, std::vector bindings, Controller::WriteResponseSuccessCallback successCb, Controller::WriteResponseFailureCallback failureCb); diff --git a/src/controller/CommissionerDiscoveryController.cpp b/src/controller/CommissionerDiscoveryController.cpp index 09a22e03e0bcd2..baf8e14bfd4e37 100644 --- a/src/controller/CommissionerDiscoveryController.cpp +++ b/src/controller/CommissionerDiscoveryController.cpp @@ -166,6 +166,11 @@ void CommissionerDiscoveryController::OnUserDirectedCommissioningRequest(UDCClie ChipLogError(AppServer, "On UDC: could not convert rotating id to hex"); rotatingIdString[0] = '\0'; } + else + { + // Store rotating ID string. Don't include null terminator character. + mRotatingId = std::string{ rotatingIdString, state.GetRotatingIdLength() * 2 }; + } ChipLogDetail(Controller, "------PROMPT USER: %s is requesting permission to cast to this TV, approve? [" ChipLogFormatMEI @@ -455,6 +460,7 @@ void CommissionerDiscoveryController::InternalHandleContentAppPasscodeResponse() cd, Transport::PeerAddress::UDP(client->GetPeerAddress().GetIPAddress(), client->GetCdPort())); return; } + client->SetCachedCommissionerPasscode(passcode); client->SetUDCClientProcessingState(UDCClientProcessingState::kWaitingForCommissionerPasscodeReady); @@ -630,10 +636,13 @@ void CommissionerDiscoveryController::CommissioningSucceeded(uint16_t vendorId, mVendorId = vendorId; mProductId = productId; mNodeId = nodeId; + if (mPostCommissioningListener != nullptr) { ChipLogDetail(Controller, "CommissionerDiscoveryController calling listener"); - mPostCommissioningListener->CommissioningCompleted(vendorId, productId, nodeId, exchangeMgr, sessionHandle); + auto rotatingIdSpan = CharSpan{ mRotatingId.data(), mRotatingId.size() }; + mPostCommissioningListener->CommissioningCompleted(vendorId, productId, nodeId, rotatingIdSpan, mPasscode, exchangeMgr, + sessionHandle); } else { diff --git a/src/controller/CommissionerDiscoveryController.h b/src/controller/CommissionerDiscoveryController.h index 5f7572b29def17..94f172c1d32750 100644 --- a/src/controller/CommissionerDiscoveryController.h +++ b/src/controller/CommissionerDiscoveryController.h @@ -232,12 +232,14 @@ class DLL_EXPORT PostCommissioningListener * @param[in] vendorId The vendorid from the DAC of the new node. * @param[in] productId The productid from the DAC of the new node. * @param[in] nodeId The node id for the newly commissioned node. + * @param[in] rotatingId The rotating ID to handle account login. + * @param[in] passcode The passcode to handle account login. * @param[in] exchangeMgr The exchange manager to be used to get an exchange context. * @param[in] sessionHandle A reference to an established session. * */ - virtual void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, - chip::Messaging::ExchangeManager & exchangeMgr, + virtual void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, chip::CharSpan rotatingId, + uint32_t passcode, chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle) = 0; virtual ~PostCommissioningListener() = default; @@ -451,6 +453,7 @@ class CommissionerDiscoveryController : public chip::Protocols::UserDirectedComm uint16_t mProductId = 0; NodeId mNodeId = 0; uint32_t mPasscode = 0; + std::string mRotatingId; UserDirectedCommissioningServer * mUdcServer = nullptr; UserPrompter * mUserPrompter = nullptr;