/* * 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 *src, size_t size, enum pack_endian e) { uintmax_t val = 0; for (size_t i = 0; i < size; i++) { val |= (uintmax_t)(src[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 (safe_islower((char)dest.type)) { intmax_t vals; if (!(val.unsigned_ & (UINTMAX_C(1) << (s * 8 - 1)))) { vals = val.unsigned_; } else { vals = minval(s); vals += (intmax_t)(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; case PACK_TYPE_COUNT: return; } } } enum pack_status unpack(const void *src_, size_t size, const char *fmt, ...) { enum pack_endian endianness = PACK_ENDIAN_BIG; const unsigned char *src = src_; enum pack_status ret = PACK_OK; size_t offset = 0; va_list ap; tr_call("unpack(%p, %" PRIuSIZE ", %s, ...)", (const void *)src, 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] == PACK_TYPE_COUNT) { 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, &src[offset], endianness); offset += s * dest.count; } stop: va_end(ap); return ret; } enum pack_status unpack_struct(const void *src_, size_t size, const struct pack_args *args, void *dest_) { const unsigned char *src = src_; char *dest = dest_; size_t offset = 0; tr_call("unpack_struct(%p, %" PRIuSIZE ", %p, %p)", \ (const void *)src, size, (const void *)args, (const void *)dest); tr_debug("args->endian: %s, args->fields: %p, args->num_fields: %" PRIuSIZE, args->endian == PACK_ENDIAN_BIG ? "big" : "little", (const void *)args->fields, args->num_fields); for (size_t i = 0; i < args->num_fields; i++) { #define FIELD (void *)&dest[f->offset] const struct pack_field *f = &args->fields[i]; struct pack_field indirect; struct dest field; size_t s; if (f->type == PACK_TYPE_COUNT) { if (i + 1 >= args->num_fields) return PACK_FMTINVAL; indirect = args->fields[++i]; indirect.count = *(size_t *)FIELD; f = &indirect; } s = getsize(f->type); if (s == (size_t)-1) return PACK_FMTINVAL; tr_debug("i: %" PRIuSIZE ", f->type: %c, f->count: %" PRIuSIZE ", " "f->offset: %" PRIuSIZE ", s: %" PRIuSIZE, i, (char)f->type, f->count, f->offset, s); field.type = f->type; switch (field.type) { #define T(type, sign, c_type, va_type) \ case PACK_TYPE_##type: field.ptr.type = FIELD; break; ITYPE_MACROS #undef T case PACK_TYPE_FLOAT: field.ptr.f = FIELD; break; case PACK_TYPE_DOUBLE: field.ptr.d = FIELD; break; case PACK_TYPE_PADDING: break; default: return PACK_FMTINVAL; } field.count = f->count; if (size - offset < s * field.count) return PACK_TOOSMALL; if (f->type != PACK_TYPE_PADDING) read_fields(field, &src[offset], args->endian); offset += s * field.count; #undef FIELD } return PACK_OK; }