diff --git a/esphome/components/hmac_md5/hmac_md5.h b/esphome/components/hmac_md5/hmac_md5.h index b83b9d5421..fb9479e3af 100644 --- a/esphome/components/hmac_md5/hmac_md5.h +++ b/esphome/components/hmac_md5/hmac_md5.h @@ -30,7 +30,7 @@ class HmacMD5 { void get_bytes(uint8_t *output); /// Retrieve the HMAC-MD5 digest as hex characters. - /// The output must be able to hold 32 bytes or more. + /// The output must be able to hold 33 bytes or more (32 hex chars + null terminator). void get_hex(char *output); /// Compare the digest against a provided byte-encoded digest (16 bytes). diff --git a/esphome/components/hmac_sha256/hmac_sha256.h b/esphome/components/hmac_sha256/hmac_sha256.h index fa6b64aa94..85622cac46 100644 --- a/esphome/components/hmac_sha256/hmac_sha256.h +++ b/esphome/components/hmac_sha256/hmac_sha256.h @@ -35,7 +35,7 @@ class HmacSHA256 { void get_bytes(uint8_t *output); /// Retrieve the HMAC-SHA256 digest as hex characters. - /// The output must be able to hold 64 bytes or more. + /// The output must be able to hold 65 bytes or more (64 hex chars + null terminator). void get_hex(char *output); /// Compare the digest against a provided byte-encoded digest (32 bytes). diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 12b14be9ff..a74d10b7ea 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -20,6 +20,8 @@ static const char *const TAG = "tuya"; static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; static const int MAX_RETRIES = 5; +// Max bytes to log for datapoint values (larger values are truncated) +static constexpr size_t MAX_DATAPOINT_LOG_BYTES = 16; void Tuya::setup() { this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); @@ -122,8 +124,11 @@ bool Tuya::validate_message_() { // valid message const uint8_t *message_data = data + 6; +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, - format_hex_pretty(message_data, length).c_str(), static_cast(this->init_state_)); + format_hex_pretty_to(hex_buf, message_data, length), static_cast(this->init_state_)); +#endif this->handle_command_(command, version, message_data, length); // return false to reset rx buffer @@ -349,7 +354,11 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { switch (datapoint.type) { case TuyaDatapointType::RAW: datapoint.value_raw = std::vector(data, data + data_size); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); + { + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, + format_hex_pretty_to(hex_buf, datapoint.value_raw.data(), datapoint.value_raw.size())); + } break; case TuyaDatapointType::BOOLEAN: if (data_size != 1) { @@ -460,8 +469,12 @@ void Tuya::send_raw_command_(TuyaCommand command) { break; } +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast(command.cmd), - version, format_hex_pretty(command.payload).c_str(), static_cast(this->init_state_)); + version, format_hex_pretty_to(hex_buf, command.payload.data(), command.payload.size()), + static_cast(this->init_state_)); +#endif this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo}); if (!command.payload.empty()) @@ -675,7 +688,8 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str()); + char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)]; + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty_to(hex_buf, value.data(), value.size())); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); diff --git a/esphome/core/hash_base.h b/esphome/core/hash_base.h index cc4fcdf920..0c1c2dce33 100644 --- a/esphome/core/hash_base.h +++ b/esphome/core/hash_base.h @@ -25,7 +25,7 @@ class HashBase { /// Retrieve the hash as bytes void get_bytes(uint8_t *output) { memcpy(output, this->digest_, this->get_size()); } - /// Retrieve the hash as hex characters + /// Retrieve the hash as hex characters. Output buffer must hold get_size() * 2 + 1 bytes. void get_hex(char *output) { format_hex_to(output, this->get_size() * 2 + 1, this->digest_, this->get_size()); } /// Compare the hash against a provided byte-encoded hash diff --git a/tests/test_helpers_hex_formatting.cpp b/tests/test_helpers_hex_formatting.cpp deleted file mode 100644 index 4c39ff6913..0000000000 --- a/tests/test_helpers_hex_formatting.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#include -#include -#include -#include - -// Copy the implementations to test them in isolation - -inline char format_hex_char(uint8_t v, char base) { return v >= 10 ? base + (v - 10) : '0' + v; } -inline char format_hex_char(uint8_t v) { return format_hex_char(v, 'a'); } -inline char format_hex_pretty_char(uint8_t v) { return format_hex_char(v, 'A'); } - -constexpr size_t format_hex_pretty_size(size_t byte_count) { return byte_count * 3; } - -static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator, - char base) { - if (length == 0) { - buffer[0] = '\0'; - return buffer; - } - uint8_t stride = separator ? 3 : 2; - size_t max_bytes = separator ? (buffer_size / stride) : ((buffer_size - 1) / stride); - if (max_bytes == 0) { - buffer[0] = '\0'; - return buffer; - } - if (length > max_bytes) { - length = max_bytes; - } - for (size_t i = 0; i < length; i++) { - size_t pos = i * stride; - buffer[pos] = format_hex_char(data[i] >> 4, base); - buffer[pos + 1] = format_hex_char(data[i] & 0x0F, base); - if (separator && i < length - 1) { - buffer[pos + 2] = separator; - } - } - buffer[length * stride - (separator ? 1 : 0)] = '\0'; - return buffer; -} - -char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) { - return format_hex_internal(buffer, buffer_size, data, length, 0, 'a'); -} - -char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) { - return format_hex_internal(buffer, buffer_size, data, length, separator, 'A'); -} - -template -char *format_hex_pretty_to(char (&buffer)[N], const uint8_t *data, size_t length, char separator = ':') { - return format_hex_pretty_to(buffer, N, data, length, separator); -} - -static constexpr size_t MAC_ADDRESS_SIZE = 6; -static constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE = format_hex_pretty_size(MAC_ADDRESS_SIZE); -static constexpr size_t MAC_ADDRESS_BUFFER_SIZE = MAC_ADDRESS_SIZE * 2 + 1; - -inline void format_mac_addr_upper(const uint8_t *mac, char *output) { - format_hex_pretty_to(output, MAC_ADDRESS_PRETTY_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE, ':'); -} - -inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) { - format_hex_to(output, MAC_ADDRESS_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE); -} - -// Tests - -void test_format_hex_char_base() { - assert(format_hex_char(0, 'a') == '0'); - assert(format_hex_char(9, 'a') == '9'); - assert(format_hex_char(10, 'a') == 'a'); - assert(format_hex_char(15, 'a') == 'f'); - assert(format_hex_char(0, 'A') == '0'); - assert(format_hex_char(10, 'A') == 'A'); - assert(format_hex_char(15, 'A') == 'F'); - printf("✓ format_hex_char with base\n"); -} - -void test_format_hex_char_lowercase() { - assert(format_hex_char(0) == '0'); - assert(format_hex_char(10) == 'a'); - assert(format_hex_char(15) == 'f'); - printf("✓ format_hex_char lowercase\n"); -} - -void test_format_hex_pretty_char_uppercase() { - assert(format_hex_pretty_char(0) == '0'); - assert(format_hex_pretty_char(10) == 'A'); - assert(format_hex_pretty_char(15) == 'F'); - printf("✓ format_hex_pretty_char uppercase\n"); -} - -void test_format_hex_to() { - uint8_t data[] = {0xde, 0xad, 0xbe, 0xef}; - char buf[9]; - format_hex_to(buf, sizeof(buf), data, 4); - assert(strcmp(buf, "deadbeef") == 0); - printf("✓ format_hex_to lowercase\n"); -} - -void test_format_hex_pretty_to_colon() { - uint8_t data[] = {0xDE, 0xAD, 0xBE, 0xEF}; - char buf[12]; - format_hex_pretty_to(buf, sizeof(buf), data, 4, ':'); - assert(strcmp(buf, "DE:AD:BE:EF") == 0); - printf("✓ format_hex_pretty_to with colon\n"); -} - -void test_format_hex_pretty_to_dot() { - uint8_t data[] = {0xAA, 0xBB, 0xCC}; - char buf[9]; - format_hex_pretty_to(buf, sizeof(buf), data, 3, '.'); - assert(strcmp(buf, "AA.BB.CC") == 0); - printf("✓ format_hex_pretty_to with dot\n"); -} - -void test_buffer_overflow_protection() { - uint8_t data[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE}; - char buf[11]; - format_hex_pretty_to(buf, 11, data, 5, ':'); - assert(strcmp(buf, "AA:BB:CC") == 0); - printf("✓ buffer overflow protection\n"); -} - -void test_exact_fit() { - uint8_t data[] = {0xAA, 0xBB, 0xCC, 0xDD}; - char buf[12]; - format_hex_pretty_to(buf, 12, data, 4, ':'); - assert(strcmp(buf, "AA:BB:CC:DD") == 0); - printf("✓ exact fit buffer\n"); -} - -void test_mac_addr_upper() { - uint8_t mac[] = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; - char buf[18]; - format_mac_addr_upper(mac, buf); - assert(strcmp(buf, "AA:BB:CC:DD:EE:FF") == 0); - printf("✓ format_mac_addr_upper\n"); -} - -void test_mac_addr_lower_no_sep() { - uint8_t mac[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; - char buf[13]; - format_mac_addr_lower_no_sep(mac, buf); - assert(strcmp(buf, "aabbccddeeff") == 0); - printf("✓ format_mac_addr_lower_no_sep\n"); -} - -void test_empty() { - uint8_t data[] = {0xAA}; - char buf[12]; - format_hex_pretty_to(buf, sizeof(buf), data, 0, ':'); - assert(strcmp(buf, "") == 0); - printf("✓ empty data\n"); -} - -void test_single_byte() { - uint8_t data[] = {0x42}; - char buf[3]; - format_hex_pretty_to(buf, sizeof(buf), data, 1, ':'); - assert(strcmp(buf, "42") == 0); - printf("✓ single byte\n"); -} - -void test_template() { - uint8_t data[] = {0xDE, 0xAD, 0xBE, 0xEF}; - char buf[format_hex_pretty_size(4)]; - format_hex_pretty_to(buf, data, 4); - assert(strcmp(buf, "DE:AD:BE:EF") == 0); - printf("✓ template version\n"); -} - -void test_no_separator() { - uint8_t data[] = {0xDE, 0xAD}; - char buf[5]; - format_hex_pretty_to(buf, sizeof(buf), data, 2, 0); - assert(strcmp(buf, "DEAD") == 0); - printf("✓ no separator\n"); -} - -void test_constexpr() { - static_assert(format_hex_pretty_size(1) == 3, ""); - static_assert(format_hex_pretty_size(4) == 12, ""); - static_assert(format_hex_pretty_size(6) == 18, ""); - static_assert(MAC_ADDRESS_SIZE == 6, ""); - static_assert(MAC_ADDRESS_PRETTY_BUFFER_SIZE == 18, ""); - static_assert(MAC_ADDRESS_BUFFER_SIZE == 13, ""); - printf("✓ constexpr values\n"); -} - -int main() { - printf("Running hex formatting tests...\n\n"); - - test_format_hex_char_base(); - test_format_hex_char_lowercase(); - test_format_hex_pretty_char_uppercase(); - test_format_hex_to(); - test_format_hex_pretty_to_colon(); - test_format_hex_pretty_to_dot(); - test_buffer_overflow_protection(); - test_exact_fit(); - test_mac_addr_upper(); - test_mac_addr_lower_no_sep(); - test_empty(); - test_single_byte(); - test_template(); - test_no_separator(); - test_constexpr(); - - printf("\n✅ All 15 tests passed!\n"); - return 0; -}