-Merge branch 'master' of ssh://gnunet.org/gnunet into gsoc2018/rest_api
[oweals/gnunet.git] / src / util / container_meta_data.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14     
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20  * @file util/container_meta_data.c
21  * @brief Storing of meta data
22  * @author Christian Grothoff
23  */
24
25 #include "platform.h"
26 #include "gnunet_util_lib.h"
27 #if HAVE_EXTRACTOR_H
28 #include <extractor.h>
29 #endif
30 #include <zlib.h>
31
32 #define LOG(kind,...) GNUNET_log_from (kind, "util-container-meta-data", __VA_ARGS__)
33
34
35
36 /**
37  * Try to compress the given block of data using libz.  Only returns
38  * the compressed block if compression worked and the new block is
39  * actually smaller.  Decompress using #GNUNET_decompress().
40  *
41  * @param data block to compress; if compression
42  *        resulted in a smaller block, the first
43  *        bytes of data are updated to the compressed
44  *        data
45  * @param old_size number of bytes in data
46  * @param[out] result set to the compressed data, if compression worked
47  * @param[out] new_size set to size of result, if compression worked
48  * @return #GNUNET_YES if compression reduce the size,
49  *         #GNUNET_NO if compression did not help
50  */
51 int
52 GNUNET_try_compression (const char *data,
53                         size_t old_size,
54                         char **result,
55                         size_t *new_size)
56 {
57   char *tmp;
58   uLongf dlen;
59
60   *result = NULL;
61   *new_size = 0;
62 #ifdef compressBound
63   dlen = compressBound (old_size);
64 #else
65   dlen = old_size + (old_size / 100) + 20;
66   /* documentation says 100.1% oldSize + 12 bytes, but we
67    * should be able to overshoot by more to be safe */
68 #endif
69   tmp = GNUNET_malloc (dlen);
70   if (Z_OK ==
71       compress2 ((Bytef *) tmp,
72                  &dlen,
73                  (const Bytef *) data,
74                  old_size, 9))
75   {
76     if (dlen < old_size)
77     {
78       *result = tmp;
79       *new_size = dlen;
80       return GNUNET_YES;
81     }
82   }
83   GNUNET_free (tmp);
84   return GNUNET_NO;
85 }
86
87
88 /**
89  * Decompress input, return the decompressed data as output.  Dual to
90  * #GNUNET_try_compression(). Caller must set @a output_size to the
91  * number of bytes that were originally compressed.
92  *
93  * @param input compressed data
94  * @param input_size number of bytes in input
95  * @param output_size expected size of the output
96  * @return NULL on error, buffer of @a output_size decompressed bytes otherwise
97  */
98 char *
99 GNUNET_decompress (const char *input,
100                    size_t input_size,
101                    size_t output_size)
102 {
103   char *output;
104   uLongf olen;
105
106   olen = output_size;
107   output = GNUNET_malloc (olen);
108   if (Z_OK ==
109       uncompress ((Bytef *) output,
110                   &olen,
111                   (const Bytef *) input,
112                   input_size))
113     return output;
114   GNUNET_free (output);
115   return NULL;
116 }
117
118
119 /**
120  * Meta data item.
121  */
122 struct MetaItem
123 {
124   /**
125    * This is a doubly linked list.
126    */
127   struct MetaItem *next;
128
129   /**
130    * This is a doubly linked list.
131    */
132   struct MetaItem *prev;
133
134   /**
135    * Name of the extracting plugin.
136    */
137   char *plugin_name;
138
139   /**
140    * Mime-type of data.
141    */
142   char *mime_type;
143
144   /**
145    * The actual meta data.
146    */
147   char *data;
148
149   /**
150    * Number of bytes in 'data'.
151    */
152   size_t data_size;
153
154   /**
155    * Type of the meta data.
156    */
157   enum EXTRACTOR_MetaType type;
158
159   /**
160    * Format of the meta data.
161    */
162   enum EXTRACTOR_MetaFormat format;
163
164 };
165
166 /**
167  * Meta data to associate with a file, directory or namespace.
168  */
169 struct GNUNET_CONTAINER_MetaData
170 {
171   /**
172    * Head of linked list of the meta data items.
173    */
174   struct MetaItem *items_head;
175
176   /**
177    * Tail of linked list of the meta data items.
178    */
179   struct MetaItem *items_tail;
180
181   /**
182    * Complete serialized and compressed buffer of the items.
183    * NULL if we have not computed that buffer yet.
184    */
185   char *sbuf;
186
187   /**
188    * Number of bytes in 'sbuf'. 0 if the buffer is stale.
189    */
190   size_t sbuf_size;
191
192   /**
193    * Number of items in the linked list.
194    */
195   unsigned int item_count;
196
197 };
198
199
200 /**
201  * Create a fresh struct CONTAINER_MetaData token.
202  *
203  * @return empty meta-data container
204  */
205 struct GNUNET_CONTAINER_MetaData *
206 GNUNET_CONTAINER_meta_data_create ()
207 {
208   return GNUNET_new (struct GNUNET_CONTAINER_MetaData);
209 }
210
211
212 /**
213  * Free meta data item.
214  *
215  * @param mi item to free
216  */
217 static void
218 meta_item_free (struct MetaItem *mi)
219 {
220   GNUNET_free_non_null (mi->plugin_name);
221   GNUNET_free_non_null (mi->mime_type);
222   GNUNET_free_non_null (mi->data);
223   GNUNET_free (mi);
224 }
225
226
227 /**
228  * The meta data has changed, invalidate its serialization
229  * buffer.
230  *
231  * @param md meta data that changed
232  */
233 static void
234 invalidate_sbuf (struct GNUNET_CONTAINER_MetaData *md)
235 {
236   if (NULL == md->sbuf)
237     return;
238   GNUNET_free (md->sbuf);
239   md->sbuf = NULL;
240   md->sbuf_size = 0;
241 }
242
243
244 /**
245  * Free meta data.
246  *
247  * @param md what to free
248  */
249 void
250 GNUNET_CONTAINER_meta_data_destroy (struct GNUNET_CONTAINER_MetaData *md)
251 {
252   struct MetaItem *pos;
253
254   if (NULL == md)
255     return;
256   while (NULL != (pos = md->items_head))
257   {
258     GNUNET_CONTAINER_DLL_remove (md->items_head, md->items_tail, pos);
259     meta_item_free (pos);
260   }
261   GNUNET_free_non_null (md->sbuf);
262   GNUNET_free (md);
263 }
264
265
266 /**
267  * Remove all items in the container.
268  *
269  * @param md metadata to manipulate
270  */
271 void
272 GNUNET_CONTAINER_meta_data_clear (struct GNUNET_CONTAINER_MetaData *md)
273 {
274   struct MetaItem *mi;
275
276   if (NULL == md)
277     return;
278   while (NULL != (mi = md->items_head))
279   {
280     GNUNET_CONTAINER_DLL_remove (md->items_head, md->items_tail, mi);
281     meta_item_free (mi);
282   }
283   GNUNET_free_non_null (md->sbuf);
284   memset (md, 0, sizeof (struct GNUNET_CONTAINER_MetaData));
285 }
286
287
288 /**
289  * Test if two MDs are equal.  We consider them equal if
290  * the meta types, formats and content match (we do not
291  * include the mime types and plugins names in this
292  * consideration).
293  *
294  * @param md1 first value to check
295  * @param md2 other value to check
296  * @return #GNUNET_YES if they are equal
297  */
298 int
299 GNUNET_CONTAINER_meta_data_test_equal (const struct GNUNET_CONTAINER_MetaData
300                                        *md1,
301                                        const struct GNUNET_CONTAINER_MetaData
302                                        *md2)
303 {
304   struct MetaItem *i;
305   struct MetaItem *j;
306   int found;
307
308   if (md1 == md2)
309     return GNUNET_YES;
310   if (md1->item_count != md2->item_count)
311     return GNUNET_NO;
312   for (i = md1->items_head; NULL != i; i = i->next)
313   {
314     found = GNUNET_NO;
315     for (j = md2->items_head; NULL != j; j = j->next)
316     {
317       if ((i->type == j->type) && (i->format == j->format) &&
318           (i->data_size == j->data_size) &&
319           (0 == memcmp (i->data, j->data, i->data_size)))
320       {
321         found = GNUNET_YES;
322         break;
323       }
324       if (j->data_size < i->data_size)
325         break; /* elements are sorted by (decreasing) size... */
326     }
327     if (GNUNET_NO == found)
328       return GNUNET_NO;
329   }
330   return GNUNET_YES;
331 }
332
333
334 /**
335  * Extend metadata.  Note that the list of meta data items is
336  * sorted by size (largest first).
337  *
338  * @param md metadata to extend
339  * @param plugin_name name of the plugin that produced this value;
340  *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
341  *        used in the main libextractor library and yielding
342  *        meta data).
343  * @param type libextractor-type describing the meta data
344  * @param format basic format information about data
345  * @param data_mime_type mime-type of data (not of the original file);
346  *        can be NULL (if mime-type is not known)
347  * @param data actual meta-data found
348  * @param data_size number of bytes in @a data
349  * @return #GNUNET_OK on success, #GNUNET_SYSERR if this entry already exists
350  *         data_mime_type and plugin_name are not considered for "exists" checks
351  */
352 int
353 GNUNET_CONTAINER_meta_data_insert (struct GNUNET_CONTAINER_MetaData *md,
354                                    const char *plugin_name,
355                                    enum EXTRACTOR_MetaType type,
356                                    enum EXTRACTOR_MetaFormat format,
357                                    const char *data_mime_type, const char *data,
358                                    size_t data_size)
359 {
360   struct MetaItem *pos;
361   struct MetaItem *mi;
362   char *p;
363
364   if ((EXTRACTOR_METAFORMAT_UTF8 == format) ||
365       (EXTRACTOR_METAFORMAT_C_STRING == format))
366     GNUNET_break ('\0' == data[data_size - 1]);
367
368   for (pos = md->items_head; NULL != pos; pos = pos->next)
369   {
370     if (pos->data_size < data_size)
371       break; /* elements are sorted by size in the list */
372     if ((pos->type == type) && (pos->data_size == data_size) &&
373         (0 == memcmp (pos->data, data, data_size)))
374     {
375       if ((NULL == pos->mime_type) && (NULL != data_mime_type))
376       {
377         pos->mime_type = GNUNET_strdup (data_mime_type);
378         invalidate_sbuf (md);
379       }
380       if ((EXTRACTOR_METAFORMAT_C_STRING == pos->format) &&
381           (EXTRACTOR_METAFORMAT_UTF8 == format))
382       {
383         pos->format = EXTRACTOR_METAFORMAT_UTF8;
384         invalidate_sbuf (md);
385       }
386       return GNUNET_SYSERR;
387     }
388   }
389   md->item_count++;
390   mi = GNUNET_new (struct MetaItem);
391   mi->type = type;
392   mi->format = format;
393   mi->data_size = data_size;
394   if (NULL == pos)
395     GNUNET_CONTAINER_DLL_insert_tail (md->items_head,
396                                       md->items_tail,
397                                       mi);
398   else
399     GNUNET_CONTAINER_DLL_insert_after (md->items_head,
400                                        md->items_tail,
401                                        pos->prev,
402                                        mi);
403   mi->mime_type =
404       (NULL == data_mime_type) ? NULL : GNUNET_strdup (data_mime_type);
405   mi->plugin_name = (NULL == plugin_name) ? NULL : GNUNET_strdup (plugin_name);
406   mi->data = GNUNET_malloc (data_size);
407   GNUNET_memcpy (mi->data, data, data_size);
408   /* change all dir separators to POSIX style ('/') */
409   if ( (EXTRACTOR_METATYPE_FILENAME == type) ||
410        (EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME == type) )
411   {
412     p = mi->data;
413     while (('\0' != *p) && (p < mi->data + data_size))
414     {
415       if ('\\' == *p)
416         *p = '/';
417       p++;
418     }
419   }
420   invalidate_sbuf (md);
421   return GNUNET_OK;
422 }
423
424
425 /**
426  * Merge given meta data.
427  *
428  * @param cls the `struct GNUNET_CONTAINER_MetaData` to merge into
429  * @param plugin_name name of the plugin that produced this value;
430  *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
431  *        used in the main libextractor library and yielding
432  *        meta data).
433  * @param type libextractor-type describing the meta data
434  * @param format basic format information about data
435  * @param data_mime_type mime-type of data (not of the original file);
436  *        can be NULL (if mime-type is not known)
437  * @param data actual meta-data found
438  * @param data_size number of bytes in @a data
439  * @return 0 (to continue)
440  */
441 static int
442 merge_helper (void *cls, const char *plugin_name, enum EXTRACTOR_MetaType type,
443               enum EXTRACTOR_MetaFormat format, const char *data_mime_type,
444               const char *data, size_t data_size)
445 {
446   struct GNUNET_CONTAINER_MetaData *md = cls;
447
448   (void) GNUNET_CONTAINER_meta_data_insert (md, plugin_name, type, format,
449                                             data_mime_type, data, data_size);
450   return 0;
451 }
452
453
454 /**
455  * Extend metadata.  Merges the meta data from the second argument
456  * into the first, discarding duplicate key-value pairs.
457  *
458  * @param md metadata to extend
459  * @param in metadata to merge
460  */
461 void
462 GNUNET_CONTAINER_meta_data_merge (struct GNUNET_CONTAINER_MetaData *md,
463                                   const struct GNUNET_CONTAINER_MetaData *in)
464 {
465   GNUNET_CONTAINER_meta_data_iterate (in, &merge_helper, md);
466 }
467
468
469 /**
470  * Remove an item.
471  *
472  * @param md metadata to manipulate
473  * @param type type of the item to remove
474  * @param data specific value to remove, NULL to remove all
475  *        entries of the given type
476  * @param data_size number of bytes in @a data
477  * @return #GNUNET_OK on success, #GNUNET_SYSERR if the item does not exist in md
478  */
479 int
480 GNUNET_CONTAINER_meta_data_delete (struct GNUNET_CONTAINER_MetaData *md,
481                                    enum EXTRACTOR_MetaType type,
482                                    const char *data, size_t data_size)
483 {
484   struct MetaItem *pos;
485
486   for (pos = md->items_head; NULL != pos; pos = pos->next)
487   {
488     if (pos->data_size < data_size)
489       break; /* items are sorted by (decreasing) size */
490     if ((pos->type == type) &&
491         ((NULL == data) ||
492          ((pos->data_size == data_size) &&
493           (0 == memcmp (pos->data, data, data_size)))))
494     {
495       GNUNET_CONTAINER_DLL_remove (md->items_head, md->items_tail, pos);
496       meta_item_free (pos);
497       md->item_count--;
498       invalidate_sbuf (md);
499       return GNUNET_OK;
500     }
501   }
502   return GNUNET_SYSERR;
503 }
504
505
506 /**
507  * Add the current time as the publication date
508  * to the meta-data.
509  *
510  * @param md metadata to modify
511  */
512 void
513 GNUNET_CONTAINER_meta_data_add_publication_date (struct
514                                                  GNUNET_CONTAINER_MetaData *md)
515 {
516   const char *dat;
517   struct GNUNET_TIME_Absolute t;
518
519   t = GNUNET_TIME_absolute_get ();
520   GNUNET_CONTAINER_meta_data_delete (md,
521                                      EXTRACTOR_METATYPE_PUBLICATION_DATE,
522                                      NULL, 0);
523   dat = GNUNET_STRINGS_absolute_time_to_string (t);
524   GNUNET_CONTAINER_meta_data_insert (md, "<gnunet>",
525                                      EXTRACTOR_METATYPE_PUBLICATION_DATE,
526                                      EXTRACTOR_METAFORMAT_UTF8, "text/plain",
527                                      dat, strlen (dat) + 1);
528 }
529
530
531 /**
532  * Iterate over MD entries.
533  *
534  * @param md metadata to inspect
535  * @param iter function to call on each entry
536  * @param iter_cls closure for iterator
537  * @return number of entries
538  */
539 int
540 GNUNET_CONTAINER_meta_data_iterate (const struct GNUNET_CONTAINER_MetaData *md,
541                                     EXTRACTOR_MetaDataProcessor iter,
542                                     void *iter_cls)
543 {
544   struct MetaItem *pos;
545
546   if (NULL == md)
547     return 0;
548   if (NULL == iter)
549     return md->item_count;
550   for (pos = md->items_head; NULL != pos; pos = pos->next)
551     if (0 !=
552         iter (iter_cls, pos->plugin_name, pos->type, pos->format,
553               pos->mime_type, pos->data, pos->data_size))
554       return md->item_count;
555   return md->item_count;
556 }
557
558
559 /**
560  * Get the first MD entry of the given type.  Caller
561  * is responsible for freeing the return value.
562  * Also, only meta data items that are strings (0-terminated)
563  * are returned by this function.
564  *
565  * @param md metadata to inspect
566  * @param type type to look for
567  * @return NULL if no entry was found
568  */
569 char *
570 GNUNET_CONTAINER_meta_data_get_by_type (const struct GNUNET_CONTAINER_MetaData *md,
571                                         enum EXTRACTOR_MetaType type)
572 {
573   struct MetaItem *pos;
574
575   if (NULL == md)
576     return NULL;
577   for (pos = md->items_head; NULL != pos; pos = pos->next)
578     if ((type == pos->type) &&
579         ((pos->format == EXTRACTOR_METAFORMAT_UTF8) ||
580          (pos->format == EXTRACTOR_METAFORMAT_C_STRING)))
581       return GNUNET_strdup (pos->data);
582   return NULL;
583 }
584
585
586 /**
587  * Get the first matching MD entry of the given types. Caller is
588  * responsible for freeing the return value.  Also, only meta data
589  * items that are strings (0-terminated) are returned by this
590  * function.
591  *
592  * @param md metadata to inspect
593  * @param ... -1-terminated list of types
594  * @return NULL if we do not have any such entry,
595  *  otherwise client is responsible for freeing the value!
596  */
597 char *
598 GNUNET_CONTAINER_meta_data_get_first_by_types (const struct
599                                                GNUNET_CONTAINER_MetaData *md,
600                                                ...)
601 {
602   char *ret;
603   va_list args;
604   int type;
605
606   if (NULL == md)
607     return NULL;
608   ret = NULL;
609   va_start (args, md);
610   while (1)
611   {
612     type = va_arg (args, int);
613     if (-1 == type)
614       break;
615     if (NULL != (ret = GNUNET_CONTAINER_meta_data_get_by_type (md, type)))
616       break;
617   }
618   va_end (args);
619   return ret;
620 }
621
622
623 /**
624  * Get a thumbnail from the meta-data (if present).
625  *
626  * @param md metadata to get the thumbnail from
627  * @param thumb will be set to the thumbnail data.  Must be
628  *        freed by the caller!
629  * @return number of bytes in thumbnail, 0 if not available
630  */
631 size_t
632 GNUNET_CONTAINER_meta_data_get_thumbnail (const struct GNUNET_CONTAINER_MetaData
633                                           * md, unsigned char **thumb)
634 {
635   struct MetaItem *pos;
636   struct MetaItem *match;
637
638   if (NULL == md)
639     return 0;
640   match = NULL;
641   for (pos = md->items_head; NULL != pos; pos = pos->next)
642   {
643     if ((NULL != pos->mime_type) &&
644         (0 == strncasecmp ("image/", pos->mime_type, strlen ("image/"))) &&
645         (EXTRACTOR_METAFORMAT_BINARY == pos->format))
646     {
647       if (NULL == match)
648         match = pos;
649       else if ((match->type != EXTRACTOR_METATYPE_THUMBNAIL) &&
650                (pos->type == EXTRACTOR_METATYPE_THUMBNAIL))
651         match = pos;
652     }
653   }
654   if ((NULL == match) || (0 == match->data_size))
655     return 0;
656   *thumb = GNUNET_malloc (match->data_size);
657   GNUNET_memcpy (*thumb, match->data, match->data_size);
658   return match->data_size;
659 }
660
661
662 /**
663  * Duplicate a `struct GNUNET_CONTAINER_MetaData`.
664  *
665  * @param md what to duplicate
666  * @return duplicate meta-data container
667  */
668 struct GNUNET_CONTAINER_MetaData *
669 GNUNET_CONTAINER_meta_data_duplicate (const struct GNUNET_CONTAINER_MetaData
670                                       *md)
671 {
672   struct GNUNET_CONTAINER_MetaData *ret;
673   struct MetaItem *pos;
674
675   if (NULL == md)
676     return NULL;
677   ret = GNUNET_CONTAINER_meta_data_create ();
678   for (pos = md->items_tail; NULL != pos; pos = pos->prev)
679     GNUNET_CONTAINER_meta_data_insert (ret, pos->plugin_name, pos->type,
680                                        pos->format, pos->mime_type, pos->data,
681                                        pos->data_size);
682   return ret;
683 }
684
685
686 /**
687  * Flag in 'version' that indicates compressed meta-data.
688  */
689 #define HEADER_COMPRESSED 0x80000000
690
691
692 /**
693  * Bits in 'version' that give the version number.
694  */
695 #define HEADER_VERSION_MASK 0x7FFFFFFF
696
697
698 /**
699  * Header for serialized meta data.
700  */
701 struct MetaDataHeader
702 {
703   /**
704    * The version of the MD serialization.  The highest bit is used to
705    * indicate compression.
706    *
707    * Version 0 is traditional (pre-0.9) meta data (unsupported)
708    * Version is 1 for a NULL pointer
709    * Version 2 is for 0.9.x (and possibly higher)
710    * Other version numbers are not yet defined.
711    */
712   uint32_t version;
713
714   /**
715    * How many MD entries are there?
716    */
717   uint32_t entries;
718
719   /**
720    * Size of the decompressed meta data.
721    */
722   uint32_t size;
723
724   /**
725    * This is followed by 'entries' values of type 'struct MetaDataEntry'
726    * and then by 'entry' plugin names, mime-types and data blocks
727    * as specified in those meta data entries.
728    */
729 };
730
731
732 /**
733  * Entry of serialized meta data.
734  */
735 struct MetaDataEntry
736 {
737   /**
738    * Meta data type.  Corresponds to an 'enum EXTRACTOR_MetaType'
739    */
740   uint32_t type;
741
742   /**
743    * Meta data format. Corresponds to an 'enum EXTRACTOR_MetaFormat'
744    */
745   uint32_t format;
746
747   /**
748    * Number of bytes of meta data.
749    */
750   uint32_t data_size;
751
752   /**
753    * Number of bytes in the plugin name including 0-terminator.  0 for NULL.
754    */
755   uint32_t plugin_name_len;
756
757   /**
758    * Number of bytes in the mime type including 0-terminator.  0 for NULL.
759    */
760   uint32_t mime_type_len;
761
762 };
763
764
765 /**
766  * Serialize meta-data to target.
767  *
768  * @param md metadata to serialize
769  * @param target where to write the serialized metadata;
770  *         *target can be NULL, in which case memory is allocated
771  * @param max maximum number of bytes available in target
772  * @param opt is it ok to just write SOME of the
773  *        meta-data to match the size constraint,
774  *        possibly discarding some data?
775  * @return number of bytes written on success,
776  *         #GNUNET_SYSERR on error (typically: not enough
777  *         space)
778  */
779 ssize_t
780 GNUNET_CONTAINER_meta_data_serialize (const struct GNUNET_CONTAINER_MetaData
781                                       *md, char **target, size_t max,
782                                       enum
783                                       GNUNET_CONTAINER_MetaDataSerializationOptions
784                                       opt)
785 {
786   struct GNUNET_CONTAINER_MetaData *vmd;
787   struct MetaItem *pos;
788   struct MetaDataHeader ihdr;
789   struct MetaDataHeader *hdr;
790   struct MetaDataEntry *ent;
791   char *dst;
792   unsigned int i;
793   uint64_t msize;
794   size_t off;
795   char *mdata;
796   char *cdata;
797   size_t mlen;
798   size_t plen;
799   size_t size;
800   size_t left;
801   size_t clen;
802   size_t rlen;
803   int comp;
804
805   if (max < sizeof (struct MetaDataHeader))
806     return GNUNET_SYSERR;       /* far too small */
807   if (NULL == md)
808     return 0;
809
810   if (NULL != md->sbuf)
811   {
812     /* try to use serialization cache */
813     if (md->sbuf_size <= max)
814     {
815       if (NULL == *target)
816         *target = GNUNET_malloc (md->sbuf_size);
817       GNUNET_memcpy (*target, md->sbuf, md->sbuf_size);
818       return md->sbuf_size;
819     }
820     if (0 == (opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_PART))
821       return GNUNET_SYSERR;     /* can say that this will fail */
822     /* need to compute a partial serialization, sbuf useless ... */
823   }
824   dst = NULL;
825   msize = 0;
826   for (pos = md->items_tail; NULL != pos; pos = pos->prev)
827   {
828     msize += sizeof (struct MetaDataEntry);
829     msize += pos->data_size;
830     if (NULL != pos->plugin_name)
831       msize += strlen (pos->plugin_name) + 1;
832     if (NULL != pos->mime_type)
833       msize += strlen (pos->mime_type) + 1;
834   }
835   size = (size_t) msize;
836   if (size != msize)
837   {
838     GNUNET_break (0);           /* integer overflow */
839     return GNUNET_SYSERR;
840   }
841   if (size >= GNUNET_MAX_MALLOC_CHECKED)
842   {
843     /* too large to be processed */
844     return GNUNET_SYSERR;
845   }
846   ent = GNUNET_malloc (size);
847   mdata = (char *) &ent[md->item_count];
848   off = size - (md->item_count * sizeof (struct MetaDataEntry));
849   i = 0;
850   for (pos = md->items_head; NULL != pos; pos = pos->next)
851   {
852     ent[i].type = htonl ((uint32_t) pos->type);
853     ent[i].format = htonl ((uint32_t) pos->format);
854     ent[i].data_size = htonl ((uint32_t) pos->data_size);
855     if (NULL == pos->plugin_name)
856       plen = 0;
857     else
858       plen = strlen (pos->plugin_name) + 1;
859     ent[i].plugin_name_len = htonl ((uint32_t) plen);
860     if (NULL == pos->mime_type)
861       mlen = 0;
862     else
863       mlen = strlen (pos->mime_type) + 1;
864     ent[i].mime_type_len = htonl ((uint32_t) mlen);
865     off -= pos->data_size;
866     if ((EXTRACTOR_METAFORMAT_UTF8 == pos->format) ||
867         (EXTRACTOR_METAFORMAT_C_STRING == pos->format))
868       GNUNET_break ('\0' == pos->data[pos->data_size - 1]);
869     GNUNET_memcpy (&mdata[off], pos->data, pos->data_size);
870     off -= plen;
871     if (NULL != pos->plugin_name)
872       GNUNET_memcpy (&mdata[off], pos->plugin_name, plen);
873     off -= mlen;
874     if (NULL != pos->mime_type)
875       GNUNET_memcpy (&mdata[off], pos->mime_type, mlen);
876     i++;
877   }
878   GNUNET_assert (0 == off);
879
880   clen = 0;
881   cdata = NULL;
882   left = size;
883   i = 0;
884   for (pos = md->items_head; NULL != pos; pos = pos->next)
885   {
886     comp = GNUNET_NO;
887     if (0 == (opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_NO_COMPRESS))
888       comp = GNUNET_try_compression ((const char *) &ent[i],
889                                      left,
890                                      &cdata,
891                                      &clen);
892
893     if ((NULL == md->sbuf) && (0 == i))
894     {
895       /* fill 'sbuf'; this "modifies" md, but since this is only
896        * an internal cache we will cast away the 'const' instead
897        * of making the API look strange. */
898       vmd = (struct GNUNET_CONTAINER_MetaData *) md;
899       hdr = GNUNET_malloc (left + sizeof (struct MetaDataHeader));
900       hdr->size = htonl (left);
901       hdr->entries = htonl (md->item_count);
902       if (GNUNET_YES == comp)
903       {
904         GNUNET_assert (clen < left);
905         hdr->version = htonl (2 | HEADER_COMPRESSED);
906         GNUNET_memcpy (&hdr[1], cdata, clen);
907         vmd->sbuf_size = clen + sizeof (struct MetaDataHeader);
908       }
909       else
910       {
911         hdr->version = htonl (2);
912         GNUNET_memcpy (&hdr[1], &ent[0], left);
913         vmd->sbuf_size = left + sizeof (struct MetaDataHeader);
914       }
915       vmd->sbuf = (char *) hdr;
916     }
917
918     if (((left + sizeof (struct MetaDataHeader)) <= max) ||
919         ((GNUNET_YES == comp) && (clen <= max)))
920     {
921       /* success, this now fits! */
922       if (GNUNET_YES == comp)
923       {
924         if (NULL == dst)
925           dst = GNUNET_malloc (clen + sizeof (struct MetaDataHeader));
926         hdr = (struct MetaDataHeader *) dst;
927         hdr->version = htonl (2 | HEADER_COMPRESSED);
928         hdr->size = htonl (left);
929         hdr->entries = htonl (md->item_count - i);
930         GNUNET_memcpy (&dst[sizeof (struct MetaDataHeader)], cdata, clen);
931         GNUNET_free (cdata);
932         cdata = NULL;
933         GNUNET_free (ent);
934         rlen = clen + sizeof (struct MetaDataHeader);
935       }
936       else
937       {
938         if (NULL == dst)
939           dst = GNUNET_malloc (left + sizeof (struct MetaDataHeader));
940         hdr = (struct MetaDataHeader *) dst;
941         hdr->version = htonl (2);
942         hdr->entries = htonl (md->item_count - i);
943         hdr->size = htonl (left);
944         GNUNET_memcpy (&dst[sizeof (struct MetaDataHeader)], &ent[i], left);
945         GNUNET_free (ent);
946         rlen = left + sizeof (struct MetaDataHeader);
947       }
948       if (NULL != *target)
949       {
950         if (GNUNET_YES == comp)
951           GNUNET_memcpy (*target, dst, clen + sizeof (struct MetaDataHeader));
952         else
953           GNUNET_memcpy (*target, dst, left + sizeof (struct MetaDataHeader));
954         GNUNET_free (dst);
955       }
956       else
957       {
958         *target = dst;
959       }
960       return rlen;
961     }
962
963     if (0 == (opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_PART))
964     {
965       /* does not fit! */
966       GNUNET_free (ent);
967       return GNUNET_SYSERR;
968     }
969
970     /* next iteration: ignore the corresponding meta data at the
971      * end and try again without it */
972     left -= sizeof (struct MetaDataEntry);
973     left -= pos->data_size;
974     if (NULL != pos->plugin_name)
975       left -= strlen (pos->plugin_name) + 1;
976     if (NULL != pos->mime_type)
977       left -= strlen (pos->mime_type) + 1;
978
979     GNUNET_free_non_null (cdata);
980     cdata = NULL;
981
982     i++;
983   }
984   GNUNET_free (ent);
985
986   /* nothing fit, only write header! */
987   ihdr.version = htonl (2);
988   ihdr.entries = htonl (0);
989   ihdr.size = htonl (0);
990   if (NULL == *target)
991     *target = (char *) GNUNET_new (struct MetaDataHeader);
992   GNUNET_memcpy (*target, &ihdr, sizeof (struct MetaDataHeader));
993   return sizeof (struct MetaDataHeader);
994 }
995
996
997 /**
998  * Get the size of the full meta-data in serialized form.
999  *
1000  * @param md metadata to inspect
1001  * @return number of bytes needed for serialization, -1 on error
1002  */
1003 ssize_t
1004 GNUNET_CONTAINER_meta_data_get_serialized_size (const struct
1005                                                 GNUNET_CONTAINER_MetaData *md)
1006 {
1007   ssize_t ret;
1008   char *ptr;
1009
1010   if (NULL != md->sbuf)
1011     return md->sbuf_size;
1012   ptr = NULL;
1013   ret =
1014       GNUNET_CONTAINER_meta_data_serialize (md, &ptr, GNUNET_MAX_MALLOC_CHECKED,
1015                                             GNUNET_CONTAINER_META_DATA_SERIALIZE_FULL);
1016   if (-1 != ret)
1017     GNUNET_free (ptr);
1018   return ret;
1019 }
1020
1021
1022 /**
1023  * Deserialize meta-data.  Initializes md.
1024  *
1025  * @param input buffer with the serialized metadata
1026  * @param size number of bytes available in input
1027  * @return MD on success, NULL on error (i.e.
1028  *         bad format)
1029  */
1030 struct GNUNET_CONTAINER_MetaData *
1031 GNUNET_CONTAINER_meta_data_deserialize (const char *input, size_t size)
1032 {
1033   struct GNUNET_CONTAINER_MetaData *md;
1034   struct MetaDataHeader hdr;
1035   struct MetaDataEntry ent;
1036   uint32_t ic;
1037   uint32_t i;
1038   char *data;
1039   const char *cdata;
1040   uint32_t version;
1041   uint32_t dataSize;
1042   int compressed;
1043   size_t left;
1044   uint32_t mlen;
1045   uint32_t plen;
1046   uint32_t dlen;
1047   const char *mdata;
1048   const char *meta_data;
1049   const char *plugin_name;
1050   const char *mime_type;
1051   enum EXTRACTOR_MetaFormat format;
1052
1053   if (size < sizeof (struct MetaDataHeader))
1054     return NULL;
1055   GNUNET_memcpy (&hdr, input, sizeof (struct MetaDataHeader));
1056   version = ntohl (hdr.version) & HEADER_VERSION_MASK;
1057   compressed = (ntohl (hdr.version) & HEADER_COMPRESSED) != 0;
1058
1059   if (1 == version)
1060     return NULL;                /* null pointer */
1061   if (2 != version)
1062   {
1063     GNUNET_break_op (0);        /* unsupported version */
1064     return NULL;
1065   }
1066
1067   ic = ntohl (hdr.entries);
1068   dataSize = ntohl (hdr.size);
1069   if ( ((sizeof (struct MetaDataEntry) * ic) > dataSize) ||
1070        ( (0 != ic) &&
1071          (dataSize / ic < sizeof (struct MetaDataEntry)) ) )
1072   {
1073     GNUNET_break_op (0);
1074     return NULL;
1075   }
1076
1077   if (compressed)
1078   {
1079     if (dataSize >= GNUNET_MAX_MALLOC_CHECKED)
1080     {
1081       /* make sure we don't blow our memory limit because of a mal-formed
1082        * message... */
1083       GNUNET_break_op (0);
1084       return NULL;
1085     }
1086     data =
1087       GNUNET_decompress ((const char *) &input[sizeof (struct MetaDataHeader)],
1088                          size - sizeof (struct MetaDataHeader),
1089                          dataSize);
1090     if (NULL == data)
1091     {
1092       GNUNET_break_op (0);
1093       return NULL;
1094     }
1095     cdata = data;
1096   }
1097   else
1098   {
1099     data = NULL;
1100     cdata = (const char *) &input[sizeof (struct MetaDataHeader)];
1101     if (dataSize != size - sizeof (struct MetaDataHeader))
1102     {
1103       GNUNET_break_op (0);
1104       return NULL;
1105     }
1106   }
1107
1108   md = GNUNET_CONTAINER_meta_data_create ();
1109   left = dataSize - ic * sizeof (struct MetaDataEntry);
1110   mdata = &cdata[ic * sizeof (struct MetaDataEntry)];
1111   for (i = 0; i < ic; i++)
1112   {
1113     GNUNET_memcpy (&ent, &cdata[i * sizeof (struct MetaDataEntry)],
1114             sizeof (struct MetaDataEntry));
1115     format = (enum EXTRACTOR_MetaFormat) ntohl (ent.format);
1116     if ((EXTRACTOR_METAFORMAT_UTF8 != format) &&
1117         (EXTRACTOR_METAFORMAT_C_STRING != format) &&
1118         (EXTRACTOR_METAFORMAT_BINARY != format))
1119     {
1120       GNUNET_break_op (0);
1121       break;
1122     }
1123     dlen = ntohl (ent.data_size);
1124     plen = ntohl (ent.plugin_name_len);
1125     mlen = ntohl (ent.mime_type_len);
1126     if (dlen > left)
1127     {
1128       GNUNET_break_op (0);
1129       break;
1130     }
1131     left -= dlen;
1132     meta_data = &mdata[left];
1133     if ((EXTRACTOR_METAFORMAT_UTF8 == format) ||
1134         (EXTRACTOR_METAFORMAT_C_STRING == format))
1135     {
1136       if (0 == dlen)
1137       {
1138         GNUNET_break_op (0);
1139         break;
1140       }
1141       if ('\0' != meta_data[dlen - 1])
1142       {
1143         GNUNET_break_op (0);
1144         break;
1145       }
1146     }
1147     if (plen > left)
1148     {
1149       GNUNET_break_op (0);
1150       break;
1151     }
1152     left -= plen;
1153     if ((plen > 0) && ('\0' != mdata[left + plen - 1]))
1154     {
1155       GNUNET_break_op (0);
1156       break;
1157     }
1158     if (0 == plen)
1159       plugin_name = NULL;
1160     else
1161       plugin_name = &mdata[left];
1162
1163     if (mlen > left)
1164     {
1165       GNUNET_break_op (0);
1166       break;
1167     }
1168     left -= mlen;
1169     if ((mlen > 0) && ('\0' != mdata[left + mlen - 1]))
1170     {
1171       GNUNET_break_op (0);
1172       break;
1173     }
1174     if (0 == mlen)
1175       mime_type = NULL;
1176     else
1177       mime_type = &mdata[left];
1178     GNUNET_CONTAINER_meta_data_insert (md, plugin_name,
1179                                        (enum EXTRACTOR_MetaType)
1180                                        ntohl (ent.type), format, mime_type,
1181                                        meta_data, dlen);
1182   }
1183   GNUNET_free_non_null (data);
1184   return md;
1185 }
1186
1187
1188 /* end of container_meta_data.c */