diff --git a/CMakeLists.txt b/CMakeLists.txt index e5fb58ae..82d0e336 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ OPTION(UCI_SUPPORT "UCI plugin support" ON) OPTION(RTNL_SUPPORT "Route Netlink plugin support" ON) OPTION(NL80211_SUPPORT "Wireless Netlink plugin support" ON) OPTION(RESOLV_SUPPORT "NS resolve plugin support" ON) +OPTION(STRUCT_SUPPORT "Struct plugin support" ON) OPTION(LEGACY_SUPPORT "Support deprecated syntax features" ON) @@ -151,6 +152,16 @@ IF(RESOLV_SUPPORT) ENDIF() ENDIF() +IF(STRUCT_SUPPORT) + SET(LIBRARIES ${LIBRARIES} struct_lib) + ADD_LIBRARY(struct_lib MODULE lib/struct.c) + SET_TARGET_PROPERTIES(struct_lib PROPERTIES OUTPUT_NAME struct PREFIX "") + CHECK_FUNCTION_EXISTS(frexp FREXP_FUNCTION_EXISTS) + IF (NOT FREXP_FUNCTION_EXISTS) + TARGET_LINK_LIBRARIES(struct_lib m) + ENDIF() +ENDIF() + IF(UNIT_TESTING) ENABLE_TESTING() ADD_DEFINITIONS(-DUNIT_TESTING) diff --git a/lib/struct.c b/lib/struct.c new file mode 100644 index 00000000..4985e2a7 --- /dev/null +++ b/lib/struct.c @@ -0,0 +1,2584 @@ +/* + * Binary data packing/unpacking module for ucode. + * Copyright (C) 2021 Jo-Philipp Wich + * + * This module is heavily based on the Python 3.10 "_struct.c" module source + * published under the following license: + * + * ----------------------------------------------------------------------------------- + * + * 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and + * the Individual or Organization ("Licensee") accessing and otherwise using Python + * 3.10.0 software in source or binary form and its associated documentation. + * + * 2. Subject to the terms and conditions of this License Agreement, PSF hereby + * grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, + * analyze, test, perform and/or display publicly, prepare derivative works, + * distribute, and otherwise use Python 3.10.0 alone or in any derivative + * version, provided, however, that PSF's License Agreement and PSF's notice of + * copyright, i.e., "Copyright © 2001-2021 Python Software Foundation; All Rights + * Reserved" are retained in Python 3.10.0 alone or in any derivative version + * prepared by Licensee. + * + * 3. In the event Licensee prepares a derivative work that is based on or + * incorporates Python 3.10.0 or any part thereof, and wants to make the + * derivative work available to others as provided herein, then Licensee hereby + * agrees to include in any such work a brief summary of the changes made to Python + * 3.10.0. + * + * 4. PSF is making Python 3.10.0 available to Licensee on an "AS IS" basis. + * PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF + * EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE + * USE OF PYTHON 3.10.0 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + * + * 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.10.0 + * FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF + * MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.10.0, OR ANY DERIVATIVE + * THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + * + * 6. This License Agreement will automatically terminate upon a material breach of + * its terms and conditions. + * + * 7. Nothing in this License Agreement shall be deemed to create any relationship + * of agency, partnership, or joint venture between PSF and Licensee. This License + * Agreement does not grant permission to use PSF trademarks or trade name in a + * trademark sense to endorse or promote products or services of Licensee, or any + * third party. + * + * 8. By copying, installing or otherwise using Python 3.10.0, Licensee agrees + * to be bound by the terms and conditions of this License Agreement. + * + * ----------------------------------------------------------------------------------- + * + * Brief summary of changes compared to the original Python 3.10 source: + * + * - Inlined and refactored IEEE 754 float conversion routines + * - Usage of stdbool for function return values and boolean parameters + * - Renamed functions and structures for clarity + * - Interface adapated to ucode C api + * - Removed unused code + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ucode/module.h" + +static uc_ressource_type_t *struct_type; + +typedef struct formatdef { + char format; + ssize_t size; + ssize_t alignment; + uc_value_t* (*unpack)(uc_vm_t *, const char *, const struct formatdef *); + bool (*pack)(uc_vm_t *, char *, uc_value_t *, const struct formatdef *); +} formatdef_t; + +typedef struct { + const formatdef_t *fmtdef; + ssize_t offset; + ssize_t size; + ssize_t repeat; +} formatcode_t; + +typedef struct { + size_t len; + size_t size; + size_t ncodes; + formatcode_t codes[]; +} formatstate_t; + + +/* Define various structs to figure out the alignments of types */ + +typedef struct { char c; short x; } st_short; +typedef struct { char c; int x; } st_int; +typedef struct { char c; long x; } st_long; +typedef struct { char c; float x; } st_float; +typedef struct { char c; double x; } st_double; +typedef struct { char c; void *x; } st_void_p; +typedef struct { char c; size_t x; } st_size_t; +typedef struct { char c; bool x; } st_bool; +typedef struct { char c; long long x; } s_long_long; + +#define SHORT_ALIGN (sizeof(st_short) - sizeof(short)) +#define INT_ALIGN (sizeof(st_int) - sizeof(int)) +#define LONG_ALIGN (sizeof(st_long) - sizeof(long)) +#define FLOAT_ALIGN (sizeof(st_float) - sizeof(float)) +#define DOUBLE_ALIGN (sizeof(st_double) - sizeof(double)) +#define VOID_P_ALIGN (sizeof(st_void_p) - sizeof(void *)) +#define SIZE_T_ALIGN (sizeof(st_size_t) - sizeof(size_t)) +#define BOOL_ALIGN (sizeof(st_bool) - sizeof(bool)) +#define LONG_LONG_ALIGN (sizeof(s_long_long) - sizeof(long long)) + +#ifdef __powerc +#pragma options align_for_entry=reset +#endif + + +static bool +ucv_as_long(uc_vm_t *vm, uc_value_t *v, long *p) +{ + char *s, *e; + int64_t i; + double d; + long x; + + errno = 0; + + switch (ucv_type(v)) { + case UC_INTEGER: + i = ucv_int64_get(v); + + if (i < LONG_MIN || i > LONG_MAX) + errno = ERANGE; + + x = (long)i; + break; + + case UC_DOUBLE: + d = ucv_double_get(v); + x = (long)d; + + if (isnan(d) || d < (double)LONG_MIN || d > (double)LONG_MAX || d - x != 0) + errno = ERANGE; + + break; + + case UC_BOOLEAN: + x = (long)ucv_boolean_get(v); + break; + + case UC_NULL: + x = 0; + break; + + case UC_STRING: + s = ucv_string_get(v); + x = strtol(s, &e, 0); + + if (e == s || *e != '\0') + errno = EINVAL; + + break; + + default: + errno = EINVAL; + x = 0; + break; + } + + if (errno != 0) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + (errno == ERANGE) + ? "Argument out of range" + : "Argument not convertible to number"); + + return false; + } + + *p = x; + + return true; +} + +static bool +ucv_as_ulong(uc_vm_t *vm, uc_value_t *v, unsigned long *p) +{ + unsigned long x; + char *s, *e; + uint64_t i; + double d; + + errno = 0; + + switch (ucv_type(v)) { + case UC_INTEGER: + i = ucv_uint64_get(v); + + if (i > ULONG_MAX) + errno = ERANGE; + + x = (unsigned long)i; + break; + + case UC_DOUBLE: + d = ucv_double_get(v); + x = (unsigned long)d; + + if (isnan(d) || d < 0 || d > (double)ULONG_MAX || d - x != 0) + errno = ERANGE; + + break; + + case UC_BOOLEAN: + x = (unsigned long)ucv_boolean_get(v); + break; + + case UC_NULL: + x = 0; + break; + + case UC_STRING: + s = ucv_string_get(v); + x = strtoul(s, &e, 0); + + if (e == s || *e != '\0') + errno = EINVAL; + + break; + + default: + errno = EINVAL; + x = 0; + break; + } + + if (errno != 0) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + (errno == ERANGE) + ? "Argument out of range" + : "Argument not convertible to number"); + + return false; + } + + *p = x; + + return true; +} + +static bool +ucv_as_longlong(uc_vm_t *vm, uc_value_t *v, long long *p) +{ + char *s, *e; + long long x; + int64_t i; + double d; + + errno = 0; + + switch (ucv_type(v)) { + case UC_INTEGER: + i = ucv_int64_get(v); + + if (i < LLONG_MIN || i > LLONG_MAX) + errno = ERANGE; + + x = (long long)i; + break; + + case UC_DOUBLE: + d = ucv_double_get(v); + x = (long long)d; + + if (isnan(d) || d < (double)LLONG_MIN || d > (double)LLONG_MAX || d - x != 0) + errno = ERANGE; + + break; + + case UC_BOOLEAN: + x = (long long)ucv_boolean_get(v); + break; + + case UC_NULL: + x = 0; + break; + + case UC_STRING: + s = ucv_string_get(v); + x = strtoll(s, &e, 0); + + if (e == s || *e != '\0') + errno = EINVAL; + + break; + + default: + errno = EINVAL; + x = 0; + break; + } + + if (errno != 0) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + (errno == ERANGE) + ? "Argument out of range" + : "Argument not convertible to number"); + + return false; + } + + *p = x; + + return true; +} + +static bool +ucv_as_ulonglong(uc_vm_t *vm, uc_value_t *v, unsigned long long *p) +{ + unsigned long long x; + char *s, *e; + uint64_t i; + double d; + + errno = 0; + + switch (ucv_type(v)) { + case UC_INTEGER: + i = ucv_uint64_get(v); + + if (i > ULLONG_MAX) + errno = ERANGE; + + x = (unsigned long long)i; + break; + + case UC_DOUBLE: + d = ucv_double_get(v); + x = (unsigned long long)d; + + if (isnan(d) || d < 0 || d > (double)ULLONG_MAX || d - x != 0) + errno = ERANGE; + + break; + + case UC_BOOLEAN: + x = (unsigned long long)ucv_boolean_get(v); + break; + + case UC_NULL: + x = 0; + break; + + case UC_STRING: + s = ucv_string_get(v); + x = strtoull(s, &e, 0); + + if (e == s || *e != '\0') + errno = EINVAL; + + break; + + default: + errno = EINVAL; + x = 0; + break; + } + + if (errno != 0) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + (errno == ERANGE) + ? "Argument out of range" + : "Argument not convertible to number"); + + return false; + } + + *p = x; + + return true; +} + +static bool +ucv_as_ssize_t(uc_vm_t *vm, uc_value_t *v, ssize_t *p) +{ + char *s, *e; + int64_t i; + ssize_t x; + double d; + + errno = 0; + + switch (ucv_type(v)) { + case UC_INTEGER: + i = ucv_int64_get(v); + + if (i < -1 || i > SSIZE_MAX) + errno = ERANGE; + + x = (ssize_t)i; + break; + + case UC_DOUBLE: + d = ucv_double_get(v); + x = (ssize_t)d; + + if (isnan(d) || d < -1 || d > (double)SSIZE_MAX || d - x != 0) + errno = ERANGE; + + break; + + case UC_BOOLEAN: + x = (ssize_t)ucv_boolean_get(v); + break; + + case UC_NULL: + x = 0; + break; + + case UC_STRING: + s = ucv_string_get(v); + i = strtoll(s, &e, 0); + + if (e == s || *e != '\0') + errno = EINVAL; + else if (i < -1 || i > SSIZE_MAX) + errno = ERANGE; + + x = (ssize_t)i; + break; + + default: + errno = EINVAL; + x = 0; + break; + } + + if (errno != 0) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + (errno == ERANGE) + ? "Argument out of range" + : "Argument not convertible to number"); + + return false; + } + + *p = x; + + return true; +} + +/* Same, but handling size_t */ + +static bool +ucv_as_size_t(uc_vm_t *vm, uc_value_t *v, size_t *p) +{ + char *s, *e; + uint64_t i; + double d; + size_t x; + + errno = 0; + + switch (ucv_type(v)) { + case UC_INTEGER: + i = ucv_uint64_get(v); + + if (i > SIZE_MAX) + errno = ERANGE; + + x = (size_t)i; + break; + + case UC_DOUBLE: + d = ucv_double_get(v); + x = (size_t)d; + + if (isnan(d) || d < 0 || d > (double)SIZE_MAX || d - x != 0) + errno = ERANGE; + + break; + + case UC_BOOLEAN: + x = (size_t)ucv_boolean_get(v); + break; + + case UC_NULL: + x = 0; + break; + + case UC_STRING: + s = ucv_string_get(v); + i = strtoull(s, &e, 0); + + if (e == s || *e != '\0') + errno = EINVAL; + else if (i > SIZE_MAX) + errno = ERANGE; + + x = (size_t)i; + break; + + default: + errno = EINVAL; + x = 0; + break; + } + + if (errno != 0) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + (errno == ERANGE) + ? "Argument out of range" + : "Argument not convertible to number"); + + return false; + } + + *p = x; + + return true; +} + +static bool +ucv_as_double(uc_vm_t *vm, uc_value_t *v, double *p) +{ + char *s, *e; + int64_t i; + double x; + + errno = 0; + + switch (ucv_type(v)) { + case UC_INTEGER: + i = ucv_int64_get(v); + + if (errno == 0) { + if (i < -DBL_MAX || i > DBL_MAX) + errno = ERANGE; + } + + x = (double)i; + break; + + case UC_DOUBLE: + x = ucv_double_get(v); + break; + + case UC_BOOLEAN: + x = (double)ucv_boolean_get(v); + break; + + case UC_NULL: + x = 0.0; + break; + + case UC_STRING: + s = ucv_string_get(v); + x = strtod(s, &e); + + if (e == s || *e != '\0') + errno = EINVAL; + + break; + + default: + errno = EINVAL; + x = 0.0; + break; + } + + if (errno != 0) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + (errno == ERANGE) + ? "Argument out of range" + : "Argument not convertible to number"); + + return false; + } + + *p = x; + + return true; +} + + +/* Floating point helpers */ + +static bool +double_pack16(double d, char *buf, bool little_endian) +{ + int32_t exponent = 0; + uint16_t bits = 0; + bool sign = false; + double fraction; + uint8_t *p; + + if (d == 0.0) { + sign = (copysign(1.0, d) == -1.0); + } + else if (isnan(d)) { + sign = (copysign(1.0, d) == -1.0); + exponent = 0x1f; + bits = 512; + } + else if (!isfinite(d)) { + sign = (d < 0.0); + exponent = 0x1f; + } + else { + if (d < 0.0) { + sign = true; + d = -d; + } + + fraction = frexp(d, &exponent); + + assert(fraction >= 0.5 && fraction < 1.0); + + fraction *= 2.0; + exponent--; + + if (exponent >= 16) { + errno = ERANGE; + + return false; + } + else if (exponent < -25) { + fraction = 0.0; + exponent = 0; + } + else if (exponent < -14) { + fraction = ldexp(fraction, 14 + exponent); + exponent = 0; + } + else { + fraction -= 1.0; + exponent += 15; + } + + fraction *= 1024.0; + bits = (uint16_t)fraction; + + assert(bits < 1024); + assert(exponent < 31); + + if ((fraction - bits > 0.5) || ((fraction - bits == 0.5) && (bits % 2))) { + if (++bits == 1024) { + bits = 0; + + if (++exponent == 31) { + errno = ERANGE; + + return false; + } + } + } + } + + bits |= (exponent << 10) | (sign << 15); + + p = (uint8_t *)buf + little_endian; + *p = (bits >> 8) & 0xff; + + p += (little_endian ? -1 : 1); + *p = bits & 0xff; + + return true; +} + +static bool +double_pack32(double d, char *buf, bool little_endian) +{ + int8_t step = little_endian ? -1 : 1; + int32_t exponent = 0; + uint32_t bits = 0; + bool sign = false; + double fraction; + uint8_t *p; + + if (d == 0.0) { + sign = (copysign(1.0, d) == -1.0); + } + else if (isnan(d)) { + sign = (copysign(1.0, d) == -1.0); + exponent = 0xff; + bits = 0x7fffff; + } + else if (!isfinite(d)) { + sign = (d < 0.0); + exponent = 0xff; + } + else { + if (d < 0.0) { + sign = true; + d = -d; + } + + fraction = frexp(d, &exponent); + + if (fraction == 0.0) { + exponent = 0; + } + else { + assert(fraction >= 0.5 && fraction < 1.0); + + fraction *= 2.0; + exponent--; + } + + if (exponent >= 128) { + errno = ERANGE; + + return false; + } + else if (exponent < -126) { + fraction = ldexp(fraction, 126 + exponent); + exponent = 0; + } + else if (exponent != 0 || fraction != 0.0) { + fraction -= 1.0; + exponent += 127; + } + + fraction *= 8388608.0; + bits = (uint32_t)(fraction + 0.5); + + assert(bits <= 8388608); + + if (bits >> 23) { + bits = 0; + + if (++exponent >= 255) { + errno = ERANGE; + + return false; + } + } + } + + p = (uint8_t *)buf + (little_endian ? 3 : 0); + *p = (sign << 7) | (exponent >> 1); + + p += step; + *p = ((exponent & 1) << 7) | (bits >> 16); + + p += step; + *p = (bits >> 8) & 0xff; + + p += step; + *p = bits & 0xff; + + return true; +} + +static bool +double_pack64(double d, char *buf, bool little_endian) +{ + int8_t step = little_endian ? -1 : 1; + uint32_t hibits = 0, lobits = 0; + int32_t exponent = 0; + bool sign = false; + double fraction; + uint8_t *p; + + if (d == 0.0) { + sign = (copysign(1.0, d) == -1.0); + } + else if (isnan(d)) { + sign = (copysign(1.0, d) == -1.0); + exponent = 0x7ff; + lobits = 0x1000000; + hibits = 0xfffffff; + } + else if (!isfinite(d)) { + sign = (d < 0.0); + exponent = 0x7ff; + } + else { + if (d < 0.0) { + sign = true; + d = -d; + } + + fraction = frexp(d, &exponent); + + if (fraction == 0.0) { + exponent = 0; + } + else { + assert(fraction >= 0.5 && fraction < 1.0); + + fraction *= 2.0; + exponent--; + } + + if (exponent >= 1024) { + errno = ERANGE; + + return false; + } + else if (exponent < -1022) { + fraction = ldexp(fraction, 1022 + exponent); + exponent = 0; + } + else if (exponent != 0 || fraction != 0.0) { + fraction -= 1.0; + exponent += 1023; + } + + fraction *= 268435456.0; + hibits = (uint32_t)fraction; + assert(hibits <= 0xfffffff); + + fraction -= (double)hibits; + fraction *= 16777216.0; + lobits = (uint32_t)(fraction + 0.5); + assert(lobits <= 0x1000000); + + if (lobits >> 24) { + lobits = 0; + + if (++hibits >> 28) { + hibits = 0; + + if (++exponent >= 2047) { + errno = ERANGE; + + return false; + } + } + } + } + + p = (uint8_t *)buf + (little_endian ? 7 : 0); + *p = (sign << 7) | (exponent >> 4); + + p += step; + *p = ((exponent & 0xf) << 4) | (hibits >> 24); + + p += step; + *p = (hibits >> 16) & 0xff; + + p += step; + *p = (hibits >> 8) & 0xff; + + p += step; + *p = hibits & 0xff; + + p += step; + *p = (lobits >> 16) & 0xff; + + p += step; + *p = (lobits >> 8) & 0xff; + + p += step; + *p = lobits & 0xff; + + return true; +} + +static double +double_unpack16(const char *buf, bool little_endian) +{ + uint32_t fraction; + int32_t exponent; + uint8_t *p; + bool sign; + double d; + + p = (uint8_t *)buf + little_endian; + sign = (*p >> 7) & 1; + exponent = (*p & 0x7c) >> 2; + fraction = (*p & 0x03) << 8; + + p += little_endian ? -1 : 1; + fraction |= *p; + + if (exponent == 0x1f) { + if (fraction == 0) + return sign ? -INFINITY : INFINITY; + else + return sign ? -NAN : NAN; + } + + d = (double)fraction / 1024.0; + + if (exponent == 0) { + exponent = -14; + } + else { + exponent -= 15; + d += 1.0; + } + + d = ldexp(d, exponent); + + return sign ? -d : d; +} + +static double +double_unpack32(const char *buf, bool little_endian) +{ + int8_t step = little_endian ? -1 : 1; + uint32_t fraction; + int32_t exponent; + uint8_t *p; + bool sign; + double d; + + p = (uint8_t *)buf + (little_endian ? 3 : 0); + sign = (*p >> 7) & 1; + exponent = (*p & 0x7f) << 1; + + p += step; + exponent |= (*p >> 7) & 1; + fraction = (*p & 0x7f) << 16; + + p += step; + fraction |= *p << 8; + + p += step; + fraction |= *p; + + if (exponent == 0xff) { + if (fraction == 0) + return sign ? -INFINITY : INFINITY; + else + return sign ? -NAN : NAN; + } + + d = (double)fraction / 8388608.0; + + if (exponent == 0) { + exponent = -126; + } + else { + exponent -= 127; + d += 1.0; + } + + d = ldexp(d, exponent); + + return sign ? -d : d; +} + + +static double +double_unpack64(const char *buf, bool little_endian) +{ + int8_t step = little_endian ? -1 : 1; + uint32_t lofrac, hifrac; + int32_t exponent; + uint8_t *p; + bool sign; + double d; + + p = (uint8_t *)buf + (little_endian ? 7 : 0); + sign = (*p >> 7) & 1; + exponent = (*p & 0x7f) << 4; + + p += step; + exponent |= (*p >> 4) & 0xf; + hifrac = (*p & 0xf) << 24; + + p += step; + hifrac |= *p << 16; + + p += step; + hifrac |= *p << 8; + + p += step; + hifrac |= *p; + + p += step; + lofrac = *p << 16; + + p += step; + lofrac |= *p << 8; + + p += step; + lofrac |= *p; + + if (exponent == 0x7ff) { + if (lofrac == 0 && hifrac == 0) + return sign ? -INFINITY : INFINITY; + else + return sign ? -NAN : NAN; + } + + d = (double)hifrac + (double)lofrac / 16777216.0; + d /= 268435456.0; + + if (exponent == 0) { + exponent = -1022; + } + else { + exponent -= 1023; + d += 1.0; + } + + d = ldexp(d, exponent); + + return sign ? -d : d; +} + +static bool +range_exception(uc_vm_t *vm, const formatdef_t *f, bool is_unsigned) +{ + /* ulargest is the largest unsigned value with f->size bytes. + * Note that the simpler: + * ((size_t)1 << (f->size * 8)) - 1 + * doesn't work when f->size == sizeof(size_t) because C doesn't + * define what happens when a left shift count is >= the number of + * bits in the integer being shifted; e.g., on some boxes it doesn't + * shift at all when they're equal. + */ + const size_t ulargest = (size_t)-1 >> ((sizeof(size_t) - f->size)*8); + + assert(f->size >= 1 && f->size <= (ssize_t)sizeof(size_t)); + + if (is_unsigned) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Format '%c' requires numeric argument between 0 and %zu", + f->format, + ulargest); + } + else { + const ssize_t largest = (ssize_t)(ulargest >> 1); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Format '%c' requires numeric argument between %zd and %zd", + f->format, + ~ largest, + largest); + } + + return false; +} + + +/* Native mode routines. ****************************************************/ + +static uc_value_t * +native_unpack_char(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_string_new_length(p, 1); +} + +static uc_value_t * +native_unpack_byte(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_int64_new(*(signed char *)p); +} + +static uc_value_t * +native_unpack_ubyte(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_uint64_new(*(unsigned char *)p); +} + +static uc_value_t * +native_unpack_short(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + short x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_int64_new(x); +} + +static uc_value_t * +native_unpack_ushort(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + unsigned short x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_uint64_new(x); +} + +static uc_value_t * +native_unpack_int(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + int x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_int64_new(x); +} + +static uc_value_t * +native_unpack_uint(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + unsigned int x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_uint64_new(x); +} + +static uc_value_t * +native_unpack_long(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + long x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_int64_new(x); +} + +static uc_value_t * +native_unpack_ulong(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + unsigned long x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_uint64_new(x); +} + +static uc_value_t * +native_unpack_ssize_t(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + ssize_t x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_int64_new(x); +} + +static uc_value_t * +native_unpack_size_t(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + size_t x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_uint64_new(x); +} + +static uc_value_t * +native_unpack_longlong(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + long long x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_int64_new(x); +} + +static uc_value_t * +native_unpack_ulonglong(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + unsigned long long x = 0; + + memcpy(&x, p, sizeof(x)); + + return ucv_uint64_new(x); +} + +static uc_value_t * +native_unpack_bool(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + bool x = false; + + memcpy(&x, p, sizeof(x)); + + return ucv_boolean_new(x != 0); +} + + +static uc_value_t * +native_unpack_halffloat(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + return ucv_double_new(double_unpack16(p, true)); +#else + return ucv_double_new(double_unpack16(p, false)); +#endif +} + +static uc_value_t * +native_unpack_float(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + float x = 0.0; + + memcpy(&x, p, sizeof(x)); + + return ucv_double_new(x); +} + +static uc_value_t * +native_unpack_double(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + double x = 0.0; + + memcpy(&x, p, sizeof(x)); + + return ucv_double_new(x); +} + +static uc_value_t * +native_unpack_void_p(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + void *x = NULL; + + memcpy(&x, p, sizeof(x)); + + return ucv_int64_new((intptr_t)x); +} + +static bool +native_pack_byte(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + long x = 0; + + if (!ucv_as_long(vm, v, &x)) + return false; + + if (x < -128 || x > 127) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Byte format requires numeric value between -128 and 127"); + + return false; + } + + *p = (char)x; + + return true; +} + +static bool +native_pack_ubyte(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + long x = 0; + + if (!ucv_as_long(vm, v, &x)) + return false; + + if (x < 0 || x > 255) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Unsigned byte format requires numeric value between 0 and 255"); + + return false; + } + + *(unsigned char *)p = (unsigned char)x; + + return true; +} + +static bool +native_pack_char(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + char *s = NULL; + + if (ucv_type(v) == UC_STRING) { + s = ucv_string_get(v); + *p = *s; + } + else { + s = ucv_to_string(vm, v); + *p = *s; + free(s); + } + + return true; +} + +static bool +native_pack_short(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + long x = 0; + short y = 0; + + if (!ucv_as_long(vm, v, &x)) + return false; + + if (x < SHRT_MIN || x > SHRT_MAX) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Short format requires numeric value between %d and %d", + (int)SHRT_MIN, (int)SHRT_MAX); + + return false; + } + + y = (short)x; + memcpy(p, &y, sizeof(y)); + + return true; +} + +static bool +native_pack_ushort(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned short y = 0; + long x = 0; + + if (!ucv_as_long(vm, v, &x)) + return false; + + if (x < 0 || x > USHRT_MAX) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Unsigned short format requires numeric value between 0 and %u", + (unsigned int)USHRT_MAX); + + return false; + } + + y = (unsigned short)x; + memcpy(p, &y, sizeof(y)); + + return true; +} + +static bool +native_pack_int(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + long x = 0; + int y = 0; + + if (!ucv_as_long(vm, v, &x)) + return false; + + if (sizeof(long) > sizeof(int)) { + if ((x < ((long)INT_MIN)) || (x > ((long)INT_MAX))) + return range_exception(vm, f, false); + } + + y = (int)x; + memcpy(p, &y, sizeof(y)); + + return true; +} + +static bool +native_pack_uint(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned long x = 0; + unsigned int y = 0; + + if (!ucv_as_ulong(vm, v, &x)) + return false; + + if (sizeof(long) > sizeof(int)) { + if (x > ((unsigned long)UINT_MAX)) + return range_exception(vm, f, true); + } + + y = (unsigned int)x; + memcpy(p, &y, sizeof(y)); + + return true; +} + +static bool +native_pack_long(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + long x = 0; + + if (!ucv_as_long(vm, v, &x)) + return false; + + memcpy(p, &x, sizeof(x)); + + return true; +} + +static bool +native_pack_ulong(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned long x = 0; + + if (!ucv_as_ulong(vm, v, &x)) + return false; + + memcpy(p, &x, sizeof(x)); + + return true; +} + +static bool +native_pack_ssize_t(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + ssize_t x = 0; + + if (!ucv_as_ssize_t(vm, v, &x)) + return false; + + memcpy(p, &x, sizeof(x)); + + return true; +} + +static bool +native_pack_size_t(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + size_t x = 0; + + if (!ucv_as_size_t(vm, v, &x)) + return false; + + memcpy(p, &x, sizeof(x)); + + return true; +} + +static bool +native_pack_longlong(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + long long x = 0; + + if (!ucv_as_longlong(vm, v, &x)) + return false; + + memcpy(p, &x, sizeof(x)); + + return true; +} + +static bool +native_pack_ulonglong(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned long long x = 0; + + if (!ucv_as_ulonglong(vm, v, &x)) + return false; + + memcpy(p, &x, sizeof(x)); + + return true; +} + + +static bool +native_pack_bool(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + bool x = 0; + + x = ucv_is_truish(v); + + memcpy(p, &x, sizeof(x)); + + return true; +} + +static bool +native_pack_halffloat(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + double x; + + if (!ucv_as_double(vm, v, &x)) + return false; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + return double_pack16(x, p, true); +#else + return double_pack16(x, p, false); +#endif +} + +static bool +native_pack_float(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + double d = 0.0; + float x = 0.0; + + if (!ucv_as_double(vm, v, &d)) + return false; + + x = (float)d; + memcpy(p, &x, sizeof(x)); + + return true; +} + +static bool +native_pack_double(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + double x = 0.0; + + if (!ucv_as_double(vm, v, &x)) + return false; + + memcpy(p, &x, sizeof(x)); + + return true; +} + +static bool +native_pack_void_p(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + long long int i = 0; + void *x = NULL; + + if (!ucv_as_longlong(vm, v, &i)) + return false; + + x = (void *)(intptr_t)i; + memcpy(p, &x, sizeof(x)); + + return true; +} + +static const formatdef_t native_endian_table[] = { + { 'x', sizeof(char), 0, NULL, NULL }, + { 'b', sizeof(char), 0, native_unpack_byte, native_pack_byte }, + { 'B', sizeof(char), 0, native_unpack_ubyte, native_pack_ubyte }, + { 'c', sizeof(char), 0, native_unpack_char, native_pack_char }, + { 's', sizeof(char), 0, NULL, NULL }, + { 'p', sizeof(char), 0, NULL, NULL }, + { 'h', sizeof(short), SHORT_ALIGN, native_unpack_short, native_pack_short }, + { 'H', sizeof(short), SHORT_ALIGN, native_unpack_ushort, native_pack_ushort }, + { 'i', sizeof(int), INT_ALIGN, native_unpack_int, native_pack_int }, + { 'I', sizeof(int), INT_ALIGN, native_unpack_uint, native_pack_uint }, + { 'l', sizeof(long), LONG_ALIGN, native_unpack_long, native_pack_long }, + { 'L', sizeof(long), LONG_ALIGN, native_unpack_ulong, native_pack_ulong }, + { 'n', sizeof(size_t), SIZE_T_ALIGN, native_unpack_ssize_t, native_pack_ssize_t }, + { 'N', sizeof(size_t), SIZE_T_ALIGN, native_unpack_size_t, native_pack_size_t }, + { 'q', sizeof(long long), LONG_LONG_ALIGN, native_unpack_longlong, native_pack_longlong }, + { 'Q', sizeof(long long), LONG_LONG_ALIGN, native_unpack_ulonglong,native_pack_ulonglong }, + { '?', sizeof(bool), BOOL_ALIGN, native_unpack_bool, native_pack_bool }, + { 'e', sizeof(short), SHORT_ALIGN, native_unpack_halffloat, native_pack_halffloat }, + { 'f', sizeof(float), FLOAT_ALIGN, native_unpack_float, native_pack_float }, + { 'd', sizeof(double), DOUBLE_ALIGN, native_unpack_double, native_pack_double }, + { 'P', sizeof(void *), VOID_P_ALIGN, native_unpack_void_p, native_pack_void_p }, + { 0 } +}; + + +/* Big-endian routines. *****************************************************/ + +static uc_value_t * +be_unpack_int(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + const unsigned char *bytes = (const unsigned char *)p; + ssize_t i = f->size; + long x = 0; + + do { + x = (x<<8) | *bytes++; + } while (--i > 0); + + /* Extend the sign bit. */ + if ((ssize_t)sizeof(long) > f->size) + x |= -(x & (1L << ((8 * f->size) - 1))); + + return ucv_int64_new(x); +} + +static uc_value_t * +be_unpack_uint(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + const unsigned char *bytes = (const unsigned char *)p; + ssize_t i = f->size; + unsigned long x = 0; + + do { + x = (x<<8) | *bytes++; + } while (--i > 0); + + return ucv_uint64_new(x); +} + +static uc_value_t * +be_unpack_longlong(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + const unsigned char *bytes = (const unsigned char *)p; + ssize_t i = f->size; + long long x = 0; + + do { + x = (x<<8) | *bytes++; + } while (--i > 0); + + /* Extend the sign bit. */ + if ((ssize_t)sizeof(long long) > f->size) + x |= -(x & ((long long)1 << ((8 * f->size) - 1))); + + return ucv_int64_new(x); +} + +static uc_value_t * +be_unpack_ulonglong(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + const unsigned char *bytes = (const unsigned char *)p; + unsigned long long x = 0; + ssize_t i = f->size; + + do { + x = (x<<8) | *bytes++; + } while (--i > 0); + + return ucv_uint64_new(x); +} + +static uc_value_t * +be_unpack_halffloat(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_double_new(double_unpack16(p, false)); +} + +static uc_value_t * +be_unpack_float(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_double_new(double_unpack32(p, false)); +} + +static uc_value_t * +be_unpack_double(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_double_new(double_unpack64(p, false)); +} + +static uc_value_t * +be_unpack_bool(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_boolean_new(*p != 0); +} + +static bool +be_pack_int(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned char *q = (unsigned char *)p; + ssize_t i = 0; + long x = 0; + + if (!ucv_as_long(vm, v, &x)) + return false; + + i = f->size; + + if (i != sizeof(long)) { + if ((i == 2) && (x < -32768 || x > 32767)) + return range_exception(vm, f, false); +#if UINT_MAX < ULONG_MAX + else if ((i == 4) && (x < -2147483648L || x > 2147483647L)) + return range_exception(vm, f, false); +#endif + } + + do { + q[--i] = (unsigned char)(x & 0xffL); + x >>= 8; + } while (i > 0); + + return true; +} + +static bool +be_pack_uint(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned char *q = (unsigned char *)p; + unsigned long x = 0; + ssize_t i = 0; + + if (!ucv_as_ulong(vm, v, &x)) + return false; + + i = f->size; + + if (i != sizeof(long)) { + unsigned long maxint = 1; + maxint <<= (unsigned long)(i * 8); + if (x >= maxint) + return range_exception(vm, f, true); + } + + do { + q[--i] = (unsigned char)(x & 0xffUL); + x >>= 8; + } while (i > 0); + + return true; +} + +static bool +be_pack_longlong(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned char *q = (unsigned char *)p; + long long x = 0; + ssize_t i = 0; + + if (!ucv_as_longlong(vm, v, &x)) + return false; + + i = f->size; + + do { + q[--i] = (unsigned char)(x & 0xffL); + x >>= 8; + } while (i > 0); + + return true; +} + +static bool +be_pack_ulonglong(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned char *q = (unsigned char *)p; + unsigned long long x = 0; + ssize_t i = 0; + + if (!ucv_as_ulonglong(vm, v, &x)) + return false; + + i = f->size; + + do { + q[--i] = (unsigned char)(x & 0xffUL); + x >>= 8; + } while (i > 0); + + return true; +} + +static bool +be_pack_halffloat(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + double x = 0.0; + + if (!ucv_as_double(vm, v, &x)) + return false; + + return double_pack16(x, p, false); +} + +static bool +be_pack_float(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + double x = 0.0; + + if (!ucv_as_double(vm, v, &x)) + return false; + + if (!double_pack32(x, p, 0)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Argument out of range"); + + return false; + } + + return true; +} + +static bool +be_pack_double(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + double x = 0.0; + + if (!ucv_as_double(vm, v, &x)) + return false; + + if (!double_pack64(x, p, 0)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Argument out of range"); + + return false; + } + + return true; +} + +static bool +be_pack_bool(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + *p = (char)ucv_is_truish(v); + + return true; +} + +static formatdef_t big_endian_table[] = { + { 'x', 1, 0, NULL, NULL }, + { 'b', 1, 0, native_unpack_byte, native_pack_byte }, + { 'B', 1, 0, native_unpack_ubyte, native_pack_ubyte }, + { 'c', 1, 0, native_unpack_char, native_pack_char }, + { 's', 1, 0, NULL, NULL }, + { 'p', 1, 0, NULL, NULL }, + { 'h', 2, 0, be_unpack_int, be_pack_int }, + { 'H', 2, 0, be_unpack_uint, be_pack_uint }, + { 'i', 4, 0, be_unpack_int, be_pack_int }, + { 'I', 4, 0, be_unpack_uint, be_pack_uint }, + { 'l', 4, 0, be_unpack_int, be_pack_int }, + { 'L', 4, 0, be_unpack_uint, be_pack_uint }, + { 'q', 8, 0, be_unpack_longlong, be_pack_longlong }, + { 'Q', 8, 0, be_unpack_ulonglong, be_pack_ulonglong }, + { '?', 1, 0, be_unpack_bool, be_pack_bool }, + { 'e', 2, 0, be_unpack_halffloat, be_pack_halffloat }, + { 'f', 4, 0, be_unpack_float, be_pack_float }, + { 'd', 8, 0, be_unpack_double, be_pack_double }, + { 0 } +}; + + +/* Little-endian routines. *****************************************************/ + +static uc_value_t * +le_unpack_int(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + const unsigned char *bytes = (const unsigned char *)p; + ssize_t i = f->size; + long x = 0; + + do { + x = (x<<8) | bytes[--i]; + } while (i > 0); + + /* Extend the sign bit. */ + if ((ssize_t)sizeof(long) > f->size) + x |= -(x & (1L << ((8 * f->size) - 1))); + + return ucv_int64_new(x); +} + +static uc_value_t * +le_unpack_uint(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + const unsigned char *bytes = (const unsigned char *)p; + ssize_t i = f->size; + unsigned long x = 0; + + do { + x = (x<<8) | bytes[--i]; + } while (i > 0); + + return ucv_uint64_new(x); +} + +static uc_value_t * +le_unpack_longlong(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + const unsigned char *bytes = (const unsigned char *)p; + ssize_t i = f->size; + long long x = 0; + + do { + x = (x<<8) | bytes[--i]; + } while (i > 0); + + /* Extend the sign bit. */ + if ((ssize_t)sizeof(long long) > f->size) + x |= -(x & ((long long)1 << ((8 * f->size) - 1))); + + return ucv_int64_new(x); +} + +static uc_value_t * +le_unpack_ulonglong(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + const unsigned char *bytes = (const unsigned char *)p; + unsigned long long x = 0; + ssize_t i = f->size; + + do { + x = (x<<8) | bytes[--i]; + } while (i > 0); + + return ucv_uint64_new(x); +} + +static uc_value_t * +le_unpack_halffloat(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_double_new(double_unpack16(p, true)); +} + +static uc_value_t * +le_unpack_float(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_double_new(double_unpack32(p, true)); +} + +static uc_value_t * +le_unpack_double(uc_vm_t *vm, const char *p, const formatdef_t *f) +{ + return ucv_double_new(double_unpack64(p, true)); +} + +static bool +le_pack_int(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned char *q = (unsigned char *)p; + ssize_t i = 0; + long x = 0; + + if (!ucv_as_long(vm, v, &x)) + return false; + + i = f->size; + + if (i != sizeof(long)) { + if ((i == 2) && (x < -32768 || x > 32767)) + return range_exception(vm, f, false); +#if UINT_MAX < ULONG_MAX + else if ((i == 4) && (x < -2147483648L || x > 2147483647L)) + return range_exception(vm, f, false); +#endif + } + + do { + *q++ = (unsigned char)(x & 0xffL); + x >>= 8; + } while (--i > 0); + + return true; +} + +static bool +le_pack_uint(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned char *q = (unsigned char *)p; + unsigned long x = 0; + ssize_t i = 0; + + if (!ucv_as_ulong(vm, v, &x)) + return false; + + i = f->size; + + if (i != sizeof(long)) { + unsigned long maxint = 1; + maxint <<= (unsigned long)(i * 8); + + if (x >= maxint) + return range_exception(vm, f, true); + } + + do { + *q++ = (unsigned char)(x & 0xffUL); + x >>= 8; + } while (--i > 0); + + return true; +} + +static bool +le_pack_longlong(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned char *q = (unsigned char *)p; + long long x = 0; + ssize_t i = 0; + + if (!ucv_as_longlong(vm, v, &x)) + return false; + + i = f->size; + + do { + *q++ = (unsigned char)(x & 0xffL); + x >>= 8; + } while (--i > 0); + + return true; +} + +static bool +le_pack_ulonglong(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + unsigned char *q = (unsigned char *)p; + unsigned long long x = 0; + ssize_t i = 0; + + if (!ucv_as_ulonglong(vm, v, &x)) + return false; + + i = f->size; + + do { + *q++ = (unsigned char)(x & 0xffUL); + x >>= 8; + } while (--i > 0); + + return true; +} + +static bool +le_pack_halffloat(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + double x = 0.0; + + if (!ucv_as_double(vm, v, &x)) + return false; + + return double_pack16(x, p, true); +} + +static bool +le_pack_float(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + double x = 0.0; + + if (!ucv_as_double(vm, v, &x)) + return false; + + if (!double_pack32(x, p, 1)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Argument out of range"); + + return false; + } + + return true; +} + +static bool +le_pack_double(uc_vm_t *vm, char *p, uc_value_t *v, const formatdef_t *f) +{ + double x = 0.0; + + if (!ucv_as_double(vm, v, &x)) + return false; + + if (!double_pack64(x, p, 1)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Argument out of range"); + + return false; + } + + return true; +} + +static formatdef_t little_endian_table[] = { + { 'x', 1, 0, NULL, NULL }, + { 'b', 1, 0, native_unpack_byte, native_pack_byte }, + { 'B', 1, 0, native_unpack_ubyte, native_pack_ubyte }, + { 'c', 1, 0, native_unpack_char, native_pack_char }, + { 's', 1, 0, NULL, NULL }, + { 'p', 1, 0, NULL, NULL }, + { 'h', 2, 0, le_unpack_int, le_pack_int }, + { 'H', 2, 0, le_unpack_uint, le_pack_uint }, + { 'i', 4, 0, le_unpack_int, le_pack_int }, + { 'I', 4, 0, le_unpack_uint, le_pack_uint }, + { 'l', 4, 0, le_unpack_int, le_pack_int }, + { 'L', 4, 0, le_unpack_uint, le_pack_uint }, + { 'q', 8, 0, le_unpack_longlong, le_pack_longlong }, + { 'Q', 8, 0, le_unpack_ulonglong, le_pack_ulonglong }, + { '?', 1, 0, be_unpack_bool, be_pack_bool }, + { 'e', 2, 0, le_unpack_halffloat, le_pack_halffloat }, + { 'f', 4, 0, le_unpack_float, le_pack_float }, + { 'd', 8, 0, le_unpack_double, le_pack_double }, + { 0 } +}; + + +static const formatdef_t * +select_format_table(const char **pfmt) +{ + const char *fmt = (*pfmt)++; /* May be backed out of later */ + + switch (*fmt) { + case '<': + return little_endian_table; + + case '>': + case '!': /* Network byte order is big-endian */ + return big_endian_table; + + case '=': /* Host byte order -- different from native in alignment! */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + return little_endian_table; +#else + return big_endian_table; +#endif + + default: + --*pfmt; /* Back out of pointer increment */ + /* Fall through */ + + case '@': + return native_endian_table; + } +} + + +/* Get the table entry for a format code */ + +static const formatdef_t * +lookup_table_entry(uc_vm_t *vm, int c, const formatdef_t *table) +{ + for (; table->format != '\0'; table++) { + if (table->format == c) { + return table; + } + } + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Unrecognized character '%c' in struct format", + c); + + return NULL; +} + + +/* Align a size according to a format code. Return -1 on overflow. */ + +static ssize_t +align_for_entry(ssize_t size, const formatdef_t *e) +{ + ssize_t extra; + + if (e->alignment && size > 0) { + extra = (e->alignment - 1) - (size - 1) % (e->alignment); + + if (extra > SSIZE_MAX - size) + return -1; + + size += extra; + } + + return size; +} + + +static void +optimize_functions(void) +{ + /* Check endian and swap in faster functions */ + const formatdef_t *native = native_endian_table; + formatdef_t *other, *ptr; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + other = little_endian_table; +#else + other = big_endian_table; +#endif + + /* Scan through the native table, find a matching + entry in the endian table and swap in the + native implementations whenever possible + (64-bit platforms may not have "standard" sizes) */ + while (native->format != '\0' && other->format != '\0') { + ptr = other; + + while (ptr->format != '\0') { + if (ptr->format == native->format) { + /* Match faster when formats are + listed in the same order */ + if (ptr == other) + other++; + + /* Only use the trick if the + size matches */ + if (ptr->size != native->size) + break; + + /* Skip float and double, could be + "unknown" float format */ + if (ptr->format == 'd' || ptr->format == 'f') + break; + + /* Skip bool, semantics are different for standard size */ + if (ptr->format == '?') + break; + + ptr->pack = native->pack; + ptr->unpack = native->unpack; + break; + } + + ptr++; + } + + native++; + } +} + +static formatstate_t * +parse_format(uc_vm_t *vm, uc_value_t *fmtval) +{ + ssize_t size, len, num, itemsize; + const formatdef_t *e, *f; + const char *fmt, *s; + formatstate_t *state; + formatcode_t *codes; + size_t ncodes; + char c; + + if (ucv_type(fmtval) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Format value not a string"); + + return NULL; + } + + fmt = ucv_string_get(fmtval); + + if (strlen(fmt) != ucv_string_length(fmtval)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Format string contains embedded null character"); + + return NULL; + } + + f = select_format_table(&fmt); + + s = fmt; + size = 0; + len = 0; + ncodes = 0; + + while ((c = *s++) != '\0') { + if (isspace(c)) + continue; + + if ('0' <= c && c <= '9') { + num = c - '0'; + + while ('0' <= (c = *s++) && c <= '9') { + /* overflow-safe version of + if (num*10 + (c - '0') > SSIZE_MAX) { ... } */ + if (num >= SSIZE_MAX / 10 && ( + num > SSIZE_MAX / 10 || + (c - '0') > SSIZE_MAX % 10)) + goto overflow; + + num = num*10 + (c - '0'); + } + + if (c == '\0') { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Format string contains repeat count given without format specifier"); + + return NULL; + } + } + else + num = 1; + + e = lookup_table_entry(vm, c, f); + + if (e == NULL) + return NULL; + + switch (c) { + case 's': /* fall through */ + case 'p': + len++; + ncodes++; + break; + + case 'x': + break; + + default: + len += num; + + if (num) + ncodes++; + + break; + } + + itemsize = e->size; + size = align_for_entry(size, e); + + if (size == -1) + goto overflow; + + /* if (size + num * itemsize > SSIZE_MAX) { ... } */ + if (num > (SSIZE_MAX - size) / itemsize) + goto overflow; + + size += num * itemsize; + } + + /* check for overflow */ + if ((ncodes + 1) > ((size_t)SSIZE_MAX / sizeof(formatcode_t))) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Out of memory"); + + return NULL; + } + + state = xalloc(sizeof(*state) + ncodes * sizeof(formatcode_t)); + state->len = len; + state->size = size; + state->ncodes = ncodes; + + codes = state->codes; + + s = fmt; + size = 0; + + while ((c = *s++) != '\0') { + if (isspace(c)) + continue; + + if ('0' <= c && c <= '9') { + num = c - '0'; + + while ('0' <= (c = *s++) && c <= '9') + num = num*10 + (c - '0'); + } + else + num = 1; + + e = lookup_table_entry(vm, c, f); + + if (e == NULL) + continue; + + size = align_for_entry(size, e); + + if (c == 's' || c == 'p') { + codes->offset = size; + codes->size = num; + codes->fmtdef = e; + codes->repeat = 1; + codes++; + size += num; + } + else if (c == 'x') { + size += num; + } + else if (num) { + codes->offset = size; + codes->size = e->size; + codes->fmtdef = e; + codes->repeat = num; + codes++; + size += e->size * num; + } + } + + return state; + +overflow: + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "Total struct size too long"); + + return NULL; +} + +static uc_value_t * +uc_pack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff) +{ + formatcode_t *code; + uc_string_t *buf; + size_t ncode; + + buf = xalloc(sizeof(*buf) + state->size + 1); + buf->header.type = UC_STRING; + buf->header.refcount = 1; + buf->length = state->size; + + for (ncode = 0, code = &state->codes[0]; + ncode < state->ncodes; + code = &state->codes[++ncode]) { + const formatdef_t *e = code->fmtdef; + char *res = buf->str + code->offset; + ssize_t j = code->repeat; + + while (j--) { + uc_value_t *v = uc_fn_arg(argoff++); + + if (e->format == 's') { + ssize_t n; + const void *p; + + if (ucv_type(v) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Argument for 's' must be a string"); + + goto err; + } + + n = ucv_string_length(v); + p = ucv_string_get(v); + + if (n > code->size) + n = code->size; + + if (n > 0) + memcpy(res, p, n); + } + else if (e->format == 'p') { + ssize_t n; + const void *p; + + if (ucv_type(v) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Argument for 's' must be a string"); + + goto err; + } + + n = ucv_string_length(v); + p = ucv_string_get(v); + + if (n > (code->size - 1)) + n = code->size - 1; + + if (n > 0) + memcpy(res + 1, p, n); + + if (n > 255) + n = 255; + + *res = (unsigned char)n; + } + else { + if (!e->pack(vm, res, v, e)) + goto err; + } + + res += code->size; + } + } + + return &buf->header; + +err: + free(buf); + + return NULL; +} + +static uc_value_t * +uc_unpack_common(uc_vm_t *vm, size_t nargs, formatstate_t *state, size_t argoff) +{ + uc_value_t *bufval = uc_fn_arg(argoff); + const char *startfrom = NULL; + uc_value_t *result; + formatcode_t *code; + size_t ncode = 0; + + if (ucv_type(bufval) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Buffer value not a string"); + + return NULL; + } + + startfrom = ucv_string_get(bufval); + result = ucv_array_new(vm); + + for (ncode = 0, code = &state->codes[0]; + ncode < state->ncodes; + code = &state->codes[++ncode]) { + const formatdef_t *e = code->fmtdef; + const char *res = startfrom + code->offset; + ssize_t j = code->repeat; + + while (j--) { + uc_value_t *v = NULL; + + if (e->format == 's') { + v = ucv_string_new_length(res, code->size); + } + else if (e->format == 'p') { + ssize_t n = *(unsigned char *)res; + + if (n >= code->size) + n = code->size - 1; + + v = ucv_string_new_length(res + 1, n); + } + else { + v = e->unpack(vm, res, e); + } + + if (v == NULL) + goto fail; + + ucv_array_push(result, v); + + res += code->size; + } + } + + return result; + +fail: + ucv_put(result); + + return NULL; +} + + +static uc_value_t * +uc_pack(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *fmtval = uc_fn_arg(0); + uc_value_t *res = NULL; + formatstate_t *state; + + state = parse_format(vm, fmtval); + + if (!state) + return NULL; + + res = uc_pack_common(vm, nargs, state, 1); + + free(state); + + return res; +} + +static uc_value_t * +uc_unpack(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *fmtval = uc_fn_arg(0); + uc_value_t *res = NULL; + formatstate_t *state; + + state = parse_format(vm, fmtval); + + if (!state) + return NULL; + + res = uc_unpack_common(vm, nargs, state, 1); + + free(state); + + return res; +} + + +static uc_value_t * +uc_struct_new(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *fmtval = uc_fn_arg(0); + formatstate_t *state; + + state = parse_format(vm, fmtval); + + if (!state) + return NULL; + + return uc_ressource_new(struct_type, state); +} + +static void +uc_struct_gc(void *ud) +{ + formatstate_t *state = ud; + + free(state); +} + +static uc_value_t * +uc_struct_pack(uc_vm_t *vm, size_t nargs) +{ + formatstate_t **state = uc_fn_this("struct"); + + if (!state || !*state) + return NULL; + + return uc_pack_common(vm, nargs, *state, 0); +} + +static uc_value_t * +uc_struct_unpack(uc_vm_t *vm, size_t nargs) +{ + formatstate_t **state = uc_fn_this("struct"); + + if (!state || !*state) + return NULL; + + return uc_unpack_common(vm, nargs, *state, 0); +} + + +static const uc_function_list_t struct_inst_fns[] = { + { "pack", uc_struct_pack }, + { "unpack", uc_struct_unpack } +}; + +static const uc_function_list_t struct_fns[] = { + { "pack", uc_pack }, + { "unpack", uc_unpack }, + { "new", uc_struct_new } +}; + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + optimize_functions(); + + uc_function_list_register(scope, struct_fns); + + struct_type = uc_type_declare(vm, "struct", struct_inst_fns, uc_struct_gc); +}