diff options
-rw-r--r-- | src/plugins/unittest/string_test.c | 1584 | ||||
-rw-r--r-- | src/vppinfra/string.c | 369 | ||||
-rw-r--r-- | src/vppinfra/string.h | 713 | ||||
-rw-r--r-- | test/test_string.py | 41 |
4 files changed, 2682 insertions, 25 deletions
diff --git a/src/plugins/unittest/string_test.c b/src/plugins/unittest/string_test.c index a5caa9094f0..2beee139ec9 100644 --- a/src/plugins/unittest/string_test.c +++ b/src/plugins/unittest/string_test.c @@ -16,7 +16,7 @@ #include <vppinfra/string.h> static int -test_clib_memset (vlib_main_t * vm, unformat_input_t * input) +test_memset_s (vlib_main_t * vm, unformat_input_t * input) { u8 dst[64]; int i; @@ -42,7 +42,28 @@ test_clib_memset (vlib_main_t * vm, unformat_input_t * input) } static int -test_memcpy (vlib_main_t * vm, unformat_input_t * input) +test_clib_memset (vlib_main_t * vm, unformat_input_t * input) +{ + u8 dst[64]; + int i; + errno_t err; + + vlib_cli_output (vm, "Test clib_memset..."); + + err = clib_memset (dst, 0xfe, ARRAY_LEN (dst)); + + if (err != EOK) + return -1; + + for (i = 0; i < ARRAY_LEN (dst); i++) + if (dst[i] != 0xFE) + return -1; + + return 0; +} + +static int +test_memcpy_s (vlib_main_t * vm, unformat_input_t * input) { char src[64], dst[64]; int i; @@ -86,40 +107,1548 @@ test_memcpy (vlib_main_t * vm, unformat_input_t * input) return 0; } +static int +test_clib_memcpy (vlib_main_t * vm, unformat_input_t * input) +{ + char src[64], dst[64]; + int i; + errno_t err; + + vlib_cli_output (vm, "Test clib_memcpy..."); + + for (i = 0; i < ARRAY_LEN (src); i++) + src[i] = i + 1; + + /* Typical case */ + err = clib_memcpy (dst, src, sizeof (src)); + + if (err != EOK) + return -1; + + /* This better not fail but check anyhow */ + for (i = 0; i < ARRAY_LEN (dst); i++) + if (src[i] != dst[i]) + return -1; + /* verify it against memcpy */ + memcpy (dst, src, sizeof (src)); + + /* This better not fail but check anyhow */ + for (i = 0; i < ARRAY_LEN (dst); i++) + if (src[i] != dst[i]) + return -1; + + /* Zero length copy */ + err = clib_memcpy (0, src, 0); + + if (err != EOK) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_memcmp_s (vlib_main_t * vm, unformat_input_t * input) +{ + char src[64], dst[64]; + errno_t err; + int diff = 0; + + vlib_cli_output (vm, "Test memcmp_s..."); + + /* Fill array with different values */ + err = clib_memset (src, 0x1, ARRAY_LEN (src)); + if (err != EOK) + return -1; + err = clib_memset (dst, 0x3, ARRAY_LEN (dst)); + if (err != EOK) + return -1; + + /* s1 > s2, > 0 is expected in diff */ + err = memcmp_s (dst, ARRAY_LEN (dst), src, ARRAY_LEN (src), &diff); + if (err != EOK) + return -1; + if (!(diff > 0)) + return -1; + + /* s1 < s2, < 0 is expected in diff */ + err = memcmp_s (src, ARRAY_LEN (src), dst, ARRAY_LEN (dst), &diff); + if (err != EOK) + return -1; + if (!(diff < 0)) + return -1; + + err = clib_memset (dst, 0x1, ARRAY_LEN (dst)); + if (err != EOK) + return -1; + + /* s1 == s2, 0 is expected in diff */ + err = memcmp_s (src, ARRAY_LEN (src), dst, ARRAY_LEN (dst), &diff); + if (err != EOK) + return -1; + if (diff != 0) + return -1; + + /* Try negative tests */ + err = memcmp_s (0, 0, 0, 0, 0); + if (err != EINVAL) + return -1; + + /* Try s2max > s1max */ + err = memcmp_s (src, ARRAY_LEN (src) - 1, dst, ARRAY_LEN (dst), &diff); + if (err != EINVAL) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_memcmp (vlib_main_t * vm, unformat_input_t * input) +{ + char src[64], dst[64]; + errno_t err; + char *s; + + vlib_cli_output (vm, "Test clib_memcmp..."); + + /* Fill array with different values */ + err = clib_memset (src, 0x1, ARRAY_LEN (src)); + if (err != EOK) + return -1; + err = clib_memset (dst, 0x3, ARRAY_LEN (dst)); + if (err != EOK) + return -1; + + /* s1 > s2, > 0 is expected in diff */ + if (!(clib_memcmp (dst, src, ARRAY_LEN (src)) > 0)) + return -1; + /* verify it against memcmp */ + if (!(memcmp (dst, src, ARRAY_LEN (src)) > 0)) + return -1; + + /* s1 < s2, < 0 is expected in diff */ + if (!(clib_memcmp (src, dst, ARRAY_LEN (dst)) < 0)) + return -1; + /* verify it against memcmp */ + if (!(memcmp (src, dst, ARRAY_LEN (dst)) < 0)) + return -1; + + err = clib_memset (dst, 0x1, ARRAY_LEN (dst)); + if (err != EOK) + return -1; + + /* s1 == s2, 0 is expected in diff */ + if (clib_memcmp (src, dst, ARRAY_LEN (dst)) != 0) + return -1; + /* verify it against memcmp */ + if (memcmp (src, dst, ARRAY_LEN (dst)) != 0) + return -1; + + /* Try negative tests */ + s = 0; + if (clib_memcmp (s, s, 0) != 0) + return -1; + /* verify it against memcmp */ + if (memcmp (s, s, 0) != 0) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_strcmp_s (vlib_main_t * vm, unformat_input_t * input) +{ + char s1[] = "Simplicity is the ultimate sophistication"; + uword s1len = sizeof (s1) - 1; // excluding null + errno_t err; + int indicator = 0; + + vlib_cli_output (vm, "Test strcmp_s..."); + + /* s1 == s2, 0 is expected */ + err = strcmp_s (s1, s1len, "Simplicity is the ultimate sophistication", + &indicator); + if (err != EOK) + return -1; + if (indicator != 0) + return -1; + + /* s1 > s2, > 0 is expected */ + err = strcmp_s (s1, s1len, "Simplicity is the ultimate", &indicator); + if (err != EOK) + return -1; + if (!(indicator > 0)) + return -1; + + /* s1 < s2, < 0 is expected */ + err = strcmp_s (s1, s1len, "Simplicity is the ultimate sophistication!", + &indicator); + if (err != EOK) + return -1; + if (!(indicator < 0)) + return -1; + + /* Try some negative tests */ + + /* Null pointers test */ + err = strcmp_s (0, 0, 0, 0); + if (err != EINVAL) + return -1; + + /* non-null terminated s1 */ + s1[s1len] = 0x1; + err = strcmp_s (s1, s1len, "Simplicity is the ultimate sophistication", + &indicator); + if (err != EINVAL) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_strcmp (vlib_main_t * vm, unformat_input_t * input) +{ + char s1[] = "Simplicity is the ultimate sophistication"; + int indicator; + char *s; + + vlib_cli_output (vm, "Test clib_strcmp..."); + + /* s1 == s2, 0 is expected */ + indicator = clib_strcmp (s1, "Simplicity is the ultimate sophistication"); + if (indicator != 0) + return -1; + /* verify it against strcmp */ + indicator = strcmp (s1, "Simplicity is the ultimate sophistication"); + if (indicator != 0) + return -1; + + /* s1 > s2, > 0 is expected */ + indicator = clib_strcmp (s1, "Simplicity is the ultimate"); + if (!(indicator > 0)) + return -1; + /* verify it against strcmp */ + indicator = strcmp (s1, "Simplicity is the ultimate"); + if (!(indicator > 0)) + return -1; + + /* s1 < s2, < 0 is expected */ + indicator = clib_strcmp (s1, "Simplicity is the ultimate sophistication!"); + if (!(indicator < 0)) + return -1; + /* verify it against strcmp */ + indicator = strcmp (s1, "Simplicity is the ultimate sophistication!"); + if (!(indicator < 0)) + return -1; + + /* Try some negative tests */ + + /* Null pointers comparison */ + s = 0; + indicator = clib_strcmp (s, s); + if (indicator != 0) + return -1; + /* verify it against strcmp */ + indicator = strcmp (s, s); + if (indicator != 0) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_strncmp_s (vlib_main_t * vm, unformat_input_t * input) +{ + char s1[] = "Every moment is a fresh beginning"; + uword s1len = sizeof (s1) - 1; // excluding null + errno_t err; + int indicator = 0; + + vlib_cli_output (vm, "Test strncmp_s..."); + + /* s1 == s2, 0 is expected */ + err = strncmp_s (s1, s1len, "Every moment is a fresh beginning", s1len, + &indicator); + if (err != EOK) + return -1; + if (indicator != 0) + return -1; + + /* s1 > s2, 0 is expected since comparison is no more than n character */ + err = strncmp_s (s1, s1len, "Every moment is a fresh begin", + sizeof ("Every moment is a fresh begin") - 1, &indicator); + if (err != EOK) + return -1; + if (indicator != 0) + return -1; + + /* s1 < s2, < 0 is expected */ + err = strncmp_s (s1, s1len, "Every moment is fresh beginning", + sizeof ("Every moment is fresh beginning") - 1, + &indicator); + if (err != EOK) + return -1; + if (!(indicator < 0)) + return -1; + + /* s1 > s2, > 0 is expected */ + err = strncmp_s ("Every moment is fresh beginning. ", + sizeof ("Every moment is fresh beginning. ") - 1, s1, + s1len, &indicator); + if (err != EOK) + return -1; + if (!(indicator > 0)) + return -1; + + /* Try some negative tests */ + + /* Null pointers */ + err = strncmp_s (0, 0, 0, 0, 0); + if (err != EINVAL) + return -1; + + /* n > s1max */ + err = strncmp_s (s1, s1len, "Every moment is a fresh beginning", s1len + 1, + &indicator); + if (err != EINVAL) + return -1; + + /* unterminated s1 */ + s1[s1len] = 0x1; + err = strncmp_s (s1, s1len, "Every moment is a fresh beginning", + sizeof ("Every moment is a fresh beginning") - 1, + &indicator); + if (err != EINVAL) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_strncmp (vlib_main_t * vm, unformat_input_t * input) +{ + char s1[] = "Every moment is a fresh beginning"; + uword s1len = sizeof (s1) - 1; // excluding null + int indicator, v_indicator; + + vlib_cli_output (vm, "Test clib_strncmp..."); + + /* s1 == s2, 0 is expected */ + indicator = clib_strncmp (s1, "Every moment is a fresh beginning", s1len); + if (indicator != 0) + return -1; + /* verify it against strncmp */ + v_indicator = strncmp (s1, "Every moment is a fresh beginning", s1len); + if (v_indicator != 0) + return -1; + if (v_indicator != indicator) + return -1; + + /* s1 > s2, 0 is expected since comparison is no more than n character */ + indicator = clib_strncmp (s1, "Every moment is a fresh begin", + sizeof ("Every moment is a fresh begin") - 1); + if (indicator != 0) + return -1; + /* verify it against strncmp */ + v_indicator = strncmp (s1, "Every moment is a fresh begin", + sizeof ("Every moment is a fresh begin") - 1); + if (v_indicator != 0) + return -1; + if (v_indicator != indicator) + return -1; + + /* s1 < s2, < 0 is expected */ + indicator = clib_strncmp (s1, "Every moment is fresh beginning", + sizeof ("Every moment is fresh beginning") - 1); + if (!(indicator < 0)) + return -1; + /* verify it against strncmp */ + v_indicator = strncmp (s1, "Every moment is fresh beginning", + sizeof ("Every moment is fresh beginning") - 1); + if (!(v_indicator < 0)) + return -1; + if (v_indicator != indicator) + return -1; + + /* s1 > s2, > 0 is expected */ + indicator = clib_strncmp ("Every moment is fresh beginning. ", s1, s1len); + if (!(indicator > 0)) + return -1; + /* verify it against strncmp */ + v_indicator = strncmp ("Every moment is fresh beginning. ", s1, s1len); + if (!(v_indicator > 0)) + return -1; + if (v_indicator != indicator) + return -1; + + /* Try some negative tests */ + + /* Null pointers */ + + /* make sure we don't crash */ + indicator = clib_strncmp (0, 0, 0); + if (indicator != EOK) + return -1; + + /* n > s1 len */ + indicator = + clib_strncmp (s1, "Every moment is a fresh beginning", s1len + 1); + if (indicator != 0) + return -1; + /* verify it against strncmp */ + v_indicator = strncmp (s1, "Every moment is a fresh beginning", s1len + 1); + if (v_indicator != 0) + return -1; + if (v_indicator != indicator) + return -1; + + /* unterminated s1 */ + s1[s1len] = 0x1; + indicator = clib_strncmp (s1, "Every moment is a fresh beginning", + sizeof ("every moment is a fresh beginning") - 1); + if (indicator != 0) + return -1; + /* verify it against strncmp */ + v_indicator = strncmp (s1, "Every moment is a fresh beginning", + sizeof ("Every moment is a fresh beginning") - 1); + if (v_indicator != 0) + return -1; + if (v_indicator != indicator) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_strcpy_s (vlib_main_t * vm, unformat_input_t * input) +{ + char src[] = "To err is human."; + char dst[64]; + int indicator; + size_t s1size = sizeof (dst); // including null + errno_t err; + + vlib_cli_output (vm, "Test strcpy_s..."); + + err = strcpy_s (dst, s1size, src); + if (err != EOK) + return -1; + + /* This better not fail but check anyhow */ + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* Negative tests */ + + err = strcpy_s (0, 0, 0); + if (err == EOK) + return -1; + + /* Size fail */ + err = strcpy_s (dst, 10, src); + if (err == EOK) + return -1; + + /* overlap fail */ + err = strcpy_s (dst, s1size, dst); + if (err == EOK) + return -1; + + /* overlap fail */ + err = strcpy_s (dst, s1size, dst + 1); + if (err == EOK) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_strcpy (vlib_main_t * vm, unformat_input_t * input) +{ + char src[] = "The journey of a one thousand miles begins with one step."; + char dst[100]; + int indicator; + errno_t err; + + vlib_cli_output (vm, "Test clib_strcpy..."); + + err = clib_strcpy (dst, src); + if (err != EOK) + return -1; + + /* This better not fail but check anyhow */ + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* verify it against strcpy */ + strcpy (dst, src); + + /* This better not fail but check anyhow */ + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* Negative tests */ + + err = clib_strcpy (0, 0); + if (err == EOK) + return -1; + + /* overlap fail */ + err = clib_strcpy (dst, dst); + if (err == EOK) + return -1; + + /* overlap fail */ + err = clib_strcpy (dst, dst + 1); + if (err == EOK) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_strncpy_s (vlib_main_t * vm, unformat_input_t * input) +{ + char src[] = "Those who dare to fail miserably can achieve greatly."; + char dst[100], old_dst[100]; + int indicator; + size_t s1size = sizeof (dst); // including null + errno_t err; + + vlib_cli_output (vm, "Test strncpy_s..."); + + /* dmax includes null, n excludes null */ + + /* n == string len of src */ + err = strncpy_s (dst, s1size, src, clib_strnlen (src, sizeof (src))); + if (err != EOK) + return -1; + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* limited copy -- strlen src > n, copy up to n */ + err = strncpy_s (dst, s1size, "The price of greatness is responsibility.", + 10); + if (err != EOK) + return -1; + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), "The price ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* n > string len of src */ + err = strncpy_s (dst, s1size, src, clib_strnlen (src, sizeof (src)) + 10); + if (err != EOK) + return -1; + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* truncation, n >= dmax */ + err = strncpy_s (dst, clib_strnlen (src, sizeof (src)), src, + clib_strnlen (src, sizeof (src))); + if (err != EOVERFLOW) + return -1; + + /* Check dst content */ + if (dst[strlen (dst)] != '\0') + return -1; + if (strncmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, + clib_strnlen (dst, sizeof (dst)), &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* zero length copy */ + clib_strncpy (old_dst, dst, clib_strnlen (dst, sizeof (dst))); + err = strncpy_s (dst, sizeof (dst), src, 0); + if (err != EOK) + return -1; + /* verify dst is untouched */ + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), old_dst, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* Negative tests */ + + err = strncpy_s (0, 0, 0, 1); + if (err == EOK) + return -1; + + /* overlap fail */ + err = strncpy_s (dst, s1size, dst + 1, s1size - 1); + if (err == EOK) + return -1; + + /* overlap fail */ + err = strncpy_s (dst, s1size, dst, s1size); + if (err == EOK) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_strncpy (vlib_main_t * vm, unformat_input_t * input) +{ + char src[] = "Those who dare to fail miserably can achieve greatly."; + char dst[100], old_dst[100]; + int indicator; + size_t s1size = sizeof (dst); // including null + errno_t err; + + vlib_cli_output (vm, "Test clib_strncpy..."); + + /* n == string len of src */ + err = clib_strncpy (dst, src, clib_strnlen (src, sizeof (src))); + if (err != EOK) + return -1; + + /* This better not fail but check anyhow */ + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* Verify it against strncpy */ + strncpy (dst, src, strlen (src)); + + /* This better not fail but check anyhow */ + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* limited copy -- strlen src > n, copy up to n */ + err = clib_strncpy (dst, "The price of greatness is responsibility.", 10); + if (err != EOK) + return -1; + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), "The price ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + /* verify it against strncpy */ + memset_s (dst, sizeof (dst), 0, sizeof (dst)); + strncpy (dst, "The price of greatness is responsibility.", 10); + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), "The price ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* n > string len of src */ + err = clib_strncpy (dst, src, clib_strnlen (src, sizeof (src)) + 10); + if (err != EOK) + return -1; + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + /* Verify it against strncpy */ + strncpy (dst, src, strlen (src)); + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), src, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* zero length copy */ + clib_strncpy (old_dst, dst, clib_strnlen (dst, sizeof (dst))); + err = clib_strncpy (dst, src, 0); + if (err != EOK) + return -1; + /* verify dst is untouched */ + if (strcmp_s (dst, clib_strnlen (dst, sizeof (dst)), old_dst, &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* Negative tests */ + + err = clib_strncpy (0, 0, 1); + if (err == EOK) + return -1; + + /* overlap fail */ + err = clib_strncpy (dst, dst + 1, s1size); + if (err == EOK) + return -1; + + /* overlap fail */ + err = clib_strncpy (dst, dst, s1size); + if (err == EOK) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_strcat_s (vlib_main_t * vm, unformat_input_t * input) +{ + char src[100], dst[100], old_dst[100]; + size_t s1size = sizeof (dst); // including null + errno_t err; + int indicator; + + vlib_cli_output (vm, "Test strcat_s..."); + + strcpy_s (dst, sizeof (dst), "Tough time never last "); + strcpy_s (src, sizeof (src), "but tough people do"); + err = strcat_s (dst, s1size, src); + if (err != EOK) + return -1; + if (strcmp_s (dst, s1size - 1, + "Tough time never last but tough people do", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* empty string concatenation */ + clib_strncpy (old_dst, dst, clib_strnlen (dst, sizeof (dst))); + err = strcat_s (dst, s1size, ""); + if (err != EOK) + return -1; + /* verify dst is untouched */ + if (strcmp_s (dst, s1size - 1, old_dst, &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* negative stuff */ + err = strcat_s (0, 0, 0); + if (err != EINVAL) + return -1; + + /* overlap fail */ + err = strcat_s (dst, s1size, dst + 1); + if (err != EINVAL) + return -1; + + /* overlap fail */ + err = strcat_s (dst, s1size, dst); + if (err != EINVAL) + return -1; + + /* not enough space for dst */ + err = strcat_s (dst, 10, src); + if (err != EINVAL) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_strcat (vlib_main_t * vm, unformat_input_t * input) +{ + char src[100], dst[100], old_dst[100]; + size_t s1size = sizeof (dst); // including null + errno_t err; + int indicator; + + vlib_cli_output (vm, "Test clib_strcat..."); + + strcpy_s (dst, sizeof (dst), "Tough time never last "); + strcpy_s (src, sizeof (src), "but tough people do"); + err = clib_strcat (dst, src); + if (err != EOK) + return -1; + if (strcmp_s (dst, s1size - 1, + "Tough time never last but tough people do", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + /* verify it against strcat */ + strcpy_s (dst, sizeof (dst), "Tough time never last "); + strcpy_s (src, sizeof (src), "but tough people do"); + strcat (dst, src); + if (strcmp_s (dst, s1size - 1, + "Tough time never last but tough people do", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* empty string concatenation */ + clib_strncpy (old_dst, dst, clib_strnlen (dst, sizeof (dst))); + err = clib_strcat (dst, ""); + if (err != EOK) + return -1; + /* verify dst is untouched */ + if (strcmp_s (dst, s1size - 1, old_dst, &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* negative stuff */ + err = clib_strcat (0, 0); + if (err != EINVAL) + return -1; + + /* overlap fail */ + err = clib_strcat (dst, dst + 1); + if (err != EINVAL) + return -1; + + /* overlap fail */ + err = clib_strcat (dst, dst); + if (err != EINVAL) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_strncat_s (vlib_main_t * vm, unformat_input_t * input) +{ + char src[100], dst[100], old_dst[100]; + size_t s1size = sizeof (dst); // including null + errno_t err; + char s1[] = "Two things are infinite: "; + char s2[] = "the universe and human stupidity; "; + char s3[] = "I am not sure about the universe."; + int indicator; + + vlib_cli_output (vm, "Test strncat_s..."); + + strcpy_s (dst, sizeof (dst), s1); + strcpy_s (src, sizeof (src), s2); + err = strncat_s (dst, s1size, src, clib_strnlen (src, sizeof (src))); + if (err != EOK) + return -1; + if (strcmp_s (dst, s1size - 1, + "Two things are infinite: the universe and human stupidity; ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* truncation, n >= dmax - strnlen_s (dst, dmax) */ + err = strncat_s (dst, clib_strnlen (dst, sizeof (dst)) + + clib_strnlen (s3, sizeof (s3)), s3, + clib_strnlen (s3, sizeof (s3))); + if (err != EOVERFLOW) + return -1; + /* + * resulting string is dst + strlen (s3) - 1 characters + null. + * notice the "." is missing at the end of the resulting string because + * the space is needed to accommodate the null + * Notice strcmp_s will check s1 or dst to make sure it is null terminated + */ + if (strcmp_s (dst, s1size - 1, + "Two things are infinite: the universe and human stupidity; " + "I am not sure about the universe", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* n > strlen src */ + strcpy_s (dst, sizeof (dst), s1); + err = strncat_s (dst, s1size, src, clib_strnlen (src, sizeof (src)) + 10); + if (err != EOK) + return -1; + if (strcmp_s (dst, s1size - 1, + "Two things are infinite: the universe and human stupidity; ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* zero length strncat */ + clib_strncpy (old_dst, dst, clib_strnlen (dst, sizeof (dst))); + err = strncat_s (dst, sizeof (dst), src, 0); + if (err != EOK) + return -1; + /* verify dst is untouched */ + if (strcmp_s (dst, s1size - 1, old_dst, &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* empty string, wrong n concatenation */ + err = strncat_s (dst, sizeof (dst), "", 10); + if (err != EOK) + return -1; + /* verify dst is untouched */ + if (strcmp_s (dst, s1size - 1, old_dst, &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* limited concatenation, string > n, copy up to n */ + strcpy_s (dst, sizeof (dst), s1); + err = strncat_s (dst, s1size, s2, 13); + if (err != EOK) + return -1; + if (strcmp_s (dst, s1size - 1, "Two things are infinite: the universe ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + /* verify it against strncat */ + strcpy_s (dst, sizeof (dst), s1); + strncat (dst, s2, 13); + if (strcmp_s (dst, s1size - 1, "Two things are infinite: the universe ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* negative stuff */ + err = strncat_s (0, 0, 0, 1); + if (err != EINVAL) + return -1; + + /* no room for dst -- dmax - strnlen_s (dst, dmax) == 0 */ + err = strncat_s (dst, clib_strnlen (dst, sizeof (dst)), s2, + clib_strnlen (s2, sizeof (s2))); + if (err != EINVAL) + return -1; + + /* overlap fail */ + err = strncat_s (dst, s1size, dst + 1, clib_strnlen (dst + 1, s1size - 1)); + if (err != EINVAL) + return -1; + + /* overlap fail */ + err = strncat_s (dst, s1size, dst, clib_strnlen (dst, sizeof (dst))); + if (err != EINVAL) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_strncat (vlib_main_t * vm, unformat_input_t * input) +{ + char src[100], dst[100], old_dst[100]; + size_t s1size = sizeof (dst); // including null + errno_t err; + char s1[] = "Two things are infinite: "; + char s2[] = "the universe and human stupidity; "; + int indicator; + + vlib_cli_output (vm, "Test clib_strncat..."); + + /* n == strlen src */ + strcpy_s (dst, sizeof (dst), s1); + strcpy_s (src, sizeof (src), s2); + err = clib_strncat (dst, src, clib_strnlen (src, sizeof (src))); + if (err != EOK) + return -1; + if (strcmp_s (dst, s1size - 1, + "Two things are infinite: the universe and human stupidity; ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + /* verify it against strncat */ + strcpy_s (dst, sizeof (dst), s1); + strncat (dst, src, clib_strnlen (src, sizeof (src))); + if (strcmp_s (dst, s1size - 1, + "Two things are infinite: the universe and human stupidity; ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* n > strlen src */ + strcpy_s (dst, sizeof (dst), s1); + err = clib_strncat (dst, src, clib_strnlen (src, sizeof (src)) + 10); + if (err != EOK) + return -1; + if (strcmp_s (dst, s1size - 1, + "Two things are infinite: the universe and human stupidity; ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + /* verify it against strncat */ + strcpy_s (dst, sizeof (dst), s1); + strncat (dst, src, clib_strnlen (src, sizeof (src))); + if (strcmp_s (dst, s1size - 1, + "Two things are infinite: the universe and human stupidity; ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* zero length strncat */ + clib_strncpy (old_dst, dst, clib_strnlen (dst, sizeof (dst))); + err = clib_strncat (dst, src, 0); + if (err != EOK) + return -1; + /* verify dst is untouched */ + if (strcmp_s (dst, s1size - 1, old_dst, &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* empty string, wrong n concatenation */ + err = clib_strncat (dst, "", 10); + if (err != EOK) + return -1; + /* verify dst is untouched */ + if (strcmp_s (dst, s1size - 1, old_dst, &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* limited concatenation, string > n, copy up to n */ + strcpy_s (dst, sizeof (dst), s1); + err = clib_strncat (dst, s2, 13); + if (err != EOK) + return -1; + if (strcmp_s (dst, s1size - 1, "Two things are infinite: the universe ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + /* verify it against strncat */ + strcpy_s (dst, sizeof (dst), s1); + strncat (dst, s2, 13); + if (strcmp_s (dst, s1size - 1, "Two things are infinite: the universe ", + &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* negative stuff */ + err = clib_strncat (0, 0, 1); + if (err != EINVAL) + return -1; + + /* overlap fail */ + err = clib_strncat (dst, dst + 1, s1size - 1); + if (err != EINVAL) + return -1; + + /* overlap fail */ + err = clib_strncat (dst, dst, clib_strnlen (dst, sizeof (dst))); + if (err != EINVAL) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_strtok_s (vlib_main_t * vm, unformat_input_t * input) +{ + int indicator; + char *tok, *ptr; + char str2[20]; + char str1[40]; + uword len; + char *p2str = 0; + char *tok1, *tok2, *tok3, *tok4, *tok5, *tok6, *tok7; + + vlib_cli_output (vm, "Test strtok_s..."); + strcpy_s (str1, sizeof (str1), "brevity is the soul of wit"); + len = strnlen_s (str1, sizeof (str1)); + tok1 = strtok_s (str1, &len, " ", &p2str); + tok2 = strtok_s (0, &len, " ", &p2str); + tok3 = strtok_s (0, &len, " ", &p2str); + tok4 = strtok_s (0, &len, " ", &p2str); + tok5 = strtok_s (0, &len, " ", &p2str); + tok6 = strtok_s (0, &len, " ", &p2str); + tok7 = strtok_s (0, &len, " ", &p2str); + if ((tok1 == 0) || + strcmp_s (tok1, strlen (tok1), "brevity", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok2 == 0) || strcmp_s (tok2, strlen (tok2), "is", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok3 == 0) || strcmp_s (tok3, strlen (tok3), "the", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok4 == 0) + || strcmp_s (tok4, strlen (tok4), "soul", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok5 == 0) || strcmp_s (tok5, strlen (tok5), "of", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok6 == 0) || strcmp_s (tok6, strlen (tok6), "wit", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if (tok7 != 0) + return -1; + + /* delimiter not present in the string -- the whole string is returned */ + strcpy_s (str1, sizeof (str1), "brevity is the soul of wit"); + len = strnlen_s (str1, sizeof (str1) - 1); + p2str = 0; + tok1 = strtok_s (str1, &len, ",", &p2str); + if ((tok1 == 0) || strcmp_s (tok1, strlen (tok1), str1, &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* negative stuff */ + tok = strtok_s (0, 0, 0, 0); + if (tok != 0) + return -1; + + /* s1 and ptr contents are null */ + ptr = 0; + tok = strtok_s (0, 0, 0, &ptr); + if (tok != 0) + return -1; + + /* unterminate s1 */ + p2str = 0; + len = strnlen_s (str1, sizeof (str1) - 1); + str1[strlen (str1)] = 0x2; + tok = strtok_s (str1, &len, ",", &p2str); + if (tok != 0) + return -1; + + /* + * unterminated s2. This test case in not perfect because there is no + * argument for s2max. But s2 len is limited to 16 characters. If the API + * does not find the null character at s2[15], it declares the string s2 + * as unterminated. + */ + memset_s (str2, sizeof (str2), 0xfa, sizeof (str2)); + tok = strtok_s (str1, &len, str2, &p2str); + if (tok != 0) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_strtok (vlib_main_t * vm, unformat_input_t * input) +{ + int indicator; + char *tok, *ptr, *s1; + char str1[40]; + char *p2str; + char *tok1, *tok2, *tok3, *tok4, *tok5, *tok6, *tok7; + + vlib_cli_output (vm, "Test clib_strtok..."); + strcpy_s (str1, sizeof (str1), "brevity is the soul of wit"); + p2str = 0; + tok1 = clib_strtok (str1, " ", &p2str); + tok2 = clib_strtok (0, " ", &p2str); + tok3 = clib_strtok (0, " ", &p2str); + tok4 = clib_strtok (0, " ", &p2str); + tok5 = clib_strtok (0, " ", &p2str); + tok6 = clib_strtok (0, " ", &p2str); + tok7 = clib_strtok (0, " ", &p2str); + if ((tok1 == 0) || + strcmp_s (tok1, strlen (tok1), "brevity", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok2 == 0) || strcmp_s (tok2, strlen (tok2), "is", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok3 == 0) || strcmp_s (tok3, strlen (tok3), "the", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok4 == 0) + || strcmp_s (tok4, strlen (tok4), "soul", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok5 == 0) || strcmp_s (tok5, strlen (tok5), "of", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok6 == 0) || strcmp_s (tok6, strlen (tok6), "wit", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if (tok7 != 0) + return -1; + /* verify it againest strtok_r */ + strcpy_s (str1, sizeof (str1), "brevity is the soul of wit"); + p2str = 0; + tok1 = strtok_r (str1, " ", &p2str); + tok2 = strtok_r (0, " ", &p2str); + tok3 = strtok_r (0, " ", &p2str); + tok4 = strtok_r (0, " ", &p2str); + tok5 = strtok_r (0, " ", &p2str); + tok6 = strtok_r (0, " ", &p2str); + tok7 = strtok_r (0, " ", &p2str); + if ((tok1 == 0) || + strcmp_s (tok1, strlen (tok1), "brevity", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok2 == 0) || strcmp_s (tok2, strlen (tok2), "is", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok3 == 0) || strcmp_s (tok3, strlen (tok3), "the", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok4 == 0) + || strcmp_s (tok4, strlen (tok4), "soul", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok5 == 0) || strcmp_s (tok5, strlen (tok5), "of", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if ((tok6 == 0) || strcmp_s (tok6, strlen (tok6), "wit", &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + if (tok7 != 0) + return -1; + + /* delimiter not present in the string -- the whole string is returned */ + strcpy_s (str1, sizeof (str1), "brevity is the soul of wit"); + p2str = 0; + tok1 = clib_strtok (str1, ",", &p2str); + if ((tok1 == 0) || strcmp_s (tok1, strlen (tok1), str1, &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + /* verify it against strtok_r */ + strcpy_s (str1, sizeof (str1), "brevity is the soul of wit"); + p2str = 0; + tok1 = strtok_r (str1, ",", &p2str); + if ((tok1 == 0) || strcmp_s (tok1, strlen (tok1), str1, &indicator) != EOK) + return -1; + if (indicator != 0) + return -1; + + /* negative stuff */ + s1 = 0; + ptr = 0; + tok = clib_strtok (s1, s1, (char **) 0); + if (tok != 0) + return -1; + + /* s1 and ptr contents are null */ + tok = clib_strtok (s1, s1, &ptr); + if (tok != 0) + return -1; + /* verify it against strtok_r */ + /* No can do. This causes a crash in strtok_r */ + // tok = strtok_r (s1, " ", &ptr); + // if (tok != 0) + // return -1; + + /* + * Can't test unterminated string s1 and s2 becuase clib_strtok does not + * supply s1 and s2 max + */ + + /* OK, seems to work */ + return 0; +} + +static int +test_strnlen_s (vlib_main_t * vm, unformat_input_t * input) +{ + const char s1[] = "Truth is incontrovertible"; + size_t len; + + vlib_cli_output (vm, "Test strnlen_s..."); + + len = strnlen_s (s1, sizeof (s1)); + if (len != sizeof (s1) - 1) + return -1; + + len = strnlen_s (s1, 5); + if (len != 5) + return -1; + + /* negative stuff */ + len = strnlen_s (0, 0); + if (len != 0) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_strnlen (vlib_main_t * vm, unformat_input_t * input) +{ + const char s1[] = "Truth is incontrovertible"; + size_t len; + + vlib_cli_output (vm, "Test clib_strnlen..."); + + len = clib_strnlen (s1, sizeof (s1)); + if (len != sizeof (s1) - 1) + return -1; + + len = clib_strnlen (s1, 5); + if (len != 5) + return -1; + + /* negative stuff */ + len = clib_strnlen (0, 0); + if (len != 0) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_strstr_s (vlib_main_t * vm, unformat_input_t * input) +{ + errno_t err; + char *sub = 0; + char s1[64]; + size_t s1len = sizeof (s1) - 1; // excluding null + int indicator; + + vlib_cli_output (vm, "Test strstr_s..."); + + /* substring not present */ + strcpy_s (s1, s1len, "success is not final, failure is not fatal."); + err = strstr_s (s1, s1len, "failures", sizeof ("failures"), &sub);; + if (err != ESRCH) + return -1; + + /* substring present */ + err = strstr_s (s1, s1len, "failure", sizeof ("failure"), &sub); + if (err != EOK) + return -1; + + if ((sub == 0) || + strcmp_s (sub, strlen (sub), "failure is not fatal.", &indicator) + != EOK) + return -1; + if (indicator != 0) + return -1; + + /* negative stuff */ + + /* Null pointers test */ + err = strstr_s (0, 0, 0, 0, 0); + if (err != EINVAL) + return -1; + + /* unterminated s1 and s2 */ + memset_s (s1, ARRAY_LEN (s1), 0xfe, ARRAY_LEN (s1)); + err = strstr_s (s1, s1len, s1, s1len, &sub); + if (err != EINVAL) + return -1; + + /* OK, seems to work */ + return 0; +} + +static int +test_clib_strstr (vlib_main_t * vm, unformat_input_t * input) +{ + char *sub, *s; + char s1[64]; + size_t s1len = sizeof (s1) - 1; // excluding null + int indicator; + + vlib_cli_output (vm, "Test clib_strstr..."); + + /* substring not present */ + strcpy_s (s1, s1len, "success is not final, failure is not fatal."); + sub = clib_strstr (s1, "failures"); + if (sub != 0) + return -1; + /* verify it against strstr */ + sub = strstr (s1, "failures"); + if (sub != 0) + return -1; + + /* substring present */ + sub = clib_strstr (s1, "failure"); + if (sub == 0) + return -1; + if (strcmp_s (sub, strlen (sub), "failure is not fatal.", &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + /* verify it against strstr */ + sub = strstr (s1, "failure"); + if (sub == 0) + return -1; + if (strcmp_s (sub, strlen (sub), "failure is not fatal.", &indicator) != + EOK) + return -1; + if (indicator != 0) + return -1; + + /* negative stuff */ + + /* Null pointers test */ + s = 0; + sub = clib_strstr (s, s); + if (sub != 0) + return -1; + /* + * Can't verify it against strstr for this test. Null pointers cause strstr + * to crash. Go figure! + */ + + /* unterminated s1 and s2 */ + memset_s (s1, ARRAY_LEN (s1), 0xfe, ARRAY_LEN (s1)); + sub = clib_strstr (s1, s1); + if (sub == 0) + return -1; + /* + * Can't verify it against strstr for this test. Unterminated string causes + * strstr to crash. Go figure! + */ + + /* OK, seems to work */ + return 0; +} + +#define foreach_string_test \ + _ (0, MEMCPY_S, "memcpy_s", memcpy_s) \ + _ (1, CLIB_MEMCPY, "clib_memcpy", clib_memcpy) \ + _ (2, MEMSET_S , "memset_s", memset_s) \ + _ (3, CLIB_MEMSET , "clib_memset", clib_memset) \ + _ (4, MEMCMP_S, "memcmp_s", memcmp_s) \ + _ (5, CLIB_MEMCMP, "clib_memcmp", clib_memcmp) \ + _ (6, STRCMP_S, "strcmp_s", strcmp_s) \ + _ (7, CLIB_STRCMP, "clib_strcmp", clib_strcmp) \ + _ (8, STRNCMP_S, "strncmp_s", strncmp_s) \ + _ (9, CLIB_STRNCMP, "clib_strncmp", clib_strncmp) \ + _ (10, STRCPY_S, "strcpy_s", strcpy_s) \ + _ (11, CLIB_STRCPY, "clib_strcpy", clib_strcpy) \ + _ (12, STRNCPY_S, "strncpy_s", strncpy_s) \ + _ (13, CLIB_STRNCPY, "clib_strncpy", clib_strncpy) \ + _ (14, STRCAT_S, "strcat_s", strcat_s) \ + _ (15, CLIB_STRCAT, "clib_strcat", clib_strcat) \ + _ (16, STRNCAT_S, "strncat_s", strncat_s) \ + _ (17, CLIB_STRNCAT, "clib_strncat", clib_strncat) \ + _ (18, STRTOK_S, "strtok_s", strtok_s) \ + _ (19, CLIB_STRTOK, "clib_strtok", clib_strtok) \ + _ (20, STRNLEN_S, "strnlen_s", strnlen_s) \ + _ (21, CLIB_STRNLEN, "clib_strnlen", clib_strnlen) \ + _ (22, STRSTR_S, "strstr_s", strstr_s) \ + _ (23, CLIB_STRSTR, "clib_strstr", clib_strstr) + +typedef enum +{ +#define _(v,f,s,p) STRING_TEST_##f = v, + foreach_string_test +#undef _ +} string_test_t; + +static uword +unformat_string_test (unformat_input_t * input, va_list * args) +{ + u8 *r = va_arg (*args, u8 *); + + if (0) + ; +#define _(v,f,s,p) else if (unformat (input, s)) *r = STRING_TEST_##f; + foreach_string_test +#undef _ + else + return 0; + + return 1; +} + +typedef int (*string_test_func) (vlib_main_t * vm, unformat_input_t * input); + +typedef struct +{ + string_test_func test; +} string_test_func_t; + static clib_error_t * string_test_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd_arg) { - int res = 0; - u8 t_memcpy = 0; - u8 t_clib_memset = 0; - u8 specific_test; + string_test_func_t string_func[] = { +#define _(v,f,s,p) { test_##p }, + foreach_string_test +#undef _ + }; + const char *string_table[] = { +#define _(v,f,s,p) s, + foreach_string_test +#undef _ + }; + int res = 0, ok; + i8 specific_test = ~0; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { - if (unformat (input, "memcpy_s")) - t_memcpy = 1; - else if (unformat (input, "memset_s")) - t_clib_memset = 1; - break; + if (unformat (input, "%U", unformat_string_test, &specific_test)) + break; + else + return clib_error_return (0, "unknown input `%U'", + format_unformat_error, input); } - specific_test = t_memcpy + t_clib_memset; - - if (specific_test == 0) + if (specific_test == ~0) { - res = test_memcpy (vm, input); - res += test_clib_memset (vm, input); - goto done; + for (specific_test = STRING_TEST_MEMCPY_S; + specific_test <= STRING_TEST_CLIB_STRSTR; specific_test++) + { + ok = (string_func[specific_test]).test (vm, input); + res += ok; + if (ok != 0) + vlib_cli_output (vm, "test_%s failed", + string_table[specific_test]); + } } - - if (t_memcpy) - res = test_memcpy (vm, input); - else if (t_clib_memset) - res = test_clib_memset (vm, input); - -done: + else + res = (string_func[specific_test]).test (vm, input); if (res) vlib_cli_output (vm, "String unit test(s) failed..."); else @@ -131,7 +1660,12 @@ done: VLIB_CLI_COMMAND (string_test_command, static) = { .path = "test string", - .short_help = "string library tests", + .short_help = "test string [memcpy_s | clib_memcpy | memset_s | " + "clib_memset | memcmp_s | clib_memcmp | strcmp_s | clib_strcmp | " + "strncmp_s | clib_strncmp | strcpy_s | clib_strcpy | strncpy_s | " + "clib_strncpy | strcat_s | clib_strcat | strncat_s | clib_strncat | " + "strtok_s | clib_strtok | strnlen_s | clib_strnlen | strstr_s | " + "clib_strstr]", .function = string_test_command_fn, }; /* *INDENT-ON* */ diff --git a/src/vppinfra/string.c b/src/vppinfra/string.c index b90f432c0cc..feae5b9165c 100644 --- a/src/vppinfra/string.c +++ b/src/vppinfra/string.c @@ -38,6 +38,12 @@ #include <vppinfra/string.h> #include <vppinfra/error.h> +/** + * @file + * @brief String Handling routines, including a performant + * implementation of many c-11 "safe" string functions. + */ + /* Exchanges source and destination. */ void clib_memswap (void *_a, void *_b, uword bytes) @@ -91,6 +97,25 @@ clib_c11_violation (const char *s) _clib_error (CLIB_ERROR_WARNING, (char *) __FUNCTION__, 0, (char *) s); } +/** + * @brief copy src to dest, at most n bytes, up to dmax + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *dest pointer to memory to copy to + * @param dmax maximum length of resulting dest + * @param *src pointer to memory to copy from + * @param n maximum number of characters to copy from src + * + * @constraints No null pointers + * n shall not be greater than dmax + * no memory overlap between src and dest + * + * @return EOK success + * EINVAL runtime constraint error + * + */ errno_t memcpy_s (void *__restrict__ dest, rsize_t dmax, const void *__restrict__ src, rsize_t n) @@ -98,12 +123,356 @@ memcpy_s (void *__restrict__ dest, rsize_t dmax, return memcpy_s_inline (dest, dmax, src, n); } +/** + * @brief set n bytes starting at s to the specified c value + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *s pointer to memory to set the c value + * @param smax maximum length of resulting s + * @param c byte value + * @param n maximum number of characters to set in s + * + * @constraints No null pointers + * n shall not be greater than smax + * + * @return EOK success + * EINVAL runtime constraint error + * + */ errno_t memset_s (void *s, rsize_t smax, int c, rsize_t n) { return memset_s_inline (s, smax, c, n); } +/** + * @brief compare memory until they differ, and their difference is returned in + * diff + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *s1 pointer to memory to compare against + * @param s1max maximum length of s1 + * @param *s2 pointer to memory to compare with s1 + * @param s2max length of s2 + * @param *diff pointer to the diff which is an integer greater than, equal to, + * or less than zero according to s1 is greater than, equal to, + * or less than s2. + * + * @constraints No null pointers + * s1max and s2max shall not be zero + * s2max shall not be greater than s1max + * + * @return EOK success + * diff when the return code is EOK + * >0 s1 greater s2 + * 0 s1 == s2 + * <0 s1 < s2 + * EINVAL runtime constraint error + * + */ +errno_t +memcmp_s (const void *s1, rsize_t s1max, const void *s2, rsize_t s2max, + int *diff) +{ + return memcmp_s_inline (s1, s1max, s2, s2max, diff); +} + +/** + * @brief compare string s2 to string s1, and their difference is returned in + * indicator + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *s1 pointer to string to compare against + * @param s1max maximum length of s1, excluding null + * @param *s2 pointer to string to compare with s1 + * @param *indicator pointer to the comparison result, which is an integer + * greater than, equal to, or less than zero according to + * s1 is greater than, equal to, or less than s2. + * + * @constraints No null pointers + * s1max shall not be zero + * s1 shall be null terminated + * n shall not be greater than the smaller of s1max and strlen + * of s1 + * + * @return EOK success + * indicator when the return code is EOK + * >0 s1 greater s2 + * 0 s1 == s2 + * <0 s1 < s2 + * EINVAL runtime constraint error + * + */ +errno_t +strcmp_s (const char *s1, rsize_t s1max, const char *s2, int *indicator) +{ + return strcmp_s_inline (s1, s1max, s2, indicator); +} + +/** + * @brief compare string s2 to string s1, no more than n characters, and their + * difference is returned in indicator + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *s1 pointer to string to compare against + * @param s1max maximum length of s1, excluding null + * @param *s2 pointer to string to compare with s1 + * @param n maximum number of characters to compare + * @param *indicator pointer to the comparison result, which is an integer + * greater than, equal to, or less than zero according to + * s1 is greater than, equal to, or less than s2. + * + * @constraints No null pointers + * s1max shall not be zero + * s1 shall be null terminated + * + * @return EOK success + * indicator when the return code is EOK + * >0 s1 greater s2 + * 0 s1 == s2 + * <0 s1 < s2 + * EINVAL runtime constraint error + * + */ +errno_t +strncmp_s (const char *s1, rsize_t s1max, const char *s2, rsize_t n, + int *indicator) +{ + return strncmp_s_inline (s1, s1max, s2, n, indicator); +} + +/** + * @brief copy src string to dest string + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *dest pointer to string to copy to + * @param dmax maximum length of resulting dest string, including null + * @param *src pointer to string to copy from + * + * @constraints No null pointers + * dmax shall not be zero + * dmax shall be greater than string length of src + * no memory overlap between src and dest + * + * @return EOK success + * EINVAL runtime constraint error + * + */ +errno_t +strcpy_s (char *__restrict__ dest, rsize_t dmax, const char *__restrict__ src) +{ + return strcpy_s_inline (dest, dmax, src); +} + +/** + * @brief copy src string to dest string, no more than n characters + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *dest pointer to string to copy to + * @param dmax maximum length of resulting dest string, including null + * @param *src pointer to string to copy from + * @param n maximum number of characters to copy from src, excluding null + * + * @constraints No null pointers + * dmax shall not be zero + * no memory overlap between src and dest + * + * @return EOK success + * EINVAL runtime constraint error + * EOVERFLOW truncated operation. dmax - 1 characters were copied. + * dest is null terminated. + * + */ +errno_t +strncpy_s (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src, rsize_t n) +{ + return strncpy_s_inline (dest, dmax, src, n); +} + +/** + * @brief append src string to dest string, including null + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *dest pointer to string to append to + * @param dmax maximum length of resulting dest string, including null + * @param *src pointer to string to append from + * + * @constraints No null pointers + * dmax shall not be zero + * dest shall be null terminated + * given m = dmax - strnlen (dest, dmax) + * n = strnlen (src, m) + * n shall not be >= m + * no memory overlap between src and dest + * + * @return EOK success + * EINVAL runtime constraint error + * + */ +errno_t +strcat_s (char *__restrict__ dest, rsize_t dmax, const char *__restrict__ src) +{ + return strcat_s_inline (dest, dmax, src); +} + +/** + * @brief append src string to dest string, including null, no more than n + * characters + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *dest pointer to string to append to + * @param dmax maximum length of resulting dest string, including null + * @param *src pointer to string to append from + * @param n maximum characters to append (excluding null) + * + * @constraints No null pointers + * dmax shall not be zero + * dest shall be null terminated + * dmax - strnlen (dest, dmax) shall not be zero + * no memory overlap between src and dest + * + * @return EOK success + * EINVAL runtime constraint error + * EOVERFLOW truncated operation. dmax - 1 characters were appended. + * dest is null terminated. + * + */ +errno_t +strncat_s (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src, rsize_t n) +{ + return strncat_s_inline (dest, dmax, src, n); +} + +/** + * @brief tokenize string s1 with delimiter specified in s2. This is a stateful + * API when it is iterately called, it returns the next token from s1 + * which is delimited by s2. s1max and ptr maintain the stateful + * information for the same caller and must not be altered by the + * caller during the iteration for the correct result + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *s1 pointer to string to be searched for substring + * @param *s1max restricted maximum length of s1 + * @param *s2 pointer to substring to search (16 characters max, + * including null) + * @param **ptr in/out pointer which maintains the stateful information + * + * @constraints s2, s1max, and ptr shall not be null + * if s1 is null, contents of ptr shall not be null + * s1 and s2 shall be null terminated + * + * @return non-null pointer to the first character of a token + * s1max and ptr are modified to contain the state + * null runtime constraint error or token is not found + * + * @example + * char *str2 = " "; + * char str1[100]; + * uword len; + * char *p2str = 0; + * char *tok1, *tok2, *tok3, *tok4, *tok5, *tok6, *tok7; + * + * strncpy (str1, "brevity is the soul of wit", sizeof (str1)); + * len = strlen (str1); + * tok1 = strtok_s (str1, &len, str2, &p2str); + * tok2 = strtok_s (0, &len, str2, &p2str); + * tok3 = strtok_s (0, &len, str2, &p2str); + * tok4 = strtok_s (0, &len, str2, &p2str); + * tok5 = strtok_s (0, &len, str2, &p2str); + * tok6 = strtok_s (0, &len, str2, &p2str); + * tok7 = strtok_s (0, &len, str2, &p2str); + * + * After the above series of calls, + * tok1 = "brevity", tok2 = "is", tok3 = "the", tok4 = "soul", tok5 = "of", + * tok6 = "wit", tok7 = null + */ +char * +strtok_s (char *__restrict__ s1, rsize_t * __restrict__ s1max, + const char *__restrict__ s2, char **__restrict__ ptr) +{ + return strtok_s_inline (s1, s1max, s2, ptr); +} + +/** + * @brief compute the length in s, no more than maxsize + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *s pointer to string + * @param maxsize restricted maximum length + * + * @constraints No null pointers + * maxsize shall not be zero + * + * @return size_t the string length in s, excluding null character, and no + * more than maxsize or 0 if there is a constraint error + * + */ +size_t +strnlen_s (const char *s, size_t maxsize) +{ + return strnlen_s_inline (s, maxsize); +} + +/** + * @brief locate the first occurrence of the substring s2 in s1 + * + * ISO/IEC 9899:2017(C11), Porgramming languages -- C + * Annex K; Bounds-checking interfaces + * + * @param *s1 pointer to string to be searched for substring + * @param s1max restricted maximum length of s1 + * @param *s2 pointer to substring to search + * @param s2max restricted maximum length of s2 + * @param **substring pointer to pointer substring to be returned + * + * @constraints No null pointers + * s1max and s2max shall not be zero + * s1 and s2 shall be null terminated + * + * @return EOK success + * substring when the return code is EOK, it contains the pointer which + * points to s1 that matches s2 + * EINVAL runtime constraint error + * ESRCH no match + * + * @example + * char *sub = 0; + * char *s1 = "success is not final, failure is not fatal."; + * + * strstr_s (s1, strlen (s1), "failure", strlen ("failure"), &sub); + * + * After the above call, + * sub = "failure is not fatal." + */ +errno_t +strstr_s (char *s1, rsize_t s1max, const char *s2, rsize_t s2max, + char **substring) +{ + return strstr_s_inline (s1, s1max, s2, s2max, substring); +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vppinfra/string.h b/src/vppinfra/string.h index b00c0cfbcc2..a25d461868b 100644 --- a/src/vppinfra/string.h +++ b/src/vppinfra/string.h @@ -92,6 +92,23 @@ void clib_memswap (void *_a, void *_b, uword bytes); #ifndef EINVAL #define EINVAL 22 #endif +#ifndef ESRCH +#define ESRCH 3 +#endif +#ifndef EOVERFLOW +#define EOVERFLOW 75 +#endif + +/* + * In order to provide smooth mapping from unsafe string API to the clib string + * macro, we often have to improvise s1max and s2max due to the additional + * arguments are required for implementing the safe API. This macro is used + * to provide the s1max/s2max. It is not perfect becuase the actual + * s1max/s2max may be greater than 4k and the mapping from the unsafe API to + * the macro would cause a regression. However, it is not terribly likely. + * So I bet against the odds. + */ +#define CLIB_STRING_MACRO_MAX 4096 typedef int errno_t; typedef uword rsize_t; @@ -693,6 +710,702 @@ clib_count_equal_u8 (u8 * data, uword max_count) return count; } +/* + * This macro is to provide smooth mapping from memcmp to memcmp_s. + * memcmp has fewer parameters and fewer returns than memcmp_s. + * This macro is somewhat a crutch. When err != EOK is returned from memcmp_s, + * we return 0 and spit out a message in the console because there is + * no way to return the error code to the memcmp callers. + * This condition happens when s1 or s2 is null. Please note + * in the extant memcmp calls, if s1, s2, or both are null, memcmp returns 0 + * anyway. So we are consistent in this case for the comparison return + * although we also spit out a C11 violation message in the console to + * warn that they pass null pointers for both s1 and s2. + * Applications are encouraged to use the cool C11 memcmp_s API to get the + * maximum benefit out of it. + */ +#define clib_memcmp(s1,s2,m1) \ + ({ int __diff = 0; \ + memcmp_s_inline (s1, m1, s2, m1, &__diff); \ + __diff; \ + }) + +errno_t memcmp_s (const void *s1, rsize_t s1max, const void *s2, + rsize_t s2max, int *diff); + +always_inline errno_t +memcmp_s_inline (const void *s1, rsize_t s1max, const void *s2, rsize_t s2max, + int *diff) +{ + u8 bad; + + bad = (s1 == 0) + (s2 == 0) + (diff == 0) + (s2max > s1max) + (s2max == 0) + + (s1max == 0); + + if (PREDICT_FALSE (bad != 0)) + { + if (s1 == NULL) + clib_c11_violation ("s1 NULL"); + if (s2 == NULL) + clib_c11_violation ("s2 NULL"); + if (diff == NULL) + clib_c11_violation ("diff NULL"); + if (s2max > s1max) + clib_c11_violation ("s2max > s1max"); + if (s2max == 0) + clib_c11_violation ("s2max 0"); + if (s1max == 0) + clib_c11_violation ("s1max 0"); + return EINVAL; + } + + if (PREDICT_FALSE (s1 == s2)) + { + *diff = 0; + return EOK; + } + + *diff = memcmp (s1, s2, s2max); + return EOK; +} + +/* + * This macro is to provide smooth mapping from strnlen to strnlen_s + */ +#define clib_strnlen(s,m) strnlen_s_inline(s,m) + +size_t strnlen_s (const char *s, size_t maxsize); + +always_inline size_t +strnlen_s_inline (const char *s, size_t maxsize) +{ + u8 bad; + + bad = (s == 0) + (maxsize == 0); + if (PREDICT_FALSE (bad != 0)) + { + if (s == 0) + clib_c11_violation ("s NULL"); + if (maxsize == 0) + clib_c11_violation ("maxsize 0"); + return 0; + } + return strnlen (s, maxsize); +} + +/* + * This macro is to provide smooth mapping from strcmp to strcmp_s. + * strcmp has fewer parameters and fewer returns than strcmp_s. + * This macro is somewhat a crutch. When err != EOK is returned from strcmp_s, + * we return 0 and spit out a message in the console because + * there is no way to return the error to the strcmp callers. + * This condition happens when s1 or s2 is null. Please note in the extant + * strcmp call, they would end up crashing if one of them is null. + * So the new behavior is no crash, but an error is displayed in the + * console which I think is more user friendly. If both s1 and s2 are null, + * strcmp returns 0. Obviously, strcmp did the pointers comparison prior + * to actually accessing the pointer contents. We are still consistent + * in this case for the comparison return although we also spit out a + * C11 violation message in the console to warn that they pass null pointers + * for both s1 and s2. The other problem is strcmp does not provide s1max, + * we use CLIB_STRING_MACRO_MAX and hopefully, s1 is null terminated. + * If not, we may be accessing memory beyonf what is intended. + * Applications are encouraged to use the cool C11 strcmp_s API to get the + * maximum benefit out of it. + */ +#define clib_strcmp(s1,s2) \ + ({ int __indicator = 0; \ + strcmp_s_inline (s1, CLIB_STRING_MACRO_MAX, s2, &__indicator); \ + __indicator; \ + }) + +errno_t strcmp_s (const char *s1, rsize_t s1max, const char *s2, + int *indicator); + +always_inline errno_t +strcmp_s_inline (const char *s1, rsize_t s1max, const char *s2, + int *indicator) +{ + u8 bad; + + bad = (indicator == 0) + (s1 == 0) + (s2 == 0) + (s1max == 0) + + (s1 && s1max && s1[clib_strnlen (s1, s1max)] != '\0'); + + if (PREDICT_FALSE (bad != 0)) + { + if (indicator == NULL) + clib_c11_violation ("indicator NULL"); + if (s1 == NULL) + clib_c11_violation ("s1 NULL"); + if (s2 == NULL) + clib_c11_violation ("s2 NULL"); + if (s1max == 0) + clib_c11_violation ("s1max 0"); + if (s1 && s1max && s1[clib_strnlen (s1, s1max)] != '\0') + clib_c11_violation ("s1 unterminated"); + return EINVAL; + } + + *indicator = strcmp (s1, s2); + return EOK; +} + +/* + * This macro is to provide smooth mapping from strncmp to strncmp_s. + * strncmp has fewer parameters and fewer returns than strncmp_s. That said, + * this macro is somewhat a crutch. When we get err != EOK from strncmp_s, + * we return 0 and spit out a message in the console because there is no + * means to return the error to the strncmp caller. + * This condition happens when s1 or s2 is null. In the extant strncmp call, + * they would end up crashing if one of them is null. So the new behavior is + * no crash, but error is displayed in the console which is more + * user friendly. If s1 and s2 are null, strncmp returns 0. Obviously, + * strncmp did the pointers comparison prior to actually accessing the + * pointer contents. We are still consistent in this case for the comparison + * return although we also spit out a C11 violation message in the console to + * warn that they pass null pointers for both s1 and s2. + * Applications are encouraged to use the cool C11 strncmp_s API to get the + * maximum benefit out of it. + */ +#define clib_strncmp(s1,s2,n) \ + ({ int __indicator = 0; \ + strncmp_s_inline (s1, CLIB_STRING_MACRO_MAX, s2, n, &__indicator); \ + __indicator; \ + }) + +errno_t strncmp_s (const char *s1, rsize_t s1max, const char *s2, rsize_t n, + int *indicator); + +always_inline errno_t +strncmp_s_inline (const char *s1, rsize_t s1max, const char *s2, rsize_t n, + int *indicator) +{ + u8 bad; + u8 s1_greater_s1max = (s1 && s1max && n > clib_strnlen (s1, s1max)); + + if (PREDICT_FALSE (s1_greater_s1max && indicator)) + { + /* + * strcmp allows n > s1max. If indicator is non null, we can still + * do the compare without any harm and return EINVAL as well as the + * result in indicator. + */ + clib_c11_violation ("n exceeds s1 length"); + *indicator = strncmp (s1, s2, n); + return EINVAL; + } + + bad = (s1 == 0) + (s2 == 0) + (indicator == 0) + (s1max == 0) + + (s1 && s1max && s1[clib_strnlen (s1, s1max)] != '\0') + s1_greater_s1max; + + if (PREDICT_FALSE (bad != 0)) + { + if (indicator == NULL) + clib_c11_violation ("indicator NULL"); + if (s1 == NULL) + clib_c11_violation ("s1 NULL"); + if (s2 == NULL) + clib_c11_violation ("s2 NULL"); + if (s1max == 0) + clib_c11_violation ("s1max 0"); + if (s1 && s1max && s1[clib_strnlen (s1, s1max)] != '\0') + clib_c11_violation ("s1 unterminated"); + if (s1_greater_s1max) + clib_c11_violation ("n exceeds s1 length"); + return EINVAL; + } + + *indicator = strncmp (s1, s2, n); + return EOK; +} + +/* + * This macro is provided for smooth migration from strcpy. It is not perfect + * because we don't know the size of the destination buffer to pass to strcpy_s. + * We improvise dmax with CLIB_STRING_MACRO_MAX. + * Applications are encouraged to move to the C11 strcpy_s API. + */ +#define clib_strcpy(d,s) strcpy_s_inline(d,CLIB_STRING_MACRO_MAX,s) + +errno_t strcpy_s (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src); + +always_inline errno_t +strcpy_s_inline (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src) +{ + u8 bad; + uword low, hi; + size_t n; + + bad = (dest == 0) + (dmax == 0) + (src == 0); + if (PREDICT_FALSE (bad != 0)) + { + if (dest == 0) + clib_c11_violation ("dest NULL"); + if (src == 0) + clib_c11_violation ("src NULL"); + if (dmax == 0) + clib_c11_violation ("dmax 0"); + return EINVAL; + } + + n = clib_strnlen (src, dmax); + if (PREDICT_FALSE (n >= dmax)) + { + clib_c11_violation ("not enough space for dest"); + return (EINVAL); + } + /* Not actually trying to copy anything is OK */ + if (PREDICT_FALSE (n == 0)) + return EOK; + + /* Check for src/dst overlap, which is not allowed */ + low = (uword) (src < dest ? src : dest); + hi = (uword) (src < dest ? dest : src); + + if (PREDICT_FALSE (low + (n - 1) >= hi)) + { + clib_c11_violation ("src/dest overlap"); + return EINVAL; + } + + clib_memcpy_fast (dest, src, n); + dest[n] = '\0'; + return EOK; +} + +/* + * This macro is provided for smooth migration from strncpy. It is not perfect + * because we don't know the size of the destination buffer to pass to + * strncpy_s. We improvise dmax with CLIB_STRING_MACRO_MAX. + * Applications are encouraged to move to the C11 strncpy_s API and provide + * the correct dmax for better error checking. + */ +#define clib_strncpy(d,s,n) strncpy_s_inline(d,CLIB_STRING_MACRO_MAX,s,n) + +errno_t +strncpy_s (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src, rsize_t n); + +always_inline errno_t +strncpy_s_inline (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src, rsize_t n) +{ + u8 bad; + uword low, hi; + rsize_t m; + errno_t status = EOK; + + bad = (dest == 0) + (dmax == 0) + (src == 0) + (n == 0); + if (PREDICT_FALSE (bad != 0)) + { + /* Not actually trying to copy anything is OK */ + if (n == 0) + return EOK; + if (dest == 0) + clib_c11_violation ("dest NULL"); + if (src == 0) + clib_c11_violation ("src NULL"); + if (dmax == 0) + clib_c11_violation ("dmax 0"); + return EINVAL; + } + + if (PREDICT_FALSE (n >= dmax)) + { + /* Relax and use strnlen of src */ + clib_c11_violation ("n >= dmax"); + m = clib_strnlen (src, dmax); + if (m >= dmax) + { + /* Truncate, adjust copy length to fit dest */ + m = dmax - 1; + status = EOVERFLOW; + } + } + else + m = n; + + /* Check for src/dst overlap, which is not allowed */ + low = (uword) (src < dest ? src : dest); + hi = (uword) (src < dest ? dest : src); + + if (PREDICT_FALSE (low + (m - 1) >= hi)) + { + clib_c11_violation ("src/dest overlap"); + return EINVAL; + } + + clib_memcpy_fast (dest, src, m); + dest[m] = '\0'; + return status; +} + +/* + * This macro is to provide smooth migration from strcat to strcat_s. + * Because there is no dmax in strcat, we improvise it with + * CLIB_STRING_MACRO_MAX. Please note there may be a chance to overwrite dest + * with too many bytes from src. + * Applications are encouraged to use C11 API to provide the actual dmax + * for proper checking and protection. + */ +#define clib_strcat(d,s) strcat_s_inline(d,CLIB_STRING_MACRO_MAX,s) + +errno_t strcat_s (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src); + +always_inline errno_t +strcat_s_inline (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src) +{ + u8 bad; + uword low, hi; + size_t m, n, dest_size; + + bad = (dest == 0) + (dmax == 0) + (src == 0); + if (PREDICT_FALSE (bad != 0)) + { + if (dest == 0) + clib_c11_violation ("dest NULL"); + if (src == 0) + clib_c11_violation ("src NULL"); + if (dmax == 0) + clib_c11_violation ("dmax 0"); + return EINVAL; + } + + dest_size = clib_strnlen (dest, dmax); + m = dmax - dest_size; + n = clib_strnlen (src, m); + if (PREDICT_FALSE (n >= m)) + { + clib_c11_violation ("not enough space for dest"); + return EINVAL; + } + + /* Not actually trying to concatenate anything is OK */ + if (PREDICT_FALSE (n == 0)) + return EOK; + + /* Check for src/dst overlap, which is not allowed */ + low = (uword) (src < dest ? src : dest); + hi = (uword) (src < dest ? dest : src); + + if (PREDICT_FALSE (low + (n - 1) >= hi)) + { + clib_c11_violation ("src/dest overlap"); + return EINVAL; + } + + clib_memcpy_fast (dest + dest_size, src, n); + dest[dest_size + n] = '\0'; + return EOK; +} + +/* + * This macro is to provide smooth migration from strncat to strncat_s. + * The unsafe strncat does not have s1max. We improvise it with + * CLIB_STRING_MACRO_MAX. Please note there may be a chance to overwrite + * dest with too many bytes from src. + * Applications are encouraged to move to C11 strncat_s which requires dmax + * from the caller and provides checking to safeguard the memory corruption. + */ +#define clib_strncat(d,s,n) strncat_s_inline(d,CLIB_STRING_MACRO_MAX,s,n) + +errno_t strncat_s (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src, rsize_t n); + +always_inline errno_t +strncat_s_inline (char *__restrict__ dest, rsize_t dmax, + const char *__restrict__ src, rsize_t n) +{ + u8 bad; + uword low, hi; + size_t m, dest_size, allowed_size; + errno_t status = EOK; + + bad = (dest == 0) + (src == 0) + (dmax == 0) + (n == 0); + if (PREDICT_FALSE (bad != 0)) + { + /* Not actually trying to concatenate anything is OK */ + if (n == 0) + return EOK; + if (dest == 0) + clib_c11_violation ("dest NULL"); + if (src == 0) + clib_c11_violation ("src NULL"); + if (dmax == 0) + clib_c11_violation ("dmax 0"); + return EINVAL; + } + + /* Check for src/dst overlap, which is not allowed */ + low = (uword) (src < dest ? src : dest); + hi = (uword) (src < dest ? dest : src); + + if (PREDICT_FALSE (low + (n - 1) >= hi)) + { + clib_c11_violation ("src/dest overlap"); + return EINVAL; + } + + dest_size = clib_strnlen (dest, dmax); + allowed_size = dmax - dest_size; + + if (PREDICT_FALSE (allowed_size == 0)) + { + clib_c11_violation ("no space left in dest"); + return (EINVAL); + } + + if (PREDICT_FALSE (n >= allowed_size)) + { + /* + * unlike strcat_s, strncat_s will do the concatenation anyway when + * there is not enough space in dest. But it will do the truncation and + * null terminate dest + */ + m = clib_strnlen (src, allowed_size); + if (m >= allowed_size) + { + m = allowed_size - 1; + status = EOVERFLOW; + } + } + else + m = clib_strnlen (src, n); + + clib_memcpy_fast (dest + dest_size, src, m); + dest[dest_size + m] = '\0'; + return status; +} + +/* + * This macro is to provide smooth mapping from strtok_r to strtok_s. + * To map strtok to this macro, the caller would have to supply an additional + * argument. strtokr_s requires s1max which the unsafe API does not have. So + * we have to improvise it with CLIB_STRING_MACRO_MAX. Unlike strtok_s, + * this macro cannot catch unterminated s1 and s2. + * Applications are encouraged to use the cool C11 strtok_s API to avoid + * these problems. + */ +#define clib_strtok(s1,s2,p) \ + ({ rsize_t __s1max = CLIB_STRING_MACRO_MAX; \ + strtok_s_inline (s1, &__s1max, s2, p); \ + }) + +char *strtok_s (char *__restrict__ s1, rsize_t * __restrict__ s1max, + const char *__restrict__ s2, char **__restrict__ ptr); + +always_inline char * +strtok_s_inline (char *__restrict__ s1, rsize_t * __restrict__ s1max, + const char *__restrict__ s2, char **__restrict__ ptr) +{ +#define STRTOK_DELIM_MAX_LEN 16 + u8 bad; + const char *pt; + char *ptoken; + uword dlen, slen; + + bad = (s1max == 0) + (s2 == 0) + (ptr == 0) + + ((s1 == 0) && ptr && (*ptr == 0)); + if (PREDICT_FALSE (bad != 0)) + { + if (s2 == NULL) + clib_c11_violation ("s2 NULL"); + if (s1max == NULL) + clib_c11_violation ("s1max is NULL"); + if (ptr == NULL) + clib_c11_violation ("ptr is NULL"); + /* s1 == 0 and *ptr == null is no good */ + if ((s1 == 0) && ptr && (*ptr == 0)) + clib_c11_violation ("s1 and ptr contents are NULL"); + return 0; + } + + if (s1 == 0) + s1 = *ptr; + + /* + * scan s1 for a delimiter + */ + dlen = *s1max; + ptoken = 0; + while (*s1 != '\0' && !ptoken) + { + if (PREDICT_FALSE (dlen == 0)) + { + *ptr = 0; + clib_c11_violation ("s1 unterminated"); + return 0; + } + + /* + * must scan the entire delimiter list + * ISO should have included a delimiter string limit!! + */ + slen = STRTOK_DELIM_MAX_LEN; + pt = s2; + while (*pt != '\0') + { + if (PREDICT_FALSE (slen == 0)) + { + *ptr = 0; + clib_c11_violation ("s2 unterminated"); + return 0; + } + slen--; + if (*s1 == *pt) + { + ptoken = 0; + break; + } + else + { + pt++; + ptoken = s1; + } + } + s1++; + dlen--; + } + + /* + * if the beginning of a token was not found, then no + * need to continue the scan. + */ + if (ptoken == 0) + { + *s1max = dlen; + return (ptoken); + } + + /* + * Now we need to locate the end of the token + */ + while (*s1 != '\0') + { + if (dlen == 0) + { + *ptr = 0; + clib_c11_violation ("s1 unterminated"); + return 0; + } + + slen = STRTOK_DELIM_MAX_LEN; + pt = s2; + while (*pt != '\0') + { + if (slen == 0) + { + *ptr = 0; + clib_c11_violation ("s2 unterminated"); + return 0; + } + slen--; + if (*s1 == *pt) + { + /* + * found a delimiter, set to null + * and return context ptr to next char + */ + *s1 = '\0'; + *ptr = (s1 + 1); /* return pointer for next scan */ + *s1max = dlen - 1; /* account for the nulled delimiter */ + return (ptoken); + } + else + { + /* + * simply scanning through the delimiter string + */ + pt++; + } + } + s1++; + dlen--; + } + + *ptr = s1; + *s1max = dlen; + return (ptoken); +} + +/* + * This macro is to provide smooth mapping from strstr to strstr_s. + * strstr_s requires s1max and s2max which the unsafe API does not have. So + * we have to improvise them with CLIB_STRING_MACRO_MAX which may cause us + * to access memory beyond it is intended if s1 or s2 is unterminated. + * For the record, strstr crashes if s1 or s2 is unterminated. But this macro + * does not. + * Applications are encouraged to use the cool C11 strstr_s API to avoid + * this problem. + */ +#define clib_strstr(s1,s2) \ + ({ char * __substring = 0; \ + strstr_s_inline (s1, CLIB_STRING_MACRO_MAX, s2, CLIB_STRING_MACRO_MAX, \ + &__substring); \ + __substring; \ + }) + +errno_t strstr_s (char *s1, rsize_t s1max, const char *s2, rsize_t s2max, + char **substring); + +always_inline errno_t +strstr_s_inline (char *s1, rsize_t s1max, const char *s2, rsize_t s2max, + char **substring) +{ + u8 bad; + size_t s1_size, s2_size; + + bad = + (s1 == 0) + (s2 == 0) + (substring == 0) + (s1max == 0) + (s2max == 0) + + (s1 && s1max && (s1[clib_strnlen (s1, s1max)] != '\0')) + + (s2 && s2max && (s2[clib_strnlen (s2, s2max)] != '\0')); + if (PREDICT_FALSE (bad != 0)) + { + if (s1 == 0) + clib_c11_violation ("s1 NULL"); + if (s2 == 0) + clib_c11_violation ("s2 NULL"); + if (s1max == 0) + clib_c11_violation ("s1max 0"); + if (s2max == 0) + clib_c11_violation ("s2max 0"); + if (substring == 0) + clib_c11_violation ("substring NULL"); + if (s1 && s1max && (s1[clib_strnlen (s1, s1max)] != '\0')) + clib_c11_violation ("s1 unterminated"); + if (s2 && s2max && (s2[clib_strnlen (s2, s1max)] != '\0')) + clib_c11_violation ("s2 unterminated"); + return EINVAL; + } + + /* + * s2 points to a string with zero length, or s2 equals s1, return s1 + */ + if (PREDICT_FALSE (*s2 == '\0' || s1 == s2)) + { + *substring = s1; + return EOK; + } + + /* + * s2_size > s1_size, it won't find match. + */ + s1_size = clib_strnlen (s1, s1max); + s2_size = clib_strnlen (s2, s2max); + if (PREDICT_FALSE (s2_size > s1_size)) + return ESRCH; + + *substring = strstr (s1, s2); + if (*substring == 0) + return ESRCH; + + return EOK; +} + #endif /* included_clib_string_h */ /* diff --git a/test/test_string.py b/test/test_string.py new file mode 100644 index 00000000000..b44489e3038 --- /dev/null +++ b/test/test_string.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +import unittest + +from framework import VppTestCase, VppTestRunner +from vpp_ip_route import VppIpTable, VppIpRoute, VppRoutePath + + +class TestString(VppTestCase): + """ String Test Cases """ + + @classmethod + def setUpClass(cls): + super(TestString, cls).setUpClass() + + def setUp(self): + super(TestString, self).setUp() + + def tearDown(self): + super(TestString, self).tearDown() + + def test_string_unittest(self): + """ String unit tests """ + names = ["memcpy_s", + "clib_memcmp", "clib_memcpy", "clib_memset", + "clib_strcat", "clib_strcmp", "clib_strcpy", + "clib_strncat", "clib_strncmp", "clib_strncpy", + "clib_strnlen", "clib_strstr", "clib_strtok", + "memcmp_s", "memcpy_s", "memset_s ", + "strcat_s", "strcmp_s", "strcpy_s", + "strncat_s", "strncmp_s", "strncpy_s", + "strnlen_s", "strstr_s", "strtok_s"] + + for name in names: + error = self.vapi.cli("test string " + name) + if error.find("failed") != -1: + self.logger.critical("FAILURE in the " + name + " test") + self.assertEqual(error.find("failed"), -1) + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) |