// Copyright (C) 2002 Andrew Tridgell
// Copyright (C) 2010-2019 Joel Rosdahl
//
// 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 3 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 Street, Fifth Floor, Boston, MA 02110-1301 USA

#include "ccache.h"
#include "hash.h"
#include "mdfour.h"

#define HASH_DELIMITER "\000cCaChE"

struct hash {
	struct mdfour md;
	FILE *debug_binary;
	FILE *debug_text;
};

static void
do_hash_buffer(struct hash *hash, const void *s, size_t len)
{
	assert(s);

	mdfour_update(&hash->md, (const unsigned char *)s, len);
	if (len > 0 && hash->debug_binary) {
		(void) fwrite(s, 1, len, hash->debug_binary);
	}
}

static void
do_debug_text(struct hash *hash, const void *s, size_t len)
{
	if (len > 0 && hash->debug_text) {
		(void) fwrite(s, 1, len, hash->debug_text);
	}
}

struct hash *
hash_init(void)
{
	struct hash *hash = malloc(sizeof(struct hash));
	mdfour_begin(&hash->md);
	hash->debug_binary = NULL;
	hash->debug_text = NULL;
	return hash;
}

struct hash *
hash_copy(struct hash *hash)
{
	struct hash *result = malloc(sizeof(struct hash));
	result->md = hash->md;
	result->debug_binary = NULL;
	result->debug_text = NULL;
	return result;
}

void hash_free(struct hash *hash)
{
	free(hash);
}

void hash_enable_debug(
	struct hash *hash, const char *section_name,
	FILE *debug_binary, FILE *debug_text)
{
	hash->debug_binary = debug_binary;
	hash->debug_text = debug_text;

	do_debug_text(hash, "=== ", 4);
	do_debug_text(hash, section_name, strlen(section_name));
	do_debug_text(hash, " ===\n", 5);
}

size_t
hash_input_size(struct hash *hash)
{
	return hash->md.totalN + hash->md.tail_len;
}

void
hash_buffer(struct hash *hash, const void *s, size_t len)
{
	do_hash_buffer(hash, s, len);
	do_debug_text(hash, s, len);
}

char *
hash_result(struct hash *hash)
{
	unsigned char sum[16];

	hash_result_as_bytes(hash, sum);
	return format_hash_as_string(sum, hash_input_size(hash));
}

void
hash_result_as_bytes(struct hash *hash, unsigned char *out)
{
	mdfour_result(&hash->md, out);
}

bool
hash_equal(struct hash *hash1, struct hash *hash2)
{
	unsigned char sum1[16];
	hash_result_as_bytes(hash1, sum1);
	unsigned char sum2[16];
	hash_result_as_bytes(hash2, sum2);
	return memcmp(sum1, sum2, sizeof(sum1)) == 0;
}

void
hash_delimiter(struct hash *hash, const char *type)
{
	do_hash_buffer(hash, HASH_DELIMITER, sizeof(HASH_DELIMITER));
	do_hash_buffer(hash, type, strlen(type) + 1); // Include NUL.
	do_debug_text(hash, "### ", 4);
	do_debug_text(hash, type, strlen(type));
	do_debug_text(hash, "\n", 1);
}

void
hash_string(struct hash *hash, const char *s)
{
	hash_string_buffer(hash, s, strlen(s));
}

void
hash_string_buffer(struct hash *hash, const char *s, int length)
{
	hash_buffer(hash, s, length);
	do_debug_text(hash, "\n", 1);
}

void
hash_int(struct hash *hash, int x)
{
	do_hash_buffer(hash, (char *)&x, sizeof(x));

	char buf[16];
	snprintf(buf, sizeof(buf), "%d", x);
	do_debug_text(hash, buf, strlen(buf));
	do_debug_text(hash, "\n", 1);
}

bool
hash_fd(struct hash *hash, int fd)
{
	char buf[READ_BUFFER_SIZE];
	ssize_t n;

	while ((n = read(fd, buf, sizeof(buf))) != 0) {
		if (n == -1 && errno != EINTR) {
			break;
		}
		if (n > 0) {
			do_hash_buffer(hash, buf, n);
			do_debug_text(hash, buf, n);
		}
	}
	return n == 0;
}

bool
hash_file(struct hash *hash, const char *fname)
{
	int fd = open(fname, O_RDONLY|O_BINARY);
	if (fd == -1) {
		cc_log("Failed to open %s: %s", fname, strerror(errno));
		return false;
	}

	bool ret = hash_fd(hash, fd);
	close(fd);
	return ret;
}
