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 <deroad@kumo.xn--q9jyb4c>
This commit is contained in:
Rot127 2025-02-20 12:34:43 -05:00 committed by NOT XVilka
parent 9f1ac64d87
commit 6d10861ace
6 changed files with 375 additions and 41 deletions

View file

@ -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

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -6,6 +6,78 @@
#include <stdio.h>
#include <ctype.h>
/**
* \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) {

View file

@ -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;
}