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