towards having download
[oweals/gnunet.git] / src / fs / fs_download.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  * @file fs/fs_download.c
22  * @brief download methods
23  * @author Christian Grothoff
24  *
25  * TODO:
26  * - process replies
27  * - callback signaling
28  * - check if blocks exist already 
29  * - location URI suppport (can wait)
30  * - persistence (can wait)
31  */
32 #include "platform.h"
33 #include "gnunet_constants.h"
34 #include "gnunet_fs_service.h"
35 #include "fs.h"
36
37 #define DEBUG_DOWNLOAD GNUNET_YES
38
39
40 /**
41  * Schedule the download of the specified
42  * block in the tree.
43  *
44  * @param dc overall download this block belongs to
45  * @param chk content-hash-key of the block
46  * @param offset offset of the block in the file
47  *         (for IBlocks, the offset is the lowest
48  *          offset of any DBlock in the subtree under
49  *          the IBlock)
50  * @param depth depth of the block, 0 is the root of the tree
51  */
52 static void
53 schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
54                          const struct ContentHashKey *chk,
55                          uint64_t offset,
56                          unsigned int depth)
57 {
58   struct DownloadRequest *sm;
59
60   // FIXME: check if block exists on disk!
61   sm = GNUNET_malloc (sizeof (struct DownloadRequest));
62   sm->chk = *chk;
63   sm->offset = offset;
64   sm->depth = depth;
65   sm->is_pending = GNUNET_YES;
66   sm->next = dc->pending;
67   dc->pending = sm;
68   GNUNET_CONTAINER_multihashmap_put (dc->active,
69                                      &chk->query,
70                                      sm,
71                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
72 }
73
74
75 /**
76  * We've lost our connection with the FS service.
77  * Re-establish it and re-transmit all of our
78  * pending requests.
79  *
80  * @param dc download context that is having trouble
81  */
82 static void
83 try_reconnect (struct GNUNET_FS_DownloadContext *dc);
84
85
86 /**
87  * Process a search result.
88  *
89  * @param sc our search context
90  * @param type type of the result
91  * @param data the (encrypted) response
92  * @param size size of data
93  */
94 static void
95 process_result (struct GNUNET_FS_DownloadContext *dc,
96                 uint32_t type,
97                 const void *data,
98                 size_t size)
99 {
100   GNUNET_HashCode query;
101   struct DownloadRequest *sm;
102   struct GNUNET_CRYPTO_AesSessionKey skey;
103   struct GNUNET_CRYPTO_AesInitializationVector iv;
104   char pt[size];
105
106   GNUNET_CRYPTO_hash (data, size, &query);
107   sm = GNUNET_CONTAINER_multihashmap_get (dc->active,
108                                           &query);
109   if (NULL == sm)
110     {
111       GNUNET_break (0);
112       return;
113     }
114   GNUNET_assert (GNUNET_YES ==
115                  GNUNET_CONTAINER_multihashmap_remove (dc->active,
116                                                        &query,
117                                                        sm));
118   GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
119   GNUNET_CRYPTO_aes_decrypt (data,
120                              size,
121                              &skey,
122                              &iv,
123                              pt);
124   // FIXME: save to disk
125   // FIXME: make persistent
126   // FIXME: call progress callback
127   // FIXME: trigger next block (if applicable)  
128 }
129
130
131 /**
132  * Type of a function to call when we receive a message
133  * from the service.
134  *
135  * @param cls closure
136  * @param msg message received, NULL on timeout or fatal error
137  */
138 static void 
139 receive_results (void *cls,
140                  const struct GNUNET_MessageHeader * msg)
141 {
142   struct GNUNET_FS_DownloadContext *dc = cls;
143   const struct ContentMessage *cm;
144   uint16_t msize;
145
146   if ( (NULL == msg) ||
147        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_CONTENT) ||
148        (ntohs (msg->size) <= sizeof (struct ContentMessage)) )
149     {
150       try_reconnect (dc);
151       return;
152     }
153   msize = ntohs (msg->size);
154   cm = (const struct ContentMessage*) msg;
155   process_result (dc, 
156                   ntohl (cm->type),
157                   &cm[1],
158                   msize - sizeof (struct ContentMessage));
159   /* continue receiving */
160   GNUNET_CLIENT_receive (dc->client,
161                          &receive_results,
162                          dc,
163                          GNUNET_TIME_UNIT_FOREVER_REL);
164 }
165
166
167
168 /**
169  * We're ready to transmit a search request to the
170  * file-sharing service.  Do it.  If there is 
171  * more than one request pending, try to send 
172  * multiple or request another transmission.
173  *
174  * @param cls closure
175  * @param size number of bytes available in buf
176  * @param buf where the callee should write the message
177  * @return number of bytes written to buf
178  */
179 static size_t
180 transmit_download_request (void *cls,
181                            size_t size, 
182                            void *buf)
183 {
184   struct GNUNET_FS_DownloadContext *dc = cls;
185   size_t msize;
186   struct SearchMessage *sm;
187
188   if (NULL == buf)
189     {
190       try_reconnect (dc);
191       return 0;
192     }
193   GNUNET_assert (size >= sizeof (struct SearchMessage));
194   msize = 0;
195   sm = buf;
196   while ( (dc->pending == NULL) &&
197           (size > msize + sizeof (struct SearchMessage)) )
198     {
199       memset (sm, 0, sizeof (struct SearchMessage));
200       sm->header.size = htons (sizeof (struct SearchMessage));
201       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
202       sm->anonymity_level = htonl (dc->anonymity);
203       sm->target = dc->target.hashPubKey;
204       sm->query = dc->pending->chk.query;
205       dc->pending->is_pending = GNUNET_NO;
206       dc->pending = dc->pending->next;
207       msize += sizeof (struct SearchMessage);
208       sm++;
209     }
210   return msize;
211 }
212
213
214 /**
215  * Reconnect to the FS service and transmit
216  * our queries NOW.
217  *
218  * @param cls our download context
219  * @param tc unused
220  */
221 static void
222 do_reconnect (void *cls,
223               const struct GNUNET_SCHEDULER_TaskContext *tc)
224 {
225   struct GNUNET_FS_DownloadContext *dc = cls;
226   struct GNUNET_CLIENT_Connection *client;
227   
228   dc->task = GNUNET_SCHEDULER_NO_TASK;
229   client = GNUNET_CLIENT_connect (dc->h->sched,
230                                   "fs",
231                                   dc->h->cfg);
232   if (NULL == client)
233     {
234       try_reconnect (dc);
235       return;
236     }
237   dc->client = client;
238   GNUNET_CLIENT_notify_transmit_ready (client,
239                                        sizeof (struct SearchMessage),
240                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
241                                        &transmit_download_request,
242                                        dc);  
243   GNUNET_CLIENT_receive (client,
244                          &receive_results,
245                          dc,
246                          GNUNET_TIME_UNIT_FOREVER_REL);
247 }
248
249
250 /**
251  * Add entries that are not yet pending back to
252  * the pending list.
253  *
254  * @param cls our download context
255  * @param key unused
256  * @param entry entry of type "struct DownloadRequest"
257  * @return GNUNET_OK
258  */
259 static int
260 retry_entry (void *cls,
261              const GNUNET_HashCode *key,
262              void *entry)
263 {
264   struct GNUNET_FS_DownloadContext *dc = cls;
265   struct DownloadRequest *dr = entry;
266
267   if (! dr->is_pending)
268     {
269       dr->next = dc->pending;
270       dr->is_pending = GNUNET_YES;
271       dc->pending = entry;
272     }
273   return GNUNET_OK;
274 }
275
276
277 /**
278  * We've lost our connection with the FS service.
279  * Re-establish it and re-transmit all of our
280  * pending requests.
281  *
282  * @param dc download context that is having trouble
283  */
284 static void
285 try_reconnect (struct GNUNET_FS_DownloadContext *dc)
286 {
287   
288   if (NULL != dc->client)
289     {
290       GNUNET_CONTAINER_multihashmap_iterate (dc->active,
291                                              &retry_entry,
292                                              dc);
293       GNUNET_CLIENT_disconnect (dc->client);
294       dc->client = NULL;
295     }
296   dc->task
297     = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
298                                     GNUNET_NO,
299                                     GNUNET_SCHEDULER_PRIORITY_IDLE,
300                                     GNUNET_SCHEDULER_NO_TASK,
301                                     GNUNET_TIME_UNIT_SECONDS,
302                                     &do_reconnect,
303                                     dc);
304 }
305
306
307 /**
308  * Download parts of a file.  Note that this will store
309  * the blocks at the respective offset in the given file.  Also, the
310  * download is still using the blocking of the underlying FS
311  * encoding.  As a result, the download may *write* outside of the
312  * given boundaries (if offset and length do not match the 32k FS
313  * block boundaries). <p>
314  *
315  * This function should be used to focus a download towards a
316  * particular portion of the file (optimization), not to strictly
317  * limit the download to exactly those bytes.
318  *
319  * @param h handle to the file sharing subsystem
320  * @param uri the URI of the file (determines what to download); CHK or LOC URI
321  * @param filename where to store the file, maybe NULL (then no file is
322  *        created on disk and data must be grabbed from the callbacks)
323  * @param offset at what offset should we start the download (typically 0)
324  * @param length how many bytes should be downloaded starting at offset
325  * @param anonymity anonymity level to use for the download
326  * @param options various options
327  * @param parent parent download to associate this download with (use NULL
328  *        for top-level downloads; useful for manually-triggered recursive downloads)
329  * @return context that can be used to control this download
330  */
331 struct GNUNET_FS_DownloadContext *
332 GNUNET_FS_file_download_start (struct GNUNET_FS_Handle *h,
333                                const struct GNUNET_FS_Uri *uri,
334                                const char *filename,
335                                uint64_t offset,
336                                uint64_t length,
337                                uint32_t anonymity,
338                                enum GNUNET_FS_DownloadOptions options,
339                                struct GNUNET_FS_DownloadContext *parent)
340 {
341   struct GNUNET_FS_DownloadContext *dc;
342   struct GNUNET_CLIENT_Connection *client;
343
344   client = GNUNET_CLIENT_connect (h->sched,
345                                   "fs",
346                                   h->cfg);
347   if (NULL == client)
348     return NULL;
349   // FIXME: add support for "loc" URIs!
350   GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
351   if ( (dc->offset + dc->length < dc->offset) ||
352        (dc->offset + dc->length > uri->data.chk.file_length) )
353     {
354       GNUNET_break (0);
355       return NULL;
356     }
357   dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
358   dc->h = h;
359   dc->client = client;
360   dc->parent = parent;
361   dc->uri = GNUNET_FS_uri_dup (uri);
362   if (NULL != filename)
363     {
364       dc->filename = GNUNET_strdup (filename);
365       dc->handle = GNUNET_DISK_file_open (filename, 
366                                           GNUNET_DISK_OPEN_READWRITE | 
367                                           GNUNET_DISK_OPEN_CREATE,
368                                           GNUNET_DISK_PERM_USER_READ |
369                                           GNUNET_DISK_PERM_USER_WRITE |
370                                           GNUNET_DISK_PERM_GROUP_READ |
371                                           GNUNET_DISK_PERM_OTHER_READ);
372       if (dc->handle == NULL)
373         {
374           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
375                       _("Download failed: could not open file `%s': %s\n"),
376                       dc->filename,
377                       STRERROR (errno));
378           GNUNET_FS_uri_destroy (dc->uri);
379           GNUNET_free (dc->filename);
380           GNUNET_CLIENT_disconnect (dc->client);
381           GNUNET_free (dc);
382           return NULL;
383         }
384     }
385   // FIXME: set "dc->target" for LOC uris!
386   dc->offset = offset;
387   dc->length = length;
388   dc->anonymity = anonymity;
389   dc->options = options;
390   dc->active = GNUNET_CONTAINER_multihashmap_create (1 + (length / DBLOCK_SIZE));
391   // FIXME: calculate tree depth
392   // FIXME: make persistent
393   schedule_block_download (dc, 
394                            &dc->uri->data.chk.chk,
395                            0, 
396                            0);
397   GNUNET_CLIENT_notify_transmit_ready (client,
398                                        sizeof (struct SearchMessage),
399                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
400                                        &transmit_download_request,
401                                        dc);  
402   GNUNET_CLIENT_receive (client,
403                          &receive_results,
404                          dc,
405                          GNUNET_TIME_UNIT_FOREVER_REL);
406   // FIXME: signal download start
407   return dc;
408 }
409
410
411 /**
412  * Free entries in the map.
413  *
414  * @param cls unused (NULL)
415  * @param key unused
416  * @param entry entry of type "struct DownloadRequest" which is freed
417  * @return GNUNET_OK
418  */
419 static int
420 free_entry (void *cls,
421             const GNUNET_HashCode *key,
422             void *entry)
423 {
424   GNUNET_free (entry);
425   return GNUNET_OK;
426 }
427
428
429 /**
430  * Stop a download (aborts if download is incomplete).
431  *
432  * @param dc handle for the download
433  * @param do_delete delete files of incomplete downloads
434  */
435 void
436 GNUNET_FS_file_download_stop (struct GNUNET_FS_DownloadContext *dc,
437                               int do_delete)
438 {
439   // FIXME: make unpersistent
440   // FIXME: signal download end
441   
442   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
443     GNUNET_SCHEDULER_cancel (dc->h->sched,
444                              dc->task);
445   if (NULL != dc->client)
446     GNUNET_CLIENT_disconnect (dc->client);
447   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
448                                          &free_entry,
449                                          NULL);
450   GNUNET_CONTAINER_multihashmap_destroy (dc->active);
451   if (dc->filename != NULL)
452     {
453       GNUNET_DISK_file_close (dc->handle);
454       if ( (dc->completed != dc->length) &&
455            (GNUNET_YES == do_delete) )
456         {
457           if (0 != UNLINK (dc->filename))
458             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
459                                       "unlink",
460                                       dc->filename);
461         }
462       GNUNET_free (dc->filename);
463     }
464   GNUNET_FS_uri_destroy (dc->uri);
465   GNUNET_free (dc);
466 }
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485 #if 0
486
487 /**
488  * Compute how many bytes of data are stored in
489  * this node.
490  */
491 static unsigned int
492 get_node_size (const struct Node *node)
493 {
494   unsigned int i;
495   unsigned int ret;
496   unsigned long long rsize;
497   unsigned long long spos;
498   unsigned long long epos;
499
500   GNUNET_GE_ASSERT (node->ctx->ectx, node->offset < node->ctx->total);
501   if (node->level == 0)
502     {
503       ret = GNUNET_ECRS_DBLOCK_SIZE;
504       if (node->offset + (unsigned long long) ret > node->ctx->total)
505         ret = (unsigned int) (node->ctx->total - node->offset);
506 #if DEBUG_DOWNLOAD
507       GNUNET_GE_LOG (node->ctx->rm->ectx,
508                      GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
509                      "Node at offset %llu and level %d has size %u\n",
510                      node->offset, node->level, ret);
511 #endif
512       return ret;
513     }
514   rsize = GNUNET_ECRS_DBLOCK_SIZE;
515   for (i = 0; i < node->level - 1; i++)
516     rsize *= GNUNET_ECRS_CHK_PER_INODE;
517   spos = rsize * (node->offset / sizeof (GNUNET_EC_ContentHashKey));
518   epos = spos + rsize * GNUNET_ECRS_CHK_PER_INODE;
519   if (epos > node->ctx->total)
520     epos = node->ctx->total;
521   ret = (epos - spos) / rsize;
522   if (ret * rsize < epos - spos)
523     ret++;                      /* need to round up! */
524 #if DEBUG_DOWNLOAD
525   GNUNET_GE_LOG (node->ctx->rm->ectx,
526                  GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
527                  "Node at offset %llu and level %d has size %u\n",
528                  node->offset, node->level,
529                  ret * sizeof (GNUNET_EC_ContentHashKey));
530 #endif
531   return ret * sizeof (GNUNET_EC_ContentHashKey);
532 }
533
534 /**
535  * Check if self block is already present on the drive.  If the block
536  * is a dblock and present, the ProgressModel is notified. If the
537  * block is present and it is an iblock, downloading the children is
538  * triggered.
539  *
540  * Also checks if the block is within the range of blocks
541  * that we are supposed to download.  If not, the method
542  * returns as if the block is present but does NOT signal
543  * progress.
544  *
545  * @param node that is checked for presence
546  * @return GNUNET_YES if present, GNUNET_NO if not.
547  */
548 static int
549 check_node_present (const struct Node *node)
550 {
551   int res;
552   int ret;
553   char *data;
554   unsigned int size;
555   GNUNET_HashCode hc;
556
557   size = get_node_size (node);
558   /* first check if node is within range.
559      For now, keeping it simple, we only do
560      this for level-0 nodes */
561   if ((node->level == 0) &&
562       ((node->offset + size < node->ctx->offset) ||
563        (node->offset >= node->ctx->offset + node->ctx->length)))
564     return GNUNET_YES;
565   data = GNUNET_malloc (size);
566   ret = GNUNET_NO;
567   res = read_from_files (node->ctx, node->level, node->offset, data, size);
568   if (res == size)
569     {
570       GNUNET_hash (data, size, &hc);
571       if (0 == memcmp (&hc, &node->chk.key, sizeof (GNUNET_HashCode)))
572         {
573           notify_client_about_progress (node, data, size);
574           if (node->level > 0)
575             iblock_download_children (node, data, size);
576           ret = GNUNET_YES;
577         }
578     }
579   GNUNET_free (data);
580   return ret;
581 }
582
583 /**
584  * DOWNLOAD children of this GNUNET_EC_IBlock.
585  *
586  * @param node the node that should be downloaded
587  */
588 static void
589 iblock_download_children (const struct Node *node,
590                           const char *data, unsigned int size)
591 {
592   struct GNUNET_GE_Context *ectx = node->ctx->ectx;
593   int i;
594   struct Node *child;
595   unsigned int childcount;
596   const GNUNET_EC_ContentHashKey *chks;
597   unsigned int levelSize;
598   unsigned long long baseOffset;
599
600   GNUNET_GE_ASSERT (ectx, node->level > 0);
601   childcount = size / sizeof (GNUNET_EC_ContentHashKey);
602   if (size != childcount * sizeof (GNUNET_EC_ContentHashKey))
603     {
604       GNUNET_GE_BREAK (ectx, 0);
605       return;
606     }
607   if (node->level == 1)
608     {
609       levelSize = GNUNET_ECRS_DBLOCK_SIZE;
610       baseOffset =
611         node->offset / sizeof (GNUNET_EC_ContentHashKey) *
612         GNUNET_ECRS_DBLOCK_SIZE;
613     }
614   else
615     {
616       levelSize =
617         sizeof (GNUNET_EC_ContentHashKey) * GNUNET_ECRS_CHK_PER_INODE;
618       baseOffset = node->offset * GNUNET_ECRS_CHK_PER_INODE;
619     }
620   chks = (const GNUNET_EC_ContentHashKey *) data;
621   for (i = 0; i < childcount; i++)
622     {
623       child = GNUNET_malloc (sizeof (struct Node));
624       child->ctx = node->ctx;
625       child->chk = chks[i];
626       child->offset = baseOffset + i * levelSize;
627       GNUNET_GE_ASSERT (ectx, child->offset < node->ctx->total);
628       child->level = node->level - 1;
629       GNUNET_GE_ASSERT (ectx, (child->level != 0) ||
630                         ((child->offset % GNUNET_ECRS_DBLOCK_SIZE) == 0));
631       if (GNUNET_NO == check_node_present (child))
632         add_request (child);
633       else
634         GNUNET_free (child);    /* done already! */
635     }
636 }
637
638 #endif
639
640
641 /* end of fs_download.c */