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