Coverage Report

Created: 2025-07-01 07:00

/src/open62541/deps/mp_printf.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @author (c) Julius Pfrommer
3
 *             2023, Fraunhofer IOSB, Germany
4
 * @author (c) Eyal Rozenberg <eyalroz1@gmx.com>
5
 *             2021-2023, Haifa, Palestine/Israel
6
 * @author (c) Marco Paland (info@paland.com)
7
 *             2014-2019, PALANDesign Hannover, Germany
8
 *
9
 * @note Others have made smaller contributions to this file: see the
10
 * contributors page at https://github.com/eyalroz/printf/graphs/contributors
11
 * or ask one of the authors. The original code for exponential specifiers was
12
 * contributed by Martijn Jasperse <m.jasperse@gmail.com>.
13
 *
14
 * @brief Small stand-alone implementation of the printf family of functions
15
 * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with
16
 * limited resources.
17
 *
18
 * @note the implementations are thread-safe; re-entrant; use no functions from
19
 * the standard library; and do not dynamically allocate any memory.
20
 *
21
 * @license The MIT License (MIT)
22
 *
23
 * Permission is hereby granted, free of charge, to any person obtaining a copy
24
 * of this software and associated documentation files (the "Software"), to deal
25
 * in the Software without restriction, including without limitation the rights
26
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27
 * copies of the Software, and to permit persons to whom the Software is
28
 * furnished to do so, subject to the following conditions:
29
 *
30
 * The above copyright notice and this permission notice shall be included in
31
 * all copies or substantial portions of the Software.
32
 *
33
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39
 * THE SOFTWARE.
40
 */
41
42
#include "mp_printf.h"
43
#include "dtoa.h"
44
45
#include <open62541/types.h>
46
47
#include <stdint.h>
48
#include <stdbool.h>
49
50
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
51
// numeric number including padded zeros (dynamically created on stack)
52
0
#define PRINTF_INTEGER_BUFFER_SIZE    32
53
54
// size of the fixed (on-stack) buffer for printing individual decimal numbers.
55
// this must be big enough to hold one converted floating-point value including
56
// padded zeros.
57
#define PRINTF_DECIMAL_BUFFER_SIZE    32
58
59
// Default precision for the floating point conversion specifiers (the C
60
// standard sets this at 6)
61
#define PRINTF_DEFAULT_FLOAT_PRECISION  6
62
63
// internal flag definitions
64
0
#define FLAGS_ZEROPAD   (1U <<  0U)
65
0
#define FLAGS_LEFT      (1U <<  1U)
66
0
#define FLAGS_PLUS      (1U <<  2U)
67
0
#define FLAGS_SPACE     (1U <<  3U)
68
0
#define FLAGS_HASH      (1U <<  4U)
69
0
#define FLAGS_UPPERCASE (1U <<  5U)
70
0
#define FLAGS_CHAR      (1U <<  6U)
71
0
#define FLAGS_SHORT     (1U <<  7U)
72
  // Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS
73
0
#define FLAGS_LONG      (1U <<  9U)
74
0
#define FLAGS_LONG_LONG (1U << 10U)
75
0
#define FLAGS_PRECISION (1U << 11U)
76
#define FLAGS_ADAPT_EXP (1U << 12U)
77
0
#define FLAGS_POINTER   (1U << 13U)
78
  // Note: Similar, but not identical, effect as FLAGS_HASH
79
0
#define FLAGS_SIGNED    (1U << 14U)
80
81
0
#define BASE_BINARY    2
82
0
#define BASE_OCTAL     8
83
0
#define BASE_DECIMAL  10
84
0
#define BASE_HEX      16
85
86
typedef unsigned int printf_flags_t;
87
typedef uint8_t numeric_base_t;
88
typedef unsigned long long printf_unsigned_value_t;
89
typedef long long printf_signed_value_t;
90
91
// Note in particular the behavior here on LONG_MIN or LLONG_MIN; it is valid
92
// and well-defined, but if you're not careful you can easily trigger undefined
93
// behavior with -LONG_MIN or -LLONG_MIN
94
#define ABS_FOR_PRINTING(_x)                                            \
95
0
    ((printf_unsigned_value_t)((_x) > 0 ? (_x) : -((printf_signed_value_t)_x)))
96
97
// internal secure strlen @return The length of the string (excluding the
98
// terminating 0) limited by 'maxsize' @note strlen uses size_t, but wes only
99
// use this function with size_t variables - hence the signature.
100
static size_t
101
0
strnlen_s_(const char *str, size_t maxsize) {
102
0
    for(size_t i = 0; i < maxsize; i++) {
103
0
        if(!str[i])
104
0
            return i;
105
0
    }
106
0
    return maxsize;
107
0
}
108
109
// internal test if char is a digit (0-9)
110
// @return true if char is a digit
111
0
static bool is_digit_(char ch) { return (ch >= '0') && (ch <= '9'); }
112
113
// internal ASCII string to size_t conversion
114
static size_t
115
0
atou_(const char **str) {
116
0
    size_t i = 0U;
117
0
    while(is_digit_(**str)) {
118
0
        i = i * 10U + (size_t)(*((*str)++) - '0');
119
0
    }
120
0
    return i;
121
0
}
122
123
// Output buffer
124
typedef struct {
125
    char *buffer;
126
    size_t pos;
127
    size_t max_chars;
128
} output_t;
129
130
static void
131
0
putchar_(output_t *out, char c) {
132
0
    size_t write_pos = out->pos++;
133
    // We're _always_ increasing pos, so as to count how may characters
134
    // _would_ have been written if not for the max_chars limitation
135
0
    if(write_pos >= out->max_chars)
136
0
        return;
137
    // it must be the case that out->buffer != NULL , due to the constraint
138
    // on output_t ; and note we're relying on write_pos being non-negative.
139
0
    out->buffer[write_pos] = c;
140
0
}
141
142
static void
143
0
out_(output_t *out, const char *buf, size_t len) {
144
0
    if(out->pos < out->max_chars) {
145
0
        size_t write_len = len;
146
0
        if(out->pos + len > out->max_chars)
147
0
            write_len = out->max_chars - out->pos;
148
0
        for(size_t i = 0; i < write_len; i++)
149
0
            out->buffer[out->pos + i] = buf[i];
150
0
    }
151
0
    out->pos += len; // Always increase pos by len
152
0
}
153
154
// output the specified string in reverse, taking care of any zero-padding
155
static void
156
out_rev_(output_t *output, const char *buf, size_t len, size_t width,
157
0
         printf_flags_t flags) {
158
0
    const size_t start_pos = output->pos;
159
160
    // pad spaces up to given width
161
0
    if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
162
0
        for(size_t i = len; i < width; i++) {
163
0
            putchar_(output, ' ');
164
0
        }
165
0
    }
166
167
    // reverse string
168
0
    while(len) {
169
0
        putchar_(output, buf[--len]);
170
0
    }
171
172
    // append pad spaces up to given width
173
0
    if(flags & FLAGS_LEFT) {
174
0
        while(output->pos - start_pos < width) {
175
0
            putchar_(output, ' ');
176
0
        }
177
0
    }
178
0
}
179
180
// Invoked by print_integer after the actual number has been printed, performing
181
// necessary work on the number's prefix (as the number is initially printed in
182
// reverse order)
183
static void
184
print_integer_finalization(output_t *output, char *buf, size_t len, bool negative,
185
                           numeric_base_t base, size_t precision, size_t width,
186
0
                           printf_flags_t flags) {
187
0
    size_t unpadded_len = len;
188
189
    // pad with leading zeros
190
0
    if(!(flags & FLAGS_LEFT)) {
191
0
        if(width && (flags & FLAGS_ZEROPAD) &&
192
0
           (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
193
0
            width--;
194
0
        }
195
0
        while((flags & FLAGS_ZEROPAD) && (len < width) &&
196
0
              (len < PRINTF_INTEGER_BUFFER_SIZE)) {
197
0
            buf[len++] = '0';
198
0
        }
199
0
    }
200
201
0
    while((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
202
0
        buf[len++] = '0';
203
0
    }
204
205
0
    if(base == BASE_OCTAL && (len > unpadded_len)) {
206
        // Since we've written some zeros, we've satisfied the alternative format
207
        // leading space requirement
208
0
        flags &= ~FLAGS_HASH;
209
0
    }
210
211
    // handle hash
212
0
    if(flags & (FLAGS_HASH | FLAGS_POINTER)) {
213
0
        if(!(flags & FLAGS_PRECISION) && len && ((len == precision) || (len == width))) {
214
            // Let's take back some padding digits to fit in what will eventually be
215
            // the format-specific prefix
216
0
            if(unpadded_len < len) {
217
0
                len--;  // This should suffice for BASE_OCTAL
218
0
            }
219
0
            if(len && (base == BASE_HEX || base == BASE_BINARY) && (unpadded_len < len)) {
220
0
                len--;  // ... and an extra one for 0x or 0b
221
0
            }
222
0
        }
223
0
        if((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) &&
224
0
           (len < PRINTF_INTEGER_BUFFER_SIZE)) {
225
0
            buf[len++] = 'x';
226
0
        } else if((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) &&
227
0
                  (len < PRINTF_INTEGER_BUFFER_SIZE)) {
228
0
            buf[len++] = 'X';
229
0
        } else if((base == BASE_BINARY) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
230
0
            buf[len++] = 'b';
231
0
        }
232
0
        if(len < PRINTF_INTEGER_BUFFER_SIZE) {
233
0
            buf[len++] = '0';
234
0
        }
235
0
    }
236
237
0
    if(len < PRINTF_INTEGER_BUFFER_SIZE) {
238
0
        if(negative) {
239
0
            buf[len++] = '-';
240
0
        } else if(flags & FLAGS_PLUS) {
241
0
            buf[len++] = '+';  // ignore the space if the '+' exists
242
0
        } else if(flags & FLAGS_SPACE) {
243
0
            buf[len++] = ' ';
244
0
        }
245
0
    }
246
247
0
    out_rev_(output, buf, len, width, flags);
248
0
}
249
250
// An internal itoa-like function
251
static void
252
print_integer(output_t *output, printf_unsigned_value_t value, bool negative,
253
0
              numeric_base_t base, size_t precision, size_t width, printf_flags_t flags) {
254
0
    char buf[PRINTF_INTEGER_BUFFER_SIZE];
255
0
    size_t len = 0U;
256
257
0
    if(!value) {
258
0
        if(!(flags & FLAGS_PRECISION)) {
259
0
            buf[len++] = '0';
260
0
            flags &= ~FLAGS_HASH;
261
            // We drop this flag this since either the alternative and regular modes
262
            // of the specifier don't differ on 0 values, or (in the case of octal)
263
            // we've already provided the special handling for this mode.
264
0
        } else if(base == BASE_HEX) {
265
0
            flags &= ~FLAGS_HASH;
266
            // We drop this flag this since either the alternative and regular modes
267
            // of the specifier don't differ on 0 values
268
0
        }
269
0
    } else {
270
0
        do {
271
0
            const char digit = (char)(value % base);
272
0
            buf[len++] =
273
0
                (char)(digit < 10 ? '0' + digit
274
0
                                  : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10);
275
0
            value /= base;
276
0
        } while(value && (len < PRINTF_INTEGER_BUFFER_SIZE));
277
0
    }
278
279
0
    print_integer_finalization(output, buf, len, negative, base, precision, width, flags);
280
0
}
281
282
static void
283
print_floating_point(output_t *output, double value, size_t precision,
284
0
                     size_t width, printf_flags_t flags) {
285
0
    if((flags & FLAGS_PLUS) && value > 0.0)
286
0
        putchar_(output, '+');
287
288
    // set default precision, if not set explicitly
289
    //if(!(flags & FLAGS_PRECISION) || precision > PRINTF_DECIMAL_BUFFER_SIZE - 5)
290
    //    precision = PRINTF_DEFAULT_FLOAT_PRECISION;
291
292
0
    char buf[PRINTF_DECIMAL_BUFFER_SIZE];
293
0
    unsigned len = dtoa(value, buf); // Fill the buffer (TODO: Consider precision)
294
0
    out_(output, buf, len); // Print the buffer
295
0
}
296
297
// Advances the format pointer past the flags, and returns the parsed flags
298
// due to the characters passed
299
static printf_flags_t
300
0
parse_flags(const char **format) {
301
0
    printf_flags_t flags = 0U;
302
0
    do {
303
0
        switch(**format) {
304
0
        case '0': flags |= FLAGS_ZEROPAD; break;
305
0
        case '-': flags |= FLAGS_LEFT; break;
306
0
        case '+': flags |= FLAGS_PLUS; break;
307
0
        case ' ': flags |= FLAGS_SPACE; break;
308
0
        case '#': flags |= FLAGS_HASH; break;
309
0
        default: return flags;
310
0
        }
311
0
        (*format)++;
312
0
    } while(true);
313
0
}
314
315
#define ADVANCE_IN_FORMAT_STRING(cptr_)                                 \
316
0
    do {                                                                \
317
0
        (cptr_)++;                                                      \
318
0
        if(!*(cptr_))                                                   \
319
0
            return;                                                     \
320
0
    } while(0)
321
322
static void
323
0
format_string_loop(output_t *output, const char *format, va_list args) {
324
0
    while(*format) {
325
0
        if(*format != '%') {
326
            // A regular content character
327
0
            putchar_(output, *format);
328
0
            format++;
329
0
            continue;
330
0
        }
331
        // We're parsing a format specifier: %[flags][width][.precision][length]
332
0
        ADVANCE_IN_FORMAT_STRING(format);
333
334
0
        printf_flags_t flags = parse_flags(&format);
335
336
        // evaluate width field
337
0
        size_t width = 0U;
338
0
        if(is_digit_(*format)) {
339
0
            width = atou_(&format);
340
0
        } else if(*format == '*') {
341
0
            const int w = va_arg(args, int);
342
0
            if(w < 0) {
343
0
                flags |= FLAGS_LEFT;  // reverse padding
344
0
                width = (size_t)-w;
345
0
            } else {
346
0
                width = (size_t)w;
347
0
            }
348
0
            ADVANCE_IN_FORMAT_STRING(format);
349
0
        }
350
351
        // evaluate precision field
352
0
        size_t precision = 0U;
353
0
        if(*format == '.') {
354
0
            flags |= FLAGS_PRECISION;
355
0
            ADVANCE_IN_FORMAT_STRING(format);
356
0
            if(is_digit_(*format)) {
357
0
                precision = atou_(&format);
358
0
            } else if(*format == '*') {
359
0
                const int precision_ = va_arg(args, int);
360
0
                precision = precision_ > 0 ? (size_t)precision_ : 0U;
361
0
                ADVANCE_IN_FORMAT_STRING(format);
362
0
            }
363
0
        }
364
365
        // evaluate length field
366
0
        switch(*format) {
367
0
            case 'l':
368
0
                flags |= FLAGS_LONG;
369
0
                ADVANCE_IN_FORMAT_STRING(format);
370
0
                if(*format == 'l') {
371
0
                    flags |= FLAGS_LONG_LONG;
372
0
                    ADVANCE_IN_FORMAT_STRING(format);
373
0
                }
374
0
                break;
375
0
            case 'h':
376
0
                flags |= FLAGS_SHORT;
377
0
                ADVANCE_IN_FORMAT_STRING(format);
378
0
                if(*format == 'h') {
379
0
                    flags |= FLAGS_CHAR;
380
0
                    ADVANCE_IN_FORMAT_STRING(format);
381
0
                }
382
0
                break;
383
0
            case 't':
384
0
                flags |=
385
0
                    (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
386
0
                ADVANCE_IN_FORMAT_STRING(format);
387
0
                break;
388
0
            case 'j':
389
0
                flags |=
390
0
                    (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
391
0
                ADVANCE_IN_FORMAT_STRING(format);
392
0
                break;
393
0
            case 'z':
394
0
                flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
395
0
                ADVANCE_IN_FORMAT_STRING(format);
396
0
                break;
397
0
            default:
398
0
                break;
399
0
        }
400
401
        // evaluate specifier
402
0
        switch(*format) {
403
0
            case 'd':
404
0
            case 'i':
405
0
            case 'u':
406
0
            case 'x':
407
0
            case 'X':
408
0
            case 'o':
409
0
            case 'b': {
410
0
                if(*format == 'd' || *format == 'i') {
411
0
                    flags |= FLAGS_SIGNED;
412
0
                }
413
414
0
                numeric_base_t base;
415
0
                if(*format == 'x' || *format == 'X') {
416
0
                    base = BASE_HEX;
417
0
                } else if(*format == 'o') {
418
0
                    base = BASE_OCTAL;
419
0
                } else if(*format == 'b') {
420
0
                    base = BASE_BINARY;
421
0
                } else {
422
0
                    base = BASE_DECIMAL;
423
0
                    flags &=
424
0
                        ~FLAGS_HASH;  // decimal integers have no alternative presentation
425
0
                }
426
427
0
                if(*format == 'X') {
428
0
                    flags |= FLAGS_UPPERCASE;
429
0
                }
430
431
0
                format++;
432
                // ignore '0' flag when precision is given
433
0
                if(flags & FLAGS_PRECISION) {
434
0
                    flags &= ~FLAGS_ZEROPAD;
435
0
                }
436
437
0
                if(flags & FLAGS_SIGNED) {
438
                    // A signed specifier: d, i or possibly I + bit size if enabled
439
0
                    if(flags & FLAGS_LONG_LONG) {
440
0
                        const long long value = va_arg(args, long long);
441
0
                        print_integer(output, ABS_FOR_PRINTING(value), value < 0, base,
442
0
                                      precision, width, flags);
443
0
                    } else if(flags & FLAGS_LONG) {
444
0
                        const long value = va_arg(args, long);
445
0
                        print_integer(output, ABS_FOR_PRINTING(value), value < 0, base,
446
0
                                      precision, width, flags);
447
0
                    } else {
448
                        // We never try to interpret the argument as something
449
                        // potentially-smaller than int, due to integer promotion rules:
450
                        // Even if the user passed a short int, short unsigned etc. -
451
                        // these will come in after promotion, as int's (or unsigned for
452
                        // the case of short unsigned when it has the same size as int)
453
0
                        const int value =
454
0
                            (flags & FLAGS_CHAR)    ? (signed char)va_arg(args, int)
455
0
                            : (flags & FLAGS_SHORT) ? (short int)va_arg(args, int)
456
0
                                                    : va_arg(args, int);
457
0
                        print_integer(output, ABS_FOR_PRINTING(value), value < 0, base,
458
0
                                      precision, width, flags);
459
0
                    }
460
0
                } else {
461
                    // An unsigned specifier: u, x, X, o, b
462
0
                    flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
463
464
0
                    if(flags & FLAGS_LONG_LONG) {
465
0
                        print_integer(output, (printf_unsigned_value_t)
466
0
                                      va_arg(args, unsigned long long),
467
0
                                      false, base, precision, width, flags);
468
0
                    } else if(flags & FLAGS_LONG) {
469
0
                        print_integer(output, (printf_unsigned_value_t)
470
0
                                      va_arg(args, unsigned long),
471
0
                                      false, base, precision, width, flags);
472
0
                    } else {
473
0
                        const unsigned int value = (flags & FLAGS_CHAR)
474
0
                            ? (unsigned char)va_arg(args, unsigned int)
475
0
                            : (flags & FLAGS_SHORT)
476
0
                            ? (unsigned short int)va_arg(args, unsigned int)
477
0
                            : va_arg(args, unsigned int);
478
0
                        print_integer(output, (printf_unsigned_value_t)value, false, base,
479
0
                                      precision, width, flags);
480
0
                    }
481
0
                }
482
0
                break;
483
0
            }
484
485
0
            case 'f':
486
0
            case 'F':
487
0
                if(*format == 'F')
488
0
                    flags |= FLAGS_UPPERCASE;
489
0
                print_floating_point(output, (double)va_arg(args, double),
490
0
                                     precision, width, flags);
491
0
                format++;
492
0
                break;
493
494
0
            case 'c': {
495
0
                size_t l = 1U;
496
                // pre padding
497
0
                if(!(flags & FLAGS_LEFT)) {
498
0
                    while(l++ < width) {
499
0
                        putchar_(output, ' ');
500
0
                    }
501
0
                }
502
                // char output
503
0
                putchar_(output, (char)va_arg(args, int));
504
                // post padding
505
0
                if(flags & FLAGS_LEFT) {
506
0
                    while(l++ < width) {
507
0
                        putchar_(output, ' ');
508
0
                    }
509
0
                }
510
0
                format++;
511
0
                break;
512
0
            }
513
514
0
            case 's': {
515
0
                const char *p = va_arg(args, char *);
516
0
                if(p == NULL) {
517
0
                    out_rev_(output, ")llun(", 6, width, flags);
518
0
                } else {
519
                    // string length
520
0
                    size_t l = strnlen_s_(p, precision ? precision : INT32_MAX);
521
0
                    if(flags & FLAGS_PRECISION) {
522
0
                        l = (l < precision ? l : precision);
523
0
                    }
524
525
                    // pre padding
526
0
                    if(!(flags & FLAGS_LEFT)) {
527
0
                        for(size_t i = 0; l + i < width; i++) {
528
0
                            putchar_(output, ' ');
529
0
                        }
530
0
                    }
531
532
                    // string output
533
0
                    out_(output, p, l);
534
535
                    // post padding
536
0
                    if(flags & FLAGS_LEFT) {
537
0
                        for(size_t i = 0; l + i < width; i++) {
538
0
                            putchar_(output, ' ');
539
0
                        }
540
0
                    }
541
0
                }
542
0
                format++;
543
0
                break;
544
0
            }
545
546
0
            case 'p': {
547
0
                width = sizeof(void *) * 2U + 2;  // 2 hex chars per byte + the "0x" prefix
548
0
                flags |= FLAGS_ZEROPAD | FLAGS_POINTER;
549
0
                uintptr_t value = (uintptr_t)va_arg(args, void *);
550
0
                (value == (uintptr_t)NULL)
551
0
                    ? out_rev_(output, ")lin(", 5, width, flags)
552
0
                    : print_integer(output, (printf_unsigned_value_t)value, false,
553
0
                                    BASE_HEX, precision, width, flags);
554
0
                format++;
555
0
                break;
556
0
            }
557
558
0
            case '%':
559
0
                putchar_(output, '%');
560
0
                format++;
561
0
                break;
562
563
0
            case 'S': {
564
0
                UA_String s = va_arg(args, UA_String);
565
0
                out_(output, (const char*)s.data, s.length);
566
0
                format++;
567
0
                break;
568
0
            }
569
570
0
            case 'N': {
571
0
                UA_String buf = UA_STRING_NULL;
572
0
                UA_NodeId id = va_arg(args, UA_NodeId);
573
0
                UA_StatusCode res = UA_NodeId_print(&id, &buf);
574
0
                if(res == UA_STATUSCODE_GOOD) {
575
0
                    out_(output, (const char*)buf.data, buf.length);
576
0
                    UA_String_clear(&buf);
577
0
                } else {
578
0
                    out_rev_(output, ")rre(", 5, width, flags);
579
0
                }
580
0
                format++;
581
0
                break;
582
0
            }
583
584
0
            default:
585
0
                putchar_(output, *format);
586
0
                format++;
587
0
                break;
588
0
        }
589
0
    }
590
0
}
591
592
int
593
0
mp_vsnprintf(char *s, size_t n, const char *format, va_list arg) {
594
    // Format the string
595
0
    output_t out = {s, 0, n};
596
0
    format_string_loop(&out, format, arg);
597
598
    // Write the string-terminating '\0' character
599
0
    if(n > 1) {
600
0
        size_t null_char_pos = out.pos < n ? out.pos : n - 1;
601
0
        out.buffer[null_char_pos] = '\0';
602
0
    }
603
604
    // Return written chars without terminating \0
605
0
    return (int)out.pos;
606
0
}
607
608
int
609
0
mp_snprintf(char *s, size_t n, const char *format, ...) {
610
0
    va_list args;
611
0
    va_start(args, format);
612
0
    const int ret = mp_vsnprintf(s, n, format, args);
613
0
    va_end(args);
614
0
    return ret;
615
0
}