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