* Copyright (C) 2010-2012 Lua.org, PUC-Rio. All rights reserved.
*
* SPDX-License-Identifier: MIT
*
******************************************************************************/
** {======================================================
** Library for packing/unpacking structures.
** See Copyright Notice above.
**
** Small changes were made by Hadriel Kaplan - those changes
** are in the Public Domain.
**
** Some changes are based on a patch to struct.h from
** Flemming Madsen, from here:
** http://lua-users.org/lists/lua-l/2009-10/msg00572.html
** In particular, these changes from him:
** -Can handle 'long long' integers (i8 / I8); though they're converted to doubles
** -Can insert/specify padding anywhere in a struct. ('X' eg. when a string is following a union)
** -Can report current offset in both pack and unpack ('=')
** -Can mask out return values when you only want to calculate sizes or unmarshal pascal-style strings. '(' & ')'
**
** Changes I made:
** -Added support for Int64/UInt64 being packed/unpacked, using 'e'/'E'
** -Made it follow Wireshark's conventions so we could get API docs
** =======================================================
*/
** Valid formats:
** > - big endian
** < - little endian
** ![num] - alignment
** x[num] - pad num bytes, default 1
** X[num] - pad to num align, default MAXALIGN
**
** Following are system-dependent sizes:
** i/I - signed/unsigned int
** l/L - signed/unsigned long
** f - float
** T - size_t
**
** Following are system-independent sizes:
** b/B - signed/unsigned byte
** h/H - signed/unsigned short
** in/In - signed/unsigned integer of size `n' bytes
Note: Unpack of i/I is done to a Lua_number, typically a double,
so unpacking a 64-bit field (i8/I8) will lose precision.
Use e/E to unpack into a Wireshark Int64/UInt64 object/userdata instead.
** e/E - signed/unsigned eight-byte Integer (64bits, long long), to/from Int64/UInt64 object
** d - double
** cn - sequence of `n' chars (from/to a string); when packing, n==0 means
the whole string; when unpacking, n==0 means use the previous
read number as the string length
** s - zero-terminated string
** ' ' - ignored
** '(' ')' - stop assigning items. ')' start assigning (padding when packing)
** '=' - return current position / offset
*/
#include "config.h"
#include <limits.h>
#include <wsutil/array.h>
#include "wslua.h"
The Struct class offers basic facilities to convert Lua values to and from C-style structs
in binary Lua strings. This is based on Roberto Ierusalimschy's Lua struct library found
in http://www.inf.puc-rio.br/~roberto/struct/, with some minor modifications as follows:
* Added support for `Int64`/`UInt64` being packed/unpacked, using 'e'/'E'.
* Can handle 'long long' integers (i8 / I8); though they're converted to doubles.
* Can insert/specify padding anywhere in a struct. ('X' eg. when a string is following a union).
* Can report current offset in both `pack` and `unpack` ('`=`').
* Can mask out return values when you only want to calculate sizes or unmarshal
pascal-style strings using '`(`' & '`)`'.
All but the first of those changes are based on an email from Flemming Madsen, on the lua-users
mailing list, which can be found http://lua-users.org/lists/lua-l/2009-10/msg00572.html[here].
The main functions are `Struct.pack`, which packs multiple Lua values into a struct-like
Lua binary string; and `Struct.unpack`, which unpacks multiple Lua values from a given
struct-like Lua binary string. There are some additional helper functions available as well.
All functions in the Struct library are called as static member functions, not object methods,
so they are invoked as "Struct.pack(...)" instead of "object:pack(...)".
The first argument to several of the `Struct` functions is a format string, which describes
the layout of the structure. The format string is a sequence of conversion elements, which
respect the current endianness and the current alignment requirements. Initially, the
current endianness is the machine's native endianness and the current alignment requirement
is 1 (meaning no alignment at all). You can change these settings with appropriate directives
in the format string.
The supported elements in the format string are as follows:
* `$$ $$' (empty space) ignored.
* `++!++__n__' flag to set the current alignment requirement to 'n' (necessarily a power of 2);
an absent 'n' means the machine's native alignment.
* `++>++' flag to set mode to big endian (i.e., network-order).
* `++<++' flag to set mode to little endian.
* `++x++' a padding zero byte with no corresponding Lua value.
* `++b++' a signed char.
* `++B++' an unsigned char.
* `++h++' a signed short (native size).
* `++H++' an unsigned short (native size).
* `++l++' a signed long (native size).
* `++L++' an unsigned long (native size).
* `++T++' a size_t (native size).
* `++i++__n__' a signed integer with 'n' bytes. An absent 'n' means the native size of an int.
* `++I++__n__' like `++i++__n__' but unsigned.
* `++e++' signed 8-byte Integer (64-bits, long long), to/from a +Int64+ object.
* `++E++' unsigned 8-byte Integer (64-bits, long long), to/from a +UInt64+ object.
* `++f++' a float (native size).
* `++d++' a double (native size).
* `++s++' a zero-terminated string.
* `++c++__n__' a sequence of exactly 'n' chars corresponding to a single Lua string. An absent 'n'
means 1. When packing, the given string must have at least 'n' characters (extra
characters are discarded).
* `++c0++' this is like `++c++__n__', except that the 'n' is given by other means: When packing, 'n' is
the length of the given string; when unpacking, 'n' is the value of the previous unpacked
value (which must be a number). In that case, this previous value is not returned.
* `++x++__n__' pad to 'n' number of bytes, default 1.
* `++X++__n__' pad to 'n' alignment, default MAXALIGN.
* `++(++' to stop assigning items, and `++)++' start assigning (padding when packing).
* `++=++' to return the current position / offset.
[IMPORTANT]
====
Using `i`, `I`, `h`, `H`, `l`, `L`, `f`, and `T` is strongly discouraged, as those sizes
are system-dependent. Use the explicitly sized variants instead, such as `i4` or `E`.
Unpacking of `i`/`I` is done to a Lua number, a double-precision floating point,
so unpacking a 64-bit field (`i8`/`I8`) will lose precision.
Use `e`/`E` to unpack into a Wireshark `Int64`/`UInt64` object instead.
====
[NOTE]
====
Lua 5.3 and later provides several built-in functions for struct unpacking and packing:
https://www.lua.org/manual/5.4/manual.html#pdf-string.pack[string.pack],
https://www.lua.org/manual/5.4/manual.html#pdf-string.packsize[string.packsize], and
https://www.lua.org/manual/5.4/manual.html#pdf-string.unpack[string.unpack].
You can use those as well, but note that the
https://www.lua.org/manual/5.4/manual.html#6.4.2[format string] conversion elements
are slightly different, and they do not support the Wireshark `Int64`/`UInt64` objects.
====
*/
isn't really a class, so it doesn't have the checkStruct/pushStruct/etc. functions
the following macro would generate; but it does need to be registered and such, so...
WSLUA_CLASS_DEFINE_BASE(Struct,NOP,0);
*/
#if !defined(STRUCT_INT)
#define STRUCT_INT long
#endif
typedef STRUCT_INT Inttype;
typedef unsigned STRUCT_INT Uinttype;
#define MAXINTSIZE 32
#define isp2(x) ((x) > 0 && ((x) & ((x) - 1)) == 0)
struct cD {
char c;
double d;
};
#define PADDING (sizeof(struct cD) - sizeof(double))
#define MAXALIGN (PADDING > sizeof(int) ? PADDING : sizeof(int))
#define BIG 0
#define LITTLE 1
static union {
int dummy;
char endian;
} const native = {1};
typedef struct Header {
int endian;
int align;
bool noassign;
} Header;
static int getnum (lua_State *L, const char **fmt, int df) {
if (!g_ascii_isdigit(**fmt))
return df;
else {
int a = 0;
do {
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
luaL_error(L, "integral size overflow");
a = a*10 + *((*fmt)++) - '0';
} while (g_ascii_isdigit(**fmt));
return a;
}
}
#define defaultoptions(h) ((h)->endian = native.endian, (h)->align = 1, (h)->noassign = false)
static size_t optsize (lua_State *L, char opt, const char **fmt) {
switch (opt) {
case 'B': case 'b': return sizeof(char);
case 'H': case 'h': return sizeof(short);
case 'L': case 'l': return sizeof(long);
case 'E': case 'e': return sizeof(int64_t);
case 'T': return sizeof(size_t);
case 'f': return sizeof(float);
case 'd': return sizeof(double);
case 'x': return getnum(L, fmt, 1);
case 'X': return getnum(L, fmt, MAXALIGN);
case 'c': return getnum(L, fmt, 1);
case 'i': case 'I': {
int sz = getnum(L, fmt, sizeof(int));
if (sz > MAXINTSIZE)
luaL_error(L, "integral size %d is larger than limit of %d",
sz, MAXINTSIZE);
return sz;
}
case 's': case ' ':
case '<': case '>':
case '(': case ')':
case '!': case '=':
return 0;
default: {
const char *msg = lua_pushfstring(L, "invalid format option [%c]", opt);
return luaL_argerror(L, 1, msg);
}
}
}
** return number of bytes needed to align an element of size 'size'
** at current position 'len'
*/
static int gettoalign (size_t len, Header *h, int opt, size_t size) {
if (size == 0 || opt == 'c' || opt == 's') return 0;
if (size > (size_t)h->align)
size = h->align;
return (int)((size - (len & (size - 1))) & (size - 1));
}
** options to control endianness and alignment settings
*/
static void controloptions (lua_State *L, int opt, const char **fmt,
Header *h) {
switch (opt) {
case ' ': return;
case '>': h->endian = BIG; return;
case '<': h->endian = LITTLE; return;
case '(': h->noassign = true; return;
case ')': h->noassign = false; return;
case '!': {
int a = getnum(L, fmt, MAXALIGN);
if (!isp2(a))
luaL_error(L, "alignment %d is not a power of 2", a);
h->align = a;
return;
}
default: {
const char *msg = lua_pushfstring(L, "invalid format option '%c'", opt);
luaL_argerror(L, 1, msg);
}
}
}
static void putinteger (lua_State *L, luaL_Buffer *b, int arg, int endian,
int size) {
lua_Number n = luaL_checknumber(L, arg);
int64_t value;
char buff[MAXINTSIZE];
if (n < 0)
value = (uint64_t)(int64_t)n;
else
value = (uint64_t)n;
if (endian == LITTLE) {
int i;
for (i = 0; i < size; i++) {
buff[i] = (value & 0xff);
value >>= 8;
}
}
else {
int i;
for (i = size - 1; i >= 0; i--) {
buff[i] = (value & 0xff);
value >>= 8;
}
}
luaL_addlstring(b, buff, size);
}
* used for float/doubles, since on some platforms they're endian'ed as well
*/
static void correctbytes (char *b, int size, int endian) {
if (endian != native.endian) {
int i = 0;
while (i < --size) {
char temp = b[i];
b[i++] = b[size];
b[size] = temp;
}
}
}
WSLUA_CONSTRUCTOR Struct_pack (lua_State *L) {
#define WSLUA_ARG_Struct_pack_FORMAT 1
#define WSLUA_ARG_Struct_pack_VALUE 2
luaL_Buffer b;
const char *fmt = wslua_checkstring_only(L, WSLUA_ARG_Struct_pack_FORMAT);
Header h;
int poscnt = 0;
int posBuf[10];
int arg = 2;
size_t totalsize = 0;
defaultoptions(&h);
lua_pushnil(L);
luaL_buffinit(L, &b);
while (*fmt != '\0') {
int opt = *fmt++;
size_t size = optsize(L, opt, &fmt);
int toalign = gettoalign(totalsize, &h, opt, size);
totalsize += toalign;
while (toalign-- > 0) luaL_addchar(&b, '\0');
if (opt == 'X') size = 0;
if (h.noassign && size) opt = 'x';
switch (opt) {
case 'b': case 'B': case 'h': case 'H':
case 'l': case 'L': case 'T': case 'i': case 'I': {
putinteger(L, &b, arg++, h.endian, (int)size);
break;
}
case 'e': {
Int64_pack(L, &b, arg++, h.endian == LITTLE);
break;
}
case 'E': {
UInt64_pack(L, &b, arg++, h.endian == LITTLE);
break;
}
case 'x': case 'X': {
size_t len = size;
while (len-- > 0)
luaL_addchar(&b, '\0');
break;
}
case 'f': {
float f = (float)luaL_checknumber(L, arg++);
correctbytes((char *)&f, (int)size, h.endian);
luaL_addlstring(&b, (char *)&f, size);
break;
}
case 'd': {
double d = luaL_checknumber(L, arg++);
correctbytes((char *)&d, (int)size, h.endian);
luaL_addlstring(&b, (char *)&d, size);
break;
}
case 'c': case 's': {
size_t l;
const char *s = luaL_checklstring(L, arg++, &l);
if (size == 0) size = l;
luaL_argcheck(L, l >= (size_t)size, arg, "string too short");
luaL_addlstring(&b, s, size);
if (opt == 's') {
luaL_addchar(&b, '\0');
size++;
}
break;
}
case '=': {
if (poscnt < (int)array_length(posBuf))
posBuf[poscnt++] = (int)totalsize + 1;
break;
}
default: controloptions(L, opt, &fmt, &h);
}
totalsize += size;
}
luaL_pushresult(&b);
for (arg = 0; arg < poscnt; arg++)
lua_pushinteger(L, posBuf[arg]);
WSLUA_RETURN(poscnt + 1);
}
static Uinttype decodeinteger (const char *buff, int endian, int size)
{
Uinttype l = 0;
int i;
if (endian == BIG) {
for (i = 0; i < size; i++) {
l <<= 8;
l |= (Uinttype)(unsigned char)buff[i];
}
}
else {
for (i = size - 1; i >= 0; i--) {
l <<= 8;
l |= (Uinttype)(unsigned char)buff[i];
}
}
return l;
}
* without truncation, or a lua_Number, based on given endianness and size.
* If the integer type is signed, that is handled correctly as well.
* Note for large values of size there can be a loss of precision.
*/
static void getinteger (lua_State *L, const char *buff, int endian,
int issigned, int size) {
Uinttype l = decodeinteger(buff, endian, size);
if (!issigned) {
if (size < LUA_INTEGER_SIZE) {
* is signed.) */
lua_pushinteger(L, (lua_Integer)l);
} else {
lua_pushnumber(L, (lua_Number)l);
}
}
else {
Uinttype mask = (Uinttype)(~((Uinttype)0)) << (size*8 - 1);
if (l & mask)
l |= mask;
if (size <= LUA_INTEGER_SIZE) {
lua_pushinteger(L, (lua_Integer)(Inttype)l);
} else {
lua_pushnumber(L, (lua_Number)(Inttype)l);
}
}
}
#define b_pushnumber(n) { if (!h.noassign) lua_pushnumber(L, (lua_Number)(n)); }
WSLUA_CONSTRUCTOR Struct_unpack (lua_State *L) {
The number of returned values depends on the format given, plus an additional value of the position where it stopped reading is returned. */
#define WSLUA_ARG_Struct_unpack_FORMAT 1
#define WSLUA_ARG_Struct_unpack_STRUCT 2
#define WSLUA_OPTARG_Struct_unpack_BEGIN 3
Header h;
const char *fmt = wslua_checkstring_only(L, WSLUA_ARG_Struct_unpack_FORMAT);
size_t ld;
const char *data = wslua_checklstring_only(L, WSLUA_ARG_Struct_unpack_STRUCT, &ld);
size_t pos = luaL_optinteger(L, WSLUA_OPTARG_Struct_unpack_BEGIN, 1) - 1;
defaultoptions(&h);
lua_settop(L, 2);
while (*fmt) {
int opt = *fmt++;
size_t size = optsize(L, opt, &fmt);
pos += gettoalign(pos, &h, opt, size);
luaL_argcheck(L, pos+size <= ld, 2, "data string too short");
if (opt == 'X') size = 0;
if (h.noassign && size > 0) {
pos += size;
continue;
}
luaL_checkstack(L, 1, "too many results");
switch (opt) {
case 'b': case 'B': case 'h': case 'H':
case 'l': case 'L': case 'T': case 'i': case 'I': {
int issigned = g_ascii_islower(opt);
getinteger(L, data+pos, h.endian, issigned, (int)size);
break;
}
case 'e': {
Int64_unpack(L, data+pos, h.endian == LITTLE);
break;
}
case 'E': {
UInt64_unpack(L, data+pos, h.endian == LITTLE);
break;
}
case 'x': case 'X': {
break;
}
case 'f': {
float f;
memcpy(&f, data+pos, size);
correctbytes((char *)&f, sizeof(f), h.endian);
lua_pushnumber(L, f);
break;
}
case 'd': {
double d;
memcpy(&d, data+pos, size);
correctbytes((char *)&d, sizeof(d), h.endian);
lua_pushnumber(L, d);
break;
}
case 'c': {
if (size == 0) {
if (!lua_isnumber(L, -1))
luaL_error(L, "format `c0' needs a previous size");
size = wslua_touint32(L, -1);
lua_pop(L, 1);
luaL_argcheck(L, pos+size <= ld, 2, "data string too short");
}
if (!h.noassign)
lua_pushlstring(L, data+pos, size);
break;
}
case 's': {
const char *e = (const char *)memchr(data+pos, '\0', ld - pos);
if (e == NULL)
luaL_error(L, "unfinished string in data");
size = (e - (data+pos)) + 1;
if (!h.noassign)
lua_pushlstring(L, data+pos, size - 1);
break;
}
case '=': {
lua_pushinteger(L, pos + 1);
break;
}
default: controloptions(L, opt, &fmt, &h);
}
pos += size;
}
lua_pushinteger(L, pos + 1);
WSLUA_RETURN(lua_gettop(L) - 2);
}
WSLUA_CONSTRUCTOR Struct_size (lua_State *L) {
#define WSLUA_ARG_Struct_size_FORMAT 1
Header h;
const char *fmt = wslua_checkstring_only(L, WSLUA_ARG_Struct_size_FORMAT);
size_t pos = 0;
defaultoptions(&h);
while (*fmt) {
int opt = *fmt++;
size_t size = optsize(L, opt, &fmt);
pos += gettoalign(pos, &h, opt, size);
if (opt == 's')
luaL_argerror(L, 1, "option 's' has no fixed size");
else if (opt == 'c' && size == 0)
luaL_argerror(L, 1, "option 'c0' has no fixed size");
if (!g_ascii_isalnum(opt))
controloptions(L, opt, &fmt, &h);
pos += size;
}
lua_pushinteger(L, pos);
WSLUA_RETURN(1);
}
WSLUA_CONSTRUCTOR Struct_values (lua_State *L) {
This will be the number of returned values from a call to Struct.unpack()
not including the extra return value of offset position. (i.e., Struct.values()
does not count that extra return value) This will also be the number of
arguments Struct.pack() expects, not including the format string argument. */
#define WSLUA_ARG_Struct_values_FORMAT 1
Header h;
const char *fmt = wslua_checkstring_only(L, WSLUA_ARG_Struct_values_FORMAT);
size_t vals = 0;
defaultoptions(&h);
while (*fmt) {
int opt = *fmt++;
size_t size = optsize(L, opt, &fmt);
switch (opt) {
case 's': case 'c':
size = 1;
break;
case 'x': case 'X':
size = 0;
break;
default:
break;
}
if (!g_ascii_isalnum(opt))
controloptions(L, opt, &fmt, &h);
else if (size && !h.noassign)
vals++;
}
lua_pushinteger(L, vals);
WSLUA_RETURN(1);
}
WSLUA_CONSTRUCTOR Struct_tohex (lua_State *L) {
#define WSLUA_ARG_Struct_tohex_BYTESTRING 1
#define WSLUA_OPTARG_Struct_tohex_LOWERCASE 2
#define WSLUA_OPTARG_Struct_tohex_SEPARATOR 3
const char* s = NULL;
size_t len = 0;
bool lowercase = false;
const char* sep = NULL;
just not fromhex. In fact, we should accept/coerce a Int64/UInt64 here too someday. */
s = luaL_checklstring(L, WSLUA_ARG_Struct_tohex_BYTESTRING, &len);
lowercase = wslua_optbool(L,WSLUA_OPTARG_Struct_tohex_LOWERCASE,false);
sep = luaL_optstring(L,WSLUA_OPTARG_Struct_tohex_SEPARATOR,NULL);
wslua_bin2hex(L, (const uint8_t*)s, (unsigned)len, lowercase, sep);
WSLUA_RETURN(1);
}
WSLUA_CONSTRUCTOR Struct_fromhex (lua_State *L) {
#define WSLUA_ARG_Struct_fromhex_HEXBYTES 1
#define WSLUA_OPTARG_Struct_fromhex_SEPARATOR 2
const char* s = NULL;
size_t len = 0;
const char* sep = NULL;
s = wslua_checklstring_only(L, WSLUA_ARG_Struct_fromhex_HEXBYTES, &len);
sep = luaL_optstring(L,WSLUA_OPTARG_Struct_fromhex_SEPARATOR,NULL);
wslua_hex2bin(L, s, (unsigned)len, sep);
WSLUA_RETURN(1);
}
static int Struct__gc(lua_State* L _U_) {
return 0;
}
WSLUA_METHODS Struct_methods[] = {
WSLUA_CLASS_FNREG(Struct,pack),
WSLUA_CLASS_FNREG(Struct,unpack),
WSLUA_CLASS_FNREG(Struct,size),
WSLUA_CLASS_FNREG(Struct,values),
WSLUA_CLASS_FNREG(Struct,tohex),
WSLUA_CLASS_FNREG(Struct,fromhex),
{ NULL, NULL }
};
WSLUA_META Struct_meta[] = {
{ NULL, NULL }
};
LUALIB_API int Struct_register(lua_State* L) {
WSLUA_REGISTER_CLASS(Struct);
return 0;
}
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local Variables:
* c-basic-offset: 2
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=2 tabstop=8 expandtab:
* :indentSize=2:tabSize=8:noTabs=true:
*/