Skip to content

Commit

Permalink
Accessors will not be indexed by EndpointId/ClusterId. Caller queries…
Browse files Browse the repository at this point in the history
… all accessors to satisfy the request and returns as soon as a succes/failure is encountered. Accessors will return 'pw::Status::NotFound()' as a way of hinting the caller 'look for an alternative'. If no accessors were registered or all returned 'NotFound' the fallback path is taken.
  • Loading branch information
sxb427 committed Feb 4, 2025
1 parent 95c1d6b commit 9347c92
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 175 deletions.
51 changes: 22 additions & 29 deletions examples/common/pigweed/rpc_services/AttributeAccessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ namespace rpc {
class AttributeAccessor
{
public:
/**
* aEndpointId can be Missing to indicate that this object is meant to be
* used with all endpoints.
*/
AttributeAccessor(Optional<EndpointId> aEndpointId, ClusterId aClusterId) : mEndpointId(aEndpointId), mClusterId(aClusterId) {}
AttributeAccessor() = default;
virtual ~AttributeAccessor() {}

/**
Expand All @@ -56,15 +52,18 @@ class AttributeAccessor
* The implementation can do one of three things:
*
* 1) Return a failure. This is treated as a failed read and the error is
* returned to the client, by converting it to a StatusIB.
* 2) Return success and attempt to encode data using aEncoder. The data is
* returned to the client.
* 3) Return success and not attempt to encode any data using aEncoder. In
* this case, Ember attribute access will happen for the read. This may
* involve reading from the attribute store or external attribute
* callbacks.
* returned to the client. Caller will not look for alternatives
* paths to read when any of the accessors returns a failure.
* 2) Return not found. This implies the accessor does not handle read to
* the specified attribute. Caller can look for another accessor or
* use the fallback path in when a "not found" is returned.
* 3) Return success and attempt to encode data using aEncoder. The data is
* returned to the client. Fallback path will not be taken.
*/
virtual ::pw::Status Read(const chip::app::ConcreteReadAttributePath & aPath, chip::app::AttributeValueEncoder & aEncoder) = 0;
virtual ::pw::Status Read(const chip::app::ConcreteReadAttributePath & aPath, chip::app::AttributeValueEncoder & aEncoder)
{
return ::pw::Status::NotFound();
}

/**
* Callback for writing attributes.
Expand All @@ -75,26 +74,20 @@ class AttributeAccessor
*
* The implementation can do one of three things:
*
* 1) Return a failure. This is treated as a failed write and the error is
* sent to the client, by converting it to a StatusIB.
* 2) Return success and attempt to decode from aDecoder. This is
* treated as a successful write.
* 3) Return success and not attempt to decode from aDecoder. In
* this case, Ember attribute access will happen for the write. This may
* involve writing to the attribute store or external attribute
* callbacks.
* 1) Return a failure. This is treated as a failed write and the error is
* sent to the client. Caller will not look for alternatives
* paths to write when any of the accessors returns a failure.
* 2) Return not found. This implies the accessor does not handle write to
* the specified attribute. Caller can look for another accessor or
* use the fallback path in when a "not found" is returned.
* 3) Return success and attempt to decode from aDecoder. This is
* treated as a successful write. Caller will treat this as a successful
* write and report to client. Fallback path will not be taken.
*/
virtual ::pw::Status Write(const chip::app::ConcreteDataAttributePath & aPath, chip::app::AttributeValueDecoder & aDecoder)
{
return ::pw::Status::PermissionDenied();
return ::pw::Status::NotFound();
}

Optional<EndpointId> GetEndpointId() { return mEndpointId; }
ClusterId GetClusterId() { return mClusterId; }

private:
Optional<EndpointId> mEndpointId;
ClusterId mClusterId;
};

} // namespace rpc
Expand Down
130 changes: 10 additions & 120 deletions examples/common/pigweed/rpc_services/AttributeAccessorRegistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,8 @@
#pragma once

#include "pw_status/status.h"
#include <app/AttributeReportBuilder.h>
#include <app/AttributeValueDecoder.h>
#include <app/AttributeValueEncoder.h>
#include <map>
#include <pigweed/rpc_services/AttributeAccessor.h>
#include <vector>
#include <set>

namespace chip {
namespace rpc {
Expand All @@ -42,133 +38,28 @@ class AttributeAccessorRegistry
{
public:
/**
* Register an attribute access override. If the accessor objects EndpointId
* id NULL, the registration happens at cluster level, i.e. all endpoints
* can use this accessor. Registrations can be unregistered via one of the
* Unregister* calls.
* Registration will fail if there is an already-registered override for the
* same set of attributes.
*
* @return false if there is an existing override that the new one would
* conflict with. In this case the override is not registered.
* @return true if registration was successful.
* Register an attribute access override.
*/
bool Register(AttributeAccessor * attrOverride)
{
Optional<EndpointId> endpointId = attrOverride->GetEndpointId();
ClusterId clusterId = attrOverride->GetClusterId();
if (endpointId.HasValue())
{
std::pair<EndpointId, ClusterId> endpointClusterPair = std::make_pair(endpointId.Value(), clusterId);
if (perEndpointClusterAccessors.find(endpointClusterPair) != perEndpointClusterAccessors.end())
{
ChipLogError(Support, "Duplicate attribute override registration failed. endpointId: %u , clusterId: %u",
endpointId.Value(), clusterId);
return false;
}
ChipLogProgress(Support, "Registering attribute accessor for endpointId: %u , clusterId: %u", endpointId.Value(),
clusterId);
perEndpointClusterAccessors.insert(std::make_pair(endpointClusterPair, attrOverride));
}
else
{
if (perClusterAccessors.find(clusterId) != perClusterAccessors.end())
{
ChipLogError(Support, "Duplicate attribute override registration failed. clusterId: %u", clusterId);
return false;
}
ChipLogProgress(Support, "Registering attribute accessor for clusterId: %u", clusterId);
perClusterAccessors.insert(std::make_pair(clusterId, attrOverride));
}
return true;
}
void Register(AttributeAccessor * attrOverride) { mAccessors.insert(attrOverride); }

/**
* Unregister an attribute access override (for example if the object
* implementing AttributeAccessor is being destroyed).
*/
void Unregister(AttributeAccessor * attrOverride)
{
Optional<EndpointId> endpointId = attrOverride->GetEndpointId();
ClusterId clusterId = attrOverride->GetClusterId();
if (endpointId.HasValue())
{
std::pair<EndpointId, ClusterId> endpointClusterPair = std::make_pair(endpointId.Value(), clusterId);
if (perEndpointClusterAccessors.find(endpointClusterPair) == perEndpointClusterAccessors.end())
{
ChipLogError(Support, "Unregistration failed. No registration found for endpointId: %u , clusterId: %u",
endpointId.Value(), clusterId);
return;
}
perEndpointClusterAccessors.erase(endpointClusterPair);
}
else
{
if (perClusterAccessors.find(clusterId) == perClusterAccessors.end())
{
ChipLogError(Support, "Unregistration failed. No registration found for clusterId: %u", clusterId);
return;
}
perClusterAccessors.erase(clusterId);
}
}

/**
* Unregister all attribute accessors that match this given endpoint.
* Cluster level attribute accessors do not get unregistered.
*/
void UnregisterAllForEndpoint(EndpointId endpointId)
{
std::vector<std::pair<EndpointId, ClusterId>> removeRegistrations;
for (std::map<std::pair<EndpointId, ClusterId>, AttributeAccessor *>::iterator it = perEndpointClusterAccessors.begin();
it != perEndpointClusterAccessors.end(); ++it)
{
if ((it->first).first == endpointId)
{
removeRegistrations.push_back(it->first);
}
}
for (std::pair<EndpointId, ClusterId> endpointClusterPair : removeRegistrations)
if (mAccessors.find(attrOverride) == mAccessors.end())
{
ChipLogProgress(Support, "Removing attribute accessor registration for endpointId: %u , clusterId: %u",
endpointClusterPair.first, endpointClusterPair.second);
perEndpointClusterAccessors.erase(endpointClusterPair);
ChipLogError(Support, "Attempt to unregister accessor that is not registered.");
return;
}
mAccessors.erase(attrOverride);
}

/**
* Unregisters all attribute accessors.
* Get all registered accessors.
*/
void UnregisterAll()
{
perClusterAccessors.clear();
perEndpointClusterAccessors.clear();
}

/**
* Get the registered attribute access override that is cluster-endpoint specific.
*/
AttributeAccessor * Get(EndpointId aEndpointId, ClusterId aClusterId)
{
std::pair<EndpointId, ClusterId> endpointClusterPair = std::make_pair(aEndpointId, aClusterId);
if (perEndpointClusterAccessors.find(endpointClusterPair) != perEndpointClusterAccessors.end())
{
return perEndpointClusterAccessors.at(endpointClusterPair);
}
return nullptr;
}

/**
* Get the registered attribute access override that is cluster specific (Global to all endpoints)
*/
AttributeAccessor * Get(ClusterId aClusterId)
{
if (perClusterAccessors.find(aClusterId) != perClusterAccessors.end())
{
return perClusterAccessors.at(aClusterId);
}
return nullptr;
}
std::set<AttributeAccessor *> GetAllAccessors() { return mAccessors; }

/**
* Returns the singleton instance of the attribute accessor registory.
Expand All @@ -180,8 +71,7 @@ class AttributeAccessorRegistry
}

private:
std::map<ClusterId, AttributeAccessor *> perClusterAccessors;
std::map<std::pair<EndpointId, ClusterId>, AttributeAccessor *> perEndpointClusterAccessors;
std::set<AttributeAccessor *> mAccessors;
};

} // namespace rpc
Expand Down
43 changes: 17 additions & 26 deletions examples/common/pigweed/rpc_services/Attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,25 @@
#include <pigweed/rpc_services/AttributeAccessor.h>
#include <pigweed/rpc_services/AttributeAccessorRegistry.h>
#include <platform/PlatformManager.h>
#include <set>

namespace chip {
namespace rpc {

std::optional<::pw::Status> TryWriteViaAccessor(const chip::app::ConcreteDataAttributePath & path, AttributeAccessor * attrAccess,
chip::app::AttributeValueDecoder & decoder)
::pw::Status TryWriteViaAccessor(const chip::app::ConcreteDataAttributePath & path, chip::app::AttributeValueDecoder & decoder)
{
// Processing can happen only if an attribute access interface actually exists..
if (attrAccess == nullptr)
{
return std::nullopt;
}
std::set<AttributeAccessor *> accessors = AttributeAccessorRegistry::Instance().GetAllAccessors();

::pw::Status status = attrAccess->Write(path, decoder);

if (status != ::pw::OkStatus())
for (AttributeAccessor * accessor : accessors)
{
return std::make_optional(status);
::pw::Status result = accessor->Write(path, decoder);
if (result != ::pw::Status::NotFound()) // Write was either a success or failure.
{
return result;
}
}

// If the decoder tried to decode, then a value should have been read for processing.
// - if decoding was done, assume DONE (i.e. final pw::OkStatus())
// - otherwise, if no decoding done, return that processing must continue via nullopt
return decoder.TriedDecode() ? std::make_optional(::pw::OkStatus()) : std::nullopt;
return ::pw::Status::NotFound();
}

// Implementation class for chip.rpc.Attributes.
Expand Down Expand Up @@ -234,20 +229,16 @@ class Attributes : public pw_rpc::nanopb::Attributes::Service<Attributes>
app::AttributeValueDecoder decoder(tlvReader.value(), subjectDescriptor);

// Try to write using a custom Accessor first
AttributeAccessor * customAttrAccess =
AttributeAccessorRegistry::Instance().Get(write_request.path.mEndpointId, write_request.path.mClusterId);
if (!customAttrAccess) // If endpoint specific registration not found, look for cluster level registration.
{
customAttrAccess = AttributeAccessorRegistry::Instance().Get(write_request.path.mClusterId);
}
std::optional<::pw::Status> attrAccessResult = TryWriteViaAccessor(write_request.path, customAttrAccess, decoder);
if (attrAccessResult.has_value())
::pw::Status interceptResult =
TryWriteViaAccessor(write_request.path, decoder) if (interceptResult != ::pw::Status::NotFound())
{
return attrAccessResult.value();
return interceptResult;
}

ChipLogProgress(Support, "No custom Attribute Accessor Write registration found for: endpoint=%u cluster=%u",
write_request.path.mEndpointId, write_request.path.mClusterId);
ChipLogProgress(
Support,
"No custom PigweedRPC Attribute Accessor registration found for: endpoint=%u cluster=%u, using fake write access.",
write_request.path.mEndpointId, write_request.path.mClusterId);

app::DataModel::ActionReturnStatus result = provider->WriteAttribute(write_request, decoder);

Expand Down

0 comments on commit 9347c92

Please sign in to comment.