bbe6dad3bc1cd9460c3af569f6c4748abf6a5201
[oweals/gnunet.git] / src / util / container_meta_data.c
1 /*
2      This file is part of GNUnet.
3      (C) 2003, 2004, 2005, 2006, 2008, 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
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_common.h"
29 #include "gnunet_container_lib.h"
30 #include "gnunet_strings_lib.h"
31 #include "gnunet_time_lib.h"
32 #include <extractor.h>
33 #include <zlib.h>
34
35 #define EXTRA_CHECKS ALLOW_EXTRA_CHECKS
36
37 struct Item
38 {
39   EXTRACTOR_KeywordType type;
40   char *data;
41 };
42
43 /**
44  * Meta data to associate with a file, directory or namespace.
45  */
46 struct GNUNET_CONTAINER_MetaData
47 {
48   uint32_t itemCount;
49   struct Item *items;
50 };
51
52 /**
53  * Create a fresh struct CONTAINER_MetaData token.
54  */
55 struct GNUNET_CONTAINER_MetaData *
56 GNUNET_CONTAINER_meta_data_create ()
57 {
58   struct GNUNET_CONTAINER_MetaData *ret;
59   ret = GNUNET_malloc (sizeof (struct GNUNET_CONTAINER_MetaData));
60   ret->items = NULL;
61   ret->itemCount = 0;
62   return ret;
63 }
64
65 /**
66  * Free meta data.
67  */
68 void
69 GNUNET_CONTAINER_meta_data_destroy (struct GNUNET_CONTAINER_MetaData *md)
70 {
71   int i;
72
73   if (md == NULL)
74     return;
75   for (i = 0; i < md->itemCount; i++)
76     GNUNET_free (md->items[i].data);
77   GNUNET_array_grow (md->items, md->itemCount, 0);
78   GNUNET_free (md);
79 }
80
81 /**
82  * Add the current time as the publication date
83  * to the meta-data.
84  */
85 void
86 GNUNET_CONTAINER_meta_data_add_publication_date (struct
87                                                  GNUNET_CONTAINER_MetaData
88                                                  *md)
89 {
90   char *dat;
91   struct GNUNET_TIME_Absolute t;
92
93   t = GNUNET_TIME_absolute_get ();
94   GNUNET_CONTAINER_meta_data_delete (md, EXTRACTOR_PUBLICATION_DATE, NULL);
95   dat = GNUNET_STRINGS_absolute_time_to_string (t);
96   GNUNET_CONTAINER_meta_data_insert (md, EXTRACTOR_PUBLICATION_DATE, dat);
97   GNUNET_free (dat);
98 }
99
100 /**
101  * Extend metadata.
102  * @return GNUNET_OK on success, GNUNET_SYSERR if this entry already exists
103  */
104 int
105 GNUNET_CONTAINER_meta_data_insert (struct GNUNET_CONTAINER_MetaData *md,
106                                    EXTRACTOR_KeywordType type,
107                                    const char *data)
108 {
109   uint32_t idx;
110   char *p;
111
112   GNUNET_assert (data != NULL);
113   for (idx = 0; idx < md->itemCount; idx++)
114     {
115       if ((md->items[idx].type == type) &&
116           (0 == strcmp (md->items[idx].data, data)))
117         return GNUNET_SYSERR;
118     }
119   idx = md->itemCount;
120   GNUNET_array_grow (md->items, md->itemCount, md->itemCount + 1);
121   md->items[idx].type = type;
122   md->items[idx].data = p = GNUNET_strdup (data);
123
124   /* change OS native dir separators to unix '/' and others to '_' */
125   if (type == EXTRACTOR_FILENAME)
126     {
127       while (*p != '\0')
128         {
129           if (*p == DIR_SEPARATOR)
130             *p = '/';
131           else if (*p == '\\')
132             *p = '_';
133           p++;
134         }
135     }
136
137   return GNUNET_OK;
138 }
139
140 /**
141  * Remove an item.
142  * @return GNUNET_OK on success, GNUNET_SYSERR if the item does not exist in md
143  */
144 int
145 GNUNET_CONTAINER_meta_data_delete (struct GNUNET_CONTAINER_MetaData *md,
146                                    EXTRACTOR_KeywordType type,
147                                    const char *data)
148 {
149   uint32_t idx;
150   int ret = GNUNET_SYSERR;
151   for (idx = 0; idx < md->itemCount; idx++)
152     {
153       if ((md->items[idx].type == type) &&
154           ((data == NULL) || (0 == strcmp (md->items[idx].data, data))))
155         {
156           GNUNET_free (md->items[idx].data);
157           md->items[idx] = md->items[md->itemCount - 1];
158           GNUNET_array_grow (md->items, md->itemCount, md->itemCount - 1);
159           if (data == NULL)
160             {
161               ret = GNUNET_OK;
162               continue;
163             }
164           return GNUNET_OK;
165         }
166     }
167   return ret;
168 }
169
170 /**
171  * Iterate over MD entries, excluding thumbnails.
172  *
173  * @return number of entries
174  */
175 int
176 GNUNET_CONTAINER_meta_data_get_contents (const struct
177                                          GNUNET_CONTAINER_MetaData *md,
178                                          GNUNET_CONTAINER_MetaDataProcessor
179                                          iterator, void *closure)
180 {
181   uint32_t i;
182   uint32_t sub;
183
184   sub = 0;
185   for (i = 0; i < md->itemCount; i++)
186     {
187       if (!EXTRACTOR_isBinaryType (md->items[i].type))
188         {
189           if ((iterator != NULL) &&
190               (GNUNET_OK != iterator (closure,
191                                       md->items[i].type,
192                                       md->items[i].data)))
193             return GNUNET_SYSERR;
194         }
195       else
196         sub++;
197     }
198   return (int) (md->itemCount - sub);
199 }
200
201 /**
202  * Iterate over MD entries
203  *
204  * @return number of entries
205  */
206 char *
207 GNUNET_CONTAINER_meta_data_get_by_type (const struct GNUNET_CONTAINER_MetaData
208                                         *md, EXTRACTOR_KeywordType type)
209 {
210   uint32_t i;
211
212   for (i = 0; i < md->itemCount; i++)
213     if (type == md->items[i].type)
214       return GNUNET_strdup (md->items[i].data);
215   return NULL;
216 }
217
218 /**
219  * Iterate over MD entries
220  *
221  * @return number of entries
222  */
223 char *
224 GNUNET_CONTAINER_meta_data_get_first_by_types (const struct
225                                                GNUNET_CONTAINER_MetaData *md,
226                                                ...)
227 {
228   char *ret;
229   va_list args;
230   EXTRACTOR_KeywordType type;
231
232   ret = NULL;
233   va_start (args, md);
234   while (1)
235     {
236       type = va_arg (args, EXTRACTOR_KeywordType);
237       if (type == -1)
238         break;
239       ret = GNUNET_CONTAINER_meta_data_get_by_type (md, type);
240       if (ret != NULL)
241         break;
242     }
243   va_end (args);
244   return ret;
245 }
246
247 /**
248  * Get a thumbnail from the meta-data (if present).
249  *
250  * @param md metadata to get the thumbnail from
251  * @param thumb will be set to the thumbnail data.  Must be
252  *        freed by the caller!
253  * @return number of bytes in thumbnail, 0 if not available
254  */
255 size_t
256 GNUNET_CONTAINER_meta_data_get_thumbnail (const struct
257                                           GNUNET_CONTAINER_MetaData * md,
258                                           unsigned char **thumb)
259 {
260   char *encoded;
261   int ret;
262   size_t size;
263
264   encoded =
265     GNUNET_CONTAINER_meta_data_get_by_type (md, EXTRACTOR_THUMBNAIL_DATA);
266   if (encoded == NULL)
267     return 0;
268   if (strlen (encoded) == 0)
269     {
270       GNUNET_free (encoded);
271       return 0;                 /* invalid */
272     }
273   *thumb = NULL;
274   ret = EXTRACTOR_binaryDecode (encoded, thumb, &size);
275   GNUNET_free (encoded);
276   if (ret != 0)
277     return 0;
278   return size;
279 }
280
281 /**
282  * Duplicate struct GNUNET_CONTAINER_MetaData.
283  */
284 struct GNUNET_CONTAINER_MetaData *
285 GNUNET_CONTAINER_meta_data_duplicate (const struct GNUNET_CONTAINER_MetaData
286                                       *md)
287 {
288   uint32_t i;
289   struct GNUNET_CONTAINER_MetaData *ret;
290
291   if (md == NULL)
292     return NULL;
293   ret = GNUNET_CONTAINER_meta_data_create ();
294   for (i = 0; i < md->itemCount; i++)
295     GNUNET_CONTAINER_meta_data_insert (ret, md->items[i].type,
296                                        md->items[i].data);
297   return ret;
298 }
299
300 /**
301  * Extract meta-data from a file.
302  *
303  * @return GNUNET_SYSERR on error, otherwise the number
304  *   of meta-data items obtained
305  */
306 int
307 GNUNET_CONTAINER_meta_data_extract_from_file (struct GNUNET_CONTAINER_MetaData
308                                               *md, const char *filename,
309                                               EXTRACTOR_ExtractorList *
310                                               extractors)
311 {
312   EXTRACTOR_KeywordList *head;
313   EXTRACTOR_KeywordList *pos;
314   int ret;
315
316   if (filename == NULL)
317     return GNUNET_SYSERR;
318   if (extractors == NULL)
319     return 0;
320   head = EXTRACTOR_getKeywords (extractors, filename);
321   head = EXTRACTOR_removeDuplicateKeywords (head,
322                                             EXTRACTOR_DUPLICATES_REMOVE_UNKNOWN);
323   pos = head;
324   ret = 0;
325   while (pos != NULL)
326     {
327       if (GNUNET_OK ==
328           GNUNET_CONTAINER_meta_data_insert (md, pos->keywordType,
329                                              pos->keyword))
330         ret++;
331       pos = pos->next;
332     }
333   EXTRACTOR_freeKeywords (head);
334   return ret;
335 }
336
337
338 static unsigned int
339 tryCompression (char *data, unsigned int oldSize)
340 {
341   char *tmp;
342   uLongf dlen;
343
344 #ifdef compressBound
345   dlen = compressBound (oldSize);
346 #else
347   dlen = oldSize + (oldSize / 100) + 20;
348   /* documentation says 100.1% oldSize + 12 bytes, but we
349      should be able to overshoot by more to be safe */
350 #endif
351   tmp = GNUNET_malloc (dlen);
352   if (Z_OK == compress2 ((Bytef *) tmp,
353                          &dlen, (const Bytef *) data, oldSize, 9))
354     {
355       if (dlen < oldSize)
356         {
357           memcpy (data, tmp, dlen);
358           GNUNET_free (tmp);
359           return dlen;
360         }
361     }
362   GNUNET_free (tmp);
363   return oldSize;
364 }
365
366 /**
367  * Decompress input, return the decompressed data
368  * as output, set outputSize to the number of bytes
369  * that were found.
370  *
371  * @return NULL on error
372  */
373 static char *
374 decompress (const char *input,
375             unsigned int inputSize, unsigned int outputSize)
376 {
377   char *output;
378   uLongf olen;
379
380   olen = outputSize;
381   output = GNUNET_malloc (olen);
382   if (Z_OK == uncompress ((Bytef *) output,
383                           &olen, (const Bytef *) input, inputSize))
384     {
385       return output;
386     }
387   else
388     {
389       GNUNET_free (output);
390       return NULL;
391     }
392 }
393
394 /**
395  * Flag in 'version' that indicates compressed meta-data.
396  */
397 #define HEADER_COMPRESSED 0x80000000
398
399 /**
400  * Bits in 'version' that give the version number.
401  */
402 #define HEADER_VERSION_MASK 0x7FFFFFFF
403
404 struct MetaDataHeader
405 {
406   /**
407    * The version of the MD serialization.
408    * The highest bit is used to indicate
409    * compression.
410    *
411    * Version 0 is the current version;
412    * Version is 1 for a NULL pointer.
413    * Other version numbers are not yet defined.
414    */
415   uint32_t version;
416
417   /**
418    * How many MD entries are there?
419    */
420   uint32_t entries;
421
422   /**
423    * Size of the MD (decompressed)
424    */
425   uint32_t size;
426
427   /**
428    * This is followed by 'entries' values of type 'unsigned int' that
429    * correspond to EXTRACTOR_KeywordTypes.  After that, the meta-data
430    * keywords follow (0-terminated).  The MD block always ends with
431    * 0-termination, padding with 0 until a multiple of 8 bytes.
432    */
433
434 };
435
436 /**
437  * Serialize meta-data to target.
438  *
439  * @param md metadata to serialize
440  * @param target where to write the serialized metadata
441  * @param max maximum number of bytes available in target
442  * @param part is it ok to just write SOME of the
443  *        meta-data to match the size constraint,
444  *        possibly discarding some data?
445  * @return number of bytes written on success,
446  *         GNUNET_SYSERR on error (typically: not enough
447  *         space)
448  */
449 ssize_t
450 GNUNET_CONTAINER_meta_data_serialize (const struct GNUNET_CONTAINER_MetaData
451                                       *md, char *target, size_t max,
452                                       enum
453                                       GNUNET_CONTAINER_MetaDataSerializationOptions
454                                       part)
455 {
456   struct MetaDataHeader *hdr;
457   size_t size;
458   size_t pos;
459   uint32_t i;
460   size_t len;
461   uint32_t ic;
462
463   if (max < sizeof (struct MetaDataHeader))
464     return GNUNET_SYSERR;       /* far too small */
465   ic = md ? md->itemCount : 0;
466   hdr = NULL;
467   while (1)
468     {
469       size = sizeof (struct MetaDataHeader);
470       size += sizeof (uint32_t) * ic;
471       for (i = 0; i < ic; i++)
472         size += 1 + strlen (md->items[i].data);
473       while (size % 8 != 0)
474         size++;
475       hdr = GNUNET_malloc (size);
476       hdr->version = htonl (md == NULL ? 1 : 0);
477       hdr->entries = htonl (ic);
478       for (i = 0; i < ic; i++)
479         ((uint32_t *) &hdr[1])[i] =
480           htonl ((uint32_t) md->items[i].type);
481       pos = sizeof (struct MetaDataHeader);
482       pos += sizeof (unsigned int) * ic;
483       for (i = 0; i < ic; i++)
484         {
485           len = strlen (md->items[i].data) + 1;
486           memcpy (&((char *) hdr)[pos], md->items[i].data, len);
487           pos += len;
488         }
489
490       hdr->size = htonl (size);
491       if ((part & GNUNET_CONTAINER_META_DATA_SERIALIZE_NO_COMPRESS) == 0)
492         {
493           pos = tryCompression ((char *) &hdr[1],
494                                 size - sizeof (struct MetaDataHeader));
495         }
496       else
497         {
498           pos = size - sizeof (struct MetaDataHeader);
499         }
500       if (pos < size - sizeof (struct MetaDataHeader))
501         {
502           hdr->version = htonl (HEADER_COMPRESSED);
503           size = pos + sizeof (struct MetaDataHeader);
504         }
505       if (size <= max)
506         break;
507       GNUNET_free (hdr);
508       hdr = NULL;
509
510       if ((part & GNUNET_CONTAINER_META_DATA_SERIALIZE_PART) == 0)
511         {
512           return GNUNET_SYSERR; /* does not fit! */
513         }
514       /* partial serialization ok, try again with less meta-data */
515       if (size > 2 * max)
516         ic = ic * 2 / 3;        /* still far too big, make big reductions */
517       else
518         ic--;                   /* small steps, we're close */
519     }
520   GNUNET_assert (size <= max);
521   memcpy (target, hdr, size);
522   GNUNET_free (hdr);
523   /* extra check: deserialize! */
524 #if EXTRA_CHECKS
525   {
526     struct GNUNET_CONTAINER_MetaData *mdx;
527     mdx = GNUNET_CONTAINER_meta_data_deserialize (target, size);
528     GNUNET_assert (NULL != mdx);
529     GNUNET_CONTAINER_meta_data_destroy (mdx);
530   }
531 #endif
532   return size;
533 }
534
535 /**
536  * Estimate (!) the size of the meta-data in
537  * serialized form.  The estimate MAY be higher
538  * than what is strictly needed.
539  *
540  * @param md metadata to inspect
541  * @param opt is it ok to just write SOME of the
542  *        meta-data to match the size constraint,
543  *        possibly discarding some data?
544  * @return number of bytes needed for serialization, -1 on error
545  */
546 ssize_t
547 GNUNET_CONTAINER_meta_data_get_serialized_size (const struct
548                                                 GNUNET_CONTAINER_MetaData *md,
549                                                 enum
550                                                 GNUNET_CONTAINER_MetaDataSerializationOptions
551                                                 opt)
552 {
553   struct MetaDataHeader *hdr;
554   size_t size;
555   size_t pos;
556   uint32_t i;
557   size_t len;
558   uint32_t ic;
559
560   ic = md ? md->itemCount : 0;
561   size = sizeof (struct MetaDataHeader);
562   size += sizeof (uint32_t) * ic;
563   for (i = 0; i < ic; i++)
564     size += 1 + strlen (md->items[i].data);
565   while (size % 8 != 0)
566     size++;
567   hdr = GNUNET_malloc (size);
568   hdr->version = htonl (md == NULL ? 1 : 0);
569   hdr->entries = htonl (ic);
570   for (i = 0; i < ic; i++)
571     ((uint32_t *) &hdr[1])[i] = htonl ((uint32_t) md->items[i].type);
572   pos = sizeof (struct MetaDataHeader);
573   pos += sizeof (uint32_t) * ic;
574   for (i = 0; i < ic; i++)
575     {
576       len = strlen (md->items[i].data) + 1;
577       memcpy (&((char *) hdr)[pos], md->items[i].data, len);
578       pos += len;
579     }
580   if ((opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_NO_COMPRESS) == 0)
581     {
582       pos =
583         tryCompression ((char *) &hdr[1],
584                         size - sizeof (struct MetaDataHeader));
585     }
586   else
587     {
588       pos = size - sizeof (struct MetaDataHeader);
589     }
590   if (pos < size - sizeof (struct MetaDataHeader))
591     size = pos + sizeof (struct MetaDataHeader);
592   GNUNET_free (hdr);
593   return size;
594 }
595
596
597 /**
598  * Deserialize meta-data.  Initializes md.
599  *
600  * @param input buffer with the serialized metadata
601  * @param size number of bytes available in input
602  * @return MD on success, NULL on error (i.e.
603  *         bad format)
604  */
605 struct GNUNET_CONTAINER_MetaData *
606 GNUNET_CONTAINER_meta_data_deserialize (const char *input, size_t size)
607 {
608   struct GNUNET_CONTAINER_MetaData *md;
609   const struct MetaDataHeader *hdr;
610   uint32_t ic;
611   char *data;
612   const char *cdata;
613   uint32_t dataSize;
614   int compressed;
615   uint32_t i;
616   size_t pos;
617   size_t len;
618   uint32_t version;
619
620   if (size < sizeof (struct MetaDataHeader))
621     return NULL;
622   hdr = (const struct MetaDataHeader *) input;
623   version = ntohl (MAKE_UNALIGNED (hdr->version)) & HEADER_VERSION_MASK;
624   if (version == 1)
625     return NULL;                /* null pointer */
626   if (version != 0)
627     {
628       GNUNET_break_op (0);      /* unsupported version */
629       return NULL;
630     }
631   ic = ntohl (MAKE_UNALIGNED (hdr->entries));
632   compressed =
633     (ntohl (MAKE_UNALIGNED (hdr->version)) & HEADER_COMPRESSED) != 0;
634   if (compressed)
635     {
636       dataSize =
637         ntohl (MAKE_UNALIGNED (hdr->size)) - sizeof (struct MetaDataHeader);
638       if (dataSize > 2 * 1042 * 1024)
639         {
640           GNUNET_break (0);
641           return NULL;          /* only 2 MB allowed [to make sure we don't blow
642                                    our memory limit because of a mal-formed
643                                    message... ] */
644         }
645       data =
646         decompress ((const char *) &input[sizeof (struct MetaDataHeader)],
647                     size - sizeof (struct MetaDataHeader), dataSize);
648       if (data == NULL)
649         {
650           GNUNET_break_op (0);
651           return NULL;
652         }
653       cdata = data;
654     }
655   else
656     {
657       data = NULL;
658       cdata = (const char *) &hdr[1];
659       dataSize = size - sizeof (struct MetaDataHeader);
660       if (size != ntohl (MAKE_UNALIGNED (hdr->size)))
661         {
662           GNUNET_break (0);
663           return NULL;
664         }
665     }
666
667   if ((sizeof (uint32_t) * ic + ic) > dataSize)
668     {
669       GNUNET_break (0);
670       goto FAILURE;
671     }
672   if ((ic > 0) && (cdata[dataSize - 1] != '\0'))
673     {
674       GNUNET_break (0);
675       goto FAILURE;
676     }
677
678   md = GNUNET_CONTAINER_meta_data_create ();
679   GNUNET_array_grow (md->items, md->itemCount, ic);
680   i = 0;
681   pos = sizeof (uint32_t) * ic;
682   while ((pos < dataSize) && (i < ic))
683     {
684       len = strlen (&cdata[pos]) + 1;
685       md->items[i].type = (EXTRACTOR_KeywordType)
686         ntohl (MAKE_UNALIGNED (((const uint32_t *) cdata)[i]));
687       md->items[i].data = GNUNET_strdup (&cdata[pos]);
688       pos += len;
689       i++;
690     }
691   if (i < ic)
692     {                           /* oops */
693       GNUNET_CONTAINER_meta_data_destroy (md);
694       goto FAILURE;
695     }
696   GNUNET_free_non_null (data);
697   return md;
698 FAILURE:
699   GNUNET_free_non_null (data);
700   return NULL;                  /* size too small */
701 }
702
703 /**
704  * Test if two MDs are equal.
705  *
706  * @param md1 first value to check
707  * @param md2 other value to check
708  * @return GNUNET_YES if they are equal
709  */
710 int
711 GNUNET_CONTAINER_meta_data_test_equal (const struct GNUNET_CONTAINER_MetaData
712                                        *md1,
713                                        const struct GNUNET_CONTAINER_MetaData
714                                        *md2)
715 {
716   uint32_t i;
717   uint32_t j;
718   int found;
719
720   if (md1->itemCount != md2->itemCount)
721     return GNUNET_NO;
722   for (i = 0; i < md1->itemCount; i++)
723     {
724       found = GNUNET_NO;
725       for (j = 0; j < md2->itemCount; j++)
726         if ((md1->items[i].type == md2->items[j].type) &&
727             (0 == strcmp (md1->items[i].data, md2->items[j].data)))
728           {
729             found = GNUNET_YES;
730             break;
731           }
732       if (found == GNUNET_NO)
733         return GNUNET_NO;
734     }
735   return GNUNET_YES;
736 }
737
738
739 /* end of container_meta_data.c */