diff options
Diffstat (limited to 'src/fs/fs_tree.c')
-rw-r--r-- | src/fs/fs_tree.c | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/src/fs/fs_tree.c b/src/fs/fs_tree.c new file mode 100644 index 0000000..b3bbdc7 --- /dev/null +++ b/src/fs/fs_tree.c @@ -0,0 +1,441 @@ +/* + This file is part of GNUnet. + (C) 2009-2011 Christian Grothoff (and other contributing authors) + + 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 3, or (at your + option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ +/** + * @file fs/fs_tree.c + * @brief Merkle-tree-ish-CHK file encoding for GNUnet + * @see http://gnunet.org/encoding.php3 + * @author Krista Bennett + * @author Christian Grothoff + */ +#include "platform.h" +#include "fs_tree.h" + + +/** + * Context for an ECRS-based file encoder that computes + * the Merkle-ish-CHK tree. + */ +struct GNUNET_FS_TreeEncoder +{ + + /** + * Global FS context. + */ + struct GNUNET_FS_Handle *h; + + /** + * Closure for all callbacks. + */ + void *cls; + + /** + * Function to call on encrypted blocks. + */ + GNUNET_FS_TreeBlockProcessor proc; + + /** + * Function to call with progress information. + */ + GNUNET_FS_TreeProgressCallback progress; + + /** + * Function to call to receive input data. + */ + GNUNET_FS_DataReader reader; + + /** + * Function to call once we're done with processing. + */ + GNUNET_SCHEDULER_Task cont; + + /** + * Set to an error message (if we had an error). + */ + char *emsg; + + /** + * Set to the URI (upon successful completion) + */ + struct GNUNET_FS_Uri *uri; + + /** + * Overall file size. + */ + uint64_t size; + + /** + * How far are we? + */ + uint64_t publish_offset; + + /** + * How deep are we? Depth 0 is for the DBLOCKs. + */ + unsigned int current_depth; + + /** + * How deep is the tree? Always > 0. + */ + unsigned int chk_tree_depth; + + /** + * In-memory cache of the current CHK tree. + * This struct will contain the CHK values + * from the root to the currently processed + * node in the tree as identified by + * "current_depth" and "publish_offset". + * The "chktree" will be initially NULL, + * then allocated to a sufficient number of + * entries for the size of the file and + * finally freed once the upload is complete. + */ + struct ContentHashKey *chk_tree; + + /** + * Are we currently in 'GNUNET_FS_tree_encoder_next'? + * Flag used to prevent recursion. + */ + int in_next; +}; + + +/** + * Compute the depth of the CHK tree. + * + * @param flen file length for which to compute the depth + * @return depth of the tree, always > 0. A depth of 1 means only a DBLOCK. + */ +unsigned int +GNUNET_FS_compute_depth (uint64_t flen) +{ + unsigned int treeDepth; + uint64_t fl; + + treeDepth = 1; + fl = DBLOCK_SIZE; + while (fl < flen) + { + treeDepth++; + if (fl * CHK_PER_INODE < fl) + { + /* integer overflow, this is a HUGE file... */ + return treeDepth; + } + fl = fl * CHK_PER_INODE; + } + return treeDepth; +} + + +/** + * Calculate how many bytes of payload a block tree of the given + * depth MAY correspond to at most (this function ignores the fact that + * some blocks will only be present partially due to the total file + * size cutting some blocks off at the end). + * + * @param depth depth of the block. depth==0 is a DBLOCK. + * @return number of bytes of payload a subtree of this depth may correspond to + */ +uint64_t +GNUNET_FS_tree_compute_tree_size (unsigned int depth) +{ + uint64_t rsize; + unsigned int i; + + rsize = DBLOCK_SIZE; + for (i = 0; i < depth; i++) + rsize *= CHK_PER_INODE; + return rsize; +} + + +/** + * Compute the size of the current IBLOCK. The encoder is + * triggering the calculation of the size of an IBLOCK at the + * *end* (hence end_offset) of its construction. The IBLOCK + * maybe a full or a partial IBLOCK, and this function is to + * calculate how long it should be. + * + * @param depth depth of the IBlock in the tree, 0 would be a DBLOCK, + * must be > 0 (this function is for IBLOCKs only!) + * @param end_offset current offset in the payload (!) of the overall file, + * must be > 0 (since this function is called at the + * end of a block). + * @return size of the corresponding IBlock + */ +static uint16_t +GNUNET_FS_tree_compute_iblock_size (unsigned int depth, uint64_t end_offset) +{ + unsigned int ret; + uint64_t mod; + uint64_t bds; + + GNUNET_assert (depth > 0); + GNUNET_assert (end_offset > 0); + bds = GNUNET_FS_tree_compute_tree_size (depth); + mod = end_offset % bds; + if (0 == mod) + { + /* we were triggered at the end of a full block */ + ret = CHK_PER_INODE; + } + else + { + /* we were triggered at the end of the file */ + bds /= CHK_PER_INODE; + ret = mod / bds; + if (0 != mod % bds) + ret++; + } + return (uint16_t) (ret * sizeof (struct ContentHashKey)); +} + + +/** + * Compute how many bytes of data should be stored in + * the specified block. + * + * @param fsize overall file size, must be > 0. + * @param offset offset in the original data corresponding + * to the beginning of the tree induced by the block; + * must be <= fsize + * @param depth depth of the node in the tree, 0 for DBLOCK + * @return number of bytes stored in this node + */ +size_t +GNUNET_FS_tree_calculate_block_size (uint64_t fsize, uint64_t offset, + unsigned int depth) +{ + size_t ret; + uint64_t rsize; + uint64_t epos; + unsigned int chks; + + GNUNET_assert (fsize > 0); + GNUNET_assert (offset <= fsize); + if (depth == 0) + { + ret = DBLOCK_SIZE; + if ((offset + ret > fsize) || (offset + ret < offset)) + ret = (size_t) (fsize - offset); + return ret; + } + + rsize = GNUNET_FS_tree_compute_tree_size (depth - 1); + epos = offset + rsize * CHK_PER_INODE; + if ((epos < offset) || (epos > fsize)) + epos = fsize; + /* round up when computing #CHKs in our IBlock */ + chks = (epos - offset + rsize - 1) / rsize; + GNUNET_assert (chks <= CHK_PER_INODE); + return chks * sizeof (struct ContentHashKey); +} + + +/** + * Initialize a tree encoder. This function will call "proc" and + * "progress" on each block in the tree. Once all blocks have been + * processed, "cont" will be scheduled. The "reader" will be called + * to obtain the (plaintext) blocks for the file. Note that this + * function will not actually call "proc". The client must + * call "GNUNET_FS_tree_encoder_next" to trigger encryption (and + * calling of "proc") for the each block. + * + * @param h the global FS context + * @param size overall size of the file to encode + * @param cls closure for reader, proc, progress and cont + * @param reader function to call to read plaintext data + * @param proc function to call on each encrypted block + * @param progress function to call with progress information + * @param cont function to call when done + */ +struct GNUNET_FS_TreeEncoder * +GNUNET_FS_tree_encoder_create (struct GNUNET_FS_Handle *h, uint64_t size, + void *cls, GNUNET_FS_DataReader reader, + GNUNET_FS_TreeBlockProcessor proc, + GNUNET_FS_TreeProgressCallback progress, + GNUNET_SCHEDULER_Task cont) +{ + struct GNUNET_FS_TreeEncoder *te; + + te = GNUNET_malloc (sizeof (struct GNUNET_FS_TreeEncoder)); + te->h = h; + te->size = size; + te->cls = cls; + te->reader = reader; + te->proc = proc; + te->progress = progress; + te->cont = cont; + te->chk_tree_depth = GNUNET_FS_compute_depth (size); + te->chk_tree = + GNUNET_malloc (te->chk_tree_depth * CHK_PER_INODE * + sizeof (struct ContentHashKey)); + return te; +} + + +/** + * Compute the offset of the CHK for the + * current block in the IBlock above. + * + * @param depth depth of the IBlock in the tree (aka overall + * number of tree levels minus depth); 0 == DBlock + * @param end_offset current offset in the overall file, + * at the *beginning* of the block for DBLOCKs (depth==0), + * otherwise at the *end* of the block (exclusive) + * @return (array of CHKs') offset in the above IBlock + */ +static unsigned int +compute_chk_offset (unsigned int depth, uint64_t end_offset) +{ + uint64_t bds; + unsigned int ret; + + bds = GNUNET_FS_tree_compute_tree_size (depth); + if (depth > 0) + end_offset--; /* round down since for depth > 0 offset is at the END of the block */ + ret = end_offset / bds; + return ret % CHK_PER_INODE; +} + + +/** + * Encrypt the next block of the file (and call proc and progress + * accordingly; or of course "cont" if we have already completed + * encoding of the entire file). + * + * @param te tree encoder to use + */ +void +GNUNET_FS_tree_encoder_next (struct GNUNET_FS_TreeEncoder *te) +{ + struct ContentHashKey *mychk; + const void *pt_block; + uint16_t pt_size; + char iob[DBLOCK_SIZE]; + char enc[DBLOCK_SIZE]; + struct GNUNET_CRYPTO_AesSessionKey sk; + struct GNUNET_CRYPTO_AesInitializationVector iv; + unsigned int off; + + GNUNET_assert (GNUNET_NO == te->in_next); + te->in_next = GNUNET_YES; + if (te->chk_tree_depth == te->current_depth) + { + off = CHK_PER_INODE * (te->chk_tree_depth - 1); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "TE done, reading CHK `%s' from %u\n", + GNUNET_h2s (&te->chk_tree[off].query), off); + te->uri = GNUNET_malloc (sizeof (struct GNUNET_FS_Uri)); + te->uri->type = chk; + te->uri->data.chk.chk = te->chk_tree[off]; + te->uri->data.chk.file_length = GNUNET_htonll (te->size); + te->in_next = GNUNET_NO; + te->cont (te->cls, NULL); + return; + } + if (0 == te->current_depth) + { + /* read DBLOCK */ + pt_size = GNUNET_MIN (DBLOCK_SIZE, te->size - te->publish_offset); + if (pt_size != + te->reader (te->cls, te->publish_offset, pt_size, iob, &te->emsg)) + { + te->cont (te->cls, NULL); + te->in_next = GNUNET_NO; + return; + } + pt_block = iob; + } + else + { + pt_size = + GNUNET_FS_tree_compute_iblock_size (te->current_depth, + te->publish_offset); + pt_block = &te->chk_tree[(te->current_depth - 1) * CHK_PER_INODE]; + } + off = compute_chk_offset (te->current_depth, te->publish_offset); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "TE is at offset %llu and depth %u with block size %u and target-CHK-offset %u\n", + (unsigned long long) te->publish_offset, te->current_depth, + (unsigned int) pt_size, (unsigned int) off); + mychk = &te->chk_tree[te->current_depth * CHK_PER_INODE + off]; + GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key); + GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv); + GNUNET_CRYPTO_aes_encrypt (pt_block, pt_size, &sk, &iv, enc); + GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "TE calculates query to be `%s', stored at %u\n", + GNUNET_h2s (&mychk->query), + te->current_depth * CHK_PER_INODE + off); + if (NULL != te->proc) + te->proc (te->cls, mychk, te->publish_offset, te->current_depth, + (0 == + te->current_depth) ? GNUNET_BLOCK_TYPE_FS_DBLOCK : + GNUNET_BLOCK_TYPE_FS_IBLOCK, enc, pt_size); + if (NULL != te->progress) + te->progress (te->cls, te->publish_offset, pt_block, pt_size, + te->current_depth); + if (0 == te->current_depth) + { + te->publish_offset += pt_size; + if ((te->publish_offset == te->size) || + (0 == te->publish_offset % (CHK_PER_INODE * DBLOCK_SIZE))) + te->current_depth++; + } + else + { + if ((off == CHK_PER_INODE) || (te->publish_offset == te->size)) + te->current_depth++; + else + te->current_depth = 0; + } + te->in_next = GNUNET_NO; +} + + +/** + * Clean up a tree encoder and return information + * about the resulting URI or an error message. + * + * @param te the tree encoder to clean up + * @param uri set to the resulting URI (if encoding finished) + * @param emsg set to an error message (if an error occured + * within the tree encoder; if this function is called + * prior to completion and prior to an internal error, + * both "*uri" and "*emsg" will be set to NULL). + */ +void +GNUNET_FS_tree_encoder_finish (struct GNUNET_FS_TreeEncoder *te, + struct GNUNET_FS_Uri **uri, char **emsg) +{ + GNUNET_assert (GNUNET_NO == te->in_next); + if (uri != NULL) + *uri = te->uri; + else if (NULL != te->uri) + GNUNET_FS_uri_destroy (te->uri); + if (emsg != NULL) + *emsg = te->emsg; + else + GNUNET_free_non_null (te->emsg); + GNUNET_free (te->chk_tree); + GNUNET_free (te); +} + +/* end of fs_tree.c */ |