/* * Copyright (C) 2020-2021 Tomasz Kramkowski * SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include "common.h" #include "ieee754b.h" #include "pack.h" #include "trace.h" static uintmax_t read_val(const unsigned char *buf, size_t size, enum pack_endian e) { uintmax_t val = 0; for (size_t i = 0; i < size; i++) { val |= (uintmax_t)(buf[i] & 0xff) << (e == PACK_ENDIAN_LITTLE ? i : size - i - 1) * 8; } return val; } static intmax_t minval(size_t s) { switch (s) { case 1: return INTMAX_C(-128); case 2: return INTMAX_C(-32768); case 4: return INTMAX_C(-2147483648); default: return -INTMAX_C(9223372036854775807) - 1; } } enum pack_status unpack(const void *buf_, size_t size, const char *fmt, ...) { enum pack_endian endianness = PACK_ENDIAN_BIG; const unsigned char *buf = buf_; enum pack_status ret = PACK_OK; size_t offset = 0; va_list ap; tr_call("unpack(%p, %" PRIuSIZE ", %s, ...)", (const void *)buf, size, fmt); va_start(ap, fmt); for (int i = 0; fmt[i] != '\0'; i++) { bool sign; size_t count = 1, s; enum pack_type t; union { #define T(type, sign, c_type, va_type) \ sign c_type *type; ITYPE_MACROS #undef T float *f; double *d; } arg; union { uintmax_t unsigned_; intmax_t signed_; float f; double d; } val; tr_debug("i: %d, fmt[i]: %c", i, fmt[i]); if (isdigit(fmt[i])) { unsigned long long c; char *end; errno = 0; c = strtoull(&fmt[i], &end, 10); if ((c == ULLONG_MAX && errno == ERANGE) || c > SIZE_MAX) SET_AND_GOTO(ret, PACK_FMTINVAL, stop); count = c; i += end - &fmt[i]; } else if (fmt[i] == '*') { count = va_arg(ap, size_t); i++; } sign = islower(fmt[i]); tr_debug("count: %" PRIuSIZE ", i: %d, fmt[i]: %c, sign: %ssigned", count, i, fmt[i], sign ? "" : "un"); switch (fmt[i]) { case '>': endianness = PACK_ENDIAN_BIG; continue; case '<': endianness = PACK_ENDIAN_LITTLE; continue; #define T(type, sign, c_type, va_type) \ case (char)PACK_TYPE_##type: \ arg.type = va_arg(ap, sign c_type *); \ break; ITYPE_MACROS #undef T case PACK_TYPE_FLOAT: arg.f = va_arg(ap, float *); break; case PACK_TYPE_DOUBLE: arg.d = va_arg(ap, double *); break; case PACK_TYPE_PADDING: break; default: SET_AND_GOTO(ret, PACK_FMTINVAL, stop); } t = fmt[i]; s = getsize(t); tr_debug("s: %" PRIuSIZE, s); if (s == (size_t)-1) SET_AND_GOTO(ret, PACK_FMTINVAL, stop); if (size - offset < s * count) SET_AND_GOTO(ret, PACK_TOOSMALL, stop); if (t == PACK_TYPE_PADDING) goto skip; for (size_t j = 0; j < count; j++) { val.unsigned_ = read_val(buf + offset + s * j, s, endianness); tr_debug("val.u: %" PRIuMAX ", at: %" PRIuSIZE, val.unsigned_, offset + s * j); if (t == PACK_TYPE_FLOAT) { float f = ieee754b32_deserialise(val.unsigned_); val.f = f; tr_debug("val.f: %f", val.f); } else if (t == PACK_TYPE_DOUBLE) { double d = ieee754b64_deserialise(val.unsigned_); val.d = d; tr_debug("val.d: %f", val.d); } else if (sign) { intmax_t vals; if (!(val.unsigned_ & (UINTMAX_C(1) << (s * 8 - 1)))) { vals = val.unsigned_; } else { vals = minval(s); vals += val.unsigned_ ^ (UINTMAX_C(1) << (s * 8 - 1)); } val.signed_ = vals; tr_debug("val.s: %" PRIdMAX, val.signed_); } switch (t) { #define T(type, sign, c_type, va_type) \ case (char)PACK_TYPE_##type: \ arg.type[j] = val.sign##_; \ break; ITYPE_MACROS #undef T case PACK_TYPE_FLOAT: arg.f[j] = val.f; break; case PACK_TYPE_DOUBLE: arg.d[j] = val.d; break; case PACK_TYPE_PADDING: break; } } skip: offset += s * count; } stop: va_end(ap); return ret; }