update tests to use new MQ API
[oweals/gnunet.git] / src / namestore / plugin_namestore_flat.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2015 GNUnet e.V.
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 3, 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., 51 Franklin Street, Fifth Floor,
18   * Boston, MA 02110-1301, USA.
19   */
20
21 /**
22  * @file namestore/plugin_namestore_flat.c
23  * @brief file-based namestore backend
24  * @author Martin Schanzenbach
25  */
26
27 #include "platform.h"
28 #include "gnunet_namestore_plugin.h"
29 #include "gnunet_namestore_service.h"
30 #include "gnunet_gnsrecord_lib.h"
31 #include "namestore.h"
32
33 /**
34  * Context for all functions in this plugin.
35  */
36 struct Plugin
37 {
38
39   const struct GNUNET_CONFIGURATION_Handle *cfg;
40
41   /**
42    * Database filename.
43    */
44   char *fn;
45
46   /**
47    * HashMap
48    */
49   struct GNUNET_CONTAINER_MultiHashMap *hm;
50
51   /**
52    * Offset
53    */
54   uint32_t offset;
55
56   /**
57    * Target Offset
58    */
59   uint32_t target_offset;
60
61   /**
62    * Iterator closure
63    */
64   void *iter_cls;
65
66   /**
67    * Iterator
68    */
69   GNUNET_NAMESTORE_RecordIterator iter;
70
71   /**
72    * Zone to iterate
73    */
74   const struct GNUNET_CRYPTO_EcdsaPrivateKey *iter_zone;
75
76   /**
77    * PKEY to look for in zone to name
78    */
79   struct GNUNET_CRYPTO_EcdsaPublicKey *iter_pkey;
80
81   /**
82    * Iteration result found
83    */
84   int iter_result_found;
85
86 };
87
88 struct FlatFileEntry
89 {
90   /**
91    * Entry zone
92    */
93   struct GNUNET_CRYPTO_EcdsaPrivateKey *private_key;
94
95   /**
96    * Record cound
97    */
98   uint32_t record_count;
99
100   /**
101    * Rvalue
102    */
103   uint64_t rvalue;
104
105   /**
106    * Record data
107    */
108   struct GNUNET_GNSRECORD_Data *record_data;
109
110   /**
111    * Label
112    */
113   char *label;
114
115
116 };
117
118
119 /**
120  * Initialize the database connections and associated
121  * data structures (create tables and indices
122  * as needed as well).
123  *
124  * @param plugin the plugin context (state for this module)
125  * @return #GNUNET_OK on success
126  */
127 static int
128 database_setup (struct Plugin *plugin)
129 {
130   char *afsdir;
131   char *key;
132   char *record_data;
133   char *zone_private_key;
134   char *record_data_b64;
135   char *buffer;
136   char *line;
137   char *label;
138   char *rvalue;
139   char *record_count;
140   size_t record_data_size;
141   size_t size;
142   size_t key_len;
143   struct GNUNET_HashCode hkey;
144   struct GNUNET_DISK_FileHandle *fh;
145   struct FlatFileEntry *entry; 
146
147   if (GNUNET_OK !=
148       GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, 
149                                                "namestore-flat",
150                                                "FILENAME", &afsdir))
151   {
152     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
153                                "namestore-flat", "FILENAME");
154     return GNUNET_SYSERR;
155   }
156   if (GNUNET_OK != GNUNET_DISK_file_test (afsdir))
157   {
158     if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (afsdir))
159     {
160       GNUNET_break (0);
161       GNUNET_free (afsdir);
162       return GNUNET_SYSERR;
163     }
164   }
165   /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
166   plugin->fn = afsdir;
167
168   /* Load data from file into hashmap */
169   plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
170                                                      GNUNET_NO);
171   fh = GNUNET_DISK_file_open (afsdir,
172                               GNUNET_DISK_OPEN_CREATE |
173                               GNUNET_DISK_OPEN_READWRITE,
174                               GNUNET_DISK_PERM_USER_WRITE |
175                               GNUNET_DISK_PERM_USER_READ);
176   if (NULL == fh)
177   {
178     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
179          _("Unable to initialize file: %s.\n"),
180          afsdir);
181     return GNUNET_SYSERR;
182   }
183
184   if (GNUNET_SYSERR == GNUNET_DISK_file_size (afsdir,
185                                               &size,
186                                               GNUNET_YES,
187                                               GNUNET_YES))
188   {
189     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
190          _("Unable to get filesize: %s.\n"),
191          afsdir);
192     return GNUNET_SYSERR;
193   }
194
195   buffer = GNUNET_malloc (size);
196
197   if (GNUNET_SYSERR == GNUNET_DISK_file_read (fh,
198                                               buffer,
199                                               size))
200   {
201     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
202          _("Unable to read file: %s.\n"),
203          afsdir);
204     return GNUNET_SYSERR;
205   }
206
207   GNUNET_DISK_file_close (fh);
208   if (0 < size) {
209     line = strtok (buffer, "\n");
210     while (line != NULL) {
211       zone_private_key = strtok (line, ",");
212       if (NULL == zone_private_key)
213         break;
214       rvalue = strtok (NULL, ",");
215       if (NULL == rvalue)
216         break;
217       record_count = strtok (NULL, ",");
218       if (NULL == record_count)
219         break;
220       record_data_b64 = strtok (NULL, ",");
221       if (NULL == record_data_b64)
222         break;
223       label = strtok (NULL, ",");
224       if (NULL == label)
225         break;
226       line = strtok (NULL, "\n");
227       entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
228       sscanf (rvalue, "%lu", &entry->rvalue);
229       sscanf (record_count, "%u", &entry->record_count);
230       entry->label = GNUNET_strdup (label);
231       record_data_size = GNUNET_STRINGS_base64_decode (record_data_b64,
232                                                        strlen (record_data_b64),
233                                                        &record_data);
234       entry->record_data = 
235         GNUNET_malloc (sizeof (struct GNUNET_GNSRECORD_Data) * entry->record_count);
236       GNUNET_GNSRECORD_records_deserialize (record_data_size,
237                                             record_data,
238                                             entry->record_count,
239                                             entry->record_data);
240       GNUNET_free (record_data);
241       GNUNET_STRINGS_base64_decode (zone_private_key,
242                                     strlen (zone_private_key),
243                                     (char**)&entry->private_key);
244       key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
245       key = GNUNET_malloc (strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
246       memcpy (key, label, strlen (label));
247       memcpy (key+strlen(label),
248               entry->private_key,
249               sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
250       GNUNET_CRYPTO_hash (key,
251                           key_len,
252                           &hkey);
253       GNUNET_free (key);
254       if (GNUNET_OK != 
255           GNUNET_CONTAINER_multihashmap_put (plugin->hm,
256                                              &hkey,
257                                              entry,
258                                              GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
259       {
260         GNUNET_free (entry);
261         GNUNET_break (0);
262       }
263     }
264   }
265   GNUNET_free (buffer);
266   return GNUNET_OK;
267 }
268
269
270 /**
271  * Store values in hashmap in file and free data
272  *
273  * @param plugin the plugin context
274  */
275 static int
276 store_and_free_entries (void *cls,
277                         const struct GNUNET_HashCode *key,
278                         void *value)
279 {
280   struct GNUNET_DISK_FileHandle *fh = cls;
281   struct FlatFileEntry *entry = value;
282   char *line;
283   char *zone_private_key;
284   char *rvalue;
285   char *record_count;
286   char *record_data_b64;
287   size_t data_size;
288
289   GNUNET_STRINGS_base64_encode ((char*)entry->private_key,
290                                 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey),
291                                 &zone_private_key);
292   GNUNET_asprintf (&rvalue, "%lu", entry->rvalue);
293   GNUNET_asprintf (&record_count, "%u", entry->record_count);
294   
295   data_size = GNUNET_GNSRECORD_records_get_size (entry->record_count,
296                                                  entry->record_data);
297   char data[data_size];
298   if (data_size != GNUNET_GNSRECORD_records_serialize (entry->record_count,
299                                                        entry->record_data,
300                                                        data_size,
301                                                        data))
302   {
303     GNUNET_break (0);
304     return GNUNET_SYSERR;
305   }
306   GNUNET_STRINGS_base64_encode (data,
307                                 data_size,
308                                 &record_data_b64);
309
310   GNUNET_asprintf (&line,
311                    "%s,%s,%s,%s,%s\n",
312                    zone_private_key,
313                    rvalue,
314                    record_count,
315                    record_data_b64,
316                    entry->label);
317
318   GNUNET_free (rvalue);
319   GNUNET_free (record_count);
320   GNUNET_free (record_data_b64);
321
322   GNUNET_DISK_file_write (fh,
323                           line,
324                           strlen (line));
325
326   GNUNET_free (entry->private_key);
327   GNUNET_free (entry->label);
328   GNUNET_free (entry->record_data);
329   GNUNET_free (entry);
330   return GNUNET_YES;
331 }
332
333 /**
334  * Shutdown database connection and associate data
335  * structures.
336  * @param plugin the plugin context (state for this module)
337  */
338 static void
339 database_shutdown (struct Plugin *plugin)
340 {
341   struct GNUNET_DISK_FileHandle *fh;
342   fh = GNUNET_DISK_file_open (plugin->fn,
343                               GNUNET_DISK_OPEN_CREATE |
344                               GNUNET_DISK_OPEN_TRUNCATE |
345                               GNUNET_DISK_OPEN_READWRITE,
346                               GNUNET_DISK_PERM_USER_WRITE |
347                               GNUNET_DISK_PERM_USER_READ);
348   if (NULL == fh)
349   {
350     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
351                 _("Unable to initialize file: %s.\n"),
352                 plugin->fn);
353     return;
354   }
355
356   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
357                                          &store_and_free_entries,
358                                          fh);
359   GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
360   GNUNET_DISK_file_close (fh);
361 }
362
363
364 /**
365  * Store a record in the datastore.  Removes any existing record in the
366  * same zone with the same name.
367  *
368  * @param cls closure (internal context for the plugin)
369  * @param zone_key private key of the zone
370  * @param label name that is being mapped (at most 255 characters long)
371  * @param rd_count number of entries in @a rd array
372  * @param rd array of records with data to store
373  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
374  */
375 static int
376 namestore_store_records (void *cls,
377                          const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
378                          const char *label,
379                          unsigned int rd_count,
380                          const struct GNUNET_GNSRECORD_Data *rd)
381 {
382   struct Plugin *plugin = cls;
383   uint64_t rvalue;
384   size_t key_len;
385   char *key;
386   struct GNUNET_HashCode hkey;
387   struct FlatFileEntry *entry;
388   int i;
389
390   rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
391   key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
392   key = GNUNET_malloc (key_len);
393   memcpy (key, label, strlen (label));
394   memcpy (key+strlen(label),
395           zone_key,
396           sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
397   GNUNET_CRYPTO_hash (key,
398                       key_len,
399                       &hkey);
400
401   GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, &hkey);
402
403   if (0 < rd_count)
404   {
405     entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
406     entry->private_key = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
407     GNUNET_asprintf (&entry->label,
408                      label,
409                      strlen (label));
410     memcpy (entry->private_key,
411             zone_key,
412             sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
413     entry->rvalue = rvalue;
414     entry->record_count = rd_count;
415     entry->record_data = GNUNET_malloc (sizeof (struct GNUNET_GNSRECORD_Data) * rd_count);
416     for (i = 0; i < rd_count; i++)
417     {
418       entry->record_data[i].expiration_time = rd[i].expiration_time;
419       entry->record_data[i].record_type = rd[i].record_type;
420       entry->record_data[i].flags = rd[i].flags;
421       entry->record_data[i].data_size = rd[i].data_size;
422       entry->record_data[i].data = GNUNET_malloc (rd[i].data_size);
423       memcpy ((char*)entry->record_data[i].data, rd[i].data, rd[i].data_size);
424     }
425     return GNUNET_CONTAINER_multihashmap_put (plugin->hm,
426                                               &hkey,
427                                               entry,
428                                               GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
429   }
430   return GNUNET_NO;
431 }
432
433
434 /**
435  * Lookup records in the datastore for which we are the authority.
436  *
437  * @param cls closure (internal context for the plugin)
438  * @param zone private key of the zone
439  * @param label name of the record in the zone
440  * @param iter function to call with the result
441  * @param iter_cls closure for @a iter
442  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
443  */
444 static int
445 namestore_lookup_records (void *cls,
446                           const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
447                           const char *label,
448                           GNUNET_NAMESTORE_RecordIterator iter,
449                           void *iter_cls)
450 {
451   struct Plugin *plugin = cls;
452   struct FlatFileEntry *entry;
453   struct GNUNET_HashCode hkey;
454   char *key;
455   size_t key_len;
456
457   if (NULL == zone)
458   {
459     return GNUNET_SYSERR;
460   }
461   key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
462   key = GNUNET_malloc (key_len);
463   memcpy (key, label, strlen (label));
464   memcpy (key+strlen(label),
465           zone,
466           sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
467   GNUNET_CRYPTO_hash (key,
468                       key_len,
469                       &hkey);
470   GNUNET_free (key);
471   
472   entry = GNUNET_CONTAINER_multihashmap_get (plugin->hm, &hkey);
473
474   if (NULL == entry)
475     return GNUNET_NO;
476   if (NULL != iter)
477     iter (iter_cls, entry->private_key, entry->label, entry->record_count, entry->record_data);
478   return GNUNET_YES;
479 }
480
481
482 static int
483 iterate_zones (void *cls,
484                const struct GNUNET_HashCode *key,
485                void *value)
486 {
487   struct Plugin *plugin = cls;
488   struct FlatFileEntry *entry = value;
489
490   if ((plugin->target_offset > plugin->offset) ||
491       ( (NULL != plugin->iter_zone) &&
492         (0 != memcmp (entry->private_key,
493                       plugin->iter_zone,
494                       sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey))))) {
495     plugin->offset++;
496     return GNUNET_YES;
497   }
498   plugin->iter (plugin->iter_cls,
499                 entry->private_key,
500                 entry->label,
501                 entry->record_count,
502                 entry->record_data);
503   plugin->iter_result_found = GNUNET_YES;
504   return GNUNET_NO;
505 }
506
507 /**
508  * Iterate over the results for a particular key and zone in the
509  * datastore.  Will return at most one result to the iterator.
510  *
511  * @param cls closure (internal context for the plugin)
512  * @param zone hash of public key of the zone, NULL to iterate over all zones
513  * @param offset offset in the list of all matching records
514  * @param iter function to call with the result
515  * @param iter_cls closure for @a iter
516  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
517  */
518 static int
519 namestore_iterate_records (void *cls,
520                            const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
521                            uint64_t offset,
522                            GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
523 {
524   struct Plugin *plugin = cls;
525   plugin->target_offset = offset;
526   plugin->offset = 0;
527   plugin->iter = iter;
528   plugin->iter_cls = iter_cls;
529   plugin->iter_zone = zone;
530   plugin->iter_result_found = GNUNET_NO;
531   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
532                                          &iterate_zones,
533                                          plugin);
534   return plugin->iter_result_found;
535 }
536
537 static int
538 zone_to_name (void *cls,
539               const struct GNUNET_HashCode *key,
540               void *value)
541 {
542   struct Plugin *plugin = cls;
543   struct FlatFileEntry *entry = value;
544   int i;
545
546   if (0 != memcmp (entry->private_key,
547                    plugin->iter_zone,
548                    sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)))
549     return GNUNET_YES;
550
551   for (i = 0; i < entry->record_count; i++) {
552     if (GNUNET_GNSRECORD_TYPE_PKEY != entry->record_data[i].record_type)
553       continue;
554     if (0 == memcmp (plugin->iter_pkey,
555                      entry->record_data[i].data,
556                      sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)))
557     {
558       plugin->iter (plugin->iter_cls,
559                     entry->private_key,
560                     entry->label,
561                     entry->record_count,
562                     entry->record_data);
563       plugin->iter_result_found = GNUNET_YES;
564
565     }
566   }
567
568   return GNUNET_YES;
569 }
570
571 /**
572  * Look for an existing PKEY delegation record for a given public key.
573  * Returns at most one result to the iterator.
574  *
575  * @param cls closure (internal context for the plugin)
576  * @param zone private key of the zone to look up in, never NULL
577  * @param value_zone public key of the target zone (value), never NULL
578  * @param iter function to call with the result
579  * @param iter_cls closure for @a iter
580  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
581  */
582 static int
583 namestore_zone_to_name (void *cls,
584                         const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
585                         const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
586                         GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
587 {
588   struct Plugin *plugin = cls;
589
590   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
591               "Performing reverse lookup for `%s'\n",
592               GNUNET_GNSRECORD_z2s (value_zone));
593
594   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
595                                          &zone_to_name,
596                                          plugin);
597
598
599   return plugin->iter_result_found;
600 }
601
602
603 /**
604  * Entry point for the plugin.
605  *
606  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
607  * @return NULL on error, otherwise the plugin context
608  */
609 void *
610 libgnunet_plugin_namestore_flat_init (void *cls)
611 {
612   static struct Plugin plugin;
613   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
614   struct GNUNET_NAMESTORE_PluginFunctions *api;
615
616   if (NULL != plugin.cfg)
617     return NULL;                /* can only initialize once! */
618   memset (&plugin, 0, sizeof (struct Plugin));
619   plugin.cfg = cfg;
620   if (GNUNET_OK != database_setup (&plugin))
621   {
622     database_shutdown (&plugin);
623     return NULL;
624   }
625   api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
626   api->cls = &plugin;
627   api->store_records = &namestore_store_records;
628   api->iterate_records = &namestore_iterate_records;
629   api->zone_to_name = &namestore_zone_to_name;
630   api->lookup_records = &namestore_lookup_records;
631   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
632               _("flat file database running\n"));
633   return api;
634 }
635
636
637 /**
638  * Exit point from the plugin.
639  *
640  * @param cls the plugin context (as returned by "init")
641  * @return always NULL
642  */
643 void *
644 libgnunet_plugin_namestore_flat_done (void *cls)
645 {
646   struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
647   struct Plugin *plugin = api->cls;
648
649   database_shutdown (plugin);
650   plugin->cfg = NULL;
651   GNUNET_free (api);
652   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
653               "flat file plugin is finished\n");
654   return NULL;
655 }
656
657 /* end of plugin_namestore_flat.c */