+
+ /* let parent know */
+ if (NULL != dc->parent)
+ check_completed (dc->parent);
+}
+
+
+/**
+ * We got a block of plaintext data (from the meta data).
+ * Try it for upward reconstruction of the data. On success,
+ * the top-level block will move to state BRS_DOWNLOAD_UP.
+ *
+ * @param dc context for the download
+ * @param dr download request to match against
+ * @param data plaintext data, starting from the beginning of the file
+ * @param data_len number of bytes in data
+ */
+static void
+try_match_block (struct GNUNET_FS_DownloadContext *dc,
+ struct DownloadRequest *dr, const char *data, size_t data_len)
+{
+ struct GNUNET_FS_ProgressInfo pi;
+ unsigned int i;
+ char enc[DBLOCK_SIZE];
+ struct ContentHashKey chks[CHK_PER_INODE];
+ struct ContentHashKey in_chk;
+ struct GNUNET_CRYPTO_AesSessionKey sk;
+ struct GNUNET_CRYPTO_AesInitializationVector iv;
+ size_t dlen;
+ struct DownloadRequest *drc;
+ struct GNUNET_DISK_FileHandle *fh;
+ int complete;
+ const char *fn;
+ const char *odata;
+ size_t odata_len;
+
+ odata = data;
+ odata_len = data_len;
+ if (BRS_DOWNLOAD_UP == dr->state)
+ return;
+ if (dr->depth > 0)
+ {
+ complete = GNUNET_YES;
+ for (i = 0; i < dr->num_children; i++)
+ {
+ drc = dr->children[i];
+ try_match_block (dc, drc, data, data_len);
+ if (drc->state != BRS_RECONSTRUCT_META_UP)
+ complete = GNUNET_NO;
+ else
+ chks[i] = drc->chk;
+ }
+ if (GNUNET_YES != complete)
+ return;
+ data = (const char *) chks;
+ dlen = dr->num_children * sizeof (struct ContentHashKey);
+ }
+ else
+ {
+ if (dr->offset > data_len)
+ return; /* oops */
+ dlen = GNUNET_MIN (data_len - dr->offset, DBLOCK_SIZE);
+ }
+ GNUNET_CRYPTO_hash (&data[dr->offset], dlen, &in_chk.key);
+ GNUNET_CRYPTO_hash_to_aes_key (&in_chk.key, &sk, &iv);
+ if (-1 == GNUNET_CRYPTO_aes_encrypt (&data[dr->offset], dlen, &sk, &iv, enc))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_CRYPTO_hash (enc, dlen, &in_chk.query);
+ switch (dr->state)
+ {
+ case BRS_INIT:
+ dr->chk = in_chk;
+ dr->state = BRS_RECONSTRUCT_META_UP;
+ break;
+ case BRS_CHK_SET:
+ if (0 != memcmp (&in_chk, &dr->chk, sizeof (struct ContentHashKey)))
+ {
+ /* other peer provided bogus meta data */
+ GNUNET_break_op (0);
+ break;
+ }
+ /* write block to disk */
+ fn = (NULL != dc->filename) ? dc->filename : dc->temp_filename;
+ fh = GNUNET_DISK_file_open (fn,
+ GNUNET_DISK_OPEN_READWRITE |
+ GNUNET_DISK_OPEN_CREATE |
+ GNUNET_DISK_OPEN_TRUNCATE,
+ GNUNET_DISK_PERM_USER_READ |
+ GNUNET_DISK_PERM_USER_WRITE |
+ GNUNET_DISK_PERM_GROUP_READ |
+ GNUNET_DISK_PERM_OTHER_READ);
+ if (NULL == fh)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", fn);
+ GNUNET_asprintf (&dc->emsg, _("Failed to open file `%s' for writing"),
+ fn);
+ GNUNET_DISK_file_close (fh);
+ dr->state = BRS_ERROR;
+ pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
+ pi.value.download.specifics.error.message = dc->emsg;
+ GNUNET_FS_download_make_status_ (&pi, dc);
+ return;
+ }
+ if (data_len != GNUNET_DISK_file_write (fh, odata, odata_len))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "write", fn);
+ GNUNET_asprintf (&dc->emsg, _("Failed to open file `%s' for writing"),
+ fn);
+ GNUNET_DISK_file_close (fh);
+ dr->state = BRS_ERROR;
+ pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
+ pi.value.download.specifics.error.message = dc->emsg;
+ GNUNET_FS_download_make_status_ (&pi, dc);
+ return;
+ }
+ GNUNET_DISK_file_close (fh);
+ /* signal success */
+ dr->state = BRS_DOWNLOAD_UP;
+ dc->completed = dc->length;
+ GNUNET_FS_download_sync_ (dc);
+ pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
+ pi.value.download.specifics.progress.data = data;
+ pi.value.download.specifics.progress.offset = 0;
+ pi.value.download.specifics.progress.data_len = dlen;
+ pi.value.download.specifics.progress.depth = 0;
+ pi.value.download.specifics.progress.trust_offered = 0;
+ pi.value.download.specifics.progress.block_download_duration = GNUNET_TIME_UNIT_ZERO;
+ GNUNET_FS_download_make_status_ (&pi, dc);
+ if ((NULL != dc->filename) &&
+ (0 !=
+ truncate (dc->filename,
+ GNUNET_ntohll (dc->uri->data.chk.file_length))))
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "truncate",
+ dc->filename);
+ check_completed (dc);
+ break;
+ default:
+ /* how did we get here? */
+ GNUNET_break (0);
+ break;
+ }
+}
+
+
+/**
+ * Type of a function that libextractor calls for each
+ * meta data item found. If we find full data meta data,
+ * call 'try_match_block' on it.
+ *
+ * @param cls our 'struct GNUNET_FS_DownloadContext*'
+ * @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 GNUNET_FS_DownloadContext *dc = cls;
+
+ if (EXTRACTOR_METATYPE_GNUNET_FULL_DATA != type)
+ return 0;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found %u bytes of FD!\n",
+ (unsigned int) data_len);
+ if (GNUNET_FS_uri_chk_get_file_size (dc->uri) != data_len)
+ {
+ GNUNET_break_op (0);
+ return 1; /* bogus meta data */
+ }
+ try_match_block (dc, dc->top_request, data, data_len);
+ return 1;
+}
+
+
+/**
+ * Set the state of the given download request to
+ * BRS_DOWNLOAD_UP and propagate it up the tree.
+ *
+ * @param dr download request that is done
+ */
+static void
+propagate_up (struct DownloadRequest *dr)
+{
+ unsigned int i;
+
+ do
+ {
+ dr->state = BRS_DOWNLOAD_UP;
+ dr = dr->parent;
+ if (NULL == dr)
+ break;
+ for (i = 0; i < dr->num_children; i++)
+ if (dr->children[i]->state != BRS_DOWNLOAD_UP)
+ break;
+ }
+ while (i == dr->num_children);
+}
+
+
+/**
+ * Try top-down reconstruction. Before, the given request node
+ * must have the state BRS_CHK_SET. Afterwards, more nodes may
+ * have that state or advanced to BRS_DOWNLOAD_DOWN or even
+ * BRS_DOWNLOAD_UP. It is also possible to get BRS_ERROR on the
+ * top level.
+ *
+ * @param dc overall download this block belongs to
+ * @param dr block to reconstruct
+ */
+static void
+try_top_down_reconstruction (struct GNUNET_FS_DownloadContext *dc,
+ struct DownloadRequest *dr)
+{
+ uint64_t off;
+ char block[DBLOCK_SIZE];
+ GNUNET_HashCode key;
+ uint64_t total;
+ size_t len;
+ unsigned int i;
+ struct DownloadRequest *drc;
+ uint64_t child_block_size;
+ const struct ContentHashKey *chks;
+ int up_done;
+
+ GNUNET_assert (NULL != dc->rfh);
+ GNUNET_assert (BRS_CHK_SET == dr->state);
+ total = GNUNET_FS_uri_chk_get_file_size (dc->uri);
+ GNUNET_assert (dr->depth < dc->treedepth);
+ len = GNUNET_FS_tree_calculate_block_size (total, dr->offset, dr->depth);
+ GNUNET_assert (len <= DBLOCK_SIZE);
+ off = compute_disk_offset (total, dr->offset, dr->depth);
+ if (dc->old_file_size < off + len)
+ return; /* failure */
+ if (off != GNUNET_DISK_file_seek (dc->rfh, off, GNUNET_DISK_SEEK_SET))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "seek", dc->filename);
+ return; /* failure */
+ }
+ if (len != GNUNET_DISK_file_read (dc->rfh, block, len))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "read", dc->filename);
+ return; /* failure */
+ }
+ GNUNET_CRYPTO_hash (block, len, &key);
+ if (0 != memcmp (&key, &dr->chk.key, sizeof (GNUNET_HashCode)))
+ return; /* mismatch */
+ if (GNUNET_OK !=
+ encrypt_existing_match (dc, &dr->chk, dr, block, len, GNUNET_NO))
+ {
+ /* hash matches but encrypted block does not, really bad */
+ dr->state = BRS_ERROR;
+ /* propagate up */
+ while (NULL != dr->parent)
+ {
+ dr = dr->parent;
+ dr->state = BRS_ERROR;
+ }
+ return;
+ }
+ /* block matches */
+ dr->state = BRS_DOWNLOAD_DOWN;
+
+ /* set CHKs for children */
+ up_done = GNUNET_YES;
+ chks = (const struct ContentHashKey *) block;
+ for (i = 0; i < dr->num_children; i++)
+ {
+ drc = dr->children[i];
+ GNUNET_assert (drc->offset >= dr->offset);
+ child_block_size = GNUNET_FS_tree_compute_tree_size (drc->depth);
+ GNUNET_assert (0 == (drc->offset - dr->offset) % child_block_size);
+ if (BRS_INIT == drc->state)
+ {
+ drc->state = BRS_CHK_SET;
+ drc->chk = chks[drc->chk_idx];
+ try_top_down_reconstruction (dc, drc);
+ }
+ if (BRS_DOWNLOAD_UP != drc->state)
+ up_done = GNUNET_NO; /* children not all done */
+ }
+ if (GNUNET_YES == up_done)
+ propagate_up (dr); /* children all done (or no children...) */
+}
+
+
+/**
+ * Schedule the download of the specified block in the tree.
+ *
+ * @param dc overall download this block belongs to
+ * @param dr request to schedule
+ */
+static void
+schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
+ struct DownloadRequest *dr)
+{
+ unsigned int i;
+
+ switch (dr->state)
+ {
+ case BRS_INIT:
+ GNUNET_assert (0);
+ break;
+ case BRS_RECONSTRUCT_DOWN:
+ GNUNET_assert (0);
+ break;
+ case BRS_RECONSTRUCT_META_UP:
+ GNUNET_assert (0);
+ break;
+ case BRS_RECONSTRUCT_UP:
+ GNUNET_assert (0);
+ break;
+ case BRS_CHK_SET:
+ /* normal case, start download */
+ break;
+ case BRS_DOWNLOAD_DOWN:
+ for (i = 0; i < dr->num_children; i++)
+ schedule_block_download (dc, dr->children[i]);
+ return;
+ case BRS_DOWNLOAD_UP:
+ /* We're done! */
+ return;
+ case BRS_ERROR:
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Scheduling download at offset %llu and depth %u for `%s'\n",
+ (unsigned long long) dr->offset, dr->depth,
+ GNUNET_h2s (&dr->chk.query));
+ if (GNUNET_NO !=
+ GNUNET_CONTAINER_multihashmap_contains_value (dc->active, &dr->chk.query,
+ dr))
+ return; /* already active */
+ GNUNET_CONTAINER_multihashmap_put (dc->active, &dr->chk.query, dr,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+ if (NULL == dc->client)
+ return; /* download not active */
+ GNUNET_CONTAINER_DLL_insert (dc->pending_head, dc->pending_tail, dr);
+ dr->is_pending = GNUNET_YES;
+ if (NULL == dc->th)
+ dc->th =
+ GNUNET_CLIENT_notify_transmit_ready (dc->client,
+ sizeof (struct SearchMessage),
+ GNUNET_CONSTANTS_SERVICE_TIMEOUT,
+ GNUNET_NO,
+ &transmit_download_request, dc);