blob: 1b9f15b121b44c7a308c142f2172323b6ae4585d [file] [log] [blame]
/*
* Copyright (c) 2021-2025 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 <app/clusters/user-label-server/UserLabelCluster.h>
#include <app/server-cluster/AttributeListBuilder.h>
#include <app/server/Server.h>
#include <clusters/UserLabel/Metadata.h>
#include <array>
namespace chip::app::Clusters {
using namespace UserLabel;
using namespace UserLabel::Attributes;
namespace {
class AutoReleaseIterator
{
public:
AutoReleaseIterator(DeviceLayer::DeviceInfoProvider * provider, EndpointId endpointId) :
mIterator(provider != nullptr ? provider->IterateUserLabel(endpointId) : nullptr)
{}
~AutoReleaseIterator()
{
if (mIterator != nullptr)
{
mIterator->Release();
}
}
bool IsValid() const { return mIterator != nullptr; }
DeviceLayer::DeviceInfoProvider::UserLabelIterator * operator->() { return mIterator; }
private:
DeviceLayer::DeviceInfoProvider::UserLabelIterator * mIterator;
};
CHIP_ERROR ReadLabelList(EndpointId endpoint, AttributeValueEncoder & encoder)
{
AutoReleaseIterator it(DeviceLayer::GetDeviceInfoProvider(), endpoint);
VerifyOrReturnValue(it.IsValid(), encoder.EncodeEmptyList());
return encoder.EncodeList([&it](const auto & encod) -> CHIP_ERROR {
UserLabel::Structs::LabelStruct::Type userlabel;
while (it->Next(userlabel))
{
ReturnErrorOnFailure(encod.Encode(userlabel));
}
return CHIP_NO_ERROR;
});
}
/// Matches constraints on a LabelStruct.
bool IsValidLabelEntry(const Structs::LabelStruct::Type & entry)
{
// NOTE: spec default for label and value is empty, so empty is accepted here
return (entry.label.size() <= UserLabelCluster::kMaxLabelSize) && (entry.value.size() <= UserLabelCluster::kMaxValueSize);
}
CHIP_ERROR WriteLabelList(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder)
{
DeviceLayer::DeviceInfoProvider * provider = DeviceLayer::GetDeviceInfoProvider();
VerifyOrReturnError(provider != nullptr, CHIP_ERROR_NOT_IMPLEMENTED);
EndpointId endpoint = path.mEndpointId;
if (!path.IsListItemOperation())
{
size_t numLabels = 0;
std::array<Structs::LabelStruct::Type, DeviceLayer::kMaxUserLabelListLength> labels;
LabelList::TypeInfo::DecodableType decodablelist;
ReturnErrorOnFailure(decoder.Decode(decodablelist));
auto iter = decodablelist.begin();
while (iter.Next())
{
auto & label = iter.GetValue();
VerifyOrReturnError(IsValidLabelEntry(label), CHIP_IM_GLOBAL_STATUS(ConstraintError));
VerifyOrReturnError(numLabels < labels.size(), CHIP_ERROR_NO_MEMORY);
labels[numLabels++] = label;
}
ReturnErrorOnFailure(iter.GetStatus());
return provider->SetUserLabelList(endpoint, Span(labels.data(), numLabels));
}
if (path.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
{
Structs::LabelStruct::DecodableType entry;
ReturnErrorOnFailure(decoder.Decode(entry));
VerifyOrReturnError(IsValidLabelEntry(entry), CHIP_IM_GLOBAL_STATUS(ConstraintError));
// Append the single user label entry
CHIP_ERROR err = provider->AppendUserLabel(endpoint, entry);
if (err == CHIP_ERROR_NO_MEMORY)
{
return CHIP_IM_GLOBAL_STATUS(ResourceExhausted);
}
return err;
}
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
}
} // namespace
UserLabelCluster::UserLabelCluster(EndpointId endpoint) : DefaultServerCluster({ endpoint, UserLabel::Id }) {}
DataModel::ActionReturnStatus UserLabelCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder)
{
switch (request.path.mAttributeId)
{
case LabelList::Id:
return ReadLabelList(mPath.mEndpointId, encoder);
case ClusterRevision::Id:
return encoder.Encode(UserLabel::kRevision);
case FeatureMap::Id:
return encoder.Encode<uint32_t>(0);
default:
return Protocols::InteractionModel::Status::UnsupportedAttribute;
}
}
DataModel::ActionReturnStatus UserLabelCluster::WriteAttribute(const DataModel::WriteAttributeRequest & request,
AttributeValueDecoder & decoder)
{
switch (request.path.mAttributeId)
{
case LabelList::Id:
return NotifyAttributeChangedIfSuccess(LabelList::Id, WriteLabelList(request.path, decoder));
default:
return Protocols::InteractionModel::Status::UnsupportedWrite;
}
}
CHIP_ERROR UserLabelCluster::Attributes(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
{
AttributeListBuilder listBuilder(builder);
return listBuilder.Append(Span(UserLabel::Attributes::kMandatoryMetadata), {});
}
CHIP_ERROR UserLabelCluster::Startup(ServerClusterContext & context)
{
ReturnErrorOnFailure(DefaultServerCluster::Startup(context));
return Server::GetInstance().GetFabricTable().AddFabricDelegate(this);
}
void UserLabelCluster::Shutdown(ClusterShutdownType shutdownType)
{
Server::GetInstance().GetFabricTable().RemoveFabricDelegate(this);
DefaultServerCluster::Shutdown(shutdownType);
}
void UserLabelCluster::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex)
{
// If the FabricIndex matches the last remaining entry in the Fabrics list, then the device SHALL delete all Matter
// related data on the node which was created since it was commissioned.
VerifyOrReturn(Server::GetInstance().GetFabricTable().FabricCount() == 0);
ChipLogProgress(Zcl, "UserLabel: Last Fabric index 0x%x was removed", static_cast<unsigned>(fabricIndex));
// Delete all user label data on the node which was added since it was commissioned.
DeviceLayer::DeviceInfoProvider * provider = DeviceLayer::GetDeviceInfoProvider();
VerifyOrReturn(provider != nullptr);
// If UserLabel cluster is implemented on this endpoint
if (CHIP_NO_ERROR != provider->ClearUserLabelList(mPath.mEndpointId))
{
ChipLogError(Zcl, "UserLabel: Failed to clear UserLabelList for endpoint: %d", mPath.mEndpointId);
}
}
} // namespace chip::app::Clusters