/* tvbuff_composite.c
 *
 * $Id$
 *
 * Copyright (c) 2000 by Gilbert Ramirez <gram@alumni.rice.edu>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"

#include <epan/emem.h>

#include "tvbuff.h"
#include "tvbuff-int.h"
#include "proto.h"	/* XXX - only used for DISSECTOR_ASSERT, probably a new header file? */

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 tvb_composite {
	struct tvbuff tvb;

	tvb_comp_t	composite;
};

static void
composite_free(tvbuff_t *tvb)
{
	struct tvb_composite *composite_tvb = (struct tvb_composite *) tvb;
	tvb_comp_t *composite = &composite_tvb->composite;

	g_slist_free(composite->tvbs);

	g_free(composite->start_offsets);
	g_free(composite->end_offsets);
	if (tvb->real_data) {
		/*
		 * XXX - do this with a union?
		 */
		g_free((gpointer)tvb->real_data);
	}
}

static guint
composite_offset(const tvbuff_t *tvb, const guint counter)
{
	const struct tvb_composite *composite_tvb = (const struct tvb_composite *) tvb;
	const tvbuff_t *member = (const tvbuff_t *)composite_tvb->composite.tvbs->data;

	return tvb_offset_from_real_beginning_counter(member, counter);
}

static const guint8*
composite_get_ptr(tvbuff_t *tvb, guint abs_offset, guint abs_length)
{
	struct tvb_composite *composite_tvb = (struct tvb_composite *) tvb;
	guint	    i, num_members;
	tvb_comp_t *composite;
	tvbuff_t   *member_tvb = NULL;
	guint	    member_offset;
	GSList	   *slist;

	/* DISSECTOR_ASSERT(tvb->ops == &tvb_composite_ops); */

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

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

	/* special case */
	if (!member_tvb) {
		DISSECTOR_ASSERT(abs_offset == tvb->length && abs_length == 0);
		return "";
	}

	member_offset = abs_offset - composite->start_offsets[i];

	if (tvb_bytes_exist(member_tvb, member_offset, abs_length)) {
		/*
		 * The range is, in fact, contiguous within member_tvb.
		 */
		DISSECTOR_ASSERT(!tvb->real_data);
		return tvb_get_ptr(member_tvb, member_offset, abs_length);
	}
	else {
		tvb->real_data = (guint8 *)tvb_memdup(NULL, tvb, 0, -1);
		return tvb->real_data + abs_offset;
	}

	DISSECTOR_ASSERT_NOT_REACHED();
}

static void *
composite_memcpy(tvbuff_t *tvb, void* _target, guint abs_offset, guint abs_length)
{
	struct tvb_composite *composite_tvb = (struct tvb_composite *) tvb;
	guint8 *target = (guint8 *) _target;

	guint	    i, num_members;
	tvb_comp_t *composite;
	tvbuff_t   *member_tvb = NULL;
	guint	    member_offset, member_length;
	GSList	   *slist;

	/* DISSECTOR_ASSERT(tvb->ops == &tvb_composite_ops); */

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

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

	/* special case */
	if (!member_tvb) {
		DISSECTOR_ASSERT(abs_offset == tvb->length && abs_length == 0);
		return target;
	}

	member_offset = abs_offset - composite->start_offsets[i];

	if (tvb_bytes_exist(member_tvb, member_offset, abs_length)) {
		DISSECTOR_ASSERT(!tvb->real_data);
		return tvb_memcpy(member_tvb, target, member_offset, abs_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.
		 */
		member_length = tvb_length_remaining(member_tvb, member_offset);

		/* composite_memcpy() can't handle a member_length of zero. */
		DISSECTOR_ASSERT(member_length > 0);

		tvb_memcpy(member_tvb, target, member_offset, member_length);
		abs_offset	+= member_length;
		abs_length	-= member_length;

		/* Recurse */
		if (abs_length > 0) {
			composite_memcpy(tvb, target + member_length, abs_offset, abs_length);
		}

		return target;
	}

	DISSECTOR_ASSERT_NOT_REACHED();
}

static const struct tvb_ops tvb_composite_ops = {
	sizeof(struct tvb_composite), /* size */

	composite_free,       /* free */
	composite_offset,     /* offset */
	composite_get_ptr,    /* get_ptr */
	composite_memcpy,     /* memcpy */
	NULL,                 /* find_guint8 XXX */
	NULL,                 /* pbrk_guint8 XXX */
	NULL,                 /* clone */
};

/*
 * Composite tvb
 *
 *   1. A composite tvb is automatically chained to its first member when the
 *      tvb is finalized.
 *      This means that composite tvb members must all be in the same chain.
 *      ToDo: enforce this: By searching the chain?
 */
tvbuff_t *
tvb_new_composite(void)
{
	tvbuff_t *tvb = tvb_new(&tvb_composite_ops);
	struct tvb_composite *composite_tvb = (struct tvb_composite *) tvb;
	tvb_comp_t *composite = &composite_tvb->composite;

	composite->tvbs		 = NULL;
	composite->start_offsets = NULL;
	composite->end_offsets	 = NULL;

	return tvb;
}

void
tvb_composite_append(tvbuff_t *tvb, tvbuff_t *member)
{
	struct tvb_composite *composite_tvb = (struct tvb_composite *) tvb;
	tvb_comp_t *composite;

	DISSECTOR_ASSERT(tvb && !tvb->initialized);
	DISSECTOR_ASSERT(tvb->ops == &tvb_composite_ops);

	/* Don't allow zero-length TVBs: composite_memcpy() can't handle them
	 * and anyway it makes no sense.
	 */
	DISSECTOR_ASSERT(member->length);

	composite       = &composite_tvb->composite;
	composite->tvbs = g_slist_append(composite->tvbs, member);
}

void
tvb_composite_prepend(tvbuff_t *tvb, tvbuff_t *member)
{
	struct tvb_composite *composite_tvb = (struct tvb_composite *) tvb;
	tvb_comp_t *composite;

	DISSECTOR_ASSERT(tvb && !tvb->initialized);
	DISSECTOR_ASSERT(tvb->ops == &tvb_composite_ops);

	/* Don't allow zero-length TVBs: composite_memcpy() can't handle them
	 * and anyway it makes no sense.
	 */
	DISSECTOR_ASSERT(member->length);

	composite       = &composite_tvb->composite;
	composite->tvbs = g_slist_prepend(composite->tvbs, member);
}

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

	DISSECTOR_ASSERT(tvb && !tvb->initialized);
	DISSECTOR_ASSERT(tvb->ops == &tvb_composite_ops);
	DISSECTOR_ASSERT(tvb->length == 0);
	DISSECTOR_ASSERT(tvb->reported_length == 0);

	composite   = &composite_tvb->composite;
	num_members = g_slist_length(composite->tvbs);

	/* Dissectors should not create composite TVBs if they're not going to
	 * put at least one TVB in them.
	 * (Without this check--or something similar--we'll seg-fault below.)
	 */
	DISSECTOR_ASSERT(num_members);

	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) {
		DISSECTOR_ASSERT((guint) i < num_members);
		member_tvb = (tvbuff_t *)slist->data;
		composite->start_offsets[i] = tvb->length;
		tvb->length += member_tvb->length;
		tvb->reported_length += member_tvb->reported_length;
		composite->end_offsets[i] = tvb->length - 1;
		i++;
	}
	tvb_add_to_chain((tvbuff_t *)composite->tvbs->data, tvb); /* chain composite tvb to first member */
	tvb->initialized = TRUE;
}
