working on fs, bugfixes
[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_encrypt (&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_break_op (0);     /* sblock malformed */
468       GNUNET_free_non_null (emsg);
469       GNUNET_CONTAINER_meta_data_destroy (meta);
470       return;
471     }
472   /* process */
473   process_sks_result (sc, id, uri, meta);
474   /* clean up */
475   GNUNET_FS_uri_destroy (uri);
476   GNUNET_CONTAINER_meta_data_destroy (meta);
477 }
478
479
480 /**
481  * Process a search result.
482  *
483  * @param sc our search context
484  * @param type type of the result
485  * @param expiration when it will expire
486  * @param data the (encrypted) response
487  * @param size size of data
488  */
489 static void
490 process_result (struct GNUNET_FS_SearchContext *sc,
491                 uint32_t type,
492                 struct GNUNET_TIME_Absolute expiration,
493                 const void *data,
494                 size_t size)
495 {
496   if (GNUNET_TIME_absolute_get_duration (expiration).value > 0)
497     return; /* result expired */
498   switch (type)
499     {
500     case GNUNET_DATASTORE_BLOCKTYPE_KBLOCK:
501       if (! GNUNET_FS_uri_test_ksk (sc->uri))
502         {
503           GNUNET_break (0);
504           return;
505         }
506       if (sizeof (struct KBlock) > size)
507         {
508           GNUNET_break_op (0);
509           return;
510         }
511       process_kblock (sc, data, size);
512       break;
513     case GNUNET_DATASTORE_BLOCKTYPE_SBLOCK:
514       if (! GNUNET_FS_uri_test_ksk (sc->uri))
515         {
516           GNUNET_break (0);
517           return;
518         }
519       if (sizeof (struct SBlock) > size)
520         {
521           GNUNET_break_op (0);
522           return;
523         }
524       process_sblock (sc, data, size);
525       break;
526     case GNUNET_DATASTORE_BLOCKTYPE_ANY:
527     case GNUNET_DATASTORE_BLOCKTYPE_DBLOCK:
528     case GNUNET_DATASTORE_BLOCKTYPE_ONDEMAND:
529     case GNUNET_DATASTORE_BLOCKTYPE_IBLOCK:
530       GNUNET_break (0);
531       break;
532     default:
533       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
534                   _("Got result with unknown block type `%d', ignoring"),
535                   type);
536       break;
537     }
538 }
539
540
541 /**
542  * Shutdown any existing connection to the FS
543  * service and try to establish a fresh one
544  * (and then re-transmit our search request).
545  *
546  * @param sc the search to reconnec
547  */
548 static void 
549 try_reconnect (struct GNUNET_FS_SearchContext *sc);
550
551
552 /**
553  * Type of a function to call when we receive a message
554  * from the service.
555  *
556  * @param cls closure
557  * @param msg message received, NULL on timeout or fatal error
558  */
559 static void 
560 receive_results (void *cls,
561                  const struct GNUNET_MessageHeader * msg)
562 {
563   struct GNUNET_FS_SearchContext *sc = cls;
564   const struct PutMessage *cm;
565   uint16_t msize;
566
567   if ( (NULL == msg) ||
568        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) ||
569        (ntohs (msg->size) <= sizeof (struct PutMessage)) )
570     {
571       try_reconnect (sc);
572       return;
573     }
574   msize = ntohs (msg->size);
575   cm = (const struct PutMessage*) msg;
576   process_result (sc, 
577                   ntohl (cm->type),
578                   GNUNET_TIME_absolute_ntoh (cm->expiration),
579                   &cm[1],
580                   msize - sizeof (struct PutMessage));
581   /* continue receiving */
582   GNUNET_CLIENT_receive (sc->client,
583                          &receive_results,
584                          sc,
585                          GNUNET_TIME_UNIT_FOREVER_REL);
586 }
587
588
589 /**
590  * We're ready to transmit the search request to the
591  * file-sharing service.  Do it.
592  *
593  * @param cls closure
594  * @param size number of bytes available in buf
595  * @param buf where the callee should write the message
596  * @return number of bytes written to buf
597  */
598 static size_t
599 transmit_search_request (void *cls,
600                          size_t size, 
601                          void *buf)
602 {
603   struct GNUNET_FS_SearchContext *sc = cls;
604   size_t msize;
605   struct SearchMessage *sm;
606   unsigned int i;
607   const char *identifier;
608   GNUNET_HashCode key;
609   GNUNET_HashCode idh;
610
611   if (NULL == buf)
612     {
613       try_reconnect (sc);
614       return 0;
615     }
616   if (GNUNET_FS_uri_test_ksk (sc->uri))
617     {
618       msize = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
619       GNUNET_assert (size >= msize);
620       sm = buf;
621       memset (sm, 0, msize);
622       for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
623         {
624           sm[i].header.size = htons (sizeof (struct SearchMessage));
625           sm[i].header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
626           sm[i].type = htonl (GNUNET_DATASTORE_BLOCKTYPE_KBLOCK);
627   sm[i].anonymity_level = htonl (sc->anonymity);
628           sm[i].query = sc->requests[i].query;
629         }
630     }
631   else
632     {
633       GNUNET_assert (GNUNET_FS_uri_test_sks (sc->uri));
634       msize = sizeof (struct SearchMessage);
635       GNUNET_assert (size >= msize);
636       sm = buf;
637       memset (sm, 0, msize);
638       sm->header.size = htons (sizeof (struct SearchMessage));
639       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
640       sm->type = htonl (GNUNET_DATASTORE_BLOCKTYPE_SBLOCK);
641       sm->anonymity_level = htonl (sc->anonymity);
642       sm->target = sc->uri->data.sks.namespace;
643       identifier = sc->uri->data.sks.identifier;
644       GNUNET_CRYPTO_hash (identifier,
645                           strlen (identifier),
646                           &key);
647       GNUNET_CRYPTO_hash (&key,
648                           sizeof (GNUNET_HashCode),
649                           &idh);
650       GNUNET_CRYPTO_hash_xor (&idh,
651                               &sm->target,
652                               &sm->query);
653    }
654   GNUNET_CLIENT_receive (sc->client,
655                          &receive_results,
656                          sc,
657                          GNUNET_TIME_UNIT_FOREVER_REL);
658   return msize;
659 }
660
661
662 /**
663  * Reconnect to the FS service and transmit
664  * our queries NOW.
665  *
666  * @param cls our search context
667  * @param tc unused
668  */
669 static void
670 do_reconnect (void *cls,
671               const struct GNUNET_SCHEDULER_TaskContext *tc)
672 {
673   struct GNUNET_FS_SearchContext *sc = cls;
674   struct GNUNET_CLIENT_Connection *client;
675   size_t size;
676   
677   sc->task = GNUNET_SCHEDULER_NO_TASK;
678   client = GNUNET_CLIENT_connect (sc->h->sched,
679                                   "fs",
680                                   sc->h->cfg);
681   if (NULL == client)
682     {
683       try_reconnect (sc);
684       return;
685     }
686   sc->client = client;
687   if (GNUNET_FS_uri_test_ksk (sc->uri))
688     size = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
689   else
690     size = sizeof (struct SearchMessage);
691   GNUNET_CLIENT_notify_transmit_ready (client,
692                                        size,
693                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
694                                        GNUNET_NO,
695                                        &transmit_search_request,
696                                        sc);  
697 }
698
699
700 /**
701  * Shutdown any existing connection to the FS
702  * service and try to establish a fresh one
703  * (and then re-transmit our search request).
704  *
705  * @param sc the search to reconnec
706  */
707 static void 
708 try_reconnect (struct GNUNET_FS_SearchContext *sc)
709 {
710   if (NULL != sc->client)
711     {
712       GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO);
713       sc->client = NULL;
714     }
715   sc->task
716     = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
717                                     GNUNET_TIME_UNIT_SECONDS,
718                                     &do_reconnect,
719                                     sc);
720 }
721
722
723 /**
724  * Start search for content, internal API.
725  *
726  * @param h handle to the file sharing subsystem
727  * @param uri specifies the search parameters; can be
728  *        a KSK URI or an SKS URI.
729  * @param anonymity desired level of anonymity
730  * @param cctx initial value for the client context
731  * @param parent parent search (for namespace update searches)
732  * @return context that can be used to control the search
733  */
734 static struct GNUNET_FS_SearchContext *
735 search_start (struct GNUNET_FS_Handle *h,
736               const struct GNUNET_FS_Uri *uri,
737               uint32_t anonymity,
738               void *cctx,
739               struct GNUNET_FS_SearchContext *parent)
740 {
741   struct GNUNET_FS_SearchContext *sc;
742   struct GNUNET_CLIENT_Connection *client;
743   struct GNUNET_FS_ProgressInfo pi;
744   size_t size;
745   unsigned int i;
746   const char *keyword;
747   GNUNET_HashCode hc;
748   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;  
749   struct GNUNET_CRYPTO_RsaPrivateKey *pk;
750
751   if (GNUNET_FS_uri_test_ksk (uri))
752     {
753       size = sizeof (struct SearchMessage) * uri->data.ksk.keywordCount;
754     }
755   else
756     {
757       GNUNET_assert (GNUNET_FS_uri_test_sks (uri));
758       size = sizeof (struct SearchMessage);
759     }
760   if (size >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
761     {
762       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
763                   _("Too many keywords specified for a single search."));
764       return NULL;
765     }
766   client = GNUNET_CLIENT_connect (h->sched,
767                                   "fs",
768                                   h->cfg);
769   if (NULL == client)
770     return NULL;
771   sc = GNUNET_malloc (sizeof(struct GNUNET_FS_SearchContext));
772   sc->h = h;
773   sc->uri = GNUNET_FS_uri_dup (uri);
774   sc->anonymity = anonymity;
775   sc->start_time = GNUNET_TIME_absolute_get ();
776   sc->client = client;  
777   sc->parent = parent;
778   sc->master_result_map = GNUNET_CONTAINER_multihashmap_create (16);
779   sc->client_info = cctx;
780   if (GNUNET_FS_uri_test_ksk (uri))
781     {
782       GNUNET_assert (0 != sc->uri->data.ksk.keywordCount);
783       sc->requests = GNUNET_malloc (sizeof (struct SearchRequestEntry) *
784                                     sc->uri->data.ksk.keywordCount);
785       for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
786         {
787           keyword = &sc->uri->data.ksk.keywords[i][1];
788           GNUNET_CRYPTO_hash (keyword, strlen (keyword), &hc);
789           pk = GNUNET_CRYPTO_rsa_key_create_from_hash (&hc);
790           GNUNET_CRYPTO_rsa_key_get_public (pk, &pub);
791           GNUNET_CRYPTO_rsa_key_free (pk);
792           GNUNET_CRYPTO_hash (&pub,
793                               sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), 
794                               &sc->requests[i].query);
795           sc->requests[i].mandatory = (sc->uri->data.ksk.keywords[i][0] == '+');
796           if (sc->requests[i].mandatory)
797             sc->mandatory_count++;
798           sc->requests[i].results = GNUNET_CONTAINER_multihashmap_create (4);
799           GNUNET_CRYPTO_hash (keyword,
800                               strlen (keyword),
801                               &sc->requests[i].key);
802         }
803     }
804   if (NULL != parent)
805     {
806       // FIXME: need to track children
807       // in parent in case parent is stopped!
808     }
809   pi.status = GNUNET_FS_STATUS_SEARCH_START;
810   make_search_status (&pi, sc);
811   sc->client_info = h->upcb (h->upcb_cls,
812                              &pi);
813   GNUNET_CLIENT_notify_transmit_ready (client,
814                                        size,
815                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
816                                        GNUNET_NO,
817                                        &transmit_search_request,
818                                        sc);  
819   return sc;
820 }
821
822
823 /**
824  * Start search for content.
825  *
826  * @param h handle to the file sharing subsystem
827  * @param uri specifies the search parameters; can be
828  *        a KSK URI or an SKS URI.
829  * @param anonymity desired level of anonymity
830  * @param cctx initial value for the client context
831  * @return context that can be used to control the search
832  */
833 struct GNUNET_FS_SearchContext *
834 GNUNET_FS_search_start (struct GNUNET_FS_Handle *h,
835                         const struct GNUNET_FS_Uri *uri,
836                         uint32_t anonymity,
837                         void *cctx)
838 {
839   return search_start (h, uri, anonymity, cctx, NULL);
840 }
841
842
843 /**
844  * Pause search.  
845  *
846  * @param sc context for the search that should be paused
847  */
848 void 
849 GNUNET_FS_search_pause (struct GNUNET_FS_SearchContext *sc)
850 {
851   struct GNUNET_FS_ProgressInfo pi;
852
853   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
854     GNUNET_SCHEDULER_cancel (sc->h->sched,
855                              sc->task);
856   sc->task = GNUNET_SCHEDULER_NO_TASK;
857   if (NULL != sc->client)
858     GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO);
859   sc->client = NULL;
860   // FIXME: make persistent!
861   // FIXME: should this freeze all active probes?
862   pi.status = GNUNET_FS_STATUS_SEARCH_PAUSED;
863   make_search_status (&pi, sc);
864   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
865                                  &pi);
866 }
867
868
869 /**
870  * Continue paused search.
871  *
872  * @param sc context for the search that should be resumed
873  */
874 void 
875 GNUNET_FS_search_continue (struct GNUNET_FS_SearchContext *sc)
876 {
877   struct GNUNET_FS_ProgressInfo pi;
878
879   GNUNET_assert (sc->client == NULL);
880   GNUNET_assert (sc->task == GNUNET_SCHEDULER_NO_TASK);
881   do_reconnect (sc, NULL);
882   // FIXME: make persistent!
883   pi.status = GNUNET_FS_STATUS_SEARCH_CONTINUED;
884   make_search_status (&pi, sc);
885   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
886                                  &pi);
887 }
888
889
890 /**
891  * Free the given search result.
892  *
893  * @param cls the global FS handle
894  * @param key the key for the search result (unused)
895  * @param value the search result to free
896  * @return GNUNET_OK
897  */
898 static int
899 search_result_free (void *cls,
900                     const GNUNET_HashCode * key,
901                     void *value)
902 {
903   struct GNUNET_FS_SearchContext *sc = cls;
904   struct GNUNET_FS_Handle *h = sc->h;
905   struct SearchResult *sr = value;
906   struct GNUNET_FS_ProgressInfo pi;
907
908   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED;
909   make_search_status (&pi, sc);
910   pi.value.search.specifics.result_stopped.cctx = sr->client_info;
911   pi.value.search.specifics.result_stopped.meta = sr->meta;
912   pi.value.search.specifics.result_stopped.uri = sr->uri;
913   sr->client_info = h->upcb (h->upcb_cls,
914                              &pi);
915   GNUNET_break (NULL == sr->client_info);
916   
917   GNUNET_FS_uri_destroy (sr->uri);
918   GNUNET_CONTAINER_meta_data_destroy (sr->meta);
919   if (sr->probe_ctx != NULL)
920     {
921       GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
922       h->active_probes--;
923       /* FIXME: trigger starting of new
924          probes here!? Maybe not -- could
925          cause new probes to be immediately
926          stopped again... */
927     }
928   if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK)
929     {
930       GNUNET_SCHEDULER_cancel (h->sched,
931                                sr->probe_cancel_task);
932     }
933   GNUNET_free (sr);
934   return GNUNET_OK;
935 }
936
937
938 /**
939  * Stop search for content.
940  *
941  * @param sc context for the search that should be stopped
942  */
943 void 
944 GNUNET_FS_search_stop (struct GNUNET_FS_SearchContext *sc)
945 {
946   struct GNUNET_FS_ProgressInfo pi;
947   unsigned int i;
948
949   // FIXME: make un-persistent!
950   if (NULL != sc->parent)
951     {
952       // FIXME: need to untrack sc
953       // in parent!
954     }
955   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
956                                          &search_result_free,
957                                          sc);
958   pi.status = GNUNET_FS_STATUS_SEARCH_STOPPED;
959   make_search_status (&pi, sc);
960   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
961                                  &pi);
962   GNUNET_break (NULL == sc->client_info);
963   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
964     GNUNET_SCHEDULER_cancel (sc->h->sched,
965                              sc->task);
966   if (NULL != sc->client)
967     GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO);
968   GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map);
969   for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
970     GNUNET_CONTAINER_multihashmap_destroy (sc->requests[i].results);
971   GNUNET_free_non_null (sc->requests);
972   GNUNET_FS_uri_destroy (sc->uri);
973   GNUNET_free (sc);
974 }
975
976 /* end of fs_search.c */