/*
 *   DIS/x : An implementation of the IEEE 1278.1 protocol
 *
 *   Copyright (C) 1996, Riley Rainey (rainey@netcom.com)
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of either:
 *
 *   a) the GNU Library General Public License as published by the Free
 *   Software Foundation; either version 2 of the License, or (at your
 *   option) any later version.  A description of the terms and conditions
 *   of the GLPL may be found in the "COPYING.LIB" file.
 *
 *   b) the "Artistic License" which comes with this Kit.  Information
 *   about this license may be found in the "Artistic" file.
 *
 *   This library 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
 *   Library General Public License or the Artistic License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *   Information describing how to contact the author can be found in the
 *   README file.
 */
/*
 * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
 * unrestricted use provided that this legend is included on all tape
 * media and as a part of the software program in whole or part.  Users
 * may copy or modify Sun RPC without charge, but are not authorized
 * to license or distribute it to anyone else except as part of a product or
 * program developed by the user.
 * 
 * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 * 
 * Sun RPC is provided with no support and without any obligation on the
 * part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 * 
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
 * OR ANY PART THEREOF.
 * 
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even if
 * Sun has been advised of the possibility of such damages.
 * 
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 */

/*
 * xdr_mem.h, XDR implementation using memory buffers.
 *
 * Copyright (C) 1984, Sun Microsystems, Inc.
 *
 * If you have some data to be interpreted as external data representation
 * or to be converted to external data representation in a memory buffer,
 * then this is the package for you.
 */

#include <strings.h>
#ifdef WINNT
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>

#include "../../util/memory.h"

#define xdr_IMPORT
#include "xdr.h"


/**
 * Entry of the list of dynamically allocated blocks created while decoding.
 * If the decoding process fails, the allocated blocks can then be released.
 */
typedef struct xdr_Allocated {
	/** Next element in the list of allocated blocks, or next recycled element. */
	struct xdr_Allocated *next;
	/** Allocated block. */
	void *allocated;
} xdr_Allocated;


/**
 * Released elements that can be recycled.
 */
static xdr_Allocated *recycle_xdr_Allocated;


/**
 * Encoder/decoder state. The currently performed operation is set in the
 * constructor and never changes during the lifetime of this object.
 * The working buffer must be provided by the user; while encoding, its size
 * should be large enough to contain the resulting encoded packet or a failure
 * is triggered. While decoding, manages a list of dynamically allocated blocks
 * that can be released if the operation fails; this implies that any user's
 * data decoded so far gets destroyed if the decoding fails.
 */
struct xdr_Type {
	xdr_Type *next_recycled;  /* recycle list */
	xdr_Operation op;         /* operation */
	char * buffer;            /* working buffer */
	char * next_byte;         /* pointer to next byte in the buffer */
	unsigned int bytes_left;  /* bytes left in the buffer */
	xdr_Allocated *allocated; /* list of allocated blocks while decoding */
	char *error_description;  /* latest error description */
};


/**
 * Objects that where released with xdr_free() and can be recycled.
 */
static xdr_Type *recycle_xdr_Type;


/**
 * Callback for memory module that releases the content of a xdr_Type.
 * @param p Pointer to the xdr_Type to release.
 */
static void
xdr_destruct(void *p)
{
	xdr_Type *xdr = p;
	while( xdr->allocated != NULL ){
		xdr_Allocated *e = xdr->allocated;
		xdr->allocated = e->next;
		e->next = recycle_xdr_Allocated;
		e->allocated = NULL;
		recycle_xdr_Allocated = e;
	}
}


/**
 * Module cleanup callback to be registered in the memory module.
 */
static void
xdr_cleanup()
{
	while(recycle_xdr_Type != NULL){
		xdr_Type *next = recycle_xdr_Type->next_recycled;
		memory_dispose(recycle_xdr_Type);
		recycle_xdr_Type = next;
	}
	
	while(recycle_xdr_Allocated != NULL){
		xdr_Allocated *next = recycle_xdr_Allocated->next;
		memory_dispose(recycle_xdr_Allocated);
		recycle_xdr_Allocated = next;
	}
}


xdr_Type *
xdr_new(char * buffer, unsigned int size, xdr_Operation op)
{
	xdr_Type *xdr;
	if( recycle_xdr_Type == NULL ){
		memory_registerCleanup(xdr_cleanup);
		xdr = memory_allocate(sizeof(*xdr), xdr_destruct);
	} else {
		xdr = recycle_xdr_Type;
		recycle_xdr_Type = recycle_xdr_Type->next_recycled;
	}
	memory_zero(xdr);
	xdr->op = op;
	xdr->buffer = buffer;
	xdr->next_byte = buffer;
	xdr->bytes_left = size;
	xdr->allocated = NULL;
	xdr->error_description = NULL;
	return xdr;
}


void
xdr_free(xdr_Type *xdr)
{
	if( xdr == NULL )
		return;
	xdr_destruct(xdr);
	xdr->next_recycled = recycle_xdr_Type;
	recycle_xdr_Type = xdr;
}


int xdr_setError(xdr_Type *xdr, char *description)
{
	xdr->error_description = description;
	while(xdr->allocated != NULL){
		xdr_Allocated *a = xdr->allocated;
		memory_dispose(a->allocated);
		a->allocated = NULL;
		xdr->allocated = a->next;
		a->next = recycle_xdr_Allocated;
		recycle_xdr_Allocated = a;
	}
	return 0;
}


char * xdr_getErrorDescription(xdr_Type *xdr)
{
	return xdr->error_description;
}


/**
 * Registers a dynamically allocated block created while decoding, so that
 * these blocks can be released if the decoding operation fails.
 * @param xdr Decoding context.
 * @param allocated Pointer to the allocated block.
 */
static void xdr_addAllocated(xdr_Type *xdr, void *allocated)
{
	xdr_Allocated *a;
	
	if( recycle_xdr_Allocated == NULL ){
		a = memory_allocate(sizeof(*a), NULL);
	} else {
		a = recycle_xdr_Allocated;
		recycle_xdr_Allocated = a->next;
	}
	
	a->next = xdr->allocated;
	a->allocated = allocated;
	xdr->allocated = a;
}


xdr_Operation xdr_getOperation(xdr_Type * xdr)
{
	return xdr->op;
}


unsigned int xdr_getpos(xdr_Type *xdr)
{
	return (unsigned int) (xdr->next_byte - xdr->buffer);
}


int xdr_setpos(xdr_Type *xdr, unsigned int pos)
{
	char *newaddr = xdr->buffer + pos;
	char *lastaddr = xdr->next_byte + xdr->bytes_left;

	if ((long) newaddr > (long) lastaddr
	|| (UINT_MAX < LONG_MAX && (long) UINT_MAX < (long) lastaddr - (long) newaddr))
		return xdr_setError(xdr, "xdr_setpos(): position set beyond unsigned int 32 bits limits");
	xdr->next_byte = newaddr;
	xdr->bytes_left = (int) (lastaddr - newaddr);
	return 1;
}


static    int
xdr_getint32(xdr_Type * xdr, int32_t *lp)
{
	int32_t  tmp;

	if (xdr->bytes_left < sizeof(int32_t))
		return xdr_setError(xdr, "xdr_getint32(): premature end of the buffer");
	xdr->bytes_left -= sizeof(int32_t);
	bcopy(xdr->next_byte, (char *) &tmp, sizeof(int32_t));

	*lp = (int32_t) ntohl((int32_t) tmp);
	xdr->next_byte += sizeof(int32_t);

	return 1;
}


static    int
xdr_putint32(xdr_Type * xdr, int32_t *lp)
{
	int32_t  tmp;

	if (xdr->bytes_left < sizeof(int32_t))
		return xdr_setError(xdr, "xdr_putint32(): premature end of the buffer");
	xdr->bytes_left -= sizeof(int32_t);

	tmp = (int32_t) htonl((uint32_t) (*lp));
	bcopy((char *) &tmp, xdr->next_byte, sizeof(int32_t));
	xdr->next_byte += sizeof(int32_t);

	return 1;
}


int
xdr_getBytes(xdr_Type * xdr, void *addr, unsigned int len)
{
	if (xdr->bytes_left < len)
		return xdr_setError(xdr, "xdr_getBytes(): premature end of the buffer");
	xdr->bytes_left -= len;
	bcopy(xdr->next_byte, addr, len);
	xdr->next_byte += len;
	return 1;
}


int xdr_getBytesAllocated(xdr_Type * xdr, void **addr, unsigned int len, int add_nul)
{
	if (xdr->bytes_left < len)
		return xdr_setError(xdr, "xdr_getBytesAllocated(): premature end of the buffer");
	xdr->bytes_left -= len;
	*addr = memory_allocate(len + (add_nul? 1 : 0), NULL);
	xdr_addAllocated(xdr, *addr);
	bcopy(xdr->next_byte, *addr, len);
	if( add_nul )
		((char *) *addr)[len] = 0;
	xdr->next_byte += len;
	return 1;
}


int
xdr_putBytes(xdr_Type * xdr, void *addr, unsigned int len)
{
	if (xdr->bytes_left < len)
		return xdr_setError(xdr, "xdr_putBytes(): premature end of the buffer");
	xdr->bytes_left -= len;
	bcopy(addr, xdr->next_byte, len);
	xdr->next_byte += len;
	return 1;
}


int
xdr_var_array(xdr_Type * xdr, void * * addrp, unsigned int size,
		unsigned int elsize, xdr_Callback elproc)
{
	unsigned int i;
	void *target;
	int stat;
	unsigned int nodesize;
	
	if ( UINT_MAX/elsize < size )
		return xdr_setError(xdr, "xdr_var_array(): array size exceeds unsigned int 32 bits capacity");
	nodesize = size * elsize;

	/*
	 * The memory module causes a fatal error if a huge memory space is requested,
	 * so ill formed packets coming from the network may cause a crash is we try
	 * to blindly allocate memory. Since we expect at least one byte per element,
	 * there can't be more elements to decode than the number of bytes left in
	 * the buffer.
	 */
	if( size > xdr->bytes_left )
		return xdr_setError(xdr, "xdr_var_array(): array size exceeds the space left in the buffer");

	/*
	 * If we are deserializing, we may need to allocate an array.
	 * We also save time by checking for a null array if we are freeing.
	 *
	 * * Always allocate storage when decoding. *
	 */
	if (*addrp == NULL || xdr->op == xdr_DECODE){
		switch (xdr->op) {
			
		case xdr_DECODE:
			if (size == 0){
				*addrp = NULL;
				return 1;
			}
			*addrp = memory_allocate(nodesize, NULL);
			xdr_addAllocated(xdr, *addrp);
			memset(*addrp, 0, nodesize);
			target = *addrp;
			break;

		case xdr_FREE:
			return 1;

		case xdr_ENCODE:
			break;
		}
	}

	/*
	 * now we xdr each element of array
	 */
	target = *addrp;
	stat = 1;
	for (i = 0; (i < size) && stat; i++) {
		stat = (*elproc) (xdr, target);
		target += elsize;
	}

	/*
	 * the array may need freeing
	 */
	if (xdr->op == xdr_FREE) {
		memory_dispose(*addrp);
		*addrp = NULL;
	}
	return stat;
}


#define IS_LITTLE_ENDIAN (((char *)&(int){1})[0] == 1)


int	xdr_int32(xdr_Type *xdr, int32_t *i)
{
	switch(xdr->op){
	case xdr_ENCODE: return xdr_putint32(xdr, i);
	case xdr_DECODE: return xdr_getint32(xdr, i);
	case xdr_FREE: return 1;
	default: abort();
	}
}


int	xdr_uint32(xdr_Type *xdr, uint32_t *ui)
{
	return xdr_int32(xdr, (int32_t *) ui);
}


int xdr_char(xdr_Type *xdr, char *c)
{
	int32_t tmp;
	
	if( xdr->op == xdr_ENCODE ){
		tmp = *c & 255;
		return xdr_int32(xdr, &tmp);
	} else if( xdr->op == xdr_DECODE ){
		if( ! xdr_int32(xdr, &tmp) )
			return xdr_setError(xdr, "xdr_char(): premature end of the buffer while reading 32 bits word");
		*c = tmp & 255;
		return 1;
	}
	return 1;
}


int xdr_float(xdr_Type *xdr, float *f)
{
	return xdr_int32(xdr, (int32_t *) f);
}


int xdr_double(xdr_Type *xdr, double *d)
{
	switch(xdr->op){
		
	case xdr_ENCODE:
		if( IS_LITTLE_ENDIAN )
			return xdr_putint32(xdr, (int32_t *)((char *) d + 4))
				&& xdr_putint32(xdr, (int32_t *)((char *) d));
		else
			return xdr_putint32(xdr, (int32_t *)((char *) d))
				&& xdr_putint32(xdr, (int32_t *)((char *) d + 4));
		
	case xdr_DECODE:
		if( IS_LITTLE_ENDIAN )
			return xdr_getint32(xdr, (int32_t *)((char *) d + 4))
				&& xdr_getint32(xdr, (int32_t *)((char *) d));
		else
			return xdr_getint32(xdr, (int32_t *)((char *) d))
				&& xdr_getint32(xdr, (int32_t *)((char *) d + 4));
		
	case xdr_FREE:
		return 1;
	
	default: abort();
	}
}


int xdr_vector(xdr_Type *xdr, char *arrp, unsigned int size,
	unsigned int elsize, xdr_Callback elproc)
{
	unsigned int i;
	for(i = 0; i < size; i++){
		if( ! elproc(xdr, arrp + i*elsize) )
			return 0;
	}
	return 1;
}
