/*
  Copyright 2007
  (Y) Yellowbank
  Ronald Peterson

  https://www.yellowbank.com/

  This file is part of y_clib.

  y_clib is free software; you can redistribute it and/or modify it
  under the terms of the GNU Affero GPL version 3.0.  These license
  terms can be found in the included file agpl-3.0.txt.
*/

#include "y_clib.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <sys/stat.h>

// according to strerror man page
#define _XOPEN_SOURCE 600

#define Y_MAX_READLEN 2048

static
char
hexchar2bin( char h ) {
    char r;
    r = ('0' <= h) && (h <= '9') ? h - '0' : 0xFF;
    r = ('A' <= h) && (h <= 'Z') ? h - 'A' + 10 : r;
    r = ('a' <= h) && (h <= 'z') ? h - 'a' + 10 : r;
    if( r < 0 || r > 15 ) {
        perror( "hexchar2bin: invalid hex character" );
        exit( EXIT_FAILURE );
    }
    return( r );
}

static
void
y_octstr_zero( y_octstr os ) {
    memset( Y_OS_STR(os), '\0', Y_OS_LEN( os ) );
}

void
y_octstr_init( y_octstr os ) {
    Y_OS_LEN(os) = 0;
    Y_OS_STR(os) = NULL;
}

void
y_octstr_clear( y_octstr os ) {
    if( os ) {
        if( Y_OS_STR(os) ) {
            // zero memory
            y_octstr_zero( os );
            free( Y_OS_STR(os) );
            Y_OS_STR(os) = NULL;
        }
        Y_OS_LEN(os) = 0;
    }
}

void
y_octstr_set( y_octstr os, int c, uint n ) {
    uint len;
    y_oct_bin str = NULL;
    y_oct_bin tmp = NULL;
    len = Y_OS_LEN( os );
    str = Y_OS_STR( os );
    if( n <= len ) {
        memset( str, c, n );
        return;
    }
    // zero any existing string before reallocating
    if( len > 0 ) {
        memset( str, '\0', len );
    }
    tmp = realloc( str, n );
    if( tmp == NULL ) {
        perror( "y_octstr_set: error allocating new octstr" );
        exit( EXIT_FAILURE );
    }
    str = tmp;
    memset( str, c, n );
    Y_OS_STR( os ) = str;
    Y_OS_LEN( os ) = n;
}

void
y_octstr_set_count( y_octstr os, int val, uint start, uint count ) {
    uint len;
    y_oct_bin str = NULL;
    y_oct_bin tmp = NULL;
    len = Y_OS_LEN( os );
    str = Y_OS_STR( os );
    if( start + count <= len ) {
        memset( str + start, val, count );
        return;
    }
    // zero any existing string before reallocating
    if( len > 0 ) {
        memset( str, '\0', len );
    }
    tmp = realloc( str, start + count );
    if( tmp == NULL ) {
        perror( "y_octstr_set_count: error allocating new octstr" );
        exit( EXIT_FAILURE );
    }
    str = tmp;
    memset( str + start, val, count );
    Y_OS_STR( os ) = str;
    Y_OS_LEN( os ) = start + count;
}

void
y_octstr_set_mask( y_octstr os, uint bit_count ) {
    uint i;
    uint octets;
    uint bits;
    y_oct_bin_t oct;
    y_oct_bin os_str;
    y_oct_bin os_tmp;
   
    if( bit_count == 0 ) {
        octets = 1;
    } else {
        octets = bit_count / 8;
        if( bit_count % 8 ) {
            octets += 1;
        }
    }
    os_str = Y_OS_STR( os );
    os_tmp = realloc( os_str, octets );
    if( os_tmp == NULL ) {
        perror( "y_octstr_set_mask: error allocating string" );
        exit( EXIT_FAILURE );
    }
    os_str = os_tmp;

    // fill with one's
    for( i = 0; i < octets; i++ ) {
        memset( os_str + i, 0xFF, 1 );
    }

    oct = '\0';
    bits = 8 - ((octets * 8) - bit_count);
    for( i = 0; i < bits; i++ ) {
        oct = (oct >> 1) | 0x80;
    }

    memset( os_str + octets - 1, oct, 1 );

    Y_OS_STR( os ) = os_str;
    Y_OS_LEN( os ) = octets;
}

void
y_octstr_mask_out( y_octstr os, y_octstr mask ) {
    uint i;
    uint minLen;
    y_oct_bin os_str;
    y_oct_bin mask_str;

    minLen = Y_OS_LEN(mask) < Y_OS_LEN(os) ? Y_OS_LEN(mask) : Y_OS_LEN(os);

    os_str = Y_OS_STR( os );
    mask_str = Y_OS_STR( mask );
   
    for( i = 0; i < minLen; i++ ) {
        *(os_str + i) &= ~*(mask_str + i);
    }
}

void
y_octstr_replace_substr( y_octstr os, y_octstr sub, uint pos ) {
    uint os_len;
    uint sub_len;
    y_oct_bin os_str = NULL;
    y_oct_bin sub_str = NULL;
    y_oct_bin tmp = NULL;
    os_len = Y_OS_LEN( os );
    os_str = Y_OS_STR( os );
    sub_len = Y_OS_LEN( sub );
    sub_str = Y_OS_STR( sub );
    if( pos > os_len ) {
        perror( "y_octstr_replace_substr: start past end of string" );
        exit( EXIT_FAILURE );
    }
    if( pos + sub_len <= os_len ) {
        memcpy( os_str + pos, sub_str, sub_len );
        return;
    }
    tmp = realloc( os_str, pos + sub_len );
    if( tmp == NULL ) {
        perror( "y_octstr_replace_substr: error allocating new octstr" );
        exit( EXIT_FAILURE );
    }
    os_str = tmp;
    memcpy( os_str + pos, sub_str, sub_len );
    Y_OS_STR( os ) = os_str;
    Y_OS_LEN( os ) = pos + sub_len;
}

void
y_octstr_set_from_cp_hex( y_octstr os, char *hex ) {
    int i, o, hexlen, binlen;
    char l, r, h;
    y_oct_bin bin = NULL;
    y_oct_bin newbin = NULL;
    if( ! hex ) {
        return;
    }
    hexlen = strlen( hex );
    if( hexlen == 0 ) {
        y_octstr_clear( os );
        y_octstr_init( os );
        return;
    }
    binlen = (hexlen / 2) + (hexlen % 2);

    bin = Y_OS_STR(os);

    newbin = realloc( bin, binlen );
    if( newbin == NULL ) {
        perror( "y_octstr_set_from_cp_hex: realloc failure" );
        exit( EXIT_FAILURE );
    }
    bin = newbin;
    Y_OS_STR( os ) = newbin;
    newbin = NULL;

    Y_OS_LEN(os) = binlen;
    o = 0; // nibble offset
    if( hexlen % 2 ) {
        // if we have an odd number of hex characters, get the first
        // nibble
        o = 1;
        l = 0;
        h = hex[0];
        r = hexchar2bin( h );
        bin[0] = l | r;
    }
    for( i = 0; i < binlen - o; i++ ) {
        h = hex[(i*2)+o];
        l = (hexchar2bin( h ) << 4) & 0xF0;

        h = hex[(i*2)+1+o];
        r = hexchar2bin( h ) & 0x0F;

        bin[i+o] = l | r;
    }
    return;
}

char *
y_cp_hex_realloc_from_octstr( char *hex, y_octstr os ) {
    int i, binlen;
    char *tmp;
    char c, h, l, r;
    y_oct_bin bin;
    bin = Y_OS_STR(os);
    if( ! bin ) {
        return NULL;
    }
    binlen = Y_OS_LEN(os);

    tmp = realloc( hex, binlen * 2 + 1 );
    if( tmp == NULL ) {
        perror( "Error: y_cp_alloc_from_octstr: realloc failure" );
        if( hex ) { free( hex ); }
        return NULL;
        // exit( EXIT_FAILURE );
    }
    hex = tmp;

    for( i = 0; i < binlen; i++ ) {
        c = *(bin + i);
        l = (c >> 4) & 0x0F;
        r = (c & 0x0F);
        h = (int)l < 10 ? '0' : 'a' - 10;
        hex[i*2] = l + h;
        h = (int)r < 10 ? '0' : 'a' - 10;
        hex[i*2+1] = r + h;
    }
    memset( hex + (binlen * 2), '\0', 1 );
    return hex;
}

bool
y_are_octstr_equal( y_octstr left, y_octstr right ) {
    uint llen, rlen;
    uint count = 0;
    y_oct_bin ls, rs;
    llen = Y_OS_LEN( left );
    rlen = Y_OS_LEN( right );
    ls = Y_OS_STR( left );
    rs = Y_OS_STR( right );
    if( llen != rlen ) {
        return false;
    }
    while( *(ls++) == *(rs++) && ++count < llen ) {}
    if( count == llen ) {
        return true;
    }
    return false;
}

void
y_print_os_as_hex( y_octstr os ) {
    char *test = NULL;
    char *hex = NULL;
    test = y_cp_hex_realloc_from_octstr( hex, os );
//   if( test == NULL ) {
//      perror( "error allocating hex" );
//      exit( EXIT_FAILURE );
//   }
    hex = test;
    printf( "%s", hex );
    free( test );
}

uint
y_octets_required_by_bigint( mpz_t bigint ) {
    uint octets = 0;
    octets = (mpz_sizeinbase( bigint, 2 ) + 7) / 8;
    return octets;
}

void
y_octstr_set_from_gmp_int( y_octstr os, mpz_t bigint ) {
    char *hex = NULL;
    uint size, hexlen;
    int ret;

    size = y_octets_required_by_bigint( bigint );
    hexlen = size * 2;
    hex = malloc( hexlen );
    if( hex == NULL ) {
        perror( "y_octstr_set_from_gmp_int: error allocating hex" );
        exit( EXIT_FAILURE );
    }
    ret = gmp_snprintf( hex, hexlen + 1, "%Zx", hexlen, bigint );
    y_octstr_set_from_cp_hex( os, hex );
    free( hex );
}

void
y_gmp_int_set_from_octstr( mpz_t bigint, y_octstr os ) {
    int valid;
    char *hex = NULL;
    char *newhex = NULL;
    newhex = y_cp_hex_realloc_from_octstr( hex, os );
    // this should really return NaN somehow
    if( newhex == NULL ) {
        mpz_set_ui( bigint, 0 );
        return;
    }
    hex = newhex;
    valid = mpz_set_str( bigint, hex, 16 );
//    if( ! valid ) {
//       perror( "y_gmp_int_set_from_octstr: invalid hex" );
//       exit( EXIT_FAILURE );
//    }
}

void
y_octstr_copy( y_octstr dst, y_octstr src ) {
    uint slen;
    y_oct_bin tmp;
    y_oct_bin dbin;
    y_oct_bin sbin;

    sbin = Y_OS_STR( src );
    if( sbin == NULL ) {
        y_octstr_clear( dst );
        return;
    }

    y_octstr_zero( dst );

    slen = Y_OS_LEN( src );
    dbin = Y_OS_STR( dst );
    tmp = realloc( dbin, slen );
    if( tmp == NULL ) {
        perror( "y_octstr_copy: error allocating destination octet string" );
        exit( EXIT_FAILURE );
    }
    dbin = tmp;
    memcpy( dbin, sbin, slen );
    Y_OS_STR( dst ) = dbin;
    Y_OS_LEN( dst ) = slen;
}

void
y_octstr_concat( y_octstr dest, y_octstr left, y_octstr right ) {
    y_octstr tmp_dest;
    uint len, llen, rlen;
    y_oct_bin str = NULL;
    y_oct_bin tmp = NULL;

    // dest might be also be left or right, so we concatenate
    // to temporary octet string, then copy to dest.
    // An optimization would be to check whether dest is
    // actually equal to left or right, and skip the copy
    // step if not.
    y_octstr_init( tmp_dest );

    llen = Y_OS_LEN( left );
    rlen = Y_OS_LEN( right );
    len = llen + rlen;
    str = Y_OS_STR( tmp_dest );
    tmp = realloc( str, len );
    if( tmp == NULL ) {
        perror( "y_octstr_concat: error allocating octet string" );
        exit( EXIT_FAILURE );
    }
    str = tmp;

    memcpy( str, Y_OS_STR( left ), llen );
    memcpy( str + llen, Y_OS_STR( right ), rlen );

    Y_OS_STR( tmp_dest ) = str;
    Y_OS_LEN( tmp_dest ) = len;
    y_octstr_copy( dest, tmp_dest );

    y_octstr_clear( tmp_dest );
}

int
y_get_os_dev_random( y_octstr os, uint octet_count, char *randdev ) {

    // Fill os with byte_count bytes of random data.
    // Linux specific code for now.

    int ret = 0;
    int fd;
    int total = 0;

    y_oct_bin bin;

    bin = realloc( Y_OS_STR(os), octet_count );
    if( bin == NULL ) {
        perror( "y_get_os_dev_random: error allocating octet string" );
        return( Y_BAD_RANDOM );
    }
    Y_OS_STR( os ) = bin;
    Y_OS_LEN( os ) = octet_count;
   
    if( strcmp( randdev, "/dev/random" ) == 0 ) {
        fd = open( "/dev/random", O_RDONLY );
    } else if( strcmp( randdev, "/dev/urandom" ) == 0 )	{
        fd = open( "/dev/urandom", O_RDONLY );
    } else {
        return( Y_NO_RANDOM );
    }

    if( fd == -1 ) {
        perror( "get_dev_random: random source must be '/dev/random' or '/dev/urandom'" );
        return( Y_NO_RANDOM );
    }

    while( (ret = read( fd, bin, octet_count - total ) > 0) &&
           (ret != (octet_count - total)) ) {
        total += ret;
        bin += ret;
    }

    close( fd );

    if( ret == -1 ) {
        return( Y_BAD_RANDOM );
    }
    return( Y_GOOD_RANDOM );
}

char *
y_realloc_random_hex( char *hex, uint octets ) {
    y_octstr os;
    int ret;
    char *rand;

    y_octstr_init( os );

    ret = y_get_os_dev_random( os, octets, Y_DEFAULT_RANDDEV );

    if( (ret = Y_GOOD_RANDOM) != 0 ) {
        rand = y_cp_hex_realloc_from_octstr( hex, os );
    }

    y_octstr_clear( os );

    if( rand ) {
        return rand;
    }
    return NULL;
}

static
void
_y_set_gmp_random_state( gmp_randstate_t state,
                         uint octets )
{
    char *rand_hex = NULL;
    char *ret;
    mpz_t seed;

    mpz_init( seed );

    ret = y_realloc_random_hex( rand_hex, octets );
    if( ret == NULL ) {
        perror( "y_set_gmp_random_state: error generating random hex" );
        exit( EXIT_FAILURE );
    }
    rand_hex = ret;

    if( mpz_set_str( seed, rand_hex, 16 ) != 0 ) {
        perror( "y_set_gmp_random_state: not valid hex" );
        exit( EXIT_FAILURE );
    }

    gmp_randseed( state, seed );


    mpz_clear( seed );
}

int
y_gmp_random_init( gmp_randstate_t *random_ctx, int len )
{
    int retval;
    y_octstr seed;
    mpz_t mpz_seed;
    int ret = 0;

    y_octstr_init( seed );
    mpz_init( mpz_seed );

//     printf( "Initializing random seed, this may take a while...\n" );

    retval = y_get_os_dev_random( seed, len, "/dev/random" );

    if( retval == Y_GOOD_RANDOM ) {
        y_gmp_int_set_from_octstr( mpz_seed, seed );
        gmp_randinit_mt( *random_ctx );
        gmp_randseed( *random_ctx, mpz_seed );
        ret = 1;
    }

    y_octstr_clear( seed );
    mpz_clear( mpz_seed );
    return ret;
}

void
y_gmp_random_func( gmp_randstate_t *random_ctx,
                   unsigned length,
                   uint8_t *dst )
{
    mpz_t     mpz_random;
    mpz_t     max;
    mpz_init( mpz_random );
    mpz_init( max );

    mpz_ui_pow_ui( max, 256, length );
    mpz_sub_ui( max, max, 1 );

    mpz_urandomm( mpz_random, *random_ctx, max );
    
    bzero( dst, length );

    mpz_export( dst,          // rop
                NULL,         // countp (NULL to discard count)
                1,            // order
                1,            // size
                0,            // endian (0 for OS native)
                0,            // nails (0 for full words)
                mpz_random ); // op

    mpz_clear( mpz_random );
}

int
y_get_mpz_dev_random( mpz_t m, uint octets, char *dev ) {
    y_octstr seed_os;
    char *seed = NULL;
    y_octstr_init( seed_os );
    if( y_get_os_dev_random( seed_os, octets, dev ) != Y_GOOD_RANDOM ) {
        y_octstr_clear( seed_os );
        return 0;
    }
    seed = y_cp_hex_realloc_from_octstr( seed, seed_os );
    mpz_set_str( m, seed, 16 );
    y_octstr_clear( seed_os );
    if( seed ) { free( seed ); }
    return 1;
}

static
bool
_y_gen_prime( mpz_t m, bool preserve_bitlength ) {
    unsigned bitlen;
    bitlen = mpz_sizeinbase( m, 2 );
    for(;;) {
        mpz_nextprime( m, m );
        if( mpz_probab_prime_p( m, 24 ) &&
            mpz_congruent_ui_p( m, 3, 4 ) ) {
            break;
        }
    }
    if( preserve_bitlength ) {
        // check that generated prime is no longer than starting integer
        if( bitlen == mpz_sizeinbase( m, 2 ) ) {
            return true;
        }
        return false;
    }
    return true;
}

static
void
_y_bbs_gen_prime( mpz_t m, int octets ) {
    // create prime m such that m is congruent to 3 (mod 4)
    // todo: gcd( totient(p - 1), totient(q - 1) ) should be small
    y_get_mpz_dev_random( m, octets, "/dev/random" );
    _y_gen_prime( m, false );
}

void
y_bbs_rand_init( y_bbs_state bbs_ctx ) {
    mpz_init( bbs_ctx->x );
    mpz_init( bbs_ctx->p );
    mpz_init( bbs_ctx->q );
    mpz_init( bbs_ctx->M );

    // Initialize with valid primes, so we can run immediately, without waiting for
    // random device to collect enough entropy.  If you really want random numbers,
    // though, you had better re-seed, e.g. call y_bbs_rand_seed.

    mpz_set_str( bbs_ctx->x, "1234567890abcdef", 16 );
    mpz_set_str( bbs_ctx->p, "1597b2a735d3952f7a85c9c456a5324f", 16 );
    mpz_set_str( bbs_ctx->q, "29f667f2dd7b51a6e0f9ccadea207023", 16 );
    mpz_set_str( bbs_ctx->M, "38a1426f94320ef3bf55e18208b6f39b54d22d4411905e638df43c59e7870cd", 16 );
}

void
y_bbs_rand_seed( y_bbs_state bbs_ctx, int x_octets, int key_octets ) {
    y_get_mpz_dev_random( bbs_ctx->x, x_octets, "/dev/random" );
    _y_bbs_gen_prime( bbs_ctx->p, key_octets );
    _y_bbs_gen_prime( bbs_ctx->q, key_octets );
    mpz_mul( bbs_ctx->M, bbs_ctx->p, bbs_ctx->q );
}

void
y_bbs_spawn( y_bbs_state new_ctx, y_bbs_state old_ctx, unsigned octets ) {
    y_bbs_rand_mpz_octets( old_ctx, octets, new_ctx->x );
    for(;;) {
        y_bbs_rand_mpz_octets( old_ctx, octets, new_ctx->p );
        if( _y_gen_prime( new_ctx->p, true ) ) { break; }
    }

    for(;;) {
        y_bbs_rand_mpz_octets( old_ctx, octets, new_ctx->q );
        if( _y_gen_prime( new_ctx->q, true ) ) { break; }
    }
    mpz_mul( new_ctx->M, new_ctx->p, new_ctx->q );
}

void
y_bbs_rand_clear( y_bbs_state bbs_ctx ) {
    mpz_clear( bbs_ctx->x );
    mpz_clear( bbs_ctx->p );
    mpz_clear( bbs_ctx->q );
    mpz_clear( bbs_ctx->M );
}

void
y_bbs_rand_octets( y_bbs_state bbs_ctx, unsigned octets, uint8_t *dst ) {
    // x = x^2 Mod M
    // take least significant bit of x each iteration
    // i.e. eight iterations of above to create one random octet
    int i, j;

    // zero destination buffer
    memset( dst, '\0', octets );

    // generate random octets one bit at a time
    for( i = 0; i < octets; i++ ) {
        for( j = 0; j < 8; j++ ) {
            mpz_powm_ui( bbs_ctx->x, bbs_ctx->x, 2, bbs_ctx->M );
            dst[i] |= (mpz_tstbit( bbs_ctx->x, 0 )) << j;
        }
    }
}

void
y_bbs_rand_mpz_octets( y_bbs_state bbs_ctx, unsigned octets, mpz_t dst ) {
    uint8_t *dest;
    dest = (uint8_t *)malloc( octets );
    y_bbs_rand_octets( bbs_ctx, octets, dest );
    mpz_import( dst, octets, 1, 1, 0, 0, dest );
    free( dest );
}

void y_bbs_rand_range( y_bbs_state bbs_ctx, mpz_t range, mpz_t randret ) {
    unsigned octets;
    uint8_t *random;
    mpz_t random_m;

    octets = (mpz_sizeinbase( range, 2 ) + 7) / 8;
    random = (uint8_t *)malloc( octets );
    mpz_init( random_m );

    y_bbs_rand_octets( bbs_ctx, octets, random );
    mpz_import( random_m, octets, 1, 1, 0, 0, random );

    // random % max;
    // OK??
    mpz_mod( randret, random_m, range );

    free( random );
    mpz_clear( random_m );
}

void
y_gen_mpz_random( mpz_t rand,
                  const char* random_source,
                  uint seed_len,
                  uint max_bits )
{
    gmp_randstate_t state;
    gmp_randinit_default( state );
    _y_set_gmp_random_state( state, seed_len );
    // random number in the range 0 to 2^max_bits-1, inclusive
    mpz_urandomb( rand, state, max_bits );
    gmp_randclear( state );
}

void
y_gen_mpz_random_of_bit_len( mpz_t rand,
                             uint seed_len,
                             uint bits )
{
    mpz_t min;
    mpz_t max;
    mpz_t range;
    mpz_init( min );
    mpz_init( max );
    mpz_init( range );
// e.g. 1000 for bits = 4.
    mpz_ui_pow_ui( min, 2, bits - 1); 
// e.g. 10000 for bits = 4.  We want to subtract 1 to
// get 1111 for bits = 4, but mpz_urandomm below takes
// care of that for us.
    mpz_ui_pow_ui( max, 2, bits );
    mpz_sub( range, max, min );

    gmp_randstate_t state;
    gmp_randinit_default( state );
    _y_set_gmp_random_state( state, seed_len );

// random number in the range 0 to 2^range-1, inclusive
    mpz_urandomm( rand, state, range );
// e.g. between 1000 and 1111 inclusive for bits = 4.
    mpz_add( rand, min, rand );

    gmp_randclear( state );
    mpz_clear( min );
    mpz_clear( max );
    mpz_clear( range );
}

void
y_generate_random_prime( mpz_t prime,
                         uint seed_len,
                         uint bits )
{
    mpz_t rand;
    mpz_t max;
    mpz_init( rand );
    mpz_init( max );

    mpz_ui_pow_ui( prime, 2, bits + 1 );
    mpz_ui_pow_ui ( max, 2, bits);
    mpz_sub_ui( max, max, 1 );

    // Eliminate the extremely small chance that our prime falls
    // out of the range 0 to 2^max_bits - 1, inclusive.
    //
    // while prime > max (exceedingly unlikely for large
    // primes, but just to be sure)
    while( mpz_cmp( prime, max ) > 0 ) {
        y_gen_mpz_random_of_bit_len( rand, seed_len, bits );
        mpz_nextprime( prime, rand );
    }

    mpz_clear( rand );
    mpz_clear( max );
}

void
y_exclusive_or( y_octstr ret, y_octstr l, y_octstr r ) {
    uint i;
    uint llen;
    uint rlen;
    y_oct_bin lbin;
    y_oct_bin rbin;
    y_oct_bin bin;
    y_oct_bin tmp = NULL;

    llen = Y_OS_LEN( l );
    rlen = Y_OS_LEN( r );
    if( llen != rlen ) {
        perror( "y_exclusive_or: inconsistent input lengths" );
        exit( EXIT_FAILURE );
    }

    lbin = Y_OS_STR( l );
    rbin = Y_OS_STR( r );
    bin = Y_OS_STR( ret );

    tmp = realloc( bin, llen );
    if( tmp == NULL ) {
        perror( "y_exclusive_or: error allocating return value" );
        exit( EXIT_FAILURE );
    }
    bin = tmp;

    for( i = 0; i < llen; i++ ) {
        *(bin + i) = *(lbin + i) ^ *(rbin + i);
    }
    Y_OS_STR( ret ) = bin;
    Y_OS_LEN( ret ) = llen;
}

// On success, return length in octets of selected hash type, else return 0.
static
int
_y_get_hash_id( hashid* hash_id, const char* hash_type )
{
    if      (strcmp( hash_type, "TIGER" ) == 0 ) { *hash_id = MHASH_TIGER; }
    else if (strcmp( hash_type, "TIGER160" ) == 0) { *hash_id = MHASH_TIGER160; }
    else if (strcmp( hash_type, "TIGER128" ) == 0) { *hash_id = MHASH_TIGER128; }
    else if (strcmp( hash_type, "WHIRLPOOL" ) == 0) { *hash_id = MHASH_WHIRLPOOL; }
    else if (strcmp( hash_type, "RIPEMD320" ) == 0) { *hash_id = MHASH_RIPEMD320; }
    else if (strcmp( hash_type, "RIPEMD256" ) == 0) { *hash_id = MHASH_RIPEMD256; }
    else if (strcmp( hash_type, "RIPEMD128" ) == 0) { *hash_id = MHASH_RIPEMD128; }
    else if (strcmp( hash_type, "SHA512" ) == 0) { *hash_id = MHASH_SHA512; }
    else if (strcmp( hash_type, "SHA384" ) == 0) { *hash_id = MHASH_SHA384; }
    else if (strcmp( hash_type, "SHA256" ) == 0) { *hash_id = MHASH_SHA256; }
    else if (strcmp( hash_type, "SHA224" ) == 0) { *hash_id = MHASH_SHA224; }
    else if (strcmp( hash_type, "SHA1" ) == 0) { *hash_id = MHASH_SHA1; }
    else if (strcmp( hash_type, "HAVAL256" ) == 0) { *hash_id = MHASH_HAVAL256; }
    else if (strcmp( hash_type, "HAVAL224" ) == 0) { *hash_id = MHASH_HAVAL224; }
    else if (strcmp( hash_type, "HAVAL192" ) == 0) { *hash_id = MHASH_HAVAL192; }
    else if (strcmp( hash_type, "HAVAL160" ) == 0) { *hash_id = MHASH_HAVAL160; }
    else if (strcmp( hash_type, "HAVAL128" ) == 0) { *hash_id = MHASH_HAVAL128; }
    else if (strcmp( hash_type, "GOST" ) == 0) { *hash_id = MHASH_GOST; }
    else if (strcmp( hash_type, "SNEFRU256" ) == 0) { *hash_id = MHASH_SNEFRU256; }
    else if (strcmp( hash_type, "SNEFRU128" ) == 0) { *hash_id = MHASH_SNEFRU128; }
    else if (strcmp( hash_type, "MD5" ) == 0) { *hash_id = MHASH_MD5; }
    else if (strcmp( hash_type, "MD4" ) == 0) { *hash_id = MHASH_MD4; }
    else if (strcmp( hash_type, "CRC32" ) == 0) { *hash_id = MHASH_CRC32; }
    else if (strcmp( hash_type, "ADLER32" ) == 0) { *hash_id = MHASH_ADLER32; }
    else {
        return( 0 );
    }
    return( mhash_get_block_size(*hash_id) );
}

// Calculate hash of data according to algorithm specified by
// hash_type; 'hash' argument should be initialized to NULL, or point
// to previously allocated memory (which should be
// mhash_get_block_size(hash_id) + 1 bytes).  Will put null terminated
// hex string representation of hash value into octet string, and
// hash_len will contain the length of the hash indicated by
// hash_type.
//

//              const y_octstr os,

int
y_calc_mhash( y_octstr hash,
              const char *hash_type,
              char *input_data,
              uint data_len,
              uint *hash_len )
{
    hashid hash_id = 0;
    MHASH td;
    uint offset = 0;
    uint chunk_size = 2048;
//   uint os_len;
//   y_oct_bin input_data = NULL;
    y_oct_bin hash_data = NULL;
    unsigned char *buffer = NULL;

//   os_len = Y_OS_LEN( os );
//   input_data = Y_OS_STR( os );
    *hash_len = _y_get_hash_id( &hash_id, hash_type );

    hash_data = realloc( Y_OS_STR( hash ), *hash_len );
    if( hash_data == NULL ) {
        perror( "error allocating octet string for hash" );
        return( Y_HASH_ALLOC_FAILURE );
    }
    Y_OS_STR( hash ) = hash_data;
    Y_OS_LEN( hash ) = *hash_len;

    td = mhash_init( hash_id );
    if (td == MHASH_FAILED) {
        // hash initialization failed
        return( Y_HASH_INIT_FAILURE );
    }
	
    // Chew data up in bites, so that very large data doesn't
    // blow things up.

    buffer = malloc( chunk_size );
    if( data_len < chunk_size ) { chunk_size = data_len; }

    while( (data_len - offset) > 0 ) {
        memcpy( buffer, input_data + offset, chunk_size );
        mhash( td, buffer, chunk_size );
        offset += chunk_size;
        if( (data_len - offset) < chunk_size ) {
            chunk_size = data_len - offset;
        }
    }

    mhash_deinit( td, hash_data );

    free( buffer );

    return( Y_HASH_OK );
}

static
int
_y_calc_mhash( y_octstr hash,
               const char *hash_type,
               y_octstr os )
{
    uint hash_len;
    return y_calc_mhash( hash,
                         hash_type,
                         (char *)Y_OS_STR(os),
                         Y_OS_LEN(os),
                         &hash_len );
}

//______________________________________________________________________
// append 'depth' directories to 'base' using characters from the
// beginning of 'hash'.  E.g. - if 'base' is '/some/place', 'hash' is
// 'abc123', and 'depth' is 2, then the result would be
// '/some/place/a/b'.
//______________________________________________________________________
char
*y_mkdir_and_realloc_hashdir_name( char *hashdir,
                                   const char *basedir,
                                   const char *hash,
                                   uint depth,
                                   char *errstr )
{
    char *tmp = NULL;
    uint blen;
    uint hlen;
    uint i;
    int ret;
    int errret;
    struct stat statBuf;

    assert( basedir );
    assert( hash );
    assert( depth );

    blen = strlen( basedir );
    hlen = strlen( hash );

    if( depth > hlen ) {
        snprintf( errstr, MAX_ERR_LEN, "Depth greater than hash length" );
        return NULL;
    }

    if( (depth + (hlen * 2)) > FLOB_MAX_DIRSTR_LEN ) {
        snprintf( errstr,
                  MAX_ERR_LEN,
                  "Directory name longer than %d characters",
                  FLOB_MAX_DIRSTR_LEN );
        return NULL;
    }

    tmp = realloc( hashdir, blen + (depth * 2) + 1 );
    if( tmp == NULL ) {
        snprintf( errstr, MAX_ERR_LEN, "Error allocating hash directory string" );
        return NULL;
    }
    hashdir = tmp;

    ret = stat( basedir, &statBuf ) == 0;
    if( ! ret ) {
        snprintf( errstr, MAX_ERR_LEN, "Base directory (%s) does not exist", basedir );
        return NULL;
    }
    ret = statBuf.st_mode & S_IFDIR;
    if( ! ret ) {
        snprintf( errstr, MAX_ERR_LEN, "Base (%s) exists, but is not a directory", basedir );
        return NULL;
    }

    memcpy( hashdir, basedir, blen );
    for( i = 0; i < depth; i++ ) {
        memset( hashdir + blen + (i*2), '/', 1 );
        memset( hashdir + blen + (i*2) + 1, hash[i], 1 );
        memset( hashdir + blen + (i*2) + 2, '\0', 1 );
        ret = mkdir( hashdir, S_IRUSR | S_IWUSR | S_IXUSR );
        if( ret == -1 ) {
            switch( errno ) {
            case EEXIST:
                break;
            case EACCES:
            case EMLINK:
            case ENOSPC:
            case EROFS:
                errret = strerror_r( errno, errstr, MAX_ERR_LEN );
            default:
                snprintf( errstr, MAX_ERR_LEN, "Unexpected error creating %s", hashdir );
                return NULL;
            }
        }
    }
    return( hashdir );
}

int
y_octstr_from_file( y_octstr filedat,
                    const char *filename,
                    char *errstr )
{
    int fd;
    ssize_t bytes = 0;
    uint total = 0;
    int ret = 0;
    int errret = 0;
    y_oct_bin tmp = NULL;
    y_oct_bin bin = NULL;
    y_oct_bin dat[Y_MAX_READLEN];

    y_octstr_clear( filedat );

    fd = open( filename, O_RDONLY );
    if( fd == -1 ) {
        switch( errno ) {
        case EACCES:
        case EEXIST:
        case EINTR:
        case EISDIR:
        case EMFILE:
        case ENFILE:
        case ENOENT:
        case ENOSPC:
        case ENXIO:
        case EROFS:
            errret = strerror_r( errno, errstr, MAX_ERR_LEN );
            ret = -1;
            goto end;
        default:
            snprintf( errstr, MAX_ERR_LEN, "Unexpected error reading %s", filename );
            ret = -1;
            goto end;
        }
    }

    while( ((bytes = read( fd, dat, Y_MAX_READLEN )) > 0) &&
           (total <= MAX_HASH_FILE_SIZE) ) {
        if( bytes == -1 ) {
            switch( errno ) {
            case EAGAIN:
            case EBADF:
            case EINTR:
            case EIO:
            case EINVAL:
                errret = strerror_r( errno, errstr, MAX_ERR_LEN );
                ret = -1;
                goto end;
            default:
                snprintf( errstr, MAX_ERR_LEN, "Unexpected error reading %s", filename );
                ret = -1;
                goto end;
            }
        }
        total += bytes;
        tmp = realloc( bin, total );
        if( tmp == NULL ) {
            snprintf( errstr, MAX_ERR_LEN, "Error allocating buffer for file data" );
            ret = -1;
            goto end;
        }
        bin = tmp;
        memcpy( bin + total - bytes, dat, bytes );
    }
    Y_OS_LEN( filedat ) = total;
    Y_OS_STR( filedat ) = bin;

end:
    if( bin && (Y_OS_LEN(filedat) == 0) )
        free( bin );
    if( fd )
        close( fd );
    return ret;
}

char
*y_octstr_to_hashfile( y_octstr os,
                       const char *basedir,
                       const char *hashType,
                       uint depth,
                       char *errstr )
{
    char *tmp = NULL;
    char *hash_hex = NULL;
    char *filename = NULL;
    y_octstr hash;
    uint dirlen;
    uint hashlen;
    FILE *fs = NULL;
    struct stat statBuf;
    int chk;
    int old_mask;

    y_octstr_init( hash );

    if( _y_calc_mhash( hash, hashType, os ) != Y_HASH_OK ) {
        snprintf( errstr, MAX_ERR_LEN, "Error calculating hash" );
        goto end;
    }

    tmp = y_cp_hex_realloc_from_octstr( hash_hex, hash );
    if( tmp == NULL ) {
        snprintf( errstr, MAX_ERR_LEN, "Error allocating hash string" );
        goto end;
    }
    hash_hex = tmp;

    tmp = y_mkdir_and_realloc_hashdir_name( filename,
                                            basedir,
                                            hash_hex,
                                            depth,
                                            errstr );

    if( tmp == NULL ) {
        goto end;
    }
    filename = tmp;

    dirlen = strlen( filename );
    hashlen = strlen( hash_hex );
    tmp = realloc( filename, dirlen + hashlen + 2 );
    if( tmp == NULL ) {
        snprintf( errstr, MAX_ERR_LEN, "Error allocating filename" );
        if( filename )
            free( filename );
        filename = NULL;
        goto end;
    }
    filename = tmp;

    memset( filename + dirlen, '/', 1 );
    memcpy( filename + dirlen + 1, hash_hex, hashlen );
    memset( filename + dirlen + 1 + hashlen, '\0', 1 );

    // Don't waste time writing a file that already exists.
    // We should be able to assume that the content is identical.
    chk = stat( filename, &statBuf ) == 0;
    if( chk ) {
        snprintf( errstr, MAX_ERR_LEN, "File (%s) already exists", filename );
        if( filename )
            free( filename );
        filename = NULL;
        goto end;
    }

    old_mask = umask(077);

    fs = fopen( filename, "wx" );
    if( fs == NULL ) {
        snprintf( errstr, MAX_ERR_LEN, "Error opening file (%s) for write, possibly already exists", filename );
        if( filename )
            free( filename );
        filename = NULL;
        goto end;
    }
    chk = fwrite( Y_OS_STR(os), Y_OS_LEN(os), 1, fs );
    if( chk != 1 ) {
        snprintf( errstr, MAX_ERR_LEN, "Error writing data to file" );
        if( filename )
            free( filename );
        filename = NULL;
        goto end;
    }

    umask( old_mask );

end:
    y_octstr_clear( hash );
    if( hash_hex ) 
        free( hash_hex );
    if( fs )
        fclose( fs );
    return filename;
}
////////////////////////////////////////////////////////////////////////

