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 * Represents comparison mode when matching strings.
61 */
62 enum StringMatchMode : uint8_t
63 {
64 kStringExactMatch, ///< Exact match of characters.
65 kStringCaseInsensitiveMatch, ///< Case insensitive match (uppercase and lowercase characters are treated as equal).
66 };
67
68 /**
69 * Represents string encoding check when copying string.
70 */
71 enum StringEncodingCheck : uint8_t
72 {
73 kStringNoEncodingCheck, ///< Do not check the string encoding.
74 kStringCheckUtf8Encoding, ///< Validate that string follows UTF-8 encoding.
75 };
76
77 static constexpr char kNullChar = '\0'; ///< null character.
78
79 /**
80 * Returns the number of characters that precede the terminating null character.
81 *
82 * @param[in] aString A pointer to the string.
83 * @param[in] aMaxLength The maximum length in bytes.
84 *
85 * @returns The number of characters that precede the terminating null character or @p aMaxLength,
86 * whichever is smaller. `0` if @p aString is `nullptr`.
87 */
88 uint16_t StringLength(const char *aString, uint16_t aMaxLength);
89
90 /**
91 * Finds the first occurrence of a given character in a null-terminated string.
92 *
93 * @param[in] aString A pointer to the string.
94 * @param[in] aChar A char to search for in the string.
95 *
96 * @returns The pointer to first occurrence of the @p aChar in @p aString, or `nullptr` if cannot be found.
97 */
98 const char *StringFind(const char *aString, char aChar);
99
100 /**
101 * Finds the first occurrence of a given sub-string in a null-terminated string.
102 *
103 * @param[in] aString A pointer to the string.
104 * @param[in] aSubString A sub-string to search for.
105 * @param[in] aMode The string comparison mode, exact match or case insensitive match.
106 *
107 * @returns The pointer to first match of the @p aSubString in @p aString (using comparison @p aMode), or `nullptr` if
108 * cannot be found.
109 */
110 const char *StringFind(const char *aString, const char *aSubString, StringMatchMode aMode = kStringExactMatch);
111
112 /**
113 * Checks whether a null-terminated string starts with a given prefix string.
114 *
115 * @param[in] aString A pointer to the string.
116 * @param[in] aPrefixString A prefix string.
117 * @param[in] aMode The string comparison mode, exact match or case insensitive match.
118 *
119 * @retval TRUE If @p aString starts with @p aPrefixString.
120 * @retval FALSE If @p aString does not start with @p aPrefixString.
121 */
122 bool StringStartsWith(const char *aString, const char *aPrefixString, StringMatchMode aMode = kStringExactMatch);
123
124 /**
125 * Checks whether a null-terminated string ends with a given character.
126 *
127 * @param[in] aString A pointer to the string.
128 * @param[in] aChar A char to check.
129 *
130 * @retval TRUE If @p aString ends with character @p aChar.
131 * @retval FALSE If @p aString does not end with character @p aChar.
132 */
133 bool StringEndsWith(const char *aString, char aChar);
134
135 /**
136 * Checks whether a null-terminated string ends with a given sub-string.
137 *
138 * @param[in] aString A pointer to the string.
139 * @param[in] aSubString A sub-string to check against.
140 * @param[in] aMode The string comparison mode, exact match or case insensitive match.
141 *
142 * @retval TRUE If @p aString ends with sub-string @p aSubString.
143 * @retval FALSE If @p aString does not end with sub-string @p aSubString.
144 */
145 bool StringEndsWith(const char *aString, const char *aSubString, StringMatchMode aMode = kStringExactMatch);
146
147 /**
148 * Checks whether or not two null-terminated strings match exactly.
149 *
150 * @param[in] aFirstString A pointer to the first string.
151 * @param[in] aSecondString A pointer to the second string.
152 *
153 * @retval TRUE If @p aFirstString matches @p aSecondString.
154 * @retval FALSE If @p aFirstString does not match @p aSecondString.
155 */
156 bool StringMatch(const char *aFirstString, const char *aSecondString);
157
158 /**
159 * Checks whether or not two null-terminated strings match.
160 *
161 * @param[in] aFirstString A pointer to the first string.
162 * @param[in] aSecondString A pointer to the second string.
163 * @param[in] aMode The string comparison mode, exact match or case insensitive match.
164 *
165 * @retval TRUE If @p aFirstString matches @p aSecondString using match mode @p aMode.
166 * @retval FALSE If @p aFirstString does not match @p aSecondString using match mode @p aMode.
167 */
168 bool StringMatch(const char *aFirstString, const char *aSecondString, StringMatchMode aMode);
169
170 /**
171 * Copies a string into a given target buffer with a given size if it fits.
172 *
173 * @param[out] aTargetBuffer A pointer to the target buffer to copy into.
174 * @param[out] aTargetSize The size (number of characters) in @p aTargetBuffer array.
175 * @param[in] aSource A pointer to null-terminated string to copy from. Can be `nullptr` which treated as "".
176 * @param[in] aEncodingCheck Specifies the encoding format check (e.g., UTF-8) to perform.
177 *
178 * @retval kErrorNone The @p aSource fits in the given buffer. @p aTargetBuffer is updated.
179 * @retval kErrorInvalidArgs The @p aSource does not fit in the given buffer.
180 * @retval kErrorParse The @p aSource does not follow the encoding format specified by @p aEncodingCheck.
181 */
182 Error StringCopy(char *TargetBuffer, uint16_t aTargetSize, const char *aSource, StringEncodingCheck aEncodingCheck);
183
184 /**
185 * Copies a string into a given target buffer with a given size if it fits.
186 *
187 * @tparam kSize The size of buffer.
188 *
189 * @param[out] aTargetBuffer A reference to the target buffer array to copy into.
190 * @param[in] aSource A pointer to null-terminated string to copy from. Can be `nullptr` which treated as "".
191 * @param[in] aEncodingCheck Specifies the encoding format check (e.g., UTF-8) to perform.
192 *
193 * @retval kErrorNone The @p aSource fits in the given buffer. @p aTargetBuffer is updated.
194 * @retval kErrorInvalidArgs The @p aSource does not fit in the given buffer.
195 * @retval kErrorParse The @p aSource does not follow the encoding format specified by @p aEncodingCheck.
196 */
197 template <uint16_t kSize>
StringCopy(char (& aTargetBuffer)[kSize],const char * aSource,StringEncodingCheck aEncodingCheck=kStringNoEncodingCheck)198 Error StringCopy(char (&aTargetBuffer)[kSize],
199 const char *aSource,
200 StringEncodingCheck aEncodingCheck = kStringNoEncodingCheck)
201 {
202 return StringCopy(aTargetBuffer, kSize, aSource, aEncodingCheck);
203 }
204
205 /**
206 * Parses a decimal number from a string as `uint8_t` and skips over the parsed characters.
207 *
208 * If the string does not start with a digit, `kErrorParse` is returned.
209 *
210 * All the digit characters in the string are parsed until reaching a non-digit character. The pointer `aString` is
211 * updated to point to the first non-digit character after the parsed digits.
212 *
213 * If the parsed number value is larger than @p aMaxValue, `kErrorParse` is returned.
214 *
215 * @param[in,out] aString A reference to a pointer to string to parse.
216 * @param[out] aUint8 A reference to return the parsed value.
217 * @param[in] aMaxValue Maximum allowed value for the parsed number.
218 *
219 * @retval kErrorNone Successfully parsed the number from string. @p aString and @p aUint8 are updated.
220 * @retval kErrorParse Failed to parse the number from @p aString, or parsed number is larger than @p aMaxValue.
221 */
222 Error StringParseUint8(const char *&aString, uint8_t &aUint8, uint8_t aMaxValue);
223
224 /**
225 * Parses a decimal number from a string as `uint8_t` and skips over the parsed characters.
226 *
227 * If the string does not start with a digit, `kErrorParse` is returned.
228 *
229 * All the digit characters in the string are parsed until reaching a non-digit character. The pointer `aString` is
230 * updated to point to the first non-digit character after the parsed digits.
231 *
232 * If the parsed number value is larger than maximum `uint8_t` value, `kErrorParse` is returned.
233 *
234 * @param[in,out] aString A reference to a pointer to string to parse.
235 * @param[out] aUint8 A reference to return the parsed value.
236 *
237 * @retval kErrorNone Successfully parsed the number from string. @p aString and @p aUint8 are updated.
238 * @retval kErrorParse Failed to parse the number from @p aString, or parsed number is out of range.
239 */
240 Error StringParseUint8(const char *&aString, uint8_t &aUint8);
241
242 /**
243 * Converts all uppercase letter characters in a given string to lowercase.
244 *
245 * @param[in,out] aString A pointer to the string to convert.
246 */
247 void StringConvertToLowercase(char *aString);
248
249 /**
250 * Converts all lowercase letter characters in a given string to uppercase.
251 *
252 * @param[in,out] aString A pointer to the string to convert.
253 */
254 void StringConvertToUppercase(char *aString);
255
256 /**
257 * Converts an uppercase letter character to lowercase.
258 *
259 * If @p aChar is uppercase letter it is converted lowercase. Otherwise, it remains unchanged.
260 *
261 * @param[in] aChar The character to convert
262 *
263 * @returns The character converted to lowercase.
264 */
265 char ToLowercase(char aChar);
266
267 /**
268 * Converts a lowercase letter character to uppercase.
269 *
270 * If @p aChar is lowercase letter it is converted uppercase. Otherwise, it remains unchanged.
271 *
272 * @param[in] aChar The character to convert
273 *
274 * @returns The character converted to uppercase.
275 */
276 char ToUppercase(char aChar);
277
278 /**
279 * Checks whether a given character is an uppercase letter ('A'-'Z').
280 *
281 * @param[in] aChar The character to check.
282 *
283 * @retval TRUE @p aChar is an uppercase letter.
284 * @retval FALSE @p aChar is not an uppercase letter.
285 */
286 bool IsUppercase(char aChar);
287
288 /**
289 * Checks whether a given character is a lowercase letter ('a'-'z').
290 *
291 * @param[in] aChar The character to check.
292 *
293 * @retval TRUE @p aChar is a lowercase letter.
294 * @retval FALSE @p aChar is not a lowercase letter.
295 */
296 bool IsLowercase(char aChar);
297
298 /**
299 * Checks whether a given character is a digit character ('0'-'9').
300 *
301 * @param[in] aChar The character to check.
302 *
303 * @retval TRUE @p aChar is a digit character.
304 * @retval FALSE @p aChar is not a digit character.
305 */
306 bool IsDigit(char aChar);
307
308 /**
309 * Parse a given digit character to its numeric value.
310 *
311 * @param[in] aDigitChar The digit character to parse.
312 * @param[out] aValue A reference to return the parsed value on success.
313 *
314 * @retval kErrorNone Successfully parsed the digit, @p aValue is updated.
315 * @retval kErrorInvalidArgs @p aDigitChar is not a valid digit character.
316 */
317 Error ParseDigit(char aDigitChar, uint8_t &aValue);
318
319 /**
320 * Parse a given hex digit character ('0'-'9', 'A'-'F', or 'a'-'f') to its numeric value.
321 *
322 * @param[in] aHexChar The hex digit character to parse.
323 * @param[out] aValue A reference to return the parsed value on success.
324 *
325 * @retval kErrorNone Successfully parsed the digit, @p aValue is updated.
326 * @retval kErrorInvalidArgs @p aHexChar is not a valid hex digit character.
327 */
328 Error ParseHexDigit(char aHexChar, uint8_t &aValue);
329
330 /**
331 * Converts a boolean to "yes" or "no" string.
332 *
333 * @param[in] aBool A boolean value to convert.
334 *
335 * @returns The converted string representation of @p aBool ("yes" for TRUE and "no" for FALSE).
336 */
337 const char *ToYesNo(bool aBool);
338
339 /**
340 * Validates whether a given byte sequence (string) follows UTF-8 encoding.
341 * Control characters are not allowed.
342 *
343 * @param[in] aString A null-terminated byte sequence.
344 *
345 * @retval TRUE The sequence is a valid UTF-8 string.
346 * @retval FALSE The sequence is not a valid UTF-8 string.
347 */
348 bool IsValidUtf8String(const char *aString);
349
350 /**
351 * Validates whether a given byte sequence (string) follows UTF-8 encoding.
352 * Control characters are not allowed.
353 *
354 * @param[in] aString A byte sequence.
355 * @param[in] aLength Length of the sequence.
356 *
357 * @retval TRUE The sequence is a valid UTF-8 string.
358 * @retval FALSE The sequence is not a valid UTF-8 string.
359 */
360 bool IsValidUtf8String(const char *aString, size_t aLength);
361
362 /**
363 * This `constexpr` function checks whether two given C strings are in order (alphabetical order).
364 *
365 * This is intended for use from `static_assert`, e.g., checking if a lookup table entries are sorted. It is not
366 * recommended to use this function in other situations as it uses recursion so that it can be `constexpr`.
367 *
368 * @param[in] aFirst The first string.
369 * @param[in] aSecond The second string.
370 *
371 * @retval TRUE If first string is strictly before second string (alphabetical order).
372 * @retval FALSE If first string is not strictly before second string (alphabetical order).
373 */
AreStringsInOrder(const char * aFirst,const char * aSecond)374 inline constexpr bool AreStringsInOrder(const char *aFirst, const char *aSecond)
375 {
376 return (*aFirst < *aSecond)
377 ? true
378 : ((*aFirst > *aSecond) || (*aFirst == '\0') ? false : AreStringsInOrder(aFirst + 1, aSecond + 1));
379 }
380
381 /**
382 * Implements writing to a string buffer.
383 */
384 class StringWriter
385 {
386 public:
387 /**
388 * Initializes the object as cleared on the provided buffer.
389 *
390 * @param[in] aBuffer A pointer to the char buffer to write into.
391 * @param[in] aSize The size of @p aBuffer.
392 */
393 StringWriter(char *aBuffer, uint16_t aSize);
394
395 /**
396 * Clears the string writer.
397 *
398 * @returns The string writer.
399 */
400 StringWriter &Clear(void);
401
402 /**
403 * Returns whether the output is truncated.
404 *
405 * @note If the output is truncated, the buffer is still null-terminated.
406 *
407 * @retval true The output is truncated.
408 * @retval false The output is not truncated.
409 */
IsTruncated(void) const410 bool IsTruncated(void) const { return mLength >= mSize; }
411
412 /**
413 * Gets the length of the wanted string.
414 *
415 * Similar to `strlen()` the length does not include the null character at the end of the string.
416 *
417 * @returns The string length.
418 */
GetLength(void) const419 uint16_t GetLength(void) const { return mLength; }
420
421 /**
422 * Returns the size (number of chars) in the buffer.
423 *
424 * @returns The size of the buffer.
425 */
GetSize(void) const426 uint16_t GetSize(void) const { return mSize; }
427
428 /**
429 * Appends `printf()` style formatted data to the buffer.
430 *
431 * @param[in] aFormat A pointer to the format string.
432 * @param[in] ... Arguments for the format specification.
433 *
434 * @returns The string writer.
435 */
436 StringWriter &Append(const char *aFormat, ...) OT_TOOL_PRINTF_STYLE_FORMAT_ARG_CHECK(2, 3);
437
438 /**
439 * Appends `printf()` style formatted data to the buffer.
440 *
441 * @param[in] aFormat A pointer to the format string.
442 * @param[in] aArgs Arguments for the format specification (as `va_list`).
443 *
444 * @returns The string writer.
445 */
446 StringWriter &AppendVarArgs(const char *aFormat, va_list aArgs);
447
448 /**
449 * Appends an array of bytes in hex representation (using "%02x" style) to the buffer.
450 *
451 * @param[in] aBytes A pointer to buffer containing the bytes to append.
452 * @param[in] aLength The length of @p aBytes buffer (in bytes).
453 *
454 * @returns The string writer.
455 */
456 StringWriter &AppendHexBytes(const uint8_t *aBytes, uint16_t aLength);
457
458 /**
459 * Appends a given character a given number of times.
460 *
461 * @param[in] aChar The character to append.
462 * @param[in] aCount Number of times to append @p aChar.
463 */
464 StringWriter &AppendCharMultipleTimes(char aChar, uint16_t aCount);
465
466 /**
467 * Converts all uppercase letter characters in the string to lowercase.
468 */
ConvertToLowercase(void)469 void ConvertToLowercase(void) { StringConvertToLowercase(mBuffer); }
470
471 /**
472 * Converts all lowercase letter characters in the string to uppercase.
473 */
ConvertToUppercase(void)474 void ConvertToUppercase(void) { StringConvertToUppercase(mBuffer); }
475
476 private:
477 char *mBuffer;
478 uint16_t mLength;
479 const uint16_t mSize;
480 };
481
482 /**
483 * Defines a fixed-size string.
484 */
485 template <uint16_t kSize> class String : public StringWriter
486 {
487 static_assert(kSize > 0, "String buffer cannot be empty.");
488
489 public:
490 /**
491 * Initializes the string as empty.
492 */
String(void)493 String(void)
494 : StringWriter(mBuffer, sizeof(mBuffer))
495 {
496 }
497
498 /**
499 * Returns the string as a null-terminated C string.
500 *
501 * @returns The null-terminated C string.
502 */
AsCString(void) const503 const char *AsCString(void) const { return mBuffer; }
504
505 private:
506 char mBuffer[kSize];
507 };
508
509 /**
510 * Provides helper methods to convert from a set of `uint16_t` values (e.g., a non-sequential `enum`) to
511 * string using binary search in a lookup table.
512 */
513 class Stringify : public BinarySearch
514 {
515 public:
516 /**
517 * Represents a entry in the lookup table.
518 */
519 class Entry
520 {
521 friend class BinarySearch;
522
523 public:
524 uint16_t mKey; ///< The key value.
525 const char *mString; ///< The associated string.
526
527 private:
Compare(uint16_t aKey) const528 int Compare(uint16_t aKey) const { return ThreeWayCompare(aKey, mKey); }
529
AreInOrder(const Entry & aFirst,const Entry & aSecond)530 constexpr static bool AreInOrder(const Entry &aFirst, const Entry &aSecond)
531 {
532 return aFirst.mKey < aSecond.mKey;
533 }
534 };
535
536 /**
537 * Looks up a key in a given sorted table array (using binary search) and return the associated
538 * strings with the key.
539 *
540 * @note This method requires the array to be sorted, otherwise its behavior is undefined.
541 *
542 * @tparam kLength The array length (number of entries in the array).
543 *
544 * @param[in] aKey The key to search for within the table.
545 * @param[in] aTable A reference to an array of `kLength` entries.
546 * @param[in] aNotFound A C string to return if @p aKey was not found in the table.
547 *
548 * @returns The associated string with @p aKey in @p aTable if found, or @p aNotFound otherwise.
549 */
550 template <uint16_t kLength>
Lookup(uint16_t aKey,const Entry (& aTable)[kLength],const char * aNotFound="unknown")551 static const char *Lookup(uint16_t aKey, const Entry (&aTable)[kLength], const char *aNotFound = "unknown")
552 {
553 const Entry *entry = BinarySearch::Find(aKey, aTable);
554
555 return (entry != nullptr) ? entry->mString : aNotFound;
556 }
557
558 Stringify(void) = delete;
559 };
560
561 /**
562 * @}
563 */
564
565 } // namespace ot
566
567 #endif // STRING_HPP_
568