towards search
[oweals/gnunet.git] / src / fs / fs_search.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 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 fs/fs_search.c
23  * @brief Helper functions for searching.
24  * @author Christian Grothoff
25  *
26  * TODO:
27  * - aggregate and process results (FSUI-style)
28  * - call progress callbacks
29  * - make operations persistent (can wait)
30  * - add support for pushing "already seen" information
31  *   to FS service for bloomfilter (can wait)
32  */
33
34 #include "platform.h"
35 #include "gnunet_constants.h"
36 #include "gnunet_fs_service.h"
37 #include "gnunet_protocols.h"
38 #include "fs.h"
39
40 #define DEBUG_SEARCH GNUNET_YES
41
42
43 /**
44  * We have received a KSK result.  Check
45  * how it fits in with the overall query
46  * and notify the client accordingly.
47  *
48  * @param sc context for the overall query
49  * @param ent entry for the specific keyword
50  * @param uri the URI that was found
51  * @param meta metadata associated with the URI
52  *        under the "ent" keyword
53  */
54 static void
55 process_ksk_result (struct GNUNET_FS_SearchContext *sc, 
56                     struct SearchRequestEntry *ent,
57                     const struct GNUNET_FS_Uri *uri,
58                     const struct GNUNET_CONTAINER_MetaData *meta)
59 {
60   // FIXME: check if new
61   // FIXME: check if mandatory satisfied
62   // FIXME: notify client!
63 }
64
65
66 /**
67  * We have received an SKS result.  Start
68  * searching for updates and notify the
69  * client if it is a new result.
70  *
71  * @param sc context for the overall query
72  * @param id_update identifier for updates, NULL for none
73  * @param uri the URI that was found
74  * @param meta metadata associated with the URI
75   */
76 static void
77 process_sks_result (struct GNUNET_FS_SearchContext *sc, 
78                     const char *id_update,
79                     const struct GNUNET_FS_Uri *uri,
80                     const struct GNUNET_CONTAINER_MetaData *meta)
81 {
82   // FIXME: check if new
83   // FIXME: notify client
84
85   if (strlen (id_update) > 0)
86     {
87       // FIXME: search for updates!
88 #if 0
89       updateURI.type = sks;
90       GNUNET_hash (&sb->subspace,
91                    sizeof (GNUNET_RSA_PublicKey),
92                    &updateURI.data.sks.namespace);
93       updateURI.data.sks.identifier = GNUNET_strdup (id);
94       add_search_for_uri (&updateURI, sqc);
95 #endif
96     }
97 }
98
99
100 /**
101  * Process a keyword-search result.
102  *
103  * @param sc our search context
104  * @param kb the kblock
105  * @param size size of kb
106  */
107 static void
108 process_kblock (struct GNUNET_FS_SearchContext *sc,
109                 const struct KBlock *kb,
110                 size_t size)
111 {
112   unsigned int i;
113   size_t j;
114   GNUNET_HashCode q;
115   char pt[size - sizeof (struct KBlock)];
116   struct GNUNET_CRYPTO_AesSessionKey skey;
117   struct GNUNET_CRYPTO_AesInitializationVector iv;
118   const char *eos;
119   struct GNUNET_CONTAINER_MetaData *meta;
120   struct GNUNET_FS_Uri *uri;
121   char *emsg;
122   
123   GNUNET_CRYPTO_hash (&kb->keyspace,
124                       sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
125                       &q);
126   /* find key */
127   for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
128     if (0 == memcmp (&q,
129                      &sc->requests[i].query,
130                      sizeof (GNUNET_HashCode)))
131       break;
132   if (i == sc->uri->data.ksk.keywordCount)
133     {
134       /* oops, does not match any of our keywords!? */
135       GNUNET_break (0);
136       return;
137     }
138   /* decrypt */
139   GNUNET_CRYPTO_hash_to_aes_key (&sc->requests[i].key, &skey, &iv);
140   GNUNET_CRYPTO_aes_encrypt (&kb[1],
141                              size - sizeof (struct KBlock),
142                              &skey,
143                              &iv,
144                              pt);
145   /* parse */
146   eos = memchr (pt, 0, sizeof (pt));
147   if (NULL == eos)
148     {
149       GNUNET_break_op (0);
150       return;
151     }
152   j = eos - pt + 1;
153   meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[j],
154                                                  sizeof (pt) - j);
155   if (meta == NULL)
156     {
157       GNUNET_break_op (0);       /* kblock malformed */
158       return;
159     }
160   uri = GNUNET_FS_uri_parse (pt, &emsg);
161   if (uri == NULL)
162     {
163       GNUNET_break_op (0);       /* kblock malformed */
164       GNUNET_free_non_null (emsg);
165       GNUNET_CONTAINER_meta_data_destroy (meta);
166       return;
167     }
168   /* process */
169   process_ksk_result (sc, &sc->requests[i], uri, meta);
170
171   /* clean up */
172   GNUNET_CONTAINER_meta_data_destroy (meta);
173   GNUNET_FS_uri_destroy (uri);
174 }
175
176
177 /**
178  * Process a namespace-search result.
179  *
180  * @param sc our search context
181  * @param sb the sblock
182  * @param size size of sb
183  */
184 static void
185 process_sblock (struct GNUNET_FS_SearchContext *sc,
186                 const struct SBlock *sb,
187                 size_t size)
188 {
189   size_t len = size - sizeof (struct SBlock);
190   char pt[len];
191   struct GNUNET_CRYPTO_AesSessionKey skey;
192   struct GNUNET_CRYPTO_AesInitializationVector iv;
193   struct GNUNET_FS_Uri *uri;
194   struct GNUNET_CONTAINER_MetaData *meta;
195   const char *id;
196   const char *uris;
197   size_t off;
198   char *emsg;
199   GNUNET_HashCode key;
200   char *identifier;
201
202   /* decrypt */
203   identifier = sc->uri->data.sks.identifier;
204   GNUNET_CRYPTO_hash (identifier, 
205                       strlen (identifier), 
206                       &key);
207   GNUNET_CRYPTO_hash_to_aes_key (&key, &skey, &iv);
208   GNUNET_CRYPTO_aes_encrypt (&sb[1],
209                              len,
210                              &skey,
211                              &iv,
212                              pt);
213   /* parse */
214   off = GNUNET_STRINGS_buffer_tokenize (pt,
215                                         len, 
216                                         2, 
217                                         &id, 
218                                         &uris);
219   if (off == 0)
220     {
221       GNUNET_break_op (0);     /* sblock malformed */
222       return;
223     }
224   meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[off], 
225                                                  len - off);
226   if (meta == NULL)
227     {
228       GNUNET_break_op (0);     /* sblock malformed */
229       return;
230     }
231   uri = GNUNET_FS_uri_parse (uris, &emsg);
232   if (uri == NULL)
233     {
234       GNUNET_break_op (0);     /* sblock malformed */
235       GNUNET_free_non_null (emsg);
236       GNUNET_CONTAINER_meta_data_destroy (meta);
237       return;
238     }
239   /* process */
240   process_sks_result (sc, id, uri, meta);
241   /* clean up */
242   GNUNET_FS_uri_destroy (uri);
243   GNUNET_CONTAINER_meta_data_destroy (meta);
244 }
245
246
247 /**
248  * Process a search result.
249  *
250  * @param sc our search context
251  * @param type type of the result
252  * @param expiration when it will expire
253  * @param data the (encrypted) response
254  * @param size size of data
255  */
256 static void
257 process_result (struct GNUNET_FS_SearchContext *sc,
258                 uint32_t type,
259                 struct GNUNET_TIME_Absolute expiration,
260                 const void *data,
261                 size_t size)
262 {
263   if (GNUNET_TIME_absolute_get_duration (expiration).value > 0)
264     return; /* result expired */
265   switch (type)
266     {
267     case GNUNET_DATASTORE_BLOCKTYPE_KBLOCK:
268       if (! GNUNET_FS_uri_test_ksk (sc->uri))
269         {
270           GNUNET_break (0);
271           return;
272         }
273       if (sizeof (struct KBlock) > size)
274         {
275           GNUNET_break_op (0);
276           return;
277         }
278       process_kblock (sc, data, size);
279       break;
280     case GNUNET_DATASTORE_BLOCKTYPE_SBLOCK:
281       if (! GNUNET_FS_uri_test_ksk (sc->uri))
282         {
283           GNUNET_break (0);
284           return;
285         }
286       if (sizeof (struct SBlock) > size)
287         {
288           GNUNET_break_op (0);
289           return;
290         }
291       process_sblock (sc, data, size);
292       break;
293     case GNUNET_DATASTORE_BLOCKTYPE_ANY:
294     case GNUNET_DATASTORE_BLOCKTYPE_DBLOCK:
295     case GNUNET_DATASTORE_BLOCKTYPE_ONDEMAND:
296     case GNUNET_DATASTORE_BLOCKTYPE_IBLOCK:
297       GNUNET_break (0);
298       break;
299     default:
300       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
301                   _("Got result with unknown block type `%d', ignoring"),
302                   type);
303       break;
304     }
305 }
306
307
308 /**
309  * Shutdown any existing connection to the FS
310  * service and try to establish a fresh one
311  * (and then re-transmit our search request).
312  *
313  * @param sc the search to reconnec
314  */
315 static void 
316 try_reconnect (struct GNUNET_FS_SearchContext *sc);
317
318
319 /**
320  * Type of a function to call when we receive a message
321  * from the service.
322  *
323  * @param cls closure
324  * @param msg message received, NULL on timeout or fatal error
325  */
326 static void 
327 receive_results (void *cls,
328                  const struct GNUNET_MessageHeader * msg)
329 {
330   struct GNUNET_FS_SearchContext *sc = cls;
331   const struct ContentMessage *cm;
332   uint16_t msize;
333
334   if ( (NULL == msg) ||
335        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_CONTENT) ||
336        (ntohs (msg->size) <= sizeof (struct ContentMessage)) )
337     {
338       try_reconnect (sc);
339       return;
340     }
341   msize = ntohs (msg->size);
342   cm = (const struct ContentMessage*) msg;
343   process_result (sc, 
344                   ntohl (cm->type),
345                   GNUNET_TIME_absolute_ntoh (cm->expiration),
346                   &cm[1],
347                   msize - sizeof (struct ContentMessage));
348   /* continue receiving */
349   GNUNET_CLIENT_receive (sc->client,
350                          &receive_results,
351                          sc,
352                          GNUNET_TIME_UNIT_FOREVER_REL);
353 }
354
355
356 /**
357  * We're ready to transmit the search request to the
358  * file-sharing service.  Do it.
359  *
360  * @param cls closure
361  * @param size number of bytes available in buf
362  * @param buf where the callee should write the message
363  * @return number of bytes written to buf
364  */
365 static size_t
366 transmit_search_request (void *cls,
367                          size_t size, 
368                          void *buf)
369 {
370   struct GNUNET_FS_SearchContext *sc = cls;
371   size_t msize;
372   struct SearchMessage *sm;
373   unsigned int i;
374   const char *keyword;
375   const char *identifier;
376   GNUNET_HashCode idh;
377   GNUNET_HashCode hc;
378   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;  
379   struct GNUNET_CRYPTO_RsaPrivateKey *pk;
380
381   if (NULL == buf)
382     {
383       try_reconnect (sc);
384       return 0;
385     }
386   if (GNUNET_FS_uri_test_ksk (sc->uri))
387     {
388       msize = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
389       GNUNET_assert (size >= msize);
390       sm = buf;
391       memset (sm, 0, msize);
392       sc->requests = GNUNET_malloc (sizeof (struct SearchRequestEntry) *
393                                     sc->uri->data.ksk.keywordCount);
394       for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
395         {
396           sm[i].header.size = htons (sizeof (struct SearchMessage));
397           sm[i].header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
398           sm[i].anonymity_level = htonl (sc->anonymity);
399           keyword = &sc->uri->data.ksk.keywords[i][1];
400
401           GNUNET_CRYPTO_hash (keyword, strlen (keyword), &hc);
402           pk = GNUNET_CRYPTO_rsa_key_create_from_hash (&hc);
403           GNUNET_CRYPTO_rsa_key_get_public (pk, &pub);
404           GNUNET_CRYPTO_rsa_key_free (pk);
405           GNUNET_CRYPTO_hash (&pub,
406                               sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), 
407                               &sm[i].query);
408           sc->requests[i].query = sm[i].query;
409           GNUNET_CRYPTO_hash (keyword,
410                               strlen (keyword),
411                               &sc->requests[i].key);
412         }
413     }
414   else
415     {
416       msize = sizeof (struct SearchMessage);
417       GNUNET_assert (size >= msize);
418       sm = buf;
419       memset (sm, 0, msize);
420       sm->header.size = htons (sizeof (struct SearchMessage));
421       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
422       sm->anonymity_level = htonl (sc->anonymity);
423       sm->target = sc->uri->data.sks.namespace;
424       identifier = sc->uri->data.sks.identifier;
425       GNUNET_CRYPTO_hash (identifier,
426                           strlen (identifier),
427                           &idh);
428       GNUNET_CRYPTO_hash_xor (&idh,
429                               &sm->target,
430                               &sm->query);
431     }
432   GNUNET_CLIENT_receive (sc->client,
433                          &receive_results,
434                          sc,
435                          GNUNET_TIME_UNIT_FOREVER_REL);
436   return msize;
437 }
438
439
440 /**
441  * Reconnect to the FS service and transmit
442  * our queries NOW.
443  *
444  * @param cls our search context
445  * @param tc unused
446  */
447 static void
448 do_reconnect (void *cls,
449               const struct GNUNET_SCHEDULER_TaskContext *tc)
450 {
451   struct GNUNET_FS_SearchContext *sc = cls;
452   struct GNUNET_CLIENT_Connection *client;
453   size_t size;
454   
455   sc->task = GNUNET_SCHEDULER_NO_TASK;
456   client = GNUNET_CLIENT_connect (sc->h->sched,
457                                   "fs",
458                                   sc->h->cfg);
459   if (NULL == client)
460     {
461       try_reconnect (sc);
462       return;
463     }
464   sc->client = client;
465   if (GNUNET_FS_uri_test_ksk (sc->uri))
466     size = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
467   else
468     size = sizeof (struct SearchMessage);
469   GNUNET_CLIENT_notify_transmit_ready (client,
470                                        size,
471                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
472                                        &transmit_search_request,
473                                        sc);  
474 }
475
476
477 /**
478  * Shutdown any existing connection to the FS
479  * service and try to establish a fresh one
480  * (and then re-transmit our search request).
481  *
482  * @param sc the search to reconnec
483  */
484 static void 
485 try_reconnect (struct GNUNET_FS_SearchContext *sc)
486 {
487   if (NULL != sc->client)
488     {
489       GNUNET_CLIENT_disconnect (sc->client);
490       sc->client = NULL;
491     }
492   sc->task
493     = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
494                                     GNUNET_NO,
495                                     GNUNET_SCHEDULER_PRIORITY_IDLE,
496                                     GNUNET_SCHEDULER_NO_TASK,
497                                     GNUNET_TIME_UNIT_SECONDS,
498                                     &do_reconnect,
499                                     sc);
500 }
501
502
503 /**
504  * Start search for content.
505  *
506  * @param h handle to the file sharing subsystem
507  * @param uri specifies the search parameters; can be
508  *        a KSK URI or an SKS URI.
509  * @param anonymity desired level of anonymity
510  * @return context that can be used to control the search
511  */
512 struct GNUNET_FS_SearchContext *
513 GNUNET_FS_search_start (struct GNUNET_FS_Handle *h,
514                         const struct GNUNET_FS_Uri *uri,
515                         unsigned int anonymity)
516 {
517   struct GNUNET_FS_SearchContext *sc;
518   struct GNUNET_CLIENT_Connection *client;
519   size_t size;
520
521   if (GNUNET_FS_uri_test_ksk (uri))
522     {
523       size = sizeof (struct SearchMessage) * uri->data.ksk.keywordCount;
524     }
525   else
526     {
527       GNUNET_assert (GNUNET_FS_uri_test_sks (uri));
528       size = sizeof (struct SearchMessage);
529     }
530   if (size >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
531     {
532       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
533                   _("Too many keywords specified for a single search."));
534       return NULL;
535     }
536   client = GNUNET_CLIENT_connect (sc->h->sched,
537                                   "fs",
538                                   sc->h->cfg);
539   if (NULL == client)
540     return NULL;
541   sc = GNUNET_malloc (sizeof(struct GNUNET_FS_SearchContext));
542   sc->h = h;
543   sc->uri = GNUNET_FS_uri_dup (uri);
544   sc->anonymity = anonymity;
545   sc->start_time = GNUNET_TIME_absolute_get ();
546   sc->client = client;  
547   // FIXME: call callback!
548   GNUNET_CLIENT_notify_transmit_ready (client,
549                                        size,
550                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
551                                        &transmit_search_request,
552                                        sc);  
553   return sc;
554 }
555
556
557 /**
558  * Pause search.  
559  *
560  * @param sc context for the search that should be paused
561  */
562 void 
563 GNUNET_FS_search_pause (struct GNUNET_FS_SearchContext *sc)
564 {
565   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
566     GNUNET_SCHEDULER_cancel (sc->h->sched,
567                              sc->task);
568   sc->task = GNUNET_SCHEDULER_NO_TASK;
569   if (NULL != sc->client)
570     GNUNET_CLIENT_disconnect (sc->client);
571   sc->client = NULL;
572   // FIXME: make persistent!
573   // FIXME: call callback!
574 }
575
576
577 /**
578  * Continue paused search.
579  *
580  * @param sc context for the search that should be resumed
581  */
582 void 
583 GNUNET_FS_search_continue (struct GNUNET_FS_SearchContext *sc)
584 {
585   GNUNET_assert (sc->client == NULL);
586   GNUNET_assert (sc->task == GNUNET_SCHEDULER_NO_TASK);
587   do_reconnect (sc, NULL);
588   // FIXME: make persistent!
589   // FIXME: call callback!
590 }
591
592
593 /**
594  * Stop search for content.
595  *
596  * @param sc context for the search that should be stopped
597  */
598 void 
599 GNUNET_FS_search_stop (struct GNUNET_FS_SearchContext *sc)
600 {
601   // FIXME: make un-persistent!
602   // FIXME: call callback!
603   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
604     GNUNET_SCHEDULER_cancel (sc->h->sched,
605                              sc->task);
606   if (NULL != sc->client)
607     GNUNET_CLIENT_disconnect (sc->client);
608   GNUNET_free_non_null (sc->requests);
609   GNUNET_FS_uri_destroy (sc->uri);
610   GNUNET_free (sc);
611 }
612
613 /* end of fs_search.c */