diff --git a/include/openthread/instance.h b/include/openthread/instance.h index a5f01c0db..ec252aa40 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (316) +#define OPENTHREAD_API_VERSION (317) /** * @addtogroup api-instance diff --git a/include/openthread/ip6.h b/include/openthread/ip6.h index 5c38e3121..c04bdc2ca 100644 --- a/include/openthread/ip6.h +++ b/include/openthread/ip6.h @@ -610,12 +610,27 @@ bool otIp6ArePrefixesEqual(const otIp6Prefix *aFirst, const otIp6Prefix *aSecond * @param[in] aString A pointer to a NULL-terminated string. * @param[out] aAddress A pointer to an IPv6 address. * - * @retval OT_ERROR_NONE Successfully parsed the string. - * @retval OT_ERROR_INVALID_ARGS Failed to parse the string. + * @retval OT_ERROR_NONE Successfully parsed @p aString and updated @p aAddress. + * @retval OT_ERROR_PARSE Failed to parse @p aString as an IPv6 address. * */ otError otIp6AddressFromString(const char *aString, otIp6Address *aAddress); +/** + * This function converts a human-readable IPv6 prefix string into a binary representation. + * + * The @p aString parameter should be a string in the format "
/", where `
` is an IPv6 + * address and `` is a prefix length. + * + * @param[in] aString A pointer to a NULL-terminated string. + * @param[out] aPrefix A pointer to an IPv6 prefix. + * + * @retval OT_ERROR_NONE Successfully parsed the string as an IPv6 prefix and updated @p aPrefix. + * @retval OT_ERROR_PARSE Failed to parse @p aString as an IPv6 prefix. + * + */ +otError otIp6PrefixFromString(const char *aString, otIp6Prefix *aPrefix); + #define OT_IP6_ADDRESS_STRING_SIZE 40 ///< Recommended size for string representation of an IPv6 address. /** diff --git a/src/core/api/ip6_api.cpp b/src/core/api/ip6_api.cpp index 18769f7d0..98da9489e 100644 --- a/src/core/api/ip6_api.cpp +++ b/src/core/api/ip6_api.cpp @@ -188,6 +188,11 @@ otError otIp6AddressFromString(const char *aString, otIp6Address *aAddress) return AsCoreType(aAddress).FromString(aString); } +otError otIp6PrefixFromString(const char *aString, otIp6Prefix *aPrefix) +{ + return AsCoreType(aPrefix).FromString(aString); +} + void otIp6AddressToString(const otIp6Address *aAddress, char *aBuffer, uint16_t aSize) { AssertPointerIsNotNull(aBuffer); diff --git a/src/core/net/ip6_address.cpp b/src/core/net/ip6_address.cpp index 38b941963..710496eb9 100644 --- a/src/core/net/ip6_address.cpp +++ b/src/core/net/ip6_address.cpp @@ -157,6 +157,39 @@ bool Prefix::IsValidNat64PrefixLength(uint8_t aLength) (aLength == 96); } +Error Prefix::FromString(const char *aString) +{ + constexpr char kSlashChar = '/'; + constexpr char kNullChar = '\0'; + + Error error = kErrorParse; + const char *slashPosition; + uint16_t plen = 0; + + VerifyOrExit(aString != nullptr); + + slashPosition = StringFind(aString, kSlashChar); + VerifyOrExit(slashPosition != nullptr); + + SuccessOrExit(AsCoreType(&mPrefix).ParseFrom(aString, kSlashChar)); + + VerifyOrExit(slashPosition[1] != 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: + return error; +} + Prefix::InfoString Prefix::ToString(void) const { InfoString string; @@ -491,11 +524,17 @@ void Address::SynthesizeFromIp4Address(const Prefix &aPrefix, const Ip4::Address } Error Address::FromString(const char *aString) +{ + constexpr char kNullChar = '\0'; + + return ParseFrom(aString, kNullChar); +} + +Error Address::ParseFrom(const char *aString, char aTerminatorChar) { constexpr uint8_t kInvalidIndex = 0xff; constexpr char kColonChar = ':'; constexpr char kDotChar = '.'; - constexpr char kNullChar = '\0'; Error error = kErrorParse; uint8_t index = 0; @@ -513,7 +552,7 @@ Error Address::FromString(const char *aString) colonIndex = index; } - while (*aString != kNullChar) + while (*aString != aTerminatorChar) { const char *start = aString; uint32_t value = 0; @@ -560,7 +599,7 @@ Error Address::FromString(const char *aString) break; } - VerifyOrExit((*aString == kColonChar) || (*aString == kNullChar)); + VerifyOrExit((*aString == kColonChar) || (*aString == aTerminatorChar)); VerifyOrExit(index < endIndex); mFields.m16[index++] = HostSwap16(static_cast(value)); @@ -594,7 +633,7 @@ Error Address::FromString(const char *aString) { Ip4::Address ip4Addr; - SuccessOrExit(error = ip4Addr.FromString(aString)); + SuccessOrExit(error = ip4Addr.FromString(aString, aTerminatorChar)); memcpy(GetArrayEnd(mFields.m8) - Ip4::Address::kSize, ip4Addr.GetBytes(), Ip4::Address::kSize); } diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index 25df8f889..3323690fb 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -315,6 +315,17 @@ public: */ bool IsValidNat64(void) const { return IsValidNat64PrefixLength(mLength); } + /** + * This method parses a given IPv6 prefix string and sets the prefix. + * + * @param[in] aString A null-terminated string, with format "/" + * + * @retval kErrorNone Successfully parsed the IPv6 prefix from @p aString. + * @retval kErrorParse Failed to parse the IPv6 prefix from @p aString. + * + */ + Error FromString(const char *aString); + /** * This method converts the prefix to a string. * @@ -1028,6 +1039,8 @@ private: static void CopyBits(uint8_t *aDst, const uint8_t *aSrc, uint8_t aNumBits); + Error ParseFrom(const char *aString, char aTerminatorChar); + } OT_TOOL_PACKED_END; /** diff --git a/src/core/utils/parse_cmdline.cpp b/src/core/utils/parse_cmdline.cpp index 5261ce0b5..3f30c8337 100644 --- a/src/core/utils/parse_cmdline.cpp +++ b/src/core/utils/parse_cmdline.cpp @@ -250,30 +250,7 @@ Error ParseAsIp4Address(const char *aString, otIp4Address &aAddress) Error ParseAsIp6Prefix(const char *aString, otIp6Prefix &aPrefix) { - enum : uint8_t - { - kMaxIp6AddressStringSize = 45, - }; - - Error error = kErrorInvalidArgs; - char string[kMaxIp6AddressStringSize]; - const char *prefixLengthStr; - - VerifyOrExit(aString != nullptr); - - prefixLengthStr = StringFind(aString, '/'); - VerifyOrExit(prefixLengthStr != nullptr); - - VerifyOrExit(prefixLengthStr - aString < static_cast(sizeof(string))); - - memcpy(string, aString, static_cast(prefixLengthStr - aString)); - string[prefixLengthStr - aString] = '\0'; - - SuccessOrExit(static_cast(aPrefix.mPrefix).FromString(string)); - error = ParseAsUint8(prefixLengthStr + 1, aPrefix.mLength); - -exit: - return error; + return (aString != nullptr) ? otIp6PrefixFromString(aString, &aPrefix) : kErrorInvalidArgs; } #endif // #if OPENTHREAD_FTD || OPENTHREAD_MTD diff --git a/tests/unit/test_ip_address.cpp b/tests/unit/test_ip_address.cpp index c19b7d2ec..96a2a70f7 100644 --- a/tests/unit/test_ip_address.cpp +++ b/tests/unit/test_ip_address.cpp @@ -29,6 +29,7 @@ #include #include "common/encoding.hpp" +#include "common/string.hpp" #include "net/ip4_types.hpp" #include "net/ip6_address.hpp" @@ -166,6 +167,56 @@ void TestIp6AddressFromString(void) { checkAddressFromString(&testVector); } + + // Validate parsing all test vectors now as an IPv6 prefix. + + for (Ip6AddressTestVector &testVector : testVectors) + { + constexpr uint16_t kMaxString = 80; + + ot::Ip6::Prefix prefix; + char string[kMaxString]; + uint16_t length; + + length = ot::StringLength(testVector.mString, kMaxString); + memcpy(string, testVector.mString, length); + VerifyOrQuit(length + sizeof("/128") <= kMaxString); + strcpy(&string[length], "/128"); + + printf("%s\n", string); + + VerifyOrQuit(prefix.FromString(string) == testVector.mError); + + if (testVector.mError == ot::kErrorNone) + { + VerifyOrQuit(memcmp(prefix.GetBytes(), testVector.mAddr, sizeof(ot::Ip6::Address)) == 0); + VerifyOrQuit(prefix.GetLength() == 128); + } + } +} + +void TestIp6PrefixFromString(void) +{ + ot::Ip6::Prefix prefix; + + SuccessOrQuit(prefix.FromString("::/128")); + VerifyOrQuit(prefix.GetLength() == 128); + + SuccessOrQuit(prefix.FromString("::/0128")); + VerifyOrQuit(prefix.GetLength() == 128); + + SuccessOrQuit(prefix.FromString("::/5")); + VerifyOrQuit(prefix.GetLength() == 5); + + SuccessOrQuit(prefix.FromString("::/0")); + VerifyOrQuit(prefix.GetLength() == 0); + + VerifyOrQuit(prefix.FromString("::") == ot::kErrorParse); + VerifyOrQuit(prefix.FromString("::/") == ot::kErrorParse); + VerifyOrQuit(prefix.FromString("::/129") == ot::kErrorParse); + VerifyOrQuit(prefix.FromString(":: /12") == ot::kErrorParse); + VerifyOrQuit(prefix.FromString("::/a1") == ot::kErrorParse); + VerifyOrQuit(prefix.FromString("::/12 ") == ot::kErrorParse); } void TestIp4AddressFromString(void) @@ -651,6 +702,7 @@ int main(void) TestIp6AddressSetPrefix(); TestIp4AddressFromString(); TestIp6AddressFromString(); + TestIp6PrefixFromString(); TestIp6Prefix(); TestIp4Ip6Translation(); TestIp4Cidr();