From 4fb42e245bfc6f818d3c88918acbcb0d2e5ae15b Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 29 Mar 2021 07:54:40 -0700 Subject: [PATCH] [ip6-address] simplify `FromString()` (#6346) This commit updates the implementation `Ip6::Address::FromString()` simplifying it and handling more special cases where address starts or ends with `::`. The unit test `test_ip6_address` is also updated to cover some new test cases. --- src/core/net/ip6_address.cpp | 158 +++++++++++++++++++-------------- src/core/net/ip6_address.hpp | 6 +- tests/unit/test_ip_address.cpp | 23 +++++ 3 files changed, 117 insertions(+), 70 deletions(-) diff --git a/src/core/net/ip6_address.cpp b/src/core/net/ip6_address.cpp index 653aa6894..9a15db546 100644 --- a/src/core/net/ip6_address.cpp +++ b/src/core/net/ip6_address.cpp @@ -38,6 +38,7 @@ #include "common/code_utils.hpp" #include "common/encoding.hpp" #include "common/instance.hpp" +#include "common/numeric_limits.hpp" #include "common/random.hpp" #include "net/ip4_address.hpp" #include "net/netif.hpp" @@ -452,100 +453,123 @@ bool Address::MatchesFilter(TypeFilter aFilter) const return matches; } -Error Address::FromString(const char *aBuf) +Error Address::FromString(const char *aString) { - Error error = kErrorNone; - uint8_t * dst = reinterpret_cast(mFields.m8); - uint8_t * endp = reinterpret_cast(mFields.m8 + 15); - uint8_t * colonp = nullptr; - const char *colonc = nullptr; - uint16_t val = 0; - uint8_t count = 0; - bool first = true; - bool hasIp4 = false; - char ch; - uint8_t d; - - Clear(); - - dst--; - - for (;;) + enum : uint8_t { - ch = *aBuf++; - d = ch & 0xf; + kInvalidIndex = 0xff, + }; - if (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F')) - { - d += 9; - } - else if (ch == ':' || ch == '\0' || ch == ' ') - { - if (count) - { - VerifyOrExit(dst + 2 <= endp, error = kErrorParse); - *(dst + 1) = static_cast(val >> 8); - *(dst + 2) = static_cast(val); - dst += 2; - count = 0; - val = 0; - } - else if (ch == ':') - { - VerifyOrExit(colonp == nullptr || first, error = kErrorParse); - colonp = dst; - } + enum : char + { + kColonChar = ':', + kDotChar = '.', + kNullChar = '\0', + }; - if (ch == '\0' || ch == ' ') + Error error = kErrorParse; + uint8_t index = 0; + uint8_t endIndex = kSize / sizeof(uint16_t); + uint8_t colonIndex = kInvalidIndex; + bool hasIp4 = false; + + // Check if the string starts with "::". + + if (*aString == kColonChar) + { + aString++; + VerifyOrExit(*aString == kColonChar); + aString++; + colonIndex = index; + } + + while (*aString != kNullChar) + { + const char *start = aString; + uint32_t value = 0; + + // Parse hex number + + while (true) + { + char c = *aString; + uint8_t digit; + + if (('A' <= c) && (c <= 'F')) + { + digit = static_cast(c - 'A' + 10); + } + else if (('a' <= c) && (c <= 'f')) + { + digit = static_cast(c - 'a' + 10); + } + else if (('0' <= c) && (c <= '9')) + { + digit = static_cast(c - '0'); + } + else { break; } - colonc = aBuf; + aString++; + value = (value << 4) + digit; - continue; + VerifyOrExit(value <= NumericLimits::Max()); } - else if (ch == '.') + + VerifyOrExit(aString != start); + + if (*aString == kDotChar) { - hasIp4 = true; - - // Do not count bytes of the embedded IPv4 address. - endp -= Ip4::Address::kSize; - - VerifyOrExit(dst <= endp, error = kErrorParse); - + // IPv6 address contains an embedded IPv4 address. + aString = start; + hasIp4 = true; + endIndex -= Ip4::Address::kSize / sizeof(uint16_t); + VerifyOrExit(index <= endIndex); break; } - else + + VerifyOrExit((*aString == kColonChar) || (*aString == kNullChar)); + + VerifyOrExit(index < endIndex); + mFields.m16[index++] = HostSwap16(static_cast(value)); + + if (*aString == kColonChar) { - VerifyOrExit('0' <= ch && ch <= '9', error = kErrorParse); + aString++; + + if (*aString == kColonChar) + { + VerifyOrExit(colonIndex == kInvalidIndex); + colonIndex = index; + aString++; + } } - - first = false; - val = static_cast((val << 4) | d); - VerifyOrExit(++count <= 4, error = kErrorParse); } - VerifyOrExit(colonp || dst == endp, error = kErrorParse); - - while (colonp && dst > colonp) + if (index < endIndex) { - *endp-- = *dst--; - } + uint8_t wordsToCopy; - while (endp > dst) - { - *endp-- = 0; + VerifyOrExit(colonIndex != kInvalidIndex); + + wordsToCopy = index - colonIndex; + + memmove(&mFields.m16[endIndex - wordsToCopy], &mFields.m16[colonIndex], wordsToCopy * sizeof(uint16_t)); + memset(&mFields.m16[colonIndex], 0, (endIndex - index) * sizeof(uint16_t)); } if (hasIp4) { Ip4::Address ip4Addr; - SuccessOrExit(error = ip4Addr.FromString(colonc)); - memcpy(mFields.m8 + 12, ip4Addr.GetBytes(), Ip4::Address::kSize); + SuccessOrExit(error = ip4Addr.FromString(aString)); + memcpy(OT_ARRAY_END(mFields.m8) - Ip4::Address::kSize, ip4Addr.GetBytes(), Ip4::Address::kSize); } + error = kErrorNone; + exit: return error; } diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index 98359a6fc..69835fb42 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -883,13 +883,13 @@ public: /** * This method converts an IPv6 address string to binary. * - * @param[in] aBuf A pointer to the null-terminated string. + * @param[in] aString A pointer to the null-terminated string. * * @retval kErrorNone Successfully parsed the IPv6 address string. - * @retval kErrorInvalidArgs Failed to parse the IPv6 address string. + * @retval kErrorParse Failed to parse the IPv6 address string. * */ - Error FromString(const char *aBuf); + Error FromString(const char *aString); /** * This method converts an IPv6 address object to a string diff --git a/tests/unit/test_ip_address.cpp b/tests/unit/test_ip_address.cpp index 21dd9011d..8f38349c4 100644 --- a/tests/unit/test_ip_address.cpp +++ b/tests/unit/test_ip_address.cpp @@ -75,6 +75,11 @@ void TestIp6AddressFromString(void) {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00}, ot::kErrorNone}, + // Valid full address using capital letters. + {"0102:0304:0506:0708:090A:0B0C:0D0E:0F00", + {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00}, + ot::kErrorNone}, + // Valid full IPv6 address with mixed capital and small letters. {"0102:0304:0506:0708:090a:0B0C:0d0E:0F00", {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00}, @@ -98,6 +103,16 @@ void TestIp6AddressFromString(void) // Unspecified address. {"::", {0}, ot::kErrorNone}, + // Starts with :: + {"::1:2:3:4", + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04}, + ot::kErrorNone}, + + // Ends with :: + {"1001:2002:3003:4004::", + {0x10, 0x01, 0x20, 0x02, 0x30, 0x03, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + ot::kErrorNone}, + // Valid embedded IPv4 address. {"64:ff9b::100.200.15.4", {0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0xc8, 0x0f, 0x04}, @@ -108,6 +123,11 @@ void TestIp6AddressFromString(void) {0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbc, 0xde, 0xf1, 0x7f, 0x00, 0x00, 0x01}, ot::kErrorNone}, + // Valid embedded IPv4 address. + {"1:2:3:4:5:6:127.1.2.3", + {0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x7f, 0x01, 0x02, 0x03}, + ot::kErrorNone}, + // Two :: should cause a parse error. {"2001:db8::a::b", {0}, ot::kErrorParse}, @@ -129,6 +149,9 @@ void TestIp6AddressFromString(void) // Invalid embedded IPv4 address. {"64:ff9b::1.22.33.44.5", {0}, ot::kErrorParse}, + // Too long with embedded IPv4 address. + {"1:2:3:4:5:6:7:127.1.2.3", {0}, ot::kErrorParse}, + // Invalid embedded IPv4 address. {".", {0}, ot::kErrorParse},