giving client API option for auto-retry, making more often use of transmit_and_get_re...
[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
165  * to the given URI and if so 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
190  * how it fits in with the overall query
191  * and notify the client accordingly.
192  *
193  * @param sc context for the overall query
194  * @param ent entry for the specific keyword
195  * @param uri the URI that was found
196  * @param meta metadata associated with the URI
197  *        under the "ent" keyword
198  */
199 static void
200 process_ksk_result (struct GNUNET_FS_SearchContext *sc, 
201                     struct SearchRequestEntry *ent,
202                     const struct GNUNET_FS_Uri *uri,
203                     const struct GNUNET_CONTAINER_MetaData *meta)
204 {
205   GNUNET_HashCode key;
206   struct SearchResult *sr;
207   struct GetResultContext grc;
208   int is_new;
209
210   /* check if new */
211   if (! GNUNET_FS_uri_test_chk (uri))
212     {
213       GNUNET_break_op (0);
214       return;
215     }
216   GNUNET_CRYPTO_hash_xor (&uri->data.chk.chk.key,
217                           &uri->data.chk.chk.query,
218                           &key);
219   if (GNUNET_SYSERR ==
220       GNUNET_CONTAINER_multihashmap_get_multiple (ent->results,
221                                                   &key,
222                                                   &test_result_present,
223                                                   (void*) uri))
224     return; /* duplicate result */
225   /* try to find search result in master map */
226   grc.sr = NULL;
227   grc.uri = uri;
228   GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map,
229                                               &key,
230                                               &get_result_present,
231                                               &grc);
232   sr = grc.sr;
233   is_new = (NULL == sr) || (sr->mandatory_missing > 0);
234   if (NULL == sr)
235     {
236       sr = GNUNET_malloc (sizeof (struct SearchResult));
237       sr->uri = GNUNET_FS_uri_dup (uri);
238       sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
239       sr->mandatory_missing = sc->mandatory_count;
240       GNUNET_CONTAINER_multihashmap_put (sc->master_result_map,
241                                          &key,
242                                          sr,
243                                          GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
244     }
245   else
246     {
247       /* FIXME: consider combining the meta data */
248     }
249   /* check if mandatory satisfied */
250   if (ent->mandatory)
251     sr->mandatory_missing--;
252   else
253     sr->optional_support++;
254   if (0 != sr->mandatory_missing)
255     return;
256   if (is_new)
257     notify_client_chk_result (sc, sr);
258   else
259     notify_client_chk_update (sc, sr);
260   /* FIXME: consider starting probes for "sr" */
261 }
262
263
264 /**
265  * Start search for content, internal API.
266  *
267  * @param h handle to the file sharing subsystem
268  * @param uri specifies the search parameters; can be
269  *        a KSK URI or an SKS URI.
270  * @param anonymity desired level of anonymity
271  * @param parent parent search (for namespace update searches)
272  * @return context that can be used to control the search
273  */
274 static struct GNUNET_FS_SearchContext *
275 search_start (struct GNUNET_FS_Handle *h,
276               const struct GNUNET_FS_Uri *uri,
277               uint32_t anonymity,
278               struct GNUNET_FS_SearchContext *parent);
279
280
281 /**
282  * We have received an SKS result.  Start
283  * searching for updates and notify the
284  * client if it is a new result.
285  *
286  * @param sc context for the overall query
287  * @param id_update identifier for updates, NULL for none
288  * @param uri the URI that was found
289  * @param meta metadata associated with the URI
290   */
291 static void
292 process_sks_result (struct GNUNET_FS_SearchContext *sc, 
293                     const char *id_update,
294                     const struct GNUNET_FS_Uri *uri,
295                     const struct GNUNET_CONTAINER_MetaData *meta)
296 {
297   struct GNUNET_FS_Uri uu;
298   GNUNET_HashCode key;
299   struct SearchResult *sr;
300
301   /* check if new */
302   if (! GNUNET_FS_uri_test_ksk (uri))
303     {
304       GNUNET_break_op (0);
305       return;
306     }
307   GNUNET_CRYPTO_hash_xor (&uri->data.chk.chk.key,
308                           &uri->data.chk.chk.query,
309                           &key);
310   if (GNUNET_SYSERR ==
311       GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map,
312                                                   &key,
313                                                   &test_result_present,
314                                                   (void*) uri))
315     return; /* duplicate result */
316   sr = GNUNET_malloc (sizeof (struct SearchResult));
317   sr->uri = GNUNET_FS_uri_dup (uri);
318   sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
319   GNUNET_CONTAINER_multihashmap_put (sc->master_result_map,
320                                      &key,
321                                      sr,
322                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
323   /* FIXME: consider starting probes for "sr" */
324
325   /* notify client */
326   notify_client_chk_result (sc, sr);
327   /* search for updates */
328   if (strlen (id_update) == 0)
329     return; /* no updates */
330   uu.type = sks;
331   uu.data.sks.namespace = sc->uri->data.sks.namespace;
332   uu.data.sks.identifier = GNUNET_strdup (id_update);
333   /* FIXME: should attach update search
334      to the individual result, not
335      the entire SKS search! */
336   search_start (sc->h,
337                 &uu,
338                 sc->anonymity,
339                 sc);
340 }
341
342
343 /**
344  * Process a keyword-search result.
345  *
346  * @param sc our search context
347  * @param kb the kblock
348  * @param size size of kb
349  */
350 static void
351 process_kblock (struct GNUNET_FS_SearchContext *sc,
352                 const struct KBlock *kb,
353                 size_t size)
354 {
355   unsigned int i;
356   size_t j;
357   GNUNET_HashCode q;
358   char pt[size - sizeof (struct KBlock)];
359   struct GNUNET_CRYPTO_AesSessionKey skey;
360   struct GNUNET_CRYPTO_AesInitializationVector iv;
361   const char *eos;
362   struct GNUNET_CONTAINER_MetaData *meta;
363   struct GNUNET_FS_Uri *uri;
364   char *emsg;
365   
366   GNUNET_CRYPTO_hash (&kb->keyspace,
367                       sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
368                       &q);
369   /* find key */
370   for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
371     if (0 == memcmp (&q,
372                      &sc->requests[i].query,
373                      sizeof (GNUNET_HashCode)))
374       break;
375   if (i == sc->uri->data.ksk.keywordCount)
376     {
377       /* oops, does not match any of our keywords!? */
378       GNUNET_break (0);
379       return;
380     }
381   /* decrypt */
382   GNUNET_CRYPTO_hash_to_aes_key (&sc->requests[i].key, &skey, &iv);
383   GNUNET_CRYPTO_aes_decrypt (&kb[1],
384                              size - sizeof (struct KBlock),
385                              &skey,
386                              &iv,
387                              pt);
388   /* parse */
389   eos = memchr (pt, 0, sizeof (pt));
390   if (NULL == eos)
391     {
392       GNUNET_break_op (0);
393       return;
394     }
395   j = eos - pt + 1;
396   meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[j],
397                                                  sizeof (pt) - j);
398   if (meta == NULL)
399     {
400       GNUNET_break_op (0);       /* kblock malformed */
401       return;
402     }
403   uri = GNUNET_FS_uri_parse (pt, &emsg);
404   if (uri == NULL)
405     {
406       GNUNET_break_op (0);       /* kblock malformed */
407       GNUNET_free_non_null (emsg);
408       GNUNET_CONTAINER_meta_data_destroy (meta);
409       return;
410     }
411   /* process */
412   process_ksk_result (sc, &sc->requests[i], uri, meta);
413
414   /* clean up */
415   GNUNET_CONTAINER_meta_data_destroy (meta);
416   GNUNET_FS_uri_destroy (uri);
417 }
418
419
420 /**
421  * Process a namespace-search result.
422  *
423  * @param sc our search context
424  * @param sb the sblock
425  * @param size size of sb
426  */
427 static void
428 process_sblock (struct GNUNET_FS_SearchContext *sc,
429                 const struct SBlock *sb,
430                 size_t size)
431 {
432   size_t len = size - sizeof (struct SBlock);
433   char pt[len];
434   struct GNUNET_CRYPTO_AesSessionKey skey;
435   struct GNUNET_CRYPTO_AesInitializationVector iv;
436   struct GNUNET_FS_Uri *uri;
437   struct GNUNET_CONTAINER_MetaData *meta;
438   const char *id;
439   const char *uris;
440   size_t off;
441   char *emsg;
442   GNUNET_HashCode key;
443   char *identifier;
444
445   /* decrypt */
446   identifier = sc->uri->data.sks.identifier;
447   GNUNET_CRYPTO_hash (identifier, 
448                       strlen (identifier), 
449                       &key);
450   GNUNET_CRYPTO_hash_to_aes_key (&key, &skey, &iv);
451   GNUNET_CRYPTO_aes_encrypt (&sb[1],
452                              len,
453                              &skey,
454                              &iv,
455                              pt);
456   /* parse */
457   off = GNUNET_STRINGS_buffer_tokenize (pt,
458                                         len, 
459                                         2, 
460                                         &id, 
461                                         &uris);
462   if (off == 0)
463     {
464       GNUNET_break_op (0);     /* sblock malformed */
465       return;
466     }
467   meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[off], 
468                                                  len - off);
469   if (meta == NULL)
470     {
471       GNUNET_break_op (0);     /* sblock malformed */
472       return;
473     }
474   uri = GNUNET_FS_uri_parse (uris, &emsg);
475   if (uri == NULL)
476     {
477       GNUNET_break_op (0);     /* sblock malformed */
478       GNUNET_free_non_null (emsg);
479       GNUNET_CONTAINER_meta_data_destroy (meta);
480       return;
481     }
482   /* process */
483   process_sks_result (sc, id, uri, meta);
484   /* clean up */
485   GNUNET_FS_uri_destroy (uri);
486   GNUNET_CONTAINER_meta_data_destroy (meta);
487 }
488
489
490 /**
491  * Process a search result.
492  *
493  * @param sc our search context
494  * @param type type of the result
495  * @param expiration when it will expire
496  * @param data the (encrypted) response
497  * @param size size of data
498  */
499 static void
500 process_result (struct GNUNET_FS_SearchContext *sc,
501                 uint32_t type,
502                 struct GNUNET_TIME_Absolute expiration,
503                 const void *data,
504                 size_t size)
505 {
506   if (GNUNET_TIME_absolute_get_duration (expiration).value > 0)
507     return; /* result expired */
508   switch (type)
509     {
510     case GNUNET_DATASTORE_BLOCKTYPE_KBLOCK:
511       if (! GNUNET_FS_uri_test_ksk (sc->uri))
512         {
513           GNUNET_break (0);
514           return;
515         }
516       if (sizeof (struct KBlock) > size)
517         {
518           GNUNET_break_op (0);
519           return;
520         }
521       process_kblock (sc, data, size);
522       break;
523     case GNUNET_DATASTORE_BLOCKTYPE_SBLOCK:
524       if (! GNUNET_FS_uri_test_ksk (sc->uri))
525         {
526           GNUNET_break (0);
527           return;
528         }
529       if (sizeof (struct SBlock) > size)
530         {
531           GNUNET_break_op (0);
532           return;
533         }
534       process_sblock (sc, data, size);
535       break;
536     case GNUNET_DATASTORE_BLOCKTYPE_ANY:
537     case GNUNET_DATASTORE_BLOCKTYPE_DBLOCK:
538     case GNUNET_DATASTORE_BLOCKTYPE_ONDEMAND:
539     case GNUNET_DATASTORE_BLOCKTYPE_IBLOCK:
540       GNUNET_break (0);
541       break;
542     default:
543       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
544                   _("Got result with unknown block type `%d', ignoring"),
545                   type);
546       break;
547     }
548 }
549
550
551 /**
552  * Shutdown any existing connection to the FS
553  * service and try to establish a fresh one
554  * (and then re-transmit our search request).
555  *
556  * @param sc the search to reconnec
557  */
558 static void 
559 try_reconnect (struct GNUNET_FS_SearchContext *sc);
560
561
562 /**
563  * Type of a function to call when we receive a message
564  * from the service.
565  *
566  * @param cls closure
567  * @param msg message received, NULL on timeout or fatal error
568  */
569 static void 
570 receive_results (void *cls,
571                  const struct GNUNET_MessageHeader * msg)
572 {
573   struct GNUNET_FS_SearchContext *sc = cls;
574   const struct ContentMessage *cm;
575   uint16_t msize;
576
577   if ( (NULL == msg) ||
578        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_CONTENT) ||
579        (ntohs (msg->size) <= sizeof (struct ContentMessage)) )
580     {
581       try_reconnect (sc);
582       return;
583     }
584   msize = ntohs (msg->size);
585   cm = (const struct ContentMessage*) msg;
586   process_result (sc, 
587                   ntohl (cm->type),
588                   GNUNET_TIME_absolute_ntoh (cm->expiration),
589                   &cm[1],
590                   msize - sizeof (struct ContentMessage));
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 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].anonymity_level = htonl (sc->anonymity);
636           sm[i].query = sc->requests[i].query;
637         }
638     }
639   else
640     {
641       msize = sizeof (struct SearchMessage);
642       GNUNET_assert (size >= msize);
643       sm = buf;
644       memset (sm, 0, msize);
645       sm->header.size = htons (sizeof (struct SearchMessage));
646       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
647       sm->anonymity_level = htonl (sc->anonymity);
648       sm->target = sc->uri->data.sks.namespace;
649       identifier = sc->uri->data.sks.identifier;
650       GNUNET_CRYPTO_hash (identifier,
651                           strlen (identifier),
652                           &idh);
653       GNUNET_CRYPTO_hash_xor (&idh,
654                               &sm->target,
655                               &sm->query);
656     }
657   GNUNET_CLIENT_receive (sc->client,
658                          &receive_results,
659                          sc,
660                          GNUNET_TIME_UNIT_FOREVER_REL);
661   return msize;
662 }
663
664
665 /**
666  * Reconnect to the FS service and transmit
667  * our queries NOW.
668  *
669  * @param cls our search context
670  * @param tc unused
671  */
672 static void
673 do_reconnect (void *cls,
674               const struct GNUNET_SCHEDULER_TaskContext *tc)
675 {
676   struct GNUNET_FS_SearchContext *sc = cls;
677   struct GNUNET_CLIENT_Connection *client;
678   size_t size;
679   
680   sc->task = GNUNET_SCHEDULER_NO_TASK;
681   client = GNUNET_CLIENT_connect (sc->h->sched,
682                                   "fs",
683                                   sc->h->cfg);
684   if (NULL == client)
685     {
686       try_reconnect (sc);
687       return;
688     }
689   sc->client = client;
690   if (GNUNET_FS_uri_test_ksk (sc->uri))
691     size = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
692   else
693     size = sizeof (struct SearchMessage);
694   GNUNET_CLIENT_notify_transmit_ready (client,
695                                        size,
696                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
697                                        GNUNET_NO,
698                                        &transmit_search_request,
699                                        sc);  
700 }
701
702
703 /**
704  * Shutdown any existing connection to the FS
705  * service and try to establish a fresh one
706  * (and then re-transmit our search request).
707  *
708  * @param sc the search to reconnec
709  */
710 static void 
711 try_reconnect (struct GNUNET_FS_SearchContext *sc)
712 {
713   if (NULL != sc->client)
714     {
715       GNUNET_CLIENT_disconnect (sc->client);
716       sc->client = NULL;
717     }
718   sc->task
719     = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
720                                     GNUNET_NO,
721                                     GNUNET_SCHEDULER_PRIORITY_IDLE,
722                                     GNUNET_SCHEDULER_NO_TASK,
723                                     GNUNET_TIME_UNIT_SECONDS,
724                                     &do_reconnect,
725                                     sc);
726 }
727
728
729 /**
730  * Start search for content, internal API.
731  *
732  * @param h handle to the file sharing subsystem
733  * @param uri specifies the search parameters; can be
734  *        a KSK URI or an SKS URI.
735  * @param anonymity desired level of anonymity
736  * @param parent parent search (for namespace update searches)
737  * @return context that can be used to control the search
738  */
739 static struct GNUNET_FS_SearchContext *
740 search_start (struct GNUNET_FS_Handle *h,
741               const struct GNUNET_FS_Uri *uri,
742               uint32_t anonymity,
743               struct GNUNET_FS_SearchContext *parent)
744 {
745   struct GNUNET_FS_SearchContext *sc;
746   struct GNUNET_CLIENT_Connection *client;
747   struct GNUNET_FS_ProgressInfo pi;
748   size_t size;
749   unsigned int i;
750   const char *keyword;
751   GNUNET_HashCode hc;
752   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;  
753   struct GNUNET_CRYPTO_RsaPrivateKey *pk;
754
755   if (GNUNET_FS_uri_test_ksk (uri))
756     {
757       size = sizeof (struct SearchMessage) * uri->data.ksk.keywordCount;
758     }
759   else
760     {
761       GNUNET_assert (GNUNET_FS_uri_test_sks (uri));
762       size = sizeof (struct SearchMessage);
763     }
764   if (size >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
765     {
766       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
767                   _("Too many keywords specified for a single search."));
768       return NULL;
769     }
770   client = GNUNET_CLIENT_connect (h->sched,
771                                   "fs",
772                                   h->cfg);
773   if (NULL == client)
774     return NULL;
775   sc = GNUNET_malloc (sizeof(struct GNUNET_FS_SearchContext));
776   sc->h = h;
777   sc->uri = GNUNET_FS_uri_dup (uri);
778   sc->anonymity = anonymity;
779   sc->start_time = GNUNET_TIME_absolute_get ();
780   sc->client = client;  
781   sc->parent = parent;
782   sc->master_result_map = GNUNET_CONTAINER_multihashmap_create (16);
783
784   sc->requests = GNUNET_malloc (sizeof (struct SearchRequestEntry) *
785                                 sc->uri->data.ksk.keywordCount);
786   for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
787     {
788       keyword = &sc->uri->data.ksk.keywords[i][1];
789       GNUNET_CRYPTO_hash (keyword, strlen (keyword), &hc);
790       pk = GNUNET_CRYPTO_rsa_key_create_from_hash (&hc);
791       GNUNET_CRYPTO_rsa_key_get_public (pk, &pub);
792       GNUNET_CRYPTO_rsa_key_free (pk);
793       GNUNET_CRYPTO_hash (&pub,
794                           sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), 
795                           &sc->requests[i].query);
796       sc->requests[i].mandatory = (sc->uri->data.ksk.keywords[i][0] == '+');
797       if (sc->requests[i].mandatory)
798         sc->mandatory_count++;
799       sc->requests[i].results = GNUNET_CONTAINER_multihashmap_create (4);
800       GNUNET_CRYPTO_hash (keyword,
801                           strlen (keyword),
802                           &sc->requests[i].key);
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  * @return context that can be used to control the search
831  */
832 struct GNUNET_FS_SearchContext *
833 GNUNET_FS_search_start (struct GNUNET_FS_Handle *h,
834                         const struct GNUNET_FS_Uri *uri,
835                         uint32_t anonymity)
836 {
837   return search_start (h, uri, anonymity, NULL);
838 }
839
840
841 /**
842  * Pause search.  
843  *
844  * @param sc context for the search that should be paused
845  */
846 void 
847 GNUNET_FS_search_pause (struct GNUNET_FS_SearchContext *sc)
848 {
849   struct GNUNET_FS_ProgressInfo pi;
850
851   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
852     GNUNET_SCHEDULER_cancel (sc->h->sched,
853                              sc->task);
854   sc->task = GNUNET_SCHEDULER_NO_TASK;
855   if (NULL != sc->client)
856     GNUNET_CLIENT_disconnect (sc->client);
857   sc->client = NULL;
858   // FIXME: make persistent!
859   // FIXME: should this freeze all active probes?
860   pi.status = GNUNET_FS_STATUS_SEARCH_PAUSED;
861   make_search_status (&pi, sc);
862   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
863                                  &pi);
864 }
865
866
867 /**
868  * Continue paused search.
869  *
870  * @param sc context for the search that should be resumed
871  */
872 void 
873 GNUNET_FS_search_continue (struct GNUNET_FS_SearchContext *sc)
874 {
875   struct GNUNET_FS_ProgressInfo pi;
876
877   GNUNET_assert (sc->client == NULL);
878   GNUNET_assert (sc->task == GNUNET_SCHEDULER_NO_TASK);
879   do_reconnect (sc, NULL);
880   // FIXME: make persistent!
881   pi.status = GNUNET_FS_STATUS_SEARCH_CONTINUED;
882   make_search_status (&pi, sc);
883   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
884                                  &pi);
885 }
886
887
888 /**
889  * Free the given search result.
890  *
891  * @param cls the global FS handle
892  * @param key the key for the search result (unused)
893  * @param value the search result to free
894  * @return GNUNET_OK
895  */
896 static int
897 search_result_free (void *cls,
898                     const GNUNET_HashCode * key,
899                     void *value)
900 {
901   struct GNUNET_FS_SearchContext *sc = cls;
902   struct GNUNET_FS_Handle *h = sc->h;
903   struct SearchResult *sr = value;
904   struct GNUNET_FS_ProgressInfo pi;
905
906   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED;
907   make_search_status (&pi, sc);
908   pi.value.search.specifics.result_stopped.cctx = sr->client_info;
909   pi.value.search.specifics.result_stopped.meta = sr->meta;
910   pi.value.search.specifics.result_stopped.uri = sr->uri;
911   sr->client_info = h->upcb (h->upcb_cls,
912                              &pi);
913   GNUNET_break (NULL == sr->client_info);
914   
915   GNUNET_FS_uri_destroy (sr->uri);
916   GNUNET_CONTAINER_meta_data_destroy (sr->meta);
917   if (sr->probe_ctx != NULL)
918     {
919       GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
920       h->active_probes--;
921       /* FIXME: trigger starting of new
922          probes here!? Maybe not -- could
923          cause new probes to be immediately
924          stopped again... */
925     }
926   if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK)
927     {
928       GNUNET_SCHEDULER_cancel (h->sched,
929                                sr->probe_cancel_task);
930     }
931   GNUNET_free (sr);
932   return GNUNET_OK;
933 }
934
935
936 /**
937  * Stop search for content.
938  *
939  * @param sc context for the search that should be stopped
940  */
941 void 
942 GNUNET_FS_search_stop (struct GNUNET_FS_SearchContext *sc)
943 {
944   struct GNUNET_FS_ProgressInfo pi;
945   unsigned int i;
946
947   // FIXME: make un-persistent!
948   if (NULL != sc->parent)
949     {
950       // FIXME: need to untrack sc
951       // in parent!
952     }
953   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
954                                          &search_result_free,
955                                          sc);
956   pi.status = GNUNET_FS_STATUS_SEARCH_STOPPED;
957   make_search_status (&pi, sc);
958   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
959                                  &pi);
960   GNUNET_break (NULL == sc->client_info);
961   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
962     GNUNET_SCHEDULER_cancel (sc->h->sched,
963                              sc->task);
964   if (NULL != sc->client)
965     GNUNET_CLIENT_disconnect (sc->client);
966   GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map);
967   for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
968     GNUNET_CONTAINER_multihashmap_destroy (sc->requests[i].results);
969   GNUNET_free_non_null (sc->requests);
970   GNUNET_FS_uri_destroy (sc->uri);
971   GNUNET_free (sc);
972 }
973
974 /* end of fs_search.c */