/* * Copyright (C) 2015 Kaspar Schleiser * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup sys_fmt * @{ * * @file * @brief String formatting library implementation * * @author Kaspar Schleiser * * @} */ #include #include #include #include #include #include #include #include #include "compiler_hints.h" #include "container.h" #include "fmt.h" #include "modules.h" extern ssize_t stdio_write(const void* buffer, size_t len); NONSTRING static const char _hex_chars[16] = "0123456789ABCDEF"; static const uint32_t _tenmap[] = { 0, 10LU, 100LU, 1000LU, 10000LU, 100000LU, 1000000LU, 10000000LU, }; #define TENMAP_SIZE ARRAY_SIZE(_tenmap) static inline char _to_lower(char c) { return 'a' + (c - 'A'); } int fmt_is_number(const char *str) { if (!str || !*str) { return 0; } for (; *str; str++) { if (!fmt_is_digit(*str)) { return 0; } } return 1; } size_t fmt_byte_hex(char *out, uint8_t byte) { if (out) { *out++ = _hex_chars[byte >> 4]; *out = _hex_chars[byte & 0x0F]; } return 2; } size_t fmt_bytes_hex(char *out, const uint8_t *ptr, size_t n) { size_t len = n * 2; if (out) { while (n--) { out += fmt_byte_hex(out, *ptr++); } } return len; } size_t fmt_bytes_hex_reverse(char *out, const uint8_t *ptr, size_t n) { size_t len = n * 2; if (out) { while (n--) { out += fmt_byte_hex(out, ptr[n]); } } return len; } size_t fmt_strlen(const char *str) { const char *tmp = str; while (*tmp) { tmp++; } return (tmp - str); } size_t fmt_strnlen(const char *str, size_t maxlen) { const char *tmp = str; while (*tmp && maxlen--) { tmp++; } return (tmp - str); } size_t fmt_str(char *out, const char *str) { int len = 0; if (!out) { len = fmt_strlen(str); } else { char c; while ((c = *str++)) { *out++ = c; len++; } } return len; } static uint8_t _byte_mod25(uint8_t x) { for (unsigned divisor = 200; divisor >= 25; divisor >>= 1) { if (x >= divisor) { x -= divisor; } } return x; } static uint8_t _hex_nib(uint8_t nib) { return _byte_mod25((nib & 0x1f) + 9); } uint8_t fmt_hex_byte(const char *hex) { return (_hex_nib(hex[0]) << 4) | _hex_nib(hex[1]); } size_t fmt_hex_bytes(uint8_t *out, const char *hex) { size_t len = fmt_strlen(hex); if (len & 1) { return 0; } size_t final_len = len >> 1; if (out == NULL) { return final_len; } for (size_t i = 0, j = 0; j < final_len; i += 2, j++) { out[j] = fmt_hex_byte(hex + i); } return final_len; } size_t fmt_u16_hex(char *out, uint16_t val) { return fmt_bytes_hex_reverse(out, (uint8_t *)&val, 2); } size_t fmt_u32_hex(char *out, uint32_t val) { return fmt_bytes_hex_reverse(out, (uint8_t *)&val, 4); } size_t fmt_u64_hex(char *out, uint64_t val) { return fmt_bytes_hex_reverse(out, (uint8_t *)&val, 8); } size_t fmt_u64_dec(char *out, uint64_t val) { uint32_t d[5]; uint32_t q; size_t len = 0; d[0] = val & 0xFFFF; d[1] = (val >> 16) & 0xFFFF; d[2] = (val >> 32) & 0xFFFF; d[3] = (val >> 48) & 0xFFFF; d[0] = 656 * d[3] + 7296 * d[2] + 5536 * d[1] + d[0]; q = d[0] / 10000; d[0] = d[0] % 10000; d[1] = q + 7671 * d[3] + 9496 * d[2] + 6 * d[1]; q = d[1] / 10000; d[1] = d[1] % 10000; d[2] = q + 4749 * d[3] + 42 * d[2]; q = d[2] / 10000; d[2] = d[2] % 10000; d[3] = q + 281 * d[3]; q = d[3] / 10000; d[3] = d[3] % 10000; d[4] = q; int first = 4; while (!d[first] && first) { first--; } len = fmt_u32_dec(out, d[first]); int total_len = len + (first * 4); if (out) { out += len; memset(out, '0', total_len - len); while (first) { first--; if (d[first]) { size_t tmp = fmt_u32_dec(NULL, d[first]); fmt_u32_dec(out + (4 - tmp), d[first]); } out += 4; } } return total_len; } size_t fmt_u32_dec(char *out, uint32_t val) { size_t len = 1; /* count needed characters */ /* avoid multiply overflow: uint32_t max len = 10 digits */ if (val >= 1000000000ul) { len = 10; } else { for (uint32_t tmp = 10; tmp <= val; len++) { tmp *= 10; } } if (out) { char *ptr = out + len; do { *--ptr = (val % 10) + '0'; } while ((val /= 10)); } return len; } size_t fmt_u16_dec(char *out, uint16_t val) { return fmt_u32_dec(out, val); } size_t fmt_s64_dec(char *out, int64_t val) { unsigned negative = (val < 0); uint64_t sval; if (negative) { if (out) { *out++ = '-'; } sval = -(uint64_t)(val); } else { sval = +(uint64_t)(val); } return fmt_u64_dec(out, sval) + negative; } size_t fmt_s32_dec(char *out, int32_t val) { unsigned negative = (val < 0); uint32_t sval; if (negative) { if (out) { *out++ = '-'; } sval = -((uint32_t)(val)); } else { sval = +((uint32_t)(val)); } return fmt_u32_dec(out, sval) + negative; } size_t fmt_s16_dec(char *out, int16_t val) { return fmt_s32_dec(out, val); } size_t fmt_s16_dfp(char *out, int16_t val, int scale) { return fmt_s32_dfp(out, val, scale); } size_t fmt_s32_dfp(char *out, int32_t val, int scale) { unsigned pos = 0; if (scale == 0) { pos = fmt_s32_dec(out, val); } else if (scale > 0) { pos = fmt_s32_dec(out, val); if (out) { memset(&out[pos], '0', scale); } pos += scale; } else { scale = -scale; char buf[10]; /* "2147483648" */ int negative = val < 0; uint32_t uval = negative ? -val : val; int len = fmt_u32_dec(buf, uval); if (negative) { if (out) { out[pos] = '-'; } pos++; } if (len <= scale) { int zeroes = scale - len + 1; if (out) { memset(&out[pos], '0', zeroes); } pos += zeroes; } if (out) { memcpy(&out[pos], buf, len); } pos += len; if (out) { unsigned dot_pos = pos - scale; for (unsigned i = pos; i >= dot_pos; i--) { out[i] = out[i - 1]; } out[dot_pos] = '.'; } pos += 1; } return pos; } /* this is very probably not the most efficient implementation, as it at least * pulls in floating point math. But it works, and it's always nice to have * low hanging fruits when optimizing. (Kaspar) */ size_t fmt_float(char *out, float f, unsigned precision) { assert(precision < TENMAP_SIZE); unsigned negative = (f < 0); uint32_t integer; if (negative) { f = -f; } integer = (uint32_t)f; f -= integer; uint32_t fraction = f * _tenmap[precision]; if (negative && out) { *out++ = '-'; } size_t res = fmt_u32_dec(out, integer); if (precision && fraction) { if (out) { out += res; *out++ = '.'; size_t tmp = fmt_u32_dec(out, fraction); fmt_lpad(out, tmp, precision, '0'); } res += (1 + precision); } res += negative; return res; } size_t fmt_lpad(char *out, size_t in_len, size_t pad_len, char pad_char) { if (in_len >= pad_len) { return in_len; } if (out) { size_t n = pad_len - in_len; if (FMT_USE_MEMMOVE) { memmove(out + n, out, in_len); memset(out, pad_char, n); } else { char *pos = out + pad_len - 1; out += in_len - 1; while (in_len--) { *pos-- = *out--; } while (n--) { *pos-- = pad_char; } } } return pad_len; } size_t fmt_char(char *out, char c) { if (out) { *out = c; } return 1; } size_t fmt_to_lower(char *out, const char *str) { size_t len = 0; while (str && *str) { if (fmt_is_upper(*str)) { if (out) { *out++ = _to_lower(*str); } } else if (out) { *out++ = *str; } str++; len++; } return len; } int fmt_time_tm_iso8601(char out[20], const struct tm *tm, char separator) { assert(out); assert(tm); /* The lowest year allowed in ISO 8601 is 0000 (year zero), which represents 1 BCE */ if ((tm->tm_year < -1900 || tm->tm_year > 9999 - 1900) || (tm->tm_mon < -1 || tm->tm_mon > 99 - 1) || (tm->tm_mday < 0 || tm->tm_mday > 99) || (tm->tm_hour < 0 || tm->tm_hour > 99) || (tm->tm_min < 0 || tm->tm_min > 99) || (tm->tm_sec < 0 || tm->tm_sec > 99)) { return -EINVAL; } char *pos = out; int len; len = fmt_u16_dec(pos, tm->tm_year + 1900); fmt_lpad(pos, len, 4, '0'); pos += 4; *pos++ = '-'; len = fmt_u16_dec(pos, tm->tm_mon + 1); fmt_lpad(pos, len, 2, '0'); pos += 2; *pos++ = '-'; len = fmt_u16_dec(pos, tm->tm_mday); fmt_lpad(pos, len, 2, '0'); pos += 2; *pos++ = separator; len = fmt_u16_dec(pos, tm->tm_hour); fmt_lpad(pos, len, 2, '0'); pos += 2; *pos++ = ':'; len = fmt_u16_dec(pos, tm->tm_min); fmt_lpad(pos, len, 2, '0'); pos += 2; *pos++ = ':'; len = fmt_u16_dec(pos, tm->tm_sec); fmt_lpad(pos, len, 2, '0'); pos += 2; *pos = '\0'; return pos - out; } uint32_t scn_u32_dec(const char *str, size_t n) { uint32_t res = 0; while (n--) { unsigned c = *str++; unsigned d = c - (unsigned)'0'; if ( !( '0'<= c && c <= '9') ) { break; } res *= 10U; res += d; } return res; } uint32_t scn_u32_hex(const char *str, size_t n) { uint32_t res = 0; while (n--) { unsigned c = *str++; unsigned d; if (('0'<= c) && (c <= '9')){ d = c - (unsigned)'0'; } else if (('A' <= c) && (c <= 'F')) { d = c - (unsigned)'A' + 0xaU; } else if (('a' <= c) && (c <= 'f')) { d = c - (unsigned)'a' + 0xaU; } else { break; } res <<= 4U; res |= d; } return res; } static bool _get_nibble(uint8_t *dest, char _c) { uint8_t c = _c; if (((uint8_t)'0' <= c) && (c <= (uint8_t)'9')) { *dest = c - (uint8_t)'0'; return true; } if (((uint8_t)'a' <= c) && (c <= (uint8_t)'f')) { *dest = c - (uint8_t)'a' + 10; return true; } if (((uint8_t)'A' <= c) && (c <= (uint8_t)'F')) { *dest = c - (uint8_t)'A' + 10; return true; } return false; } ssize_t scn_buf_hex(void *_dest, size_t dest_len, const char *hex, size_t hex_len) { uint8_t *dest = _dest; assert((dest != NULL) || (dest_len == 0)); assert((hex != NULL) || (hex_len == 0)); if (hex_len & 1) { /* we need to chars per every byte, so odd inputs don't work */ return -EINVAL; } size_t len = hex_len >> 1; if (len > dest_len) { return -EOVERFLOW; } for (size_t pos = 0; pos < len; pos++) { uint8_t high, low; if (!_get_nibble(&high, hex[pos << 1])) { return -EINVAL; } if (!_get_nibble(&low, hex[(pos << 1) + 1])) { return -EINVAL; } dest[pos] = (high << 4) | low; } return len; } int scn_time_tm_iso8601(struct tm *tm, const char *str, char separator) { assert(tm); assert(str); memset(tm, 0, sizeof(*tm)); tm->tm_isdst = -1; /* undefined */ if (!fmt_is_digit(str[0]) || !fmt_is_digit(str[1]) || !fmt_is_digit(str[2]) || !fmt_is_digit(str[3]) || str[4] != '-' || !fmt_is_digit(str[5]) || !fmt_is_digit(str[6]) || str[7] != '-' || !fmt_is_digit(str[8]) || !fmt_is_digit(str[9])) { return -EINVAL; } uint32_t num = scn_u32_dec(str, 4); tm->tm_year = num - 1900; num = scn_u32_dec(str + 5, 2); tm->tm_mon = num - 1; num = scn_u32_dec(str + 8, 2); tm->tm_mday = num; if (str[10] == '\0') { /* no time, just date */ return 10; } if (str[10] != separator) { return -EBADF; } if (!fmt_is_digit(str[11]) || !fmt_is_digit(str[12]) || str[13] != ':' || !fmt_is_digit(str[14]) || !fmt_is_digit(str[15]) || str[16] != ':' || !fmt_is_digit(str[17]) || !fmt_is_digit(str[18])) { return -EINVAL; } num = scn_u32_dec(str + 11, 2); tm->tm_hour = num; num = scn_u32_dec(str + 14, 2); tm->tm_min = num; num = scn_u32_dec(str + 17, 2); tm->tm_sec = num; return 19; } /* native gets special treatment as native's stdio code is ... special. * And when not building for RIOT, there's no `stdio_write()`. * In those cases, just defer to `printf()`. */ #if IS_USED(MODULE_STDIO_NATIVE) || !defined(RIOT_VERSION) void print(const char *s, size_t n) { printf("%.*s", (int)n, s); } #else void print(const char *s, size_t n) { /* flush the libc's output buffer so output is not intermingled. */ fflush(stdout); stdio_write(s, n); } #endif void print_u32_dec(uint32_t val) { char buf[10]; /* "4294967295" */ size_t len = fmt_u32_dec(buf, val); print(buf, len); } void print_s32_dec(int32_t val) { char buf[11]; /* "-2147483648" */ size_t len = fmt_s32_dec(buf, val); print(buf, len); } void print_byte_hex(uint8_t byte) { char buf[2]; fmt_byte_hex(buf, byte); print(buf, sizeof(buf)); } void print_bytes_hex(const void *bytes, size_t num) { const uint8_t *b = bytes; while (num--) { print_byte_hex(*b++); } } void print_u32_hex(uint32_t val) { char buf[8]; fmt_u32_hex(buf, val); print(buf, sizeof(buf)); } void print_u64_hex(uint64_t val) { print_u32_hex(val >> 32); print_u32_hex(val); } void print_u64_dec(uint64_t val) { char buf[20]; /* "18446744073709551615" */ size_t len = fmt_u64_dec(buf, val); print(buf, len); } void print_s64_dec(uint64_t val) { char buf[20]; /* "-9223372036854775808" */ size_t len = fmt_s64_dec(buf, val); print(buf, len); } void print_float(float f, unsigned precision) { char buf[19]; size_t len = fmt_float(buf, f, precision); print(buf, len); } void print_str(const char *str) { print(str, fmt_strlen(str)); }