| /* | |
| * Copyright (c) 2018, The OpenThread Authors. | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * 3. Neither the name of the copyright holder nor the | |
| * names of its contributors may be used to endorse or promote products | |
| * derived from this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| * POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| /** | |
| * @file | |
| * This file implements OpenThread String class and functions. | |
| */ | |
| #include "string.hpp" | |
| #include "debug.hpp" | |
| #include <string.h> | |
| namespace ot { | |
| namespace { | |
| // The definitions below are included in an unnamed namespace | |
| // to limit their scope to this translation unit (this file). | |
| enum MatchType : uint8_t | |
| { | |
| kNoMatch, | |
| kPrefixMatch, | |
| kFullMatch, | |
| }; | |
| MatchType Match(const char *aString, const char *aPrefixString, StringMatchMode aMode) | |
| { | |
| // This is file private function that is used by other functions. | |
| // It matches @p aString with @p aPrefixString using match @ aMode. | |
| // | |
| // If @p aString and @p aPrefixString match and have the | |
| // same length `kFullMatch` is returned. If @p aString starts | |
| // with @p aPrefixString but contains more characters, then | |
| // `kPrefixMatch` is returned. Otherwise `kNoMatch` is returned. | |
| MatchType match = kNoMatch; | |
| switch (aMode) | |
| { | |
| case kStringExactMatch: | |
| while (*aPrefixString != kNullChar) | |
| { | |
| VerifyOrExit(*aString++ == *aPrefixString++); | |
| } | |
| break; | |
| case kStringCaseInsensitiveMatch: | |
| while (*aPrefixString != kNullChar) | |
| { | |
| VerifyOrExit(ToLowercase(*aString++) == ToLowercase(*aPrefixString++)); | |
| } | |
| break; | |
| } | |
| match = (*aString == kNullChar) ? kFullMatch : kPrefixMatch; | |
| exit: | |
| return match; | |
| } | |
| } // namespace | |
| uint16_t StringLength(const char *aString, uint16_t aMaxLength) | |
| { | |
| uint16_t ret = 0; | |
| VerifyOrExit(aString != nullptr); | |
| for (; (ret < aMaxLength) && (aString[ret] != kNullChar); ret++) | |
| { | |
| // Empty loop. | |
| } | |
| exit: | |
| return ret; | |
| } | |
| const char *StringFind(const char *aString, char aChar) | |
| { | |
| const char *ret = nullptr; | |
| for (; *aString != kNullChar; aString++) | |
| { | |
| if (*aString == aChar) | |
| { | |
| ret = aString; | |
| break; | |
| } | |
| } | |
| return ret; | |
| } | |
| const char *StringFind(const char *aString, const char *aSubString, StringMatchMode aMode) | |
| { | |
| const char *ret = nullptr; | |
| size_t len = strlen(aString); | |
| size_t subLen = strlen(aSubString); | |
| VerifyOrExit(subLen <= len); | |
| for (size_t index = 0; index <= static_cast<size_t>(len - subLen); index++) | |
| { | |
| if (Match(&aString[index], aSubString, aMode) != kNoMatch) | |
| { | |
| ExitNow(ret = &aString[index]); | |
| } | |
| } | |
| exit: | |
| return ret; | |
| } | |
| bool StringStartsWith(const char *aString, const char *aPrefixString, StringMatchMode aMode) | |
| { | |
| return Match(aString, aPrefixString, aMode) != kNoMatch; | |
| } | |
| bool StringEndsWith(const char *aString, char aChar) | |
| { | |
| size_t len = strlen(aString); | |
| return (len > 0) && (aString[len - 1] == aChar); | |
| } | |
| bool StringEndsWith(const char *aString, const char *aSubString, StringMatchMode aMode) | |
| { | |
| size_t len = strlen(aString); | |
| size_t subLen = strlen(aSubString); | |
| return (subLen > 0) && (len >= subLen) && (Match(&aString[len - subLen], aSubString, aMode) != kNoMatch); | |
| } | |
| bool StringMatch(const char *aFirstString, const char *aSecondString, StringMatchMode aMode) | |
| { | |
| return Match(aFirstString, aSecondString, aMode) == kFullMatch; | |
| } | |
| Error StringCopy(char *aTargetBuffer, uint16_t aTargetSize, const char *aSource, StringEncodingCheck aEncodingCheck) | |
| { | |
| Error error = kErrorNone; | |
| uint16_t length; | |
| if (aSource == nullptr) | |
| { | |
| aTargetBuffer[0] = kNullChar; | |
| ExitNow(); | |
| } | |
| length = StringLength(aSource, aTargetSize); | |
| VerifyOrExit(length < aTargetSize, error = kErrorInvalidArgs); | |
| switch (aEncodingCheck) | |
| { | |
| case kStringNoEncodingCheck: | |
| break; | |
| case kStringCheckUtf8Encoding: | |
| VerifyOrExit(IsValidUtf8String(aSource), error = kErrorParse); | |
| break; | |
| } | |
| memcpy(aTargetBuffer, aSource, length + 1); // `+ 1` to also copy null char. | |
| exit: | |
| return error; | |
| } | |
| Error StringParseUint8(const char *&aString, uint8_t &aUint8) | |
| { | |
| return StringParseUint8(aString, aUint8, NumericLimits<uint8_t>::kMax); | |
| } | |
| Error StringParseUint8(const char *&aString, uint8_t &aUint8, uint8_t aMaxValue) | |
| { | |
| Error error = kErrorParse; | |
| const char *cur = aString; | |
| uint16_t value = 0; | |
| uint8_t digit; | |
| while (ParseDigit(*cur, digit) == kErrorNone) | |
| { | |
| value *= 10; | |
| value += digit; | |
| VerifyOrExit(value <= aMaxValue, error = kErrorParse); | |
| error = kErrorNone; | |
| cur++; | |
| } | |
| aString = cur; | |
| aUint8 = static_cast<uint8_t>(value); | |
| exit: | |
| return error; | |
| } | |
| void StringConvertToLowercase(char *aString) | |
| { | |
| for (; *aString != kNullChar; aString++) | |
| { | |
| *aString = ToLowercase(*aString); | |
| } | |
| } | |
| void StringConvertToUppercase(char *aString) | |
| { | |
| for (; *aString != kNullChar; aString++) | |
| { | |
| *aString = ToUppercase(*aString); | |
| } | |
| } | |
| char ToLowercase(char aChar) | |
| { | |
| if (IsUppercase(aChar)) | |
| { | |
| aChar += 'a' - 'A'; | |
| } | |
| return aChar; | |
| } | |
| char ToUppercase(char aChar) | |
| { | |
| if (IsLowercase(aChar)) | |
| { | |
| aChar -= 'a' - 'A'; | |
| } | |
| return aChar; | |
| } | |
| bool IsDigit(char aChar) { return ('0' <= aChar && aChar <= '9'); } | |
| bool IsUppercase(char aChar) { return ('A' <= aChar && aChar <= 'Z'); } | |
| bool IsLowercase(char aChar) { return ('a' <= aChar && aChar <= 'z'); } | |
| Error ParseDigit(char aDigitChar, uint8_t &aValue) | |
| { | |
| Error error = kErrorNone; | |
| VerifyOrExit(IsDigit(aDigitChar), error = kErrorInvalidArgs); | |
| aValue = static_cast<uint8_t>(aDigitChar - '0'); | |
| exit: | |
| return error; | |
| } | |
| Error ParseHexDigit(char aHexChar, uint8_t &aValue) | |
| { | |
| Error error = kErrorNone; | |
| if (('A' <= aHexChar) && (aHexChar <= 'F')) | |
| { | |
| ExitNow(aValue = static_cast<uint8_t>(aHexChar - 'A' + 10)); | |
| } | |
| if (('a' <= aHexChar) && (aHexChar <= 'f')) | |
| { | |
| ExitNow(aValue = static_cast<uint8_t>(aHexChar - 'a' + 10)); | |
| } | |
| error = ParseDigit(aHexChar, aValue); | |
| exit: | |
| return error; | |
| } | |
| const char *ToYesNo(bool aBool) | |
| { | |
| static const char *const kYesNoStrings[] = {"no", "yes"}; | |
| return kYesNoStrings[aBool]; | |
| } | |
| StringWriter::StringWriter(char *aBuffer, uint16_t aSize) | |
| : mBuffer(aBuffer) | |
| , mLength(0) | |
| , mSize(aSize) | |
| { | |
| mBuffer[0] = kNullChar; | |
| } | |
| StringWriter &StringWriter::Clear(void) | |
| { | |
| mBuffer[0] = kNullChar; | |
| mLength = 0; | |
| return *this; | |
| } | |
| StringWriter &StringWriter::Append(const char *aFormat, ...) | |
| { | |
| va_list args; | |
| va_start(args, aFormat); | |
| AppendVarArgs(aFormat, args); | |
| va_end(args); | |
| return *this; | |
| } | |
| StringWriter &StringWriter::AppendVarArgs(const char *aFormat, va_list aArgs) | |
| { | |
| int len; | |
| len = vsnprintf(mBuffer + mLength, (mSize > mLength ? (mSize - mLength) : 0), aFormat, aArgs); | |
| OT_ASSERT(len >= 0); | |
| mLength += static_cast<uint16_t>(len); | |
| if (IsTruncated()) | |
| { | |
| mBuffer[mSize - 1] = kNullChar; | |
| } | |
| return *this; | |
| } | |
| StringWriter &StringWriter::AppendHexBytes(const uint8_t *aBytes, uint16_t aLength) | |
| { | |
| while (aLength--) | |
| { | |
| Append("%02x", *aBytes++); | |
| } | |
| return *this; | |
| } | |
| StringWriter &StringWriter::AppendCharMultipleTimes(char aChar, uint16_t aCount) | |
| { | |
| while (aCount--) | |
| { | |
| Append("%c", aChar); | |
| } | |
| return *this; | |
| } | |
| bool IsValidUtf8String(const char *aString) { return IsValidUtf8String(aString, strlen(aString)); } | |
| bool IsValidUtf8String(const char *aString, size_t aLength) | |
| { | |
| bool ret = true; | |
| uint8_t byte; | |
| uint8_t continuationBytes = 0; | |
| size_t position = 0; | |
| while (position < aLength) | |
| { | |
| byte = *reinterpret_cast<const uint8_t *>(aString + position); | |
| ++position; | |
| if ((byte & 0x80) == 0) | |
| { | |
| // We don't allow control characters. | |
| VerifyOrExit(!iscntrl(byte), ret = false); | |
| continue; | |
| } | |
| // This is a leading byte 1xxx-xxxx. | |
| if ((byte & 0x40) == 0) // 10xx-xxxx | |
| { | |
| // We got a continuation byte pattern without seeing a leading byte earlier. | |
| ExitNow(ret = false); | |
| } | |
| else if ((byte & 0x20) == 0) // 110x-xxxx | |
| { | |
| continuationBytes = 1; | |
| } | |
| else if ((byte & 0x10) == 0) // 1110-xxxx | |
| { | |
| continuationBytes = 2; | |
| } | |
| else if ((byte & 0x08) == 0) // 1111-0xxx | |
| { | |
| continuationBytes = 3; | |
| } | |
| else // 1111-1xxx (invalid pattern). | |
| { | |
| ExitNow(ret = false); | |
| } | |
| while (continuationBytes-- != 0) | |
| { | |
| VerifyOrExit(position < aLength, ret = false); | |
| byte = *reinterpret_cast<const uint8_t *>(aString + position); | |
| ++position; | |
| // Verify the continuation byte pattern 10xx-xxxx | |
| VerifyOrExit((byte & 0xc0) == 0x80, ret = false); | |
| } | |
| } | |
| exit: | |
| return ret; | |
| } | |
| } // namespace ot |