2005-01-17 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / dbus / dbus-message-builder.c
index e91fa39..83b37af 100644 (file)
@@ -1,9 +1,9 @@
 /* -*- mode: C; c-file-style: "gnu" -*- */
 /* dbus-message-builder.c Build messages from text files for testing (internal to D-BUS implementation)
  * 
- * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003, 2004 Red Hat, Inc.
  *
- * Licensed under the Academic Free License version 1.2
+ * Licensed under the Academic Free License version 2.1
  * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  */
+#include <config.h>
+
 #ifdef DBUS_BUILD_TESTS
+
 #include "dbus-message-builder.h"
+#include "dbus-hash.h"
+#include "dbus-internals.h"
+#include "dbus-marshal.h"
 
 /**
  * @defgroup DBusMessageBuilder code for loading test message data
  */
 
 /**
+ * Saved length
+ */
+typedef struct
+{
+  DBusString name; /**< Name of the length */
+  int start;  /**< Calculate length since here */
+  int length; /**< length to write */
+  int offset; /**< where to write it into the data */
+  int endian; /**< endianness to write with */
+} SavedLength;
+
+static void
+free_saved_length (void *data)
+{
+  SavedLength *sl = data;
+
+  if (sl == NULL)
+    return; /* all hash free functions have to accept NULL */
+  
+  _dbus_string_free (&sl->name);
+  dbus_free (sl);
+}
+
+static SavedLength*
+ensure_saved_length (DBusHashTable    *hash,
+                     const DBusString *name)
+{
+  SavedLength *sl;
+  const char *s;
+  
+  s = _dbus_string_get_const_data (name);
+
+  sl = _dbus_hash_table_lookup_string (hash, s);
+  if (sl != NULL)
+    return sl;
+  
+  sl = dbus_new0 (SavedLength, 1);
+
+  if (!_dbus_string_init (&sl->name))
+    {
+      dbus_free (sl);
+      return NULL;
+    }
+
+  if (!_dbus_string_copy (name, 0, &sl->name, 0))
+    goto failed;
+
+  s = _dbus_string_get_const_data (&sl->name);
+
+  if (!_dbus_hash_table_insert_string (hash, (char*)s, sl))
+    goto failed;
+
+  sl->start = -1;
+  sl->length = -1;
+  sl->offset = -1;
+  sl->endian = -1;
+  
+  return sl;
+  
+ failed:
+  free_saved_length (sl);
+  return NULL;
+}
+
+static dbus_bool_t
+save_start (DBusHashTable    *hash,
+            const DBusString *name,
+            int               start)
+{
+  SavedLength *sl;
+
+  sl = ensure_saved_length (hash, name);
+
+  if (sl == NULL)
+    return FALSE;
+  else if (sl->start >= 0)
+    {
+      _dbus_warn ("Same START_LENGTH given twice\n");
+      return FALSE;
+    }
+  else
+    sl->start = start;
+
+  return TRUE;
+}
+
+static dbus_bool_t
+save_length (DBusHashTable    *hash,
+             const DBusString *name,
+             int               length)
+{
+  SavedLength *sl;
+
+  sl = ensure_saved_length (hash, name);
+
+  if (sl == NULL)
+    return FALSE;
+  else if (sl->length >= 0)
+    {
+      _dbus_warn ("Same END_LENGTH given twice\n");
+      return FALSE;
+    }
+  else
+    sl->length = length;
+
+  return TRUE;
+}
+
+static dbus_bool_t
+save_offset (DBusHashTable    *hash,
+             const DBusString *name,
+             int               offset,
+             int               endian)
+{
+  SavedLength *sl;
+
+  sl = ensure_saved_length (hash, name);
+
+  if (sl == NULL)
+    return FALSE;
+  else if (sl->offset >= 0)
+    {
+      _dbus_warn ("Same LENGTH given twice\n");
+      return FALSE;
+    }
+  else
+    {
+      sl->offset = offset;
+      sl->endian = endian;
+    }
+
+  return TRUE;
+}
+
+/** Saves the segment to delete in order to unalign the next item */
+#define SAVE_FOR_UNALIGN(str, boundary)                                 \
+  int align_pad_start = _dbus_string_get_length (str);                  \
+  int align_pad_end = _DBUS_ALIGN_VALUE (align_pad_start, (boundary))
+
+/** Deletes the alignment padding */
+#define PERFORM_UNALIGN(str)                                    \
+  if (unalign)                                                  \
+    {                                                           \
+      _dbus_string_delete ((str), align_pad_start,              \
+                           align_pad_end - align_pad_start);    \
+      unalign = FALSE;                                          \
+    }
+
+
+static dbus_bool_t
+append_quoted_string (DBusString       *dest,
+                      const DBusString *quoted,
+                     int               start_pos,
+                     int              *new_pos)
+{
+  dbus_bool_t in_quotes = FALSE;
+  int i;
+
+  /* FIXME: We might want to add escaping in case we want to put '
+   * characters in our strings.
+   */
+  
+  i = start_pos;
+  while (i < _dbus_string_get_length (quoted))
+    {
+      unsigned char b;
+
+      b = _dbus_string_get_byte (quoted, i);
+      
+      if (in_quotes)
+        {
+          if (b == '\'')
+           break;
+          else
+            {
+              if (!_dbus_string_append_byte (dest, b))
+                return FALSE;
+            }
+        }
+      else
+        {
+          if (b == '\'')
+            in_quotes = TRUE;
+          else if (b == ' ' || b == '\n' || b == '\t')
+            break; /* end on whitespace if not quoted */
+          else
+            {
+              if (!_dbus_string_append_byte (dest, b))
+                return FALSE;
+            }
+        }
+      
+      ++i;
+    }
+
+  if (new_pos)
+    *new_pos = i;
+  
+  if (!_dbus_string_append_byte (dest, '\0'))
+    return FALSE;
+  return TRUE;
+}
+
+static dbus_bool_t
+append_saved_length (DBusString       *dest,
+                     DBusHashTable    *length_hash,
+                     const DBusString *name,
+                     int               offset,
+                     int               endian)
+{
+  if (!save_offset (length_hash, name,
+                    offset, endian))
+    {
+      _dbus_warn ("failed to save offset to LENGTH\n");
+      return FALSE;
+    }
+  
+  if (!_dbus_marshal_uint32 (dest, endian,
+                             -1))
+    {
+      _dbus_warn ("failed to append a length\n");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static int
+message_type_from_string (const DBusString *str,
+                          int               start)
+{
+  const char *s;
+
+  s = _dbus_string_get_const_data_len (str, start,
+                                       _dbus_string_get_length (str) - start);
+
+  if (strncmp (s, "method_call", strlen ("method_call")) == 0)
+    return DBUS_MESSAGE_TYPE_METHOD_CALL;
+  else if (strncmp (s, "method_return", strlen ("method_return")) == 0)
+    return DBUS_MESSAGE_TYPE_METHOD_RETURN;
+  else if (strncmp (s, "signal", strlen ("signal")) == 0)
+    return DBUS_MESSAGE_TYPE_SIGNAL;
+  else if (strncmp (s, "error", strlen ("error")) == 0)
+    return DBUS_MESSAGE_TYPE_ERROR;
+  else if (strncmp (s, "invalid", strlen ("invalid")) == 0)
+    return DBUS_MESSAGE_TYPE_INVALID;
+  else
+    return -1;
+}
+
+static dbus_bool_t
+append_string_field (DBusString *dest,
+                     int         endian,
+                     int         field,
+                     int         type,
+                     const char *value)
+{
+  int len;
+  
+  if (!_dbus_string_append_byte (dest, field))
+    {
+      _dbus_warn ("couldn't append field name byte\n");
+      return FALSE;
+    }
+  
+  if (!_dbus_string_append_byte (dest, type))
+    {
+      _dbus_warn ("could not append typecode byte\n");
+      return FALSE;
+    }
+
+  len = strlen (value);
+
+  if (!_dbus_marshal_uint32 (dest, endian, len))
+    {
+      _dbus_warn ("couldn't append string length\n");
+      return FALSE;
+    }
+  
+  if (!_dbus_string_append (dest, value))
+    {
+      _dbus_warn ("couldn't append field value\n");
+      return FALSE;
+    }
+
+  if (!_dbus_string_append_byte (dest, 0))
+    {
+      _dbus_warn ("couldn't append string nul term\n");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+#ifdef DBUS_BUILD_TESTS
+/**
+ * Parses a basic type defined by type contained in a DBusString. The
+ * end_return parameter may be #NULL if you aren't interested in it. The
+ * type is parsed and stored in value_return. Return parameters are not
+ * initialized if the function returns #FALSE.
+ *
+ * @param str the string
+ * @param type the type of the basic type
+ * @param start the byte index of the start of the type
+ * @param value_return return location of the value or #NULL
+ * @param end_return return location of the end of the type, or #NULL
+ * @returns #TRUE on success
+ */
+static dbus_bool_t
+_dbus_string_parse_basic_type (const DBusString  *str,
+                              char               type,
+                              int                start,
+                              void              *value,
+                              int               *end_return)
+{
+  int end = start;
+
+  switch (type)
+    {
+    case DBUS_TYPE_BOOLEAN:
+      {
+       int len = _dbus_string_get_length (str) - start;
+       if (len >= 5 && _dbus_string_find_to (str, start, start + 5, "false", NULL))
+         {
+           end += 5;
+           *(unsigned char *) value = TRUE;
+         }
+       else if (len >= 4 && _dbus_string_find_to (str, start, start + 4, "true", NULL))
+         {
+           end += 4;
+           *(unsigned char *) value = FALSE;
+         }
+       else
+         _dbus_warn ("could not parse BOOLEAN\n");
+       break;
+      }
+    case DBUS_TYPE_BYTE:
+      {
+       long val = 0;
+
+       if (_dbus_string_get_byte (str, start) == '\'' &&
+           _dbus_string_get_length (str) >= start + 4 &&
+           _dbus_string_get_byte (str, start + 1) == '\\' &&
+           _dbus_string_get_byte (str, start + 2) == '\'' &&
+           _dbus_string_get_byte (str, start + 3) == '\'')
+         {
+           val = '\'';
+           end += 4;
+         }
+       else if (_dbus_string_get_byte (str, start) == '\'' &&
+                _dbus_string_get_length (str) >= start + 3 &&
+                _dbus_string_get_byte (str, start + 2) == '\'')
+         {
+           val = _dbus_string_get_byte (str, start + 1);
+           end += 3;
+         }
+       else
+         {
+           if (!_dbus_string_parse_int (str, start, &val, &end)) 
+             _dbus_warn ("Failed to parse integer for BYTE\n");
+         }
+
+       if (val > 255)
+         _dbus_warn ("A byte must be in range 0-255 not %ld\n", val);
+
+       *(unsigned char *) value = val;
+       break;
+      }
+    case DBUS_TYPE_INT32:
+      {
+       long val;
+       if (_dbus_string_parse_int (str, start, &val, &end))
+         *(dbus_int32_t *)value = val;
+       break;
+      }
+    case DBUS_TYPE_UINT32:
+      {
+       unsigned long val;
+       if (_dbus_string_parse_uint (str, start, &val, &end))
+         *(dbus_uint32_t *)value = val;
+       break;
+      }
+#ifdef DBUS_HAVE_INT64
+    case DBUS_TYPE_INT64:
+    case DBUS_TYPE_UINT64: 
+      /* use stroll oull */
+      _dbus_assert_not_reached ("string -> [u]int64 not supported yet");
+      break;
+#endif /* DBUS_HAVE_INT64 */
+    case DBUS_TYPE_DOUBLE:
+      _dbus_string_parse_double (str, start, value, &end);
+      break;
+    default:
+      _dbus_assert_not_reached ("not a basic type");
+      break;
+    }
+  if (end_return)
+    *end_return = end;
+
+  return end != start;
+}
+#endif /* DBUS_BUILD_TESTS */
+
+static dbus_bool_t
+parse_basic_type (DBusString *src, char type,
+                 DBusString *dest, dbus_bool_t *unalign,
+                 int endian)
+{
+  int align;
+  int align_pad_start, align_pad_end;
+  unsigned char data[16];
+
+  switch (type)
+    {
+    case DBUS_TYPE_BYTE:
+      align = 1;
+      break;
+    case DBUS_TYPE_BOOLEAN:
+    case DBUS_TYPE_UINT32:
+    case DBUS_TYPE_INT32:
+      align = 4;
+      break;
+    case DBUS_TYPE_DOUBLE:
+      align = 8;
+      break;
+    default:
+      _dbus_assert_not_reached ("not a basic type");
+      break;
+    }
+
+  align_pad_start = _dbus_string_get_length (dest);
+  align_pad_end = _DBUS_ALIGN_VALUE (align_pad_start, align);
+
+  _dbus_string_delete_first_word (src);
+
+  if (!_dbus_string_parse_basic_type (src, type, 0, data, NULL))
+    {
+      _dbus_verbose ("failed to parse type '%c'", type);
+      return FALSE;
+    }
+
+  if (!_dbus_marshal_basic_type (dest, type, data, endian))
+    {
+      _dbus_verbose ("failed to marshal type '%c'", type);
+      return FALSE;
+    }
+
+  if (*unalign)
+    {
+      _dbus_string_delete (dest, align_pad_start,
+                           align_pad_end - align_pad_start);
+      *unalign = FALSE;
+    }
+
+  return TRUE;
+}
+
+static dbus_bool_t
+parse_basic_array (DBusString *src, char type,
+                  DBusString *dest, dbus_bool_t *unalign,
+                  int endian)
+{
+  int array_align, elem_size;
+  int i, len, allocated;
+  unsigned char *values, b;
+  int values_offset;
+  int align_pad_start, align_pad_end;
+  dbus_bool_t retval = FALSE;
+
+  array_align = 4; /* length */
+  switch (type)
+    {
+    case DBUS_TYPE_BYTE:
+      elem_size = 1;
+      break;
+    case DBUS_TYPE_BOOLEAN:
+    case DBUS_TYPE_UINT32:
+    case DBUS_TYPE_INT32:
+      elem_size = 4;
+      break;
+    case DBUS_TYPE_DOUBLE:
+      array_align = 8;
+      elem_size = 8;
+      break;
+    default:
+      _dbus_assert_not_reached ("not a basic type");
+      break;
+    }
+
+  align_pad_start = _dbus_string_get_length (dest);
+  align_pad_end = _DBUS_ALIGN_VALUE (align_pad_start, array_align);
+
+  len = 0;
+  allocated = 2;
+  values = NULL;
+  values_offset = 0;
+         
+  _dbus_string_delete_first_word (src);
+  _dbus_string_skip_blank (src, 0, &i);
+  b = _dbus_string_get_byte (src, i++);
+
+  if (b != '{')
+    goto failed;
+
+  while (i < _dbus_string_get_length (src))
+    {
+      _dbus_string_skip_blank (src, i, &i);
+
+      if (!values || len == allocated - 1)
+       {
+         allocated *= 2;
+         values = dbus_realloc (values, allocated * elem_size);
+         if (!values)
+           {
+             _dbus_warn ("could not allocate memory for '%c' ARRAY\n", type);
+             goto failed;
+           }
+       }
+
+      if (!_dbus_string_parse_basic_type (src, type, i, values + values_offset, &i))
+       {
+         _dbus_warn ("could not parse integer element %d of '%c' ARRAY\n", len, type);
+         goto failed;
+       }
+
+      values_offset += elem_size;
+      len++;
+             
+      _dbus_string_skip_blank (src, i, &i);
+
+      b = _dbus_string_get_byte (src, i++);
+
+      if (b == '}')
+       break;
+      else if (b != ',')
+       goto failed;
+    }
+
+  if (!_dbus_marshal_basic_type_array (dest, type, values, len, endian))
+    {
+      _dbus_warn ("failed to append '%c' ARRAY\n", type);
+      goto failed;
+    }
+
+  if (*unalign)
+    {
+      _dbus_string_delete (dest, align_pad_start,
+                           align_pad_end - align_pad_start);
+      *unalign = FALSE;
+    }
+
+  retval = TRUE;
+
+ failed:
+  dbus_free (values);
+  return retval;
+}
+
+static char
+lookup_basic_type (const DBusString *str, dbus_bool_t *is_array)
+{
+  int i;
+  char type = DBUS_TYPE_INVALID;
+  static struct {
+    const char *name;
+    char        type;
+  } name_to_type[] = {
+    { "BYTE",    DBUS_TYPE_BYTE },
+    { "BOOLEAN", DBUS_TYPE_BOOLEAN },
+    { "INT32",   DBUS_TYPE_INT32 },
+    { "UINT32",  DBUS_TYPE_UINT32 },
+    { "DOUBLE",  DBUS_TYPE_DOUBLE }
+  };
+
+  for (i = 0; i < _DBUS_N_ELEMENTS(name_to_type); i++)
+    {
+      const char *name = name_to_type[i].name;
+      if (_dbus_string_starts_with_c_str (str, name)) 
+       {
+         int offset = strlen (name);
+         type = name_to_type[i].type;
+         if (is_array)
+           *is_array = _dbus_string_find (str, offset, "_ARRAY", NULL);
+         break;
+       }
+    }
+
+  return type;
+}
+
+/**
  * Reads the given filename, which should be in "message description
  * language" (look at some examples), and builds up the message data
  * from it.  The message data may be invalid, or valid.
  *
+ * The parser isn't very strict, it's just a hack for test programs.
+ * 
  * The file format is:
  * @code
+ *   VALID_HEADER <type> normal header; byte order, type, padding, header len, body len, serial
+ *   REQUIRED_FIELDS add required fields with placeholder values
+ *   BIG_ENDIAN switch to big endian
+ *   LITTLE_ENDIAN switch to little endian
+ *   OPPOSITE_ENDIAN switch to opposite endian
  *   ALIGN <N> aligns to the given value
  *   UNALIGN skips alignment for the next marshal
- *   BYTE <N> inserts the given integer in [0,255]
- *   SAVE_LENGTH <name> records the current length under the given name
+ *   BYTE <N> inserts the given integer in [0,255] or char in 'a' format
+ *   START_LENGTH <name> marks the start of a length to measure
+ *   END_LENGTH <name> records the length since START_LENGTH under the given name
+ *                     (or if no START_LENGTH, absolute length)
  *   LENGTH <name> inserts the saved length of the same name
+ *   CHOP <N> chops last N bytes off the data
+ *   HEADER_FIELD <fieldname> inserts a header field name byte
+ *   TYPE <typename> inserts a typecode byte 
  * @endcode
  * 
  * Following commands insert aligned data unless
  * @code
  *   INT32 <N> marshals an INT32
  *   UINT32 <N> marshals a UINT32
+ *   INT64 <N> marshals an INT64
+ *   UINT64 <N> marshals a UINT64
  *   DOUBLE <N> marshals a double
- *   STRING "Foo" marshals a string
+ *   STRING 'Foo' marshals a string
+ *   OBJECT_PATH '/foo/bar' marshals an object path
+ *   BYTE_ARRAY { 'a', 3, 4, 5, 6} marshals a BYTE array
+ *   BOOLEAN_ARRAY { false, true, false} marshals a BOOLEAN array
+ *   INT32_ARRAY { 3, 4, 5, 6} marshals an INT32 array
+ *   UINT32_ARRAY { 3, 4, 5, 6} marshals an UINT32 array
+ *   DOUBLE_ARRAY { 1.0, 2.0, 3.0, 4.0} marshals a DOUBLE array  
+ *   STRING_ARRAY { "foo", "bar", "gazonk"} marshals a STRING array  
  * @endcode
+ *
+ * @todo add support for array types INT32_ARRAY { 3, 4, 5, 6 }
+ * and so forth.
  * 
  * @param dest the string to append the message data to
  * @param filename the filename to load
@@ -65,8 +686,597 @@ dbus_bool_t
 _dbus_message_data_load (DBusString       *dest,
                          const DBusString *filename)
 {
-  /* FIXME implement */
+  DBusString file;
+  DBusError error;
+  DBusString line;
+  dbus_bool_t retval;
+  int line_no;
+  dbus_bool_t unalign;
+  DBusHashTable *length_hash;
+  int endian;
+  DBusHashIter iter;
+  char type;
+  dbus_bool_t is_array;
+  
+  retval = FALSE;
+  length_hash = NULL;
+  
+  if (!_dbus_string_init (&file))
+    return FALSE;
+
+  if (!_dbus_string_init (&line))
+    {
+      _dbus_string_free (&file);
+      return FALSE;
+    }
+
+  _dbus_verbose ("Loading %s\n", _dbus_string_get_const_data (filename));
+
+  dbus_error_init (&error);
+  if (!_dbus_file_get_contents (&file, filename, &error))
+    {
+      _dbus_warn ("Getting contents of %s failed: %s\n",
+                  _dbus_string_get_const_data (filename), error.message);
+      dbus_error_free (&error);
+      goto out;
+    }
+
+  length_hash = _dbus_hash_table_new (DBUS_HASH_STRING,
+                                      NULL,
+                                      free_saved_length);
+  if (length_hash == NULL)
+    goto out;
+  
+  endian = DBUS_COMPILER_BYTE_ORDER;
+  unalign = FALSE;
+  line_no = 0;
+ next_iteration:
+  while (_dbus_string_pop_line (&file, &line))
+    {
+      dbus_bool_t just_set_unalign;
+
+      just_set_unalign = FALSE;
+      line_no += 1;
+
+      _dbus_string_delete_leading_blanks (&line);
+
+      if (_dbus_string_get_length (&line) == 0)
+        {
+          /* empty line */
+          goto next_iteration;
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "#"))
+        {
+          /* Ignore this comment */
+          goto next_iteration;
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "VALID_HEADER"))
+        {
+          int i;
+          DBusString name;
+          int message_type;
+
+          if (_dbus_string_get_length (&line) < (int) strlen ("VALID_HEADER "))
+            {
+              _dbus_warn ("no args to VALID_HEADER\n");
+              goto parse_failed;
+            }
+          
+          if (!_dbus_string_append_byte (dest, endian))
+            {
+              _dbus_warn ("could not append endianness\n");
+              goto parse_failed;
+            }
+
+          message_type = message_type_from_string (&line,
+                                                   strlen ("VALID_HEADER "));
+          if (message_type < 0)
+            {
+              _dbus_warn ("VALID_HEADER not followed by space then known message type\n");
+              goto parse_failed;
+            }
+          
+          if (!_dbus_string_append_byte (dest, message_type))
+            {
+              _dbus_warn ("could not append message type\n");
+              goto parse_failed;
+            }
+          
+          i = 0;
+          while (i < 2)
+            {
+              if (!_dbus_string_append_byte (dest, '\0'))
+                {
+                  _dbus_warn ("could not append nul pad\n");
+                  goto parse_failed;
+                }
+              ++i;
+            }
+
+          _dbus_string_init_const (&name, "Header");
+          if (!append_saved_length (dest, length_hash,
+                                    &name, _dbus_string_get_length (dest),
+                                    endian))
+            goto parse_failed;
+
+          _dbus_string_init_const (&name, "Body");
+          if (!append_saved_length (dest, length_hash,
+                                    &name, _dbus_string_get_length (dest),
+                                    endian))
+            goto parse_failed;
+          
+          /* client serial */
+          if (!_dbus_marshal_uint32 (dest, endian, 1))
+            {
+              _dbus_warn ("couldn't append client serial\n");
+              goto parse_failed;
+            }
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "REQUIRED_FIELDS"))
+        {
+          if (!append_string_field (dest, endian,
+                                    DBUS_HEADER_FIELD_INTERFACE,
+                                    DBUS_TYPE_STRING,
+                                    "org.freedesktop.BlahBlahInterface"))
+            goto parse_failed;
+          if (!append_string_field (dest, endian,
+                                    DBUS_HEADER_FIELD_MEMBER,
+                                    DBUS_TYPE_STRING,
+                                    "BlahBlahMethod"))
+            goto parse_failed;
+          if (!append_string_field (dest, endian,
+                                    DBUS_HEADER_FIELD_PATH,
+                                    DBUS_TYPE_OBJECT_PATH,
+                                    "/blah/blah/path"))
+            goto parse_failed;
+
+          /* FIXME later we'll validate this, and then it will break
+           * and the .message files will have to include the right thing
+           */
+          if (!append_string_field (dest, endian,
+                                    DBUS_HEADER_FIELD_SIGNATURE,
+                                    DBUS_TYPE_STRING,
+                                    "iii"))
+            goto parse_failed;
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "BIG_ENDIAN"))
+        {
+          endian = DBUS_BIG_ENDIAN;
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "LITTLE_ENDIAN"))
+        {
+          endian = DBUS_LITTLE_ENDIAN;
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "OPPOSITE_ENDIAN"))
+        {
+          if (endian == DBUS_BIG_ENDIAN)
+            endian = DBUS_LITTLE_ENDIAN;
+          else
+            endian = DBUS_BIG_ENDIAN;
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "ALIGN"))
+        {
+          long val;
+          int end;
+          int orig_len;
+          
+          _dbus_string_delete_first_word (&line);
+
+          if (!_dbus_string_parse_int (&line, 0, &val, &end))
+            {
+              _dbus_warn ("Failed to parse integer\n");
+              goto parse_failed;
+            }
+
+          if (val > 8)
+            {
+              _dbus_warn ("Aligning to %ld boundary is crack\n",
+                          val);
+              goto parse_failed;
+            }
+
+          orig_len = _dbus_string_get_length (dest);
+          
+          if (!_dbus_string_align_length (dest, val))
+            goto parse_failed;
+
+          if (_dbus_string_parse_int (&line, end, &val, NULL))
+            {
+              /* If there's an optional second int argument,
+               * fill in align padding with that value
+               */
+              if (val < 0 || val > 255)
+                {
+                  _dbus_warn ("can't fill align padding with %ld, must be a byte value\n", val);
+                  goto parse_failed;
+                }
+
+              end = orig_len;
+              while (end < _dbus_string_get_length (dest))
+                {
+                  _dbus_string_set_byte (dest, end, val);
+                  ++end;
+                }
+            }
+        }
+      else if (_dbus_string_starts_with_c_str (&line, "UNALIGN"))
+        {
+          unalign = TRUE;
+          just_set_unalign = TRUE;
+        }
+      else if (_dbus_string_starts_with_c_str (&line, "CHOP"))
+        {
+          long val;
+
+          /* FIXME if you CHOP the offset for a LENGTH
+           * command, we segfault.
+           */
+          
+          _dbus_string_delete_first_word (&line);
+
+          if (!_dbus_string_parse_int (&line, 0, &val, NULL))
+            {
+              _dbus_warn ("Failed to parse integer to chop\n");
+              goto parse_failed;
+            }
+
+          if (val > _dbus_string_get_length (dest))
+            {
+              _dbus_warn ("Trying to chop %ld bytes but we only have %d\n",
+                          val,
+                          _dbus_string_get_length (dest));
+              goto parse_failed;
+            }
+          
+          _dbus_string_shorten (dest, val);
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "START_LENGTH"))
+        {
+          _dbus_string_delete_first_word (&line);
+
+          if (!save_start (length_hash, &line,
+                           _dbus_string_get_length (dest)))
+            {
+              _dbus_warn ("failed to save length start\n");
+              goto parse_failed;
+            }
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "END_LENGTH"))
+        {
+          _dbus_string_delete_first_word (&line);
+
+          if (!save_length (length_hash, &line,
+                            _dbus_string_get_length (dest)))
+            {
+              _dbus_warn ("failed to save length end\n");
+              goto parse_failed;
+            }
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "LENGTH"))
+        {
+          SAVE_FOR_UNALIGN (dest, 4);
+          
+          _dbus_string_delete_first_word (&line);
+
+          if (!append_saved_length (dest, length_hash,
+                                    &line,
+                                    unalign ? align_pad_start : align_pad_end,
+                                    endian))
+            {
+              _dbus_warn ("failed to add LENGTH\n");
+              goto parse_failed;
+            }
+
+          PERFORM_UNALIGN (dest);
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "HEADER_FIELD"))
+        {
+         int field;
+
+          _dbus_string_delete_first_word (&line);
+
+          if (_dbus_string_starts_with_c_str (&line, "INVALID"))
+            field = DBUS_HEADER_FIELD_INVALID;
+          else if (_dbus_string_starts_with_c_str (&line, "PATH"))
+           field = DBUS_HEADER_FIELD_PATH;
+          else if (_dbus_string_starts_with_c_str (&line, "INTERFACE"))
+           field = DBUS_HEADER_FIELD_INTERFACE;
+          else if (_dbus_string_starts_with_c_str (&line, "MEMBER"))
+           field = DBUS_HEADER_FIELD_MEMBER;
+          else if (_dbus_string_starts_with_c_str (&line, "ERROR_NAME"))
+           field = DBUS_HEADER_FIELD_ERROR_NAME;
+          else if (_dbus_string_starts_with_c_str (&line, "REPLY_SERIAL"))
+           field = DBUS_HEADER_FIELD_REPLY_SERIAL;
+          else if (_dbus_string_starts_with_c_str (&line, "DESTINATION"))
+           field = DBUS_HEADER_FIELD_DESTINATION;
+          else if (_dbus_string_starts_with_c_str (&line, "SENDER"))
+           field = DBUS_HEADER_FIELD_SENDER;
+          else if (_dbus_string_starts_with_c_str (&line, "SIGNATURE"))
+           field = DBUS_HEADER_FIELD_SIGNATURE;
+         else if (_dbus_string_starts_with_c_str (&line, "UNKNOWN"))
+           field = 22; /* random unknown header field */
+          else
+            {
+              _dbus_warn ("%s is not a valid header field name\n",
+                         _dbus_string_get_const_data (&line));
+              goto parse_failed;
+            }
+
+          if (!_dbus_string_append_byte (dest, field))
+           {
+              _dbus_warn ("could not append header field name byte\n");
+             goto parse_failed;
+           }
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "TYPE"))
+        {
+          int code;
+          
+          _dbus_string_delete_first_word (&line);          
+
+          if (_dbus_string_starts_with_c_str (&line, "INVALID"))
+            code = DBUS_TYPE_INVALID;
+          else if (_dbus_string_starts_with_c_str (&line, "NIL"))
+            code = DBUS_TYPE_NIL;
+         else if ((code = lookup_basic_type (&line, NULL)) != DBUS_TYPE_INVALID)
+           ;
+          else if (_dbus_string_starts_with_c_str (&line, "STRING"))
+            code = DBUS_TYPE_STRING;
+          else if (_dbus_string_starts_with_c_str (&line, "OBJECT_PATH"))
+            code = DBUS_TYPE_OBJECT_PATH;
+          else if (_dbus_string_starts_with_c_str (&line, "CUSTOM"))
+            code = DBUS_TYPE_CUSTOM;
+          else if (_dbus_string_starts_with_c_str (&line, "ARRAY"))
+            code = DBUS_TYPE_ARRAY;
+          else if (_dbus_string_starts_with_c_str (&line, "DICT"))
+            code = DBUS_TYPE_DICT;
+          else
+            {
+              _dbus_warn ("%s is not a valid type name\n", _dbus_string_get_const_data (&line));
+              goto parse_failed;
+            }
+
+          if (!_dbus_string_append_byte (dest, code))
+            {
+              _dbus_warn ("could not append typecode byte\n");
+              goto parse_failed;
+            }
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                              "STRING_ARRAY"))
+       {
+         SAVE_FOR_UNALIGN (dest, 4);
+         int i, len, allocated;
+         char **values;
+         char *val;
+         DBusString val_str;
+         unsigned char b;
+
+         allocated = 4;
+         values = dbus_new (char *, allocated);
+         if (!values)
+           {
+             _dbus_warn ("could not allocate memory for STRING_ARRAY\n");
+             goto parse_failed;
+           }
+         
+         len = 0;
+         
+         _dbus_string_delete_first_word (&line);
+         _dbus_string_skip_blank (&line, 0, &i);
+         b = _dbus_string_get_byte (&line, i++);
+
+         if (b != '{')
+           goto parse_failed;
+
+         _dbus_string_init (&val_str);
+         while (i < _dbus_string_get_length (&line))
+           {
+             _dbus_string_skip_blank (&line, i, &i);
+
+             if (!append_quoted_string (&val_str, &line, i, &i))
+               {
+                 _dbus_warn ("could not parse quoted string for STRING_ARRAY\n");
+                 goto parse_failed;
+               }
+             i++;
+
+             if (!_dbus_string_steal_data (&val_str, &val))
+               {
+                 _dbus_warn ("could not allocate memory for STRING_ARRAY string\n");
+                 goto parse_failed;
+               }
+             
+             values[len++] = val;
+             if (len == allocated)
+               {
+                 allocated *= 2;
+                 values = dbus_realloc (values, allocated * sizeof (char *));
+                 if (!values)
+                   {
+                     _dbus_warn ("could not allocate memory for STRING_ARRAY\n");
+                     goto parse_failed;
+                   }
+               }
+             
+             _dbus_string_skip_blank (&line, i, &i);
+             
+             b = _dbus_string_get_byte (&line, i++);
+
+             if (b == '}')
+               break;
+             else if (b != ',')
+               {
+                 _dbus_warn ("missing comma when parsing STRING_ARRAY\n");
+                 goto parse_failed;
+               }
+           }
+         _dbus_string_free (&val_str);
+         
+          if (!_dbus_marshal_string_array (dest, endian, (const char **)values, len))
+            {
+              _dbus_warn ("failed to append STRING_ARRAY\n");
+              goto parse_failed;
+            }
+
+         values[len] = NULL;
+         dbus_free_string_array (values);
+         
+         PERFORM_UNALIGN (dest);
+       }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                               "STRING"))
+        {
+          SAVE_FOR_UNALIGN (dest, 4);
+          int size_offset;
+          int old_len;
+          
+          _dbus_string_delete_first_word (&line);
+
+          size_offset = _dbus_string_get_length (dest);
+          size_offset = _DBUS_ALIGN_VALUE (size_offset, 4);
+          if (!_dbus_marshal_uint32 (dest, endian, 0))
+            {
+              _dbus_warn ("Failed to append string size\n");
+              goto parse_failed;
+            }
+
+          old_len = _dbus_string_get_length (dest);
+          if (!append_quoted_string (dest, &line, 0, NULL))
+            {
+              _dbus_warn ("Failed to append quoted string\n");
+              goto parse_failed;
+            }
+
+          _dbus_marshal_set_uint32 (dest, size_offset,
+                                    /* subtract 1 for nul */
+                                    _dbus_string_get_length (dest) - old_len - 1,
+                                    endian);
+          
+          PERFORM_UNALIGN (dest);
+        }
+      else if ((type = lookup_basic_type (&line, &is_array)) != DBUS_TYPE_INVALID)
+       {
+         if (is_array)
+           {
+             if (!parse_basic_array (&line, type, dest, &unalign, endian))
+               goto parse_failed;
+           }
+         else
+           {
+             if (!parse_basic_type (&line, type, dest, &unalign, endian))
+               goto parse_failed;
+           }
+        }
+      else if (_dbus_string_starts_with_c_str (&line,
+                                              "OBJECT_PATH"))
+        {
+          SAVE_FOR_UNALIGN (dest, 4);
+          int size_offset;
+          int old_len;
+          
+          _dbus_string_delete_first_word (&line);
+          
+          size_offset = _dbus_string_get_length (dest);
+          size_offset = _DBUS_ALIGN_VALUE (size_offset, 4);
+          if (!_dbus_marshal_uint32 (dest, endian, 0))
+            {
+              _dbus_warn ("Failed to append string size\n");
+              goto parse_failed;
+            }
+
+          old_len = _dbus_string_get_length (dest);
+          if (!append_quoted_string (dest, &line, 0, NULL))
+            {
+              _dbus_warn ("Failed to append quoted string\n");
+              goto parse_failed;
+            }
+
+          _dbus_marshal_set_uint32 (dest, size_offset,
+                                    /* subtract 1 for nul */
+                                    _dbus_string_get_length (dest) - old_len - 1,
+                                    endian);
+          
+          PERFORM_UNALIGN (dest);
+        }      
+      else
+        goto parse_failed;
+      
+      if (!just_set_unalign && unalign)
+        {
+          _dbus_warn ("UNALIGN prior to something that isn't aligned\n");
+          goto parse_failed;
+        }
+
+      goto next_iteration; /* skip parse_failed */
+      
+    parse_failed:
+      {
+        _dbus_warn ("couldn't process line %d \"%s\"\n",
+                    line_no, _dbus_string_get_const_data (&line));
+        goto out;
+      }
+    }
+
+  _dbus_hash_iter_init (length_hash, &iter);
+  while (_dbus_hash_iter_next (&iter))
+    {
+      SavedLength *sl = _dbus_hash_iter_get_value (&iter);
+      const char *s;
+
+      s = _dbus_string_get_const_data (&sl->name);
+      
+      if (sl->length < 0)
+        {
+          _dbus_warn ("Used LENGTH %s but never did END_LENGTH\n",
+                      s);
+          goto out;
+        }
+      else if (sl->offset < 0)
+        {
+          _dbus_warn ("Did END_LENGTH %s but never used LENGTH\n",
+                      s);
+          goto out;
+        }
+      else
+        {
+          if (sl->start < 0)
+            sl->start = 0;
+          
+          _dbus_verbose ("Filling in length %s endian = %d offset = %d start = %d length = %d\n",
+                         s, sl->endian, sl->offset, sl->start, sl->length);
+          _dbus_marshal_set_int32 (dest,
+                                   sl->endian,
+                                   sl->offset,
+                                   sl->length - sl->start);
+        }
+
+      _dbus_hash_iter_remove_entry (&iter);
+    }
+  
+  retval = TRUE;
+
+  _dbus_verbose_bytes_of_string (dest, 0, _dbus_string_get_length (dest));
+  
+ out:
+  if (length_hash != NULL)
+    _dbus_hash_table_unref (length_hash);
   
+  _dbus_string_free (&file);
+  _dbus_string_free (&line);
+  return retval;
 }
 
 /** @} */