ebf6ce778d8a10afded9b8d61e145410bb617eb3
[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  *
143  * @param md metadata to manipulate
144  * @param type type of the item to remove
145  * @param data specific value to remove, NULL to remove all
146  *        entries of the given type
147  * @return GNUNET_OK on success, GNUNET_SYSERR if the item does not exist in md
148  */
149 int
150 GNUNET_CONTAINER_meta_data_delete (struct GNUNET_CONTAINER_MetaData *md,
151                                    EXTRACTOR_KeywordType type,
152                                    const char *data)
153 {
154   uint32_t idx;
155   int ret = GNUNET_SYSERR;
156   for (idx = 0; idx < md->itemCount; idx++)
157     {
158       if ((md->items[idx].type == type) &&
159           ((data == NULL) || (0 == strcmp (md->items[idx].data, data))))
160         {
161           GNUNET_free (md->items[idx].data);
162           md->items[idx] = md->items[md->itemCount - 1];
163           GNUNET_array_grow (md->items, md->itemCount, md->itemCount - 1);
164           if (data == NULL)
165             {
166               ret = GNUNET_OK;
167               continue;
168             }
169           return GNUNET_OK;
170         }
171     }
172   return ret;
173 }
174
175 /**
176  * Iterate over MD entries, excluding thumbnails.
177  *
178  * @param md metadata to inspect
179  * @param iter function to call on each entry
180  * @param iter_cls closure for iterator
181  * @return number of entries
182  */
183 int
184 GNUNET_CONTAINER_meta_data_get_contents (const struct
185                                          GNUNET_CONTAINER_MetaData *md,
186                                          GNUNET_CONTAINER_MetaDataProcessor
187                                          iter, void *iter_cls)
188 {
189   uint32_t i;
190   uint32_t sub;
191
192   sub = 0;
193   for (i = 0; i < md->itemCount; i++)
194     {
195       if (!EXTRACTOR_isBinaryType (md->items[i].type))
196         {
197           if ((iter != NULL) &&
198               (GNUNET_OK != iter (iter_cls,
199                                   md->items[i].type, md->items[i].data)))
200             return GNUNET_SYSERR;
201         }
202       else
203         sub++;
204     }
205   return (int) (md->itemCount - sub);
206 }
207
208 /**
209  * Iterate over MD entries
210  *
211  * @return number of entries
212  */
213 char *
214 GNUNET_CONTAINER_meta_data_get_by_type (const struct GNUNET_CONTAINER_MetaData
215                                         *md, EXTRACTOR_KeywordType type)
216 {
217   uint32_t i;
218
219   for (i = 0; i < md->itemCount; i++)
220     if (type == md->items[i].type)
221       return GNUNET_strdup (md->items[i].data);
222   return NULL;
223 }
224
225 /**
226  * Iterate over MD entries
227  *
228  * @return number of entries
229  */
230 char *
231 GNUNET_CONTAINER_meta_data_get_first_by_types (const struct
232                                                GNUNET_CONTAINER_MetaData *md,
233                                                ...)
234 {
235   char *ret;
236   va_list args;
237   EXTRACTOR_KeywordType type;
238
239   ret = NULL;
240   va_start (args, md);
241   while (1)
242     {
243       type = va_arg (args, EXTRACTOR_KeywordType);
244       if (type == -1)
245         break;
246       ret = GNUNET_CONTAINER_meta_data_get_by_type (md, type);
247       if (ret != NULL)
248         break;
249     }
250   va_end (args);
251   return ret;
252 }
253
254 /**
255  * Get a thumbnail from the meta-data (if present).
256  *
257  * @param md metadata to get the thumbnail from
258  * @param thumb will be set to the thumbnail data.  Must be
259  *        freed by the caller!
260  * @return number of bytes in thumbnail, 0 if not available
261  */
262 size_t
263 GNUNET_CONTAINER_meta_data_get_thumbnail (const struct
264                                           GNUNET_CONTAINER_MetaData * md,
265                                           unsigned char **thumb)
266 {
267   char *encoded;
268   int ret;
269   size_t size;
270
271   encoded =
272     GNUNET_CONTAINER_meta_data_get_by_type (md, EXTRACTOR_THUMBNAIL_DATA);
273   if (encoded == NULL)
274     return 0;
275   if (strlen (encoded) == 0)
276     {
277       GNUNET_free (encoded);
278       return 0;                 /* invalid */
279     }
280   *thumb = NULL;
281   ret = EXTRACTOR_binaryDecode (encoded, thumb, &size);
282   GNUNET_free (encoded);
283   if (ret != 0)
284     return 0;
285   return size;
286 }
287
288 /**
289  * Duplicate struct GNUNET_CONTAINER_MetaData.
290  * 
291  * @param md what to duplicate
292  * @return duplicate meta-data container
293  */
294 struct GNUNET_CONTAINER_MetaData *
295 GNUNET_CONTAINER_meta_data_duplicate (const struct GNUNET_CONTAINER_MetaData
296                                       *md)
297 {
298   uint32_t i;
299   struct GNUNET_CONTAINER_MetaData *ret;
300
301   if (md == NULL)
302     return NULL;
303   ret = GNUNET_CONTAINER_meta_data_create ();
304   for (i = 0; i < md->itemCount; i++)
305     GNUNET_CONTAINER_meta_data_insert (ret, md->items[i].type,
306                                        md->items[i].data);
307   return ret;
308 }
309
310 /**
311  * Extract meta-data from a file.
312  *
313  * @return GNUNET_SYSERR on error, otherwise the number
314  *   of meta-data items obtained
315  */
316 int
317 GNUNET_CONTAINER_meta_data_extract_from_file (struct GNUNET_CONTAINER_MetaData
318                                               *md, const char *filename,
319                                               EXTRACTOR_ExtractorList *
320                                               extractors)
321 {
322   EXTRACTOR_KeywordList *head;
323   EXTRACTOR_KeywordList *pos;
324   int ret;
325
326   if (filename == NULL)
327     return GNUNET_SYSERR;
328   if (extractors == NULL)
329     return 0;
330   head = EXTRACTOR_getKeywords (extractors, filename);
331   head = EXTRACTOR_removeDuplicateKeywords (head,
332                                             EXTRACTOR_DUPLICATES_REMOVE_UNKNOWN);
333   pos = head;
334   ret = 0;
335   while (pos != NULL)
336     {
337       if (GNUNET_OK ==
338           GNUNET_CONTAINER_meta_data_insert (md, pos->keywordType,
339                                              pos->keyword))
340         ret++;
341       pos = pos->next;
342     }
343   EXTRACTOR_freeKeywords (head);
344   return ret;
345 }
346
347
348 static unsigned int
349 tryCompression (char *data, unsigned int oldSize)
350 {
351   char *tmp;
352   uLongf dlen;
353
354 #ifdef compressBound
355   dlen = compressBound (oldSize);
356 #else
357   dlen = oldSize + (oldSize / 100) + 20;
358   /* documentation says 100.1% oldSize + 12 bytes, but we
359      should be able to overshoot by more to be safe */
360 #endif
361   tmp = GNUNET_malloc (dlen);
362   if (Z_OK == compress2 ((Bytef *) tmp,
363                          &dlen, (const Bytef *) data, oldSize, 9))
364     {
365       if (dlen < oldSize)
366         {
367           memcpy (data, tmp, dlen);
368           GNUNET_free (tmp);
369           return dlen;
370         }
371     }
372   GNUNET_free (tmp);
373   return oldSize;
374 }
375
376 /**
377  * Decompress input, return the decompressed data
378  * as output, set outputSize to the number of bytes
379  * that were found.
380  *
381  * @return NULL on error
382  */
383 static char *
384 decompress (const char *input,
385             unsigned int inputSize, unsigned int outputSize)
386 {
387   char *output;
388   uLongf olen;
389
390   olen = outputSize;
391   output = GNUNET_malloc (olen);
392   if (Z_OK == uncompress ((Bytef *) output,
393                           &olen, (const Bytef *) input, inputSize))
394     {
395       return output;
396     }
397   else
398     {
399       GNUNET_free (output);
400       return NULL;
401     }
402 }
403
404 /**
405  * Flag in 'version' that indicates compressed meta-data.
406  */
407 #define HEADER_COMPRESSED 0x80000000
408
409 /**
410  * Bits in 'version' that give the version number.
411  */
412 #define HEADER_VERSION_MASK 0x7FFFFFFF
413
414 struct MetaDataHeader
415 {
416   /**
417    * The version of the MD serialization.
418    * The highest bit is used to indicate
419    * compression.
420    *
421    * Version 0 is the current version;
422    * Version is 1 for a NULL pointer.
423    * Other version numbers are not yet defined.
424    */
425   uint32_t version;
426
427   /**
428    * How many MD entries are there?
429    */
430   uint32_t entries;
431
432   /**
433    * Size of the MD (decompressed)
434    */
435   uint32_t size;
436
437   /**
438    * This is followed by 'entries' values of type 'unsigned int' that
439    * correspond to EXTRACTOR_KeywordTypes.  After that, the meta-data
440    * keywords follow (0-terminated).  The MD block always ends with
441    * 0-termination, padding with 0 until a multiple of 8 bytes.
442    */
443
444 };
445
446 /**
447  * Serialize meta-data to target.
448  *
449  * @param md metadata to serialize
450  * @param target where to write the serialized metadata
451  * @param max maximum number of bytes available in target
452  * @param opt is it ok to just write SOME of the
453  *        meta-data to match the size constraint,
454  *        possibly discarding some data?
455  * @return number of bytes written on success,
456  *         GNUNET_SYSERR on error (typically: not enough
457  *         space)
458  */
459 ssize_t
460 GNUNET_CONTAINER_meta_data_serialize (const struct GNUNET_CONTAINER_MetaData
461                                       *md, char *target, size_t max,
462                                       enum
463                                       GNUNET_CONTAINER_MetaDataSerializationOptions
464                                       opt)
465 {
466   struct MetaDataHeader *hdr;
467   size_t size;
468   size_t pos;
469   uint32_t i;
470   size_t len;
471   uint32_t ic;
472
473   if (max < sizeof (struct MetaDataHeader))
474     return GNUNET_SYSERR;       /* far too small */
475   ic = md ? md->itemCount : 0;
476   hdr = NULL;
477   while (1)
478     {
479       size = sizeof (struct MetaDataHeader);
480       size += sizeof (uint32_t) * ic;
481       for (i = 0; i < ic; i++)
482         size += 1 + strlen (md->items[i].data);
483       while (size % 8 != 0)
484         size++;
485       hdr = GNUNET_malloc (size);
486       hdr->version = htonl (md == NULL ? 1 : 0);
487       hdr->entries = htonl (ic);
488       for (i = 0; i < ic; i++)
489         ((uint32_t *) & hdr[1])[i] = htonl ((uint32_t) md->items[i].type);
490       pos = sizeof (struct MetaDataHeader);
491       pos += sizeof (unsigned int) * ic;
492       for (i = 0; i < ic; i++)
493         {
494           len = strlen (md->items[i].data) + 1;
495           memcpy (&((char *) hdr)[pos], md->items[i].data, len);
496           pos += len;
497         }
498
499       hdr->size = htonl (size);
500       if ((opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_NO_COMPRESS) == 0)
501         {
502           pos = tryCompression ((char *) &hdr[1],
503                                 size - sizeof (struct MetaDataHeader));
504         }
505       else
506         {
507           pos = size - sizeof (struct MetaDataHeader);
508         }
509       if (pos < size - sizeof (struct MetaDataHeader))
510         {
511           hdr->version = htonl (HEADER_COMPRESSED);
512           size = pos + sizeof (struct MetaDataHeader);
513         }
514       if (size <= max)
515         break;
516       GNUNET_free (hdr);
517       hdr = NULL;
518
519       if ((opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_PART) == 0)
520         {
521           return GNUNET_SYSERR; /* does not fit! */
522         }
523       /* partial serialization ok, try again with less meta-data */
524       if (size > 2 * max)
525         ic = ic * 2 / 3;        /* still far too big, make big reductions */
526       else
527         ic--;                   /* small steps, we're close */
528     }
529   GNUNET_assert (size <= max);
530   memcpy (target, hdr, size);
531   GNUNET_free (hdr);
532   /* extra check: deserialize! */
533 #if EXTRA_CHECKS
534   {
535     struct GNUNET_CONTAINER_MetaData *mdx;
536     mdx = GNUNET_CONTAINER_meta_data_deserialize (target, size);
537     GNUNET_assert (NULL != mdx);
538     GNUNET_CONTAINER_meta_data_destroy (mdx);
539   }
540 #endif
541   return size;
542 }
543
544 /**
545  * Estimate (!) the size of the meta-data in
546  * serialized form.  The estimate MAY be higher
547  * than what is strictly needed.
548  *
549  * @param md metadata to inspect
550  * @param opt is it ok to just write SOME of the
551  *        meta-data to match the size constraint,
552  *        possibly discarding some data?
553  * @return number of bytes needed for serialization, -1 on error
554  */
555 ssize_t
556 GNUNET_CONTAINER_meta_data_get_serialized_size (const struct
557                                                 GNUNET_CONTAINER_MetaData *
558                                                 md,
559                                                 enum
560                                                 GNUNET_CONTAINER_MetaDataSerializationOptions
561                                                 opt)
562 {
563   struct MetaDataHeader *hdr;
564   size_t size;
565   size_t pos;
566   uint32_t i;
567   size_t len;
568   uint32_t ic;
569
570   ic = md ? md->itemCount : 0;
571   size = sizeof (struct MetaDataHeader);
572   size += sizeof (uint32_t) * ic;
573   for (i = 0; i < ic; i++)
574     size += 1 + strlen (md->items[i].data);
575   while (size % 8 != 0)
576     size++;
577   hdr = GNUNET_malloc (size);
578   hdr->version = htonl (md == NULL ? 1 : 0);
579   hdr->entries = htonl (ic);
580   for (i = 0; i < ic; i++)
581     ((uint32_t *) & hdr[1])[i] = htonl ((uint32_t) md->items[i].type);
582   pos = sizeof (struct MetaDataHeader);
583   pos += sizeof (uint32_t) * ic;
584   for (i = 0; i < ic; i++)
585     {
586       len = strlen (md->items[i].data) + 1;
587       memcpy (&((char *) hdr)[pos], md->items[i].data, len);
588       pos += len;
589     }
590   if ((opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_NO_COMPRESS) == 0)
591     {
592       pos =
593         tryCompression ((char *) &hdr[1],
594                         size - sizeof (struct MetaDataHeader));
595     }
596   else
597     {
598       pos = size - sizeof (struct MetaDataHeader);
599     }
600   if (pos < size - sizeof (struct MetaDataHeader))
601     size = pos + sizeof (struct MetaDataHeader);
602   GNUNET_free (hdr);
603   return size;
604 }
605
606
607 /**
608  * Deserialize meta-data.  Initializes md.
609  *
610  * @param input buffer with the serialized metadata
611  * @param size number of bytes available in input
612  * @return MD on success, NULL on error (i.e.
613  *         bad format)
614  */
615 struct GNUNET_CONTAINER_MetaData *
616 GNUNET_CONTAINER_meta_data_deserialize (const char *input, size_t size)
617 {
618   struct GNUNET_CONTAINER_MetaData *md;
619   const struct MetaDataHeader *hdr;
620   uint32_t ic;
621   char *data;
622   const char *cdata;
623   uint32_t dataSize;
624   int compressed;
625   uint32_t i;
626   size_t pos;
627   size_t len;
628   uint32_t version;
629
630   if (size < sizeof (struct MetaDataHeader))
631     return NULL;
632   hdr = (const struct MetaDataHeader *) input;
633   version = ntohl (MAKE_UNALIGNED (hdr->version)) & HEADER_VERSION_MASK;
634   if (version == 1)
635     return NULL;                /* null pointer */
636   if (version != 0)
637     {
638       GNUNET_break_op (0);      /* unsupported version */
639       return NULL;
640     }
641   ic = ntohl (MAKE_UNALIGNED (hdr->entries));
642   compressed =
643     (ntohl (MAKE_UNALIGNED (hdr->version)) & HEADER_COMPRESSED) != 0;
644   if (compressed)
645     {
646       dataSize =
647         ntohl (MAKE_UNALIGNED (hdr->size)) - sizeof (struct MetaDataHeader);
648       if (dataSize > 2 * 1042 * 1024)
649         {
650           GNUNET_break (0);
651           return NULL;          /* only 2 MB allowed [to make sure we don't blow
652                                    our memory limit because of a mal-formed
653                                    message... ] */
654         }
655       data =
656         decompress ((const char *) &input[sizeof (struct MetaDataHeader)],
657                     size - sizeof (struct MetaDataHeader), dataSize);
658       if (data == NULL)
659         {
660           GNUNET_break_op (0);
661           return NULL;
662         }
663       cdata = data;
664     }
665   else
666     {
667       data = NULL;
668       cdata = (const char *) &hdr[1];
669       dataSize = size - sizeof (struct MetaDataHeader);
670       if (size != ntohl (MAKE_UNALIGNED (hdr->size)))
671         {
672           GNUNET_break (0);
673           return NULL;
674         }
675     }
676
677   if ((sizeof (uint32_t) * ic + ic) > dataSize)
678     {
679       GNUNET_break (0);
680       goto FAILURE;
681     }
682   if ((ic > 0) && (cdata[dataSize - 1] != '\0'))
683     {
684       GNUNET_break (0);
685       goto FAILURE;
686     }
687
688   md = GNUNET_CONTAINER_meta_data_create ();
689   GNUNET_array_grow (md->items, md->itemCount, ic);
690   i = 0;
691   pos = sizeof (uint32_t) * ic;
692   while ((pos < dataSize) && (i < ic))
693     {
694       len = strlen (&cdata[pos]) + 1;
695       md->items[i].type = (EXTRACTOR_KeywordType)
696         ntohl (MAKE_UNALIGNED (((const uint32_t *) cdata)[i]));
697       md->items[i].data = GNUNET_strdup (&cdata[pos]);
698       pos += len;
699       i++;
700     }
701   if (i < ic)
702     {                           /* oops */
703       GNUNET_CONTAINER_meta_data_destroy (md);
704       goto FAILURE;
705     }
706   GNUNET_free_non_null (data);
707   return md;
708 FAILURE:
709   GNUNET_free_non_null (data);
710   return NULL;                  /* size too small */
711 }
712
713 /**
714  * Test if two MDs are equal.
715  *
716  * @param md1 first value to check
717  * @param md2 other value to check
718  * @return GNUNET_YES if they are equal
719  */
720 int
721 GNUNET_CONTAINER_meta_data_test_equal (const struct GNUNET_CONTAINER_MetaData
722                                        *md1,
723                                        const struct GNUNET_CONTAINER_MetaData
724                                        *md2)
725 {
726   uint32_t i;
727   uint32_t j;
728   int found;
729
730   if (md1->itemCount != md2->itemCount)
731     return GNUNET_NO;
732   for (i = 0; i < md1->itemCount; i++)
733     {
734       found = GNUNET_NO;
735       for (j = 0; j < md2->itemCount; j++)
736         if ((md1->items[i].type == md2->items[j].type) &&
737             (0 == strcmp (md1->items[i].data, md2->items[j].data)))
738           {
739             found = GNUNET_YES;
740             break;
741           }
742       if (found == GNUNET_NO)
743         return GNUNET_NO;
744     }
745   return GNUNET_YES;
746 }
747
748
749 /* end of container_meta_data.c */