diff --git a/examples/air-quality-sensor-app/linux/AirQualitySensorAppAttrUpdateDelegate.cpp b/examples/air-quality-sensor-app/linux/AirQualitySensorAppAttrUpdateDelegate.cpp index 7aaa873a90..b17db0107d 100644 --- a/examples/air-quality-sensor-app/linux/AirQualitySensorAppAttrUpdateDelegate.cpp +++ b/examples/air-quality-sensor-app/linux/AirQualitySensorAppAttrUpdateDelegate.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include diff --git a/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp b/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp index 0d66550940..e0278f30cc 100644 --- a/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp +++ b/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include diff --git a/examples/all-clusters-app/linux/main-common.cpp b/examples/all-clusters-app/linux/main-common.cpp index b62d154f0d..6d8728da53 100644 --- a/examples/all-clusters-app/linux/main-common.cpp +++ b/examples/all-clusters-app/linux/main-common.cpp @@ -53,7 +53,6 @@ #include #include #include -#include #include #include #include diff --git a/examples/all-clusters-minimal-app/linux/main-common.cpp b/examples/all-clusters-minimal-app/linux/main-common.cpp index d99927cea0..306a4b51a2 100644 --- a/examples/all-clusters-minimal-app/linux/main-common.cpp +++ b/examples/all-clusters-minimal-app/linux/main-common.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include diff --git a/examples/bridge-app/esp32/README.md b/examples/bridge-app/esp32/README.md index ac22c7f8b3..2a0bde25f5 100644 --- a/examples/bridge-app/esp32/README.md +++ b/examples/bridge-app/esp32/README.md @@ -54,10 +54,10 @@ defined: definition. - All attributes defined with these macros will be configured as - `ATTRIBUTE_MASK_EXTERNAL_STORAGE` in the ZCL database and therefore will - rely on the application to maintain storage for the attribute. Consequently, - reads or writes to these attributes must be handled within the application - by the `emberAfExternalAttributeWriteCallback` and + `MATTER_ATTRIBUTE_FLAG_EXTERNAL_STORAGE` in the ZCL database and therefore + will rely on the application to maintain storage for the attribute. + Consequently, reads or writes to these attributes must be handled within the + application by the `emberAfExternalAttributeWriteCallback` and `emberAfExternalAttributeReadCallback` functions. See the bridge application's `main.cpp` for an example of this implementation. diff --git a/examples/bridge-app/linux/README.md b/examples/bridge-app/linux/README.md index a416923669..6502655685 100644 --- a/examples/bridge-app/linux/README.md +++ b/examples/bridge-app/linux/README.md @@ -45,10 +45,10 @@ defined: definition. - All attributes defined with these macros will be configured as - `ATTRIBUTE_MASK_EXTERNAL_STORAGE` in the ZCL database and therefore will - rely on the application to maintain storage for the attribute. Consequently, - reads or writes to these attributes must be handled within the application - by the `emberAfExternalAttributeWriteCallback` and + `MATTER_ATTRIBUTE_FLAG_EXTERNAL_STORAGE` in the ZCL database and therefore + will rely on the application to maintain storage for the attribute. + Consequently, reads or writes to these attributes must be handled within the + application by the `emberAfExternalAttributeWriteCallback` and `emberAfExternalAttributeReadCallback` functions. See the bridge application's `main.cpp` for an example of this implementation. diff --git a/examples/bridge-app/telink/README.md b/examples/bridge-app/telink/README.md index 00b050e93c..2dccc834cb 100644 --- a/examples/bridge-app/telink/README.md +++ b/examples/bridge-app/telink/README.md @@ -56,10 +56,10 @@ defined: definition. - All attributes defined with these macros will be configured as - `ATTRIBUTE_MASK_EXTERNAL_STORAGE` in the ZCL database and therefore will - rely on the application to maintain storage for the attribute. Consequently, - reads or writes to these attributes must be handled within the application - by the `emberAfExternalAttributeWriteCallback` and + `MATTER_ATTRIBUTE_FLAG_EXTERNAL_STORAGE` in the ZCL database and therefore + will rely on the application to maintain storage for the attribute. + Consequently, reads or writes to these attributes must be handled within the + application by the `emberAfExternalAttributeWriteCallback` and `emberAfExternalAttributeReadCallback` functions. See the bridge application's `main.cpp` for an example of this implementation. diff --git a/examples/chef/common/clusters/switch/SwitchEventHandler.cpp b/examples/chef/common/clusters/switch/SwitchEventHandler.cpp index dd32e2b907..55fae2ae70 100644 --- a/examples/chef/common/clusters/switch/SwitchEventHandler.cpp +++ b/examples/chef/common/clusters/switch/SwitchEventHandler.cpp @@ -20,7 +20,6 @@ #ifdef MATTER_DM_PLUGIN_SWITCH_SERVER #include #include -#include #include #include "SwitchEventHandler.h" diff --git a/examples/chef/common/clusters/switch/SwitchManager.cpp b/examples/chef/common/clusters/switch/SwitchManager.cpp index 45d39dd4bc..c308cb1111 100644 --- a/examples/chef/common/clusters/switch/SwitchManager.cpp +++ b/examples/chef/common/clusters/switch/SwitchManager.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include diff --git a/examples/chef/esp32/main/main.cpp b/examples/chef/esp32/main/main.cpp index b61fcf22bc..460ac0c657 100644 --- a/examples/chef/esp32/main/main.cpp +++ b/examples/chef/esp32/main/main.cpp @@ -41,7 +41,6 @@ #include #include #include -#include #include #include "Display.h" diff --git a/examples/fabric-bridge-app/linux/README.md b/examples/fabric-bridge-app/linux/README.md index 27a01d9ffe..12a0ac2093 100644 --- a/examples/fabric-bridge-app/linux/README.md +++ b/examples/fabric-bridge-app/linux/README.md @@ -51,10 +51,10 @@ defined: definition. - All attributes defined with these macros will be configured as - `ATTRIBUTE_MASK_EXTERNAL_STORAGE` in the ZCL database and therefore will - rely on the application to maintain storage for the attribute. Consequently, - reads or writes to these attributes must be handled within the application - by the `emberAfExternalAttributeWriteCallback` and + `MATTER_ATTRIBUTE_FLAG_EXTERNAL_STORAGE` in the ZCL database and therefore + will rely on the application to maintain storage for the attribute. + Consequently, reads or writes to these attributes must be handled within the + application by the `emberAfExternalAttributeWriteCallback` and `emberAfExternalAttributeReadCallback` functions. See the bridge application's `main.cpp` for an example of this implementation. diff --git a/examples/fabric-sync/README.md b/examples/fabric-sync/README.md new file mode 100644 index 0000000000..ab08bd42af --- /dev/null +++ b/examples/fabric-sync/README.md @@ -0,0 +1,185 @@ +# Matter Linux Fabric Sync Example + +An example application to implement the Fabric Synchronization feature and +demonstrates the end-to-end Fabric Synchronization feature using dynamic +endpoints. + +Fabric Synchronization feature will facilitate the commissioning of end devices +from one fabric to another without requiring user intervention for every end +device. It defines mechanisms that can be used by multiple +ecosystems/controllers to communicate with one another to simplify the +experience for users. + +This doc is tested on **Ubuntu 22.04 LTS (aarch64)** + +
+ +- [Matter Linux Fabric Sync Example](#matter-linux-fabric-sync-example) + - [Theory of Operation](#theory-of-operation) + - [Building](#building) + - [Running the Complete Example on Ubuntu](#running-the-complete-example-on-ubuntu) + +
+ +## Theory of Operation + +### Dynamic Endpoints + +The Fabric-Sync Example makes use of Dynamic Endpoints. Current SDK support is +limited for dynamic endpoints, since endpoints are typically defined (along with +the clusters and attributes they contain) in a .zap file which then generates +code and static structures to define the endpoints. + +To support endpoints that are not statically defined, the ZCL attribute storage +mechanisms will hold additional endpoint information for `NUM_DYNAMIC_ENDPOINTS` +additional endpoints. These additional endpoint structures must be defined by +the application and can change at runtime. + +To facilitate the creation of these endpoint structures, several macros are +defined: + +`DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(attrListName)` +`DECLARE_DYNAMIC_ATTRIBUTE(attId, attType, attSizeBytes, attrMask)` +`DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(clusterRevision)` + +- These three macros are used to declare a list of attributes for use within a + cluster. The declaration must begin with the + `DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN` macro which will define the name of + the allocated attribute structure. Each attribute is then added by the + `DECLARE_DYNAMIC_ATTRIBUTE` macro. Finally, + `DECLARE_DYNAMIC_ATTRIBUTE_LIST_END` macro should be used to close the + definition. + +- All attributes defined with these macros will be configured as + `MATTER_ATTRIBUTE_FLAG_EXTERNAL_STORAGE` in the ZCL database and therefore + will rely on the application to maintain storage for the attribute. + Consequently, reads or writes to these attributes must be handled within the + application by the `emberAfExternalAttributeWriteCallback` and + `emberAfExternalAttributeReadCallback` functions. See the bridge + application's `main.cpp` for an example of this implementation. + +`DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(clusterListName)` +`DECLARE_DYNAMIC_CLUSTER(clusterId, clusterAttrs, role, incomingCommands, outgoingCommands)` +`DECLARE_DYNAMIC_CLUSTER_LIST_END` + +- These three macros are used to declare a list of clusters for use within a + endpoint. The declaration must begin with the + `DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN` macro which will define the name of the + allocated cluster structure. Each cluster is then added by the + `DECLARE_DYNAMIC_CLUSTER` macro referencing attribute list previously + defined by the `DECLARE_DYNAMIC_ATTRIBUTE...` macros and the lists of + incoming/outgoing commands terminated by kInvalidCommandId (or nullptr if + there aren't any commands in the list). Finally, + `DECLARE_DYNAMIC_CLUSTER_LIST_END` macro should be used to close the + definition. + +`DECLARE_DYNAMIC_ENDPOINT(endpointName, clusterList)` + +- This macro is used to declare an endpoint and its associated cluster list, + which must be previously defined by the `DECLARE_DYNAMIC_CLUSTER...` macros. + +## Building + +- For Linux host example: + + ```sh + source scripts/activate.sh + ./scripts/build/build_examples.py --target linux-x64-fabric-sync-no-ble build + ``` + +- For Raspberry Pi 4 example: + + Pull Docker Images + + ```sh + docker pull ghcr.io/project-chip/chip-build-crosscompile:112 + ``` + + Run docker + + ```sh + docker run -it -v ~/connectedhomeip:/var/connectedhomeip ghcr.io/project-chip/chip-build-crosscompile:112 /bin/bash + ``` + + Build + + ```sh + cd /var/connectedhomeip + + git config --global --add safe.directory /var/connectedhomeip + + ./scripts/run_in_build_env.sh \ + "./scripts/build/build_examples.py \ + --target linux-arm64-fabric-sync-no-ble-clang \ + build" + ``` + + Transfer the fabric-bridge-app binary to a Raspberry Pi + + ```sh + scp ./fabric-sync ubuntu@xxx.xxx.xxx.xxx:/home/ubuntu + ``` + +## Running the Complete Example on Ubuntu + +- Building + + Follow [Building](#building) section of this document. + +- Run Linux Fabric Sync Example App on two Linux machine E1 and E2 + + ```sh + sudo rm -rf /tmp/chip_* + cd ~/connectedhomeip/ + out/debug/fabric-sync + ``` + +- Initiate the FS Setup Process from E1 to E2 + + ```sh + > app add-bridge 1 20202021 192.168.86.246 5540 + Done + > New device with Node ID: 0000000000000001 has been successfully added. + A new device has been added on Endpoint: 2. + ``` + +- Verify Reverse Commissioning of the Fabric-Bridge from E1 on E2 + + ```sh + > New device with Node ID: 0000000000000002 has been successfully added. + ``` + +- Pair Light Example to E2 + + Since Fabric-Bridge also functions as a Matter server, running it alongside + the Light Example app on the same machine would cause conflicts. Therefore, + you need to run the Matter Light Example app on a separate physical machine + from the one hosting Fabric-Sync. + + ```sh + > app add-device 3 + ``` + + After the device is successfully added, you will observe the following + message on E2 with the newly assigned Node ID: + + ```sh + > New device with Node ID: 0x3 has been successfully added. + ``` + + Additionally, you should also get notified when a new device is added to E2 + from the E1: + + ```sh + > A new device is added on Endpoint 3. + ``` + +- Synchronize Light Example to E1 + + After the Light Example is successfully paired in E2, we can start to + synchronize the light device to E1 using the new assigned dynamic endpointid + on Ecosystem 2. + + ```sh + > app sync-device + ``` diff --git a/examples/lighting-app-data-mode-no-unique-id/linux/LightingAppCommandDelegate.cpp b/examples/lighting-app-data-mode-no-unique-id/linux/LightingAppCommandDelegate.cpp index cc5af976eb..c6d1cdead6 100644 --- a/examples/lighting-app-data-mode-no-unique-id/linux/LightingAppCommandDelegate.cpp +++ b/examples/lighting-app-data-mode-no-unique-id/linux/LightingAppCommandDelegate.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include diff --git a/examples/lighting-app/infineon/cyw30739/src/LightingManager.cpp b/examples/lighting-app/infineon/cyw30739/src/LightingManager.cpp index eb707676b5..7fd4003f36 100644 --- a/examples/lighting-app/infineon/cyw30739/src/LightingManager.cpp +++ b/examples/lighting-app/infineon/cyw30739/src/LightingManager.cpp @@ -19,7 +19,6 @@ #include "LightingManager.h" #include -#include #include using namespace chip; diff --git a/examples/lighting-app/linux/LightingAppCommandDelegate.cpp b/examples/lighting-app/linux/LightingAppCommandDelegate.cpp index cc5af976eb..c6d1cdead6 100644 --- a/examples/lighting-app/linux/LightingAppCommandDelegate.cpp +++ b/examples/lighting-app/linux/LightingAppCommandDelegate.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include diff --git a/src/app/AttributePathExpandIterator-Ember.cpp b/src/app/AttributePathExpandIterator-Ember.cpp index 1149f0c702..b1d6cb2a12 100644 --- a/src/app/AttributePathExpandIterator-Ember.cpp +++ b/src/app/AttributePathExpandIterator-Ember.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -98,7 +97,7 @@ void AttributePathExpandIteratorEmber::PrepareClusterIndexRange(const AttributeP } else { - mClusterIndex = emberAfClusterIndex(aEndpointId, aAttributePath.mClusterId, CLUSTER_MASK_SERVER); + mClusterIndex = emberAfClusterIndex(aEndpointId, aAttributePath.mClusterId, MATTER_CLUSTER_FLAG_SERVER); // If the given cluster id does not exist on the given endpoint, it will return uint8(0xFF), then endClusterIndex // will be 0, means we should iterate a null cluster set (skip it). mEndClusterIndex = static_cast(mClusterIndex + 1); diff --git a/src/app/clusters/group-key-mgmt-server/group-key-mgmt-server.cpp b/src/app/clusters/group-key-mgmt-server/group-key-mgmt-server.cpp index 39b9f59d23..6874fc9f56 100644 --- a/src/app/clusters/group-key-mgmt-server/group-key-mgmt-server.cpp +++ b/src/app/clusters/group-key-mgmt-server/group-key-mgmt-server.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include diff --git a/src/app/clusters/groups-server/groups-server.cpp b/src/app/clusters/groups-server/groups-server.cpp index add0261927..fe772caad1 100644 --- a/src/app/clusters/groups-server/groups-server.cpp +++ b/src/app/clusters/groups-server/groups-server.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -339,7 +338,7 @@ bool emberAfGroupsClusterRemoveAllGroupsCallback(app::CommandHandler * commandOb } iter->Release(); ScenesManagement::ScenesServer::Instance().GroupWillBeRemoved(fabricIndex, commandPath.mEndpointId, - ZCL_SCENES_GLOBAL_SCENE_GROUP_ID); + ScenesManagement::ScenesServer::kGlobalSceneGroupId); } #endif diff --git a/src/app/clusters/mode-select-server/mode-select-server.cpp b/src/app/clusters/mode-select-server/mode-select-server.cpp index 813d16d965..db771004a5 100644 --- a/src/app/clusters/mode-select-server/mode-select-server.cpp +++ b/src/app/clusters/mode-select-server/mode-select-server.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include diff --git a/src/app/clusters/on-off-server/on-off-server.cpp b/src/app/clusters/on-off-server/on-off-server.cpp index d18aa09383..85465e085c 100644 --- a/src/app/clusters/on-off-server/on-off-server.cpp +++ b/src/app/clusters/on-off-server/on-off-server.cpp @@ -667,8 +667,9 @@ bool OnOffServer::offWithEffectCommand(app::CommandHandler * commandObj, const a if (globalSceneControl) { #ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT - ScenesManagement::ScenesServer::Instance().StoreCurrentScene(fabric, endpoint, ZCL_SCENES_GLOBAL_SCENE_GROUP_ID, - ZCL_SCENES_GLOBAL_SCENE_SCENE_ID); + ScenesManagement::ScenesServer::Instance().StoreCurrentScene(fabric, endpoint, + ScenesManagement::ScenesServer::kGlobalSceneGroupId, + ScenesManagement::ScenesServer::kGlobalSceneId); #endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT OnOff::Attributes::GlobalSceneControl::Set(endpoint, false); } @@ -723,8 +724,8 @@ bool OnOffServer::OnWithRecallGlobalSceneCommand(app::CommandHandler * commandOb } #ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT - ScenesManagement::ScenesServer::Instance().RecallScene(fabric, endpoint, ZCL_SCENES_GLOBAL_SCENE_GROUP_ID, - ZCL_SCENES_GLOBAL_SCENE_SCENE_ID); + ScenesManagement::ScenesServer::Instance().RecallScene(fabric, endpoint, ScenesManagement::ScenesServer::kGlobalSceneGroupId, + ScenesManagement::ScenesServer::kGlobalSceneId); #endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT OnOff::Attributes::GlobalSceneControl::Set(endpoint, true); diff --git a/src/app/clusters/scenes-server/scenes-server.h b/src/app/clusters/scenes-server/scenes-server.h index 81c7d41f65..024b6f64f6 100644 --- a/src/app/clusters/scenes-server/scenes-server.h +++ b/src/app/clusters/scenes-server/scenes-server.h @@ -40,6 +40,8 @@ class ScenesServer : public CommandHandlerInterface, public AttributeAccessInter MATTER_DM_SCENES_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; static_assert(kScenesServerMaxEndpointCount <= kEmberInvalidEndpointIndex, "Scenes endpoint count error"); static constexpr uint8_t kScenesServerMaxFabricCount = CHIP_CONFIG_MAX_FABRICS; + static constexpr SceneId kGlobalSceneId = 0x00; + static constexpr GroupId kGlobalSceneGroupId = 0x0000; // FabricSceneInfo class FabricSceneInfo diff --git a/src/app/codegen-data-model-provider/EmberMetadata.cpp b/src/app/codegen-data-model-provider/EmberMetadata.cpp index 69f9e3e889..a01b70532f 100644 --- a/src/app/codegen-data-model-provider/EmberMetadata.cpp +++ b/src/app/codegen-data-model-provider/EmberMetadata.cpp @@ -62,7 +62,7 @@ FindAttributeMetadata(const ConcreteAttributePath & aPath) return Status::UnsupportedEndpoint; } - const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, CLUSTER_MASK_SERVER); + const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, MATTER_CLUSTER_FLAG_SERVER); if (cluster == nullptr) { return Status::UnsupportedCluster; diff --git a/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp b/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp index 3840de9c81..ff21ae0d28 100644 --- a/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp +++ b/src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp @@ -263,11 +263,12 @@ class ScopedMockAccessControl #define MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x1000) #define MOCK_ATTRIBUTE_CONFIG_NULLABLE(zcl_type) \ - MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE) + MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(zcl_type), zcl_type, \ + MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_NULLABLE) #define MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type) MockAttributeId(zcl_type + 0x2000) #define MOCK_ATTRIBUTE_CONFIG_NON_NULLABLE(zcl_type) \ - MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type), zcl_type, ATTRIBUTE_MASK_WRITABLE) + MockAttributeConfig(MOCK_ATTRIBUTE_ID_FOR_NON_NULLABLE_TYPE(zcl_type), zcl_type, MATTER_ATTRIBUTE_FLAG_WRITABLE) // clang-format off const MockNodeConfig gTestNodeConfig({ @@ -325,7 +326,7 @@ const MockNodeConfig gTestNodeConfig({ MockAttributeConfig( kReadOnlyAttributeId, ZCL_INT32U_ATTRIBUTE_TYPE, - ATTRIBUTE_MASK_NULLABLE // NOTE: explicltly NOT ATTRIBUTE_MASK_WRITABLE + MATTER_ATTRIBUTE_FLAG_NULLABLE // NOTE: explicltly NOT MATTER_ATTRIBUTE_FLAG_WRITABLE ) }), MockClusterConfig(MockClusterId(4), { @@ -481,7 +482,7 @@ const MockNodeConfig gTestNodeConfig({ // Special case handling MockAttributeConfig(kAttributeIdReadOnly, ZCL_INT32S_ATTRIBUTE_TYPE, 0), - MockAttributeConfig(kAttributeIdTimedWrite, ZCL_INT32S_ATTRIBUTE_TYPE, ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE), + MockAttributeConfig(kAttributeIdTimedWrite, ZCL_INT32S_ATTRIBUTE_TYPE, MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_MUST_USE_TIMED_WRITE), }), }), }); diff --git a/src/app/dynamic_server/DynamicDispatcher.cpp b/src/app/dynamic_server/DynamicDispatcher.cpp index e1f458f0bc..cc6ea6a1a2 100644 --- a/src/app/dynamic_server/DynamicDispatcher.cpp +++ b/src/app/dynamic_server/DynamicDispatcher.cpp @@ -31,7 +31,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -328,7 +329,7 @@ Optional emberAfGetServerAttributeIdByIndex(EndpointId endpoint, Cl uint8_t emberAfClusterIndex(EndpointId endpoint, ClusterId clusterId, EmberAfClusterMask mask) { - if (endpoint == kSupportedEndpoint && clusterId == OtaSoftwareUpdateProvider::Id && (mask & CLUSTER_MASK_SERVER)) + if (endpoint == kSupportedEndpoint && clusterId == OtaSoftwareUpdateProvider::Id && (mask & MATTER_CLUSTER_FLAG_SERVER)) { return 0; } @@ -352,7 +353,7 @@ const EmberAfCluster otaProviderCluster{ .attributes = nullptr, .attributeCount = 0, .clusterSize = 0, - .mask = CLUSTER_MASK_SERVER, + .mask = MATTER_CLUSTER_FLAG_SERVER, .functions = nullptr, .acceptedCommandList = acceptedCommands, .generatedCommandList = generatedCommands, diff --git a/src/app/tests/TestSceneTable.cpp b/src/app/tests/TestSceneTable.cpp index 2e541cb0e7..6f6093cd09 100644 --- a/src/app/tests/TestSceneTable.cpp +++ b/src/app/tests/TestSceneTable.cpp @@ -176,7 +176,7 @@ static EmberAfAttributeMetadata mockMetadataBool = { .attributeId = 0, .size = 1, .attributeType = ZCL_BOOLEAN_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataUint8 = { @@ -184,7 +184,7 @@ static EmberAfAttributeMetadata mockMetadataUint8 = { .attributeId = 0, .size = 1, .attributeType = ZCL_INT8U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_NULLABLE, }; static EmberAfAttributeMetadata mockMetadataUint8Max = { @@ -192,7 +192,7 @@ static EmberAfAttributeMetadata mockMetadataUint8Max = { .attributeId = 0, .size = 1, .attributeType = ZCL_INT8U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE | ATTRIBUTE_MASK_MIN_MAX, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_NULLABLE | MATTER_ATTRIBUTE_FLAG_MIN_MAX, }; static EmberAfAttributeMetadata mockMetadataUint16 = { @@ -200,7 +200,7 @@ static EmberAfAttributeMetadata mockMetadataUint16 = { .attributeId = 0, .size = 2, .attributeType = ZCL_INT16U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataUint24 = { @@ -208,7 +208,7 @@ static EmberAfAttributeMetadata mockMetadataUint24 = { .attributeId = 0, .size = 3, .attributeType = ZCL_INT24U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataUint32 = { @@ -216,7 +216,7 @@ static EmberAfAttributeMetadata mockMetadataUint32 = { .attributeId = 0, .size = 4, .attributeType = ZCL_INT32U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataUint40 = { @@ -224,7 +224,7 @@ static EmberAfAttributeMetadata mockMetadataUint40 = { .attributeId = 0, .size = 5, .attributeType = ZCL_INT40U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataUint48 = { @@ -232,7 +232,7 @@ static EmberAfAttributeMetadata mockMetadataUint48 = { .attributeId = 0, .size = 6, .attributeType = ZCL_INT48U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataUint56 = { @@ -240,7 +240,7 @@ static EmberAfAttributeMetadata mockMetadataUint56 = { .attributeId = 0, .size = 7, .attributeType = ZCL_INT56U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataUint64 = { @@ -248,7 +248,7 @@ static EmberAfAttributeMetadata mockMetadataUint64 = { .attributeId = 0, .size = 8, .attributeType = ZCL_INT64U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataInt8 = { @@ -256,7 +256,7 @@ static EmberAfAttributeMetadata mockMetadataInt8 = { .attributeId = 0, .size = 1, .attributeType = ZCL_INT8S_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_MIN_MAX, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_MIN_MAX, }; static EmberAfAttributeMetadata mockMetadataInt16 = { @@ -264,7 +264,7 @@ static EmberAfAttributeMetadata mockMetadataInt16 = { .attributeId = 0, .size = 2, .attributeType = ZCL_INT16S_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_MIN_MAX, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_MIN_MAX, }; static EmberAfAttributeMetadata mockMetadataInt24 = { @@ -272,7 +272,7 @@ static EmberAfAttributeMetadata mockMetadataInt24 = { .attributeId = 0, .size = 3, .attributeType = ZCL_INT24S_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataInt32 = { @@ -280,7 +280,7 @@ static EmberAfAttributeMetadata mockMetadataInt32 = { .attributeId = 0, .size = 4, .attributeType = ZCL_INT32S_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataInt40 = { @@ -288,7 +288,7 @@ static EmberAfAttributeMetadata mockMetadataInt40 = { .attributeId = 0, .size = 5, .attributeType = ZCL_INT40S_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataInt48 = { @@ -296,7 +296,7 @@ static EmberAfAttributeMetadata mockMetadataInt48 = { .attributeId = 0, .size = 6, .attributeType = ZCL_INT48S_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataInt56 = { @@ -304,7 +304,7 @@ static EmberAfAttributeMetadata mockMetadataInt56 = { .attributeId = 0, .size = 7, .attributeType = ZCL_INT56S_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; static EmberAfAttributeMetadata mockMetadataInt64 = { @@ -312,7 +312,7 @@ static EmberAfAttributeMetadata mockMetadataInt64 = { .attributeId = 0, .size = 8, .attributeType = ZCL_INT64S_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE, }; // clang-format off diff --git a/src/app/util/BUILD.gn b/src/app/util/BUILD.gn index 29b3bc9563..763480f159 100644 --- a/src/app/util/BUILD.gn +++ b/src/app/util/BUILD.gn @@ -32,7 +32,6 @@ source_set("nullable-primitives") { # These headers/cpp only depend on core/common source_set("types") { sources = [ - "att-storage.h", "attribute-metadata.cpp", "attribute-metadata.h", "basic-types.h", diff --git a/src/app/util/af-types.h b/src/app/util/af-types.h index 04275a2515..dbdcaa0a46 100644 --- a/src/app/util/af-types.h +++ b/src/app/util/af-types.h @@ -23,7 +23,6 @@ * @{ */ -#include "att-storage.h" #include // For bool #include // For various uint*_t types @@ -43,11 +42,6 @@ #include #include -/** - * @brief Type for the cluster mask - */ -typedef uint8_t EmberAfClusterMask; - /** * @brief Generic function type, used for either of the cluster function. * @@ -62,6 +56,21 @@ typedef void (*EmberAfGenericClusterFunction)(void); */ #define MATTER_DM_NULL_MANUFACTURER_CODE 0x0000 +// The following define names are relevant to the ZAP_CLUSTER_MASK macro. +#define MATTER_CLUSTER_FLAG_INIT_FUNCTION 0x01 +#define MATTER_CLUSTER_FLAG_ATTRIBUTE_CHANGED_FUNCTION 0x02 +// Bit 2 (0x04) and Bit3 (0x08) are free. +#define MATTER_CLUSTER_FLAG_SHUTDOWN_FUNCTION 0x10 +#define MATTER_CLUSTER_FLAG_PRE_ATTRIBUTE_CHANGED_FUNCTION 0x20 +#define MATTER_CLUSTER_FLAG_SERVER 0x40 +#define MATTER_CLUSTER_FLAG_CLIENT 0x80 + +/** + * @brief Type for the cluster mask + * Value of the mask represents a single, or aggregated, MATTER_CLUSTER_FLAG_X + */ +typedef uint8_t EmberAfClusterMask; + /** * @brief Struct describing cluster */ @@ -119,7 +128,9 @@ struct EmberAfCluster */ uint16_t eventCount; - bool IsServer() const { return (mask & CLUSTER_MASK_SERVER) != 0; } + bool IsServer() const { return (mask & MATTER_CLUSTER_FLAG_SERVER) != 0; } + + bool IsClient() const { return (mask & MATTER_CLUSTER_FLAG_CLIENT) != 0; } }; /** @@ -232,30 +243,6 @@ struct EmberAfDefinedEndpoint chip::Span tagList; }; -// Cluster specific types - -/** - * @brief Indicates the absence of a Scene table entry. - */ -#define MATTER_DM_SCENE_TABLE_NULL_INDEX 0xFF -/** - * @brief Value used when setting or getting the endpoint in a Scene table - * entry. It indicates that the entry is not in use. - */ -#define MATTER_DM_SCENE_TABLE_UNUSED_ENDPOINT_ID 0x00 -/** - * @brief Maximum length of Scene names, not including the length byte. - */ -#define ZCL_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH 16 -/** - * @brief The group identifier for the global scene. - */ -#define ZCL_SCENES_GLOBAL_SCENE_GROUP_ID 0x0000 -/** - * @brief The scene identifier for the global scene. - */ -#define ZCL_SCENES_GLOBAL_SCENE_SCENE_ID 0x00 - /** * @brief Type for referring to the tick callback for cluster. * diff --git a/src/app/util/att-storage.h b/src/app/util/att-storage.h deleted file mode 100644 index 9befaacf7c..0000000000 --- a/src/app/util/att-storage.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * - * Copyright (c) 2022 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. - */ -#pragma once - -// Cluster masks modify how clusters are used by the framework -// -// Does this cluster have init function? -#define CLUSTER_MASK_INIT_FUNCTION (0x01) -// Does this cluster have attribute changed function? -#define CLUSTER_MASK_ATTRIBUTE_CHANGED_FUNCTION (0x02) -// Bits 0x04 and 0x08 are free. -// Does this cluster have shutdown function? -#define CLUSTER_MASK_SHUTDOWN_FUNCTION (0x10) -// Does this cluster have pre-attribute changed function? -#define CLUSTER_MASK_PRE_ATTRIBUTE_CHANGED_FUNCTION (0x20) -// Cluster is a server -#define CLUSTER_MASK_SERVER (0x40) -// Cluster is a client -#define CLUSTER_MASK_CLIENT (0x80) diff --git a/src/app/util/attribute-metadata.h b/src/app/util/attribute-metadata.h index 6703e397d5..368a90418e 100644 --- a/src/app/util/attribute-metadata.h +++ b/src/app/util/attribute-metadata.h @@ -105,23 +105,23 @@ union EmberAfDefaultOrMinMaxAttributeValue }; // Attribute masks modify how attributes are used by the framework -// +// The following define names are relevant to the ZAP_ATTRIBUTE_MASK macro. // Attribute that has this mask is NOT read-only -#define ATTRIBUTE_MASK_WRITABLE (0x01) +#define MATTER_ATTRIBUTE_FLAG_WRITABLE (0x01) // Attribute that has this mask is saved in non-volatile memory -#define ATTRIBUTE_MASK_NONVOLATILE (0x02) -// Alias until ZAP gets updated to output ATTRIBUTE_MASK_NONVOLATILE -#define ATTRIBUTE_MASK_TOKENIZE ATTRIBUTE_MASK_NONVOLATILE +#define MATTER_ATTRIBUTE_FLAG_NONVOLATILE (0x02) +// Alias until ZAP gets updated to output MATTER_ATTRIBUTE_FLAG_NONVOLATILE +#define MATTER_ATTRIBUTE_FLAG_TOKENIZE MATTER_ATTRIBUTE_FLAG_NONVOLATILE // Attribute that has this mask has a min/max values -#define ATTRIBUTE_MASK_MIN_MAX (0x04) +#define MATTER_ATTRIBUTE_FLAG_MIN_MAX (0x04) // Attribute requires a timed interaction to write -#define ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE (0x08) +#define MATTER_ATTRIBUTE_FLAG_MUST_USE_TIMED_WRITE (0x08) // Attribute deferred to external storage -#define ATTRIBUTE_MASK_EXTERNAL_STORAGE (0x10) +#define MATTER_ATTRIBUTE_FLAG_EXTERNAL_STORAGE (0x10) // Attribute is singleton -#define ATTRIBUTE_MASK_SINGLETON (0x20) +#define MATTER_ATTRIBUTE_FLAG_SINGLETON (0x20) // Attribute is nullable -#define ATTRIBUTE_MASK_NULLABLE (0x40) +#define MATTER_ATTRIBUTE_FLAG_NULLABLE (0x40) /** * @brief Each attribute has it's metadata stored in such struct. @@ -170,41 +170,41 @@ struct EmberAfAttributeMetadata /** * Check whether this attribute has a define min and max. */ - bool HasMinMax() const { return mask & ATTRIBUTE_MASK_MIN_MAX; } + bool HasMinMax() const { return mask & MATTER_ATTRIBUTE_FLAG_MIN_MAX; } /** * Check whether this attribute is nullable. */ - bool IsNullable() const { return mask & ATTRIBUTE_MASK_NULLABLE; } + bool IsNullable() const { return mask & MATTER_ATTRIBUTE_FLAG_NULLABLE; } /** * Check whether this attribute is readonly. */ - bool IsReadOnly() const { return !(mask & ATTRIBUTE_MASK_WRITABLE); } + bool IsReadOnly() const { return !(mask & MATTER_ATTRIBUTE_FLAG_WRITABLE); } /** * Check whether this attribute requires a timed write. */ - bool MustUseTimedWrite() const { return mask & ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE; } + bool MustUseTimedWrite() const { return mask & MATTER_ATTRIBUTE_FLAG_MUST_USE_TIMED_WRITE; } /** * Check whether this attibute's storage is managed outside the built-in * attribute store. */ - bool IsExternal() const { return mask & ATTRIBUTE_MASK_EXTERNAL_STORAGE; } + bool IsExternal() const { return mask & MATTER_ATTRIBUTE_FLAG_EXTERNAL_STORAGE; } /** * Check whether this is a "singleton" attribute, in the sense that it has a * single value across multiple instances of the cluster. This is not * mutually exclusive with the attribute being external. */ - bool IsSingleton() const { return mask & ATTRIBUTE_MASK_SINGLETON; } + bool IsSingleton() const { return mask & MATTER_ATTRIBUTE_FLAG_SINGLETON; } /** * Check whether this attribute is automatically stored in non-volatile * memory. */ - bool IsAutomaticallyPersisted() const { return (mask & ATTRIBUTE_MASK_NONVOLATILE) && !IsExternal(); } + bool IsAutomaticallyPersisted() const { return (mask & MATTER_ATTRIBUTE_FLAG_NONVOLATILE) && !IsExternal(); } }; /** @brief Returns true if the given attribute type is a string. */ diff --git a/src/app/util/attribute-storage-detail.h b/src/app/util/attribute-storage-detail.h index 922605f3f9..6972115562 100644 --- a/src/app/util/attribute-storage-detail.h +++ b/src/app/util/attribute-storage-detail.h @@ -23,7 +23,6 @@ #include #include -#include #include #include diff --git a/src/app/util/attribute-storage.cpp b/src/app/util/attribute-storage.cpp index 0d1b3827e8..19736e5651 100644 --- a/src/app/util/attribute-storage.cpp +++ b/src/app/util/attribute-storage.cpp @@ -356,7 +356,7 @@ void emAfClusterAttributeChangedCallback(const ConcreteAttributePath & attribute const EmberAfCluster * cluster = emberAfFindServerCluster(attributePath.mEndpointId, attributePath.mClusterId); if (cluster != nullptr) { - EmberAfGenericClusterFunction f = emberAfFindClusterFunction(cluster, CLUSTER_MASK_ATTRIBUTE_CHANGED_FUNCTION); + EmberAfGenericClusterFunction f = emberAfFindClusterFunction(cluster, MATTER_CLUSTER_FLAG_ATTRIBUTE_CHANGED_FUNCTION); if (f != nullptr) { ((EmberAfClusterAttributeChangedCallback) f)(attributePath); @@ -382,7 +382,7 @@ Status emAfClusterPreAttributeChangedCallback(const ConcreteAttributePath & attr // 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)); + cluster, MATTER_CLUSTER_FLAG_PRE_ATTRIBUTE_CHANGED_FUNCTION)); if (f != nullptr) { status = f(attributePath, attributeType, size, value); @@ -399,7 +399,7 @@ static void initializeEndpoint(EmberAfDefinedEndpoint * definedEndpoint) const EmberAfCluster * cluster = &(epType->cluster[clusterIndex]); EmberAfGenericClusterFunction f; emberAfClusterInitCallback(definedEndpoint->endpoint, cluster->clusterId); - f = emberAfFindClusterFunction(cluster, CLUSTER_MASK_INIT_FUNCTION); + f = emberAfFindClusterFunction(cluster, MATTER_CLUSTER_FLAG_INIT_FUNCTION); if (f != nullptr) { ((EmberAfInitFunction) f)(definedEndpoint->endpoint); @@ -415,7 +415,7 @@ static void shutdownEndpoint(EmberAfDefinedEndpoint * definedEndpoint) for (clusterIndex = 0; clusterIndex < epType->clusterCount; clusterIndex++) { const EmberAfCluster * cluster = &(epType->cluster[clusterIndex]); - EmberAfGenericClusterFunction f = emberAfFindClusterFunction(cluster, CLUSTER_MASK_SHUTDOWN_FUNCTION); + EmberAfGenericClusterFunction f = emberAfFindClusterFunction(cluster, MATTER_CLUSTER_FLAG_SHUTDOWN_FUNCTION); if (f != nullptr) { ((EmberAfShutdownFunction) f)(definedEndpoint->endpoint); @@ -539,7 +539,7 @@ static Status typeSensitiveMemCopy(ClusterId clusterId, uint8_t * dest, uint8_t */ bool emAfMatchCluster(const EmberAfCluster * cluster, const EmberAfAttributeSearchRecord * attRecord) { - return (cluster->clusterId == attRecord->clusterId && (cluster->mask & CLUSTER_MASK_SERVER)); + return (cluster->clusterId == attRecord->clusterId && (cluster->mask & MATTER_CLUSTER_FLAG_SERVER)); } /** @@ -607,8 +607,8 @@ Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, { uint8_t * attributeLocation = - (am->mask & ATTRIBUTE_MASK_SINGLETON ? singletonAttributeLocation(am) - : attributeData + attributeOffsetIndex); + (am->mask & MATTER_ATTRIBUTE_FLAG_SINGLETON ? singletonAttributeLocation(am) + : attributeData + attributeOffsetIndex); uint8_t *src, *dst; if (write) { @@ -637,7 +637,7 @@ Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, } // Is the attribute externally stored? - if (am->mask & ATTRIBUTE_MASK_EXTERNAL_STORAGE) + if (am->mask & MATTER_ATTRIBUTE_FLAG_EXTERNAL_STORAGE) { return (write ? emberAfExternalAttributeWriteCallback(attRecord->endpoint, attRecord->clusterId, am, buffer) @@ -657,7 +657,8 @@ Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord, 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)) + if (!(am->mask & MATTER_ATTRIBUTE_FLAG_EXTERNAL_STORAGE) && + !(am->mask & MATTER_ATTRIBUTE_FLAG_SINGLETON)) { attributeOffsetIndex = static_cast(attributeOffsetIndex + emberAfAttributeSize(am)); } @@ -759,7 +760,7 @@ bool emberAfContainsClient(EndpointId endpoint, ClusterId clusterId) return false; } - return (emberAfFindClusterInType(emAfEndpoints[ep].endpointType, clusterId, CLUSTER_MASK_CLIENT) != nullptr); + return (emberAfFindClusterInType(emAfEndpoints[ep].endpointType, clusterId, MATTER_CLUSTER_FLAG_CLIENT) != nullptr); } // This will find the first server that has the clusterId given from the index of endpoint. @@ -770,7 +771,7 @@ bool emberAfContainsServerFromIndex(uint16_t index, ClusterId clusterId) return false; } - return emberAfFindClusterInType(emAfEndpoints[index].endpointType, clusterId, CLUSTER_MASK_SERVER); + return emberAfFindClusterInType(emAfEndpoints[index].endpointType, clusterId, MATTER_CLUSTER_FLAG_SERVER); } namespace chip { @@ -822,7 +823,7 @@ const EmberAfCluster * emberAfFindServerCluster(EndpointId endpoint, ClusterId c return nullptr; } - return emberAfFindClusterInType(emAfEndpoints[ep].endpointType, clusterId, CLUSTER_MASK_SERVER); + return emberAfFindClusterInType(emAfEndpoints[ep].endpointType, clusterId, MATTER_CLUSTER_FLAG_SERVER); } // Returns cluster within the endpoint; Does not ignore disabled endpoints @@ -848,7 +849,7 @@ uint16_t emberAfGetClusterServerEndpointIndex(EndpointId endpoint, ClusterId clu return kEmberInvalidEndpointIndex; } - if (emberAfFindClusterInType(emAfEndpoints[epIndex].endpointType, cluster, CLUSTER_MASK_SERVER) == nullptr) + if (emberAfFindClusterInType(emAfEndpoints[epIndex].endpointType, cluster, MATTER_CLUSTER_FLAG_SERVER) == nullptr) { // The provided endpoint does not contain the given cluster server. return kEmberInvalidEndpointIndex; @@ -864,7 +865,7 @@ uint16_t emberAfGetClusterServerEndpointIndex(EndpointId endpoint, ClusterId clu // 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)) + (emberAfFindClusterInType(emAfEndpoints[i].endpointType, cluster, MATTER_CLUSTER_FLAG_SERVER) != nullptr)) { adjustedEndpointIndex++; } @@ -989,7 +990,7 @@ uint8_t emberAfClusterCountByIndex(uint16_t endpointIndex, bool server) uint8_t emberAfClusterCountForEndpointType(const EmberAfEndpointType * type, bool server) { - const EmberAfClusterMask cluster_mask = server ? CLUSTER_MASK_SERVER : CLUSTER_MASK_CLIENT; + const EmberAfClusterMask cluster_mask = server ? MATTER_CLUSTER_FLAG_SERVER : MATTER_CLUSTER_FLAG_CLIENT; return static_cast(std::count_if(type->cluster, type->cluster + type->clusterCount, [=](const EmberAfCluster & cluster) { return (cluster.mask & cluster_mask) != 0; })); @@ -1070,7 +1071,7 @@ const EmberAfCluster * emberAfGetNthCluster(EndpointId endpoint, uint8_t n, bool } const EmberAfEndpointType * endpointType = emAfEndpoints[index].endpointType; - const EmberAfClusterMask cluster_mask = server ? CLUSTER_MASK_SERVER : CLUSTER_MASK_CLIENT; + const EmberAfClusterMask cluster_mask = server ? MATTER_CLUSTER_FLAG_SERVER : MATTER_CLUSTER_FLAG_CLIENT; const uint8_t clusterCount = endpointType->clusterCount; uint8_t c = 0; @@ -1212,7 +1213,7 @@ void emAfLoadAttributeDefaults(EndpointId endpoint, Optional clusterI size_t defaultValueSizeForBigEndianNudger = 0; // Bypasses compiler warning about unused variable for little endian platforms. (void) defaultValueSizeForBigEndianNudger; - if ((am->mask & ATTRIBUTE_MASK_MIN_MAX) != 0U) + if ((am->mask & MATTER_ATTRIBUTE_FLAG_MIN_MAX) != 0U) { // This is intentionally 2 and not 4 bytes since defaultValue in min/max // attributes is still uint16_t. @@ -1454,7 +1455,8 @@ DataVersion * emberAfDataVersionStorage(const ConcreteClusterPath & aConcreteClu // 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); + auto clusterIndex = + emberAfClusterIndex(aConcreteClusterPath.mEndpointId, aConcreteClusterPath.mClusterId, MATTER_CLUSTER_FLAG_SERVER); if (clusterIndex == 0xFF) { // No such cluster on this endpoint. diff --git a/src/app/util/attribute-storage.h b/src/app/util/attribute-storage.h index 9d2dcc60bb..3b8a2e3fb9 100644 --- a/src/app/util/attribute-storage.h +++ b/src/app/util/attribute-storage.h @@ -18,7 +18,6 @@ #pragma once #include -#include #include #include #include @@ -60,12 +59,12 @@ static constexpr uint16_t kEmberInvalidEndpointIndex = 0xFFFF; } /* cluster revision */ \ } -// The attrMask must contain the relevant ATTRIBUTE_MASK_* bits from +// The attrMask must contain the relevant MATTER_ATTRIBUTE_FLAG_* bits from // attribute-metadata.h. Specifically: // -// * Writable attributes must have ATTRIBUTE_MASK_WRITABLE -// * Nullable attributes (have X in the quality column in the spec) must have ATTRIBUTE_MASK_NULLABLE -// * Attributes that have T in the Access column in the spec must have ATTRIBUTE_MASK_MUST_USE_TIMED_WRITE +// * Writable attributes must have MATTER_ATTRIBUTE_FLAG_WRITABLE +// * Nullable attributes (have X in the quality column in the spec) must have MATTER_ATTRIBUTE_FLAG_NULLABLE +// * Attributes that have T in the Access column in the spec must have MATTER_ATTRIBUTE_FLAG_MUST_USE_TIMED_WRITE #define DECLARE_DYNAMIC_ATTRIBUTE(attId, attType, attSizeBytes, attrMask) \ { \ ZAP_EMPTY_DEFAULT(), attId, attSizeBytes, ZAP_TYPE(attType), attrMask | ZAP_ATTRIBUTE_MASK(EXTERNAL_STORAGE) \ @@ -223,8 +222,8 @@ uint8_t emberAfGetClusterCountForEndpoint(chip::EndpointId endpoint); // Check if a cluster is implemented or not. If yes, the cluster is returned. // // mask = 0 -> find either client or server -// mask = CLUSTER_MASK_CLIENT -> find client -// mask = CLUSTER_MASK_SERVER -> find server +// mask = MATTER_CLUSTER_FLAG_CLIENT -> find client +// mask = MATTER_CLUSTER_FLAG_SERVER -> find server // // If a pointer to an index is provided, it will be updated to point to the relative index of the cluster // within the set of clusters that match the mask criteria. diff --git a/src/app/util/attribute-table.cpp b/src/app/util/attribute-table.cpp index 0d68701b63..b84104d42a 100644 --- a/src/app/util/attribute-table.cpp +++ b/src/app/util/attribute-table.cpp @@ -365,7 +365,7 @@ Status emAfWriteAttribute(const ConcreteAttributePath & path, const EmberAfWrite // if the value the attribute is being set to is out of range // return Status::ConstraintError - if ((metadata->mask & ATTRIBUTE_MASK_MIN_MAX) != 0U) + if ((metadata->mask & MATTER_ATTRIBUTE_FLAG_MIN_MAX) != 0U) { EmberAfDefaultAttributeValue minv = metadata->defaultValue.ptrToMinMaxValue->minValue; EmberAfDefaultAttributeValue maxv = metadata->defaultValue.ptrToMinMaxValue->maxValue; diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index 0ea63cba30..af336dd39f 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -95,7 +94,7 @@ Protocols::InteractionModel::Status ServerClusterCommandExists(const ConcreteCom return Status::UnsupportedEndpoint; } - const EmberAfCluster * cluster = emberAfFindClusterInType(type, aCommandPath.mClusterId, CLUSTER_MASK_SERVER); + const EmberAfCluster * cluster = emberAfFindClusterInType(type, aCommandPath.mClusterId, MATTER_CLUSTER_FLAG_SERVER); if (cluster == nullptr) { return Status::UnsupportedCluster; @@ -223,7 +222,7 @@ Protocols::InteractionModel::Status UnsupportedAttributeStatus(const ConcreteAtt return Status::UnsupportedEndpoint; } - const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, CLUSTER_MASK_SERVER); + const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, MATTER_CLUSTER_FLAG_SERVER); if (cluster == nullptr) { return Status::UnsupportedCluster; @@ -800,7 +799,7 @@ Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventP return Status::UnsupportedEndpoint; } - const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, CLUSTER_MASK_SERVER); + const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, MATTER_CLUSTER_FLAG_SERVER); if (cluster == nullptr) { return Status::UnsupportedCluster; diff --git a/src/app/util/endpoint-config-defines.h b/src/app/util/endpoint-config-defines.h index 2a54c5d581..7da14edc50 100644 --- a/src/app/util/endpoint-config-defines.h +++ b/src/app/util/endpoint-config-defines.h @@ -29,5 +29,5 @@ { \ (uint32_t) x \ } -#define ZAP_ATTRIBUTE_MASK(mask) ATTRIBUTE_MASK_##mask -#define ZAP_CLUSTER_MASK(mask) CLUSTER_MASK_##mask +#define ZAP_ATTRIBUTE_MASK(mask) MATTER_ATTRIBUTE_FLAG_##mask +#define ZAP_CLUSTER_MASK(bit) MATTER_CLUSTER_FLAG_##bit diff --git a/src/app/util/mock/MockNodeConfig.cpp b/src/app/util/mock/MockNodeConfig.cpp index c6572ed0ae..a643daefad 100644 --- a/src/app/util/mock/MockNodeConfig.cpp +++ b/src/app/util/mock/MockNodeConfig.cpp @@ -19,7 +19,6 @@ #include "app/util/af-types.h" #include -#include #include #include #include @@ -127,9 +126,10 @@ MockClusterConfig::MockClusterConfig(ClusterId aId, std::initializer_list(attributes.size()); - mEmberCluster.mask = CLUSTER_MASK_SERVER; + mEmberCluster.mask = MATTER_CLUSTER_FLAG_SERVER; mEmberCluster.eventCount = static_cast(mEmberEventList.size()); mEmberCluster.eventList = mEmberEventList.data(); diff --git a/src/app/util/mock/MockNodeConfig.h b/src/app/util/mock/MockNodeConfig.h index 39f11052a7..6510a0e1a1 100644 --- a/src/app/util/mock/MockNodeConfig.h +++ b/src/app/util/mock/MockNodeConfig.h @@ -44,7 +44,7 @@ constexpr EmberAfAttributeMetadata DefaultAttributeMetadata(chip::AttributeId id .attributeId = id, .size = 4, .attributeType = ZCL_INT32U_ATTRIBUTE_TYPE, - .mask = ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE, + .mask = MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_NULLABLE, }; } @@ -55,7 +55,7 @@ struct MockAttributeConfig MockAttributeConfig(AttributeId aId) : id(aId), attributeMetaData(internal::DefaultAttributeMetadata(aId)) {} MockAttributeConfig(AttributeId aId, EmberAfAttributeMetadata metadata) : id(aId), attributeMetaData(metadata) {} MockAttributeConfig(AttributeId aId, EmberAfAttributeType type, - EmberAfAttributeMask mask = ATTRIBUTE_MASK_WRITABLE | ATTRIBUTE_MASK_NULLABLE) : + EmberAfAttributeMask mask = MATTER_ATTRIBUTE_FLAG_WRITABLE | MATTER_ATTRIBUTE_FLAG_NULLABLE) : id(aId), attributeMetaData(internal::DefaultAttributeMetadata(aId)) { diff --git a/src/app/util/mock/attribute-storage.cpp b/src/app/util/mock/attribute-storage.cpp index 256d0a3a88..d0843d89cb 100644 --- a/src/app/util/mock/attribute-storage.cpp +++ b/src/app/util/mock/attribute-storage.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -272,7 +271,7 @@ chip::Optional emberAfGetServerAttributeIdByIndex(chip::Endpo uint8_t emberAfClusterIndex(chip::EndpointId endpointId, chip::ClusterId clusterId, EmberAfClusterMask mask) { - VerifyOrReturnValue(mask == 0 || (mask & CLUSTER_MASK_SERVER) != 0, UINT8_MAX); // only server clusters supported + VerifyOrReturnValue(mask == 0 || (mask & MATTER_CLUSTER_FLAG_SERVER) != 0, UINT8_MAX); // only server clusters supported ptrdiff_t index; auto cluster = GetMockNodeConfig().clusterByIds(endpointId, clusterId, &index); VerifyOrReturnValue(cluster != nullptr, UINT8_MAX); diff --git a/src/controller/tests/TestWriteChunking.cpp b/src/controller/tests/TestWriteChunking.cpp index 5bb7ef9521..fadba53dfe 100644 --- a/src/controller/tests/TestWriteChunking.cpp +++ b/src/controller/tests/TestWriteChunking.cpp @@ -97,8 +97,8 @@ class TestWriteChunking : public Test::AppContext //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrsOnEndpoint) -DECLARE_DYNAMIC_ATTRIBUTE(kTestListAttribute, ARRAY, 1, ATTRIBUTE_MASK_WRITABLE), - DECLARE_DYNAMIC_ATTRIBUTE(kTestListAttribute2, ARRAY, 1, ATTRIBUTE_MASK_WRITABLE), DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); +DECLARE_DYNAMIC_ATTRIBUTE(kTestListAttribute, ARRAY, 1, MATTER_ATTRIBUTE_FLAG_WRITABLE), + DECLARE_DYNAMIC_ATTRIBUTE(kTestListAttribute2, ARRAY, 1, MATTER_ATTRIBUTE_FLAG_WRITABLE), DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(testEndpointClusters) DECLARE_DYNAMIC_CLUSTER(Clusters::UnitTesting::Id, testClusterAttrsOnEndpoint, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRIMDispatch.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRIMDispatch.mm index d21242956b..b283c8b819 100644 --- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRIMDispatch.mm +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRIMDispatch.mm @@ -18,7 +18,6 @@ #include #include #include -#include #include #include diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm index e2f6fecbbb..2927efa181 100644 --- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm @@ -312,7 +312,7 @@ - (BOOL)associateWithController:(nullable MTRDeviceController *)controller // Size in bytes does not matter, since we plan to // handle this entirely via AttributeAccessInterface. 0, - // ATTRIBUTE_MASK_NULLABLE is not relevant because we + // MATTER_ATTRIBUTE_FLAG_NULLABLE is not relevant because we // are handling this all via AttributeAccessInterface. 0))); } diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm index 1b0f27fbaf..8e238d31e5 100644 --- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm @@ -27,11 +27,10 @@ #include #include -// TODO: These af-types.h and att-storage.h and attribute-storage.h and +// TODO: These af-types.h and attribute-storage.h and // endpoint-config-api.h and probably CodeUtils.h bits are a hack that should // eventually go away. #include -#include #include #include #include @@ -257,7 +256,7 @@ - (BOOL)finishAssociationWithController:(nullable MTRDeviceController *)controll metadata.clusterSize = 0; // All our attributes are external. - metadata.mask = CLUSTER_MASK_SERVER; + metadata.mask = MATTER_CLUSTER_FLAG_SERVER; metadata.functions = nullptr; // None of our clusters, including Descriptor, uses these. @@ -278,7 +277,7 @@ - (BOOL)finishAssociationWithController:(nullable MTRDeviceController *)controll metadata.clusterSize = 0; // All our attributes are external. - metadata.mask = CLUSTER_MASK_SERVER; + metadata.mask = MATTER_CLUSTER_FLAG_SERVER; metadata.functions = nullptr; // Descriptor does not use these. diff --git a/src/data-model-providers/codegen/tests/TestEmberAttributeDataBuffer.cpp b/src/data-model-providers/codegen/tests/TestEmberAttributeDataBuffer.cpp new file mode 100644 index 0000000000..3d9571817a --- /dev/null +++ b/src/data-model-providers/codegen/tests/TestEmberAttributeDataBuffer.cpp @@ -0,0 +1,1179 @@ +/* + * Copyright (c) 2024 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 + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace chip; +using namespace chip::app; + +namespace { + +/// encodes a simple value in a TLV buffer +class TLVEncodedValue +{ +public: + TLVEncodedValue() = default; + ~TLVEncodedValue() = default; + + template + TLV::TLVReader EncodeValue(const T & value) + { + const auto kTag = TLV::ContextTag(AttributeDataIB::Tag::kData); + + TLV::TLVWriter writer; + writer.Init(mBuffer, sizeof(mBuffer)); + + TLV::TLVType outer; + + VerifyOrDie(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outer) == CHIP_NO_ERROR); + VerifyOrDie(DataModel::Encode(writer, kTag, value) == CHIP_NO_ERROR); + VerifyOrDie(writer.EndContainer(outer) == CHIP_NO_ERROR); + + VerifyOrDie(writer.Finalize() == CHIP_NO_ERROR); + size_t fill = writer.GetLengthWritten(); + + TLV::TLVReader reader; + reader.Init(mBuffer, fill); + VerifyOrDie(reader.Next() == CHIP_NO_ERROR); + VerifyOrDie(reader.GetTag() == TLV::AnonymousTag()); + VerifyOrDie(reader.EnterContainer(outer) == CHIP_NO_ERROR); + VerifyOrDie(reader.Next() == CHIP_NO_ERROR); + VerifyOrDie(reader.GetTag() == kTag); + + return reader; + } + +private: + static constexpr size_t kMaxSize = 128; + uint8_t mBuffer[kMaxSize]; +}; + +class EncodeResult +{ +public: + explicit EncodeResult() = default; + EncodeResult(CHIP_ERROR error) : mResult(error) { VerifyOrDie(error != CHIP_NO_ERROR); } + + static EncodeResult Ok() { return EncodeResult(); } + + bool IsSuccess() const { return !mResult.has_value(); } + + bool operator==(const CHIP_ERROR & other) const { return mResult.has_value() && (*mResult == other); } + + const std::optional & Value() const { return mResult; } + +private: + std::optional mResult; +}; + +template +bool IsEqual(const T & a, const T & b) +{ + return a == b; +} + +template <> +bool IsEqual(const float & a, const float & b) +{ + if (std::isnan(a) && std::isnan(b)) + { + return true; + } + + return a == b; +} + +template <> +bool IsEqual(const double & a, const double & b) +{ + if (std::isnan(a) && std::isnan(b)) + { + return true; + } + + return a == b; +} + +template <> +bool IsEqual(const ByteSpan & a, const ByteSpan & b) +{ + return a.data_equal(b); +} + +template <> +bool IsEqual(const CharSpan & a, const CharSpan & b) +{ + return a.data_equal(b); +} + +template +bool IsEqual(const std::optional & a, const std::optional & b) +{ + if (a.has_value() != b.has_value()) + { + return false; + } + + if (!a.has_value()) + { + return true; + } + + return IsEqual(*a, *b); +} + +template +bool IsEqual(const DataModel::Nullable & a, const DataModel::Nullable & b) +{ + if (a.IsNull() != b.IsNull()) + { + return false; + } + + if (a.IsNull()) + { + return true; + } + + return IsEqual(a.Value(), b.Value()); +} + +/// Validates that an encoded value in ember takes a specific format +template +class EncodeTester +{ +public: + EncodeTester(const EmberAfAttributeMetadata * meta) : mMetaData(meta) {} + ~EncodeTester() = default; + + template + EncodeResult TryEncode(const T & value, const uint8_t (&arr)[N]) + { + ByteSpan expected(arr); + MutableByteSpan out_span(mEmberAttributeDataBuffer); + Ember::EmberAttributeDataBuffer buffer(mMetaData, out_span); + + TLVEncodedValue tlvEncoded; + TLV::TLVReader reader = tlvEncoded.EncodeValue(value); + + CHIP_ERROR err = buffer.Decode(reader); + if (err != CHIP_NO_ERROR) + { + return err; + } + + if (expected.size() != out_span.size()) + { + ChipLogError(Test, "Decode mismatch in size: expected %u, got %u", static_cast(expected.size()), + static_cast(out_span.size())); + return CHIP_ERROR_INTERNAL; + } + + if (!expected.data_equal(out_span)) + { + ChipLogError(Test, "Decode mismatch in content for %u bytes", static_cast(expected.size())); + return CHIP_ERROR_INTERNAL; + } + + return EncodeResult::Ok(); + } + + template + EncodeResult TryDecode(const T & value, const uint8_t (&arr)[N]) + { + // Write data to TLV + { + uint8_t mutableBuffer[N]; + memcpy(mutableBuffer, arr, N); + + MutableByteSpan data_span(mutableBuffer); + Ember::EmberAttributeDataBuffer buffer(mMetaData, data_span); + + TLV::TLVWriter writer; + writer.Init(mEmberAttributeDataBuffer, sizeof(mEmberAttributeDataBuffer)); + ReturnErrorOnFailure(buffer.Encode(writer, TLV::AnonymousTag())); + ReturnErrorOnFailure(writer.Finalize()); + } + + // Data was written in TLV. Take it back out + + TLV::TLVReader reader; + reader.Init(mEmberAttributeDataBuffer, sizeof(mEmberAttributeDataBuffer)); + + ReturnErrorOnFailure(reader.Next()); + + T encodedValue; + ReturnErrorOnFailure(DataModel::Decode(reader, encodedValue)); + + if (!IsEqual(encodedValue, value)) + { + ChipLogError(Test, "Encode mismatch: different data"); + return CHIP_ERROR_INTERNAL; + } + + return EncodeResult::Ok(); + } + +private: + const EmberAfAttributeMetadata * mMetaData; + uint8_t mEmberAttributeDataBuffer[kMaxSize]; +}; + +const EmberAfAttributeMetadata * CreateFakeMeta(EmberAfAttributeType type, bool nullable) +{ + static EmberAfAttributeMetadata meta = { + .defaultValue = EmberAfDefaultOrMinMaxAttributeValue(static_cast(nullptr)), + .attributeId = 0, + .size = 0, // likely not valid, however not used for tests + .attributeType = ZCL_UNKNOWN_ATTRIBUTE_TYPE, + .mask = 0, + }; + + meta.attributeType = type; + meta.mask = nullable ? MATTER_ATTRIBUTE_FLAG_NULLABLE : 0; + + return &meta; +} + +} // namespace + +// All the tests below assume buffer ordering in little endian format +// Since currently all chip platforms in CI are little endian, we just kept tests +// as-is +static_assert(!CHIP_CONFIG_BIG_ENDIAN_TARGET); + +TEST(TestEmberAttributeBuffer, TestEncodeUnsignedTypes) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8U_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0xFD, { 0xFD }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(255, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8U_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0xFD, { 0xFD }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + + // Not allowed to encode null-equivalent + EXPECT_EQ(tester.TryEncode(0xFF, { 0xFF }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0xFD, { 0xFD, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(255, { 0xFF, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0xABCD, { 0xCD, 0xAB }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0xFFFF, { 0xFF, 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0xFD, { 0xFD, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(255, { 0xFF, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0xABCD, { 0xCD, 0xAB }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + + // Not allowed to encode null-equivalent + EXPECT_EQ(tester.TryEncode(0xFFFF, { 0xFF, 0xFF }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64U_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0, 0, 0, 0, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0x1234567, { 0x67, 0x45, 0x23, 0x01, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0xAABBCCDDEEFF1122, { 0x22, 0x11, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA }).IsSuccess()); + EXPECT_TRUE( + tester.TryEncode(std::numeric_limits::max() - 1, { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + + EXPECT_TRUE(tester + .TryEncode>(DataModel::NullNullable, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + + EXPECT_EQ( + tester.TryEncode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }), + CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64U_ATTRIBUTE_TYPE, false /* nullable */)); + + // we should be able to encode the maximum value + EXPECT_TRUE( + tester.TryEncode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + } + + /// Odd sized integers + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24U_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryEncode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0xFFFFFF, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + + // Out of range + EXPECT_EQ(tester.TryEncode(0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + EXPECT_EQ(tester.TryEncode(0xFF000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + + // Out of range + EXPECT_EQ(tester.TryEncode(0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + // cannot encode null equivalent value + EXPECT_EQ(tester.TryEncode(0xFFFFFF, { 0x56, 0x34, 0x12 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT40U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(0, { 0, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0x123456FFFF, { 0xFF, 0xFF, 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE( + tester.TryEncode>(DataModel::NullNullable, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + + // Out of range + EXPECT_EQ(tester.TryEncode(0x10011001100, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + // cannot encode null equivalent value + EXPECT_EQ(tester.TryEncode(0xFFFFFFFFFF, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + + // Double-check tests, not as exhaustive, to cover all other unsigned values and get + // more test line coverage + { + EncodeTester tester(CreateFakeMeta(ZCL_INT32U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(0x1234, { 0x34, 0x12, 0, 0 }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT48U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(0x1234, { 0x34, 0x12, 0, 0, 0, 0 }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT56U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(0x1234, { 0x34, 0x12, 0, 0, 0, 0, 0 }).IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestEncodeSignedTypes) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(127, { 127 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-10, { 0xF6 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-128, { 0x80 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(127, { 127 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-10, { 0xF6 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-127, { 0x81 }).IsSuccess()); + + // NULL canot be encoded + EXPECT_EQ(tester.TryEncode(std::numeric_limits::min(), { 0x80 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + { + + EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(127, { 127, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-10, { 0xF6, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-128, { 0x80, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-1234, { 0x2E, 0xFB }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(std::numeric_limits::min(), { 0x0, 0x80 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(127, { 127, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-10, { 0xF6, 0xFF }).IsSuccess()); + + // NULL canot be encoded + EXPECT_EQ(tester.TryEncode(std::numeric_limits::min(), { 0x80 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + + // Odd size integers + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess()); + + // Out of range + EXPECT_EQ(tester.TryEncode(0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + EXPECT_EQ(tester.TryEncode(0x0F000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + EXPECT_EQ(tester.TryEncode(-0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess()); + + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0x00, 0x00, 0x80 }).IsSuccess()); + + // Out of range + EXPECT_EQ(tester.TryEncode(0x1000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + // cannot encode null equivalent value - this is the minimum negative value + // for 24-bit + EXPECT_EQ(tester.TryEncode(-(1 << 24) - 1, { 0x56, 0x34, 0x12 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + + // Out of range for signed - these are unsigned values that are larger + EXPECT_EQ(tester.TryEncode(0xFFFFFF, { 0x56, 0x34, 0x12 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + EXPECT_EQ(tester.TryEncode(0x800000, { 0x56, 0x34, 0x12 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT40S_ATTRIBUTE_TYPE, true /* nullable */)); + + // NOTE: to generate encoded values, you an use commands like: + // + // python -c 'import struct; print(", ".join(["0x%X" % v for v in struct.pack("(0, { 0, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-123456789, { 0xeb, 0x32, 0xa4, 0xf8, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(-12345678910, { 0xc2, 0xe3, 0x23, 0x20, 0xfd }).IsSuccess()); + + EXPECT_TRUE( + tester.TryEncode>(DataModel::NullNullable, { 0x00, 0x00, 0x00, 0x00, 0x80 }).IsSuccess()); + + // Out of range + EXPECT_EQ(tester.TryEncode(0x10011001100, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + // cannot encode null equivalent value + EXPECT_EQ(tester.TryEncode(-(1LL << 40) - 1, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + // negative out of range + EXPECT_EQ(tester.TryEncode(-0x10000000000, { 0 }), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + + // Double-check tests, not as exhaustive, to cover all other unsigned values and get + // more test line coverage + { + EncodeTester tester(CreateFakeMeta(ZCL_INT32S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT48S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT56S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + + // min/max ranges too + EXPECT_TRUE( + tester.TryEncode(std::numeric_limits::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + EXPECT_TRUE( + tester.TryEncode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }) + .IsSuccess()); + + // Reserved value for NULL + EXPECT_EQ( + tester.TryEncode(std::numeric_limits::min(), { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }), + CHIP_IM_GLOBAL_STATUS(ConstraintError)); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryEncode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + + EXPECT_TRUE( + tester.TryEncode(std::numeric_limits::min(), { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + EXPECT_TRUE( + tester.TryEncode(std::numeric_limits::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + EXPECT_TRUE( + tester.TryEncode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }) + .IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestEncodeBool) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(true, { 1 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(false, { 0 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryEncode(true, { 1 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(false, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestEncodeFloatingPoint) +{ + // NOTE: to generate encoded values, you an use commands like: + // + // python -c 'import struct; print(", ".join(["0x%X" % v for v in struct.pack("(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_SINGLE_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0, 0, 0xC0, 0x7F }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryEncode(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess()); + EXPECT_TRUE( + tester.TryEncode>(DataModel::NullNullable, { 0, 0, 0, 0, 0, 0, 0xF8, 0x7F }).IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestEncodeStrings) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryEncode(""_span, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode("foo"_span, { 3, 'f', 'o', 'o' }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(""_span, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryEncode(""_span, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode("foo"_span, { 3, 0, 'f', 'o', 'o' }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } + + const uint8_t kOctetData[] = { 1, 2, 3 }; + + // Binary data + { + EncodeTester tester(CreateFakeMeta(ZCL_OCTET_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryEncode(ByteSpan({}), { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(ByteSpan({}), { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryEncode(ByteSpan({}), { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryEncode(ByteSpan({}), { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess()); + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestEncodeFailures) +{ + { + // attribute type that is not handled + EncodeTester tester(CreateFakeMeta(ZCL_UNKNOWN_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_EQ(tester.TryEncode>(DataModel::NullNullable, { 0 }), CHIP_IM_GLOBAL_STATUS(Failure)); + } + + { + // Insufficient space + EncodeTester<3> tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + + // Empty is ok + EXPECT_TRUE(tester.TryEncode(""_span, { 0 }).IsSuccess()); + + // Short strings (with and without count) is wrong. + EXPECT_EQ(tester.TryEncode("test"_span, { 0 }), CHIP_ERROR_NO_MEMORY); + EXPECT_EQ(tester.TryEncode("foo"_span, { 3, 'f', 'o' }), CHIP_ERROR_NO_MEMORY); + + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + // Insufficient space + EncodeTester<3> tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + + // Empty is ok + EXPECT_TRUE(tester.TryEncode(""_span, { 0, 0 }).IsSuccess()); + + // Short strings (with and without count) is wrong. + EXPECT_EQ(tester.TryEncode("test"_span, { 0 }), CHIP_ERROR_NO_MEMORY); + EXPECT_EQ(tester.TryEncode("foo"_span, { 0, 3, 'f', 'o' }), CHIP_ERROR_NO_MEMORY); + EXPECT_EQ(tester.TryEncode("test"_span, { 0xFF }), CHIP_ERROR_NO_MEMORY); + + EXPECT_TRUE(tester.TryEncode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } + + { + // Insufficient space even for length + EncodeTester<1> tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_EQ(tester.TryEncode(""_span, { 0 }), CHIP_ERROR_NO_MEMORY); + } + + // bad type casts + { + EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryEncode(ByteSpan({}), { 0 }), CHIP_ERROR_WRONG_TLV_TYPE); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT32U_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryEncode(true, { 0 }), CHIP_ERROR_WRONG_TLV_TYPE); + } +} + +TEST(TestEmberAttributeBuffer, TestNoData) +{ + EncodeTester tester(CreateFakeMeta(ZCL_NO_DATA_ATTRIBUTE_TYPE, true /* nullable */)); + + // support a always-null type + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0 }).IsSuccess()); +} + +TEST(TestEmberAttributeBuffer, TestDecodeFailures) +{ + { + // attribute type that is not handled + EncodeTester tester(CreateFakeMeta(ZCL_UNKNOWN_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_EQ(tester.TryDecode>(DataModel::NullNullable, { 0 }), CHIP_IM_GLOBAL_STATUS(Failure)); + } + + { + // Insufficient input + EncodeTester<3> tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_EQ(tester.TryDecode("test"_span, { 10 }), CHIP_ERROR_BUFFER_TOO_SMALL); + EXPECT_EQ(tester.TryDecode("foo"_span, { 3, 'f', 'o' }), CHIP_ERROR_BUFFER_TOO_SMALL); + } + + { + // Insufficient data buffer - should never happen, but test that we will error out + EncodeTester tester(CreateFakeMeta(ZCL_INT32U_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryDecode(123, { 1, 2, 3 }), CHIP_ERROR_BUFFER_TOO_SMALL); + } + + { + // Insufficient data buffer - should never happen, but test that we will error out + EncodeTester tester(CreateFakeMeta(ZCL_SINGLE_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryDecode(1.5f, { 1, 2, 3 }), CHIP_ERROR_BUFFER_TOO_SMALL); + } + + { + // Insufficient data buffer - should never happen, but test that we will error out + EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryDecode(1.5, { 1, 2, 3 }), CHIP_ERROR_BUFFER_TOO_SMALL); + } + + { + // Bad boolean data + EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryDecode(true, { 123 }), CHIP_ERROR_INVALID_ARGUMENT); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeSignedTypes) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(127, { 127 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-128, { 0x80 }).IsSuccess()); + + // longer data is ok + EXPECT_TRUE(tester.TryDecode(-128, { 0x80, 1, 2, 3, 4 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8S_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(127, { 127 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-127, { 0x81 }).IsSuccess()); + + // NULL can be decoded + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0x80 }).IsSuccess()); + + // decoding as nullable proceeds as normal + EXPECT_TRUE(tester.TryDecode>(-127, { 0x81 }).IsSuccess()); + } + + { + + EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(127, { 127, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-128, { 0x80, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(std::numeric_limits::min(), { 0x0, 0x80 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT16S_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(127, { 127, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6, 0xFF }).IsSuccess()); + + // NULL decoding + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0x00, 0x80 }).IsSuccess()); + } + + // Odd size integers + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24S_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-10, { 0xF6, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF }).IsSuccess()); + + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0x00, 0x00, 0x80 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT40S_ATTRIBUTE_TYPE, true /* nullable */)); + + // NOTE: to generate encoded values, you an use commands like: + // + // python -c 'import struct; print(", ".join(["0x%X" % v for v in struct.pack("(0, { 0, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-123456789, { 0xeb, 0x32, 0xa4, 0xf8, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(-12345678910, { 0xc2, 0xe3, 0x23, 0x20, 0xfd }).IsSuccess()); + + EXPECT_TRUE( + tester.TryDecode>(DataModel::NullNullable, { 0x00, 0x00, 0x00, 0x00, 0x80 }).IsSuccess()); + } + + // Double-check tests, not as exhaustive, to cover all other unsigned values and get + // more test line coverage + { + EncodeTester tester(CreateFakeMeta(ZCL_INT32S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT48S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT56S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + + // min/max ranges too + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }) + .IsSuccess()); + + EXPECT_TRUE(tester + .TryDecode>(DataModel::NullNullable, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64S_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(-1234, { 0x2E, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::min(), { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::min() + 1, { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 }) + .IsSuccess()); + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }) + .IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeUnsignedTypes) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8U_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFD, { 0xFD }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(255, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT8U_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFD, { 0xFD }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + + // NULL decoding should work + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFD, { 0xFD, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(255, { 0xFF, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xABCD, { 0xCD, 0xAB }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFFFF, { 0xFF, 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT16U_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(123, { 123, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFD, { 0xFD, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(255, { 0xFF, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xABCD, { 0xCD, 0xAB }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + + // NULL SUPPORT + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64U_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0, 0, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x1234567, { 0x67, 0x45, 0x23, 0x01, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xAABBCCDDEEFF1122, { 0x22, 0x11, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA }).IsSuccess()); + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::max() - 1, { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + + EXPECT_TRUE(tester + .TryDecode>(DataModel::NullNullable, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + + EXPECT_TRUE(tester + .TryDecode>(DataModel::NullNullable, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT64U_ATTRIBUTE_TYPE, false /* nullable */)); + + // we should be able to encode the maximum value + EXPECT_TRUE( + tester.TryDecode(std::numeric_limits::max(), { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }) + .IsSuccess()); + } + + /// Odd sized integers + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24U_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0xFFFFFF, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT24U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF, 0xFF }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(0x1234, { 0x34, 0x12, 0x00 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_INT40U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0, { 0, 0, 0, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456, { 0x56, 0x34, 0x12, 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(0x123456FFFF, { 0xFF, 0xFF, 0x56, 0x34, 0x12 }).IsSuccess()); + EXPECT_TRUE( + tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }).IsSuccess()); + } + + // Double-check tests, not as exhaustive, to cover all other unsigned values and get + // more test line coverage + { + EncodeTester tester(CreateFakeMeta(ZCL_INT32U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0x1234, { 0x34, 0x12, 0, 0 }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT48U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0x1234, { 0x34, 0x12, 0, 0, 0, 0 }).IsSuccess()); + } + { + EncodeTester tester(CreateFakeMeta(ZCL_INT56U_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(0x1234, { 0x34, 0x12, 0, 0, 0, 0, 0 }).IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeStrings) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(""_span, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("foo"_span, { 3, 'f', 'o', 'o' }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(""_span, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("test"_span, { 4, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(""_span, { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode("foo"_span, { 3, 0, 'f', 'o', 'o' }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode("test"_span, { 4, 0, 't', 'e', 's', 't' }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } + + const uint8_t kOctetData[] = { 1, 2, 3 }; + + // Binary data + { + EncodeTester tester(CreateFakeMeta(ZCL_OCTET_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(ByteSpan({}), { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(ByteSpan({}), { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(ByteSpan(kOctetData), { 3, 1, 2, 3 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(ByteSpan({}), { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(ByteSpan({}), { 0, 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(ByteSpan(kOctetData), { 3, 0, 1, 2, 3 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF, 0xFF }).IsSuccess()); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeBool) +{ + { + EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, false /* nullable */)); + + EXPECT_TRUE(tester.TryDecode(true, { 1 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode(false, { 0 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, true /* nullable */)); + + EXPECT_TRUE(tester.TryDecode>(true, { 1 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(false, { 0 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0xFF }).IsSuccess()); + } + + { + // Boolean that is NOT nullable + EncodeTester tester(CreateFakeMeta(ZCL_BOOLEAN_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_EQ(tester.TryDecode>(DataModel::NullNullable, { 0xFF }), CHIP_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(tester.TryDecode(true, { 0xFF }), CHIP_ERROR_INVALID_ARGUMENT); + } +} + +TEST(TestEmberAttributeBuffer, TestDecodeFloatingPoint) +{ + // NOTE: to generate encoded values, you an use commands like: + // + // python -c 'import struct; print(", ".join(["0x%X" % v for v in struct.pack("(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_SINGLE_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(123.55f, { 0x9A, 0x19, 0xF7, 0x42 }).IsSuccess()); + EXPECT_TRUE(tester.TryDecode>(DataModel::NullNullable, { 0, 0, 0xC0, 0x7F }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_SINGLE_ATTRIBUTE_TYPE, false /* nullable */)); + // non-nullable float + EXPECT_TRUE(tester.TryDecode(std::nanf("0"), { 0, 0, 0xC0, 0x7F }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, false /* nullable */)); + EXPECT_TRUE(tester.TryDecode(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, true /* nullable */)); + EXPECT_TRUE(tester.TryDecode(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess()); + EXPECT_TRUE( + tester.TryDecode>(123.55, { 0x33, 0x33, 0x33, 0x33, 0x33, 0xE3, 0x5E, 0x40 }).IsSuccess()); + EXPECT_TRUE( + tester.TryDecode>(DataModel::NullNullable, { 0, 0, 0, 0, 0, 0, 0xF8, 0x7F }).IsSuccess()); + } + + { + EncodeTester tester(CreateFakeMeta(ZCL_DOUBLE_ATTRIBUTE_TYPE, false /* nullable */)); + // non-nullable double + EXPECT_TRUE(tester.TryDecode(std::nan("0"), { 0, 0, 0, 0, 0, 0, 0xF8, 0x7F }).IsSuccess()); + } +}