| /* |
| * 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 |