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