diff options
author | Hadriel Kaplan <hadrielk@yahoo.com> | 2014-04-12 00:32:20 -0400 |
---|---|---|
committer | Anders Broman <a.broman58@gmail.com> | 2014-04-14 11:47:39 +0000 |
commit | dd002649c32a0f16720236b34fe5a7aefe04c457 (patch) | |
tree | d03bdc6f0f5ac36bb2d43a68f0cc8bcb72c27c3a /epan | |
parent | 92b501303bdeb163cec55825da8b683138517e4d (diff) | |
download | wireshark-dd002649c32a0f16720236b34fe5a7aefe04c457.tar.gz |
Add tvb_get and proto_tree_add for string-encoded timestamps
This commit adds tvb_get_string_time and proto_tree_add_time_item routines for
getting nstime fields from the tvb when they are encoded in ASCII string form.
The proto_tree_add_time_item routine is also usable for normal
big/little-endian encoded time_t, and has the advantage of retrieving
the value even if there's no proto tree.
It also exposes the routines to Lua, both so that a Lua script can take
advantage of this, but also so I can write a testsuite to test the functions.
Change-Id: I955da10f68f2680e3da3a5be5ad8fdce7ed6808c
Reviewed-on: https://code.wireshark.org/review/1084
Reviewed-by: Anders Broman <a.broman58@gmail.com>
Diffstat (limited to 'epan')
-rw-r--r-- | epan/proto.c | 387 | ||||
-rw-r--r-- | epan/proto.h | 63 | ||||
-rw-r--r-- | epan/tvbuff.c | 291 | ||||
-rw-r--r-- | epan/tvbuff.h | 32 | ||||
-rw-r--r-- | epan/wslua/wslua_tree.c | 135 | ||||
-rw-r--r-- | epan/wslua/wslua_tvb.c | 48 |
6 files changed, 799 insertions, 157 deletions
diff --git a/epan/proto.c b/epan/proto.c index a3284a07d8..2707f056ec 100644 --- a/epan/proto.c +++ b/epan/proto.c @@ -27,6 +27,7 @@ #include <ctype.h> #include <glib.h> #include <float.h> +#include <errno.h> #include <wsutil/bits_ctz.h> #include <wsutil/bits_count_ones.h> @@ -245,6 +246,12 @@ static expert_field ei_type_length_mismatch_error = EI_INIT; static expert_field ei_type_length_mismatch_warn = EI_INIT; static void register_type_length_mismatch(void); +/* Handle number string decoding errors with expert info */ +static int proto_number_string_decoding_error = -1; +static expert_field ei_number_string_decoding_failed_error = EI_INIT; +static expert_field ei_number_string_decoding_erange_error = EI_INIT; +static void register_number_string_decoding_error(void); + static int proto_register_field_init(header_field_info *hfinfo, const int parent); /* special-case header field used within proto.c */ @@ -445,6 +452,7 @@ proto_init(void (register_all_protocols_func)(register_cb cb, gpointer client_da /* Register the pseudo-protocols used for exceptions. */ register_show_exception(); register_type_length_mismatch(); + register_number_string_decoding_error(); /* Have each built-in dissector register its protocols, fields, dissector tables, and dissectors to be called through a @@ -1261,6 +1269,137 @@ get_int_value(proto_tree *tree, tvbuff_t *tvb, gint offset, gint length, const g return value; } +/* this can be called when there is no tree, so don't add that as a param */ +static void +get_time_value(tvbuff_t *tvb, const gint start, const gint length, const guint encoding, + nstime_t *time_stamp, const gboolean is_relative) +{ + guint32 tmpsecs; + guint64 todsecs; + + /* relative timestamps don't do TOD/NTP */ + if (is_relative && + (encoding != (ENC_TIME_TIMESPEC|ENC_BIG_ENDIAN)) && + (encoding != (ENC_TIME_TIMESPEC|ENC_LITTLE_ENDIAN)) ) + { + /* XXX: I think this should call REPORT_DISSECTOR_BUG(), but + the existing code didn't do that, so I'm not either */ + return; + } + + switch (encoding) { + + case ENC_TIME_TIMESPEC|ENC_BIG_ENDIAN: + /* + * 4-byte UNIX epoch, possibly followed by + * 4-byte fractional time in nanoseconds, + * both big-endian. + */ + time_stamp->secs = (time_t)tvb_get_ntohl(tvb, start); + if (length == 8) + time_stamp->nsecs = tvb_get_ntohl(tvb, start+4); + else + time_stamp->nsecs = 0; + break; + + case ENC_TIME_TIMESPEC|ENC_LITTLE_ENDIAN: + /* + * 4-byte UNIX epoch, possibly followed by + * 4-byte fractional time in nanoseconds, + * both little-endian. + */ + time_stamp->secs = (time_t)tvb_get_letohl(tvb, start); + if (length == 8) + time_stamp->nsecs = tvb_get_letohl(tvb, start+4); + else + time_stamp->nsecs = 0; + break; + + case ENC_TIME_TOD|ENC_BIG_ENDIAN: + /* + * TOD time stamp, big-endian. + */ +/* XXX - where should this go? */ +#define TOD_BASETIME G_GUINT64_CONSTANT(2208988800) + + todsecs = tvb_get_ntoh64(tvb, start) >> 12; + time_stamp->secs = (time_t)((todsecs / 1000000) - TOD_BASETIME); + time_stamp->nsecs = (int)((todsecs % 1000000) * 1000); + break; + + case ENC_TIME_TOD|ENC_LITTLE_ENDIAN: + /* + * TOD time stamp, big-endian. + */ + todsecs = tvb_get_letoh64(tvb, start) >> 12 ; + time_stamp->secs = (time_t)((todsecs / 1000000) - TOD_BASETIME); + time_stamp->nsecs = (int)((todsecs % 1000000) * 1000); + break; + + case ENC_TIME_NTP|ENC_BIG_ENDIAN: + /* + * NTP time stamp, big-endian. + */ + +/* XXX - where should this go? */ +#define NTP_BASETIME G_GUINT64_CONSTANT(2208988800) + + /* We need a temporary variable here so the unsigned math + * works correctly (for years > 2036 according to RFC 2030 + * chapter 3). + */ + tmpsecs = tvb_get_ntohl(tvb, start); + if (tmpsecs) + time_stamp->secs = (time_t)(tmpsecs - (guint32)NTP_BASETIME); + else + time_stamp->secs = tmpsecs; /* 0 */ + + if (length == 8) { + /* + * We're using nanoseconds here (and we will + * display nanoseconds), but NTP's timestamps + * have a precision in microseconds or greater. + * Round to 1 microsecond. + */ + time_stamp->nsecs = (int)(1000000*(tvb_get_ntohl(tvb, start+4)/4294967296.0)); + time_stamp->nsecs *= 1000; + } else { + time_stamp->nsecs = 0; + } + break; + + case ENC_TIME_NTP|ENC_LITTLE_ENDIAN: + /* + * NTP time stamp, big-endian. + */ + tmpsecs = tvb_get_letohl(tvb, start); + if (tmpsecs) + time_stamp->secs = (time_t)(tmpsecs - (guint32)NTP_BASETIME); + else + time_stamp->secs = tmpsecs; /* 0 */ + + if (length == 8) { + /* + * We're using nanoseconds here (and we will + * display nanoseconds), but NTP's timestamps + * have a precision in microseconds or greater. + * Round to 1 microsecond. + */ + time_stamp->nsecs = (int)(1000000*(tvb_get_letohl(tvb, start+4)/4294967296.0)); + time_stamp->nsecs *= 1000; + } else { + time_stamp->nsecs = 0; + } + break; + + default: + DISSECTOR_ASSERT_NOT_REACHED(); + time_stamp->secs = (time_t)0; + time_stamp->nsecs = 0; + break; + } +} + static void tree_data_add_maybe_interesting_field(tree_data_t *tree_data, field_info *fi) { @@ -1300,8 +1439,6 @@ proto_tree_new_item(field_info *new_fi, proto_tree *tree, double doubleval; const char *string; nstime_t time_stamp; - guint32 tmpsecs; - guint64 todsecs; gboolean length_error; switch (new_fi->hfinfo->type) { @@ -1676,117 +1813,8 @@ proto_tree_new_item(field_info *new_fi, proto_tree *tree, report_type_length_mismatch(tree, "an absolute time value", length, length_error); } - switch (encoding) { - - case ENC_TIME_TIMESPEC|ENC_BIG_ENDIAN: - /* - * 4-byte UNIX epoch, possibly followed by - * 4-byte fractional time in nanoseconds, - * both big-endian. - */ - time_stamp.secs = (time_t)tvb_get_ntohl(tvb, start); - if (length == 8) - time_stamp.nsecs = tvb_get_ntohl(tvb, start+4); - else - time_stamp.nsecs = 0; - break; - - case ENC_TIME_TIMESPEC|ENC_LITTLE_ENDIAN: - /* - * 4-byte UNIX epoch, possibly followed by - * 4-byte fractional time in nanoseconds, - * both little-endian. - */ - time_stamp.secs = (time_t)tvb_get_letohl(tvb, start); - if (length == 8) - time_stamp.nsecs = tvb_get_letohl(tvb, start+4); - else - time_stamp.nsecs = 0; - break; - - case ENC_TIME_TOD|ENC_BIG_ENDIAN: - /* - * TOD time stamp, big-endian. - */ -/* XXX - where should this go? */ -#define TOD_BASETIME G_GUINT64_CONSTANT(2208988800) - - todsecs = tvb_get_ntoh64(tvb, start) >> 12; - time_stamp.secs = (time_t)((todsecs / 1000000) - TOD_BASETIME); - time_stamp.nsecs = (int)((todsecs % 1000000) * 1000); - break; + get_time_value(tvb, start, length, encoding, &time_stamp, FALSE); - case ENC_TIME_TOD|ENC_LITTLE_ENDIAN: - /* - * TOD time stamp, big-endian. - */ - todsecs = tvb_get_letoh64(tvb, start) >> 12 ; - time_stamp.secs = (time_t)((todsecs / 1000000) - TOD_BASETIME); - time_stamp.nsecs = (int)((todsecs % 1000000) * 1000); - break; - - case ENC_TIME_NTP|ENC_BIG_ENDIAN: - /* - * NTP time stamp, big-endian. - */ - -/* XXX - where should this go? */ -#define NTP_BASETIME G_GUINT64_CONSTANT(2208988800) - - /* We need a temporary variable here so the unsigned math - * works correctly (for years > 2036 according to RFC 2030 - * chapter 3). - */ - tmpsecs = tvb_get_ntohl(tvb, start); - if (tmpsecs) - time_stamp.secs = (time_t)(tmpsecs - (guint32)NTP_BASETIME); - else - time_stamp.secs = tmpsecs; /* 0 */ - - if (length == 8) { - /* - * We're using nanoseconds here (and we will - * display nanoseconds), but NTP's timestamps - * have a precision in microseconds or greater. - * Round to 1 microsecond. - */ - time_stamp.nsecs = (int)(1000000*(tvb_get_ntohl(tvb, start+4)/4294967296.0)); - time_stamp.nsecs *= 1000; - } else { - time_stamp.nsecs = 0; - } - break; - - case ENC_TIME_NTP|ENC_LITTLE_ENDIAN: - /* - * NTP time stamp, big-endian. - */ - tmpsecs = tvb_get_letohl(tvb, start); - if (tmpsecs) - time_stamp.secs = (time_t)(tmpsecs - (guint32)NTP_BASETIME); - else - time_stamp.secs = tmpsecs; /* 0 */ - - if (length == 8) { - /* - * We're using nanoseconds here (and we will - * display nanoseconds), but NTP's timestamps - * have a precision in microseconds or greater. - * Round to 1 microsecond. - */ - time_stamp.nsecs = (int)(1000000*(tvb_get_letohl(tvb, start+4)/4294967296.0)); - time_stamp.nsecs *= 1000; - } else { - time_stamp.nsecs = 0; - } - break; - - default: - DISSECTOR_ASSERT_NOT_REACHED(); - time_stamp.secs = (time_t)0; - time_stamp.nsecs = 0; - break; - } proto_tree_set_time(new_fi, &time_stamp); break; @@ -1806,39 +1834,14 @@ proto_tree_new_item(field_info *new_fi, proto_tree *tree, */ if (encoding == TRUE) encoding = ENC_TIME_TIMESPEC|ENC_LITTLE_ENDIAN; - switch (encoding) { if (length != 8 && length != 4) { length_error = length < 4 ? TRUE : FALSE; report_type_length_mismatch(tree, "a relative time value", length, length_error); } - case ENC_TIME_TIMESPEC|ENC_BIG_ENDIAN: - /* - * 4-byte UNIX epoch, possibly followed by - * 4-byte fractional time in nanoseconds, - * both big-endian. - */ - time_stamp.secs = (time_t)tvb_get_ntohl(tvb, start); - if (length == 8) - time_stamp.nsecs = tvb_get_ntohl(tvb, start+4); - else - time_stamp.nsecs = 0; - break; + get_time_value(tvb, start, length, encoding, &time_stamp, TRUE); - case ENC_TIME_TIMESPEC|ENC_LITTLE_ENDIAN: - /* - * 4-byte UNIX epoch, possibly followed by - * 4-byte fractional time in nanoseconds, - * both little-endian. - */ - time_stamp.secs = (time_t)tvb_get_letohl(tvb, start); - if (length == 8) - time_stamp.nsecs = tvb_get_letohl(tvb, start+4); - else - time_stamp.nsecs = 0; - break; - } proto_tree_set_time(new_fi, &time_stamp); break; @@ -1952,6 +1955,82 @@ proto_tree_add_item(proto_tree *tree, int hfindex, tvbuff_t *tvb, return proto_tree_add_item_new(tree, proto_registrar_get_nth(hfindex), tvb, start, length, encoding); } +proto_item * +proto_tree_add_time_item(proto_tree *tree, int hfindex, tvbuff_t *tvb, + const gint start, gint length, const guint encoding, + nstime_t *retval, gint *endoff, gint *err) +{ + field_info *new_fi; + nstime_t time_stamp; + gint saved_err = 0; + header_field_info *hfinfo = proto_registrar_get_nth(hfindex); + + DISSECTOR_ASSERT_HINT(hfinfo != NULL, "Not passed hfi!"); + + DISSECTOR_ASSERT_HINT((hfinfo->type == FT_ABSOLUTE_TIME || + hfinfo->type == FT_RELATIVE_TIME), + "Called proto_tree_add_time_item but not a FT_XXX_TIME"); + + /* length has to be -1 or > 0 regardless of encoding */ + if (length < -1 || length == 0) { + REPORT_DISSECTOR_BUG(wmem_strdup_printf(wmem_packet_scope(), + "Invalid length %d passed to proto_tree_add_time_item", length)); + } + + time_stamp.secs = 0; + time_stamp.nsecs = 0; + + if (encoding & ENC_STR_TIME_MASK) { + tvb_get_string_time(tvb, start, length, encoding, &time_stamp, endoff); + /* grab the errno now before it gets overwritten */ + saved_err = errno; + } + else { + const gboolean is_relative = (hfinfo->type == FT_RELATIVE_TIME) ? TRUE : FALSE; + + if (length != 8 && length != 4) { + const gboolean length_error = length < 4 ? TRUE : FALSE; + if (is_relative) + report_type_length_mismatch(tree, "a relative time value", length, length_error); + else + report_type_length_mismatch(tree, "an absolute time value", length, length_error); + } + + tvb_ensure_bytes_exist(tvb, start, length); + get_time_value(tvb, start, length, encoding, &time_stamp, is_relative); + if (endoff) *endoff = length; + } + + if (err) *err = saved_err; + + if (retval) { + retval->secs = time_stamp.secs; + retval->nsecs = time_stamp.nsecs; + } + + TRY_TO_FAKE_THIS_ITEM(tree, hfinfo->id, hfinfo); + + new_fi = new_field_info(tree, hfinfo, tvb, start, length); + + if (new_fi == NULL) + return NULL; + + proto_tree_set_time(new_fi, &time_stamp); + + if (encoding & ENC_STRING) { + if (saved_err == ERANGE) + expert_add_info(NULL, tree, &ei_number_string_decoding_erange_error); + else if (saved_err == EDOM) + expert_add_info(NULL, tree, &ei_number_string_decoding_failed_error); + } + else { + FI_SET_FLAG(new_fi, + (encoding & ENC_LITTLE_ENDIAN) ? FI_LITTLE_ENDIAN : FI_BIG_ENDIAN); + } + + return proto_tree_add_node(tree, new_fi); +} + /* Add a FT_NONE to a proto_tree */ proto_item * proto_tree_add_none_format(proto_tree *tree, const int hfindex, tvbuff_t *tvb, @@ -5253,6 +5332,38 @@ register_type_length_mismatch(void) proto_set_cant_toggle(proto_type_length_mismatch); } +static void +register_number_string_decoding_error(void) +{ + static ei_register_info ei[] = { + { &ei_number_string_decoding_failed_error, + { "_ws.number_string.decoding_error.failed", PI_MALFORMED, PI_ERROR, + "Failed to decode number from string", EXPFILL + } + }, + { &ei_number_string_decoding_erange_error, + { "_ws.number_string.decoding_error.erange", PI_MALFORMED, PI_ERROR, + "Decoded number from string is out of valid range", EXPFILL + } + }, + }; + + expert_module_t* expert_number_string_decoding_error; + + proto_number_string_decoding_error = + proto_register_protocol("Number-String Decoding Error", + "Number-string decoding error", + "_ws.number_string.decoding_error"); + + expert_number_string_decoding_error = + expert_register_protocol(proto_number_string_decoding_error); + expert_register_field_array(expert_number_string_decoding_error, ei, array_length(ei)); + + /* "Number-String Decoding Error" isn't really a protocol, it's an error indication; + disabling them makes no sense. */ + proto_set_cant_toggle(proto_number_string_decoding_error); +} + #define PROTO_PRE_ALLOC_HF_FIELDS_MEM (144000+PRE_ALLOC_EXPERT_FIELDS_MEM) static int proto_register_field_init(header_field_info *hfinfo, const int parent) diff --git a/epan/proto.h b/epan/proto.h index 1e05072099..cde9dad3da 100644 --- a/epan/proto.h +++ b/epan/proto.h @@ -332,6 +332,33 @@ WS_DLL_PUBLIC WS_MSVC_NORETURN void proto_report_dissector_bug(const char *messa */ #define ENC_NA 0x00000000 +/* For cases where either native type or string encodings could both be + * valid arguments, we need something to distinguish which one is being + * passed as the argument, because ENC_BIG_ENDIAN and ENC_ASCII are both + * 0x00000000. So we use ENC_STR_NUM or ENC_STR_HEX bit-or'ed with + * ENC_ASCII and its ilk. + */ +/* this is for strings as numbers "12345" */ +#define ENC_STR_NUM 0x01000000 +/* this is for strings as hex "1a2b3c" */ +#define ENC_STR_HEX 0x02000000 +/* a convenience macro for either of the above */ +#define ENC_STRING 0x03000000 +/* mask out ENC_STR_* and related bits - should this replace ENC_CHARENCODING_MASK? */ +#define ENC_STR_MASK 0x0000FFFE + +/* For cases where a string encoding contains a timestamp, use one + * of these (but only one). These values can collide with above, because + * you can't do both at the same time. + */ +#define ENC_ISO_8601_DATE 0x00010000 +#define ENC_ISO_8601_TIME 0x00020000 +#define ENC_ISO_8601_DATE_TIME 0x00030000 +#define ENC_RFC_822 0x00040000 +#define ENC_RFC_1123 0x00080000 +/* a convenience macro for the above - for internal use only */ +#define ENC_STR_TIME_MASK 0x000F0000 + /* Values for header_field_info.display */ /* For integral types, the display format is a BASE_* field_display_e value @@ -922,6 +949,42 @@ WS_DLL_PUBLIC proto_item * proto_tree_add_time(proto_tree *tree, int hfindex, tvbuff_t *tvb, gint start, gint length, const nstime_t* value_ptr); +/** Get and add a FT_ABSOLUTE_TIME or FT_RELATIVE_TIME to a proto_tree. + The item is extracted from the tvbuff handed to it, based on the ENC_* passed + in for the encoding, and the retrieved value is also set to *retval so the + caller gets it back for other uses. + + This function retrieves the value even if the passed-in tree param is NULL, + so that it can be used by dissectors at all times to both get the value + and set the tree item to it. + + Like other proto_tree_add functions, if there is a tree and the value cannot + be decoded from the tvbuff, then an expert info error is reported. For string + encoding, this means that a failure to decode the time value from the string + results in an expert info error being added to the tree. + + If encoding is string-based, it will convert using tvb_get_string_time(); see + that function's comments for details. + + @note The nstime_t *retval must be pre-allocated as a nstime_t. + + @param tree the tree to append this item to + @param hfindex field index + @param tvb the tv buffer of the current data + @param start start of data in tvb + @param length length of data in tvb + @param encoding data encoding (e.g, ENC_LITTLE_ENDIAN, ENC_UTF_8|ENC_ISO_8601_DATE_TIME, etc.) + @param[in,out] retval points to a nstime_t which will be set to the value + @param[in,out] endoff if not NULL, gets set to the character after those consumed. + @param[in,out] err if not NULL, gets set to 0 if no failure, else the errno code (e.g., EDOM, ERANGE). + @return the newly created item, and retval is set to the decoded value + */ +WS_DLL_PUBLIC proto_item * +proto_tree_add_time_item(proto_tree *tree, int hfindex, tvbuff_t *tvb, + const gint start, gint length, const guint encoding, + nstime_t *retval, gint *endoff, gint *err); + + /** Add a formatted FT_ABSOLUTE_TIME or FT_RELATIVE_TIME to a proto_tree, with the format generating the string for the value and with the field name being included automatically. diff --git a/epan/tvbuff.c b/epan/tvbuff.c index c9eff6d192..668d1c0f8b 100644 --- a/epan/tvbuff.c +++ b/epan/tvbuff.c @@ -36,10 +36,13 @@ #include "config.h" #include <string.h> +#include <stdio.h> +#include <errno.h> #include "wsutil/pint.h" #include "wsutil/sign_ext.h" #include "wsutil/unicode-utils.h" +#include "wsutil/nstime.h" #include "tvbuff.h" #include "tvbuff-int.h" #include "strutil.h" @@ -48,6 +51,18 @@ #include "proto.h" /* XXX - only used for DISSECTOR_ASSERT, probably a new header file? */ #include "exceptions.h" +/* + * Just make sure we include the prototype for strptime as well 55 + * (needed for glibc 2.2) but make sure we do this only if not 56 + * yet defined. 57 + */ +#include <time.h> +/*#ifdef NEED_STRPTIME_H*/ +#ifndef strptime +#include "wsutil/strptime.h" +#endif + /*#endif*/ + static guint64 _tvb_get_bits64(tvbuff_t *tvb, guint bit_offset, const gint total_no_of_bits); @@ -57,6 +72,9 @@ _tvb_captured_length_remaining(const tvbuff_t *tvb, const gint offset); static inline const guint8* ensure_contiguous(tvbuff_t *tvb, const gint offset, const gint length); +static inline guint8 * +tvb_get_raw_string(wmem_allocator_t *scope, tvbuff_t *tvb, const gint offset, const gint length); + tvbuff_t * tvb_new(const struct tvb_ops *ops) { @@ -1268,6 +1286,250 @@ tvb_get_letohieee_double(tvbuff_t *tvb, const int offset) #endif } +static inline void +validate_single_byte_ascii_encoding(const guint encoding) +{ + const guint enc = encoding & ~ENC_STR_MASK; + + switch (enc) { + case ENC_UTF_16: + case ENC_UCS_2: + case ENC_UCS_4: + case ENC_3GPP_TS_23_038_7BITS: + case ENC_EBCDIC: + 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"); +} + +/* converts a broken down date representation, relative to UTC, + * to a timestamp; it uses timegm() if it's available. + * Copied from Glib source gtimer.c + */ +static time_t +mktime_utc (struct tm *tm) +{ + time_t retval; + +#ifndef HAVE_TIMEGM + static const gint days_before[] = + { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; +#endif + +#ifndef HAVE_TIMEGM + if (tm->tm_mon < 0 || tm->tm_mon > 11) + return (time_t) -1; + + retval = (tm->tm_year - 70) * 365; + retval += (tm->tm_year - 68) / 4; + retval += days_before[tm->tm_mon] + tm->tm_mday - 1; + + if (tm->tm_year % 4 == 0 && tm->tm_mon < 2) + retval -= 1; + + retval = ((((retval * 24) + tm->tm_hour) * 60) + tm->tm_min) * 60 + tm->tm_sec; +#else + retval = timegm (tm); +#endif /* !HAVE_TIMEGM */ + + return retval; +} + +/* support hex-encoded time values? */ +nstime_t* +tvb_get_string_time(tvbuff_t *tvb, const gint offset, const gint length, + const guint encoding, nstime_t *ns, gint *endoff) +{ + const gchar *begin = (gchar*) tvb_get_raw_string(wmem_packet_scope(), tvb, offset, length); + const gchar *ptr = begin; + const gchar *end = NULL; + struct tm tm; + nstime_t* retval = NULL; + char sign = '+'; + int off_hr = 0; + int off_min = 0; + int num_chars = 0; + gboolean matched = FALSE; + + errno = EDOM; + + validate_single_byte_ascii_encoding(encoding); + + DISSECTOR_ASSERT(ns); + + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + ns->secs = 0; + ns->nsecs = 0; + + while (*ptr == ' ') ptr++; + + if (*ptr) { + /* 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_TIME) == ENC_ISO_8601_DATE_TIME) { + /* TODO: using sscanf this many times is probably slow; might want + to parse it by hand in the future */ + /* 2014-04-07T05:41:56+00:00 */ + if (sscanf(ptr, "%d-%d-%d%*c%d:%d:%d%c%d:%d%n", + &tm.tm_year, + &tm.tm_mon, + &tm.tm_mday, + &tm.tm_hour, + &tm.tm_min, + &tm.tm_sec, + &sign, + &off_hr, + &off_min, + &num_chars) >= 9) + { + matched = TRUE; + } + /* no seconds is ok */ + else if (sscanf(ptr, "%d-%d-%d%*c%d:%d%c%d:%d%n", + &tm.tm_year, + &tm.tm_mon, + &tm.tm_mday, + &tm.tm_hour, + &tm.tm_min, + &sign, + &off_hr, + &off_min, + &num_chars) >= 8) + { + matched = TRUE; + } + /* 2007-04-05T14:30:56Z */ + else if (sscanf(ptr, "%d-%d-%d%*c%d:%d:%dZ%n", + &tm.tm_year, + &tm.tm_mon, + &tm.tm_mday, + &tm.tm_hour, + &tm.tm_min, + &tm.tm_sec, + &num_chars) >= 6) + { + matched = TRUE; + off_hr = 0; + off_min = 0; + } + /* 2007-04-05T14:30Z no seconds is ok */ + else if (sscanf(ptr, "%d-%d-%d%*c%d:%dZ%n", + &tm.tm_year, + &tm.tm_mon, + &tm.tm_mday, + &tm.tm_hour, + &tm.tm_min, + &num_chars) >= 5) + { + matched = TRUE; + off_hr = 0; + off_min = 0; + } + + if (matched) { + errno = 0; + end = ptr + num_chars; + tm.tm_mon--; + if (tm.tm_year > 1900) tm.tm_year -= 1900; + if (sign == '-') off_hr = -off_hr; + } + } + else 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) + { + errno = 0; + end = ptr + num_chars; + tm.tm_mon--; + if (tm.tm_year > 1900) tm.tm_year -= 1900; + } + } + 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); + tm.tm_year = tm_now->tm_year; + tm.tm_mon = tm_now->tm_mon; + tm.tm_mday = tm_now->tm_mday; + end = ptr + num_chars; + errno = 0; + + } + } + else if (encoding & ENC_RFC_822 || encoding & ENC_RFC_1123) { + if (encoding & ENC_RFC_822) { + /* this will unfortunately match ENC_RFC_1123 style + strings too, partially - probably need to do this the long way */ + end = strptime(ptr, "%a, %d %b %y %H:%M:%S", &tm); + if (!end) end = strptime(ptr, "%a, %d %b %y %H:%M", &tm); + if (!end) end = strptime(ptr, "%d %b %y %H:%M:%S", &tm); + if (!end) end = strptime(ptr, "%d %b %y %H:%M", &tm); + } + else if (encoding & ENC_RFC_1123) { + end = strptime(ptr, "%a, %d %b %Y %H:%M:%S", &tm); + if (!end) end = strptime(ptr, "%a, %d %b %Y %H:%M", &tm); + if (!end) end = strptime(ptr, "%d %b %Y %H:%M:%S", &tm); + if (!end) end = strptime(ptr, "%d %b %Y %H:%M", &tm); + } + if (end) { + errno = 0; + if (*end == ' ') end++; + if (g_ascii_strncasecmp(end, "UT", 2) == 0) + { + end += 2; + } + else if (g_ascii_strncasecmp(end, "GMT", 3) == 0) + { + end += 3; + } + else if (sscanf(end, "%c%2d%2d%n", + &sign, + &off_hr, + &off_min, + &num_chars) < 3) + { + errno = ERANGE; + } + if (sign == '-') off_hr = -off_hr; + } + } + } + + if (errno == 0) { + ns->secs = mktime_utc (&tm); + if (off_hr > 0) + ns->secs += (off_hr * 3600) + (off_min * 60); + else if (off_hr < 0) + ns->secs -= ((-off_hr) * 3600) + (off_min * 60); + retval = ns; + if (endoff) + *endoff = (gint)(offset + (end - begin)); + } + + return retval; +} + /* Fetch an IPv4 address, in network byte order. * We do *not* convert them to host byte order; we leave them in * network byte order. */ @@ -1899,6 +2161,35 @@ tvb_get_utf_8_string(wmem_allocator_t *scope, tvbuff_t *tvb, const gint offset, /* * Given a tvbuff, an offset, and a length, treat the string of bytes + * referred to by them as a raw string, and return a pointer to that + * string. This means a null is appended at the end, but no replacement + * checking is done otherwise. Currently tvb_get_utf_8_string() does + * not replace either, but it might in the future. + * + * Also, this one allows a length of -1 to mean get all, but does not + * allow a negative offset. + */ +static inline guint8 * +tvb_get_raw_string(wmem_allocator_t *scope, tvbuff_t *tvb, const gint offset, const gint length) +{ + guint8 *strbuf; + gint abs_length = length; + + DISSECTOR_ASSERT(offset >= 0); + DISSECTOR_ASSERT(abs_length >= -1); + + if (abs_length < 0) + abs_length = tvb->length - offset; + + tvb_ensure_bytes_exist(tvb, offset, abs_length); + strbuf = (guint8 *)wmem_alloc(scope, abs_length + 1); + tvb_memcpy(tvb, strbuf, offset, abs_length); + strbuf[abs_length] = '\0'; + return strbuf; +} + +/* + * Given a tvbuff, an offset, and a length, treat the string of bytes * referred to by them as an ISO 8859/1 string, with all bytes with the * high-order bit set being invalid, and return a pointer to a UTF-8 * string. diff --git a/epan/tvbuff.h b/epan/tvbuff.h index a365276ee1..14789d318b 100644 --- a/epan/tvbuff.h +++ b/epan/tvbuff.h @@ -55,6 +55,7 @@ struct tvbuff; typedef struct tvbuff tvbuff_t; struct e_in6_addr; /* ipv6-utils.h */ +struct nstime_t; /* nstime.h */ /** @defgroup tvbuff Testy, Virtual(-izable) Buffers * @@ -335,6 +336,37 @@ WS_DLL_PUBLIC gdouble tvb_get_letohieee_double(tvbuff_t *tvb, #error "Unsupported byte order" #endif + +/* Fetch a time value from an ASCII-style string in the tvb. + * + * @param[in] offset The beginning offset in the tvb (cannot be negative) + * @param[in] length The field's length in the tvb (or -1 for remaining) + * @param[in] encoding The ENC_* that defines the format (e.g., ENC_ISO_8601_DATE_TIME) + * @param[in,out] ns The pre-allocated nstime_t that will be set to the decoded value + * @param[out] endoff if not NULL, should point to a gint that this + * routine will then set to be the offset to the character after + * the last character used in the conversion. This is useful because + * they may not consume the whole section. + * + * @return a pointer to the nstime_t passed-in, or NULL on failure; if no + * valid conversion could be performed, *endoff is set to 0, and errno will be + * EDOM or ERANGE, and the nstime_t* passed-in will be cleared. + * + * @note The conversion ignores leading spaces, and will succeed even if it does + * not consume the entire string. If you care about such things, always compare + * the *endoff to where you expect it to be (namely, offset+length). + * + * This routine will not throw an error unless the passed-in arguments are + * invalid (e.g., offset is beyond the tvb's length). + * + * @warning This only works for string encodings which encode ASCII characters in + * a single byte: ENC_ASCII, ENC_UTF_8, ENC_ISO_8859_*, etc. It does NOT work + * for purely multi-byte encodings such as ENC_UTF_16, ENC_UCS_*, etc. + */ +WS_DLL_PUBLIC +struct nstime_t* tvb_get_string_time(tvbuff_t *tvb, const gint offset, const gint length, + const guint encoding, struct nstime_t* ns, gint *endoff); + /** * Fetch an IPv4 address, in network byte order. * We do *not* convert it to host byte order; we leave it in diff --git a/epan/wslua/wslua_tree.c b/epan/wslua/wslua_tree.c index 5eee9e561e..356054f518 100644 --- a/epan/wslua/wslua_tree.c +++ b/epan/wslua/wslua_tree.c @@ -32,6 +32,8 @@ /* WSLUA_MODULE Tree Adding information to the dissection tree */ #include "wslua.h" +#include <epan/exceptions.h> +#include <epan/show_exception.h> static gint wslua_ett = -1; @@ -50,19 +52,113 @@ WSLUA_CLASS_DEFINE(TreeItem,FAIL_ON_NULL_OR_EXPIRED("TreeItem"),NOP); /* `TreeItem`s represent information in the packet-details pane. A root `TreeItem` is passed to dissectors as the third argument. */ +/* the following is used by TreeItem_add_packet_field() - this can THROW errors */ +static proto_item * +try_add_packet_field(lua_State *L, TreeItem tree_item, TvbRange tvbr, const int hfid, + const ftenum_t type, const guint encoding, gint *ret_err) +{ + gint err = 0; + proto_item* item = NULL; + gint endoff = 0; + + switch(type) { + case FT_ABSOLUTE_TIME: + case FT_RELATIVE_TIME: + { + /* nstime_t will be g_free'd by Lua */ + nstime_t *nstime = (nstime_t *) g_malloc0(sizeof(nstime_t)); + item = proto_tree_add_time_item(tree_item->tree, hfid, tvbr->tvb->ws_tvb, + tvbr->offset, tvbr->len, encoding, + nstime, &endoff, &err); + if (err == 0) { + pushNSTime(L,nstime); + lua_pushinteger(L, endoff); + } + } + break; + + /* anything else just needs to be done the old fashioned way */ + default: + item = proto_tree_add_item(tree_item->tree, hfid, tvbr->tvb->ws_tvb, tvbr->offset, tvbr->len, encoding); + lua_pushnil(L); + lua_pushnil(L); + break; + } + + if (ret_err) *ret_err = err; + + return item; +} + WSLUA_METHOD TreeItem_add_packet_field(lua_State *L) { /* - Adds an child item to a given item, returning the child. - tree_item:add_packet_field([proto_field], [tvbrange], [encoding], ...) + Adds a new child tree for the given `ProtoField` object to this tree item, + returning the new child `TreeItem`. + + Unlike `TreeItem:add()` and `TreeItem:add_le()`, the `ProtoField` argument + is not optional, and cannot be a `Proto` object. Instead, this function always + uses the `ProtoField` to determine the type of field to extract from the + passed-in `TvbRange`, highlighting the relevant bytes in the Packet Bytes pane + of the GUI (if there is a GUI), etc. If no `TvbRange` is given, no bytes are + highlighted and the field's value cannot be determined; the `ProtoField` must + have been defined/created not to have a length in such a case, or an error will + occur. For backwards-compatibility reasons the `encoding` argument, however, + must still be given. + + Unlike `TreeItem:add()` and `TreeItem:add_le()`, this function performs both + big-endian and little-endian decoding, by setting the `encoding` argument to + be `ENC_BIG_ENDIAN` or `ENC_LITTLE_ENDIAN`. + + The signature of this function: + @code + tree_item:add_packet_field(proto_field [,tvbrange], encoding, ...) + @endcode + + In Wireshark version 1.11.3, this function was changed to return more than + just the new child `TreeItem`. The child is the first return value, so that + function chaining will still work as before; but it now also returns the value + of the extracted field (i.e., a number, `UInt64`, `Address`, etc.). If the + value could not be extracted from the `TvbRange`, the child `TreeItem` is still + returned, but the second returned value is `nil`. + + Another new feature added to this function in Wireshark version 1.11.3 is the + ability to extract native number `ProtoField`s from string encoding in the + `TvbRange`, for ASCII-based and similar string encodings. For example, a + `ProtoField` of as `ftypes.UINT32` type can be extracted from a `TvbRange` + containing the ASCII string "123", and it will correctly decode the ASCII to + the number `123`, both in the tree as well as for the second return value of + this function. To do so, you must set the `encoding` argument of this function + to the appropriate string `ENC_*` value, bitwise-or'd with the `ENC_STRING` + value (see `init.lua`). `ENC_STRING` is guaranteed to be a unique bit flag, and + thus it can added instead of bitwise-or'ed as well. Only single-byte ASCII digit + string encoding types can be used for this, such as `ENC_ASCII` and `ENC_UTF_8`. + + For example, assuming the `Tvb` named "`tvb`" contains the string "123": + @code + -- this is done earlier in the script + local myfield = ProtoField.new("Transaction ID", "myproto.trans_id", ftypes.UINT16) + + -- this is done inside a dissector, post-dissector, or heuristic function + -- child will be the created child tree, and value will be the number 123 or nil on failure + local child, value = tree:add_packet_field(myfield, tvb:range(0,3), ENC_UTF_8 + ENC_STRING) + @encode + */ +#define WSLUA_ARG_TreeItem_add_packet_field_PROTOFIELD 2 /* The ProtoField field object to add to the tree. */ +#define WSLUA_OPTARG_TreeItem_add_packet_field_TVBRANGE 3 /* The `TvbRange` of bytes in the packet this tree item covers/represents. */ +#define WSLUA_ARG_TreeItem_add_packet_field_ENCODING 4 /* The field's encoding in the `TvbRange`. */ +#define WSLUA_OPTARG_TreeItem_add_packet_field_LABEL 5 /* One or more strings to append to the created `TreeItem`. */ TvbRange tvbr; ProtoField field; int hfid; int ett; ftenum_t type; - TreeItem tree_item = shiftTreeItem(L,1); + TreeItem tree_item = shiftTreeItem(L,1); guint encoding; proto_item* item = NULL; + int nargs; + gint err = 0; + const char *volatile error = NULL; if (!tree_item) { return luaL_error(L,"not a TreeItem!"); @@ -92,6 +188,14 @@ WSLUA_METHOD TreeItem_add_packet_field(lua_State *L) { encoding = wslua_checkguint(L,1); lua_remove(L,1); + + /* get the number of additional args before we add more to the stack */ + nargs = lua_gettop(L); + + /* XXX: why is this being done? If the length was -1, FT_STRINGZ figures out + * the right length in tvb_get_stringz_enc(); if it was 0, it should remain zero; + * if it was greater than zero, then it's the length the caller wanted. + */ if (type == FT_STRINGZ) { switch (encoding & ENC_CHARENCODING_MASK) { @@ -105,13 +209,29 @@ WSLUA_METHOD TreeItem_add_packet_field(lua_State *L) { break; } } - item = proto_tree_add_item(tree_item->tree, hfid, tvbr->tvb->ws_tvb, tvbr->offset, tvbr->len, encoding); - while(lua_gettop(L)) { + TRY { + + item = try_add_packet_field(L, tree_item, tvbr, hfid, type, encoding, &err); + + } CATCH_ALL { + show_exception(tvbr->tvb->ws_tvb, lua_pinfo, tree_item->tree, EXCEPT_CODE, GET_MESSAGE); + error = "Lua programming error"; + } ENDTRY; + + if (error) { WSLUA_ERROR(TreeItem_add_packet_field,error); } + + if (err != 0) { + lua_pushnil(L); + lua_pushnil(L); + } + + while(nargs) { const gchar* s; s = lua_tostring(L,1); if (s) proto_item_append_text(item, " %s", s); lua_remove(L,1); + nargs--; } tree_item = (TreeItem)g_malloc(sizeof(struct _wslua_treeitem)); @@ -121,7 +241,10 @@ WSLUA_METHOD TreeItem_add_packet_field(lua_State *L) { PUSH_TREEITEM(L,tree_item); - WSLUA_RETURN(1); /* The new child TreeItem. */ + /* move the tree object before the field value */ + lua_insert(L, 1); + + WSLUA_RETURN(3); /* The new child `TreeItem`, the field's extracted value or nil, and offset or nil. */ } static int TreeItem_add_item_any(lua_State *L, gboolean little_endian) { diff --git a/epan/wslua/wslua_tvb.c b/epan/wslua/wslua_tvb.c index bfad06c51e..9746ff77a4 100644 --- a/epan/wslua/wslua_tvb.c +++ b/epan/wslua/wslua_tvb.c @@ -1055,8 +1055,10 @@ WSLUA_METHOD TvbRange_ether(lua_State* L) { WSLUA_METHOD TvbRange_nstime(lua_State* L) { /* Obtain a time_t structure from a `TvbRange`, as an `NSTime` object. */ +#define WSLUA_OPTARG_TvbRange_nstime_ENCODING 2 /* An optional ENC_* encoding value to use */ TvbRange tvbr = checkTvbRange(L,1); NSTime nstime; + const guint encoding = luaL_optint(L, WSLUA_OPTARG_TvbRange_nstime_ENCODING, 0); if ( !(tvbr && tvbr->tvb)) return 0; if (tvbr->tvb->expired) { @@ -1066,21 +1068,41 @@ WSLUA_METHOD TvbRange_nstime(lua_State* L) { nstime = g_new(nstime_t,1); - if (tvbr->len == 4) { - nstime->secs = tvb_get_ntohl(tvbr->tvb->ws_tvb, tvbr->offset); - nstime->nsecs = 0; - } else if (tvbr->len == 8) { - nstime->secs = tvb_get_ntohl(tvbr->tvb->ws_tvb, tvbr->offset); - nstime->nsecs = tvb_get_ntohl(tvbr->tvb->ws_tvb, tvbr->offset + 4); - } else { - g_free(nstime); - WSLUA_ERROR(TvbRange_nstime,"The range must be 4 or 8 bytes long"); - return 0; + if (encoding == 0) { + if (tvbr->len == 4) { + nstime->secs = tvb_get_ntohl(tvbr->tvb->ws_tvb, tvbr->offset); + nstime->nsecs = 0; + } else if (tvbr->len == 8) { + nstime->secs = tvb_get_ntohl(tvbr->tvb->ws_tvb, tvbr->offset); + nstime->nsecs = tvb_get_ntohl(tvbr->tvb->ws_tvb, tvbr->offset + 4); + } else { + g_free(nstime); + WSLUA_ERROR(TvbRange_nstime,"The range must be 4 or 8 bytes long"); + return 0; + } + pushNSTime(L, nstime); + lua_pushinteger(L, tvbr->len); + } + else if (encoding & ~ENC_STR_TIME_MASK) { + WSLUA_OPTARG_ERROR(TvbRange_nstime, ENCODING, "invalid encoding value"); + } + else { + gint endoff = 0; + nstime_t *retval = tvb_get_string_time(tvbr->tvb->ws_tvb, tvbr->offset, tvbr->len, + encoding, nstime, &endoff); + if (!retval || endoff == 0) { + g_free(nstime); + /* push nil nstime and offset */ + lua_pushnil(L); + lua_pushnil(L); + } + else { + pushNSTime(L, nstime); + lua_pushinteger(L, endoff); + } } - pushNSTime(L, nstime); - - WSLUA_RETURN(1); /* The `NSTime` object. */ + WSLUA_RETURN(2); /* The `NSTime` object and number of bytes used, or nil on failure. */ } WSLUA_METHOD TvbRange_le_nstime(lua_State* L) { |