/* 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.7 2000/08/11 13:34:31 deniel 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 "tvbuff.h" #include /* Pointer versions of ntohs and ntohl. Given a pointer to a member of a * byte array, returns the value of the two or four bytes at the pointer. * The pletoh[sl] versions return the little-endian representation. * * We also provide "pntoh24()" and "pletoh24()", to extract 24-bit * quantities. * * If G_HAVE_GINT64 is defined, so we can use "gint64" and "guint64" to * refer to 64-bit integral quantities, we also provide pntohll and * phtolell, which extract 64-bit integral quantities. */ #define pntohs(p) ((guint16) \ ((guint16)*((guint8 *)p+0)<<8| \ (guint16)*((guint8 *)p+1)<<0)) #define pntoh24(p) ((guint32)*((guint8 *)p+0)<<16| \ (guint32)*((guint8 *)p+1)<<8| \ (guint32)*((guint8 *)p+2)<<0) #define pntohl(p) ((guint32)*((guint8 *)p+0)<<24| \ (guint32)*((guint8 *)p+1)<<16| \ (guint32)*((guint8 *)p+2)<<8| \ (guint32)*((guint8 *)p+3)<<0) #ifdef G_HAVE_GINT64 #define pntohll(p) ((guint64)*((guint8 *)p+0)<<56| \ (guint64)*((guint8 *)p+1)<<48| \ (guint64)*((guint8 *)p+2)<<40| \ (guint64)*((guint8 *)p+3)<<32| \ (guint64)*((guint8 *)p+4)<<24| \ (guint64)*((guint8 *)p+5)<<16| \ (guint64)*((guint8 *)p+6)<<8| \ (guint64)*((guint8 *)p+7)<<0) #endif #define pletohs(p) ((guint16) \ ((guint16)*((guint8 *)p+1)<<8| \ (guint16)*((guint8 *)p+0)<<0)) #define pletoh24(p) ((guint32)*((guint8 *)p+2)<<16| \ (guint32)*((guint8 *)p+1)<<8| \ (guint32)*((guint8 *)p+0)<<0) #define pletohl(p) ((guint32)*((guint8 *)p+3)<<24| \ (guint32)*((guint8 *)p+2)<<16| \ (guint32)*((guint8 *)p+1)<<8| \ (guint32)*((guint8 *)p+0)<<0) #ifdef G_HAVE_GINT64 #define pletohll(p) ((guint64)*((guint8 *)p+7)<<56| \ (guint64)*((guint8 *)p+6)<<48| \ (guint64)*((guint8 *)p+5)<<40| \ (guint64)*((guint8 *)p+4)<<32| \ (guint64)*((guint8 *)p+3)<<24| \ (guint64)*((guint8 *)p+2)<<16| \ (guint64)*((guint8 *)p+1)<<8| \ (guint64)*((guint8 *)p+0)<<0) #endif 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; } /************** 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; 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