/* * 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 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; for (ret = 0; (ret < aMaxLength) && (aString[ret] != kNullChar); ret++) { // Empty loop. } 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(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::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++) { *aString = ToLowercase(*aString); } } void StringConvertToUppercase(char *aString) { for (; *aString != kNullChar; aString++) { *aString = ToUppercase(*aString); } } char ToLowercase(char aChar) { if ((aChar >= 'A') && (aChar <= 'Z')) { aChar += 'a' - 'A'; } return aChar; } char ToUppercase(char aChar) { if ((aChar >= 'a') && (aChar <= 'z')) { aChar -= 'a' - 'A'; } return aChar; } 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(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(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(aString + position); ++position; // Verify the continuation byte pattern 10xx-xxxx VerifyOrExit((byte & 0xc0) == 0x80, ret = false); } } exit: return ret; } } // namespace ot