/* tvbuff.c * * Testy, Virtual(-izable) Buffer of guint8*'s * * "Testy" -- the buffer gets mad when an attempt to access data * beyond the bounds of the buffer. An exception is thrown. * * "Virtual" -- the buffer can have its own data, can use a subset of * the data of a backing tvbuff, or can be a composite of * other tvbuffs. * * $Id: tvbuff.c,v 1.13 2000/09/11 07:33:56 sharpe Exp $ * * Copyright (c) 2000 by Gilbert Ramirez * * Ethereal - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "pint.h" #include "tvbuff.h" #include "packet.h" typedef struct { /* The backing tvbuff_t */ tvbuff_t *tvb; /* The offset/length of 'tvb' to which I'm privy */ guint offset; guint length; } tvb_backing_t; typedef struct { GSList *tvbs; /* Used for quick testing to see if this * is the tvbuff that a COMPOSITE is * interested in. */ guint *start_offsets; guint *end_offsets; } tvb_comp_t; struct tvbuff { /* Record-keeping */ tvbuff_type type; gboolean initialized; guint usage_count; /* The tvbuffs in which this tvbuff is a member * (that is, a backing tvbuff for a TVBUFF_SUBSET * or a member for a TVB_COMPOSITE) */ GSList *used_in; /* TVBUFF_SUBSET and TVBUFF_COMPOSITE keep track * of the other tvbuff's they use */ union { tvb_backing_t subset; tvb_comp_t composite; } tvbuffs; /* We're either a TVBUFF_REAL_DATA or a * TVBUFF_SUBSET that has a backing buffer that * has real_data != NULL, or a TVBUFF_COMPOSITE * which has flattened its data due to a call * to tvb_get_ptr(). */ guint8 *real_data; /* Length of virtual buffer (and/or real_data). */ guint length; /* Reported length. */ guint reported_length; /* Offset from beginning of first TVBUFF_REAL. */ gint raw_offset; /* Func to call when actually freed */ tvbuff_free_cb_t free_cb; }; static guint8* ensure_contiguous(tvbuff_t *tvb, gint offset, gint length); /* We dole out tvbuff's from this memchunk. */ GMemChunk *tvbuff_mem_chunk = NULL; void tvbuff_init(void) { if (!tvbuff_mem_chunk) tvbuff_mem_chunk = g_mem_chunk_create(tvbuff_t, 20, G_ALLOC_AND_FREE); } void tvbuff_cleanup(void) { if (tvbuff_mem_chunk) g_mem_chunk_destroy(tvbuff_mem_chunk); tvbuff_mem_chunk = NULL; } static void tvb_init(tvbuff_t *tvb, tvbuff_type type) { tvb_backing_t *backing; tvb_comp_t *composite; tvb->type = type; tvb->initialized = FALSE; tvb->usage_count = 1; tvb->length = 0; tvb->reported_length = 0; tvb->free_cb = NULL; tvb->real_data = NULL; tvb->raw_offset = -1; tvb->used_in = NULL; switch(type) { case TVBUFF_REAL_DATA: /* Nothing */ break; case TVBUFF_SUBSET: backing = &tvb->tvbuffs.subset; backing->tvb = NULL; backing->offset = 0; backing->length = 0; break; case TVBUFF_COMPOSITE: composite = &tvb->tvbuffs.composite; composite->tvbs = NULL; composite->start_offsets = NULL; composite->end_offsets = NULL; break; } } tvbuff_t* tvb_new(tvbuff_type type) { tvbuff_t *tvb; tvb = g_chunk_new(tvbuff_t, tvbuff_mem_chunk); g_assert(tvb); tvb_init(tvb, type); return tvb; } void tvb_free(tvbuff_t* tvb) { tvbuff_t *member_tvb; tvb_comp_t *composite; GSList *slist; tvb->usage_count--; if (tvb->usage_count == 0) { switch (tvb->type) { case TVBUFF_REAL_DATA: if (tvb->free_cb) { tvb->free_cb(tvb->real_data); } break; case TVBUFF_SUBSET: tvb_decrement_usage_count(tvb->tvbuffs.subset.tvb, 1); break; case TVBUFF_COMPOSITE: composite = &tvb->tvbuffs.composite; for (slist = composite->tvbs; slist != NULL ; slist = slist->next) { member_tvb = slist->data; tvb_decrement_usage_count(member_tvb, 1); } g_slist_free(composite->tvbs); if (composite->start_offsets) g_free(composite->start_offsets); if (composite->end_offsets) g_free(composite->end_offsets); if (tvb->real_data) g_free(tvb->real_data); break; } if (tvb->used_in) { g_slist_free(tvb->used_in); } g_chunk_free(tvb, tvbuff_mem_chunk); } } guint tvb_increment_usage_count(tvbuff_t* tvb, guint count) { tvb->usage_count += count; return tvb->usage_count; } guint tvb_decrement_usage_count(tvbuff_t* tvb, guint count) { if (tvb->usage_count <= count) { tvb->usage_count = 1; tvb_free(tvb); return 0; } else { tvb->usage_count -= count; return tvb->usage_count; } } void tvb_free_chain(tvbuff_t* tvb) { GSList *slist; /* Recursively call tvb_free_chain() */ for (slist = tvb->used_in; slist != NULL ; slist = slist->next) { tvb_free_chain( (tvbuff_t*)slist->data ); } /* Stop the recursion */ tvb_free(tvb); } void tvb_set_free_cb(tvbuff_t* tvb, tvbuff_free_cb_t func) { g_assert(tvb->type == TVBUFF_REAL_DATA); tvb->free_cb = func; } void tvb_set_real_data(tvbuff_t* tvb, const guint8* data, guint length, gint reported_length) { g_assert(tvb->type == TVBUFF_REAL_DATA); g_assert(!tvb->initialized); g_assert(reported_length >= -1); tvb->real_data = (gpointer) data; tvb->length = length; tvb->reported_length = reported_length; tvb->initialized = TRUE; } tvbuff_t* tvb_new_real_data(const guint8* data, guint length, gint reported_length) { tvbuff_t *tvb; tvb = tvb_new(TVBUFF_REAL_DATA); tvb_set_real_data(tvb, data, length, reported_length); return tvb; } /* Computes the absolute offset and length based on a possibly-negative offset * and a length that is possible -1 (which means "to the end of the data"). * Returns TRUE/FALSE indicating whether the offset is in bounds or * not. The integer ptrs are modified with the new offset and length. * No exception is thrown. * * XXX - we return TRUE, not FALSE, 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 gboolean compute_offset_length(tvbuff_t *tvb, gint offset, gint length, guint *offset_ptr, guint *length_ptr, int *exception) { g_assert(offset_ptr); g_assert(length_ptr); /* Compute the offset */ if (offset >= 0) { /* Positive offset - relative to the beginning of the packet. */ if (offset > tvb->reported_length) { if (exception) { *exception = ReportedBoundsError; } return FALSE; } else if (offset > tvb->length) { if (exception) { *exception = BoundsError; } return FALSE; } else { *offset_ptr = offset; } } else { /* Negative offset - relative to the end of the packet. */ if (-offset > tvb->reported_length) { if (exception) { *exception = ReportedBoundsError; } return FALSE; } else if (-offset > tvb->length) { if (exception) { *exception = BoundsError; } return FALSE; } else { *offset_ptr = tvb->length + offset; } } /* Compute the length */ g_assert(length >= -1); if (length == -1) { *length_ptr = tvb->length - *offset_ptr; } else { *length_ptr = length; } return TRUE; } static gboolean check_offset_length_no_exception(tvbuff_t *tvb, gint offset, gint length, guint *offset_ptr, guint *length_ptr, int *exception) { g_assert(tvb->initialized); if (!compute_offset_length(tvb, offset, length, offset_ptr, length_ptr, exception)) { return FALSE; } if (*offset_ptr + *length_ptr <= tvb->length) { return TRUE; } else if (*offset_ptr + *length_ptr <= tvb->reported_length) { if (exception) { *exception = BoundsError; } return FALSE; } else { if (exception) { *exception = ReportedBoundsError; } return FALSE; } g_assert_not_reached(); } /* Checks (+/-) offset and length and throws BoundsError if * either is out of bounds. Sets integer ptrs to the new offset * and length. */ static void check_offset_length(tvbuff_t *tvb, gint offset, gint length, guint *offset_ptr, guint *length_ptr) { int exception = 0; if (!check_offset_length_no_exception(tvb, offset, length, offset_ptr, length_ptr, &exception)) { g_assert(exception > 0); THROW(exception); } return; } static void add_to_used_in_list(tvbuff_t *tvb, tvbuff_t *used_in) { tvb->used_in = g_slist_prepend(tvb->used_in, used_in); } void tvb_set_subset(tvbuff_t *tvb, tvbuff_t *backing, gint backing_offset, gint backing_length, gint reported_length) { g_assert(tvb->type == TVBUFF_SUBSET); g_assert(!tvb->initialized); check_offset_length(backing, backing_offset, backing_length, &tvb->tvbuffs.subset.offset, &tvb->tvbuffs.subset.length); tvb_increment_usage_count(backing, 1); tvb->tvbuffs.subset.tvb = backing; tvb->length = tvb->tvbuffs.subset.length; g_assert(reported_length >= -1); if (reported_length == -1) { tvb->reported_length = backing->reported_length - tvb->tvbuffs.subset.offset; } else { tvb->reported_length = reported_length; } tvb->initialized = TRUE; add_to_used_in_list(backing, tvb); /* Optimization. If the backing buffer has a pointer to contiguous, real data, * then we can point directly to our starting offset in that buffer */ if (backing->real_data != NULL) { tvb->real_data = backing->real_data + tvb->tvbuffs.subset.offset; } } tvbuff_t* tvb_new_subset(tvbuff_t *backing, gint backing_offset, gint backing_length, gint reported_length) { tvbuff_t *tvb; tvb = tvb_new(TVBUFF_SUBSET); tvb_set_subset(tvb, backing, backing_offset, backing_length, reported_length); return tvb; } void tvb_composite_append(tvbuff_t* tvb, tvbuff_t* member) { tvb_comp_t *composite; g_assert(!tvb->initialized); composite = &tvb->tvbuffs.composite; composite->tvbs = g_slist_append( composite->tvbs, member ); } void tvb_composite_prepend(tvbuff_t* tvb, tvbuff_t* member) { tvb_comp_t *composite; g_assert(!tvb->initialized); composite = &tvb->tvbuffs.composite; composite->tvbs = g_slist_prepend( composite->tvbs, member ); } tvbuff_t* tvb_new_composite(void) { return tvb_new(TVBUFF_COMPOSITE); } void tvb_composite_finalize(tvbuff_t* tvb) { GSList *slist; guint num_members; tvbuff_t *member_tvb; tvb_comp_t *composite; int i = 0; g_assert(!tvb->initialized); g_assert(tvb->length == 0); composite = &tvb->tvbuffs.composite; num_members = g_slist_length(composite->tvbs); composite->start_offsets = g_new(guint, num_members); composite->end_offsets = g_new(guint, num_members); for (slist = composite->tvbs; slist != NULL; slist = slist->next) { g_assert(i < num_members); member_tvb = slist->data; composite->start_offsets[i] = tvb->length; tvb->length += member_tvb->length; composite->end_offsets[i] = tvb->length - 1; i++; } tvb->initialized = TRUE; } guint tvb_length(tvbuff_t* tvb) { g_assert(tvb->initialized); return tvb->length; } guint tvb_length_remaining(tvbuff_t *tvb, gint offset) { guint abs_offset, abs_length; g_assert(tvb->initialized); if (compute_offset_length(tvb, offset, -1, &abs_offset, &abs_length, NULL)) { return abs_length; } else { return -1; } } /* Validates that 'length' bytes are available starting from * offset (pos/neg). Does not throw BoundsError exception. */ gboolean tvb_bytes_exist(tvbuff_t *tvb, gint offset, gint length) { guint abs_offset, abs_length; g_assert(tvb->initialized); if (!compute_offset_length(tvb, offset, length, &abs_offset, &abs_length, NULL)) return FALSE; if (abs_offset + abs_length <= tvb->length) { return TRUE; } else { return FALSE; } } gboolean tvb_offset_exists(tvbuff_t *tvb, gint offset) { guint abs_offset, abs_length; g_assert(tvb->initialized); if (!compute_offset_length(tvb, offset, -1, &abs_offset, &abs_length, NULL)) return FALSE; if (abs_offset < tvb->length) { return TRUE; } else { return FALSE; } } guint tvb_reported_length(tvbuff_t* tvb) { g_assert(tvb->initialized); return tvb->reported_length; } static guint8* first_real_data_ptr(tvbuff_t *tvb) { tvbuff_t *member; switch(tvb->type) { case TVBUFF_REAL_DATA: return tvb->real_data; case TVBUFF_SUBSET: member = tvb->tvbuffs.subset.tvb; return first_real_data_ptr(member); case TVBUFF_COMPOSITE: member = tvb->tvbuffs.composite.tvbs->data; return first_real_data_ptr(member); } g_assert_not_reached(); return NULL; } static int offset_from_real_beginning(tvbuff_t *tvb, int counter) { tvbuff_t *member; switch(tvb->type) { case TVBUFF_REAL_DATA: return counter; case TVBUFF_SUBSET: member = tvb->tvbuffs.subset.tvb; return offset_from_real_beginning(member, counter + tvb->tvbuffs.subset.offset); case TVBUFF_COMPOSITE: member = tvb->tvbuffs.composite.tvbs->data; return offset_from_real_beginning(member, counter); } g_assert_not_reached(); return 0; } gint tvb_raw_offset(tvbuff_t *tvb) { if (tvb->raw_offset == -1) { tvb->raw_offset = offset_from_real_beginning(tvb, 0); } return tvb->raw_offset; } void tvb_compat(tvbuff_t *tvb, const guint8 **pd, int *offset) { g_assert(tvb->initialized); *pd = first_real_data_ptr(tvb); *offset = tvb_raw_offset(tvb); } static guint8* composite_ensure_contiguous(tvbuff_t *tvb, guint abs_offset, guint abs_length) { guint i, num_members; tvb_comp_t *composite; tvbuff_t *member_tvb = NULL; guint member_offset, member_length; GSList *slist; g_assert(tvb->type == TVBUFF_COMPOSITE); /* Maybe the range specified by offset/length * is contiguous inside one of the member tvbuffs */ composite = &tvb->tvbuffs.composite; num_members = g_slist_length(composite->tvbs); for (i = 0; i < num_members; i++) { if (abs_offset <= composite->end_offsets[i]) { slist = g_slist_nth(composite->tvbs, i); member_tvb = slist->data; break; } } g_assert(member_tvb); if (check_offset_length_no_exception(member_tvb, abs_offset - composite->start_offsets[i], abs_length, &member_offset, &member_length, NULL)) { g_assert(!tvb->real_data); return ensure_contiguous(member_tvb, member_offset, member_length); } else { tvb->real_data = tvb_memdup(tvb, 0, -1); return tvb->real_data + abs_offset; } g_assert_not_reached(); return NULL; } static guint8* ensure_contiguous(tvbuff_t *tvb, gint offset, gint length) { guint abs_offset, abs_length; check_offset_length(tvb, offset, length, &abs_offset, &abs_length); if (tvb->real_data) { return tvb->real_data + abs_offset; } else { switch(tvb->type) { case TVBUFF_REAL_DATA: g_assert_not_reached(); case TVBUFF_SUBSET: return ensure_contiguous(tvb->tvbuffs.subset.tvb, abs_offset - tvb->tvbuffs.subset.offset, abs_length); case TVBUFF_COMPOSITE: return composite_ensure_contiguous(tvb, abs_offset, abs_length); } } g_assert_not_reached(); return NULL; } static const guint8* guint8_find(const guint8* haystack, size_t haystacklen, guint8 needle) { const guint8 *b; int i; for (b = haystack, i = 0; i < haystacklen; i++, b++) { if (*b == needle) { return b; } } return NULL; } /************** ACCESSORS **************/ static guint8* composite_memcpy(tvbuff_t *tvb, guint8* target, guint abs_offset, guint abs_length) { guint i, num_members; tvb_comp_t *composite; tvbuff_t *member_tvb = NULL; guint member_offset, member_length; gboolean retval; GSList *slist; g_assert(tvb->type == TVBUFF_COMPOSITE); /* Maybe the range specified by offset/length * is contiguous inside one of the member tvbuffs */ composite = &tvb->tvbuffs.composite; num_members = g_slist_length(composite->tvbs); for (i = 0; i < num_members; i++) { if (abs_offset <= composite->end_offsets[i]) { slist = g_slist_nth(composite->tvbs, i); member_tvb = slist->data; break; } } g_assert(member_tvb); if (check_offset_length_no_exception(member_tvb, abs_offset - composite->start_offsets[i], abs_length, &member_offset, &member_length, NULL)) { g_assert(!tvb->real_data); return tvb_memcpy(member_tvb, target, member_offset, member_length); } else { /* The requested data is non-contiguous inside * the member tvb. We have to memcpy() the part that's in the member tvb, * then iterate across the other member tvb's, copying their portions * until we have copied all data. */ retval = compute_offset_length(member_tvb, abs_offset - composite->start_offsets[i], -1, &member_offset, &member_length, NULL); g_assert(retval); tvb_memcpy(member_tvb, target, member_offset, member_length); abs_offset += member_length; abs_length -= member_length; /* Recurse */ if (abs_length > 0) { composite_memcpy(tvb, target + member_length, abs_offset, abs_length); } return target; } g_assert_not_reached(); return NULL; } guint8* tvb_memcpy(tvbuff_t *tvb, guint8* target, gint offset, gint length) { guint abs_offset, abs_length; g_assert(length >= -1); check_offset_length(tvb, offset, length, &abs_offset, &abs_length); if (tvb->real_data) { return (guint8*) memcpy(target, tvb->real_data + abs_offset, abs_length); } switch(tvb->type) { case TVBUFF_REAL_DATA: g_assert_not_reached(); case TVBUFF_SUBSET: return tvb_memcpy(tvb->tvbuffs.subset.tvb, target, abs_offset - tvb->tvbuffs.subset.offset, abs_length); case TVBUFF_COMPOSITE: return composite_memcpy(tvb, target, offset, length); } g_assert_not_reached(); return NULL; } guint8* tvb_memdup(tvbuff_t *tvb, gint offset, gint length) { guint abs_offset, abs_length; guint8 *duped; check_offset_length(tvb, offset, length, &abs_offset, &abs_length); duped = g_malloc(abs_length); return tvb_memcpy(tvb, duped, abs_offset, abs_length); } guint8* tvb_get_ptr(tvbuff_t *tvb, gint offset, gint length) { return ensure_contiguous(tvb, offset, length); } guint8 tvb_get_guint8(tvbuff_t *tvb, gint offset) { guint8* ptr; ptr = ensure_contiguous(tvb, offset, sizeof(guint8)); return *ptr; } guint16 tvb_get_ntohs(tvbuff_t *tvb, gint offset) { guint8* ptr; ptr = ensure_contiguous(tvb, offset, sizeof(guint16)); return pntohs(ptr); } guint32 tvb_get_ntoh24(tvbuff_t *tvb, gint offset) { guint8* ptr; ptr = ensure_contiguous(tvb, offset, 3); return pntoh24(ptr); } guint32 tvb_get_ntohl(tvbuff_t *tvb, gint offset) { guint8* ptr; ptr = ensure_contiguous(tvb, offset, sizeof(guint32)); return pntohl(ptr); } #ifdef G_HAVE_GINT64 guint64 tvb_get_ntohll(tvbuff_t *tvb, gint offset) { guint8* ptr; ptr = ensure_contiguous(tvb, offset, sizeof(guint64)); return pntohll(ptr); } #endif guint16 tvb_get_letohs(tvbuff_t *tvb, gint offset) { guint8* ptr; ptr = ensure_contiguous(tvb, offset, sizeof(guint16)); return pletohs(ptr); } guint32 tvb_get_letoh24(tvbuff_t *tvb, gint offset) { guint8* ptr; ptr = ensure_contiguous(tvb, offset, 3); return pletoh24(ptr); } guint32 tvb_get_letohl(tvbuff_t *tvb, gint offset) { guint8* ptr; ptr = ensure_contiguous(tvb, offset, sizeof(guint32)); return pletohl(ptr); } #ifdef G_HAVE_GINT64 guint64 tvb_get_letohll(tvbuff_t *tvb, gint offset) { guint8* ptr; ptr = ensure_contiguous(tvb, offset, sizeof(guint64)); return pletohll(ptr); } #endif /* Find first occurence of needle in tvbuff, starting at offset. Searches * at most maxlength number of bytes. Returns the offset of the found needle, * or -1 if not found. Will not throw an exception, even if maxlength exceeds * boundary of tvbuff; in that case, -1 will be returned if the boundary is * reached before finding needle. */ gint tvb_find_guint8(tvbuff_t *tvb, gint offset, guint maxlength, guint8 needle) { guint abs_offset, junk_length; const guint8 *result; guint limit; check_offset_length(tvb, offset, 0, &abs_offset, &junk_length); /* Only search to end of tvbuff, w/o throwing exception. */ if (tvb_length_remaining(tvb, abs_offset) < maxlength) { limit = maxlength - (tvb_length(tvb) - abs_offset); } else { limit = maxlength; } /* If we have real data, perform our search now. */ if (tvb->real_data) { result = guint8_find(tvb->real_data + abs_offset, limit, needle); if (result == NULL) { return -1; } else { return result - tvb->real_data; } } switch(tvb->type) { case TVBUFF_REAL_DATA: g_assert_not_reached(); case TVBUFF_SUBSET: return tvb_find_guint8(tvb->tvbuffs.subset.tvb, abs_offset - tvb->tvbuffs.subset.offset, limit, needle); case TVBUFF_COMPOSITE: g_assert_not_reached(); /* XXX - return composite_find_guint8(tvb, offset, limit, needle); */ } g_assert_not_reached(); return -1; } /* Find length of string by looking for end of string ('\0'), up to * 'max_length' characters'. Returns -1 if 'max_length' reached * before finding EOS. */ gint tvb_strnlen(tvbuff_t *tvb, gint offset, guint maxlength) { gint result_offset; guint abs_offset, junk_length; check_offset_length(tvb, offset, 0, &abs_offset, &junk_length); result_offset = tvb_find_guint8(tvb, abs_offset, maxlength, 0); if (result_offset == -1) { return -1; } else { return result_offset; } } /* * Implement strneql etc */ /* Call strncmp after checking if enough chars left, otherwise return -1 */ gint tvb_strneql(tvbuff_t *tvb, gint offset, guint8 *str, gint size) { guint8 *ptr; ptr = ensure_contiguous(tvb, offset, size); if (ptr) { int cmp = strncmp(ptr, str, size); return (cmp == 0 ? 0 : -1); /* Make it -1 if comparison failed */ } else { return -1; /* Not enough chars in the TVB */ } } /* * Format the data in the tvb from offset for length ... */ guint8 * tvb_format_text(tvbuff_t *tvb, gint offset, gint size) { guint8 *ptr; gint len = size; if ((ptr = ensure_contiguous(tvb, offset, size)) == NULL) { len = tvb_length_remaining(tvb, offset); ptr = ensure_contiguous(tvb, offset, len); } return format_text(ptr, len); } /* Looks for a stringz (NUL-terminated string) in tvbuff and copies * no more than maxlength number of bytes, including terminating NUL, to buffer. * Returns length of string (not including terminating NUL), or -1 if the string was * truncated in the buffer due to not having reached the terminating NUL. * In this way, it acts like snprintf(). */ gint tvb_get_nstringz(tvbuff_t *tvb, gint offset, guint maxlength, guint8* buffer) { gint stringlen, NUL_offset; guint abs_offset, junk_length; gint limit; check_offset_length(tvb, offset, 0, &abs_offset, &junk_length); if (maxlength == 0) { buffer[0] = 0; return 0; } /* Only copy to end of tvbuff, w/o throwing exception. */ if (tvb_length_remaining(tvb, abs_offset) < maxlength) { limit = maxlength - (tvb_length(tvb) - abs_offset); } else { limit = maxlength; } NUL_offset = tvb_strnlen(tvb, abs_offset, limit); /* If NUL wasn't found, copy the data and return -1 */ if (NUL_offset == -1) { tvb_memcpy(tvb, buffer, abs_offset, limit); return -1; } /* Copy the string to buffer */ stringlen = NUL_offset - abs_offset; tvb_memcpy(tvb, buffer, abs_offset, stringlen + 1); return stringlen; } /* Like tvb_get_nstringz(), but never returns -1. The string is guaranteed to * have a terminating NUL. If the string was truncated when copied into buffer, * a NUL is placed at the end of buffer to terminate it. */ gint tvb_get_nstringz0(tvbuff_t *tvb, gint offset, guint maxlength, guint8* buffer) { gint len; len = tvb_get_nstringz(tvb, offset, maxlength, buffer); if (len == -1) { buffer[maxlength] = 0; return maxlength - 1; } else { return len; } }