Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
sapi_string.c
Go to the documentation of this file.
1
8#include <string.h>
9
10sapi_status_t sapi_string_init(sapi_string_t *str, char *storage, size_t capacity)
11{
12 if (str == NULL)
13 {
15 }
16 return sapi_buffer_init(&str->buf, (void *)storage, capacity);
17}
18
20{
21 if (str == NULL)
22 {
24 }
25 return sapi_buffer_clear(&str->buf);
26}
27
29{
30 if (str == NULL)
31 {
32 return 0U;
33 }
34 return str->buf.length;
35}
36
37sapi_status_t sapi_string_c_str(sapi_string_t *str, const char **out_cstr)
38{
39 char *data;
40
41 if ((str == NULL) || (out_cstr == NULL))
42 {
44 }
45 if (!sapi_buffer_is_valid(&str->buf))
46 {
48 }
49 if (str->buf.length >= str->buf.capacity)
50 {
52 }
53 data = (char *)str->buf.data;
54 data[str->buf.length] = '\0';
55 *out_cstr = (const char *)str->buf.data;
56 return SAPI_STATUS_OK;
57}
58
59sapi_status_t sapi_string_copy_n(sapi_string_t *dest, const char *src, size_t src_len)
60{
61 if ((dest == NULL) || (src == NULL))
62 {
64 }
65 return sapi_buffer_copy_in(&dest->buf, (const void *)src, src_len);
66}
67
69{
70 size_t max_scan;
71 size_t i;
72 bool found_nul = false;
73
74 if ((dest == NULL) || (src == NULL))
75 {
77 }
78 if (!sapi_buffer_is_valid(&dest->buf))
79 {
81 }
82 /* A NUL at index == capacity is acceptable (content length == capacity
83 * exactly fills dest); scan capacity+1 positions, 0..capacity inclusive. */
84 max_scan = dest->buf.capacity;
85 for (i = 0U; i <= max_scan; i++)
86 {
87 if (src[i] == '\0')
88 {
89 found_nul = true;
90 break;
91 }
92 }
93 if (!found_nul)
94 {
96 }
97 return sapi_string_copy_n(dest, src, i);
98}
99
101{
102 size_t remaining;
103 size_t i;
104 bool found_nul = false;
105 char *data;
106
107 if ((dest == NULL) || (src == NULL))
108 {
110 }
111 if (!sapi_buffer_is_valid(&dest->buf))
112 {
114 }
115 /* Same off-by-one reasoning as sapi_string_copy(): a NUL at
116 * index == remaining is acceptable (appended content exactly fills
117 * the remaining capacity). */
118 remaining = dest->buf.capacity - dest->buf.length;
119 for (i = 0U; i <= remaining; i++)
120 {
121 if (src[i] == '\0')
122 {
123 found_nul = true;
124 break;
125 }
126 }
127 if (!found_nul)
128 {
130 }
131 data = (char *)dest->buf.data;
132 if (i > 0U)
133 {
134 (void)memcpy(&data[dest->buf.length], src, i);
135 }
136 dest->buf.length += i;
137 return SAPI_STATUS_OK;
138}
139
141{
142 size_t min_len;
143 size_t i;
144 int32_t result = 0;
145 const unsigned char *pa;
146 const unsigned char *pb;
147
148 if ((a == NULL) || (b == NULL) || (out_cmp == NULL))
149 {
151 }
152 if ((!sapi_buffer_is_valid(&a->buf)) || (!sapi_buffer_is_valid(&b->buf)))
153 {
155 }
156 min_len = (a->buf.length < b->buf.length) ? a->buf.length : b->buf.length;
157 pa = (const unsigned char *)a->buf.data;
158 pb = (const unsigned char *)b->buf.data;
159 for (i = 0U; i < min_len; i++)
160 {
161 if (pa[i] != pb[i])
162 {
163 result = (pa[i] < pb[i]) ? -1 : 1;
164 break;
165 }
166 }
167 if (result == 0)
168 {
169 if (a->buf.length < b->buf.length)
170 {
171 result = -1;
172 }
173 else if (a->buf.length > b->buf.length)
174 {
175 result = 1;
176 }
177 else
178 {
179 result = 0;
180 }
181 }
182 *out_cmp = result;
183 return SAPI_STATUS_OK;
184}
185
187 bool *out_found, size_t *out_index)
188{
189 const char *data;
190 size_t i;
191
192 if ((str == NULL) || (out_found == NULL) || (out_index == NULL))
193 {
195 }
196 if (!sapi_buffer_is_valid(&str->buf))
197 {
199 }
200 data = (const char *)str->buf.data;
201 for (i = 0U; i < str->buf.length; i++)
202 {
203 if (data[i] == c)
204 {
205 *out_found = true;
206 *out_index = i;
207 return SAPI_STATUS_OK;
208 }
209 }
210 *out_found = false;
211 return SAPI_STATUS_OK;
212}
213
215 bool *out_found, size_t *out_index)
216{
217 const char *hdata;
218 const char *ndata;
219 size_t max_start;
220 size_t i;
221
222 if ((haystack == NULL) || (needle == NULL) || (out_found == NULL) || (out_index == NULL))
223 {
225 }
226 if ((!sapi_buffer_is_valid(&haystack->buf)) || (!sapi_buffer_is_valid(&needle->buf)))
227 {
229 }
230 if (needle->buf.length == 0U)
231 {
232 *out_found = true;
233 *out_index = 0U;
234 return SAPI_STATUS_OK;
235 }
236 if (needle->buf.length > haystack->buf.length)
237 {
238 *out_found = false;
239 return SAPI_STATUS_OK;
240 }
241 hdata = (const char *)haystack->buf.data;
242 ndata = (const char *)needle->buf.data;
243 max_start = haystack->buf.length - needle->buf.length;
244 for (i = 0U; i <= max_start; i++)
245 {
246 if (memcmp(&hdata[i], ndata, needle->buf.length) == 0)
247 {
248 *out_found = true;
249 *out_index = i;
250 return SAPI_STATUS_OK;
251 }
252 }
253 *out_found = false;
254 return SAPI_STATUS_OK;
255}
256
258 size_t *io_cursor,
259 sapi_const_buffer_t *out_token,
260 bool *out_has_token)
261{
262 const char *data;
263 size_t start;
264 size_t i;
265
266 if ((str == NULL) || (io_cursor == NULL) || (out_token == NULL) || (out_has_token == NULL))
267 {
269 }
270 if (!sapi_buffer_is_valid(&str->buf))
271 {
273 }
274 if (*io_cursor > str->buf.length)
275 {
277 }
278 if (*io_cursor == str->buf.length)
279 {
280 *out_has_token = false;
281 return SAPI_STATUS_OK;
282 }
283 data = (const char *)str->buf.data;
284 start = *io_cursor;
285 i = start;
286 while ((i < str->buf.length) && (data[i] != delimiter))
287 {
288 i++;
289 }
290 out_token->data = &data[start];
291 out_token->length = i - start;
292 *out_has_token = true;
293 if (i < str->buf.length)
294 {
295 *io_cursor = i + 1U; /* skip the delimiter */
296 }
297 else
298 {
299 *io_cursor = i;
300 }
301 return SAPI_STATUS_OK;
302}
303
312static void sapi_string_format_u64_digits(uint64_t value, char *out, size_t *out_len)
313{
314 char rev[20];
315 size_t n = 0U;
316 uint64_t v = value;
317
318 if (v == 0U)
319 {
320 out[0] = '0';
321 *out_len = 1U;
322 return;
323 }
324 while (v > 0U)
325 {
326 char digit = (char)(v % 10U);
327 rev[n] = (char)('0' + digit);
328 n++;
329 v /= 10U;
330 }
331 {
332 size_t i;
333 for (i = 0U; i < n; i++)
334 {
335 out[i] = rev[(n - 1U) - i];
336 }
337 }
338 *out_len = n;
339}
340
342{
343 char digits[20];
344 size_t len = 0U;
345
346 if (dest == NULL)
347 {
349 }
350 sapi_string_format_u64_digits(value, digits, &len);
351 return sapi_string_copy_n(dest, digits, len);
352}
353
355{
356 char buf[21]; /* 1 sign byte + up to 20 digits */
357 size_t len = 0U;
358 uint64_t magnitude;
359 bool negative;
360
361 if (dest == NULL)
362 {
364 }
365 negative = (value < 0);
366 if (negative)
367 {
368 /* Safe negation avoiding overflow for INT64_MIN: -(value+1) always
369 * fits int64_t since value >= INT64_MIN implies value+1 >= INT64_MIN+1. */
370 magnitude = (uint64_t)(-(value + 1)) + 1U;
371 }
372 else
373 {
374 magnitude = (uint64_t)value;
375 }
376 if (negative)
377 {
378 buf[0] = '-';
379 sapi_string_format_u64_digits(magnitude, &buf[1], &len);
380 return sapi_string_copy_n(dest, buf, len + 1U);
381 }
382 sapi_string_format_u64_digits(magnitude, buf, &len);
383 return sapi_string_copy_n(dest, buf, len);
384}
385
387{
388 uint64_t widened = 0U;
389 sapi_status_t st;
390
391 if (dest == NULL)
392 {
394 }
395 st = sapi_cast_u32_to_u64(value, &widened);
396 if (st != SAPI_STATUS_OK) /* GCOVR_EXCL_START - sapi_cast_u32_to_u64() can
397 * only fail for a NULL out pointer, and
398 * &widened is always a valid local address. */
399 {
400 return st;
401 } /* GCOVR_EXCL_STOP */
402 return sapi_string_from_u64(dest, widened);
403}
404
406{
407 int64_t widened = 0;
408 sapi_status_t st;
409
410 if (dest == NULL)
411 {
413 }
414 st = sapi_cast_i32_to_i64(value, &widened);
415 if (st != SAPI_STATUS_OK) /* GCOVR_EXCL_START - sapi_cast_i32_to_i64() can
416 * only fail for a NULL out pointer, and
417 * &widened is always a valid local address. */
418 {
419 return st;
420 } /* GCOVR_EXCL_STOP */
421 return sapi_string_from_i64(dest, widened);
422}
423
436static sapi_status_t sapi_string_append_bytes(sapi_string_t *dest, const char *src, size_t n)
437{
438 size_t remaining;
439 char *data;
440
441 if (dest == NULL)
442 {
444 }
445 if (!sapi_buffer_is_valid(&dest->buf))
446 {
448 }
449 remaining = dest->buf.capacity - dest->buf.length;
450 if (n > remaining)
451 {
453 }
454 if (n > 0U)
455 {
456 data = (char *)dest->buf.data;
457 (void)memcpy(&data[dest->buf.length], src, n);
458 dest->buf.length += n;
459 }
460 return SAPI_STATUS_OK;
461}
462
464{
465 char digits[20];
466 size_t len = 0U;
467
468 if (dest == NULL)
469 {
471 }
472 sapi_string_format_u64_digits(value, digits, &len);
473 return sapi_string_append_bytes(dest, digits, len);
474}
475
477{
478 char buf[21]; /* 1 sign byte + up to 20 digits */
479 size_t len = 0U;
480 uint64_t magnitude;
481 bool negative;
482
483 if (dest == NULL)
484 {
486 }
487 negative = (value < 0);
488 if (negative)
489 {
490 /* Safe negation avoiding overflow for INT64_MIN (see sapi_string_from_i64). */
491 magnitude = (uint64_t)(-(value + 1)) + 1U;
492 buf[0] = '-';
493 sapi_string_format_u64_digits(magnitude, &buf[1], &len);
494 return sapi_string_append_bytes(dest, buf, len + 1U);
495 }
496 magnitude = (uint64_t)value;
497 sapi_string_format_u64_digits(magnitude, buf, &len);
498 return sapi_string_append_bytes(dest, buf, len);
499}
500
502{
503 uint64_t widened = 0U;
504 sapi_status_t st;
505
506 if (dest == NULL)
507 {
509 }
510 st = sapi_cast_u32_to_u64(value, &widened);
511 if (st != SAPI_STATUS_OK) /* GCOVR_EXCL_START - only fails for a NULL out
512 * pointer; &widened is always valid. */
513 {
514 return st;
515 } /* GCOVR_EXCL_STOP */
516 return sapi_string_append_u64(dest, widened);
517}
518
520{
521 int64_t widened = 0;
522 sapi_status_t st;
523
524 if (dest == NULL)
525 {
527 }
528 st = sapi_cast_i32_to_i64(value, &widened);
529 if (st != SAPI_STATUS_OK) /* GCOVR_EXCL_START - only fails for a NULL out
530 * pointer; &widened is always valid. */
531 {
532 return st;
533 } /* GCOVR_EXCL_STOP */
534 return sapi_string_append_i64(dest, widened);
535}
536
537sapi_status_t sapi_string_append_hex_u32(sapi_string_t *dest, uint32_t value, uint8_t min_digits)
538{
539 static const char hex_lc[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
540 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
541 char out[8] = {0}; /* n is provably >= 1 below; zero-init keeps static
542 * analysis from flagging a can't-happen n == 0 path. */
543 size_t real_digits = 1U;
544 size_t width;
545 size_t n;
546 size_t i;
547 uint32_t w = value;
548
549 if (dest == NULL)
550 {
552 }
553 width = (size_t)min_digits;
554 if (width < 1U)
555 {
556 width = 1U;
557 }
558 if (width > 8U)
559 {
560 width = 8U;
561 }
562 while (w > 0x0FU)
563 {
564 real_digits++;
565 w >>= 4U;
566 }
567 n = (real_digits > width) ? real_digits : width;
568 for (i = 0U; i < n; i++)
569 {
570 /* Digit weight, counting from the right: position i from the left
571 * is shift (n - 1 - i) nibbles. Positions past real_digits are
572 * left-pad zeros. */
573 size_t shift = (n - 1U) - i;
574 if (shift >= real_digits)
575 {
576 out[i] = '0';
577 }
578 else
579 {
580 out[i] = hex_lc[(value >> (4U * (uint32_t)shift)) & 0x0FU];
581 }
582 }
583 return sapi_string_append_bytes(dest, out, n);
584}
585
586sapi_status_t sapi_string_to_u64(const sapi_string_t *str, uint64_t *out_value)
587{
588 size_t i;
589 uint64_t acc = 0U;
590 const char *data;
591
592 if ((str == NULL) || (out_value == NULL))
593 {
595 }
596 if (!sapi_buffer_is_valid(&str->buf))
597 {
599 }
600 if (str->buf.length == 0U)
601 {
603 }
604 data = (const char *)str->buf.data;
605 for (i = 0U; i < str->buf.length; i++)
606 {
607 char c = data[i];
608 uint64_t digit;
609
610 if ((c < '0') || (c > '9'))
611 {
613 }
614 digit = (uint64_t)(c - '0');
615 if (acc > ((UINT64_MAX - digit) / 10U))
616 {
618 }
619 acc = (acc * 10U) + digit;
620 }
621 *out_value = acc;
622 return SAPI_STATUS_OK;
623}
624
625sapi_status_t sapi_string_to_i64(const sapi_string_t *str, int64_t *out_value)
626{
627 size_t i;
628 size_t start;
629 bool negative;
630 uint64_t acc = 0U;
631 const char *data;
632
633 if ((str == NULL) || (out_value == NULL))
634 {
636 }
637 if (!sapi_buffer_is_valid(&str->buf))
638 {
640 }
641 if (str->buf.length == 0U)
642 {
644 }
645 data = (const char *)str->buf.data;
646 negative = false;
647 start = 0U;
648 if (data[0] == '-')
649 {
650 negative = true;
651 start = 1U;
652 }
653 if (start >= str->buf.length)
654 {
655 return SAPI_STATUS_INVALID_PARAM; /* "-" with no digits */
656 }
657 for (i = start; i < str->buf.length; i++)
658 {
659 char c = data[i];
660 uint64_t digit;
661
662 if ((c < '0') || (c > '9'))
663 {
665 }
666 digit = (uint64_t)(c - '0');
667 if (acc > ((UINT64_MAX - digit) / 10U))
668 {
670 }
671 acc = (acc * 10U) + digit;
672 }
673 if (negative)
674 {
675 uint64_t min_magnitude = (uint64_t)INT64_MAX + 1U;
676 if (acc > min_magnitude)
677 {
679 }
680 if (acc == min_magnitude)
681 {
682 *out_value = INT64_MIN;
683 }
684 else
685 {
686 *out_value = -(int64_t)acc;
687 }
688 }
689 else
690 {
691 if (acc > (uint64_t)INT64_MAX)
692 {
694 }
695 *out_value = (int64_t)acc;
696 }
697 return SAPI_STATUS_OK;
698}
699
700sapi_status_t sapi_string_to_u32(const sapi_string_t *str, uint32_t *out_value)
701{
702 uint64_t v = 0U;
703 sapi_status_t st;
704
705 if (out_value == NULL)
706 {
708 }
709 st = sapi_string_to_u64(str, &v);
710 if (st != SAPI_STATUS_OK)
711 {
712 return st;
713 }
714 return sapi_cast_u64_to_u32(v, out_value);
715}
716
717sapi_status_t sapi_string_to_i32(const sapi_string_t *str, int32_t *out_value)
718{
719 int64_t v = 0;
720 sapi_status_t st;
721
722 if (out_value == NULL)
723 {
725 }
726 st = sapi_string_to_i64(str, &v);
727 if (st != SAPI_STATUS_OK)
728 {
729 return st;
730 }
731 return sapi_cast_i64_to_i32(v, out_value);
732}
sapi_status_t sapi_buffer_clear(sapi_buffer_t *buf)
Resets length to 0; capacity and data are unchanged.
Definition sapi_buffer.c:21
sapi_status_t sapi_buffer_init(sapi_buffer_t *buf, void *storage, size_t capacity)
Binds a buffer view to caller-owned storage. Initial length is 0.
Definition sapi_buffer.c:9
sapi_status_t sapi_buffer_copy_in(sapi_buffer_t *buf, const void *src, size_t src_len)
Bounds-checked copy of external data into the buffer; sets length.
Definition sapi_buffer.c:45
bool sapi_buffer_is_valid(const sapi_buffer_t *buf)
Defensive validity check: non-null data and length <= capacity.
sapi_status_t sapi_cast_u64_to_u32(uint64_t in, uint32_t *out)
Checked cast from uint64_t to uint32_t.
Definition sapi_cast.c:937
sapi_status_t sapi_cast_i64_to_i32(int64_t in, int32_t *out)
Checked cast from int64_t to int32_t.
Definition sapi_cast.c:423
sapi_status_t sapi_cast_i32_to_i64(int32_t in, int64_t *out)
Checked cast from int32_t to int64_t.
Definition sapi_cast.c:286
sapi_status_t sapi_cast_u32_to_u64(uint32_t in, uint64_t *out)
Checked cast from uint32_t to uint64_t.
Definition sapi_cast.c:818
sapi_status_t
Common result/status codes.
Definition sapi_status.h:27
@ SAPI_STATUS_INVALID_PARAM
Definition sapi_status.h:29
@ SAPI_STATUS_VALUE_OUT_OF_RANGE
Definition sapi_status.h:39
@ SAPI_STATUS_RESOURCE_EXHAUSTED
Definition sapi_status.h:33
@ SAPI_STATUS_OK
Definition sapi_status.h:28
sapi_status_t sapi_string_from_i32(sapi_string_t *dest, int32_t value)
Bounded base-10 itoa equivalent for int32_t. Replaces dest's content.
sapi_status_t sapi_string_from_u64(sapi_string_t *dest, uint64_t value)
Bounded base-10 itoa equivalent for uint64_t. Replaces dest's content.
sapi_status_t sapi_string_split_next(const sapi_string_t *str, char delimiter, size_t *io_cursor, sapi_const_buffer_t *out_token, bool *out_has_token)
Reentrant, bounded string splitting - unlike strtok(), all state is caller-owned via io_cursor,...
sapi_status_t sapi_string_init(sapi_string_t *str, char *storage, size_t capacity)
Binds a string to caller-owned storage. Initial length is 0 (empty string).
Definition sapi_string.c:10
sapi_status_t sapi_string_from_i64(sapi_string_t *dest, int64_t value)
Bounded base-10 itoa equivalent for int64_t. Replaces dest's content.
sapi_status_t sapi_string_c_str(sapi_string_t *str, const char **out_cstr)
Ensures a NUL terminator is present within capacity (without incrementing length) and returns a point...
Definition sapi_string.c:37
sapi_status_t sapi_string_append_hex_u32(sapi_string_t *dest, uint32_t value, uint8_t min_digits)
Bounded append of a uint32_t formatted as lowercase hexadecimal (no "0x" prefix - the caller prepends...
sapi_status_t sapi_string_append_i64(sapi_string_t *dest, int64_t value)
Bounded base-10 append of an int64_t; a leading '-' is emitted for negative values....
size_t sapi_string_length(const sapi_string_t *str)
Returns a string's current length (not counting a NUL terminator).
Definition sapi_string.c:28
sapi_status_t sapi_string_concat(sapi_string_t *dest, const char *src)
Bounded strcat equivalent. Never calls strlen(src) - scans for a NUL only up to dest's remaining capa...
sapi_status_t sapi_string_clear(sapi_string_t *str)
Resets a string to empty. Capacity and storage are unchanged.
Definition sapi_string.c:19
sapi_status_t sapi_string_append_u32(sapi_string_t *dest, uint32_t value)
Bounded base-10 append of a uint32_t to dest's existing content (unlike sapi_string_from_u32(),...
sapi_status_t sapi_string_copy(sapi_string_t *dest, const char *src)
Bounded strcpy equivalent. Never calls strlen(src) - scans for a NUL only up to dest's capacity (REQ-...
Definition sapi_string.c:68
sapi_status_t sapi_string_find_char(const sapi_string_t *str, char c, bool *out_found, size_t *out_index)
Bounded strchr equivalent. "Not found" is a normal outcome, not an error - see out_found.
sapi_status_t sapi_string_to_i64(const sapi_string_t *str, int64_t *out_value)
As sapi_string_to_i32(), for int64_t.
sapi_status_t sapi_string_append_u64(sapi_string_t *dest, uint64_t value)
Bounded base-10 append of a uint64_t. See sapi_string_append_u32().
sapi_status_t sapi_string_find_substr(const sapi_string_t *haystack, const sapi_string_t *needle, bool *out_found, size_t *out_index)
Bounded strstr equivalent. "Not found" is a normal outcome, not an error - see out_found.
sapi_status_t sapi_string_copy_n(sapi_string_t *dest, const char *src, size_t src_len)
Bounded copy of an exact-length, not-necessarily-NUL-terminated source (e.g. a length-prefixed field)...
Definition sapi_string.c:59
sapi_status_t sapi_string_compare(const sapi_string_t *a, const sapi_string_t *b, int32_t *out_cmp)
Bounded strcmp equivalent. Compares up to the shorter string's length, then by length if that prefix ...
sapi_status_t sapi_string_to_u32(const sapi_string_t *str, uint32_t *out_value)
Bounded base-10 atoi equivalent for uint32_t.
sapi_status_t sapi_string_append_i32(sapi_string_t *dest, int32_t value)
Bounded base-10 append of an int32_t; a leading '-' is emitted for negative values....
sapi_status_t sapi_string_to_i32(const sapi_string_t *str, int32_t *out_value)
As sapi_string_to_u32(), for int32_t; a leading '-' is accepted.
sapi_status_t sapi_string_from_u32(sapi_string_t *dest, uint32_t value)
Bounded base-10 itoa equivalent for uint32_t. Replaces dest's content.
sapi_status_t sapi_string_to_u64(const sapi_string_t *str, uint64_t *out_value)
As sapi_string_to_u32(), for uint64_t.
Checked integer casting between all fixed-width types and size_t (ADR-003). Every conversion in the c...
static sapi_status_t sapi_string_append_bytes(sapi_string_t *dest, const char *src, size_t n)
Appends exactly n bytes of src to dest, no NUL scan, bounds-checked against dest's remaining capacity...
static void sapi_string_format_u64_digits(uint64_t value, char *out, size_t *out_len)
Fills out[0..*out_len) with value's base-10 digits, most-significant first, no leading zeros (except ...
Bounded, checked string manipulation (ADR-006). Replaces strcpy/strcat/sprintf/atoi/strtok-style unbo...
size_t capacity
Definition sapi_buffer.h:38
Read-only view of a buffer's currently valid bytes. Grants no write access to the underlying storage.
Definition sapi_buffer.h:47
const void * data
Definition sapi_buffer.h:48
Bounded string: buf.length is the string length, not counting a NUL.
Definition sapi_string.h:39
sapi_buffer_t buf
Definition sapi_string.h:40