/* * * Copyright (c) 2021 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. */ /**************************************************************************** * @file * @brief Implementation for the User Label Server Cluster ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::UserLabel; using namespace chip::app::Clusters::UserLabel::Attributes; namespace { class UserLabelAttrAccess : public AttributeAccessInterface { public: // Register for the User Label cluster on all endpoints. UserLabelAttrAccess() : AttributeAccessInterface(Optional::Missing(), UserLabel::Id) {} CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override; private: CHIP_ERROR ReadLabelList(EndpointId endpoint, AttributeValueEncoder & aEncoder); CHIP_ERROR WriteLabelList(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder); }; /// Matches constraints on a LabelStruct. bool IsValidLabelEntry(const Structs::LabelStruct::Type & entry) { constexpr size_t kMaxLabelSize = 16; constexpr size_t kMaxValueSize = 16; // NOTE: spec default for label and value is empty, so empty is accepted here return (entry.label.size() <= kMaxLabelSize) && (entry.value.size() <= kMaxValueSize); } bool IsValidLabelEntryList(const LabelList::TypeInfo::DecodableType & list) { auto iter = list.begin(); while (iter.Next()) { if (!IsValidLabelEntry(iter.GetValue())) { return false; } } return true; } UserLabelAttrAccess gAttrAccess; CHIP_ERROR UserLabelAttrAccess::ReadLabelList(EndpointId endpoint, AttributeValueEncoder & aEncoder) { CHIP_ERROR err = CHIP_NO_ERROR; DeviceLayer::DeviceInfoProvider * provider = DeviceLayer::GetDeviceInfoProvider(); if (provider) { DeviceLayer::DeviceInfoProvider::UserLabelIterator * it = provider->IterateUserLabel(endpoint); if (it) { err = aEncoder.EncodeList([&it](const auto & encoder) -> CHIP_ERROR { UserLabel::Structs::LabelStruct::Type userlabel; while (it->Next(userlabel)) { ReturnErrorOnFailure(encoder.Encode(userlabel)); } return CHIP_NO_ERROR; }); it->Release(); } else { err = aEncoder.EncodeEmptyList(); } } else { err = aEncoder.EncodeEmptyList(); } return err; } CHIP_ERROR UserLabelAttrAccess::WriteLabelList(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) { EndpointId endpoint = aPath.mEndpointId; DeviceLayer::DeviceInfoProvider * provider = DeviceLayer::GetDeviceInfoProvider(); VerifyOrReturnError(provider != nullptr, CHIP_ERROR_NOT_IMPLEMENTED); if (!aPath.IsListItemOperation()) { DeviceLayer::AttributeList labelList; LabelList::TypeInfo::DecodableType decodablelist; ReturnErrorOnFailure(aDecoder.Decode(decodablelist)); VerifyOrReturnError(IsValidLabelEntryList(decodablelist), CHIP_IM_GLOBAL_STATUS(ConstraintError)); auto iter = decodablelist.begin(); while (iter.Next()) { auto & entry = iter.GetValue(); ReturnErrorOnFailure(labelList.add(entry)); } ReturnErrorOnFailure(iter.GetStatus()); return provider->SetUserLabelList(endpoint, labelList); } if (aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem) { Structs::LabelStruct::DecodableType entry; ReturnErrorOnFailure(aDecoder.Decode(entry)); VerifyOrReturnError(IsValidLabelEntry(entry), CHIP_IM_GLOBAL_STATUS(ConstraintError)); return provider->AppendUserLabel(endpoint, entry); } return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; } CHIP_ERROR UserLabelAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) { VerifyOrDie(aPath.mClusterId == UserLabel::Id); switch (aPath.mAttributeId) { case LabelList::Id: return ReadLabelList(aPath.mEndpointId, aEncoder); default: break; } return CHIP_NO_ERROR; } CHIP_ERROR UserLabelAttrAccess::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) { VerifyOrDie(aPath.mClusterId == UserLabel::Id); switch (aPath.mAttributeId) { case LabelList::Id: return WriteLabelList(aPath, aDecoder); default: break; } return CHIP_NO_ERROR; } } // anonymous namespace class UserLabelFabricTableDelegate : public chip::FabricTable::Delegate { public: // Gets called when a fabric is deleted void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override { // 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. if (Server::GetInstance().GetFabricTable().FabricCount() == 0) { ChipLogProgress(Zcl, "UserLabel: Last Fabric index 0x%x was removed", static_cast(fabricIndex)); // Delete all user label data on the node which was added since it was commissioned. DeviceLayer::DeviceInfoProvider * provider = DeviceLayer::GetDeviceInfoProvider(); if (provider) { for (auto endpoint : EnabledEndpointsWithServerCluster(UserLabel::Id)) { // If UserLabel cluster is implemented on this endpoint if (CHIP_NO_ERROR != provider->ClearUserLabelList(endpoint)) { ChipLogError(Zcl, "UserLabel::Failed to clear UserLabelList for endpoint:%d", endpoint); } } } } } }; UserLabelFabricTableDelegate gUserLabelFabricDelegate; void MatterUserLabelPluginServerInitCallback() { AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); Server::GetInstance().GetFabricTable().AddFabricDelegate(&gUserLabelFabricDelegate); }