/*
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)geniconvtbl.c 1.7	99/10/05 SMI"

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <synch.h>

#if defined(DEBUG)
#include <stdarg.h>
#endif /* !DEBUG */

#include "iconv_tm.h"
#include "hash.h"


/*
 * Debug
 */
#if defined(DEBUG)

static void	trace_init(void);
static void	trace_message(char *, ...);

static char	trace_option[128];

#define	TRACE(c)		(*(trace_option + (c & 0x007f)))
#define	TRACE_MESSAGE(c, args)	((TRACE(c))? trace_message args: (void)0)

#else /* !DEBUG */

#define	trace_init()
#define	TRACE()
#define	TRACE_MESSAGE(c, args)

#endif /* !DEBUG */


/*
 * ITM reference information
 */
typedef struct _itm_ref {
	char		*name;		/* ITM file name */
	itm_hdr_t	*hdr;		/* address of ITM */
	size_t		len;		/* length of ITM */
} itm_ref_t;


/*
 * struct _icv_state; to keep status
 */
typedef struct _icv_state {
	struct _itm_ref	*itm;		/* reference to ITM */
	itm_hdr_t	*itm_hdr;	/* address of ITM */
	itm_tbl_hdr_t	*direc;		/* current direction */
	itm_place_t	default_action;	/* default action */
	itm_num_t	*regs;		/* register */
	itm_num_t	reg_num;	/* number of register */
#if defined(OP_DEPTH_MAX)
	int		op_depth;	/* depth of operation */
#endif /* OP_DEPTH_MAX */
} icv_state_t;


/*
 * function prototype
 */
void *	_icv_open(const char *);
void	_icv_close(icv_state_t *);
size_t	_icv_iconv(icv_state_t *, const unsigned char **,
		    size_t *, unsigned char **, size_t *);

static size_t	map_i_f(itm_tbl_hdr_t *,
			const unsigned char **, size_t *,
			unsigned char **, size_t *, long);
static size_t	map_l_f(itm_tbl_hdr_t *,
			const unsigned char **, size_t *,
			unsigned char **, size_t *, long);
static size_t	map_h_l(itm_tbl_hdr_t *,
			const unsigned char **, size_t *,
			unsigned char **, size_t *, long);
static size_t	map_d_e_l(itm_tbl_hdr_t *,
			const unsigned char **, size_t *,
			unsigned char **, size_t *, long);
static size_t	eval_cond_tbl(icv_state_t *, itm_place_t,
			const unsigned char **, size_t *,
			size_t, itm_direc_t *);
static size_t	eval_op_tbl(icv_state_t *, itm_place_t,
			const unsigned char **, size_t *,
			unsigned char **, size_t *);
static size_t	eval_op(icv_state_t *, itm_place2_t,
			const unsigned char **, size_t *,
			unsigned char **, size_t *);

static itm_num_t	eval_expr(icv_state_t *, itm_place_t,
				size_t, const unsigned char *, size_t);

static void		itm_ref_free(int, void *, void *, void *, size_t);
static itm_ref_t	*itm_ref_inc(const char *);
static void		itm_ref_dec(itm_ref_t *);

static void		op_init_default(icv_state_t *);
static void		op_reset_default(icv_state_t *);
static void		regs_init(icv_state_t *);


/*
 * macro definition
 */

#define	ADDR(place)	((void *)(((char *)(ist->itm_hdr)) +\
				((itm_place2_t)(place.itm_ptr))))
#define	ADDR2(place2)	((void *)(((char *)(ist->itm_hdr)) +\
				((itm_place2_t)(place2))))
#define	DADDR(n)	(((n)->size <= (sizeof ((n)->place.itm_64d))) ?	\
				((unsigned char *)(&((n)->place.itm_64d))) :\
				((unsigned char *)(ADDR((n)->place))))

#define	REG(n)		(*(ist->regs + (n)))
#define	DISCARD(c)	(((*inbuf) = (void *)((*inbuf) + (c))),\
			((*inbytesleft) -= (c)))
#define	GET(c)		((c) = **inbuf, (*inbuf)++, (*inbytesleft)--)
#define	PUT(c)		(**outbuf = (c), (*outbuf)++, (*outbytesleft)--)

#define	RETVALERR	((size_t)(-1))
#define	RETVALDIR	((size_t)(-2))
#define	RETVALBRK	((size_t)(-3))
#define	RETVALRET	((size_t)(-4))

#define	UPDATE_ARGS()	(*inbuf = ip, \
			 *inbytesleft = ileft, \
			 *outbuf = op, \
			 *outbytesleft = oleft)

/*
 * Open; called from iconv_open()
 */
void *
_icv_open(const char	*itm)
{
	icv_state_t	*ist;
	itm_hdr_t	*hdr;
	itm_ref_t	*itm_ref;
	int		r;

	/*
	 * for debug
	 */
	trace_init();

	/*
	 * _icv_open() primaty task
	 */
	itm_ref = itm_ref_inc(itm);
	if (NULL == itm_ref) {
		return ((void *)(-1));
	}

	if (NULL == (ist = malloc(sizeof (icv_state_t)))) {
		r = errno;
		itm_ref_dec(itm_ref);
		errno = r;
		return	(NULL);
	}

	ist->itm = itm_ref;
	ist->itm_hdr = ist->itm->hdr;
	ist->reg_num = ist->itm->hdr->reg_num;

	hdr =  ist->itm->hdr;
	ist->direc = ADDR(hdr->direc_init_tbl);
#if defined(OP_DEPTH_MAX)
	ist->op_depth = 0;
#endif /* OP_DEPTH_MAX */


	/*
	 * brief sanity check
	 */
	if (hdr->itm_size.itm_ptr <= hdr->direc_init_tbl.itm_ptr) {
		_icv_close(ist);
		errno = ELIBBAD;
		return ((void *)(-1));
	}


	/* allocate register region */
	if (hdr->reg_num < 0) {
		ist->regs = NULL;
	} else {
		ist->regs = malloc((sizeof (itm_num_t)) * hdr->reg_num);
		if (NULL == ist->regs) {
			r = errno;
			_icv_close(ist);
			errno = r;
			return ((void *)(-1));
		}
		(void) memset(ist->regs, 0,
			(sizeof (itm_num_t)) * hdr->reg_num);
	}


	/* evaluate init operation */
	if (0 != ist->itm_hdr->op_init_tbl.itm_ptr) {
		const unsigned char	*ip = NULL;
		size_t			ileft = 0;
		unsigned char		*op = NULL;
		size_t			oleft = 0;
		(void) eval_op_tbl(ist,
			    ist->itm_hdr->op_init_tbl,
			    &ip,
			    &ileft,
			    &op, &oleft);
	} else {
		op_init_default(ist);
	}

	return	(ist);
}


/*
 * Close; called from iconv_close
 */
void
_icv_close(icv_state_t		*ist)
{
	if (NULL == ist) {
		errno = EBADF;
		return;
	}
	itm_ref_dec(ist->itm);
	free(ist->regs);
	free(ist);
}


/*
 * Actual conversion; called from iconv()
 */
size_t
_icv_iconv(
	icv_state_t		*ist,
	const unsigned char	**inbuf,
	size_t			*inbytesleft,
	unsigned char		**outbuf,
	size_t			*outbytesleft)
{
	size_t			retval;
	itm_hdr_t		*hdr;
	itm_type_t		type;
	const unsigned char	*ip;
	size_t			ileft;
	itm_place_t		action;

	if (NULL == ist) {
		errno = EBADF;
		TRACE_MESSAGE('e', ("_icv_iconv: error=%d\n", errno));
		return ((size_t)(-1));
	}
	if (NULL == inbuf) {
		ip = NULL;
		inbuf = &ip;
	}
	if (NULL == inbytesleft) {
		ileft = 0;
		inbytesleft = &ileft;
	}

	hdr = ist->itm_hdr;

	retval = 0;

	TRACE_MESSAGE('i', ("_icv_iconv(inbuf=%p inbytesleft=%ld "
			    "outbuf=%p outbytesleft=%ld)\n",
			    (NULL == inbuf) ? 0 : *inbuf,
			    (NULL == inbytesleft) ? 0 : *inbytesleft,
			    (NULL == outbuf) ? 0 : *outbuf,
			    (NULL == outbytesleft) ? 0 : *outbytesleft));

	/*
	 * If (NULL == inbuf || NULL == *inbuf) then this conversion is
	 * placed into initial state.
	 */
	if ((NULL == inbuf) || (NULL == *inbuf)) {
		if (0 != hdr->op_reset_tbl.itm_ptr) {
			ist->direc = ADDR(hdr->direc_init_tbl);
			retval = eval_op_tbl(ist,
						hdr->op_reset_tbl,
						inbuf, inbytesleft,
						outbuf, outbytesleft);
			if ((size_t)(-1) == retval) {
				return	(retval);
			}
		} else {
			op_reset_default(ist);
		}
		return ((size_t)(0));
	}

	if (ITM_TBL_MAP_INDEX_FIXED_1_1 == ist->direc->type) {
		itm_map_idx_fix_hdr_t	*map_hdr;
		char			*map;
		const unsigned char	*ip;
		size_t			ileft;
		unsigned char		*op;
		size_t			oleft;

		map_hdr = (itm_map_idx_fix_hdr_t *)(ist->direc + 1);
		map = (char *)(map_hdr + 1);

		if (1 == map_hdr->default_error) {
			retval = map_i_f(ist->direc,
					inbuf, inbytesleft,
					outbuf, outbytesleft, 0);
			return	(retval);
		}

		ip = *inbuf;
		ileft = *inbytesleft;
		op = *outbuf;
		oleft = *outbytesleft;

		while (1 <= ileft) {
			if (oleft < 1) {
				UPDATE_ARGS();
				errno = E2BIG;
				TRACE_MESSAGE('e', ("_icv_iconv: error=%d\n",
						errno));
				return ((size_t)-1);
			}
			*(op++) = *(map + *(ip++));
			ileft--;
			oleft--;

		}
		UPDATE_ARGS();
		return (0);
	} else if (ITM_TBL_MAP_INDEX_FIXED == ist->direc->type) {
		retval = map_i_f(ist->direc,
				inbuf, inbytesleft,
				outbuf, outbytesleft, 0);
		return	(retval);
	} else if (ITM_TBL_MAP_HASH == ist->direc->type) {
		retval = map_h_l(ist->direc,
				inbuf, inbytesleft,
				outbuf, outbytesleft, 0);
		return	(retval);
	} else if (ITM_TBL_MAP_DENSE_ENC == ist->direc->type) {
		retval = map_d_e_l(ist->direc,
				inbuf, inbytesleft,
				outbuf, outbytesleft, 0);
		return	(retval);
	} else if (ITM_TBL_MAP_LOOKUP == ist->direc->type) {
		retval = map_l_f(ist->direc,
				inbuf, inbytesleft,
				outbuf, outbytesleft, 0);
		return	(retval);
	}

#if defined(OP_DEPTH_MAX)
	ist->op_depth = 0;
#endif /* OP_DEPTH_MAX */


	/*
	 * Main loop; basically 1 loop per 1 output character
	 */
retry_cond_eval:
	while (0 < *inbytesleft) {
		itm_tbl_hdr_t	*direc_hdr;
		itm_direc_t	*direc;
		long		i;

		direc_hdr = ist->direc;
		direc = (itm_direc_t *)(ist->direc + 1);
		for (i = 0; /* NULL */; i++, direc++) {
			if (i >= direc_hdr->number) {
				if (0 == ist->default_action.itm_ptr) {
					errno = EILSEQ;
					TRACE_MESSAGE('e',
						("_icv_iconv:error=%d\n",
						errno));
					return ((size_t)(-1));
				}



				action = ist->default_action;
				type = ((itm_tbl_hdr_t *)(ADDR(action)))->type;
				TRACE_MESSAGE('E',
				("escape seq (default action=%6p, type=%ld) "
				"excuting\n", action.itm_ptr, type));
			} else if (0 != direc->condition.itm_ptr) {
				retval = eval_cond_tbl(ist, direc->condition,
							inbuf, inbytesleft,
							*outbytesleft,
							direc);
				if ((size_t)(0) == retval) {
					continue;
				} else if ((size_t)(-1) == retval) {
					return	(retval);
				} else if ((size_t)(2) == retval) {
					goto retry_cond_eval;
				}
				action = direc->action;
				type = ((itm_tbl_hdr_t *)(ADDR(action)))->type;
			} else {
				action = direc->action;
				type = ((itm_tbl_hdr_t *)(ADDR(action)))->type;
			}

			TRACE_MESSAGE('a',
				("inbytesleft=%ld; type=%ld:action=%p\n",
				*inbytesleft, type, action.itm_ptr));
			switch (ITM_TBL_MASK & type) {
			case ITM_TBL_OP:
				retval = eval_op_tbl(ist, action,
						inbuf, inbytesleft,
						outbuf, outbytesleft);
				if ((size_t)(-1) == retval) {
					return	(retval);
				}
				break;
			case ITM_TBL_DIREC:
				ist->direc = ADDR(action);
				break;
			case ITM_TBL_MAP:
				switch (type) {
				case ITM_TBL_MAP_INDEX_FIXED_1_1:
				case ITM_TBL_MAP_INDEX_FIXED:
					retval = map_i_f(
						    ADDR(action),
						    inbuf, inbytesleft,
						    outbuf, outbytesleft,
						    1);
					break;
				case ITM_TBL_MAP_HASH:
					retval = map_h_l(ADDR(action),
						    inbuf, inbytesleft,
						    outbuf, outbytesleft, 1);
					break;
				case ITM_TBL_MAP_DENSE_ENC:
					retval = map_d_e_l(ADDR(action),
						    inbuf, inbytesleft,
						    outbuf, outbytesleft, 1);
					break;
				case ITM_TBL_MAP_LOOKUP:
					retval = map_l_f(
						    ADDR(action),
						    inbuf, inbytesleft,
						    outbuf, outbytesleft,
						    1);
					break;
				default:
					errno = ELIBBAD;
					TRACE_MESSAGE('e',
					("_icv_iconv:error=%d\n", errno));
					return ((size_t)(-1));

				}
				if ((size_t)(-1) == retval) {
					return	(retval);
				}
				break;
			default:	/* never */
				errno = ELIBBAD;
				TRACE_MESSAGE('e',
				("_icv_iconv:error=%d\n", errno));
				return ((size_t)(-1));
			}
			break;
		}
	}
	return	(retval);
}



/*
 * map-indexed-fixed
 */
static size_t
map_i_f(
	itm_tbl_hdr_t		*tbl_hdr,
	const unsigned char	**inbuf,
	size_t			*inbytesleft,
	unsigned char		**outbuf,
	size_t			*outbytesleft,
	long			once)
{
	itm_map_idx_fix_hdr_t	*map_hdr;
	long			i;
	unsigned char		c;
	unsigned long		j;
	const unsigned char	*p;

	TRACE_MESSAGE('i', ("map_i_f\n"));

	map_hdr = (itm_map_idx_fix_hdr_t *)(tbl_hdr + 1);

	do {
		if (*inbytesleft < map_hdr->source_len) {
			errno = EINVAL;
			TRACE_MESSAGE('e', ("map_i_f:error=%d\n", errno));
			return ((size_t)(-1));
		}

		j = 0;
		for (i = 0; i < map_hdr->source_len; i++) {
			GET(c);
			j = ((j << 8) | c);
		}

		if (((j < map_hdr->start.itm_ptr) ||
			(map_hdr->end.itm_ptr < j)) &&
		    (0 < map_hdr->default_error)) {
			errno = EILSEQ;
			(*inbuf) = (void*) ((*inbuf) - map_hdr->source_len);
			(*inbytesleft) += map_hdr->source_len;
			TRACE_MESSAGE('e', ("map_i_f:error=%d\n", errno));
			return ((size_t)(-1));
		}

		if (*outbytesleft < map_hdr->result_len) {
			errno = E2BIG;
			(*inbuf) = (void *)((*inbuf) - map_hdr->source_len);
			(*inbytesleft) += map_hdr->source_len;
			TRACE_MESSAGE('e', ("map_i_f:error=%d\n", errno));
			return ((size_t)(-1));
		}

		if ((j < map_hdr->start.itm_ptr) ||
			(map_hdr->end.itm_ptr < j)) {
			if (0 == map_hdr->default_error) {
				p = (((unsigned char *)(map_hdr + 1)) +
					(map_hdr->result_len *
					(tbl_hdr->number)));
				for (i = 0; i < map_hdr->result_len; i++) {
					PUT(*(p + i));
				}
			} else {
				p = ((*inbuf) - map_hdr->source_len);
				for (i = 0; i < map_hdr->source_len; i++) {
					PUT(*(p + i));
				}
			}
		} else {
			char	*map_error;
			map_error = (((char *)(map_hdr + 1)) +
				    (map_hdr->result_len *
				    (tbl_hdr->number)) +
				    (j - map_hdr->start.itm_ptr));
			if (0 == map_hdr->default_error) {
				map_error = (void *)
					(map_error + map_hdr->result_len);
			}
			if (((1 == map_hdr->default_error) ||
			    (0 < map_hdr->error_num)) &&
			    (0 != *(map_error))) {
				errno = EILSEQ;
				(*inbuf) = (void *)
					((*inbuf) - map_hdr->source_len);
				(*inbytesleft) += map_hdr->source_len;
				TRACE_MESSAGE('e',
				("map_i_f:error=%d\n", errno));
				return ((size_t)(-1));
			}
			p = (((unsigned char *)(map_hdr + 1)) +
				(map_hdr->result_len *
				(j - map_hdr->start.itm_ptr)));
			for (i = 0; i < map_hdr->result_len; i++) {
				PUT(*(p + i));
			}
		}
	} while ((0 < *inbytesleft) && (0 == once));

	return (size_t)(0);
}


/*
 * map-lookup-fixed
 */
static size_t
map_l_f(
	itm_tbl_hdr_t	*tbl_hdr,
	const unsigned char	**inbuf,
	size_t		*inbytesleft,
	unsigned char	**outbuf,
	size_t		*outbytesleft,
	long		once)
{
	itm_map_lookup_hdr_t	*map_hdr;
	long			i;
	unsigned char		*map;
	const unsigned char	*p;
	long			high;
	long			mid;
	long			low;
	long			result;
	itm_size_t		pair_size;

	TRACE_MESSAGE('i', ("map_l_f\n"));

	map_hdr = (itm_map_lookup_hdr_t *)(tbl_hdr + 1);
	map = (unsigned char *)(map_hdr + 1);
	pair_size = map_hdr->source_len + 1 + map_hdr->result_len;

	do {
		if (*inbytesleft < map_hdr->source_len) {
			errno = EINVAL;
			TRACE_MESSAGE('e', ("map_l_f:error=%d\n", errno));
			return ((size_t)(-1));
		}

		for (low = 0, high = tbl_hdr->number; low < high; ) {
			mid = (low + high) / 2;
			p = map + (pair_size * mid);
			for (i = 0, result = 0; i < map_hdr->source_len;
			    i++, p++) {
				if (*(unsigned char *)(*inbuf + i) < *p) {
					result = -1;
					break;
				}
				if (*p < *(unsigned char *)(*inbuf + i)) {
					result = 1;
					break;
				}
			}
			if (result < 0) {
				high = mid;
			} else if (0 < result) {
				low = mid + 1;
			} else { /* 0 == result */
				break;
			}
		}

		if (0 != result) {
			if (map_hdr->default_error < 0) {
				p = *inbuf;
			} else if (0 == map_hdr->default_error) {
				p = map + (pair_size * tbl_hdr->number) +
					map_hdr->source_len + 1;
			} else if (0 < map_hdr->default_error) {
				errno = EILSEQ;
				TRACE_MESSAGE('e', ("map_l_f:error=%d\n",
				errno));
				return ((size_t)(-1));
			}
		} else {
			if (0 != (*p)) {
				errno = EILSEQ;
				TRACE_MESSAGE('e', ("map_l_f:error=%d\n",
				errno));
				return ((size_t)(-1));
			}
			p++;
		}

		if (*outbytesleft < map_hdr->result_len) {
			errno = E2BIG;
			TRACE_MESSAGE('e', ("map_l_f:error=%d\n", errno));
			return ((size_t)(-1));
		}
		DISCARD(map_hdr->source_len);

		for (i = 0; i < map_hdr->result_len; i++) {
			PUT(*(p + i));
		}
	} while ((0 < *inbytesleft) && (0 == once));

	return ((size_t)(0));
}


/*
 * map-hash-lookup
 */
static size_t
map_h_l(
	itm_tbl_hdr_t	*tbl_hdr,
	const unsigned char	**inbuf,
	size_t		*inbytesleft,
	unsigned char	**outbuf,
	size_t		*outbytesleft,
	long		once)
{
	itm_map_hash_hdr_t	*map_hdr;
	long			i;
	unsigned char	*map_error;
	unsigned char	*map_hash;
	unsigned char	*map_of;
	const unsigned char	*p;
	const unsigned char	*q;
	long			high;
	long			mid;
	long			low;
	long			result;
	itm_size_t		pair_size;
	itm_size_t		hash_value;
	itm_size_t		source_len;
	itm_size_t		result_len;

	TRACE_MESSAGE('i', ("map_hash\n"));

	map_hdr = (itm_map_hash_hdr_t *)(tbl_hdr + 1);
	map_error = (unsigned char *)(map_hdr + 1);
	map_hash = (map_error + map_hdr->hash_tbl_num);
	map_of = map_hash + map_hdr->hash_tbl_size;
	pair_size = map_hdr->source_len + 1 + map_hdr->result_len;
	source_len = map_hdr->source_len;
	result_len = map_hdr->result_len;

	do {
		if (*inbytesleft < source_len) {
			errno = EINVAL;
			TRACE_MESSAGE('e', ("map_h_l:error=%d\n", errno));
			return ((size_t)(-1));
		}

		result = 1;
		q = *inbuf;
		hash_value = hash((const char *)(q),
				source_len, map_hdr->hash_tbl_num);
		p = map_hash + (pair_size * hash_value);
		if (1 == *(map_error + hash_value)) {
			for (i = 0, result = 0; i < source_len; i++) {
				if (*(q + i) != *(p++)) {
					result = -2;
					break;
				}
			}
			TRACE_MESSAGE('G',
			("(h=%d): find pair without conflict\n",
			hash_value));
		} else if (0 == *(map_error + hash_value)) {
			TRACE_MESSAGE('G',
			("(h=%d): No Pair\n",
			hash_value));
			result = -3;
		} else /* if (0 < *(map_error + hash_value)) */ {
			for (i = 0, result = 0; i < source_len; i++) {
				if (*(q + i) != *(p++)) {
					result = 1;
					break;
				}
			}
			if (0 < result) {
				for (low = 0, high = map_hdr->hash_of_num;
				    low < high; /* NOP */) {
					mid = (low + high) / 2;
					p = map_of + (pair_size * mid);
					for (i = 0, result = 0;
					    i < source_len;
					    i++, p++) {
						if (*(q + i) < *p) {
							result = -1;
							break;
						}
						if (*p < *(q + i)) {
							result = 1;
							break;
						}
					}
					if (result < 0) {
						high = mid;
					} else if (0 < result) {
						low = mid + 1;
					} else { /* 0 == result */
						TRACE_MESSAGE('G',
						("(h=%d): "
						"find data on out ot "
						"hashtable with CONFLICT\n",
						hash_value));
						break;
					}
				}
			}
		}
		if (0 != result) {
			if (map_hdr->default_error < 0) {
				p = q;
			} else if (0 == map_hdr->default_error) {
				p = map_of + map_hdr->hash_of_size;
			} else if (0 < map_hdr->default_error) {
				TRACE_MESSAGE('G',
				("(h=%d): NO PAIR\n",
				hash_value));
				errno = EILSEQ;
				TRACE_MESSAGE('e',
				("map_h_l:error=%d\n", errno));
				return ((size_t)(-1));
			}
		} else {
			if (0 != (*p)) {
				errno = EILSEQ;
				TRACE_MESSAGE('G',
				("	      : error pair\n"));
				TRACE_MESSAGE('e', ("map_l_f:error\n",
				errno));
				return ((size_t)(-1));
			}
			p++;
		}

		if (*outbytesleft < result_len) {
			errno = E2BIG;
			TRACE_MESSAGE('e', ("map_h_l:error=%d\n", errno));
			return ((size_t)(-1));
		}
		DISCARD(source_len);

		for (i = 0; i < result_len; i++) {
			PUT(*(p + i));
		}
	} while ((0 < *inbytesleft) && (0 == once));

	return ((size_t)(0));
}


/*
 * map-dense_encoding-lookup
 */
static size_t
map_d_e_l(
	itm_tbl_hdr_t		*tbl_hdr,
	const unsigned char	**inbuf,
	size_t			*inbytesleft,
	unsigned char		**outbuf,
	size_t			*outbytesleft,
	long			once)
{
	itm_map_dense_enc_hdr_t	*map_hdr;
	long			i;
	itm_num_t		j;
	const unsigned char	*p;
	unsigned char		*map_ptr;
	unsigned char		*map_error;
	unsigned char		*byte_seq_min;
	unsigned char		*byte_seq_max;

	TRACE_MESSAGE('i', ("map_d_e_l\n"));

	map_hdr = (itm_map_dense_enc_hdr_t *)(tbl_hdr + 1);
	map_ptr = ((unsigned char *)(map_hdr + 1) +
			map_hdr->source_len + map_hdr->source_len);
	map_error = (map_ptr + (tbl_hdr->number * map_hdr->result_len));
	if (0 == map_hdr->default_error) {
		map_error = (void *)(map_error + map_hdr->result_len);
	}
	byte_seq_min = (unsigned char *)(map_hdr + 1);
	byte_seq_max = byte_seq_min + map_hdr->source_len;

	do {
		if (*inbytesleft < map_hdr->source_len) {
			errno = EINVAL;
			TRACE_MESSAGE('e', ("map_d_e_l:error=%d\n", errno));
			return ((size_t)(-1));
		}

		j = hash_dense_encoding(*inbuf, map_hdr->source_len,
					byte_seq_min, byte_seq_max);

		if (((j < 0) || (tbl_hdr->number < j)) &&
		    (0 < map_hdr->default_error)) {
			errno = EILSEQ;
			TRACE_MESSAGE('e', ("map_d_e_l:error=%d\n", errno));
			return ((size_t)(-1));
		}

		if (*outbytesleft < map_hdr->result_len) {
			errno = E2BIG;
			TRACE_MESSAGE('e', ("map_d_e_l:error=%d\n", errno));
			return ((size_t)(-1));
		}

		if ((j < 0) || (tbl_hdr->number < j)) {
			if (0 == map_hdr->default_error) {
				p = (map_ptr +
				(tbl_hdr->number * map_hdr->result_len));
				for (i = 0; i < map_hdr->result_len; i++) {
					PUT(*(p + i));
				}
			} else {
				p = *inbuf;
				for (i = 0; i < map_hdr->source_len; i++) {
					PUT(*(p + i));
				}
			}
		} else {
			if ((1 == map_hdr->default_error) ||
			    (0 < map_hdr->error_num)) {
				if (0 != *(map_error + j)) {
					errno = EILSEQ;
					TRACE_MESSAGE('e',
					("map_d_e_l:error=%d\n", errno));
					return ((size_t)(-1));
				}
			}
			p = (map_ptr + (map_hdr->result_len * j));
			for (i = 0; i < map_hdr->result_len; i++) {
				PUT(*(p + i));
			}
		}
		DISCARD(map_hdr->source_len);
	} while ((0 < *inbytesleft) && (0 == once));

	return ((size_t)(0));
}



/*
 * Evaluate condition table
 *
 */
static size_t
eval_cond_tbl(
	icv_state_t		*ist,
	itm_place_t		cond_place,
	const unsigned char	**inbuf,
	size_t			*inbytesleft,
	size_t			outbytesleft,
	itm_direc_t		*direc
)
{
	itm_tbl_hdr_t		*cond_hdr;
	itm_cond_t		*cond;
	long			i;
	long			j;
	long			k;
	size_t			retval;
	itm_tbl_hdr_t		*rth;
	itm_range_hdr_t		*rtsh;
	unsigned char		*p;
	itm_tbl_hdr_t		*eth;
	itm_escapeseq_hdr_t	*eh;
	itm_data_t		*d;
	const unsigned char	*ip;
	size_t			ileft;

	retval = 0;
	ip =	*inbuf;
	ileft = *inbytesleft;
	cond_hdr = ADDR(cond_place);
	cond = (itm_cond_t *)(cond_hdr + 1);
	for (i = 0; i < cond_hdr->number; i++, cond++) {
		switch (cond->type) {
		case ITM_COND_BETWEEN:
			rth = ADDR(cond->operand.place);
			rtsh = (itm_range_hdr_t *)(rth + 1);
			if (ileft < rtsh->len) {
				errno = EINVAL;
				TRACE_MESSAGE('e',
				("eval_cond_tbl:error=%d\n", errno));
				retval = ((size_t)(-1));
				goto eval_cond_return;
			}
			p = (unsigned char *)(rtsh + 1);
			retval = 0;
			for (j = 0; j < rth->number;
			    j++,  p = (void *)(p + (2 * rtsh->len))) {
				retval = 1;
				for (k = 0; k < rtsh->len; k++) {
					if ((*(ip + k) < *(p + k)) ||
					    (*(p + rtsh->len + k) <
					    *(ip + k))) {
						retval = 0;
						break;
					}
				}
				if (1 == retval) {
					break;
				}
			}
			if (0 == retval) {
				TRACE_MESSAGE('b',
				("out of between (%p) len= rtsh=%ld\n",
				*ip, rtsh->len));
				goto eval_cond_return;
			}
			break; /* continue */
		case ITM_COND_ESCAPESEQ:
			/*
			 * if escape sequence occur,
			 * change ist->default_action and return 2.
			 * never return 1.
			 */
			retval = 0;
			eth = ADDR(cond->operand.place);
			eh = (itm_escapeseq_hdr_t *)(eth + 1);
			if (NULL == ist->default_action.itm_ptr) {
				ist->default_action = direc->action;
				TRACE_MESSAGE('E',
				("escape seq (default action=%6p, "
				"type=%ld) set\n",
				direc->action.itm_ptr,
				((itm_tbl_hdr_t *)(ADDR(direc->action)))
				->type));
			}
			retval = 0;
			if (*inbytesleft < eh->len_min) {
				break;
			}
			for (j = 0, d = (itm_data_t *)(eh + 1);
			    j < eth->number;
			    j++, d++) {
				if (*inbytesleft < d->size) {
					continue;
				}
				if (0 == memcmp(*inbuf, DADDR(d), d->size)) {
					TRACE_MESSAGE('E',
					("escape seq: discard=%ld chars\n",
					d->size));
					TRACE_MESSAGE('E', (
					"escape seq (default action=%6p, "
					"type=%ld) set\n",
					direc->action.itm_ptr,
					((itm_tbl_hdr_t *)
					(ADDR(direc->action)))->type));
					ist->default_action = direc->action;
					DISCARD(d->size);
					retval = 2;
					goto eval_cond_return;
				}
			}
			if (0 == retval) {
				goto eval_cond_return;
			}
			break; /* continue */
		case ITM_COND_EXPR:
			retval = eval_expr(ist, cond->operand.place,
					*inbytesleft, ip, outbytesleft);
			if (0 == retval) {
				goto eval_cond_return;
			} else {
				retval = 1;
			}
			break; /* continue */
		default:
			TRACE_MESSAGE('e', ("eval_cond_tbl:illegal cond=%d\n",
			cond->type));
			retval = (size_t)-1;
			goto eval_cond_return;
		}
	}

eval_cond_return:
	return (retval);
}

/*
 * Evaluate operation table
 *
 */
static size_t
eval_op_tbl(
	icv_state_t	*ist,
	itm_place_t	op_tbl_place,
	const unsigned char	**inbuf,
	size_t		*inbytesleft,
	unsigned char	**outbuf,
	size_t		*outbytesleft)
{
	itm_tbl_hdr_t	*op_hdr;
	itm_op_t	*operation;
	itm_place2_t	op_place;
	size_t		retval;
	long		i;

	retval = 0;

#if defined(OP_DEPTH_MAX)
	if (OP_DEPTH_MAX <= ist->op_depth) {
		errno = ELIBBAD;
		TRACE_MESSAGE('e', ("eval_op_tbl:error=%d\n", errno));
		return	(RETVALERR);
	}
	ist->op_depth += 1;
#endif /* OP_DEPTH_MAX */

	op_hdr = ADDR(op_tbl_place);
	operation = (itm_op_t *)(op_hdr + 1);

	op_place = op_tbl_place.itm_ptr + (sizeof (itm_tbl_hdr_t));
	for (i = 0; i < op_hdr->number; i++, operation++,
	    op_place += (sizeof (itm_op_t))) {
		TRACE_MESSAGE('O', ("eval_op_tbl: %ld %p\n", i, op_place));
		retval = eval_op(ist, op_place,
				inbuf, inbytesleft,
				outbuf, outbytesleft);
		if (((long)(retval)) < 0) {
#if defined(OP_DEPTH_MAX)
			ist->op_depth -= 1;
#endif /* OP_DEPTH_MAX */
			switch (retval) {
			case RETVALERR:
				return	(retval);
			case RETVALRET:
				if (0 == op_hdr->name.itm_ptr) {
					return	(RETVALRET);
				} else {
					return (0);
				}
			}
		}
	}
#if defined(OP_DEPTH_MAX)
	ist->op_depth -= 1;
#endif /* OP_DEPTH_MAX */
	return	(retval);
}


/*
 * Evaluate single operation
 *
 */
static size_t
eval_op(
	icv_state_t		*ist,
	itm_place2_t		op_place,
	const unsigned char	**inbuf,
	size_t			*inbytesleft,
	unsigned char		**outbuf,
	size_t			*outbytesleft)
{
	size_t			retval;
	itm_num_t		num;
	itm_op_t		*operation;
	itm_expr_t		*expr;
	itm_num_t		c;
	itm_num_t		i;
	itm_size_t		z;
	unsigned char		*p;
	itm_expr_t		*expr0;

#define	EVAL_EXPR(n)							\
	(expr0 = ADDR(operation->data.operand[(n)]),			\
	(itm_num_t)((expr0->type == ITM_EXPR_INT) ?		       	\
		expr0->data.itm_exnum :					\
		((expr0->type == ITM_EXPR_REG) ?			\
		REG(expr0->data.itm_exnum) :				\
		((expr0->type == ITM_EXPR_IN_VECTOR_D) ?		\
		((expr0->data.itm_exnum < 0) ?				\
		(((-1) == expr0->data.itm_exnum) ? *inbytesleft : 0) :	\
		((expr0->data.itm_exnum < *inbytesleft) ?		\
		(*(uchar_t *)(*inbuf + expr0->data.itm_exnum)) : 0)):	\
		eval_expr(ist, operation->data.operand[(n)],		\
		*inbytesleft, *inbuf, *outbytesleft)))))

	retval = 0;

	operation = (itm_op_t *)ADDR2(op_place);

	switch (operation->type) {
	case ITM_OP_EXPR:
		num = eval_expr(ist, operation->data.operand[0],
				*inbytesleft, *inbuf, *outbytesleft);
		TRACE_MESSAGE('o', ("ITM_OP_EXPR: %ld\n", retval));
		break;
	case ITM_OP_ERROR:
		num = eval_expr(ist, operation->data.operand[0],
				*inbytesleft, *inbuf, *outbytesleft);
		errno = (int)num;
		TRACE_MESSAGE('o', ("ITM_OP_ERROR: %ld\n", num));
		retval = (size_t)(-1);
		break;
	case ITM_OP_ERROR_D:
		errno = (int)operation->data.itm_opnum;
		TRACE_MESSAGE('o', ("ITM_OP_ERROR_D: %d\n", errno));
		retval = (size_t)(-1);
		break;
	case ITM_OP_OUT:
		expr = ADDR(operation->data.operand[0]);
		if ((*outbytesleft) == 0) {
			errno = E2BIG;
			TRACE_MESSAGE('e', ("eval_op:error=%d\n", errno));
			return ((size_t)(-1));
		}
		c = eval_expr(ist, operation->data.operand[0],
				*inbytesleft, *inbuf, *outbytesleft);
		PUT((uchar_t)c);
		retval = *inbytesleft;
		TRACE_MESSAGE('o', ("ITM_OP_OUT: %ld %ld\n", c, *inbytesleft));
		break;
	case ITM_OP_OUT_D:
		expr = ADDR(operation->data.operand[0]);
		if ((*outbytesleft) == 0) {
			errno = E2BIG;
			TRACE_MESSAGE('e', ("eval_op:error=%d\n", errno));
			return ((size_t)(-1));
		}
		PUT(0xff & (expr->data.itm_exnum));
		break;
	case ITM_OP_OUT_S:
		expr = ADDR(operation->data.operand[0]);
		if ((*outbytesleft) == 0) {
			errno = E2BIG;
			TRACE_MESSAGE('e', ("eval_op:error=%d\n", errno));
			return ((size_t)(-1));
		}
		z = expr->data.value.size;
		if (*outbytesleft < z) {
			errno = E2BIG;
			TRACE_MESSAGE('e', ("eval_op:error=%d\n", errno));
			return ((size_t)(-1));
		}
		p = DADDR(&(expr->data.value));
		for (; 0 < z; --z, p++) {
			PUT(*p);
		}
		break;
	case ITM_OP_OUT_R:
		expr = ADDR(operation->data.operand[0]);
		if ((*outbytesleft) == 0) {
			errno = E2BIG;
			TRACE_MESSAGE('e', ("eval_op:error=%d\n", errno));
			return ((size_t)(-1));
		}
		c = REG(expr->data.itm_exnum);
		PUT((uchar_t)c);
		break;
	case ITM_OP_OUT_INVD:
		expr = ADDR(operation->data.operand[0]);
		if ((*outbytesleft) == 0) {
			errno = E2BIG;
			TRACE_MESSAGE('e', ("eval_op:error=%d\n", errno));
			return ((size_t)(-1));
		}
		z = (((0 <= expr->data.itm_exnum) &&
			(expr->data.itm_exnum < *inbytesleft)) ?
			(*((unsigned char *)(*inbuf + expr->data.itm_exnum))) :
			(((-1) == expr->data.itm_exnum) ? *inbytesleft : 0));
		PUT((uchar_t)z);
		break;
	case ITM_OP_DISCARD:
#if defined(EVAL_EXPR)
		num = EVAL_EXPR(0);
#else /* !defined(EVAL_EXPR) */
		num = eval_expr(ist, operation->data.operand[0],
				*inbytesleft, *inbuf, *outbytesleft);
#endif /* defined(EVAL_EXPR) */
		TRACE_MESSAGE('o', ("ITM_OP_DISCARD: %ld\n", num));
#if defined(DISCARD)
		DISCARD((num <= *inbytesleft) ? ((ulong_t)num) :
						*inbytesleft);
#else /* defined(DISCARD) */
		for (num = ((num <= *inbytesleft) ? num : *inbytesleft);
		    0 < num; --num) {
			GET(c);
		}
#endif /* defined(DISCARD) */
		break;
	case ITM_OP_DISCARD_D:
		num = operation->data.itm_opnum;
		TRACE_MESSAGE('o', ("ITM_OP_DISCARD_D: %ld\n", num));
#if defined(DISCARD)
		DISCARD((num <= *inbytesleft) ? num : *inbytesleft);
#else /* defined(DISCARD) */
		for (num = ((num <= *inbytesleft) ? num : *inbytesleft);
		    0 < num; --num) {
			GET(c);
		}
#endif /* defined(DISCARD) */
		break;
	case ITM_OP_IF:
		c = eval_expr(ist, operation->data.operand[0],
				*inbytesleft, *inbuf, *outbytesleft);
		TRACE_MESSAGE('o', ("ITM_OP_IF: %ld\n", c));
		if (c) {
			retval = eval_op_tbl(ist,
					    operation->data.operand[1],
					    inbuf, inbytesleft,
					    outbuf, outbytesleft);
		}
		break;
	case ITM_OP_IF_ELSE:
		c = eval_expr(ist, operation->data.operand[0],
				*inbytesleft, *inbuf, *outbytesleft);
		TRACE_MESSAGE('o', ("ITM_OP_IF_ELSE: %ld\n", c));
		if (c) {
			retval = eval_op_tbl(ist,
						operation->data.operand[1],
						inbuf, inbytesleft,
						outbuf, outbytesleft);
		} else {
			retval = eval_op_tbl(ist,
						operation->data.operand[2],
						inbuf, inbytesleft,
						outbuf, outbytesleft);
		}
		break;
	case ITM_OP_DIRECTION:
		TRACE_MESSAGE('o', ("ITM_OP_DIRECTION: %p\n",
			operation->data.operand[0].itm_ptr));
		ist->direc = ADDR(operation->data.operand[0]);
		return ((size_t)(-2));
	case ITM_OP_MAP:
		TRACE_MESSAGE('o', ("ITM_OP_MAP: %p\n",
				operation->data.operand[0].itm_ptr));
		i = 0;
		if (0 != operation->data.operand[1].itm_ptr) {
#if defined(EVAL_EXPR)
			i = EVAL_EXPR(1);
#else /* !defined(EVAL_EXPR) */
			i = eval_expr(ist, operation->data.operand[1],
				*inbytesleft, *inbuf, *outbytesleft);
#endif /* defined(EVAL_EXPR) */
			(*inbytesleft) -= i;
			(*inbuf) += i;
		}

		retval = map_i_f(ADDR(operation->data.operand[0]),
				inbuf, inbytesleft,
				outbuf, outbytesleft, 1);
		if ((size_t)(-1) == retval) {
			(*outbytesleft) += i;
			(*outbuf) -= i;
		}
		break;
	case ITM_OP_OPERATION:
		TRACE_MESSAGE('o', ("ITM_OP_OPERATION: %p\n",
				operation->data.operand[0].itm_ptr));
		retval = eval_op_tbl(ist,
				operation->data.operand[0],
				inbuf, inbytesleft,
				outbuf, outbytesleft);

		break;
	case ITM_OP_INIT:
		TRACE_MESSAGE('o', ("ITM_OP_INIT: %p\n",
				ist->itm_hdr->op_init_tbl));
		if (0 != ist->itm_hdr->op_init_tbl.itm_ptr) {
			retval = eval_op_tbl(ist,
						ist->itm_hdr->op_init_tbl,
						inbuf, inbytesleft,
						outbuf, outbytesleft);
		} else {
			op_init_default(ist);
			retval = (size_t)-2;
		}
		break;
	case ITM_OP_RESET:
		TRACE_MESSAGE('o', ("ITM_OP_RESET: %p\n",
				ist->itm_hdr->op_reset_tbl));
		if (0 != ist->itm_hdr->op_reset_tbl.itm_ptr) {
			retval = eval_op_tbl(ist,
						ist->itm_hdr->op_reset_tbl,
						inbuf, inbytesleft,
						outbuf, outbytesleft);
		} else {
			op_reset_default(ist);
			retval = (size_t)-2;
		}
		break;
	case ITM_OP_BREAK:
		TRACE_MESSAGE('o', ("ITM_OP_BREAK\n"));
		return	(RETVALBRK);
	case ITM_OP_RETURN:
		TRACE_MESSAGE('o', ("ITM_OP_RETURN\n"));
		return	(RETVALRET);
	case ITM_OP_PRINTCHR:
		c = eval_expr(ist, operation->data.operand[0],
				*inbytesleft, *inbuf, *outbytesleft);
		(void) fputc((uchar_t)c, stderr);
		TRACE_MESSAGE('o',
		("ITM_OP_PRINTCHR: %ld %ld\n", c, *inbytesleft));
		break;
	case ITM_OP_PRINTHD:
		c = eval_expr(ist, operation->data.operand[0],
				 *inbytesleft, *inbuf, *outbytesleft);
		(void) fprintf(stderr, "%lx", c);
		TRACE_MESSAGE('o',
		("ITM_OP_PRINTHD: %ld %ld\n", c, *inbytesleft));
		break;
	case ITM_OP_PRINTINT:
		c = eval_expr(ist, operation->data.operand[0],
				*inbytesleft, *inbuf, *outbytesleft);
		(void) fprintf(stderr, "%ld", c);
		TRACE_MESSAGE('o',
		("ITM_OP_PRINTINT: %ld %ld\n", c, *inbytesleft));
		break;
	default: /* never */
		errno = ELIBBAD;
		TRACE_MESSAGE('e', ("eval_op:error=%d\n", errno));
		return (size_t)(-1);
	}
	return	(retval);

#undef EVAL_EXPR
}


/*
 * Evaluate expression
 */
static itm_num_t
eval_expr(
	icv_state_t		*ist,
	itm_place_t		expr_place,
	size_t			inbytesleft,
	const unsigned char	*inbuf,
	size_t			outbytesleft)
{
	itm_expr_t		*expr;
	itm_expr_t		*expr_op;
	itm_num_t		num;
	unsigned char		*p;
	long			i;
	itm_expr_t		*expr0;
	itm_num_t		num00;
	itm_num_t		num01;

#define	EVAL_EXPR_E(n) (eval_expr(ist, expr->data.operand[(n)],		\
					inbytesleft, inbuf, outbytesleft))
#define	EVAL_EXPR_D(n)	((itm_num_t)(expr->data.operand[(n)].itm_ptr))
#define	EVAL_EXPR_R(n)	(REG((itm_num_t)(expr->data.operand[(n)].itm_ptr)))
#define	EVAL_EXPR_INVD(n)						\
	((num0 ## n) = ((itm_num_t)(expr->data.operand[(n)].itm_ptr)),	\
	((num0 ## n) < 0) ?						\
	(((-1) == (num0 ## n)) ? inbytesleft : 0) :			\
	(((num0 ## n) < inbytesleft) ?					\
	(*(unsigned char *)(inbuf + (num0 ## n))) : 0))
#define	EVAL_EXPR(n)							\
	(expr0 = ADDR(expr->data.operand[(n)]),				\
	(itm_num_t)((expr0->type == ITM_EXPR_INT) ?		       	\
	expr0->data.itm_exnum :						\
	((expr0->type == ITM_EXPR_REG) ?				\
	REG(expr0->data.itm_exnum) :					\
	((expr0->type == ITM_EXPR_IN_VECTOR_D) ?			\
	((expr0->data.itm_exnum < 0) ?					\
	(((-1) == expr0->data.itm_exnum) ? inbytesleft : 0) :		\
	((expr0->data.itm_exnum < inbytesleft) ?			\
	(*(uchar_t *)(inbuf + expr0->data.itm_exnum)) : 0)) :		\
	eval_expr(ist, expr->data.operand[(n)],				\
	inbytesleft, inbuf, outbytesleft)))))

#define	EVAL_OP_BIN_PROTO(op, name, name0, name1)			\
	case ITM_EXPR_##name##_##name0##_##name1:			\
		return (EVAL_EXPR_##name0(0) op EVAL_EXPR_##name1(1));

#define	EVAL_OP_BIN1(op, name)					\
		EVAL_OP_BIN_PROTO(op, name, E, E)		\
		EVAL_OP_BIN_PROTO(op, name, E, D)		\
		EVAL_OP_BIN_PROTO(op, name, E, R)		\
		EVAL_OP_BIN_PROTO(op, name, E, INVD)

#define	EVAL_OP_BIN2(op, name)					\
		EVAL_OP_BIN_PROTO(op, name, D, E)		\
		EVAL_OP_BIN_PROTO(op, name, D, D)		\
		EVAL_OP_BIN_PROTO(op, name, D, R)		\
		EVAL_OP_BIN_PROTO(op, name, D, INVD)

#define	EVAL_OP_BIN3(op, name)					\
		EVAL_OP_BIN_PROTO(op, name, R, E)		\
		EVAL_OP_BIN_PROTO(op, name, R, D)		\
		EVAL_OP_BIN_PROTO(op, name, R, R)		\
		EVAL_OP_BIN_PROTO(op, name, R, INVD)

#define	EVAL_OP_BIN4(op, name)					\
		EVAL_OP_BIN_PROTO(op, name, INVD, E)		\
		EVAL_OP_BIN_PROTO(op, name, INVD, D)		\
		EVAL_OP_BIN_PROTO(op, name, INVD, R)		\
		EVAL_OP_BIN_PROTO(op, name, INVD, INVD)

#define	EVAL_OP_BIN_PROTECT_PROTO(op, name, name0, name1)	\
	case ITM_EXPR_##name##_##name0##_##name1:		\
		num = EVAL_EXPR_##name1(1);			\
		if (0 != num) {					\
			return (EVAL_EXPR_##name0(0) op num);	\
		} else {					\
			return (0);				\
		}

#define	EVAL_OP_BIN_PROTECT1(op, name)				\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, E, E)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, E, D)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, E, R)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, E, INVD)

#define	EVAL_OP_BIN_PROTECT2(op, name)				\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, D, E)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, D, D)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, D, R)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, D, INVD)

#define	EVAL_OP_BIN_PROTECT3(op, name)				\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, R, E)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, R, D)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, R, R)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, R, INVD)

#define	EVAL_OP_BIN_PROTECT4(op, name)				\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, INVD, E)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, INVD, D)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, INVD, R)	\
		EVAL_OP_BIN_PROTECT_PROTO(op, name, INVD, INVD)

	expr = ADDR(expr_place);

	switch (expr->type) {
	case ITM_EXPR_NONE:		/* not used */
		return (0);
	case ITM_EXPR_NOP:		/* not used */
		return (0);
	case ITM_EXPR_NAME:		/* not used */
		return (0);
	case ITM_EXPR_INT:		/* integer */
		return (expr->data.itm_exnum);
	case ITM_EXPR_SEQ:		/* byte sequence */
		if ((sizeof (itm_place_t)) < expr->data.value.size) {
			p = (unsigned char *)ADDR(expr->data.value.place);
		} else {
			p = (unsigned char *)&(expr->data.value.place);
		}
		for (i = 0, num = 0; i < expr->data.value.size; i++, p++) {
			num = ((num << 8) | *p);
		}
		return	(num);
	case ITM_EXPR_REG:		/* register */
		return (REG(expr->data.itm_exnum));
	case ITM_EXPR_IN_VECTOR:	/* in[expr] */
		num = EVAL_EXPR(0);
		if ((0 <= num) && (num < inbytesleft)) {
			return (*((unsigned char *)(inbuf + num)));
		} else if ((-1) == num) {
			return	(inbytesleft);
		} else {
			return (0);
		}
	case ITM_EXPR_IN_VECTOR_D:	/* in[DECIMAL] */
		num = expr->data.itm_exnum;
		if ((0 <= num) && (num < inbytesleft)) {
			return (*((unsigned char *)(inbuf + num)));
		} else if ((-1) == num) {
			return	(inbytesleft);
		} else {
			return (0);
		}
	case ITM_EXPR_OUT:		/* out */
		return	(outbytesleft);
	case ITM_EXPR_TRUE:		/* true */
		return (1);
	case ITM_EXPR_FALSE:		/* false */
		return (0);
	case ITM_EXPR_UMINUS:		/* unary minus */
		return ((-1) * EVAL_EXPR(0));
#define	PLUS_FOR_CSTYLE_CLEAN +
#define	MINUS_FOR_CSTYLE_CLEAN -
#define	MUL_FOR_CSTYLE_CLEAN *
#define	DIV_FOR_CSTYLE_CLEAN /
#define	MOD_FOR_CSTYLE_CLEAN %
#define	SHIFT_L_FOR_CSTYLE_CLEAN <<
#define	SHIFT_R_FOR_CSTYLE_CLEAN >>
#define	OR_FOR_CSTYLE_CLEAN |
#define	XOR_FOR_CSTYLE_CLEAN ^
#define	AND_FOR_CSTYLE_CLEAN &
#define	EQ_FOR_CSTYLE_CLEAN ==
#define	NE_FOR_CSTYLE_CLEAN !=
#define	GT_FOR_CSTYLE_CLEAN >
#define	GE_FOR_CSTYLE_CLEAN >=
#define	LT_FOR_CSTYLE_CLEAN <
#define	LE_FOR_CSTYLE_CLEAN <=
	EVAL_OP_BIN1(PLUS_FOR_CSTYLE_CLEAN, PLUS)	/* A + B */
	EVAL_OP_BIN2(PLUS_FOR_CSTYLE_CLEAN, PLUS)	/* A + B */
	EVAL_OP_BIN3(PLUS_FOR_CSTYLE_CLEAN, PLUS)	/* A + B */
	EVAL_OP_BIN4(PLUS_FOR_CSTYLE_CLEAN, PLUS)	/* A + B */

	EVAL_OP_BIN1(MINUS_FOR_CSTYLE_CLEAN, MINUS)	/* A - B */
	EVAL_OP_BIN2(MINUS_FOR_CSTYLE_CLEAN, MINUS)	/* A - B */
	EVAL_OP_BIN3(MINUS_FOR_CSTYLE_CLEAN, MINUS)	/* A - B */
	EVAL_OP_BIN4(MINUS_FOR_CSTYLE_CLEAN, MINUS)	/* A - B */

	EVAL_OP_BIN1(MUL_FOR_CSTYLE_CLEAN, MUL)		/* A * B */
	EVAL_OP_BIN2(MUL_FOR_CSTYLE_CLEAN, MUL)		/* A * B */
	EVAL_OP_BIN3(MUL_FOR_CSTYLE_CLEAN, MUL)		/* A * B */
	EVAL_OP_BIN4(MUL_FOR_CSTYLE_CLEAN, MUL)		/* A * B */

	EVAL_OP_BIN_PROTECT1(DIV_FOR_CSTYLE_CLEAN, DIV)	/* A / B */
	EVAL_OP_BIN_PROTECT2(DIV_FOR_CSTYLE_CLEAN, DIV)	/* A / B */
	EVAL_OP_BIN_PROTECT3(DIV_FOR_CSTYLE_CLEAN, DIV)	/* A / B */
	EVAL_OP_BIN_PROTECT4(DIV_FOR_CSTYLE_CLEAN, DIV)	/* A / B */

	EVAL_OP_BIN_PROTECT1(MOD_FOR_CSTYLE_CLEAN, MOD)	/* A % B */
	EVAL_OP_BIN_PROTECT2(MOD_FOR_CSTYLE_CLEAN, MOD)	/* A % B */
	EVAL_OP_BIN_PROTECT3(MOD_FOR_CSTYLE_CLEAN, MOD)	/* A % B */
	EVAL_OP_BIN_PROTECT4(MOD_FOR_CSTYLE_CLEAN, MOD)	/* A % B */

	EVAL_OP_BIN1(SHIFT_L_FOR_CSTYLE_CLEAN, SHIFT_L)	/* A << B */
	EVAL_OP_BIN2(SHIFT_L_FOR_CSTYLE_CLEAN, SHIFT_L)	/* A << B */
	EVAL_OP_BIN3(SHIFT_L_FOR_CSTYLE_CLEAN, SHIFT_L)	/* A << B */
	EVAL_OP_BIN4(SHIFT_L_FOR_CSTYLE_CLEAN, SHIFT_L)	/* A << B */

	EVAL_OP_BIN1(SHIFT_R_FOR_CSTYLE_CLEAN, SHIFT_R)	/* A >> B */
	EVAL_OP_BIN2(SHIFT_R_FOR_CSTYLE_CLEAN, SHIFT_R)	/* A >> B */
	EVAL_OP_BIN3(SHIFT_R_FOR_CSTYLE_CLEAN, SHIFT_R)	/* A >> B */
	EVAL_OP_BIN4(SHIFT_R_FOR_CSTYLE_CLEAN, SHIFT_R)	/* A >> B */

	EVAL_OP_BIN1(OR_FOR_CSTYLE_CLEAN, OR)		/* A |	B */
	EVAL_OP_BIN2(OR_FOR_CSTYLE_CLEAN, OR)		/* A |	B */
	EVAL_OP_BIN3(OR_FOR_CSTYLE_CLEAN, OR)		/* A |	B */
	EVAL_OP_BIN4(OR_FOR_CSTYLE_CLEAN, OR)		/* A |	B */

	EVAL_OP_BIN1(XOR_FOR_CSTYLE_CLEAN, XOR)		/* A ^	B */
	EVAL_OP_BIN2(XOR_FOR_CSTYLE_CLEAN, XOR)		/* A ^	B */
	EVAL_OP_BIN3(XOR_FOR_CSTYLE_CLEAN, XOR)		/* A ^	B */
	EVAL_OP_BIN4(XOR_FOR_CSTYLE_CLEAN, XOR)		/* A ^	B */

	EVAL_OP_BIN1(AND_FOR_CSTYLE_CLEAN, AND)		/* A &	B */
	EVAL_OP_BIN2(AND_FOR_CSTYLE_CLEAN, AND)		/* A &	B */
	EVAL_OP_BIN3(AND_FOR_CSTYLE_CLEAN, AND)		/* A &	B */
	EVAL_OP_BIN4(AND_FOR_CSTYLE_CLEAN, AND)		/* A &	B */

	EVAL_OP_BIN1(EQ_FOR_CSTYLE_CLEAN, EQ)		/* A == B */
	EVAL_OP_BIN2(EQ_FOR_CSTYLE_CLEAN, EQ)		/* A == B */
	EVAL_OP_BIN3(EQ_FOR_CSTYLE_CLEAN, EQ)		/* A == B */
	EVAL_OP_BIN4(EQ_FOR_CSTYLE_CLEAN, EQ)		/* A == B */

	EVAL_OP_BIN1(NE_FOR_CSTYLE_CLEAN, NE)		/* A != B */
	EVAL_OP_BIN2(NE_FOR_CSTYLE_CLEAN, NE)		/* A != B */
	EVAL_OP_BIN3(NE_FOR_CSTYLE_CLEAN, NE)		/* A != B */
	EVAL_OP_BIN4(NE_FOR_CSTYLE_CLEAN, NE)		/* A != B */

	EVAL_OP_BIN1(GT_FOR_CSTYLE_CLEAN, GT)		/* A >	B */
	EVAL_OP_BIN2(GT_FOR_CSTYLE_CLEAN, GT)		/* A >	B */
	EVAL_OP_BIN3(GT_FOR_CSTYLE_CLEAN, GT)		/* A >	B */
	EVAL_OP_BIN4(GT_FOR_CSTYLE_CLEAN, GT)		/* A >	B */

	EVAL_OP_BIN1(GE_FOR_CSTYLE_CLEAN, GE)		/* A >= B */
	EVAL_OP_BIN2(GE_FOR_CSTYLE_CLEAN, GE)		/* A >= B */
	EVAL_OP_BIN3(GE_FOR_CSTYLE_CLEAN, GE)		/* A >= B */
	EVAL_OP_BIN4(GE_FOR_CSTYLE_CLEAN, GE)		/* A >= B */

	EVAL_OP_BIN1(LT_FOR_CSTYLE_CLEAN, LT)		/* A <	B */
	EVAL_OP_BIN2(LT_FOR_CSTYLE_CLEAN, LT)		/* A <	B */
	EVAL_OP_BIN3(LT_FOR_CSTYLE_CLEAN, LT)		/* A <	B */
	EVAL_OP_BIN4(LT_FOR_CSTYLE_CLEAN, LT)		/* A <	B */

	EVAL_OP_BIN1(LE_FOR_CSTYLE_CLEAN, LE)		/* A <= B */
	EVAL_OP_BIN2(LE_FOR_CSTYLE_CLEAN, LE)		/* A <= B */
	EVAL_OP_BIN3(LE_FOR_CSTYLE_CLEAN, LE)		/* A <= B */
	EVAL_OP_BIN4(LE_FOR_CSTYLE_CLEAN, LE)		/* A <= B */

	case ITM_EXPR_NOT:		/*   !A	  */
		return (!(EVAL_EXPR(0)));
	case ITM_EXPR_NEG:		/*   ~A	  */
		return (~(EVAL_EXPR(0)));
	case ITM_EXPR_LOR:		/* A || B */
		if (0 != (num = EVAL_EXPR(0)))
			return	(num);
		if (0 != (num = EVAL_EXPR(1)))
			return	(num);
		return (0);
	case ITM_EXPR_LAND:		/* A && B */
		if (0 == EVAL_EXPR(0))
			return (0);
		if (0 == (num = EVAL_EXPR(1)))
			return (0);
		return	(num);
	case ITM_EXPR_ASSIGN:		/* A  = B */
		num = EVAL_EXPR(1);
		if (expr->data.operand[0].itm_ptr < ist->itm_hdr->reg_num) {
			return (*(ist->regs + expr->data.operand[0].itm_ptr)
				= num);
		} else {
			return (0);
		}
	case ITM_EXPR_IN_EQ:		/* in == A */
		expr_op = ADDR(expr->data.operand[0]);
		switch (expr_op->type) {
		case ITM_EXPR_SEQ:
			if (inbytesleft < expr_op->data.value.size) {
				return (0);
			}
			p = DADDR(&(expr_op->data.value));
			for (i = 0; i < expr_op->data.value.size; i++, p++) {
				if (*p != *(inbuf + i)) {
					return (0);
				}
			}
			return (1);
		default:
			num = EVAL_EXPR(0);
			return (num == *((unsigned char *)inbuf));
		}
	default:
		break;
	}

	return (0);

#undef EVAL_EXPR_E
#undef EVAL_EXPR_D
#undef EVAL_EXPR_R
#undef EVAL_EXPR_INVD
#undef EVAL_EXPR
}


/*
 * maintain ITM reference information
 */
static void
itm_ref_free(int fd, void *ptr0, void *ptr1, void *ptr2, size_t len)
{
	int	r;
	r = errno;
	if (0 <= fd) {
		(void) close(fd);
	}
	free(ptr0);
	free(ptr1);
	if (0 < len) {
		(void) munmap(ptr2, len);
	}
	errno = r;
}

static itm_ref_t *
itm_ref_inc(const char		*itm)
{
	itm_ref_t	*ref;
	itm_hdr_t	*hdr;
	struct stat	st;
	int		fd;

	fd = open(itm, O_RDONLY, 0);
	if (fd == -1) {
		itm_ref_free(-1, NULL, NULL, NULL, 0);
		return	(NULL);
	}

	if (fstat(fd, &st) == -1) {
		itm_ref_free(fd, NULL, NULL, NULL, 0);
		return	(NULL);
	}
	hdr = (void *) mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
	if (MAP_FAILED == hdr) {
		itm_ref_free(fd, NULL, NULL, NULL, 0);
		return	(NULL);
	}

	(void) close(fd);

	ref = malloc(sizeof (itm_ref_t));
	if (NULL == ref) {
		itm_ref_free(-1, NULL, NULL, hdr, st.st_size);
		return	(NULL);
	}
	ref->name = malloc(strlen(itm) + 1);
	if (NULL == ref->name) {
		itm_ref_free(-1, ref, NULL, hdr, st.st_size);
		return	(NULL);
	}
	(void) strcpy(ref->name, itm);
	ref->hdr = hdr;
	ref->len = st.st_size;

	if ((hdr->ident[0] != ITM_IDENT_0) ||
	    (hdr->ident[1] != ITM_IDENT_1) ||
	    (hdr->ident[2] != ITM_IDENT_2) ||
	    (hdr->ident[3] != ITM_IDENT_3) ||
	    (hdr->spec[0] != ITM_SPEC_0) ||
	    (hdr->spec[1] != ITM_SPEC_1) ||
	    (hdr->spec[2] != ITM_SPEC_2) ||
#if defined(_LITTLE_ENDIAN)
#if defined(_LP64)
	    ((hdr->spec[3] != ITM_SPEC_3_32_LITTLE_ENDIAN) &&
	    (hdr->spec[3] != ITM_SPEC_3_64_LITTLE_ENDIAN)) ||
#else
	    (hdr->spec[3] != ITM_SPEC_3_32_LITTLE_ENDIAN) ||
#endif
#else
#if defined(_LP64)
	    ((hdr->spec[3] != ITM_SPEC_3_32_BIG_ENDIAN) &&
	    (hdr->spec[3] != ITM_SPEC_3_64_BIG_ENDIAN)) ||
#else
	    (hdr->spec[3] != ITM_SPEC_3_32_BIG_ENDIAN) ||
#endif
#endif
	    (hdr->version[0] != ITM_VER_0) ||
	    (hdr->version[1] != ITM_VER_1) ||
	    (hdr->version[2] != ITM_VER_2) ||
	    (hdr->version[3] != ITM_VER_3) ||
	    (((size_t)(hdr->itm_size.itm_ptr)) != st.st_size)) {
		itm_ref_free(-1, ref, ref->name,
				ref->hdr, ref->len);
		errno = ELIBBAD;
		TRACE_MESSAGE('e', ("itm_ref_inc:error=%d\n", errno));
		return	(NULL);
	}

	return	(ref);
}


static void
itm_ref_dec(itm_ref_t	*ref)
{
	(void) munmap((char *)(ref->hdr), ref->len);
	free(ref->name);
	free(ref);
}


static void
op_init_default(icv_state_t	*ist)
{
	ist->direc = ADDR(ist->itm_hdr->direc_init_tbl);
	regs_init(ist);
}


static void
op_reset_default(icv_state_t	*ist)
{
	ist->direc = ADDR(ist->itm_hdr->direc_init_tbl);
	regs_init(ist);
}


static void
regs_init(icv_state_t	*ist)
{
	if (0 < ist->itm_hdr->reg_num) {
		(void) memset(ist->regs, 0,
			(sizeof (itm_num_t)) * ist->itm_hdr->reg_num);
	}
}


#if defined(DEBUG)
static void
trace_init()
{
	char	*env_val;
	char	*p;

	env_val = getenv("ITM_INT_TRACE");
	if (NULL == env_val)
		return;

	for (p = env_val; *p; p++) {
		trace_option[(*p) & 0x007f] = 1;
	}
}

static void
trace_message(char	*format, ...)
{
	va_list	ap;

	va_start(ap, format);

	(void) vfprintf(stderr, format, ap);

	va_end(ap);
}
#endif /* DEBUG */
