diff --git a/include/cli_parse_utils.h b/include/cli_parse_utils.h index 7cff7f9..7aa1621 100644 --- a/include/cli_parse_utils.h +++ b/include/cli_parse_utils.h @@ -1,11 +1,14 @@ #ifndef CLI_PARSE_UTILS_H #define CLI_PARSE_UTILS_H +#include #include int cli_parse_uint64(const char *s, uint64_t *out); int cli_parse_int64(const char *s, int64_t *out); int cli_parse_float(const char *s, float *out); int cli_parse_ufloat(const char *s, float *out); +int cli_parse_hex(const char *str, uint8_t *out, size_t max_len, size_t + *out_len); #endif diff --git a/src/Makefile.am b/src/Makefile.am index b7a97f3..d6944b8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,7 +6,8 @@ libcli_la_SOURCES = \ parse_utils/parse_uint.c \ parse_utils/parse_int.c \ parse_utils/parse_float.c \ - parse_utils/parse_ufloat.c + parse_utils/parse_ufloat.c \ + parse_utils/parse_hex.c libcli_la_CPPFLAGS = -I$(top_srcdir)/include libcli_la_CFLAGS = -std=c99 -Wall -Wextra diff --git a/src/parse_utils/parse_hex.c b/src/parse_utils/parse_hex.c new file mode 100644 index 0000000..a94f8a2 --- /dev/null +++ b/src/parse_utils/parse_hex.c @@ -0,0 +1,41 @@ +#include +#include +#include + +/* Forward declarations */ +static int hex_nibble(char c); +/* -------------------- */ + +int +cli_parse_hex(const char *str, uint8_t *out, size_t max_len, size_t *out_len) +{ + size_t str_len; + size_t byte_count; + + if (NULL == str || '\0' == str[0]) + return 1; + str_len = strlen(str); + byte_count = (str_len + 1) / 2; + if (byte_count > max_len) + return 1; + *out_len = 0; + for (size_t i = 0; i < str_len; i += 2) + { + int hi = hex_nibble(str[i]); + int lo = (i + 1 < str_len) ? hex_nibble(str[i + 1]) : 0; + + if (hi < 0 || lo < 0) + return 1; + out[(*out_len)++] = (uint8_t)(hi << 4 | lo); + } + return 0; +} + +static int +hex_nibble(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index bd68604..e9dc1e6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -6,7 +6,8 @@ check_PROGRAMS = test_cli test_cli_SOURCES = \ test_main.c \ parse_utils/test_parse_int.c \ - parse_utils/test_parse_float.c + parse_utils/test_parse_float.c \ + parse_utils/test_parse_hex.c test_cli_LDADD = \ $(top_builddir)/src/libcli.la \ diff --git a/tests/parse_utils/test_parse_hex.c b/tests/parse_utils/test_parse_hex.c new file mode 100644 index 0000000..a6c41c1 --- /dev/null +++ b/tests/parse_utils/test_parse_hex.c @@ -0,0 +1,142 @@ +#include +#include +#include "cli_parse_utils.h" + +/* Test 1: Valid even-length string */ +Test(parse_hex, even_length) +{ + uint8_t out[4]; + size_t len; + int ret = cli_parse_hex("deadbeef", out, 4, &len); + + cr_assert_eq(ret, 0); + cr_assert_eq(len, 4); + cr_assert_eq(out[0], 0xde); + cr_assert_eq(out[1], 0xad); + cr_assert_eq(out[2], 0xbe); + cr_assert_eq(out[3], 0xef); +} + +/* Test 2: Valid odd-length string — trailing nibble padded with 0 */ +Test(parse_hex, odd_length) +{ + uint8_t out[2]; + size_t len; + int ret = cli_parse_hex("abc", out, 2, &len); + + cr_assert_eq(ret, 0); + cr_assert_eq(len, 2); + cr_assert_eq(out[0], 0xab); + cr_assert_eq(out[1], 0xc0); +} + +/* Test 3: Single char */ +Test(parse_hex, single_char) +{ + uint8_t out[1]; + size_t len; + int ret = cli_parse_hex("f", out, 1, &len); + + cr_assert_eq(ret, 0); + cr_assert_eq(len, 1); + cr_assert_eq(out[0], 0xf0); +} + +/* Test 4: Single byte */ +Test(parse_hex, single_byte) +{ + uint8_t out[1]; + size_t len; + int ret = cli_parse_hex("ff", out, 1, &len); + + cr_assert_eq(ret, 0); + cr_assert_eq(len, 1); + cr_assert_eq(out[0], 0xff); +} + +/* Test 5: Mixed case */ +Test(parse_hex, mixed_case) +{ + uint8_t out[2]; + size_t len; + int ret = cli_parse_hex("DeAd", out, 2, &len); + + cr_assert_eq(ret, 0); + cr_assert_eq(len, 2); + cr_assert_eq(out[0], 0xde); + cr_assert_eq(out[1], 0xad); +} + +/* Test 6: All zeros */ +Test(parse_hex, all_zeros) +{ + uint8_t out[2]; + size_t len; + int ret = cli_parse_hex("0000", out, 2, &len); + + cr_assert_eq(ret, 0); + cr_assert_eq(len, 2); + cr_assert_eq(out[0], 0x00); + cr_assert_eq(out[1], 0x00); +} + +/* Test 7: Exactly at max_len */ +Test(parse_hex, exact_max_len) +{ + uint8_t out[4]; + size_t len; + int ret = cli_parse_hex("deadbeef", out, 4, &len); + + cr_assert_eq(ret, 0); + cr_assert_eq(len, 4); +} + +/* Test 8: Exceeds max_len */ +Test(parse_hex, exceeds_max_len) +{ + uint8_t out[2]; + size_t len; + int ret = cli_parse_hex("deadbeef", out, 2, &len); + + cr_assert_eq(ret, 1); +} + +/* Test 9: NULL input */ +Test(parse_hex, null_input) +{ + uint8_t out[4]; + size_t len; + int ret = cli_parse_hex(NULL, out, 4, &len); + + cr_assert_eq(ret, 1); +} + +/* Test 10: Empty string */ +Test(parse_hex, empty_string) +{ + uint8_t out[4]; + size_t len; + int ret = cli_parse_hex("", out, 4, &len); + + cr_assert_eq(ret, 1); +} + +/* Test 11: Invalid hex characters */ +Test(parse_hex, invalid_chars) +{ + uint8_t out[4]; + size_t len; + int ret = cli_parse_hex("xyz", out, 4, &len); + + cr_assert_eq(ret, 1); +} + +/* Test 12: Invalid char mid-string */ +Test(parse_hex, invalid_char_mid) +{ + uint8_t out[4]; + size_t len; + int ret = cli_parse_hex("de_d", out, 4, &len); + + cr_assert_eq(ret, 1); +}