/* * * Copyright (c) 2020 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 Binding Server Cluster ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using TargetStructType = Binding::Structs::TargetStruct::Type; using DecodableBindingListType = Binding::Attributes::Binding::TypeInfo::DecodableType; // TODO: add binding table to the persistent storage namespace { class BindingTableAccess : public AttributeAccessInterface { public: // Register for the Binding cluster on all endpoints. BindingTableAccess() : AttributeAccessInterface(NullOptional, Binding::Id) {} CHIP_ERROR Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) override; CHIP_ERROR Write(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) override; void OnListWriteEnd(const app::ConcreteAttributePath & aPath, bool aWriteWasSuccessful) override; private: CHIP_ERROR ReadBindingTable(EndpointId endpoint, AttributeValueEncoder & encoder); CHIP_ERROR WriteBindingTable(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder); CHIP_ERROR NotifyBindingsChanged(); FabricIndex mAccessingFabricIndex; }; BindingTableAccess gAttrAccess; bool IsValidBinding(const EndpointId localEndpoint, const TargetStructType & entry) { bool isValid = false; // Entry has endpoint, node id and no group id if (!entry.group.HasValue() && entry.endpoint.HasValue() && entry.node.HasValue()) { if (entry.cluster.HasValue()) { if (emberAfContainsClient(localEndpoint, entry.cluster.Value())) { // Valid node/endpoint/cluster binding isValid = true; } } else { // Valid node/endpoint (no cluster id) binding isValid = true; } } // Entry has group id and no endpoint and node id else if (!entry.endpoint.HasValue() && !entry.node.HasValue() && entry.group.HasValue()) { // Valid group binding isValid = true; } return isValid; } CHIP_ERROR CheckValidBindingList(const EndpointId localEndpoint, const DecodableBindingListType & bindingList, FabricIndex accessingFabricIndex) { size_t listSize = 0; auto iter = bindingList.begin(); while (iter.Next()) { VerifyOrReturnError(IsValidBinding(localEndpoint, iter.GetValue()), CHIP_IM_GLOBAL_STATUS(ConstraintError)); listSize++; } ReturnErrorOnFailure(iter.GetStatus()); // Check binding table size uint8_t oldListSize = 0; for (const auto & entry : BindingTable::GetInstance()) { if (entry.fabricIndex == accessingFabricIndex) { oldListSize++; } } VerifyOrReturnError(BindingTable::GetInstance().Size() - oldListSize + listSize <= MATTER_BINDING_TABLE_SIZE, CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); return CHIP_NO_ERROR; } CHIP_ERROR CreateBindingEntry(const TargetStructType & entry, EndpointId localEndpoint) { EmberBindingTableEntry bindingEntry; if (entry.group.HasValue()) { bindingEntry = EmberBindingTableEntry::ForGroup(entry.fabricIndex, entry.group.Value(), localEndpoint, entry.cluster.std_optional()); } else { bindingEntry = EmberBindingTableEntry::ForNode(entry.fabricIndex, entry.node.Value(), localEndpoint, entry.endpoint.Value(), entry.cluster.std_optional()); } return AddBindingEntry(bindingEntry); } CHIP_ERROR BindingTableAccess::Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder) { switch (path.mAttributeId) { case Binding::Attributes::Binding::Id: return ReadBindingTable(path.mEndpointId, encoder); default: break; } return CHIP_NO_ERROR; } CHIP_ERROR BindingTableAccess::ReadBindingTable(EndpointId endpoint, AttributeValueEncoder & encoder) { return encoder.EncodeList([&](const auto & subEncoder) { for (const EmberBindingTableEntry & entry : BindingTable::GetInstance()) { if (entry.local == endpoint && entry.type == MATTER_UNICAST_BINDING) { Binding::Structs::TargetStruct::Type value = { .node = MakeOptional(entry.nodeId), .group = NullOptional, .endpoint = MakeOptional(entry.remote), .cluster = FromStdOptional(entry.clusterId), .fabricIndex = entry.fabricIndex, }; ReturnErrorOnFailure(subEncoder.Encode(value)); } else if (entry.local == endpoint && entry.type == MATTER_MULTICAST_BINDING) { Binding::Structs::TargetStruct::Type value = { .node = NullOptional, .group = MakeOptional(entry.groupId), .endpoint = NullOptional, .cluster = FromStdOptional(entry.clusterId), .fabricIndex = entry.fabricIndex, }; ReturnErrorOnFailure(subEncoder.Encode(value)); } } return CHIP_NO_ERROR; }); } CHIP_ERROR BindingTableAccess::Write(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) { switch (path.mAttributeId) { case Binding::Attributes::Binding::Id: return WriteBindingTable(path, decoder); default: break; } return CHIP_NO_ERROR; } void BindingTableAccess::OnListWriteEnd(const app::ConcreteAttributePath & aPath, bool aWriteWasSuccessful) { // Notify binding table has changed LogErrorOnFailure(NotifyBindingsChanged()); } CHIP_ERROR BindingTableAccess::WriteBindingTable(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder) { mAccessingFabricIndex = decoder.AccessingFabricIndex(); if (!path.IsListOperation() || path.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll) { DecodableBindingListType newBindingList; ReturnErrorOnFailure(decoder.Decode(newBindingList)); ReturnErrorOnFailure(CheckValidBindingList(path.mEndpointId, newBindingList, mAccessingFabricIndex)); // Clear all entries for the current accessing fabric and endpoint auto bindingTableIter = BindingTable::GetInstance().begin(); while (bindingTableIter != BindingTable::GetInstance().end()) { if (bindingTableIter->local == path.mEndpointId && bindingTableIter->fabricIndex == mAccessingFabricIndex) { if (bindingTableIter->type == MATTER_UNICAST_BINDING) { BindingManager::GetInstance().UnicastBindingRemoved(bindingTableIter.GetIndex()); } ReturnErrorOnFailure(BindingTable::GetInstance().RemoveAt(bindingTableIter)); } else { ++bindingTableIter; } } // Add new entries auto iter = newBindingList.begin(); CHIP_ERROR err = CHIP_NO_ERROR; while (iter.Next() && err == CHIP_NO_ERROR) { err = CreateBindingEntry(iter.GetValue(), path.mEndpointId); } // If this was not caused by a list operation, OnListWriteEnd is not going to be triggered // so a notification is sent here. if (!path.IsListOperation()) { // Notify binding table has changed LogErrorOnFailure(NotifyBindingsChanged()); } return err; } if (path.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem) { TargetStructType target; ReturnErrorOnFailure(decoder.Decode(target)); if (!IsValidBinding(path.mEndpointId, target)) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } return CreateBindingEntry(target, path.mEndpointId); } return CHIP_IM_GLOBAL_STATUS(UnsupportedWrite); } CHIP_ERROR BindingTableAccess::NotifyBindingsChanged() { DeviceLayer::ChipDeviceEvent event{ .Type = DeviceLayer::DeviceEventType::kBindingsChangedViaCluster, .BindingsChanged = { .fabricIndex = mAccessingFabricIndex } }; return chip::DeviceLayer::PlatformMgr().PostEvent(&event); } } // namespace void MatterBindingPluginServerInitCallback() { AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); } CHIP_ERROR AddBindingEntry(const EmberBindingTableEntry & entry) { CHIP_ERROR err = BindingTable::GetInstance().Add(entry); if (err == CHIP_ERROR_NO_MEMORY) { return CHIP_IM_GLOBAL_STATUS(ResourceExhausted); } if (err != CHIP_NO_ERROR) { return err; } if (entry.type == MATTER_UNICAST_BINDING) { err = BindingManager::GetInstance().UnicastBindingCreated(entry.fabricIndex, entry.nodeId); if (err != CHIP_NO_ERROR) { // Unicast connection failure can happen if peer is offline. We'll retry connection on-demand. ChipLogError( Zcl, "Binding: Failed to create session for unicast binding to device " ChipLogFormatX64 ": %" CHIP_ERROR_FORMAT, ChipLogValueX64(entry.nodeId), err.Format()); } } return CHIP_NO_ERROR; }