add quiet flag (fix for test case)
[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  * - handle namespace advertisements (can wait; might already work!?)
28  * - add support for pushing "already seen" information
29  *   to FS service for bloomfilter (can wait)
30  * - handle availability probes (can wait)
31  * - make operations persistent (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_NO
41
42
43
44 /**
45  * Fill in all of the generic fields for 
46  * a search event.
47  *
48  * @param pi structure to fill in
49  * @param sc overall search context
50  */
51 static void
52 make_search_status (struct GNUNET_FS_ProgressInfo *pi,
53                     struct GNUNET_FS_SearchContext *sc)
54 {
55   pi->value.search.sc = sc;
56   pi->value.search.cctx
57     = sc->client_info;
58   pi->value.search.pctx
59     = (sc->parent == NULL) ? NULL : sc->parent->client_info;
60   pi->value.search.query 
61     = sc->uri;
62   pi->value.search.duration = GNUNET_TIME_absolute_get_duration (sc->start_time);
63   pi->value.search.anonymity = sc->anonymity;
64 }
65
66
67 /**
68  * Check if the given result is identical
69  * to the given URI.
70  * 
71  * @param cls points to the URI we check against
72  * @param key not used
73  * @param value a "struct SearchResult" who's URI we
74  *        should compare with
75  * @return GNUNET_SYSERR if the result is present,
76  *         GNUNET_OK otherwise
77  */
78 static int
79 test_result_present (void *cls,
80                      const GNUNET_HashCode * key,
81                      void *value)
82 {
83   const struct GNUNET_FS_Uri *uri = cls;
84   struct SearchResult *sr = value;
85
86   if (GNUNET_FS_uri_test_equal (uri,
87                                 sr->uri))
88     return GNUNET_SYSERR;
89   return GNUNET_OK;
90 }
91
92
93 /**
94  * We've found a new CHK result.  Let the client
95  * know about it.
96  * 
97  * @param sc the search context
98  * @param sr the specific result
99  */
100 static void
101 notify_client_chk_result (struct GNUNET_FS_SearchContext *sc, 
102                           struct SearchResult *sr)
103 {                         
104   struct GNUNET_FS_ProgressInfo pi;
105
106   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT;
107   make_search_status (&pi, sc);
108   pi.value.search.specifics.result.meta = sr->meta;
109   pi.value.search.specifics.result.uri = sr->uri;
110   sr->client_info = sc->h->upcb (sc->h->upcb_cls,
111                                  &pi);
112 }
113
114
115 /**
116  * We've found new information about an existing CHK result.  Let the
117  * client know about it.
118  * 
119  * @param sc the search context
120  * @param sr the specific result
121  */
122 static void
123 notify_client_chk_update (struct GNUNET_FS_SearchContext *sc, 
124                           struct SearchResult *sr)
125 {                         
126   struct GNUNET_FS_ProgressInfo pi;
127
128   pi.status = GNUNET_FS_STATUS_SEARCH_UPDATE;
129   make_search_status (&pi, sc);
130   pi.value.search.specifics.update.cctx = sr->client_info;
131   pi.value.search.specifics.update.meta = sr->meta;
132   pi.value.search.specifics.update.uri = sr->uri;
133   pi.value.search.specifics.update.availability_rank
134     = 2*sr->availability_success - sr->availability_trials;
135   pi.value.search.specifics.update.availability_certainty 
136     = sr->availability_trials;
137   pi.value.search.specifics.update.applicability_rank 
138     = sr->optional_support;
139   sr->client_info = sc->h->upcb (sc->h->upcb_cls,
140                                  &pi);
141 }
142
143
144 /**
145  * Context for "get_result_present".
146  */
147 struct GetResultContext 
148 {
149   /**
150    * The URI we're looking for.
151    */
152   const struct GNUNET_FS_Uri *uri;
153
154   /**
155    * Where to store a pointer to the search
156    * result struct if we found a match.
157    */
158   struct SearchResult *sr;
159 };
160
161
162 /**
163  * Check if the given result is identical to the given URI and if so
164  * return it.
165  * 
166  * @param cls a "struct GetResultContext"
167  * @param key not used
168  * @param value a "struct SearchResult" who's URI we
169  *        should compare with
170  * @return GNUNET_OK
171  */
172 static int
173 get_result_present (void *cls,
174                      const GNUNET_HashCode * key,
175                      void *value)
176 {
177   struct GetResultContext *grc = cls;
178   struct SearchResult *sr = value;
179
180   if (GNUNET_FS_uri_test_equal (grc->uri,
181                                 sr->uri))
182     grc->sr = sr;
183   return GNUNET_OK;
184 }
185
186
187 /**
188  * We have received a KSK result.  Check how it fits in with the
189  * overall query and notify the client accordingly.
190  *
191  * @param sc context for the overall query
192  * @param ent entry for the specific keyword
193  * @param uri the URI that was found
194  * @param meta metadata associated with the URI
195  *        under the "ent" keyword
196  */
197 static void
198 process_ksk_result (struct GNUNET_FS_SearchContext *sc, 
199                     struct SearchRequestEntry *ent,
200                     const struct GNUNET_FS_Uri *uri,
201                     const struct GNUNET_CONTAINER_MetaData *meta)
202 {
203   GNUNET_HashCode key;
204   struct SearchResult *sr;
205   struct GetResultContext grc;
206   int is_new;
207
208   /* check if new */
209   GNUNET_FS_uri_to_key (uri, &key);
210   if (GNUNET_SYSERR ==
211       GNUNET_CONTAINER_multihashmap_get_multiple (ent->results,
212                                                   &key,
213                                                   &test_result_present,
214                                                   (void*) uri))
215     return; /* duplicate result */
216   /* try to find search result in master map */
217   grc.sr = NULL;
218   grc.uri = uri;
219   GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map,
220                                               &key,
221                                               &get_result_present,
222                                               &grc);
223   sr = grc.sr;
224   is_new = (NULL == sr) || (sr->mandatory_missing > 0);
225   if (NULL == sr)
226     {
227       sr = GNUNET_malloc (sizeof (struct SearchResult));
228       sr->uri = GNUNET_FS_uri_dup (uri);
229       sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
230       sr->mandatory_missing = sc->mandatory_count;
231       GNUNET_CONTAINER_multihashmap_put (sc->master_result_map,
232                                          &key,
233                                          sr,
234                                          GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
235     }
236   else
237     {
238       /* FIXME: consider combining the meta data */
239     }
240   /* check if mandatory satisfied */
241   if (ent->mandatory)
242     sr->mandatory_missing--;
243   else
244     sr->optional_support++;
245   if (0 != sr->mandatory_missing)
246     return;
247   if (is_new)
248     notify_client_chk_result (sc, sr);
249   else
250     notify_client_chk_update (sc, sr);
251   /* FIXME: consider starting probes for "sr" */
252 }
253
254
255 /**
256  * Start search for content, internal API.
257  *
258  * @param h handle to the file sharing subsystem
259  * @param uri specifies the search parameters; can be
260  *        a KSK URI or an SKS URI.
261  * @param anonymity desired level of anonymity
262  * @param cctx client context
263  * @param parent parent search (for namespace update searches)
264  * @return context that can be used to control the search
265  */
266 static struct GNUNET_FS_SearchContext *
267 search_start (struct GNUNET_FS_Handle *h,
268               const struct GNUNET_FS_Uri *uri,
269               uint32_t anonymity,
270               void *cctx,
271               struct GNUNET_FS_SearchContext *parent);
272
273
274 /**
275  * We have received an SKS result.  Start searching for updates and
276  * notify the client if it is a new result.
277  *
278  * @param sc context for the overall query
279  * @param id_update identifier for updates, NULL for none
280  * @param uri the URI that was found
281  * @param meta metadata associated with the URI
282   */
283 static void
284 process_sks_result (struct GNUNET_FS_SearchContext *sc, 
285                     const char *id_update,
286                     const struct GNUNET_FS_Uri *uri,
287                     const struct GNUNET_CONTAINER_MetaData *meta)
288 {
289   struct GNUNET_FS_Uri uu;
290   GNUNET_HashCode key;
291   struct SearchResult *sr;
292
293   /* check if new */
294   GNUNET_FS_uri_to_key (uri, &key);
295   GNUNET_CRYPTO_hash_xor (&uri->data.chk.chk.key,
296                           &uri->data.chk.chk.query,
297                           &key);
298   if (GNUNET_SYSERR ==
299       GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map,
300                                                   &key,
301                                                   &test_result_present,
302                                                   (void*) uri))
303     return; /* duplicate result */
304   sr = GNUNET_malloc (sizeof (struct SearchResult));
305   sr->uri = GNUNET_FS_uri_dup (uri);
306   sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
307   GNUNET_CONTAINER_multihashmap_put (sc->master_result_map,
308                                      &key,
309                                      sr,
310                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
311   /* FIXME: consider starting probes for "sr" */
312
313   /* notify client */
314   notify_client_chk_result (sc, sr);
315   /* search for updates */
316   if (strlen (id_update) == 0)
317     return; /* no updates */
318   uu.type = sks;
319   uu.data.sks.namespace = sc->uri->data.sks.namespace;
320   uu.data.sks.identifier = GNUNET_strdup (id_update);
321   /* FIXME: should attach update search
322      to the individual result, not
323      the entire SKS search! */
324   search_start (sc->h,
325                 &uu,
326                 sc->anonymity,
327                 NULL,
328                 sc);
329 }
330
331
332 /**
333  * Process a keyword-search result.
334  *
335  * @param sc our search context
336  * @param kb the kblock
337  * @param size size of kb
338  */
339 static void
340 process_kblock (struct GNUNET_FS_SearchContext *sc,
341                 const struct KBlock *kb,
342                 size_t size)
343 {
344   unsigned int i;
345   size_t j;
346   GNUNET_HashCode q;
347   char pt[size - sizeof (struct KBlock)];
348   struct GNUNET_CRYPTO_AesSessionKey skey;
349   struct GNUNET_CRYPTO_AesInitializationVector iv;
350   const char *eos;
351   struct GNUNET_CONTAINER_MetaData *meta;
352   struct GNUNET_FS_Uri *uri;
353   char *emsg;
354   
355   GNUNET_CRYPTO_hash (&kb->keyspace,
356                       sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
357                       &q);
358   /* find key */
359   for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
360     if (0 == memcmp (&q,
361                      &sc->requests[i].query,
362                      sizeof (GNUNET_HashCode)))
363       break;
364   if (i == sc->uri->data.ksk.keywordCount)
365     {
366       /* oops, does not match any of our keywords!? */
367       GNUNET_break (0);
368       return;
369     }
370   /* decrypt */
371   GNUNET_CRYPTO_hash_to_aes_key (&sc->requests[i].key, &skey, &iv);
372   GNUNET_CRYPTO_aes_decrypt (&kb[1],
373                              size - sizeof (struct KBlock),
374                              &skey,
375                              &iv,
376                              pt);
377   /* parse */
378   eos = memchr (pt, 0, sizeof (pt));
379   if (NULL == eos)
380     {
381       GNUNET_break_op (0);
382       return;
383     }
384   j = eos - pt + 1;
385   meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[j],
386                                                  sizeof (pt) - j);
387   if (meta == NULL)
388     {
389       GNUNET_break_op (0);       /* kblock malformed */
390       return;
391     }
392   uri = GNUNET_FS_uri_parse (pt, &emsg);
393   if (uri == NULL)
394     {
395       GNUNET_break_op (0);       /* kblock malformed */
396       GNUNET_free_non_null (emsg);
397       GNUNET_CONTAINER_meta_data_destroy (meta);
398       return;
399     }
400   /* process */
401   process_ksk_result (sc, &sc->requests[i], uri, meta);
402
403   /* clean up */
404   GNUNET_CONTAINER_meta_data_destroy (meta);
405   GNUNET_FS_uri_destroy (uri);
406 }
407
408
409 /**
410  * Process a namespace-search result.
411  *
412  * @param sc our search context
413  * @param sb the sblock
414  * @param size size of sb
415  */
416 static void
417 process_sblock (struct GNUNET_FS_SearchContext *sc,
418                 const struct SBlock *sb,
419                 size_t size)
420 {
421   size_t len = size - sizeof (struct SBlock);
422   char pt[len];
423   struct GNUNET_CRYPTO_AesSessionKey skey;
424   struct GNUNET_CRYPTO_AesInitializationVector iv;
425   struct GNUNET_FS_Uri *uri;
426   struct GNUNET_CONTAINER_MetaData *meta;
427   const char *id;
428   const char *uris;
429   size_t off;
430   char *emsg;
431   GNUNET_HashCode key;
432   char *identifier;
433
434   /* decrypt */
435   identifier = sc->uri->data.sks.identifier;
436   GNUNET_CRYPTO_hash (identifier, 
437                       strlen (identifier), 
438                       &key);
439   GNUNET_CRYPTO_hash_to_aes_key (&key, &skey, &iv);
440   GNUNET_CRYPTO_aes_decrypt (&sb[1],
441                              len,
442                              &skey,
443                              &iv,
444                              pt);
445   /* parse */
446   off = GNUNET_STRINGS_buffer_tokenize (pt,
447                                         len, 
448                                         2, 
449                                         &id, 
450                                         &uris);
451   if (off == 0)
452     {
453       GNUNET_break_op (0);     /* sblock malformed */
454       return;
455     }
456   meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[off], 
457                                                  len - off);
458   if (meta == NULL)
459     {
460       GNUNET_break_op (0);     /* sblock malformed */
461       return;
462     }
463   uri = GNUNET_FS_uri_parse (uris, &emsg);
464   if (uri == NULL)
465     {
466       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
467                   "Failed to parse URI `%s': %s\n",
468                   uris, emsg);
469       GNUNET_break_op (0);     /* sblock malformed */
470       GNUNET_free_non_null (emsg);
471       GNUNET_CONTAINER_meta_data_destroy (meta);
472       return;
473     }
474   /* process */
475   process_sks_result (sc, id, uri, meta);
476   /* clean up */
477   GNUNET_FS_uri_destroy (uri);
478   GNUNET_CONTAINER_meta_data_destroy (meta);
479 }
480
481
482 /**
483  * Process a search result.
484  *
485  * @param sc our search context
486  * @param type type of the result
487  * @param expiration when it will expire
488  * @param data the (encrypted) response
489  * @param size size of data
490  */
491 static void
492 process_result (struct GNUNET_FS_SearchContext *sc,
493                 uint32_t type,
494                 struct GNUNET_TIME_Absolute expiration,
495                 const void *data,
496                 size_t size)
497 {
498   if (GNUNET_TIME_absolute_get_duration (expiration).value > 0)
499     {
500       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
501                   "Result received has already expired.\n");
502       return; /* result expired */
503     }
504   switch (type)
505     {
506     case GNUNET_DATASTORE_BLOCKTYPE_KBLOCK:
507       if (! GNUNET_FS_uri_test_ksk (sc->uri))
508         {
509           GNUNET_break (0);
510           return;
511         }
512       if (sizeof (struct KBlock) > size)
513         {
514           GNUNET_break_op (0);
515           return;
516         }
517       process_kblock (sc, data, size);
518       break;
519     case GNUNET_DATASTORE_BLOCKTYPE_SBLOCK:
520       if (! GNUNET_FS_uri_test_sks (sc->uri))
521         {
522           GNUNET_break (0);
523           return;
524         }
525       if (sizeof (struct SBlock) > size)
526         {
527           GNUNET_break_op (0);
528           return;
529         }
530       process_sblock (sc, data, size);
531       break;
532     case GNUNET_DATASTORE_BLOCKTYPE_ANY:
533     case GNUNET_DATASTORE_BLOCKTYPE_DBLOCK:
534     case GNUNET_DATASTORE_BLOCKTYPE_ONDEMAND:
535     case GNUNET_DATASTORE_BLOCKTYPE_IBLOCK:
536       GNUNET_break (0);
537       break;
538     default:
539       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
540                   _("Got result with unknown block type `%d', ignoring"),
541                   type);
542       break;
543     }
544 }
545
546
547 /**
548  * Shutdown any existing connection to the FS
549  * service and try to establish a fresh one
550  * (and then re-transmit our search request).
551  *
552  * @param sc the search to reconnec
553  */
554 static void 
555 try_reconnect (struct GNUNET_FS_SearchContext *sc);
556
557
558 /**
559  * Type of a function to call when we receive a message
560  * from the service.
561  *
562  * @param cls closure
563  * @param msg message received, NULL on timeout or fatal error
564  */
565 static void 
566 receive_results (void *cls,
567                  const struct GNUNET_MessageHeader * msg)
568 {
569   struct GNUNET_FS_SearchContext *sc = cls;
570   const struct PutMessage *cm;
571   uint16_t msize;
572
573   if ( (NULL == msg) ||
574        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) ||
575        (ntohs (msg->size) <= sizeof (struct PutMessage)) )
576     {
577       try_reconnect (sc);
578       return;
579     }
580   msize = ntohs (msg->size);
581   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
582               "Receiving %u bytes of result from fs service\n",
583               msize);
584   cm = (const struct PutMessage*) msg;
585   process_result (sc, 
586                   ntohl (cm->type),
587                   GNUNET_TIME_absolute_ntoh (cm->expiration),
588                   &cm[1],
589                   msize - sizeof (struct PutMessage));
590   /* continue receiving */
591   GNUNET_CLIENT_receive (sc->client,
592                          &receive_results,
593                          sc,
594                          GNUNET_TIME_UNIT_FOREVER_REL);
595 }
596
597
598 /**
599  * We're ready to transmit the search request to the
600  * file-sharing service.  Do it.
601  *
602  * @param cls closure
603  * @param size number of bytes available in buf
604  * @param buf where the callee should write the message
605  * @return number of bytes written to buf
606  */
607 static size_t
608 transmit_search_request (void *cls,
609                          size_t size, 
610                          void *buf)
611 {
612   struct GNUNET_FS_SearchContext *sc = cls;
613   size_t msize;
614   struct SearchMessage *sm;
615   unsigned int i;
616   const char *identifier;
617   GNUNET_HashCode key;
618   GNUNET_HashCode idh;
619
620   if (NULL == buf)
621     {
622       try_reconnect (sc);
623       return 0;
624     }
625   if (GNUNET_FS_uri_test_ksk (sc->uri))
626     {
627       msize = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
628       GNUNET_assert (size >= msize);
629       sm = buf;
630       memset (sm, 0, msize);
631       for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
632         {
633           sm[i].header.size = htons (sizeof (struct SearchMessage));
634           sm[i].header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
635           sm[i].type = htonl (GNUNET_DATASTORE_BLOCKTYPE_KBLOCK);
636           sm[i].anonymity_level = htonl (sc->anonymity);
637           sm[i].query = sc->requests[i].query;
638         }
639     }
640   else
641     {
642       GNUNET_assert (GNUNET_FS_uri_test_sks (sc->uri));
643       msize = sizeof (struct SearchMessage);
644       GNUNET_assert (size >= msize);
645       sm = buf;
646       memset (sm, 0, msize);
647       sm->header.size = htons (sizeof (struct SearchMessage));
648       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
649       sm->type = htonl (GNUNET_DATASTORE_BLOCKTYPE_SBLOCK);
650       sm->anonymity_level = htonl (sc->anonymity);
651       sm->target = sc->uri->data.sks.namespace;
652       identifier = sc->uri->data.sks.identifier;
653       GNUNET_CRYPTO_hash (identifier,
654                           strlen (identifier),
655                           &key);
656       GNUNET_CRYPTO_hash (&key,
657                           sizeof (GNUNET_HashCode),
658                           &idh);
659       GNUNET_CRYPTO_hash_xor (&idh,
660                               &sm->target,
661                               &sm->query);
662    }
663   GNUNET_CLIENT_receive (sc->client,
664                          &receive_results,
665                          sc,
666                          GNUNET_TIME_UNIT_FOREVER_REL);
667   return msize;
668 }
669
670
671 /**
672  * Reconnect to the FS service and transmit
673  * our queries NOW.
674  *
675  * @param cls our search context
676  * @param tc unused
677  */
678 static void
679 do_reconnect (void *cls,
680               const struct GNUNET_SCHEDULER_TaskContext *tc)
681 {
682   struct GNUNET_FS_SearchContext *sc = cls;
683   struct GNUNET_CLIENT_Connection *client;
684   size_t size;
685   
686   sc->task = GNUNET_SCHEDULER_NO_TASK;
687   client = GNUNET_CLIENT_connect (sc->h->sched,
688                                   "fs",
689                                   sc->h->cfg);
690   if (NULL == client)
691     {
692       try_reconnect (sc);
693       return;
694     }
695   sc->client = client;
696   if (GNUNET_FS_uri_test_ksk (sc->uri))
697     size = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
698   else
699     size = sizeof (struct SearchMessage);
700   GNUNET_CLIENT_notify_transmit_ready (client,
701                                        size,
702                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
703                                        GNUNET_NO,
704                                        &transmit_search_request,
705                                        sc);  
706 }
707
708
709 /**
710  * Shutdown any existing connection to the FS
711  * service and try to establish a fresh one
712  * (and then re-transmit our search request).
713  *
714  * @param sc the search to reconnec
715  */
716 static void 
717 try_reconnect (struct GNUNET_FS_SearchContext *sc)
718 {
719   if (NULL != sc->client)
720     {
721       GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO);
722       sc->client = NULL;
723     }
724   sc->task
725     = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
726                                     GNUNET_TIME_UNIT_SECONDS,
727                                     &do_reconnect,
728                                     sc);
729 }
730
731
732 /**
733  * Start search for content, internal API.
734  *
735  * @param h handle to the file sharing subsystem
736  * @param uri specifies the search parameters; can be
737  *        a KSK URI or an SKS URI.
738  * @param anonymity desired level of anonymity
739  * @param cctx initial value for the client context
740  * @param parent parent search (for namespace update searches)
741  * @return context that can be used to control the search
742  */
743 static struct GNUNET_FS_SearchContext *
744 search_start (struct GNUNET_FS_Handle *h,
745               const struct GNUNET_FS_Uri *uri,
746               uint32_t anonymity,
747               void *cctx,
748               struct GNUNET_FS_SearchContext *parent)
749 {
750   struct GNUNET_FS_SearchContext *sc;
751   struct GNUNET_CLIENT_Connection *client;
752   struct GNUNET_FS_ProgressInfo pi;
753   size_t size;
754   unsigned int i;
755   const char *keyword;
756   GNUNET_HashCode hc;
757   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;  
758   struct GNUNET_CRYPTO_RsaPrivateKey *pk;
759
760   if (GNUNET_FS_uri_test_ksk (uri))
761     {
762       size = sizeof (struct SearchMessage) * uri->data.ksk.keywordCount;
763     }
764   else
765     {
766       GNUNET_assert (GNUNET_FS_uri_test_sks (uri));
767       size = sizeof (struct SearchMessage);
768     }
769   if (size >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
770     {
771       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
772                   _("Too many keywords specified for a single search."));
773       return NULL;
774     }
775   client = GNUNET_CLIENT_connect (h->sched,
776                                   "fs",
777                                   h->cfg);
778   if (NULL == client)
779     return NULL;
780   sc = GNUNET_malloc (sizeof(struct GNUNET_FS_SearchContext));
781   sc->h = h;
782   sc->uri = GNUNET_FS_uri_dup (uri);
783   sc->anonymity = anonymity;
784   sc->start_time = GNUNET_TIME_absolute_get ();
785   sc->client = client;  
786   sc->parent = parent;
787   sc->master_result_map = GNUNET_CONTAINER_multihashmap_create (16);
788   sc->client_info = cctx;
789   if (GNUNET_FS_uri_test_ksk (uri))
790     {
791       GNUNET_assert (0 != sc->uri->data.ksk.keywordCount);
792       sc->requests = GNUNET_malloc (sizeof (struct SearchRequestEntry) *
793                                     sc->uri->data.ksk.keywordCount);
794       for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
795         {
796           keyword = &sc->uri->data.ksk.keywords[i][1];
797           GNUNET_CRYPTO_hash (keyword, strlen (keyword), &hc);
798           pk = GNUNET_CRYPTO_rsa_key_create_from_hash (&hc);
799           GNUNET_CRYPTO_rsa_key_get_public (pk, &pub);
800           GNUNET_CRYPTO_rsa_key_free (pk);
801           GNUNET_CRYPTO_hash (&pub,
802                               sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), 
803                               &sc->requests[i].query);
804           sc->requests[i].mandatory = (sc->uri->data.ksk.keywords[i][0] == '+');
805           if (sc->requests[i].mandatory)
806             sc->mandatory_count++;
807           sc->requests[i].results = GNUNET_CONTAINER_multihashmap_create (4);
808           GNUNET_CRYPTO_hash (keyword,
809                               strlen (keyword),
810                               &sc->requests[i].key);
811         }
812     }
813   if (NULL != parent)
814     GNUNET_CONTAINER_DLL_insert (parent->child_head,
815                                  parent->child_tail,
816                                  sc);
817   pi.status = GNUNET_FS_STATUS_SEARCH_START;
818   make_search_status (&pi, sc);
819   sc->client_info = h->upcb (h->upcb_cls,
820                              &pi);
821   GNUNET_CLIENT_notify_transmit_ready (client,
822                                        size,
823                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
824                                        GNUNET_NO,
825                                        &transmit_search_request,
826                                        sc);  
827   return sc;
828 }
829
830
831 /**
832  * Start search for content.
833  *
834  * @param h handle to the file sharing subsystem
835  * @param uri specifies the search parameters; can be
836  *        a KSK URI or an SKS URI.
837  * @param anonymity desired level of anonymity
838  * @param cctx initial value for the client context
839  * @return context that can be used to control the search
840  */
841 struct GNUNET_FS_SearchContext *
842 GNUNET_FS_search_start (struct GNUNET_FS_Handle *h,
843                         const struct GNUNET_FS_Uri *uri,
844                         uint32_t anonymity,
845                         void *cctx)
846 {
847   return search_start (h, uri, anonymity, cctx, NULL);
848 }
849
850
851 /**
852  * Pause search.  
853  *
854  * @param sc context for the search that should be paused
855  */
856 void 
857 GNUNET_FS_search_pause (struct GNUNET_FS_SearchContext *sc)
858 {
859   struct GNUNET_FS_ProgressInfo pi;
860
861   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
862     GNUNET_SCHEDULER_cancel (sc->h->sched,
863                              sc->task);
864   sc->task = GNUNET_SCHEDULER_NO_TASK;
865   if (NULL != sc->client)
866     GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO);
867   sc->client = NULL;
868   // FIXME: make persistent!
869   // FIXME: should this freeze all active probes?
870   pi.status = GNUNET_FS_STATUS_SEARCH_PAUSED;
871   make_search_status (&pi, sc);
872   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
873                                  &pi);
874 }
875
876
877 /**
878  * Continue paused search.
879  *
880  * @param sc context for the search that should be resumed
881  */
882 void 
883 GNUNET_FS_search_continue (struct GNUNET_FS_SearchContext *sc)
884 {
885   struct GNUNET_FS_ProgressInfo pi;
886
887   GNUNET_assert (sc->client == NULL);
888   GNUNET_assert (sc->task == GNUNET_SCHEDULER_NO_TASK);
889   do_reconnect (sc, NULL);
890   // FIXME: make persistent!
891   pi.status = GNUNET_FS_STATUS_SEARCH_CONTINUED;
892   make_search_status (&pi, sc);
893   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
894                                  &pi);
895 }
896
897
898 /**
899  * Free the given search result.
900  *
901  * @param cls the global FS handle
902  * @param key the key for the search result (unused)
903  * @param value the search result to free
904  * @return GNUNET_OK
905  */
906 static int
907 search_result_free (void *cls,
908                     const GNUNET_HashCode * key,
909                     void *value)
910 {
911   struct GNUNET_FS_SearchContext *sc = cls;
912   struct GNUNET_FS_Handle *h = sc->h;
913   struct SearchResult *sr = value;
914   struct GNUNET_FS_ProgressInfo pi;
915
916   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED;
917   make_search_status (&pi, sc);
918   pi.value.search.specifics.result_stopped.cctx = sr->client_info;
919   pi.value.search.specifics.result_stopped.meta = sr->meta;
920   pi.value.search.specifics.result_stopped.uri = sr->uri;
921   sr->client_info = h->upcb (h->upcb_cls,
922                              &pi);
923   GNUNET_break (NULL == sr->client_info);
924   
925   GNUNET_FS_uri_destroy (sr->uri);
926   GNUNET_CONTAINER_meta_data_destroy (sr->meta);
927   if (sr->probe_ctx != NULL)
928     {
929       GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
930       h->active_probes--;
931       /* FIXME: trigger starting of new
932          probes here!? Maybe not -- could
933          cause new probes to be immediately
934          stopped again... */
935     }
936   if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK)
937     {
938       GNUNET_SCHEDULER_cancel (h->sched,
939                                sr->probe_cancel_task);
940     }
941   GNUNET_free (sr);
942   return GNUNET_OK;
943 }
944
945
946 /**
947  * Stop search for content.
948  *
949  * @param sc context for the search that should be stopped
950  */
951 void 
952 GNUNET_FS_search_stop (struct GNUNET_FS_SearchContext *sc)
953 {
954   struct GNUNET_FS_ProgressInfo pi;
955   unsigned int i;
956   struct GNUNET_FS_SearchContext *parent;
957
958   // FIXME: make un-persistent!
959   if (NULL != (parent = sc->parent))
960     {
961       GNUNET_CONTAINER_DLL_remove (parent->child_head,
962                                    parent->child_tail,
963                                    sc);
964       sc->parent = NULL;
965     }
966   while (NULL != sc->child_head)
967     GNUNET_FS_search_stop (sc->child_head);
968   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
969                                          &search_result_free,
970                                          sc);
971   pi.status = GNUNET_FS_STATUS_SEARCH_STOPPED;
972   make_search_status (&pi, sc);
973   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
974                                  &pi);
975   GNUNET_break (NULL == sc->client_info);
976   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
977     GNUNET_SCHEDULER_cancel (sc->h->sched,
978                              sc->task);
979   if (NULL != sc->client)
980     GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO);
981   GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map);
982   if (sc->requests != NULL)
983     {
984       GNUNET_assert (GNUNET_FS_uri_test_ksk (sc->uri));
985       for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
986         GNUNET_CONTAINER_multihashmap_destroy (sc->requests[i].results);
987     }
988   GNUNET_free_non_null (sc->requests);
989   GNUNET_FS_uri_destroy (sc->uri);
990   GNUNET_free (sc);
991 }
992
993 /* end of fs_search.c */