-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Read/Subscribe Attribute Cache (#11938)
* Read-Only Attribute Cache This implements an attribute cache designed to aggregate attribute data received by a client from either read or subscribe interactions and keep it resident and available for clients to query at any time while the cache is active. The cache can be used with either read/subscribe, with the consumer connecting it up appropriately to the right ReadClient instance. The cache provides an up-to-date and consistent view of the state of a target node, with the scope of the state being determined by the associated ReadClient's path set. The cache provides a number of getters and helper functions to iterate over the topology of the received data which is organized by endpoint, cluster and attribute ID. These permit greater flexibility when dealing with interactions that use wildcards heavily. The data is stored internally in the cache as TLV. This permits re-use of the existing cluster objects to de-serialize the state on-demand. The cache serves as a callback adapter as well in that it 'forwards' the ReadClient::Callback calls transparently through to a registered callback. In addition, it provides its own enhancements to the base ReadClient::Callback to make it easier to know what has changed in the cache. Tests: Unit-test * Apply suggestions from code review Co-authored-by: Boris Zbarsky <[email protected]> * Review feedback * Restyle Co-authored-by: Boris Zbarsky <[email protected]>
- Loading branch information
Showing
21 changed files
with
9,558 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
/* | ||
* | ||
* Copyright (c) 2021 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 "system/SystemPacketBuffer.h" | ||
#include <app/AttributeCache.h> | ||
#include <app/InteractionModelEngine.h> | ||
#include <tuple> | ||
|
||
namespace chip { | ||
namespace app { | ||
|
||
CHIP_ERROR AttributeCache::UpdateCache(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) | ||
{ | ||
AttributeState state; | ||
System::PacketBufferHandle handle; | ||
System::PacketBufferTLVWriter writer; | ||
|
||
if (apData) | ||
{ | ||
handle = System::PacketBufferHandle::New(chip::app::kMaxSecureSduLengthBytes); | ||
|
||
writer.Init(std::move(handle), false); | ||
|
||
ReturnErrorOnFailure(writer.CopyElement(TLV::AnonymousTag, *apData)); | ||
ReturnErrorOnFailure(writer.Finalize(&handle)); | ||
|
||
// | ||
// Compact the buffer down to a more reasonably sized packet buffer | ||
// if we can. | ||
// | ||
handle.RightSize(); | ||
|
||
state.Set<System::PacketBufferHandle>(std::move(handle)); | ||
} | ||
else | ||
{ | ||
state.Set<StatusIB>(aStatus); | ||
} | ||
|
||
// | ||
// if the endpoint didn't exist previously, let's track the insertion | ||
// so that we can inform our callback of a new endpoint being added appropriately. | ||
// | ||
if (mCache.find(aPath.mEndpointId) == mCache.end()) | ||
{ | ||
mAddedEndpoints.push_back(aPath.mEndpointId); | ||
} | ||
|
||
mCache[aPath.mEndpointId][aPath.mClusterId][aPath.mAttributeId] = std::move(state); | ||
mChangedAttributeSet.insert(aPath); | ||
return CHIP_NO_ERROR; | ||
} | ||
|
||
void AttributeCache::OnReportBegin(const ReadClient * apReadClient) | ||
{ | ||
mChangedAttributeSet.clear(); | ||
mAddedEndpoints.clear(); | ||
mCallback.OnReportBegin(apReadClient); | ||
} | ||
|
||
void AttributeCache::OnReportEnd(const ReadClient * apReadClient) | ||
{ | ||
std::set<std::tuple<EndpointId, ClusterId>> changedClusters; | ||
|
||
// | ||
// Add the EndpointId and ClusterId into a set so that we only | ||
// convey unique combinations in the subsequent OnClusterChanged callback. | ||
// | ||
for (auto & path : mChangedAttributeSet) | ||
{ | ||
mCallback.OnAttributeChanged(this, path); | ||
changedClusters.insert(std::make_tuple(path.mEndpointId, path.mClusterId)); | ||
} | ||
|
||
for (auto & item : changedClusters) | ||
{ | ||
mCallback.OnClusterChanged(this, std::get<0>(item), std::get<1>(item)); | ||
} | ||
|
||
for (auto endpoint : mAddedEndpoints) | ||
{ | ||
mCallback.OnEndpointAdded(this, endpoint); | ||
} | ||
|
||
mCallback.OnReportEnd(apReadClient); | ||
} | ||
|
||
void AttributeCache::OnAttributeData(const ReadClient * apReadClient, const ConcreteDataAttributePath & aPath, | ||
TLV::TLVReader * apData, const StatusIB & aStatus) | ||
{ | ||
// | ||
// Since the cache itself is a ReadClient::Callback, it may be incorrectly passed in directly when registering with the | ||
// ReadClient. This should be avoided, since that bypasses the built-in buffered reader adapter callback that is needed for | ||
// lists to work correctly. | ||
// | ||
// Instead, the right callback should be retrieved using GetBufferedCallback(). | ||
// | ||
// To catch such errors, we validate that the provided concrete path never indicates a raw list item operation (which the | ||
// buffered reader will handle and convert for us). | ||
// | ||
// | ||
VerifyOrDie(!aPath.IsListItemOperation()); | ||
|
||
UpdateCache(aPath, apData, aStatus); | ||
|
||
// | ||
// Forward the call through. | ||
// | ||
mCallback.OnAttributeData(apReadClient, aPath, apData, aStatus); | ||
} | ||
|
||
CHIP_ERROR AttributeCache::Get(const ConcreteAttributePath & path, TLV::TLVReader & reader) | ||
{ | ||
CHIP_ERROR err; | ||
|
||
auto attributeState = GetAttributeState(path.mEndpointId, path.mClusterId, path.mAttributeId, err); | ||
ReturnErrorOnFailure(err); | ||
|
||
if (attributeState->Is<StatusIB>()) | ||
{ | ||
return CHIP_ERROR_IM_STATUS_CODE_RECEIVED; | ||
} | ||
|
||
System::PacketBufferTLVReader bufReader; | ||
|
||
bufReader.Init(attributeState->Get<System::PacketBufferHandle>().Retain()); | ||
ReturnErrorOnFailure(bufReader.Next()); | ||
|
||
reader.Init(bufReader); | ||
return CHIP_NO_ERROR; | ||
} | ||
|
||
AttributeCache::EndpointState * AttributeCache::GetEndpointState(EndpointId endpointId, CHIP_ERROR & err) | ||
{ | ||
auto endpointIter = mCache.find(endpointId); | ||
if (endpointIter == mCache.end()) | ||
{ | ||
err = CHIP_ERROR_KEY_NOT_FOUND; | ||
return nullptr; | ||
} | ||
|
||
err = CHIP_NO_ERROR; | ||
return &endpointIter->second; | ||
} | ||
|
||
AttributeCache::ClusterState * AttributeCache::GetClusterState(EndpointId endpointId, ClusterId clusterId, CHIP_ERROR & err) | ||
{ | ||
auto endpointState = GetEndpointState(endpointId, err); | ||
if (err != CHIP_NO_ERROR) | ||
{ | ||
return nullptr; | ||
} | ||
|
||
auto clusterState = endpointState->find(clusterId); | ||
if (clusterState == endpointState->end()) | ||
{ | ||
err = CHIP_ERROR_KEY_NOT_FOUND; | ||
return nullptr; | ||
} | ||
|
||
err = CHIP_NO_ERROR; | ||
return &clusterState->second; | ||
} | ||
|
||
AttributeCache::AttributeState * AttributeCache::GetAttributeState(EndpointId endpointId, ClusterId clusterId, | ||
AttributeId attributeId, CHIP_ERROR & err) | ||
{ | ||
auto clusterState = GetClusterState(endpointId, clusterId, err); | ||
if (err != CHIP_NO_ERROR) | ||
{ | ||
return nullptr; | ||
} | ||
|
||
auto attributeState = clusterState->find(attributeId); | ||
if (attributeState == clusterState->end()) | ||
{ | ||
err = CHIP_ERROR_KEY_NOT_FOUND; | ||
return nullptr; | ||
} | ||
|
||
err = CHIP_NO_ERROR; | ||
return &attributeState->second; | ||
} | ||
|
||
CHIP_ERROR AttributeCache::GetStatus(const ConcreteAttributePath & path, StatusIB & status) | ||
{ | ||
CHIP_ERROR err; | ||
|
||
auto attributeState = GetAttributeState(path.mEndpointId, path.mClusterId, path.mAttributeId, err); | ||
ReturnErrorOnFailure(err); | ||
|
||
if (!attributeState->Is<StatusIB>()) | ||
{ | ||
return CHIP_ERROR_INVALID_ARGUMENT; | ||
} | ||
|
||
status = attributeState->Get<StatusIB>(); | ||
return CHIP_NO_ERROR; | ||
} | ||
|
||
} // namespace app | ||
} // namespace chip |
Oops, something went wrong.