#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <rhash.h>

typedef unsigned long long ulonglong;

/* helper macros and functions */
#define BASE32_LENGTH(size) (((size) * 8 + 4) / 5)
#define BASE64_LENGTH(size) ((((size) + 2) / 3) * 4)

void verify_single_bit_hash_id(unsigned hash_id, CV* cv)
{
	const char* error;
	const GV *gv;
	const char *func_name;

	if(0 == (hash_id & RHASH_ALL_HASHES)) {
		error = "%s: unknown hash hash_id = %d";
	} else if(0 != (hash_id & (hash_id - 1))) {
		error = "%s: hash_id is not a single bit: %d";
	} else {
		return; /* success */
	}

	gv = CvGV(cv);
	func_name = (gv ? GvNAME(gv) : "Rhash");
	croak(error, func_name, hash_id);
}

/* allocate a perl string scalar variable, containing str_len + 1 bytes */
SV * allocate_string_buffer(STRLEN str_len)
{
	SV * sv = newSV(str_len); /* allocates (str_len + 1) bytes */
	SvPOK_only(sv);
	SvCUR_set(sv, str_len);
	return sv;
}

MODULE = Crypt::Rhash      PACKAGE = Crypt::Rhash

##############################################################################
# Initialize LibRHash in the module bootstrap function

BOOT:
	rhash_library_init();

##############################################################################
# perl bindings for Hi-level functions

SV *
rhash_msg_raw(hash_id, message)
		unsigned	hash_id
	PROTOTYPE: $$
	PREINIT:
		STRLEN length;
		unsigned char out[264];
		int res;
	INPUT:
		char* message = SvPV(ST(1), length);
	CODE:
		verify_single_bit_hash_id(hash_id, cv);
		res = rhash_msg(hash_id, message, length, out);
		if(res < 0) {
			croak("%s: %s", "rhash_msg_raw", strerror(errno));
		}
		RETVAL = newSVpv((char*)out, rhash_get_digest_size(hash_id));
	OUTPUT:
		RETVAL

SV *
rhash_file_raw(hash_id, filepath)
		unsigned hash_id
		char * filepath
	PROTOTYPE: $$
	PREINIT:
		int res;
		unsigned char out[264];
	CODE:
		verify_single_bit_hash_id(hash_id, cv);
		res = rhash_file(hash_id, filepath, out);
		if(res < 0) {
			croak("%s: %s: %s", "rhash_file", filepath, strerror(errno));
		}
		RETVAL = newSVpv((char*)out, rhash_get_digest_size(hash_id));
	OUTPUT:
		RETVAL

##############################################################################
# perl bindings for Low-level functions

rhash_context *
rhash_init(hash_id)
		unsigned hash_id
	PROTOTYPE: $

int
rhash_update(ctx, message)
		rhash_context * ctx
	PROTOTYPE: $$
	PREINIT:
		STRLEN length;
	INPUT:
		char* message = SvPV(ST(1), length);
	CODE:
		RETVAL = rhash_update(ctx, message, length);
	OUTPUT:
		RETVAL

int
rhash_final(ctx)
		rhash_context * ctx
	PROTOTYPE: $
	CODE:
		RETVAL = rhash_final(ctx, 0);
	OUTPUT:
		RETVAL

void
rhash_reset(ctx)
		rhash_context * ctx
	PROTOTYPE: $

void
rhash_free(ctx)
		rhash_context * ctx
	PROTOTYPE: $

SV *
rhash_print(ctx, hash_id, flags = 0)
		rhash_context * ctx
		unsigned hash_id
		int flags
	PROTOTYPE: $$;$
	PREINIT:
		int len;
		char out[264];
	CODE:
		if(hash_id != 0) verify_single_bit_hash_id(hash_id, cv);

		len = rhash_print(out, ctx, hash_id, flags);

		/* set exact length to support raw output (RHPR_RAW) */
		RETVAL = newSVpv(out, len);
	OUTPUT:
		RETVAL

SV *
rhash_print_magnet(ctx, filename, hash_mask)
		rhash_context * ctx
		SV * filename
		SV * hash_mask
	PROTOTYPE: $;$$
	PREINIT:
		/* process undefined values */
		char * name = (SvOK(filename) ? SvPV_nolen(filename) : 0);
		unsigned mask = (SvOK(hash_mask) ? (unsigned)SvUV(hash_mask) : RHASH_ALL_HASHES);
		size_t buf_size;
	CODE:
		/* allocate a string buffer and print magnet link into it */
		buf_size = rhash_print_magnet(0, name, ctx, mask, RHPR_FILESIZE);
		RETVAL = allocate_string_buffer(buf_size - 1);
		rhash_print_magnet(SvPVX(RETVAL), name, ctx, mask, RHPR_FILESIZE);

		/* note: length(RETVAL) = (buf_size - 1),
		 * so the following call is not required:
		 * SvCUR_set(RETVAL, strlen(SvPVX(RETVAL))); */
	OUTPUT:
		RETVAL

unsigned
rhash_get_hash_id(ctx)
		rhash_context * ctx
	PROTOTYPE: $
	CODE:
		RETVAL = ctx->hash_id;
	OUTPUT:
		RETVAL

ulonglong
rhash_get_hashed_length(ctx)
		rhash_context * ctx
	PROTOTYPE: $
	CODE:
		RETVAL = ctx->msg_size;
	OUTPUT:
		RETVAL

##############################################################################
# Hash information functions

int
count()
	CODE:
		RETVAL = rhash_count();
	OUTPUT:
		RETVAL

int
is_base32(hash_id)
		unsigned hash_id
	PROTOTYPE: $
	CODE:
		RETVAL = rhash_is_base32(hash_id);
	OUTPUT:
		RETVAL

int
get_digest_size(hash_id)
		unsigned hash_id
	PROTOTYPE: $
	CODE:
		RETVAL = rhash_get_digest_size(hash_id);
	OUTPUT:
		RETVAL

int
get_hash_length(hash_id)
		unsigned hash_id
	PROTOTYPE: $
	CODE:
		RETVAL = rhash_get_hash_length(hash_id);
	OUTPUT:
		RETVAL

const char *
get_name(hash_id)
		unsigned hash_id
	PROTOTYPE: $
	CODE:
		RETVAL = rhash_get_name(hash_id);
	OUTPUT:
		RETVAL

##############################################################################
# Hash printing functions

##############################################################################
# Hash conversion functions

SV *
raw2hex(bytes)
	PROTOTYPE: $
	PREINIT:
		STRLEN size;
	INPUT:
		unsigned char * bytes = (unsigned char*)SvPV(ST(0), size);
	CODE:
		RETVAL = allocate_string_buffer(size * 2);
		rhash_print_bytes(SvPVX(RETVAL), bytes, size, RHPR_HEX);
	OUTPUT:
		RETVAL

SV *
raw2base32(bytes)
	PROTOTYPE: $
	PREINIT:
		STRLEN size;
	INPUT:
		unsigned char * bytes = (unsigned char*)SvPV(ST(0), size);
	CODE:
		RETVAL = allocate_string_buffer(BASE32_LENGTH(size));
		rhash_print_bytes(SvPVX(RETVAL), bytes, size, RHPR_BASE32);
	OUTPUT:
		RETVAL

SV *
raw2base64(bytes)
	PROTOTYPE: $
	PREINIT:
		STRLEN size;
	INPUT:
		unsigned char * bytes = (unsigned char*)SvPV(ST(0), size);
	CODE:
		RETVAL = allocate_string_buffer(BASE64_LENGTH(size));
		rhash_print_bytes(SvPVX(RETVAL), bytes, size, RHPR_BASE64);
	OUTPUT:
		RETVAL

# rhash_print_bytes should not be used directly
#SV *
#rhash_print_bytes(bytes, flags)
#	PROTOTYPE: $;$
#	PREINIT:
#		STRLEN size;
#	INPUT:
#		unsigned char * bytes = SvPV(ST(0), size);
#		int flags
#	CODE:
#		RETVAL = allocate_string_buffer(size * 2);
#		rhash_print_bytes(SvPVX(RETVAL), bytes, size, flags);
#	OUTPUT:
#		RETVAL

#rhash_uptr_t
#rhash_transmit(msg_id, dst, ldata, rdata)
#	unsigned msg_id
#	void * dst
#	rhash_uptr_t ldata
#	rhash_uptr_t rdata

##############################################################################
# BTIH / BitTorrent support functions

void
rhash_bt_add_filename(ctx, filename, filesize)
		rhash_context * ctx
		char * filename
		ulonglong filesize
	PROTOTYPE: $$$
	CODE:
		rhash_transmit(RMSG_BT_ADD_FILE, ctx, RHASH_STR2UPTR(filename), (rhash_uptr_t)&filesize);

void
rhash_bt_set_piece_length(ctx, piece_length)
		rhash_context * ctx
		unsigned piece_length
	PROTOTYPE: $$
	CODE:
		rhash_transmit(RMSG_BT_SET_PIECE_LENGTH, ctx, RHASH_STR2UPTR(piece_length), 0);

void
rhash_bt_set_private(ctx)
		rhash_context * ctx
	PROTOTYPE: $
	CODE:
		rhash_transmit(RMSG_BT_SET_OPTIONS, ctx, RHASH_BT_OPT_PRIVATE, 0);

SV *
rhash_bt_get_torrent_text(ctx)
		rhash_context * ctx
	PROTOTYPE: $
	PREINIT:
		size_t len;
		char *text;
	CODE:
		len = rhash_transmit(RMSG_BT_GET_TEXT, ctx, RHASH_STR2UPTR(&text), 0);
		if(len == RHASH_ERROR) {
			XSRETURN_UNDEF;
		}
		RETVAL = newSVpv(text, len);
	OUTPUT:
		RETVAL
