From 428f657f4fbb2469f1f57bbf2b10ec0c7b7aa504 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sat, 20 Dec 2025 00:05:17 +0100 Subject: [PATCH] Add hex encode/decode helpers --- CMakeLists.txt | 1 + include/amduat/util/hex.h | 34 ++++++++++ src/internal/hex.c | 134 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 include/amduat/util/hex.h create mode 100644 src/internal/hex.c diff --git a/CMakeLists.txt b/CMakeLists.txt index de639c6..ce484b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ set(AMDUAT_UTIL_SRCS src/internal/arena.c src/internal/varint.c src/internal/endian.c + src/internal/hex.c ) set(AMDUAT_ASL_SRCS diff --git a/include/amduat/util/hex.h b/include/amduat/util/hex.h new file mode 100644 index 0000000..84a61c2 --- /dev/null +++ b/include/amduat/util/hex.h @@ -0,0 +1,34 @@ +#ifndef AMDUAT_UTIL_HEX_H +#define AMDUAT_UTIL_HEX_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +size_t amduat_hex_encoded_len(size_t byte_len); /* returns byte_len * 2 */ +size_t amduat_hex_encoded_size(size_t byte_len); /* returns encoded_len + 1 for NUL */ + +bool amduat_hex_encode_lower(const uint8_t *bytes, + size_t byte_len, + char *out, + size_t out_len); + +/* Decodes exactly; rejects odd length or invalid hex. Accept upper/lowercase. */ +bool amduat_hex_decode(const char *hex, + uint8_t *out, + size_t out_len); + +/* Convenience: allocates output; caller frees with free(). */ +bool amduat_hex_decode_alloc(const char *hex, + uint8_t **out, + size_t *out_len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AMDUAT_UTIL_HEX_H */ diff --git a/src/internal/hex.c b/src/internal/hex.c new file mode 100644 index 0000000..2b50b70 --- /dev/null +++ b/src/internal/hex.c @@ -0,0 +1,134 @@ +#include "amduat/util/hex.h" + +#include +#include +#include + +size_t amduat_hex_encoded_len(size_t byte_len) { + if (byte_len > (SIZE_MAX / 2u)) { + return 0; + } + return byte_len * 2u; +} + +size_t amduat_hex_encoded_size(size_t byte_len) { + size_t encoded_len = amduat_hex_encoded_len(byte_len); + if (encoded_len == 0 && byte_len != 0) { + return 0; + } + if (encoded_len > (SIZE_MAX - 1u)) { + return 0; + } + return encoded_len + 1u; +} + +bool amduat_hex_encode_lower(const uint8_t *bytes, + size_t byte_len, + char *out, + size_t out_len) { + static const char kHexLower[] = "0123456789abcdef"; + size_t encoded_len = amduat_hex_encoded_len(byte_len); + size_t encoded_size = amduat_hex_encoded_size(byte_len); + + if (encoded_len == 0 && byte_len != 0) { + return false; + } + if (encoded_size == 0 || out == NULL || out_len < encoded_size) { + return false; + } + if (byte_len > 0 && bytes == NULL) { + return false; + } + + for (size_t i = 0; i < byte_len; ++i) { + uint8_t byte = bytes[i]; + out[i * 2u] = kHexLower[(byte >> 4u) & 0x0fu]; + out[i * 2u + 1u] = kHexLower[byte & 0x0fu]; + } + out[encoded_len] = '\0'; + return true; +} + +static bool amduat_hex_nibble(char c, uint8_t *out) { + if (c >= '0' && c <= '9') { + *out = (uint8_t)(c - '0'); + return true; + } + if (c >= 'a' && c <= 'f') { + *out = (uint8_t)(c - 'a' + 10); + return true; + } + if (c >= 'A' && c <= 'F') { + *out = (uint8_t)(c - 'A' + 10); + return true; + } + return false; +} + +bool amduat_hex_decode(const char *hex, uint8_t *out, size_t out_len) { + if (hex == NULL) { + return false; + } + if (out_len > 0 && out == NULL) { + return false; + } + + size_t hex_len = strlen(hex); + if ((hex_len & 1u) != 0u) { + return false; + } + + size_t expected_len = hex_len / 2u; + if (expected_len != out_len) { + return false; + } + + for (size_t i = 0; i < out_len; ++i) { + uint8_t hi = 0; + uint8_t lo = 0; + + if (!amduat_hex_nibble(hex[i * 2u], &hi) || + !amduat_hex_nibble(hex[i * 2u + 1u], &lo)) { + return false; + } + out[i] = (uint8_t)((hi << 4u) | lo); + } + + return true; +} + +bool amduat_hex_decode_alloc(const char *hex, uint8_t **out, size_t *out_len) { + if (out != NULL) { + *out = NULL; + } + if (out_len != NULL) { + *out_len = 0; + } + if (hex == NULL || out == NULL || out_len == NULL) { + return false; + } + + size_t hex_len = strlen(hex); + if ((hex_len & 1u) != 0u) { + return false; + } + + size_t bytes_len = hex_len / 2u; + if (bytes_len == 0) { + return true; + } + + uint8_t *buf = (uint8_t *)malloc(bytes_len); + if (buf == NULL) { + return false; + } + + if (!amduat_hex_decode(hex, buf, bytes_len)) { + free(buf); + return false; + } + + *out = buf; + *out_len = bytes_len; + return true; +}