expose our hello to plugins
[oweals/gnunet.git] / src / fs / fs_download.c
index 8d5a936f0a5a978eddb246350833e86ce1325e99..80758ebc72dc3b16b69177d67b19b5f7ee0c49d6 100644 (file)
@@ -4,7 +4,7 @@
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
-     by the Free Software Foundation; either version 2, or (at your
+     by the Free Software Foundation; either version 3, or (at your
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
@@ -23,7 +23,6 @@
  * @author Christian Grothoff
  *
  * TODO:
- * - location URI suppport (can wait, easy)
  * - different priority for scheduling probe downloads?
  * - check if iblocks can be computed from existing blocks (can wait, hard)
  */
@@ -170,6 +169,7 @@ GNUNET_FS_download_make_status_ (struct GNUNET_FS_ProgressInfo *pi,
     = GNUNET_TIME_calculate_eta (dc->start_time,
                                 dc->completed,
                                 dc->length);
+  pi->value.download.is_active = (dc->client == NULL) ? GNUNET_NO : GNUNET_YES;
   if (0 == (dc->options & GNUNET_FS_DOWNLOAD_IS_PROBE))
     dc->client_info = dc->h->upcb (dc->h->upcb_cls,
                                   pi);
@@ -249,6 +249,171 @@ process_result_with_request (void *cls,
                             void *value);
 
 
+/**
+ * We've found a matching block without downloading it.
+ * Encrypt it and pass it to our "receive" function as
+ * if we had received it from the network.
+ * 
+ * @param dc download in question
+ * @param chk request this relates to
+ * @param sm request details
+ * @param block plaintext data matching request
+ * @param len number of bytes in block
+ * @param depth depth of the block
+ * @param do_store should we still store the block on disk?
+ * @return GNUNET_OK on success
+ */
+static int
+encrypt_existing_match (struct GNUNET_FS_DownloadContext *dc,
+                       const struct ContentHashKey *chk,
+                       struct DownloadRequest *sm,
+                       const char * block,                    
+                       size_t len,
+                       int depth,
+                       int do_store)
+{
+  struct ProcessResultClosure prc;
+  char enc[len];
+  struct GNUNET_CRYPTO_AesSessionKey sk;
+  struct GNUNET_CRYPTO_AesInitializationVector iv;
+  GNUNET_HashCode query;
+  
+  GNUNET_CRYPTO_hash_to_aes_key (&chk->key, &sk, &iv);
+  if (-1 == GNUNET_CRYPTO_aes_encrypt (block, len,
+                                      &sk,
+                                      &iv,
+                                      enc))
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+  GNUNET_CRYPTO_hash (enc, len, &query);
+  if (0 != memcmp (&query,
+                  &chk->query,
+                  sizeof (GNUNET_HashCode)))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Matching block already present, no need for download!\n");
+#endif
+  /* already got it! */
+  prc.dc = dc;
+  prc.data = enc;
+  prc.size = len;
+  prc.type = (dc->treedepth == depth) 
+    ? GNUNET_BLOCK_TYPE_DBLOCK 
+    : GNUNET_BLOCK_TYPE_IBLOCK;
+  prc.query = chk->query;
+  prc.do_store = do_store;
+  process_result_with_request (&prc,
+                              &chk->key,
+                              sm);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Closure for match_full_data.
+ */
+struct MatchDataContext 
+{
+  /**
+   * CHK we are looking for.
+   */
+  const struct ContentHashKey *chk;
+
+  /**
+   * Download we're processing.
+   */
+  struct GNUNET_FS_DownloadContext *dc;
+
+  /**
+   * Request details.
+   */
+  struct DownloadRequest *sm;
+
+  /**
+   * Overall offset in the file.
+   */
+  uint64_t offset;
+
+  /**
+   * Desired length of the block.
+   */
+  size_t len;
+
+  /**
+   * Flag set to GNUNET_YES on success.
+   */
+  int done;
+};
+
+/**
+ * Type of a function that libextractor calls for each
+ * meta data item found.
+ *
+ * @param cls closure (user-defined)
+ * @param plugin_name name of the plugin that produced this value;
+ *        special values can be used (i.e. '<zlib>' for zlib being
+ *        used in the main libextractor library and yielding
+ *        meta data).
+ * @param type libextractor-type describing the meta data
+ * @param format basic format information about data 
+ * @param data_mime_type mime-type of data (not of the original file);
+ *        can be NULL (if mime-type is not known)
+ * @param data actual meta-data found
+ * @param data_len number of bytes in data
+ * @return 0 to continue extracting, 1 to abort
+ */ 
+static int
+match_full_data (void *cls,
+                const char *plugin_name,
+                enum EXTRACTOR_MetaType type,
+                enum EXTRACTOR_MetaFormat format,
+                const char *data_mime_type,
+                const char *data,
+                size_t data_len)
+{
+  struct MatchDataContext *mdc = cls;
+  GNUNET_HashCode key;
+
+  if (type == EXTRACTOR_METATYPE_GNUNET_FULL_DATA) 
+    {
+      if ( (mdc->offset > data_len) ||
+          (mdc->offset + mdc->len > data_len) )
+       return 1;
+      GNUNET_CRYPTO_hash (&data[mdc->offset],
+                         mdc->len,
+                         &key);
+      if (0 != memcmp (&key,
+                      &mdc->chk->key,
+                      sizeof (GNUNET_HashCode)))
+       {
+         GNUNET_break_op (0);
+         return 1;
+       }
+      /* match found! */
+      if (GNUNET_OK !=
+         encrypt_existing_match (mdc->dc,
+                                 mdc->chk,
+                                 mdc->sm,
+                                 &data[mdc->offset],
+                                 mdc->len,
+                                 0,
+                                 GNUNET_YES))
+       {
+         GNUNET_break_op (0);
+         return 1;
+       }
+      mdc->done = GNUNET_YES;
+      return 1;
+    }
+  return 0;
+}
+
 
 /**
  * Schedule the download of the specified block in the tree.
@@ -273,25 +438,18 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
   size_t len;
   char block[DBLOCK_SIZE];
   GNUNET_HashCode key;
-  struct ProcessResultClosure prc;
+  struct MatchDataContext mdc;
   struct GNUNET_DISK_FileHandle *fh;
 
-#if DEBUG_DOWNLOAD
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Scheduling download at offset %llu and depth %u for `%s'\n",
-             (unsigned long long) offset,
-             depth,
-             GNUNET_h2s (&chk->query));
-#endif
   total = GNUNET_ntohll (dc->uri->data.chk.file_length);
-  off = compute_disk_offset (total,
-                            offset,
-                            depth,
-                            dc->treedepth);
   len = GNUNET_FS_tree_calculate_block_size (total,
                                             dc->treedepth,
                                             offset,
                                             depth);
+  off = compute_disk_offset (total,
+                            offset,
+                            depth,
+                            dc->treedepth);
   sm = GNUNET_malloc (sizeof (struct DownloadRequest));
   sm->chk = *chk;
   sm->offset = offset;
@@ -303,14 +461,35 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
                                     &chk->query,
                                     sm,
                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+  if ( (dc->tried_full_data == GNUNET_NO) &&
+       (depth == 0) )
+    {      
+      mdc.dc = dc;
+      mdc.sm = sm;
+      mdc.chk = chk;
+      mdc.offset = offset;
+      mdc.len = len;
+      mdc.done = GNUNET_NO;
+      GNUNET_CONTAINER_meta_data_iterate (dc->meta,
+                                         &match_full_data,
+                                         &mdc);
+      if (mdc.done == GNUNET_YES)
+       return;
+      dc->tried_full_data = GNUNET_YES; 
+    }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Scheduling download at offset %llu and depth %u for `%s'\n",
+             (unsigned long long) offset,
+             depth,
+             GNUNET_h2s (&chk->query));
+#endif
   fh = NULL;
   if ( (dc->old_file_size > off) &&
-       (dc->filename != NULL) )
-    {
-      fh = GNUNET_DISK_file_open (dc->filename,
-                                 GNUNET_DISK_OPEN_READ,
-                                 GNUNET_DISK_PERM_NONE);
-    }
+       (dc->filename != NULL) )    
+    fh = GNUNET_DISK_file_open (dc->filename,
+                               GNUNET_DISK_OPEN_READ,
+                               GNUNET_DISK_PERM_NONE);    
   if ( (fh != NULL) &&
        (off  == 
        GNUNET_DISK_file_seek (fh,
@@ -322,42 +501,19 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
                               len)) )
     {
       GNUNET_CRYPTO_hash (block, len, &key);
-      if (0 == memcmp (&key,
-                      &chk->key,
-                      sizeof (GNUNET_HashCode)))
+      if ( (0 == memcmp (&key,
+                        &chk->key,
+                        sizeof (GNUNET_HashCode))) &&
+          (GNUNET_OK ==
+           encrypt_existing_match (dc,
+                                   chk,
+                                   sm,
+                                   block,
+                                   len,
+                                   depth,
+                                   GNUNET_NO)) )
        {
-         char enc[len];
-         struct GNUNET_CRYPTO_AesSessionKey sk;
-         struct GNUNET_CRYPTO_AesInitializationVector iv;
-         GNUNET_HashCode query;
-
-         GNUNET_CRYPTO_hash_to_aes_key (&key, &sk, &iv);
-         GNUNET_CRYPTO_aes_encrypt (block, len,
-                                    &sk,
-                                    &iv,
-                                    enc);
-         GNUNET_CRYPTO_hash (enc, len, &query);
-         if (0 == memcmp (&query,
-                          &chk->query,
-                          sizeof (GNUNET_HashCode)))
-           {
-             /* already got it! */
-             prc.dc = dc;
-             prc.data = enc;
-             prc.size = len;
-             prc.type = (dc->treedepth == depth) 
-               ? GNUNET_BLOCK_TYPE_DBLOCK 
-               : GNUNET_BLOCK_TYPE_IBLOCK;
-             prc.query = chk->query;
-             prc.do_store = GNUNET_NO; /* useless */
-             process_result_with_request (&prc,
-                                          &key,
-                                          sm);
-           }
-         else
-           {
-             GNUNET_break_op (0);
-           }
+         GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
          return;
        }
     }
@@ -374,12 +530,29 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
 
   if ( (dc->th == NULL) &&
        (dc->client != NULL) )
-    dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
-                                                 sizeof (struct SearchMessage),
-                                                 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
-                                                 GNUNET_NO,
-                                                 &transmit_download_request,
-                                                 dc);
+    {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Asking for transmission to FS service\n");
+#endif
+      dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
+                                                   sizeof (struct SearchMessage),
+                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
+                                                   GNUNET_NO,
+                                                   &transmit_download_request,
+                                                   dc);
+    }
+  else
+    {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Transmission request not issued (%p %p)\n",
+                 dc->th, 
+                 dc->client);
+#endif
+
+    }
+
 }
 
 
@@ -676,6 +849,7 @@ trigger_recursive_download (void *cls,
   char *us;
   char *ext;
   char *dn;
+  char *pos;
   char *full_name;
 
   if (NULL == uri)
@@ -696,7 +870,7 @@ trigger_recursive_download (void *cls,
   fn = NULL;
   if (NULL == filename)
     {
-      fn = GNUNET_FS_meta_data_suggest_filename (meta);      
+      fn = GNUNET_FS_meta_data_suggest_filename (meta);
       if (fn == NULL)
        {
          us = GNUNET_FS_uri_to_string (uri);
@@ -715,6 +889,20 @@ trigger_recursive_download (void *cls,
          GNUNET_free (ext);
          GNUNET_free (us);
        }
+      /* change '\' to '/' (this should have happened
+       during insertion, but malicious peers may
+       not have done this) */
+      while (NULL != (pos = strstr (fn, "\\")))
+       *pos = '/';
+      /* remove '../' everywhere (again, well-behaved
+        peers don't do this, but don't trust that
+        we did not get something nasty) */
+      while (NULL != (pos = strstr (fn, "../")))
+       {
+         pos[0] = '_';
+         pos[1] = '_';
+         pos[2] = '_';
+       }
       filename = fn;
     }
   if (dc->filename == NULL)
@@ -821,6 +1009,24 @@ trigger_recursive_download (void *cls,
 }
 
 
+/**
+ * Free entries in the map.
+ *
+ * @param cls unused (NULL)
+ * @param key unused
+ * @param entry entry of type "struct DownloadRequest" which is freed
+ * @return GNUNET_OK
+ */
+static int
+free_entry (void *cls,
+           const GNUNET_HashCode *key,
+           void *entry)
+{
+  GNUNET_free (entry);
+  return GNUNET_OK;
+}
+
+
 /**
  * Iterator over entries in the pending requests in the 'active' map for the
  * reply that we just got.
@@ -888,11 +1094,16 @@ process_result_with_request (void *cls,
       ppos = ppos->next;
     }
   GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
-  GNUNET_CRYPTO_aes_decrypt (prc->data,
-                            prc->size,
-                            &skey,
-                            &iv,
-                            pt);
+  if (-1 == GNUNET_CRYPTO_aes_decrypt (prc->data,
+                                      prc->size,
+                                      &skey,
+                                      &iv,
+                                      pt))
+    {
+      GNUNET_break (0);
+      dc->emsg = GNUNET_strdup ("internal error decrypting content");
+      goto signal_error;
+    }
   off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
                             sm->offset,
                             sm->depth,
@@ -1076,7 +1287,10 @@ process_result_with_request (void *cls,
       dc->th = NULL;
     }
   GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
-  /* FIXME: clean up dc->active / pending! */
+  GNUNET_CONTAINER_multihashmap_iterate (dc->active,
+                                        &free_entry,
+                                        NULL);
+  dc->pending = NULL;
   dc->client = NULL;
   GNUNET_free (sm);
   GNUNET_FS_download_sync_ (dc);
@@ -1182,6 +1396,10 @@ transmit_download_request (void *cls,
   dc->th = NULL;
   if (NULL == buf)
     {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Transmitting download request failed, trying to reconnect\n");
+#endif
       try_reconnect (dc);
       return 0;
     }
@@ -1189,7 +1407,7 @@ transmit_download_request (void *cls,
   msize = 0;
   sm = buf;
   while ( (dc->pending != NULL) &&
-         (size > msize + sizeof (struct SearchMessage)) )
+         (size >= msize + sizeof (struct SearchMessage)) )
     {
 #if DEBUG_DOWNLOAD
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -1305,6 +1523,10 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
   
   if (NULL != dc->client)
     {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Moving all requests back to pending list\n");
+#endif
       if (NULL != dc->th)
        {
          GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
@@ -1316,6 +1538,10 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
       dc->client = NULL;
     }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Will try to reconnect in 1s\n");
+#endif
   dc->task
     = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
                                    GNUNET_TIME_UNIT_SECONDS,
@@ -1338,7 +1564,13 @@ activate_fs_download (void *cls,
   struct GNUNET_FS_DownloadContext *dc = cls;
   struct GNUNET_FS_ProgressInfo pi;
 
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Download activated\n");
+#endif
   GNUNET_assert (NULL != client);
+  GNUNET_assert (dc->client == NULL);
+  GNUNET_assert (dc->th == NULL);
   dc->client = client;
   GNUNET_CLIENT_receive (client,
                         &receive_results,
@@ -1349,14 +1581,17 @@ activate_fs_download (void *cls,
   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
                                         &retry_entry,
                                         dc);
-  if ( (dc->th == NULL) &&
-       (dc->client != NULL) )
-    dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
-                                                 sizeof (struct SearchMessage),
-                                                 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
-                                                 GNUNET_NO,
-                                                 &transmit_download_request,
-                                                 dc);
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Asking for transmission to FS service\n");
+#endif
+  dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
+                                               sizeof (struct SearchMessage),
+                                               GNUNET_CONSTANTS_SERVICE_TIMEOUT,
+                                               GNUNET_NO,
+                                               &transmit_download_request,
+                                               dc);    
+  GNUNET_assert (dc->th != NULL);
 }
 
 
@@ -1370,7 +1605,11 @@ deactivate_fs_download (void *cls)
 {
   struct GNUNET_FS_DownloadContext *dc = cls;
   struct GNUNET_FS_ProgressInfo pi;
-  
+
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Download deactivated\n");
+#endif  
   if (NULL != dc->th)
     {
       GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
@@ -1386,24 +1625,6 @@ deactivate_fs_download (void *cls)
 }
 
 
-/**
- * Free entries in the map.
- *
- * @param cls unused (NULL)
- * @param key unused
- * @param entry entry of type "struct DownloadRequest" which is freed
- * @return GNUNET_OK
- */
-static int
-free_entry (void *cls,
-           const GNUNET_HashCode *key,
-           void *entry)
-{
-  GNUNET_free (entry);
-  return GNUNET_OK;
-}
-
-
 /**
  * Create SUSPEND event for the given download operation
  * and then clean up our state (without stop signal).
@@ -1556,6 +1777,12 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
              "Download tree has depth %u\n",
              dc->treedepth);
 #endif
+  if (parent == NULL)
+    {
+      dc->top = GNUNET_FS_make_top (dc->h,
+                                   &GNUNET_FS_download_signal_suspend_,
+                                   dc);
+    }
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
   pi.value.download.specifics.start.meta = meta;
   GNUNET_FS_download_make_status_ (&pi, dc);
@@ -1565,11 +1792,6 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
                           1 /* 0 == CHK, 1 == top */); 
   GNUNET_FS_download_sync_ (dc);
   GNUNET_FS_download_start_downloading_ (dc);
-  if (parent == NULL)
-    dc->top = GNUNET_FS_make_top (dc->h,
-                                 &GNUNET_FS_download_signal_suspend_,
-                                 dc);
-
   return dc;
 }
 
@@ -1622,7 +1844,8 @@ GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
   struct GNUNET_FS_ProgressInfo pi;
   struct GNUNET_FS_DownloadContext *dc;
 
-  if (sr->download != NULL)
+  if ( (sr == NULL) ||
+       (sr->download != NULL) )
     {
       GNUNET_break (0);
       return NULL;
@@ -1706,6 +1929,7 @@ GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
 void
 GNUNET_FS_download_start_downloading_ (struct GNUNET_FS_DownloadContext *dc)
 {
+  GNUNET_assert (dc->job_queue == NULL);
   dc->job_queue = GNUNET_FS_queue_ (dc->h, 
                                    &activate_fs_download,
                                    &deactivate_fs_download,
@@ -1749,13 +1973,16 @@ GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
                                 dc);  
   if (dc->serialization != NULL)
     GNUNET_FS_remove_sync_file_ (dc->h,
-                                (dc->parent != NULL) 
-                                ? "subdownloads" 
-                                : "download"
+                                ( (dc->parent != NULL)  || (dc->search != NULL) )
+                                ? GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD 
+                                : GNUNET_FS_SYNC_PATH_MASTER_DOWNLOAD 
                                 dc->serialization);
-  if (GNUNET_YES == have_children)
-    GNUNET_FS_remove_sync_dir_ (dc->h,                       
-                               "subdownloads",
+  if ( (GNUNET_YES == have_children) &&
+       (dc->parent == NULL) )
+    GNUNET_FS_remove_sync_dir_ (dc->h, 
+                               (dc->search != NULL) 
+                               ? GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD 
+                               : GNUNET_FS_SYNC_PATH_MASTER_DOWNLOAD,
                                dc->serialization);  
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
   GNUNET_FS_download_make_status_ (&pi, dc);