From 6d10861acefcaa8ceb71b982caa1cc1b4db0f594 Mon Sep 17 00:00:00 2001 From: Rot127 Date: Thu, 20 Feb 2025 12:34:43 -0500 Subject: [PATCH] String/Hex-Search 1/9: Add hexadecimal number helpers, doxygen and type annotations. Part 1/9. Likely won't build in between parts. Co-authored-by: wargio --- librz/debug/p/debug_dmp.c | 8 +- librz/include/rz_util/rz_hex.h | 7 +- librz/main/rz-asm.c | 7 +- librz/search/keyword.c | 2 +- librz/util/hex.c | 251 +++++++++++++++++++++++++++++---- test/unit/test_hex.c | 141 +++++++++++++++++- 6 files changed, 375 insertions(+), 41 deletions(-) diff --git a/librz/debug/p/debug_dmp.c b/librz/debug/p/debug_dmp.c index b9825d8cb8..0ca16e54c9 100644 --- a/librz/debug/p/debug_dmp.c +++ b/librz/debug/p/debug_dmp.c @@ -32,13 +32,13 @@ static bool rz_debug_dmp_init(RzDebug *dbg, void **user) { DmpCtx *ctx = dbg->plugin_data; ctx->bf = core->bin->cur; - int ret = rz_hex_str2bin(core->bin->cur->o->regstate, NULL); - ctx->context = malloc(ret); + ctx->context = malloc((strlen(core->bin->cur->o->regstate) / 2) + 1); if (!ctx->context) { return false; } - ctx->context_sz = ret; - rz_hex_str2bin(core->bin->cur->o->regstate, ctx->context); + int size = rz_hex_str2bin(core->bin->cur->o->regstate, ctx->context); + size *= size < 0 ? -1 : 1; + ctx->context_sz = size; ut32 MachineImageType = 0; // Windows Architecture (IMAGE_FILE_MACHINE) ut32 MinorVersion = 0; // Windows Version diff --git a/librz/include/rz_util/rz_hex.h b/librz/include/rz_util/rz_hex.h index 6c74cf2eaf..a0d39ff682 100644 --- a/librz/include/rz_util/rz_hex.h +++ b/librz/include/rz_util/rz_hex.h @@ -8,12 +8,15 @@ extern "C" { #endif RZ_API int rz_hex_pair2bin(const char *arg); -RZ_API int rz_hex_str2binmask(const char *in, ut8 *out, ut8 *mask); -RZ_API int rz_hex_str2bin(const char *in, ut8 *out); +RZ_API int rz_hex_str2bin_msb(RZ_NONNULL const char *in, RZ_NONNULL RZ_OUT ut8 *out); +RZ_API size_t rz_hex_str2bin_mask(RZ_NONNULL const char *in, RZ_NONNULL RZ_OUT ut8 *out, RZ_NULLABLE RZ_OUT ut8 *mask, bool lsb_extend); +RZ_API int rz_hex_str2bin(RZ_NONNULL const char *in, RZ_NONNULL RZ_OUT ut8 *out); RZ_API int rz_hex_bin2str(const ut8 *in, int len, char *out); RZ_API void rz_hex_ut2st_str(const ut32 in, RZ_INOUT char *out, const int len); RZ_API char *rz_hex_bin2strdup(const ut8 *in, int len); RZ_API bool rz_hex_to_byte(ut8 *val, ut8 c); +RZ_API ut8 rz_hex_digit_to_byte(const char c); +RZ_API ut16 rz_hex_digit_pair_to_byte(const char *c); RZ_API int rz_hex_str_is_valid(const char *s, bool allow_prefix); RZ_API st64 rz_hex_bin_truncate(ut64 in, int n); RZ_API char *rz_hex_from_c(const char *code); diff --git a/librz/main/rz-asm.c b/librz/main/rz-asm.c index 95da890855..8a8c9987a4 100644 --- a/librz/main/rz-asm.c +++ b/librz/main/rz-asm.c @@ -303,12 +303,13 @@ static int rasm_disasm(RzAsmState *as, ut64 addr, const char *buf, int len, int clen = len; // XXX data = (ut8 *)buf; } else { - clen = rz_hex_str2bin(buf, NULL); - if ((int)clen < 1 || !(data = malloc(clen))) { + if (!(data = malloc((strlen(buf) / 2) + 1))) { ret = 0; goto beach; } - rz_hex_str2bin(buf, data); + clen = rz_hex_str2bin(buf, data); + // Odd number of nibbles case. + clen *= clen < 0 ? -1 : 1; len = clen; } diff --git a/librz/search/keyword.c b/librz/search/keyword.c index c4bfe00a75..2a32994dde 100644 --- a/librz/search/keyword.c +++ b/librz/search/keyword.c @@ -189,7 +189,7 @@ RZ_API RzSearchKeyword *rz_search_keyword_new_hexmask(const char *kwstr, const c kw = malloc(len + 4); bm = malloc(len + 4); if (kw != NULL && bm != NULL) { - len = rz_hex_str2binmask(kwstr, (ut8 *)kw, (ut8 *)bm); + len = rz_hex_str2bin_mask(kwstr, (ut8 *)kw, (ut8 *)bm, true); if (len < 0) { len = -len - 1; } diff --git a/librz/util/hex.c b/librz/util/hex.c index 981332bbbe..edb9de60a1 100644 --- a/librz/util/hex.c +++ b/librz/util/hex.c @@ -6,6 +6,78 @@ #include #include +/** + * \brief Returns the byte value for a hexadecimal nibble. + * + * \param The hexadecimal nibble to get the raw byte value for. + * + * \param The byte value of the nibble. + * Or UT8_MAX if the nibble is no hexadecimal character. + * + * \return The byte value of the hex digit. + * Or UT8_MAX if the character was not a hexadecimal character. + * + * Example: + * ```c + * assert(rz_hex_nibble_to_byte('1') == 1); + * assert(rz_hex_nibble_to_byte('A') == 10); + * assert(rz_hex_nibble_to_byte('b') == 11); + * assert(rz_hex_nibble_to_byte('S') == UT8_MAX); + * assert(rz_hex_nibble_to_byte('\0') == UT8_MAX); + * ``` + */ +RZ_API ut8 rz_hex_digit_to_byte(const char c) { + if (!isxdigit(c)) { + return UT8_MAX; + } + // Check an ASCII table for this. + // Bit 6 indicates if it is A-F or a-f. + ut8 byte = (c & 0x40) ? 9 : 0; + // Bit 3-0 are the same bits for upper and lower case A-F. + // And 'a' & 0xf == 1, so 9 + 1 == 10. + byte += (c & 0xf); + return byte; +} + +/** + * \brief Returns the byte value for a hexadecimal nibble pair. + * It stops parsing at the first non hex digit. + * + * \param The string to parse as hex digit pair. + * + * \return The byte value of the nibble pair. + * Or UT16_MAX if the first nibble is no hexadecimal character. + * + * Example: + * ```c + * assert(rz_hex_nibble_pair_to_byte("1") == 1); + * assert(rz_hex_nibble_pair_to_byte("11") == 17); + * assert(rz_hex_nibble_pair_to_byte("fe") == 254); + * + * assert(rz_hex_nibble_pair_to_byte("ff01") == 255); + * assert(rz_hex_nibble_pair_to_byte("F@") == 15); + * assert(rz_hex_nibble_pair_to_byte("p1") == UT16_MAX); + * assert(rz_hex_nibble_pair_to_byte("") == UT16_MAX); + * ``` + */ +RZ_API ut16 rz_hex_digit_pair_to_byte(const char *npair) { + if (!isxdigit(npair[0])) { + return UT16_MAX; + } + ut8 n0 = rz_hex_digit_to_byte(npair[0]); + if (n0 == UT8_MAX) { + return UT16_MAX; + } + if (!isxdigit(npair[1])) { + return n0; + } + ut8 n1 = rz_hex_digit_to_byte(npair[1]); + if (n1 == UT8_MAX) { + return UT16_MAX; + } + return (n0 << 4 | n1); +} + /* int c = 0; ret = hex_to_byte(&c, 'c'); */ RZ_API bool rz_hex_to_byte(ut8 *val, ut8 c) { if (IS_DIGIT(c)) { @@ -437,16 +509,76 @@ RZ_API char *rz_hex_bin2strdup(const ut8 *in, int len) { } /** - * \brief Convert an input string \p in into the binary form in \p out + * \brief Convert an input string \p in into the binary form in \p out. + * For odd number of nibbles, the MSB side is extended with a 0 nibble. + * + * If \p in contains non-hexadecimal digits, the result is undefined. * * Convert an input string in the hexadecimal form (e.g. "41424344") into the - * raw binary form (e.g. "ABCD") + * raw binary form (e.g. "\x41\x42\x43\x44" or "ABCD"). + * + * Note: If an odd number of nibbles is given, the buffer is extended on the side of the MSB with a 0 nibble. + * So, "444" becomes "\x04\x44". + * Use rz_hex_str2bin() if you need to extend it on the LSB side. * * \param in Input string in hexadecimal form. An optional "0x" prefix may be present. * \param out Output buffer having at least strlen(in) / 2 bytes available - * \return number of bytes written into \p out + * \return Number of bytes written into \p out. The number is negative if an odd number of nibbles was parsed. */ -RZ_API int rz_hex_str2bin(const char *in, ut8 *out) { +RZ_API int rz_hex_str2bin_msb(RZ_NONNULL const char *in, RZ_NONNULL RZ_OUT ut8 *out) { + rz_return_val_if_fail(in && out, 0); + + if (!in[0]) { + return 0; + } + + size_t i = 0, j = 0; + if (in[0] == '0' && in[1] == 'x') { + i += 2; + } + + ut16 byte = 0; + size_t nibbles = rz_str_ansi_len(in + i); + + bool odd_nibble = (nibbles % 2) == 1; + if (odd_nibble) { + byte = rz_hex_digit_to_byte(in[i]); + if (byte >= UT8_MAX) { + return 0; + } + out[j] = byte; + i++; + j++; + } + + for (byte = rz_hex_digit_pair_to_byte(in + i); i < strlen(in) && byte <= UT8_MAX; j++, i += 2, byte = rz_hex_digit_pair_to_byte(in + i)) { + out[j] = byte; + } + + return odd_nibble ? -j : j; +} + +/** + * \brief Convert an input string \p in into the binary form in \p out + * For odd number of nibbles, the LSB side is extended with a 0 nibble. + * + * If \p in contains non-hexadecimal digits, the result is undefined. + * + * Convert an input string in the hexadecimal form (e.g. "41424344") into the + * raw binary form (e.g. "\x41\x42\x43\x44" or "ABCD"). + * + * Note: If an odd number of nibbles is given, the buffer is extended on the side of the LSB with a 0 nibble. + * So, "444" becomes "\x44\x40". + * Use rz_hex_str2bin_msb() if you need to extend it on the MSB side. + * + * \param in Input string in hexadecimal form. An optional "0x" prefix may be present. + * \param out Output buffer having at least (strlen(in) / 2) + 1 bytes available. + * + * \return Number of bytes written into \p out. The number is negative if an odd number of nibbles was parsed. + */ +RZ_API int rz_hex_str2bin(RZ_NONNULL const char *in, RZ_NONNULL RZ_OUT ut8 *out) { + rz_return_val_if_fail(in && out, 0); + long nibbles = 0; while (in && *in) { @@ -492,39 +624,98 @@ RZ_API int rz_hex_str2bin(const char *in, ut8 *out) { return nibbles / 2; } -RZ_API int rz_hex_str2binmask(const char *in, ut8 *out, ut8 *mask) { - ut8 *ptr; - int len, ilen = strlen(in) + 1; - int has_nibble = 0; - memcpy(out, in, ilen); - for (ptr = out; *ptr; ptr++) { - if (*ptr == '.') { - *ptr = '0'; +/** + * \brief Transforms an input hex string to its byte array eqivalent and a mask for it. + * The hex string is allowed to contain '.' characters as wildcard. + * Wildcards in \p in are set to '0' in the mask and \p out. + * It stops at the first invalid character and returns. + * + * If \p in contains non-hexadecimal digits, the result is undefined. + * + * The input string may be prefixed with a "0x". + * + * \param in The hex string to parse and transform. + * \param out The output buffer. It must be the same size as \p strlen(in) / 2. + * \param mask The output buffer for the mask. It must be the same size as \p out. + * Can be NULL, if no mask is required. + * \param lsb_extend If set and \p in has an odd number hex digits, + * it extends the byte buffer with a wildcard nibble at the LSB (right) side. + * If unset and with an odd digit count, it extends on the MSB (left) side. + * + * \return The number of bytes written to \p out and \p mask. In case of failure it returns less then 0. + * Note: In case of failure the content of \p out and \p mask are undefined. + * + * Example: + * ```c + * rz_hex_str2bin_mask("ffe4", out, mask, false); + * assert_mem_eq(out, { 0xff, 0xe4 }); + * assert_mem_eq(mask, { 0xff, 0xff }); + * + * rz_hex_str2bin_mask("ff.4", out, mask, false); + * assert_mem_eq(out, { 0xff, 0x04 }); + * assert_mem_eq(mask, { 0xff, 0x0f }); + * + * // Extend on MSB side + * rz_hex_str2bin_mask("ffee4", out, mask, false); + * assert_mem_eq(out, { 0x0f, 0xfe, 0xe4 }); + * assert_mem_eq(mask, { 0x0f, 0xff, 0xff }); + * + * // Extend on LSB side + * rz_hex_str2bin_mask("ee4", out, mask, true); + * assert_mem_eq(out, { 0xee, 0x40 }); + * assert_mem_eq(mask, { 0xff, 0xf0 }); + * ``` + */ +RZ_API size_t rz_hex_str2bin_mask(RZ_NONNULL const char *in, RZ_NONNULL RZ_OUT ut8 *out, RZ_NULLABLE RZ_OUT ut8 *mask, bool lsb_extend) { + rz_return_val_if_fail(in && out, 0); + + if (in[0] == '\0') { + return 0; + } + + char *in_cpy = strdup(in); + for (size_t i = 0; in_cpy[i]; ++i) { + if (in_cpy[i] == '.') { + in_cpy[i] = '0'; } } - len = rz_hex_str2bin((char *)out, out); - if (len < 0) { - has_nibble = 1; - len = -(len + 1); + + int bytes_copied; + if (lsb_extend) { + bytes_copied = rz_hex_str2bin(in_cpy, out); + } else { + bytes_copied = rz_hex_str2bin_msb(in_cpy, out); } - if (len != -1) { - memcpy(mask, in, ilen); - if (has_nibble) { - memcpy(mask + ilen, "f0", 3); + size_t ret = bytes_copied < 0 ? -bytes_copied : bytes_copied; + + if (!mask) { + free(in_cpy); + return ret; + } + bool odd_nibbles = bytes_copied < 0; + for (size_t i = 0; i < ret; ++i) { + int low_offset = (i * 2); + int high_offset = (i * 2) + 1; + char low_digit; + char high_digit; + if (odd_nibbles && (i == 0 || i == (ret - 1))) { + low_digit = (i == 0 && !lsb_extend) ? '.' : in[low_offset]; + high_digit = (i == ret - 1) && lsb_extend ? '.' : in[high_offset - 1]; + } else { + low_digit = in[low_offset]; + high_digit = in[high_offset]; } - for (ptr = mask; *ptr; ptr++) { - if (IS_HEXCHAR(*ptr)) { - *ptr = 'f'; - } else if (*ptr == '.') { - *ptr = '0'; - } + + mask[i] = 0x00; + if (low_digit != '.') { + mask[i] |= 0xf0; } - len = rz_hex_str2bin((char *)mask, mask); - if (len < 0) { - len++; + if (high_digit != '.') { + mask[i] |= 0x0f; } } - return len; + free(in_cpy); + return ret; } RZ_API st64 rz_hex_bin_truncate(ut64 in, int n) { diff --git a/test/unit/test_hex.c b/test/unit/test_hex.c index 56114d1c9f..cbd7137cf8 100644 --- a/test/unit/test_hex.c +++ b/test/unit/test_hex.c @@ -157,24 +157,163 @@ bool test_rz_hex_no_code() { } bool test_rz_str2bin(void) { - ut8 *buf = malloc(100); + ut8 *buf = calloc(100, sizeof(ut8)); + mu_assert_eq(rz_hex_str2bin("", buf), 0, "0 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"", 0, "'0' has been written"); + mu_assert_eq(rz_hex_str2bin("41424344", buf), 4, "4 bytes are written"); mu_assert_memeq(buf, (ut8 *)"ABCD", 4, "ABCD has been written"); + mu_assert_eq(rz_hex_str2bin("0x41424344", buf), 4, "4 bytes are written"); mu_assert_memeq(buf, (ut8 *)"ABCD", 4, "ABCD has been written"); + mu_assert_eq(rz_hex_str2bin("616263646566", buf), 6, "6 bytes are written"); mu_assert_memeq(buf, (ut8 *)"abcdef", 6, "abcdef has been written"); + mu_assert_eq(rz_hex_str2bin("61626364656", buf), -6, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"abcde\x60", 6, "abcdef has been written"); + + mu_assert_eq(rz_hex_str2bin("61", buf), 1, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"a", 1, "a has been written"); free(buf); mu_end; } +bool test_rz_str2bin_msb(void) { + ut8 *buf = calloc(100, sizeof(ut8)); + mu_assert_eq(rz_hex_str2bin_msb("", buf), 0, "0 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"", 0, "'0' has been written"); + + mu_assert_eq(rz_hex_str2bin_msb("61", buf), 1, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"a", 1, "a has been written"); + + mu_assert_eq(rz_hex_str2bin_msb("41424344", buf), 4, "4 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"ABCD", 4, "ABCD has been written"); + + mu_assert_eq(rz_hex_str2bin_msb("0x41424344", buf), 4, "4 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"ABCD", 4, "ABCD has been written"); + + mu_assert_eq(rz_hex_str2bin_msb("616263646566", buf), 6, "6 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"abcdef", 6, "abcdef has been written"); + + mu_assert_eq(rz_hex_str2bin_msb("61626364656", buf), -6, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"\x06\x16\x26\x36\x46\x56", 6, "abcdef has been written"); + + mu_assert_eq(rz_hex_str2bin_msb("0x61626364656", buf), -6, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"\x06\x16\x26\x36\x46\x56", 6, "abcdef has been written"); + free(buf); + mu_end; +} + +bool test_rz_str2bin_mask(void) { + ut8 *buf = calloc(100, sizeof(ut8)); + ut8 *mask = calloc(100, sizeof(ut8)); + mu_assert_eq(rz_hex_str2bin_mask("", buf, mask, false), 0, "0 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"", 0, "Buffer mismatch"); + mu_assert_memeq(mask, (ut8 *)"", 0, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("41424344", buf, mask, false), 4, "4 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"ABCD", 4, "Buffer mismatch"); + mu_assert_memeq(mask, (ut8 *)"\xff\xff\xff\xff", 4, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask(".14.", buf, mask, false), 2, "2 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"\x01\x40", 2, "Buffer mismatch"); + mu_assert_memeq(mask, (ut8 *)"\x0f\xf0", 2, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("0140", buf, mask, false), 2, "2 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"\x01\x40", 2, "Buffer mismatch"); + mu_assert_memeq(mask, (ut8 *)"\xff\xff", 2, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("0x41424344", buf, mask, false), 4, "4 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"ABCD", 4, "Buffer mismatch"); + mu_assert_memeq(mask, (ut8 *)"\xff\xff\xff\xff", 4, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("616263646566", buf, mask, false), 6, "6 bytes are written"); + mu_assert_memeq(buf, (ut8 *)"abcdef", 6, "Buffer mismatch"); + mu_assert_memeq(mask, (ut8 *)"\xff\xff\xff\xff\xff\xff", 6, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("61626364656", buf, mask, true), 6, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"abcde\x60", 6, "Buffer mismatch"); + mu_assert_memeq(mask, (ut8 *)"\xff\xff\xff\xff\xff\xf0", 6, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("61626364656", buf, mask, false), 6, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"\x06\x16\x26\x36\x46\x56", 6, "Buffer mismatch"); + mu_assert_memeq(mask, (ut8 *)"\x0f\xff\xff\xff\xff\xff", 6, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("6", buf, mask, true), 1, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"\x60", 1, "Mem mismatch"); + mu_assert_memeq(mask, (ut8 *)"\xf0", 1, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("6", buf, mask, false), 1, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"\x06", 1, "Mem mismatch"); + mu_assert_memeq(mask, (ut8 *)"\x0f", 1, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("61", buf, mask, false), 1, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"a", 1, "Buffer mismatch"); + mu_assert_memeq(mask, (ut8 *)"\xff", 1, "Mask doesn't match"); + + mu_assert_eq(rz_hex_str2bin_mask("ffffffffff", buf, NULL, false), 5, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"\xff\xff\xff\xff\xff", 5, "Buffer mismatch"); + + mu_assert_eq(rz_hex_str2bin_mask("fffffffff", buf, NULL, false), 5, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"\x0f\xff\xff\xff\xff", 5, "Buffer mismatch"); + + mu_assert_eq(rz_hex_str2bin_mask("0xffffffffff", buf, NULL, false), 5, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"\xff\xff\xff\xff\xff", 5, "Buffer mismatch"); + + mu_assert_eq(rz_hex_str2bin_mask("0xfffffffff", buf, NULL, false), 5, "error should be returned"); + mu_assert_memeq(buf, (ut8 *)"\x0f\xff\xff\xff\xff", 5, "Buffer mismatch"); + free(mask); + free(buf); + mu_end; +} + +bool test_rz_hex_nibble_to_byte(void) { + mu_assert_eq(rz_hex_digit_to_byte('/'), UT8_MAX, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('0'), 0, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('1'), 1, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('8'), 8, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('9'), 9, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte(':'), UT8_MAX, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('@'), UT8_MAX, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('A'), 10, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('B'), 11, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('E'), 14, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('F'), 15, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('G'), UT8_MAX, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('`'), UT8_MAX, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('a'), 10, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('b'), 11, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('e'), 14, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('f'), 15, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('g'), UT8_MAX, "Mismatch"); + mu_assert_eq(rz_hex_digit_to_byte('\0'), UT8_MAX, "Mismatch"); + mu_end; +} + +bool test_rz_hex_nibble_pair_to_byte(void) { + mu_assert_eq(rz_hex_digit_pair_to_byte("1"), 1, "Mismatch"); + mu_assert_eq(rz_hex_digit_pair_to_byte("11"), 17, "Mismatch"); + mu_assert_eq(rz_hex_digit_pair_to_byte("12"), 18, "Mismatch"); + mu_assert_eq(rz_hex_digit_pair_to_byte("fe"), 254, "Mismatch"); + mu_assert_eq(rz_hex_digit_pair_to_byte("ff01"), 255, "Mismatch"); + mu_assert_eq(rz_hex_digit_pair_to_byte("ff"), 255, "Mismatch"); + mu_assert_eq(rz_hex_digit_pair_to_byte("F@"), 15, "Mismatch"); + mu_assert_eq(rz_hex_digit_pair_to_byte("p1"), UT16_MAX, "Mismatch"); + mu_assert_eq(rz_hex_digit_pair_to_byte(""), UT16_MAX, "Mismatch"); + mu_end; +} + bool all_tests() { mu_run_test(test_rz_hex_from_c); mu_run_test(test_rz_hex_from_py); mu_run_test(test_rz_hex_from_code); mu_run_test(test_rz_hex_no_code); + mu_run_test(test_rz_hex_nibble_to_byte); + mu_run_test(test_rz_hex_nibble_pair_to_byte); mu_run_test(test_rz_str2bin); + mu_run_test(test_rz_str2bin_msb); + mu_run_test(test_rz_str2bin_mask); return tests_passed != tests_run; }