2003-01-26 Havoc Pennington <hp@pobox.com>
[platform/upstream/dbus.git] / dbus / dbus-message-builder.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-message-builder.c Build messages from text files for testing (internal to D-BUS implementation)
3  * 
4  * Copyright (C) 2003 Red Hat, Inc.
5  *
6  * Licensed under the Academic Free License version 1.2
7  * 
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  * 
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23 #include <config.h>
24
25 #ifdef DBUS_BUILD_TESTS
26
27 #include "dbus-message-builder.h"
28 #include "dbus-hash.h"
29 #include "dbus-internals.h"
30 #include "dbus-marshal.h"
31
32 /**
33  * @defgroup DBusMessageBuilder code for loading test message data
34  * @ingroup  DBusInternals
35  * @brief code for loading up test data for unit tests
36  *
37  * The code in here is used for unit testing, it loads
38  * up message data from a description in a file.
39  *
40  * @{
41  */
42
43 static dbus_bool_t
44 pop_line (DBusString *source,
45           DBusString *dest)
46 {
47   int eol;
48   
49   _dbus_string_set_length (dest, 0);
50   
51   eol = 0;
52   if (_dbus_string_find (source, 0, "\n", &eol))
53     eol += 1; /* include newline */
54   else
55     eol = _dbus_string_get_length (source);
56
57   if (eol == 0)
58     {
59       _dbus_verbose ("no more data in file\n");
60       return FALSE;
61     }
62   
63   if (!_dbus_string_move_len (source, 0, eol,
64                               dest, 0))
65     {
66       _dbus_warn ("failed to pop line\n");
67       return FALSE;
68     }
69
70   /* dump the newline */
71   _dbus_string_set_length (dest,
72                            _dbus_string_get_length (dest) - 1);
73   
74   return TRUE;
75 }
76
77 static void
78 strip_command_name (DBusString *str)
79 {
80   int i;
81   
82   i = 0;
83   if (_dbus_string_find_blank (str, 0, &i))
84     _dbus_string_skip_blank (str, i, &i);
85
86   _dbus_string_delete (str, 0, i);
87 }
88
89 static void
90 strip_leading_space (DBusString *str)
91 {
92   int i;
93   
94   i = 0;
95   _dbus_string_skip_blank (str, 0, &i);
96
97   if (i > 0)
98     _dbus_string_delete (str, 0, i);
99 }
100
101 typedef struct
102 {
103   DBusString name;
104   int length; /**< length to write */
105   int offset; /**< where to write it into the data */
106   int endian; /**< endianness to write with */
107 } SavedLength;
108
109 static void
110 free_saved_length (void *data)
111 {
112   SavedLength *sl = data;
113
114   _dbus_string_free (&sl->name);
115   dbus_free (sl);
116 }
117
118 static SavedLength*
119 ensure_saved_length (DBusHashTable    *hash,
120                      const DBusString *name)
121 {
122   SavedLength *sl;
123   const char *s;
124
125   _dbus_string_get_const_data (name, &s);
126
127   sl = _dbus_hash_table_lookup_string (hash, s);
128   if (sl != NULL)
129     return sl;
130   
131   sl = dbus_new0 (SavedLength, 1);
132
133   if (!_dbus_string_init (&sl->name, _DBUS_INT_MAX))
134     {
135       dbus_free (sl);
136       return NULL;
137     }
138
139   if (!_dbus_string_copy (name, 0, &sl->name, 0))
140     goto failed;
141
142   _dbus_string_get_const_data (&sl->name, &s);
143
144   if (!_dbus_hash_table_insert_string (hash, (char*)s, sl))
145     goto failed;
146
147   sl->length = -1;
148   sl->offset = -1;
149   sl->endian = -1;
150   
151   return sl;
152   
153  failed:
154   free_saved_length (sl);
155   return NULL;
156 }
157
158 static dbus_bool_t
159 save_length (DBusHashTable    *hash,
160              const DBusString *name,
161              int               length)
162 {
163   SavedLength *sl;
164
165   sl = ensure_saved_length (hash, name);
166
167   if (sl == NULL)
168     return FALSE;
169   else if (sl->length >= 0)
170     {
171       _dbus_warn ("Same SAVE_LENGTH given twice\n");
172       return FALSE;
173     }
174   else
175     sl->length = length;
176
177   return TRUE;
178 }
179
180 static dbus_bool_t
181 save_offset (DBusHashTable    *hash,
182              const DBusString *name,
183              int               offset,
184              int               endian)
185 {
186   SavedLength *sl;
187
188   sl = ensure_saved_length (hash, name);
189
190   if (sl == NULL)
191     return FALSE;
192   else if (sl->offset >= 0)
193     {
194       _dbus_warn ("Same LENGTH given twice\n");
195       return FALSE;
196     }
197   else
198     {
199       sl->offset = offset;
200       sl->endian = endian;
201     }
202
203   return TRUE;
204 }
205
206 /** Saves the segment to delete in order to unalign the next item */
207 #define SAVE_FOR_UNALIGN(str, boundary)                                 \
208   int align_pad_start = _dbus_string_get_length (str);                  \
209   int align_pad_end = _DBUS_ALIGN_VALUE (align_pad_start, (boundary))
210
211 /** Deletes the alignment padding */
212 #define PERFORM_UNALIGN(str)                                    \
213   if (unalign)                                                  \
214     {                                                           \
215       _dbus_string_delete ((str), align_pad_start,              \
216                            align_pad_end - align_pad_start);    \
217       unalign = FALSE;                                          \
218     }
219
220
221 static dbus_bool_t
222 append_quoted_string (DBusString       *dest,
223                       const DBusString *quoted)
224 {
225   dbus_bool_t in_quotes = FALSE;
226   int i;
227
228   i = 0;
229   while (i < _dbus_string_get_length (quoted))
230     {
231       unsigned char b;
232
233       b = _dbus_string_get_byte (quoted, i);
234       
235       if (in_quotes)
236         {
237           if (b == '\'')
238             in_quotes = FALSE;
239           else
240             {
241               if (!_dbus_string_append_byte (dest, b))
242                 return FALSE;
243             }
244         }
245       else
246         {
247           if (b == '\'')
248             in_quotes = TRUE;
249           else if (b == ' ' || b == '\n' || b == '\t')
250             break; /* end on whitespace if not quoted */
251           else
252             {
253               if (!_dbus_string_append_byte (dest, b))
254                 return FALSE;
255             }
256         }
257       
258       ++i;
259     }
260
261   if (!_dbus_string_append_byte (dest, '\0'))
262     return FALSE;
263   return TRUE;
264 }
265
266 static dbus_bool_t
267 append_saved_length (DBusString       *dest,
268                      DBusHashTable    *length_hash,
269                      const DBusString *name,
270                      int               offset,
271                      int               endian)
272 {
273   if (!save_offset (length_hash, name,
274                     offset, endian))
275     {
276       _dbus_warn ("failed to save offset to LENGTH\n");
277       return FALSE;
278     }
279   
280   if (!_dbus_marshal_int32 (dest, endian,
281                             -1))
282     {
283       _dbus_warn ("failed to append a length\n");
284       return FALSE;
285     }
286
287   return TRUE;
288 }
289
290 /**
291  * Reads the given filename, which should be in "message description
292  * language" (look at some examples), and builds up the message data
293  * from it.  The message data may be invalid, or valid.
294  *
295  * The parser isn't very strict, it's just a hack for test programs.
296  * 
297  * The file format is:
298  * @code
299  *   VALID_HEADER normal header; byte order, padding, header len, body len, serial
300  *   BIG_ENDIAN switch to big endian
301  *   LITTLE_ENDIAN switch to little endian
302  *   OPPOSITE_ENDIAN switch to opposite endian
303  *   ALIGN <N> aligns to the given value
304  *   UNALIGN skips alignment for the next marshal
305  *   BYTE <N> inserts the given integer in [0,255] or char in 'a' format
306  *   SAVE_LENGTH <name> records the current length under the given name
307  *   LENGTH <name> inserts the saved length of the same name
308  *   CHOP <N> chops last N bytes off the data
309  *   FIELD_NAME <abcd> inserts 4-byte field name
310  *   TYPE <typename> inserts a typecode byte 
311  * @endcode
312  * 
313  * Following commands insert aligned data unless
314  * preceded by "UNALIGN":
315  * @code
316  *   INT32 <N> marshals an INT32
317  *   UINT32 <N> marshals a UINT32
318  *   DOUBLE <N> marshals a double
319  *   STRING 'Foo' marshals a string
320  * @endcode
321  *
322  * @todo add support for array types INT32_ARRAY { 3, 4, 5, 6 }
323  * and so forth.
324  * 
325  * @param dest the string to append the message data to
326  * @param filename the filename to load
327  * @returns #TRUE on success
328  */
329 dbus_bool_t
330 _dbus_message_data_load (DBusString       *dest,
331                          const DBusString *filename)
332 {
333   DBusString file;
334   DBusResultCode result;
335   DBusString line;
336   dbus_bool_t retval;
337   int line_no;
338   dbus_bool_t unalign;
339   DBusHashTable *length_hash;
340   int endian;
341   DBusHashIter iter;
342   
343   retval = FALSE;
344   length_hash = NULL;
345   
346   if (!_dbus_string_init (&file, _DBUS_INT_MAX))
347     return FALSE;
348
349   if (!_dbus_string_init (&line, _DBUS_INT_MAX))
350     {
351       _dbus_string_free (&file);
352       return FALSE;
353     }
354   
355   if ((result = _dbus_file_get_contents (&file, filename)) != DBUS_RESULT_SUCCESS)
356     {
357       const char *s;
358       _dbus_string_get_const_data (filename, &s);
359       _dbus_warn ("Getting contents of %s failed: %s\n",
360                      s, dbus_result_to_string (result));
361                      
362       goto out;
363     }
364
365   length_hash = _dbus_hash_table_new (DBUS_HASH_STRING,
366                                       NULL,
367                                       free_saved_length);
368   if (length_hash == NULL)
369     goto out;
370   
371   endian = DBUS_COMPILER_BYTE_ORDER;
372   unalign = FALSE;
373   line_no = 0;
374  next_iteration:
375   while (pop_line (&file, &line))
376     {
377       dbus_bool_t just_set_unalign;
378
379       just_set_unalign = FALSE;
380       line_no += 1;
381
382       strip_leading_space (&line);
383       
384       if (_dbus_string_starts_with_c_str (&line,
385                                           "#"))
386         {
387           /* Ignore this comment */
388           goto next_iteration;
389         }
390       else if (_dbus_string_starts_with_c_str (&line,
391                                                "VALID_HEADER"))
392         {
393           int i;
394           DBusString name;
395           
396           if (!_dbus_string_append_byte (dest, endian))
397             {
398               _dbus_warn ("could not append endianness\n");
399               goto parse_failed;
400             }
401
402           i = 0;
403           while (i < 3)
404             {
405               if (!_dbus_string_append_byte (dest, '\0'))
406                 {
407                   _dbus_warn ("could not append nul pad\n");
408                   goto parse_failed;
409                 }
410               ++i;
411             }
412
413           _dbus_string_init_const (&name, "Header");
414           if (!append_saved_length (dest, length_hash,
415                                     &name, _dbus_string_get_length (dest),
416                                     endian))
417             goto parse_failed;
418
419           _dbus_string_init_const (&name, "Body");
420           if (!append_saved_length (dest, length_hash,
421                                     &name, _dbus_string_get_length (dest),
422                                     endian))
423             goto parse_failed;
424           
425           /* client serial */
426           if (!_dbus_marshal_int32 (dest, endian, 1))
427             {
428               _dbus_warn ("couldn't append client serial\n");
429               goto parse_failed;
430             }
431         }
432       else if (_dbus_string_starts_with_c_str (&line,
433                                                "BIG_ENDIAN"))
434         {
435           endian = DBUS_BIG_ENDIAN;
436         }
437       else if (_dbus_string_starts_with_c_str (&line,
438                                                "LITTLE_ENDIAN"))
439         {
440           endian = DBUS_LITTLE_ENDIAN;
441         }
442       else if (_dbus_string_starts_with_c_str (&line,
443                                                "OPPOSITE_ENDIAN"))
444         {
445           if (endian == DBUS_BIG_ENDIAN)
446             endian = DBUS_LITTLE_ENDIAN;
447           else
448             endian = DBUS_BIG_ENDIAN;
449         }
450       else if (_dbus_string_starts_with_c_str (&line,
451                                                "ALIGN"))
452         {
453           long val;
454
455           strip_command_name (&line);
456
457           if (!_dbus_string_parse_int (&line, 0, &val, NULL))
458             goto parse_failed;
459
460           if (val > 16)
461             {
462               _dbus_warn ("Aligning to %ld boundary is crack\n",
463                              val);
464               goto parse_failed;
465             }
466           
467           if (!_dbus_string_align_length (dest, val))
468             goto parse_failed;
469         }
470       else if (_dbus_string_starts_with_c_str (&line, "UNALIGN"))
471         {
472           unalign = TRUE;
473           just_set_unalign = TRUE;
474         }
475       else if (_dbus_string_starts_with_c_str (&line, "CHOP"))
476         {
477           long val;
478
479           /* FIXME if you CHOP the offset for a LENGTH
480            * command, we segfault.
481            */
482           
483           strip_command_name (&line);
484
485           if (!_dbus_string_parse_int (&line, 0, &val, NULL))
486             goto parse_failed;
487
488           if (val > _dbus_string_get_length (dest))
489             {
490               _dbus_warn ("Trying to chop %ld bytes but we only have %d\n",
491                           val,
492                           _dbus_string_get_length (dest));
493               goto parse_failed;
494             }
495           
496           _dbus_string_shorten (dest, val);
497         }
498       else if (_dbus_string_starts_with_c_str (&line, "BYTE"))
499         {
500           unsigned char the_byte;
501           
502           strip_command_name (&line);
503
504           if (_dbus_string_equal_c_str (&line, "'\\''"))
505             the_byte = '\'';
506           else if (_dbus_string_get_byte (&line, 0) == '\'' &&
507                    _dbus_string_get_length (&line) >= 3 &&
508                    _dbus_string_get_byte (&line, 2) == '\'')
509             the_byte = _dbus_string_get_byte (&line, 1);
510           else
511             {
512               long val;
513               if (!_dbus_string_parse_int (&line, 0, &val, NULL))
514                 goto parse_failed;
515               if (val > 255)
516                 {
517                   _dbus_warn ("A byte must be in range 0-255 not %ld\n",
518                                  val);
519                   goto parse_failed;
520                 }
521               the_byte = (unsigned char) val;
522             }
523
524           _dbus_string_append_byte (dest, the_byte);
525         }
526       else if (_dbus_string_starts_with_c_str (&line,
527                                                "SAVE_LENGTH"))
528         {
529           strip_command_name (&line);
530
531           if (!save_length (length_hash, &line,
532                             _dbus_string_get_length (dest)))
533             {
534               _dbus_warn ("failed to save length\n");
535               goto parse_failed;
536             }
537         }
538       else if (_dbus_string_starts_with_c_str (&line,
539                                                "LENGTH"))
540         {
541           SAVE_FOR_UNALIGN (dest, 4);
542           
543           strip_command_name (&line);
544
545           if (!append_saved_length (dest, length_hash,
546                                     &line,
547                                     unalign ? align_pad_start : align_pad_end,
548                                     endian))
549             {
550               _dbus_warn ("failed to add LENGTH\n");
551               goto parse_failed;
552             }
553
554           PERFORM_UNALIGN (dest);
555         }
556       else if (_dbus_string_starts_with_c_str (&line,
557                                                "FIELD_NAME"))
558         {
559           strip_command_name (&line);
560
561           if (_dbus_string_get_length (&line) != 4)
562             {
563               const char *s;
564               _dbus_string_get_const_data (&line, &s);
565               _dbus_warn ("Field name must be four characters not \"%s\"\n",
566                              s);
567               goto parse_failed;
568             }
569
570           if (unalign)
571             unalign = FALSE;
572           else
573             _dbus_string_align_length (dest, 4);
574           
575           if (!_dbus_string_copy (&line, 0, dest,
576                                   _dbus_string_get_length (dest)))
577             goto parse_failed;
578         }
579       else if (_dbus_string_starts_with_c_str (&line,
580                                                "TYPE"))
581         {
582           int code;
583           
584           strip_command_name (&line);          
585
586           if (_dbus_string_starts_with_c_str (&line, "INVALID"))
587             code = DBUS_TYPE_INVALID;
588           else if (_dbus_string_starts_with_c_str (&line, "NIL"))
589             code = DBUS_TYPE_NIL;
590           else if (_dbus_string_starts_with_c_str (&line, "INT32"))
591             code = DBUS_TYPE_INT32;
592           else if (_dbus_string_starts_with_c_str (&line, "UINT32"))
593             code = DBUS_TYPE_UINT32;
594           else if (_dbus_string_starts_with_c_str (&line, "DOUBLE"))
595             code = DBUS_TYPE_DOUBLE;
596           else if (_dbus_string_starts_with_c_str (&line, "STRING"))
597             code = DBUS_TYPE_STRING;
598           else if (_dbus_string_starts_with_c_str (&line, "INT32_ARRAY"))
599             code = DBUS_TYPE_INT32_ARRAY;
600           else if (_dbus_string_starts_with_c_str (&line, "UINT32_ARRAY"))
601             code = DBUS_TYPE_UINT32_ARRAY;
602           else if (_dbus_string_starts_with_c_str (&line, "DOUBLE_ARRAY"))
603             code = DBUS_TYPE_DOUBLE_ARRAY;
604           else if (_dbus_string_starts_with_c_str (&line, "BYTE_ARRAY"))
605             code = DBUS_TYPE_BYTE_ARRAY;
606           else if (_dbus_string_starts_with_c_str (&line, "STRING_ARRAY"))
607             code = DBUS_TYPE_STRING_ARRAY;
608           else
609             {
610               const char *s;
611               _dbus_string_get_const_data (&line, &s);
612               _dbus_warn ("%s is not a valid type name\n", s);
613               goto parse_failed;
614             }
615
616           if (!_dbus_string_append_byte (dest, code))
617             {
618               _dbus_warn ("could not append typecode byte\n");
619               goto parse_failed;
620             }
621         }
622       else if (_dbus_string_starts_with_c_str (&line,
623                                                "INT32"))
624         {
625           SAVE_FOR_UNALIGN (dest, 4);
626           long val;
627           
628           strip_command_name (&line);
629
630           if (!_dbus_string_parse_int (&line, 0, &val, NULL))
631             goto parse_failed;
632           
633           if (!_dbus_marshal_int32 (dest, endian,
634                                     val))
635             {
636               _dbus_warn ("failed to append INT32\n");
637               goto parse_failed;
638             }
639
640           PERFORM_UNALIGN (dest);
641         }
642       else if (_dbus_string_starts_with_c_str (&line,
643                                                "UINT32"))
644         {
645           SAVE_FOR_UNALIGN (dest, 4);
646           long val;
647           
648           strip_command_name (&line);
649
650           /* FIXME should have _dbus_string_parse_uint32 */
651           if (!_dbus_string_parse_int (&line, 0, &val, NULL))
652             goto parse_failed;
653           
654           if (!_dbus_marshal_uint32 (dest, endian,
655                                      val))
656             {
657               _dbus_warn ("failed to append UINT32\n");
658               goto parse_failed;
659             }
660
661           PERFORM_UNALIGN (dest);
662         }
663       else if (_dbus_string_starts_with_c_str (&line,
664                                                "DOUBLE"))
665         {
666           SAVE_FOR_UNALIGN (dest, 8);
667           double val;
668           
669           strip_command_name (&line);
670
671           if (!_dbus_string_parse_double (&line, 0, &val, NULL))
672             goto parse_failed;
673           
674           if (!_dbus_marshal_double (dest, endian,
675                                      val))
676             {
677               _dbus_warn ("failed to append DOUBLE\n");
678               goto parse_failed;
679             }
680
681           PERFORM_UNALIGN (dest);
682         }
683       else if (_dbus_string_starts_with_c_str (&line,
684                                                "STRING"))
685         {
686           SAVE_FOR_UNALIGN (dest, 4);
687           int size_offset;
688           int old_len;
689           
690           strip_command_name (&line);
691
692           size_offset = _dbus_string_get_length (dest);
693           size_offset = _DBUS_ALIGN_VALUE (size_offset, 4);
694           if (!_dbus_marshal_uint32 (dest, endian, 0))
695             {
696               _dbus_warn ("Failed to append string size\n");
697               goto parse_failed;
698             }
699
700           old_len = _dbus_string_get_length (dest);
701           if (!append_quoted_string (dest, &line))
702             {
703               _dbus_warn ("Failed to append quoted string\n");
704               goto parse_failed;
705             }
706
707           _dbus_marshal_set_uint32 (dest, endian, size_offset,
708                                     /* subtract 1 for nul */
709                                     _dbus_string_get_length (dest) - old_len - 1);
710           
711           PERFORM_UNALIGN (dest);
712         }
713       else
714         goto parse_failed;
715       
716       if (!just_set_unalign && unalign)
717         {
718           _dbus_warn ("UNALIGN prior to something that isn't aligned\n");
719           goto parse_failed;
720         }
721
722       goto next_iteration; /* skip parse_failed */
723       
724     parse_failed:
725       {
726         const char *s;
727         _dbus_string_get_const_data (&line, &s);
728         _dbus_warn ("couldn't process line %d \"%s\"\n",
729                     line_no, s);
730         goto out;
731       }
732     }
733
734   _dbus_hash_iter_init (length_hash, &iter);
735   while (_dbus_hash_iter_next (&iter))
736     {
737       SavedLength *sl = _dbus_hash_iter_get_value (&iter);
738       const char *s;
739
740       _dbus_string_get_const_data (&sl->name, &s);
741       
742       if (sl->length < 0)
743         {
744           _dbus_warn ("Used LENGTH %s but never did SAVE_LENGTH\n",
745                       s);
746           goto out;
747         }
748       else if (sl->offset < 0)
749         {
750           _dbus_warn ("Did SAVE_LENGTH %s but never used LENGTH\n",
751                       s);
752           goto out;
753         }
754       else
755         {
756           _dbus_verbose ("Filling in length %s endian = %d offset = %d length = %d\n",
757                          s, sl->endian, sl->offset, sl->length);
758           _dbus_marshal_set_int32 (dest,
759                                    sl->endian,
760                                    sl->offset,
761                                    sl->length);
762         }
763     }
764   
765   retval = TRUE;
766   
767  out:
768   if (length_hash != NULL)
769     _dbus_hash_table_unref (length_hash);
770   
771   _dbus_string_free (&file);
772   _dbus_string_free (&line);
773   return retval;
774 }
775
776 /** @} */
777 #endif /* DBUS_BUILD_TESTS */