[core] Add format_hex_pretty_to buffer helper and reduce code duplication (#12687)
Some checks failed
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Has been cancelled
CI / Test components batch (${{ matrix.components }}) (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / Build target branch for memory impact (push) Has been cancelled
CI / Build PR branch for memory impact (push) Has been cancelled
CI / Comment memory impact (push) Has been cancelled
CI / CI Status (push) Has been cancelled
Stale / stale (push) Has been cancelled
Lock closed issues and PRs / lock (push) Has been cancelled
Publish Release / Initialize build (push) Has been cancelled
Publish Release / Build and publish to PyPi (push) Has been cancelled
Publish Release / Build ESPHome amd64 (push) Has been cancelled
Publish Release / Build ESPHome arm64 (push) Has been cancelled
Publish Release / Publish ESPHome docker to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome docker to ghcr (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to dockerhub (push) Has been cancelled
Publish Release / Publish ESPHome ha-addon to ghcr (push) Has been cancelled
Publish Release / deploy-ha-addon-repo (push) Has been cancelled
Publish Release / deploy-esphome-schema (push) Has been cancelled
Publish Release / version-notifier (push) Has been cancelled
Synchronise Device Classes from Home Assistant / Sync Device Classes (push) Has been cancelled

This commit is contained in:
J. Nick Koston
2025-12-30 11:51:51 -10:00
committed by GitHub
parent dae7ba604a
commit bd3ecad3a1
7 changed files with 96 additions and 68 deletions

View File

@@ -30,7 +30,7 @@ class HmacMD5 {
void get_bytes(uint8_t *output); void get_bytes(uint8_t *output);
/// Retrieve the HMAC-MD5 digest as hex characters. /// 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); void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (16 bytes). /// Compare the digest against a provided byte-encoded digest (16 bytes).

View File

@@ -35,7 +35,7 @@ class HmacSHA256 {
void get_bytes(uint8_t *output); void get_bytes(uint8_t *output);
/// Retrieve the HMAC-SHA256 digest as hex characters. /// 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); void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (32 bytes). /// Compare the digest against a provided byte-encoded digest (32 bytes).

View File

@@ -123,10 +123,11 @@ void ZWaveProxy::process_uart_() {
} }
void ZWaveProxy::dump_config() { void ZWaveProxy::dump_config() {
char hex_buf[format_hex_pretty_size(ZWAVE_HOME_ID_SIZE)];
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
"Z-Wave Proxy:\n" "Z-Wave Proxy:\n"
" Home ID: %s", " Home ID: %s",
format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str()); format_hex_pretty_to(hex_buf, this->home_id_.data(), this->home_id_.size()));
} }
void ZWaveProxy::api_connection_authenticated(api::APIConnection *conn) { void ZWaveProxy::api_connection_authenticated(api::APIConnection *conn) {
@@ -167,7 +168,8 @@ bool ZWaveProxy::set_home_id(const uint8_t *new_home_id) {
return false; // No change return false; // No change
} }
std::memcpy(this->home_id_.data(), new_home_id, this->home_id_.size()); std::memcpy(this->home_id_.data(), new_home_id, this->home_id_.size());
ESP_LOGI(TAG, "Home ID: %s", format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str()); char hex_buf[format_hex_pretty_size(ZWAVE_HOME_ID_SIZE)];
ESP_LOGI(TAG, "Home ID: %s", format_hex_pretty_to(hex_buf, this->home_id_.data(), this->home_id_.size()));
this->home_id_ready_ = true; this->home_id_ready_ = true;
return true; // Home ID was changed return true; // Home ID was changed
} }

View File

@@ -14,6 +14,7 @@
namespace esphome::zwave_proxy { namespace esphome::zwave_proxy {
static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257; // Maximum Z-Wave frame size static constexpr size_t MAX_ZWAVE_FRAME_SIZE = 257; // Maximum Z-Wave frame size
static constexpr size_t ZWAVE_HOME_ID_SIZE = 4; // Z-Wave Home ID size in bytes
enum ZWaveResponseTypes : uint8_t { enum ZWaveResponseTypes : uint8_t {
ZWAVE_FRAME_TYPE_ACK = 0x06, ZWAVE_FRAME_TYPE_ACK = 0x06,
@@ -73,8 +74,8 @@ class ZWaveProxy : public uart::UARTDevice, public Component {
// Pre-allocated message - always ready to send // Pre-allocated message - always ready to send
api::ZWaveProxyFrame outgoing_proto_msg_; api::ZWaveProxyFrame outgoing_proto_msg_;
std::array<uint8_t, MAX_ZWAVE_FRAME_SIZE> buffer_; // Fixed buffer for incoming data std::array<uint8_t, MAX_ZWAVE_FRAME_SIZE> buffer_; // Fixed buffer for incoming data
std::array<uint8_t, 4> home_id_{0, 0, 0, 0}; // Fixed buffer for home ID std::array<uint8_t, ZWAVE_HOME_ID_SIZE> home_id_{}; // Fixed buffer for home ID
// Pointers and 32-bit values (aligned together) // Pointers and 32-bit values (aligned together)
api::APIConnection *api_connection_{nullptr}; // Current subscribed client api::APIConnection *api_connection_{nullptr}; // Current subscribed client

View File

@@ -25,14 +25,8 @@ class HashBase {
/// Retrieve the hash as bytes /// Retrieve the hash as bytes
void get_bytes(uint8_t *output) { memcpy(output, this->digest_, this->get_size()); } 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) { void get_hex(char *output) { format_hex_to(output, this->get_size() * 2 + 1, this->digest_, this->get_size()); }
for (size_t i = 0; i < this->get_size(); i++) {
uint8_t byte = this->digest_[i];
output[i * 2] = format_hex_char(byte >> 4);
output[i * 2 + 1] = format_hex_char(byte & 0x0F);
}
}
/// Compare the hash against a provided byte-encoded hash /// Compare the hash against a provided byte-encoded hash
bool equals_bytes(const uint8_t *expected) { return memcmp(this->digest_, expected, this->get_size()) == 0; } bool equals_bytes(const uint8_t *expected) { return memcmp(this->digest_, expected, this->get_size()) == 0; }

View File

@@ -286,43 +286,60 @@ std::string format_mac_address_pretty(const uint8_t *mac) {
return std::string(buf); return std::string(buf);
} }
std::string format_hex(const uint8_t *data, size_t length) { // Internal helper for hex formatting - base is 'a' for lowercase or 'A' for uppercase
std::string ret; static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator,
ret.resize(length * 2); char base) {
for (size_t i = 0; i < length; i++) { if (length == 0) {
ret[2 * i] = format_hex_char(data[i] >> 4); buffer[0] = '\0';
ret[2 * i + 1] = format_hex_char(data[i] & 0x0F); return buffer;
}
// With separator: total length is 3*length (2*length hex chars, (length-1) separators, 1 null terminator)
// Without separator: total length is 2*length + 1 (2*length hex chars, 1 null terminator)
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;
} }
return ret;
}
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
size_t max_bytes = (buffer_size - 1) / 2;
if (length > max_bytes) { if (length > max_bytes) {
length = max_bytes; length = max_bytes;
} }
for (size_t i = 0; i < length; i++) { for (size_t i = 0; i < length; i++) {
buffer[2 * i] = format_hex_char(data[i] >> 4); size_t pos = i * stride;
buffer[2 * i + 1] = format_hex_char(data[i] & 0x0F); 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 * 2] = '\0'; buffer[length * stride - (separator ? 1 : 0)] = '\0';
return buffer; 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');
}
std::string format_hex(const uint8_t *data, size_t length) {
std::string ret;
ret.resize(length * 2);
format_hex_to(&ret[0], length * 2 + 1, data, length);
return ret;
}
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
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');
}
// Shared implementation for uint8_t and string hex formatting // Shared implementation for uint8_t and string hex formatting
static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) { static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) {
if (data == nullptr || length == 0) if (data == nullptr || length == 0)
return ""; return "";
std::string ret; std::string ret;
uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise size_t hex_len = separator ? (length * 3 - 1) : (length * 2);
ret.resize(multiple * length - (separator ? 1 : 0)); ret.resize(hex_len);
for (size_t i = 0; i < length; i++) { format_hex_pretty_to(&ret[0], hex_len + 1, data, length, separator);
ret[multiple * i] = format_hex_pretty_char(data[i] >> 4);
ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
if (separator && i != length - 1)
ret[multiple * i + 2] = separator;
}
if (show_length && length > 4) if (show_length && length > 4)
return ret + " (" + std::to_string(length) + ")"; return ret + " (" + std::to_string(length) + ")";
return ret; return ret;

View File

@@ -677,12 +677,14 @@ constexpr uint8_t parse_hex_char(char c) {
return 255; return 255;
} }
/// Convert a nibble (0-15) to hex char with specified base ('a' for lowercase, 'A' for uppercase)
inline char format_hex_char(uint8_t v, char base) { return v >= 10 ? base + (v - 10) : '0' + v; }
/// Convert a nibble (0-15) to lowercase hex char /// Convert a nibble (0-15) to lowercase hex char
inline char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } inline char format_hex_char(uint8_t v) { return format_hex_char(v, 'a'); }
/// Convert a nibble (0-15) to uppercase hex char (used for pretty printing) /// Convert a nibble (0-15) to uppercase hex char (used for pretty printing)
/// This always uses uppercase (A-F) for pretty/human-readable output inline char format_hex_pretty_char(uint8_t v) { return format_hex_char(v, 'A'); }
inline char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
/// Write int8 value to buffer without modulo operations. /// Write int8 value to buffer without modulo operations.
/// Buffer must have at least 4 bytes free. Returns pointer past last char written. /// Buffer must have at least 4 bytes free. Returns pointer past last char written.
@@ -708,28 +710,6 @@ inline char *int8_to_str(char *buf, int8_t val) {
return buf; return buf;
} }
/// Format MAC address as XX:XX:XX:XX:XX:XX (uppercase)
inline void format_mac_addr_upper(const uint8_t *mac, char *output) {
for (size_t i = 0; i < 6; i++) {
uint8_t byte = mac[i];
output[i * 3] = format_hex_pretty_char(byte >> 4);
output[i * 3 + 1] = format_hex_pretty_char(byte & 0x0F);
if (i < 5)
output[i * 3 + 2] = ':';
}
output[17] = '\0';
}
/// Format MAC address as xxxxxxxxxxxxxx (lowercase, no separators)
inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) {
for (size_t i = 0; i < 6; i++) {
uint8_t byte = mac[i];
output[i * 2] = format_hex_char(byte >> 4);
output[i * 2 + 1] = format_hex_char(byte & 0x0F);
}
output[12] = '\0';
}
/// Format byte array as lowercase hex to buffer (base implementation). /// Format byte array as lowercase hex to buffer (base implementation).
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length); char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length);
@@ -748,6 +728,46 @@ inline char *format_hex_to(char (&buffer)[N], T val) {
return format_hex_to(buffer, reinterpret_cast<const uint8_t *>(&val), sizeof(T)); return format_hex_to(buffer, reinterpret_cast<const uint8_t *>(&val), sizeof(T));
} }
/// Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0"
constexpr size_t format_hex_pretty_size(size_t byte_count) { return byte_count * 3; }
/** Format byte array as uppercase hex to buffer (base implementation).
*
* @param buffer Output buffer to write to.
* @param buffer_size Size of the output buffer.
* @param data Pointer to the byte array to format.
* @param length Number of bytes in the array.
* @param separator Character to use between hex bytes, or '\0' for no separator.
* @return Pointer to buffer.
*
* Buffer size needed: length * 3 with separator (for "XX:XX:XX\0"), length * 2 + 1 without.
*/
char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator = ':');
/// Format byte array as uppercase hex with separator to buffer. Automatically deduces buffer size.
template<size_t N>
inline char *format_hex_pretty_to(char (&buffer)[N], const uint8_t *data, size_t length, char separator = ':') {
static_assert(N >= 3, "Buffer must hold at least one hex byte");
return format_hex_pretty_to(buffer, N, data, length, separator);
}
/// MAC address size in bytes
static constexpr size_t MAC_ADDRESS_SIZE = 6;
/// Buffer size for MAC address with separators: "XX:XX:XX:XX:XX:XX\0"
static constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE = format_hex_pretty_size(MAC_ADDRESS_SIZE);
/// Buffer size for MAC address without separators: "XXXXXXXXXXXX\0"
static constexpr size_t MAC_ADDRESS_BUFFER_SIZE = MAC_ADDRESS_SIZE * 2 + 1;
/// Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
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, ':');
}
/// Format MAC address as xxxxxxxxxxxxxx (lowercase, no separators)
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);
}
/// Format the six-byte array \p mac into a MAC address. /// Format the six-byte array \p mac into a MAC address.
std::string format_mac_address_pretty(const uint8_t mac[6]); std::string format_mac_address_pretty(const uint8_t mac[6]);
/// Format the byte array \p data of length \p len in lowercased hex. /// Format the byte array \p data of length \p len in lowercased hex.
@@ -1203,12 +1223,6 @@ class HighFrequencyLoopRequester {
/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
void get_mac_address_raw(uint8_t *mac); // NOLINT(readability-non-const-parameter) void get_mac_address_raw(uint8_t *mac); // NOLINT(readability-non-const-parameter)
/// Buffer size for MAC address in lowercase hex notation (12 hex chars + null terminator)
constexpr size_t MAC_ADDRESS_BUFFER_SIZE = 13;
/// Buffer size for MAC address in colon-separated uppercase hex notation (17 chars + null terminator)
constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE = 18;
/// Get the device MAC address as a string, in lowercase hex notation. /// Get the device MAC address as a string, in lowercase hex notation.
std::string get_mac_address(); std::string get_mac_address();