From 5344208195546ae9abe1339757c2f3c13180fec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boris=20Labb=C3=A9?= Date: Tue, 9 Apr 2024 16:03:49 +0200 Subject: [PATCH] GH-43: Base ZPC implementation Forwarded: https://github.com/SiliconLabs/UnifySDK/pull/43 Bug-SiliconLabs: UIC-3222 Bug-Github: https://github.com/SiliconLabs/UnifySDK/pull/43 --- .../attribute_store_defined_attribute_types.h | 92 + ...wave_command_class_user_credential_types.h | 83 + .../zpc_attribute_store_type_registration.cpp | 52 + .../zwave_command_classes/CMakeLists.txt | 1 + .../zwave_command_class_user_credential.cpp | 1679 ++++++++++++++ .../src/zwave_command_class_user_credential.h | 40 + .../src/zwave_command_classes_fixt.c | 2 + .../zwave_command_classes/test/CMakeLists.txt | 18 +- ...ave_command_class_user_credential_test.cpp | 2027 +++++++++++++++++ 9 files changed, 3993 insertions(+), 1 deletion(-) create mode 100644 applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_user_credential_types.h create mode 100644 applications/zpc/components/zwave_command_classes/src/zwave_command_class_user_credential.cpp create mode 100644 applications/zpc/components/zwave_command_classes/src/zwave_command_class_user_credential.h create mode 100644 applications/zpc/components/zwave_command_classes/test/zwave_command_class_user_credential_test.cpp diff --git a/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h b/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h index 4e660ac30..4678ff399 100644 --- a/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h +++ b/applications/zpc/components/zpc_attribute_store/include/attribute_store_defined_attribute_types.h @@ -1062,6 +1062,98 @@ DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_SOUND_SWITCH_TONE_INFO_NAME, DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_SOUND_SWITCH_TONE_PLAY, ((COMMAND_CLASS_SOUND_SWITCH << 8) | 0x08)) + +///////////////////////////////////////////////// +// User Credential Command Class +///< This represents the version of the User CredentialCommand class. +/// zwave_cc_version_t +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_VERSION, + ZWAVE_CC_VERSION_ATTRIBUTE(COMMAND_CLASS_USER_CREDENTIAL)) + + +/// > User Capabilities +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_NUMBER_OF_USERS, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x02)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_RULES, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x03)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_MAX_USERNAME_LENGTH, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x04)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORT_USER_SCHEDULE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x05)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORT_ALL_USERS_CHECKSUM, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x06)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORT_USER_CHECKSUM, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x07)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_USER_TYPES, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x08)) + +/// > Credential Capabilities +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORT_CREDENTIAL_CHECKSUM, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x09)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_TYPE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x0A)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_LEARN_SUPPORT, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x0B)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_SUPPORTED_SLOT_COUNT, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x0C)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_MIN_LENGTH, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x0D)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_MAX_LENGTH, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x0E)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_LEARN_RECOMMENDED_TIMEOUT, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x0F)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_LEARN_NUMBER_OF_STEPS, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x10)) + +/// > All Users Checksum +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_ALL_USERS_CHECKSUM, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x11)) + +/// > Users +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x12)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_MODIFIER_TYPE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x13)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_MODIFIER_NODE_ID, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x14)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_TYPE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x15)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_ACTIVE_STATE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x16)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_NAME, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x17)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_NAME_ENCODING, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x18)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_RULE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x19)) + +/// > Credentials +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_TYPE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x1A)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_DATA_LENGTH, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x1B)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_DATA, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x1C)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_SLOT, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x1D)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_READ_BACK, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x1E)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_MODIFIER_TYPE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x1F)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_MODIFIER_NODE_ID, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x20)) + + +// Specific to Credential SET +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_OPERATION_TYPE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x21)) + +// Specific to USER SET +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_OPERATION_TYPE, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x22)) +DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_EXPIRING_TIMEOUT_MINUTES, + ((COMMAND_CLASS_USER_CREDENTIAL << 8) | 0x23)) + ///////////////////////////////////////////////// // Z-Wave Plus Info CC DEFINE_ATTRIBUTE(ATTRIBUTE_COMMAND_ZWAVEPLUS_INFO_VERSION, diff --git a/applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_user_credential_types.h b/applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_user_credential_types.h new file mode 100644 index 000000000..c7b14cbe5 --- /dev/null +++ b/applications/zpc/components/zpc_attribute_store/include/command_class_types/zwave_command_class_user_credential_types.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * # License + * Copyright 2023 Silicon Laboratories Inc. www.silabs.com + ****************************************************************************** + * The licensor of this software is Silicon Laboratories Inc. Your use of this + * software is governed by the terms of Silicon Labs Master Software License + * Agreement (MSLA) available at + * www.silabs.com/about-us/legal/master-software-license-agreement. This + * software is distributed to you in Source Code format and is governed by the + * sections of the MSLA applicable to Source Code. + * + *****************************************************************************/ + +/** + * @defgroup zwave_command_class_user_credential_types Type definitions for attribute storage of the Sound Switch Command Class + * @ingroup zpc_attribute_store_command_classes_types + * @brief Type definitions for the Sound Switch Command Class. + * + * @{ + */ + +#ifndef ZWAVE_COMMAND_CLASS_USER_CREDENTIALS_TYPES_H +#define ZWAVE_COMMAND_CLASS_USER_CREDENTIALS_TYPES_H + +#include + +///> Supported credential rules bitmask. uint8_t +typedef uint8_t user_credential_supported_credential_rules_t; + +///> Supported user type bitmask. uint32_t +typedef uint32_t user_credential_supported_user_type_bitmask_t; + +///> All user checksum. uint16_t +typedef uint16_t user_credential_all_users_checksum_t; + +///> User Unique ID. uint16_t +typedef uint16_t user_credential_user_unique_id_t; +///> User Modifier Type. uint8_t +typedef uint8_t user_credential_user_modifier_type_t; +///> User Modifier Node ID. uint16_t +typedef uint16_t user_credential_user_modifier_node_id_t; +///> User Type. uint8_t +#define USER_CREDENTIAL_USER_TYPE_GENERAL_USER 0x00 +#define USER_CREDENTIAL_USER_TYPE_PROGRAMMING_USER 0x03 +#define USER_CREDENTIAL_USER_TYPE_NON_ACCESS_USER 0x04 +#define USER_CREDENTIAL_USER_TYPE_DURESS_USER 0x05 +#define USER_CREDENTIAL_USER_TYPE_DISPOSABLE_USER 0x06 +#define USER_CREDENTIAL_USER_TYPE_EXPIRING_USER 0x07 +#define USER_CREDENTIAL_USER_TYPE_REMOTE_ONLY_USER 0x09 +typedef uint8_t user_credential_user_type_t; +///> User active state (0 or 1). uint8_t +typedef uint8_t user_credential_user_active_state_t; +///> User name encoding. uint8_t +typedef uint8_t user_credential_user_name_encoding_t; + +///> Credential type. uint8_t +typedef uint8_t user_credential_type_t; + +///> Credential rule. uint8_t +typedef uint8_t user_credential_rule_t; + +///> Credential slot. uint16_t +typedef uint16_t user_credential_slot_t; + +///> Operation type. uint8_t +#define USER_CREDENTIAL_OPERATION_TYPE_ADD 0x00 +#define USER_CREDENTIAL_OPERATION_TYPE_MODIFY 0x01 +#define USER_CREDENTIAL_OPERATION_TYPE_DELETE 0x02 +typedef uint8_t user_credential_operation_type_t; + +///> Expiring Timeout for User (Minutes). uint16_t +typedef uint16_t user_credential_expiring_timeout_minutes_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif //ZWAVE_COMMAND_CLASS_USER_CREDENTIALS_TYPES_H +/** @} end zwave_command_class_user_credentials_types */ diff --git a/applications/zpc/components/zpc_attribute_store/src/zpc_attribute_store_type_registration.cpp b/applications/zpc/components/zpc_attribute_store/src/zpc_attribute_store_type_registration.cpp index 26d3e8cf1..2eb9f9526 100644 --- a/applications/zpc/components/zpc_attribute_store/src/zpc_attribute_store_type_registration.cpp +++ b/applications/zpc/components/zpc_attribute_store/src/zpc_attribute_store_type_registration.cpp @@ -431,6 +431,58 @@ static const std::vector attribute_schema = { {ATTRIBUTE_COMMAND_CLASS_USER_CODE_SUPPORTED_KEYS, "Supported Keys bitmask", ATTRIBUTE_COMMAND_CLASS_USER_CODE_CAPABILITIES, BYTE_ARRAY_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_USER_CODE_CHECKSUM, "User Code Checksum", ATTRIBUTE_COMMAND_CLASS_USER_CODE_DATA, U16_STORAGE_TYPE}, {ATTRIBUTE_COMMAND_CLASS_USER_CODE_KEYPAD_MODE, "Keypad Mode", ATTRIBUTE_COMMAND_CLASS_USER_CODE_DATA, U8_STORAGE_TYPE}, + + + ///////////////////////////////////////////////////////////////////// + // User Credential Command Class attributes + ///////////////////////////////////////////////////////////////////// + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_VERSION, "User Credential Version", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + + // User capabilities + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_NUMBER_OF_USERS, "User Credential User Count", ATTRIBUTE_ENDPOINT_ID, U16_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_RULES, "User Credential Supported Rules", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_MAX_USERNAME_LENGTH, "User Credential Max Username Length", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORT_USER_SCHEDULE, "User Credential Support User Schedule", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORT_ALL_USERS_CHECKSUM, "User Credential Support All User Checksum", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORT_USER_CHECKSUM, "User Credential Support By User Checksum", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_USER_TYPES, "User Credential Supported User types", ATTRIBUTE_ENDPOINT_ID, U32_STORAGE_TYPE}, + + // Credential capabilities + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORT_CREDENTIAL_CHECKSUM, "User Credential Support Credential Checksum", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_TYPE, "User Credential Supported Credential Type", ATTRIBUTE_ENDPOINT_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_LEARN_SUPPORT, "User Credential Learn Support", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_TYPE, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_SUPPORTED_SLOT_COUNT, "User Credential Supported Slot Count", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_TYPE, U16_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_MIN_LENGTH, "User Credential Min Length", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_TYPE, U16_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_MAX_LENGTH, "User Credential Max Length", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_TYPE, U16_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_LEARN_RECOMMENDED_TIMEOUT, "User Credential Learn Recommended Timeout", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_TYPE, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_LEARN_NUMBER_OF_STEPS, "User Credential Learn Number Of Steps", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_SUPPORTED_CREDENTIAL_TYPE, U8_STORAGE_TYPE}, + + + // All Users Checksum + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_ALL_USERS_CHECKSUM, "User Credential All User Checksum", ATTRIBUTE_ENDPOINT_ID, U16_STORAGE_TYPE}, + + // User + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, "User Credential User Unique Identifier", ATTRIBUTE_ENDPOINT_ID, U16_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_MODIFIER_TYPE, "User Credential User Modifier Type", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_MODIFIER_NODE_ID, "User Credential User Modifier Node ID", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, U16_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_TYPE, "User Credential User Type", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_ACTIVE_STATE, "User Credential User Active State", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_NAME, "User Credential User Name", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, C_STRING_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_NAME_ENCODING, "User Credential User Name Encoding", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_RULE, "User Credential User Credential Rule", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_OPERATION_TYPE, "User Credential User Operation type", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_EXPIRING_TIMEOUT_MINUTES, "User Credential User Expiring Timeout Minutes", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, U16_STORAGE_TYPE}, + + // Credential + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_TYPE, "User Credential Credential Type", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_USER_UNIQUE_ID, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_SLOT, "User Credential Credential Slot", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_TYPE, U16_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_DATA_LENGTH, "User Credential Credential Data Length", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_SLOT, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_DATA, "User Credential Credential Data", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_DATA_LENGTH, BYTE_ARRAY_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_READ_BACK, "User Credential Credential Read Back Flag", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_SLOT, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_MODIFIER_TYPE, "User Credential Credential Modifier type", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_SLOT, U8_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_MODIFIER_NODE_ID, "User Credential Credential Node ID", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_SLOT, U16_STORAGE_TYPE}, + {ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_OPERATION_TYPE, "User Credential Credential Operation Type", ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_CREDENTIAL_SLOT, U8_STORAGE_TYPE}, + ///////////////////////////////////////////////////////////////////// // Z-Wave Plus Info Command Class attributes ///////////////////////////////////////////////////////////////////// diff --git a/applications/zpc/components/zwave_command_classes/CMakeLists.txt b/applications/zpc/components/zwave_command_classes/CMakeLists.txt index 5594af72e..f42cc6c1b 100644 --- a/applications/zpc/components/zwave_command_classes/CMakeLists.txt +++ b/applications/zpc/components/zwave_command_classes/CMakeLists.txt @@ -54,6 +54,7 @@ add_library( src/zwave_command_class_thermostat_operating_state.c src/zwave_command_class_time.c src/zwave_command_class_user_code.c + src/zwave_command_class_user_credential.cpp src/zwave_command_class_version.c src/zwave_command_class_wake_up.c src/zwave_command_class_zwave_plus_info.c diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_user_credential.cpp b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_user_credential.cpp new file mode 100644 index 000000000..5a3810ae0 --- /dev/null +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_user_credential.cpp @@ -0,0 +1,1679 @@ + +/****************************************************************************** + * # License + * Copyright 2024 Silicon Laboratories Inc. www.silabs.com + ****************************************************************************** + * The licensor of this software is Silicon Laboratories Inc. Your use of this + * software is governed by the terms of Silicon Labs Master Software License + * Agreement (MSLA) available at + * www.silabs.com/about-us/legal/master-software-license-agreement. This + * software is distributed to you in Source Code format and is governed by the + * sections of the MSLA applicable to Source Code. + * + *****************************************************************************/ + +// System +#include + +#include "zwave_command_class_user_credential.h" +#include "zwave_command_class_user_credential_types.h" +#include "zwave_command_classes_utils.h" +#include "ZW_classcmd.h" + +// Includes from other ZPC Components +#include "zwave_command_class_indices.h" +#include "zwave_command_handler.h" +#include "zwave_command_class_version_types.h" +#include "attribute_store_defined_attribute_types.h" +#include "zpc_attribute_store.h" + +// Unify +#include "attribute_resolver.h" +#include "attribute_store.h" +#include "attribute_store_helper.h" +#include "attribute_store_type_registration.h" +#include "sl_log.h" + +// DotDot +#include "unify_dotdot_attribute_store_node_state.h" + +// Cpp related +#include +#include + +// Macro +#define LOG_TAG "zwave_command_class_user_credential" +#define ATTRIBUTE(type) ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_##type +// Used to get user names +// If the user name has a size > to this number, we will truncate it +// Specification says that payload data should not exceeded 64 bytes. +#define MAX_CHAR_SIZE 64 + +/** + * @brief Implementation notes + * + * 1. Perform mandatory interview with User and Credentials Capabilities + * 2. Once interview is finished we retrieve all Users/Credentials (see zwave_network_status_changed) + * > To get all Users and Credential we create a new node with a desired value. + * > The lack of reported value will trigger a GET + * > GET will set the reported value so we can get a report for this USER/CREDENTIAL + * + */ + +///////////////////////////////////////////////////////////////////////////// +// Data struct +///////////////////////////////////////////////////////////////////////////// + +struct uint16_exploded { + uint8_t msb; // Most Significant Bit + uint8_t lsb; // Less Significant Bit +}; + +// Used to define reported values +struct user_field_data { + attribute_store_type_t node_type; + uint8_t report_index; + uint8_t bitmask = 0; + uint8_t shift_right = 0; +}; + +// Used to create command frame +struct attribute_command_data { + // Attribute type that will be fetched from the base_node + attribute_store_type_t attribute_type; + // Attribute value state (reported, desired,...) + attribute_store_node_value_state_t attribute_state; + // If not ATTRIBUTE_STORE_INVALID_NODE, the function will not fetch attribute_type + // but will use this node directly + attribute_store_node_t node = ATTRIBUTE_STORE_INVALID_NODE; +}; + +///////////////////////////////////////////////////////////////////////////// +// Type Helpers +///////////////////////////////////////////////////////////////////////////// +uint16_t get_uint16_value(const uint8_t *frame, uint16_t start_index) +{ + uint16_t extracted_value = 0; + for (int i = 0; i < 2; i++) { + extracted_value = (extracted_value << 8) | frame[start_index + i]; + } + + return extracted_value; +} + +// Transform a uint16_t into 2 uint8_t +uint16_exploded explode_uint16(uint16_t value) +{ + uint8_t msb = (value & 0xFF00) >> 8; + uint8_t lsb = (value & 0x00FF); + return uint16_exploded {msb, lsb}; +} + +std::string get_string_value(const uint8_t *frame_data, + uint16_t start_index, + uint8_t str_size) +{ + std::string value; + // Check if our name fits our buffer ; if not it is truncated + if (str_size > MAX_CHAR_SIZE) { + sl_log_warning(LOG_TAG, "Invalid char size"); + str_size = MAX_CHAR_SIZE; + } + + for (int i = 0; i < str_size; i++) { + value += frame_data[start_index + i]; + } + + return value; +} + +///////////////////////////////////////////////////////////////////////////// +// Command Class Helper +///////////////////////////////////////////////////////////////////////////// + +/** + * @brief Get user id node + * + * @warning state can't be DESIRED_OR_REPORTED_ATTRIBUTE or it will not work + * + * @param endpoint_node Endpoint point node + * @param user_id User ID to find + * @param state Check reported or desired value. + * @param user_id_node User id node will be stored here if found + * + * @return true User id exists + * @return false User id doesn't exists + */ +bool get_user_id_node(attribute_store_node_t endpoint_node, + user_credential_user_unique_id_t user_id, + attribute_store_node_value_state_t state, + attribute_store_node_t &user_id_node) +{ + user_id_node + = attribute_store_get_node_child_by_value(endpoint_node, + ATTRIBUTE(USER_UNIQUE_ID), + state, + (uint8_t *)&user_id, + sizeof(user_id), + 0); + + return attribute_store_node_exists(user_id_node); +} + +/** + * @brief Get node associated with user ID (reported) + * + * @warning This function only checks the reported User Unique + * + * @param endpoint_node Current endpoint node + * @param user_id User ID + * + * @return attribute_store_node_t If User ID exists + * @return INVALID_ATTRIBUTE_STORE_NODE If User ID does not exist + */ +attribute_store_node_t + get_reported_user_id_node(attribute_store_node_t endpoint_node, + user_credential_user_unique_id_t user_id) +{ + attribute_store_node_t user_id_node; + get_user_id_node(endpoint_node, user_id, REPORTED_ATTRIBUTE, user_id_node); + return user_id_node; +} + +/** + * @brief Get node associated with user ID (desired) + * + * @warning This function only checks the reported User Unique + * + * @param endpoint_node Current endpoint node + * @param user_id User ID + * + * @return attribute_store_node_t If User ID exists + * @return INVALID_ATTRIBUTE_STORE_NODE If User ID does not exist + */ +attribute_store_node_t + get_desired_user_id_node(attribute_store_node_t endpoint_node, + user_credential_user_unique_id_t user_id) +{ + attribute_store_node_t user_id_node; + get_user_id_node(endpoint_node, user_id, DESIRED_ATTRIBUTE, user_id_node); + return user_id_node; +} + +/** + * @brief Get credential node associated with credential_type and user_id. + * + * @param endpoint_node Current endpoint node + * @param user_id User ID + * @param credential_type Credential type + * @param state Check reported or desired value. + * @param credential_type_node Credential node will be stored here if found + * + * @return true Credential Type exists + * @return false Credential Type doesn't exists + */ +bool get_credential_type_node(attribute_store_node_t endpoint_node, + user_credential_user_unique_id_t user_id, + user_credential_type_t credential_type, + attribute_store_node_value_state_t state, + attribute_store_node_t &credential_type_node) +{ + attribute_store_node_t user_id_node + = get_reported_user_id_node(endpoint_node, user_id); + + credential_type_node + = attribute_store_get_node_child_by_value(user_id_node, + ATTRIBUTE(CREDENTIAL_TYPE), + state, + (uint8_t *)&credential_type, + sizeof(credential_type), + 0); + + return attribute_store_node_exists(credential_type_node); +} + +/** + * @brief Get credential slot node + * + * @warning state can't be DESIRED_OR_REPORTED_ATTRIBUTE or it will not work + * + * @param credential_type_node Endpoint point node + * @param credential_slot Credential Slot to find + * @param state Check reported or desired value. + * @param credential_slot_node Credential Slot node will be stored here if found + * + * @return true Credential Slot exists + * @return false Credential Slot doesn't exists + */ +bool get_credential_slot_node(attribute_store_node_t credential_type_node, + user_credential_slot_t credential_slot, + attribute_store_node_value_state_t state, + attribute_store_node_t &credential_slot_node) +{ + credential_slot_node + = attribute_store_get_node_child_by_value(credential_type_node, + ATTRIBUTE(CREDENTIAL_SLOT), + state, + (uint8_t *)&credential_slot, + sizeof(credential_slot), + 0); + + return attribute_store_node_exists(credential_slot_node); +} + +///////////////////////////////////////////////////////////////////////////// +// Attributes helpers +///////////////////////////////////////////////////////////////////////////// + +/** @brief Set reported attributes based on user_data + * + * @note This function also undefine all desired values + * + * @param base_node Parent node of the newly created attributes + * @param frame_data Frame data to interpret + * @param user_data User data to interpret frame_data + * + * @return sl_status_t SL_STATUS_OK if everything was fine +*/ +sl_status_t + set_reported_attributes(attribute_store_node_t base_node, + const uint8_t *frame_data, + const std::vector &user_data) +{ + sl_status_t status = SL_STATUS_OK; + + for (const auto &field: user_data) { + attribute_store_storage_type_t storage_type + = attribute_store_get_storage_type(field.node_type); + + switch (storage_type) { + case U8_STORAGE_TYPE: { + uint8_t uint8_value = frame_data[field.report_index]; + if (field.bitmask != 0) { + uint8_value = (uint8_value & field.bitmask) >> field.shift_right; + } + status |= attribute_store_set_child_reported(base_node, + field.node_type, + &uint8_value, + sizeof(uint8_value)); + } break; + // Unsigned 16-bit integers are used for this attribute + case U16_STORAGE_TYPE: { + uint16_t uint16_value + = get_uint16_value(frame_data, field.report_index); + status |= attribute_store_set_child_reported(base_node, + field.node_type, + &uint16_value, + sizeof(uint16_value)); + + break; + } + default: + sl_log_error(LOG_TAG, + "Internal error : unsupported storage_type in " + "set_reported_attributes"); + return SL_STATUS_NOT_SUPPORTED; + } + + // Undefined desired value + status + |= attribute_store_set_child_desired(base_node, field.node_type, NULL, 0); + } + + return status; +} + +/** + * @brief Create a command frame (SET or GET) based on the attribute store + * + * @param command Command to send (will be in frame[1], e.g USER_SET) + * @param command_data Attributes that will be in the frame (in order of appearance in the frame) + * @param base_node If not specified otherwise will fetch the attributes that are under this node + * @param frame Frame object from the callback + * @param frame_length Frame size from the callback + * + * @return sl_status_t SL_STATUS_OK if everything was fine + */ +sl_status_t + create_command_frame(uint8_t command, + std::vector command_data, + attribute_store_node_t base_node, + uint8_t *frame, + uint16_t *frame_length) +{ + frame[0] = COMMAND_CLASS_USER_CREDENTIAL; + frame[1] = command; + + uint16_t current_index = 2; + + for (auto &attribute_info: command_data) { + auto node_storage_type + = attribute_store_get_storage_type(attribute_info.attribute_type); + auto attribute_description + = attribute_store_get_type_name(attribute_info.attribute_type); + + attribute_store_node_t node; + if (attribute_info.node == ATTRIBUTE_STORE_INVALID_NODE) { + node = attribute_store_get_first_child_by_type( + base_node, + attribute_info.attribute_type); + } else { + node = attribute_info.node; + } + + if (node == ATTRIBUTE_STORE_INVALID_NODE) { + sl_log_critical(LOG_TAG, + "Can't find node for Attribute %s", + attribute_description); + return SL_STATUS_FAIL; + } + + sl_status_t status; + switch (node_storage_type) { + case U8_STORAGE_TYPE: { + uint8_t uint8_value; + status = attribute_store_read_value(node, + attribute_info.attribute_state, + &uint8_value, + sizeof(uint8_value)); + frame[current_index++] = uint8_value; + } break; + case U16_STORAGE_TYPE: { + uint16_t uint16_value; + status = attribute_store_read_value(node, + attribute_info.attribute_state, + &uint16_value, + sizeof(uint16_value)); + auto exploded_uint16 = explode_uint16(uint16_value); + frame[current_index++] = exploded_uint16.msb; + frame[current_index++] = exploded_uint16.lsb; + } break; + // Variable length field + case BYTE_ARRAY_STORAGE_TYPE: { + // First get the length + auto credential_length_node = attribute_store_get_node_parent(node); + + uint8_t credential_data_length = 0; + status = attribute_store_read_value(credential_length_node, + attribute_info.attribute_state, + &credential_data_length, + sizeof(credential_data_length)); + + if (status != SL_STATUS_OK) { + sl_log_error( + LOG_TAG, + "Missing BYTE_ARRAY_STORAGE_TYPE length for attribute %s", + attribute_description); + return SL_STATUS_NOT_SUPPORTED; + } + + frame[current_index++] = credential_data_length; + + // Then the data + std::vector credential_data; + credential_data.resize(credential_data_length); + status = attribute_store_read_value(node, + attribute_info.attribute_state, + credential_data.data(), + credential_data_length); + + for (const uint8_t &cred: credential_data) { + frame[current_index++] = cred; + } + + } break; + + case C_STRING_STORAGE_TYPE: { + char c_user_name[MAX_CHAR_SIZE]; + // Unfortunately attribute_store_get_string is not exposed so we need to do this + switch (attribute_info.attribute_state) { + case DESIRED_OR_REPORTED_ATTRIBUTE: + status + = attribute_store_get_desired_else_reported_string(node, + c_user_name, + MAX_CHAR_SIZE); + break; + case DESIRED_ATTRIBUTE: + status = attribute_store_get_desired_string(node, + c_user_name, + MAX_CHAR_SIZE); + break; + case REPORTED_ATTRIBUTE: + status = attribute_store_get_reported_string(node, + c_user_name, + MAX_CHAR_SIZE); + break; + } + + std::string user_name = c_user_name; + frame[current_index++] = user_name.length(); + for (const char &c: user_name) { + frame[current_index++] = c; + } + + } break; + default: + sl_log_critical(LOG_TAG, + "Not supported type for %s", + attribute_description); + return SL_STATUS_FAIL; + } + + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, + "Can't get value of Attribute %s", + attribute_description); + return SL_STATUS_NOT_SUPPORTED; + } + } + + *frame_length = current_index; + + return SL_STATUS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Version & Attribute Creation +///////////////////////////////////////////////////////////////////////////// +static void zwave_command_class_user_credential_on_version_attribute_update( + attribute_store_node_t updated_node, attribute_store_change_t change) +{ + if (change == ATTRIBUTE_DELETED) { + return; + } + + zwave_cc_version_t version = 0; + attribute_store_get_reported(updated_node, &version, sizeof(version)); + + if (version == 0) { + return; + } + + sl_log_debug(LOG_TAG, "User Credential version %d", version); + + attribute_store_node_t endpoint_node + = attribute_store_get_first_parent_with_type(updated_node, + ATTRIBUTE_ENDPOINT_ID); + + // The order of the attribute matter since it defines the order of the + // Z-Wave get command order. + const attribute_store_type_t attributes[] = { + ATTRIBUTE(NUMBER_OF_USERS), + ATTRIBUTE(SUPPORT_CREDENTIAL_CHECKSUM), + }; + + attribute_store_add_if_missing(endpoint_node, + attributes, + COUNT_OF(attributes)); +} + +///////////////////////////////////////////////////////////////////////////// +// User Credential User Capabilities Get/Report +///////////////////////////////////////////////////////////////////////////// +static sl_status_t zwave_command_class_user_credential_user_capabilities_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + sl_log_debug(LOG_TAG, "User Capabilities Get"); + + (void)node; // unused. + ZW_USER_CAPABILITIES_GET_FRAME *get_frame + = (ZW_USER_CAPABILITIES_GET_FRAME *)frame; + get_frame->cmdClass = COMMAND_CLASS_USER_CREDENTIAL; + get_frame->cmd = USER_CAPABILITIES_GET; + *frame_length = sizeof(ZW_USER_CAPABILITIES_GET_FRAME); + return SL_STATUS_OK; +} + +sl_status_t zwave_command_class_user_credential_user_capabilities_handle_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + constexpr uint8_t INDEX_USER_COUNT = 2; + constexpr uint8_t INDEX_CREDENTIAL_RULES = 4; + constexpr uint8_t INDEX_MAX_LENGTH = 5; + constexpr uint8_t INDEX_SUPPORT_BITS = 6; + constexpr uint8_t INDEX_BITMASK_LENGTH = 7; + + if (frame_length < INDEX_BITMASK_LENGTH) { + return SL_STATUS_NOT_SUPPORTED; + } + + sl_log_debug(LOG_TAG, "User Capabilities Report"); + + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + uint16_t user_count = get_uint16_value(frame_data, INDEX_USER_COUNT); + + attribute_store_set_child_reported(endpoint_node, + ATTRIBUTE(NUMBER_OF_USERS), + &user_count, + sizeof(user_count)); + + user_credential_supported_credential_rules_t credential_rules + = frame_data[INDEX_CREDENTIAL_RULES]; + attribute_store_set_child_reported(endpoint_node, + ATTRIBUTE(SUPPORTED_CREDENTIAL_RULES), + &credential_rules, + sizeof(credential_rules)); + + uint8_t max_length = frame_data[INDEX_MAX_LENGTH]; + attribute_store_set_child_reported(endpoint_node, + ATTRIBUTE(MAX_USERNAME_LENGTH), + &max_length, + sizeof(max_length)); + + // Support bits + uint8_t support_bits = frame_data[INDEX_SUPPORT_BITS]; + // Create a lambda function that do what we want for all of the 3 parameters + auto emplace_bool_value = [&support_bits, + &endpoint_node](uint8_t bitmask, + attribute_store_type_t type) { + uint8_t bool_value = (support_bits & bitmask) > 0; + + attribute_store_set_child_reported(endpoint_node, + type, + &bool_value, + sizeof(bool_value)); + + // CL:0083.XX.XX.XX.X : If the All Users Checksum Support field is set in the User Capabilities Report Command, the controlling + // node SHOULD send an All Users Checksum Get Command to check if there are existing Users or + // Credentials present on the supporting node. + if (type == ATTRIBUTE(SUPPORT_ALL_USERS_CHECKSUM) && bool_value) { + sl_log_debug(LOG_TAG, + "SUPPORT_ALL_USERS_CHECKSUM is set, sending All Users " + "Checksum Get Command"); + const attribute_store_type_t attributes[] = { + ATTRIBUTE(ALL_USERS_CHECKSUM), + }; + // Trigger get ALL_USERS_CHECKSUM + attribute_store_add_if_missing(endpoint_node, + attributes, + COUNT_OF(attributes)); + } + }; + + emplace_bool_value( + USER_CAPABILITIES_REPORT_PROPERTIES1_USER_CHECKSUM_SUPPORT_BIT_MASK, + ATTRIBUTE(SUPPORT_USER_SCHEDULE)); + emplace_bool_value( + USER_CAPABILITIES_REPORT_PROPERTIES1_ALL_USERS_CHECKSUM_SUPPORT_BIT_MASK, + ATTRIBUTE(SUPPORT_ALL_USERS_CHECKSUM)); + emplace_bool_value( + USER_CAPABILITIES_REPORT_PROPERTIES1_USER_CHECKSUM_SUPPORT_BIT_MASK, + ATTRIBUTE(SUPPORT_USER_CHECKSUM)); + + // Bit mask support + user_credential_supported_user_type_bitmask_t bitmask = 0x0000; + uint8_t bitmask_length = frame_data[INDEX_BITMASK_LENGTH]; + // Since we are using uint32_t we can't have more that 4 bit mask + if (bitmask_length > 4) { + sl_log_error(LOG_TAG, + "user_credential_supported_user_type_bitmask_t length is not " + "supported\n"); + return SL_STATUS_NOT_SUPPORTED; + } + for (int i = bitmask_length; i > 0; i--) { + bitmask = (bitmask << 8) | frame_data[INDEX_BITMASK_LENGTH + i]; + } + + attribute_store_set_child_reported(endpoint_node, + ATTRIBUTE(SUPPORTED_USER_TYPES), + &bitmask, + sizeof(bitmask)); + + return SL_STATUS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// User Credential Credential Capabilities Get/Report +///////////////////////////////////////////////////////////////////////////// +static sl_status_t + zwave_command_class_user_credential_credential_capabilities_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + sl_log_debug(LOG_TAG, "Credential Capabilities Get"); + + ZW_CREDENTIAL_CAPABILITIES_GET_FRAME *get_frame + = (ZW_CREDENTIAL_CAPABILITIES_GET_FRAME *)frame; + get_frame->cmdClass = COMMAND_CLASS_USER_CREDENTIAL; + get_frame->cmd = CREDENTIAL_CAPABILITIES_GET; + *frame_length = sizeof(ZW_CREDENTIAL_CAPABILITIES_GET_FRAME); + return SL_STATUS_OK; +} + +sl_status_t + zwave_command_class_user_credential_credential_capabilities_handle_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + if (frame_length < 5) { + return SL_STATUS_NOT_SUPPORTED; + } + + sl_log_debug(LOG_TAG, "Credential Capabilities Report"); + + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + uint8_t support_credential_checksum + = (frame_data[2] + & CREDENTIAL_CAPABILITIES_REPORT_PROPERTIES1_CREDENTIAL_CHECKSUM_SUPPORT_BIT_MASK) + > 0; + + attribute_store_set_child_reported(endpoint_node, + ATTRIBUTE(SUPPORT_CREDENTIAL_CHECKSUM), + &support_credential_checksum, + sizeof(support_credential_checksum)); + + uint8_t supported_credential_types_count = frame_data[3]; + + // Remove all previous known CREDENTIAL_TYPE + attribute_store_node_t type_node; + do { + type_node = attribute_store_get_node_child_by_type( + endpoint_node, + ATTRIBUTE(SUPPORTED_CREDENTIAL_TYPE), + 0); + attribute_store_delete_node(type_node); + } while (type_node != ATTRIBUTE_STORE_INVALID_NODE); + + uint16_t current_index = 4; + for (uint8_t i = 0; i < supported_credential_types_count; i++) { + // > Root node : Credential Type + user_credential_type_t credential_type = frame_data[current_index]; + + attribute_store_node_t credential_type_node + = attribute_store_emplace(endpoint_node, + ATTRIBUTE(SUPPORTED_CREDENTIAL_TYPE), + &credential_type, + sizeof(credential_type)); + + if (credential_type_node == ATTRIBUTE_STORE_INVALID_NODE) { + sl_log_error(LOG_TAG, "Unable to create credential type node"); + return SL_STATUS_NOT_SUPPORTED; + } + + sl_log_debug(LOG_TAG, "Supported credential type : %d", credential_type); + + // >> CL Support + // Use same define as Credential Checksum Support here since it's a same and no define is available for cl support + uint8_t cl_support_index = current_index + supported_credential_types_count; + uint8_t support_cl + = (frame_data[cl_support_index] + & CREDENTIAL_CAPABILITIES_REPORT_PROPERTIES1_CREDENTIAL_CHECKSUM_SUPPORT_BIT_MASK) + > 0; + + attribute_store_set_child_reported( + credential_type_node, + ATTRIBUTE(CREDENTIAL_LEARN_READ_BACK_SUPPORT), + &support_cl, + sizeof(support_cl)); + + // >> 16 bits values + auto store_uint16_value = [&](attribute_store_type_t type, uint8_t offset) { + // First 16bit value after CL Support is cl_support_index + supported_credential_types_count + // Then we add the 16 bit value offset to it : + // - CREDENTIAL_SUPPORTED_SLOT_COUNT [0] : supported_credential_types_count x 2 x [0] (base) + // - CREDENTIAL_MIN_LENGTH [1] : supported_credential_types_count x 2 x [1] (one batch of 16 bit data before) + // - CREDENTIAL_MAX_LENGTH [2] : supported_credential_types_count x 2 x [2] (two batch of 16 bit data before) + // Then we add i to it (0..n) to make sure we take the right pair of data to form the 16 bit unsigned integer + const uint16_t index = cl_support_index + supported_credential_types_count + + (supported_credential_types_count * 2 * offset) + + i; + uint16_t value = get_uint16_value(frame_data, index); + attribute_store_set_child_reported(credential_type_node, + type, + &value, + sizeof(value)); + }; + + store_uint16_value(ATTRIBUTE(CREDENTIAL_SUPPORTED_SLOT_COUNT), 0); + store_uint16_value(ATTRIBUTE(CREDENTIAL_MIN_LENGTH), 1); + store_uint16_value(ATTRIBUTE(CREDENTIAL_MAX_LENGTH), 2); + + current_index++; + } + + return SL_STATUS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// All User Checksum Get/Report +///////////////////////////////////////////////////////////////////////////// +static sl_status_t zwave_command_class_user_credential_all_user_checksum_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + sl_log_debug(LOG_TAG, "All User Checksum Get"); + + ZW_ALL_USERS_CHECKSUM_GET_FRAME *get_frame + = (ZW_ALL_USERS_CHECKSUM_GET_FRAME *)frame; + get_frame->cmdClass = COMMAND_CLASS_USER_CREDENTIAL; + get_frame->cmd = ALL_USERS_CHECKSUM_GET; + *frame_length = sizeof(ZW_ALL_USERS_CHECKSUM_GET_FRAME); + + return SL_STATUS_OK; +} + +sl_status_t zwave_command_class_user_credential_all_user_checksum_handle_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + if (frame_length != 4) { + return SL_STATUS_NOT_SUPPORTED; + } + + sl_log_debug(LOG_TAG, "All User Checksum Report"); + + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + user_credential_all_users_checksum_t all_users_checksum + = get_uint16_value(frame_data, 2); + + attribute_store_set_child_reported(endpoint_node, + ATTRIBUTE(ALL_USERS_CHECKSUM), + &all_users_checksum, + sizeof(all_users_checksum)); + + return SL_STATUS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Credential Set/Get/Report +///////////////////////////////////////////////////////////////////////////// + +// Start credential interview process by starting with 0,0 +void trigger_get_credential(attribute_store_node_t user_unique_id_node, + user_credential_type_t credential_type, + user_credential_slot_t credential_slot) +{ + sl_log_debug(LOG_TAG, + "Trigger GET credential for user %d : " + "Credential type %d, credential slot %d", + static_cast( + attribute_store_get_reported_number(user_unique_id_node)), + credential_type, + credential_slot); + + // Create credential type node if it doesn't exists + // Since the GET is mapped to the Credential SLOT we doesn't need to do anything specific here + attribute_store_node_t credential_type_node + = attribute_store_emplace(user_unique_id_node, + ATTRIBUTE(CREDENTIAL_TYPE), + &credential_type, + sizeof(credential_type)); + + // Then check if credential slot node exists + attribute_store_node_t credential_slot_node + = attribute_store_get_node_child_by_value(credential_type_node, + ATTRIBUTE(CREDENTIAL_SLOT), + REPORTED_ATTRIBUTE, + (uint8_t *)&credential_slot, + sizeof(credential_slot), + 0); + + // If it exists we clear it reported value and set it as desired + if (attribute_store_node_exists(credential_slot_node)) { + attribute_store_set_desired(credential_slot_node, + &credential_slot, + sizeof(credential_slot)); + attribute_store_undefine_reported(credential_slot_node); + } else { // If non existant we create it + attribute_store_emplace_desired(credential_type_node, + ATTRIBUTE(CREDENTIAL_SLOT), + &credential_slot, + sizeof(credential_slot)); + } +} + +static sl_status_t zwave_command_class_user_credential_credential_set( + attribute_store_node_t credential_operation_type_node, + uint8_t *frame, + uint16_t *frame_length) +{ + // Identifiers nodes + attribute_store_node_t credential_slot_node + = attribute_store_get_first_parent_with_type(credential_operation_type_node, + ATTRIBUTE(CREDENTIAL_SLOT)); + attribute_store_node_t credential_type_node + = attribute_store_get_first_parent_with_type(credential_slot_node, + ATTRIBUTE(CREDENTIAL_TYPE)); + attribute_store_node_t user_unique_id_node + = attribute_store_get_first_parent_with_type(credential_type_node, + ATTRIBUTE(USER_UNIQUE_ID)); + // Since CREDENTIAL_DATA is not directly under credential_slot_node we need to fetch it first + attribute_store_node_t credential_length_node + = attribute_store_get_first_child_by_type( + credential_slot_node, + ATTRIBUTE(CREDENTIAL_DATA_LENGTH)); + attribute_store_node_t credential_node + = attribute_store_get_first_child_by_type(credential_length_node, + ATTRIBUTE(CREDENTIAL_DATA)); + // Get operation type + user_credential_operation_type_t operation_type = 0; + sl_status_t status + = attribute_store_get_desired(credential_operation_type_node, + &operation_type, + sizeof(operation_type)); + + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, + "Can't get operation type. Not sending CREDENTIAL_SET."); + return SL_STATUS_NOT_SUPPORTED; + } + + sl_log_debug(LOG_TAG, + "Credential SET for Credential Slot %d, Credential Type %d, " + "User %d (operation type : %d)", + static_cast( + attribute_store_get_reported_number(credential_slot_node)), + static_cast( + attribute_store_get_reported_number(credential_type_node)), + static_cast( + attribute_store_get_reported_number(user_unique_id_node)), + operation_type); + + // Since the data is not linear we provide the node directly + std::vector set_data + = {{ATTRIBUTE(USER_UNIQUE_ID), + DESIRED_OR_REPORTED_ATTRIBUTE, + user_unique_id_node}, + {ATTRIBUTE(CREDENTIAL_TYPE), + DESIRED_OR_REPORTED_ATTRIBUTE, + credential_type_node}, + {ATTRIBUTE(CREDENTIAL_SLOT), + DESIRED_OR_REPORTED_ATTRIBUTE, + credential_slot_node}, + {ATTRIBUTE(CREDENTIAL_OPERATION_TYPE), + DESIRED_ATTRIBUTE, + credential_operation_type_node}}; + + // Add the credential data if we are not trying to remove a credential + if (operation_type != USER_CREDENTIAL_OPERATION_TYPE_DELETE) { + set_data.push_back({ATTRIBUTE(CREDENTIAL_DATA), + DESIRED_OR_REPORTED_ATTRIBUTE, + credential_node}); + } + status = create_command_frame(CREDENTIAL_SET, + set_data, + credential_slot_node, + frame, + frame_length); + + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, "Can't create Credential SET frame"); + return SL_STATUS_NOT_SUPPORTED; + } + // If we are deleting the credential we are setting 0x00 as a credential length + if (operation_type == USER_CREDENTIAL_OPERATION_TYPE_DELETE) { + frame[*frame_length] = 0x00; + *frame_length += 1; + } + + return SL_STATUS_OK; +} + +/** + * @brief Credential GET + * + * Attribute store IN : + * CREDENTIAL_TYPE : desired value => get_frame->credentialType + * CREDENTIAL_SLOT : desired value => get_frame->credentialSlot1 & credentialSlot2 + * USER_UNIQUE_ID : reported value => get_frame->userUniqueIdentifier1 & userUniqueIdentifier2 + * + * Attribute store OUT : + * CREDENTIAL_TYPE : set reported as desired & clear desired + * CREDENTIAL_SLOT : set reported as desired & clear desired + * + * @param credential_slot_node + * @param frame + * @param frame_length + * @return sl_status_t + */ +static sl_status_t zwave_command_class_user_credential_credential_get( + attribute_store_node_t credential_slot_node, + uint8_t *frame, + uint16_t *frame_length) +{ + user_credential_slot_t credential_slot = 0; + sl_status_t status = attribute_store_get_desired(credential_slot_node, + &credential_slot, + sizeof(credential_slot)); + if (status != SL_STATUS_OK) { + sl_log_error( + LOG_TAG, + "Can't get credential slot value. Not sending CREDENTIAL_GET."); + return SL_STATUS_NOT_SUPPORTED; + } + + // Get Credential type + user_credential_type_t credential_type = 0; + attribute_store_node_t credential_type_node + = attribute_store_get_first_parent_with_type(credential_slot_node, + ATTRIBUTE(CREDENTIAL_TYPE)); + status = attribute_store_get_reported(credential_type_node, + &credential_type, + sizeof(credential_type)); + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, + "Can't get credential_type. Not sending CREDENTIAL_GET."); + return SL_STATUS_NOT_SUPPORTED; + } + + // Get User Node + attribute_store_node_t user_unique_id_node + = attribute_store_get_first_parent_with_type(credential_type_node, + ATTRIBUTE(USER_UNIQUE_ID)); + user_credential_user_unique_id_t user_unique_id; + status = attribute_store_get_reported(user_unique_id_node, + &user_unique_id, + sizeof(user_unique_id)); + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, + "Can't get user_unique_id. Not sending CREDENTIAL_GET."); + return SL_STATUS_NOT_SUPPORTED; + } + + // Log credential node, type, and user ID + sl_log_debug( + LOG_TAG, + "Credential Get. Credential slot: %d / Credential Type: %d (User %d)", + credential_slot, + credential_type, + user_unique_id); + + auto exploded_credential_slot = explode_uint16(credential_slot); + auto exploded_user_id = explode_uint16(user_unique_id); + + ZW_CREDENTIAL_GET_FRAME *get_frame = (ZW_CREDENTIAL_GET_FRAME *)frame; + get_frame->cmdClass = COMMAND_CLASS_USER_CREDENTIAL; + get_frame->cmd = CREDENTIAL_GET; + get_frame->userUniqueIdentifier1 = exploded_user_id.msb; + get_frame->userUniqueIdentifier2 = exploded_user_id.lsb; + get_frame->credentialType = credential_type; + get_frame->credentialSlot1 = exploded_credential_slot.msb; + get_frame->credentialSlot2 = exploded_credential_slot.lsb; + + *frame_length = sizeof(ZW_CREDENTIAL_GET_FRAME); + return SL_STATUS_OK; +} + +sl_status_t zwave_command_class_user_credential_credential_handle_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + // Since INDEX_CREDENTIAL_DATA is a variable length field we know that we at lest expect 16 elements + if (frame_length < 15) { + return SL_STATUS_NOT_SUPPORTED; + } + + constexpr uint8_t INDEX_USER_ID = 2; + constexpr uint8_t INDEX_CREDENTIAL_TYPE = 4; + constexpr uint8_t INDEX_CREDENTIAL_SLOT = 5; + constexpr uint8_t INDEX_CREDENTIAL_READ_BACK = 7; + constexpr uint8_t INDEX_CREDENTIAL_LENGTH = 8; + constexpr uint8_t INDEX_CREDENTIAL_DATA = 9; + const uint8_t credential_length = frame_data[INDEX_CREDENTIAL_LENGTH]; + const uint8_t INDEX_CREDENTIAL_MODIFIER_TYPE + = INDEX_CREDENTIAL_DATA + credential_length; + const uint8_t INDEX_CREDENTIAL_MODIFIER_NODE_ID + = INDEX_CREDENTIAL_MODIFIER_TYPE + 1; + const uint8_t INDEX_NEXT_CREDENTIAL_TYPE + = INDEX_CREDENTIAL_MODIFIER_NODE_ID + 2; + const uint8_t INDEX_NEXT_CREDENTIAL_SLOT = INDEX_NEXT_CREDENTIAL_TYPE + 1; + + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + const user_credential_user_unique_id_t user_id + = get_uint16_value(frame_data, INDEX_USER_ID); + const user_credential_type_t credential_type + = frame_data[INDEX_CREDENTIAL_TYPE]; + const user_credential_slot_t credential_slot + = get_uint16_value(frame_data, INDEX_CREDENTIAL_SLOT); + + auto remove_credential_type_and_slot_0_if_exists = [&]() { + attribute_store_node_t type_node_0; + attribute_store_node_t slot_node_0; + + // Get nodes 0 + get_credential_type_node(endpoint_node, user_id, 0, REPORTED_ATTRIBUTE, type_node_0); + get_credential_slot_node(type_node_0, 0, DESIRED_ATTRIBUTE, slot_node_0); + + // Remove them + attribute_store_delete_node(type_node_0); + attribute_store_delete_node(slot_node_0); + }; + + if (credential_type == 0 || credential_slot == 0) { + sl_log_debug(LOG_TAG, "User %d has no credential to get", user_id); + remove_credential_type_and_slot_0_if_exists(); + sl_log_debug(LOG_TAG, "Removed credential type and slot 0"); + return SL_STATUS_OK; + } + + sl_log_debug( + LOG_TAG, + "Credential Report. Credential Type: %d / Credential Slot: %d (User %d)", + credential_type, + credential_slot, + user_id); + + // We should have a valid user id if we receive this report + attribute_store_node_t user_unique_id_node + = get_reported_user_id_node(endpoint_node, user_id); + + // Check node existence + if (!attribute_store_node_exists(user_unique_id_node)) { + sl_log_error(LOG_TAG, + "Can't find user with ID %d in CREDENTIAL_REPORT", + user_id); + return SL_STATUS_NOT_SUPPORTED; + } + + // Find credential type node based on user id and credential type + attribute_store_node_t credential_type_node = ATTRIBUTE_STORE_INVALID_NODE; + + bool credential_type_found = get_credential_type_node(endpoint_node, + user_id, + credential_type, + REPORTED_ATTRIBUTE, + credential_type_node); + + // If not found we look for the credential type node of 0 (first interview) + if (!credential_type_found) { + // We don't care about the return value, if not defined it will be catch soon. + get_credential_type_node(endpoint_node, + user_id, + 0, + REPORTED_ATTRIBUTE, + credential_type_node); + } + + // Now search for the slot node + attribute_store_node_t credential_slot_node = ATTRIBUTE_STORE_INVALID_NODE; + // Check reported value first + if (!get_credential_slot_node(credential_type_node, + credential_slot, + REPORTED_ATTRIBUTE, + credential_slot_node)) { + sl_log_debug(LOG_TAG, + "Could not find slot %d with reported value, trying desired", + credential_slot); + // Then check desired value + if (!get_credential_slot_node(credential_type_node, + credential_slot, + DESIRED_ATTRIBUTE, + credential_slot_node)) { + sl_log_debug( + LOG_TAG, + "Could not find slot %d with desired value, using the Slot with ID 0", + credential_slot); + + // If not found it will be checked in the next step + get_credential_slot_node(credential_type_node, + 0, + DESIRED_ATTRIBUTE, + credential_slot_node); + } + } + + // Check if we could retrieve all the node we need + if (!attribute_store_node_exists(credential_type_node) + || !attribute_store_node_exists(credential_slot_node)) { + sl_log_error(LOG_TAG, + "Can't find (Credential Type %d, Credential Slot %d) in " + "CREDENTIAL_REPORT", + credential_type, + credential_slot); + return SL_STATUS_NOT_SUPPORTED; + } + + // Update credential slot node & type + attribute_store_set_reported(credential_type_node, + &credential_type, + sizeof(credential_type)); + attribute_store_set_reported(credential_slot_node, + &credential_slot, + sizeof(credential_slot)); + // Since the get is listened on the credential slot node we need to clear it + attribute_store_undefine_desired(credential_slot); + + // If there is any leftovers of slot 0 we remove them to prevent infinite loop + remove_credential_type_and_slot_0_if_exists(); + + // Set standard (uint8 & uint16) data + std::vector user_data = { + {ATTRIBUTE(CREDENTIAL_READ_BACK), + INDEX_CREDENTIAL_READ_BACK, + CREDENTIAL_REPORT_PROPERTIES1_CRB_BIT_MASK, + 7}, + {ATTRIBUTE(CREDENTIAL_MODIFIER_TYPE), INDEX_CREDENTIAL_MODIFIER_TYPE}, + {ATTRIBUTE(CREDENTIAL_MODIFIER_NODE_ID), INDEX_CREDENTIAL_MODIFIER_NODE_ID}, + }; + + sl_status_t status + = set_reported_attributes(credential_slot_node, frame_data, user_data); + + if (status != SL_STATUS_OK) { + return status; + } + + // Set credential data + auto credential_data_length_node + = attribute_store_emplace(credential_slot_node, + ATTRIBUTE(CREDENTIAL_DATA_LENGTH), + &credential_length, + sizeof(credential_length)); + + if (credential_data_length_node == ATTRIBUTE_STORE_INVALID_NODE) { + sl_log_error(LOG_TAG, + "Can't set CREDENTIAL_DATA_LENGTH in attribute store"); + return SL_STATUS_NOT_SUPPORTED; + } + + status + = attribute_store_set_child_reported(credential_data_length_node, + ATTRIBUTE(CREDENTIAL_DATA), + &frame_data[INDEX_CREDENTIAL_DATA], + credential_length); + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, "Can't set CREDENTIAL_DATA in attribute store"); + return SL_STATUS_NOT_SUPPORTED; + } + + user_credential_type_t next_credential_type + = frame_data[INDEX_NEXT_CREDENTIAL_TYPE]; + user_credential_slot_t next_credential_slot + = get_uint16_value(frame_data, INDEX_NEXT_CREDENTIAL_SLOT); + + if (next_credential_type != 0 && next_credential_slot != 0) { + trigger_get_credential(user_unique_id_node, + next_credential_type, + next_credential_slot); + sl_log_debug(LOG_TAG, + "Next credential type and slot: %d, %d", + next_credential_type, + next_credential_slot); + } else { + sl_log_debug(LOG_TAG, "No more credential to get"); + } + + return SL_STATUS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// User Set/Get/Report +///////////////////////////////////////////////////////////////////////////// + +// Start user interview process by starting a user get with ID 0 +void trigger_get_user(attribute_store_node_t endpoint_node, + user_credential_user_unique_id_t user_id) +{ + // If we are not in the special case of user ID 0 we need to check if user is already here + if (user_id != 0) { + attribute_store_node_t user_node + = attribute_store_get_node_child_by_value(endpoint_node, + ATTRIBUTE(USER_UNIQUE_ID), + REPORTED_ATTRIBUTE, + (uint8_t *)&user_id, + sizeof(user_id), + 0); + // If it exists we interview it again + if (attribute_store_node_exists(user_node)) { + sl_log_debug( + LOG_TAG, + "User Unique ID %d found. Undefine its reported value to update it.", + user_id); + attribute_store_set_desired(user_node, &user_id, sizeof(user_id)); + attribute_store_undefine_reported(user_node); + return; + } + } + + // If user id is 0 or not existant we create it + sl_log_debug(LOG_TAG, "Creating User Unique ID node %d", user_id); + attribute_store_emplace_desired(endpoint_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); +} + +static sl_status_t zwave_command_class_user_credential_user_set( + attribute_store_node_t user_operation_type_node, + uint8_t *frame, + uint16_t *frame_length) +{ + // Get user unique id node + attribute_store_node_t user_unique_id_node + = attribute_store_get_first_parent_with_type(user_operation_type_node, + ATTRIBUTE(USER_UNIQUE_ID)); + + user_credential_user_unique_id_t user_id = 0; + attribute_store_get_desired_else_reported(user_unique_id_node, + &user_id, + sizeof(user_id)); + + sl_log_debug(LOG_TAG, "User SET for user %d", user_id); + + // If we can't get the user unique id we can't continue + if (!attribute_store_node_exists(user_unique_id_node)) { + return SL_STATUS_NOT_SUPPORTED; + } + + user_credential_operation_type_t user_operation_type = 0; + sl_status_t status = attribute_store_get_desired(user_operation_type_node, + &user_operation_type, + sizeof(user_operation_type)); + + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, + "Can't get user operation type value. Not sending USER_SET."); + return SL_STATUS_NOT_SUPPORTED; + } + + std::vector set_data = { + {ATTRIBUTE(USER_OPERATION_TYPE), DESIRED_ATTRIBUTE}, + {ATTRIBUTE(USER_UNIQUE_ID), DESIRED_OR_REPORTED_ATTRIBUTE, user_unique_id_node} + }; + + // If we are not deleting the user we need more data + if (user_operation_type != USER_SET_OPERATION_TYPE_DELETE) { + std::vector extra_set_data = { + {ATTRIBUTE(USER_TYPE), DESIRED_OR_REPORTED_ATTRIBUTE}, + {ATTRIBUTE(USER_ACTIVE_STATE), DESIRED_OR_REPORTED_ATTRIBUTE}, + {ATTRIBUTE(CREDENTIAL_RULE), DESIRED_OR_REPORTED_ATTRIBUTE}, + {ATTRIBUTE(USER_EXPIRING_TIMEOUT_MINUTES), DESIRED_OR_REPORTED_ATTRIBUTE}, + {ATTRIBUTE(USER_NAME_ENCODING), DESIRED_OR_REPORTED_ATTRIBUTE}, + {ATTRIBUTE(USER_NAME), DESIRED_OR_REPORTED_ATTRIBUTE}, + }; + std::copy(extra_set_data.begin(), + extra_set_data.end(), + std::back_inserter(set_data)); + } + + status = create_command_frame(USER_SET, + set_data, + user_unique_id_node, + frame, + frame_length); + + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, "Can't create User SET frame"); + return SL_STATUS_NOT_SUPPORTED; + } + + return SL_STATUS_OK; +} + +static sl_status_t zwave_command_class_user_credential_user_get( + attribute_store_node_t node, uint8_t *frame, uint16_t *frame_length) +{ + user_credential_user_unique_id_t user_id = 0; + sl_status_t status + = attribute_store_get_desired(node, &user_id, sizeof(user_id)); + + if (status != SL_STATUS_OK) { + sl_log_error(LOG_TAG, + "Can't get user unique id value. Not sending USER_GET."); + return SL_STATUS_NOT_SUPPORTED; + } + + sl_log_debug(LOG_TAG, "User Get for user %d", user_id); + + auto exploded_value = explode_uint16(user_id); + + ZW_USER_GET_FRAME *get_frame = (ZW_USER_GET_FRAME *)frame; + get_frame->cmdClass = COMMAND_CLASS_USER_CREDENTIAL; + get_frame->cmd = USER_GET; + get_frame->userUniqueIdentifier1 = exploded_value.msb; + get_frame->userUniqueIdentifier2 = exploded_value.lsb; + + *frame_length = sizeof(ZW_USER_GET_FRAME); + return SL_STATUS_OK; +} + +sl_status_t zwave_command_class_user_credential_user_handle_report( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + constexpr uint8_t INDEX_NEXT_USER_ID = 2; + constexpr uint8_t INDEX_USER_MODIFIER_TYPE = 4; + constexpr uint8_t INDEX_USER_MODIFIER_ID = 5; + constexpr uint8_t INDEX_USER_ID = 7; + constexpr uint8_t INDEX_USER_TYPE = 9; + constexpr uint8_t INDEX_USER_ACTIVE_STATE = 10; + constexpr uint8_t INDEX_CREDENTIAL_RULE = 11; + constexpr uint8_t INDEX_USER_NAME_EXPIRING_TIMEOUT_MINUTES = 12; + constexpr uint8_t INDEX_USER_NAME_ENCODING = 14; + constexpr uint8_t INDEX_USER_NAME_LENGTH = 15; + constexpr uint8_t INDEX_USER_NAME = 16; + + if (frame_length < INDEX_USER_NAME) { + return SL_STATUS_NOT_SUPPORTED; + } + + attribute_store_node_t endpoint_node + = zwave_command_class_get_endpoint_node(connection_info); + + // Find the user + const user_credential_user_unique_id_t user_id + = get_uint16_value(frame_data, INDEX_USER_ID); + + sl_log_debug(LOG_TAG, "User report for user %d", user_id); + + auto remove_node_0_if_exists = [&]() { + attribute_store_node_t node_0; + get_user_id_node(endpoint_node, 0, DESIRED_ATTRIBUTE, node_0); + return attribute_store_delete_node(node_0); + }; + + // CC:0083.01.05.11.006: Zero is an invalid User Unique Identifier and MUST NOT be used by the node + if (user_id == 0) { + sl_log_info(LOG_TAG, + "User report with ID 0 received. This probably means that no " + "user is defined on the device."); + sl_log_debug(LOG_TAG, + "Attempt to delete User Node ID with value %d", + user_id); + sl_status_t deletion_status = remove_node_0_if_exists(); + sl_log_debug(LOG_TAG, "Deletion returned status : %d", deletion_status); + return SL_STATUS_OK; + } + + // Get User ID node + attribute_store_node_t user_unique_id_node; + + // First we check if there this user already exists in our database (reported value) + if (!get_user_id_node(endpoint_node, + user_id, + REPORTED_ATTRIBUTE, + user_unique_id_node)) { + sl_log_debug(LOG_TAG, + "Could not find user %d with reported value, trying desired", + user_id); + // If User node doesn't exists with given desired attribute + if (!get_user_id_node(endpoint_node, + user_id, + DESIRED_ATTRIBUTE, + user_unique_id_node)) { + sl_log_debug( + LOG_TAG, + "Could not find user %d with desired value, using the User ID 0", + user_id); + + // If not found it will be checked in the next step + get_user_id_node(endpoint_node, + 0, + DESIRED_ATTRIBUTE, + user_unique_id_node); + } + } + + // Check node existence + if (!attribute_store_node_exists(user_unique_id_node)) { + sl_log_error(LOG_TAG, "Can't find user with ID %d in USER_REPORT", user_id); + return SL_STATUS_NOT_SUPPORTED; + } + + // Check if user still exists + if (frame_data[INDEX_USER_MODIFIER_TYPE] == USER_REPORT_DNE) { + sl_log_debug(LOG_TAG, "User %d does not exist anymore, removing from attribute store.", user_id); + attribute_store_delete_node(user_unique_id_node); + return SL_STATUS_OK; + } + + // Set reported value + attribute_store_set_reported(user_unique_id_node, &user_id, sizeof(user_id)); + attribute_store_undefine_desired(user_unique_id_node); + + // Remove leftover of node 0 if it exists + // This is necessary if we are interviewing again the user as this node will be left undefined + // and causing a get loop. + remove_node_0_if_exists(); + + // Set standard (uint8 & uint16) data + std::vector user_data + = {{ATTRIBUTE(USER_MODIFIER_TYPE), INDEX_USER_MODIFIER_TYPE}, + {ATTRIBUTE(USER_MODIFIER_NODE_ID), INDEX_USER_MODIFIER_ID}, + {ATTRIBUTE(USER_TYPE), INDEX_USER_TYPE}, + {ATTRIBUTE(USER_ACTIVE_STATE), + INDEX_USER_ACTIVE_STATE, + USER_REPORT_PROPERTIES1_USER_ACTIVE_STATE_BIT_MASK}, + {ATTRIBUTE(USER_NAME_ENCODING), + INDEX_USER_NAME_ENCODING, + USER_REPORT_PROPERTIES2_USER_NAME_ENCODING_MASK}, + {ATTRIBUTE(CREDENTIAL_RULE), INDEX_CREDENTIAL_RULE}, + {ATTRIBUTE(USER_EXPIRING_TIMEOUT_MINUTES), + INDEX_USER_NAME_EXPIRING_TIMEOUT_MINUTES}}; + + sl_status_t set_status + = set_reported_attributes(user_unique_id_node, frame_data, user_data); + + if (set_status != SL_STATUS_OK) { + return set_status; + } + + // User name + uint8_t user_name_length = frame_data[INDEX_USER_NAME_LENGTH]; + std::string user_name + = get_string_value(frame_data, INDEX_USER_NAME, user_name_length); + + // Prevent duplicate + auto user_name_node + = attribute_store_get_first_child_by_type(user_unique_id_node, + ATTRIBUTE(USER_NAME)); + if (!attribute_store_node_exists(user_name_node)) { + user_name_node + = attribute_store_add_node(ATTRIBUTE(USER_NAME), user_unique_id_node); + } + attribute_store_set_reported_string(user_name_node, user_name.c_str()); + + // Get credentials + trigger_get_credential(user_unique_id_node, 0, 0); + + // Interview next ID if needed + user_credential_user_unique_id_t next_user_id + = get_uint16_value(frame_data, INDEX_NEXT_USER_ID); + if (next_user_id != 0) { + sl_log_debug(LOG_TAG, "Trigger a get for next user (%d)", next_user_id); + trigger_get_user(endpoint_node, next_user_id); + } else { + sl_log_debug(LOG_TAG, "No more users to discover"); + } + + return SL_STATUS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Post interview actions +///////////////////////////////////////////////////////////////////////////// +void zwave_network_status_changed(attribute_store_node_t updated_node, + attribute_store_change_t change) +{ + attribute_store_node_t node_id_node + = attribute_store_get_first_parent_with_type(updated_node, + ATTRIBUTE_NODE_ID); + + zwave_node_id_t node_id; + attribute_store_get_reported(node_id_node, &node_id, sizeof(node_id)); + + // If we are updating the zpc node or if we trying to delete the attribute we don't want to do anything + if (change == ATTRIBUTE_DELETED || get_zpc_node_id_node() == node_id_node) { + return; + } + + NodeStateNetworkStatus network_status; + sl_status_t reported_value_status + = attribute_store_get_reported(updated_node, + &network_status, + sizeof(network_status)); + + // If the endpoint report is marked as ONLINE_FUNCTIONAL + if (reported_value_status == SL_STATUS_OK + && network_status == ZCL_NODE_STATE_NETWORK_STATUS_ONLINE_FUNCTIONAL) { + sl_log_debug(LOG_TAG, + "Node %d is now ONLINE_FUNCTIONAL : start the delayed " + "interview process", + node_id); + // Perform action on each endpoint that supports User Credential Command class + uint8_t endpoint_count + = attribute_store_get_node_child_count_by_type(node_id_node, + ATTRIBUTE_ENDPOINT_ID); + + sl_log_debug(LOG_TAG, "Checking endpoints (total : %d)...", endpoint_count); + + for (uint8_t i = 0; i < endpoint_count; i++) { + // Get current endpoint node + attribute_store_node_t endpoint_node + = attribute_store_get_node_child_by_type(node_id_node, + ATTRIBUTE_ENDPOINT_ID, + i); + + zwave_endpoint_id_t endpoint_id; + attribute_store_get_reported(endpoint_node, + &endpoint_id, + sizeof(endpoint_id)); + // Check if the endpoint supports User Credential Command class + if (zwave_node_supports_command_class(COMMAND_CLASS_USER_CREDENTIAL, + node_id, + endpoint_id)) { + auto user_count = attribute_store_get_node_child_count_by_type( + endpoint_node, + ATTRIBUTE(USER_UNIQUE_ID)); + sl_log_debug(LOG_TAG, + "Endpoint %d supports User Credential. Removing all " + "existing user nodes (%d)...", + i, + user_count); + for (size_t j = 0; j < user_count; j++) { + // Delete the first attribute we find until we have no more left + attribute_store_node_t user_node + = attribute_store_get_node_child_by_type(endpoint_node, + ATTRIBUTE(USER_UNIQUE_ID), + 0); + attribute_store_delete_node(user_node); + } + + sl_log_debug(LOG_TAG, "Starting User and Credential interview"); + // Start the interview process with user ID = 0 + trigger_get_user(endpoint_node, 0); + } + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// Class logic +///////////////////////////////////////////////////////////////////////////// + +// Control handler +sl_status_t zwave_command_class_user_credential_control_handler( + const zwave_controller_connection_info_t *connection_info, + const uint8_t *frame_data, + uint16_t frame_length) +{ + if (frame_length <= COMMAND_INDEX) { + return SL_STATUS_NOT_SUPPORTED; + } + + switch (frame_data[COMMAND_INDEX]) { + case USER_CAPABILITIES_REPORT: + return zwave_command_class_user_credential_user_capabilities_handle_report( + connection_info, + frame_data, + frame_length); + case CREDENTIAL_CAPABILITIES_REPORT: + return zwave_command_class_user_credential_credential_capabilities_handle_report( + connection_info, + frame_data, + frame_length); + case ALL_USERS_CHECKSUM_REPORT: + return zwave_command_class_user_credential_all_user_checksum_handle_report( + connection_info, + frame_data, + frame_length); + case USER_REPORT: + return zwave_command_class_user_credential_user_handle_report( + connection_info, + frame_data, + frame_length); + case CREDENTIAL_REPORT: + return zwave_command_class_user_credential_credential_handle_report( + connection_info, + frame_data, + frame_length); + default: + return SL_STATUS_NOT_SUPPORTED; + } +} + +// Entry point +sl_status_t zwave_command_class_user_credential_init() +{ + attribute_store_register_callback_by_type( + &zwave_command_class_user_credential_on_version_attribute_update, + ATTRIBUTE(VERSION)); + + attribute_resolver_register_rule( + ATTRIBUTE(NUMBER_OF_USERS), + NULL, + &zwave_command_class_user_credential_user_capabilities_get); + + attribute_resolver_register_rule( + ATTRIBUTE(SUPPORT_CREDENTIAL_CHECKSUM), + NULL, + &zwave_command_class_user_credential_credential_capabilities_get); + + attribute_resolver_register_rule( + ATTRIBUTE(ALL_USERS_CHECKSUM), + NULL, + &zwave_command_class_user_credential_all_user_checksum_get); + + attribute_resolver_register_rule( + ATTRIBUTE(USER_UNIQUE_ID), + NULL, + &zwave_command_class_user_credential_user_get); + + attribute_resolver_register_rule( + ATTRIBUTE(USER_OPERATION_TYPE), + &zwave_command_class_user_credential_user_set, + NULL); + + attribute_resolver_register_rule( + ATTRIBUTE(CREDENTIAL_SLOT), + NULL, + &zwave_command_class_user_credential_credential_get); + + attribute_resolver_register_rule( + ATTRIBUTE(CREDENTIAL_OPERATION_TYPE), + &zwave_command_class_user_credential_credential_set, + NULL); + + // https://github.com/Z-Wave-Alliance/AWG/pull/124#discussion_r1484473752 + // Discussion about delaying the user interview process after the inclusion + + // Proposed Unify-way to delay users get AFTER interview process + attribute_store_register_callback_by_type( + &zwave_network_status_changed, + DOTDOT_ATTRIBUTE_ID_STATE_NETWORK_STATUS); + + zwave_command_handler_t handler = {}; + handler.support_handler = NULL; + handler.control_handler = zwave_command_class_user_credential_control_handler; + // CHECKME : Is this right ? + handler.minimal_scheme = ZWAVE_CONTROLLER_ENCAPSULATION_SECURITY_2_ACCESS; + handler.manual_security_validation = false; + handler.command_class = COMMAND_CLASS_USER_CREDENTIAL; + handler.version = 1; + handler.command_class_name = "User Credential"; + handler.comments = "Experimental"; + + return zwave_command_handler_register_handler(handler); +} \ No newline at end of file diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_class_user_credential.h b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_user_credential.h new file mode 100644 index 000000000..9af030a3b --- /dev/null +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_class_user_credential.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * # License + * Copyright 2024 Silicon Laboratories Inc. www.silabs.com + ****************************************************************************** + * The licensor of this software is Silicon Laboratories Inc. Your use of this + * software is governed by the terms of Silicon Labs Master Software License + * Agreement (MSLA) available at + * www.silabs.com/about-us/legal/master-software-license-agreement. This + * software is distributed to you in Source Code format and is governed by the + * sections of the MSLA applicable to Source Code. + * + *****************************************************************************/ + +/** + * @defgroup zwave_command_class_user_credential + * @brief Sound Switch Command Class handlers and control function + * + * This module implement some of the functions to control the + * Sound Switch Command Class + * + * @{ + */ + +#ifndef ZWAVE_COMMAND_CLASS_USER_CREDENTIAL_H +#define ZWAVE_COMMAND_CLASS_USER_CREDENTIAL_H + +#include "sl_status.h" + +#ifdef __cplusplus +extern "C" { +#endif + +sl_status_t zwave_command_class_user_credential_init(); + +#ifdef __cplusplus +} +#endif + +#endif //ZWAVE_COMMAND_CLASS_USER_CREDENTIAL_H + /** @} end zwave_command_class_user_credential */ \ No newline at end of file diff --git a/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_fixt.c b/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_fixt.c index a4c8f32b6..d6f457e71 100644 --- a/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_fixt.c +++ b/applications/zpc/components/zwave_command_classes/src/zwave_command_classes_fixt.c @@ -57,6 +57,7 @@ #include "zwave_command_class_indicator_control.h" #include "zwave_command_class_manufacturer_specific_control.h" #include "zwave_command_class_humidity_control_mode.h" +#include "zwave_command_class_user_credential.h" // Generic includes #include @@ -121,6 +122,7 @@ sl_status_t zwave_command_classes_init() status |= zwave_command_class_time_init(); status |= zwave_command_class_transport_service_init(); status |= zwave_command_class_user_code_init(); + status |= zwave_command_class_user_credential_init(); status |= zwave_command_class_version_init(); status |= zwave_command_class_wake_up_init(); status |= zwave_command_class_zwave_plus_info_init(); diff --git a/applications/zpc/components/zwave_command_classes/test/CMakeLists.txt b/applications/zpc/components/zwave_command_classes/test/CMakeLists.txt index 0739c357c..e6b3608bb 100644 --- a/applications/zpc/components/zwave_command_classes/test/CMakeLists.txt +++ b/applications/zpc/components/zwave_command_classes/test/CMakeLists.txt @@ -832,4 +832,20 @@ target_add_unittest( zpc_attribute_resolver_mock uic_dotdot_mqtt_mock) - \ No newline at end of file + + + # User credential test + target_add_unittest( + zwave_command_classes + NAME + zwave_command_class_user_credential_test + SOURCES + zwave_command_class_user_credential_test.cpp + DEPENDS + zpc_attribute_store_test_helper + zwave_controller + zwave_command_handler_mock + uic_attribute_resolver_mock + zpc_attribute_resolver_mock + uic_dotdot_mqtt_mock + zwave_network_management_mock) \ No newline at end of file diff --git a/applications/zpc/components/zwave_command_classes/test/zwave_command_class_user_credential_test.cpp b/applications/zpc/components/zwave_command_classes/test/zwave_command_class_user_credential_test.cpp new file mode 100644 index 000000000..acb34f599 --- /dev/null +++ b/applications/zpc/components/zwave_command_classes/test/zwave_command_class_user_credential_test.cpp @@ -0,0 +1,2027 @@ +/****************************************************************************** + * # License + * Copyright 2024 Silicon Laboratories Inc. www.silabs.com + ****************************************************************************** + * The licensor of this software is Silicon Laboratories Inc. Your use of this + * software is governed by the terms of Silicon Labs Master Software License + * Agreement (MSLA) available at + * www.silabs.com/about-us/legal/master-software-license-agreement. This + * software is distributed to you in Source Code format and is governed by the + * sections of the MSLA applicable to Source Code. + * + *****************************************************************************/ +#include +#include "workaround_for_test.hpp" + +extern "C" { + +#include "zwave_command_class_user_credential.h" +#include "zwave_command_class_user_credential_types.h" +#include "zwave_command_classes_utils.h" +#include "unity.h" + +// Generic includes +#include + +// Includes from other components +#include "datastore.h" +#include "attribute_store.h" +#include "attribute_store_helper.h" +#include "attribute_store_fixt.h" +#include "attribute_store_type_registration.h" +#include "zpc_attribute_store_type_registration.h" + +// Interface includes +#include "attribute_store_defined_attribute_types.h" +#include "ZW_classcmd.h" +#include "zwave_utils.h" +#include "zwave_controller_types.h" + +// Test helpers +#include "zpc_attribute_store_test_helper.h" + +// Mock includes +#include "attribute_resolver_mock.h" +#include "zpc_attribute_resolver_mock.h" +#include "zwave_command_handler_mock.h" +#include "dotdot_mqtt_mock.h" +#include "dotdot_mqtt_generated_commands_mock.h" +// Used for delayed interview +#include "zwave_network_management_mock.h" + +#define ATTRIBUTE(type) ATTRIBUTE_COMMAND_CLASS_USER_CREDENTIAL_##type + +static zwave_command_handler_t handler = {}; + +// This assumes that XXX_GET and XXX_SET id are > 0 +struct bound_functions { + uint8_t get_func_id; + uint8_t set_func_id; +}; +static std::map attributes_binding + = {{ATTRIBUTE(NUMBER_OF_USERS), {USER_CAPABILITIES_GET, 0}}, + {ATTRIBUTE(SUPPORT_CREDENTIAL_CHECKSUM), {CREDENTIAL_CAPABILITIES_GET, 0}}, + {ATTRIBUTE(ALL_USERS_CHECKSUM), {ALL_USERS_CHECKSUM_GET, 0}}, + {ATTRIBUTE(USER_UNIQUE_ID), {USER_GET, 0}}, + {ATTRIBUTE(USER_OPERATION_TYPE), {0, USER_SET}}, + {ATTRIBUTE(CREDENTIAL_SLOT), {CREDENTIAL_GET, 0}}, + {ATTRIBUTE(CREDENTIAL_OPERATION_TYPE), {0, CREDENTIAL_SET}}}; + +// Filed with resolver function for given ID in attribute_resolver_register_rule_stub based on +// attributes_binding map. +// +// e.g : resolver_functions[USER_CAPABILITIES_GET] = user_capabilities_get_func() +static std::map resolver_functions; + +// Buffer for frame +static uint8_t received_frame[255] = {}; +static uint16_t received_frame_size = 0; + +const uint8_t MAX_CHAR_SIZE = 64; +///////////////////////////////////////////////////// +// C++ HELPERS +///////////////////////////////////////////////////// +} // Extern C + +#ifdef __cplusplus +template void helper_test_attribute_store_values( + std::map attribute_map_values, + attribute_store_node_t parent = ATTRIBUTE_STORE_INVALID_NODE) +{ + if (parent == ATTRIBUTE_STORE_INVALID_NODE) { + parent = endpoint_id_node; + } + + for (auto &attr: attribute_map_values) { + T value; + sl_status_t status = attribute_store_get_child_reported(parent, + attr.first, + &value, + sizeof(value)); + + std::string message + = "Can't get reported value of " + + std::string(attribute_store_get_type_name(attr.first)); + + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, status, message.c_str()); + + message = "Mismatch value for attribute : " + + std::string(attribute_store_get_type_name(attr.first)); + TEST_ASSERT_EQUAL_MESSAGE(attr.second, value, message.c_str()); + } +} +#endif + +extern "C" { +///////////////////////////////////////////////////// +// HELPERS +///////////////////////////////////////////////////// +struct uint16_exploded { + uint8_t msb; // Most Significant Bit + uint8_t lsb; // Less Significant Bit +}; +uint16_exploded explode_uint16(uint16_t value) +{ + uint8_t msb = (value & 0xFF00) >> 8; + uint8_t lsb = (value & 0x00FF); + return uint16_exploded {msb, lsb}; +} + +struct credential_structure_nodes { + attribute_store_node_t user_id_node; + attribute_store_node_t credential_type_node; + attribute_store_node_t credential_slot_node; +}; + +/** + * @brief Create credential structure and return associated nodes + * + * @param user_id + * @param credential_type + * @param credential_slot + * @param value_state If DESIRED_ATTRIBUTE : credential_type/credential_slot will have their desired value setup, reported otherwise + * @return credential_structure_nodes + */ +credential_structure_nodes helper_create_credential_structure( + user_credential_user_unique_id_t user_id, + user_credential_type_t credential_type, + user_credential_slot_t credential_slot, + attribute_store_node_value_state_t value_state = DESIRED_ATTRIBUTE) +{ + credential_structure_nodes nodes; + nodes.user_id_node = attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + // It should be always reported + nodes.credential_type_node + = attribute_store_emplace(nodes.user_id_node, + ATTRIBUTE(CREDENTIAL_TYPE), + &credential_type, + sizeof(credential_type)); + + if (value_state == DESIRED_ATTRIBUTE) { + nodes.credential_slot_node + = attribute_store_emplace_desired(nodes.credential_type_node, + ATTRIBUTE(CREDENTIAL_SLOT), + &credential_slot, + sizeof(credential_slot)); + } else { + nodes.credential_slot_node + = attribute_store_emplace(nodes.credential_type_node, + ATTRIBUTE(CREDENTIAL_SLOT), + &credential_slot, + sizeof(credential_slot)); + } + + return nodes; +} + +void helper_test_get_no_args_happy_case(uint8_t get_command_id) +{ + auto &get_func = resolver_functions[get_command_id]; + + // Ask for a Get Command, should always be the same + TEST_ASSERT_NOT_NULL_MESSAGE( + get_func, + "Couldn't find get function in resolver_functions."); + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, + get_func(0, received_frame, &received_frame_size), + "Get function should have returned OK"); + + const uint8_t expected_frame[] + = {COMMAND_CLASS_USER_CREDENTIAL, get_command_id}; + TEST_ASSERT_EQUAL_MESSAGE(sizeof(expected_frame), + received_frame_size, + "Get frame size is incorrect"); + TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(expected_frame, + received_frame, + received_frame_size, + "Get frame contents mismatch"); +} + +/** + * @brief Test given SET or GET function with args + * + * > Can also test if SET or GET command return SL_STATUS_NOT_SUPPORTED if node == INVALID_STORAGE_TYPE + * + * @param command_id Command that should be in resolver_functions + * @param base_node Base node on which the GET function will be called. If INVALID_STORAGE_TYPE expect the SET or GET function to return SL_STATUS_NOT_SUPPORTED and return. + * @param nodes Node data that will be used to check and construct the SET or GET frame (order matter since the first one will be the first argument, etc..) + * @param extra_bytes Extra bytes that will be added to the frame + */ +void helper_test_set_get_with_args( + uint8_t command_id, + attribute_store_node_t base_node, + std::vector< + std::pair> nodes + = {}, + std::vector extra_bytes = {}) +{ + auto &command_func = resolver_functions[command_id]; + + // Ask for a Get Command, should always be the same + TEST_ASSERT_NOT_NULL_MESSAGE( + command_func, + "Couldn't find command function in resolver_functions."); + + if (base_node == ATTRIBUTE_STORE_INVALID_NODE) { + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_NOT_SUPPORTED, + command_func(base_node, received_frame, &received_frame_size), + "Command function should have returned SL_STATUS_NOT_SUPPORTED since no " + "node " + "was provided"); + return; + } + + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + command_func(base_node, received_frame, &received_frame_size), + "Command function should have returned SL_STATUS_OK"); + + std::vector expected_frame + = {COMMAND_CLASS_USER_CREDENTIAL, command_id}; + + for (auto &node_data: nodes) { + auto node = node_data.first; + auto node_value_state = node_data.second; + + auto node_type = attribute_store_get_node_type(node); + auto node_storage_type = attribute_store_get_storage_type(node_type); + + sl_status_t status; + switch (node_storage_type) { + case U8_STORAGE_TYPE: { + uint8_t uint8_value; + status = attribute_store_read_value(node, + node_value_state, + &uint8_value, + sizeof(uint8_value)); + expected_frame.push_back(uint8_value); + } break; + case U16_STORAGE_TYPE: { + uint16_t uint16_value; + status = attribute_store_read_value(node, + node_value_state, + &uint16_value, + sizeof(uint16_value)); + auto exploded_uint16 = explode_uint16(uint16_value); + expected_frame.push_back(exploded_uint16.msb); + expected_frame.push_back(exploded_uint16.lsb); + } break; + // Variable length field + case BYTE_ARRAY_STORAGE_TYPE: { + // Length node should be a parent of the data node + auto data_length_node = attribute_store_get_node_parent(node); + + uint8_t data_length = 0; + attribute_store_read_value(data_length_node, + node_value_state, + &data_length, + sizeof(data_length)); + if (data_length == 0) { + TEST_FAIL_MESSAGE( + "Can't get data_length for BYTE_ARRAY_STORAGE_TYPE type"); + } + + std::vector data; + data.resize(data_length); + status = attribute_store_read_value(node, + node_value_state, + data.data(), + data_length); + + expected_frame.push_back(data_length); + for (uint8_t i = 0; i < data_length; i++) { + expected_frame.push_back(data[i]); + } + + } break; + case C_STRING_STORAGE_TYPE: { + char reported_c_str[MAX_CHAR_SIZE]; + + status = attribute_store_get_reported_string(node, + reported_c_str, + MAX_CHAR_SIZE); + std::string reported_cpp_str = reported_c_str; + if (reported_cpp_str.empty()) { + TEST_FAIL_MESSAGE("Can't get string value"); + } + + expected_frame.push_back(reported_cpp_str.length()); + for (const char &c: reported_cpp_str) { + expected_frame.push_back(c); + } + + } break; + default: + TEST_FAIL_MESSAGE( + "Unkown storage type in helper_test_set_get_with_args_happy_case"); + } + + std::string message + = "Should be able to get value of " + + std::string(attribute_store_type_get_node_type_name(node)); + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, status, message.c_str()); + } + + for(auto &byte : extra_bytes) { + expected_frame.push_back(byte); + } + + TEST_ASSERT_EQUAL_MESSAGE(expected_frame.size(), + received_frame_size, + "Command frame size is incorrect"); + TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(expected_frame.data(), + received_frame, + received_frame_size, + "Command frame contents mismatch"); +} + +void helper_test_string_value( + std::map attribute_map_values, + attribute_store_node_t parent = ATTRIBUTE_STORE_INVALID_NODE) +{ + if (parent == ATTRIBUTE_STORE_INVALID_NODE) { + parent = endpoint_id_node; + } + + for (auto &attr: attribute_map_values) { + char reported_c_str[MAX_CHAR_SIZE]; + + auto str_node = attribute_store_get_first_child_by_type(parent, attr.first); + sl_status_t status = attribute_store_get_reported_string(str_node, + reported_c_str, + MAX_CHAR_SIZE); + + std::string message + = "Can't get string reported value of " + + std::string(attribute_store_get_type_name(attr.first)); + + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, status, message.c_str()); + + message = "Mismatch value for attribute : " + + std::string(attribute_store_get_type_name(attr.first)); + TEST_ASSERT_EQUAL_CHAR_ARRAY_MESSAGE(attr.second.c_str(), + reported_c_str, + attr.second.size() + 1, + message.c_str()); + } +} + +///////////////////////////////////////////////////// +// Test case +///////////////////////////////////////////////////// +// Stub functions +static sl_status_t + attribute_resolver_register_rule_stub(attribute_store_type_t node_type, + attribute_resolver_function_t set_func, + attribute_resolver_function_t get_func, + int cmock_num_calls) +{ + std::string message; + std::string node_name = attribute_store_get_type_name(node_type); + + if (attributes_binding.find(node_type) != attributes_binding.end()) { + const auto func = attributes_binding[node_type]; + + if (func.get_func_id == 0) { + message + = "GET function should not be defined for " + node_name + + ". Did you forget to add it to the attributes_binding variable ?"; + TEST_ASSERT_NULL_MESSAGE(get_func, message.c_str()); + } else { + message = "SET function should be defined for " + node_name + + ". Did you forget to remove it to the attributes_binding " + "variable ?"; + TEST_ASSERT_NOT_NULL_MESSAGE(get_func, message.c_str()); + resolver_functions[func.get_func_id] = get_func; + } + + if (func.set_func_id == 0) { + message + = "SET function should not be defined for " + node_name + + ". Did you forget to add it to the attributes_binding variable ?"; + TEST_ASSERT_NULL_MESSAGE(set_func, message.c_str()); + } else { + message = "SET function should be defined for " + node_name + + ". Did you forget to remove it to the attributes_binding " + "variable ?"; + TEST_ASSERT_NOT_NULL_MESSAGE(set_func, message.c_str()); + resolver_functions[func.set_func_id] = set_func; + } + } + + return SL_STATUS_OK; +} + +static sl_status_t zwave_command_handler_register_handler_stub( + zwave_command_handler_t new_command_class_handler, int cmock_num_calls) +{ + handler = new_command_class_handler; + + TEST_ASSERT_EQUAL(ZWAVE_CONTROLLER_ENCAPSULATION_SECURITY_2_ACCESS, + handler.minimal_scheme); + TEST_ASSERT_EQUAL(COMMAND_CLASS_USER_CREDENTIAL, handler.command_class); + TEST_ASSERT_EQUAL(1, handler.version); + TEST_ASSERT_NOT_NULL(handler.control_handler); + TEST_ASSERT_NULL(handler.support_handler); + TEST_ASSERT_FALSE(handler.manual_security_validation); + + return SL_STATUS_OK; +} + +/// Setup the test suite (called once before all test_xxx functions are called) +void suiteSetUp() +{ + datastore_init(":memory:"); + attribute_store_init(); + zpc_attribute_store_register_known_attribute_types(); +} + +/// Teardown the test suite (called once after all test_xxx functions are called) +int suiteTearDown(int num_failures) +{ + attribute_store_teardown(); + datastore_teardown(); + return num_failures; +} + +/// Called before each and every test +void setUp() +{ + zwave_network_management_get_node_id_IgnoreAndReturn(zpc_node_id); + zwave_network_management_get_home_id_IgnoreAndReturn(home_id); + zpc_attribute_store_test_helper_create_network(); + + // Unset previous definition get/set functions + for (auto &r: resolver_functions) { + r.second = NULL; + } + + memset(received_frame, 0, sizeof(received_frame)); + received_frame_size = 0; + // Unset previous definition of handler + memset(&handler, 0, sizeof(zwave_command_handler_t)); + + // Resolution functions + attribute_resolver_register_rule_Stub(&attribute_resolver_register_rule_stub); + + // Handler registration + zwave_command_handler_register_handler_Stub( + &zwave_command_handler_register_handler_stub); + // Call init + TEST_ASSERT_EQUAL(SL_STATUS_OK, zwave_command_class_user_credential_init()); +} + +/// Called after each and every test +void tearDown() {} + +//////////////////////////////////////////////////////////////////////////// +// User Capabilities Get/Report +//////////////////////////////////////////////////////////////////////////// +void test_user_credential_user_capabilities_get_happy_case() +{ + helper_test_get_no_args_happy_case(USER_CAPABILITIES_GET); +} + +void test_user_credential_user_capabilities_report_happy_case() +{ + uint16_t number_of_users; + user_credential_supported_credential_rules_t cred_rule_bitmask; + uint8_t username_max_length; + uint8_t support_user_schedule; + uint8_t support_all_users_checksum; + uint8_t support_user_checksum; + uint8_t supported_user_types_bitmask_length; + user_credential_supported_user_type_bitmask_t supported_user_types_bitmask; + + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + // Test reported values based on the variables in this function + auto test_reported_values = [&]() { + // Create frame object + std::vector report_frame = { + COMMAND_CLASS_USER_CREDENTIAL, + USER_CAPABILITIES_REPORT, + (uint8_t)((number_of_users & 0xFF00) >> 8), // MSB + (uint8_t)(number_of_users & 0x00FF), // LSB + cred_rule_bitmask, + username_max_length, + (uint8_t)((support_user_schedule << 7) | (support_all_users_checksum << 6) + | (support_user_checksum << 5)), + supported_user_types_bitmask_length}; + + // Add bitmask value + for (uint8_t i = 0; i < supported_user_types_bitmask_length; i++) { + uint8_t offset = i; + uint8_t shift = 8 * offset; + uint32_t bitmask = 0xFF << shift; + + uint8_t value_8bit = (supported_user_types_bitmask & bitmask) >> shift; + report_frame.push_back(value_8bit); + } + + // Do the report + TEST_ASSERT_EQUAL( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size())); + + // Define data structures + std::map uint8_attribute_map = { + {ATTRIBUTE(SUPPORTED_CREDENTIAL_RULES), cred_rule_bitmask}, + {ATTRIBUTE(MAX_USERNAME_LENGTH), username_max_length}, + {ATTRIBUTE(SUPPORT_USER_SCHEDULE), support_user_schedule}, + {ATTRIBUTE(SUPPORT_ALL_USERS_CHECKSUM), support_all_users_checksum}, + {ATTRIBUTE(SUPPORT_USER_CHECKSUM), support_user_checksum}, + }; + + std::map uint16_attribute_map = { + {ATTRIBUTE(NUMBER_OF_USERS), number_of_users}, + }; + std::map uint32_attribute_map = { + {ATTRIBUTE(SUPPORTED_USER_TYPES), supported_user_types_bitmask}, + }; + + // Test values + helper_test_attribute_store_values(uint8_attribute_map); + helper_test_attribute_store_values(uint16_attribute_map); + helper_test_attribute_store_values(uint32_attribute_map); + + attribute_store_node_t all_user_checksum_node + = attribute_store_get_node_child_by_type(endpoint_id_node, + ATTRIBUTE(ALL_USERS_CHECKSUM), + 0); + if (support_all_users_checksum) { + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + all_user_checksum_node, + "ALL_USERS_CHECKSUM node should exists " + "since all_user_checksum_node == true"); + } else { + TEST_ASSERT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + all_user_checksum_node, + "ALL_USERS_CHECKSUM node should NOT exists " + "since all_user_checksum_node == false"); + } + + // Remove node for next tests cases + attribute_store_delete_node(all_user_checksum_node); + }; + + printf("Test with first set of data\n"); + number_of_users = 1212; + cred_rule_bitmask = 0b00000111; + username_max_length = 12; + support_user_schedule = 1; + support_all_users_checksum = 1; + support_user_checksum = 1; + supported_user_types_bitmask_length = 2; + supported_user_types_bitmask = 0b111111111; + // Do the testing + test_reported_values(); + + printf("Test with second set of data\n"); + number_of_users = 1313; + cred_rule_bitmask = 0b00011111; + username_max_length = 21; + support_user_schedule = 0; + support_all_users_checksum = 0; + support_user_checksum = 0; + supported_user_types_bitmask_length = 4; + supported_user_types_bitmask = 0xFFFFFFFF; + // Do the testing + test_reported_values(); +} + +//////////////////////////////////////////////////////////////////////////// +// Credential Capabilities Get/Report +//////////////////////////////////////////////////////////////////////////// +void test_user_credential_credential_capabilities_get_happy_case() +{ + helper_test_get_no_args_happy_case(CREDENTIAL_CAPABILITIES_GET); +} + +void test_user_credential_credential_capabilities_report_happy_case() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + auto create_report_frame + = [&](uint8_t credential_checksum_support, + std::vector credential_type, + std::vector cl_support, + std::vector supported_credential_slots, + std::vector min_length, + std::vector max_length) { + std::vector report_frame + = {COMMAND_CLASS_USER_CREDENTIAL, + CREDENTIAL_CAPABILITIES_REPORT, + (uint8_t)(credential_checksum_support << 7), + (uint8_t)credential_type.size()}; + + auto push_uint16 = [&](std::vector vector) { + for (auto &v: vector) { + report_frame.push_back((uint8_t)((v & 0xFF00) >> 8)); + report_frame.push_back((uint8_t)(v & 0x00FF)); + } + }; + + for (auto &c: credential_type) { + report_frame.push_back(c); + } + + for (auto &cl: cl_support) { + report_frame.push_back((uint8_t)(cl << 7)); + } + + push_uint16(supported_credential_slots); + push_uint16(min_length); + push_uint16(max_length); + + return report_frame; + }; + + uint8_t credential_checksum_support = 1; + std::vector credential_type = {1, 3, 4, 5}; + std::vector cl_support = {1, 0, 0, 1}; + std::vector supported_credential_slots = {1233, 11233, 21233, 33}; + std::vector min_length = {2, 2362, 255, 1255}; + std::vector max_length = {5632, 15632, 25632, 32568}; + + auto test_report_values = [&]() { + uint8_t reported_credential_checksum_support; + attribute_store_node_t support_node + = attribute_store_get_first_child_by_type( + endpoint_id_node, + ATTRIBUTE(SUPPORT_CREDENTIAL_CHECKSUM)); + + TEST_ASSERT_NOT_EQUAL_MESSAGE( + ATTRIBUTE_STORE_INVALID_NODE, + support_node, + "credential checksum support node should be defined"); + + attribute_store_get_reported(support_node, + &reported_credential_checksum_support, + sizeof(reported_credential_checksum_support)); + + TEST_ASSERT_EQUAL_MESSAGE(credential_checksum_support, + reported_credential_checksum_support, + "Incorrect reported credential checksum support"); + + for (uint8_t i = 0; i < credential_type.size(); i++) { + printf("Testing credential batch %d\n", i); + + attribute_store_node_t type_node = attribute_store_get_node_child_by_type( + endpoint_id_node, + ATTRIBUTE(SUPPORTED_CREDENTIAL_TYPE), + i); + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + type_node, + "Credential type node should be defined"); + user_credential_type_t reported_credential; + + attribute_store_get_reported(type_node, + &reported_credential, + sizeof(reported_credential)); + + TEST_ASSERT_EQUAL_MESSAGE(credential_type[i], + reported_credential, + "Incorrect credential type"); + + std::map uint8_attribute_map = { + {ATTRIBUTE(CREDENTIAL_LEARN_READ_BACK_SUPPORT), cl_support[i]}, + }; + + std::map uint16_attribute_map = { + {ATTRIBUTE(CREDENTIAL_SUPPORTED_SLOT_COUNT), + supported_credential_slots[i]}, + {ATTRIBUTE(CREDENTIAL_MIN_LENGTH), min_length[i]}, + {ATTRIBUTE(CREDENTIAL_MAX_LENGTH), max_length[i]}, + }; + helper_test_attribute_store_values(uint8_attribute_map, type_node); + helper_test_attribute_store_values(uint16_attribute_map, type_node); + } + }; + + printf("Test with first set of data\n"); + auto report_frame = create_report_frame(credential_checksum_support, + credential_type, + cl_support, + supported_credential_slots, + min_length, + max_length); + + TEST_ASSERT_EQUAL( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size())); + + test_report_values(); + + printf("Test with second set of data\n"); + credential_checksum_support = 0; + credential_type = {6, 7, 8}; + cl_support = {0, 1, 1}; + supported_credential_slots = {15, 1565, 153}; + min_length = {155, 15, 5}; + max_length = {1111, 111, 11}; + report_frame = create_report_frame(credential_checksum_support, + credential_type, + cl_support, + supported_credential_slots, + min_length, + max_length); + TEST_ASSERT_EQUAL( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size())); + + test_report_values(); +} + +//////////////////////////////////////////////////////////////////////////// +// All User Checksum Get/Report +//////////////////////////////////////////////////////////////////////////// +void test_user_credential_all_users_checksum_get_happy_case() +{ + helper_test_get_no_args_happy_case(ALL_USERS_CHECKSUM_GET); +} + +void test_user_credential_all_users_checksum_report_happy_case() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + auto create_report_frame + = [&](user_credential_all_users_checksum_t checksum) { + std::vector report_frame + = {COMMAND_CLASS_USER_CREDENTIAL, ALL_USERS_CHECKSUM_REPORT}; + + auto exploded_checksum = explode_uint16(checksum); + + report_frame.push_back(exploded_checksum.msb); + report_frame.push_back(exploded_checksum.lsb); + + return report_frame; + }; + + user_credential_all_users_checksum_t expected_checksum = 0xABCD; + + auto report_frame = create_report_frame(expected_checksum); + + TEST_ASSERT_EQUAL( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size())); + + std::map uint16_attribute_map + = {{ATTRIBUTE(ALL_USERS_CHECKSUM), expected_checksum}}; + helper_test_attribute_store_values(uint16_attribute_map); +} + +//////////////////////////////////////////////////////////////////////////// +// User Set/Get/Report +//////////////////////////////////////////////////////////////////////////// +void test_user_credential_user_set_add_or_modify_happy_case() +{ + user_credential_user_unique_id_t user_id = 12121; + user_credential_operation_type_t operation_type = USER_SET_OPERATION_TYPE_ADD; + user_credential_type_t user_type = 5; + user_credential_rule_t credential_rule = 2; + user_credential_user_active_state_t user_active_state = 1; + user_credential_expiring_timeout_minutes_t expiring_timeout = 55; + user_credential_user_name_encoding_t user_name_encoding = 0; + std::string user_name = "DoUzE"; + + auto user_id_node = attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + + auto operation_type_node + = attribute_store_emplace_desired(user_id_node, + ATTRIBUTE(USER_OPERATION_TYPE), + &operation_type, + sizeof(operation_type)); + auto user_type_node = attribute_store_emplace_desired(user_id_node, + ATTRIBUTE(USER_TYPE), + &user_type, + sizeof(user_type)); + auto user_active_state_node + = attribute_store_emplace(user_id_node, + ATTRIBUTE(USER_ACTIVE_STATE), + &user_active_state, + sizeof(user_active_state)); + auto credential_rule_node + = attribute_store_emplace_desired(user_id_node, + ATTRIBUTE(CREDENTIAL_RULE), + &credential_rule, + sizeof(credential_rule)); + auto expiring_timeout_node + = attribute_store_emplace(user_id_node, + ATTRIBUTE(USER_EXPIRING_TIMEOUT_MINUTES), + &expiring_timeout, + sizeof(expiring_timeout)); + auto user_name_encoding_node + = attribute_store_emplace_desired(user_id_node, + ATTRIBUTE(USER_NAME_ENCODING), + &user_name_encoding, + sizeof(user_name_encoding)); + auto user_name_node + = attribute_store_add_node(ATTRIBUTE(USER_NAME), user_id_node); + + sl_status_t status + = attribute_store_set_reported_string(user_name_node, user_name.c_str()); + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, status, "Can't set username"); + + printf("Send with USER_SET_OPERATION_TYPE_MODIFY\n"); + helper_test_set_get_with_args( + USER_SET, + operation_type_node, + {{operation_type_node, DESIRED_ATTRIBUTE}, + {user_id_node, REPORTED_ATTRIBUTE}, + {user_type_node, DESIRED_ATTRIBUTE}, + {user_active_state_node, REPORTED_ATTRIBUTE}, + {credential_rule_node, DESIRED_ATTRIBUTE}, + {expiring_timeout_node, REPORTED_ATTRIBUTE}, + {user_name_encoding_node, DESIRED_ATTRIBUTE}, + {user_name_node, REPORTED_ATTRIBUTE}}); + + + printf("Send with USER_SET_OPERATION_TYPE_MODIFY\n"); + + operation_type = USER_SET_OPERATION_TYPE_ADD; + attribute_store_set_desired(operation_type_node, + &operation_type, + sizeof(operation_type)); + helper_test_set_get_with_args( + USER_SET, + operation_type_node, + {{operation_type_node, DESIRED_ATTRIBUTE}, + {user_id_node, REPORTED_ATTRIBUTE}, + {user_type_node, DESIRED_ATTRIBUTE}, + {user_active_state_node, REPORTED_ATTRIBUTE}, + {credential_rule_node, DESIRED_ATTRIBUTE}, + {expiring_timeout_node, REPORTED_ATTRIBUTE}, + {user_name_encoding_node, DESIRED_ATTRIBUTE}, + {user_name_node, REPORTED_ATTRIBUTE}}); +} + +void test_user_credential_user_delete_remove_happy_case() +{ + user_credential_user_unique_id_t user_id = 12121; + user_credential_operation_type_t operation_type = USER_SET_OPERATION_TYPE_DELETE; + + auto user_id_node = attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + + auto operation_type_node + = attribute_store_emplace_desired(user_id_node, + ATTRIBUTE(USER_OPERATION_TYPE), + &operation_type, + sizeof(operation_type)); + + helper_test_set_get_with_args(USER_SET, + operation_type_node, + {{operation_type_node, DESIRED_ATTRIBUTE}, + {user_id_node, REPORTED_ATTRIBUTE}}); +} + +void test_user_credential_user_set_invalid_node() +{ + helper_test_set_get_with_args(USER_SET, ATTRIBUTE_STORE_INVALID_NODE); +} + + +void test_user_credential_user_get_happy_case() +{ + user_credential_user_unique_id_t user_id = 12; + auto user_node = attribute_store_emplace_desired(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + + helper_test_set_get_with_args(USER_GET, + user_node, + {{user_node, DESIRED_ATTRIBUTE}}); +} + +void test_user_credential_user_get_not_found() +{ + helper_test_set_get_with_args(USER_GET, ATTRIBUTE_STORE_INVALID_NODE); +} + +std::vector helper_create_user_report_frame( + user_credential_user_unique_id_t next_user_id, + user_credential_user_modifier_type_t user_modifier_type, + user_credential_user_modifier_node_id_t user_modifier_node_id, + user_credential_user_unique_id_t user_id, + user_credential_user_type_t user_type, + user_credential_user_active_state_t user_active_state, + user_credential_supported_credential_rules_t credential_rule, + user_credential_expiring_timeout_minutes_t expiring_timeout_minutes, + user_credential_user_name_encoding_t user_name_encoding, + std::string user_name) +{ + std::vector report_frame + = {COMMAND_CLASS_USER_CREDENTIAL, USER_REPORT}; + + auto exploded_next_user_id = explode_uint16(next_user_id); + report_frame.push_back(exploded_next_user_id.msb); + report_frame.push_back(exploded_next_user_id.lsb); + + report_frame.push_back(user_modifier_type); + + auto exploded_user_modifier_node_id = explode_uint16(user_modifier_node_id); + report_frame.push_back(exploded_user_modifier_node_id.msb); + report_frame.push_back(exploded_user_modifier_node_id.lsb); + + auto exploded_user_id = explode_uint16(user_id); + report_frame.push_back(exploded_user_id.msb); + report_frame.push_back(exploded_user_id.lsb); + + report_frame.push_back(user_type); + report_frame.push_back(user_active_state); + report_frame.push_back(credential_rule); + + auto exploded_time = explode_uint16(expiring_timeout_minutes); + report_frame.push_back(exploded_time.msb); + report_frame.push_back(exploded_time.lsb); + + report_frame.push_back(user_name_encoding); + report_frame.push_back(user_name.size()); + for (char c: user_name) { + report_frame.push_back(c); + } + return report_frame; +}; + +void test_user_credential_user_report_happy_case() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + auto user_get = resolver_functions[USER_GET]; + TEST_ASSERT_NOT_NULL_MESSAGE(user_get, "User get function should be defined"); + + constexpr user_credential_user_unique_id_t EXPECTED_FIRST_USER_ID = 12; + constexpr user_credential_user_unique_id_t EXPECTED_SECOND_USER_ID = 1212; + + user_credential_user_unique_id_t next_user_id = EXPECTED_SECOND_USER_ID; + user_credential_user_modifier_type_t user_modifier_type = 2; + user_credential_user_modifier_node_id_t user_modifier_node_id = 1313; + user_credential_user_unique_id_t user_id = EXPECTED_FIRST_USER_ID; + user_credential_user_type_t user_type = 3; + user_credential_user_active_state_t user_active_state = 1; + user_credential_supported_credential_rules_t credential_rule = 2; + user_credential_expiring_timeout_minutes_t expiring_timeout_minutes = 1515; + user_credential_user_name_encoding_t user_name_encoding = 0; + std::string user_name = "DoUzE"; + + auto test_user_values = [&](attribute_store_node_t user_node) { + // Check main node value + user_credential_user_unique_id_t reported_id; + attribute_store_get_reported(user_node, &reported_id, sizeof(reported_id)); + TEST_ASSERT_EQUAL_MESSAGE(user_id, + reported_id, + "User Unique ID is incorrect"); + // Then test values underneath + std::map uint8_attribute_map + = {{ATTRIBUTE(USER_MODIFIER_TYPE), user_modifier_type}, + {ATTRIBUTE(USER_TYPE), user_type}, + {ATTRIBUTE(USER_ACTIVE_STATE), user_active_state}, + {ATTRIBUTE(CREDENTIAL_RULE), credential_rule}}; + helper_test_attribute_store_values(uint8_attribute_map, user_node); + + std::map uint16_attribute_map + = {{ATTRIBUTE(USER_MODIFIER_NODE_ID), user_modifier_node_id}, + {ATTRIBUTE(USER_EXPIRING_TIMEOUT_MINUTES), expiring_timeout_minutes}}; + helper_test_attribute_store_values(uint16_attribute_map, user_node); + + helper_test_string_value({{ATTRIBUTE(USER_NAME), user_name}}, user_node); + + // Check user credential desired values + user_credential_type_t credential_type = 1; + user_credential_slot_t credential_slot = 12; + auto credential_type_node + = attribute_store_get_node_child_by_type(user_node, + ATTRIBUTE(CREDENTIAL_TYPE), + 0); + attribute_store_get_reported(credential_type_node, + &credential_type, + sizeof(credential_type)); + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + credential_type_node, + "credential_type_node should exist"); + + auto credential_slot_node + = attribute_store_get_node_child_by_type(credential_type_node, + ATTRIBUTE(CREDENTIAL_SLOT), + 0); + attribute_store_get_desired(credential_slot_node, + &credential_slot, + sizeof(credential_slot)); + + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + credential_slot_node, + "credential_slot_node should exist"); + + TEST_ASSERT_EQUAL_MESSAGE( + 0, + credential_type, + "Credential Type should be at 0 and should exists"); + TEST_ASSERT_EQUAL_MESSAGE( + 0, + credential_slot, + "Credential Slot should be at 0 and should exists"); + }; // end test_user_values lambda + + auto report_frame = helper_create_user_report_frame(next_user_id, + user_modifier_type, + user_modifier_node_id, + user_id, + user_type, + user_active_state, + credential_rule, + expiring_timeout_minutes, + user_name_encoding, + user_name); + + printf("First user creation\n"); + + // Create first user + auto first_user_id_node + = attribute_store_add_node(ATTRIBUTE(USER_UNIQUE_ID), endpoint_id_node); + // First user should have ID 0 and then set in the report + user_credential_user_unique_id_t first_id = 0; + attribute_store_set_desired(first_user_id_node, &first_id, sizeof(first_id)); + + // Simulate get on that user + sl_status_t user_get_status + = user_get(first_user_id_node, received_frame, &received_frame_size); + + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, + user_get_status, + "User get should have returned SL_STATUS_OK"); + + // Call report + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "User Report should have returned SL_STATUS_OK"); + // Test values + test_user_values(first_user_id_node); + + // Test structure + auto user_id_count + = attribute_store_get_node_child_count_by_type(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID)); + TEST_ASSERT_EQUAL_MESSAGE(2, + user_id_count, + "User node count mismatch. Should be 2 : 1 for " + "the reported one and 1 for the next one"); + auto second_user_id_node + = attribute_store_get_node_child_by_type(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + 1); + + printf("Second and last user creation\n"); + + // Simulate a node with desired value of 0 to see if it is properly removed + // to prevent infinite loop + user_credential_user_unique_id_t id_zero = 0; + attribute_store_emplace_desired(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &id_zero, + sizeof(id_zero)); + + // Check second & not defined user id + user_credential_user_unique_id_t second_user_id; + attribute_store_get_desired(second_user_id_node, + &second_user_id, + sizeof(second_user_id)); + TEST_ASSERT_EQUAL_MESSAGE( + next_user_id, + second_user_id, + "Second user id should have it's desired value defined"); + + // Simulate a GET on that user + // Removes the desired state and sets to reported instead + user_get_status + = user_get(second_user_id_node, received_frame, &received_frame_size); + + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, + user_get_status, + "User get should have returned SL_STATUS_OK"); + + // Second and last user + next_user_id = 0; + user_modifier_type = 3; + user_modifier_node_id = 1414; + user_id = EXPECTED_SECOND_USER_ID; + user_type = 5; + user_active_state = 0; + credential_rule = 1; + user_name_encoding = 1; + user_name = "NoDoUzE4YoU"; + + report_frame = helper_create_user_report_frame(next_user_id, + user_modifier_type, + user_modifier_node_id, + user_id, + user_type, + user_active_state, + credential_rule, + expiring_timeout_minutes, + user_name_encoding, + user_name); + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "Second User Report should have returned SL_STATUS_OK"); + // Test values + test_user_values(second_user_id_node); + + // Test structure + + // The report function should not have created an other user unique id node (next_user_id = 0) + user_id_count + = attribute_store_get_node_child_count_by_type(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID)); + TEST_ASSERT_EQUAL_MESSAGE(2, + user_id_count, + "User node count mismatch. Should be 2 : 1 for " + "the reported one and 1 for the next one"); + + user_credential_user_unique_id_t reported_id; + attribute_store_get_reported(first_user_id_node, + &reported_id, + sizeof(reported_id)); + TEST_ASSERT_EQUAL_MESSAGE(EXPECTED_FIRST_USER_ID, + reported_id, + "First user id mismatch"); + attribute_store_get_reported(second_user_id_node, + &reported_id, + sizeof(reported_id)); + TEST_ASSERT_EQUAL_MESSAGE(EXPECTED_SECOND_USER_ID, + reported_id, + "Second user id mismatch"); +} + +void test_user_credential_user_report_user_not_found() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + auto report_frame + = helper_create_user_report_frame(0, 0, 0, 12, 0, 0, 0, 0, 0, "^^"); + + // Not found since we haven't created any user_unique_id + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "User Report should have returned SL_STATUS_NOT_SUPPORTED"); + + // Check that no user_unique_id node has been created + auto user_id_count + = attribute_store_get_node_child_count_by_type(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID)); + TEST_ASSERT_EQUAL_MESSAGE(0, + user_id_count, + "No user_unique_id node should be created"); +} + +void test_user_credential_user_report_user_with_id0() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + auto report_frame + = helper_create_user_report_frame(0, 0, 0, 0, 0, 0, 0, 0, 0, "DOUZE"); + + // Create user id with reported ID of 0 to simulate user get of id 0 + user_credential_user_unique_id_t user_id = 0; + attribute_store_emplace_desired(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + + // Not found since we haven't created any user_unique_id + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "User Report should have returned SL_STATUS_OK and ignore the frame"); + + // Check that no user_unique_id node has been created (and the invalid node was deleted) + auto user_id_count + = attribute_store_get_node_child_count_by_type(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID)); + TEST_ASSERT_EQUAL_MESSAGE(0, + user_id_count, + "No user_unique_id node should be created"); +} + +void test_user_credential_user_report_user_deleted() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + // Create user id with reported ID of 0 to simulate user get of id 0 + user_credential_user_unique_id_t user_id = 12; + auto user_id_node = attribute_store_emplace_desired(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + user_id_node, + "User ID node should be created"); + + auto user_id_count + = attribute_store_get_node_child_count_by_type(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID)); + TEST_ASSERT_EQUAL_MESSAGE(1, user_id_count, "Should have one user by now"); + + auto report_frame = helper_create_user_report_frame(user_id, + USER_REPORT_DNE, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + "????"); + + // Should delete this user + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "User Report should have returned SL_STATUS_NOT_SUPPORTED"); + + user_id_count + = attribute_store_get_node_child_count_by_type(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID)); + TEST_ASSERT_EQUAL_MESSAGE(1, + user_id_count, + "Should not have any user by now"); +} + +//////////////////////////////////////////////////////////////////////////// +// Credential Set/Get/Report +//////////////////////////////////////////////////////////////////////////// + +//>> Set +void test_user_credential_credential_set_1byte_happy_case() +{ + user_credential_type_t credential_type = 2; + user_credential_slot_t credential_slot = 1212; + user_credential_operation_type_t operation_type = USER_CREDENTIAL_OPERATION_TYPE_ADD; + std::vector credential_data = {12}; + + // Create the node with reported attribute + auto nodes = helper_create_credential_structure(12, + credential_type, + credential_slot, + REPORTED_ATTRIBUTE); + auto user_node = nodes.user_id_node; + auto credential_type_node = nodes.credential_type_node; + auto credential_slot_node = nodes.credential_slot_node; + + // Operation type + auto operation_type_node + = attribute_store_emplace_desired(credential_slot_node, + ATTRIBUTE(CREDENTIAL_OPERATION_TYPE), + &operation_type, + sizeof(operation_type)); + // CREDENTIAL_DATA + uint8_t credential_data_length = credential_data.size(); + auto credential_data_length_node + = attribute_store_emplace_desired(credential_slot_node, + ATTRIBUTE(CREDENTIAL_DATA_LENGTH), + &credential_data_length, + sizeof(uint8_t)); + + auto credential_data_node + = attribute_store_emplace_desired(credential_data_length_node, + ATTRIBUTE(CREDENTIAL_DATA), + credential_data.data(), + credential_data.size()); + + helper_test_set_get_with_args(CREDENTIAL_SET, + operation_type_node, + { + {user_node, REPORTED_ATTRIBUTE}, + {credential_type_node, REPORTED_ATTRIBUTE}, + {credential_slot_node, REPORTED_ATTRIBUTE}, + {operation_type_node, DESIRED_ATTRIBUTE}, + {credential_data_node, DESIRED_ATTRIBUTE}, + }); +} + +void test_user_credential_credential_set_12byte_happy_case() +{ + user_credential_type_t credential_type = 2; + user_credential_slot_t credential_slot = 1212; + user_credential_operation_type_t operation_type = USER_CREDENTIAL_OPERATION_TYPE_MODIFY; + std::vector credential_data + = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + + // Create the node with reported attribute + auto nodes = helper_create_credential_structure(12, + credential_type, + credential_slot, + REPORTED_ATTRIBUTE); + auto user_node = nodes.user_id_node; + auto credential_type_node = nodes.credential_type_node; + auto credential_slot_node = nodes.credential_slot_node; + + // Operation type + auto operation_type_node + = attribute_store_emplace_desired(credential_slot_node, + ATTRIBUTE(CREDENTIAL_OPERATION_TYPE), + &operation_type, + sizeof(operation_type)); + // CREDENTIAL_DATA + uint8_t credential_data_length = credential_data.size(); + auto credential_data_length_node + = attribute_store_emplace(credential_slot_node, + ATTRIBUTE(CREDENTIAL_DATA_LENGTH), + &credential_data_length, + sizeof(uint8_t)); + + auto credential_data_node + = attribute_store_emplace(credential_data_length_node, + ATTRIBUTE(CREDENTIAL_DATA), + credential_data.data(), + credential_data.size()); + + helper_test_set_get_with_args(CREDENTIAL_SET, + operation_type_node, + { + {user_node, REPORTED_ATTRIBUTE}, + {credential_type_node, REPORTED_ATTRIBUTE}, + {credential_slot_node, REPORTED_ATTRIBUTE}, + {operation_type_node, DESIRED_ATTRIBUTE}, + {credential_data_node, REPORTED_ATTRIBUTE}, + }); +} + +void test_user_credential_credential_set_invalid_node() +{ + helper_test_set_get_with_args(CREDENTIAL_SET, ATTRIBUTE_STORE_INVALID_NODE); +} + +void test_user_credential_credential_set_delete_happy_case() +{ + user_credential_type_t credential_type = 2; + user_credential_slot_t credential_slot = 1212; + user_credential_operation_type_t operation_type = USER_CREDENTIAL_OPERATION_TYPE_DELETE; + + // Create the node with reported attribute + auto nodes = helper_create_credential_structure(12, + credential_type, + credential_slot, + REPORTED_ATTRIBUTE); + auto user_node = nodes.user_id_node; + auto credential_type_node = nodes.credential_type_node; + auto credential_slot_node = nodes.credential_slot_node; + + // Operation type + auto operation_type_node + = attribute_store_emplace_desired(credential_slot_node, + ATTRIBUTE(CREDENTIAL_OPERATION_TYPE), + &operation_type, + sizeof(operation_type)); + + helper_test_set_get_with_args(CREDENTIAL_SET, + operation_type_node, + { + {user_node, REPORTED_ATTRIBUTE}, + {credential_type_node, REPORTED_ATTRIBUTE}, + {credential_slot_node, REPORTED_ATTRIBUTE}, + {operation_type_node, DESIRED_ATTRIBUTE}, + }, + {0x00} // Credential data length + ); +} + + +//>> Get + +void test_user_credential_credential_get_happy_case() +{ + user_credential_type_t credential_type = 2; + user_credential_slot_t credential_slot = 1212; + + auto nodes + = helper_create_credential_structure(12, credential_type, credential_slot); + auto user_node = nodes.user_id_node; + auto credential_type_node = nodes.credential_type_node; + auto credential_slot_node = nodes.credential_slot_node; + + // Get should make both credential_type_node and credential_slot_node reported value as the desired values + helper_test_set_get_with_args(CREDENTIAL_GET, + credential_slot_node, + { + {user_node, REPORTED_ATTRIBUTE}, + {credential_type_node, REPORTED_ATTRIBUTE}, + {credential_slot_node, DESIRED_ATTRIBUTE}, + }); +} + +void test_user_credential_credential_get_no_credential_type() +{ + helper_test_set_get_with_args(CREDENTIAL_GET, ATTRIBUTE_STORE_INVALID_NODE); +} + +std::vector helper_create_credential_report_frame( + user_credential_user_unique_id_t user_id, + user_credential_type_t credential_type, + user_credential_slot_t credential_slot, + uint8_t crb, + std::vector credential_data, + user_credential_user_modifier_type_t credential_modifier_type, + user_credential_user_modifier_node_id_t credential_modifier_node_id, + user_credential_type_t next_credential_type, + user_credential_slot_t next_credential_slot) +{ + std::vector report_frame + = {COMMAND_CLASS_USER_CREDENTIAL, CREDENTIAL_REPORT}; + + auto exploded_user_id = explode_uint16(user_id); + report_frame.push_back(exploded_user_id.msb); + report_frame.push_back(exploded_user_id.lsb); + + report_frame.push_back(credential_type); + + auto exploded_credential_slot = explode_uint16(credential_slot); + report_frame.push_back(exploded_credential_slot.msb); + report_frame.push_back(exploded_credential_slot.lsb); + + report_frame.push_back(crb << 7); + report_frame.push_back(credential_data.size()); + + for (uint8_t credential_value: credential_data) { + report_frame.push_back(credential_value); + } + + report_frame.push_back(credential_modifier_type); + auto exploded_credential_modifier_node_id + = explode_uint16(credential_modifier_node_id); + report_frame.push_back(exploded_credential_modifier_node_id.msb); + report_frame.push_back(exploded_credential_modifier_node_id.lsb); + + report_frame.push_back(next_credential_type); + auto exploded_next_credential_slot = explode_uint16(next_credential_slot); + report_frame.push_back(exploded_next_credential_slot.msb); + report_frame.push_back(exploded_next_credential_slot.lsb); + + return report_frame; +}; + +void test_user_credential_credential_report_no_credential() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + user_credential_user_unique_id_t user_id = 12; + auto user_id_node = attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + user_id_node, + "User ID node should be defined"); + + auto report_frame + = helper_create_credential_report_frame(user_id, 0, 0, 0, {}, 0, 0, 0, 0); + + // SHould return ok since credential_type and credential node is 0 + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "Credential Report should have returned SL_STATUS_NOT_SUPPORTED"); +} + +void test_user_credential_credential_report_missing_user() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + auto report_frame + = helper_create_credential_report_frame(12, 1, 1, 0, {}, 0, 0, 0, 0); + + // Not found since we haven't created any user_unique_id + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "Credential Report should have returned SL_STATUS_NOT_SUPPORTED"); +} + +void test_user_credential_credential_report_missing_credential_type() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + user_credential_user_unique_id_t user_id = 12; + auto user_id_node = attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + user_id_node, + "User ID node should be defined"); + + auto report_frame + = helper_create_credential_report_frame(user_id, 1, 5, 0, {}, 0, 0, 0, 0); + + // Not found since we haven't created any credential_type + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "Credential Report should have returned SL_STATUS_NOT_SUPPORTED"); +} + +void test_user_credential_credential_report_missing_credential_slot() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + user_credential_user_unique_id_t user_id = 12; + auto user_id_node = attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + user_id_node, + "User ID node should be defined"); + user_credential_type_t credential_type = 121; + auto credential_type_node + = attribute_store_emplace(user_id_node, + ATTRIBUTE(CREDENTIAL_TYPE), + &credential_type, + sizeof(credential_type)); + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + credential_type_node, + "Credential type node should be defined"); + + auto report_frame = helper_create_credential_report_frame(user_id, + credential_type, + 1212, + 0, + {}, + 0, + 0, + 0, + 0); + + // Not found since we haven't created any credential_slot + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_NOT_SUPPORTED, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "Credential Report should have returned SL_STATUS_NOT_SUPPORTED"); +} + +void test_user_credential_credential_report_happy_case() +{ + zwave_controller_connection_info_t info = {}; + info.remote.node_id = node_id; + info.remote.endpoint_id = endpoint_id; + info.local.is_multicast = false; + + auto credential_get = resolver_functions[CREDENTIAL_GET]; + TEST_ASSERT_NOT_NULL_MESSAGE(credential_get, + "Credential get function should be defined"); + + user_credential_user_unique_id_t user_id = 122; + user_credential_type_t credential_type = 12; + user_credential_slot_t credential_slot = 1212; + uint8_t crb = 1; + std::vector credential_data = {12, 13, 14, 15, 16}; + user_credential_user_modifier_type_t credential_modifier_type = 13; + user_credential_user_modifier_node_id_t credential_modifier_node_id = 1312; + user_credential_type_t next_credential_type = 12; + user_credential_slot_t next_credential_slot = 1; + + auto test_user_values = [&](attribute_store_node_t credential_type_node, + attribute_store_node_t credential_slot_node) { + // Check main node value + user_credential_type_t reported_cred_type; + attribute_store_get_reported(credential_type_node, + &reported_cred_type, + sizeof(reported_cred_type)); + TEST_ASSERT_EQUAL_MESSAGE(credential_type, + reported_cred_type, + "Credential type value mismatch"); + + user_credential_slot_t reported_cred_slot; + attribute_store_get_reported(credential_slot_node, + &reported_cred_slot, + sizeof(reported_cred_slot)); + TEST_ASSERT_EQUAL_MESSAGE(credential_slot, + reported_cred_slot, + "Credential slot value mismatch"); + + // Then test values underneath + std::map uint8_attribute_map + = {{ATTRIBUTE(CREDENTIAL_MODIFIER_TYPE), credential_modifier_type}, + {ATTRIBUTE(CREDENTIAL_READ_BACK), crb}}; + helper_test_attribute_store_values(uint8_attribute_map, + credential_slot_node); + + std::map uint16_attribute_map + = {{ATTRIBUTE(CREDENTIAL_MODIFIER_NODE_ID), credential_modifier_node_id}}; + helper_test_attribute_store_values(uint16_attribute_map, + credential_slot_node); + + auto credential_length_node = attribute_store_get_first_child_by_type( + credential_slot_node, + ATTRIBUTE(CREDENTIAL_DATA_LENGTH)); + uint8_t reported_credential_length; + attribute_store_get_reported(credential_length_node, + &reported_credential_length, + sizeof(reported_credential_length)); + TEST_ASSERT_EQUAL_MESSAGE(credential_data.size(), + reported_credential_length, + "Credential data length value mismatch"); + + std::vector reported_credential_data; + reported_credential_data.resize(reported_credential_length); + + attribute_store_get_child_reported(credential_length_node, + ATTRIBUTE(CREDENTIAL_DATA), + reported_credential_data.data(), + reported_credential_length); + + TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(credential_data.data(), + reported_credential_data.data(), + credential_data.size(), + "Credential data mismatch"); + }; // end test_user_values lambda + + auto check_credentials_node_count = + [](attribute_store_node_t user_id_node, + size_t expected_credential_type_count, + size_t expected_credential_slot_count) { + auto count_credential_type = attribute_store_get_node_child_count_by_type( + user_id_node, + ATTRIBUTE(CREDENTIAL_TYPE)); + TEST_ASSERT_EQUAL_MESSAGE(expected_credential_type_count, + count_credential_type, + "Incorrect credential type count"); + + size_t count_credential_slot = 0; + for (size_t i = 0; i < count_credential_type; i++) { + auto credential_type_node + = attribute_store_get_node_child_by_type(user_id_node, + ATTRIBUTE(CREDENTIAL_TYPE), + i); + count_credential_slot += attribute_store_get_node_child_count_by_type( + credential_type_node, + ATTRIBUTE(CREDENTIAL_SLOT)); + } + + TEST_ASSERT_EQUAL_MESSAGE(expected_credential_slot_count, + count_credential_slot, + "Incorrect credential slot count"); + }; + + auto check_next_credential_node_value + = [&](attribute_store_node_t next_credential_type_node, + attribute_store_node_t next_credential_slot_node) { + // First check node existence + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + next_credential_type_node, + "next_credential_type_node should exist"); + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + next_credential_slot_node, + "next_credential_slot_node should exist"); + + user_credential_type_t reported_next_credential_type = 0; + user_credential_slot_t reported_next_credential_slot = 0; + + attribute_store_read_value(next_credential_type_node, + REPORTED_ATTRIBUTE, + &reported_next_credential_type, + sizeof(reported_next_credential_type)); + + attribute_store_read_value(next_credential_slot_node, + DESIRED_ATTRIBUTE, + &reported_next_credential_slot, + sizeof(reported_next_credential_slot)); + + TEST_ASSERT_EQUAL_MESSAGE(next_credential_type, + reported_next_credential_type, + "Next Credential Type value mismatch"); + TEST_ASSERT_EQUAL_MESSAGE(next_credential_slot, + reported_next_credential_slot, + "Next Credential Slot value mismatch"); + }; + + //>>>> First credential + printf("First credential creation\n"); + + auto report_frame + = helper_create_credential_report_frame(user_id, + credential_type, + credential_slot, + crb, + credential_data, + credential_modifier_type, + credential_modifier_node_id, + next_credential_type, + next_credential_slot); + + // Create first credential + auto nodes + = helper_create_credential_structure(user_id, 0, 0, DESIRED_ATTRIBUTE); + + auto user_id_node = nodes.user_id_node; + auto first_credential_type_node = nodes.credential_type_node; + auto first_credential_slot_node = nodes.credential_slot_node; + + attribute_store_log(); + + // Simulate get on that credential + sl_status_t credential_get_status = credential_get(first_credential_slot_node, + received_frame, + &received_frame_size); + + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + credential_get_status, + "First Credential get should have returned SL_STATUS_OK"); + + // Call report + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "First Credential Report should have returned SL_STATUS_OK"); + // Test values + test_user_values(first_credential_type_node, first_credential_slot_node); + // We should have 1 credential type and 2 credential slot + check_credentials_node_count(user_id_node, 1, 2); + + // Test if next credentials are well defined + auto second_credential_slot_node + = attribute_store_get_node_child_by_type(first_credential_type_node, + ATTRIBUTE(CREDENTIAL_SLOT), + 1); + check_next_credential_node_value(first_credential_type_node, + second_credential_slot_node); + + //>>>> Second credential + printf("Second credential creation\n"); + + credential_type = next_credential_type; + credential_slot = next_credential_slot; + crb = 0; + credential_data = {1, 2}; + credential_modifier_type = 3; + credential_modifier_node_id = 13121; + next_credential_type = 121; + next_credential_slot = 3; + + report_frame + = helper_create_credential_report_frame(user_id, + credential_type, + credential_slot, + crb, + credential_data, + credential_modifier_type, + credential_modifier_node_id, + next_credential_type, + next_credential_slot); + + // Simulate get on that credential + credential_get_status = credential_get(second_credential_slot_node, + received_frame, + &received_frame_size); + + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, + credential_get_status, + "Credential get should have returned SL_STATUS_OK"); + + // Call report + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "Second Credential Report should have returned SL_STATUS_OK"); + // Test values + test_user_values(first_credential_type_node, second_credential_slot_node); + // We should have 2 credential type and 2 credential slot + check_credentials_node_count(user_id_node, 2, 3); + + auto second_credential_type_node + = attribute_store_get_node_child_by_type(user_id_node, + ATTRIBUTE(CREDENTIAL_TYPE), + 1); + auto third_credential_slot_node + = attribute_store_get_node_child_by_type(second_credential_type_node, + ATTRIBUTE(CREDENTIAL_SLOT), + 0); + + // New nodes so we check their desired values + check_next_credential_node_value(second_credential_type_node, + third_credential_slot_node); + + //>>>> Third credential + printf("Third and last credential creation\n"); + credential_type = next_credential_type; + credential_slot = next_credential_slot; + crb = 1; + credential_data = {15}; + credential_modifier_type = 2; + credential_modifier_node_id = 12; + next_credential_type = 0; + next_credential_slot = 0; + + report_frame + = helper_create_credential_report_frame(user_id, + credential_type, + credential_slot, + crb, + credential_data, + credential_modifier_type, + credential_modifier_node_id, + next_credential_type, + next_credential_slot); + + // Simulate get on that credential + credential_get_status = credential_get(third_credential_slot_node, + received_frame, + &received_frame_size); + + TEST_ASSERT_EQUAL_MESSAGE(SL_STATUS_OK, + credential_get_status, + "Credential get should have returned SL_STATUS_OK"); + + // Call report + TEST_ASSERT_EQUAL_MESSAGE( + SL_STATUS_OK, + handler.control_handler(&info, report_frame.data(), report_frame.size()), + "Second Credential Report should have returned SL_STATUS_OK"); + // Test values + test_user_values(second_credential_type_node, third_credential_slot_node); + // We should still have 2 credential type and 3 credential slots since it was the last credential + check_credentials_node_count(user_id_node, 2, 3); +} + +//////////////////////////////////////////////////////////////////////////// +// Attribute creation +//////////////////////////////////////////////////////////////////////////// +void test_attribute_creation_happy_case() +{ + zwave_cc_version_t version = 1; + attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(VERSION), + &version, + sizeof(version)); + + std::vector created_types = { + ATTRIBUTE(NUMBER_OF_USERS), + ATTRIBUTE(SUPPORT_CREDENTIAL_CHECKSUM), + }; + + for (auto &type: created_types) { + const std::string error_msg + = "Attribute " + std::string(attribute_store_get_type_name(type)) + + " should exist"; + + TEST_ASSERT_NOT_EQUAL_MESSAGE( + ATTRIBUTE_STORE_INVALID_NODE, + attribute_store_get_node_child_by_type(endpoint_id_node, type, 0), + error_msg.c_str()); + } +} + +void test_attribute_creation_no_version() +{ + zwave_cc_version_t version = 0; + attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(VERSION), + &version, + sizeof(version)); + + std::vector created_types = { + ATTRIBUTE(NUMBER_OF_USERS), + ATTRIBUTE(SUPPORT_CREDENTIAL_CHECKSUM), + }; + + for (auto &type: created_types) { + const std::string error_msg + = "Attribute " + std::string(attribute_store_get_type_name(type)) + + " should NOT exist"; + + TEST_ASSERT_EQUAL_MESSAGE( + ATTRIBUTE_STORE_INVALID_NODE, + attribute_store_get_node_child_by_type(endpoint_id_node, type, 0), + error_msg.c_str()); + } +} + +void test_post_interview_discovery() +{ + // Lambdas + auto count_user_node = [](size_t expected_value) { + auto user_id_node_count + = attribute_store_get_node_child_count_by_type(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID)); + TEST_ASSERT_EQUAL_MESSAGE(expected_value, + user_id_node_count, + "User ID node count mismatch"); + }; + + // Test logic + + NodeStateNetworkStatus network_status = ZCL_NODE_STATE_NETWORK_STATUS_OFFLINE; + auto network_status_node + = attribute_store_emplace(node_id_node, + DOTDOT_ATTRIBUTE_ID_STATE_NETWORK_STATUS, + &network_status, + sizeof(network_status)); + + count_user_node(0); + + // Interview state + network_status = ZCL_NODE_STATE_NETWORK_STATUS_ONLINE_INTERVIEWING; + attribute_store_set_reported(network_status_node, + &network_status, + sizeof(network_status)); + + count_user_node(0); + + // Simulate one more endpoint and only add nif to one of them + + // Add NIF to our endpoint to tell that we support User Credential + std::vector nif_value = {COMMAND_CLASS_USER_CREDENTIAL}; + attribute_store_emplace(endpoint_id_node, + ATTRIBUTE_ZWAVE_SECURE_NIF, + nif_value.data(), + nif_value.size()); + + // Add another endpoint without User Credential support + zwave_endpoint_id_t endpoint_id_2 = endpoint_id + 12; + attribute_store_emplace(node_id_node, + ATTRIBUTE_ENDPOINT_ID, + &endpoint_id_2, + sizeof(endpoint_id_2)); + + // Display current store log for debug purpose + attribute_store_log(); + + // Set two random user id to see if discovery remove it + user_credential_user_unique_id_t user_id = 12; + attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + user_id = 1222; + attribute_store_emplace(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID), + &user_id, + sizeof(user_id)); + + // Set the network status to online + network_status = ZCL_NODE_STATE_NETWORK_STATUS_ONLINE_FUNCTIONAL; + attribute_store_set_reported(network_status_node, + &network_status, + sizeof(network_status)); + + // Now we check attribute store + auto user_id_node + = attribute_store_get_first_child_by_type(endpoint_id_node, + ATTRIBUTE(USER_UNIQUE_ID)); + + attribute_store_get_desired(user_id_node, &user_id, sizeof(user_id)); + TEST_ASSERT_NOT_EQUAL_MESSAGE(ATTRIBUTE_STORE_INVALID_NODE, + user_id_node, + "User id node should exists"); + TEST_ASSERT_EQUAL_MESSAGE(0, user_id, "User ID desired value should be 0"); + count_user_node(1); +} + +} // extern "C" \ No newline at end of file