/* tvbuff.c
 *
 * Testy, Virtual(-izable) Buffer of uint8_t*'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.
 *
 * Copyright (c) 2000 by Gilbert Ramirez <gram@alumni.rice.edu>
 *
 * Code to convert IEEE floating point formats to native floating point
 * derived from code Copyright (c) Ashok Narayanan, 2000
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <string.h>
#include <stdio.h>
#include <errno.h>

#include <glib.h>

#include "wsutil/pint.h"
#include "wsutil/sign_ext.h"
#include "wsutil/strtoi.h"
#include "wsutil/unicode-utils.h"
#include "wsutil/nstime.h"
#include "wsutil/time_util.h"
#include <wsutil/ws_assert.h>
#include "tvbuff.h"
#include "tvbuff-int.h"
#include "strutil.h"
#include "to_str.h"
#include "charsets.h"
#include "proto.h"	/* XXX - only used for DISSECTOR_ASSERT, probably a new header file? */
#include "exceptions.h"

#include <time.h>

static uint64_t
_tvb_get_bits64(tvbuff_t *tvb, unsigned bit_offset, const unsigned total_no_of_bits);

static uint64_t
_tvb_get_bits64_le(tvbuff_t *tvb, unsigned bit_offset, const unsigned total_no_of_bits);

static inline unsigned
_tvb_captured_length_remaining(const tvbuff_t *tvb, const unsigned offset);

static inline const uint8_t*
ensure_contiguous(tvbuff_t *tvb, const int offset, const int length);

/* coverity[ +taint_sanitize : arg-1 ] */
/* coverity[ +taint_sanitize : arg-2 ] */
static inline const uint8_t*
ensure_contiguous_unsigned(tvbuff_t *tvb, const unsigned offset, const unsigned length);

static inline uint8_t *
tvb_get_raw_string(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, const unsigned length);

tvbuff_t *
tvb_new(const struct tvb_ops *ops)
{
	tvbuff_t *tvb;
	size_t    size = ops->tvb_size;

	ws_assert(size >= sizeof(*tvb));

	tvb = (tvbuff_t *) g_slice_alloc(size);

	tvb->next		 = NULL;
	tvb->ops		 = ops;
	tvb->initialized	 = false;
	tvb->flags		 = 0;
	tvb->length		 = 0;
	tvb->reported_length	 = 0;
	tvb->contained_length	 = 0;
	tvb->real_data		 = NULL;
	tvb->raw_offset		 = 0;
	tvb->ds_tvb		 = NULL;

	return tvb;
}

static void
tvb_free_internal(tvbuff_t *tvb)
{
	size_t    size;

	DISSECTOR_ASSERT(tvb);

	if (tvb->ops->tvb_free)
		tvb->ops->tvb_free(tvb);

	size = tvb->ops->tvb_size;

	g_slice_free1(size, tvb);
}

/* XXX: just call tvb_free_chain();
 *      Not removed so that existing dissectors using tvb_free() need not be changed.
 *      I'd argue that existing calls to tvb_free() should have actually been
 *      calls to tvb_free_chain() although the calls were OK as long as no
 *      subsets, etc had been created on the tvb. */
void
tvb_free(tvbuff_t *tvb)
{
	tvb_free_chain(tvb);
}

void
tvb_free_chain(tvbuff_t  *tvb)
{
	tvbuff_t *next_tvb;
	DISSECTOR_ASSERT(tvb);
	while (tvb) {
		next_tvb = tvb->next;
		tvb_free_internal(tvb);
		tvb  = next_tvb;
	}
}

tvbuff_t *
tvb_new_chain(tvbuff_t *parent, tvbuff_t *backing)
{
	tvbuff_t *tvb = tvb_new_proxy(backing);

	tvb_add_to_chain(parent, tvb);
	return tvb;
}

void
tvb_add_to_chain(tvbuff_t *parent, tvbuff_t *child)
{
	tvbuff_t *tmp;

	DISSECTOR_ASSERT(parent);
	DISSECTOR_ASSERT(child);

	while (child) {
		tmp   = child;
		child = child->next;

		tmp->next    = parent->next;
		parent->next = tmp;
	}
}

/*
 * Check whether that offset goes more than one byte past the
 * end of the buffer.
 *
 * If not, return 0; otherwise, return exception
 */
static inline int
validate_offset(const tvbuff_t *tvb, const unsigned abs_offset)
{
	if (G_LIKELY(abs_offset <= tvb->length)) {
		/* It's OK. */
		return 0;
	}

	/*
	 * It's not OK, but why?  Which boundaries is it
	 * past?
	 */
	if (abs_offset <= tvb->contained_length) {
		/*
		 * It's past the captured length, but not past
		 * the reported end of any parent tvbuffs from
		 * which this is constructed, or the reported
		 * end of this tvbuff, so it's out of bounds
		 * solely because we're past the end of the
		 * captured data.
		 */
		return BoundsError;
	}

	/*
	 * There's some actual packet boundary, not just the
	 * artificial boundary imposed by packet slicing, that
	 * we're past.
	 */

	if (tvb->flags & TVBUFF_FRAGMENT) {
		/*
		 * This tvbuff is the first fragment of a larger
		 * packet that hasn't been reassembled, so we
		 * assume that's the source of the problem - if
		 * we'd reassembled the packet, we wouldn't have
		 * gone past the end.
		 *
		 * That might not be true, but for at least
		 * some forms of reassembly, such as IP
		 * reassembly, you don't know how big the
		 * reassembled packet is unless you reassemble
		 * it, so, in those cases, we can't determine
		 * whether we would have gone past the end
		 * had we reassembled the packet.
		 */
		return FragmentBoundsError;
	}

	/* OK, we're not an unreassembled fragment (that we know of). */
	if (abs_offset <= tvb->reported_length) {
		/*
		 * We're within the bounds of what this tvbuff
		 * purportedly contains, based on some length
		 * value, but we're not within the bounds of
		 * something from which this tvbuff was
		 * extracted, so that length value ran past
		 * the end of some parent tvbuff.
		 */
		return ContainedBoundsError;
	}

	/*
	 * OK, it looks as if we ran past the claimed length
	 * of data.
	 */
	return ReportedBoundsError;
}

static inline int
validate_offset_and_remaining(const tvbuff_t *tvb, const unsigned offset, unsigned *rem_len)
{
	int exception;

	exception = validate_offset(tvb, offset);
	if (!exception)
		*rem_len = tvb->length - offset;

	return exception;
}

/* Returns integer indicating whether the given offset and the end offset
 * calculated from that offset and the given length are in bounds (0) or
 * not (exception number).
 * No exception is thrown; on success, we return 0, otherwise we return an
 * exception for the caller to throw if appropriate.
 *
 * N.B. - we return success (0), if the offset is positive and right
 * after the end of the tvbuff (i.e., equal to the length).  We do this
 * so that a dissector constructing a subset tvbuff for the next protocol
 * will get a zero-length tvbuff, not an exception, if there's no data
 * left for the next protocol - we want the next protocol to be the one
 * that gets an exception, so the error is reported as an error in that
 * protocol rather than the containing protocol.  */
static inline int
validate_offset_length_no_exception(const tvbuff_t *tvb,
				    const unsigned offset, const unsigned length)
{
	unsigned end_offset;
	int   exception;

	/* Compute the offset */
	/* Since offset is unsigned, the only effect of validation is to throw
	 * a possibly different exception if offset is outside the captured
	 * bytes. E.g., offset could be outside the captured bytes but in the
	 * reported length, but end_offset outside the reported length.
	 * XXX - Which *is* the proper exception? Compare fast_ensure_contiguous
	 * which only throws the exception related to the end offset.
	 */
	exception = validate_offset(tvb, offset);
	if (exception)
		return exception;

	/*
	 * Compute the offset of the first byte past the length,
	 * checking for an overflow.
	 */
	if (ckd_add(&end_offset, offset, length))
		return BoundsError;

	return validate_offset(tvb, end_offset);
}

/* Checks offset and length and throws an exception if
 * either is out of bounds. Sets integer ptrs to the new length. */
static inline void
validate_offset_length(const tvbuff_t *tvb,
		    const unsigned offset, const unsigned length)
{
	int exception;

	exception = validate_offset_length_no_exception(tvb, offset, length);
	if (exception)
		THROW(exception);
}

/* Internal function so that other translation units can use
 * validate_offset_length. */
void
tvb_validate_offset_length(const tvbuff_t *tvb,
			   const unsigned offset, const unsigned length)
{
	validate_offset_length(tvb, offset, length);
}

/* Internal function so that other translation units can use
 * validate_offset_and_remaining. This throws the exception
 * from validate_offset_and_remaining. */
void
tvb_validate_offset_and_remaining(const tvbuff_t *tvb,
				  const unsigned offset, unsigned *rem_len)
{
	int exception;

	exception = validate_offset_and_remaining(tvb, offset, rem_len);
	if (exception)
		THROW(exception);
}

/*
 * The same as validate_offset except this accepts negative offsets, meaning
 * relative to the end of (captured) length. (That it's captured, not reported,
 * length is one reason to deprecate signed offsets, #20103.)
 */
static inline int
compute_offset(const tvbuff_t *tvb, const int offset, unsigned *offset_ptr)
{
	if (offset >= 0) {
		/* Positive offset - relative to the beginning of the packet. */
		if (G_LIKELY((unsigned) offset <= tvb->length)) {
			*offset_ptr = offset;
		} else if ((unsigned) offset <= tvb->contained_length) {
			return BoundsError;
		} else if (tvb->flags & TVBUFF_FRAGMENT) {
			return FragmentBoundsError;
		} else if ((unsigned) offset <= tvb->reported_length) {
			return ContainedBoundsError;
		} else {
			return ReportedBoundsError;
		}
	}
	else {
		/* Negative offset - relative to the end of the packet. */
		/* Prevent UB on 2's complement platforms. All tested compilers
		 * (gcc, clang, MSVC) compile this to a single instruction on
		 * x86, ARM, RISC-V, S390x, SPARC, etc. at -O1 and higher
		 * according to godbolt.org. */
		unsigned abs_offset = ((unsigned)-(offset + 1)) + 1;
		if (G_LIKELY(abs_offset <= tvb->length)) {
			*offset_ptr = tvb->length - abs_offset;
		} else if (abs_offset <= tvb->contained_length) {
			return BoundsError;
		} else if (tvb->flags & TVBUFF_FRAGMENT) {
			return FragmentBoundsError;
		} else if (abs_offset <= tvb->reported_length) {
			return ContainedBoundsError;
		} else {
			return ReportedBoundsError;
		}
	}

	return 0;
}

/* 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 integer indicating whether the offset is in bounds (0) or
 * not (exception number). The integer ptrs are modified with the new offset,
 * captured (available) length, and contained length (amount that's present
 * in the parent tvbuff based on its reported length).
 * No exception is thrown; on success, we return 0, otherwise we return an
 * exception for the caller to throw if appropriate.
 *
 * XXX - we return success (0), if the offset is positive and right
 * after the end of the tvbuff (i.e., equal to the length).  We do this
 * so that a dissector constructing a subset tvbuff for the next protocol
 * will get a zero-length tvbuff, not an exception, if there's no data
 * left for the next protocol - we want the next protocol to be the one
 * that gets an exception, so the error is reported as an error in that
 * protocol rather than the containing protocol.  */
static inline int
check_offset_length_no_exception(const tvbuff_t *tvb,
				 const int offset, int const length_val,
				 unsigned *offset_ptr, unsigned *length_ptr)
{
	unsigned end_offset;
	int   exception;

	DISSECTOR_ASSERT(offset_ptr);
	DISSECTOR_ASSERT(length_ptr);

	/* Compute the offset */
	exception = compute_offset(tvb, offset, offset_ptr);
	if (exception)
		return exception;

	if (length_val < -1) {
		/* XXX - ReportedBoundsError? */
		return BoundsError;
	}

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

	/*
	 * Compute the offset of the first byte past the length.
	 */
	end_offset = *offset_ptr + *length_ptr;

	/*
	 * Check for an overflow
	 */
	if (end_offset < *offset_ptr)
		return BoundsError;

	return validate_offset(tvb, end_offset);
}

/* Checks (+/-) offset and length and throws an exception if
 * either is out of bounds. Sets integer ptrs to the new offset
 * and length. */
static inline void
check_offset_length(const tvbuff_t *tvb,
		    const int offset, int const length_val,
		    unsigned *offset_ptr, unsigned *length_ptr)
{
	int exception;

	exception = check_offset_length_no_exception(tvb, offset, length_val, offset_ptr, length_ptr);
	if (exception)
		THROW(exception);
}

/* Internal function so that other translation units can use
 * check_offset_length. */
void
tvb_check_offset_length(const tvbuff_t *tvb,
			const int offset, int const length_val,
			unsigned *offset_ptr, unsigned *length_ptr)
{
	check_offset_length(tvb, offset, length_val, offset_ptr, length_ptr);
}

static const unsigned char left_aligned_bitmask[] = {
	0xff,
	0x80,
	0xc0,
	0xe0,
	0xf0,
	0xf8,
	0xfc,
	0xfe
};

/* tvb_new_octet_aligned used to support -1 no_of_bits as meaning "to the
 * end of the buffer." Nothing every used it. It could be supported with
 * a _remaining() function if necessary. Note that the previous implementation
 * didn't properly keep the extra reported length if the reported length
 * was greater than the captured length.
 */

tvbuff_t *
tvb_new_octet_aligned(tvbuff_t *tvb, uint32_t bit_offset, uint32_t no_of_bits)
{
	tvbuff_t     *sub_tvb = NULL;
	uint32_t      byte_offset;
	uint32_t      datalen, i;
	uint8_t       left, right, remaining_bits, *buf;
	const uint8_t *data;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	byte_offset = bit_offset >> 3;
	left = bit_offset % 8; /* for left-shifting */
	right = 8 - left; /* for right-shifting */

	datalen = no_of_bits >> 3;
	remaining_bits = no_of_bits % 8;
	if (remaining_bits) {
		datalen++;
	}

	/* already aligned -> shortcut */
	if (((left == 0) && (remaining_bits == 0)) || datalen == 0) {
		return tvb_new_subset_length_caplen(tvb, byte_offset, datalen, datalen);
	}

	/* If at least one trailing byte is available, we must use the content
	 * of that byte for the last shift (i.e. tvb_get_ptr() must use datalen + 1).
	 * If no extra byte is available, the last shifted byte requires
	 * special treatment.
	 */
	if (_tvb_captured_length_remaining(tvb, byte_offset) > datalen) {
		data = ensure_contiguous_unsigned(tvb, byte_offset, datalen + 1); /* tvb_get_ptr */

		/* Do this allocation AFTER tvb_get_ptr() (which could throw an exception) */
		buf = (uint8_t *)g_malloc(datalen);

		/* shift tvb data bit_offset bits to the left */
		for (i = 0; i < datalen; i++)
			buf[i] = (data[i] << left) | (data[i+1] >> right);
	} else {
		data = ensure_contiguous_unsigned(tvb, byte_offset, datalen); /* tvb_get_ptr() */

		/* Do this allocation AFTER tvb_get_ptr() (which could throw an exception) */
		buf = (uint8_t *)g_malloc(datalen);

		/* shift tvb data bit_offset bits to the left */
		for (i = 0; i < (datalen-1); i++)
			buf[i] = (data[i] << left) | (data[i+1] >> right);
		buf[datalen-1] = data[datalen-1] << left; /* set last octet */
	}
	buf[datalen-1] &= left_aligned_bitmask[remaining_bits];

	sub_tvb = tvb_new_child_real_data(tvb, buf, datalen, datalen);
	tvb_set_free_cb(sub_tvb, g_free);

	return sub_tvb;
}

tvbuff_t *
tvb_new_octet_right_aligned(tvbuff_t *tvb, uint32_t bit_offset, uint32_t no_of_bits)
{
	tvbuff_t     *sub_tvb = NULL;
	uint32_t      byte_offset;
	unsigned      src_len, dst_len, i;
	uint8_t       left, right, remaining_bits, *buf;
	const uint8_t *data;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	byte_offset = bit_offset / 8;
	/* right shift to put bits in place and discard least significant bits */
	right = bit_offset % 8;
	/* left shift to get most significant bits from next octet */
	left = 8 - right;

	dst_len = no_of_bits / 8;
	remaining_bits = no_of_bits % 8;
	if (remaining_bits) {
		dst_len++;
	}

	/* already aligned -> shortcut */
	if (((right == 0) && (remaining_bits == 0)) || dst_len == 0) {
		return tvb_new_subset_length_caplen(tvb, byte_offset, dst_len, dst_len);
	}

	if (_tvb_captured_length_remaining(tvb, byte_offset) > dst_len) {
		/* last octet will get data from trailing octet */
		src_len = dst_len + 1;
	} else {
		/* last octet will be zero padded */
		src_len = dst_len;
	}

	data = ensure_contiguous_unsigned(tvb, byte_offset, src_len); /* tvb_get_ptr */

	/* Do this allocation AFTER tvb_get_ptr() (which could throw an exception) */
	buf = (uint8_t *)g_malloc(dst_len);

	for (i = 0; i < (dst_len - 1); i++)
		buf[i] = (data[i] >> right) | (data[i+1] << left);

	/* Special handling for last octet */
	buf[i] = (data[i] >> right);
	/* Shift most significant bits from trailing octet if available */
	if (src_len > dst_len)
		buf[i] |= (data[i+1] << left);
	/* Preserve only remaining bits in last octet if not multiple of 8 */
	if (remaining_bits)
		buf[i] &= ((1 << remaining_bits) - 1);

	sub_tvb = tvb_new_child_real_data(tvb, buf, dst_len, dst_len);
	tvb_set_free_cb(sub_tvb, g_free);

	return sub_tvb;
}

static tvbuff_t *
tvb_generic_clone_offset_len(tvbuff_t *tvb, unsigned offset, unsigned len)
{
	tvbuff_t *cloned_tvb;
	uint8_t *data;

	DISSECTOR_ASSERT(tvb_bytes_exist(tvb, offset, len));

	data = (uint8_t *) g_malloc(len);

	tvb_memcpy(tvb, data, offset, len);

	cloned_tvb = tvb_new_real_data(data, len, len);
	tvb_set_free_cb(cloned_tvb, g_free);

	return cloned_tvb;
}

tvbuff_t *
tvb_clone_offset_len(tvbuff_t *tvb, unsigned offset, unsigned len)
{
	if (tvb->ops->tvb_clone) {
		tvbuff_t *cloned_tvb;

		cloned_tvb = tvb->ops->tvb_clone(tvb, offset, len);
		if (cloned_tvb)
			return cloned_tvb;
	}

	return tvb_generic_clone_offset_len(tvb, offset, len);
}

tvbuff_t *
tvb_clone(tvbuff_t *tvb)
{
	return tvb_clone_offset_len(tvb, 0, tvb->length);
}

inline unsigned
tvb_captured_length(const tvbuff_t *tvb)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	return tvb->length;
}

/* For tvbuff internal use */
static inline unsigned
_tvb_captured_length_remaining(const tvbuff_t *tvb, const unsigned offset)
{
	unsigned rem_length;
	int   exception;

	exception = validate_offset_and_remaining(tvb, offset, &rem_length);
	if (exception)
		return 0;

	return rem_length;
}

unsigned
tvb_captured_length_remaining(const tvbuff_t *tvb, const unsigned offset)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	return _tvb_captured_length_remaining(tvb, offset);
}

unsigned
tvb_ensure_captured_length_remaining(const tvbuff_t *tvb, const unsigned offset)
{
	unsigned rem_length = 0;
	int   exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset(tvb, offset);
	if (exception)
		THROW(exception);

	rem_length = tvb->length - offset;

	if (rem_length == 0) {
		/*
		 * This routine ensures there's at least one byte available.
		 * There aren't any bytes available, so throw the appropriate
		 * exception.
		 */
		if (offset < tvb->contained_length) {
			THROW(BoundsError);
		} else if (tvb->flags & TVBUFF_FRAGMENT) {
			THROW(FragmentBoundsError);
		} else if (offset < tvb->reported_length) {
			THROW(ContainedBoundsError);
		} else {
			THROW(ReportedBoundsError);
		}
	}
	return rem_length;
}

/* Validates that 'length' bytes are available starting from
 * offset. Does not throw an exception. */
bool
tvb_bytes_exist(const tvbuff_t *tvb, const unsigned offset, const int length)
{
	unsigned end_offset;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/*
	 * Negative lengths are not possible and indicate a bug (e.g. arithmetic
	 * error or an overly large value from packet data).
	 */
	if (length < 0)
		return false;

	/*
	 * Compute the offset of the first byte past the length.
	 * Make sure it doesn't overflow.
	 */
	if (ckd_add(&end_offset, offset, length))
		return false;

	/*
	 * Check that bytes exist up to right before that offset. (As length is
	 * positive and there was no overflow we don't need to check offset.)
	 */
	if (end_offset > tvb->length)
		return false;

	return true;
}

/* Validates that 'length' bytes, where 'length' is a 64-bit unsigned
 * integer, are available starting from offset (pos/neg). Throws an
 * exception if they aren't. */
/* coverity[ +taint_sanitize : arg-1 ] */
/* coverity[ +taint_sanitize : arg-2 ] */
void
tvb_ensure_bytes_exist64(const tvbuff_t *tvb, const unsigned offset, const uint64_t length)
{
	/*
	 * Make sure the value fits in a signed integer; if not, assume
	 * that means that it's too big.
	 */
	if (length > INT_MAX) {
		THROW(ReportedBoundsError);
	}

	/* OK, now cast it and try it with tvb_ensure_bytes_exist(). */
	tvb_ensure_bytes_exist(tvb, offset, (int)length);
}

/* Validates that 'length' bytes are available starting from
 * offset (pos/neg). Throws an exception if they aren't. */
/* coverity[ +taint_sanitize : arg-1 ] */
/* coverity[ +taint_sanitize : arg-2 ] */
void
tvb_ensure_bytes_exist(const tvbuff_t *tvb, const unsigned offset, const int length)
{
	unsigned end_offset;
	int exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/*
	 * -1 doesn't mean "until end of buffer", as that's pointless
	 * for this routine.  We must treat it as a Really Large Positive
	 * Number, so that we throw an exception; we throw
	 * ReportedBoundsError, as if it were past even the end of a
	 * reassembled packet, and past the end of even the data we
	 * didn't capture.
	 *
	 * We do the same with other negative lengths.
	 */
	if (length < 0) {
		THROW(ReportedBoundsError);
	}

	exception = validate_offset(tvb, offset);
	if (exception)
		THROW(exception);

	/*
	 * Compute the offset of the first byte past the length.
	 */
	end_offset = offset + length;

	/*
	 * Check for an overflow
	 */
	if (end_offset < offset)
		THROW(BoundsError);

	if (G_LIKELY(end_offset <= tvb->length))
		return;
	else if (end_offset <= tvb->contained_length)
		THROW(BoundsError);
	else if (tvb->flags & TVBUFF_FRAGMENT)
		THROW(FragmentBoundsError);
	else if (end_offset <= tvb->reported_length)
		THROW(ContainedBoundsError);
	else
		THROW(ReportedBoundsError);
}

bool
tvb_offset_exists(const tvbuff_t *tvb, const unsigned offset)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* We don't care why the offset doesn't exist, and unlike some
	 * other functions we don't accept an offset one past the end,
	 * so we check ourselves... */
	return offset < tvb->length;
}

unsigned
tvb_reported_length(const tvbuff_t *tvb)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	return tvb->reported_length;
}

unsigned
tvb_reported_length_remaining(const tvbuff_t *tvb, const unsigned offset)
{
	int   exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset(tvb, offset);
	if (exception)
		return 0;

	if (tvb->reported_length >= offset)
		return tvb->reported_length - offset;
	else
		return 0;
}

unsigned
tvb_ensure_reported_length_remaining(const tvbuff_t *tvb, const unsigned offset)
{
	int   exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset(tvb, offset);
	if (exception)
		THROW(exception);

	if (tvb->reported_length >= offset)
		return tvb->reported_length - offset;
	else
		THROW(ReportedBoundsError);
}

/* Set the reported length of a tvbuff to a given value; used for protocols
 * whose headers contain an explicit length and where the calling
 * dissector's payload may include padding as well as the packet for
 * this protocol.
 * Also adjusts the available and contained length. */
void
tvb_set_reported_length(tvbuff_t *tvb, const unsigned reported_length)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	if (reported_length > tvb->reported_length)
		THROW(ReportedBoundsError);

	tvb->reported_length = reported_length;
	if (reported_length < tvb->length)
		tvb->length = reported_length;
	if (reported_length < tvb->contained_length)
		tvb->contained_length = reported_length;
}

/* Repair a tvbuff where the captured length is greater than the
 * reported length; such a tvbuff makes no sense, as it's impossible
 * to capture more data than is in the packet.
 */
void
tvb_fix_reported_length(tvbuff_t *tvb)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);
	DISSECTOR_ASSERT(tvb->reported_length < tvb->length);

	tvb->reported_length = tvb->length;
	if (tvb->contained_length < tvb->length)
		tvb->contained_length = tvb->length;
}

unsigned
tvb_offset_from_real_beginning_counter(const tvbuff_t *tvb, const unsigned counter)
{
	if (tvb->ops->tvb_offset)
		return tvb->ops->tvb_offset(tvb, counter);

	DISSECTOR_ASSERT_NOT_REACHED();
	return 0;
}

unsigned
tvb_offset_from_real_beginning(const tvbuff_t *tvb)
{
	return tvb_offset_from_real_beginning_counter(tvb, 0);
}

static inline const uint8_t*
ensure_contiguous_unsigned_no_exception(tvbuff_t *tvb, const unsigned offset, const unsigned length, int *pexception)
{
	int   exception;

	exception = validate_offset_length_no_exception(tvb, offset, length);
	if (exception) {
		if (pexception)
			*pexception = exception;
		return NULL;
	}

	/*
	 * Special case: if the caller (e.g. tvb_get_ptr) requested no data,
	 * then it is acceptable to have an empty tvb (!tvb->real_data).
	 */
	if (length == 0) {
		return NULL;
	}

	/*
	 * We know that all the data is present in the tvbuff, so
	 * no exceptions should be thrown.
	 */
	if (tvb->real_data)
		return tvb->real_data + offset;

	if (tvb->ops->tvb_get_ptr)
		return tvb->ops->tvb_get_ptr(tvb, offset, length);

	DISSECTOR_ASSERT_NOT_REACHED();
	return NULL;
}

static inline const uint8_t*
ensure_contiguous_unsigned(tvbuff_t *tvb, const unsigned offset, const unsigned length)
{
	int           exception = 0;
	const uint8_t *p;

	p = ensure_contiguous_unsigned_no_exception(tvb, offset, length, &exception);
	if (p == NULL && length != 0) {
		DISSECTOR_ASSERT(exception > 0);
		THROW(exception);
	}
	return p;
}

static inline const uint8_t*
ensure_contiguous_no_exception(tvbuff_t *tvb, const int offset, const int length, int *pexception)
{
	unsigned abs_offset = 0, abs_length = 0;
	int   exception;

	exception = check_offset_length_no_exception(tvb, offset, length, &abs_offset, &abs_length);
	if (exception) {
		if (pexception)
			*pexception = exception;
		return NULL;
	}

	/*
	 * Special case: if the caller (e.g. tvb_get_ptr) requested no data,
	 * then it is acceptable to have an empty tvb (!tvb->real_data).
	 */
	if (length == 0) {
		return NULL;
	}

	/*
	 * We know that all the data is present in the tvbuff, so
	 * no exceptions should be thrown.
	 */
	if (tvb->real_data)
		return tvb->real_data + abs_offset;

	if (tvb->ops->tvb_get_ptr)
		return tvb->ops->tvb_get_ptr(tvb, abs_offset, abs_length);

	DISSECTOR_ASSERT_NOT_REACHED();
	return NULL;
}

static inline const uint8_t*
ensure_contiguous(tvbuff_t *tvb, const int offset, const int length)
{
	int           exception = 0;
	const uint8_t *p;

	p = ensure_contiguous_no_exception(tvb, offset, length, &exception);
	if (p == NULL && length != 0) {
		DISSECTOR_ASSERT(exception > 0);
		THROW(exception);
	}
	return p;
}

static inline const uint8_t*
fast_ensure_contiguous(tvbuff_t *tvb, const unsigned offset, const unsigned length)
{
	unsigned end_offset;

	/* Since offset is unsigned, we have to check for overflow. */
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* This is only called internally, never with zero. Try to satisfy
	 * Coverity that we don't run off the end. */
	ws_assert(length != 0);

	if (!tvb->real_data) {
	       return ensure_contiguous_unsigned(tvb, offset, length);
	}

	/* XXX - Is this really faster now (other than the slight difference
	 * in behavior from only throwing the exception related to the end
	 * offset?) */
	if (ckd_add(&end_offset, offset, length))
		THROW(BoundsError);

	if (G_LIKELY(end_offset <= tvb->length)) {
		return tvb->real_data + offset;
	} else if (end_offset <= tvb->contained_length) {
		THROW(BoundsError);
	} else if (tvb->flags & TVBUFF_FRAGMENT) {
		THROW(FragmentBoundsError);
	} else if (end_offset <= tvb->reported_length) {
		THROW(ContainedBoundsError);
	} else {
		THROW(ReportedBoundsError);
	}
	/* not reached */
	return NULL;
}



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

void *
tvb_memcpy(tvbuff_t *tvb, void *target, const unsigned offset, size_t length)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/*
	 * XXX - The length is a size_t, but the tvb length and tvb_ops
	 * only supports an unsigned.
	 */
	DISSECTOR_ASSERT(length <= UINT_MAX);
	validate_offset_length(tvb, offset, (unsigned)length);

	if (target && tvb->real_data) {
		return memcpy(target, tvb->real_data + offset, length);
	}

	if (target && tvb->ops->tvb_memcpy)
		return tvb->ops->tvb_memcpy(tvb, target, offset, (unsigned)length);

	/*
	 * If the length is 0, there's nothing to do.
	 * (tvb->real_data could be null if it's allocated with
	 * a size of length.)
	 */
	if (length != 0) {
		/*
		 * XXX, fallback to slower method
		 */
		DISSECTOR_ASSERT_NOT_REACHED();
	}
	return NULL;
}


/*
 * XXX - This could replace some code that calls "tvb_ensure_bytes_exist()"
 * and then allocates a buffer and copies data to it.
 *
 * If scope is NULL, memory is allocated with g_malloc() and user must
 * explicitly free it with g_free().
 * If scope is not NULL, memory is allocated with the corresponding pool
 * lifetime.
 */
void *
tvb_memdup(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, size_t length)
{
	void  *duped;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/*
	 * XXX - The length is a size_t, but the tvb length and tvb_ops
	 * only supports an unsigned.
	 */
	DISSECTOR_ASSERT(length <= UINT_MAX);
	validate_offset_length(tvb, offset, (unsigned)length);

	if (length == 0)
		return NULL;

	duped = wmem_alloc(scope, length);
	return tvb_memcpy(tvb, duped, offset, length);
}

#if 0
/* XXX - Is a _remaining variant of this necessary? The user would still need
 * to get the length from tvb_captured_length_remaining() to productively use
 * the (not necessarily null terminated) byte array. See also tvb_get_ptr(),
 * which is similar. */
void *
tvb_memdup_remaining(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset)
{
	void  *duped;
	unsigned length;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	validate_offset_and_remaining(tvb, offset, &length);

	if (length == 0)
		return NULL;

	duped = wmem_alloc(scope, length);
	return tvb_memcpy(tvb, duped, offset, length);
}
#endif

const uint8_t*
tvb_get_ptr(tvbuff_t *tvb, const unsigned offset, const unsigned length)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);
	return ensure_contiguous_unsigned(tvb, offset, length);
}

/* ---------------- */
uint8_t
tvb_get_uint8(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 1);
	return *ptr;
}

int8_t
tvb_get_int8(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 1);
	return *ptr;
}

uint16_t
tvb_get_ntohs(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 2);
	return pntohu16(ptr);
}

int16_t
tvb_get_ntohis(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 2);
	return pntohu16(ptr);
}

uint32_t
tvb_get_ntoh24(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 3);
	return pntohu24(ptr);
}

int32_t
tvb_get_ntohi24(tvbuff_t *tvb, const unsigned offset)
{
	uint32_t ret;

	ret = ws_sign_ext32(tvb_get_ntoh24(tvb, offset), 24);

	return (int32_t)ret;
}

uint32_t
tvb_get_ntohl(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 4);
	return pntohu32(ptr);
}

int32_t
tvb_get_ntohil(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 4);
	return pntohu32(ptr);
}

uint64_t
tvb_get_ntoh40(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 5);
	return pntohu40(ptr);
}

int64_t
tvb_get_ntohi40(tvbuff_t *tvb, const unsigned offset)
{
	uint64_t ret;

	ret = ws_sign_ext64(tvb_get_ntoh40(tvb, offset), 40);

	return (int64_t)ret;
}

uint64_t
tvb_get_ntoh48(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 6);
	return pntohu48(ptr);
}

int64_t
tvb_get_ntohi48(tvbuff_t *tvb, const unsigned offset)
{
	uint64_t ret;

	ret = ws_sign_ext64(tvb_get_ntoh48(tvb, offset), 48);

	return (int64_t)ret;
}

uint64_t
tvb_get_ntoh56(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 7);
	return pntohu56(ptr);
}

int64_t
tvb_get_ntohi56(tvbuff_t *tvb, const unsigned offset)
{
	uint64_t ret;

	ret = ws_sign_ext64(tvb_get_ntoh56(tvb, offset), 56);

	return (int64_t)ret;
}

uint64_t
tvb_get_ntoh64(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 8);
	return pntohu64(ptr);
}

int64_t
tvb_get_ntohi64(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 8);
	return pntohu64(ptr);
}

uint16_t
tvb_get_uint16(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohs(tvb, offset);
	} else {
		return tvb_get_ntohs(tvb, offset);
	}
}

int16_t
tvb_get_int16(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohis(tvb, offset);
	} else {
		return tvb_get_ntohis(tvb, offset);
	}
}

uint32_t
tvb_get_uint24(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letoh24(tvb, offset);
	} else {
		return tvb_get_ntoh24(tvb, offset);
	}
}

int32_t
tvb_get_int24(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohi24(tvb, offset);
	} else {
		return tvb_get_ntohi24(tvb, offset);
	}
}

uint32_t
tvb_get_uint32(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohl(tvb, offset);
	} else {
		return tvb_get_ntohl(tvb, offset);
	}
}

int32_t
tvb_get_int32(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohil(tvb, offset);
	} else {
		return tvb_get_ntohil(tvb, offset);
	}
}

uint64_t
tvb_get_uint40(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letoh40(tvb, offset);
	} else {
		return tvb_get_ntoh40(tvb, offset);
	}
}

int64_t
tvb_get_int40(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohi40(tvb, offset);
	} else {
		return tvb_get_ntohi40(tvb, offset);
	}
}

uint64_t
tvb_get_uint48(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letoh48(tvb, offset);
	} else {
		return tvb_get_ntoh48(tvb, offset);
	}
}

int64_t
tvb_get_int48(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohi48(tvb, offset);
	} else {
		return tvb_get_ntohi48(tvb, offset);
	}
}

uint64_t
tvb_get_uint56(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letoh56(tvb, offset);
	} else {
		return tvb_get_ntoh56(tvb, offset);
	}
}

int64_t
tvb_get_int56(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohi56(tvb, offset);
	} else {
		return tvb_get_ntohi56(tvb, offset);
	}
}

uint64_t
tvb_get_uint64(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letoh64(tvb, offset);
	} else {
		return tvb_get_ntoh64(tvb, offset);
	}
}

uint64_t
tvb_get_uint64_with_length(tvbuff_t *tvb, const unsigned offset, unsigned length, const unsigned encoding)
{
	uint64_t value;

	switch (length) {

	case 1:
		value = tvb_get_uint8(tvb, offset);
		break;

	case 2:
		value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohs(tvb, offset)
						       : tvb_get_ntohs(tvb, offset);
		break;

	case 3:
		value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh24(tvb, offset)
						       : tvb_get_ntoh24(tvb, offset);
		break;

	case 4:
		value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letohl(tvb, offset)
						       : tvb_get_ntohl(tvb, offset);
		break;

	case 5:
		value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh40(tvb, offset)
						       : tvb_get_ntoh40(tvb, offset);
		break;

	case 6:
		value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh48(tvb, offset)
						       : tvb_get_ntoh48(tvb, offset);
		break;

	case 7:
		value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh56(tvb, offset)
						       : tvb_get_ntoh56(tvb, offset);
		break;

	case 8:
		value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh64(tvb, offset)
						       : tvb_get_ntoh64(tvb, offset);
		break;

	default:
		if (length < 1) {
			value = 0;
		} else {
			value = (encoding & ENC_LITTLE_ENDIAN) ? tvb_get_letoh64(tvb, offset)
							       : tvb_get_ntoh64(tvb, offset);
		}
		break;
	}
	return value;
}

int64_t
tvb_get_int64(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohi64(tvb, offset);
	} else {
		return tvb_get_ntohi64(tvb, offset);
	}
}

float
tvb_get_ieee_float(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohieee_float(tvb, offset);
	} else {
		return tvb_get_ntohieee_float(tvb, offset);
	}
}

double
tvb_get_ieee_double(tvbuff_t *tvb, const unsigned offset, const unsigned encoding) {
	if (encoding & ENC_LITTLE_ENDIAN) {
		return tvb_get_letohieee_double(tvb, offset);
	} else {
		return tvb_get_ntohieee_double(tvb, offset);
	}
}

/*
 * Stuff for IEEE float handling on platforms that don't have IEEE
 * format as the native floating-point format.
 *
 * For now, we treat only the VAX as such a platform.
 *
 * XXX - other non-IEEE boxes that can run UN*X include some Crays,
 * and possibly other machines.  However, I don't know whether there
 * are any other machines that could run Wireshark and that don't use
 * IEEE format.  As far as I know, all of the main current and past
 * commercial microprocessor families on which OSes that support
 * Wireshark can run use IEEE format (x86, ARM, 68k, SPARC, MIPS,
 * PA-RISC, Alpha, IA-64, and so on), and it appears that the official
 * Linux port to System/390 and zArchitecture uses IEEE format floating-
 * point rather than IBM hex floating-point (not a huge surprise), so
 * I'm not sure that leaves any 32-bit or larger UN*X or Windows boxes,
 * other than VAXes, that don't use IEEE format.  If you're not running
 * UN*X or Windows, the floating-point format is probably going to be
 * the least of your problems in a port.
 */

#if defined(vax)

#include <math.h>

/*
 * Single-precision.
 */
#define IEEE_SP_NUMBER_WIDTH	32	/* bits in number */
#define IEEE_SP_EXP_WIDTH	8	/* bits in exponent */
#define IEEE_SP_MANTISSA_WIDTH	23	/* IEEE_SP_NUMBER_WIDTH - 1 - IEEE_SP_EXP_WIDTH */

#define IEEE_SP_SIGN_MASK	0x80000000
#define IEEE_SP_EXPONENT_MASK	0x7F800000
#define IEEE_SP_MANTISSA_MASK	0x007FFFFF
#define IEEE_SP_INFINITY	IEEE_SP_EXPONENT_MASK

#define IEEE_SP_IMPLIED_BIT (1 << IEEE_SP_MANTISSA_WIDTH)
#define IEEE_SP_INFINITE ((1 << IEEE_SP_EXP_WIDTH) - 1)
#define IEEE_SP_BIAS ((1 << (IEEE_SP_EXP_WIDTH - 1)) - 1)

static int
ieee_float_is_zero(const uint32_t w)
{
	return ((w & ~IEEE_SP_SIGN_MASK) == 0);
}

static float
get_ieee_float(const uint32_t w)
{
	long sign;
	long exponent;
	long mantissa;

	sign = w & IEEE_SP_SIGN_MASK;
	exponent = w & IEEE_SP_EXPONENT_MASK;
	mantissa = w & IEEE_SP_MANTISSA_MASK;

	if (ieee_float_is_zero(w)) {
		/* number is zero, unnormalized, or not-a-number */
		return 0.0;
	}
#if 0
	/*
	 * XXX - how to handle this?
	 */
	if (IEEE_SP_INFINITY == exponent) {
		/*
		 * number is positive or negative infinity, or a special value
		 */
		return (sign? MINUS_INFINITY: PLUS_INFINITY);
	}
#endif

	exponent = ((exponent >> IEEE_SP_MANTISSA_WIDTH) - IEEE_SP_BIAS) -
		IEEE_SP_MANTISSA_WIDTH;
	mantissa |= IEEE_SP_IMPLIED_BIT;

	if (sign)
		return -mantissa * pow(2, exponent);
	else
		return mantissa * pow(2, exponent);
}

/*
 * Double-precision.
 * We assume that if you don't have IEEE floating-point, you have a
 * compiler that understands 64-bit integral quantities.
 */
#define IEEE_DP_NUMBER_WIDTH	64	/* bits in number */
#define IEEE_DP_EXP_WIDTH	11	/* bits in exponent */
#define IEEE_DP_MANTISSA_WIDTH	52	/* IEEE_DP_NUMBER_WIDTH - 1 - IEEE_DP_EXP_WIDTH */

#define IEEE_DP_SIGN_MASK	INT64_C(0x8000000000000000)
#define IEEE_DP_EXPONENT_MASK	INT64_C(0x7FF0000000000000)
#define IEEE_DP_MANTISSA_MASK	INT64_C(0x000FFFFFFFFFFFFF)
#define IEEE_DP_INFINITY	IEEE_DP_EXPONENT_MASK

#define IEEE_DP_IMPLIED_BIT (INT64_C(1) << IEEE_DP_MANTISSA_WIDTH)
#define IEEE_DP_INFINITE ((1 << IEEE_DP_EXP_WIDTH) - 1)
#define IEEE_DP_BIAS ((1 << (IEEE_DP_EXP_WIDTH - 1)) - 1)

static int
ieee_double_is_zero(const uint64_t w)
{
	return ((w & ~IEEE_SP_SIGN_MASK) == 0);
}

static double
get_ieee_double(const uint64_t w)
{
	int64_t sign;
	int64_t exponent;
	int64_t mantissa;

	sign = w & IEEE_DP_SIGN_MASK;
	exponent = w & IEEE_DP_EXPONENT_MASK;
	mantissa = w & IEEE_DP_MANTISSA_MASK;

	if (ieee_double_is_zero(w)) {
		/* number is zero, unnormalized, or not-a-number */
		return 0.0;
	}
#if 0
	/*
	 * XXX - how to handle this?
	 */
	if (IEEE_DP_INFINITY == exponent) {
		/*
		 * number is positive or negative infinity, or a special value
		 */
		return (sign? MINUS_INFINITY: PLUS_INFINITY);
	}
#endif

	exponent = ((exponent >> IEEE_DP_MANTISSA_WIDTH) - IEEE_DP_BIAS) -
		IEEE_DP_MANTISSA_WIDTH;
	mantissa |= IEEE_DP_IMPLIED_BIT;

	if (sign)
		return -mantissa * pow(2, exponent);
	else
		return mantissa * pow(2, exponent);
}
#endif

/*
 * Fetches an IEEE single-precision floating-point number, in
 * big-endian form, and returns a "float".
 *
 * XXX - should this be "double", in case there are IEEE single-
 * precision numbers that won't fit in some platform's native
 * "float" format?
 */
float
tvb_get_ntohieee_float(tvbuff_t *tvb, const unsigned offset)
{
#if defined(vax)
	return get_ieee_float(tvb_get_ntohl(tvb, offset));
#else
	union {
		float	f;
		uint32_t w;
	} ieee_fp_union;

	ieee_fp_union.w = tvb_get_ntohl(tvb, offset);
	return ieee_fp_union.f;
#endif
}

/*
 * Fetches an IEEE double-precision floating-point number, in
 * big-endian form, and returns a "double".
 */
double
tvb_get_ntohieee_double(tvbuff_t *tvb, const unsigned offset)
{
#if defined(vax)
	union {
		uint32_t w[2];
		uint64_t dw;
	} ieee_fp_union;
#else
	union {
		double d;
		uint32_t w[2];
	} ieee_fp_union;
#endif

#if G_BYTE_ORDER == G_BIG_ENDIAN
	ieee_fp_union.w[0] = tvb_get_ntohl(tvb, offset);
	ieee_fp_union.w[1] = tvb_get_ntohl(tvb, offset+4);
#else
	ieee_fp_union.w[0] = tvb_get_ntohl(tvb, offset+4);
	ieee_fp_union.w[1] = tvb_get_ntohl(tvb, offset);
#endif
#if defined(vax)
	return get_ieee_double(ieee_fp_union.dw);
#else
	return ieee_fp_union.d;
#endif
}

uint16_t
tvb_get_letohs(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 2);
	return pletohu16(ptr);
}

int16_t
tvb_get_letohis(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 2);
	return pletohu16(ptr);
}

uint32_t
tvb_get_letoh24(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 3);
	return pletohu24(ptr);
}

int32_t
tvb_get_letohi24(tvbuff_t *tvb, const unsigned offset)
{
	uint32_t ret;

	ret = ws_sign_ext32(tvb_get_letoh24(tvb, offset), 24);

	return (int32_t)ret;
}

uint32_t
tvb_get_letohl(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 4);
	return pletohu32(ptr);
}

int32_t
tvb_get_letohil(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 4);
	return pletohu32(ptr);
}

uint64_t
tvb_get_letoh40(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 5);
	return pletohu40(ptr);
}

int64_t
tvb_get_letohi40(tvbuff_t *tvb, const unsigned offset)
{
	uint64_t ret;

	ret = ws_sign_ext64(tvb_get_letoh40(tvb, offset), 40);

	return (int64_t)ret;
}

uint64_t
tvb_get_letoh48(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 6);
	return pletohu48(ptr);
}

int64_t
tvb_get_letohi48(tvbuff_t *tvb, const unsigned offset)
{
	uint64_t ret;

	ret = ws_sign_ext64(tvb_get_letoh48(tvb, offset), 48);

	return (int64_t)ret;
}

uint64_t
tvb_get_letoh56(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 7);
	return pletohu56(ptr);
}

int64_t
tvb_get_letohi56(tvbuff_t *tvb, const unsigned offset)
{
	uint64_t ret;

	ret = ws_sign_ext64(tvb_get_letoh56(tvb, offset), 56);

	return (int64_t)ret;
}

uint64_t
tvb_get_letoh64(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 8);
	return pletohu64(ptr);
}

int64_t
tvb_get_letohi64(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;

	ptr = fast_ensure_contiguous(tvb, offset, 8);
	return pletohu64(ptr);
}

/*
 * Fetches an IEEE single-precision floating-point number, in
 * little-endian form, and returns a "float".
 *
 * XXX - should this be "double", in case there are IEEE single-
 * precision numbers that won't fit in some platform's native
 * "float" format?
 */
float
tvb_get_letohieee_float(tvbuff_t *tvb, const unsigned offset)
{
#if defined(vax)
	return get_ieee_float(tvb_get_letohl(tvb, offset));
#else
	union {
		float f;
		uint32_t w;
	} ieee_fp_union;

	ieee_fp_union.w = tvb_get_letohl(tvb, offset);
	return ieee_fp_union.f;
#endif
}

/*
 * Fetches an IEEE double-precision floating-point number, in
 * little-endian form, and returns a "double".
 */
double
tvb_get_letohieee_double(tvbuff_t *tvb, const unsigned offset)
{
#if defined(vax)
	union {
		uint32_t w[2];
		uint64_t dw;
	} ieee_fp_union;
#else
	union {
		double d;
		uint32_t w[2];
	} ieee_fp_union;
#endif

#if G_BYTE_ORDER == G_BIG_ENDIAN
	ieee_fp_union.w[0] = tvb_get_letohl(tvb, offset+4);
	ieee_fp_union.w[1] = tvb_get_letohl(tvb, offset);
#else
	ieee_fp_union.w[0] = tvb_get_letohl(tvb, offset);
	ieee_fp_union.w[1] = tvb_get_letohl(tvb, offset+4);
#endif
#if defined(vax)
	return get_ieee_double(ieee_fp_union.dw);
#else
	return ieee_fp_union.d;
#endif
}

/* This function is a slight misnomer. It accepts all encodings that are
 * ASCII "enough", which means encodings that are the same as US-ASCII
 * for textual representations of dates and hex bytes; i.e., the same
 * for the hex digits and Z (in practice, all alphanumerics), and the
 * four separators ':' '-' '.' and ' '
 * That means that any encoding that keeps the ISO/IEC 646 invariant
 * characters the same (including the T.61 8 bit encoding and multibyte
 * encodings like EUC-KR and GB18030) are OK, even if they replace characters
 * like '$' '#' and '\' with national variants, but not encodings like UTF-16
 * that include extra null bytes.
 * For our current purposes, the unpacked GSM 7-bit default alphabet (but not
 * all National Language Shift Tables) also satisfies this requirement, but
 * note that it does *not* keep all ISO/IEC 646 invariant characters the same.
 * If this internal function gets used for additional purposes than currently,
 * the set of encodings that it accepts could change.
 * */
static inline void
validate_single_byte_ascii_encoding(const unsigned encoding)
{
	const unsigned enc = encoding & ~ENC_CHARENCODING_MASK;

	switch (enc) {
	    case ENC_UTF_16:
	    case ENC_UCS_2:
	    case ENC_UCS_4:
	    case ENC_3GPP_TS_23_038_7BITS_PACKED:
	    case ENC_ASCII_7BITS:
	    case ENC_EBCDIC:
	    case ENC_EBCDIC_CP037:
	    case ENC_EBCDIC_CP500:
	    case ENC_BCD_DIGITS_0_9:
	    case ENC_KEYPAD_ABC_TBCD:
	    case ENC_KEYPAD_BC_TBCD:
	    case ENC_ETSI_TS_102_221_ANNEX_A:
	    case ENC_APN_STR:
	    case ENC_DECT_STANDARD_4BITS_TBCD:
	    REPORT_DISSECTOR_BUG("Invalid string encoding type passed to tvb_get_string_XXX");
	    break;
	    default:
	    break;
	}
	/* make sure something valid was set */
	if (enc == 0)
	    REPORT_DISSECTOR_BUG("No string encoding type passed to tvb_get_string_XXX");
}

GByteArray*
tvb_get_string_bytes(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		     const unsigned encoding, GByteArray *bytes, unsigned *endoff)
{
	char *ptr;
	const char *begin;
	const char *end    = NULL;
	GByteArray  *retval = NULL;

	validate_single_byte_ascii_encoding(encoding);

	ptr = (char*) tvb_get_raw_string(NULL, tvb, offset, length);
	begin = ptr;

	if (endoff) *endoff = offset;

	while (*begin == ' ') begin++;

	if (*begin && bytes) {
		if (hex_str_to_bytes_encoding(begin, bytes, &end, encoding, false)) {
			if (bytes->len > 0) {
				if (endoff) *endoff = offset + (unsigned)(end - ptr);
				retval = bytes;
			}
		}
	}

	wmem_free(NULL, ptr);

	return retval;
}

static bool
parse_month_name(const char *name, int *tm_mon)
{
	static const char months[][4] = { "Jan", "Feb", "Mar", "Apr", "May",
		"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
	for (int i = 0; i < 12; i++) {
		if (memcmp(months[i], name, 4) == 0) {
			*tm_mon = i;
			return true;
		}
	}
	return false;
}

bool
tvb_get_string_uint64(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		      const unsigned encoding, uint64_t *value, unsigned *endoff)
{
	const uint8_t *ptr;
	const uint8_t *endptr;
	const uint8_t **endptrptr = endoff ? &endptr : NULL;
	bool success;

	validate_single_byte_ascii_encoding(encoding);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);

	if (ptr == NULL) {
		*value = 0;
		if (endoff) {
			*endoff = offset;
		}
		return false;
	}

	switch (encoding & ENC_STRING) {
	case ENC_STR_HEX:
		success = ws_hexbuftou64(ptr, length, endptrptr, value);
		break;
	case ENC_STR_DEC:
		success = ws_buftou64(ptr, length, endptrptr, value);
		break;
	case ENC_STR_NUM:
	default:
		success = ws_basebuftou64(ptr, length, endptrptr, value, 0);
	}

	if (endoff) {
		// 0 <= endptr - ptr <= length
		*endoff = offset + (uint32_t)(endptr - ptr);
	}

	return success;
}

bool
tvb_get_string_uint(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		    const unsigned encoding, uint32_t *value, unsigned *endoff)
{
	const uint8_t *ptr;
	const uint8_t *endptr;
	const uint8_t **endptrptr = endoff ? &endptr : NULL;
	bool success;

	validate_single_byte_ascii_encoding(encoding);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);

	if (ptr == NULL) {
		*value = 0;
		if (endoff) {
			*endoff = offset;
		}
		return false;
	}

	switch (encoding & ENC_STRING) {
	case ENC_STR_HEX:
		success = ws_hexbuftou32(ptr, length, endptrptr, value);
		break;
	case ENC_STR_DEC:
		success = ws_buftou32(ptr, length, endptrptr, value);
		break;
	case ENC_STR_NUM:
	default:
		success = ws_basebuftou32(ptr, length, endptrptr, value, 0);
	}

	if (endoff) {
		// 0 <= endptr - ptr <= length
		*endoff = offset + (uint32_t)(endptr - ptr);
	}

	return success;
}

bool
tvb_get_string_uint16(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		    const unsigned encoding, uint16_t *value, unsigned *endoff)
{
	const uint8_t *ptr;
	const uint8_t *endptr;
	const uint8_t **endptrptr = endoff ? &endptr : NULL;
	bool success;

	validate_single_byte_ascii_encoding(encoding);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);

	if (ptr == NULL) {
		*value = 0;
		if (endoff) {
			*endoff = offset;
		}
		return false;
	}

	switch (encoding & ENC_STRING) {
	case ENC_STR_HEX:
		success = ws_hexbuftou16(ptr, length, endptrptr, value);
		break;
	case ENC_STR_DEC:
		success = ws_buftou16(ptr, length, endptrptr, value);
		break;
	case ENC_STR_NUM:
	default:
		success = ws_basebuftou16(ptr, length, endptrptr, value, 0);
	}

	if (endoff) {
		// 0 <= endptr - ptr <= length
		*endoff = offset + (uint32_t)(endptr - ptr);
	}

	return success;
}

bool
tvb_get_string_uint8(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		    const unsigned encoding, uint8_t *value, unsigned *endoff)
{
	const uint8_t *ptr;
	const uint8_t *endptr;
	const uint8_t **endptrptr = endoff ? &endptr : NULL;
	bool success;

	validate_single_byte_ascii_encoding(encoding);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);

	if (ptr == NULL) {
		*value = 0;
		if (endoff) {
			*endoff = offset;
		}
		return false;
	}

	switch (encoding & ENC_STRING) {
	case ENC_STR_HEX:
		success = ws_hexbuftou8(ptr, length, endptrptr, value);
		break;
	case ENC_STR_DEC:
		success = ws_buftou8(ptr, length, endptrptr, value);
		break;
	case ENC_STR_NUM:
	default:
		success = ws_basebuftou8(ptr, length, endptrptr, value, 0);
	}

	if (endoff) {
		// 0 <= endptr - ptr <= length
		*endoff = offset + (uint32_t)(endptr - ptr);
	}

	return success;
}

bool
tvb_get_string_int64(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		     const unsigned encoding, int64_t *value, unsigned *endoff)
{
	const uint8_t *ptr;
	const uint8_t *endptr;
	const uint8_t **endptrptr = endoff ? &endptr : NULL;
	bool success;

	validate_single_byte_ascii_encoding(encoding);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);

	if (ptr == NULL) {
		*value = 0;
		if (endoff) {
			*endoff = offset;
		}
		return false;
	}

	switch (encoding & ENC_STRING) {
	case ENC_STR_HEX:
		success = ws_hexbuftoi64(ptr, length, endptrptr, value);
		break;
	case ENC_STR_DEC:
		success = ws_buftoi64(ptr, length, endptrptr, value);
		break;
	case ENC_STR_NUM:
	default:
		success = ws_basebuftoi64(ptr, length, endptrptr, value, 0);
	}

	if (endoff) {
		// 0 <= endptr - ptr <= length
		*endoff = offset + (uint32_t)(endptr - ptr);
	}

	return success;
}

bool
tvb_get_string_int(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		   const unsigned encoding, int32_t *value, unsigned *endoff)
{
	const uint8_t *ptr;
	const uint8_t *endptr;
	const uint8_t **endptrptr = endoff ? &endptr : NULL;
	bool success;

	validate_single_byte_ascii_encoding(encoding);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);

	if (ptr == NULL) {
		*value = 0;
		if (endoff) {
			*endoff = offset;
		}
		return false;
	}

	switch (encoding & ENC_STRING) {
	case ENC_STR_HEX:
		success = ws_hexbuftoi32(ptr, length, endptrptr, value);
		break;
	case ENC_STR_DEC:
		success = ws_buftoi32(ptr, length, endptrptr, value);
		break;
	case ENC_STR_NUM:
	default:
		success = ws_basebuftoi32(ptr, length, endptrptr, value, 0);
	}

	if (endoff) {
		// 0 <= endptr - ptr <= length
		*endoff = offset + (uint32_t)(endptr - ptr);
	}

	return success;
}

bool
tvb_get_string_int16(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		    const unsigned encoding, int16_t *value, unsigned *endoff)
{
	const uint8_t *ptr;
	const uint8_t *endptr;
	const uint8_t **endptrptr = endoff ? &endptr : NULL;
	bool success;

	validate_single_byte_ascii_encoding(encoding);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);

	if (ptr == NULL) {
		*value = 0;
		if (endoff) {
			*endoff = offset;
		}
		return false;
	}

	switch (encoding & ENC_STRING) {
	case ENC_STR_HEX:
		success = ws_hexbuftoi16(ptr, length, endptrptr, value);
		break;
	case ENC_STR_DEC:
		success = ws_buftoi16(ptr, length, endptrptr, value);
		break;
	case ENC_STR_NUM:
	default:
		success = ws_basebuftoi16(ptr, length, endptrptr, value, 0);
	}

	if (endoff) {
		// 0 <= endptr - ptr <= length
		*endoff = offset + (uint32_t)(endptr - ptr);
	}

	return success;
}

bool
tvb_get_string_int8(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		    const unsigned encoding, int8_t *value, unsigned *endoff)
{
	const uint8_t *ptr;
	const uint8_t *endptr;
	const uint8_t **endptrptr = endoff ? &endptr : NULL;
	bool success;

	validate_single_byte_ascii_encoding(encoding);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);

	if (ptr == NULL) {
		*value = 0;
		if (endoff) {
			*endoff = offset;
		}
		return false;
	}

	switch (encoding & ENC_STRING) {
	case ENC_STR_HEX:
		success = ws_hexbuftoi8(ptr, length, endptrptr, value);
		break;
	case ENC_STR_DEC:
		success = ws_buftoi8(ptr, length, endptrptr, value);
		break;
	case ENC_STR_NUM:
	default:
		success = ws_basebuftoi8(ptr, length, endptrptr, value, 0);
	}

	if (endoff) {
		// 0 <= endptr - ptr <= length
		*endoff = offset + (uint32_t)(endptr - ptr);
	}

	return success;
}

/*
 * Is the character a WSP character, as per RFC 5234?  (space or tab).
 */
#define IS_WSP(c)	((c) == ' ' || (c) == '\t')

/* support hex-encoded time values? */
nstime_t*
tvb_get_string_time(tvbuff_t *tvb, const unsigned offset, const unsigned length,
		    const unsigned encoding, nstime_t *ns, unsigned *endoff)
{
	char *begin;
	const char *ptr;
	const char *end       = NULL;
	int	     num_chars = 0;
	int	     utc_offset = 0;

	validate_single_byte_ascii_encoding(encoding);

	DISSECTOR_ASSERT(ns);

	begin = (char*) tvb_get_raw_string(NULL, tvb, offset, length);
	ptr = begin;

	while (IS_WSP(*ptr))
		ptr++;

	if (*ptr) {
		if ((encoding & ENC_ISO_8601_DATE_TIME) == ENC_ISO_8601_DATE_TIME) {
			if (!(end = iso8601_to_nstime(ns, ptr, ISO8601_DATETIME))) {


				goto fail;
			}
		} else if ((encoding & ENC_ISO_8601_DATE_TIME_BASIC) == ENC_ISO_8601_DATE_TIME_BASIC) {
			if (!(end = iso8601_to_nstime(ns, ptr, ISO8601_DATETIME_BASIC))) {


				goto fail;
			}
		} else {
			struct tm    tm;

			memset(&tm, 0, sizeof(tm));
			tm.tm_isdst = -1;
			ns->secs    = 0;
			ns->nsecs   = 0;

			/* note: sscanf is known to be inconsistent across platforms with respect
			   to whether a %n is counted as a return value or not, so we have to use
			   '>=' a lot */
			if (encoding & ENC_ISO_8601_DATE) {
				/* 2014-04-07 */
				if (sscanf(ptr, "%d-%d-%d%n",
				    &tm.tm_year,
				    &tm.tm_mon,
				    &tm.tm_mday,
				    &num_chars) >= 3)
				{
					end = ptr + num_chars;
					tm.tm_mon--;
					if (tm.tm_year > 1900) tm.tm_year -= 1900;
				} else {
					goto fail;
				}
			}
			else if (encoding & ENC_ISO_8601_TIME) {
				/* 2014-04-07 */
				if (sscanf(ptr, "%d:%d:%d%n",
				    &tm.tm_hour,
				    &tm.tm_min,
				    &tm.tm_sec,
				    &num_chars) >= 2)
				{
					/* what should we do about day/month/year? */
					/* setting it to "now" for now */
					time_t time_now = time(NULL);
					struct tm *tm_now = gmtime(&time_now);
					if (tm_now != NULL) {
						tm.tm_year = tm_now->tm_year;
						tm.tm_mon  = tm_now->tm_mon;
						tm.tm_mday = tm_now->tm_mday;
					} else {
						/* The second before the Epoch */
						tm.tm_year = 69;
						tm.tm_mon = 12;
						tm.tm_mday = 31;
					}
					end = ptr + num_chars;
				} else {
					goto fail;
				}
			}
			else if (encoding & ENC_IMF_DATE_TIME) {
				/*
				 * Match [dow,] day month year hh:mm[:ss] with
				 * two-digit years (RFC 822) or four-digit
				 * years (RFCs 1123, 2822, 5822). Skip
				 * the day of week since it is locale
				 * dependent and does not affect the resulting
				 * date anyway.
				 */
				if (g_ascii_isalpha(ptr[0]) && g_ascii_isalpha(ptr[1]) && g_ascii_isalpha(ptr[2]) && ptr[3] == ',')
					ptr += 4;   /* Skip day of week. */

				/*
				 * Parse the day-of-month and month
				 * name.
				 */
				char month_name[4] = { 0 };

				if (sscanf(ptr, "%d %3s%n",
				    &tm.tm_mday,
				    month_name,
				    &num_chars) < 2)
				{
					/* Not matched. */
					goto fail;
				}
				if (!parse_month_name(month_name, &tm.tm_mon)) {
					goto fail;
				}
				ptr += num_chars;
				while (IS_WSP(*ptr))
					ptr++;

				/*
				 * Scan the year.  Treat 2-digit years
				 * differently from 4-digit years.
				 */
				uint32_t year;
				const char *yearendp;

				if (!ws_strtou32(ptr, &yearendp, &year)) {
					goto fail;
				}
				if (!IS_WSP(*yearendp)) {
					/* Not followed by WSP. */
					goto fail;
				}
				if (yearendp - ptr < 2) {
					/* 1-digit year.  Error. */
					goto fail;
				}
				if (yearendp - ptr == 2) {
					/*
					 * 2-digit year.
					 *
					 * Match RFC 2822/RFC 5322 behavior;
					 * add 2000 to years from 0 to
					 * 49 and 1900 to uears from 50
					 * to 99.
					 */
					if (year <= 49) {
						year += 2000;
					} else {
						year += 1900;
					}
				} else if (yearendp - ptr == 3) {
					/*
					 * 3-digit year.
					 *
					 * Match RFC 2822/RFC 5322 behavior;
					 * add 1900 to the year.
					 */
					year += 1900;
				}
				tm.tm_year = year - 1900;
				ptr = yearendp;
				while (IS_WSP(*ptr))
					ptr++;

				/* Parse the time. */
				if (sscanf(ptr, "%d:%d%n:%d%n",
				    &tm.tm_hour,
				    &tm.tm_min,
				    &num_chars,
				    &tm.tm_sec,
				    &num_chars) < 2)
				{
					goto fail;
				}
				ptr += num_chars;
				while (IS_WSP(*ptr))
					ptr++;

				/*
				 * Parse the time zone.
				 * Check for obs-zone values first.
				 */
				if (g_ascii_strncasecmp(ptr, "UT", 2) == 0)
				{
					ptr += 2;
				}
				else if (g_ascii_strncasecmp(ptr, "GMT", 3) == 0)
				{
					ptr += 3;
				}
				else
				{
					char sign;
					int off_hr;
					int off_min;

					if (sscanf(ptr, "%c%2d%2d%n",
					    &sign,
					    &off_hr,
					    &off_min,
					    &num_chars) < 3)
					{
						goto fail;
					}

					/*
					 * If sign is '+', there's a positive
					 * UTC offset.
					 *
					 * If sign is '-', there's a negative
					 * UTC offset.
					 *
					 * Otherwise, that's an invalid UTC
					 * offset string.
					 */
					if (sign == '+')
						utc_offset += (off_hr * 3600) + (off_min * 60);
					else if (sign == '-')
						utc_offset -= (off_hr * 3600) + (off_min * 60);
					else {
						/* Sign must be + or - */
						goto fail;
					}
					ptr += num_chars;
				}
				end = ptr;
			}
			ns->secs = mktime_utc(&tm);
			if (ns->secs == (time_t)-1 && errno != 0) {
				goto fail;
			}
			ns->secs += utc_offset;
		}
	} else {
		/* Empty string */
		goto fail;
	}

	if (endoff)
	    *endoff = (unsigned)(offset + (end - begin));
	wmem_free(NULL, begin);
	return ns;

fail:
	wmem_free(NULL, begin);
	return NULL;
}

/* Fetch an IPv4 address, in network byte order.
 * We do *not* convert them to host byte order; we leave them in
 * network byte order. */
uint32_t
tvb_get_ipv4(tvbuff_t *tvb, const unsigned offset)
{
	const uint8_t *ptr;
	uint32_t      addr;

	ptr = fast_ensure_contiguous(tvb, offset, sizeof(uint32_t));
	memcpy(&addr, ptr, sizeof addr);
	return addr;
}

/* Fetch an IPv6 address. */
void
tvb_get_ipv6(tvbuff_t *tvb, const unsigned offset, ws_in6_addr *addr)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, sizeof(*addr));
	memcpy(addr, ptr, sizeof *addr);
}

/*
 * These routines return the length of the address in bytes on success
 * and -1 if the prefix length is too long.
 */
int
tvb_get_ipv4_addr_with_prefix_len(tvbuff_t *tvb, const unsigned offset, ws_in4_addr *addr,
    uint32_t prefix_len)
{
	uint8_t addr_len;

	if (prefix_len > 32)
		return -1;

	addr_len = (prefix_len + 7) / 8;
	*addr = 0;
	tvb_memcpy(tvb, addr, offset, addr_len);
	if (prefix_len % 8)
		((uint8_t*)addr)[addr_len - 1] &= ((0xff00 >> (prefix_len % 8)) & 0xff);
	return addr_len;
}

/*
 * These routines return the length of the address in bytes on success
 * and -1 if the prefix length is too long.
 */
int
tvb_get_ipv6_addr_with_prefix_len(tvbuff_t *tvb, const unsigned offset, ws_in6_addr *addr,
    uint32_t prefix_len)
{
	uint32_t addr_len;

	if (prefix_len > 128)
		return -1;

	addr_len = (prefix_len + 7) / 8;
	memset(addr->bytes, 0, 16);
	tvb_memcpy(tvb, addr->bytes, offset, addr_len);
	if (prefix_len % 8) {
		addr->bytes[addr_len - 1] &=
		    ((0xff00 >> (prefix_len % 8)) & 0xff);
	}

	return addr_len;
}

/* Fetch a GUID. */
void
tvb_get_ntohguid(tvbuff_t *tvb, const unsigned offset, e_guid_t *guid)
{
	const uint8_t *ptr = ensure_contiguous_unsigned(tvb, offset, GUID_LEN);

	guid->data1 = pntohu32(ptr + 0);
	guid->data2 = pntohu16(ptr + 4);
	guid->data3 = pntohu16(ptr + 6);
	memcpy(guid->data4, ptr + 8, sizeof guid->data4);
}

void
tvb_get_letohguid(tvbuff_t *tvb, const unsigned offset, e_guid_t *guid)
{
	const uint8_t *ptr = ensure_contiguous_unsigned(tvb, offset, GUID_LEN);

	guid->data1 = pletohu32(ptr + 0);
	guid->data2 = pletohu16(ptr + 4);
	guid->data3 = pletohu16(ptr + 6);
	memcpy(guid->data4, ptr + 8, sizeof guid->data4);
}

void
tvb_get_guid(tvbuff_t *tvb, const unsigned offset, e_guid_t *guid, const unsigned encoding)
{
	if (encoding & ENC_LITTLE_ENDIAN) {
		tvb_get_letohguid(tvb, offset, guid);
	} else {
		tvb_get_ntohguid(tvb, offset, guid);
	}
}

static const uint8_t bit_mask8[] = {
	0x00,
	0x01,
	0x03,
	0x07,
	0x0f,
	0x1f,
	0x3f,
	0x7f,
	0xff
};


/* Get a variable amount of bits
 *
 * Return a byte array with bit limited data.
 * When encoding is ENC_BIG_ENDIAN, the data is aligned to the left.
 * When encoding is ENC_LITTLE_ENDIAN, the data is aligned to the right.
 */
uint8_t *
tvb_get_bits_array(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned bit_offset,
		   size_t no_of_bits, size_t *data_length, const unsigned encoding)
{
	tvbuff_t *sub_tvb;
	if (encoding & ENC_LITTLE_ENDIAN) {
		sub_tvb = tvb_new_octet_right_aligned(tvb, bit_offset, (int32_t) no_of_bits);
	} else {
		sub_tvb = tvb_new_octet_aligned(tvb, bit_offset, (int32_t) no_of_bits);
	}
	*data_length = tvb_reported_length(sub_tvb);
	return (uint8_t*)tvb_memdup(scope, sub_tvb, 0, *data_length);
}

/* Get 1 - 8 bits */
uint8_t
tvb_get_bits8(tvbuff_t *tvb, unsigned bit_offset, const unsigned no_of_bits)
{
	DISSECTOR_ASSERT_HINT(no_of_bits <= 8, "Too many bits requested for 8-bit return type");
	return (uint8_t)_tvb_get_bits64(tvb, bit_offset, no_of_bits);
}

/* Get 1 - 16 bits */
uint16_t
tvb_get_bits16(tvbuff_t *tvb, unsigned bit_offset, const unsigned no_of_bits, const unsigned encoding)
{
	DISSECTOR_ASSERT_HINT(no_of_bits <= 16, "Too many bits requested for 16-bit return type");
	return (uint16_t)tvb_get_bits64(tvb, bit_offset, no_of_bits, encoding);
}

/* Get 1 - 32 bits */
uint32_t
tvb_get_bits32(tvbuff_t *tvb, unsigned bit_offset, const unsigned no_of_bits, const unsigned encoding)
{
	DISSECTOR_ASSERT_HINT(no_of_bits <= 32, "Too many bits requested for 32-bit return type");
	return (uint32_t)tvb_get_bits64(tvb, bit_offset, no_of_bits, encoding);
}

/* Get 1 - 64 bits */
uint64_t
tvb_get_bits64(tvbuff_t *tvb, unsigned bit_offset, const unsigned no_of_bits, const unsigned encoding)
{
	DISSECTOR_ASSERT_HINT(no_of_bits <= 64, "Too many bits requested for 64-bit return type");

	/* encoding determines bit numbering within octet array */
	if (encoding & ENC_LITTLE_ENDIAN) {
		return _tvb_get_bits64_le(tvb, bit_offset, no_of_bits);
	} else {
		return _tvb_get_bits64(tvb, bit_offset, no_of_bits);
	}
}

/*
 * This function will dissect a sequence of bits that does not need to be byte aligned; the bits
 * set will be shown in the tree as ..10 10.. and the integer value returned if return_value is set.
 * Offset should be given in bits from the start of the tvb.
 * Bits within octet are numbered from MSB (0) to LSB (7). Bit at bit_offset is return value most significant bit.
 * The function tolerates requests for more than 64 bits, but will only return the least significant 64 bits.
 */
static uint64_t
_tvb_get_bits64(tvbuff_t *tvb, unsigned bit_offset, const unsigned total_no_of_bits)
{
	uint64_t value;
	unsigned	octet_offset = bit_offset >> 3;
	uint8_t	required_bits_in_first_octet = 8 - (bit_offset % 8);

	if(required_bits_in_first_octet > total_no_of_bits)
	{
		/* the required bits don't extend to the end of the first octet */
		uint8_t right_shift = required_bits_in_first_octet - total_no_of_bits;
		value = (tvb_get_uint8(tvb, octet_offset) >> right_shift) & bit_mask8[total_no_of_bits % 8];
	}
	else
	{
		uint8_t remaining_bit_length = total_no_of_bits;

		/* get the bits up to the first octet boundary */
		value = 0;
		required_bits_in_first_octet %= 8;
		if(required_bits_in_first_octet != 0)
		{
			value = tvb_get_uint8(tvb, octet_offset) & bit_mask8[required_bits_in_first_octet];
			remaining_bit_length -= required_bits_in_first_octet;
			octet_offset ++;
		}
		/* take the biggest words, shorts or octets that we can */
		while (remaining_bit_length > 7)
		{
			switch (remaining_bit_length >> 4)
			{
			case 0:
				/* 8 - 15 bits. (note that 0 - 7 would have dropped out of the while() loop) */
				value <<= 8;
				value += tvb_get_uint8(tvb, octet_offset);
				remaining_bit_length -= 8;
				octet_offset ++;
				break;

			case 1:
				/* 16 - 31 bits */
				value <<= 16;
				value += tvb_get_ntohs(tvb, octet_offset);
				remaining_bit_length -= 16;
				octet_offset += 2;
				break;

			case 2:
			case 3:
				/* 32 - 63 bits */
				value <<= 32;
				value += tvb_get_ntohl(tvb, octet_offset);
				remaining_bit_length -= 32;
				octet_offset += 4;
				break;

			default:
				/* 64 bits (or more???) */
				value = tvb_get_ntoh64(tvb, octet_offset);
				remaining_bit_length -= 64;
				octet_offset += 8;
				break;
			}
		}
		/* get bits from any partial octet at the tail */
		if(remaining_bit_length)
		{
			value <<= remaining_bit_length;
			value += (tvb_get_uint8(tvb, octet_offset) >> (8 - remaining_bit_length));
		}
	}
	return value;
}

/*
 * Offset should be given in bits from the start of the tvb.
 * Bits within octet are numbered from LSB (0) to MSB (7). Bit at bit_offset is return value least significant bit.
 * The function tolerates requests for more than 64 bits, but will only return the least significant 64 bits.
 */
static uint64_t
_tvb_get_bits64_le(tvbuff_t *tvb, unsigned bit_offset, const unsigned total_no_of_bits)
{
	uint64_t value = 0;
	unsigned octet_offset = bit_offset / 8;
	unsigned remaining_bits = total_no_of_bits;
	unsigned shift = 0;

	if (remaining_bits > 64)
	{
		remaining_bits = 64;
	}

	if (bit_offset % 8)
	{
		/* not aligned, extract bits from first octet */
		shift = 8 - (bit_offset % 8);
		value = tvb_get_uint8(tvb, octet_offset) >> (bit_offset % 8);
		if (shift > remaining_bits)
		{
			/* keep only the requested bits */
			value &= (UINT64_C(1) << remaining_bits) - 1;
			remaining_bits = 0;
		}
		else
		{
			remaining_bits -= shift;
		}
		octet_offset++;
	}

	while (remaining_bits > 0)
	{
		/* take the biggest words, shorts or octets that we can */
		if (remaining_bits >= 32)
		{
			value |= ((uint64_t)tvb_get_letohl(tvb, octet_offset) << shift);
			shift += 32;
			remaining_bits -= 32;
			octet_offset += 4;
		}
		else if (remaining_bits >= 16)
		{
			value |= ((uint64_t)tvb_get_letohs(tvb, octet_offset) << shift);
			shift += 16;
			remaining_bits -= 16;
			octet_offset += 2;
		}
		else if (remaining_bits >= 8)
		{
			value |= ((uint64_t)tvb_get_uint8(tvb, octet_offset) << shift);
			shift += 8;
			remaining_bits -= 8;
			octet_offset += 1;
		}
		else
		{
			unsigned mask = (1 << remaining_bits) - 1;
			value |= (((uint64_t)tvb_get_uint8(tvb, octet_offset) & mask) << shift);
			shift += remaining_bits;
			remaining_bits = 0;
			octet_offset += 1;
		}
	}
	return value;
}

/* Get 1 - 32 bits (should be deprecated as same as tvb_get_bits32??) */
uint32_t
tvb_get_bits(tvbuff_t *tvb, const unsigned bit_offset, const unsigned no_of_bits, const unsigned encoding)
{
	return (uint32_t)tvb_get_bits64(tvb, bit_offset, no_of_bits, encoding);
}

static bool
tvb_find_uint8_generic(tvbuff_t *tvb, unsigned abs_offset, unsigned limit, uint8_t needle, unsigned *end_offset)
{
	const uint8_t *ptr;
	const uint8_t *result;

	if (end_offset) {
		*end_offset = abs_offset + limit;
	}

	ptr = ensure_contiguous_unsigned(tvb, abs_offset, limit); /* tvb_get_ptr() */
	if (!ptr)
		return false;

	result = (const uint8_t *) memchr(ptr, needle, limit);
	if (!result)
		return false;

	if (end_offset) {
		*end_offset = (unsigned)((result - ptr) + abs_offset);
	}
	return true;
}

static bool
_tvb_find_uint8_length(tvbuff_t *tvb, const unsigned offset, const unsigned limit, const uint8_t needle, unsigned *end_offset)
{
	const uint8_t *result;

	/* If we have real data, perform our search now. */
	if (tvb->real_data) {
		result = (const uint8_t *)memchr(tvb->real_data + offset, needle, limit);
		if (result == NULL) {
			if (end_offset) {
				*end_offset = offset + limit;
			}
			return false;
		}
		else {
			if (end_offset) {
				*end_offset = (unsigned)(result - tvb->real_data);
			}
			return true;
		}
	}

	if (tvb->ops->tvb_find_uint8)
		return tvb->ops->tvb_find_uint8(tvb, offset, limit, needle, end_offset);

	return tvb_find_uint8_generic(tvb, offset, limit, needle, end_offset);
}

bool
tvb_find_uint8_length(tvbuff_t *tvb, const unsigned offset, const unsigned maxlength, const uint8_t needle, unsigned *end_offset)
{
	unsigned      limit = 0;
	int           exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	/* Only search to end of tvbuff, w/o throwing exception. */
	if (limit > maxlength) {
		/* Maximum length doesn't go past end of tvbuff; search
		   to that value. */
		limit = maxlength;
	}

	return _tvb_find_uint8_length(tvb, offset, limit, needle, end_offset);
}

bool
tvb_find_uint8_remaining(tvbuff_t *tvb, const unsigned offset, const uint8_t needle, unsigned *end_offset)
{
	unsigned      limit = 0;
	int           exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	return _tvb_find_uint8_length(tvb, offset, limit, needle, end_offset);
}

static bool
_tvb_find_uint16_length(tvbuff_t *tvb, const unsigned offset, const unsigned limit, const uint16_t needle, unsigned *end_offset)
{
	const uint8_t needle1 = ((needle & 0xFF00) >> 8);
	const uint8_t needle2 = ((needle & 0x00FF) >> 0);
	unsigned searched_bytes = 0;
	unsigned pos = offset;

	if (end_offset) {
		*end_offset = offset + limit;
	}

	do {
		if (!_tvb_find_uint8_length(tvb, pos, limit - searched_bytes, needle1, &pos)) {
			return false;
		}

		/* Bytes searched so far (not counting the second byte) */
		searched_bytes = pos - offset + 1;

		/* Test vs. equality to account for the second byte */
		if (searched_bytes >= limit) {
			return false;
		}

		if (_tvb_find_uint8_length(tvb, pos + 1, 1, needle2, NULL)) {
			if (end_offset) {
				*end_offset = pos;
			}
			return true;
		}

		pos += 1;
		searched_bytes += 1;
	} while (searched_bytes < limit);

	return false;
}

bool
tvb_find_uint16_length(tvbuff_t *tvb, const unsigned offset, const unsigned maxlength, const uint16_t needle, unsigned *end_offset)
{
	unsigned      limit = 0;
	int           exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	/* Only search to end of tvbuff, w/o throwing exception. */
	if (limit > maxlength) {
		/* Maximum length doesn't go past end of tvbuff; search
		   to that value. */
		limit = maxlength;
	}

	return _tvb_find_uint16_length(tvb, offset, limit, needle, end_offset);
}

bool
tvb_find_uint16_remaining(tvbuff_t *tvb, const unsigned offset, const uint16_t needle, unsigned *end_offset)
{
	unsigned      limit = 0;
	int           exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	return _tvb_find_uint16_length(tvb, offset, limit, needle, end_offset);
}

static inline bool
tvb_ws_mempbrk_uint8_generic(tvbuff_t *tvb, unsigned abs_offset, unsigned limit, const ws_mempbrk_pattern* pattern, unsigned *found_offset, unsigned char *found_needle)
{
	const uint8_t *ptr;
	const uint8_t *result;

	if (found_offset) {
		*found_offset = abs_offset + limit;
	}

	ptr = ensure_contiguous_unsigned(tvb, abs_offset, limit); /* tvb_get_ptr */
	if (!ptr)
		return false;

	result = ws_mempbrk_exec(ptr, limit, pattern, found_needle);
	if (!result)
		return false;

	if (found_offset) {
		*found_offset = (unsigned)((result - ptr) + abs_offset);
	}
	return true;
}

static bool
_tvb_ws_mempbrk_uint8_length(tvbuff_t *tvb, const unsigned offset, const unsigned limit,
			const ws_mempbrk_pattern* pattern, unsigned *found_offset, unsigned char *found_needle)
{
	const uint8_t *result;

	/* If we have real data, perform our search now. */
	if (tvb->real_data) {
		result = ws_mempbrk_exec(tvb->real_data + offset, limit, pattern, found_needle);
		if (result == NULL) {
			if (found_offset) {
				*found_offset = offset + limit;
			}
			return false;
		}
		else {
			if (found_offset) {
				*found_offset = (unsigned)(result - tvb->real_data);
			}
			return true;
		}
	}

	if (tvb->ops->tvb_ws_mempbrk_pattern_uint8)
		return tvb->ops->tvb_ws_mempbrk_pattern_uint8(tvb, offset, limit, pattern, found_offset, found_needle);

	return tvb_ws_mempbrk_uint8_generic(tvb, offset, limit, pattern, found_offset, found_needle);
}

bool
tvb_ws_mempbrk_uint8_remaining(tvbuff_t *tvb, const unsigned offset,
			const ws_mempbrk_pattern* pattern, unsigned *found_offset, unsigned char *found_needle)
{
	unsigned      limit = 0;
	int           exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	return _tvb_ws_mempbrk_uint8_length(tvb, offset, limit, pattern, found_offset, found_needle);
}

bool
tvb_ws_mempbrk_uint8_length(tvbuff_t *tvb, const unsigned offset, const unsigned maxlength,
			const ws_mempbrk_pattern* pattern, unsigned *found_offset, unsigned char *found_needle)
{
	unsigned      limit = 0;
	int           exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	/* Only search to end of tvbuff, w/o throwing exception. */
	if (limit > maxlength) {
		/* Maximum length doesn't go past end of tvbuff; search
		   to that value. */
		limit = maxlength;
	}

	return _tvb_ws_mempbrk_uint8_length(tvb, offset, limit, pattern, found_offset, found_needle);
}

/* Find size of stringz (NUL-terminated string) by looking for terminating
 * NUL.  The size of the string includes the terminating NUL.
 *
 * If the NUL isn't found, it throws the appropriate exception.
 */
unsigned
tvb_strsize(tvbuff_t *tvb, const unsigned offset)
{
	unsigned nul_offset;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	validate_offset(tvb, offset);
	if (!tvb_find_uint8_remaining(tvb, offset, 0, &nul_offset)) {
		/*
		 * OK, we hit the end of the tvbuff, so we should throw
		 * an exception.
		 */
		if (tvb->length < tvb->contained_length) {
			THROW(BoundsError);
		} else if (tvb->flags & TVBUFF_FRAGMENT) {
			THROW(FragmentBoundsError);
		} else if (tvb->length < tvb->reported_length) {
			THROW(ContainedBoundsError);
		} else {
			THROW(ReportedBoundsError);
		}
	}
	return (nul_offset - offset) + 1;
}

/* UTF-16/UCS-2 version of tvb_strsize */
/* Returns number of bytes including the (two-bytes) null terminator */
unsigned
tvb_unicode_strsize(tvbuff_t *tvb, const unsigned offset)
{
	unsigned  cur_offset = offset;
	gunichar2 uchar;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* Note: don't use tvb_find_uint16 because it must be aligned */
	do {
		/* Endianness doesn't matter when looking for null */
		uchar = tvb_get_ntohs(tvb, cur_offset);
		/* Make sure we don't overflow */
		if (ckd_add(&cur_offset, cur_offset, 2)) {
			THROW(ReportedBoundsError);
		}
	} while(uchar != 0);

	return cur_offset - offset;
}

/* UTF-32/UCS-4 version of tvb_strsize */
/* Returns number of bytes including the (four-bytes) null terminator */
static unsigned
tvb_ucs_4_strsize(tvbuff_t *tvb, const unsigned offset)
{

	unsigned       end_offset;
	gunichar       uchar;

	DISSECTOR_ASSERT(tvb && tvb->initialized);
	end_offset = offset;
	do {
		/* Endianness doesn't matter when looking for null */
		uchar = tvb_get_ntohl(tvb, end_offset);
		/* Make sure we don't overflow */
		if (ckd_add(&end_offset, end_offset, 4)) {
			THROW(ReportedBoundsError);
		}
	} while(uchar != 0);
	return end_offset - offset;
}

unsigned
tvb_strsize_enc(tvbuff_t *tvb, const unsigned offset, const unsigned encoding)
{
	switch (encoding & ENC_CHARENCODING_MASK) {
	case ENC_UTF_16:
	case ENC_UCS_2:
		return tvb_unicode_strsize(tvb, offset);

	case ENC_UCS_4:
		return tvb_ucs_4_strsize(tvb, offset);

        case ENC_3GPP_TS_23_038_7BITS_PACKED:
        case ENC_3GPP_TS_23_038_7BITS_UNPACKED:
        case ENC_ETSI_TS_102_221_ANNEX_A:
                REPORT_DISSECTOR_BUG("TS 23.038 7bits has no null character and doesn't support null-terminated strings");
                break;

	case ENC_ASCII_7BITS:
		REPORT_DISSECTOR_BUG("Null-terminated strings not implemented for ENC_ASCII_7BITS yet");
		break;

	case ENC_APN_STR:
		/* At least as defined in 3GPP TS 23.003 Clause 9.1, null-termination
		 * does make sense as internal nulls are not allowed. */
		REPORT_DISSECTOR_BUG("Null-terminated strings are not implemented for ENC_APN_STR");
		break;

	case ENC_BCD_DIGITS_0_9:
	case ENC_KEYPAD_ABC_TBCD:
	case ENC_KEYPAD_BC_TBCD:
	case ENC_DECT_STANDARD_4BITS_TBCD:
		REPORT_DISSECTOR_BUG("Null-terminated strings are not supported for BCD encodings.");
		break;

	case ENC_ASCII:
	case ENC_UTF_8:
	case ENC_ISO_8859_1:
	case ENC_ISO_8859_2:
	case ENC_ISO_8859_3:
	case ENC_ISO_8859_4:
	case ENC_ISO_8859_5:
	case ENC_ISO_8859_6:
	case ENC_ISO_8859_7:
	case ENC_ISO_8859_8:
	case ENC_ISO_8859_9:
	case ENC_ISO_8859_10:
	case ENC_ISO_8859_11:
	case ENC_ISO_8859_13:
	case ENC_ISO_8859_14:
	case ENC_ISO_8859_15:
	case ENC_ISO_8859_16:
	case ENC_WINDOWS_1250:
	case ENC_WINDOWS_1251:
	case ENC_WINDOWS_1252:
	case ENC_MAC_ROMAN:
	case ENC_CP437:
	case ENC_CP855:
	case ENC_CP866:
	case ENC_ISO_646_BASIC:
	case ENC_EBCDIC:
	case ENC_EBCDIC_CP037:
	case ENC_EBCDIC_CP500:
	case ENC_T61:
	case ENC_GB18030:
	case ENC_EUC_KR:
	case ENC_DECT_STANDARD_8BITS:
	default:
		return tvb_strsize(tvb, offset);
	}
}

/* Find length of string by looking for end of string ('\0'), up to
 * 'maxlength' characters'; if 'maxlength' is -1, searches to end
 * of tvbuff.
 * Returns -1 if 'maxlength' reached before finding EOS. */
int
tvb_strnlen(tvbuff_t *tvb, const unsigned offset, const unsigned maxlength)
{
	unsigned result_offset;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* TODO - this needs a variant that returns a bool
	 * and sets a unsigned offset to the value if true. */
	if (!tvb_find_uint8_length(tvb, offset, maxlength, 0, &result_offset)) {
		return -1;
	}
	else {
		return (int)(result_offset - offset);
	}
}

/*
 * Implement strneql etc
 */

/*
 * Call strncmp after checking if enough chars left, returning 0 if
 * it returns 0 (meaning "equal") and -1 otherwise, otherwise return -1.
 */
int
tvb_strneql(tvbuff_t *tvb, const unsigned offset, const char *str, const size_t size)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned_no_exception(tvb, offset, (unsigned)size, NULL);

	if (ptr) {
		int cmp = strncmp((const char *)ptr, str, size);

		/*
		 * Return 0 if equal, -1 otherwise.
		 */
		return (cmp == 0 ? 0 : -1);
	} else {
		/*
		 * Not enough characters in the tvbuff to match the
		 * string.
		 */
		return -1;
	}
}

/*
 * Call g_ascii_strncasecmp after checking if enough chars left, returning
 * 0 if it returns 0 (meaning "equal") and -1 otherwise, otherwise return -1.
 */
int
tvb_strncaseeql(tvbuff_t *tvb, const unsigned offset, const char *str, const size_t size)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned_no_exception(tvb, offset, (unsigned)size, NULL);

	if (ptr) {
		int cmp = g_ascii_strncasecmp((const char *)ptr, str, size);

		/*
		 * Return 0 if equal, -1 otherwise.
		 */
		return (cmp == 0 ? 0 : -1);
	} else {
		/*
		 * Not enough characters in the tvbuff to match the
		 * string.
		 */
		return -1;
	}
}

/*
 * Check that the tvbuff contains at least size bytes, starting at
 * offset, and that those bytes are equal to str. Return 0 for success
 * and -1 for error. This function does not throw an exception.
 */
int
tvb_memeql(tvbuff_t *tvb, const unsigned offset, const uint8_t *str, size_t size)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned_no_exception(tvb, offset, (unsigned)size, NULL);

	if (ptr) {
		int cmp = memcmp(ptr, str, size);

		/*
		 * Return 0 if equal, -1 otherwise.
		 */
		return (cmp == 0 ? 0 : -1);
	} else {
		/*
		 * Not enough characters in the tvbuff to match the
		 * string.
		 */
		return -1;
	}
}

/**
 * Format the data in the tvb from offset for size.
 */
char *
tvb_format_text(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, const unsigned size)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	return format_text(scope, (const char*)ptr, size);
}

/*
 * Format the data in the tvb from offset for length ...
 */
char *
tvb_format_text_wsp(wmem_allocator_t* allocator, tvbuff_t *tvb, const unsigned offset, const unsigned size)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	return format_text_wsp(allocator, (const char*)ptr, size);
}

/**
 * Like "tvb_format_text()", but for null-padded strings; don't show
 * the null padding characters as "\000".
 */
char *
tvb_format_stringzpad(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, const unsigned size)
{
	const uint8_t *ptr, *p;
	unsigned      stringlen;

	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	for (p = ptr, stringlen = 0; stringlen < size && *p != '\0'; p++, stringlen++)
		;
	return format_text(scope, (const char*)ptr, stringlen);
}

/*
 * Like "tvb_format_text_wsp()", but for null-padded strings; don't show
 * the null padding characters as "\000".
 */
char *
tvb_format_stringzpad_wsp(wmem_allocator_t* allocator, tvbuff_t *tvb, const unsigned offset, const unsigned size)
{
	const uint8_t *ptr, *p;
	unsigned      stringlen;

	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	for (p = ptr, stringlen = 0; stringlen < size && *p != '\0'; p++, stringlen++)
		;
	return format_text_wsp(allocator, (const char*)ptr, stringlen);
}

/*
 * All string functions below take a scope as an argument.
 *
 *
 * If scope is NULL, memory is allocated with g_malloc() and user must
 * explicitly free it with g_free().
 * If scope is not NULL, memory is allocated with the corresponding pool
 * lifetime.
 *
 * All functions throw an exception if the tvbuff ends before the string
 * does.
 */

/*
 * Given a wmem scope, a tvbuff, an offset, and a length, treat the string
 * of bytes referred to by the tvbuff, offset, and length as an ASCII string,
 * with all bytes with the high-order bit set being invalid, and return a
 * pointer to a UTF-8 string, allocated using the wmem scope.
 *
 * Octets with the highest bit set will be converted to the Unicode
 * REPLACEMENT CHARACTER.
 */
static uint8_t *
tvb_get_ascii_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned length)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_ascii_string(scope, ptr, length);
}

/*
 * Given a wmem scope, a tvbuff, an offset, a length, and a translation table,
 * treat the string of bytes referred to by the tvbuff, offset, and length
 * as a string encoded using one octet per character, with octets with the
 * high-order bit clear being mapped by the translation table to 2-byte
 * Unicode Basic Multilingual Plane characters (including REPLACEMENT
 * CHARACTER) and octets with the high-order bit set being mapped to
 * REPLACEMENT CHARACTER, and return a pointer to a UTF-8 string,
 * allocated using the wmem scope.
 *
 * Octets with the highest bit set will be converted to the Unicode
 * REPLACEMENT CHARACTER.
 */
static uint8_t *
tvb_get_iso_646_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned length, const gunichar2 table[0x80])
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_iso_646_string(scope, ptr, length, table);
}

/*
 * Given a wmem scope, a tvbuff, an offset, and a length, treat the string
 * of bytes referred to by the tvbuff, the offset. and the length as a UTF-8
 * string, and return a pointer to a UTF-8 string, allocated using the wmem
 * scope, with all ill-formed sequences replaced with the Unicode REPLACEMENT
 * CHARACTER according to the recommended "best practices" given in the Unicode
 * Standard and specified by W3C/WHATWG.
 *
 * Note that in conformance with the Unicode Standard, this treats three
 * byte sequences corresponding to UTF-16 surrogate halves (paired or unpaired)
 * and two byte overlong encodings of 7-bit ASCII characters as invalid and
 * substitutes REPLACEMENT CHARACTER for them. Explicit support for nonstandard
 * derivative encoding formats (e.g. CESU-8, Java Modified UTF-8, WTF-8) could
 * be added later.
 */
static uint8_t *
tvb_get_utf_8_string(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, const unsigned length)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_utf_8_string(scope, ptr, length);
}

/*
 * Given a wmem scope, a tvbuff, an offset, and a length, treat the string
 * of bytes referred to by the tvbuff, the offset, and the length as a
 * raw string, and return a pointer to that string, allocated using the
 * wmem scope. This means a null is appended at the end, but no replacement
 * checking is done otherwise, unlike tvb_get_utf_8_string().
 */
static inline uint8_t *
tvb_get_raw_string(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, const unsigned length)
{
	uint8_t *strbuf;

	tvb_ensure_bytes_exist(tvb, offset, length);
	strbuf = (uint8_t *)wmem_alloc(scope, length + 1);
	tvb_memcpy(tvb, strbuf, offset, length);
	strbuf[length] = '\0';
	return strbuf;
}

/*
 * Given a wmem scope, a tvbuff, an offset, and a length, treat the string
 * of bytes referred to by the tvbuff, the offset, and the length as an
 * ISO 8859/1 string, and return a pointer to a UTF-8 string, allocated
 * using the wmem scope.
 */
static uint8_t *
tvb_get_string_8859_1(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned length)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_8859_1_string(scope, ptr, length);
}

/*
 * Given a wmem scope, a tvbuff, an offset, a length, and a translation
 * table, treat the string of bytes referred to by the tvbuff, the offset,
 * and the length as a string encoded using one octet per character, with
 * octets with the high-order bit clear being ASCII and octets with the
 * high-order bit set being mapped by the translation table to 2-byte
 * Unicode Basic Multilingual Plane characters (including REPLACEMENT
 * CHARACTER), and return a pointer to a UTF-8 string, allocated with the
 * wmem scope.
 */
static uint8_t *
tvb_get_string_unichar2(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned length, const gunichar2 table[0x80])
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_unichar2_string(scope, ptr, length, table);
}

/*
 * Given a wmem scope, a tvbuff, an offset, a length, and an encoding
 * giving the byte order, treat the string of bytes referred to by the
 * tvbuff, the offset, and the length as a UCS-2 encoded string in
 * the byte order in question, containing characters from the Basic
 * Multilingual Plane (plane 0) of Unicode, and return a pointer to a
 * UTF-8 string, allocated with the wmem scope.
 *
 * Encoding parameter should be ENC_BIG_ENDIAN or ENC_LITTLE_ENDIAN,
 * optionally with ENC_BOM.
 *
 * Specify length in bytes.
 *
 * XXX - should map lead and trail surrogate values to REPLACEMENT
 * CHARACTERs (0xFFFD)?
 * XXX - if there are an odd number of bytes, should put a
 * REPLACEMENT CHARACTER at the end.
 */
static uint8_t *
tvb_get_ucs_2_string(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned length, const unsigned encoding)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_ucs_2_string(scope, ptr, length, encoding);
}

/*
 * Given a wmem scope, a tvbuff, an offset, a length, and an encoding
 * giving the byte order, treat the string of bytes referred to by the
 * tvbuff, the offset, and the length as a UTF-16 encoded string in
 * the byte order in question, and return a pointer to a UTF-8 string,
 * allocated with the wmem scope.
 *
 * Encoding parameter should be ENC_BIG_ENDIAN or ENC_LITTLE_ENDIAN,
 * optionally with ENC_BOM.
 *
 * Specify length in bytes.
 *
 * XXX - should map surrogate errors to REPLACEMENT CHARACTERs (0xFFFD).
 * XXX - should map code points > 10FFFF to REPLACEMENT CHARACTERs.
 * XXX - if there are an odd number of bytes, should put a
 * REPLACEMENT CHARACTER at the end.
 */
static uint8_t *
tvb_get_utf_16_string(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned length, const unsigned encoding)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_utf_16_string(scope, ptr, length, encoding);
}

/*
 * Given a wmem scope, a tvbuff, an offset, a length, and an encoding
 * giving the byte order, treat the string of bytes referred to by the
 * tvbuff, the offset, and the length as a UCS-4 encoded string in
 * the byte order in question, and return a pointer to a UTF-8 string,
 * allocated with the wmem scope.
 *
 * Encoding parameter should be ENC_BIG_ENDIAN or ENC_LITTLE_ENDIAN,
 * optionally with ENC_BOM.
 *
 * Specify length in bytes
 *
 * XXX - should map lead and trail surrogate values to a "substitute"
 * UTF-8 character?
 * XXX - should map code points > 10FFFF to REPLACEMENT CHARACTERs.
 * XXX - if the number of bytes isn't a multiple of 4, should put a
 * REPLACEMENT CHARACTER at the end.
 */
static char *
tvb_get_ucs_4_string(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned length, const unsigned encoding)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return (char*)get_ucs_4_string(scope, ptr, length, encoding);
}

char *
tvb_get_ts_23_038_7bits_string_packed(wmem_allocator_t *scope, tvbuff_t *tvb,
	const unsigned bit_offset, unsigned no_of_chars)
{
	unsigned       in_offset = bit_offset >> 3; /* Current pointer to the input buffer */
	unsigned       length = ((no_of_chars + 1) * 7 + (bit_offset & 0x07)) >> 3;
	const uint8_t *ptr;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	ptr = ensure_contiguous_unsigned(tvb, in_offset, length);
	return (char*)get_ts_23_038_7bits_string_packed(scope, ptr, bit_offset, no_of_chars);
}

char *
tvb_get_ts_23_038_7bits_string_unpacked(wmem_allocator_t *scope, tvbuff_t *tvb,
	const unsigned offset, unsigned length)
{
	const uint8_t *ptr;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return (char*)get_ts_23_038_7bits_string_unpacked(scope, ptr, length);
}

char *
tvb_get_etsi_ts_102_221_annex_a_string(wmem_allocator_t *scope, tvbuff_t *tvb,
	const unsigned offset, unsigned length)
{
	const uint8_t *ptr;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return (char*)get_etsi_ts_102_221_annex_a_string(scope, ptr, length);
}

char *
tvb_get_ascii_7bits_string(wmem_allocator_t *scope, tvbuff_t *tvb,
	const unsigned bit_offset, unsigned no_of_chars)
{
	unsigned       in_offset = bit_offset >> 3; /* Current pointer to the input buffer */
	unsigned       length = ((no_of_chars + 1) * 7 + (bit_offset & 0x07)) >> 3;
	const uint8_t *ptr;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	ptr = ensure_contiguous_unsigned(tvb, in_offset, length);
	return (char*)get_ascii_7bits_string(scope, ptr, bit_offset, no_of_chars);
}

/*
 * Given a wmem scope, a tvbuff, an offset, a length, and a translation
 * table, treat the string of bytes referred to by the tvbuff, the offset,
 * and the length as a string encoded using one octet per character, with
 * octets being mapped by the translation table to 2-byte Unicode Basic
 * Multilingual Plane characters (including REPLACEMENT CHARACTER), and
 * return a pointer to a UTF-8 string, allocated with the wmem scope.
 */
static uint8_t *
tvb_get_nonascii_unichar2_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned length, const gunichar2 table[256])
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_nonascii_unichar2_string(scope, ptr, length, table);
}

/*
 * Given a wmem scope, a tvbuff, an offset, and a length, treat the bytes
 * referred to by the tvbuff, offset, and length as a GB18030 encoded string,
 * and return a pointer to a UTF-8 string, allocated with the wmem scope,
 * converted having substituted REPLACEMENT CHARACTER according to the
 * Unicode Standard 5.22 U+FFFD Substitution for Conversion.
 * ( https://www.unicode.org/versions/Unicode13.0.0/ch05.pdf )
 *
 * As expected, this will also decode GBK and GB2312 strings.
 */
static uint8_t *
tvb_get_gb18030_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned length)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_gb18030_string(scope, ptr, length);
}

/*
 * Given a wmem scope, a tvbuff, an offset, and a length, treat the bytes
 * referred to by the tvbuff, offset, and length as a EUC-KR encoded string,
 * and return a pointer to a UTF-8 string, allocated with the wmem scope,
 * converted having substituted REPLACEMENT CHARACTER according to the
 * Unicode Standard 5.22 U+FFFD Substitution for Conversion.
 * ( https://www.unicode.org/versions/Unicode13.0.0/ch05.pdf )
 */
static uint8_t *
tvb_get_euc_kr_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned length)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_euc_kr_string(scope, ptr, length);
}

static uint8_t *
tvb_get_t61_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned length)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_t61_string(scope, ptr, length);
}

/*
 * Encoding tables for BCD strings.
 */
static const dgt_set_t Dgt0_9_bcd = {
	{
		/*  0   1   2   3   4   5   6   7   8   9   a   b   c   d   e  f */
		   '0','1','2','3','4','5','6','7','8','9','?','?','?','?','?','?'
	}
};

static const dgt_set_t Dgt_keypad_abc_tbcd = {
	{
		/*  0   1   2   3   4   5   6   7   8   9   a   b   c   d   e  f */
		   '0','1','2','3','4','5','6','7','8','9','*','#','a','b','c','?'
	}
};

static const dgt_set_t Dgt_ansi_tbcd = {
	{
		/*  0   1   2   3   4   5   6   7   8   9   a   b   c   d   e  f */
		   '0','1','2','3','4','5','6','7','8','9','?','B','C','*','#','?'
	}
};

static const dgt_set_t Dgt_dect_standard_4bits_tbcd = {
	{
		/*  0   1   2   3   4   5   6   7   8   9   a   b   c   d   e  f */
		   '0','1','2','3','4','5','6','7','8','9','?',' ','?','?','?','?'
	}
};

static uint8_t *
tvb_get_apn_string(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset,
			     unsigned length)
{
	wmem_strbuf_t *str;

	/*
	 * This is a domain name.
	 *
	 * 3GPP TS 23.003, section 19.4.2 "Fully Qualified Domain Names
	 * (FQDNs)", subsection 19.4.2.1 "General", says:
	 *
	 *    The encoding of any identifier used as part of a Fully
	 *    Qualified Domain Name (FQDN) shall follow the Name Syntax
	 *    defined in IETF RFC 2181 [18], IETF RFC 1035 [19] and
	 *    IETF RFC 1123 [20].  An FQDN consists of one or more
	 *    labels. Each label is coded as a one octet length field
	 *    followed by that number of octets coded as 8 bit ASCII
	 *    characters.
	 *
	 * so this does not appear to use full-blown DNS compression -
	 * the upper 2 bits of the length don't indicate that it's a
	 * pointer or an extended label (RFC 2673).
	 */
	str = wmem_strbuf_new_sized(scope, length + 1);
	if (length > 0) {
		const uint8_t *ptr;

		ptr = ensure_contiguous_unsigned(tvb, offset, length);

		for (;;) {
			unsigned label_len;

			/*
			 * Process this label.
			 */
			label_len = *ptr;
			ptr++;
			length--;

			while (label_len != 0) {
				uint8_t ch;

				if (length == 0)
					goto end;

				ch = *ptr;
				if (ch < 0x80)
					wmem_strbuf_append_c(str, ch);
				else
					wmem_strbuf_append_unichar_repl(str);
				ptr++;
				label_len--;
				length--;
			}

			if (length == 0)
				goto end;

			wmem_strbuf_append_c(str, '.');
		}
	}

end:
	return (uint8_t *) wmem_strbuf_finalize(str);
}

static uint8_t *
tvb_get_dect_standard_8bits_string(wmem_allocator_t *scope, tvbuff_t *tvb, unsigned offset, unsigned length)
{
	const uint8_t *ptr;

	ptr = ensure_contiguous_unsigned(tvb, offset, length);
	return get_dect_standard_8bits_string(scope, ptr, length);
}

/*
 * Given a tvbuff, an offset, a length, and an encoding, allocate a
 * buffer big enough to hold a non-null-terminated string of that length
 * at that offset, plus a trailing '\0', copy into the buffer the
 * string as converted from the appropriate encoding to UTF-8, and
 * return a pointer to the string.
 */
uint8_t *
tvb_get_string_enc(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset,
			     const unsigned length, const unsigned encoding)
{
	uint8_t *strptr;
	bool odd, skip_first;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	switch (encoding & ENC_CHARENCODING_MASK) {

	case ENC_ASCII:
	default:
		/*
		 * For now, we treat bogus values as meaning
		 * "ASCII" rather than reporting an error,
		 * for the benefit of old dissectors written
		 * when the last argument to proto_tree_add_item()
		 * was a bool for the byte order, not an
		 * encoding value, and passed non-zero values
		 * other than true to mean "little-endian".
		 */
		strptr = tvb_get_ascii_string(scope, tvb, offset, length);
		break;

	case ENC_UTF_8:
		strptr = tvb_get_utf_8_string(scope, tvb, offset, length);
		break;

	case ENC_UTF_16:
		strptr = tvb_get_utf_16_string(scope, tvb, offset, length,
		    encoding & (ENC_LITTLE_ENDIAN|ENC_BOM));
		break;

	case ENC_UCS_2:
		strptr = tvb_get_ucs_2_string(scope, tvb, offset, length,
		    encoding & (ENC_LITTLE_ENDIAN|ENC_BOM));
		break;

	case ENC_UCS_4:
		strptr = (uint8_t*)tvb_get_ucs_4_string(scope, tvb, offset, length,
		    encoding & (ENC_LITTLE_ENDIAN|ENC_BOM));
		break;

	case ENC_ISO_8859_1:
		/*
		 * ISO 8859-1 printable code point values are equal
		 * to the equivalent Unicode code point value, so
		 * no translation table is needed.
		 */
		strptr = tvb_get_string_8859_1(scope, tvb, offset, length);
		break;

	case ENC_ISO_8859_2:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_2);
		break;

	case ENC_ISO_8859_3:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_3);
		break;

	case ENC_ISO_8859_4:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_4);
		break;

	case ENC_ISO_8859_5:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_5);
		break;

	case ENC_ISO_8859_6:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_6);
		break;

	case ENC_ISO_8859_7:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_7);
		break;

	case ENC_ISO_8859_8:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_8);
		break;

	case ENC_ISO_8859_9:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_9);
		break;

	case ENC_ISO_8859_10:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_10);
		break;

	case ENC_ISO_8859_11:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_11);
		break;

	case ENC_ISO_8859_13:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_13);
		break;

	case ENC_ISO_8859_14:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_14);
		break;

	case ENC_ISO_8859_15:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_15);
		break;

	case ENC_ISO_8859_16:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_iso_8859_16);
		break;

	case ENC_WINDOWS_1250:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_cp1250);
		break;

	case ENC_WINDOWS_1251:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_cp1251);
		break;

	case ENC_WINDOWS_1252:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_cp1252);
		break;

	case ENC_MAC_ROMAN:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_mac_roman);
		break;

	case ENC_CP437:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_cp437);
		break;

	case ENC_CP855:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_cp855);
		break;

	case ENC_CP866:
		strptr = tvb_get_string_unichar2(scope, tvb, offset, length, charset_table_cp866);
		break;

	case ENC_ISO_646_BASIC:
		strptr = tvb_get_iso_646_string(scope, tvb, offset, length, charset_table_iso_646_basic);
		break;

	case ENC_3GPP_TS_23_038_7BITS_PACKED:
		{
			unsigned bit_offset  = offset << 3;
			unsigned no_of_chars = (length << 3) / 7;
			strptr = (uint8_t*)tvb_get_ts_23_038_7bits_string_packed(scope, tvb, bit_offset, no_of_chars);
		}
		break;

	case ENC_ASCII_7BITS:
		{
			unsigned bit_offset  = offset << 3;
			unsigned no_of_chars = (length << 3) / 7;
			strptr = (uint8_t*)tvb_get_ascii_7bits_string(scope, tvb, bit_offset, no_of_chars);
		}
		break;

	case ENC_EBCDIC:
		/*
		 * "Common" EBCDIC, covering all characters with the
		 * same code point in all Roman-alphabet EBCDIC code
		 * pages.
		 */
		strptr = tvb_get_nonascii_unichar2_string(scope, tvb, offset, length, charset_table_ebcdic);
		break;

	case ENC_EBCDIC_CP037:
		/*
		 * EBCDIC code page 037.
		 */
		strptr = tvb_get_nonascii_unichar2_string(scope, tvb, offset, length, charset_table_ebcdic_cp037);
		break;

	case ENC_EBCDIC_CP500:
		/*
		 * EBCDIC code page 500.
		 */
		strptr = tvb_get_nonascii_unichar2_string(scope, tvb, offset, length, charset_table_ebcdic_cp500);
		break;

	case ENC_T61:
		strptr = tvb_get_t61_string(scope, tvb, offset, length);
		break;

	case ENC_BCD_DIGITS_0_9:
		/*
		 * Packed BCD, with digits 0-9.
		 */
		odd = (encoding & ENC_BCD_ODD_NUM_DIG) >> 16;
		skip_first = (encoding & ENC_BCD_SKIP_FIRST) >> 17;
		strptr = (uint8_t*)tvb_get_bcd_string(scope, tvb, offset, length, &Dgt0_9_bcd, skip_first, odd, !(encoding & ENC_LITTLE_ENDIAN));
		break;

	case ENC_KEYPAD_ABC_TBCD:
		/*
		 * Keypad-with-a/b/c "telephony BCD" - packed BCD, with
		 * digits 0-9 and symbols *, #, a, b, and c.
		 */
		odd = (encoding & ENC_BCD_ODD_NUM_DIG) >> 16;
		skip_first = (encoding & ENC_BCD_SKIP_FIRST) >> 17;
		strptr = (uint8_t*)tvb_get_bcd_string(scope, tvb, offset, length, &Dgt_keypad_abc_tbcd, skip_first, odd, !(encoding & ENC_LITTLE_ENDIAN));
		break;

	case ENC_KEYPAD_BC_TBCD:
		/*
		 * Keypad-with-B/C "telephony BCD" - packed BCD, with
		 * digits 0-9 and symbols B, C, *, and #.
		 */
		odd = (encoding & ENC_BCD_ODD_NUM_DIG) >> 16;
		skip_first = (encoding & ENC_BCD_SKIP_FIRST) >> 17;
		strptr = (uint8_t*)tvb_get_bcd_string(scope, tvb, offset, length, &Dgt_ansi_tbcd, skip_first, odd, !(encoding & ENC_LITTLE_ENDIAN));
		break;

	case ENC_3GPP_TS_23_038_7BITS_UNPACKED:
		strptr = (uint8_t*)tvb_get_ts_23_038_7bits_string_unpacked(scope, tvb, offset, length);
		break;

	case ENC_ETSI_TS_102_221_ANNEX_A:
		strptr = (uint8_t*)tvb_get_etsi_ts_102_221_annex_a_string(scope, tvb, offset, length);
		break;

	case ENC_GB18030:
		strptr = tvb_get_gb18030_string(scope, tvb, offset, length);
		break;

	case ENC_EUC_KR:
		strptr = tvb_get_euc_kr_string(scope, tvb, offset, length);
		break;

	case ENC_APN_STR:
		strptr = tvb_get_apn_string(scope, tvb, offset, length);
		break;

	case ENC_DECT_STANDARD_8BITS:
		strptr = tvb_get_dect_standard_8bits_string(scope, tvb, offset, length);
		break;

	case ENC_DECT_STANDARD_4BITS_TBCD:
		/*
		 * DECT standard 4bits "telephony BCD" - packed BCD, with
		 * digits 0-9 and symbol SPACE for 0xb.
		 */
		odd = (encoding & ENC_BCD_ODD_NUM_DIG) >> 16;
		skip_first = (encoding & ENC_BCD_SKIP_FIRST) >> 17;
		strptr = (uint8_t*)tvb_get_bcd_string(scope, tvb, offset, length, &Dgt_dect_standard_4bits_tbcd, skip_first, odd, false);
		break;
	}
	return strptr;
}

/*
 * This is like tvb_get_string_enc(), except that it handles null-padded
 * strings.
 *
 * Currently, string values are stored as UTF-8 null-terminated strings,
 * so nothing needs to be done differently for null-padded strings; we
 * could save a little memory by not storing the null padding.
 *
 * If we ever store string values differently, in a fashion that doesn't
 * involve null termination, that might change.
 */
uint8_t *
tvb_get_stringzpad(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset,
		   const unsigned length, const unsigned encoding)
{
	return tvb_get_string_enc(scope, tvb, offset, length, encoding);
}

/*
 * These routines are like the above routines, except that they handle
 * null-terminated strings.  They find the length of that string (and
 * throw an exception if the tvbuff ends before we find the null), and
 * also return through a pointer the length of the string, in bytes,
 * including the terminating null (the terminating null being 2 bytes
 * for UCS-2 and UTF-16, 4 bytes for UCS-4, and 1 byte for other
 * encodings).
 */
static uint8_t *
tvb_get_ascii_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp)
{
	unsigned       size;
	const uint8_t *ptr;

	size = tvb_strsize(tvb, offset);
	ptr  = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_ascii_string(scope, ptr, size);
}

static uint8_t *
tvb_get_iso_646_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp, const gunichar2 table[0x80])
{
	unsigned       size;
	const uint8_t *ptr;

	size = tvb_strsize(tvb, offset);
	ptr  = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_iso_646_string(scope, ptr, size, table);
}

static uint8_t *
tvb_get_utf_8_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp)
{
	unsigned   size;
	const uint8_t *ptr;

	size   = tvb_strsize(tvb, offset);
	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_utf_8_string(scope, ptr, size);
}

static uint8_t *
tvb_get_stringz_8859_1(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp)
{
	unsigned size;
	const uint8_t *ptr;

	size = tvb_strsize(tvb, offset);
	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_8859_1_string(scope, ptr, size);
}

static uint8_t *
tvb_get_stringz_unichar2(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp, const gunichar2 table[0x80])
{
	unsigned size;
	const uint8_t *ptr;

	size = tvb_strsize(tvb, offset);
	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_unichar2_string(scope, ptr, size, table);
}

/*
 * Given a tvbuff and an offset, with the offset assumed to refer to
 * a null-terminated string, find the length of that string (and throw
 * an exception if the tvbuff ends before we find the null), ensure that
 * the TVB is flat, and return a pointer to the string (in the TVB).
 * Also return the length of the string (including the terminating null)
 * through a pointer.
 *
 * As long as we aren't using composite TVBs, this saves the cycles used
 * (often unnecessarily) in allocating a buffer and copying the string into
 * it. OTOH, the string returned isn't valid UTF-8, so it shouldn't be
 * added to the tree, the columns, etc., just used with various other
 * functions that operate on strings that don't have a tvb_ equivalent.
 * That's hard to enforce, which is why this is deprecated.
 */
const uint8_t *
tvb_get_const_stringz(tvbuff_t *tvb, const unsigned offset, unsigned *lengthp)
{
	unsigned      size;
	const uint8_t *strptr;

	size   = tvb_strsize(tvb, offset);
	strptr = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return strptr;
}

static char *
tvb_get_ucs_2_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp, const unsigned encoding)
{
	unsigned       size;    /* Number of bytes in string */
	const uint8_t *ptr;

	size = tvb_unicode_strsize(tvb, offset);
	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return (char*)get_ucs_2_string(scope, ptr, size, encoding);
}

static char *
tvb_get_utf_16_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp, const unsigned encoding)
{
	unsigned       size;
	const uint8_t *ptr;

	size = tvb_unicode_strsize(tvb, offset);
	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return (char*)get_utf_16_string(scope, ptr, size, encoding);
}

static char *
tvb_get_ucs_4_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp, const unsigned encoding)
{
	unsigned       size;
	const uint8_t *ptr;

	size = tvb_ucs_4_strsize(tvb, offset);

	ptr = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return (char*)get_ucs_4_string(scope, ptr, size, encoding);
}

static uint8_t *
tvb_get_nonascii_unichar2_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp, const gunichar2 table[256])
{
	unsigned       size;
	const uint8_t *ptr;

	size = tvb_strsize(tvb, offset);
	ptr  = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_nonascii_unichar2_string(scope, ptr, size, table);
}

static uint8_t *
tvb_get_t61_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp)
{
	unsigned       size;
	const uint8_t *ptr;

	size = tvb_strsize(tvb, offset);
	ptr  = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_t61_string(scope, ptr, size);
}

static uint8_t *
tvb_get_gb18030_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp)
{
	unsigned       size;
	const uint8_t *ptr;

	size = tvb_strsize(tvb, offset);
	ptr  = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_gb18030_string(scope, ptr, size);
}

static uint8_t *
tvb_get_euc_kr_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp)
{
	unsigned       size;
	const uint8_t *ptr;

	size = tvb_strsize(tvb, offset);
	ptr  = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_euc_kr_string(scope, ptr, size);
}

static uint8_t *
tvb_get_dect_standard_8bits_stringz(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp)
{
	unsigned       size;
	const uint8_t *ptr;

	size = tvb_strsize(tvb, offset);
	ptr  = ensure_contiguous_unsigned(tvb, offset, size);
	if (lengthp)
		*lengthp = size;
	return get_dect_standard_8bits_string(scope, ptr, size);
}

uint8_t *
tvb_get_stringz_enc(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned *lengthp, const unsigned encoding)
{
	uint8_t *strptr;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	switch (encoding & ENC_CHARENCODING_MASK) {

	case ENC_ASCII:
	default:
		/*
		 * For now, we treat bogus values as meaning
		 * "ASCII" rather than reporting an error,
		 * for the benefit of old dissectors written
		 * when the last argument to proto_tree_add_item()
		 * was a bool for the byte order, not an
		 * encoding value, and passed non-zero values
		 * other than true to mean "little-endian".
		 */
		strptr = tvb_get_ascii_stringz(scope, tvb, offset, lengthp);
		break;

	case ENC_UTF_8:
		strptr = tvb_get_utf_8_stringz(scope, tvb, offset, lengthp);
		break;

	case ENC_UTF_16:
		strptr = (uint8_t*)tvb_get_utf_16_stringz(scope, tvb, offset, lengthp,
		    encoding & (ENC_LITTLE_ENDIAN|ENC_BOM));
		break;

	case ENC_UCS_2:
		strptr = (uint8_t*)tvb_get_ucs_2_stringz(scope, tvb, offset, lengthp,
		    encoding & (ENC_LITTLE_ENDIAN|ENC_BOM));
		break;

	case ENC_UCS_4:
		strptr = (uint8_t*)tvb_get_ucs_4_stringz(scope, tvb, offset, lengthp,
		    encoding & (ENC_LITTLE_ENDIAN|ENC_BOM));
		break;

	case ENC_ISO_8859_1:
		/*
		 * ISO 8859-1 printable code point values are equal
		 * to the equivalent Unicode code point value, so
		 * no translation table is needed.
		 */
		strptr = tvb_get_stringz_8859_1(scope, tvb, offset, lengthp);
		break;

	case ENC_ISO_8859_2:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_2);
		break;

	case ENC_ISO_8859_3:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_3);
		break;

	case ENC_ISO_8859_4:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_4);
		break;

	case ENC_ISO_8859_5:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_5);
		break;

	case ENC_ISO_8859_6:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_6);
		break;

	case ENC_ISO_8859_7:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_7);
		break;

	case ENC_ISO_8859_8:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_8);
		break;

	case ENC_ISO_8859_9:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_9);
		break;

	case ENC_ISO_8859_10:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_10);
		break;

	case ENC_ISO_8859_11:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_11);
		break;

	case ENC_ISO_8859_13:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_13);
		break;

	case ENC_ISO_8859_14:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_14);
		break;

	case ENC_ISO_8859_15:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_15);
		break;

	case ENC_ISO_8859_16:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_iso_8859_16);
		break;

	case ENC_WINDOWS_1250:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_cp1250);
		break;

	case ENC_WINDOWS_1251:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_cp1251);
		break;

	case ENC_WINDOWS_1252:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_cp1252);
		break;

	case ENC_MAC_ROMAN:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_mac_roman);
		break;

	case ENC_CP437:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_cp437);
		break;

	case ENC_CP855:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_cp855);
		break;

	case ENC_CP866:
		strptr = tvb_get_stringz_unichar2(scope, tvb, offset, lengthp, charset_table_cp866);
		break;

	case ENC_ISO_646_BASIC:
		strptr = tvb_get_iso_646_stringz(scope, tvb, offset, lengthp, charset_table_iso_646_basic);
		break;

	case ENC_BCD_DIGITS_0_9:
	case ENC_KEYPAD_ABC_TBCD:
	case ENC_KEYPAD_BC_TBCD:
	case ENC_DECT_STANDARD_4BITS_TBCD:
		REPORT_DISSECTOR_BUG("Null-terminated strings are not supported for BCD encodings.");
		break;

	case ENC_3GPP_TS_23_038_7BITS_PACKED:
	case ENC_3GPP_TS_23_038_7BITS_UNPACKED:
	case ENC_ETSI_TS_102_221_ANNEX_A:
		REPORT_DISSECTOR_BUG("TS 23.038 7bits has no null character and doesn't support null-terminated strings");
		break;

	case ENC_ASCII_7BITS:
		REPORT_DISSECTOR_BUG("tvb_get_stringz_enc function with ENC_ASCII_7BITS not implemented yet");
		break;

	case ENC_EBCDIC:
		/*
		 * "Common" EBCDIC, covering all characters with the
		 * same code point in all Roman-alphabet EBCDIC code
		 * pages.
		 */
		strptr = tvb_get_nonascii_unichar2_stringz(scope, tvb, offset, lengthp, charset_table_ebcdic);
		break;

	case ENC_EBCDIC_CP037:
		/*
		 * EBCDIC code page 037.
		 */
		strptr = tvb_get_nonascii_unichar2_stringz(scope, tvb, offset, lengthp, charset_table_ebcdic_cp037);
		break;

	case ENC_EBCDIC_CP500:
		/*
		 * EBCDIC code page 500.
		 */
		strptr = tvb_get_nonascii_unichar2_stringz(scope, tvb, offset, lengthp, charset_table_ebcdic_cp500);
		break;

	case ENC_T61:
		strptr = tvb_get_t61_stringz(scope, tvb, offset, lengthp);
		break;

	case ENC_GB18030:
		strptr = tvb_get_gb18030_stringz(scope, tvb, offset, lengthp);
		break;

	case ENC_EUC_KR:
		strptr = tvb_get_euc_kr_stringz(scope, tvb, offset, lengthp);
		break;

	case ENC_APN_STR:
		/* At least as defined in 3GPP TS 23.003 Clause 9.1, null-termination
		 * does make sense as internal nulls are not allowed. */
		REPORT_DISSECTOR_BUG("Null-terminated strings not implemented for ENC_APN_STR");
		break;

	case ENC_DECT_STANDARD_8BITS:
		strptr = tvb_get_dect_standard_8bits_stringz(scope, tvb, offset, lengthp);
		break;
	}

	return strptr;
}

/* Looks for a stringz (NUL-terminated string) in tvbuff and copies
 * no more than bufsize number of bytes, including terminating NUL, to buffer.
 * Returns length of string (not including terminating NUL).
 * In this way, it acts like snprintf().
 *
 * bufsize MUST be greater than 0.
 *
 * This function does not otherwise throw an exception for running out of room
 * in the buffer or running out of remaining bytes in the tvbuffer. It will
 * copy as many bytes to the buffer as possible (the lesser of bufsize - 1
 * and the number of remaining captured bytes) and then NUL terminate the
 * string.
 *
 * *bytes_copied will contain the number of bytes actually copied,
 * including the terminating-NUL if present in the frame, but not
 * if it was supplied by the function instead of copied from packet data.
 * [Not currently used, but could be used to determine how much to advance
 * the offset.]
 */
static unsigned
_tvb_get_raw_bytes_as_stringz(tvbuff_t *tvb, const unsigned offset, const unsigned bufsize, uint8_t* buffer, unsigned *bytes_copied)
{
	int	 exception;
	int      stringlen;
	unsigned limit;
	unsigned len = 0;

	/* Only read to end of tvbuff, w/o throwing exception. */
	exception = validate_offset_and_remaining(tvb, offset, &len);
	if (exception)
		THROW(exception);

	/* There must at least be room for the terminating NUL. */
	DISSECTOR_ASSERT(bufsize != 0);

	/* If there's no room for anything else, just return the NUL. */
	if (bufsize == 1) {
		buffer[0] = 0;
		if (len && tvb_get_uint8(tvb, offset) == 0) {
			*bytes_copied = 1;
		} else {
			*bytes_copied = 0;
		}
		return 0;
	}

	/* validate_offset_and_remaining() won't throw an exception if we're
	 * looking at the byte immediately after the end of the tvbuff. */
	if (len == 0) {
		THROW(ReportedBoundsError);
	}

	if (len < bufsize) {
		limit = len;
	}
	else {
		limit = bufsize - 1;
	}

	stringlen = tvb_strnlen(tvb, offset, limit);
	/* If NUL wasn't found, copy the data up to the limit and terminate */
	if (stringlen == -1) {
		tvb_memcpy(tvb, buffer, offset, limit);
		buffer[limit] = 0;
		*bytes_copied = limit;
		return limit;
	}

	/* Copy the string to buffer */
	tvb_memcpy(tvb, buffer, offset, stringlen + 1);
	*bytes_copied = stringlen + 1;
	return (unsigned)stringlen;
}

unsigned
tvb_get_raw_bytes_as_stringz(tvbuff_t *tvb, const unsigned offset, const unsigned bufsize, uint8_t* buffer)
{
	unsigned bytes_copied;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	return _tvb_get_raw_bytes_as_stringz(tvb, offset, bufsize, buffer, &bytes_copied);
}

/*
 * Given a tvbuff, an offset into the tvbuff, a buffer, and a buffer size,
 * extract as many raw bytes from the tvbuff, starting at the offset,
 * as 1) are available in the tvbuff and 2) will fit in the buffer, leaving
 * room for a terminating NUL.
 */
unsigned
tvb_get_raw_bytes_as_string(tvbuff_t *tvb, const unsigned offset, char *buffer, size_t bufsize)
{
	unsigned len = 0;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* There must be room for the string and the terminating NUL. */
	DISSECTOR_ASSERT(bufsize > 0);

	/* bufsize is size_t, but tvbuffers only have up to unsigned bytes */
	DISSECTOR_ASSERT(bufsize - 1 < UINT_MAX);

	len = _tvb_captured_length_remaining(tvb, offset);
	if (len == 0) {
		buffer[0] = '\0';
		return 0;
	}
	if (len > (bufsize - 1))
		len = (unsigned)(bufsize - 1);

	/* Copy the string to buffer */
	tvb_memcpy(tvb, buffer, offset, len);
	buffer[len] = '\0';
	return len;
}

bool
tvb_ascii_isprint(tvbuff_t *tvb, const unsigned offset, const unsigned length)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* XXX - Perhaps this function should return false instead of throwing
	 * an exception. */
	const uint8_t* buf = ensure_contiguous_unsigned(tvb, offset, length);

	for (unsigned i = 0; i < length; i++, buf++)
		if (!g_ascii_isprint(*buf))
			return false;

	return true;
}

bool
tvb_ascii_isprint_remaining(tvbuff_t *tvb, const unsigned offset)
{
	int exception;
	unsigned length;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &length);
	if (exception)
		THROW(exception);

	const uint8_t* buf = ensure_contiguous_unsigned(tvb, offset, length);

	for (unsigned i = 0; i < length; i++, buf++)
		if (!g_ascii_isprint(*buf))
			return false;

	return true;
}

bool
tvb_utf_8_isprint(tvbuff_t *tvb, const unsigned offset, const unsigned length)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* XXX - Perhaps this function should return false instead of throwing
	 * an exception. */
	const uint8_t* buf = ensure_contiguous_unsigned(tvb, offset, length);

	return isprint_utf8_string((const char*)buf, length);
}

bool
tvb_utf_8_isprint_remaining(tvbuff_t *tvb, const unsigned offset)
{
	int exception;
	unsigned length;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &length);
	if (exception)
		THROW(exception);

	const uint8_t* buf = ensure_contiguous_unsigned(tvb, offset, length);

	return isprint_utf8_string((const char*)buf, length);
}

bool
tvb_ascii_isdigit(tvbuff_t *tvb, const unsigned offset, const unsigned length)
{
	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* XXX - Perhaps this function should return false instead of throwing
	 * an exception. */
	const uint8_t* buf = ensure_contiguous_unsigned(tvb, offset, length);

	for (unsigned i = 0; i < length; i++, buf++)
		if (!g_ascii_isdigit(*buf))
			return false;

	return true;
}

static ws_mempbrk_pattern pbrk_crlf;

static bool
_tvb_find_line_end_length(tvbuff_t *tvb, const unsigned offset, const unsigned limit, unsigned *linelen, unsigned *next_offset)
{
	static bool compiled = false;
	unsigned eob_offset;
	unsigned eol_offset;
	unsigned char found_needle = 0;

	if (!compiled) {
		ws_mempbrk_compile(&pbrk_crlf, "\r\n");
		compiled = true;
	}

	eob_offset = offset + limit;

	/*
	 * Look either for a CR or an LF.
	 */
	if (!_tvb_ws_mempbrk_uint8_length(tvb, offset, limit, &pbrk_crlf, &eol_offset, &found_needle)) {
		/*
		 * No CR or LF - line is presumably continued in next packet.
		 */
		/*
		 * Pretend the line runs to the end of the tvbuff.
		 */
		if (linelen)
			*linelen = eob_offset - offset;
		if (next_offset)
			*next_offset = eob_offset;
		/*
		 * Tell our caller we saw no EOL, so they can try to
		 * desegment and get the entire line into one tvbuff.
		 */
		return false;
	} else {
		/*
		 * Find the number of bytes between the starting offset
		 * and the CR or LF.
		 */
		if (linelen)
			*linelen = eol_offset - offset;

		/*
		 * Is it a CR?
		 */
		if (found_needle == '\r') {
			/*
			 * Yes - is it followed by an LF?
			 */
			if (eol_offset + 1 >= eob_offset) {
				/*
				 * Dunno - the next byte isn't in this
				 * tvbuff.
				 */
				if (next_offset)
					*next_offset = eob_offset;
				/*
				 * We'll return false, although that
				 * runs the risk that if the line
				 * really *is* terminated with a CR,
				 * we won't properly dissect this
				 * tvbuff.
				 *
				 * It's probably more likely that
				 * the line ends with CR-LF than
				 * that it ends with CR by itself.
				 *
				 * XXX - Return a third value?
				 */
				return false;
			} else {
				/*
				 * Well, we can at least look at the next
				 * byte.
				 */
				if (tvb_get_uint8(tvb, eol_offset + 1) == '\n') {
					/*
					 * It's an LF; skip over the CR.
					 */
					eol_offset++;
				}
			}
		}

		/*
		 * Return the offset of the character after the last
		 * character in the line, skipping over the last character
		 * in the line terminator.
		 */
		if (next_offset)
			*next_offset = eol_offset + 1;
	}
	return true;
}

bool
tvb_find_line_end_remaining(tvbuff_t *tvb, const unsigned offset, unsigned *linelen, unsigned *next_offset)
{
	unsigned limit;
	int exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	return _tvb_find_line_end_length(tvb, offset, limit, linelen, next_offset);
}

bool
tvb_find_line_end_length(tvbuff_t *tvb, const unsigned offset, const unsigned maxlength, unsigned *linelen, unsigned *next_offset)
{
	unsigned limit;
	int exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	/* Only search to end of tvbuff, w/o throwing exception. */
	if (limit > maxlength) {
		/* Maximum length doesn't go past end of tvbuff; search
		   to that value. */
		limit = maxlength;
	}

	return _tvb_find_line_end_length(tvb, offset, limit, linelen, next_offset);
}

static ws_mempbrk_pattern pbrk_crlf_dquote;

static bool
_tvb_find_line_end_unquoted_length(tvbuff_t *tvb, const unsigned offset, unsigned limit, unsigned *linelen, unsigned *next_offset)
{
	unsigned cur_offset, char_offset;
	bool     is_quoted;
	unsigned char   c = 0;
	unsigned eob_offset;
	static bool compiled = false;
	unsigned len;
	bool	 found;

	if (!compiled) {
		ws_mempbrk_compile(&pbrk_crlf_dquote, "\r\n\"");
		compiled = true;
	}

	eob_offset = offset + limit;

	cur_offset = offset;
	is_quoted  = false;
	for (;;) {
		len = limit - (cur_offset - offset);
		/*
		 * Is this part of the string quoted?
		 */
		if (is_quoted) {
			/*
			 * Yes - look only for the terminating quote.
			 */
			found = _tvb_find_uint8_length(tvb, cur_offset, len, '"', &char_offset);
		} else {
			/*
			 * Look either for a CR, an LF, or a '"'.
			 */
			found = _tvb_ws_mempbrk_uint8_length(tvb, cur_offset, len, &pbrk_crlf_dquote, &char_offset, &c);
		}
		if (!found) {
			/*
			 * Not found - line is presumably continued in
			 * next packet.
			 * We pretend the line runs to the end of the tvbuff.
			 */
			if (linelen)
				*linelen = eob_offset - offset;
			if (next_offset)
				*next_offset = eob_offset;
			break;
		}

		if (is_quoted) {
			/*
			 * We're processing a quoted string.
			 * We only looked for ", so we know it's a ";
			 * as we're processing a quoted string, it's a
			 * closing quote.
			 */
			is_quoted = false;
		} else {
			/*
			 * OK, what is it?
			 */
			if (c == '"') {
				/*
				 * Un-quoted "; it begins a quoted
				 * string.
				 */
				is_quoted = true;
			} else {
				/*
				 * It's a CR or LF; we've found a line
				 * terminator.
				 *
				 * Find the number of bytes between the
				 * starting offset and the CR or LF.
				 */
				if (linelen)
					*linelen = char_offset - offset;

				/*
				 * Is it a CR?
				 */
				if (c == '\r') {
					/*
					 * Yes; is it followed by an LF?
					 */
					if (char_offset + 1 < eob_offset &&
						tvb_get_uint8(tvb, char_offset + 1)
						  == '\n') {
						/*
						 * Yes; skip over the CR.
						 */
						char_offset++;
					}
				}

				/*
				 * Return the offset of the character after
				 * the last character in the line, skipping
				 * over the last character in the line
				 * terminator, and quit.
				 */
				if (next_offset)
					*next_offset = char_offset + 1;
				break;
			}
		}

		/*
		 * Step past the character we found.
		 */
		cur_offset = char_offset + 1;
		if (cur_offset >= eob_offset) {
			/*
			 * The character we found was the last character
			 * in the tvbuff - line is presumably continued in
			 * next packet.
			 * We pretend the line runs to the end of the tvbuff.
			 */
			if (linelen)
				*linelen = eob_offset - offset;
			if (next_offset)
				*next_offset = eob_offset;
			break;
		}
	}
	return found;
}

/*
 * Given a tvbuff, an offset into the tvbuff, and a length that starts
 * at that offset (which may be -1 for "all the way to the end of the
 * tvbuff"), find the end of the (putative) line that starts at the
 * specified offset in the tvbuff, going no further than the specified
 * length.
 *
 * However, treat quoted strings inside the buffer specially - don't
 * treat newlines in quoted strings as line terminators.
 *
 * Return the length of the line (not counting the line terminator at
 * the end), or the amount of data remaining in the buffer if we don't
 * find a line terminator.
 *
 * If "next_offset" is not NULL, set "*next_offset" to the offset of the
 * character past the line terminator, or past the end of the buffer if
 * we don't find a line terminator.
 */
int
tvb_find_line_end_unquoted(tvbuff_t *tvb, const unsigned offset, int len, int *next_offset)
{
	unsigned linelen;
	unsigned abs_next_offset;
	unsigned limit;
	int exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	/* Only search to end of tvbuff, w/o throwing exception. */
	if (len >= 0 && limit > (unsigned) len) {
		/* Maximum length doesn't go past end of tvbuff; search
		   to that value. */
		limit = (unsigned) len;
	}

	_tvb_find_line_end_unquoted_length(tvb, offset, limit, &linelen, &abs_next_offset);
	if (next_offset) {
		*next_offset = (int)abs_next_offset;
	}
	return (int)linelen;
}

bool
tvb_find_line_end_unquoted_remaining(tvbuff_t *tvb, const unsigned offset, unsigned *linelen, unsigned *next_offset)
{
	unsigned limit;
	int exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	return _tvb_find_line_end_unquoted_length(tvb, offset, limit, linelen, next_offset);
}

bool
tvb_find_line_end_unquoted_length(tvbuff_t *tvb, const unsigned offset, const unsigned maxlength, unsigned *linelen, unsigned *next_offset)
{
	unsigned limit;
	int exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	/* Only search to end of tvbuff, w/o throwing exception. */
	if (limit > maxlength) {
		/* Maximum length doesn't go past end of tvbuff; search
		   to that value. */
		limit = maxlength;
	}

	return _tvb_find_line_end_unquoted_length(tvb, offset, limit, linelen, next_offset);
}

/*
 * Copied from the mgcp dissector. (This function should be moved to /epan )
 * tvb_skip_wsp - Returns the position in tvb of the first non-whitespace
 *				  character following offset or offset + maxlength -1 whichever
 *				  is smaller.
 *
 * Parameters:
 * tvb - The tvbuff in which we are skipping whitespace.
 * offset - The offset in tvb from which we begin trying to skip whitespace.
 * maxlength - The maximum distance from offset that we may try to skip
 * whitespace.
 *
 * Returns: The position in tvb of the first non-whitespace
 *			character following offset or offset + maxlength -1 whichever
 *			is smaller.
 */
unsigned
tvb_skip_wsp(tvbuff_t *tvb, const unsigned offset, const unsigned maxlength)
{
	unsigned counter;
	unsigned end, tvb_len;
	uint8_t  tempchar;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* Get the length remaining */
	/*tvb_len = tvb_captured_length(tvb);*/
	tvb_len = tvb->length;

	if (ckd_add(&end, offset, maxlength) || end > tvb_len) {
		end = tvb_len;
	}

	/* Skip past spaces, tabs, CRs and LFs until run out or meet something else */
	/* XXX - The MEGACO dissector uses g_ascii_isspace(), which might be
	 * slightly faster but also tests for vertical tab and form feed. */
	for (counter = offset;
		 counter < end &&
		  ((tempchar = tvb_get_uint8(tvb,counter)) == ' ' ||
		  tempchar == '\t' || tempchar == '\r' || tempchar == '\n');
		 counter++);

	return counter;
}

unsigned
tvb_skip_wsp_return(tvbuff_t *tvb, const unsigned offset)
{
	unsigned counter;
	uint8_t  tempchar;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* XXX - DISSECTOR_ASSERT(offset > 0) and then subtract 1 from offset?
	 * The way this is used the caller almost always wants to subtract one
	 * from the offset of a non WSP separator, and they might forget to do
	 * so and then this function return the offset past the separator. */

	/* XXX - The MEGACO dissector uses g_ascii_isspace(), which might be
	 * slightly faster but also tests for vertical tab and form feed. */
	for (counter = offset; counter > 0 &&
		((tempchar = tvb_get_uint8(tvb,counter)) == ' ' ||
		tempchar == '\t' || tempchar == '\n' || tempchar == '\r'); counter--);
	counter++;

	return counter;
}

unsigned
tvb_skip_uint8(tvbuff_t *tvb, unsigned offset, const unsigned maxlength, const uint8_t ch)
{
	unsigned end, tvb_len;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	/* Get the length remaining */
	/*tvb_len = tvb_captured_length(tvb);*/
	tvb_len = tvb->length;

	if (ckd_add(&end, offset, maxlength) || end > tvb_len) {
		end = tvb_len;
	}

	while (offset < end) {
		uint8_t tempch = tvb_get_uint8(tvb, offset);

		if (tempch != ch)
			break;
		offset++;
	}

	return offset;
}

static ws_mempbrk_pattern pbrk_whitespace;

static bool
_tvb_get_token_len_length(tvbuff_t *tvb, const unsigned offset, unsigned limit, unsigned *tokenlen, unsigned *next_offset)
{
	unsigned eot_offset;
	unsigned char found_needle = 0;
	static bool compiled = false;

	if (!compiled) {
		ws_mempbrk_compile(&pbrk_whitespace, " \r\n");
		compiled = true;
	}

	/*
	* Look either for a space, CR, or LF.
	*/
	if (!_tvb_ws_mempbrk_uint8_length(tvb, offset, limit, &pbrk_whitespace, &eot_offset, &found_needle)) {
		/*
		* No space, CR or LF - token is presumably continued in next packet.
		*/
		/*
		* Pretend the token runs to the end of the tvbuff.
		*/
		if (tokenlen)
			*tokenlen = eot_offset - offset;
		if (next_offset)
			*next_offset = eot_offset;
		/*
		* Tell our caller we saw no whitespace, so they can
		* try to desegment and get the entire line
		* into one tvbuff.
		*/
		return false;
	}

	/*
	* Find the number of bytes between the starting offset
	* and the space, CR or LF.
	*/
	if (tokenlen)
		*tokenlen = eot_offset - offset;

	/*
	* Return the offset of the character after the token delimiter,
	* skipping over the last character in the separator.
	*
	* XXX - get_token_len() from strutil.h returns the start offset of
	* the next token by skipping trailing spaces (but not spaces that
	* follow a CR or LF, only consecutive spaces). Should we align
	* the two functions? Most dissectors want to skip extra spaces,
	* and while the dissector _can_ follow up with tvb_skip_wsp, this
	* probably causes dissectors to use tvb_get_ptr + get_token_len,
	* which we want to discourage. OTOH, IMAP, which uses this, says
	* "in all cases, SP refers to exactly one space. It is NOT permitted
	* to substitute TAB, insert additional spaces, or otherwise treat
	* SP as being equivalent to linear whitespace (LWSP)."
	* https://www.rfc-editor.org/rfc/rfc9051.html#name-formal-syntax
	*
	* XXX - skip over CR-LF as a unit like tvb_find_line_end()?
	* get_token_len() doesn't, probably because most dissectors have
	* already found the line end before, but it probably makes sense
	* to do and unlike above it's unlikely it would break any protocol
	* (and might even fix some.)
	*/
	if (next_offset)
		*next_offset = eot_offset + 1;

	return true;
}

int tvb_get_token_len(tvbuff_t *tvb, const unsigned offset, int len, int *next_offset, const bool desegment)
{
	unsigned tokenlen;
	unsigned abs_next_offset;
	unsigned limit;
	int exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	/* Only search to end of tvbuff, w/o throwing exception. */
	if (len >= 0 && limit > (unsigned) len) {
		/* Maximum length doesn't go past end of tvbuff; search
		   to that value. */
		limit = (unsigned) len;
	}

	if (!_tvb_get_token_len_length(tvb, offset, limit, &tokenlen, &abs_next_offset) && desegment) {
		return -1;
	}
	if (next_offset) {
		*next_offset = (int)abs_next_offset;
	}
	return (int)tokenlen;
}

bool
tvb_get_token_len_remaining(tvbuff_t *tvb, const unsigned offset, unsigned *tokenlen, unsigned *next_offset)
{
	unsigned limit;
	int exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	return _tvb_get_token_len_length(tvb, offset, limit, tokenlen, next_offset);
}

bool
tvb_get_token_len_length(tvbuff_t *tvb, const unsigned offset, const unsigned maxlength, unsigned *tokenlen, unsigned *next_offset)
{
	unsigned limit;
	int exception;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	exception = validate_offset_and_remaining(tvb, offset, &limit);
	if (exception)
		THROW(exception);

	/* Only search to end of tvbuff, w/o throwing exception. */
	if (limit > maxlength) {
		/* Maximum length doesn't go past end of tvbuff; search
		   to that value. */
		limit = maxlength;
	}

	return _tvb_get_token_len_length(tvb, offset, limit, tokenlen, next_offset);
}

/*
 * Format a bunch of data from a tvbuff as bytes, returning a pointer
 * to the string with the formatted data, with "punct" as a byte
 * separator.
 */
char *
tvb_bytes_to_str_punct(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, const unsigned len, const char punct)
{
	return bytes_to_str_punct(scope, ensure_contiguous_unsigned(tvb, offset, len), len, punct);
}

/*
 * Given a wmem scope, a tvbuff, an offset, a length, an input digit
 * set, and a boolean indicator, fetch BCD-encoded digits from a
 * tvbuff starting from either the low or high half byte of the
 * first byte depending on the boolean indicator (true means "start
 * with the high half byte, ignoring the low half byte", and false
 * means "start with the low half byte and proceed to the high half
 * byte), formating the digits into characters according to the
 * input digit set, and return a pointer to a UTF-8 string, allocated
 * using the wmem scope.  A nibble of 0xf is considered a 'filler'
 * and will end the conversion. Similarly if odd is set the last
 * high nibble will be omitted. (Note that if both skip_first and
 * odd are true, then both the first and last semi-octet are skipped,
 * i.e. an even number of nibbles are considered.)
 */
char *
tvb_get_bcd_string(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, unsigned len, const dgt_set_t *dgt, bool skip_first, bool odd, bool bigendian)
{
	const uint8_t *ptr;
	int           i = 0;
	char         *digit_str;
	uint8_t       octet, nibble;

	DISSECTOR_ASSERT(tvb && tvb->initialized);

	ptr = ensure_contiguous_unsigned(tvb, offset, len);

	/*
	 * XXX - map illegal digits (digits that map to 0) to REPLACEMENT
	 * CHARACTER, and have all the tables in epan/tvbuff.c use 0 rather
	 * than '?'?
	 */
	digit_str = (char *)wmem_alloc(scope, len*2 + 1);

	while (len > 0) {
		octet = *ptr;
		if (!skip_first) {
			if (bigendian) {
				nibble = (octet >> 4) & 0x0f;
			} else {
				nibble = octet & 0x0f;
			}
			if (nibble == 0x0f) {
				/*
				 * Stop digit.
				 */
				break;
			}
			digit_str[i] = dgt->out[nibble];
			i++;
		}
		skip_first = false;

		/*
		 * unpack second value in byte
		 */
		if (bigendian) {
			nibble = octet & 0x0f;
		} else {
			nibble = octet >> 4;
		}

		if (nibble == 0x0f) {
			/*
			 * This is the stop digit or a filler digit.  Ignore
			 * it.
			 */
			break;
		}
		if ((len == 1) && (odd == true )){
			/* Last octet, skip last high nibble in case of odd number of digits */
			break;
		}
		digit_str[i] = dgt->out[nibble];
		i++;

		ptr++;
		len--;
	}
	digit_str[i] = '\0';
	return digit_str;
}

/* XXXX Fix me - needs odd indicator added (or just use of tvb_get_bcd_string / proto_tree_add_item) */
const char *
tvb_bcd_dig_to_str(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, const unsigned len, const dgt_set_t *dgt, bool skip_first)
{
	if (!dgt)
		dgt = &Dgt0_9_bcd;

	return tvb_get_bcd_string(scope, tvb, offset, len, dgt, skip_first, false, false);
}

const char *
tvb_bcd_dig_to_str_be(wmem_allocator_t *scope, tvbuff_t *tvb, const unsigned offset, const unsigned len, const dgt_set_t *dgt, bool skip_first)
{
	if (!dgt)
		dgt = &Dgt0_9_bcd;

	return tvb_get_bcd_string(scope, tvb, offset, len, dgt, skip_first, false, true);
}

/*
 * Format a bunch of data from a tvbuff as bytes, returning a pointer
 * to the string with the formatted data.
 */
char *
tvb_bytes_to_str(wmem_allocator_t *allocator, tvbuff_t *tvb, const unsigned offset, const unsigned len)
{
	return bytes_to_str(allocator, ensure_contiguous_unsigned(tvb, offset, len), len);
}

/* Find a needle tvbuff within a haystack tvbuff. */
int
tvb_find_tvb(tvbuff_t *haystack_tvb, tvbuff_t *needle_tvb, const int haystack_offset)
{
	unsigned	      haystack_abs_offset = 0, haystack_abs_length = 0;
	const uint8_t *haystack_data;
	const uint8_t *needle_data;
	const unsigned   needle_len = needle_tvb->length;
	const uint8_t *location;

	DISSECTOR_ASSERT(haystack_tvb && haystack_tvb->initialized);

	if (haystack_tvb->length < 1 || needle_tvb->length < 1) {
		return -1;
	}

	/* Get pointers to the tvbuffs' data. */
	haystack_data = ensure_contiguous(haystack_tvb, 0, -1);
	needle_data   = ensure_contiguous(needle_tvb, 0, -1);

	check_offset_length(haystack_tvb, haystack_offset, -1,
			&haystack_abs_offset, &haystack_abs_length);

	location = ws_memmem(haystack_data + haystack_abs_offset, haystack_abs_length,
			needle_data, needle_len);

	if (location) {
		return (int) (location - haystack_data);
	}

	return -1;
}

/* Find a needle tvbuff within a haystack tvbuff. */
bool
tvb_find_tvb_remaining(tvbuff_t *haystack_tvb, tvbuff_t *needle_tvb, const unsigned haystack_offset, unsigned *found_offset)
{
	const uint8_t *haystack_data;
	const uint8_t *needle_data;
	const unsigned   needle_len = needle_tvb->length;
	const uint8_t *location;
	int exception;
	unsigned haystack_rem_length;

	DISSECTOR_ASSERT(haystack_tvb && haystack_tvb->initialized);
	DISSECTOR_ASSERT(needle_tvb && needle_tvb->initialized);

	if (haystack_tvb->length < 1 || needle_tvb->length < 1) {
		return false;
	}

	exception = validate_offset_and_remaining(haystack_tvb, haystack_offset, &haystack_rem_length);
	if (exception)
		THROW(exception);

	/* Get pointers to the tvbuffs' data. */
	haystack_data = ensure_contiguous_unsigned(haystack_tvb, haystack_offset, haystack_rem_length);
	needle_data   = ensure_contiguous_unsigned(needle_tvb, 0, needle_len);

	location = ws_memmem(haystack_data, haystack_rem_length,
			needle_data, needle_len);

	if (location) {
		if (found_offset)
			*found_offset = (unsigned) (location - haystack_data) + haystack_offset;
		return true;
	}

	return false;
}

unsigned
tvb_raw_offset(tvbuff_t *tvb)
{
	if (!(tvb->flags & TVBUFF_RAW_OFFSET)) {
		tvb->raw_offset = tvb_offset_from_real_beginning(tvb);
		tvb->flags |= TVBUFF_RAW_OFFSET;
	}
	return tvb->raw_offset;
}

void
tvb_set_fragment(tvbuff_t *tvb)
{
	tvb->flags |= TVBUFF_FRAGMENT;
}

struct tvbuff *
tvb_get_ds_tvb(tvbuff_t *tvb)
{
	return(tvb->ds_tvb);
}

unsigned
tvb_get_varint(tvbuff_t *tvb, unsigned offset, unsigned maxlen, uint64_t *value, const unsigned encoding)
{
	*value = 0;

	switch (encoding & ENC_VARINT_MASK) {
	case ENC_VARINT_PROTOBUF:
	{
		unsigned i;
		uint64_t b; /* current byte */

		for (i = 0; ((i < FT_VARINT_MAX_LEN) && (i < maxlen)); ++i) {
			b = tvb_get_uint8(tvb, offset++);
			*value |= ((b & 0x7F) << (i * 7)); /* add lower 7 bits to val */

			if (b < 0x80) {
				/* end successfully because of last byte's msb(most significant bit) is zero */
				return i + 1;
			}
		}
		break;
	}

	case ENC_VARINT_ZIGZAG:
	{
		unsigned i;
		uint64_t b; /* current byte */

		for (i = 0; ((i < FT_VARINT_MAX_LEN) && (i < maxlen)); ++i) {
			b = tvb_get_uint8(tvb, offset++);
			*value |= ((b & 0x7F) << (i * 7)); /* add lower 7 bits to val */

			if (b < 0x80) {
				/* end successfully because of last byte's msb(most significant bit) is zero */
				*value = (*value >> 1) ^ ((*value & 1) ? -1 : 0);
				return i + 1;
			}
		}
		break;
	}

	case ENC_VARINT_SDNV:
	{
		/* Decodes similar to protobuf but in MSByte order */
		unsigned i;
		uint64_t b; /* current byte */

		for (i = 0; ((i < FT_VARINT_MAX_LEN) && (i < maxlen)); ++i) {
			b = tvb_get_uint8(tvb, offset++);
			if ((i == 9) && (*value >= UINT64_C(1)<<(64-7))) {
				// guaranteed overflow, not valid SDNV
				return 0;
			}
			*value <<= 7;
			*value |= (b & 0x7F); /* add lower 7 bits to val */

			if (b < 0x80) {
				/* end successfully because of last byte's msb(most significant bit) is zero */
				return i + 1;
			}
		}
		break;
	}

	case ENC_VARINT_QUIC:
	{
		/* calculate variable length */
		*value = tvb_get_uint8(tvb, offset);
		switch((*value) >> 6) {
		case 0: /* 0b00 => 1 byte length (6 bits Usable) */
			(*value) &= 0x3F;
			return 1;
		case 1: /* 0b01 => 2 bytes length (14 bits Usable) */
			*value = tvb_get_ntohs(tvb, offset) & 0x3FFF;
			return 2;
		case 2: /* 0b10 => 4 bytes length (30 bits Usable) */
			*value = tvb_get_ntohl(tvb, offset) & 0x3FFFFFFF;
			return 4;
		case 3: /* 0b11 => 8 bytes length (62 bits Usable) */
			*value = tvb_get_ntoh64(tvb, offset) & UINT64_C(0x3FFFFFFFFFFFFFFF);
			return 8;
		default: /* No Possible */
			ws_assert_not_reached();
			break;
		}
		break;
	}

	default:
		DISSECTOR_ASSERT_NOT_REACHED();
	}

	return 0; /* 10 bytes scanned, but no bytes' msb is zero */
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: t
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
 * :indentSize=8:tabSize=8:noTabs=false:
 */
