Ethereal-dev: Re: [ethereal-dev] Dissector exceptions

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: Gilbert Ramirez <gram@xxxxxxxxxx>
Date: Wed, 10 May 2000 02:24:14 -0500
I've got the rough draft of the dissector exceptions working well now.
Composite tvbuffs are now implemented, although Ethereal won't use them yet.
I'll be able to check in the code soon, after a little more testing, and
some cross-platform testing (read as "win32"). I also need to set up my
regression testing environment; perhaps someone can provide a generic
script/Makefile for regression testing with tethereal so as we convert
we don't introduce new bugs.

I've converted the ethernet and fddi dissectors so far.  I'll
convert LLC or IPX next, to make sure TVBUFF_SUBSET is working properly.
(my tests show that TVBUFF_SUBSET is working, but the tests are external to
Ethereal).

There's enough compatibility that we can convert dissector-by-dissector,
because a tvbuff-using dissector can call a non-tvbuff-using one, and
vice-versa.  However, the dissectors that are called via the dissector
table routines will have to be done at one time, because the dissector_t*
typedef will have to be changed from:

/* types for sub-dissector lookup */
typedef void (*dissector_t)(const u_char *, int, frame_data *, proto_tree *);

to:

/* types for sub-dissector lookup */
typedef void (*dissector_t)(tvbuf_t *, packet_info*, proto_tree *);

(unless we change the dissector table routines)

Also, I'll change the format of *all* proto_tree_add_*() calls to always
include a tvbuff_t* before the offset argument. This allows the dissector
to specify the offset of a field relative to the start of the tvbuff,
and allow the proto_tree routines to ask the tvbuff what its offset is
relative to the start of the frame. For example:

void
dissect_eth(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
	...

  if (tree) {

          ti = proto_tree_add_protocol_format(tree, proto_eth, tvb, 0, ETH_HEADER_SIZE,
		  "IEEE 802.3 %s", (ethhdr_type == ETHERNET_802_3 ? "Raw " : ""));

	  fh_tree = proto_item_add_subtree(ti, ett_ieee8023);

	  proto_tree_add_item(fh_tree, hf_eth_dst, tvb, 0, 6, dst_addr);
	  proto_tree_add_item(fh_tree, hf_eth_src, tvb, 6, 6, src_addr);

	  ...
  }
  ...
}


When I check in the code, I'll change all invocations of proto_tree_add_*()
for the dissectors that I won't have changed to use tvbuff (which will be
the majority of them) to send a 'NULL' in lieu of the tvbuff pointer.
The proto_tree routines will understand that NULL to mean that there is only
one offset to consider, and it's the offset of the field relative to the
start of the frame. This will allow the tvbuff code to be checked in
with only a few dissectors converted.

The conversion process won't be difficult, but it will be tedious.
It would be helpful if, after the initial tvbuff/exception code is checked in,
many people could convert at least one dissector.
The benefits of converting all the dissectors are, of course:

1) Dissectors won't have to check the bounds of the frame.
2) Ethereal won't crash when a dissector reads beyond the end of the frame.
3) A short frame will be automatically labelled as such in the GUI protocol tree.

Attached are my current tvbuff.[ch] files for those that are interested.

--gilbert
/* tvbuff.c
 *
 * Testy, Virtual(-izable) Buffer of guint8*'s
 * 
 * "Testy" -- the buffer gets mad when an attempt to access data
 * 		beyond the bounds of the buffer. An exception is thrown.
 *
 * "Virtual" -- the buffer can have its own data, can use a subset of
 * 		the data of a backing tvbuff, or can be a composite of
 * 		other tvbuffs.
 *
 * $Id$
 *
 * Copyright (c) 2000 by Gilbert Ramirez <gram@xxxxxxxxxx>
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxx>
 * Copyright 1998 Gerald Combs
 *
 * 
 * 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 2
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#ifndef __TVBUFF_H__
#include "tvbuff.h"
#endif

#include <stdio.h>	/* for tvb_dump() */

#include <string.h>

#define g_debug(format, args...) \
		g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
		"%s:(%s):%d: " format , \
		__FILE__, G_GNUC_PRETTY_FUNCTION, __LINE__ , ##args) ;

	
/* Pointer versions of ntohs and ntohl.  Given a pointer to a member of a
 * byte array, returns the value of the two or four bytes at the pointer.
 * The pletoh[sl] versions return the little-endian representation.
 */

#define pntohs(p)  ((guint16)                       \
                    ((guint16)*((guint8 *)p+0)<<8|  \
                     (guint16)*((guint8 *)p+1)<<0))

#define pntohl(p)  ((guint32)*((guint8 *)p+0)<<24|  \
                    (guint32)*((guint8 *)p+1)<<16|  \
                    (guint32)*((guint8 *)p+2)<<8|   \
                    (guint32)*((guint8 *)p+3)<<0)

#define pletohs(p) ((guint16)                       \
                    ((guint16)*((guint8 *)p+1)<<8|  \
                     (guint16)*((guint8 *)p+0)<<0))

#define pletohl(p) ((guint32)*((guint8 *)p+3)<<24|  \
                    (guint32)*((guint8 *)p+2)<<16|  \
                    (guint32)*((guint8 *)p+1)<<8|   \
                    (guint32)*((guint8 *)p+0)<<0)


typedef struct {
	/* The backing tvbuff_t */
	tvbuff_t	*tvb;

	/* The offset/length of 'tvb' to which I'm privy */
	guint		offset;
	guint		length;

} tvb_backing_t;

typedef struct {
	GSList		*tvbs;

	/* Used for quick testing to see if this
	 * is the tvbuff that a COMPOSITE is
	 * interested in. */
	guint		*start_offsets;
	guint		*end_offsets;

} tvb_comp_t;

struct tvbuff {
	/* Record-keeping */
	tvbuff_type		type;
	gboolean		initialized;
	guint			usage_count;

	/* The tvbuffs in which this tvbuff is a member
	 * (that is, a backing tvbuff for a TVBUFF_SUBSET
	 * or a member for a TVB_COMPOSITE) */
	GSList			*used_in;

	/* TVBUFF_SUBSET and TVBUFF_COMPOSITE keep track
	 * of the other tvbuff's they use */
	union {
		tvb_backing_t	subset;
		tvb_comp_t	composite;
	} tvbuffs;

	/* We're either a TVBUFF_REAL_DATA or a
	 * TVBUFF_SUBSET that has a backing buffer that
	 * has real_data != NULL, or a TVBUFF_COMPOSITE
	 * which has flattened its data due to a call
	 * to tvb_get_ptr().
	 */
	guint8			*real_data;

	/* Length of virtual buffer (and/or real_data). */
	guint			length;

	/* Func to call when actually freed */
	tvbuff_free_cb_t	free_cb;
};

static guint8*
ensure_contiguous(tvbuff_t *tvb, gint offset, gint length);

/* We dole out tvbuff's from this memchunk. */
GMemChunk *tvbuff_mem_chunk = NULL;

void
tvbuff_init(void)
{
	if (!tvbuff_mem_chunk)
		tvbuff_mem_chunk = g_mem_chunk_create(tvbuff_t, 20, G_ALLOC_AND_FREE);
}

void
tvbuff_cleanup(void)
{
	if (tvbuff_mem_chunk)
		g_mem_chunk_destroy(tvbuff_mem_chunk);

	tvbuff_mem_chunk = NULL;
}




static void
tvb_init(tvbuff_t *tvb, tvbuff_type type)
{
	tvb_backing_t	*backing;
	tvb_comp_t	*composite;

	tvb->type		= type;
	tvb->initialized	= FALSE;
	tvb->usage_count	= 1;
	tvb->length		= 0;
	tvb->free_cb		= NULL;
	tvb->real_data		= NULL;
	tvb->used_in		= NULL;

	switch(type) {
		case TVBUFF_REAL_DATA:
			/* Nothing */
			break;

		case TVBUFF_SUBSET:
			backing = &tvb->tvbuffs.subset;
			backing->tvb	= NULL;
			backing->offset	= 0;
			backing->length	= 0;
			break;

		case TVBUFF_COMPOSITE:
			composite = &tvb->tvbuffs.composite;
			composite->tvbs			= NULL;
			composite->start_offsets	= NULL;
			composite->end_offsets		= NULL;
			break;
	}
}


tvbuff_t*
tvb_new(tvbuff_type type)
{
	tvbuff_t	*tvb;

	tvb = g_chunk_new(tvbuff_t, tvbuff_mem_chunk);
	g_assert(tvb);

	tvb_init(tvb, type);

	return tvb;
}

void
tvb_free(tvbuff_t* tvb)
{
	tvbuff_t	*member_tvb;
	tvb_comp_t	*composite;
	GSList		*slist;

	tvb->usage_count--;

	if (tvb->usage_count == 0) {
		switch (tvb->type) {
		case TVBUFF_REAL_DATA:
			if (tvb->free_cb) {
				tvb->free_cb(tvb->real_data);
			}
			break;

		case TVBUFF_SUBSET:
			tvb_decrement_usage_count(tvb->tvbuffs.subset.tvb, 1);
			break;

		case TVBUFF_COMPOSITE:
			composite = &tvb->tvbuffs.composite;
			for (slist = composite->tvbs; slist != NULL ; slist = slist->next) {
				member_tvb = slist->data;
				tvb_decrement_usage_count(member_tvb, 1);
			}

			g_slist_free(composite->tvbs);

			if (composite->start_offsets)
				g_free(composite->start_offsets);
			if (composite->end_offsets)
				g_free(composite->end_offsets);
			if (tvb->real_data)
				g_free(tvb->real_data);

			break;
		}

		if (tvb->used_in) {
			g_slist_free(tvb->used_in);
		}

		g_debug("Freeing tvbuff=0x%08x", (unsigned int) tvb);
		g_chunk_free(tvb, tvbuff_mem_chunk);
	}
}

guint
tvb_increment_usage_count(tvbuff_t* tvb, guint count)
{
	tvb->usage_count += count;

	return tvb->usage_count;
}

guint
tvb_decrement_usage_count(tvbuff_t* tvb, guint count)
{
	if (tvb->usage_count <= count) {
		tvb->usage_count = 1;
		tvb_free(tvb);
		return 0;
	}
	else {
		tvb->usage_count -= count;
		return tvb->usage_count;
	}

}

void
tvb_free_chain(tvbuff_t* tvb)
{
	GSList		*slist;

	/* Recursively call tvb_free_chain() */
	for (slist = tvb->used_in; slist != NULL ; slist = slist->next) {
		tvb_free_chain( (tvbuff_t*)slist->data );
	}

	/* Stop the recursion */
	tvb_free(tvb);
}



void
tvb_set_free_cb(tvbuff_t* tvb, tvbuff_free_cb_t func)
{
	g_assert(tvb->type == TVBUFF_REAL_DATA);
	tvb->free_cb = func;
}

void
tvb_set_real_data(tvbuff_t* tvb, const guint8* data, guint length)
{
	g_assert(tvb->type == TVBUFF_REAL_DATA);
	g_assert(!tvb->initialized);

	tvb->real_data = (gpointer) data;
	tvb->length = length;
	tvb->initialized = TRUE;
}

tvbuff_t*
tvb_new_real_data(const guint8* data, guint length)
{
	tvbuff_t	*tvb;

	tvb = tvb_new(TVBUFF_REAL_DATA);
	tvb_set_real_data(tvb, data, length);

	return tvb;
}

/* Computes the absolute offset and length based on a possibly-negative offset
 * and a length that is possible -1 (which means "to the end of the data").
 * Returns TRUE/FALSE indicating whether the offset is in bounds or
 * not. The integer ptrs are modified with the new offset and length.
 * No exception is thrown. */
static gboolean
compute_offset_length(tvbuff_t *tvb, gint offset, gint length,
		guint *offset_ptr, guint *length_ptr)
{
	g_assert(offset_ptr);
	g_assert(length_ptr);

	/* Compute the offset */
	if (offset >= 0) {
		*offset_ptr = offset;
	}
	else if (-offset > tvb->length) {
		return FALSE;
	}
	else {
		*offset_ptr = tvb->length + offset;
	}

	/* Compute the length */
	g_assert(length >= -1);
	if (length == -1) {
		*length_ptr = tvb->length - *offset_ptr;
	}
	else {
		*length_ptr = length;
	}

	return TRUE;
}


static gboolean
check_offset_length_no_exception(tvbuff_t *tvb, gint offset, gint length,
		guint *offset_ptr, guint *length_ptr)
{
	g_assert(tvb->initialized);

	if (!compute_offset_length(tvb, offset, length, offset_ptr, length_ptr)) {
		return FALSE;
	}

	if (*offset_ptr + *length_ptr <= tvb->length) {
		return TRUE;
	}
	else {
		return FALSE;
	}

	g_assert_not_reached();
}

/* Checks (+/-) offset and length and throws BoundsError if
 * either is out of bounds. Sets integer ptrs to the new offset
 * and length. */
static void
check_offset_length(tvbuff_t *tvb, gint offset, gint length,
		guint *offset_ptr, guint *length_ptr)
{
	if (!check_offset_length_no_exception(tvb, offset, length, offset_ptr, length_ptr)) {
		THROW(BoundsError);
	}
	return;
}

static void
add_to_used_in_list(tvbuff_t *tvb, tvbuff_t *used_in)
{
	tvb->used_in = g_slist_prepend(tvb->used_in, used_in);
}

void
tvb_set_subset(tvbuff_t *tvb, tvbuff_t *backing,
		gint backing_offset, gint backing_length)
{
	g_assert(tvb->type == TVBUFF_SUBSET);
	g_assert(!tvb->initialized);

	check_offset_length(backing, backing_offset, backing_length,
			&tvb->tvbuffs.subset.offset,
			&tvb->tvbuffs.subset.length);

	tvb_increment_usage_count(backing, 1);
	tvb->tvbuffs.subset.tvb = backing;
	tvb->length = tvb->tvbuffs.subset.length;
	tvb->initialized = TRUE;
	add_to_used_in_list(backing, tvb);

	/* Optimization. If the backing buffer has a pointer to contiguous, real data,
	 * then we can point directly to our starting offset in that buffer */
	if (backing->real_data != NULL) {
		tvb->real_data = backing->real_data + tvb->tvbuffs.subset.offset;
	}
}


tvbuff_t*
tvb_new_subset(tvbuff_t *backing, gint backing_offset, gint backing_length)
{
	tvbuff_t	*tvb;

	tvb = tvb_new(TVBUFF_SUBSET);
	tvb_set_subset(tvb, backing, backing_offset, backing_length);

	return tvb;
}

void
tvb_composite_append(tvbuff_t* tvb, tvbuff_t* member)
{
	tvb_comp_t	*composite;

	g_assert(!tvb->initialized);
	composite = &tvb->tvbuffs.composite;
	composite->tvbs = g_slist_append( composite->tvbs, member );
}

void
tvb_composite_prepend(tvbuff_t* tvb, tvbuff_t* member)
{
	tvb_comp_t	*composite;

	g_assert(!tvb->initialized);
	composite = &tvb->tvbuffs.composite;
	composite->tvbs = g_slist_prepend( composite->tvbs, member );
}

tvbuff_t*
tvb_new_composite(void)
{
	return tvb_new(TVBUFF_COMPOSITE);
}

void
tvb_composite_finalize(tvbuff_t* tvb)
{
	GSList		*slist;
	guint		num_members;
	tvbuff_t	*member_tvb;
	tvb_comp_t	*composite;
	int		i = 0;

	g_assert(!tvb->initialized);
	g_assert(tvb->length == 0);

	composite = &tvb->tvbuffs.composite;
	num_members = g_slist_length(slist);

	composite->start_offsets = g_new(guint, num_members);
	composite->end_offsets = g_new(guint, num_members);

	for (slist = composite->tvbs; slist != NULL; slist = slist->next) {
		member_tvb = slist->data;
		composite->start_offsets[i] = tvb->length;
		tvb->length += member_tvb->length;
		composite->end_offsets[i] = tvb->length - 1;
		g_debug("Composite member: start_offset=%d end_offset=%d",
				composite->start_offsets[i],
				composite->end_offsets[i]);
		i++;
	}

	tvb->initialized = TRUE;
}



guint
tvb_length(tvbuff_t* tvb)
{
	g_assert(tvb->initialized);

	return tvb->length;
}

guint
tvb_length_remaining(tvbuff_t *tvb, gint offset)
{
	guint	abs_offset, abs_length;

	g_assert(tvb->initialized);

	if (compute_offset_length(tvb, offset, -1,
			&abs_offset, &abs_length)) {
		return abs_length;
	}
	else {
		return -1;
	}
}



/* Validates that 'length' bytes are available starting from
 * offset (pos/neg). Does not throw BoundsError exception. */
gboolean
tvb_bytes_exist(tvbuff_t *tvb, gint offset, gint length)
{
	guint		abs_offset, abs_length;

	g_assert(tvb->initialized);

	if (!compute_offset_length(tvb, offset, length, &abs_offset, &abs_length))
		return FALSE;

	if (abs_offset + abs_length <= tvb->length) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}

gboolean
tvb_offset_exists(tvbuff_t *tvb, gint offset)
{
	guint		abs_offset, abs_length;

	g_assert(tvb->initialized);
	if (compute_offset_length(tvb, offset, -1, &abs_offset, &abs_length)) {
		return TRUE;
	}
	else {
		return FALSE;
	}
}




guint8*
first_real_data_ptr(tvbuff_t *tvb)
{
	tvbuff_t	*member;

	switch(tvb->type) {
		case TVBUFF_REAL_DATA:
			return tvb->real_data;
		case TVBUFF_SUBSET:
			member = tvb->tvbuffs.subset.tvb;
			return first_real_data_ptr(member);
		case TVBUFF_COMPOSITE:
			member = tvb->tvbuffs.composite.tvbs->data;
			return first_real_data_ptr(member);
	}

	g_assert_not_reached();
	return NULL;
}

int
offset_from_real_beginning(tvbuff_t *tvb, int counter)
{
	tvbuff_t	*member;

	switch(tvb->type) {
		case TVBUFF_REAL_DATA:
			return counter;
		case TVBUFF_SUBSET:
			member = tvb->tvbuffs.subset.tvb;
			return offset_from_real_beginning(member, counter + tvb->tvbuffs.subset.offset);
		case TVBUFF_COMPOSITE:
			member = tvb->tvbuffs.composite.tvbs->data;
			return offset_from_real_beginning(member, counter);
	}

	g_assert_not_reached();
	return 0;
}

void
tvb_compat(tvbuff_t *tvb, const u_char **pd, int *offset)
{
	g_assert(tvb->initialized);
	*pd = first_real_data_ptr(tvb);
	*offset = offset_from_real_beginning(tvb, 0);
}


static guint8*
composite_ensure_contiguous(tvbuff_t *tvb, guint abs_offset, guint abs_length)
{
	guint		i, num_members;
	tvb_comp_t	*composite;
	tvbuff_t	*member_tvb;
	guint		member_offset, member_length;
#if 0
	guint8		*from_ptr, *to_ptr;
#endif
	g_assert(tvb->type == TVBUFF_COMPOSITE);

	/* Maybe the range specified by offset/length
	 * is contiguous inside one of the member tvbuffs */
	composite = &tvb->tvbuffs.composite;
	num_members = g_slist_length(composite->tvbs);

	for (i = 0; i < num_members; i++) {
		if (abs_offset <= composite->end_offsets[i]) {
			member_tvb = g_slist_nth(composite->tvbs, i)->data;
			break;
		}
	}
	g_assert(member_tvb);

	g_debug("Checking for member_tvb 0x%08x offset=%d length=%d",
			(unsigned int) member_tvb,
			abs_offset - composite->start_offsets[i],
			abs_length);

	if (check_offset_length_no_exception(member_tvb, abs_offset - composite->start_offsets[i],
				abs_length, &member_offset, &member_length)) {

		g_debug("Yup. using it");
		g_assert(!tvb->real_data);
		return ensure_contiguous(member_tvb, member_offset, member_length);
	}
	else {
#if 0
		/* The requested data is non-contiguous inside
		 * the member tvb. We have to memdup() all our
		 * data and make it contiguous.
		 */
		tvb->real_data = g_malloc(tvb->length);
		to_ptr = tvb->real_data;

		for (i = 0; i < num_members; i++) {
			member_tvb = g_slist_nth(composite->tvbs, i)->data;
			from_ptr = ensure_contiguous(member_tvb, 0, -1);
			memcpy(to_ptr, from_ptr, member_tvb->length);
			to_ptr += member_tvb->length;
		}
#endif
		g_debug("Nope. Calling tvb_memdup()");
		tvb->real_data = tvb_memdup(tvb, 0, -1);
		return tvb->real_data + abs_offset;
	}

	g_assert_not_reached();
	return NULL;
}

static guint8*
ensure_contiguous(tvbuff_t *tvb, gint offset, gint length)
{
	guint	abs_offset, abs_length;

	check_offset_length(tvb, offset, length, &abs_offset, &abs_length);

	if (tvb->real_data) {
		return tvb->real_data + abs_offset;
	}
	else{
		switch(tvb->type) {
			case TVBUFF_REAL_DATA:
				g_assert_not_reached();
			case TVBUFF_SUBSET:
				return ensure_contiguous(tvb->tvbuffs.subset.tvb,
						abs_offset - tvb->tvbuffs.subset.offset,
						abs_length);
			case TVBUFF_COMPOSITE:
				return composite_ensure_contiguous(tvb, abs_offset, abs_length);
		}
	}

	g_assert_not_reached();
	return NULL;
}

/* This routine was created by Dan Lasley <DLASLEY@xxxxxxxxxx>, and
only slightly modified for ethereal by Gilbert Ramirez. */
static
void print_hex_data(FILE *fh, register const guint8 *cp,
		register guint length)
{
        register int ad, i, j, k;
        guint8 c;
        guint8 line[60];
	static guint8 binhex[16] = {
		'0', '1', '2', '3', '4', '5', '6', '7',
		'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

        memset (line, ' ', sizeof line);
        line[sizeof (line)-1] = 0;
        for (ad=i=j=k=0; i<length; i++) {
                c = *cp++;
                line[j++] = binhex[c>>4];
                line[j++] = binhex[c&0xf];
                if (i&1) j++;
                line[42+k++] = c >= ' ' && c < 0x7f ? c : '.';
                if ((i & 15) == 15) {
                        fprintf (fh, "\n%4x  %s", ad, line);
                        /*if (i==15) printf (" %d", length);*/
                        memset (line, ' ', sizeof line);
                        line[sizeof (line)-1] = j = k = 0;
                        ad += 16;
                }
        }

        if (line[0] != ' ') fprintf (fh, "\n%4x  %s", ad, line);
        fprintf(fh, "\n");
        return;

}

/* Can throw BoundsError */
void
tvb_dump(tvbuff_t* tvb)
{
	guint abs_offset, abs_length;

	check_offset_length(tvb, 0, -1, &abs_offset, &abs_length);

	printf("\n");
	switch(tvb->type) {
		case TVBUFF_REAL_DATA:
			printf("Type: TVBUFF_REAL_DATA=0x%08x data=0x%08x length=%d\n",
					(unsigned int) tvb,
					(unsigned int) tvb->real_data, tvb->length);
			print_hex_data(stdout, (guint8*)(tvb->real_data + abs_offset),
					abs_length);
			break;

		case TVBUFF_SUBSET:
			printf("Type: TVBUFF_SUBSET=0x%08x length=%d\n",
					(unsigned int) tvb, tvb->length);
			if (!tvb->real_data) {
				ensure_contiguous(tvb, 0, -1);
				g_assert(tvb);
			}
			print_hex_data(stdout, (guint8*)(tvb->real_data + abs_offset),
					abs_length);
			break;

		case TVBUFF_COMPOSITE:
			printf("Type: TVBUFF_COMPOSITE\n");
			if (!tvb->real_data) {
				ensure_contiguous(tvb, 0, -1);
				g_assert(tvb);
			}
			print_hex_data(stdout, (guint8*)(tvb->real_data + abs_offset),
					abs_length);
			break;

		default:
			printf("Type: ??? ERROR ???\n");
			break;
	}
	printf("\n");
}

/************** ACCESSORS **************/

static guint8*
composite_memcpy(tvbuff_t *tvb, guint8* target, guint abs_offset, guint abs_length)
{
	guint		i, num_members;
	tvb_comp_t	*composite;
	tvbuff_t	*member_tvb;
	guint		member_offset, member_length;
	gboolean	retval;

	g_debug("Entered with target=0x%08x abs_offset=%u abs_length=%u",
			(unsigned int) target, abs_offset, abs_length);

	g_assert(tvb->type == TVBUFF_COMPOSITE);

	/* Maybe the range specified by offset/length
	 * is contiguous inside one of the member tvbuffs */
	composite = &tvb->tvbuffs.composite;
	num_members = g_slist_length(composite->tvbs);

	for (i = 0; i < num_members; i++) {
		if (abs_offset <= composite->end_offsets[i]) {
			member_tvb = g_slist_nth(composite->tvbs, i)->data;
			break;
		}
	}
	g_assert(member_tvb);

	g_debug("TVB=0x%08x Checking for member_tvb 0x%08x offset=%d length=%d",
			(unsigned int) tvb,
			(unsigned int) member_tvb,
			abs_offset - composite->start_offsets[i],
			abs_length);

	if (check_offset_length_no_exception(member_tvb, abs_offset - composite->start_offsets[i],
				abs_length, &member_offset, &member_length)) {

		g_debug("Member tvb had contiguous data. Calling it");
		g_assert(!tvb->real_data);
		return tvb_memcpy(member_tvb, target, member_offset, member_length);
	}
	else {
		/* The requested data is non-contiguous inside
		 * the member tvb. We have to memcpy() the part that's in the member tvb,
		 * then iterate across the other member tvb's, copying their portions
		 * until we have copied all data.
		 */
		retval = compute_offset_length(member_tvb, abs_offset - composite->start_offsets[i], -1,
				&member_offset, &member_length);
		g_assert(retval);

		g_debug("Member tvb had partially-contiguous data. tvb_memcpy(%d,%d)", member_offset, member_length);
		tvb_memcpy(member_tvb, target, member_offset, member_length);
		abs_offset	+= member_length;
		abs_length	-= member_length;

		/* Recurse */
		if (abs_length > 0) {
			g_debug("Recursing... (%d,%d)", abs_offset, abs_length);
			composite_memcpy(tvb, target + member_length, abs_offset, abs_length);
		}

		return target;
	}

	g_assert_not_reached();
	return NULL;
}

guint8*
tvb_memcpy(tvbuff_t *tvb, guint8* target, gint offset, gint length)
{
	guint	abs_offset, abs_length;

	check_offset_length(tvb, offset, length, &abs_offset, &abs_length);

	if (tvb->real_data) {
		return (guint8*) memcpy(target, tvb->real_data + abs_offset, abs_length);
	}

	switch(tvb->type) {
		case TVBUFF_REAL_DATA:
			g_assert_not_reached();

		case TVBUFF_SUBSET:
			return tvb_memcpy(tvb->tvbuffs.subset.tvb, target,
					abs_offset - tvb->tvbuffs.subset.offset,
					abs_length);

		case TVBUFF_COMPOSITE:
			return composite_memcpy(tvb, target, offset, length);
	}

	g_assert_not_reached();
	return NULL;
}


guint8*
tvb_memdup(tvbuff_t *tvb, gint offset, gint length)
{
	guint	abs_offset, abs_length;
	guint8	*duped;

	check_offset_length(tvb, offset, length, &abs_offset, &abs_length);

	duped = g_malloc(abs_length);
	return tvb_memcpy(tvb, duped, abs_offset, abs_length);
}


	
guint8*
tvb_get_ptr(tvbuff_t *tvb, gint offset, gint length)
{
	return ensure_contiguous(tvb, offset, length);
}

guint8
tvb_get_guint8(tvbuff_t *tvb, gint offset)
{
	guint8* ptr;

	ptr = ensure_contiguous(tvb, offset, sizeof(guint8));
	return *ptr;
}

guint16
tvb_get_ntohs(tvbuff_t *tvb, gint offset)
{
	guint8* ptr;

	ptr = ensure_contiguous(tvb, offset, sizeof(guint16));
	return pntohs(ptr);
}

guint32
tvb_get_ntohl(tvbuff_t *tvb, gint offset)
{
	guint8* ptr;

	ptr = ensure_contiguous(tvb, offset, sizeof(guint32));
	return pntohl(ptr);
}

guint16
tvb_get_letohs(tvbuff_t *tvb, gint offset)
{
	guint8* ptr;

	ptr = ensure_contiguous(tvb, offset, sizeof(guint16));
	return pletohs(ptr);
}

guint32
tvb_get_letohl(tvbuff_t *tvb, gint offset)
{
	guint8* ptr;

	ptr = ensure_contiguous(tvb, offset, sizeof(guint32));
	return pletohl(ptr);
}
/* tvbuff.h
 *
 * Testy, Virtual(-izable) Buffer of guint8*'s
 * 
 * "Testy" -- the buffer gets mad when an attempt is made to access data
 * 		beyond the bounds of the buffer. An exception is thrown.
 *
 * "Virtual" -- the buffer can have its own data, can use a subset of
 * 		the data of a backing tvbuff, or can be a composite of
 * 		other tvbuffs.
 *
 * $Id$
 *
 * Copyright (c) 2000 by Gilbert Ramirez <gram@xxxxxxxxxx>
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxx>
 * Copyright 1998 Gerald Combs
 *
 * 
 * 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 2
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#ifndef __TVBUFF_H__
#define __TVBUFF_H__

#ifndef __GLIB_H__
#include <glib.h>
#endif

#ifndef __EXCEPTIONS_H__
#include "exceptions.h"
#endif

typedef struct tvbuff tvbuff_t;

typedef void (*tvbuff_free_cb_t)(void*);

/* The different types of tvbuff's */
typedef enum {
	TVBUFF_REAL_DATA,
	TVBUFF_SUBSET,
	TVBUFF_COMPOSITE
} tvbuff_type;

/* TVBUFF_REAL_DATA contains a guint8* that points to real data.
 * The data is allocated and contiguous.
 *
 * TVBUFF_SUBSET has a backing tvbuff. The TVBUFF_SUBSET is a "window"
 * through which the program sees only a portion of the backing tvbuff.
 *
 * TVBUFF_COMPOSITE combines multiple tvbuffs sequentually to produce
 * a larger byte array.
 *
 * tvbuff's of any type can be used as the backing-tvbuff of a
 * TVBUFF_SUBSET or as the member of a TVBUFF_COMPOSITE.
 * TVBUFF_COMPOSITEs can have member-tvbuffs of different types.
 *
 * Once a tvbuff is create/initialized/finalized, the tvbuff is read-only.
 * That is, it cannot point to any other data. A new tvbuff must be created if
 * you want a tvbuff that points to other data.
 */


/* "class" initialization. Called once during execution of program
 * so that tvbuff.c can initialize its data. */
void tvbuff_init(void);

/* "class" cleanup. Called once during execution of program
 * so that tvbuff.c can clean up its data. */
void tvbuff_cleanup(void);


/* Returns a pointer to a newly initialized tvbuff. Note that
 * tvbuff's of types TVBUFF_SUBSET and TVBUFF_COMPOSITE
 * require further initialization via the appropriate functions */
tvbuff_t* tvb_new(tvbuff_type);

/* Marks a tvbuff for freeing. The guint8* data is *never* freed by
 * the tvbuff routines. The tvbuff is actually freed once its usage
 * count drops to 0. Usage counts increment for any time the tvbuff is
 * used as a member of another tvbuff, i.e., as the backing buffer for
 * a TVBUFF_SUBSET or as a member of a TVBUFF_COMPOSITE.
 *
 * The caller can artificially increment/decrement the usage count
 * with tvbuff_increment_usage_count()/tvbuff_decrement_usage_count().
 */
void tvb_free(tvbuff_t*);

/* Free the tvbuff_t and all tvbuff's created from it. */
void tvb_free_chain(tvbuff_t*);

/* Both return the new usage count, after the increment or decrement */
guint tvb_increment_usage_count(tvbuff_t*, guint count);
/* If a decrement causes the usage count to drop to 0, a the tvbuff
 * is immediately freed. Be sure you know exactly what you're doing
 * if you decide to use this function, as another tvbuff could
 * still have a pointer to the just-freed tvbuff, causing corrupted data
 * or a segfault in the future */
guint tvb_decrement_usage_count(tvbuff_t*, guint count);

/* Set a callback function to call when a tvbuff is actually freed
 * (once the usage count drops to 0). One argument is passed to
 * that callback --- the guint* that points to the real data.
 * Obviously, this only applies to a TVBUFF_REAL_DATA tvbuff. */
void tvb_set_free_cb(tvbuff_t*, tvbuff_free_cb_t);


/* Sets parameters for TVBUFF_REAL_DATA */
void tvb_set_real_data(tvbuff_t*, const guint8* data, guint length);

/* Combination of tvb_new() and tvb_set_real_data() */
tvbuff_t* tvb_new_real_data(const guint8* data, guint length);


/* Define the subset of the backing buffer to use.
 *
 * 'backing_offset' can be negative, to indicate bytes from
 * the end of the backing buffer.
 *
 * 'backing_length' can be 0, although the usefulness of the buffer would
 * be rather limited.
 *
 * 'backing_length' of -1 means "to the end of the backing buffer"
 *
 * Will throw BoundsError if 'backing_offset'/'length'
 * is beyond the bounds of the backing tvbuff. */
void tvb_set_subset(tvbuff_t* tvb, tvbuff_t* backing,
		gint backing_offset, gint backing_length);

/* Combination of tvb_new() and tvb_set_subset() */
tvbuff_t* tvb_new_subset(tvbuff_t* backing,
		gint backing_offset, gint backing_length);


/* Both tvb_composite_append and tvb_composite_prepend can throw
 * BoundsError if member_offset/member_length goes beyond bounds of
 * the 'member' tvbuff. */

/* Append to the list of tvbuffs that make up this composite tvbuff */
void tvb_composite_append(tvbuff_t* tvb, tvbuff_t* member);

/* Prepend to the list of tvbuffs that make up this composite tvbuff */
void tvb_composite_prepend(tvbuff_t* tvb, tvbuff_t* member);

/* Helper function that calls tvb_new(TVBUFF_COMPOSITE).
 * Provided only to maintain symmetry with other constructors */
tvbuff_t* tvb_new_composite(void);

/* Mark a composite tvbuff as initialized. No further appends or prepends
 * occur, data access can finally happen after this finalization. */
void tvb_composite_finalize(tvbuff_t* tvb);


/* Get total length of buffer */
guint tvb_length(tvbuff_t*);

/* Computes bytes to end of buffer, from offset (which can be negative,
 * to indicate bytes from end of buffer). Function returns -1 to
 * indicate that offset is out of bounds. No exception is thrown. */
guint tvb_length_remaining(tvbuff_t*, gint offset);

/* Checks (w/o throwing exception) that the bytes referred to by 'offset'/'length'
 * actualy exist in the buffer */
gboolean tvb_bytes_exist(tvbuff_t*, gint offset, gint length);

/* Checks (w/o throwing exception) that offset exists in buffer */
gboolean tvb_offset_exists(tvbuff_t*, gint offset);


/************** START OF ACCESSORS ****************/
/* All accessors will throw BoundsError if appropriate */

guint8  tvb_get_guint8(tvbuff_t*, gint offset);
guint16 tvb_get_ntohs(tvbuff_t*, gint offset);
guint32 tvb_get_ntohl(tvbuff_t*, gint offset);
guint16 tvb_get_letohs(tvbuff_t*, gint offset);
guint32 tvb_get_letohl(tvbuff_t*, gint offset);

/* Returns target for convenience. Does not suffer from possible
 * expense of tvb_get_ptr(), since this routine is smart enough
 * to copy data in chunks if the request range actually exists in
 * different TVBUFF_REAL_DATA tvbuffs. */
guint8* tvb_memcpy(tvbuff_t*, guint8* target, gint offset, gint length);

/* It is the user's responsibility to g_free() the memory allocated by
 * tvb_memdup(). Calls tvb_memcpy() */
guint8* tvb_memdup(tvbuff_t*, gint offset, gint length);

/* WARNING! This function is possibly expensive, temporarily allocating
 * another copy of the packet data. Furthermore, it's dangerous because once
 * this pointer is given to the user, there's no guarantee that the user will
 * honor the 'length' and not overstep the boundaries of the buffer.
 *
 * Return a pointer into our buffer if the data asked for via 'offset'/'length'
 * is contiguous (which might not be the case for TVBUFF_COMPOSITE). If the
 * data is not contiguous, a tvb_memdup() is called for the entire buffer
 * and the pointer to the newly-contiguous data is returned. This dynamically-
 * allocated memory will be freed when the tvbuff is freed, after the
 * tvbuff_free_cb_t() is called, if any. */
guint8* tvb_get_ptr(tvbuff_t*, gint offset, gint length);

/* Find length of string by looking for end of string ('\0'), up to
 * 'max_length' characters'. Returns -1 if 'max_length' reached
 * before finding EOS. */
/*gint tvb_strnlen(tvbuff_t*, gint offset, gint max_length);*/

/************** END OF ACCESSORS ****************/

/* Sets pd and offset so that tvbuff's can be used with code
 * that only understands pd/offset and not tvbuffs.
 * This is the "compatibility" function */
void tvb_compat(tvbuff_t*, const u_char **pd, int *offset);

/* For testing purposes, send hexdump of tvbuff data contents
 * to stdout. */
void tvb_dump(tvbuff_t*);

#endif /* __TVBUFF_H__ */