/*
 * Basically, the XDR encoding works in words of 32 bits with bytes in big
 * endian order (that is, high order byte comes first in memory). Smaller
 * quantities (char, short) are then padded with zero bytes. Larger quantities
 * (that is, double precision floating point numbers) take 2 words for a total
 * of 64 bites.
 * 
 * The stdint.h defines several common useful int types with names that are self
 * explanatory and helps resolving ambiguities (xdr libraries should have used
 * them just from the start, it would helped very much):
 * 
 * signed:     int8_t,  int16_t,  int32_t,  int64_t
 * unsigned:  uint8_t, uint16_t, uint32_t, uint64_t
 */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include "../../../src/dis/dis/xdr.h"
#include "../../../src/util/memory.h"

static int err;


static void printbuf(char *b, int blen)
{
	int i;
	
	for(i = 0; i < blen; i++)
		printf(" 0x%02x", (uint8_t) b[i]);
}


static void cmp(int line, char *got, int gotlen, char *exp, int explen)
{
	if( gotlen == explen && memcmp(got, exp, explen) == 0 )
		return;
	
	printf("test failed in line %d:", line);
	printf("\n    got: ");  printbuf(got, gotlen);
	printf("\n    exp: ");  printbuf(exp, explen);
	printf("\n");
	err++;
}


/**
 * Test memory allocation roll-back on decoding error.
 */
static void decoding_error_recovery_test()
{
	char      buf[99];
	xdr_Type *xdr;
	int       buflen;
	
	/*
	 * First, create a test packet:
	 */
	xdr = xdr_new(buf, sizeof(buf), xdr_ENCODE);
	char *s_in = "ABCD";
	assert( xdr_putBytes(xdr, s_in, strlen(s_in)) );
	//int32_t arr_in[] = {1, 2};
	int32_t *arr_in = (int32_t[]){1, 2};
	assert( xdr_var_array(xdr, (void **)&arr_in, 2, sizeof(int32_t), (xdr_Callback) xdr_int32) );
	buflen = xdr_getpos(xdr);
	xdr_free(xdr);
	
	/*
	 * ...then decode that packet:
	 */
	
	xdr = xdr_new(buf, buflen, xdr_DECODE);
	char *s_out;
	assert( xdr_getBytesAllocated(xdr, (void **)&s_out, 4, 1) );
	assert( strcmp(s_out, s_in) == 0 );
	int32_t *arr_out;
	assert( xdr_var_array(xdr, (void **)&arr_out, 2, sizeof(int32_t), (xdr_Callback) xdr_int32) );
	assert( arr_out[0] == 1 );
	/*
	 * Read one more data that does not exist in the packet, so intentionally
	 * triggering an error:
	 */
	int32_t does_not_exist_in_packet;
	assert( ! xdr_int32(xdr, &does_not_exist_in_packet) );
	// ... causing the expected "premature end of the buffer":
	//printf("Expected error: %s\n", xdr_getErrorDescription(xdr) );
	xdr_free(xdr);
	/*
	 * Now all the dynamically allocated memory should have been released and
	 * the program should exit with no memory leaks whatsoever.
	 */
}


int main(int argc, char** argv)
{
	char      buf[99];
	xdr_Type *xdr;
	int       buflen;
	
	//printf("sizeof(long) = %ld\n", sizeof(long));
	
	uint8_t  uc;
	memset(buf, 255, sizeof(buf));
	xdr = xdr_new(buf, sizeof(buf), xdr_ENCODE);
	uc = 0x12;
	assert( xdr_char(xdr, (char *) &uc) );
	uc = 0x34;
	assert( xdr_char(xdr, (char *) &uc ) );
	buflen = xdr_getpos(xdr);
	cmp(__LINE__, buf, buflen, "\x00\x00\x00\x12\x00\x00\x00\x34", 8);
	xdr_free(xdr);
	// now decoding:
	xdr = xdr_new(buf, buflen, xdr_DECODE);
	assert( xdr_char(xdr, (char *) &uc) );
	assert( uc == 0x12 );
	assert( xdr_char(xdr, (char *) &uc) );
	assert( uc == 0x34 );
	assert( ! xdr_char(xdr, (char *) &uc) ); // reading beyond buffer end must fail
	xdr_free(xdr);
	
	
	uint32_t   ui;
	memset(buf, 255, sizeof(buf));
	xdr = xdr_new(buf, sizeof(buf), xdr_ENCODE);
	ui = 0x01020304;
	assert( xdr_uint32(xdr, &ui ) );
	buflen = xdr_getpos(xdr);
	cmp(__LINE__, buf, buflen, "\x01\x02\x03\x04", 4);
	xdr_free(xdr);
	// now decoding:
	xdr = xdr_new(buf, buflen, xdr_DECODE);
	assert( xdr_uint32(xdr, &ui) );
	assert( ui == 0x01020304 );
	assert( ! xdr_char(xdr, (char *) &uc) ); // reading beyond buffer end must fail
	xdr_free(xdr);
	
	// same as above, but "negative" int
	memset(buf, 255, sizeof(buf));
	xdr = xdr_new(buf, sizeof(buf), xdr_ENCODE);
	ui = 0x10203040; // note high bit set
	assert( xdr_uint32(xdr, &ui ) );
	buflen = xdr_getpos(xdr);
	cmp(__LINE__, buf, buflen, "\x10\x20\x30\x40", 4);
	xdr_free(xdr);
	// now decoding:
	xdr = xdr_new(buf, buflen, xdr_DECODE);
	assert( xdr_uint32(xdr, &ui) );
	assert( ui == 0x10203040 );
	assert( ! xdr_char(xdr, (char *) &uc) ); // reading beyond buffer end must fail
	xdr_free(xdr);
	
	float f = 7.0;
	memset(buf, 255, sizeof(buf));
	xdr = xdr_new(buf, sizeof(buf), xdr_ENCODE);
	assert( xdr_float(xdr, &f ) );
	buflen = xdr_getpos(xdr);
	cmp(__LINE__, buf, buflen, "\x40\xe0\x00\x00", 4);
	xdr_free(xdr);
	// now decoding:
	xdr = xdr_new(buf, buflen, xdr_DECODE);
	assert( xdr_float(xdr, &f) );
	assert( f == 7.0 );
	assert( ! xdr_char(xdr, (char *) &uc) ); // reading beyond buffer end must fail
	xdr_free(xdr);
	
	double d = 7.0;
	memset(buf, 255, sizeof(buf));
	xdr = xdr_new(buf, sizeof(buf), xdr_ENCODE);
	assert( xdr_double(xdr, &d ) );
	buflen = xdr_getpos(xdr);
	cmp(__LINE__, buf, buflen, "\x40\x1c\x00\x00\x00\x00\x00\x00", 8);
	xdr_free(xdr);
	// now decoding:
	xdr = xdr_new(buf, buflen, xdr_DECODE);
	assert( xdr_double(xdr, &d) );
	assert( d == 7.0 );
	assert( ! xdr_char(xdr, (char *) &uc) ); // reading beyond buffer end must fail
	xdr_free(xdr);
	
	decoding_error_recovery_test();
	
	err += memory_report();
	
	return err? 1 : 0;
}

