blob: 3a9a027e5844a29e6eac10a63fda15c9932020ad [file] [log] [blame]
/*
* 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