/* vim: set ts=8 sts=4 sw=4 tw=80 noet: */
/*======================================================================
Copyright (C) 2004,2005,2009 Walter Doekes <walter+tthsum@wjd.nu>
This file is part of tthsum.

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

tthsum 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 tthsum.  If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
#include "base32.h"

#include "endian.h"
#include <assert.h>

#if BYTE_ORDER == BIG_ENDIAN
#    include <stdlib.h>
#    include <string.h>
#endif /* BYTE_ORDER == BIG_ENDIAN */

#ifdef USE_TEXTS
#   include "texts.h"
#endif


static const int8_t base32_table[] = {
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,26,27,28,29,30,31,-1,-1,-1,-1,-1,-1,-1,-1,
    -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
    15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
    -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
    15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};

static const unsigned char base32_alpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

int uint64tobase32(char* dest, const uint64_t* src, unsigned len) {
#if BYTE_ORDER == BIG_ENDIAN
    int i;
    uint8_t* tmp;
    len *= 8;
    if ((tmp = (uint8_t*)malloc(len)) == NULL) {
#ifdef USE_TEXTS
	set_error("malloc", -1);
#endif /* USE_TEXTS */
	return -1;
    }
    for (i = 0; i < len; ++i)
	tmp[i ^ 7] = ((uint8_t*)src)[i];
    i = uint8tobase32(dest, tmp, len);
    free(tmp);
    return i;
#else /* BYTE_ORDER != BIG_ENDIAN */
    return uint8tobase32(dest, (const uint8_t*)src, len * 8);
#endif /* BYTE_ORDER != BIG_ENDIAN */
}

int uint8tobase32(char* dest, const uint8_t* src, unsigned len) {
    unsigned index = 0;
    uint8_t word;
    const uint8_t* srce = src + len;
    while (src != srce) {
	/* Spanning byte boundary? */
	if (index > 3) {
	    word = (*src & (0xFF >> index));
	    index = (index + 5) % 8;
	    word <<= index;
	    if (src + 1 != srce)
		word |= *(src + 1) >> (8 - index);
	    ++src;
	} else {
	    word = (*src >> (8 - (index + 5))) & 0x1F;
	    index = (index + 5) % 8;
	    if (index == 0)
		++src;
	}
	assert(word < 32);
	*dest++ = base32_alpha[word];
    }
    *dest = '\0';
    return 0;
}

int base32touint64(uint64_t* dest, const char* src, unsigned len) {
#if BYTE_ORDER == BIG_ENDIAN
    int ret, i;
    uint8_t* tmp;
    len *= 8;
    if ((tmp = (uint8_t*)malloc(len)) == NULL) {
#ifdef USE_TEXTS
	set_error("malloc", -1);
#endif /* USE_TEXTS */
	return -1;
    }
    memset(tmp, 0, len); /* dest must be initialized to zeroes */
    ret = base32touint8(tmp, src, len);
    for (i = 0; i < len; ++i)
	((uint8_t*)dest)[i ^ 7] = tmp[i];
    free(tmp);
    return ret;
#else /* BYTE_ORDER != BIG_ENDIAN */
    return base32touint8((uint8_t*)dest, src, len * 8);
#endif /* BYTE_ORDER != BIG_ENDIAN */
}

int base32touint8(uint8_t* dest, const char* src, unsigned len) {
    unsigned index = 0;
    int8_t tmp;
    const uint8_t* deste = dest + len;
    for (; dest != deste; ++src) {
	if ((tmp = base32_table[(unsigned)*src]) == -1) {
#ifdef USE_TEXTS
	    set_error("base32touint8", BASE32_INVALID_CHARACTER);
#endif /* USE_TEXTS */
	    return -1;
	}
	if (index <= 3) {
	    index = (index + 5) % 8;
	    if (index == 0)
		*dest++ |= tmp;
	    else
		*dest |= tmp << (8 - index);
	} else {
	    index = (index + 5) % 8;
	    *dest++ |= (tmp >> index);
	    *dest |= tmp << (8 - index);
	}
    }
    return 0;
}
