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 #include "fs_tree.h"
37
38 #define DEBUG_DOWNLOAD GNUNET_YES
39
40
41 /**
42  * Schedule the download of the specified
43  * block in the tree.
44  *
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
50  *          the IBlock)
51  * @param depth depth of the block, 0 is the root of the tree
52  */
53 static void
54 schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
55                          const struct ContentHashKey *chk,
56                          uint64_t offset,
57                          unsigned int depth)
58 {
59   struct DownloadRequest *sm;
60
61   // FIXME: check if block exists on disk!
62   sm = GNUNET_malloc (sizeof (struct DownloadRequest));
63   sm->chk = *chk;
64   sm->offset = offset;
65   sm->depth = depth;
66   sm->is_pending = GNUNET_YES;
67   sm->next = dc->pending;
68   dc->pending = sm;
69   GNUNET_CONTAINER_multihashmap_put (dc->active,
70                                      &chk->query,
71                                      sm,
72                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
73 }
74
75
76 /**
77  * We've lost our connection with the FS service.
78  * Re-establish it and re-transmit all of our
79  * pending requests.
80  *
81  * @param dc download context that is having trouble
82  */
83 static void
84 try_reconnect (struct GNUNET_FS_DownloadContext *dc);
85
86
87 /**
88  * Process a search result.
89  *
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
94  */
95 static void
96 process_result (struct GNUNET_FS_DownloadContext *dc,
97                 uint32_t type,
98                 const void *data,
99                 size_t size)
100 {
101   GNUNET_HashCode query;
102   struct DownloadRequest *sm;
103   struct GNUNET_CRYPTO_AesSessionKey skey;
104   struct GNUNET_CRYPTO_AesInitializationVector iv;
105   char pt[size];
106
107   GNUNET_CRYPTO_hash (data, size, &query);
108   sm = GNUNET_CONTAINER_multihashmap_get (dc->active,
109                                           &query);
110   if (NULL == sm)
111     {
112       GNUNET_break (0);
113       return;
114     }
115   GNUNET_assert (GNUNET_YES ==
116                  GNUNET_CONTAINER_multihashmap_remove (dc->active,
117                                                        &query,
118                                                        sm));
119   GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
120   GNUNET_CRYPTO_aes_decrypt (data,
121                              size,
122                              &skey,
123                              &iv,
124                              pt);
125   // FIXME: save to disk
126   // FIXME: make persistent
127   // FIXME: call progress callback
128   // FIXME: trigger next block (if applicable)  
129 }
130
131
132 /**
133  * Type of a function to call when we receive a message
134  * from the service.
135  *
136  * @param cls closure
137  * @param msg message received, NULL on timeout or fatal error
138  */
139 static void 
140 receive_results (void *cls,
141                  const struct GNUNET_MessageHeader * msg)
142 {
143   struct GNUNET_FS_DownloadContext *dc = cls;
144   const struct ContentMessage *cm;
145   uint16_t msize;
146
147   if ( (NULL == msg) ||
148        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_CONTENT) ||
149        (ntohs (msg->size) <= sizeof (struct ContentMessage)) )
150     {
151       try_reconnect (dc);
152       return;
153     }
154   msize = ntohs (msg->size);
155   cm = (const struct ContentMessage*) msg;
156   process_result (dc, 
157                   ntohl (cm->type),
158                   &cm[1],
159                   msize - sizeof (struct ContentMessage));
160   /* continue receiving */
161   GNUNET_CLIENT_receive (dc->client,
162                          &receive_results,
163                          dc,
164                          GNUNET_TIME_UNIT_FOREVER_REL);
165 }
166
167
168
169 /**
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.
174  *
175  * @param cls closure
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
179  */
180 static size_t
181 transmit_download_request (void *cls,
182                            size_t size, 
183                            void *buf)
184 {
185   struct GNUNET_FS_DownloadContext *dc = cls;
186   size_t msize;
187   struct SearchMessage *sm;
188
189   if (NULL == buf)
190     {
191       try_reconnect (dc);
192       return 0;
193     }
194   GNUNET_assert (size >= sizeof (struct SearchMessage));
195   msize = 0;
196   sm = buf;
197   while ( (dc->pending == NULL) &&
198           (size > msize + sizeof (struct SearchMessage)) )
199     {
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);
209       sm++;
210     }
211   return msize;
212 }
213
214
215 /**
216  * Reconnect to the FS service and transmit
217  * our queries NOW.
218  *
219  * @param cls our download context
220  * @param tc unused
221  */
222 static void
223 do_reconnect (void *cls,
224               const struct GNUNET_SCHEDULER_TaskContext *tc)
225 {
226   struct GNUNET_FS_DownloadContext *dc = cls;
227   struct GNUNET_CLIENT_Connection *client;
228   
229   dc->task = GNUNET_SCHEDULER_NO_TASK;
230   client = GNUNET_CLIENT_connect (dc->h->sched,
231                                   "fs",
232                                   dc->h->cfg);
233   if (NULL == client)
234     {
235       try_reconnect (dc);
236       return;
237     }
238   dc->client = client;
239   GNUNET_CLIENT_notify_transmit_ready (client,
240                                        sizeof (struct SearchMessage),
241                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
242                                        &transmit_download_request,
243                                        dc);  
244   GNUNET_CLIENT_receive (client,
245                          &receive_results,
246                          dc,
247                          GNUNET_TIME_UNIT_FOREVER_REL);
248 }
249
250
251 /**
252  * Add entries that are not yet pending back to
253  * the pending list.
254  *
255  * @param cls our download context
256  * @param key unused
257  * @param entry entry of type "struct DownloadRequest"
258  * @return GNUNET_OK
259  */
260 static int
261 retry_entry (void *cls,
262              const GNUNET_HashCode *key,
263              void *entry)
264 {
265   struct GNUNET_FS_DownloadContext *dc = cls;
266   struct DownloadRequest *dr = entry;
267
268   if (! dr->is_pending)
269     {
270       dr->next = dc->pending;
271       dr->is_pending = GNUNET_YES;
272       dc->pending = entry;
273     }
274   return GNUNET_OK;
275 }
276
277
278 /**
279  * We've lost our connection with the FS service.
280  * Re-establish it and re-transmit all of our
281  * pending requests.
282  *
283  * @param dc download context that is having trouble
284  */
285 static void
286 try_reconnect (struct GNUNET_FS_DownloadContext *dc)
287 {
288   
289   if (NULL != dc->client)
290     {
291       GNUNET_CONTAINER_multihashmap_iterate (dc->active,
292                                              &retry_entry,
293                                              dc);
294       GNUNET_CLIENT_disconnect (dc->client);
295       dc->client = NULL;
296     }
297   dc->task
298     = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
299                                     GNUNET_NO,
300                                     GNUNET_SCHEDULER_PRIORITY_IDLE,
301                                     GNUNET_SCHEDULER_NO_TASK,
302                                     GNUNET_TIME_UNIT_SECONDS,
303                                     &do_reconnect,
304                                     dc);
305 }
306
307
308 /**
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>
315  *
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.
319  *
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
331  */
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,
336                                uint64_t offset,
337                                uint64_t length,
338                                uint32_t anonymity,
339                                enum GNUNET_FS_DownloadOptions options,
340                                struct GNUNET_FS_DownloadContext *parent)
341 {
342   struct GNUNET_FS_DownloadContext *dc;
343   struct GNUNET_CLIENT_Connection *client;
344
345   client = GNUNET_CLIENT_connect (h->sched,
346                                   "fs",
347                                   h->cfg);
348   if (NULL == client)
349     return NULL;
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) )
354     {
355       GNUNET_break (0);
356       return NULL;
357     }
358   dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
359   dc->h = h;
360   dc->client = client;
361   dc->parent = parent;
362   dc->uri = GNUNET_FS_uri_dup (uri);
363   if (NULL != filename)
364     {
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)
374         {
375           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
376                       _("Download failed: could not open file `%s': %s\n"),
377                       dc->filename,
378                       STRERROR (errno));
379           GNUNET_FS_uri_destroy (dc->uri);
380           GNUNET_free (dc->filename);
381           GNUNET_CLIENT_disconnect (dc->client);
382           GNUNET_free (dc);
383           return NULL;
384         }
385     }
386   // FIXME: set "dc->target" for LOC uris!
387   dc->offset = offset;
388   dc->length = length;
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,
396                            0, 
397                            0);
398   GNUNET_CLIENT_notify_transmit_ready (client,
399                                        sizeof (struct SearchMessage),
400                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
401                                        &transmit_download_request,
402                                        dc);  
403   GNUNET_CLIENT_receive (client,
404                          &receive_results,
405                          dc,
406                          GNUNET_TIME_UNIT_FOREVER_REL);
407   // FIXME: signal download start
408   return dc;
409 }
410
411
412 /**
413  * Free entries in the map.
414  *
415  * @param cls unused (NULL)
416  * @param key unused
417  * @param entry entry of type "struct DownloadRequest" which is freed
418  * @return GNUNET_OK
419  */
420 static int
421 free_entry (void *cls,
422             const GNUNET_HashCode *key,
423             void *entry)
424 {
425   GNUNET_free (entry);
426   return GNUNET_OK;
427 }
428
429
430 /**
431  * Stop a download (aborts if download is incomplete).
432  *
433  * @param dc handle for the download
434  * @param do_delete delete files of incomplete downloads
435  */
436 void
437 GNUNET_FS_file_download_stop (struct GNUNET_FS_DownloadContext *dc,
438                               int do_delete)
439 {
440   // FIXME: make unpersistent
441   // FIXME: signal download end
442   
443   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
444     GNUNET_SCHEDULER_cancel (dc->h->sched,
445                              dc->task);
446   if (NULL != dc->client)
447     GNUNET_CLIENT_disconnect (dc->client);
448   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
449                                          &free_entry,
450                                          NULL);
451   GNUNET_CONTAINER_multihashmap_destroy (dc->active);
452   if (dc->filename != NULL)
453     {
454       GNUNET_DISK_file_close (dc->handle);
455       if ( (dc->completed != dc->length) &&
456            (GNUNET_YES == do_delete) )
457         {
458           if (0 != UNLINK (dc->filename))
459             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
460                                       "unlink",
461                                       dc->filename);
462         }
463       GNUNET_free (dc->filename);
464     }
465   GNUNET_FS_uri_destroy (dc->uri);
466   GNUNET_free (dc);
467 }
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486 #if 0
487
488 /**
489  * Compute how many bytes of data are stored in
490  * this node.
491  */
492 static unsigned int
493 get_node_size (const struct Node *node)
494 {
495   unsigned int i;
496   unsigned int ret;
497   unsigned long long rsize;
498   unsigned long long spos;
499   unsigned long long epos;
500
501   GNUNET_GE_ASSERT (node->ctx->ectx, node->offset < node->ctx->total);
502   if (node->level == 0)
503     {
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);
507 #if DEBUG_DOWNLOAD
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);
512 #endif
513       return ret;
514     }
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! */
525 #if DEBUG_DOWNLOAD
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));
531 #endif
532   return ret * sizeof (GNUNET_EC_ContentHashKey);
533 }
534
535 /**
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
539  * triggered.
540  *
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
544  * progress.
545  *
546  * @param node that is checked for presence
547  * @return GNUNET_YES if present, GNUNET_NO if not.
548  */
549 static int
550 check_node_present (const struct Node *node)
551 {
552   int res;
553   int ret;
554   char *data;
555   unsigned int size;
556   GNUNET_HashCode hc;
557
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)))
565     return GNUNET_YES;
566   data = GNUNET_malloc (size);
567   ret = GNUNET_NO;
568   res = read_from_files (node->ctx, node->level, node->offset, data, size);
569   if (res == size)
570     {
571       GNUNET_hash (data, size, &hc);
572       if (0 == memcmp (&hc, &node->chk.key, sizeof (GNUNET_HashCode)))
573         {
574           notify_client_about_progress (node, data, size);
575           if (node->level > 0)
576             iblock_download_children (node, data, size);
577           ret = GNUNET_YES;
578         }
579     }
580   GNUNET_free (data);
581   return ret;
582 }
583
584 /**
585  * DOWNLOAD children of this GNUNET_EC_IBlock.
586  *
587  * @param node the node that should be downloaded
588  */
589 static void
590 iblock_download_children (const struct Node *node,
591                           const char *data, unsigned int size)
592 {
593   struct GNUNET_GE_Context *ectx = node->ctx->ectx;
594   int i;
595   struct Node *child;
596   unsigned int childcount;
597   const GNUNET_EC_ContentHashKey *chks;
598   unsigned int levelSize;
599   unsigned long long baseOffset;
600
601   GNUNET_GE_ASSERT (ectx, node->level > 0);
602   childcount = size / sizeof (GNUNET_EC_ContentHashKey);
603   if (size != childcount * sizeof (GNUNET_EC_ContentHashKey))
604     {
605       GNUNET_GE_BREAK (ectx, 0);
606       return;
607     }
608   if (node->level == 1)
609     {
610       levelSize = GNUNET_ECRS_DBLOCK_SIZE;
611       baseOffset =
612         node->offset / sizeof (GNUNET_EC_ContentHashKey) *
613         GNUNET_ECRS_DBLOCK_SIZE;
614     }
615   else
616     {
617       levelSize =
618         sizeof (GNUNET_EC_ContentHashKey) * GNUNET_ECRS_CHK_PER_INODE;
619       baseOffset = node->offset * GNUNET_ECRS_CHK_PER_INODE;
620     }
621   chks = (const GNUNET_EC_ContentHashKey *) data;
622   for (i = 0; i < childcount; i++)
623     {
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))
633         add_request (child);
634       else
635         GNUNET_free (child);    /* done already! */
636     }
637 }
638
639 #endif
640
641
642 /* end of fs_download.c */