- htons => htonl
[oweals/gnunet.git] / src / fs / fs_search.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001-2013 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 3, 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 Tem ple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file fs/fs_search.c
22  * @brief Helper functions for searching.
23  * @author Christian Grothoff
24  */
25 #include "platform.h"
26 #include "gnunet_constants.h"
27 #include "gnunet_fs_service.h"
28 #include "gnunet_protocols.h"
29 #include "fs_api.h"
30 #include "fs_publish_ublock.h"
31
32
33 /**
34  * Number of availability trials we perform per search result.
35  */
36 #define AVAILABILITY_TRIALS_MAX 8
37
38 /**
39  * Fill in all of the generic fields for a search event and
40  * call the callback.
41  *
42  * @param pi structure to fill in
43  * @param h file-sharing handle
44  * @param sc overall search context
45  * @return value returned by the callback
46  */
47 void *
48 GNUNET_FS_search_make_status_ (struct GNUNET_FS_ProgressInfo *pi,
49                                struct GNUNET_FS_Handle *h,
50                                struct GNUNET_FS_SearchContext *sc)
51 {
52   void *ret;
53
54   pi->value.search.sc = sc;
55   pi->value.search.cctx = (NULL != sc) ? sc->client_info : NULL;
56   pi->value.search.pctx =
57     ((NULL == sc) || (NULL == sc->psearch_result))
58     ? NULL
59     : sc->psearch_result->client_info;
60   pi->value.search.query = (NULL != sc) ? sc->uri : NULL;
61   pi->value.search.duration = (NULL != sc)
62     ? GNUNET_TIME_absolute_get_duration (sc->start_time)
63     : GNUNET_TIME_UNIT_ZERO;
64   pi->value.search.anonymity = (NULL != sc) ? sc->anonymity : 0;
65   pi->fsh = h;
66   ret = h->upcb (h->upcb_cls, pi);
67   return ret;
68 }
69
70
71 /**
72  * Check if the given result is identical to the given URI.
73  *
74  * @param cls points to the URI we check against
75  * @param key not used
76  * @param value a `struct GNUNET_FS_SearchResult` who's URI we
77  *        should compare with
78  * @return #GNUNET_SYSERR if the result is present,
79  *         #GNUNET_OK otherwise
80  */
81 static int
82 test_result_present (void *cls,
83                      const struct GNUNET_HashCode * key,
84                      void *value)
85 {
86   const struct GNUNET_FS_Uri *uri = cls;
87   struct GNUNET_FS_SearchResult *sr = value;
88
89   if (GNUNET_FS_uri_test_equal (uri, 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 GNUNET_FS_SearchResult *sr)
105 {
106   struct GNUNET_FS_ProgressInfo pi;
107
108   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT;
109   pi.value.search.specifics.result.meta = sr->meta;
110   pi.value.search.specifics.result.uri = sr->uri;
111   pi.value.search.specifics.result.result = sr;
112   pi.value.search.specifics.result.applicability_rank = sr->optional_support;
113   sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc->h, sc);
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 GNUNET_FS_SearchResult *sr)
127 {
128   struct GNUNET_FS_ProgressInfo pi;
129
130   pi.status = GNUNET_FS_STATUS_SEARCH_UPDATE;
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 = sr->optional_support;
139   pi.value.search.specifics.update.current_probe_time
140     = GNUNET_TIME_absolute_get_duration (sr->probe_active_time);
141   sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc->h, sc);
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 GNUNET_FS_SearchResult *sr;
160 };
161
162
163 /**
164  * Check if the given result is identical to the given URI and if so
165  * return it.
166  *
167  * @param cls a `struct GetResultContext`
168  * @param key not used
169  * @param value a `struct GNUNET_FS_SearchResult` who's URI we
170  *        should compare with
171  * @return #GNUNET_OK
172  */
173 static int
174 get_result_present (void *cls, const struct GNUNET_HashCode * key, void *value)
175 {
176   struct GetResultContext *grc = cls;
177   struct GNUNET_FS_SearchResult *sr = value;
178
179   if (GNUNET_FS_uri_test_equal (grc->uri, sr->uri))
180     grc->sr = sr;
181   return GNUNET_OK;
182 }
183
184
185 /**
186  * Signal result of last probe to client and then schedule next
187  * probe.
188  *
189  * @param sr search result to signal for
190  */
191 static void
192 signal_probe_result (struct GNUNET_FS_SearchResult *sr)
193 {
194   struct GNUNET_FS_ProgressInfo pi;
195
196   pi.status = GNUNET_FS_STATUS_SEARCH_UPDATE;
197   pi.value.search.specifics.update.cctx = sr->client_info;
198   pi.value.search.specifics.update.meta = sr->meta;
199   pi.value.search.specifics.update.uri = sr->uri;
200   pi.value.search.specifics.update.availability_rank
201     = 2 * sr->availability_success - sr->availability_trials;
202   pi.value.search.specifics.update.availability_certainty
203     = sr->availability_trials;
204   pi.value.search.specifics.update.applicability_rank = sr->optional_support;
205   pi.value.search.specifics.update.current_probe_time
206     = GNUNET_TIME_absolute_get_duration (sr->probe_active_time);
207   sr->client_info = GNUNET_FS_search_make_status_ (&pi, sr->h, sr->sc);
208   GNUNET_FS_search_start_probe_ (sr);
209 }
210
211
212 /**
213  * Handle the case where we have failed to receive a response for our probe.
214  *
215  * @param cls our `struct GNUNET_FS_SearchResult *`
216  * @param tc scheduler context
217  */
218 static void
219 probe_failure_handler (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
220 {
221   struct GNUNET_FS_SearchResult *sr = cls;
222
223   sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
224   sr->availability_trials++;
225   GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
226   sr->probe_ctx = NULL;
227   if (GNUNET_SCHEDULER_NO_TASK != sr->probe_ping_task)
228   {
229     GNUNET_SCHEDULER_cancel (sr->probe_ping_task);
230     sr->probe_ping_task = GNUNET_SCHEDULER_NO_TASK;
231   }
232   GNUNET_FS_search_result_sync_ (sr);
233   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
234               "Probe #%u for search result %p failed\n",
235               sr->availability_trials,
236               sr);
237   signal_probe_result (sr);
238 }
239
240
241 /**
242  * Handle the case where we have gotten a response for our probe.
243  *
244  * @param cls our `struct GNUNET_FS_SearchResult *`
245  * @param tc scheduler context
246  */
247 static void
248 probe_success_handler (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
249 {
250   struct GNUNET_FS_SearchResult *sr = cls;
251
252   sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
253   sr->availability_trials++;
254   sr->availability_success++;
255   GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
256   sr->probe_ctx = NULL;
257   if (GNUNET_SCHEDULER_NO_TASK != sr->probe_ping_task)
258   {
259     GNUNET_SCHEDULER_cancel (sr->probe_ping_task);
260     sr->probe_ping_task = GNUNET_SCHEDULER_NO_TASK;
261   }
262   GNUNET_FS_search_result_sync_ (sr);
263   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
264               "Probe #%u for search result %p succeeded\n",
265               sr->availability_trials,
266               sr);
267   signal_probe_result (sr);
268 }
269
270
271 /**
272  * Notification of FS that a search probe has made progress.
273  * This function is used INSTEAD of the client's event handler
274  * for downloads where the #GNUNET_FS_DOWNLOAD_IS_PROBE flag is set.
275  *
276  * @param cls closure, always NULL (!), actual closure
277  *        is in the client-context of the info struct
278  * @param info details about the event, specifying the event type
279  *        and various bits about the event
280  * @return client-context (for the next progress call
281  *         for this operation; should be set to NULL for
282  *         SUSPEND and STOPPED events).  The value returned
283  *         will be passed to future callbacks in the respective
284  *         field in the `struct GNUNET_FS_ProgressInfo`.
285  */
286 void *
287 GNUNET_FS_search_probe_progress_ (void *cls,
288                                   const struct GNUNET_FS_ProgressInfo *info)
289 {
290   struct GNUNET_FS_SearchResult *sr = info->value.download.cctx;
291   struct GNUNET_TIME_Relative dur;
292
293   switch (info->status)
294   {
295   case GNUNET_FS_STATUS_DOWNLOAD_START:
296     /* ignore */
297     break;
298   case GNUNET_FS_STATUS_DOWNLOAD_RESUME:
299     /* probes should never be resumed */
300     GNUNET_assert (0);
301     break;
302   case GNUNET_FS_STATUS_DOWNLOAD_SUSPEND:
303     /* probes should never be suspended */
304     GNUNET_break (0);
305     break;
306   case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS:
307     /* ignore */
308     break;
309   case GNUNET_FS_STATUS_DOWNLOAD_ERROR:
310     if (GNUNET_SCHEDULER_NO_TASK != sr->probe_cancel_task)
311     {
312       GNUNET_SCHEDULER_cancel (sr->probe_cancel_task);
313       sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
314     }
315     sr->probe_cancel_task =
316         GNUNET_SCHEDULER_add_delayed (sr->remaining_probe_time,
317                                       &probe_failure_handler, sr);
318     break;
319   case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED:
320     if (GNUNET_SCHEDULER_NO_TASK != sr->probe_cancel_task)
321     {
322       GNUNET_SCHEDULER_cancel (sr->probe_cancel_task);
323       sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
324     }
325     sr->probe_cancel_task =
326         GNUNET_SCHEDULER_add_now (&probe_success_handler, sr);
327     break;
328   case GNUNET_FS_STATUS_DOWNLOAD_STOPPED:
329     if (GNUNET_SCHEDULER_NO_TASK != sr->probe_cancel_task)
330     {
331       GNUNET_SCHEDULER_cancel (sr->probe_cancel_task);
332       sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
333     }
334     sr = NULL;
335     break;
336   case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE:
337     if (GNUNET_SCHEDULER_NO_TASK == sr->probe_cancel_task)
338     {
339       sr->probe_active_time = GNUNET_TIME_absolute_get ();
340       sr->probe_cancel_task =
341         GNUNET_SCHEDULER_add_delayed (sr->remaining_probe_time,
342                                       &probe_failure_handler, sr);
343     }
344     else
345     {
346       /* should only happen if the cancel task was already
347          created on 'DOWNLOAD_INACTIVE' as we were out of time */
348       GNUNET_break (0 == sr->remaining_probe_time.rel_value_us);
349     }
350     break;
351   case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE:
352     if (GNUNET_SCHEDULER_NO_TASK != sr->probe_cancel_task)
353     {
354       GNUNET_SCHEDULER_cancel (sr->probe_cancel_task);
355       sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
356     }
357     dur = GNUNET_TIME_absolute_get_duration (sr->probe_active_time);
358     sr->remaining_probe_time =
359         GNUNET_TIME_relative_subtract (sr->remaining_probe_time, dur);
360     if (0 == sr->remaining_probe_time.rel_value_us)
361       sr->probe_cancel_task =
362         GNUNET_SCHEDULER_add_now (&probe_failure_handler, sr);
363     GNUNET_FS_search_result_sync_ (sr);
364     break;
365   default:
366     GNUNET_break (0);
367     return NULL;
368   }
369   return sr;
370 }
371
372
373 /**
374  * Task run periodically to remind clients that a probe is active.
375  *
376  * @param cls the 'struct GNUNET_FS_SearchResult' that we are probing for
377  * @param tc scheduler context
378  */
379 static void
380 probe_ping_task (void *cls,
381                  const struct GNUNET_SCHEDULER_TaskContext *tc)
382 {
383   struct GNUNET_FS_SearchResult *sr = cls;
384
385   signal_probe_result (sr);
386   sr->probe_ping_task
387     = GNUNET_SCHEDULER_add_delayed (GNUNET_FS_PROBE_UPDATE_FREQUENCY,
388                                     &probe_ping_task,
389                                     sr);
390 }
391
392
393 /**
394  * Start download probes for the given search result.
395  *
396  * @param sr the search result
397  */
398 void
399 GNUNET_FS_search_start_probe_ (struct GNUNET_FS_SearchResult *sr)
400 {
401   uint64_t off;
402   uint64_t len;
403
404   if (NULL != sr->probe_ctx)
405     return;
406   if (NULL != sr->download)
407     return;
408   if (0 == (sr->h->flags & GNUNET_FS_FLAGS_DO_PROBES))
409     return;
410   if (sr->availability_trials > AVAILABILITY_TRIALS_MAX)
411     return;
412   if ( (GNUNET_FS_URI_CHK != sr->uri->type) && (GNUNET_FS_URI_LOC != sr->uri->type))
413     return;
414   len = GNUNET_FS_uri_chk_get_file_size (sr->uri);
415   if (0 == len)
416     return;
417   if ((len <= DBLOCK_SIZE) && (sr->availability_success > 0))
418     return;
419   off = len / DBLOCK_SIZE;
420   if (off > 0)
421     off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, off);
422   off *= DBLOCK_SIZE;
423   if (len - off < DBLOCK_SIZE)
424     len = len - off;
425   else
426     len = DBLOCK_SIZE;
427   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
428               "Starting probe #%u (at offset %llu) for search result %p\n",
429               sr->availability_trials + 1,
430               (unsigned long long) off,
431               sr);
432   sr->remaining_probe_time =
433       GNUNET_TIME_relative_multiply (sr->h->avg_block_latency,
434                                      2 * (1 + sr->availability_trials));
435   sr->probe_ctx =
436       GNUNET_FS_download_start (sr->h, sr->uri, sr->meta, NULL, NULL, off,
437                                 len, sr->anonymity,
438                                 GNUNET_FS_DOWNLOAD_NO_TEMPORARIES |
439                                 GNUNET_FS_DOWNLOAD_IS_PROBE, sr, NULL);
440   sr->probe_ping_task
441     = GNUNET_SCHEDULER_add_now (&probe_ping_task,
442                                 sr);
443 }
444
445
446 /**
447  * Start download probes for the given search result.
448  *
449  * @param h file-sharing handle to use for the operation
450  * @param uri URI to probe
451  * @param meta meta data associated with the URI
452  * @param client_info client info pointer to use for associated events
453  * @param anonymity anonymity level to use for the probes
454  * @return the search result handle to access the probe activity
455  */
456 struct GNUNET_FS_SearchResult *
457 GNUNET_FS_probe (struct GNUNET_FS_Handle *h,
458                  const struct GNUNET_FS_Uri *uri,
459                  const struct GNUNET_CONTAINER_MetaData *meta,
460                  void *client_info,
461                  uint32_t anonymity)
462 {
463   struct GNUNET_FS_SearchResult *sr;
464
465   GNUNET_assert (NULL != h);
466   sr = GNUNET_new (struct GNUNET_FS_SearchResult);
467   sr->h = h;
468   sr->uri = GNUNET_FS_uri_dup (uri);
469   sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
470   sr->client_info = client_info;
471   sr->anonymity = anonymity;
472   GNUNET_FS_search_start_probe_ (sr);
473   return sr;
474 }
475
476
477 /**
478  * Stop probing activity associated with a search result.
479  *
480  * @param sr search result
481  */
482 static void
483 GNUNET_FS_search_stop_probe_ (struct GNUNET_FS_SearchResult *sr)
484 {
485   if (NULL != sr->probe_ctx)
486   {
487     GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
488     sr->probe_ctx = NULL;
489   }
490   if (GNUNET_SCHEDULER_NO_TASK != sr->probe_ping_task)
491   {
492     GNUNET_SCHEDULER_cancel (sr->probe_ping_task);
493     sr->probe_ping_task = GNUNET_SCHEDULER_NO_TASK;
494   }
495   if (GNUNET_SCHEDULER_NO_TASK != sr->probe_cancel_task)
496   {
497     GNUNET_SCHEDULER_cancel (sr->probe_cancel_task);
498     sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
499   }
500 }
501
502
503 /**
504  * Stop probe activity.  Must ONLY be used on values
505  * returned from #GNUNET_FS_probe.
506  *
507  * @param sr search result to stop probing for (freed)
508  * @return the value of the 'client_info' pointer
509  */
510 void *
511 GNUNET_FS_probe_stop (struct GNUNET_FS_SearchResult *sr)
512 {
513   void *client_info;
514
515   GNUNET_assert (NULL == sr->sc);
516   GNUNET_FS_search_stop_probe_ (sr);
517   GNUNET_FS_uri_destroy (sr->uri);
518   GNUNET_CONTAINER_meta_data_destroy (sr->meta);
519   client_info = sr->client_info;
520   GNUNET_free (sr);
521   return client_info;
522 }
523
524
525 /**
526  * We have received a KSK result.  Check how it fits in with the
527  * overall query and notify the client accordingly.
528  *
529  * @param sc context for the overall query
530  * @param ent entry for the specific keyword
531  * @param uri the URI that was found
532  * @param meta metadata associated with the URI
533  *        under the @a ent keyword
534  */
535 static void
536 process_ksk_result (struct GNUNET_FS_SearchContext *sc,
537                     struct SearchRequestEntry *ent,
538                     const struct GNUNET_FS_Uri *uri,
539                     const struct GNUNET_CONTAINER_MetaData *meta)
540 {
541   struct GNUNET_HashCode key;
542   struct GNUNET_FS_SearchResult *sr;
543   struct GetResultContext grc;
544   int is_new;
545   unsigned int koff;
546
547   /* check if new */
548   GNUNET_assert (NULL != sc);
549   GNUNET_FS_uri_to_key (uri, &key);
550   if (GNUNET_SYSERR ==
551       GNUNET_CONTAINER_multihashmap_get_multiple (ent->results, &key,
552                                                   &test_result_present,
553                                                   (void *) uri))
554     return;                     /* duplicate result */
555   /* try to find search result in master map */
556   grc.sr = NULL;
557   grc.uri = uri;
558   GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map, &key,
559                                               &get_result_present, &grc);
560   sr = grc.sr;
561   is_new = (NULL == sr) || (sr->mandatory_missing > 0);
562   if (NULL == sr)
563   {
564     sr = GNUNET_new (struct GNUNET_FS_SearchResult);
565     sr->h = sc->h;
566     sr->sc = sc;
567     sr->anonymity = sc->anonymity;
568     sr->uri = GNUNET_FS_uri_dup (uri);
569     sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
570     sr->mandatory_missing = sc->mandatory_count;
571     sr->key = key;
572     sr->keyword_bitmap = GNUNET_malloc ((sc->uri->data.ksk.keywordCount + 7) / 8); /* round up, count bits */
573     GNUNET_CONTAINER_multihashmap_put (sc->master_result_map, &key, sr,
574                                        GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
575   }
576   else
577   {
578     GNUNET_CONTAINER_meta_data_merge (sr->meta, meta);
579   }
580   koff = ent - sc->requests;
581   GNUNET_assert ( (ent >= sc->requests) && (koff < sc->uri->data.ksk.keywordCount));
582   sr->keyword_bitmap[koff / 8] |= (1 << (koff % 8));
583   /* check if mandatory satisfied */
584   if (ent->mandatory)
585     sr->mandatory_missing--;
586   else
587     sr->optional_support++;
588   if (0 != sr->mandatory_missing)
589     return;
590   if (is_new)
591     notify_client_chk_result (sc, sr);
592   else
593     notify_client_chk_update (sc, sr);
594   GNUNET_FS_search_result_sync_ (sr);
595   GNUNET_FS_search_start_probe_ (sr);
596 }
597
598
599 /**
600  * Start search for content, internal API.
601  *
602  * @param h handle to the file sharing subsystem
603  * @param uri specifies the search parameters; can be
604  *        a KSK URI or an SKS URI.
605  * @param anonymity desired level of anonymity
606  * @param options options for the search
607  * @param cctx client context
608  * @param psearch parent search result (for namespace update searches)
609  * @return context that can be used to control the search
610  */
611 static struct GNUNET_FS_SearchContext *
612 search_start (struct GNUNET_FS_Handle *h, const struct GNUNET_FS_Uri *uri,
613               uint32_t anonymity, enum GNUNET_FS_SearchOptions options,
614               void *cctx, struct GNUNET_FS_SearchResult *psearch);
615
616
617 /**
618  * We have received an SKS result.  Start searching for updates and
619  * notify the client if it is a new result.
620  *
621  * @param sc context for the overall query
622  * @param id_update identifier for updates, NULL for none
623  * @param uri the URI that was found
624  * @param meta metadata associated with the URI
625   */
626 static void
627 process_sks_result (struct GNUNET_FS_SearchContext *sc,
628                     const char *id_update,
629                     const struct GNUNET_FS_Uri *uri,
630                     const struct GNUNET_CONTAINER_MetaData *meta)
631 {
632   struct GNUNET_FS_Uri uu;
633   struct GNUNET_HashCode key;
634   struct GNUNET_FS_SearchResult *sr;
635
636   /* check if new */
637   GNUNET_assert (NULL != sc);
638   GNUNET_FS_uri_to_key (uri, &key);
639   GNUNET_CRYPTO_hash_xor (&uri->data.chk.chk.key, &uri->data.chk.chk.query,
640                           &key);
641   if (GNUNET_SYSERR ==
642       GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map, &key,
643                                                   &test_result_present,
644                                                   (void *) uri))
645     return;                     /* duplicate result */
646   sr = GNUNET_new (struct GNUNET_FS_SearchResult);
647   sr->h = sc->h;
648   sr->sc = sc;
649   sr->anonymity = sc->anonymity;
650   sr->uri = GNUNET_FS_uri_dup (uri);
651   sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
652   sr->key = key;
653   GNUNET_CONTAINER_multihashmap_put (sc->master_result_map, &key, sr,
654                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
655   GNUNET_FS_search_result_sync_ (sr);
656   GNUNET_FS_search_start_probe_ (sr);
657   /* notify client */
658   notify_client_chk_result (sc, sr);
659   /* search for updates */
660   if (0 == strlen (id_update))
661     return;                     /* no updates */
662   uu.type = GNUNET_FS_URI_SKS;
663   uu.data.sks.ns = sc->uri->data.sks.ns;
664   uu.data.sks.identifier = GNUNET_strdup (id_update);
665   (void) search_start (sc->h, &uu, sc->anonymity, sc->options, NULL, sr);
666   GNUNET_free (uu.data.sks.identifier);
667 }
668
669
670 /**
671  * Decrypt a ublock using a 'keyword' as the passphrase.  Given the
672  * KSK public key derived from the keyword, this function looks up
673  * the original keyword in the search context and decrypts the
674  * given ciphertext block.
675  *
676  * @param sc search context with the keywords
677  * @param dpub derived public key used for the search
678  * @param edata encrypted data
679  * @param edata_size number of bytes in @a edata (and @a data)
680  * @param data where to store the plaintext
681  * @return keyword index on success, #GNUNET_SYSERR on error (no such
682  *         keyword, internal error)
683  */
684 static int
685 decrypt_block_with_keyword (const struct GNUNET_FS_SearchContext *sc,
686                             const struct GNUNET_CRYPTO_EcdsaPublicKey *dpub,
687                             const void *edata,
688                             size_t edata_size,
689                             char *data)
690 {
691   const struct GNUNET_CRYPTO_EcdsaPrivateKey *anon;
692   struct GNUNET_CRYPTO_EcdsaPublicKey anon_pub;
693   unsigned int i;
694
695   /* find key */
696   for (i = 0; i < sc->uri->data.ksk.keywordCount; i++)
697     if (0 == memcmp (dpub,
698                      &sc->requests[i].dpub,
699                      sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)))
700       break;
701   if (i == sc->uri->data.ksk.keywordCount)
702   {
703     /* oops, does not match any of our keywords!? */
704     GNUNET_break (0);
705     return GNUNET_SYSERR;
706   }
707   /* decrypt */
708   anon = GNUNET_CRYPTO_ecdsa_key_get_anonymous ();
709   GNUNET_CRYPTO_ecdsa_key_get_public (anon, &anon_pub);
710   GNUNET_FS_ublock_decrypt_ (edata, edata_size,
711                              &anon_pub,
712                              sc->requests[i].keyword,
713                              data);
714   return i;
715 }
716
717
718 /**
719  * Process a keyword search result.  The actual type of block is
720  * a UBlock; we know it is a keyword search result because that's
721  * what we were searching for.
722  *
723  * @param sc our search context
724  * @param ub the ublock with the keyword search result
725  * @param size size of @a ub
726  */
727 static void
728 process_kblock (struct GNUNET_FS_SearchContext *sc,
729                 const struct UBlock *ub,
730                 size_t size)
731 {
732   size_t j;
733   char pt[size - sizeof (struct UBlock)];
734   const char *eos;
735   struct GNUNET_CONTAINER_MetaData *meta;
736   struct GNUNET_FS_Uri *uri;
737   char *emsg;
738   int i;
739
740   if (-1 == (i = decrypt_block_with_keyword (sc,
741                                              &ub->verification_key,
742                                              &ub[1],
743                                              size - sizeof (struct UBlock),
744                                              pt)))
745     return;
746   /* parse; pt[0] is just '\0', so we skip over that */
747   eos = memchr (&pt[1], '\0', sizeof (pt) - 1);
748   if (NULL == eos)
749   {
750     GNUNET_break_op (0);
751     return;
752   }
753   if (NULL == (uri = GNUNET_FS_uri_parse (&pt[1], &emsg)))
754   {
755     GNUNET_break_op (0);        /* ublock malformed */
756     GNUNET_free_non_null (emsg);
757     return;
758   }
759   j = eos - pt + 1;
760   if (sizeof (pt) == j)
761     meta = GNUNET_CONTAINER_meta_data_create ();
762   else
763     meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[j], sizeof (pt) - j);
764   if (NULL == meta)
765   {
766     GNUNET_break_op (0);        /* ublock malformed */
767     GNUNET_FS_uri_destroy (uri);
768     return;
769   }
770   process_ksk_result (sc, &sc->requests[i], uri, meta);
771
772   /* clean up */
773   GNUNET_CONTAINER_meta_data_destroy (meta);
774   GNUNET_FS_uri_destroy (uri);
775 }
776
777
778 /**
779  * Process a namespace-search result.  The actual type of block is
780  * a UBlock; we know it is a namespace search result because that's
781  * what we were searching for.
782  *
783  * @param sc our search context
784  * @param ub the ublock with a namespace result
785  * @param size size of @a ub
786  */
787 static void
788 process_sblock (struct GNUNET_FS_SearchContext *sc,
789                 const struct UBlock *ub,
790                 size_t size)
791 {
792   size_t len = size - sizeof (struct UBlock);
793   char pt[len];
794   struct GNUNET_FS_Uri *uri;
795   struct GNUNET_CONTAINER_MetaData *meta;
796   const char *id;
797   const char *uris;
798   size_t off;
799   char *emsg;
800
801   GNUNET_FS_ublock_decrypt_ (&ub[1], len,
802                              &sc->uri->data.sks.ns,
803                              sc->uri->data.sks.identifier,
804                              pt);
805   /* parse */
806   if (0 == (off = GNUNET_STRINGS_buffer_tokenize (pt, len, 2, &id, &uris)))
807   {
808     GNUNET_break_op (0);        /* ublock malformed */
809     return;
810   }
811   if (NULL == (meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[off], len - off)))
812   {
813     GNUNET_break_op (0);        /* ublock malformed */
814     return;
815   }
816   if (NULL == (uri = GNUNET_FS_uri_parse (uris, &emsg)))
817   {
818     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
819                 _("Failed to parse URI `%s': %s\n"),
820                 uris, emsg);
821     GNUNET_break_op (0);        /* ublock malformed */
822     GNUNET_free_non_null (emsg);
823     GNUNET_CONTAINER_meta_data_destroy (meta);
824     return;
825   }
826   /* process */
827   process_sks_result (sc, id, uri, meta);
828   /* clean up */
829   GNUNET_FS_uri_destroy (uri);
830   GNUNET_CONTAINER_meta_data_destroy (meta);
831 }
832
833
834 /**
835  * Process a search result.
836  *
837  * @param sc our search context
838  * @param type type of the result
839  * @param expiration when it will expire
840  * @param data the (encrypted) response
841  * @param size size of @a data
842  */
843 static void
844 process_result (struct GNUNET_FS_SearchContext *sc,
845                 enum GNUNET_BLOCK_Type type,
846                 struct GNUNET_TIME_Absolute expiration,
847                 const void *data,
848                 size_t size)
849 {
850   if (GNUNET_TIME_absolute_get_duration (expiration).rel_value_us > 0)
851   {
852     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
853                 "Result received has already expired.\n");
854     return;                     /* result expired */
855   }
856   switch (type)
857   {
858   case GNUNET_BLOCK_TYPE_FS_UBLOCK:
859     if (GNUNET_FS_URI_SKS == sc->uri->type)
860       process_sblock (sc, data, size);
861     else
862       process_kblock (sc, data, size);
863     break;
864   case GNUNET_BLOCK_TYPE_ANY:
865     GNUNET_break (0);
866     break;
867   case GNUNET_BLOCK_TYPE_FS_DBLOCK:
868     GNUNET_break (0);
869     break;
870   case GNUNET_BLOCK_TYPE_FS_ONDEMAND:
871     GNUNET_break (0);
872     break;
873   case GNUNET_BLOCK_TYPE_FS_IBLOCK:
874     GNUNET_break (0);
875     break;
876   default:
877     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
878                 _("Got result with unknown block type `%d', ignoring"), type);
879     break;
880   }
881 }
882
883
884 /**
885  * Shutdown any existing connection to the FS
886  * service and try to establish a fresh one
887  * (and then re-transmit our search request).
888  *
889  * @param sc the search to reconnec
890  */
891 static void
892 try_reconnect (struct GNUNET_FS_SearchContext *sc);
893
894
895 /**
896  * Type of a function to call when we receive a message
897  * from the service.
898  *
899  * @param cls closure
900  * @param msg message received, NULL on timeout or fatal error
901  */
902 static void
903 receive_results (void *cls, const struct GNUNET_MessageHeader *msg)
904 {
905   struct GNUNET_FS_SearchContext *sc = cls;
906   const struct ClientPutMessage *cm;
907   uint16_t msize;
908
909   if ((NULL == msg) || (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) ||
910       (ntohs (msg->size) <= sizeof (struct ClientPutMessage)))
911   {
912     try_reconnect (sc);
913     return;
914   }
915   msize = ntohs (msg->size);
916   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
917               "Receiving %u bytes of result from fs service\n", msize);
918   cm = (const struct ClientPutMessage *) msg;
919   process_result (sc, ntohl (cm->type),
920                   GNUNET_TIME_absolute_ntoh (cm->expiration), &cm[1],
921                   msize - sizeof (struct ClientPutMessage));
922   /* continue receiving */
923   GNUNET_CLIENT_receive (sc->client, &receive_results, sc,
924                          GNUNET_TIME_UNIT_FOREVER_REL);
925 }
926
927
928 /**
929  * Schedule the transmission of the (next) search request
930  * to the service.
931  *
932  * @param sc context for the search
933  */
934 static void
935 schedule_transmit_search_request (struct GNUNET_FS_SearchContext *sc);
936
937
938 /**
939  * Closure for 'build_result_set'.
940  */
941 struct MessageBuilderContext
942 {
943   /**
944    * How many entries can we store to xoff.
945    */
946   unsigned int put_cnt;
947
948   /**
949    * How many entries should we skip.
950    */
951   unsigned int skip_cnt;
952
953   /**
954    * Where to store the keys.
955    */
956   struct GNUNET_HashCode *xoff;
957
958   /**
959    * Search context we are iterating for.
960    */
961   struct GNUNET_FS_SearchContext *sc;
962
963   /**
964    * Keyword offset the search result must match (0 for SKS)
965    */
966   unsigned int keyword_offset;
967 };
968
969
970 /**
971  * Iterating over the known results, pick those matching the given
972  * result range and store their keys at 'xoff'.
973  *
974  * @param cls the `struct MessageBuilderContext`
975  * @param key key for a result
976  * @param value the search result
977  * @return #GNUNET_OK to continue iterating
978  */
979 static int
980 build_result_set (void *cls, const struct GNUNET_HashCode * key, void *value)
981 {
982   struct MessageBuilderContext *mbc = cls;
983   struct GNUNET_FS_SearchResult *sr = value;
984
985   if ( (NULL != sr->keyword_bitmap) &&
986        (0 == (sr->keyword_bitmap[mbc->keyword_offset / 8] & (1 << (mbc->keyword_offset % 8)))) )
987     return GNUNET_OK; /* have no match for this keyword yet */
988   if (mbc->skip_cnt > 0)
989   {
990     mbc->skip_cnt--;
991     return GNUNET_OK;
992   }
993   if (0 == mbc->put_cnt)
994     return GNUNET_SYSERR;
995   mbc->sc->search_request_map_offset++;
996   mbc->xoff[--mbc->put_cnt] = *key;
997
998   return GNUNET_OK;
999 }
1000
1001
1002 /**
1003  * Iterating over the known results, count those
1004  * matching the given result range and increment
1005  * put count for each.
1006  *
1007  * @param cls the `struct MessageBuilderContext`
1008  * @param key key for a result
1009  * @param value the search result
1010  * @return #GNUNET_OK to continue iterating
1011  */
1012 static int
1013 find_result_set (void *cls, const struct GNUNET_HashCode * key, void *value)
1014 {
1015   struct MessageBuilderContext *mbc = cls;
1016   struct GNUNET_FS_SearchResult *sr = value;
1017
1018   if ( (NULL != sr->keyword_bitmap) &&
1019        (0 == (sr->keyword_bitmap[mbc->keyword_offset / 8] & (1 << (mbc->keyword_offset % 8)))) )
1020     return GNUNET_OK; /* have no match for this keyword yet */
1021   mbc->put_cnt++;
1022   return GNUNET_OK;
1023 }
1024
1025
1026 /**
1027  * We're ready to transmit the search request to the
1028  * file-sharing service.  Do it.
1029  *
1030  * @param cls closure
1031  * @param size number of bytes available in @a buf
1032  * @param buf where the callee should write the message
1033  * @return number of bytes written to @a buf
1034  */
1035 static size_t
1036 transmit_search_request (void *cls, size_t size, void *buf)
1037 {
1038   struct GNUNET_FS_SearchContext *sc = cls;
1039   struct MessageBuilderContext mbc;
1040   size_t msize;
1041   struct SearchMessage *sm;
1042   struct GNUNET_CRYPTO_EcdsaPublicKey dpub;
1043   unsigned int sqms;
1044   uint32_t options;
1045
1046   if (NULL == buf)
1047   {
1048     try_reconnect (sc);
1049     return 0;
1050   }
1051   mbc.sc = sc;
1052   mbc.skip_cnt = sc->search_request_map_offset;
1053   sm = buf;
1054   sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
1055   mbc.xoff = (struct GNUNET_HashCode *) & sm[1];
1056   options = SEARCH_MESSAGE_OPTION_NONE;
1057   if (0 != (sc->options & GNUNET_FS_SEARCH_OPTION_LOOPBACK_ONLY))
1058     options |= SEARCH_MESSAGE_OPTION_LOOPBACK_ONLY;
1059   if (GNUNET_FS_uri_test_ksk (sc->uri))
1060   {
1061     msize = sizeof (struct SearchMessage);
1062     GNUNET_assert (size >= msize);
1063     mbc.keyword_offset = sc->keyword_offset;
1064     /* calculate total number of known results (in put_cnt => sqms) */
1065     mbc.put_cnt = 0;
1066     GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
1067                                            &find_result_set, &mbc);
1068     sqms = mbc.put_cnt;
1069     /* calculate how many results we can send in this message */
1070     mbc.put_cnt = (size - msize) / sizeof (struct GNUNET_HashCode);
1071     mbc.put_cnt = GNUNET_MIN (mbc.put_cnt, sqms - mbc.skip_cnt);
1072     if (sc->search_request_map_offset < sqms)
1073       GNUNET_assert (mbc.put_cnt > 0);
1074
1075     /* now build message */
1076     msize += sizeof (struct GNUNET_HashCode) * mbc.put_cnt;
1077     sm->header.size = htons (msize);
1078     sm->type = htonl (GNUNET_BLOCK_TYPE_FS_UBLOCK);
1079     sm->anonymity_level = htonl (sc->anonymity);
1080     memset (&sm->target, 0, sizeof (struct GNUNET_PeerIdentity));
1081     sm->query = sc->requests[sc->keyword_offset].uquery;
1082     GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
1083                                            &build_result_set, &mbc);
1084     GNUNET_assert (sqms >= sc->search_request_map_offset);
1085     if (sqms != sc->search_request_map_offset)
1086     {
1087       /* more requesting to be done... */
1088       sm->options = htonl (options | SEARCH_MESSAGE_OPTION_CONTINUED);
1089       schedule_transmit_search_request (sc);
1090       return msize;
1091     }
1092     sm->options = htonl (options);
1093     sc->keyword_offset++;
1094     if (sc->uri->data.ksk.keywordCount != sc->keyword_offset)
1095     {
1096       /* more requesting to be done... */
1097       schedule_transmit_search_request (sc);
1098       return msize;
1099     }
1100   }
1101   else
1102   {
1103     GNUNET_assert (GNUNET_FS_uri_test_sks (sc->uri));
1104     msize = sizeof (struct SearchMessage);
1105     GNUNET_assert (size >= msize);
1106     sm->type = htonl (GNUNET_BLOCK_TYPE_FS_UBLOCK);
1107     sm->anonymity_level = htonl (sc->anonymity);
1108     memset (&sm->target, 0, sizeof (struct GNUNET_PeerIdentity));
1109     GNUNET_CRYPTO_ecdsa_public_key_derive (&sc->uri->data.sks.ns,
1110                                          sc->uri->data.sks.identifier,
1111                                          "fs-ublock",
1112                                          &dpub);
1113     GNUNET_CRYPTO_hash (&dpub,
1114                         sizeof (dpub),
1115                         &sm->query);
1116     mbc.put_cnt = (size - msize) / sizeof (struct GNUNET_HashCode);
1117     sqms = GNUNET_CONTAINER_multihashmap_size (sc->master_result_map);
1118     mbc.put_cnt = GNUNET_MIN (mbc.put_cnt, sqms - mbc.skip_cnt);
1119     mbc.keyword_offset = 0;
1120     if (sc->search_request_map_offset < sqms)
1121       GNUNET_assert (mbc.put_cnt > 0);
1122     msize += sizeof (struct GNUNET_HashCode) * mbc.put_cnt;
1123     GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
1124                                            &build_result_set, &mbc);
1125     sm->header.size = htons (msize);
1126     GNUNET_assert (sqms >= sc->search_request_map_offset);
1127     if (sqms != sc->search_request_map_offset)
1128     {
1129       /* more requesting to be done... */
1130       sm->options = htonl (options | SEARCH_MESSAGE_OPTION_CONTINUED);
1131       schedule_transmit_search_request (sc);
1132       return msize;
1133     }
1134     sm->options = htonl (options);
1135   }
1136   GNUNET_CLIENT_receive (sc->client, &receive_results, sc,
1137                          GNUNET_TIME_UNIT_FOREVER_REL);
1138   return msize;
1139 }
1140
1141
1142 /**
1143  * Schedule the transmission of the (next) search request
1144  * to the service.
1145  *
1146  * @param sc context for the search
1147  */
1148 static void
1149 schedule_transmit_search_request (struct GNUNET_FS_SearchContext *sc)
1150 {
1151   size_t size;
1152   unsigned int sqms;
1153   unsigned int fit;
1154
1155   size = sizeof (struct SearchMessage);
1156   sqms =
1157       GNUNET_CONTAINER_multihashmap_size (sc->master_result_map) -
1158       sc->search_request_map_offset;
1159   fit = (GNUNET_SERVER_MAX_MESSAGE_SIZE - 1 - size) / sizeof (struct GNUNET_HashCode);
1160   fit = GNUNET_MIN (fit, sqms);
1161   size += sizeof (struct GNUNET_HashCode) * fit;
1162   GNUNET_CLIENT_notify_transmit_ready (sc->client, size,
1163                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1164                                        GNUNET_NO, &transmit_search_request, sc);
1165
1166 }
1167
1168
1169 /**
1170  * Reconnect to the FS service and transmit
1171  * our queries NOW.
1172  *
1173  * @param cls our search context
1174  * @param tc unused
1175  */
1176 static void
1177 do_reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
1178 {
1179   struct GNUNET_FS_SearchContext *sc = cls;
1180   struct GNUNET_CLIENT_Connection *client;
1181
1182   sc->task = GNUNET_SCHEDULER_NO_TASK;
1183   client = GNUNET_CLIENT_connect ("fs", sc->h->cfg);
1184   if (NULL == client)
1185   {
1186     try_reconnect (sc);
1187     return;
1188   }
1189   sc->client = client;
1190   sc->search_request_map_offset = 0;
1191   sc->keyword_offset = 0;
1192   schedule_transmit_search_request (sc);
1193 }
1194
1195
1196 /**
1197  * Shutdown any existing connection to the FS
1198  * service and try to establish a fresh one
1199  * (and then re-transmit our search request).
1200  *
1201  * @param sc the search to reconnec
1202  */
1203 static void
1204 try_reconnect (struct GNUNET_FS_SearchContext *sc)
1205 {
1206   if (NULL != sc->client)
1207   {
1208     GNUNET_CLIENT_disconnect (sc->client);
1209     sc->client = NULL;
1210   }
1211   sc->reconnect_backoff = GNUNET_TIME_STD_BACKOFF (sc->reconnect_backoff);
1212   sc->task =
1213       GNUNET_SCHEDULER_add_delayed (sc->reconnect_backoff,
1214                                     &do_reconnect,
1215                                     sc);
1216 }
1217
1218
1219 /**
1220  * Start search for content, internal API.
1221  *
1222  * @param h handle to the file sharing subsystem
1223  * @param uri specifies the search parameters; can be
1224  *        a KSK URI or an SKS URI.
1225  * @param anonymity desired level of anonymity
1226  * @param options options for the search
1227  * @param cctx initial value for the client context
1228  * @param psearch parent search result (for namespace update searches)
1229  * @return context that can be used to control the search
1230  */
1231 static struct GNUNET_FS_SearchContext *
1232 search_start (struct GNUNET_FS_Handle *h,
1233               const struct GNUNET_FS_Uri *uri,
1234               uint32_t anonymity,
1235               enum GNUNET_FS_SearchOptions options,
1236               void *cctx,
1237               struct GNUNET_FS_SearchResult *psearch)
1238 {
1239   struct GNUNET_FS_SearchContext *sc;
1240   struct GNUNET_FS_ProgressInfo pi;
1241
1242   sc = GNUNET_new (struct GNUNET_FS_SearchContext);
1243   sc->h = h;
1244   sc->options = options;
1245   sc->uri = GNUNET_FS_uri_dup (uri);
1246   sc->anonymity = anonymity;
1247   sc->start_time = GNUNET_TIME_absolute_get ();
1248   if (NULL != psearch)
1249   {
1250     sc->psearch_result = psearch;
1251     psearch->update_search = sc;
1252   }
1253   sc->master_result_map = GNUNET_CONTAINER_multihashmap_create (16, GNUNET_NO);
1254   sc->client_info = cctx;
1255   if (GNUNET_OK != GNUNET_FS_search_start_searching_ (sc))
1256   {
1257     GNUNET_FS_uri_destroy (sc->uri);
1258     GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map);
1259     GNUNET_free (sc);
1260     return NULL;
1261   }
1262   GNUNET_FS_search_sync_ (sc);
1263   pi.status = GNUNET_FS_STATUS_SEARCH_START;
1264   sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc->h, sc);
1265   return sc;
1266 }
1267
1268
1269 /**
1270  * Build the request and actually initiate the search using the
1271  * GNUnet FS service.
1272  *
1273  * @param sc search context
1274  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
1275  */
1276 int
1277 GNUNET_FS_search_start_searching_ (struct GNUNET_FS_SearchContext *sc)
1278 {
1279   unsigned int i;
1280   const char *keyword;
1281   const struct GNUNET_CRYPTO_EcdsaPrivateKey *anon;
1282   struct GNUNET_CRYPTO_EcdsaPublicKey anon_pub;
1283   struct SearchRequestEntry *sre;
1284
1285   GNUNET_assert (NULL == sc->client);
1286   if (GNUNET_FS_uri_test_ksk (sc->uri))
1287   {
1288     GNUNET_assert (0 != sc->uri->data.ksk.keywordCount);
1289     anon = GNUNET_CRYPTO_ecdsa_key_get_anonymous ();
1290     GNUNET_CRYPTO_ecdsa_key_get_public (anon, &anon_pub);
1291     sc->requests =
1292         GNUNET_malloc (sizeof (struct SearchRequestEntry) *
1293                        sc->uri->data.ksk.keywordCount);
1294     for (i = 0; i < sc->uri->data.ksk.keywordCount; i++)
1295     {
1296       keyword = &sc->uri->data.ksk.keywords[i][1];
1297       sre = &sc->requests[i];
1298       sre->keyword = GNUNET_strdup (keyword);
1299       GNUNET_CRYPTO_ecdsa_public_key_derive (&anon_pub,
1300                                            keyword,
1301                                            "fs-ublock",
1302                                            &sre->dpub);
1303       GNUNET_CRYPTO_hash (&sre->dpub,
1304                           sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
1305                           &sre->uquery);
1306       sre->mandatory = (sc->uri->data.ksk.keywords[i][0] == '+');
1307       if (sre->mandatory)
1308         sc->mandatory_count++;
1309       sre->results = GNUNET_CONTAINER_multihashmap_create (4, GNUNET_NO);
1310     }
1311   }
1312   sc->client = GNUNET_CLIENT_connect ("fs", sc->h->cfg);
1313   if (NULL == sc->client)
1314     return GNUNET_SYSERR;
1315   schedule_transmit_search_request (sc);
1316   return GNUNET_OK;
1317 }
1318
1319
1320 /**
1321  * Freeze probes for the given search result.
1322  *
1323  * @param cls the global FS handle
1324  * @param key the key for the search result (unused)
1325  * @param value the search result to free
1326  * @return #GNUNET_OK
1327  */
1328 static int
1329 search_result_freeze_probes (void *cls,
1330                              const struct GNUNET_HashCode *key,
1331                              void *value)
1332 {
1333   struct GNUNET_FS_SearchResult *sr = value;
1334
1335   if (NULL != sr->probe_ctx)
1336   {
1337     GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
1338     sr->probe_ctx = NULL;
1339   }
1340   if (GNUNET_SCHEDULER_NO_TASK != sr->probe_ping_task)
1341   {
1342     GNUNET_SCHEDULER_cancel (sr->probe_ping_task);
1343     sr->probe_ping_task = GNUNET_SCHEDULER_NO_TASK;
1344   }
1345   if (GNUNET_SCHEDULER_NO_TASK != sr->probe_cancel_task)
1346   {
1347     GNUNET_SCHEDULER_cancel (sr->probe_cancel_task);
1348     sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
1349   }
1350   if (NULL != sr->update_search)
1351     GNUNET_FS_search_pause (sr->update_search);
1352   return GNUNET_OK;
1353 }
1354
1355
1356 /**
1357  * Resume probes for the given search result.
1358  *
1359  * @param cls the global FS handle
1360  * @param key the key for the search result (unused)
1361  * @param value the search result to free
1362  * @return #GNUNET_OK
1363  */
1364 static int
1365 search_result_resume_probes (void *cls,
1366                              const struct GNUNET_HashCode * key,
1367                              void *value)
1368 {
1369   struct GNUNET_FS_SearchResult *sr = value;
1370
1371   GNUNET_FS_search_start_probe_ (sr);
1372   if (NULL != sr->update_search)
1373     GNUNET_FS_search_continue (sr->update_search);
1374   return GNUNET_OK;
1375 }
1376
1377
1378 /**
1379  * Signal suspend and free the given search result.
1380  *
1381  * @param cls the global FS handle
1382  * @param key the key for the search result (unused)
1383  * @param value the search result to free
1384  * @return #GNUNET_OK
1385  */
1386 static int
1387 search_result_suspend (void *cls,
1388                        const struct GNUNET_HashCode * key,
1389                        void *value)
1390 {
1391   struct GNUNET_FS_SearchContext *sc = cls;
1392   struct GNUNET_FS_SearchResult *sr = value;
1393   struct GNUNET_FS_ProgressInfo pi;
1394
1395   if (NULL != sr->download)
1396   {
1397     GNUNET_FS_download_signal_suspend_ (sr->download);
1398     sr->download = NULL;
1399   }
1400   if (NULL != sr->update_search)
1401   {
1402     GNUNET_FS_search_signal_suspend_ (sr->update_search);
1403     sr->update_search = NULL;
1404   }
1405   GNUNET_FS_search_stop_probe_ (sr);
1406   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT_SUSPEND;
1407   pi.value.search.specifics.result_suspend.cctx = sr->client_info;
1408   pi.value.search.specifics.result_suspend.meta = sr->meta;
1409   pi.value.search.specifics.result_suspend.uri = sr->uri;
1410   sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc->h, sc);
1411   GNUNET_break (NULL == sr->client_info);
1412   GNUNET_free_non_null (sr->serialization);
1413   GNUNET_FS_uri_destroy (sr->uri);
1414   GNUNET_CONTAINER_meta_data_destroy (sr->meta);
1415   GNUNET_free_non_null (sr->keyword_bitmap);
1416   GNUNET_free (sr);
1417   return GNUNET_OK;
1418 }
1419
1420
1421 /**
1422  * Create SUSPEND event for the given search operation
1423  * and then clean up our state (without stop signal).
1424  *
1425  * @param cls the `struct GNUNET_FS_SearchContext` to signal for
1426  */
1427 void
1428 GNUNET_FS_search_signal_suspend_ (void *cls)
1429 {
1430   struct GNUNET_FS_SearchContext *sc = cls;
1431   struct GNUNET_FS_ProgressInfo pi;
1432   unsigned int i;
1433
1434   GNUNET_FS_end_top (sc->h, sc->top);
1435   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
1436                                          &search_result_suspend, sc);
1437   pi.status = GNUNET_FS_STATUS_SEARCH_SUSPEND;
1438   sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc->h, sc);
1439   GNUNET_break (NULL == sc->client_info);
1440   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
1441   {
1442     GNUNET_SCHEDULER_cancel (sc->task);
1443     sc->task = GNUNET_SCHEDULER_NO_TASK;
1444   }
1445   if (NULL != sc->client)
1446   {
1447     GNUNET_CLIENT_disconnect (sc->client);
1448     sc->client = NULL;
1449   }
1450   GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map);
1451   if (NULL != sc->requests)
1452   {
1453     GNUNET_assert (GNUNET_FS_uri_test_ksk (sc->uri));
1454     for (i = 0; i < sc->uri->data.ksk.keywordCount; i++)
1455     {
1456       GNUNET_CONTAINER_multihashmap_destroy (sc->requests[i].results);
1457       GNUNET_free (sc->requests[i].keyword);
1458     }
1459   }
1460   GNUNET_free_non_null (sc->requests);
1461   GNUNET_free_non_null (sc->emsg);
1462   GNUNET_FS_uri_destroy (sc->uri);
1463   GNUNET_free_non_null (sc->serialization);
1464   GNUNET_free (sc);
1465 }
1466
1467
1468 /**
1469  * Start search for content.
1470  *
1471  * @param h handle to the file sharing subsystem
1472  * @param uri specifies the search parameters; can be
1473  *        a KSK URI or an SKS URI.
1474  * @param anonymity desired level of anonymity
1475  * @param options options for the search
1476  * @param cctx initial value for the client context
1477  * @return context that can be used to control the search
1478  */
1479 struct GNUNET_FS_SearchContext *
1480 GNUNET_FS_search_start (struct GNUNET_FS_Handle *h,
1481                         const struct GNUNET_FS_Uri *uri, uint32_t anonymity,
1482                         enum GNUNET_FS_SearchOptions options, void *cctx)
1483 {
1484   struct GNUNET_FS_SearchContext *ret;
1485
1486   ret = search_start (h, uri, anonymity, options, cctx, NULL);
1487   if (NULL == ret)
1488     return NULL;
1489   ret->top = GNUNET_FS_make_top (h, &GNUNET_FS_search_signal_suspend_, ret);
1490   return ret;
1491 }
1492
1493
1494 /**
1495  * Pause search.
1496  *
1497  * @param sc context for the search that should be paused
1498  */
1499 void
1500 GNUNET_FS_search_pause (struct GNUNET_FS_SearchContext *sc)
1501 {
1502   struct GNUNET_FS_ProgressInfo pi;
1503
1504   if (GNUNET_SCHEDULER_NO_TASK != sc->task)
1505   {
1506     GNUNET_SCHEDULER_cancel (sc->task);
1507     sc->task = GNUNET_SCHEDULER_NO_TASK;
1508   }
1509   if (NULL != sc->client)
1510     GNUNET_CLIENT_disconnect (sc->client);
1511   sc->client = NULL;
1512   GNUNET_FS_search_sync_ (sc);
1513   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
1514                                          &search_result_freeze_probes, sc);
1515   pi.status = GNUNET_FS_STATUS_SEARCH_PAUSED;
1516   sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc->h, sc);
1517 }
1518
1519
1520 /**
1521  * Continue paused search.
1522  *
1523  * @param sc context for the search that should be resumed
1524  */
1525 void
1526 GNUNET_FS_search_continue (struct GNUNET_FS_SearchContext *sc)
1527 {
1528   struct GNUNET_FS_ProgressInfo pi;
1529
1530   GNUNET_assert (NULL == sc->client);
1531   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == sc->task);
1532   do_reconnect (sc, NULL);
1533   GNUNET_FS_search_sync_ (sc);
1534   pi.status = GNUNET_FS_STATUS_SEARCH_CONTINUED;
1535   sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc->h, sc);
1536   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
1537                                          &search_result_resume_probes, sc);
1538 }
1539
1540
1541 /**
1542  * Signal stop for the given search result.
1543  *
1544  * @param cls the global FS handle
1545  * @param key the key for the search result (unused)
1546  * @param value the search result to free
1547  * @return #GNUNET_OK
1548  */
1549 static int
1550 search_result_stop (void *cls,
1551                     const struct GNUNET_HashCode *key,
1552                     void *value)
1553 {
1554   struct GNUNET_FS_SearchContext *sc = cls;
1555   struct GNUNET_FS_SearchResult *sr = value;
1556   struct GNUNET_FS_ProgressInfo pi;
1557
1558   GNUNET_FS_search_stop_probe_ (sr);
1559   if (NULL != sr->download)
1560   {
1561     sr->download->search = NULL;
1562     sr->download->top =
1563         GNUNET_FS_make_top (sr->download->h,
1564                             &GNUNET_FS_download_signal_suspend_, sr->download);
1565     if (NULL != sr->download->serialization)
1566     {
1567       GNUNET_FS_remove_sync_file_ (sc->h, GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD,
1568                                    sr->download->serialization);
1569       GNUNET_free (sr->download->serialization);
1570       sr->download->serialization = NULL;
1571     }
1572     pi.status = GNUNET_FS_STATUS_DOWNLOAD_LOST_PARENT;
1573     GNUNET_FS_download_make_status_ (&pi, sr->download);
1574     GNUNET_FS_download_sync_ (sr->download);
1575     sr->download = NULL;
1576   }
1577   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED;
1578   pi.value.search.specifics.result_stopped.cctx = sr->client_info;
1579   pi.value.search.specifics.result_stopped.meta = sr->meta;
1580   pi.value.search.specifics.result_stopped.uri = sr->uri;
1581   sr->client_info = GNUNET_FS_search_make_status_ (&pi, sr->h, sc);
1582   return GNUNET_OK;
1583 }
1584
1585
1586 /**
1587  * Free the given search result.
1588  *
1589  * @param cls the global FS handle
1590  * @param key the key for the search result (unused)
1591  * @param value the search result to free
1592  * @return #GNUNET_OK
1593  */
1594 static int
1595 search_result_free (void *cls,
1596                     const struct GNUNET_HashCode *key,
1597                     void *value)
1598 {
1599   struct GNUNET_FS_SearchResult *sr = value;
1600
1601   if (NULL != sr->update_search)
1602   {
1603     GNUNET_FS_search_stop (sr->update_search);
1604     GNUNET_assert (NULL == sr->update_search);
1605   }
1606   GNUNET_break (NULL == sr->probe_ctx);
1607   GNUNET_break (GNUNET_SCHEDULER_NO_TASK == sr->probe_cancel_task);
1608   GNUNET_break (GNUNET_SCHEDULER_NO_TASK == sr->probe_ping_task);
1609   GNUNET_break (NULL == sr->client_info);
1610   GNUNET_free_non_null (sr->serialization);
1611   GNUNET_FS_uri_destroy (sr->uri);
1612   GNUNET_CONTAINER_meta_data_destroy (sr->meta);
1613   GNUNET_free_non_null (sr->keyword_bitmap);
1614   GNUNET_free (sr);
1615   return GNUNET_OK;
1616 }
1617
1618
1619 /**
1620  * Stop search for content.
1621  *
1622  * @param sc context for the search that should be stopped
1623  */
1624 void
1625 GNUNET_FS_search_stop (struct GNUNET_FS_SearchContext *sc)
1626 {
1627   struct GNUNET_FS_ProgressInfo pi;
1628   unsigned int i;
1629
1630   if (NULL != sc->top)
1631     GNUNET_FS_end_top (sc->h, sc->top);
1632   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
1633                                          &search_result_stop, sc);
1634   if (NULL != sc->psearch_result)
1635     sc->psearch_result->update_search = NULL;
1636   if (NULL != sc->serialization)
1637   {
1638     GNUNET_FS_remove_sync_file_ (sc->h,
1639                                  (sc->psearch_result !=
1640                                   NULL) ? GNUNET_FS_SYNC_PATH_CHILD_SEARCH :
1641                                  GNUNET_FS_SYNC_PATH_MASTER_SEARCH,
1642                                  sc->serialization);
1643     GNUNET_FS_remove_sync_dir_ (sc->h,
1644                                 (sc->psearch_result !=
1645                                  NULL) ? GNUNET_FS_SYNC_PATH_CHILD_SEARCH :
1646                                 GNUNET_FS_SYNC_PATH_MASTER_SEARCH,
1647                                 sc->serialization);
1648     GNUNET_free (sc->serialization);
1649   }
1650   pi.status = GNUNET_FS_STATUS_SEARCH_STOPPED;
1651   sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc->h, sc);
1652   GNUNET_break (NULL == sc->client_info);
1653   if (GNUNET_SCHEDULER_NO_TASK != sc->task)
1654     GNUNET_SCHEDULER_cancel (sc->task);
1655   if (NULL != sc->client)
1656     GNUNET_CLIENT_disconnect (sc->client);
1657   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
1658                                          &search_result_free, sc);
1659   GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map);
1660   if (NULL != sc->requests)
1661   {
1662     GNUNET_assert (GNUNET_FS_uri_test_ksk (sc->uri));
1663     for (i = 0; i < sc->uri->data.ksk.keywordCount; i++)
1664       GNUNET_CONTAINER_multihashmap_destroy (sc->requests[i].results);
1665   }
1666   GNUNET_free_non_null (sc->requests);
1667   GNUNET_free_non_null (sc->emsg);
1668   GNUNET_FS_uri_destroy (sc->uri);
1669   GNUNET_free (sc);
1670 }
1671
1672 /* end of fs_search.c */