blob: d154b95cf9ab9e15b000138ca859951bcdca06e0 [file] [log] [blame]
/**
*
* Copyright (c) 2020-2023 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/util/attribute-storage.h>
#include <app/util/attribute-storage-detail.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/AttributePersistenceProvider.h>
#include <app/InteractionModelEngine.h>
#include <app/reporting/reporting.h>
#include <app/util/config.h>
#include <app/util/ember-strings.h>
#include <app/util/endpoint-config-api.h>
#include <app/util/generic-callbacks.h>
#include <lib/core/CHIPConfig.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/LockTracker.h>
#include <protocols/interaction_model/StatusCode.h>
using chip::Protocols::InteractionModel::Status;
// Attribute storage depends on knowing the current layout/setup of attributes
// and corresponding callbacks. Specifically:
// - zap-generated/callback.h is needed because endpoint_config will call the
// corresponding callbacks (via GENERATED_FUNCTION_ARRAYS) and the include
// for it is:
// util/config.h -> zap-generated/endpoint_config.h
#include <app-common/zap-generated/callback.h>
using namespace chip;
using namespace chip::app;
//------------------------------------------------------------------------------
// Globals
// This is not declared CONST in order to handle dynamic endpoint information
// retrieved from tokens.
EmberAfDefinedEndpoint emAfEndpoints[MAX_ENDPOINT_COUNT];
#if (ATTRIBUTE_MAX_SIZE == 0)
#define ACTUAL_ATTRIBUTE_SIZE 1
#else
#define ACTUAL_ATTRIBUTE_SIZE ATTRIBUTE_MAX_SIZE
#endif
uint8_t attributeData[ACTUAL_ATTRIBUTE_SIZE];
// ----- internal-only methods, not part of the external API -----
// Loads the attributes from built-in default and storage.
static void emAfLoadAttributeDefaults(chip::EndpointId endpoint, chip::Optional<chip::ClusterId> = chip::NullOptional);
static bool emAfMatchCluster(const EmberAfCluster * cluster, const EmberAfAttributeSearchRecord * attRecord);
static bool emAfMatchAttribute(const EmberAfCluster * cluster, const EmberAfAttributeMetadata * am,
const EmberAfAttributeSearchRecord * attRecord);
// If server == true, returns the number of server clusters,
// otherwise number of client clusters on the endpoint at the given index.
static uint8_t emberAfClusterCountForEndpointType(const EmberAfEndpointType * endpointType, bool server);
// If server == true, returns the number of server clusters,
// otherwise number of client clusters on the endpoint at the given index.
static uint8_t emberAfClusterCountByIndex(uint16_t endpointIndex, bool server);
// Check whether there is an endpoint defined with the given endpoint id that is
// enabled.
static bool emberAfEndpointIsEnabled(chip::EndpointId endpoint);
namespace {
#if (!defined(ATTRIBUTE_SINGLETONS_SIZE)) || (ATTRIBUTE_SINGLETONS_SIZE == 0)
#define ACTUAL_SINGLETONS_SIZE 1
#else
#define ACTUAL_SINGLETONS_SIZE ATTRIBUTE_SINGLETONS_SIZE
#endif
uint8_t singletonAttributeData[ACTUAL_SINGLETONS_SIZE];
uint16_t emberEndpointCount = 0;
// If we have attributes that are more than 4 bytes, then
// we need this data block for the defaults
#if (defined(GENERATED_DEFAULTS) && GENERATED_DEFAULTS_COUNT)
constexpr const uint8_t generatedDefaults[] = GENERATED_DEFAULTS;
#define ZAP_LONG_DEFAULTS_INDEX(index) \
{ \
&generatedDefaults[index] \
}
#endif // GENERATED_DEFAULTS
#if (defined(GENERATED_MIN_MAX_DEFAULTS) && GENERATED_MIN_MAX_DEFAULT_COUNT)
constexpr const EmberAfAttributeMinMaxValue minMaxDefaults[] = GENERATED_MIN_MAX_DEFAULTS;
#define ZAP_MIN_MAX_DEFAULTS_INDEX(index) \
{ \
&minMaxDefaults[index] \
}
#endif // GENERATED_MIN_MAX_DEFAULTS
#ifdef GENERATED_FUNCTION_ARRAYS
GENERATED_FUNCTION_ARRAYS
#endif
#ifdef GENERATED_COMMANDS
constexpr const chip::CommandId generatedCommands[] = GENERATED_COMMANDS;
#define ZAP_GENERATED_COMMANDS_INDEX(index) (&generatedCommands[index])
#endif // GENERATED_COMMANDS
#if (defined(GENERATED_EVENTS) && (GENERATED_EVENT_COUNT > 0))
constexpr const chip::EventId generatedEvents[] = GENERATED_EVENTS;
#define ZAP_GENERATED_EVENTS_INDEX(index) (&generatedEvents[index])
#endif // GENERATED_EVENTS
constexpr const EmberAfAttributeMetadata generatedAttributes[] = GENERATED_ATTRIBUTES;
#define ZAP_ATTRIBUTE_INDEX(index) (&generatedAttributes[index])
#ifdef GENERATED_CLUSTERS
constexpr const EmberAfCluster generatedClusters[] = GENERATED_CLUSTERS;
#define ZAP_CLUSTER_INDEX(index) (&generatedClusters[index])
#endif
#if FIXED_ENDPOINT_COUNT > 0
constexpr const EmberAfEndpointType generatedEmberAfEndpointTypes[] = GENERATED_ENDPOINT_TYPES;
constexpr const EmberAfDeviceType fixedDeviceTypeList[] = FIXED_DEVICE_TYPES;
// Not const, because these need to mutate.
DataVersion fixedEndpointDataVersions[ZAP_FIXED_ENDPOINT_DATA_VERSION_COUNT];
#endif // FIXED_ENDPOINT_COUNT > 0
bool emberAfIsThisDataTypeAListType(EmberAfAttributeType dataType)
{
return dataType == ZCL_ARRAY_ATTRIBUTE_TYPE;
}
uint16_t findIndexFromEndpoint(EndpointId endpoint, bool ignoreDisabledEndpoints)
{
if (endpoint == kInvalidEndpointId)
{
return kEmberInvalidEndpointIndex;
}
uint16_t epi;
for (epi = 0; epi < emberAfEndpointCount(); epi++)
{
if (emAfEndpoints[epi].endpoint == endpoint &&
(!ignoreDisabledEndpoints || emAfEndpoints[epi].bitmask.Has(EmberAfEndpointOptions::isEnabled)))
{
return epi;
}
}
return kEmberInvalidEndpointIndex;
}
// Returns the index of a given endpoint. Considers disabled endpoints.
uint16_t emberAfIndexFromEndpointIncludingDisabledEndpoints(EndpointId endpoint)
{
return findIndexFromEndpoint(endpoint, false /* ignoreDisabledEndpoints */);
}
} // anonymous namespace
// Initial configuration
void emberAfEndpointConfigure()
{
uint16_t ep;
static_assert(FIXED_ENDPOINT_COUNT <= std::numeric_limits<decltype(ep)>::max(),
"FIXED_ENDPOINT_COUNT must not exceed the size of the endpoint data type");
emberEndpointCount = FIXED_ENDPOINT_COUNT;
#if FIXED_ENDPOINT_COUNT > 0
constexpr uint16_t fixedEndpoints[] = FIXED_ENDPOINT_ARRAY;
constexpr uint16_t fixedDeviceTypeListLengths[] = FIXED_DEVICE_TYPE_LENGTHS;
constexpr uint16_t fixedDeviceTypeListOffsets[] = FIXED_DEVICE_TYPE_OFFSETS;
constexpr uint8_t fixedEmberAfEndpointTypes[] = FIXED_ENDPOINT_TYPES;
constexpr EndpointId fixedParentEndpoints[] = FIXED_PARENT_ENDPOINTS;
#if ZAP_FIXED_ENDPOINT_DATA_VERSION_COUNT > 0
// Initialize our data version storage. If
// ZAP_FIXED_ENDPOINT_DATA_VERSION_COUNT == 0, gcc complains about a memset
// with size equal to number of elements without multiplication by element
// size, because the sizeof() is also 0 in that case...
if (Crypto::DRBG_get_bytes(reinterpret_cast<uint8_t *>(fixedEndpointDataVersions), sizeof(fixedEndpointDataVersions)) !=
CHIP_NO_ERROR)
{
// Now what? At least 0-init it.
memset(fixedEndpointDataVersions, 0, sizeof(fixedEndpointDataVersions));
}
#endif // ZAP_FIXED_ENDPOINT_DATA_VERSION_COUNT > 0
DataVersion * currentDataVersions = fixedEndpointDataVersions;
for (ep = 0; ep < FIXED_ENDPOINT_COUNT; ep++)
{
emAfEndpoints[ep].endpoint = fixedEndpoints[ep];
emAfEndpoints[ep].deviceTypeList =
Span<const EmberAfDeviceType>(&fixedDeviceTypeList[fixedDeviceTypeListOffsets[ep]], fixedDeviceTypeListLengths[ep]);
emAfEndpoints[ep].endpointType = &generatedEmberAfEndpointTypes[fixedEmberAfEndpointTypes[ep]];
emAfEndpoints[ep].dataVersions = currentDataVersions;
emAfEndpoints[ep].parentEndpointId = fixedParentEndpoints[ep];
emAfEndpoints[ep].bitmask.Set(EmberAfEndpointOptions::isEnabled);
emAfEndpoints[ep].bitmask.Set(EmberAfEndpointOptions::isFlatComposition);
// Increment currentDataVersions by 1 (slot) for every server cluster
// this endpoint has.
currentDataVersions += emberAfClusterCountByIndex(ep, /* server = */ true);
}
#endif // FIXED_ENDPOINT_COUNT > 0
#if CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT
if (MAX_ENDPOINT_COUNT > FIXED_ENDPOINT_COUNT)
{
//
// Reset instances tracking dynamic endpoints to safe defaults.
//
for (ep = FIXED_ENDPOINT_COUNT; ep < MAX_ENDPOINT_COUNT; ep++)
{
emAfEndpoints[ep] = EmberAfDefinedEndpoint();
}
}
#endif
}
void emberAfSetDynamicEndpointCount(uint16_t dynamicEndpointCount)
{
emberEndpointCount = static_cast<uint16_t>(FIXED_ENDPOINT_COUNT + dynamicEndpointCount);
}
uint16_t emberAfGetDynamicIndexFromEndpoint(EndpointId id)
{
if (id == kInvalidEndpointId)
{
return kEmberInvalidEndpointIndex;
}
uint16_t index;
for (index = FIXED_ENDPOINT_COUNT; index < MAX_ENDPOINT_COUNT; index++)
{
if (emAfEndpoints[index].endpoint == id)
{
return static_cast<uint8_t>(index - FIXED_ENDPOINT_COUNT);
}
}
return kEmberInvalidEndpointIndex;
}
CHIP_ERROR emberAfSetDynamicEndpoint(uint16_t index, EndpointId id, const EmberAfEndpointType * ep,
const chip::Span<chip::DataVersion> & dataVersionStorage,
chip::Span<const EmberAfDeviceType> deviceTypeList, EndpointId parentEndpointId)
{
auto realIndex = index + FIXED_ENDPOINT_COUNT;
if (realIndex >= MAX_ENDPOINT_COUNT)
{
return CHIP_ERROR_NO_MEMORY;
}
if (id == kInvalidEndpointId)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
auto serverClusterCount = emberAfClusterCountForEndpointType(ep, /* server = */ true);
if (dataVersionStorage.size() < serverClusterCount)
{
return CHIP_ERROR_NO_MEMORY;
}
index = static_cast<uint16_t>(realIndex);
for (uint16_t i = FIXED_ENDPOINT_COUNT; i < MAX_ENDPOINT_COUNT; i++)
{
if (emAfEndpoints[i].endpoint == id)
{
return CHIP_ERROR_ENDPOINT_EXISTS;
}
}
emAfEndpoints[index].endpoint = id;
emAfEndpoints[index].deviceTypeList = deviceTypeList;
emAfEndpoints[index].endpointType = ep;
emAfEndpoints[index].dataVersions = dataVersionStorage.data();
// Start the endpoint off as disabled.
emAfEndpoints[index].bitmask.Clear(EmberAfEndpointOptions::isEnabled);
emAfEndpoints[index].parentEndpointId = parentEndpointId;
emberAfSetDynamicEndpointCount(MAX_ENDPOINT_COUNT - FIXED_ENDPOINT_COUNT);
// Initialize the data versions.
size_t dataSize = sizeof(DataVersion) * serverClusterCount;
if (dataSize != 0)
{
if (Crypto::DRBG_get_bytes(reinterpret_cast<uint8_t *>(dataVersionStorage.data()), dataSize) != CHIP_NO_ERROR)
{
// Now what? At least 0-init it.
memset(dataVersionStorage.data(), 0, dataSize);
}
}
// Now enable the endpoint.
emberAfEndpointEnableDisable(id, true);
return CHIP_NO_ERROR;
}
EndpointId emberAfClearDynamicEndpoint(uint16_t index)
{
EndpointId ep = 0;
index = static_cast<uint8_t>(index + FIXED_ENDPOINT_COUNT);
if ((index < MAX_ENDPOINT_COUNT) && (emAfEndpoints[index].endpoint != kInvalidEndpointId) &&
(emberAfEndpointIndexIsEnabled(index)))
{
ep = emAfEndpoints[index].endpoint;
emberAfEndpointEnableDisable(ep, false);
emAfEndpoints[index].endpoint = kInvalidEndpointId;
}
return ep;
}
uint16_t emberAfFixedEndpointCount()
{
return FIXED_ENDPOINT_COUNT;
}
uint16_t emberAfEndpointCount()
{
return emberEndpointCount;
}
bool emberAfEndpointIndexIsEnabled(uint16_t index)
{
return (emAfEndpoints[index].bitmask.Has(EmberAfEndpointOptions::isEnabled));
}
// This function is used to call the per-cluster attribute changed callback
void emAfClusterAttributeChangedCallback(const app::ConcreteAttributePath & attributePath)
{
const EmberAfCluster * cluster = emberAfFindServerCluster(attributePath.mEndpointId, attributePath.mClusterId);
if (cluster != nullptr)
{
EmberAfGenericClusterFunction f = emberAfFindClusterFunction(cluster, CLUSTER_MASK_ATTRIBUTE_CHANGED_FUNCTION);
if (f != nullptr)
{
((EmberAfClusterAttributeChangedCallback) f)(attributePath);
}
}
}
// This function is used to call the per-cluster pre-attribute changed callback
Status emAfClusterPreAttributeChangedCallback(const app::ConcreteAttributePath & attributePath, EmberAfAttributeType attributeType,
uint16_t size, uint8_t * value)
{
const EmberAfCluster * cluster = emberAfFindServerCluster(attributePath.mEndpointId, attributePath.mClusterId);
if (cluster == nullptr)
{
if (!emberAfEndpointIsEnabled(attributePath.mEndpointId))
{
return Status::UnsupportedEndpoint;
}
return Status::UnsupportedCluster;
}
Status status = Status::Success;
// Casting and calling a function pointer on the same line results in ignoring the return
// of the call on gcc-arm-none-eabi-9-2019-q4-major
EmberAfClusterPreAttributeChangedCallback f = (EmberAfClusterPreAttributeChangedCallback) (emberAfFindClusterFunction(
cluster, CLUSTER_MASK_PRE_ATTRIBUTE_CHANGED_FUNCTION));
if (f != nullptr)
{
status = f(attributePath, attributeType, size, value);
}
return status;
}
static void initializeEndpoint(EmberAfDefinedEndpoint * definedEndpoint)
{
uint8_t clusterIndex;
const EmberAfEndpointType * epType = definedEndpoint->endpointType;
for (clusterIndex = 0; clusterIndex < epType->clusterCount; clusterIndex++)
{
const EmberAfCluster * cluster = &(epType->cluster[clusterIndex]);
EmberAfGenericClusterFunction f;
emberAfClusterInitCallback(definedEndpoint->endpoint, cluster->clusterId);
f = emberAfFindClusterFunction(cluster, CLUSTER_MASK_INIT_FUNCTION);
if (f != nullptr)
{
((EmberAfInitFunction) f)(definedEndpoint->endpoint);
}
}
}
static void shutdownEndpoint(EmberAfDefinedEndpoint * definedEndpoint)
{
// Call shutdown callbacks from clusters, mainly for canceling pending timers
uint8_t clusterIndex;
const EmberAfEndpointType * epType = definedEndpoint->endpointType;
for (clusterIndex = 0; clusterIndex < epType->clusterCount; clusterIndex++)
{
const EmberAfCluster * cluster = &(epType->cluster[clusterIndex]);
EmberAfGenericClusterFunction f = emberAfFindClusterFunction(cluster, CLUSTER_MASK_SHUTDOWN_FUNCTION);
if (f != nullptr)
{
((EmberAfShutdownFunction) f)(definedEndpoint->endpoint);
}
}
// Clear out any command handler overrides registered for this
// endpoint.
chip::app::InteractionModelEngine::GetInstance()->UnregisterCommandHandlers(definedEndpoint->endpoint);
unregisterAllAttributeAccessOverridesForEndpoint(definedEndpoint);
}
// Calls the init functions.
void emAfCallInits()
{
uint16_t index;
for (index = 0; index < emberAfEndpointCount(); index++)
{
if (emberAfEndpointIndexIsEnabled(index))
{
initializeEndpoint(&(emAfEndpoints[index]));
}
}
}
// Returns the pointer to metadata, or null if it is not found
const EmberAfAttributeMetadata * emberAfLocateAttributeMetadata(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId)
{
const EmberAfAttributeMetadata * metadata = nullptr;
EmberAfAttributeSearchRecord record;
record.endpoint = endpoint;
record.clusterId = clusterId;
record.attributeId = attributeId;
emAfReadOrWriteAttribute(&record, &metadata,
nullptr, // buffer
0, // buffer size
false); // write?
return metadata;
}
static uint8_t * singletonAttributeLocation(const EmberAfAttributeMetadata * am)
{
const EmberAfAttributeMetadata * m = &(generatedAttributes[0]);
uint16_t index = 0;
while (m < am)
{
if (m->IsSingleton() && !m->IsExternal())
{
index = static_cast<uint16_t>(index + m->size);
}
m++;
}
return (uint8_t *) (singletonAttributeData + index);
}
// This function does mem copy, but smartly, which means that if the type is a
// string, it will copy as much as it can.
// If src == NULL, then this method will set memory to zeroes
// See documentation for emAfReadOrWriteAttribute for the semantics of
// readLength when reading and writing.
static Status typeSensitiveMemCopy(ClusterId clusterId, uint8_t * dest, uint8_t * src, const EmberAfAttributeMetadata * am,
bool write, uint16_t readLength)
{
EmberAfAttributeType attributeType = am->attributeType;
// readLength == 0 for a read indicates that we should just trust that the
// caller has enough space for an attribute...
bool ignoreReadLength = write || (readLength == 0);
uint16_t bufferSize = ignoreReadLength ? am->size : readLength;
if (emberAfIsStringAttributeType(attributeType))
{
if (bufferSize < 1)
{
return Status::ResourceExhausted;
}
emberAfCopyString(dest, src, bufferSize - 1);
}
else if (emberAfIsLongStringAttributeType(attributeType))
{
if (bufferSize < 2)
{
return Status::ResourceExhausted;
}
emberAfCopyLongString(dest, src, bufferSize - 2);
}
else if (emberAfIsThisDataTypeAListType(attributeType))
{
if (bufferSize < 2)
{
return Status::ResourceExhausted;
}
// Just copy the length.
memmove(dest, src, 2);
}
else
{
if (!ignoreReadLength && readLength < am->size)
{
return Status::ResourceExhausted;
}
if (src == nullptr)
{
memset(dest, 0, am->size);
}
else
{
memmove(dest, src, am->size);
}
}
return Status::Success;
}
/**
* @brief Matches a cluster based on cluster id and direction.
*
* This function assumes that the passed cluster's endpoint already
* matches the endpoint of the EmberAfAttributeSearchRecord.
*
* Clusters match if:
* 1. Cluster ids match AND
* 2. Cluster is a server cluster (because there are no client attributes).
*/
bool emAfMatchCluster(const EmberAfCluster * cluster, const EmberAfAttributeSearchRecord * attRecord)
{
return (cluster->clusterId == attRecord->clusterId && (cluster->mask & CLUSTER_MASK_SERVER));
}
/**
* @brief Matches an attribute based on attribute id.
* This function assumes that the passed cluster already matches the
* clusterId and direction of the passed EmberAfAttributeSearchRecord.
*
* Attributes match if attr ids match.
*/
bool emAfMatchAttribute(const EmberAfCluster * cluster, const EmberAfAttributeMetadata * am,
const EmberAfAttributeSearchRecord * attRecord)
{
return (am->attributeId == attRecord->attributeId);
}
// When reading non-string attributes, this function returns an error when destination
// buffer isn't large enough to accommodate the attribute type. For strings, the
// function will copy at most readLength bytes. This means the resulting string
// may be truncated. The length byte(s) in the resulting string will reflect
// any truncation. If readLength is zero, we are working with backwards-
// compatibility wrapper functions and we just cross our fingers and hope for
// the best.
//
// When writing attributes, readLength is ignored. For non-string attributes,
// this function assumes the source buffer is the same size as the attribute
// type. For strings, the function will copy as many bytes as will fit in the
// attribute. This means the resulting string may be truncated. The length
// byte(s) in the resulting string will reflect any truncated.
Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, const EmberAfAttributeMetadata ** metadata,
uint8_t * buffer, uint16_t readLength, bool write)
{
assertChipStackLockedByCurrentThread();
uint16_t attributeOffsetIndex = 0;
for (uint16_t ep = 0; ep < emberAfEndpointCount(); ep++)
{
// Is this a dynamic endpoint?
bool isDynamicEndpoint = (ep >= emberAfFixedEndpointCount());
if (emAfEndpoints[ep].endpoint == attRecord->endpoint)
{
const EmberAfEndpointType * endpointType = emAfEndpoints[ep].endpointType;
uint8_t clusterIndex;
if (!emberAfEndpointIndexIsEnabled(ep))
{
continue;
}
for (clusterIndex = 0; clusterIndex < endpointType->clusterCount; clusterIndex++)
{
const EmberAfCluster * cluster = &(endpointType->cluster[clusterIndex]);
if (emAfMatchCluster(cluster, attRecord))
{ // Got the cluster
uint16_t attrIndex;
for (attrIndex = 0; attrIndex < cluster->attributeCount; attrIndex++)
{
const EmberAfAttributeMetadata * am = &(cluster->attributes[attrIndex]);
if (emAfMatchAttribute(cluster, am, attRecord))
{ // Got the attribute
// If passed metadata location is not null, populate
if (metadata != nullptr)
{
*metadata = am;
}
{
uint8_t * attributeLocation =
(am->mask & ATTRIBUTE_MASK_SINGLETON ? singletonAttributeLocation(am)
: attributeData + attributeOffsetIndex);
uint8_t *src, *dst;
if (write)
{
src = buffer;
dst = attributeLocation;
if (!emberAfAttributeWriteAccessCallback(attRecord->endpoint, attRecord->clusterId,
am->attributeId))
{
return Status::UnsupportedAccess;
}
}
else
{
if (buffer == nullptr)
{
return Status::Success;
}
src = attributeLocation;
dst = buffer;
if (!emberAfAttributeReadAccessCallback(attRecord->endpoint, attRecord->clusterId,
am->attributeId))
{
return Status::UnsupportedAccess;
}
}
// Is the attribute externally stored?
if (am->mask & ATTRIBUTE_MASK_EXTERNAL_STORAGE)
{
return (write ? emberAfExternalAttributeWriteCallback(attRecord->endpoint, attRecord->clusterId,
am, buffer)
: emberAfExternalAttributeReadCallback(attRecord->endpoint, attRecord->clusterId,
am, buffer, emberAfAttributeSize(am)));
}
// Internal storage is only supported for fixed endpoints
if (!isDynamicEndpoint)
{
return typeSensitiveMemCopy(attRecord->clusterId, dst, src, am, write, readLength);
}
return Status::Failure;
}
}
else
{ // Not the attribute we are looking for
// Increase the index if attribute is not externally stored
if (!(am->mask & ATTRIBUTE_MASK_EXTERNAL_STORAGE) && !(am->mask & ATTRIBUTE_MASK_SINGLETON))
{
attributeOffsetIndex = static_cast<uint16_t>(attributeOffsetIndex + emberAfAttributeSize(am));
}
}
}
// Attribute is not in the cluster.
return Status::UnsupportedAttribute;
}
// Not the cluster we are looking for
attributeOffsetIndex = static_cast<uint16_t>(attributeOffsetIndex + cluster->clusterSize);
}
// Cluster is not in the endpoint.
return Status::UnsupportedCluster;
}
// Not the endpoint we are looking for
// Dynamic endpoints are external and don't factor into storage size
if (!isDynamicEndpoint)
{
attributeOffsetIndex = static_cast<uint16_t>(attributeOffsetIndex + emAfEndpoints[ep].endpointType->endpointSize);
}
}
return Status::UnsupportedEndpoint; // Sorry, endpoint was not found.
}
const EmberAfEndpointType * emberAfFindEndpointType(chip::EndpointId endpointId)
{
uint16_t ep = emberAfIndexFromEndpoint(endpointId);
if (ep == kEmberInvalidEndpointIndex)
{
return nullptr;
}
return emAfEndpoints[ep].endpointType;
}
const EmberAfCluster * emberAfFindClusterInType(const EmberAfEndpointType * endpointType, ClusterId clusterId,
EmberAfClusterMask mask, uint8_t * index)
{
uint8_t i;
uint8_t scopedIndex = 0;
for (i = 0; i < endpointType->clusterCount; i++)
{
const EmberAfCluster * cluster = &(endpointType->cluster[i]);
if (mask == 0 || ((cluster->mask & mask) != 0))
{
if (cluster->clusterId == clusterId)
{
if (index)
{
*index = scopedIndex;
}
return cluster;
}
scopedIndex++;
}
}
return nullptr;
}
uint8_t emberAfClusterIndex(EndpointId endpoint, ClusterId clusterId, EmberAfClusterMask mask)
{
for (uint16_t ep = 0; ep < emberAfEndpointCount(); ep++)
{
// Check the endpoint id first, because that way we avoid examining the
// endpoint type for endpoints that are not actually defined.
if (emAfEndpoints[ep].endpoint == endpoint)
{
const EmberAfEndpointType * endpointType = emAfEndpoints[ep].endpointType;
uint8_t index = 0xFF;
if (emberAfFindClusterInType(endpointType, clusterId, mask, &index) != nullptr)
{
return index;
}
}
}
return 0xFF;
}
// Returns whether the given endpoint has the server of the given cluster on it.
bool emberAfContainsServer(EndpointId endpoint, ClusterId clusterId)
{
return (emberAfFindServerCluster(endpoint, clusterId) != nullptr);
}
// Returns whether the given endpoint has the client of the given cluster on it.
bool emberAfContainsClient(EndpointId endpoint, ClusterId clusterId)
{
uint16_t ep = emberAfIndexFromEndpoint(endpoint);
if (ep == kEmberInvalidEndpointIndex)
{
return false;
}
return (emberAfFindClusterInType(emAfEndpoints[ep].endpointType, clusterId, CLUSTER_MASK_CLIENT) != nullptr);
}
// This will find the first server that has the clusterId given from the index of endpoint.
bool emberAfContainsServerFromIndex(uint16_t index, ClusterId clusterId)
{
if (index == kEmberInvalidEndpointIndex)
{
return false;
}
return emberAfFindClusterInType(emAfEndpoints[index].endpointType, clusterId, CLUSTER_MASK_SERVER);
}
namespace chip {
namespace app {
EnabledEndpointsWithServerCluster::EnabledEndpointsWithServerCluster(ClusterId clusterId) :
mEndpointCount(emberAfEndpointCount()), mClusterId(clusterId)
{
EnsureMatchingEndpoint();
}
EndpointId EnabledEndpointsWithServerCluster::operator*() const
{
return emberAfEndpointFromIndex(mEndpointIndex);
}
EnabledEndpointsWithServerCluster & EnabledEndpointsWithServerCluster::operator++()
{
++mEndpointIndex;
EnsureMatchingEndpoint();
return *this;
}
void EnabledEndpointsWithServerCluster::EnsureMatchingEndpoint()
{
for (; mEndpointIndex < mEndpointCount; ++mEndpointIndex)
{
if (!emberAfEndpointIndexIsEnabled(mEndpointIndex))
{
continue;
}
if (emberAfContainsServerFromIndex(mEndpointIndex, mClusterId))
{
break;
}
}
}
} // namespace app
} // namespace chip
// Finds the cluster that matches endpoint, clusterId, direction.
const EmberAfCluster * emberAfFindServerCluster(EndpointId endpoint, ClusterId clusterId)
{
uint16_t ep = emberAfIndexFromEndpoint(endpoint);
if (ep == kEmberInvalidEndpointIndex)
{
return nullptr;
}
return emberAfFindClusterInType(emAfEndpoints[ep].endpointType, clusterId, CLUSTER_MASK_SERVER);
}
// Returns cluster within the endpoint; Does not ignore disabled endpoints
const EmberAfCluster * emberAfFindClusterIncludingDisabledEndpoints(EndpointId endpoint, ClusterId clusterId,
EmberAfClusterMask mask)
{
uint16_t ep = emberAfIndexFromEndpointIncludingDisabledEndpoints(endpoint);
if (ep < MAX_ENDPOINT_COUNT)
{
return emberAfFindClusterInType(emAfEndpoints[ep].endpointType, clusterId, mask);
}
return nullptr;
}
uint16_t emberAfGetClusterServerEndpointIndex(EndpointId endpoint, ClusterId cluster, uint16_t fixedClusterServerEndpointCount)
{
VerifyOrDie(fixedClusterServerEndpointCount <= FIXED_ENDPOINT_COUNT);
uint16_t epIndex = findIndexFromEndpoint(endpoint, true /*ignoreDisabledEndpoints*/);
// Endpoint must be configured and enabled
if (epIndex == kEmberInvalidEndpointIndex)
{
return kEmberInvalidEndpointIndex;
}
if (emberAfFindClusterInType(emAfEndpoints[epIndex].endpointType, cluster, CLUSTER_MASK_SERVER) == nullptr)
{
// The provided endpoint does not contain the given cluster server.
return kEmberInvalidEndpointIndex;
}
if (epIndex < FIXED_ENDPOINT_COUNT)
{
// This endpoint is a fixed one.
// Return the index of this endpoint in the list of fixed endpoints that support the given cluster.
uint16_t adjustedEndpointIndex = 0;
for (uint16_t i = 0; i < epIndex; i++)
{
// Increase adjustedEndpointIndex for every endpoint containing the cluster server
// before our endpoint of interest
if (emAfEndpoints[i].endpoint != kInvalidEndpointId &&
(emberAfFindClusterInType(emAfEndpoints[i].endpointType, cluster, CLUSTER_MASK_SERVER) != nullptr))
{
adjustedEndpointIndex++;
}
}
// If this asserts, the provided fixedClusterServerEndpointCount doesn't match the app data model.
VerifyOrDie(adjustedEndpointIndex < fixedClusterServerEndpointCount);
epIndex = adjustedEndpointIndex;
}
else
{
// This is a dynamic endpoint.
// Its index is just its index in the dynamic endpoint list, offset by fixedClusterServerEndpointCount.
epIndex = static_cast<uint16_t>(fixedClusterServerEndpointCount + (epIndex - FIXED_ENDPOINT_COUNT));
}
return epIndex;
}
bool emberAfEndpointIsEnabled(EndpointId endpoint)
{
uint16_t index = findIndexFromEndpoint(endpoint, false /* ignoreDisabledEndpoints */);
if (kEmberInvalidEndpointIndex == index)
{
return false;
}
return emberAfEndpointIndexIsEnabled(index);
}
bool emberAfEndpointEnableDisable(EndpointId endpoint, bool enable)
{
uint16_t index = findIndexFromEndpoint(endpoint, false /* ignoreDisabledEndpoints */);
bool currentlyEnabled;
if (kEmberInvalidEndpointIndex == index)
{
return false;
}
currentlyEnabled = emAfEndpoints[index].bitmask.Has(EmberAfEndpointOptions::isEnabled);
if (enable)
{
emAfEndpoints[index].bitmask.Set(EmberAfEndpointOptions::isEnabled);
}
if (currentlyEnabled != enable)
{
if (enable)
{
initializeEndpoint(&(emAfEndpoints[index]));
MatterReportingAttributeChangeCallback(endpoint);
}
else
{
shutdownEndpoint(&(emAfEndpoints[index]));
emAfEndpoints[index].bitmask.Clear(EmberAfEndpointOptions::isEnabled);
}
EndpointId parentEndpointId = emberAfParentEndpointFromIndex(index);
while (parentEndpointId != kInvalidEndpointId)
{
MatterReportingAttributeChangeCallback(parentEndpointId, app::Clusters::Descriptor::Id,
app::Clusters::Descriptor::Attributes::PartsList::Id);
uint16_t parentIndex = emberAfIndexFromEndpoint(parentEndpointId);
if (parentIndex == kEmberInvalidEndpointIndex)
{
// Something has gone wrong.
break;
}
parentEndpointId = emberAfParentEndpointFromIndex(parentIndex);
}
MatterReportingAttributeChangeCallback(/* endpoint = */ 0, app::Clusters::Descriptor::Id,
app::Clusters::Descriptor::Attributes::PartsList::Id);
}
return true;
}
// Returns the index of a given endpoint. Does not consider disabled endpoints.
uint16_t emberAfIndexFromEndpoint(EndpointId endpoint)
{
return findIndexFromEndpoint(endpoint, true /* ignoreDisabledEndpoints */);
}
EndpointId emberAfEndpointFromIndex(uint16_t index)
{
return emAfEndpoints[index].endpoint;
}
EndpointId emberAfParentEndpointFromIndex(uint16_t index)
{
return emAfEndpoints[index].parentEndpointId;
}
// If server == true, returns the number of server clusters,
// otherwise number of client clusters on this endpoint
uint8_t emberAfClusterCount(EndpointId endpoint, bool server)
{
uint16_t index = emberAfIndexFromEndpoint(endpoint);
if (index == kEmberInvalidEndpointIndex)
{
return 0;
}
return emberAfClusterCountByIndex(index, server);
}
uint8_t emberAfClusterCountByIndex(uint16_t endpointIndex, bool server)
{
const EmberAfDefinedEndpoint * de = &(emAfEndpoints[endpointIndex]);
if (de->endpointType == nullptr)
{
return 0;
}
return emberAfClusterCountForEndpointType(de->endpointType, server);
}
uint8_t emberAfClusterCountForEndpointType(const EmberAfEndpointType * type, bool server)
{
const EmberAfClusterMask cluster_mask = server ? CLUSTER_MASK_SERVER : CLUSTER_MASK_CLIENT;
return static_cast<uint8_t>(std::count_if(type->cluster, type->cluster + type->clusterCount,
[=](const EmberAfCluster & cluster) { return (cluster.mask & cluster_mask) != 0; }));
}
uint8_t emberAfGetClusterCountForEndpoint(EndpointId endpoint)
{
uint16_t index = emberAfIndexFromEndpoint(endpoint);
if (index == kEmberInvalidEndpointIndex)
{
return 0;
}
return emAfEndpoints[index].endpointType->clusterCount;
}
chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpoint(chip::EndpointId endpoint, CHIP_ERROR & err)
{
uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint);
chip::Span<const EmberAfDeviceType> ret;
if (endpointIndex == 0xFFFF)
{
err = CHIP_ERROR_INVALID_ARGUMENT;
return ret;
}
err = CHIP_NO_ERROR;
return emAfEndpoints[endpointIndex].deviceTypeList;
}
CHIP_ERROR GetSemanticTagForEndpointAtIndex(EndpointId endpoint, size_t index,
Clusters::Descriptor::Structs::SemanticTagStruct::Type & tag)
{
uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint);
if (endpointIndex == 0xFFFF || index >= emAfEndpoints[endpointIndex].tagList.size())
{
return CHIP_ERROR_NOT_FOUND;
}
tag = emAfEndpoints[endpointIndex].tagList[index];
return CHIP_NO_ERROR;
}
CHIP_ERROR emberAfSetDeviceTypeList(EndpointId endpoint, Span<const EmberAfDeviceType> deviceTypeList)
{
uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint);
if (endpointIndex == 0xFFFF)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
emAfEndpoints[endpointIndex].deviceTypeList = deviceTypeList;
return CHIP_NO_ERROR;
}
CHIP_ERROR SetTagList(EndpointId endpoint, Span<const Clusters::Descriptor::Structs::SemanticTagStruct::Type> tagList)
{
uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint);
if (endpointIndex == 0xFFFF)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
emAfEndpoints[endpointIndex].tagList = tagList;
return CHIP_NO_ERROR;
}
// Returns the cluster of Nth server or client cluster,
// depending on server toggle.
const EmberAfCluster * emberAfGetNthCluster(EndpointId endpoint, uint8_t n, bool server)
{
uint16_t index = emberAfIndexFromEndpoint(endpoint);
if (index == kEmberInvalidEndpointIndex)
{
return nullptr;
}
const EmberAfEndpointType * endpointType = emAfEndpoints[index].endpointType;
const EmberAfClusterMask cluster_mask = server ? CLUSTER_MASK_SERVER : CLUSTER_MASK_CLIENT;
const uint8_t clusterCount = endpointType->clusterCount;
uint8_t c = 0;
for (uint8_t i = 0; i < clusterCount; i++)
{
const EmberAfCluster * cluster = &(endpointType->cluster[i]);
if ((cluster->mask & cluster_mask) == 0)
{
continue;
}
if (c == n)
{
return cluster;
}
c++;
}
return nullptr;
}
// Returns the cluster id of Nth server or client cluster,
// depending on server toggle.
// Returns Optional<ClusterId>::Missing() if cluster does not exist.
Optional<ClusterId> emberAfGetNthClusterId(EndpointId endpoint, uint8_t n, bool server)
{
const EmberAfCluster * cluster = emberAfGetNthCluster(endpoint, n, server);
if (cluster == nullptr)
{
return Optional<ClusterId>::Missing();
}
return Optional<ClusterId>(cluster->clusterId);
}
// Returns number of clusters put into the passed cluster list
// for the given endpoint and client/server polarity
uint8_t emberAfGetClustersFromEndpoint(EndpointId endpoint, ClusterId * clusterList, uint8_t listLen, bool server)
{
uint8_t clusterCount = emberAfClusterCount(endpoint, server);
uint8_t i;
const EmberAfCluster * cluster;
if (clusterCount > listLen)
{
clusterCount = listLen;
}
for (i = 0; i < clusterCount; i++)
{
cluster = emberAfGetNthCluster(endpoint, i, server);
clusterList[i] = (cluster == nullptr ? kEmberInvalidEndpointIndex : cluster->clusterId);
}
return clusterCount;
}
void emberAfInitializeAttributes(EndpointId endpoint)
{
emAfLoadAttributeDefaults(endpoint);
}
void emAfLoadAttributeDefaults(EndpointId endpoint, Optional<ClusterId> clusterId)
{
uint16_t ep;
uint8_t clusterI;
uint16_t attr;
uint8_t * ptr;
uint16_t epCount = emberAfEndpointCount();
uint8_t attrData[ATTRIBUTE_LARGEST];
auto * attrStorage = app::GetAttributePersistenceProvider();
// Don't check whether we actually have an attrStorage here, because it's OK
// to have one if none of our attributes have NVM storage.
for (ep = 0; ep < epCount; ep++)
{
EmberAfDefinedEndpoint * de;
if (endpoint != chip::kInvalidEndpointId)
{
ep = emberAfIndexFromEndpoint(endpoint);
if (ep == kEmberInvalidEndpointIndex)
{
return;
}
}
de = &(emAfEndpoints[ep]);
for (clusterI = 0; clusterI < de->endpointType->clusterCount; clusterI++)
{
const EmberAfCluster * cluster = &(de->endpointType->cluster[clusterI]);
if (clusterId.HasValue())
{
if (clusterId.Value() != cluster->clusterId)
{
continue;
}
}
// when the attributeCount is high, the loop takes too long to run and a
// watchdog kicks in causing a reset. As a workaround, we'll
// conditionally manually reset the watchdog. 300 sounds like a good
// magic number for now.
if (cluster->attributeCount > 300)
{
// halResetWatchdog();
}
for (attr = 0; attr < cluster->attributeCount; attr++)
{
const EmberAfAttributeMetadata * am = &(cluster->attributes[attr]);
ptr = nullptr; // Will get set to the value to write, as needed.
// First check for a persisted value.
if (am->IsAutomaticallyPersisted())
{
VerifyOrDieWithMsg(attrStorage != nullptr, Zcl, "Attribute persistence needs a persistence provider");
MutableByteSpan bytes(attrData);
CHIP_ERROR err = attrStorage->ReadValue(
app::ConcreteAttributePath(de->endpoint, cluster->clusterId, am->attributeId), am, bytes);
if (err == CHIP_NO_ERROR)
{
ptr = attrData;
}
else
{
ChipLogDetail(
DataManagement,
"Failed to read stored attribute (%u, " ChipLogFormatMEI ", " ChipLogFormatMEI ": %" CHIP_ERROR_FORMAT,
de->endpoint, ChipLogValueMEI(cluster->clusterId), ChipLogValueMEI(am->attributeId), err.Format());
// Just fall back to default value.
}
}
if (!am->IsExternal())
{
EmberAfAttributeSearchRecord record;
record.endpoint = de->endpoint;
record.clusterId = cluster->clusterId;
record.attributeId = am->attributeId;
if (ptr == nullptr)
{
size_t defaultValueSizeForBigEndianNudger = 0;
// Bypasses compiler warning about unused variable for little endian platforms.
(void) defaultValueSizeForBigEndianNudger;
if ((am->mask & ATTRIBUTE_MASK_MIN_MAX) != 0U)
{
// This is intentionally 2 and not 4 bytes since defaultValue in min/max
// attributes is still uint16_t.
if (emberAfAttributeSize(am) <= 2)
{
static_assert(sizeof(am->defaultValue.ptrToMinMaxValue->defaultValue.defaultValue) == 2,
"if statement relies on size of max/min defaultValue being 2");
ptr = (uint8_t *) &(am->defaultValue.ptrToMinMaxValue->defaultValue.defaultValue);
defaultValueSizeForBigEndianNudger =
sizeof(am->defaultValue.ptrToMinMaxValue->defaultValue.defaultValue);
}
else
{
ptr = (uint8_t *) am->defaultValue.ptrToMinMaxValue->defaultValue.ptrToDefaultValue;
}
}
else
{
if ((emberAfAttributeSize(am) <= 4) && !emberAfIsStringAttributeType(am->attributeType))
{
ptr = (uint8_t *) &(am->defaultValue.defaultValue);
defaultValueSizeForBigEndianNudger = sizeof(am->defaultValue.defaultValue);
}
else
{
ptr = (uint8_t *) am->defaultValue.ptrToDefaultValue;
}
}
// At this point, ptr either points to a default value, or is NULL, in which case
// it should be treated as if it is pointing to an array of all zeroes.
#if (CHIP_CONFIG_BIG_ENDIAN_TARGET)
// The default values for attributes that are less than or equal to
// defaultValueSizeForBigEndianNudger in bytes are stored in an
// uint32_t. On big-endian platforms, a pointer to the default value
// of size less than defaultValueSizeForBigEndianNudger will point to the wrong
// byte. So, for those cases, nudge the pointer forward so it points
// to the correct byte.
if (emberAfAttributeSize(am) < defaultValueSizeForBigEndianNudger && ptr != NULL)
{
ptr += (defaultValueSizeForBigEndianNudger - emberAfAttributeSize(am));
}
#endif // BIGENDIAN
}
emAfReadOrWriteAttribute(&record,
nullptr, // metadata - unused
ptr,
0, // buffer size - unused
true); // write?
}
}
}
if (endpoint != chip::kInvalidEndpointId)
{
break;
}
}
}
// 'data' argument may be null, since we changed the ptrToDefaultValue
// to be null instead of pointing to all zeroes.
// This function has to be able to deal with that.
void emAfSaveAttributeToStorageIfNeeded(uint8_t * data, EndpointId endpoint, ClusterId clusterId,
const EmberAfAttributeMetadata * metadata)
{
// Get out of here if this attribute isn't marked non-volatile.
if (!metadata->IsAutomaticallyPersisted())
{
return;
}
// TODO: Maybe we should have a separate constant for the size of the
// largest non-volatile attribute?
uint8_t allZeroData[ATTRIBUTE_LARGEST] = { 0 };
if (data == nullptr)
{
data = allZeroData;
}
size_t dataSize;
EmberAfAttributeType type = metadata->attributeType;
if (emberAfIsStringAttributeType(type))
{
dataSize = emberAfStringLength(data) + 1;
}
else if (emberAfIsLongStringAttributeType(type))
{
dataSize = emberAfLongStringLength(data) + 2;
}
else
{
dataSize = metadata->size;
}
auto * attrStorage = app::GetAttributePersistenceProvider();
if (attrStorage)
{
attrStorage->WriteValue(app::ConcreteAttributePath(endpoint, clusterId, metadata->attributeId), ByteSpan(data, dataSize));
}
else
{
ChipLogProgress(DataManagement, "Can't store attribute value: no persistence provider");
}
}
// This function returns the actual function point from the array,
// iterating over the function bits.
EmberAfGenericClusterFunction emberAfFindClusterFunction(const EmberAfCluster * cluster, EmberAfClusterMask functionMask)
{
EmberAfClusterMask mask = 0x01;
uint8_t functionIndex = 0;
if ((cluster->mask & functionMask) == 0)
{
return nullptr;
}
while (mask < functionMask)
{
if ((cluster->mask & mask) != 0)
{
functionIndex++;
}
mask = static_cast<EmberAfClusterMask>(mask << 1);
}
return cluster->functions[functionIndex];
}
namespace chip {
namespace app {
CHIP_ERROR SetParentEndpointForEndpoint(EndpointId childEndpoint, EndpointId parentEndpoint)
{
uint16_t childIndex = emberAfIndexFromEndpoint(childEndpoint);
uint16_t parentIndex = emberAfIndexFromEndpoint(parentEndpoint);
if (childIndex == kEmberInvalidEndpointIndex || parentIndex == kEmberInvalidEndpointIndex)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
emAfEndpoints[childIndex].parentEndpointId = parentEndpoint;
return CHIP_NO_ERROR;
}
CHIP_ERROR SetFlatCompositionForEndpoint(EndpointId endpoint)
{
uint16_t index = emberAfIndexFromEndpoint(endpoint);
if (index == kEmberInvalidEndpointIndex)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
emAfEndpoints[index].bitmask.Clear(EmberAfEndpointOptions::isTreeComposition);
emAfEndpoints[index].bitmask.Set(EmberAfEndpointOptions::isFlatComposition);
return CHIP_NO_ERROR;
}
CHIP_ERROR SetTreeCompositionForEndpoint(EndpointId endpoint)
{
uint16_t index = emberAfIndexFromEndpoint(endpoint);
if (index == kEmberInvalidEndpointIndex)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
emAfEndpoints[index].bitmask.Clear(EmberAfEndpointOptions::isFlatComposition);
emAfEndpoints[index].bitmask.Set(EmberAfEndpointOptions::isTreeComposition);
return CHIP_NO_ERROR;
}
bool IsFlatCompositionForEndpoint(EndpointId endpoint)
{
uint16_t index = emberAfIndexFromEndpoint(endpoint);
if (index == kEmberInvalidEndpointIndex)
{
return false;
}
return emAfEndpoints[index].bitmask.Has(EmberAfEndpointOptions::isFlatComposition);
}
bool IsTreeCompositionForEndpoint(EndpointId endpoint)
{
uint16_t index = emberAfIndexFromEndpoint(endpoint);
if (index == kEmberInvalidEndpointIndex)
{
return false;
}
return emAfEndpoints[index].bitmask.Has(EmberAfEndpointOptions::isTreeComposition);
}
} // namespace app
} // namespace chip
uint16_t emberAfGetServerAttributeCount(chip::EndpointId endpoint, chip::ClusterId cluster)
{
const EmberAfCluster * clusterObj = emberAfFindServerCluster(endpoint, cluster);
VerifyOrReturnError(clusterObj != nullptr, 0);
return clusterObj->attributeCount;
}
uint16_t emberAfGetServerAttributeIndexByAttributeId(chip::EndpointId endpoint, chip::ClusterId cluster,
chip::AttributeId attributeId)
{
const EmberAfCluster * clusterObj = emberAfFindServerCluster(endpoint, cluster);
VerifyOrReturnError(clusterObj != nullptr, UINT16_MAX);
for (uint16_t i = 0; i < clusterObj->attributeCount; i++)
{
if (clusterObj->attributes[i].attributeId == attributeId)
{
return i;
}
}
return UINT16_MAX;
}
Optional<AttributeId> emberAfGetServerAttributeIdByIndex(EndpointId endpoint, ClusterId cluster, uint16_t attributeIndex)
{
const EmberAfCluster * clusterObj = emberAfFindServerCluster(endpoint, cluster);
if (clusterObj == nullptr || clusterObj->attributeCount <= attributeIndex)
{
return Optional<AttributeId>::Missing();
}
return Optional<AttributeId>(clusterObj->attributes[attributeIndex].attributeId);
}
DataVersion * emberAfDataVersionStorage(const chip::app::ConcreteClusterPath & aConcreteClusterPath)
{
uint16_t index = emberAfIndexFromEndpoint(aConcreteClusterPath.mEndpointId);
if (index == kEmberInvalidEndpointIndex)
{
// Unknown endpoint.
return nullptr;
}
const EmberAfDefinedEndpoint & ep = emAfEndpoints[index];
if (!ep.dataVersions)
{
// No storage provided.
return nullptr;
}
// This does a second walk over endpoints to find the right one, but
// probably worth it to avoid duplicating code.
auto clusterIndex = emberAfClusterIndex(aConcreteClusterPath.mEndpointId, aConcreteClusterPath.mClusterId, CLUSTER_MASK_SERVER);
if (clusterIndex == 0xFF)
{
// No such cluster on this endpoint.
return nullptr;
}
return ep.dataVersions + clusterIndex;
}