search deserialization
[oweals/gnunet.git] / src / fs / fs_search.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file fs/fs_search.c
23  * @brief Helper functions for searching.
24  * @author Christian Grothoff
25  *
26  * TODO:
27  * - insert code for serialization where needed
28  * - remove *directory* with search results upon completion
29  * - centralize code that sprintf's the 'pbuf[32]' strings
30  * - add support for pushing "already seen" information
31  *   to FS service for bloomfilter (can wait)
32  */
33
34 #include "platform.h"
35 #include "gnunet_constants.h"
36 #include "gnunet_fs_service.h"
37 #include "gnunet_protocols.h"
38 #include "fs.h"
39
40 #define DEBUG_SEARCH GNUNET_NO
41
42
43
44 /**
45  * Fill in all of the generic fields for a search event and
46  * call the callback.
47  *
48  * @param pi structure to fill in
49  * @param sc overall search context
50  * @return value returned by the callback
51  */
52 void *
53 GNUNET_FS_search_make_status_ (struct GNUNET_FS_ProgressInfo *pi,
54                                struct GNUNET_FS_SearchContext *sc)
55 {
56   pi->value.search.sc = sc;
57   pi->value.search.cctx
58     = sc->client_info;
59   pi->value.search.pctx
60     = (sc->parent == NULL) ? NULL : sc->parent->client_info;
61   pi->value.search.query 
62     = sc->uri;
63   pi->value.search.duration = GNUNET_TIME_absolute_get_duration (sc->start_time);
64   pi->value.search.anonymity = sc->anonymity;
65   return sc->h->upcb (sc->h->upcb_cls,
66                       pi);
67 }
68
69
70 /**
71  * Check if the given result is identical
72  * 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 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 GNUNET_HashCode * key,
84                      void *value)
85 {
86   const struct GNUNET_FS_Uri *uri = cls;
87   struct SearchResult *sr = value;
88
89   if (GNUNET_FS_uri_test_equal (uri,
90                                 sr->uri))
91     return GNUNET_SYSERR;
92   return GNUNET_OK;
93 }
94
95
96 /**
97  * We've found a new CHK result.  Let the client
98  * know about it.
99  * 
100  * @param sc the search context
101  * @param sr the specific result
102  */
103 static void
104 notify_client_chk_result (struct GNUNET_FS_SearchContext *sc, 
105                           struct SearchResult *sr)
106 {                         
107   struct GNUNET_FS_ProgressInfo pi;
108
109   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT;
110   pi.value.search.specifics.result.meta = sr->meta;
111   pi.value.search.specifics.result.uri = sr->uri;
112   sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc);
113 }
114
115
116 /**
117  * We've found new information about an existing CHK result.  Let the
118  * client know about it.
119  * 
120  * @param sc the search context
121  * @param sr the specific result
122  */
123 static void
124 notify_client_chk_update (struct GNUNET_FS_SearchContext *sc, 
125                           struct SearchResult *sr)
126 {                         
127   struct GNUNET_FS_ProgressInfo pi;
128
129   pi.status = GNUNET_FS_STATUS_SEARCH_UPDATE;
130   pi.value.search.specifics.update.cctx = sr->client_info;
131   pi.value.search.specifics.update.meta = sr->meta;
132   pi.value.search.specifics.update.uri = sr->uri;
133   pi.value.search.specifics.update.availability_rank
134     = 2*sr->availability_success - sr->availability_trials;
135   pi.value.search.specifics.update.availability_certainty 
136     = sr->availability_trials;
137   pi.value.search.specifics.update.applicability_rank 
138     = sr->optional_support;
139   sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc);
140 }
141
142
143 /**
144  * Context for "get_result_present".
145  */
146 struct GetResultContext 
147 {
148   /**
149    * The URI we're looking for.
150    */
151   const struct GNUNET_FS_Uri *uri;
152
153   /**
154    * Where to store a pointer to the search
155    * result struct if we found a match.
156    */
157   struct SearchResult *sr;
158 };
159
160
161 /**
162  * Check if the given result is identical to the given URI and if so
163  * return it.
164  * 
165  * @param cls a "struct GetResultContext"
166  * @param key not used
167  * @param value a "struct SearchResult" who's URI we
168  *        should compare with
169  * @return GNUNET_OK
170  */
171 static int
172 get_result_present (void *cls,
173                      const GNUNET_HashCode * key,
174                      void *value)
175 {
176   struct GetResultContext *grc = cls;
177   struct SearchResult *sr = value;
178
179   if (GNUNET_FS_uri_test_equal (grc->uri,
180                                 sr->uri))
181     grc->sr = sr;
182   return GNUNET_OK;
183 }
184
185
186 /**
187  * Signal result of last probe to client and then schedule next
188  * probe.
189  */
190 static void
191 signal_probe_result (struct SearchResult *sr)
192 {
193   struct GNUNET_FS_ProgressInfo pi;
194
195   pi.status = GNUNET_FS_STATUS_SEARCH_START;
196   pi.value.search.specifics.update.cctx = sr->client_info;
197   pi.value.search.specifics.update.meta = sr->meta;
198   pi.value.search.specifics.update.uri = sr->uri;
199   pi.value.search.specifics.update.availability_rank = sr->availability_success;
200   pi.value.search.specifics.update.availability_certainty = sr->availability_trials;
201   pi.value.search.specifics.update.applicability_rank = sr->optional_support;
202   sr->sc->client_info = GNUNET_FS_search_make_status_ (&pi, sr->sc);
203   GNUNET_FS_search_start_probe_ (sr);
204 }
205
206
207 /**
208  * Handle the case where we have failed to receive a response for our probe.
209  *
210  * @param cls our 'struct SearchResult*'
211  * @param tc scheduler context
212  */
213 static void
214 probe_failure_handler (void *cls,
215                        const struct GNUNET_SCHEDULER_TaskContext *tc)
216 {
217   struct SearchResult *sr = cls;
218   sr->availability_trials++;
219   signal_probe_result (sr);
220 }
221
222
223 /**
224  * Handle the case where we have gotten a response for our probe.
225  *
226  * @param cls our 'struct SearchResult*'
227  * @param tc scheduler context
228  */
229 static void
230 probe_success_handler (void *cls,
231                        const struct GNUNET_SCHEDULER_TaskContext *tc)
232 {
233   struct SearchResult *sr = cls;
234   sr->availability_trials++;
235   sr->availability_success++;
236   signal_probe_result (sr);
237 }
238
239
240 /**
241  * Notification of FS that a search probe has made progress.
242  * This function is used INSTEAD of the client's event handler
243  * for downloads where the GNUNET_FS_DOWNLOAD_IS_PROBE flag is set.
244  *
245  * @param cls closure, always NULL (!), actual closure
246  *        is in the client-context of the info struct
247  * @param info details about the event, specifying the event type
248  *        and various bits about the event
249  * @return client-context (for the next progress call
250  *         for this operation; should be set to NULL for
251  *         SUSPEND and STOPPED events).  The value returned
252  *         will be passed to future callbacks in the respective
253  *         field in the GNUNET_FS_ProgressInfo struct.
254  */
255 void*
256 GNUNET_FS_search_probe_progress_ (void *cls,
257                                   const struct GNUNET_FS_ProgressInfo *info)
258 {
259   struct SearchResult *sr = info->value.download.cctx;
260   struct GNUNET_TIME_Relative dur;
261
262   switch (info->status)
263     {
264     case GNUNET_FS_STATUS_DOWNLOAD_START:
265       /* ignore */
266       break;
267     case GNUNET_FS_STATUS_DOWNLOAD_RESUME:
268       /* probes should never be resumed */
269       GNUNET_assert (0);
270       break;
271     case GNUNET_FS_STATUS_DOWNLOAD_SUSPEND:
272       /* probes should never be suspended */
273       GNUNET_break (0);
274       break;
275     case GNUNET_FS_STATUS_DOWNLOAD_PROGRESS:
276       /* ignore */
277       break;
278     case GNUNET_FS_STATUS_DOWNLOAD_ERROR:
279       if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK)
280         {
281           GNUNET_SCHEDULER_cancel (sr->sc->h->sched,
282                                    sr->probe_cancel_task);
283           sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
284         }     
285       sr->probe_cancel_task = GNUNET_SCHEDULER_add_delayed (sr->sc->h->sched,
286                                                             sr->remaining_probe_time,
287                                                             &probe_failure_handler,
288                                                             sr);
289       break;
290     case GNUNET_FS_STATUS_DOWNLOAD_COMPLETED:
291       if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK)
292         {
293           GNUNET_SCHEDULER_cancel (sr->sc->h->sched,
294                                    sr->probe_cancel_task);
295           sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
296         }     
297       sr->probe_cancel_task = GNUNET_SCHEDULER_add_delayed (sr->sc->h->sched,
298                                                             sr->remaining_probe_time,
299                                                             &probe_success_handler,
300                                                             sr);
301       break;
302     case GNUNET_FS_STATUS_DOWNLOAD_STOPPED:
303       /* FIXME: clean up? schedule next probe? or already done? */
304       sr = NULL;
305       break;
306     case GNUNET_FS_STATUS_DOWNLOAD_ACTIVE:
307       GNUNET_assert (sr->probe_cancel_task == GNUNET_SCHEDULER_NO_TASK);
308       sr->probe_active_time = GNUNET_TIME_absolute_get ();
309       sr->probe_cancel_task = GNUNET_SCHEDULER_add_delayed (sr->sc->h->sched,
310                                                             sr->remaining_probe_time,
311                                                             &probe_failure_handler,
312                                                             sr);
313       break;
314     case GNUNET_FS_STATUS_DOWNLOAD_INACTIVE:
315       if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK)
316         {
317           GNUNET_SCHEDULER_cancel (sr->sc->h->sched,
318                                    sr->probe_cancel_task);
319           sr->probe_cancel_task = GNUNET_SCHEDULER_NO_TASK;
320         }
321       dur = GNUNET_TIME_absolute_get_duration (sr->probe_active_time);
322       sr->remaining_probe_time = GNUNET_TIME_relative_subtract (sr->remaining_probe_time,
323                                                                 dur);
324       break;
325     default:
326       GNUNET_break (0);
327       return NULL;
328     }
329   return sr;
330 }
331
332
333 /**
334  * Start download probes for the given search result.
335  *
336  * @param sr the search result
337  */
338 void
339 GNUNET_FS_search_start_probe_ (struct SearchResult *sr)
340 {
341   uint64_t off;
342   uint64_t len;
343   
344   if (sr->probe_ctx != NULL)
345     return;
346   if (0 == (sr->sc->h->flags & GNUNET_FS_FLAGS_DO_PROBES))
347     return;
348   if (sr->availability_trials > AVAILABILITY_TRIALS_MAX)
349     return;
350   len = GNUNET_FS_uri_chk_get_file_size (sr->uri);
351   if (len == 0)
352     return;
353   if ( (len <= DBLOCK_SIZE) && (sr->availability_success > 0))
354     return;
355   off = len / DBLOCK_SIZE;
356   if (off > 0)
357     off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, off);
358   off *= DBLOCK_SIZE;
359   if (len - off < DBLOCK_SIZE)
360     len = len - off;
361   else
362     len = DBLOCK_SIZE;
363   sr->remaining_probe_time = GNUNET_TIME_relative_multiply (sr->sc->h->avg_block_latency,
364                                                             2 * (1 + sr->availability_trials));
365   sr->probe_ctx = GNUNET_FS_download_start (sr->sc->h,
366                                             sr->uri,
367                                             sr->meta,
368                                             NULL, NULL,
369                                             off, len, 
370                                             sr->sc->anonymity,
371                                             GNUNET_FS_DOWNLOAD_NO_TEMPORARIES |
372                                             GNUNET_FS_DOWNLOAD_IS_PROBE,
373                                             sr, NULL);    
374 }
375
376
377 /**
378  * We have received a KSK result.  Check how it fits in with the
379  * overall query and notify the client accordingly.
380  *
381  * @param sc context for the overall query
382  * @param ent entry for the specific keyword
383  * @param uri the URI that was found
384  * @param meta metadata associated with the URI
385  *        under the "ent" keyword
386  */
387 static void
388 process_ksk_result (struct GNUNET_FS_SearchContext *sc, 
389                     struct SearchRequestEntry *ent,
390                     const struct GNUNET_FS_Uri *uri,
391                     const struct GNUNET_CONTAINER_MetaData *meta)
392 {
393   GNUNET_HashCode key;
394   struct SearchResult *sr;
395   struct GetResultContext grc;
396   int is_new;
397
398   /* check if new */
399   GNUNET_FS_uri_to_key (uri, &key);
400   if (GNUNET_SYSERR ==
401       GNUNET_CONTAINER_multihashmap_get_multiple (ent->results,
402                                                   &key,
403                                                   &test_result_present,
404                                                   (void*) uri))
405     return; /* duplicate result */
406   /* try to find search result in master map */
407   grc.sr = NULL;
408   grc.uri = uri;
409   GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map,
410                                               &key,
411                                               &get_result_present,
412                                               &grc);
413   sr = grc.sr;
414   is_new = (NULL == sr) || (sr->mandatory_missing > 0);
415   if (NULL == sr)
416     {
417       sr = GNUNET_malloc (sizeof (struct SearchResult));
418       sr->sc = sc;
419       sr->uri = GNUNET_FS_uri_dup (uri);
420       sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
421       sr->mandatory_missing = sc->mandatory_count;
422       GNUNET_CONTAINER_multihashmap_put (sc->master_result_map,
423                                          &key,
424                                          sr,
425                                          GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
426     }
427   else
428     {
429       GNUNET_CONTAINER_meta_data_merge (sr->meta, meta);
430     }
431   /* check if mandatory satisfied */
432   if (ent->mandatory)
433     sr->mandatory_missing--;
434   else
435     sr->optional_support++;
436   if (0 != sr->mandatory_missing)
437     return;
438   if (is_new)
439     notify_client_chk_result (sc, sr);
440   else
441     notify_client_chk_update (sc, sr);
442   GNUNET_FS_search_start_probe_ (sr);
443 }
444
445
446 /**
447  * Start search for content, internal API.
448  *
449  * @param h handle to the file sharing subsystem
450  * @param uri specifies the search parameters; can be
451  *        a KSK URI or an SKS URI.
452  * @param anonymity desired level of anonymity
453  * @param options options for the search
454  * @param cctx client context
455  * @param parent parent search (for namespace update searches)
456  * @return context that can be used to control the search
457  */
458 static struct GNUNET_FS_SearchContext *
459 search_start (struct GNUNET_FS_Handle *h,
460               const struct GNUNET_FS_Uri *uri,
461               uint32_t anonymity,
462               enum GNUNET_FS_SearchOptions options,
463               void *cctx,
464               struct GNUNET_FS_SearchContext *parent);
465
466
467 /**
468  * We have received an SKS result.  Start searching for updates and
469  * notify the client if it is a new result.
470  *
471  * @param sc context for the overall query
472  * @param id_update identifier for updates, NULL for none
473  * @param uri the URI that was found
474  * @param meta metadata associated with the URI
475   */
476 static void
477 process_sks_result (struct GNUNET_FS_SearchContext *sc, 
478                     const char *id_update,
479                     const struct GNUNET_FS_Uri *uri,
480                     const struct GNUNET_CONTAINER_MetaData *meta)
481 {
482   struct GNUNET_FS_Uri uu;
483   GNUNET_HashCode key;
484   struct SearchResult *sr;
485
486   /* check if new */
487   GNUNET_FS_uri_to_key (uri, &key);
488   GNUNET_CRYPTO_hash_xor (&uri->data.chk.chk.key,
489                           &uri->data.chk.chk.query,
490                           &key);
491   if (GNUNET_SYSERR ==
492       GNUNET_CONTAINER_multihashmap_get_multiple (sc->master_result_map,
493                                                   &key,
494                                                   &test_result_present,
495                                                   (void*) uri))
496     return; /* duplicate result */
497   sr = GNUNET_malloc (sizeof (struct SearchResult));
498   sr->sc = sc;
499   sr->uri = GNUNET_FS_uri_dup (uri);
500   sr->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
501   GNUNET_CONTAINER_multihashmap_put (sc->master_result_map,
502                                      &key,
503                                      sr,
504                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
505   GNUNET_FS_search_start_probe_ (sr);
506   /* notify client */
507   notify_client_chk_result (sc, sr);
508   /* search for updates */
509   if (strlen (id_update) == 0)
510     return; /* no updates */
511   uu.type = sks;
512   uu.data.sks.namespace = sc->uri->data.sks.namespace;
513   uu.data.sks.identifier = GNUNET_strdup (id_update);
514   /* FIXME: should attach update search
515      to the individual result, not
516      the entire SKS search! */
517   search_start (sc->h,
518                 &uu,
519                 sc->anonymity,
520                 sc->options,
521                 NULL,
522                 sc);
523 }
524
525
526 /**
527  * Process a keyword-search result.
528  *
529  * @param sc our search context
530  * @param kb the kblock
531  * @param size size of kb
532  */
533 static void
534 process_kblock (struct GNUNET_FS_SearchContext *sc,
535                 const struct KBlock *kb,
536                 size_t size)
537 {
538   unsigned int i;
539   size_t j;
540   GNUNET_HashCode q;
541   char pt[size - sizeof (struct KBlock)];
542   struct GNUNET_CRYPTO_AesSessionKey skey;
543   struct GNUNET_CRYPTO_AesInitializationVector iv;
544   const char *eos;
545   struct GNUNET_CONTAINER_MetaData *meta;
546   struct GNUNET_FS_Uri *uri;
547   char *emsg;
548   
549   GNUNET_CRYPTO_hash (&kb->keyspace,
550                       sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
551                       &q);
552   /* find key */
553   for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
554     if (0 == memcmp (&q,
555                      &sc->requests[i].query,
556                      sizeof (GNUNET_HashCode)))
557       break;
558   if (i == sc->uri->data.ksk.keywordCount)
559     {
560       /* oops, does not match any of our keywords!? */
561       GNUNET_break (0);
562       return;
563     }
564   /* decrypt */
565   GNUNET_CRYPTO_hash_to_aes_key (&sc->requests[i].key, &skey, &iv);
566   GNUNET_CRYPTO_aes_decrypt (&kb[1],
567                              size - sizeof (struct KBlock),
568                              &skey,
569                              &iv,
570                              pt);
571   /* parse */
572   eos = memchr (pt, 0, sizeof (pt));
573   if (NULL == eos)
574     {
575       GNUNET_break_op (0);
576       return;
577     }
578   j = eos - pt + 1;
579   if (sizeof (pt) == j)
580     meta = GNUNET_CONTAINER_meta_data_create ();
581   else
582     meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[j],
583                                                    sizeof (pt) - j);
584   if (meta == NULL)
585     {
586       GNUNET_break_op (0);       /* kblock malformed */
587       return;
588     }
589   uri = GNUNET_FS_uri_parse (pt, &emsg);
590   if (uri == NULL)
591     {
592       GNUNET_break_op (0);       /* kblock malformed */
593       GNUNET_free_non_null (emsg);
594       GNUNET_CONTAINER_meta_data_destroy (meta);
595       return;
596     }
597   /* process */
598   process_ksk_result (sc, &sc->requests[i], uri, meta);
599
600   /* clean up */
601   GNUNET_CONTAINER_meta_data_destroy (meta);
602   GNUNET_FS_uri_destroy (uri);
603 }
604
605
606 /**
607  * Process a keyword-search result with a namespace advertisment.
608  *
609  * @param sc our search context
610  * @param nb the nblock
611  * @param size size of nb
612  */
613 static void
614 process_nblock (struct GNUNET_FS_SearchContext *sc,
615                 const struct NBlock *nb,
616                 size_t size)
617 {
618   unsigned int i;
619   size_t j;
620   GNUNET_HashCode q;
621   char pt[size - sizeof (struct NBlock)];
622   struct GNUNET_CRYPTO_AesSessionKey skey;
623   struct GNUNET_CRYPTO_AesInitializationVector iv;
624   const char *eos;
625   struct GNUNET_CONTAINER_MetaData *meta;
626   struct GNUNET_FS_Uri *uri;
627   char *uris;
628   
629   GNUNET_CRYPTO_hash (&nb->keyspace,
630                       sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
631                       &q);
632   /* find key */
633   for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
634     if (0 == memcmp (&q,
635                      &sc->requests[i].query,
636                      sizeof (GNUNET_HashCode)))
637       break;
638   if (i == sc->uri->data.ksk.keywordCount)
639     {
640       /* oops, does not match any of our keywords!? */
641       GNUNET_break (0);
642       return;
643     }
644   /* decrypt */
645   GNUNET_CRYPTO_hash_to_aes_key (&sc->requests[i].key, &skey, &iv);
646   GNUNET_CRYPTO_aes_decrypt (&nb[1],
647                              size - sizeof (struct NBlock),
648                              &skey,
649                              &iv,
650                              pt);
651   /* parse */
652   eos = memchr (pt, 0, sizeof (pt));
653   if (NULL == eos)
654     {
655       GNUNET_break_op (0);
656       return;
657     }
658   j = eos - pt + 1;
659   if (sizeof (pt) == j)
660     meta = GNUNET_CONTAINER_meta_data_create ();
661   else
662     meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[j],
663                                                    sizeof (pt) - j);
664   if (meta == NULL)
665     {
666       GNUNET_break_op (0);       /* nblock malformed */
667       return;
668     }
669
670   uri = GNUNET_malloc (sizeof (struct GNUNET_FS_Uri));
671   uri->type = sks;
672   uri->data.sks.identifier = GNUNET_strdup (pt);
673   GNUNET_CRYPTO_hash (&nb->subspace,
674                       sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
675                       &uri->data.sks.namespace);
676   uris = GNUNET_FS_uri_to_string (uri);
677   GNUNET_CONTAINER_meta_data_insert (meta,
678                                      "<gnunet>",
679                                      EXTRACTOR_METATYPE_URI,
680                                      EXTRACTOR_METAFORMAT_UTF8,
681                                      "text/plain",
682                                      uris,
683                                      strlen (uris)+1);
684   GNUNET_free (uris);
685   GNUNET_PSEUDONYM_add (sc->h->cfg,
686                         &uri->data.sks.namespace,
687                         meta);
688   /* process */
689   process_ksk_result (sc, &sc->requests[i], uri, meta);
690
691   /* clean up */
692   GNUNET_CONTAINER_meta_data_destroy (meta);
693   GNUNET_FS_uri_destroy (uri);
694 }
695
696
697 /**
698  * Process a namespace-search result.
699  *
700  * @param sc our search context
701  * @param sb the sblock
702  * @param size size of sb
703  */
704 static void
705 process_sblock (struct GNUNET_FS_SearchContext *sc,
706                 const struct SBlock *sb,
707                 size_t size)
708 {
709   size_t len = size - sizeof (struct SBlock);
710   char pt[len];
711   struct GNUNET_CRYPTO_AesSessionKey skey;
712   struct GNUNET_CRYPTO_AesInitializationVector iv;
713   struct GNUNET_FS_Uri *uri;
714   struct GNUNET_CONTAINER_MetaData *meta;
715   const char *id;
716   const char *uris;
717   size_t off;
718   char *emsg;
719   GNUNET_HashCode key;
720   char *identifier;
721
722   /* decrypt */
723   identifier = sc->uri->data.sks.identifier;
724   GNUNET_CRYPTO_hash (identifier, 
725                       strlen (identifier), 
726                       &key);
727   GNUNET_CRYPTO_hash_to_aes_key (&key, &skey, &iv);
728   GNUNET_CRYPTO_aes_decrypt (&sb[1],
729                              len,
730                              &skey,
731                              &iv,
732                              pt);
733   /* parse */
734   off = GNUNET_STRINGS_buffer_tokenize (pt,
735                                         len, 
736                                         2, 
737                                         &id, 
738                                         &uris);
739   if (off == 0)
740     {
741       GNUNET_break_op (0);     /* sblock malformed */
742       return;
743     }
744   meta = GNUNET_CONTAINER_meta_data_deserialize (&pt[off], 
745                                                  len - off);
746   if (meta == NULL)
747     {
748       GNUNET_break_op (0);     /* sblock malformed */
749       return;
750     }
751   uri = GNUNET_FS_uri_parse (uris, &emsg);
752   if (uri == NULL)
753     {
754       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
755                   "Failed to parse URI `%s': %s\n",
756                   uris, emsg);
757       GNUNET_break_op (0);     /* sblock malformed */
758       GNUNET_free_non_null (emsg);
759       GNUNET_CONTAINER_meta_data_destroy (meta);
760       return;
761     }
762   /* process */
763   process_sks_result (sc, id, uri, meta);
764   /* clean up */
765   GNUNET_FS_uri_destroy (uri);
766   GNUNET_CONTAINER_meta_data_destroy (meta);
767 }
768
769
770 /**
771  * Process a search result.
772  *
773  * @param sc our search context
774  * @param type type of the result
775  * @param expiration when it will expire
776  * @param data the (encrypted) response
777  * @param size size of data
778  */
779 static void
780 process_result (struct GNUNET_FS_SearchContext *sc,
781                 enum GNUNET_BLOCK_Type type,
782                 struct GNUNET_TIME_Absolute expiration,
783                 const void *data,
784                 size_t size)
785 {
786   if (GNUNET_TIME_absolute_get_duration (expiration).value > 0)
787     {
788       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
789                   "Result received has already expired.\n");
790       return; /* result expired */
791     }
792   switch (type)
793     {
794     case GNUNET_BLOCK_TYPE_KBLOCK:
795       if (! GNUNET_FS_uri_test_ksk (sc->uri))
796         {
797           GNUNET_break (0);
798           return;
799         }
800       if (sizeof (struct KBlock) > size)
801         {
802           GNUNET_break_op (0);
803           return;
804         }
805       process_kblock (sc, data, size);
806       break;
807     case GNUNET_BLOCK_TYPE_SBLOCK:
808       if (! GNUNET_FS_uri_test_sks (sc->uri))
809         {
810           GNUNET_break (0);
811           return;
812         }
813       if (sizeof (struct SBlock) > size)
814         {
815           GNUNET_break_op (0);
816           return;
817         }
818       process_sblock (sc, data, size);
819       break;
820     case GNUNET_BLOCK_TYPE_NBLOCK:
821       if (! GNUNET_FS_uri_test_ksk (sc->uri))
822         {
823           GNUNET_break (0);
824           return;
825         }
826       if (sizeof (struct NBlock) > size)
827         {
828           GNUNET_break_op (0);
829           return;
830         }
831       process_nblock (sc, data, size);
832       break;
833     case GNUNET_BLOCK_TYPE_ANY:
834     case GNUNET_BLOCK_TYPE_DBLOCK:
835     case GNUNET_BLOCK_TYPE_ONDEMAND:
836     case GNUNET_BLOCK_TYPE_IBLOCK:
837       GNUNET_break (0);
838       break;
839     default:
840       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
841                   _("Got result with unknown block type `%d', ignoring"),
842                   type);
843       break;
844     }
845 }
846
847
848 /**
849  * Shutdown any existing connection to the FS
850  * service and try to establish a fresh one
851  * (and then re-transmit our search request).
852  *
853  * @param sc the search to reconnec
854  */
855 static void 
856 try_reconnect (struct GNUNET_FS_SearchContext *sc);
857
858
859 /**
860  * Type of a function to call when we receive a message
861  * from the service.
862  *
863  * @param cls closure
864  * @param msg message received, NULL on timeout or fatal error
865  */
866 static void 
867 receive_results (void *cls,
868                  const struct GNUNET_MessageHeader * msg)
869 {
870   struct GNUNET_FS_SearchContext *sc = cls;
871   const struct PutMessage *cm;
872   uint16_t msize;
873
874   if ( (NULL == msg) ||
875        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) ||
876        (ntohs (msg->size) <= sizeof (struct PutMessage)) )
877     {
878       try_reconnect (sc);
879       return;
880     }
881   msize = ntohs (msg->size);
882   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
883               "Receiving %u bytes of result from fs service\n",
884               msize);
885   cm = (const struct PutMessage*) msg;
886   process_result (sc, 
887                   ntohl (cm->type),
888                   GNUNET_TIME_absolute_ntoh (cm->expiration),
889                   &cm[1],
890                   msize - sizeof (struct PutMessage));
891   /* continue receiving */
892   GNUNET_CLIENT_receive (sc->client,
893                          &receive_results,
894                          sc,
895                          GNUNET_TIME_UNIT_FOREVER_REL);
896 }
897
898
899 /**
900  * We're ready to transmit the search request to the
901  * file-sharing service.  Do it.
902  *
903  * @param cls closure
904  * @param size number of bytes available in buf
905  * @param buf where the callee should write the message
906  * @return number of bytes written to buf
907  */
908 static size_t
909 transmit_search_request (void *cls,
910                          size_t size, 
911                          void *buf)
912 {
913   struct GNUNET_FS_SearchContext *sc = cls;
914   size_t msize;
915   struct SearchMessage *sm;
916   unsigned int i;
917   const char *identifier;
918   GNUNET_HashCode key;
919   GNUNET_HashCode idh;
920
921   if (NULL == buf)
922     {
923       try_reconnect (sc);
924       return 0;
925     }
926   if (GNUNET_FS_uri_test_ksk (sc->uri))
927     {
928       msize = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
929       GNUNET_assert (size >= msize);
930       sm = buf;
931       memset (sm, 0, msize);
932       for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
933         {
934           sm[i].header.size = htons (sizeof (struct SearchMessage));
935           sm[i].header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
936           if (0 != (sc->options & GNUNET_FS_SEARCH_OPTION_LOOPBACK_ONLY))
937             sm[i].options = htonl (1);
938           else
939             sm[i].options = htonl (0);            
940           sm[i].type = htonl (GNUNET_BLOCK_TYPE_ANY);
941           sm[i].anonymity_level = htonl (sc->anonymity);
942           sm[i].query = sc->requests[i].query;
943         }
944     }
945   else
946     {
947       GNUNET_assert (GNUNET_FS_uri_test_sks (sc->uri));
948       msize = sizeof (struct SearchMessage);
949       GNUNET_assert (size >= msize);
950       sm = buf;
951       memset (sm, 0, msize);
952       sm->header.size = htons (sizeof (struct SearchMessage));
953       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
954       if (0 != (sc->options & GNUNET_FS_SEARCH_OPTION_LOOPBACK_ONLY))
955         sm->options = htonl (1);
956       else
957         sm->options = htonl (0);      
958       sm->type = htonl (GNUNET_BLOCK_TYPE_SBLOCK);
959       sm->anonymity_level = htonl (sc->anonymity);
960       sm->target = sc->uri->data.sks.namespace;
961       identifier = sc->uri->data.sks.identifier;
962       GNUNET_CRYPTO_hash (identifier,
963                           strlen (identifier),
964                           &key);
965       GNUNET_CRYPTO_hash (&key,
966                           sizeof (GNUNET_HashCode),
967                           &idh);
968       GNUNET_CRYPTO_hash_xor (&idh,
969                               &sm->target,
970                               &sm->query);
971    }
972   GNUNET_CLIENT_receive (sc->client,
973                          &receive_results,
974                          sc,
975                          GNUNET_TIME_UNIT_FOREVER_REL);
976   return msize;
977 }
978
979
980 /**
981  * Reconnect to the FS service and transmit
982  * our queries NOW.
983  *
984  * @param cls our search context
985  * @param tc unused
986  */
987 static void
988 do_reconnect (void *cls,
989               const struct GNUNET_SCHEDULER_TaskContext *tc)
990 {
991   struct GNUNET_FS_SearchContext *sc = cls;
992   struct GNUNET_CLIENT_Connection *client;
993   size_t size;
994   
995   sc->task = GNUNET_SCHEDULER_NO_TASK;
996   client = GNUNET_CLIENT_connect (sc->h->sched,
997                                   "fs",
998                                   sc->h->cfg);
999   if (NULL == client)
1000     {
1001       try_reconnect (sc);
1002       return;
1003     }
1004   sc->client = client;
1005   if (GNUNET_FS_uri_test_ksk (sc->uri))
1006     size = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
1007   else
1008     size = sizeof (struct SearchMessage);
1009   GNUNET_CLIENT_notify_transmit_ready (client,
1010                                        size,
1011                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1012                                        GNUNET_NO,
1013                                        &transmit_search_request,
1014                                        sc);  
1015 }
1016
1017
1018 /**
1019  * Shutdown any existing connection to the FS
1020  * service and try to establish a fresh one
1021  * (and then re-transmit our search request).
1022  *
1023  * @param sc the search to reconnec
1024  */
1025 static void 
1026 try_reconnect (struct GNUNET_FS_SearchContext *sc)
1027 {
1028   if (NULL != sc->client)
1029     {
1030       GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO);
1031       sc->client = NULL;
1032     }
1033   sc->task
1034     = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
1035                                     GNUNET_TIME_UNIT_SECONDS,
1036                                     &do_reconnect,
1037                                     sc);
1038 }
1039
1040
1041 /**
1042  * Start search for content, internal API.
1043  *
1044  * @param h handle to the file sharing subsystem
1045  * @param uri specifies the search parameters; can be
1046  *        a KSK URI or an SKS URI.
1047  * @param anonymity desired level of anonymity
1048  * @param options options for the search
1049  * @param cctx initial value for the client context
1050  * @param parent parent search (for namespace update searches)
1051  * @return context that can be used to control the search
1052  */
1053 static struct GNUNET_FS_SearchContext *
1054 search_start (struct GNUNET_FS_Handle *h,
1055               const struct GNUNET_FS_Uri *uri,
1056               uint32_t anonymity,
1057               enum GNUNET_FS_SearchOptions options,
1058               void *cctx,
1059               struct GNUNET_FS_SearchContext *parent)
1060 {
1061   struct GNUNET_FS_SearchContext *sc;
1062   struct GNUNET_FS_ProgressInfo pi;
1063
1064   sc = GNUNET_malloc (sizeof(struct GNUNET_FS_SearchContext));
1065   sc->h = h;
1066   sc->options = options;
1067   sc->uri = GNUNET_FS_uri_dup (uri);
1068   sc->anonymity = anonymity;
1069   sc->start_time = GNUNET_TIME_absolute_get ();
1070   sc->parent = parent;
1071   sc->master_result_map = GNUNET_CONTAINER_multihashmap_create (16);
1072   sc->client_info = cctx;
1073   if (NULL != parent)
1074     GNUNET_CONTAINER_DLL_insert (parent->child_head,
1075                                  parent->child_tail,
1076                                  sc);
1077   if (GNUNET_OK !=
1078       GNUNET_FS_search_start_searching_ (sc))
1079     {
1080       GNUNET_FS_uri_destroy (sc->uri);
1081       GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map);
1082       GNUNET_free (sc);      
1083       return NULL;
1084     }
1085   pi.status = GNUNET_FS_STATUS_SEARCH_START;
1086   sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc);
1087   return sc;
1088 }
1089
1090
1091 /**
1092  * Build the request and actually initiate the search using the
1093  * GNUnet FS service.
1094  *
1095  * @param sc search context
1096  * @return GNUNET_OK on success, GNUNET_SYSERR on error
1097  */
1098 int
1099 GNUNET_FS_search_start_searching_ (struct GNUNET_FS_SearchContext *sc)
1100 {
1101   unsigned int i;
1102   const char *keyword;
1103   GNUNET_HashCode hc;
1104   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;  
1105   struct GNUNET_CRYPTO_RsaPrivateKey *pk;
1106   size_t size;
1107
1108   GNUNET_assert (NULL == sc->client);
1109   if (GNUNET_FS_uri_test_ksk (sc->uri))
1110     {
1111       size = sizeof (struct SearchMessage) * sc->uri->data.ksk.keywordCount;
1112     }
1113   else
1114     {
1115       GNUNET_assert (GNUNET_FS_uri_test_sks (sc->uri));
1116       size = sizeof (struct SearchMessage);
1117     }
1118   if (size >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
1119     {
1120       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1121                   _("Too many keywords specified for a single search."));
1122       return GNUNET_SYSERR;
1123     }
1124   if (GNUNET_FS_uri_test_ksk (sc->uri))
1125     {
1126       GNUNET_assert (0 != sc->uri->data.ksk.keywordCount);
1127       sc->requests = GNUNET_malloc (sizeof (struct SearchRequestEntry) *
1128                                     sc->uri->data.ksk.keywordCount);
1129       for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
1130         {
1131           keyword = &sc->uri->data.ksk.keywords[i][1];
1132           GNUNET_CRYPTO_hash (keyword, strlen (keyword), &hc);
1133           pk = GNUNET_CRYPTO_rsa_key_create_from_hash (&hc);
1134           GNUNET_CRYPTO_rsa_key_get_public (pk, &pub);
1135           GNUNET_CRYPTO_rsa_key_free (pk);
1136           GNUNET_CRYPTO_hash (&pub,
1137                               sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), 
1138                               &sc->requests[i].query);
1139           sc->requests[i].mandatory = (sc->uri->data.ksk.keywords[i][0] == '+');
1140           if (sc->requests[i].mandatory)
1141             sc->mandatory_count++;
1142           sc->requests[i].results = GNUNET_CONTAINER_multihashmap_create (4);
1143           GNUNET_CRYPTO_hash (keyword,
1144                               strlen (keyword),
1145                               &sc->requests[i].key);
1146         }
1147     }
1148   sc->client = GNUNET_CLIENT_connect (sc->h->sched,
1149                                       "fs",
1150                                       sc->h->cfg);
1151   if (NULL == sc->client)
1152     return GNUNET_SYSERR;
1153   GNUNET_CLIENT_notify_transmit_ready (sc->client,
1154                                        size,
1155                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1156                                        GNUNET_NO,
1157                                        &transmit_search_request,
1158                                        sc);
1159   return GNUNET_OK;
1160 }
1161
1162
1163
1164
1165 /**
1166  * Start search for content.
1167  *
1168  * @param h handle to the file sharing subsystem
1169  * @param uri specifies the search parameters; can be
1170  *        a KSK URI or an SKS URI.
1171  * @param anonymity desired level of anonymity
1172  * @param options options for the search
1173  * @param cctx initial value for the client context
1174  * @return context that can be used to control the search
1175  */
1176 struct GNUNET_FS_SearchContext *
1177 GNUNET_FS_search_start (struct GNUNET_FS_Handle *h,
1178                         const struct GNUNET_FS_Uri *uri,
1179                         uint32_t anonymity,
1180                         enum GNUNET_FS_SearchOptions options,
1181                         void *cctx)
1182 {
1183   return search_start (h, uri, anonymity, options, cctx, NULL);
1184 }
1185
1186
1187 /**
1188  * Pause search.  
1189  *
1190  * @param sc context for the search that should be paused
1191  */
1192 void 
1193 GNUNET_FS_search_pause (struct GNUNET_FS_SearchContext *sc)
1194 {
1195   struct GNUNET_FS_ProgressInfo pi;
1196
1197   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
1198     GNUNET_SCHEDULER_cancel (sc->h->sched,
1199                              sc->task);
1200   sc->task = GNUNET_SCHEDULER_NO_TASK;
1201   if (NULL != sc->client)
1202     GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO);
1203   sc->client = NULL;
1204   // FIXME: make persistent!
1205   // FIXME: should this freeze all active probes?
1206   pi.status = GNUNET_FS_STATUS_SEARCH_PAUSED;
1207   sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc);
1208 }
1209
1210
1211 /**
1212  * Continue paused search.
1213  *
1214  * @param sc context for the search that should be resumed
1215  */
1216 void 
1217 GNUNET_FS_search_continue (struct GNUNET_FS_SearchContext *sc)
1218 {
1219   struct GNUNET_FS_ProgressInfo pi;
1220
1221   GNUNET_assert (sc->client == NULL);
1222   GNUNET_assert (sc->task == GNUNET_SCHEDULER_NO_TASK);
1223   do_reconnect (sc, NULL);
1224   // FIXME: make persistent!
1225   pi.status = GNUNET_FS_STATUS_SEARCH_CONTINUED;
1226   sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc);
1227 }
1228
1229
1230 /**
1231  * Free the given search result.
1232  *
1233  * @param cls the global FS handle
1234  * @param key the key for the search result (unused)
1235  * @param value the search result to free
1236  * @return GNUNET_OK
1237  */
1238 static int
1239 search_result_free (void *cls,
1240                     const GNUNET_HashCode * key,
1241                     void *value)
1242 {
1243   struct GNUNET_FS_SearchContext *sc = cls;
1244   struct GNUNET_FS_Handle *h = sc->h;
1245   char pbuf[32];
1246   struct SearchResult *sr = value;
1247   struct GNUNET_FS_ProgressInfo pi;
1248
1249   pi.status = GNUNET_FS_STATUS_SEARCH_RESULT_STOPPED;
1250   pi.value.search.specifics.result_stopped.cctx = sr->client_info;
1251   pi.value.search.specifics.result_stopped.meta = sr->meta;
1252   pi.value.search.specifics.result_stopped.uri = sr->uri;
1253   sr->client_info = GNUNET_FS_search_make_status_ (&pi, sc);
1254   GNUNET_break (NULL == sr->client_info);
1255   if (sr->serialization != NULL)
1256     {
1257       GNUNET_snprintf (pbuf,
1258                        sizeof (pbuf),
1259                        "%s%s%s",
1260                        "search-results",
1261                        DIR_SEPARATOR_STR,
1262                        sc->serialization);
1263       GNUNET_FS_remove_sync_file_ (sc->h,
1264                                    pbuf,
1265                                    sr->serialization);
1266       GNUNET_free (sr->serialization);
1267     }
1268   GNUNET_FS_uri_destroy (sr->uri);
1269   GNUNET_CONTAINER_meta_data_destroy (sr->meta);
1270   if (sr->probe_ctx != NULL)
1271     GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);    
1272   if (sr->probe_cancel_task != GNUNET_SCHEDULER_NO_TASK)
1273     GNUNET_SCHEDULER_cancel (h->sched,
1274                              sr->probe_cancel_task);    
1275   GNUNET_free (sr);
1276   return GNUNET_OK;
1277 }
1278
1279
1280 /**
1281  * Stop search for content.
1282  *
1283  * @param sc context for the search that should be stopped
1284  */
1285 void 
1286 GNUNET_FS_search_stop (struct GNUNET_FS_SearchContext *sc)
1287 {
1288   struct GNUNET_FS_ProgressInfo pi;
1289   unsigned int i;
1290   struct GNUNET_FS_SearchContext *parent;
1291
1292   // FIXME: make un-persistent!
1293   if (NULL != (parent = sc->parent))
1294     {
1295       GNUNET_CONTAINER_DLL_remove (parent->child_head,
1296                                    parent->child_tail,
1297                                    sc);
1298       sc->parent = NULL;
1299     }
1300   while (NULL != sc->child_head)
1301     GNUNET_FS_search_stop (sc->child_head);
1302   GNUNET_CONTAINER_multihashmap_iterate (sc->master_result_map,
1303                                          &search_result_free,
1304                                          sc);
1305   pi.status = GNUNET_FS_STATUS_SEARCH_STOPPED;
1306   sc->client_info = GNUNET_FS_search_make_status_ (&pi, sc);
1307   GNUNET_break (NULL == sc->client_info);
1308   if (sc->task != GNUNET_SCHEDULER_NO_TASK)
1309     GNUNET_SCHEDULER_cancel (sc->h->sched,
1310                              sc->task);
1311   if (NULL != sc->client)
1312     GNUNET_CLIENT_disconnect (sc->client, GNUNET_NO);
1313   GNUNET_CONTAINER_multihashmap_destroy (sc->master_result_map);
1314   if (sc->requests != NULL)
1315     {
1316       GNUNET_assert (GNUNET_FS_uri_test_ksk (sc->uri));
1317       for (i=0;i<sc->uri->data.ksk.keywordCount;i++)
1318         GNUNET_CONTAINER_multihashmap_destroy (sc->requests[i].results);
1319     }
1320   GNUNET_free_non_null (sc->requests);
1321   GNUNET_free_non_null (sc->emsg);
1322   GNUNET_FS_uri_destroy (sc->uri);
1323   GNUNET_free (sc);
1324 }
1325
1326 /* end of fs_search.c */