/* GNet - Networking library * Copyright (C) 2000, 2001 David Helder * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "pack.h" #include static gsize strlenn(char* str, gsize n); static void flipmemcpy(char* dst, char* src, gsize n); #define MEMCPY(D,S,N) \ do { \ if ((N) == 1) \ *((char*) (D)) = *((char*) (S)); \ else if ((N) == 2) \ { \ ((char*) (D))[0] = ((char*) (S))[0]; \ ((char*) (D))[1] = ((char*) (S))[1]; \ } \ else if ((N) == 4) \ { \ ((char*) (D))[0] = ((char*) (S))[0]; \ ((char*) (D))[1] = ((char*) (S))[1]; \ ((char*) (D))[2] = ((char*) (S))[2]; \ ((char*) (D))[3] = ((char*) (S))[3]; \ } \ else \ { \ memcpy((D), (S), (N)); \ } \ } while(0) #define FLIPMEMCPY(D,S,N) \ do { \ if ((N) == 1) \ *((char*) (D)) = *((char*) (S)); \ else if ((N) == 2) \ { \ ((char*) (D))[0] = ((char*) (S))[1]; \ ((char*) (D))[1] = ((char*) (S))[0]; \ } \ else if ((N) == 4) \ { \ ((char*) (D))[0] = ((char*) (S))[3]; \ ((char*) (D))[1] = ((char*) (S))[2]; \ ((char*) (D))[2] = ((char*) (S))[1]; \ ((char*) (D))[3] = ((char*) (S))[0]; \ } \ else \ { \ flipmemcpy((D), (S), (N)); \ } \ } while(0) /* Get the length of string @str. @str is at most @n characters long. @str need not be NUL-terminated (which is why we pass @n). */ static gsize strlenn(char* str, gsize n) { gsize len = 0; while (*str++ && len < n) ++len; return len; } static void flipmemcpy(char* dst, char* src, gsize n) { int nn = n; for (; n; --n) *dst++ = src[nn-n]; } # if (G_BYTE_ORDER == G_LITTLE_ENDIAN) # define LEMEMCPY(D,S,N) MEMCPY(D,S,N) # define BEMEMCPY(D,S,N) FLIPMEMCPY(D,S,N) # else # define BEMEMCPY(D,S,N) MEMCPY(D,S,N) # define LEMEMCPY(D,S,N) FLIPMEMCPY(D,S,N) # endif /* **************************************** */ /* SIZE of type */ /* TYPE is actual data type, VTYPE is type according to vargs, TYPESTD is the standard type (in endian or standard mode) */ #define SIZE(TYPENATIVE, VTYPE, TYPESTD) \ do { \ if (mult == 0) mult = 1; \ n += mult * (!sizemode ? sizeof(TYPENATIVE) : sizeof(TYPESTD)); \ for (; mult != 0; mult--) { TYPENATIVE t = (TYPENATIVE) va_arg (args, VTYPE); t=t;} \ /* mult = 0; */ \ } while(0) /* PACK does MEMCPY regardless of endian */ /* TYPE is actual data type, VTYPE is type according to vargs */ #define PACK(TYPE, VTYPE) \ do { \ for (mult=(mult?mult:1); mult; --mult) \ { \ TYPE t; \ g_return_val_if_fail (n + sizeof(TYPE) <= length, -1); \ t = (TYPE) va_arg (args, VTYPE); \ MEMCPY(buffer, (char*) &t, sizeof(TYPE)); \ buffer += sizeof(TYPE); \ n += sizeof(TYPE); \ } \ mult = 0; \ } while(0) /* PACK2 does memcpy based on endian */ #define PACK2(TYPENATIVE, VTYPE, TYPESTD) \ do { \ for (mult=(mult?mult:1); mult; --mult) \ { \ if (sizemode == 0) \ { \ TYPENATIVE t; \ g_return_val_if_fail (n + sizeof(TYPENATIVE) <= length, -1);\ t = (TYPENATIVE) va_arg (args, VTYPE); \ MEMCPY(buffer, (char*) &t, sizeof(TYPENATIVE)); \ buffer += sizeof(TYPENATIVE); \ n += sizeof(TYPENATIVE); \ } \ else if (sizemode == 1) \ { \ TYPESTD t; \ g_return_val_if_fail (n + sizeof(TYPESTD) <= length, -1); \ t = (TYPESTD) va_arg (args, VTYPE); \ LEMEMCPY(buffer, (char*) &t, sizeof(TYPESTD)); \ buffer += sizeof(TYPESTD); \ n += sizeof(TYPESTD); \ } \ else if (sizemode == 2) \ { \ TYPESTD t; \ g_return_val_if_fail (n + sizeof(TYPESTD) <= length, -1); \ t = (TYPESTD) va_arg (args, VTYPE); \ BEMEMCPY(buffer, (char*) &t, sizeof(TYPESTD)); \ buffer += sizeof(TYPESTD); \ n += sizeof(TYPESTD); \ } \ } \ mult = 0; \ } while(0) /* **************************************** */ /** * gnet_pack * @format: pack data format * @buffer: buffer to pack to * @length: length of @buffer * @Varargs: variables to pack from * * Writes @Varargs to @buffer. @format is a string that describes * the @Varargs and how they are to be packed. This string is a list * of characters, each describing the type of an argument in * @Varargs. * * An example: * * * * char buf[5]; * int myint = 42; * int mybyte = 23; * &space; * gnet_pack("!ib", buf, sizeof(buf), myint, mybyte); * &comstart; Now buf is { 42, 0, 0, 0, 23 }; &comend; * * * * As a shortcut, most types can be prefixed by an integer to specify * how many times the type is repeated. For example, "4i2b" is * equivalent to "iiiibb". This repeat argument is refered below to * as REPEAT. * * Native byte order and sizes are used by default. If the first * character of @format is < then little endian order and standard * sizes are used. If the first character is > or !, then big endian * (or network) order and standard size are used. Standard sizes are * 1 byte for chars, 2 bytes for shorts, and 4 bytes for ints and * longs. * * The types: * * x is a NULL character. It can be used for padding. It does not * correspond to an argument in @Varargs. * * b/B is a signed/unsigned char. * * h/H is a signed/unsigned short. * * i/I is a signed/unsigned int. * * l/L is a signed/unsigned long. * * f/D is a float/double (always native order/size). * * v is a void pointer (always native size). * * s is a zero-terminated string. * * S is a zero-padded string of maximum length REPEAT. We write * up-to a NULL character or REPEAT characters, whichever comes * first. We then write NULL characters up to a total of REPEAT * characters. Special case: if REPEAT is not specified, we write * the string as a non-NULL-terminated string (note that it can't be * unpacked easily then). * * r is a byte array of NEXT bytes. NEXT is the next argument passed * to gnet_pack() and is a gint. * * R is a byte array of REPEAT bytes. REPEAT must be specified. * * p is a Pascal string. The string passed is a NULL-termiated * string of less than 256 character. The string writen is a * non-NULL-terminated string with a byte before the string storing * the string length. REPEAT is repeat. * * Mnemonics: (B)yte, s(H)ort, (I)nteger, (F)loat, (D)ouble, (V)oid * pointer, (S)tring, (R)aw * * Pack was inspired by Python's and Perl's pack. It is more like * Python's than Perl's. Note that in GNet, a repeat of 0 does not * align the data (as in Python). * * Returns: number of bytes packed; -1 on error. * **/ gint gnet_pack (const gchar* format, gchar* buffer, const gint length, ...) { va_list args; gint rv; va_start (args, length); rv = gnet_vpack (format, buffer, length, args); va_end (args); return rv; } /** * gnet_pack_strdup * @format: pack data format * @bufferp: pointer to a buffer (buffer is caller owned) * @Varargs: variables to pack from * * Writes @Varargs into a buffer pointed to by @bufferp. The buffer * is allocated by the function and is caller owned. See gnet_pack() * for more information. * * Returns: bytes packed; -1 on error. * **/ gint gnet_pack_strdup (const gchar* format, gchar** bufferp, ...) { va_list args; gint size; gint rv; g_return_val_if_fail (format, -1); g_return_val_if_fail (bufferp, -1); /* Get size */ va_start (args, bufferp); size = gnet_vcalcsize (format, args); va_end (args); g_return_val_if_fail (size >= 0, -1); if (size == 0) { *bufferp = NULL; return 0; } *bufferp = g_new (gchar, size); /* Pack */ va_start (args, bufferp); rv = gnet_vpack (format, *bufferp, size, args); va_end (args); return rv; } /* **************************************** */ /** * gnet_calcsize * @format: pack data format * @Varargs: variables * * Calculates the size of the buffer needed to pack @Varargs by the * given format. See gnet_pack() for more information. * * Returns: number of bytes required to pack; -1 on error. * **/ gint gnet_calcsize (const gchar* format, ...) { va_list args; gint size; va_start (args, format); size = gnet_vcalcsize (format, args); va_end (args); return size; } /** * gnet_vcalcsize * @format: pack data format * @args: var args * * Var arg interface to gnet_calcsize(). See gnet_calcsize() for * additional information. * * Returns: number of bytes required to pack; -1 on error. * **/ gint gnet_vcalcsize (const gchar* format, va_list args) { gint n = 0; gchar* p = (gchar*) format; guint mult = 0; gint sizemode = 0; /* 1 = little, 2 = big */ if (!format) return 0; switch (*p) { case '@': ++p; break; case '<': sizemode = 1; ++p; break; case '>': case '!': sizemode = 2; ++p; break; } for (; *p; ++p) { switch (*p) { case 'x': { n += mult?mult:1; mult = 0; break; } case 'b': { SIZE(gint8, int, gint8); break; } case 'B': { SIZE(guint8, unsigned int, guint8); break; } case 'h': { SIZE(short, int, gint16); break; } case 'H': { SIZE(unsigned short, unsigned int, guint16); break; } case 'i': { SIZE(int, int, gint32); break; } case 'I': { SIZE(unsigned int, unsigned int, guint32); break; } case 'l': { SIZE(long, long, gint32); break; } case 'L': { SIZE(unsigned long, unsigned long, guint32); break; } case 'f': { SIZE(float, double, float); break; } case 'd': { SIZE(double, double, double); break; } case 'v': { SIZE(void*, void*, void*); break; } case 's': { for (mult=(mult?mult:1); mult; --mult) { char* s; s = va_arg (args, char*); g_return_val_if_fail (s, -1); n += strlen(s) + 1; } mult = 0; break; } case 'S': { if (mult != 0) n += mult; else { char* s; s = va_arg (args, char*); n += strlen(s); } mult = 0; break; } case 'r': { for (mult=(mult?mult:1); mult; --mult) { char* s; int ln; s = va_arg (args, char*); g_return_val_if_fail (s, -1); ln = va_arg (args, int); n += ln; } mult = 0; break; } case 'R': { char* s; s = va_arg (args, char*); g_return_val_if_fail (s, -1); g_return_val_if_fail (mult, -1); n += mult; mult = 0; break; } case 'p': { for (mult=(mult?mult:1); mult; --mult) { char* s; int slen; s = va_arg (args, char*); g_return_val_if_fail (s, -1); slen = strlen(s); n += slen + 1; } mult = 0; break; } case '0':case '1':case '2':case '3':case '4': case '5':case '6':case '7':case '8':case '9': { mult *= 10; mult += (*p - '0'); break; } case ' ':case '\t':case '\n': break; default: g_return_val_if_fail (FALSE, -1); } } return n; } /** * gnet_vpack * @format: pack data format * @buffer: buffer to pack to * @length: length of @buffer * @args: var args * * Var arg interface to gnet_pack(). See gnet_pack() for more * information. * * Returns: bytes packed; -1 on error. * **/ gint gnet_vpack (const gchar* format, gchar* buffer, const gint length, va_list args) { gint n = 0; gchar* p = (gchar*) format; guint mult = 0; gint sizemode = 0; /* 1 = little, 2 = big */ g_return_val_if_fail (format, -1); g_return_val_if_fail (buffer, -1); g_return_val_if_fail (length, -1); switch (*p) { case '@': ++p; break; case '<': sizemode = 1; ++p; break; case '>': case '!': sizemode = 2; ++p; break; } for (; *p; ++p) { switch (*p) { case 'x': { for (mult=(mult?mult:1); mult; --mult) { g_return_val_if_fail (n + 1 <= length, -1); *buffer++ = 0; ++n; } mult = 0; break; } case 'b': { PACK(gint8, int); break; } case 'B': { PACK(guint8, unsigned int); break; } case 'h': { PACK2(short, int, gint16); break; } case 'H': { PACK2(unsigned short, unsigned int, guint16); break; } case 'i': { PACK2(int, int, gint32); break; } case 'I': { PACK2(unsigned int, unsigned int, guint32); break; } case 'l': { PACK2(long, long, gint32); break; } case 'L': { PACK2(unsigned long, unsigned long, guint32); break; } case 'f': { PACK(float, double); break; } case 'd': { PACK(double, double); break; } case 'v': { PACK2(void*, void*, void*); break; } case 's': { for (mult=(mult?mult:1); mult; --mult) { gchar* s; gsize slen; s = va_arg (args, gchar*); g_return_val_if_fail (s, -1); slen = strlen(s); g_return_val_if_fail (n + slen + 1 <= length, -1); memcpy (buffer, s, slen + 1); /* include the 0 */ buffer += slen + 1; n += slen + 1; } mult = 0; break; } case 'S': { gchar* s; s = va_arg (args, gchar*); g_return_val_if_fail (s, -1); if (!mult) { gsize slen; slen = strlen(s); g_return_val_if_fail (n + slen <= length, -1); memcpy (buffer, s, slen); /* don't include the 0 */ buffer += slen; n += slen; } else { guint i; g_return_val_if_fail (n + mult <= length, -1); for (i = 0; i < mult && s[i]; ++i) *buffer++ = s[i]; for (; i < mult; ++i) *buffer++ = 0; n += mult; } mult = 0; break; } case 'r': { for (mult=(mult?mult:1); mult; --mult) { gchar* s; guint ln; s = va_arg (args, gchar*); ln = va_arg (args, guint); g_return_val_if_fail (s, -1); g_return_val_if_fail (n + ln <= length, -1); memcpy(buffer, s, ln); buffer += ln; n += ln; } mult = 0; break; } case 'R': { gchar* s; s = va_arg (args, gchar*); g_return_val_if_fail (s, -1); g_return_val_if_fail (mult, -1); g_return_val_if_fail (n + mult <= length, -1); memcpy (buffer, s, mult); buffer += mult; n += mult; mult = 0; break; } case 'p': { for (mult=(mult?mult:1); mult; --mult) { gchar* s; gsize slen; s = va_arg (args, char*); g_return_val_if_fail (s, -1); slen = strlen(s); g_return_val_if_fail (n < 256, -1); g_return_val_if_fail (n + slen + 1 <= length, -1); *buffer++ = slen; memcpy (buffer, s, slen); buffer += slen; n += slen + 1; } mult = 0; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { mult *= 10; mult += (*p - '0'); break; } case ' ': case '\t': case '\n': break; default: g_return_val_if_fail (FALSE, -1); } } return n; } /* **************************************** */ #define UNPACK(TYPE) \ do { \ for (mult=(mult?mult:1); mult; --mult) \ { \ TYPE* t; \ g_return_val_if_fail (n + sizeof(TYPE) <= length, FALSE); \ t = va_arg (args, TYPE*); \ MEMCPY((char*) t, buffer, sizeof(TYPE)); \ buffer += sizeof(TYPE); \ n += sizeof(TYPE); \ } \ mult = 0; \ } while(0) #define UNPACK2(TYPENATIVE, TYPESTD) \ do { \ for (mult=(mult?mult:1); mult; --mult) \ { \ if (sizemode == 0) \ { \ TYPENATIVE* t; \ g_return_val_if_fail (n + sizeof(TYPENATIVE) <= length, -1); \ t = va_arg (args, TYPENATIVE*); \ MEMCPY((char*) t, buffer, sizeof(TYPENATIVE)); \ buffer += sizeof(TYPENATIVE); \ n += sizeof(TYPENATIVE); \ } \ else if (sizemode == 1) \ { \ TYPESTD* t; \ g_return_val_if_fail (n + sizeof(TYPESTD) <= length, -1); \ t = va_arg (args, TYPESTD*); \ LEMEMCPY((char*) t, buffer, sizeof(TYPESTD)); \ buffer += sizeof(TYPESTD); \ n += sizeof(TYPESTD); \ } \ else if (sizemode == 2) \ { \ TYPESTD* t; \ g_return_val_if_fail (n + sizeof(TYPESTD) <= length, -1); \ t = va_arg (args, TYPESTD*); \ BEMEMCPY((char*) t, buffer, sizeof(TYPESTD)); \ buffer += sizeof(TYPESTD); \ n += sizeof(TYPESTD); \ } \ } \ mult = 0; \ } while(0) /* **************************************** */ /** * gnet_unpack * @format: unpack data format * @buffer: buffer to unpack from * @length: length of @buffer * @Varargs: addresses of variables to unpack to * * Reads the data in @buffer into @Varargs. @format is a string that * describes the @Varargs and how they are to be packed. This string * is a list of characters, each describing the type of an argument * in @Varargs. * * An example: * * * * char buf[5] = { 42, 0, 0, 0, 23 }; * int myint; * int mybyte; * &space; * gnet_unpack("!ib", buf, sizeof(buf), &myint, &mybyte); * &comstart; Now myint is 42 and mybyte is 23 &comend; * * * * In unpack, the arguments must be pointers to the appropriate type. * Strings and byte arrays are allocated dynamicly (by g_new()). The * caller is responsible for g_free()-ing it. * * As a shortcut, most types can be prefixed by an integer to specify * how many times the type is repeated. For example, "4i2b" is * equivalent to "iiiibb". This repeat argument is refered below to * as REPEAT. * * The types: * * x is a pad byte. The byte is skipped and not stored. We do not * check its value. * * b/B is a signed/unsigned char. * * h/H is a signed/unsigned short. * * i/I is a signed/unsigned int. * * l/L is a signed/unsigned long. * * f/D is a float/double (always native order/size). * * v is a void pointer (always native size). * * s is a zero-terminated string. * * S is a zero-padded string of length REPEAT. We read REPEAT * characters or until a NULL character. Any remaining characters * are filled in with 0's. REPEAT must be specified. * * r is a byte array of NEXT bytes. NEXT is the next argument and is * a gint. REPEAT is repeat. * * R is a byte array of REPEAT bytes. REPEAT must be specified. * * p is a Pascal string. The first byte read is the length of the * string and the string follows. The unpacked string will be a * normal, NULL-terminated string. REPEAT is repeat. * * Returns: number of bytes unpacked; -1 on error. The bytes are * unpacked to the variables pointed to by the @Varargs. * **/ gint gnet_unpack (const gchar* format, gchar* buffer, gint length, ...) { va_list args; gint rv; va_start (args, length); rv = gnet_vunpack (format, buffer, length, args); va_end (args); return rv; } /** * gnet_vunpack * @format: unpack data format * @buffer: buffer to unpack from * @length: length of @buffer * @args: var args * * Var arg interface to gnet_unpack(). See gnet_unpack() for more * information. * * Returns: number of bytes packed; -1 on error. * **/ gint gnet_vunpack (const gchar* format, gchar* buffer, gint length, va_list args) { gint n = 0; gchar* p = (gchar*) format; guint mult = 0; gint sizemode = 0; /* 1 = little, 2 = big */ g_return_val_if_fail (format, -1); g_return_val_if_fail (buffer, -1); switch (*p) { case '@': ++p; break; case '<': sizemode = 1; ++p; break; case '>': case '!': sizemode = 2; ++p; break; } for (; *p; ++p) { switch (*p) { case 'x': { mult = mult? mult:1; g_return_val_if_fail (n + mult <= length, FALSE); buffer += mult; n += mult; mult = 0; break; } case 'b': { UNPACK(gint8); break; } case 'B': { UNPACK(guint8); break; } case 'h': { UNPACK2(short, gint16); break; } case 'H': { UNPACK2(unsigned short, guint16); break; } case 'i': { UNPACK2(int, gint32); break; } case 'I': { UNPACK2(unsigned int, guint32); break; } case 'l': { UNPACK2(long, gint32); break; } case 'L': { UNPACK2(unsigned long, guint32); break; } case 'f': { UNPACK(float); break; } case 'd': { UNPACK(double); break; } case 'v': { UNPACK2(void*, void*); break; } case 's': { for (mult=(mult?mult:1); mult; --mult) { gchar** sp; gsize slen; sp = va_arg (args, gchar**); g_return_val_if_fail (sp, -1); slen = strlenn(buffer, length - n); g_return_val_if_fail (n + slen <= length, FALSE); *sp = g_new(gchar, slen + 1); memcpy (*sp, buffer, slen); (*sp)[slen] = 0; buffer += slen + 1; n += slen + 1; } mult = 0; break; } case 'S': { gchar** sp; gsize slen; g_return_val_if_fail (mult, -1); sp = va_arg (args, gchar**); g_return_val_if_fail (sp, -1); slen = MIN(mult, strlenn(buffer, length - n)); g_return_val_if_fail ((n + slen) <= length, -1); *sp = g_new(gchar, mult + 1); memcpy (*sp, buffer, slen); while (slen < (mult + 1)) (*sp)[slen++] = '\0'; buffer += mult; n += mult; mult = 0; break; } case 'r': /* r is the same as s, in this case. */ { for (mult=(mult?mult:1); mult; --mult) { gchar** sp; guint ln; sp = va_arg (args, gchar**); ln = va_arg (args, guint); g_return_val_if_fail (sp, -1); g_return_val_if_fail (n + ln <= length, FALSE); *sp = g_new(char, ln); memcpy(*sp, buffer, ln); buffer += ln; n += ln; } mult = 0; break; } case 'R': { gchar** sp; sp = va_arg (args, gchar**); g_return_val_if_fail (sp, -1); g_return_val_if_fail (mult, -1); g_return_val_if_fail (n + mult <= length, -1); *sp = g_new(char, mult); memcpy(*sp, buffer, mult); buffer += mult; n += mult; mult = 0; break; } case 'p': { for (mult=(mult?mult:1); mult; --mult) { gchar** sp; guint slen; sp = va_arg (args, gchar**); g_return_val_if_fail (sp, -1); g_return_val_if_fail (n + 1 <= length, FALSE); slen = *buffer++; ++n; g_return_val_if_fail (n + slen <= length, FALSE); *sp = g_new(gchar, slen + 1); memcpy (*sp, buffer, slen); (*sp)[slen] = 0; buffer += slen; n += slen; } mult = 0; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { mult *= 10; mult += (*p - '0'); break; } case ' ': case '\t': case '\n': break; default: g_return_val_if_fail (FALSE, -1); } } return n; }