1 /*
2  *  Copyright (c) 2018, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *  This file defines OpenThread String class.
32  */
33 
34 #ifndef STRING_HPP_
35 #define STRING_HPP_
36 
37 #include "openthread-core-config.h"
38 
39 #include <stdarg.h>
40 #include <stdint.h>
41 #include <stdio.h>
42 
43 #include "common/binary_search.hpp"
44 #include "common/code_utils.hpp"
45 #include "common/error.hpp"
46 #include "common/num_utils.hpp"
47 
48 namespace ot {
49 
50 /**
51  * @addtogroup core-string
52  *
53  * @brief
54  *   This module includes definitions for OpenThread String class.
55  *
56  * @{
57  *
58  */
59 
60 /**
61  * Represents comparison mode when matching strings.
62  *
63  */
64 enum StringMatchMode : uint8_t
65 {
66     kStringExactMatch,           ///< Exact match of characters.
67     kStringCaseInsensitiveMatch, ///< Case insensitive match (uppercase and lowercase characters are treated as equal).
68 };
69 
70 /**
71  * Represents string encoding check when copying string.
72  *
73  */
74 enum StringEncodingCheck : uint8_t
75 {
76     kStringNoEncodingCheck,   ///< Do not check the string encoding.
77     kStringCheckUtf8Encoding, ///< Validate that string follows UTF-8 encoding.
78 };
79 
80 static constexpr char kNullChar = '\0'; ///< null character.
81 
82 /**
83  * Returns the number of characters that precede the terminating null character.
84  *
85  * @param[in] aString      A pointer to the string.
86  * @param[in] aMaxLength   The maximum length in bytes.
87  *
88  * @returns The number of characters that precede the terminating null character or @p aMaxLength,
89  *          whichever is smaller. `0` if @p aString is `nullptr`.
90  *
91  */
92 uint16_t StringLength(const char *aString, uint16_t aMaxLength);
93 
94 /**
95  * Finds the first occurrence of a given character in a null-terminated string.
96  *
97  * @param[in] aString     A pointer to the string.
98  * @param[in] aChar       A char to search for in the string.
99  *
100  * @returns The pointer to first occurrence of the @p aChar in @p aString, or `nullptr` if cannot be found.
101  *
102  */
103 const char *StringFind(const char *aString, char aChar);
104 
105 /**
106  * Finds the first occurrence of a given sub-string in a null-terminated string.
107  *
108  * @param[in] aString     A pointer to the string.
109  * @param[in] aSubString  A sub-string to search for.
110  * @param[in] aMode       The string comparison mode, exact match or case insensitive match.
111  *
112  * @returns The pointer to first match of the @p aSubString in @p aString (using comparison @p aMode), or `nullptr` if
113  *          cannot be found.
114  *
115  */
116 const char *StringFind(const char *aString, const char *aSubString, StringMatchMode aMode = kStringExactMatch);
117 
118 /**
119  * Checks whether a null-terminated string starts with a given prefix string.
120  *
121  * @param[in] aString         A pointer to the string.
122  * @param[in] aPrefixString   A prefix string.
123  * @param[in] aMode           The string comparison mode, exact match or case insensitive match.
124  *
125  * @retval TRUE   If @p aString starts with @p aPrefixString.
126  * @retval FALSE  If @p aString does not start with @p aPrefixString.
127  *
128  */
129 bool StringStartsWith(const char *aString, const char *aPrefixString, StringMatchMode aMode = kStringExactMatch);
130 
131 /**
132  * Checks whether a null-terminated string ends with a given character.
133  *
134  * @param[in] aString  A pointer to the string.
135  * @param[in] aChar    A char to check.
136  *
137  * @retval TRUE   If @p aString ends with character @p aChar.
138  * @retval FALSE  If @p aString does not end with character @p aChar.
139  *
140  */
141 bool StringEndsWith(const char *aString, char aChar);
142 
143 /**
144  * Checks whether a null-terminated string ends with a given sub-string.
145  *
146  * @param[in] aString      A pointer to the string.
147  * @param[in] aSubString   A sub-string to check against.
148  * @param[in] aMode        The string comparison mode, exact match or case insensitive match.
149  *
150  * @retval TRUE   If @p aString ends with sub-string @p aSubString.
151  * @retval FALSE  If @p aString does not end with sub-string @p aSubString.
152  *
153  */
154 bool StringEndsWith(const char *aString, const char *aSubString, StringMatchMode aMode = kStringExactMatch);
155 
156 /**
157  * Checks whether or not two null-terminated strings match exactly.
158  *
159  * @param[in] aFirstString   A pointer to the first string.
160  * @param[in] aSecondString  A pointer to the second string.
161  *
162  * @retval TRUE   If @p aFirstString matches @p aSecondString.
163  * @retval FALSE  If @p aFirstString does not match @p aSecondString.
164  *
165  */
166 bool StringMatch(const char *aFirstString, const char *aSecondString);
167 
168 /**
169  * Checks whether or not two null-terminated strings match.
170  *
171  * @param[in] aFirstString   A pointer to the first string.
172  * @param[in] aSecondString  A pointer to the second string.
173  * @param[in] aMode          The string comparison mode, exact match or case insensitive match.
174  *
175  * @retval TRUE   If @p aFirstString matches @p aSecondString using match mode @p aMode.
176  * @retval FALSE  If @p aFirstString does not match @p aSecondString using match mode @p aMode.
177  *
178  */
179 bool StringMatch(const char *aFirstString, const char *aSecondString, StringMatchMode aMode);
180 
181 /**
182  * Copies a string into a given target buffer with a given size if it fits.
183  *
184  * @param[out] aTargetBuffer  A pointer to the target buffer to copy into.
185  * @param[out] aTargetSize    The size (number of characters) in @p aTargetBuffer array.
186  * @param[in]  aSource        A pointer to null-terminated string to copy from. Can be `nullptr` which treated as "".
187  * @param[in]  aEncodingCheck Specifies the encoding format check (e.g., UTF-8) to perform.
188  *
189  * @retval kErrorNone         The @p aSource fits in the given buffer. @p aTargetBuffer is updated.
190  * @retval kErrorInvalidArgs  The @p aSource does not fit in the given buffer.
191  * @retval kErrorParse        The @p aSource does not follow the encoding format specified by @p aEncodingCheck.
192  *
193  */
194 Error StringCopy(char *TargetBuffer, uint16_t aTargetSize, const char *aSource, StringEncodingCheck aEncodingCheck);
195 
196 /**
197  * Copies a string into a given target buffer with a given size if it fits.
198  *
199  * @tparam kSize  The size of buffer.
200  *
201  * @param[out] aTargetBuffer  A reference to the target buffer array to copy into.
202  * @param[in]  aSource        A pointer to null-terminated string to copy from. Can be `nullptr` which treated as "".
203  * @param[in]  aEncodingCheck Specifies the encoding format check (e.g., UTF-8) to perform.
204  *
205  * @retval kErrorNone         The @p aSource fits in the given buffer. @p aTargetBuffer is updated.
206  * @retval kErrorInvalidArgs  The @p aSource does not fit in the given buffer.
207  * @retval kErrorParse        The @p aSource does not follow the encoding format specified by @p aEncodingCheck.
208  *
209  */
210 template <uint16_t kSize>
StringCopy(char (& aTargetBuffer)[kSize],const char * aSource,StringEncodingCheck aEncodingCheck=kStringNoEncodingCheck)211 Error StringCopy(char (&aTargetBuffer)[kSize],
212                  const char         *aSource,
213                  StringEncodingCheck aEncodingCheck = kStringNoEncodingCheck)
214 {
215     return StringCopy(aTargetBuffer, kSize, aSource, aEncodingCheck);
216 }
217 
218 /**
219  * Parses a decimal number from a string as `uint8_t` and skips over the parsed characters.
220  *
221  * If the string does not start with a digit, `kErrorParse` is returned.
222  *
223  * All the digit characters in the string are parsed until reaching a non-digit character. The pointer `aString` is
224  * updated to point to the first non-digit character after the parsed digits.
225  *
226  * If the parsed number value is larger than @p aMaxValue, `kErrorParse` is returned.
227  *
228  * @param[in,out] aString    A reference to a pointer to string to parse.
229  * @param[out]    aUint8     A reference to return the parsed value.
230  * @param[in]     aMaxValue  Maximum allowed value for the parsed number.
231  *
232  * @retval kErrorNone   Successfully parsed the number from string. @p aString and @p aUint8 are updated.
233  * @retval kErrorParse  Failed to parse the number from @p aString, or parsed number is larger than @p aMaxValue.
234  *
235  */
236 Error StringParseUint8(const char *&aString, uint8_t &aUint8, uint8_t aMaxValue);
237 
238 /**
239  * Parses a decimal number from a string as `uint8_t` and skips over the parsed characters.
240  *
241  * If the string does not start with a digit, `kErrorParse` is returned.
242  *
243  * All the digit characters in the string are parsed until reaching a non-digit character. The pointer `aString` is
244  * updated to point to the first non-digit character after the parsed digits.
245  *
246  * If the parsed number value is larger than maximum `uint8_t` value, `kErrorParse` is returned.
247  *
248  * @param[in,out] aString    A reference to a pointer to string to parse.
249  * @param[out]    aUint8     A reference to return the parsed value.
250  *
251  * @retval kErrorNone   Successfully parsed the number from string. @p aString and @p aUint8 are updated.
252  * @retval kErrorParse  Failed to parse the number from @p aString, or parsed number is out of range.
253  *
254  */
255 Error StringParseUint8(const char *&aString, uint8_t &aUint8);
256 
257 /**
258  * Converts all uppercase letter characters in a given string to lowercase.
259  *
260  * @param[in,out] aString   A pointer to the string to convert.
261  *
262  */
263 void StringConvertToLowercase(char *aString);
264 
265 /**
266  * Converts all lowercase letter characters in a given string to uppercase.
267  *
268  * @param[in,out] aString   A pointer to the string to convert.
269  *
270  */
271 void StringConvertToUppercase(char *aString);
272 
273 /**
274  * Converts an uppercase letter character to lowercase.
275  *
276  * If @p aChar is uppercase letter it is converted lowercase. Otherwise, it remains unchanged.
277  *
278  * @param[in] aChar   The character to convert
279  *
280  * @returns The character converted to lowercase.
281  *
282  */
283 char ToLowercase(char aChar);
284 
285 /**
286  * Converts a lowercase letter character to uppercase.
287  *
288  * If @p aChar is lowercase letter it is converted uppercase. Otherwise, it remains unchanged.
289  *
290  * @param[in] aChar   The character to convert
291  *
292  * @returns The character converted to uppercase.
293  *
294  */
295 char ToUppercase(char aChar);
296 
297 /**
298  * Checks whether a given character is an uppercase letter ('A'-'Z').
299  *
300  * @param[in] aChar   The character to check.
301  *
302  * @retval TRUE    @p aChar is an uppercase letter.
303  * @retval FALSE   @p aChar is not an uppercase letter.
304  *
305  */
306 bool IsUppercase(char aChar);
307 
308 /**
309  * Checks whether a given character is a lowercase letter ('a'-'z').
310  *
311  * @param[in] aChar   The character to check.
312  *
313  * @retval TRUE    @p aChar is a lowercase letter.
314  * @retval FALSE   @p aChar is not a lowercase letter.
315  *
316  */
317 bool IsLowercase(char aChar);
318 
319 /**
320  * Checks whether a given character is a digit character ('0'-'9').
321  *
322  * @param[in] aChar   The character to check.
323  *
324  * @retval TRUE    @p aChar is a digit character.
325  * @retval FALSE   @p aChar is not a digit character.
326  *
327  */
328 bool IsDigit(char aChar);
329 
330 /**
331  * Parse a given digit character to its numeric value.
332  *
333  * @param[in]  aDigitChar   The digit character to parse.
334  * @param[out] aValue       A reference to return the parsed value on success.
335  *
336  * @retval kErrorNone            Successfully parsed the digit, @p aValue is updated.
337  * @retval kErrorInvalidArgs     @p aDigitChar is not a valid digit character.
338  *
339  */
340 Error ParseDigit(char aDigitChar, uint8_t &aValue);
341 
342 /**
343  * Parse a given hex digit character ('0'-'9', 'A'-'F', or 'a'-'f') to its numeric value.
344  *
345  * @param[in]  aHexChar     The hex digit character to parse.
346  * @param[out] aValue       A reference to return the parsed value on success.
347  *
348  * @retval kErrorNone            Successfully parsed the digit, @p aValue is updated.
349  * @retval kErrorInvalidArgs     @p aHexChar is not a valid hex digit character.
350  *
351  */
352 Error ParseHexDigit(char aHexChar, uint8_t &aValue);
353 
354 /**
355  * Converts a boolean to "yes" or "no" string.
356  *
357  * @param[in] aBool  A boolean value to convert.
358  *
359  * @returns The converted string representation of @p aBool ("yes" for TRUE and "no" for FALSE).
360  *
361  */
362 const char *ToYesNo(bool aBool);
363 
364 /**
365  * Validates whether a given byte sequence (string) follows UTF-8 encoding.
366  * Control characters are not allowed.
367  *
368  * @param[in]  aString  A null-terminated byte sequence.
369  *
370  * @retval TRUE   The sequence is a valid UTF-8 string.
371  * @retval FALSE  The sequence is not a valid UTF-8 string.
372  *
373  */
374 bool IsValidUtf8String(const char *aString);
375 
376 /**
377  * Validates whether a given byte sequence (string) follows UTF-8 encoding.
378  * Control characters are not allowed.
379  *
380  * @param[in]  aString  A byte sequence.
381  * @param[in]  aLength  Length of the sequence.
382  *
383  * @retval TRUE   The sequence is a valid UTF-8 string.
384  * @retval FALSE  The sequence is not a valid UTF-8 string.
385  *
386  */
387 bool IsValidUtf8String(const char *aString, size_t aLength);
388 
389 /**
390  * This `constexpr` function checks whether two given C strings are in order (alphabetical order).
391  *
392  * This is intended for use from `static_assert`, e.g., checking if a lookup table entries are sorted. It is not
393  * recommended to use this function in other situations as it uses recursion so that it can be `constexpr`.
394  *
395  * @param[in] aFirst    The first string.
396  * @param[in] aSecond   The second string.
397  *
398  * @retval TRUE  If first string is strictly before second string (alphabetical order).
399  * @retval FALSE If first string is not strictly before second string (alphabetical order).
400  *
401  */
AreStringsInOrder(const char * aFirst,const char * aSecond)402 inline constexpr bool AreStringsInOrder(const char *aFirst, const char *aSecond)
403 {
404     return (*aFirst < *aSecond)
405                ? true
406                : ((*aFirst > *aSecond) || (*aFirst == '\0') ? false : AreStringsInOrder(aFirst + 1, aSecond + 1));
407 }
408 
409 /**
410  * Implements writing to a string buffer.
411  *
412  */
413 class StringWriter
414 {
415 public:
416     /**
417      * Initializes the object as cleared on the provided buffer.
418      *
419      * @param[in] aBuffer  A pointer to the char buffer to write into.
420      * @param[in] aSize    The size of @p aBuffer.
421      *
422      */
423     StringWriter(char *aBuffer, uint16_t aSize);
424 
425     /**
426      * Clears the string writer.
427      *
428      * @returns The string writer.
429      *
430      */
431     StringWriter &Clear(void);
432 
433     /**
434      * Returns whether the output is truncated.
435      *
436      * @note If the output is truncated, the buffer is still null-terminated.
437      *
438      * @retval  true    The output is truncated.
439      * @retval  false   The output is not truncated.
440      *
441      */
IsTruncated(void) const442     bool IsTruncated(void) const { return mLength >= mSize; }
443 
444     /**
445      * Gets the length of the wanted string.
446      *
447      * Similar to `strlen()` the length does not include the null character at the end of the string.
448      *
449      * @returns The string length.
450      *
451      */
GetLength(void) const452     uint16_t GetLength(void) const { return mLength; }
453 
454     /**
455      * Returns the size (number of chars) in the buffer.
456      *
457      * @returns The size of the buffer.
458      *
459      */
GetSize(void) const460     uint16_t GetSize(void) const { return mSize; }
461 
462     /**
463      * Appends `printf()` style formatted data to the buffer.
464      *
465      * @param[in] aFormat    A pointer to the format string.
466      * @param[in] ...        Arguments for the format specification.
467      *
468      * @returns The string writer.
469      *
470      */
471     StringWriter &Append(const char *aFormat, ...) OT_TOOL_PRINTF_STYLE_FORMAT_ARG_CHECK(2, 3);
472 
473     /**
474      * Appends `printf()` style formatted data to the buffer.
475      *
476      * @param[in] aFormat    A pointer to the format string.
477      * @param[in] aArgs      Arguments for the format specification (as `va_list`).
478      *
479      * @returns The string writer.
480      *
481      */
482     StringWriter &AppendVarArgs(const char *aFormat, va_list aArgs);
483 
484     /**
485      * Appends an array of bytes in hex representation (using "%02x" style) to the buffer.
486      *
487      * @param[in] aBytes    A pointer to buffer containing the bytes to append.
488      * @param[in] aLength   The length of @p aBytes buffer (in bytes).
489      *
490      * @returns The string writer.
491      *
492      */
493     StringWriter &AppendHexBytes(const uint8_t *aBytes, uint16_t aLength);
494 
495     /**
496      * Appends a given character a given number of times.
497      *
498      * @param[in] aChar    The character to append.
499      * @param[in] aCount   Number of times to append @p aChar.
500      *
501      */
502     StringWriter &AppendCharMultipleTimes(char aChar, uint16_t aCount);
503 
504     /**
505      * Converts all uppercase letter characters in the string to lowercase.
506      *
507      */
ConvertToLowercase(void)508     void ConvertToLowercase(void) { StringConvertToLowercase(mBuffer); }
509 
510     /**
511      * Converts all lowercase letter characters in the string to uppercase.
512      *
513      */
ConvertToUppercase(void)514     void ConvertToUppercase(void) { StringConvertToUppercase(mBuffer); }
515 
516 private:
517     char          *mBuffer;
518     uint16_t       mLength;
519     const uint16_t mSize;
520 };
521 
522 /**
523  * Defines a fixed-size string.
524  *
525  */
526 template <uint16_t kSize> class String : public StringWriter
527 {
528     static_assert(kSize > 0, "String buffer cannot be empty.");
529 
530 public:
531     /**
532      * Initializes the string as empty.
533      *
534      */
String(void)535     String(void)
536         : StringWriter(mBuffer, sizeof(mBuffer))
537     {
538     }
539 
540     /**
541      * Returns the string as a null-terminated C string.
542      *
543      * @returns The null-terminated C string.
544      *
545      */
AsCString(void) const546     const char *AsCString(void) const { return mBuffer; }
547 
548 private:
549     char mBuffer[kSize];
550 };
551 
552 /**
553  * Provides helper methods to convert from a set of `uint16_t` values (e.g., a non-sequential `enum`) to
554  * string using binary search in a lookup table.
555  *
556  */
557 class Stringify : public BinarySearch
558 {
559 public:
560     /**
561      * Represents a entry in the lookup table.
562      *
563      */
564     class Entry
565     {
566         friend class BinarySearch;
567 
568     public:
569         uint16_t    mKey;    ///< The key value.
570         const char *mString; ///< The associated string.
571 
572     private:
Compare(uint16_t aKey) const573         int Compare(uint16_t aKey) const { return ThreeWayCompare(aKey, mKey); }
574 
AreInOrder(const Entry & aFirst,const Entry & aSecond)575         constexpr static bool AreInOrder(const Entry &aFirst, const Entry &aSecond)
576         {
577             return aFirst.mKey < aSecond.mKey;
578         }
579     };
580 
581     /**
582      * Looks up a key in a given sorted table array (using binary search) and return the associated
583      * strings with the key.
584      *
585      * @note This method requires the array to be sorted, otherwise its behavior is undefined.
586      *
587      * @tparam kLength     The array length (number of entries in the array).
588      *
589      * @param[in] aKey       The key to search for within the table.
590      * @param[in] aTable     A reference to an array of `kLength` entries.
591      * @param[in] aNotFound  A C string to return if @p aKey was not found in the table.
592      *
593      * @returns The associated string with @p aKey in @p aTable if found, or @p aNotFound otherwise.
594      *
595      */
596     template <uint16_t kLength>
Lookup(uint16_t aKey,const Entry (& aTable)[kLength],const char * aNotFound="unknown")597     static const char *Lookup(uint16_t aKey, const Entry (&aTable)[kLength], const char *aNotFound = "unknown")
598     {
599         const Entry *entry = BinarySearch::Find(aKey, aTable);
600 
601         return (entry != nullptr) ? entry->mString : aNotFound;
602     }
603 
604     Stringify(void) = delete;
605 };
606 
607 /**
608  * @}
609  *
610  */
611 
612 } // namespace ot
613 
614 #endif // STRING_HPP_
615