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