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