From a259d23e7f274c883c26be1d27cff3f171b0a997 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 9 May 2023 12:54:39 -0700 Subject: [PATCH] [string] add `StringParseUint8()` helper function (#9026) This commit adds a helper function to parse a decimal number from a given string as `uint8_t` and skip over the parsed characters. This is used to simplify the methods parsing IPv6/IPv4 address or prefix from a string. --- src/core/common/string.cpp | 26 +++++++++++++++ src/core/common/string.hpp | 41 ++++++++++++++++++++++- src/core/net/ip4_types.cpp | 59 ++++++++-------------------------- src/core/net/ip6_address.cpp | 20 ++++-------- tests/unit/test_ip_address.cpp | 1 + tests/unit/test_string.cpp | 58 +++++++++++++++++++++++++++++++++ 6 files changed, 144 insertions(+), 61 deletions(-) diff --git a/src/core/common/string.cpp b/src/core/common/string.cpp index bc3069c7c..c7aa7218a 100644 --- a/src/core/common/string.cpp +++ b/src/core/common/string.cpp @@ -160,6 +160,32 @@ bool StringMatch(const char *aFirstString, const char *aSecondString, StringMatc return Match(aFirstString, aSecondString, aMode) == kFullMatch; } +Error StringParseUint8(const char *&aString, uint8_t &aUint8) +{ + return StringParseUint8(aString, aUint8, NumericLimits::kMax); +} + +Error StringParseUint8(const char *&aString, uint8_t &aUint8, uint8_t aMaxValue) +{ + Error error = kErrorParse; + const char *cur = aString; + uint16_t value = 0; + + for (; (*cur >= '0') && (*cur <= '9'); cur++) + { + value *= 10; + value += static_cast(*cur - '0'); + VerifyOrExit(value <= aMaxValue, error = kErrorParse); + error = kErrorNone; + } + + aString = cur; + aUint8 = static_cast(value); + +exit: + return error; +} + void StringConvertToLowercase(char *aString) { for (; *aString != kNullChar; aString++) diff --git a/src/core/common/string.hpp b/src/core/common/string.hpp index f80454d33..fbbaca7ee 100644 --- a/src/core/common/string.hpp +++ b/src/core/common/string.hpp @@ -144,7 +144,7 @@ bool StringEndsWith(const char *aString, char aChar); bool StringEndsWith(const char *aString, const char *aSubString, StringMatchMode aMode = kStringExactMatch); /** - * This method checks whether or not two null-terminated strings match. + * This function checks whether or not two null-terminated strings match. * * @param[in] aFirstString A pointer to the first string. * @param[in] aSecondString A pointer to the second string. @@ -156,6 +156,45 @@ bool StringEndsWith(const char *aString, const char *aSubString, StringMatchMode */ bool StringMatch(const char *aFirstString, const char *aSecondString, StringMatchMode aMode = kStringExactMatch); +/** + * This function parses a decimal number from a string as `uint8_t` and skips over the parsed characters. + * + * If the string does not start with a digit, `kErrorParse` is returned. + * + * All the digit characters in the string are parsed until reaching a non-digit character. The pointer `aString` is + * updated to point to the first non-digit character after the parsed digits. + * + * If the parsed number value is larger than @p aMaxValue, `kErrorParse` is returned. + * + * @param[in,out] aString A reference to a pointer to string to parse. + * @param[out] aUint8 A reference to return the parsed value. + * @param[in] aMaxValue Maximum allowed value for the parsed number. + * + * @retval kErrorNone Successfully parsed the number from string. @p aString and @p aUint8 are updated. + * @retval kErrorParse Failed to parse the number from @p aString, or parsed number is larger than @p aMaxValue. + * + */ +Error StringParseUint8(const char *&aString, uint8_t &aUint8, uint8_t aMaxValue); + +/** + * This function parses a decimal number from a string as `uint8_t` and skips over the parsed characters. + * + * If the string does not start with a digit, `kErrorParse` is returned. + * + * All the digit characters in the string are parsed until reaching a non-digit character. The pointer `aString` is + * updated to point to the first non-digit character after the parsed digits. + * + * If the parsed number value is larger than maximum `uint8_t` value, `kErrorParse` is returned. + * + * @param[in,out] aString A reference to a pointer to string to parse. + * @param[out] aUint8 A reference to return the parsed value. + * + * @retval kErrorNone Successfully parsed the number from string. @p aString and @p aUint8 are updated. + * @retval kErrorParse Failed to parse the number from @p aString, or parsed number is out of range. + * + */ +Error StringParseUint8(const char *&aString, uint8_t &aUint8); + /** * This function converts all uppercase letter characters in a given string to lowercase. * diff --git a/src/core/net/ip4_types.cpp b/src/core/net/ip4_types.cpp index f4f53267b..bbe80f198 100644 --- a/src/core/net/ip4_types.cpp +++ b/src/core/net/ip4_types.cpp @@ -41,39 +41,23 @@ Error Address::FromString(const char *aString, char aTerminatorChar) { constexpr char kSeparatorChar = '.'; - Error error = kErrorParse; + Error error = kErrorParse; + const char *cur = aString; for (uint8_t index = 0;; index++) { - uint16_t value = 0; - uint8_t hasFirstDigit = false; - - for (char digitChar = *aString;; ++aString, digitChar = *aString) - { - if ((digitChar < '0') || (digitChar > '9')) - { - break; - } - - value = static_cast((value * 10) + static_cast(digitChar - '0')); - VerifyOrExit(value <= NumericLimits::kMax); - hasFirstDigit = true; - } - - VerifyOrExit(hasFirstDigit); - - mFields.m8[index] = static_cast(value); + SuccessOrExit(StringParseUint8(cur, mFields.m8[index])); if (index == sizeof(Address) - 1) { break; } - VerifyOrExit(*aString == kSeparatorChar); - aString++; + VerifyOrExit(*cur == kSeparatorChar); + cur++; } - VerifyOrExit(*aString == aTerminatorChar); + VerifyOrExit(*cur == aTerminatorChar); error = kErrorNone; exit: @@ -152,34 +136,17 @@ Error Cidr::FromString(const char *aString) constexpr char kSlashChar = '/'; constexpr uint16_t kMaxCidrLength = 32; - Error error = kErrorParse; + Error error = kErrorParse; + const char *cur; SuccessOrExit(AsCoreType(&mAddress).FromString(aString, kSlashChar)); - aString = StringFind(aString, kSlashChar); - VerifyOrExit(aString != nullptr); - aString++; + cur = StringFind(aString, kSlashChar); + VerifyOrExit(cur != nullptr); + cur++; - { - uint8_t hasFirstDigit = false; - uint16_t value = 0; - - for (char digitChar = *aString;; ++aString, digitChar = *aString) - { - if ((digitChar < '0') || (digitChar > '9')) - { - break; - } - - value = static_cast((value * 10) + static_cast(digitChar - '0')); - VerifyOrExit(value <= kMaxCidrLength); - hasFirstDigit = true; - } - - VerifyOrExit(*aString == kNullChar); - VerifyOrExit(hasFirstDigit); - mLength = static_cast(value); - } + SuccessOrExit(StringParseUint8(cur, mLength, kMaxCidrLength)); + VerifyOrExit(*cur == kNullChar); error = kErrorNone; diff --git a/src/core/net/ip6_address.cpp b/src/core/net/ip6_address.cpp index 710496eb9..58ac92d7c 100644 --- a/src/core/net/ip6_address.cpp +++ b/src/core/net/ip6_address.cpp @@ -163,27 +163,19 @@ Error Prefix::FromString(const char *aString) constexpr char kNullChar = '\0'; Error error = kErrorParse; - const char *slashPosition; - uint16_t plen = 0; + const char *cur; VerifyOrExit(aString != nullptr); - slashPosition = StringFind(aString, kSlashChar); - VerifyOrExit(slashPosition != nullptr); + cur = StringFind(aString, kSlashChar); + VerifyOrExit(cur != nullptr); SuccessOrExit(AsCoreType(&mPrefix).ParseFrom(aString, kSlashChar)); - VerifyOrExit(slashPosition[1] != kNullChar); + cur++; + SuccessOrExit(StringParseUint8(cur, mLength, kMaxLength)); + VerifyOrExit(*cur == kNullChar); - for (const char *cur = slashPosition + 1; *cur != kNullChar; cur++) - { - VerifyOrExit((*cur >= '0') && (*cur <= '9')); - plen *= 10; - plen += static_cast(*cur - '0'); - VerifyOrExit(plen <= kMaxLength); - } - - SetLength(static_cast(plen)); error = kErrorNone; exit: diff --git a/tests/unit/test_ip_address.cpp b/tests/unit/test_ip_address.cpp index 96a2a70f7..06cef10d8 100644 --- a/tests/unit/test_ip_address.cpp +++ b/tests/unit/test_ip_address.cpp @@ -295,6 +295,7 @@ void TestIp4CidrFromString(void) // valid address, invalid suffix {"1.2.3.4/33", {0}, 0, ot::kErrorParse}, // Prefix length too large {"1.2.3.4/12345678", {0}, 0, ot::kErrorParse}, // Prefix length too large? + {"1.2.3.4/12a", {0}, 0, ot::kErrorParse}, // Extra char after prefix length. {"1.2.3.4/-1", {0}, 0, ot::kErrorParse}, // Not even a non-negative integer. {"1.2.3.4/3.14", {0}, 0, ot::kErrorParse}, // Not even a integer. {"1.2.3.4/abcd", {0}, 0, ot::kErrorParse}, // Not even a number. diff --git a/tests/unit/test_string.cpp b/tests/unit/test_string.cpp index bd7fb8d77..80ff3b099 100644 --- a/tests/unit/test_string.cpp +++ b/tests/unit/test_string.cpp @@ -309,6 +309,63 @@ void TestStringToLowercase(void) printf(" -- PASS\n"); } +void TestStringParseUint8(void) +{ + struct TestCase + { + const char *mString; + Error mError; + uint8_t mExpectedValue; + uint16_t mParsedLength; + }; + + static const TestCase kTestCases[] = { + {"0", kErrorNone, 0, 1}, + {"1", kErrorNone, 1, 1}, + {"12", kErrorNone, 12, 2}, + {"91", kErrorNone, 91, 2}, + {"200", kErrorNone, 200, 3}, + {"00000", kErrorNone, 0, 5}, + {"00000255", kErrorNone, 255, 8}, + {"2 00", kErrorNone, 2, 1}, + {"77a12", kErrorNone, 77, 2}, + {"", kErrorParse}, // Does not start with digit char ['0'-'9'] + {"a12", kErrorParse}, // Does not start with digit char ['0'-'9'] + {" 12", kErrorParse}, // Does not start with digit char ['0'-'9'] + {"256", kErrorParse}, // Larger than max `uint8_t` + {"1000", kErrorParse}, // Larger than max `uint8_t` + {"0256", kErrorParse}, // Larger than max `uint8_t` + }; + + printf("\nTest 11: TestStringParseUint8() function\n"); + + for (const TestCase &testCase : kTestCases) + { + const char *string = testCase.mString; + Error error; + uint8_t u8; + + error = StringParseUint8(string, u8); + + VerifyOrQuit(error == testCase.mError); + + if (testCase.mError == kErrorNone) + { + printf("\n%-10s -> %-3u (expect: %-3u), len:%u (expect:%u)", testCase.mString, u8, testCase.mExpectedValue, + static_cast(string - testCase.mString), testCase.mParsedLength); + + VerifyOrQuit(u8 == testCase.mExpectedValue); + VerifyOrQuit(string - testCase.mString == testCase.mParsedLength); + } + else + { + printf("\n%-10s -> kErrorParse", testCase.mString); + } + } + + printf("\n\n -- PASS\n"); +} + // gcc-4 does not support constexpr function #if __GNUC__ > 4 static_assert(ot::AreStringsInOrder("a", "b"), "AreStringsInOrder() failed"); @@ -331,6 +388,7 @@ int main(void) ot::TestStringEndsWith(); ot::TestStringMatch(); ot::TestStringToLowercase(); + ot::TestStringParseUint8(); printf("\nAll tests passed.\n"); return 0; }