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