| /* |
| * Copyright (c) 2025 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 "CodeDrivenDataModelProvider.h" |
| #include <app/persistence/AttributePersistenceProvider.h> |
| #include <app/server-cluster/ServerClusterContext.h> |
| #include <app/server-cluster/ServerClusterInterface.h> |
| #include <data-model-providers/codedriven/endpoint/EndpointInterface.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <protocols/interaction_model/StatusCode.h> |
| |
| using chip::Protocols::InteractionModel::Status; |
| |
| namespace chip { |
| namespace app { |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::Startup(DataModel::InteractionModelContext context) |
| { |
| ReturnErrorOnFailure(DataModel::Provider::Startup(context)); |
| |
| mInteractionModelContext.emplace(context); |
| |
| mServerClusterContext.emplace(ServerClusterContext{ |
| .provider = *this, |
| .storage = mPersistentStorageDelegate, |
| .attributeStorage = mAttributePersistenceProvider, |
| .interactionContext = *mInteractionModelContext, |
| }); |
| |
| // Start up registered server clusters if one of their associated endpoints is registered. |
| bool had_failure = false; |
| for (auto * cluster : mServerClusterRegistry.AllServerClusterInstances()) |
| { |
| bool endpointRegistered = false; |
| for (const auto & path : cluster->GetPaths()) |
| { |
| if (mEndpointInterfaceRegistry.Get(path.mEndpointId) != nullptr) |
| { |
| endpointRegistered = true; |
| break; |
| } |
| } |
| |
| if (endpointRegistered) |
| { |
| if (cluster->Startup(*mServerClusterContext) != CHIP_NO_ERROR) |
| { |
| had_failure = true; |
| } |
| } |
| } |
| |
| if (had_failure) |
| { |
| return CHIP_ERROR_HAD_FAILURES; |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::Shutdown() |
| { |
| bool had_failure = false; |
| |
| // Remove all endpoints. This will trigger Shutdown() on associated clusters. |
| while (mEndpointInterfaceRegistry.begin() != mEndpointInterfaceRegistry.end()) |
| { |
| if (RemoveEndpoint(mEndpointInterfaceRegistry.begin()->GetEndpointEntry().id) != CHIP_NO_ERROR) |
| { |
| had_failure = true; |
| } |
| } |
| |
| // Now we're safe to clean up the cluster registry. |
| while (mServerClusterRegistry.AllServerClusterInstances().begin() != mServerClusterRegistry.AllServerClusterInstances().end()) |
| { |
| ServerClusterInterface * clusterToRemove = *mServerClusterRegistry.AllServerClusterInstances().begin(); |
| if (mServerClusterRegistry.Unregister(clusterToRemove) != CHIP_NO_ERROR) |
| { |
| had_failure = true; |
| } |
| } |
| |
| mServerClusterContext.reset(); |
| mInteractionModelContext.reset(); |
| |
| if (had_failure) |
| { |
| return CHIP_ERROR_HAD_FAILURES; |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| DataModel::ActionReturnStatus CodeDrivenDataModelProvider::ReadAttribute(const DataModel::ReadAttributeRequest & request, |
| AttributeValueEncoder & encoder) |
| { |
| ServerClusterInterface * serverCluster = GetServerClusterInterface(request.path); |
| VerifyOrReturnError(serverCluster != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| return serverCluster->ReadAttribute(request, encoder); |
| } |
| |
| DataModel::ActionReturnStatus CodeDrivenDataModelProvider::WriteAttribute(const DataModel::WriteAttributeRequest & request, |
| AttributeValueDecoder & decoder) |
| { |
| ServerClusterInterface * serverCluster = GetServerClusterInterface(request.path); |
| VerifyOrReturnError(serverCluster != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| return serverCluster->WriteAttribute(request, decoder); |
| } |
| |
| void CodeDrivenDataModelProvider::ListAttributeWriteNotification(const ConcreteAttributePath & path, |
| DataModel::ListWriteOperation opType) |
| { |
| ServerClusterInterface * serverCluster = GetServerClusterInterface(path); |
| VerifyOrReturn(serverCluster != nullptr); |
| serverCluster->ListAttributeWriteNotification(path, opType); |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> CodeDrivenDataModelProvider::InvokeCommand(const DataModel::InvokeRequest & request, |
| TLV::TLVReader & input_arguments, |
| CommandHandler * handler) |
| { |
| ServerClusterInterface * serverCluster = GetServerClusterInterface(request.path); |
| VerifyOrReturnError(serverCluster != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| return serverCluster->InvokeCommand(request, input_arguments, handler); |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::Endpoints(ReadOnlyBufferBuilder<DataModel::EndpointEntry> & out) |
| { |
| // TODO: Add a size() method to EndpointInterfaceRegistry to avoid iterating twice. |
| size_t count = 0; |
| for (const auto & registration : mEndpointInterfaceRegistry) |
| { |
| (void) registration; // Silence unused variable warning |
| count++; |
| } |
| |
| ReturnErrorOnFailure(out.EnsureAppendCapacity(count)); |
| for (const auto & registration : mEndpointInterfaceRegistry) |
| { |
| ReturnErrorOnFailure(out.Append(registration.GetEndpointEntry())); |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR |
| CodeDrivenDataModelProvider::SemanticTags(EndpointId endpointId, |
| ReadOnlyBufferBuilder<Clusters::Descriptor::Structs::SemanticTagStruct::Type> & out) |
| { |
| EndpointInterface * endpoint = GetEndpointInterface(endpointId); |
| VerifyOrReturnError(endpoint != nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); |
| return endpoint->SemanticTags(out); |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::DeviceTypes(EndpointId endpointId, ReadOnlyBufferBuilder<DataModel::DeviceTypeEntry> & out) |
| { |
| EndpointInterface * endpoint = GetEndpointInterface(endpointId); |
| VerifyOrReturnError(endpoint != nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); |
| return endpoint->DeviceTypes(out); |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::ClientClusters(EndpointId endpointId, ReadOnlyBufferBuilder<ClusterId> & out) |
| { |
| EndpointInterface * endpoint = GetEndpointInterface(endpointId); |
| VerifyOrReturnError(endpoint != nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); |
| return endpoint->ClientClusters(out); |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::ServerClusters(EndpointId endpointId, |
| ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> & out) |
| { |
| EndpointInterface * endpoint = GetEndpointInterface(endpointId); |
| VerifyOrReturnError(endpoint != nullptr, CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); |
| |
| size_t count = 0; |
| for (auto * cluster : mServerClusterRegistry.AllServerClusterInstances()) |
| { |
| for (const auto & path : cluster->GetPaths()) |
| { |
| if (path.mEndpointId == endpointId) |
| { |
| count++; |
| } |
| } |
| } |
| |
| ReturnErrorOnFailure(out.EnsureAppendCapacity(count)); |
| |
| for (auto * cluster : mServerClusterRegistry.AllServerClusterInstances()) |
| { |
| for (const auto & path : cluster->GetPaths()) |
| { |
| if (path.mEndpointId == endpointId) |
| { |
| ReturnErrorOnFailure( |
| out.Append({ path.mClusterId, cluster->GetDataVersion(path), cluster->GetClusterFlags(path) })); |
| } |
| } |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & out) |
| { |
| ServerClusterInterface * serverCluster = GetServerClusterInterface(path); |
| VerifyOrReturnError(serverCluster != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| return serverCluster->GeneratedCommands(path, out); |
| } |
| CHIP_ERROR CodeDrivenDataModelProvider::AcceptedCommands(const ConcreteClusterPath & path, |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & out) |
| { |
| ServerClusterInterface * serverCluster = GetServerClusterInterface(path); |
| VerifyOrReturnError(serverCluster != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| return serverCluster->AcceptedCommands(path, out); |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::Attributes(const ConcreteClusterPath & path, |
| ReadOnlyBufferBuilder<DataModel::AttributeEntry> & out) |
| { |
| ServerClusterInterface * serverCluster = GetServerClusterInterface(path); |
| VerifyOrReturnError(serverCluster != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| return serverCluster->Attributes(path, out); |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::EventInfo(const ConcreteEventPath & path, DataModel::EventEntry & eventInfo) |
| { |
| ServerClusterInterface * serverCluster = GetServerClusterInterface(path); |
| VerifyOrReturnError(serverCluster != nullptr, CHIP_ERROR_KEY_NOT_FOUND); |
| return serverCluster->EventInfo(path, eventInfo); |
| } |
| |
| void CodeDrivenDataModelProvider::Temporary_ReportAttributeChanged(const AttributePathParams & path) |
| { |
| if (!mInteractionModelContext) |
| { |
| ChipLogError(DataManagement, "Temporary_ReportAttributeChanged called before provider has been started."); |
| return; |
| } |
| mInteractionModelContext->dataModelChangeListener.MarkDirty(path); |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::AddEndpoint(EndpointInterfaceRegistration & registration) |
| { |
| VerifyOrReturnError(registration.endpointEntry.id != kInvalidEndpointId, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| // If the endpoint ID is already in use, return an error. |
| if (mEndpointInterfaceRegistry.Get(registration.endpointEntry.id) != nullptr) |
| { |
| return CHIP_ERROR_DUPLICATE_KEY_ID; |
| } |
| |
| ReturnErrorOnFailure(mEndpointInterfaceRegistry.Register(registration)); |
| |
| if (mServerClusterContext.has_value()) |
| { |
| // If the provider has been started, we need to check if any clusters on this new endpoint |
| // should be started up. |
| for (auto * cluster : mServerClusterRegistry.AllServerClusterInstances()) |
| { |
| bool clusterIsOnNewEndpoint = false; |
| int registeredEndpointCount = 0; |
| |
| for (const auto & path : cluster->GetPaths()) |
| { |
| if (mEndpointInterfaceRegistry.Get(path.mEndpointId) != nullptr) |
| { |
| registeredEndpointCount++; |
| } |
| if (path.mEndpointId == registration.endpointEntry.id) |
| { |
| clusterIsOnNewEndpoint = true; |
| } |
| } |
| |
| // If the cluster is on the endpoint we just added, and this is the *only* |
| // registered endpoint for this cluster, it's time to start it. |
| if (clusterIsOnNewEndpoint && registeredEndpointCount == 1) |
| { |
| ReturnErrorOnFailure(cluster->Startup(*mServerClusterContext)); |
| } |
| } |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::RemoveEndpoint(EndpointId endpointId) |
| { |
| if (mServerClusterContext.has_value()) |
| { |
| // If the provider has been started, we need to check if any clusters on this endpoint |
| // need to be shut down because it's their last registered endpoint. |
| for (auto * cluster : mServerClusterRegistry.AllServerClusterInstances()) |
| { |
| bool clusterIsOnEndpoint = false; |
| int registeredEndpointCount = 0; |
| |
| for (const auto & path : cluster->GetPaths()) |
| { |
| if (mEndpointInterfaceRegistry.Get(path.mEndpointId) != nullptr) |
| { |
| registeredEndpointCount++; |
| } |
| if (path.mEndpointId == endpointId) |
| { |
| clusterIsOnEndpoint = true; |
| } |
| } |
| |
| if (clusterIsOnEndpoint && registeredEndpointCount == 1) |
| { |
| // This is the last registered endpoint for this cluster. Shut it down. |
| cluster->Shutdown(); |
| } |
| } |
| } |
| |
| return mEndpointInterfaceRegistry.Unregister(endpointId); |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::AddCluster(ServerClusterRegistration & entry) |
| { |
| VerifyOrReturnError(entry.serverClusterInterface != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (mServerClusterContext.has_value()) |
| { |
| // If the provider has been started, prevent non-atomic changes to an endpoint. |
| // Check if any of the cluster's paths are associated with an already registered endpoint. |
| for (const auto & path : entry.serverClusterInterface->GetPaths()) |
| { |
| if (mEndpointInterfaceRegistry.Get(path.mEndpointId) != nullptr) |
| { |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| } |
| } |
| |
| return mServerClusterRegistry.Register(entry); |
| } |
| |
| CHIP_ERROR CodeDrivenDataModelProvider::RemoveCluster(ServerClusterInterface * cluster) |
| { |
| VerifyOrReturnError(cluster != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (mServerClusterContext.has_value()) |
| { |
| for (const auto & path : cluster->GetPaths()) |
| { |
| if (mEndpointInterfaceRegistry.Get(path.mEndpointId) != nullptr) |
| { |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| } |
| } |
| |
| return mServerClusterRegistry.Unregister(cluster); |
| } |
| |
| EndpointInterface * CodeDrivenDataModelProvider::GetEndpointInterface(EndpointId endpointId) |
| { |
| return mEndpointInterfaceRegistry.Get(endpointId); |
| } |
| |
| ServerClusterInterface * CodeDrivenDataModelProvider::GetServerClusterInterface(const ConcreteClusterPath & clusterPath) |
| { |
| return mServerClusterRegistry.Get(clusterPath); |
| } |
| |
| } // namespace app |
| } // namespace chip |