packages feed

TigerHash-0.2: c_lib/tigertree.c

/*
 * Copyright (C) 2010 Nikolay Orlyuk (virkony _at_ gmail _dot_ com)
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
    DC++ protocl utls for GHC
    Copyright (C) 2010 Nikolay Orlyuk (virkony _at_ gmail _dot_ com)

    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

#include <assert.h>
#include <string.h>

#include "tiger.h"
#include "tigertree.h"

typedef struct tigertree_stack_s {
    char buf[1+2*TIGER_HASH_SIZE]; /* first byte shows: 00 - empty, 01 - half */
    struct tigertree_stack_s *upper;
} tigertree_stack;

typedef struct tigertree_allocs_s {
    tigertree_stack data[6]; /* (19+6) => 32 GB, (19+6+6) => 2 TB, so I wouldn't expect more than 2 allocs */
    struct tigertree_allocs_s *prev;
} tigertree_allocs;

struct tigertree_context_s {
    size_t left;

    tiger_context *tiger;

    unsigned int level;
    tigertree_stack *bottom, *top;
    tigertree_allocs *allocs;

    tigertree_stack stack[19]; /* 19 => 512 MB, should be enough for many files */
};

const size_t tigertree_context_size() { return sizeof(struct tigertree_context_s); }


static void tigertree_format_alloc(tigertree_stack *data, const size_t cnt, tigertree_stack *top)
{
    size_t i;

    if (top != NULL) top->upper = data+0;

    /* format allocated levels by linking them each with other */
    for(i=1;i<cnt;++i) data[i-1].upper = data+i;
    data[i-1].upper = NULL; /* last entry require next alloc frame */
}

/* returns first allocated */
static tigertree_stack *tigertree_alloc(tigertree_context *ctx)
{
    size_t i;
    tigertree_allocs *next = (tigertree_allocs*)malloc(sizeof(tigertree_allocs));
    tigertree_stack *const data = next->data;
    const size_t cnt = sizeof(next->data)/sizeof(data[0]);

    if (ctx->allocs != NULL) {
        assert( ctx->allocs->data[cnt-1].upper == NULL );
        next->prev = ctx->allocs;
    }
    else next->prev = NULL;

    tigertree_format_alloc(data, cnt, ctx->top);
    ctx->top = data + cnt-1;
    ctx->allocs = next;

    return data;
}

void tigertree_init(tigertree_context *ctx)
{
    const size_t cnt = sizeof(ctx->stack) / sizeof(ctx->stack[0]);
    ctx->tiger = tiger_new();
    tiger_feed(ctx->tiger, "\0", 1);

    ctx->left = TIGERTREE_BLOCK_SIZE;

    ctx->level = 0;
    ctx->allocs = NULL;

    tigertree_format_alloc(ctx->stack, cnt, NULL);
    ctx->top = ctx->stack + cnt-1;
    ctx->bottom = ctx->stack + 0; /* atleast one level */
}

void tigertree_done(tigertree_context *ctx)
{
    tigertree_allocs *f = ctx->allocs;
    while(f != NULL) {
        tigertree_allocs * const u = f->prev;
        free(f);
        f = u;
    }

    tiger_free(ctx->tiger);
}


static void tigertree_feed_leaf(tigertree_context *ctx)
{
    const unsigned int clevel = ctx->level;
    unsigned int level = 1;
    tigertree_stack *node = ctx->bottom;
    assert( ctx->bottom != NULL );


    while((node->buf[0] == '\1') && (level <= clevel)) { /* carry up */
        tiger_finalize(ctx->tiger, node->buf + 1 + TIGER_HASH_SIZE);

        tiger_reset(ctx->tiger);
        tiger_feed(ctx->tiger, node->buf, 1 + 2*TIGER_HASH_SIZE);
        node->buf[0] = '\0'; /* clear up this node */

        /* now we have new hash to finalize in our ctx */
        node = node->upper;
        if (node == NULL ) {
            node = tigertree_alloc(ctx);
            ++level;
            break;
        }
        else if ((++level) > clevel) break; /* we've reached the top */
    }

    if (level > clevel) ctx->level = level;

    node->buf[0] = '\1';
    return tiger_finalize(ctx->tiger, node->buf + 1);
}

void tigertree_feed(tigertree_context *ctx, const void *buf, size_t len)
{
    const char *p = buf;

    while(1) {
        if (len < ctx->left) {
            ctx->left -= len;
            return tiger_feed(ctx->tiger, p, len);
        }
        tiger_feed(ctx->tiger, p, ctx->left);
        tigertree_feed_leaf(ctx);

        tiger_reset(ctx->tiger);
        tiger_feed(ctx->tiger, "\0", 1);

        p += ctx->left;
        len -= ctx->left;
        ctx->left = TIGERTREE_BLOCK_SIZE;
    }
}

void tigertree_finalize(tigertree_context *ctx, void *hash)
{
    if ((ctx->left < TIGERTREE_BLOCK_SIZE) || /* last block */
        (ctx->level == 0)) /* finalize for empty file (no leafs were added) */
            tigertree_feed_leaf(ctx);
    tiger_reset(ctx->tiger);

    {
        const size_t clevel = ctx->level;
        size_t level;
        tigertree_stack *node = ctx->bottom;
        const void *rhash = NULL;

        assert( ctx->level > 0 );

        for(level = 1; level <= clevel; ++level, node = node->upper) {
            assert( node != NULL );

            if (node->buf[0] == '\0') continue;
            assert( node->buf[0] == '\1' );

            if (rhash == NULL) {
                rhash = node->buf+1;
                continue;
            }

            tiger_feed(ctx->tiger, node->buf, 1+TIGER_HASH_SIZE);
            tiger_feed(ctx->tiger, rhash, TIGER_HASH_SIZE);
            tiger_finalize(ctx->tiger, hash);
            tiger_reset(ctx->tiger);
            rhash = hash;
        }

        if (rhash != hash) memcpy(hash, rhash, TIGER_HASH_SIZE);
    }
}

void tigertree_reset(tigertree_context *ctx)
{
    tiger_reset(ctx->tiger);
    tiger_feed(ctx->tiger, "\0", 1);

    ctx->left = TIGERTREE_BLOCK_SIZE;

    ctx->level = 0; /* will cut carrying up and force use next node (bottom) as empty */
}

tigertree_context *tigertree_new()
{
    tigertree_context *ctx = (tigertree_context*)malloc(sizeof(tigertree_context));
    tigertree_init(ctx);
    return ctx;
}

void tigertree_free(tigertree_context *ctx)
{
    tigertree_done(ctx);
    free(ctx);
}