/*
 * Copyright (c) 2012 Tim Ruehsen
 * Copyright (c) 2015-2021 Free Software Foundation, Inc.
 *
 * This file is part of Wget.
 *
 * Wget 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.
 *
 * Wget 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 Wget.  If not, see <https://www.gnu.org/licenses/>.
 *
 *
 * Hash routines
 *
 * Changelog
 * 29.07.2012  Tim Ruehsen  created
 *
 */

#include <config.h>

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_MMAP
#	include <sys/mman.h>
#endif

#include <wget.h>
#include "private.h"

/**
 * \file
 * \brief Hashing functions
 * \ingroup libwget-hash
 * @{
 *
 */

/**
 * \param[in] hashname Name of the hashing algorithm (see table below)
 * \return A constant to be used by libwget hashing functions
 *
 * Get the hashing algorithms list item that corresponds to the named hashing algorithm.
 *
 * This function returns a constant that uniquely identifies a known supported hashing algorithm
 * within libwget. All the supported algorithms are listed in the ::wget_digest_algorithm enum.
 *
 * Algorithm name | Constant
 * -------------- | --------
 * sha1 or sha-1|WGET_DIGTYPE_SHA1
 * sha256 or sha-256|WGET_DIGTYPE_SHA256
 * sha512 or sha-512|WGET_DIGTYPE_SHA512
 * sha224 or sha-224|WGET_DIGTYPE_SHA224
 * sha384 or sha-384|WGET_DIGTYPE_SHA384
 * md5|WGET_DIGTYPE_MD5
 * md2|WGET_DIGTYPE_MD2
 * rmd160|WGET_DIGTYPE_RMD160
 */
wget_digest_algorithm wget_hash_get_algorithm(const char *hashname)
{
	if (hashname) {
		if (*hashname == 's' || *hashname == 'S') {
			if (!wget_strcasecmp_ascii(hashname, "sha-1") || !wget_strcasecmp_ascii(hashname, "sha1"))
				return WGET_DIGTYPE_SHA1;
			else if (!wget_strcasecmp_ascii(hashname, "sha-256") || !wget_strcasecmp_ascii(hashname, "sha256"))
				return WGET_DIGTYPE_SHA256;
			else if (!wget_strcasecmp_ascii(hashname, "sha-512") || !wget_strcasecmp_ascii(hashname, "sha512"))
				return WGET_DIGTYPE_SHA512;
			else if (!wget_strcasecmp_ascii(hashname, "sha-224") || !wget_strcasecmp_ascii(hashname, "sha224"))
				return WGET_DIGTYPE_SHA224;
			else if (!wget_strcasecmp_ascii(hashname, "sha-384") || !wget_strcasecmp_ascii(hashname, "sha384"))
				return WGET_DIGTYPE_SHA384;
		}
		else if (!wget_strcasecmp_ascii(hashname, "md5"))
			return WGET_DIGTYPE_MD5;
		else if (!wget_strcasecmp_ascii(hashname, "md2"))
			return WGET_DIGTYPE_MD2;
		else if (!wget_strcasecmp_ascii(hashname, "rmd160"))
			return WGET_DIGTYPE_RMD160;
	}

	error_printf(_("Unknown hash type '%s'\n"), hashname);
	return WGET_DIGTYPE_UNKNOWN;
}

#if defined WITH_GNUTLS && !defined WITH_LIBNETTLE
#include <gnutls/gnutls.h>
#ifdef HAVE_GNUTLS_CRYPTO_H
#  include <gnutls/crypto.h>
#endif

struct wget_hash_hd_st {
	gnutls_hash_hd_t
		dig;
};

static const gnutls_digest_algorithm_t
	algorithms[WGET_DIGTYPE_MAX] = {
//		[WGET_DIGTYPE_UNKNOWN] = GNUTLS_DIG_UNKNOWN, // both values are 0
		[WGET_DIGTYPE_MD2] = GNUTLS_DIG_MD2,
		[WGET_DIGTYPE_MD5] = GNUTLS_DIG_MD5,
		[WGET_DIGTYPE_RMD160] = GNUTLS_DIG_RMD160,
		[WGET_DIGTYPE_SHA1] = GNUTLS_DIG_SHA1,
		[WGET_DIGTYPE_SHA224] = GNUTLS_DIG_SHA224,
		[WGET_DIGTYPE_SHA256] = GNUTLS_DIG_SHA256,
		[WGET_DIGTYPE_SHA384] = GNUTLS_DIG_SHA384,
		[WGET_DIGTYPE_SHA512] = GNUTLS_DIG_SHA512
};

/**
 * \param[in] algorithm One of the hashing algorithms returned by wget_hash_get_algorithm()
 * \param[in] text Input data to hash
 * \param[in] textlen Length of the input data
 * \param[in] digest Caller-supplied buffer where the output hash will be placed
 * \return Zero on success or a negative value on error
 *
 * Convenience function to hash the given data in a single call.
 *
 * The caller must ensure that the provided output buffer \p digest is large enough
 * to store the hash. A particular hash algorithm is guaranteed to always generate
 * the same amount of data (e.g. 512 bits) but different hash algorithms will output
 * different lengths of data. To get the output length of the chosen algorithm \p algorithm,
 * call wget_hash_get_len().
 *
 * \note This function's behavior depends on the underlying cryptographic engine libwget was compiled with.
 */
int wget_hash_fast(wget_digest_algorithm algorithm, const void *text, size_t textlen, void *digest)
{
	if ((unsigned) algorithm >= countof(algorithms))
		return WGET_E_INVALID;

	gnutls_digest_algorithm_t hashtype = algorithms[algorithm];
	if (hashtype == GNUTLS_DIG_UNKNOWN)
		return WGET_E_UNSUPPORTED;

	if (gnutls_hash_fast(algorithms[algorithm], text, textlen, digest) != 0)
		return WGET_E_UNKNOWN;

	return WGET_E_SUCCESS;
}

/**
 * \param[in] algorithm One of the hashing algorithms returned by wget_hash_get_algorithm()
 * \return The length of the output data generated by the algorithm
 *
 * Determines the output length of the given hashing algorithm.
 *
 * A particular hash algorithm is guaranteed to always generate
 * the same amount of data (e.g. 512 bits) but different hash algorithms will output
 * different lengths of data.
 */
int wget_hash_get_len(wget_digest_algorithm algorithm)
{
	if ((unsigned)algorithm < countof(algorithms))
		return gnutls_hash_get_len(algorithms[algorithm]);
	else
		return 0;
}

/**
 * \param[out] handle Caller-provided pointer to a ::wget_hash_hd structure where the handle to this
 * hashing primitive will be stored, needed in subsequent calls to wget_hash()
 * \param[in] algorithm One of the hashing algorithms returned by wget_hash_get_algorithm()
 * \return Zero on success or a negative value on error
 *
 * Initialize the cryptographic engine to compute hashes with the given hashing algorithm,
 * as well as the hashing algorithm itself.
 *
 * After this function returns, wget_hash() might be called as many times as desired.
 */
int wget_hash_init(wget_hash_hd **handle, wget_digest_algorithm algorithm)
{
	if ((unsigned)algorithm >= countof(algorithms))
		return WGET_E_INVALID;

	gnutls_digest_algorithm_t hashtype = algorithms[algorithm];
	if (hashtype == GNUTLS_DIG_UNKNOWN)
		return WGET_E_UNSUPPORTED;

	if (!(*handle = wget_malloc(sizeof(struct wget_hash_hd_st))))
		return WGET_E_MEMORY;

	if (gnutls_hash_init(&(*handle)->dig, algorithms[algorithm]) != 0) {
		xfree(*handle);
		return WGET_E_UNKNOWN;
	}

	return WGET_E_SUCCESS;
}

/**
 * \param[in] handle Handle to the hashing primitive returned by a subsequent call to wget_hash_init()
 * \param[in] text Input data
 * \param[in] textlen Length of the input data
 * \return Zero on success or a negative value on error
 *
 * Update the digest by adding additional input data to it. This method can be called
 * as many times as desired. Once finished, call wget_hash_deinit() to complete
 * the computation and get the resulting hash.
 */
int wget_hash(wget_hash_hd *handle, const void *text, size_t textlen)
{
	return gnutls_hash(handle->dig, text, textlen) == 0 ? 0 : -1;
}

/**
 * \param[in] handle Handle to the hashing primitive returned by a subsequent call to wget_hash_init()
 * \param[out] digest Caller-supplied buffer where the output hash will be placed.
 * \return 0 on success, < 0 on failure
 *
 * Complete the hash computation by performing final operations, such as padding,
 * and obtain the final result. The result will be placed in the caller-supplied
 * buffer \p digest. The caller must ensure that the provided output buffer \p digest
 * is large enough to store the hash. To get the output length of the chosen algorithm
 * \p algorithm, call wget_hash_get_len().
 */
int wget_hash_deinit(wget_hash_hd **handle, void *digest)
{
	gnutls_hash_deinit((*handle)->dig, digest);

	xfree(*handle);

	return WGET_E_SUCCESS;
}

#elif defined WITH_LIBWOLFCRYPT
#define WOLFSSL_SHA224
#define WOLFSSL_SHA384
#define WOLFSSL_SHA512
#define WC_NO_HARDEN
#include <wolfssl/wolfcrypt/hash.h>

struct wget_hash_hd_st {
	wc_HashAlg
		hash;
	enum wc_HashType
		type;
};

static const enum wc_HashType
	algorithms[] = {
		// [WGET_DIGTYPE_UNKNOWN] = WC_HASH_TYPE_NONE, // both values are 0
		// [WGET_DIGTYPE_MD2] = WC_HASH_TYPE_MD2, // not in wc_hashAlg
		[WGET_DIGTYPE_MD5] = WC_HASH_TYPE_MD5,
		[WGET_DIGTYPE_SHA1] = WC_HASH_TYPE_SHA,
		[WGET_DIGTYPE_SHA224] = WC_HASH_TYPE_SHA224,
		[WGET_DIGTYPE_SHA256] = WC_HASH_TYPE_SHA256,
		[WGET_DIGTYPE_SHA384] = WC_HASH_TYPE_SHA384,
		[WGET_DIGTYPE_SHA512] = WC_HASH_TYPE_SHA512,
};

int wget_hash_fast(wget_digest_algorithm algorithm, const void *text, size_t textlen, void *digest)
{
	if ((unsigned) algorithm >= countof(algorithms))
		return WGET_E_INVALID;

	enum wc_HashType hashtype = algorithms[algorithm];
	if (hashtype == WC_HASH_TYPE_NONE)
		return WGET_E_UNSUPPORTED;

	if (wc_Hash(hashtype, text, (unsigned) textlen, digest, wc_HashGetDigestSize(hashtype)) != 0)
		return WGET_E_UNKNOWN;

	return WGET_E_SUCCESS;
}

int wget_hash_get_len(wget_digest_algorithm algorithm)
{
	if ((unsigned) algorithm < countof(algorithms)) {
		int ret = wc_HashGetDigestSize(algorithms[algorithm]);
		if (ret > 0)
			return ret;
	}

	return 0;
}

int wget_hash_init(wget_hash_hd **handle, wget_digest_algorithm algorithm)
{
	if ((unsigned) algorithm >= countof(algorithms))
		return WGET_E_INVALID;

	enum wc_HashType hashtype = algorithms[algorithm];
	if (hashtype == WC_HASH_TYPE_NONE)
		return WGET_E_UNSUPPORTED;

	if (!(*handle = wget_malloc(sizeof(struct wget_hash_hd_st))))
		return WGET_E_MEMORY;

	if (wc_HashInit(&(*handle)->hash, hashtype) != 0) {
		xfree(*handle);
		return WGET_E_UNKNOWN;
	}

	(*handle)->type = hashtype;

	return WGET_E_SUCCESS;
}

int wget_hash(wget_hash_hd *handle, const void *text, size_t textlen)
{
	if (wc_HashUpdate(&handle->hash, handle->type, text, (unsigned) textlen) == 0)
		return WGET_E_SUCCESS;

	return WGET_E_UNKNOWN;
}

int wget_hash_deinit(wget_hash_hd **handle, void *digest)
{
	int rc = wc_HashFinal(&(*handle)->hash, (*handle)->type, digest);

	xfree(*handle);

	return rc == 0 ? rc : WGET_E_UNKNOWN;
}

#elif defined WITH_LIBCRYPTO
#include <openssl/evp.h>

typedef const EVP_MD *evp_md_func(void);

struct wget_hash_hd_st {
	EVP_MD_CTX
		*ctx;
};

static evp_md_func *
	algorithms[] = {
//		[WGET_DIGTYPE_UNKNOWN] = NULL,
//		[WGET_DIGTYPE_MD2]     = EVP_md2,
		[WGET_DIGTYPE_MD5]     = EVP_md5,
		[WGET_DIGTYPE_RMD160]  = EVP_ripemd160,
		[WGET_DIGTYPE_SHA1]    = EVP_sha1,
		[WGET_DIGTYPE_SHA224]  = EVP_sha224,
		[WGET_DIGTYPE_SHA256]  = EVP_sha256,
		[WGET_DIGTYPE_SHA384]  = EVP_sha384,
		[WGET_DIGTYPE_SHA512]  = EVP_sha512,
	};

int wget_hash_fast(wget_digest_algorithm algorithm, const void *text, size_t textlen, void *digest)
{
	if ((unsigned) algorithm >= countof(algorithms))
		return WGET_E_INVALID;

	evp_md_func *evp = algorithms[algorithm];
	if (!evp)
		return WGET_E_UNSUPPORTED;

	if (EVP_Digest(text, textlen, digest, NULL, evp(), NULL) == 0)
		return WGET_E_UNKNOWN;

	return WGET_E_SUCCESS;
}

int wget_hash_get_len(wget_digest_algorithm algorithm)
{
	evp_md_func *evp;

	if ((unsigned) algorithm >= countof(algorithms)
		|| (evp = algorithms[algorithm]) == NULL)
		return 0;

	return EVP_MD_size(evp());
}

int wget_hash_init(wget_hash_hd **handle, wget_digest_algorithm algorithm)
{
	evp_md_func *evp;

	if ((unsigned) algorithm >= countof(algorithms))
		return WGET_E_UNSUPPORTED;

	if ((evp = algorithms[algorithm]) == NULL)
		return WGET_E_UNSUPPORTED;

	if (!(*handle = wget_malloc(sizeof(struct wget_hash_hd_st))))
		return WGET_E_MEMORY;

	if (!((*handle)->ctx = EVP_MD_CTX_new())) {
		xfree(*handle);
		return WGET_E_UNKNOWN;
	}

	if (EVP_DigestInit_ex((*handle)->ctx, evp(), NULL))
		return WGET_E_SUCCESS;

	EVP_MD_CTX_free((*handle)->ctx);
	xfree(*handle);

	return WGET_E_UNKNOWN;
}

int wget_hash(wget_hash_hd *handle, const void *text, size_t textlen)
{
	if (EVP_DigestUpdate(handle->ctx, text, textlen))
		return WGET_E_SUCCESS;

	return WGET_E_INVALID;
}

int wget_hash_deinit(wget_hash_hd **handle, void *digest)
{
	EVP_DigestFinal_ex((*handle)->ctx, digest, NULL);

	EVP_MD_CTX_free((*handle)->ctx);
	xfree(*handle);

	return WGET_E_SUCCESS;
}

#elif defined WITH_LIBNETTLE
#include <nettle/nettle-meta.h>
#include <nettle/md2.h>
#include <nettle/md5.h>
#include <nettle/ripemd160.h>
#include <nettle/sha2.h>

struct wget_hash_hd_st {
	const struct nettle_hash
		*hash;
	void
		*context;
};

static const struct nettle_hash *
	algorithms[WGET_DIGTYPE_MAX] = {
//		[WGET_DIGTYPE_UNKNOWN] = NULL,
		[WGET_DIGTYPE_MD2] = &nettle_md2,
		[WGET_DIGTYPE_MD5] = &nettle_md5,
#ifdef RIPEMD160_DIGEST_SIZE
		[WGET_DIGTYPE_RMD160] = &nettle_ripemd160,
#endif
		[WGET_DIGTYPE_SHA1] = &nettle_sha1,
#ifdef SHA224_DIGEST_SIZE
		[WGET_DIGTYPE_SHA224] = &nettle_sha224,
#endif
#ifdef SHA256_DIGEST_SIZE
		[WGET_DIGTYPE_SHA256] = &nettle_sha256,
#endif
#ifdef SHA384_DIGEST_SIZE
		[WGET_DIGTYPE_SHA384] = &nettle_sha384,
#endif
#ifdef SHA512_DIGEST_SIZE
		[WGET_DIGTYPE_SHA512] = &nettle_sha512,
#endif
};

int wget_hash_fast(wget_digest_algorithm algorithm, const void *text, size_t textlen, void *digest)
{
	wget_hash_hd *dig;
	int rc;

	if ((rc = wget_hash_init(&dig, algorithm)) == 0) {
		rc = wget_hash(dig, text, textlen);
		wget_hash_deinit(&dig, digest);
	}

	return rc;
}

int wget_hash_get_len(wget_digest_algorithm algorithm)
{
	if ((unsigned)algorithm < countof(algorithms))
		return algorithms[algorithm]->digest_size;
	else
		return 0;
}

int wget_hash_init(wget_hash_hd **handle, wget_digest_algorithm algorithm)
{
	if ((unsigned)algorithm >= countof(algorithms))
		return WGET_E_INVALID;

	if (!algorithms[algorithm])
		return WGET_E_UNSUPPORTED;

	wget_hash_hd *h;

	if (!(h = wget_malloc(sizeof(struct wget_hash_hd_st))))
		return WGET_E_MEMORY;

	h->hash = algorithms[algorithm];

	if (!(h->context = wget_malloc(h->hash->context_size))) {
		xfree(h);
		return WGET_E_MEMORY;
	}

	h->hash->init(h->context);
	*handle = h;

	return WGET_E_SUCCESS;
}

int wget_hash(wget_hash_hd *handle, const void *text, size_t textlen)
{
	handle->hash->update(handle->context, textlen, text);
	return WGET_E_SUCCESS;
}

int wget_hash_deinit(wget_hash_hd **handle, void *digest)
{
	(*handle)->hash->digest((*handle)->context, (*handle)->hash->digest_size, digest);
	xfree((*handle)->context);
	xfree(*handle);
	return WGET_E_SUCCESS;
}

#elif defined WITH_GCRYPT
#ifdef HAVE_GCRYPT_H
  #include <gcrypt.h>
#endif

struct wget_hash_hd_st {
	int
		algorithm;
	gcry_md_hd_t
		context;
};

static const int algorithms[] = {
//	[WGET_DIGTYPE_UNKNOWN] = GCRY_MD_NONE,
	[WGET_DIGTYPE_MD2] = GCRY_MD_MD2,
	[WGET_DIGTYPE_MD5] = GCRY_MD_MD5,
	[WGET_DIGTYPE_RMD160] = GCRY_MD_RMD160,
	[WGET_DIGTYPE_SHA1] = GCRY_MD_SHA1,
	[WGET_DIGTYPE_SHA224] = GCRY_MD_SHA224,
	[WGET_DIGTYPE_SHA256] = GCRY_MD_SHA256,
	[WGET_DIGTYPE_SHA384] = GCRY_MD_SHA384,
	[WGET_DIGTYPE_SHA512] = GCRY_MD_SHA512
};

int wget_hash_fast(wget_digest_algorithm algorithm, const void *text, size_t textlen, void *digest)
{
	wget_hash_hd *dig;
	int rc;

	if ((rc = wget_hash_init(&dig, algorithm)) == 0) {
		rc = wget_hash(dig, text, textlen);
		wget_hash_deinit(&dig, digest);
	}

	return rc;
}

int wget_hash_get_len(wget_digest_algorithm algorithm)
{
	if ((unsigned)algorithm < countof(algorithms))
		return gcry_md_get_algo_dlen(algorithms[algorithm]);
	else
		return 0;
}

int wget_hash_init(wget_hash_hd **handle, wget_digest_algorithm algorithm)
{
	if ((unsigned)algorithm >= countof(algorithms))
		return WGET_E_INVALID;

	if (!algorithms[algorithm])
		return WGET_E_UNSUPPORTED;

	wget_hash_hd *h;

	if (!(h = wget_malloc(sizeof(struct wget_hash_hd_st))))
		return WGET_E_MEMORY;

	h->algorithm = algorithms[algorithm];
	gcry_md_open(&h->context, h->algorithm, 0);

	*handle = h;

	return WGET_E_SUCCESS;
}

int wget_hash(wget_hash_hd *handle, const void *text, size_t textlen)
{
	gcry_md_write(handle->context, text, textlen);
	return 0;
}

int wget_hash_deinit(wget_hash_hd **handle, void *digest)
{
	gcry_md_final((*handle)->context);
	void *ret = gcry_md_read((*handle)->context, (*handle)->algorithm);
	memcpy(digest, ret, gcry_md_get_algo_dlen((*handle)->algorithm));
	gcry_md_close((*handle)->context);
	xfree(*handle);

	return WGET_E_SUCCESS;
}

#else // use the gnulib functions

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wundef"
#include "md2.h"
#include "md5.h"
#include "sha1.h"
#include "sha256.h"
#include "sha512.h"
#pragma GCC diagnostic pop

typedef void (*_hash_init_t)(void *);
typedef void (*_hash_process_t)(const void *, size_t, void *);
typedef void (*_hash_finish_t)(void *, void *);
typedef void (*_hash_read_t)(const void *, void *);

static struct _algorithm {
	_hash_init_t init;
	_hash_process_t process;
	_hash_finish_t finish;
	_hash_read_t read;
	size_t ctx_len;
	size_t digest_len;
} _algorithm[WGET_DIGTYPE_MAX] = {
	[WGET_DIGTYPE_MD2] = {
		(_hash_init_t)md2_init_ctx,
		(_hash_process_t)md2_process_bytes,
		(_hash_finish_t)md2_finish_ctx,
		(_hash_read_t)md2_read_ctx,
		sizeof(struct md2_ctx),
		MD2_DIGEST_SIZE
	},
	[WGET_DIGTYPE_MD5] = {
		(_hash_init_t)md5_init_ctx,
		(_hash_process_t)md5_process_bytes,
		(_hash_finish_t)md5_finish_ctx,
		(_hash_read_t)md5_read_ctx,
		sizeof(struct md5_ctx),
		MD5_DIGEST_SIZE
	},
	[WGET_DIGTYPE_SHA1] = {
		(_hash_init_t)sha1_init_ctx,
		(_hash_process_t)sha1_process_bytes,
		(_hash_finish_t)sha1_finish_ctx,
		(_hash_read_t)sha1_read_ctx,
		sizeof(struct sha1_ctx),
		SHA1_DIGEST_SIZE
	},
	[WGET_DIGTYPE_SHA224] = {
		(_hash_init_t)sha224_init_ctx,
		(_hash_process_t)sha256_process_bytes, // sha256 is intentional
		(_hash_finish_t)sha224_finish_ctx,
		(_hash_read_t)sha224_read_ctx,
		sizeof(struct sha256_ctx), // sha256 is intentional
		SHA224_DIGEST_SIZE
	},
	[WGET_DIGTYPE_SHA256] = {
		(_hash_init_t)sha256_init_ctx,
		(_hash_process_t)sha256_process_bytes,
		(_hash_finish_t)sha256_finish_ctx,
		(_hash_read_t)sha256_read_ctx,
		sizeof(struct sha256_ctx),
		SHA256_DIGEST_SIZE
	},
	[WGET_DIGTYPE_SHA384] = {
		(_hash_init_t)sha384_init_ctx,
		(_hash_process_t)sha512_process_bytes, // sha512 is intentional
		(_hash_finish_t)sha384_finish_ctx,
		(_hash_read_t)sha384_read_ctx,
		sizeof(struct sha512_ctx), // sha512 is intentional
		SHA384_DIGEST_SIZE
	},
	[WGET_DIGTYPE_SHA512] = {
		(_hash_init_t)sha512_init_ctx,
		(_hash_process_t)sha512_process_bytes,
		(_hash_finish_t)sha512_finish_ctx,
		(_hash_read_t)sha512_read_ctx,
		sizeof(struct sha512_ctx),
		SHA512_DIGEST_SIZE
	}
};

struct wget_hash_hd_st {
	const struct _algorithm
		*algorithm;
	void
		*context;
};

int wget_hash_fast(wget_digest_algorithm algorithm, const void *text, size_t textlen, void *digest)
{
	wget_hash_hd *dig;
	int rc;

	if ((rc = wget_hash_init(&dig, algorithm)) == WGET_E_SUCCESS) {
		rc = wget_hash(dig, text, textlen);
		wget_hash_deinit(&dig, digest);
	}

	return rc;
}

int wget_hash_get_len(wget_digest_algorithm algorithm)
{
	if ((unsigned)algorithm < countof(_algorithm))
		return (int) _algorithm[algorithm].digest_len;
	else
		return 0;
}

int wget_hash_init(wget_hash_hd **handle, wget_digest_algorithm algorithm)
{
	if ((unsigned)algorithm >= countof(_algorithm))
		return WGET_E_INVALID;

	if (!_algorithm[algorithm].ctx_len)
		return WGET_E_UNSUPPORTED;

	wget_hash_hd *h;

	if (!(h = wget_malloc(sizeof(struct wget_hash_hd_st))))
		return WGET_E_MEMORY;

	h->algorithm = &_algorithm[algorithm];

	if (!(h->context = wget_malloc(h->algorithm->ctx_len))) {
		xfree(h);
		return WGET_E_MEMORY;
	}

	h->algorithm->init(h->context);
	*handle = h;

	return WGET_E_SUCCESS;
}

int wget_hash(wget_hash_hd *handle, const void *text, size_t textlen)
{
	handle->algorithm->process(text, textlen, handle->context);
	return 0;
}

int wget_hash_deinit(wget_hash_hd **handle, void *digest)
{
	(*handle)->algorithm->finish((*handle)->context, digest);
	xfree((*handle)->context);
	xfree(*handle);

	return WGET_E_SUCCESS;
}
#endif

/**
 * \param[in] hashname Name of the hashing algorithm. See wget_hash_get_algorithm()
 * \param[in] fd File descriptor for the target file
 * \param[out] digest_hex caller-supplied buffer that will contain the resulting hex string
 * \param[in] digest_hex_size Length of \p digest_hex
 * \param[in] offset File offset to start hashing at
 * \param[in] length Number of bytes to hash, starting from \p offset. Zero will hash up to the end of the file
 * \return 0 on success or -1 in case of failure
 *
 * Compute the hash of the contents of the target file and return its hex representation.
 *
 * This function will encode the resulting hash in a string of hex digits, and
 * place that string in the user-supplied buffer \p digest_hex.
 */
int wget_hash_file_fd(const char *hashname, int fd, char *digest_hex, size_t digest_hex_size, off_t offset, off_t length)
{
	wget_digest_algorithm algorithm;
	int ret = WGET_E_UNKNOWN;
	struct stat st;

	if (digest_hex_size)
		*digest_hex=0;

	if (fd == -1 || fstat(fd, &st) != 0)
		return WGET_E_IO;

	if (length == 0)
		length = st.st_size;

	if (offset + length > st.st_size)
		return WGET_E_INVALID;

	debug_printf("%s hashing pos %llu, length %llu...\n", hashname, (unsigned long long)offset, (unsigned long long)length);

	if ((algorithm = wget_hash_get_algorithm(hashname)) != WGET_DIGTYPE_UNKNOWN) {
		unsigned char digest[wget_hash_get_len(algorithm)];

#ifdef HAVE_MMAP
		char *buf = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, offset);

		if (buf != MAP_FAILED) {
			if (wget_hash_fast(algorithm, buf, length, digest) == 0) {
				wget_memtohex(digest, sizeof(digest), digest_hex, digest_hex_size);
				ret = WGET_E_SUCCESS;
			}
			munmap(buf, length);
		} else {
#endif
			// Fallback to read
			ssize_t nbytes = 0;
			wget_hash_hd *dig;
			char tmp[65536];

			if ((ret = wget_hash_init(&dig, algorithm))) {
				error_printf(_("%s: Hash init failed for type '%s': %s\n"), __func__, hashname, wget_strerror(ret));
				return ret;
			}

			while (length > 0 && (nbytes = read(fd, tmp, sizeof(tmp))) > 0) {
				if ((ret = wget_hash(dig, tmp, nbytes))) {
					error_printf(_("%s: Hash update failed: %s\n"), __func__, wget_strerror(ret));
					return ret;
				}

				if (nbytes <= length)
					length -= nbytes;
				else
					length = 0;
			}

			if ((ret = wget_hash_deinit(&dig, digest))) {
				error_printf(_("%s: Hash finalization failed: %s\n"), __func__, wget_strerror(ret));
				return ret;
			}

			if (nbytes < 0) {
				error_printf(_("%s: Failed to read %llu bytes\n"), __func__, (unsigned long long)length);
				return WGET_E_IO;
			}

			wget_memtohex(digest, sizeof(digest), digest_hex, digest_hex_size);
			ret = WGET_E_SUCCESS;
#ifdef HAVE_MMAP
		}
#endif
	}

	return ret;
}

/**
 * \param[in] hashname Name of the hashing algorithm. See wget_hash_get_algorithm()
 * \param[in] fname Target file name
 * \param[out] digest_hex Caller-supplied buffer that will contain the resulting hex string
 * \param[in] digest_hex_size Length of \p digest_hex
 * \param[in] offset File offset to start hashing at
 * \param[in] length Number of bytes to hash, starting from \p offset.  Zero will hash up to the end of the file
 * \return 0 on success or -1 in case of failure
 *
 * Compute the hash of the contents of the target file starting from \p offset and up to \p length bytes
 * and return its hex representation.
 *
 * This function will encode the resulting hash in a string of hex digits, and
 * place that string in the user-supplied buffer \p digest_hex.
 */
int wget_hash_file_offset(const char *hashname, const char *fname, char *digest_hex, size_t digest_hex_size, off_t offset, off_t length)
{
	int fd, ret;

	if ((fd = open(fname, O_RDONLY|O_BINARY)) == -1) {
		if (digest_hex_size)
			*digest_hex=0;
		return 0;
	}

	ret = wget_hash_file_fd(hashname, fd, digest_hex, digest_hex_size, offset, length);
	close(fd);

	return ret;
}

/**
 * \param[in] hashname Name of the hashing algorithm. See wget_hash_get_algorithm()
 * \param[in] fname Target file name
 * \param[out] digest_hex Caller-supplied buffer that will contain the resulting hex string
 * \param[in] digest_hex_size Length of \p digest_hex
 * \return 0 on success or -1 in case of failure
 *
 * Compute the hash of the contents of the target file and return its hex representation.
 *
 * This function will encode the resulting hash in a string of hex digits, and
 * place that string in the user-supplied buffer \p digest_hex.
 */
int wget_hash_file(const char *hashname, const char *fname, char *digest_hex, size_t digest_hex_size)
{
	return wget_hash_file_offset(hashname, fname, digest_hex, digest_hex_size, 0, 0);
}

/**@}*/
