2 This file is part of GNUnet.
3 (C) 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009 Christian Grothoff (and other contributing authors)
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.
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.
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.
21 * @file fs/fs_download.c
22 * @brief download methods
23 * @author Christian Grothoff
27 * - callback signaling
28 * - check if blocks exist already
29 * - location URI suppport (can wait)
30 * - persistence (can wait)
33 #include "gnunet_constants.h"
34 #include "gnunet_fs_service.h"
38 #define DEBUG_DOWNLOAD GNUNET_YES
42 * Schedule the download of the specified
45 * @param dc overall download this block belongs to
46 * @param chk content-hash-key of the block
47 * @param offset offset of the block in the file
48 * (for IBlocks, the offset is the lowest
49 * offset of any DBlock in the subtree under
51 * @param depth depth of the block, 0 is the root of the tree
54 schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
55 const struct ContentHashKey *chk,
59 struct DownloadRequest *sm;
61 // FIXME: check if block exists on disk!
62 sm = GNUNET_malloc (sizeof (struct DownloadRequest));
66 sm->is_pending = GNUNET_YES;
67 sm->next = dc->pending;
69 GNUNET_CONTAINER_multihashmap_put (dc->active,
72 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
77 * We've lost our connection with the FS service.
78 * Re-establish it and re-transmit all of our
81 * @param dc download context that is having trouble
84 try_reconnect (struct GNUNET_FS_DownloadContext *dc);
88 * Process a search result.
90 * @param sc our search context
91 * @param type type of the result
92 * @param data the (encrypted) response
93 * @param size size of data
96 process_result (struct GNUNET_FS_DownloadContext *dc,
101 GNUNET_HashCode query;
102 struct DownloadRequest *sm;
103 struct GNUNET_CRYPTO_AesSessionKey skey;
104 struct GNUNET_CRYPTO_AesInitializationVector iv;
107 GNUNET_CRYPTO_hash (data, size, &query);
108 sm = GNUNET_CONTAINER_multihashmap_get (dc->active,
115 GNUNET_assert (GNUNET_YES ==
116 GNUNET_CONTAINER_multihashmap_remove (dc->active,
119 GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
120 GNUNET_CRYPTO_aes_decrypt (data,
125 // FIXME: save to disk
126 // FIXME: make persistent
127 // FIXME: call progress callback
128 // FIXME: trigger next block (if applicable)
133 * Type of a function to call when we receive a message
137 * @param msg message received, NULL on timeout or fatal error
140 receive_results (void *cls,
141 const struct GNUNET_MessageHeader * msg)
143 struct GNUNET_FS_DownloadContext *dc = cls;
144 const struct ContentMessage *cm;
147 if ( (NULL == msg) ||
148 (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_CONTENT) ||
149 (ntohs (msg->size) <= sizeof (struct ContentMessage)) )
154 msize = ntohs (msg->size);
155 cm = (const struct ContentMessage*) msg;
159 msize - sizeof (struct ContentMessage));
160 /* continue receiving */
161 GNUNET_CLIENT_receive (dc->client,
164 GNUNET_TIME_UNIT_FOREVER_REL);
170 * We're ready to transmit a search request to the
171 * file-sharing service. Do it. If there is
172 * more than one request pending, try to send
173 * multiple or request another transmission.
176 * @param size number of bytes available in buf
177 * @param buf where the callee should write the message
178 * @return number of bytes written to buf
181 transmit_download_request (void *cls,
185 struct GNUNET_FS_DownloadContext *dc = cls;
187 struct SearchMessage *sm;
194 GNUNET_assert (size >= sizeof (struct SearchMessage));
197 while ( (dc->pending == NULL) &&
198 (size > msize + sizeof (struct SearchMessage)) )
200 memset (sm, 0, sizeof (struct SearchMessage));
201 sm->header.size = htons (sizeof (struct SearchMessage));
202 sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
203 sm->anonymity_level = htonl (dc->anonymity);
204 sm->target = dc->target.hashPubKey;
205 sm->query = dc->pending->chk.query;
206 dc->pending->is_pending = GNUNET_NO;
207 dc->pending = dc->pending->next;
208 msize += sizeof (struct SearchMessage);
216 * Reconnect to the FS service and transmit
219 * @param cls our download context
223 do_reconnect (void *cls,
224 const struct GNUNET_SCHEDULER_TaskContext *tc)
226 struct GNUNET_FS_DownloadContext *dc = cls;
227 struct GNUNET_CLIENT_Connection *client;
229 dc->task = GNUNET_SCHEDULER_NO_TASK;
230 client = GNUNET_CLIENT_connect (dc->h->sched,
239 GNUNET_CLIENT_notify_transmit_ready (client,
240 sizeof (struct SearchMessage),
241 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
242 &transmit_download_request,
244 GNUNET_CLIENT_receive (client,
247 GNUNET_TIME_UNIT_FOREVER_REL);
252 * Add entries that are not yet pending back to
255 * @param cls our download context
257 * @param entry entry of type "struct DownloadRequest"
261 retry_entry (void *cls,
262 const GNUNET_HashCode *key,
265 struct GNUNET_FS_DownloadContext *dc = cls;
266 struct DownloadRequest *dr = entry;
268 if (! dr->is_pending)
270 dr->next = dc->pending;
271 dr->is_pending = GNUNET_YES;
279 * We've lost our connection with the FS service.
280 * Re-establish it and re-transmit all of our
283 * @param dc download context that is having trouble
286 try_reconnect (struct GNUNET_FS_DownloadContext *dc)
289 if (NULL != dc->client)
291 GNUNET_CONTAINER_multihashmap_iterate (dc->active,
294 GNUNET_CLIENT_disconnect (dc->client);
298 = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
300 GNUNET_SCHEDULER_PRIORITY_IDLE,
301 GNUNET_SCHEDULER_NO_TASK,
302 GNUNET_TIME_UNIT_SECONDS,
309 * Download parts of a file. Note that this will store
310 * the blocks at the respective offset in the given file. Also, the
311 * download is still using the blocking of the underlying FS
312 * encoding. As a result, the download may *write* outside of the
313 * given boundaries (if offset and length do not match the 32k FS
314 * block boundaries). <p>
316 * This function should be used to focus a download towards a
317 * particular portion of the file (optimization), not to strictly
318 * limit the download to exactly those bytes.
320 * @param h handle to the file sharing subsystem
321 * @param uri the URI of the file (determines what to download); CHK or LOC URI
322 * @param filename where to store the file, maybe NULL (then no file is
323 * created on disk and data must be grabbed from the callbacks)
324 * @param offset at what offset should we start the download (typically 0)
325 * @param length how many bytes should be downloaded starting at offset
326 * @param anonymity anonymity level to use for the download
327 * @param options various options
328 * @param parent parent download to associate this download with (use NULL
329 * for top-level downloads; useful for manually-triggered recursive downloads)
330 * @return context that can be used to control this download
332 struct GNUNET_FS_DownloadContext *
333 GNUNET_FS_file_download_start (struct GNUNET_FS_Handle *h,
334 const struct GNUNET_FS_Uri *uri,
335 const char *filename,
339 enum GNUNET_FS_DownloadOptions options,
340 struct GNUNET_FS_DownloadContext *parent)
342 struct GNUNET_FS_DownloadContext *dc;
343 struct GNUNET_CLIENT_Connection *client;
345 client = GNUNET_CLIENT_connect (h->sched,
350 // FIXME: add support for "loc" URIs!
351 GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
352 if ( (dc->offset + dc->length < dc->offset) ||
353 (dc->offset + dc->length > uri->data.chk.file_length) )
358 dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
362 dc->uri = GNUNET_FS_uri_dup (uri);
363 if (NULL != filename)
365 dc->filename = GNUNET_strdup (filename);
366 dc->handle = GNUNET_DISK_file_open (filename,
367 GNUNET_DISK_OPEN_READWRITE |
368 GNUNET_DISK_OPEN_CREATE,
369 GNUNET_DISK_PERM_USER_READ |
370 GNUNET_DISK_PERM_USER_WRITE |
371 GNUNET_DISK_PERM_GROUP_READ |
372 GNUNET_DISK_PERM_OTHER_READ);
373 if (dc->handle == NULL)
375 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
376 _("Download failed: could not open file `%s': %s\n"),
379 GNUNET_FS_uri_destroy (dc->uri);
380 GNUNET_free (dc->filename);
381 GNUNET_CLIENT_disconnect (dc->client);
386 // FIXME: set "dc->target" for LOC uris!
389 dc->anonymity = anonymity;
390 dc->options = options;
391 dc->active = GNUNET_CONTAINER_multihashmap_create (1 + (length / DBLOCK_SIZE));
392 dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
393 // FIXME: make persistent
394 schedule_block_download (dc,
395 &dc->uri->data.chk.chk,
398 GNUNET_CLIENT_notify_transmit_ready (client,
399 sizeof (struct SearchMessage),
400 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
401 &transmit_download_request,
403 GNUNET_CLIENT_receive (client,
406 GNUNET_TIME_UNIT_FOREVER_REL);
407 // FIXME: signal download start
413 * Free entries in the map.
415 * @param cls unused (NULL)
417 * @param entry entry of type "struct DownloadRequest" which is freed
421 free_entry (void *cls,
422 const GNUNET_HashCode *key,
431 * Stop a download (aborts if download is incomplete).
433 * @param dc handle for the download
434 * @param do_delete delete files of incomplete downloads
437 GNUNET_FS_file_download_stop (struct GNUNET_FS_DownloadContext *dc,
440 // FIXME: make unpersistent
441 // FIXME: signal download end
443 if (GNUNET_SCHEDULER_NO_TASK != dc->task)
444 GNUNET_SCHEDULER_cancel (dc->h->sched,
446 if (NULL != dc->client)
447 GNUNET_CLIENT_disconnect (dc->client);
448 GNUNET_CONTAINER_multihashmap_iterate (dc->active,
451 GNUNET_CONTAINER_multihashmap_destroy (dc->active);
452 if (dc->filename != NULL)
454 GNUNET_DISK_file_close (dc->handle);
455 if ( (dc->completed != dc->length) &&
456 (GNUNET_YES == do_delete) )
458 if (0 != UNLINK (dc->filename))
459 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
463 GNUNET_free (dc->filename);
465 GNUNET_FS_uri_destroy (dc->uri);
489 * Compute how many bytes of data are stored in
493 get_node_size (const struct Node *node)
497 unsigned long long rsize;
498 unsigned long long spos;
499 unsigned long long epos;
501 GNUNET_GE_ASSERT (node->ctx->ectx, node->offset < node->ctx->total);
502 if (node->level == 0)
504 ret = GNUNET_ECRS_DBLOCK_SIZE;
505 if (node->offset + (unsigned long long) ret > node->ctx->total)
506 ret = (unsigned int) (node->ctx->total - node->offset);
508 GNUNET_GE_LOG (node->ctx->rm->ectx,
509 GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
510 "Node at offset %llu and level %d has size %u\n",
511 node->offset, node->level, ret);
515 rsize = GNUNET_ECRS_DBLOCK_SIZE;
516 for (i = 0; i < node->level - 1; i++)
517 rsize *= GNUNET_ECRS_CHK_PER_INODE;
518 spos = rsize * (node->offset / sizeof (GNUNET_EC_ContentHashKey));
519 epos = spos + rsize * GNUNET_ECRS_CHK_PER_INODE;
520 if (epos > node->ctx->total)
521 epos = node->ctx->total;
522 ret = (epos - spos) / rsize;
523 if (ret * rsize < epos - spos)
524 ret++; /* need to round up! */
526 GNUNET_GE_LOG (node->ctx->rm->ectx,
527 GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
528 "Node at offset %llu and level %d has size %u\n",
529 node->offset, node->level,
530 ret * sizeof (GNUNET_EC_ContentHashKey));
532 return ret * sizeof (GNUNET_EC_ContentHashKey);
536 * Check if self block is already present on the drive. If the block
537 * is a dblock and present, the ProgressModel is notified. If the
538 * block is present and it is an iblock, downloading the children is
541 * Also checks if the block is within the range of blocks
542 * that we are supposed to download. If not, the method
543 * returns as if the block is present but does NOT signal
546 * @param node that is checked for presence
547 * @return GNUNET_YES if present, GNUNET_NO if not.
550 check_node_present (const struct Node *node)
558 size = get_node_size (node);
559 /* first check if node is within range.
560 For now, keeping it simple, we only do
561 this for level-0 nodes */
562 if ((node->level == 0) &&
563 ((node->offset + size < node->ctx->offset) ||
564 (node->offset >= node->ctx->offset + node->ctx->length)))
566 data = GNUNET_malloc (size);
568 res = read_from_files (node->ctx, node->level, node->offset, data, size);
571 GNUNET_hash (data, size, &hc);
572 if (0 == memcmp (&hc, &node->chk.key, sizeof (GNUNET_HashCode)))
574 notify_client_about_progress (node, data, size);
576 iblock_download_children (node, data, size);
585 * DOWNLOAD children of this GNUNET_EC_IBlock.
587 * @param node the node that should be downloaded
590 iblock_download_children (const struct Node *node,
591 const char *data, unsigned int size)
593 struct GNUNET_GE_Context *ectx = node->ctx->ectx;
596 unsigned int childcount;
597 const GNUNET_EC_ContentHashKey *chks;
598 unsigned int levelSize;
599 unsigned long long baseOffset;
601 GNUNET_GE_ASSERT (ectx, node->level > 0);
602 childcount = size / sizeof (GNUNET_EC_ContentHashKey);
603 if (size != childcount * sizeof (GNUNET_EC_ContentHashKey))
605 GNUNET_GE_BREAK (ectx, 0);
608 if (node->level == 1)
610 levelSize = GNUNET_ECRS_DBLOCK_SIZE;
612 node->offset / sizeof (GNUNET_EC_ContentHashKey) *
613 GNUNET_ECRS_DBLOCK_SIZE;
618 sizeof (GNUNET_EC_ContentHashKey) * GNUNET_ECRS_CHK_PER_INODE;
619 baseOffset = node->offset * GNUNET_ECRS_CHK_PER_INODE;
621 chks = (const GNUNET_EC_ContentHashKey *) data;
622 for (i = 0; i < childcount; i++)
624 child = GNUNET_malloc (sizeof (struct Node));
625 child->ctx = node->ctx;
626 child->chk = chks[i];
627 child->offset = baseOffset + i * levelSize;
628 GNUNET_GE_ASSERT (ectx, child->offset < node->ctx->total);
629 child->level = node->level - 1;
630 GNUNET_GE_ASSERT (ectx, (child->level != 0) ||
631 ((child->offset % GNUNET_ECRS_DBLOCK_SIZE) == 0));
632 if (GNUNET_NO == check_node_present (child))
635 GNUNET_free (child); /* done already! */
642 /* end of fs_download.c */