/* * 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" struct dest { enum pack_type type; union ptr { #define T(type, sign, c_type, va_type) sign c_type *type; ITYPE_MACROS #undef T float *f; double *d; } ptr; size_t count; }; 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; } } static void read_fields(struct dest dest, const void *src_, enum pack_endian endianness) { union { uintmax_t unsigned_; intmax_t signed_; float f; double d; } val; const unsigned char *src = src_; size_t s = getsize(dest.type); for (size_t i = 0; i < dest.count; i++) { val.unsigned_ = read_val(&src[s * i], s, endianness); tr_debug("val.u: %" PRIuMAX ", at: %" PRIuSIZE, val.unsigned_, s * i); if (dest.type == PACK_TYPE_FLOAT) { float f = ieee754b32_deserialise(val.unsigned_); val.f = f; tr_debug("val.f: %f", val.f); } else if (dest.type == PACK_TYPE_DOUBLE) { double d = ieee754b64_deserialise(val.unsigned_); val.d = d; tr_debug("val.d: %f", val.d); } else if (islower((char)dest.type)) { 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 (dest.type) { #define T(type, sign, c_type, va_type) \ case (char)PACK_TYPE_##type: \ dest.ptr.type[i] = val.sign##_; \ break; ITYPE_MACROS #undef T case PACK_TYPE_FLOAT: dest.ptr.f[i] = val.f; break; case PACK_TYPE_DOUBLE: dest.ptr.d[i] = val.d; break; case PACK_TYPE_PADDING: break; } } } 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++) { size_t s; struct dest dest = { .count = 1 }; 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); dest.count = c; i += end - &fmt[i]; } else if (fmt[i] == '*') { dest.count = va_arg(ap, size_t); i++; } tr_debug("dest.count: %" PRIuSIZE ", i: %d, fmt[i]: %c", dest.count, i, fmt[i]); 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: \ dest.ptr.type = va_arg(ap, sign c_type *); \ break; ITYPE_MACROS #undef T case PACK_TYPE_FLOAT: dest.ptr.f = va_arg(ap, float *); break; case PACK_TYPE_DOUBLE: dest.ptr.d = va_arg(ap, double *); break; case PACK_TYPE_PADDING: break; default: SET_AND_GOTO(ret, PACK_FMTINVAL, stop); } dest.type = fmt[i]; s = getsize(dest.type); tr_debug("s: %" PRIuSIZE, s); if (s == (size_t)-1) SET_AND_GOTO(ret, PACK_FMTINVAL, stop); if (size - offset < s * dest.count) SET_AND_GOTO(ret, PACK_TOOSMALL, stop); if (dest.type != PACK_TYPE_PADDING) read_fields(dest, &buf[offset], endianness); offset += s * dest.count; } stop: va_end(ap); return ret; }