diff --git a/examples/tv-app/android/java/AppImpl.cpp b/examples/tv-app/android/java/AppImpl.cpp index 57418ebeb6c724..12fce44e3e6bae 100644 --- a/examples/tv-app/android/java/AppImpl.cpp +++ b/examples/tv-app/android/java/AppImpl.cpp @@ -403,6 +403,19 @@ Access::Privilege ContentAppFactoryImpl::GetVendorPrivilege(uint16_t vendorId) return Access::Privilege::kOperate; } +std::list ContentAppFactoryImpl::GetAllowedClusterListForStaticEndpoint(EndpointId endpointId, uint16_t vendorId, + uint16_t productId) +{ + if (endpointId == kLocalVideoPlayerEndpointId) + { + return { chip::app::Clusters::Descriptor::Id, chip::app::Clusters::OnOff::Id, + chip::app::Clusters::WakeOnLan::Id, chip::app::Clusters::MediaPlayback::Id, + chip::app::Clusters::LowPower::Id, chip::app::Clusters::KeypadInput::Id, + chip::app::Clusters::ContentLauncher::Id, chip::app::Clusters::AudioOutput::Id }; + } + return {}; +} + } // namespace AppPlatform } // namespace chip diff --git a/examples/tv-app/android/java/AppImpl.h b/examples/tv-app/android/java/AppImpl.h index 70ea36f452ac30..df257e081577dc 100644 --- a/examples/tv-app/android/java/AppImpl.h +++ b/examples/tv-app/android/java/AppImpl.h @@ -163,6 +163,11 @@ class DLL_EXPORT ContentAppFactoryImpl : public ContentAppFactory // When a vendor has admin privileges, it will get access to all clusters on ep1 Access::Privilege GetVendorPrivilege(uint16_t vendorId) override; + // Get the cluster list this vendorId/productId should have on static endpoints such as ep1 for casting video clients. + // When a vendor has admin privileges, it will get access to all clusters on ep1 + std::list GetAllowedClusterListForStaticEndpoint(EndpointId endpointId, uint16_t vendorId, + uint16_t productId) override; + void AddAdminVendorId(uint16_t vendorId); void setContentAppAttributeDelegate(ContentAppAttributeDelegate * attributeDelegate); diff --git a/examples/tv-app/android/java/TVApp-JNI.cpp b/examples/tv-app/android/java/TVApp-JNI.cpp index c59893106a8554..405554e073f950 100644 --- a/examples/tv-app/android/java/TVApp-JNI.cpp +++ b/examples/tv-app/android/java/TVApp-JNI.cpp @@ -41,9 +41,11 @@ #include #include #include +#include using namespace chip; using namespace chip::app; +using namespace chip::app::Clusters; using namespace chip::AppPlatform; using namespace chip::Credentials; @@ -201,9 +203,44 @@ class MyPostCommissioningListener : public PostCommissioningListener void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle) override { + // read current binding list + chip::Controller::BindingCluster cluster(exchangeMgr, sessionHandle, kTargetBindingClusterEndpointId); - ContentAppPlatform::GetInstance().ManageClientAccess( - exchangeMgr, sessionHandle, vendorId, GetDeviceCommissioner()->GetNodeId(), OnSuccessResponse, OnFailureResponse); + cacheContext(vendorId, productId, nodeId, exchangeMgr, sessionHandle); + + CHIP_ERROR err = + cluster.ReadAttribute(this, OnReadSuccessResponse, OnReadFailureResponse); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed in reading binding. Error %s", ErrorStr(err)); + clearContext(); + } + } + + /* Callback when command results in success */ + static void + OnReadSuccessResponse(void * context, + const app::DataModel::DecodableList & responseData) + { + ChipLogProgress(Controller, "OnReadSuccessResponse - Binding Read Successfully"); + + MyPostCommissioningListener * listener = static_cast(context); + listener->finishTargetConfiguration(responseData); + } + + /* Callback when command results in failure */ + static void OnReadFailureResponse(void * context, CHIP_ERROR error) + { + ChipLogProgress(Controller, "OnReadFailureResponse - Binding Read Failed"); + + MyPostCommissioningListener * listener = static_cast(context); + listener->clearContext(); + + CommissionerDiscoveryController * cdc = GetCommissionerDiscoveryController(); + if (cdc != nullptr) + { + cdc->PostCommissioningFailed(error); + } } /* Callback when command results in success */ @@ -227,6 +264,60 @@ class MyPostCommissioningListener : public PostCommissioningListener cdc->PostCommissioningFailed(error); } } + + void + finishTargetConfiguration(const app::DataModel::DecodableList & responseList) + { + std::vector bindings; + NodeId localNodeId = GetDeviceCommissioner()->GetNodeId(); + + auto iter = responseList.begin(); + while (iter.Next()) + { + auto & binding = iter.GetValue(); + ChipLogProgress(Controller, "Binding found nodeId=0x" ChipLogFormatX64 " my nodeId=0x" ChipLogFormatX64, + ChipLogValueX64(binding.node.ValueOr(0)), ChipLogValueX64(localNodeId)); + if (binding.node.ValueOr(0) != localNodeId) + { + ChipLogProgress(Controller, "Found a binding for a different node, preserving"); + bindings.push_back(binding); + } + else + { + ChipLogProgress(Controller, "Found a binding for a matching node, dropping"); + } + } + + Optional opt = mSecureSession.Get(); + SessionHandle & sessionHandle = opt.Value(); + ContentAppPlatform::GetInstance().ManageClientAccess(*mExchangeMgr, sessionHandle, mVendorId, mProductId, localNodeId, + bindings, OnSuccessResponse, OnFailureResponse); + clearContext(); + } + + void cacheContext(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr, + SessionHandle & sessionHandle) + { + mVendorId = vendorId; + mProductId = productId; + mNodeId = nodeId; + mExchangeMgr = &exchangeMgr; + mSecureSession.ShiftToSession(sessionHandle); + } + + void clearContext() + { + mVendorId = 0; + mProductId = 0; + mNodeId = 0; + mExchangeMgr = nullptr; + mSecureSession.SessionReleased(); + } + uint16_t mVendorId = 0; + uint16_t mProductId = 0; + NodeId mNodeId = 0; + Messaging::ExchangeManager * mExchangeMgr = nullptr; + SessionHolder mSecureSession; }; MyPostCommissioningListener gMyPostCommissioningListener; diff --git a/examples/tv-app/linux/AppImpl.cpp b/examples/tv-app/linux/AppImpl.cpp index ddfa2dff130bfe..20eff22cef8cdc 100644 --- a/examples/tv-app/linux/AppImpl.cpp +++ b/examples/tv-app/linux/AppImpl.cpp @@ -42,9 +42,11 @@ #include #include #include +#include using namespace chip; using namespace chip::AppPlatform; +using namespace chip::app::Clusters; #if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE class MyUserPrompter : public UserPrompter @@ -89,9 +91,44 @@ class MyPostCommissioningListener : public PostCommissioningListener void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle) override { + // read current binding list + chip::Controller::BindingCluster cluster(exchangeMgr, sessionHandle, kTargetBindingClusterEndpointId); - ContentAppPlatform::GetInstance().ManageClientAccess( - exchangeMgr, sessionHandle, vendorId, GetDeviceCommissioner()->GetNodeId(), OnSuccessResponse, OnFailureResponse); + cacheContext(vendorId, productId, nodeId, exchangeMgr, sessionHandle); + + CHIP_ERROR err = + cluster.ReadAttribute(this, OnReadSuccessResponse, OnReadFailureResponse); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed in reading binding. Error %s", ErrorStr(err)); + clearContext(); + } + } + + /* Callback when command results in success */ + static void + OnReadSuccessResponse(void * context, + const app::DataModel::DecodableList & responseData) + { + ChipLogProgress(Controller, "OnReadSuccessResponse - Binding Read Successfully"); + + MyPostCommissioningListener * listener = static_cast(context); + listener->finishTargetConfiguration(responseData); + } + + /* Callback when command results in failure */ + static void OnReadFailureResponse(void * context, CHIP_ERROR error) + { + ChipLogProgress(Controller, "OnReadFailureResponse - Binding Read Failed"); + + MyPostCommissioningListener * listener = static_cast(context); + listener->clearContext(); + + CommissionerDiscoveryController * cdc = GetCommissionerDiscoveryController(); + if (cdc != nullptr) + { + cdc->PostCommissioningFailed(error); + } } /* Callback when command results in success */ @@ -115,6 +152,60 @@ class MyPostCommissioningListener : public PostCommissioningListener cdc->PostCommissioningFailed(error); } } + + void + finishTargetConfiguration(const app::DataModel::DecodableList & responseList) + { + std::vector bindings; + NodeId localNodeId = GetDeviceCommissioner()->GetNodeId(); + + auto iter = responseList.begin(); + while (iter.Next()) + { + auto & binding = iter.GetValue(); + ChipLogProgress(Controller, "Binding found nodeId=0x" ChipLogFormatX64 " my nodeId=0x" ChipLogFormatX64, + ChipLogValueX64(binding.node.ValueOr(0)), ChipLogValueX64(localNodeId)); + if (binding.node.ValueOr(0) != localNodeId) + { + ChipLogProgress(Controller, "Found a binding for a different node, preserving"); + bindings.push_back(binding); + } + else + { + ChipLogProgress(Controller, "Found a binding for a matching node, dropping"); + } + } + + Optional opt = mSecureSession.Get(); + SessionHandle & sessionHandle = opt.Value(); + ContentAppPlatform::GetInstance().ManageClientAccess(*mExchangeMgr, sessionHandle, mVendorId, mProductId, localNodeId, + bindings, OnSuccessResponse, OnFailureResponse); + clearContext(); + } + + void cacheContext(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr, + SessionHandle & sessionHandle) + { + mVendorId = vendorId; + mProductId = productId; + mNodeId = nodeId; + mExchangeMgr = &exchangeMgr; + mSecureSession.ShiftToSession(sessionHandle); + } + + void clearContext() + { + mVendorId = 0; + mProductId = 0; + mNodeId = 0; + mExchangeMgr = nullptr; + mSecureSession.SessionReleased(); + } + uint16_t mVendorId = 0; + uint16_t mProductId = 0; + NodeId mNodeId = 0; + Messaging::ExchangeManager * mExchangeMgr = nullptr; + SessionHolder mSecureSession; }; MyPostCommissioningListener gMyPostCommissioningListener; @@ -407,6 +498,19 @@ Access::Privilege ContentAppFactoryImpl::GetVendorPrivilege(uint16_t vendorId) return Access::Privilege::kOperate; } +std::list ContentAppFactoryImpl::GetAllowedClusterListForStaticEndpoint(EndpointId endpointId, uint16_t vendorId, + uint16_t productId) +{ + if (endpointId == kLocalVideoPlayerEndpointId) + { + return { chip::app::Clusters::Descriptor::Id, chip::app::Clusters::OnOff::Id, + chip::app::Clusters::WakeOnLan::Id, chip::app::Clusters::MediaPlayback::Id, + chip::app::Clusters::LowPower::Id, chip::app::Clusters::KeypadInput::Id, + chip::app::Clusters::ContentLauncher::Id, chip::app::Clusters::AudioOutput::Id }; + } + return {}; +} + } // namespace AppPlatform } // namespace chip diff --git a/examples/tv-app/linux/AppImpl.h b/examples/tv-app/linux/AppImpl.h index 68c96d135e0630..46cdb650bd38c8 100644 --- a/examples/tv-app/linux/AppImpl.h +++ b/examples/tv-app/linux/AppImpl.h @@ -131,6 +131,11 @@ class DLL_EXPORT ContentAppFactoryImpl : public ContentAppFactory // When a vendor has admin privileges, it will get access to all clusters on ep1 Access::Privilege GetVendorPrivilege(uint16_t vendorId) override; + // Get the cluster list this vendorId/productId should have on static endpoints such as ep1 for casting video clients. + // When a vendor has admin privileges, it will get access to all clusters on ep1 + std::list GetAllowedClusterListForStaticEndpoint(EndpointId endpointId, uint16_t vendorId, + uint16_t productId) override; + void AddAdminVendorId(uint16_t vendorId); protected: diff --git a/src/app/app-platform/ContentAppPlatform.cpp b/src/app/app-platform/ContentAppPlatform.cpp index 6555e55b04aaba..736cf164fdfede 100644 --- a/src/app/app-platform/ContentAppPlatform.cpp +++ b/src/app/app-platform/ContentAppPlatform.cpp @@ -510,27 +510,11 @@ CHIP_ERROR ContentAppPlatform::GetACLEntryIndex(size_t * foundIndex, FabricIndex return CHIP_ERROR_NOT_FOUND; } -constexpr EndpointId kTargetBindingClusterEndpointId = 0; -constexpr EndpointId kLocalVideoPlayerEndpointId = 1; -constexpr EndpointId kLocalSpeakerEndpointId = 2; -constexpr ClusterId kClusterIdDescriptor = 0x001d; -constexpr ClusterId kClusterIdOnOff = 0x0006; -constexpr ClusterId kClusterIdWakeOnLAN = 0x0503; -// constexpr ClusterId kClusterIdChannel = 0x0504; -// constexpr ClusterId kClusterIdTargetNavigator = 0x0505; -constexpr ClusterId kClusterIdMediaPlayback = 0x0506; -// constexpr ClusterId kClusterIdMediaInput = 0x0507; -constexpr ClusterId kClusterIdLowPower = 0x0508; -constexpr ClusterId kClusterIdKeypadInput = 0x0509; -constexpr ClusterId kClusterIdContentLauncher = 0x050a; -constexpr ClusterId kClusterIdAudioOutput = 0x050b; -// constexpr ClusterId kClusterIdApplicationLauncher = 0x050c; -// constexpr ClusterId kClusterIdAccountLogin = 0x050e; - // Add ACLs on this device for the given client, // 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, NodeId localNodeId, + uint16_t targetVendorId, uint16_t targetProductId, NodeId localNodeId, + std::vector bindings, Controller::WriteResponseSuccessCallback successCb, Controller::WriteResponseFailureCallback failureCb) { @@ -564,8 +548,6 @@ CHIP_ERROR ContentAppPlatform::ManageClientAccess(Messaging::ExchangeManager & e ReturnErrorOnFailure(entry.SetPrivilege(vendorPrivilege)); ReturnErrorOnFailure(entry.AddSubject(nullptr, subjectNodeId)); - std::vector bindings; - /** * Here we are creating a single ACL entry containing: * a) selection of clusters on video player endpoint (8 targets) @@ -588,6 +570,7 @@ CHIP_ERROR ContentAppPlatform::ManageClientAccess(Messaging::ExchangeManager & e ChipLogProgress(Controller, "Create video player endpoint ACL and binding"); { + bool hasClusterAccess = false; if (vendorPrivilege == Access::Privilege::kAdminister) { ChipLogProgress(Controller, "ContentAppPlatform::ManageClientAccess Admin privilege granted"); @@ -595,14 +578,14 @@ CHIP_ERROR ContentAppPlatform::ManageClientAccess(Messaging::ExchangeManager & e Access::AccessControl::Entry::Target target = { .flags = Access::AccessControl::Entry::Target::kEndpoint, .endpoint = kLocalVideoPlayerEndpointId }; ReturnErrorOnFailure(entry.AddTarget(nullptr, target)); + hasClusterAccess = true; } else { ChipLogProgress(Controller, "ContentAppPlatform::ManageClientAccess non-Admin privilege granted"); // a vendor with non-admin privilege gets access to select clusters on ep1 - std::list allowedClusterList = { kClusterIdDescriptor, kClusterIdOnOff, kClusterIdWakeOnLAN, - kClusterIdMediaPlayback, kClusterIdLowPower, kClusterIdKeypadInput, - kClusterIdContentLauncher, kClusterIdAudioOutput }; + std::list allowedClusterList = mContentAppFactory->GetAllowedClusterListForStaticEndpoint( + kLocalVideoPlayerEndpointId, targetVendorId, targetProductId); for (const auto & clusterId : allowedClusterList) { @@ -611,16 +594,21 @@ CHIP_ERROR ContentAppPlatform::ManageClientAccess(Messaging::ExchangeManager & e .cluster = clusterId, .endpoint = kLocalVideoPlayerEndpointId }; ReturnErrorOnFailure(entry.AddTarget(nullptr, target)); + hasClusterAccess = true; } } - bindings.push_back(Binding::Structs::TargetStruct::Type{ - .node = MakeOptional(localNodeId), - .group = NullOptional, - .endpoint = MakeOptional(kLocalVideoPlayerEndpointId), - .cluster = NullOptional, - .fabricIndex = kUndefinedFabricIndex, - }); + if (hasClusterAccess) + { + ChipLogProgress(Controller, "ContentAppPlatform::ManageClientAccess adding a binding on ep1"); + bindings.push_back(Binding::Structs::TargetStruct::Type{ + .node = MakeOptional(localNodeId), + .group = NullOptional, + .endpoint = MakeOptional(kLocalVideoPlayerEndpointId), + .cluster = NullOptional, + .fabricIndex = kUndefinedFabricIndex, + }); + } } ChipLogProgress(Controller, "Create speaker endpoint ACL and binding"); diff --git a/src/app/app-platform/ContentAppPlatform.h b/src/app/app-platform/ContentAppPlatform.h index 3744a0fa0edf1d..69248cd431e3c7 100644 --- a/src/app/app-platform/ContentAppPlatform.h +++ b/src/app/app-platform/ContentAppPlatform.h @@ -38,6 +38,10 @@ using BindingListType = chip::app::Clusters::Binding::Attributes::Binding::TypeI namespace chip { namespace AppPlatform { +constexpr EndpointId kTargetBindingClusterEndpointId = 0; +constexpr EndpointId kLocalVideoPlayerEndpointId = 1; +constexpr EndpointId kLocalSpeakerEndpointId = 2; + class DLL_EXPORT ContentAppFactory { public: @@ -63,6 +67,11 @@ class DLL_EXPORT ContentAppFactory // and for voice agents, this may be Access::Privilege::kAdminister // When a vendor has admin privileges, it will get access to all clusters on ep1 virtual Access::Privilege GetVendorPrivilege(uint16_t vendorId) = 0; + + // Get the cluster list this vendorId/productId should have on static endpoints such as ep1 for casting video clients. + // When a vendor has admin privileges, it will get access to all clusters on ep1 + virtual std::list GetAllowedClusterListForStaticEndpoint(EndpointId endpointId, uint16_t vendorId, + uint16_t productId) = 0; }; class DLL_EXPORT ContentAppPlatform @@ -146,14 +155,18 @@ class DLL_EXPORT ContentAppPlatform * @param[in] exchangeMgr Exchange manager to be used to get an exchange context. * @param[in] sessionHandle Reference to an established session. * @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] 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. * * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error */ CHIP_ERROR ManageClientAccess(Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle, uint16_t targetVendorId, - NodeId localNodeId, Controller::WriteResponseSuccessCallback successCb, + uint16_t targetProductId, NodeId localNodeId, + std::vector bindings, + Controller::WriteResponseSuccessCallback successCb, Controller::WriteResponseFailureCallback failureCb); protected: