hacking on fs
[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                                        &transmit_search_request,
698                                        sc);  
699 }
700
701
702 /**
703  * Shutdown any existing connection to the FS
704  * service and try to establish a fresh one
705  * (and then re-transmit our search request).
706  *
707  * @param sc the search to reconnec
708  */
709 static void 
710 try_reconnect (struct GNUNET_FS_SearchContext *sc)
711 {
712   if (NULL != sc->client)
713     {
714       GNUNET_CLIENT_disconnect (sc->client);
715       sc->client = NULL;
716     }
717   sc->task
718     = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
719                                     GNUNET_NO,
720                                     GNUNET_SCHEDULER_PRIORITY_IDLE,
721                                     GNUNET_SCHEDULER_NO_TASK,
722                                     GNUNET_TIME_UNIT_SECONDS,
723                                     &do_reconnect,
724                                     sc);
725 }
726
727
728 /**
729  * Start search for content, internal API.
730  *
731  * @param h handle to the file sharing subsystem
732  * @param uri specifies the search parameters; can be
733  *        a KSK URI or an SKS URI.
734  * @param anonymity desired level of anonymity
735  * @param parent parent search (for namespace update searches)
736  * @return context that can be used to control the search
737  */
738 static struct GNUNET_FS_SearchContext *
739 search_start (struct GNUNET_FS_Handle *h,
740               const struct GNUNET_FS_Uri *uri,
741               uint32_t anonymity,
742               struct GNUNET_FS_SearchContext *parent)
743 {
744   struct GNUNET_FS_SearchContext *sc;
745   struct GNUNET_CLIENT_Connection *client;
746   struct GNUNET_FS_ProgressInfo pi;
747   size_t size;
748   unsigned int i;
749   const char *keyword;
750   GNUNET_HashCode hc;
751   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;  
752   struct GNUNET_CRYPTO_RsaPrivateKey *pk;
753
754   if (GNUNET_FS_uri_test_ksk (uri))
755     {
756       size = sizeof (struct SearchMessage) * uri->data.ksk.keywordCount;
757     }
758   else
759     {
760       GNUNET_assert (GNUNET_FS_uri_test_sks (uri));
761       size = sizeof (struct SearchMessage);
762     }
763   if (size >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
764     {
765       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
766                   _("Too many keywords specified for a single search."));
767       return NULL;
768     }
769   client = GNUNET_CLIENT_connect (h->sched,
770                                   "fs",
771                                   h->cfg);
772   if (NULL == client)
773     return NULL;
774   sc = GNUNET_malloc (sizeof(struct GNUNET_FS_SearchContext));
775   sc->h = h;
776   sc->uri = GNUNET_FS_uri_dup (uri);
777   sc->anonymity = anonymity;
778   sc->start_time = GNUNET_TIME_absolute_get ();
779   sc->client = client;  
780   sc->parent = parent;
781   sc->master_result_map = GNUNET_CONTAINER_multihashmap_create (16);
782
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   if (NULL != parent)
804     {
805       // FIXME: need to track children
806       // in parent in case parent is stopped!
807     }
808   pi.status = GNUNET_FS_STATUS_SEARCH_START;
809   make_search_status (&pi, sc);
810   sc->client_info = h->upcb (h->upcb_cls,
811                              &pi);
812   GNUNET_CLIENT_notify_transmit_ready (client,
813                                        size,
814                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
815                                        &transmit_search_request,
816                                        sc);  
817   return sc;
818 }
819
820
821 /**
822  * Start search for content.
823  *
824  * @param h handle to the file sharing subsystem
825  * @param uri specifies the search parameters; can be
826  *        a KSK URI or an SKS URI.
827  * @param anonymity desired level of anonymity
828  * @return context that can be used to control the search
829  */
830 struct GNUNET_FS_SearchContext *
831 GNUNET_FS_search_start (struct GNUNET_FS_Handle *h,
832                         const struct GNUNET_FS_Uri *uri,
833                         uint32_t anonymity)
834 {
835   return search_start (h, uri, anonymity, NULL);
836 }
837
838
839 /**
840  * Pause search.  
841  *
842  * @param sc context for the search that should be paused
843  */
844 void 
845 GNUNET_FS_search_pause (struct GNUNET_FS_SearchContext *sc)
846 {
847   struct GNUNET_FS_ProgressInfo pi;
848
849   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
850     GNUNET_SCHEDULER_cancel (sc->h->sched,
851                              sc->task);
852   sc->task = GNUNET_SCHEDULER_NO_TASK;
853   if (NULL != sc->client)
854     GNUNET_CLIENT_disconnect (sc->client);
855   sc->client = NULL;
856   // FIXME: make persistent!
857   // FIXME: should this freeze all active probes?
858   pi.status = GNUNET_FS_STATUS_SEARCH_PAUSED;
859   make_search_status (&pi, sc);
860   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
861                                  &pi);
862 }
863
864
865 /**
866  * Continue paused search.
867  *
868  * @param sc context for the search that should be resumed
869  */
870 void 
871 GNUNET_FS_search_continue (struct GNUNET_FS_SearchContext *sc)
872 {
873   struct GNUNET_FS_ProgressInfo pi;
874
875   GNUNET_assert (sc->client == NULL);
876   GNUNET_assert (sc->task == GNUNET_SCHEDULER_NO_TASK);
877   do_reconnect (sc, NULL);
878   // FIXME: make persistent!
879   pi.status = GNUNET_FS_STATUS_SEARCH_CONTINUED;
880   make_search_status (&pi, sc);
881   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
882                                  &pi);
883 }
884
885
886 /**
887  * Free the given search result.
888  *
889  * @param cls the global FS handle
890  * @param key the key for the search result (unused)
891  * @param value the search result to free
892  * @return GNUNET_OK
893  */
894 static int
895 search_result_free (void *cls,
896                     const GNUNET_HashCode * key,
897                     void *value)
898 {
899   struct GNUNET_FS_SearchContext *sc = cls;
900   struct GNUNET_FS_Handle *h = sc->h;
901   struct SearchResult *sr = value;
902   struct GNUNET_FS_ProgressInfo pi;
903
904   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED;
905   make_search_status (&pi, sc);
906   pi.value.search.specifics.result_stopped.cctx = sr->client_info;
907   pi.value.search.specifics.result_stopped.meta = sr->meta;
908   pi.value.search.specifics.result_stopped.uri = sr->uri;
909   sr->client_info = h->upcb (h->upcb_cls,
910                              &pi);
911   GNUNET_break (NULL == sr->client_info);
912   
913   GNUNET_FS_uri_destroy (sr->uri);
914   GNUNET_CONTAINER_meta_data_destroy (sr->meta);
915   if (sr->probe_ctx != NULL)
916     {
917       GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
918       h->active_probes--;
919       /* FIXME: trigger starting of new
920          probes here!? Maybe not -- could
921          cause new probes to be immediately
922          stopped again... */
923     }
924   if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK)
925     {
926       GNUNET_SCHEDULER_cancel (h->sched,
927                                sr->probe_cancel_task);
928     }
929   GNUNET_free (sr);
930   return GNUNET_OK;
931 }
932
933
934 /**
935  * Stop search for content.
936  *
937  * @param sc context for the search that should be stopped
938  */
939 void 
940 GNUNET_FS_search_stop (struct GNUNET_FS_SearchContext *sc)
941 {
942   struct GNUNET_FS_ProgressInfo pi;
943   unsigned int i;
944
945   // FIXME: make un-persistent!
946   if (NULL != sc->parent)
947     {
948       // FIXME: need to untrack sc
949       // in parent!
950     }
951   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
952                                          &search_result_free,
953                                          sc);
954   pi.status = GNUNET_FS_STATUS_SEARCH_STOPPED;
955   make_search_status (&pi, sc);
956   sc->client_info = sc->h->upcb (sc->h->upcb_cls,
957                                  &pi);
958   GNUNET_break (NULL == sc->client_info);
959   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
960     GNUNET_SCHEDULER_cancel (sc->h->sched,
961                              sc->task);
962   if (NULL != sc->client)
963     GNUNET_CLIENT_disconnect (sc->client);
964   GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map);
965   for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
966     GNUNET_CONTAINER_multihashmap_destroy (sc->requests[i].results);
967   GNUNET_free_non_null (sc->requests);
968   GNUNET_FS_uri_destroy (sc->uri);
969   GNUNET_free (sc);
970 }
971
972 /* end of fs_search.c */