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 | |
parent | 92b501303bdeb163cec55825da8b683138517e4d (diff) |
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>
-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 | ||||
-rw-r--r-- | test/lua/tvb.lua | 582 | ||||
-rwxr-xr-x | test/suite-wslua.sh | 17 | ||||
-rwxr-xr-x | tools/checkAPIs.pl | 2 | ||||
-rw-r--r-- | wsutil/nstime.h | 2 |
10 files changed, 1400 insertions, 159 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) { diff --git a/test/lua/tvb.lua b/test/lua/tvb.lua new file mode 100644 index 0000000000..52f271b9da --- /dev/null +++ b/test/lua/tvb.lua @@ -0,0 +1,582 @@ +---------------------------------------- +-- script-name: tvb.lua +-- This tests the Tvb/TvbRange and proto_add_XXX_item API. +---------------------------------------- + +------------- general test helper funcs ------------ +local FRAME = "frame" +local OTHER = "other" + +local total_tests = 0 +local function getTotal() + return total_tests +end + + +local packet_counts = {} +local function incPktCount(name) + if not packet_counts[name] then + packet_counts[name] = 1 + else + packet_counts[name] = packet_counts[name] + 1 + end +end +local function getPktCount(name) + return packet_counts[name] or 0 +end + +local passed = {} +local function setPassed(name) + if not passed[name] then + passed[name] = 1 + else + passed[name] = passed[name] + 1 + end + total_tests = total_tests + 1 +end + +local fail_count = 0 +local function setFailed(name) + fail_count = fail_count + 1 + total_tests = total_tests + 1 +end + +-- expected number of runs per type +-- +-- CHANGE THIS TO MATCH HOW MANY TESTS THERE ARE +-- +local taptests = { [FRAME]=4, [OTHER]=247 } + +local function getResults() + print("\n-----------------------------\n") + for k,v in pairs(taptests) do + -- each frame run executes the same test again, so multiply by #frames + if k ~= "frame" and v ~= 0 then v = (v * taptests.frame) end + + if v ~= 0 and passed[k] ~= v then + print("Something didn't run or ran too much... tests failed!") + print("Dissector type " .. k .. + " expected: " .. v .. + " (" .. ( v / taptests.frame) .. ")" .. + ", but got: " .. tostring(passed[k]) .. + " (" .. (tonumber(passed[k] or 0) / taptests.frame) .. ")" ) + return false + end + end + print("All tests passed!\n\n") + return true +end + + +local function testing(type,...) + print("\n-------- Testing " .. tostring(...) .. + " ---- for packet # " .. getPktCount(type) .. + " --------\n") +end + +local function execute(type,name, ...) + io.stdout:write("test --> "..name.."-"..getTotal().."-"..getPktCount(type).."...") + local results = { ... } + if #results > 0 and results[1] == true then + setPassed(type) + io.stdout:write("passed\n") + return true + else + setFailed(type) + io.stdout:write("failed!\n") + if #results > 1 then + print("Got the following error: '" .. tostring(results[2]) .. "'") + end + error(name.." test failed!") + end +end + +--------- +-- the following are so we can use pcall (which needs a function to call) +local function callFunc(func,...) + func(...) +end + +local function callObjFuncGetter(vart,varn,tobj,name,...) + vart[varn] = tobj[name](...) +end + +local function setValue(tobj,name,value) + tobj[name] = value +end + +local function getValue(tobj,name) + local foo = tobj[name] +end + +------------- test script ------------ + +---------------------------------- +-- modify original test function for now, kinda sorta +local orig_execute = execute +execute = function (...) + return orig_execute(OTHER,...) +end + +---------------------------------------- +-- creates a Proto object for our testing +local test_proto = Proto("test","Test Protocol") + +local numinits = 0 +function test_proto.init() + numinits = numinits + 1 + if numinits == 2 then + getResults() + end +end + + +---------------------------------------- +-- a table of all of our Protocol's fields and test input and expected output +local testfield = +{ + basic = + { + STRING = ProtoField.string ("test.basic.string", "Basic string"), + BOOLEAN = ProtoField.bool ("test.basic.boolean", "Basic boolean", 16, {"yes","no"}, 0x0001), + UINT16 = ProtoField.uint16 ("test.basic.uint16", "Basic uint16") + }, + + time = + { + ABSOLUTE_LOCAL = ProtoField.absolute_time("test.time.absolute.local","Time absolute local"), + ABSOLUTE_UTC = ProtoField.absolute_time("test.time.absolute.utc", "Time absolute utc", 1001), + }, + +} + +-- create a flat array table of the above that can be registered +local pfields = {} +for _,t in pairs(testfield) do + for k,v in pairs(t) do + pfields[#pfields+1] = v + end +end + +-- register them +test_proto.fields = pfields + +print("test_proto ProtoFields registered") + + +local getfield = +{ + basic = + { + STRING = Field.new ("test.basic.string"), + BOOLEAN = Field.new ("test.basic.boolean"), + UINT16 = Field.new ("test.basic.uint16") + }, + + time = + { + ABSOLUTE_LOCAL = Field.new ("test.time.absolute.local"), + ABSOLUTE_UTC = Field.new ("test.time.absolute.utc"), + }, + +} + +local function addMatchFields(match_fields, ... ) + match_fields[#match_fields + 1] = { ... } +end + +local function getFieldInfos(name) + local base, field = name:match("([^.]+)%.(.+)") + if not base or not field then + error("failed to get base.field from '" .. name .. "'") + end + local t = { getfield[base][field]() } + return t +end + +local function verifyFields(name, match_fields) + local finfos = getFieldInfos(name) + + execute ("verify-fields-size-" .. name, #finfos == #match_fields, + "#finfos=" .. #finfos .. ", #match_fields=" .. #match_fields) + + for i, t in ipairs(match_fields) do + if type(t) ~= 'table' then + error("verifyFields didn't get a table inside the matches table") + end + if #t ~= 1 then + error("verifyFields matches table's table is not size 1") + end + local result = finfos[i]() + local value = t[1] + print( + name .. " got:", + "\n\tfinfos [" .. i .. "]='" .. tostring( result ) .. "'", + "\n\tmatches[" .. i .. "]='" .. tostring( value ) .. "'" + ) + execute ( "verify-fields-value-" .. name .. "-" .. i, result == value ) + end +end + + +local function addMatchValues(match_values, ... ) + match_values[#match_values + 1] = { ... } +end + +local function addMatchFieldValues(match_fields, match_values, match_both, ...) + addMatchFields(match_fields, match_both) + addMatchValues(match_values, match_both, ...) +end + +local result_values = {} +local function resetResults() + result_values = {} +end + +local function treeAddPField(...) + local t = { pcall ( TreeItem.add_packet_field, ... ) } + if t[1] == nil then + return nil, t[2] + end + -- it gives back a TreeItem, then the results + if typeof(t[2]) ~= 'TreeItem' then + return nil, "did not get a TreeItem returned from TreeItem.add_packet_field, ".. + "got a '" .. typeof(t[2]) .."'" + end + + if #t ~= 4 then + return nil, "did not get 3 return values from TreeItem.add_packet_field" + end + + result_values[#result_values + 1] = { t[3], t[4] } + + return true +end + +local function verifyResults(name, match_values) + execute ("verify-results-size-" .. name, #result_values == #match_values, + "#result_values=" .. #result_values .. + ", #match_values=" .. #match_values) + + for j, t in ipairs(match_values) do + if type(t) ~= 'table' then + error("verifyResults didn't get a table inside the matches table") + end + for i, match in ipairs(t) do + local r = result_values[j][i] + print( + name .. " got:", + "\n\tresults[" .. j .. "][" .. i .. "]='" .. tostring( r ) .. "'", + "\n\tmatches[" .. j .. "][" .. i .. "]='" .. tostring( match ) .. "'" + ) + local result_type, match_type + if type(match) == 'userdata' then + match_type = typeof(match) + else + match_type = type(match) + end + if type(r) == 'userdata' then + result_type = typeof(r) + else + result_type = type(r) + end + execute ( "verify-results-type-" .. name .. "-" .. i, result_type == match_type ) + execute ( "verify-results-value-" .. name .. "-" .. i, r == match ) + end + end +end + +-- Compute the difference in seconds between local time and UTC +-- from http://lua-users.org/wiki/TimeZone +local function get_timezone() + local now = os.time() + return os.difftime(now, os.time(os.date("!*t", now))) +end +local timezone = get_timezone() +print ("timezone = " .. timezone) + +---------------------------------------- +-- The following creates the callback function for the dissector. +-- The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object. +function test_proto.dissector(tvbuf,pktinfo,root) + + incPktCount(FRAME) + incPktCount(OTHER) + + testing(OTHER, "Basic") + + local tree = root:add(test_proto, tvbuf:range(0,tvbuf:len())) + + -- create a fake Tvb to use for testing + local teststring = "this is the string for the first test" + local bytearray = ByteArray.new(teststring, true) + local tvb = bytearray:tvb("Basic") + + local function callTreeAdd(tree,...) + tree:add(...) + end + + local string_match_fields = {} + + execute ("basic-string", tree:add(testfield.basic.STRING, tvb:range(0,tvb:len())) ~= nil ) + addMatchFields(string_match_fields, teststring) + + execute ("basic-string", pcall (callTreeAdd, tree, testfield.basic.STRING, tvb:range() ) ) + addMatchFields(string_match_fields, teststring) + + verifyFields("basic.STRING", string_match_fields) + + tvb = ByteArray.new("00FF 0001 8000"):tvb("Basic") + local bool_match_fields = {} + + execute ("basic-boolean", pcall (callTreeAdd, tree, testfield.basic.BOOLEAN, tvb:range(0,2)) ) + addMatchFields(bool_match_fields, true) + + execute ("basic-boolean", pcall (callTreeAdd, tree, testfield.basic.BOOLEAN, tvb:range(2,2)) ) + addMatchFields(bool_match_fields, true) + + execute ("basic-boolean", pcall (callTreeAdd, tree, testfield.basic.BOOLEAN, tvb:range(4,2)) ) + addMatchFields(bool_match_fields, false) + + verifyFields("basic.BOOLEAN", bool_match_fields ) + + local uint16_match_fields = {} + + execute ("basic-uint16", pcall (callTreeAdd, tree, testfield.basic.UINT16, tvb:range(0,2)) ) + addMatchFields(uint16_match_fields, 255) + + execute ("basic-uint16", pcall (callTreeAdd, tree, testfield.basic.UINT16, tvb:range(2,2)) ) + addMatchFields(uint16_match_fields, 1) + + execute ("basic-uint16", pcall (callTreeAdd, tree, testfield.basic.UINT16, tvb:range(4,2)) ) + addMatchFields(uint16_match_fields, 32768) + + verifyFields("basic.UINT16", uint16_match_fields) + + local function callTreeAddLE(tree,...) + tree:add_le(...) + end + + execute ("basic-uint16-le", pcall (callTreeAddLE, tree, testfield.basic.UINT16, tvb:range(0,2)) ) + addMatchFields(uint16_match_fields, 65280) + + execute ("basic-uint16-le", pcall (callTreeAddLE, tree, testfield.basic.UINT16, tvb:range(2,2)) ) + addMatchFields(uint16_match_fields, 256) + + execute ("basic-uint16-le", pcall (callTreeAddLE, tree, testfield.basic.UINT16, tvb:range(4,2)) ) + addMatchFields(uint16_match_fields, 128) + + verifyFields("basic.UINT16", uint16_match_fields) + + +---------------------------------------- + testing(OTHER, "tree:add Time") + + tvb = ByteArray.new("00000000 00000000 0000FF0F 00FF000F"):tvb("Time") + local ALOCAL = testfield.time.ABSOLUTE_LOCAL + local alocal_match_fields = {} + + execute ("time-local", pcall (callTreeAdd, tree, ALOCAL, tvb:range(0,8)) ) + addMatchFields(alocal_match_fields, NSTime()) + + execute ("time-local", pcall (callTreeAdd, tree, ALOCAL, tvb:range(8,8)) ) + addMatchFields(alocal_match_fields, NSTime( 0x0000FF0F, 0x00FF000F) ) + + execute ("time-local-le", pcall (callTreeAddLE, tree, ALOCAL, tvb:range(0,8)) ) + addMatchFields(alocal_match_fields, NSTime()) + + execute ("time-local-le", pcall (callTreeAddLE, tree, ALOCAL, tvb:range(8,8)) ) + addMatchFields(alocal_match_fields, NSTime( 0x0FFF0000, 0x0F00FF00 ) ) + + verifyFields("time.ABSOLUTE_LOCAL", alocal_match_fields) + + local AUTC = testfield.time.ABSOLUTE_UTC + local autc_match_fields = {} + + execute ("time-utc", pcall (callTreeAdd, tree, AUTC, tvb:range(0,8)) ) + addMatchFields(autc_match_fields, NSTime()) + + execute ("time-utc", pcall (callTreeAdd, tree, AUTC, tvb:range(8,8)) ) + addMatchFields(autc_match_fields, NSTime( 0x0000FF0F, 0x00FF000F) ) + + execute ("time-utc-le", pcall (callTreeAddLE, tree, AUTC, tvb:range(0,8)) ) + addMatchFields(autc_match_fields, NSTime()) + + execute ("time-utc-le", pcall (callTreeAddLE, tree, AUTC, tvb:range(8,8)) ) + addMatchFields(autc_match_fields, NSTime( 0x0FFF0000, 0x0F00FF00 ) ) + + verifyFields("time.ABSOLUTE_UTC", autc_match_fields ) + +---------------------------------------- + testing(OTHER, "tree:add_packet_field Time bytes") + + resetResults() + local autc_match_values = {} + + -- something to make this easier to read + local function addMatch(...) + addMatchFieldValues(autc_match_fields, autc_match_values, ...) + end + + -- tree:add_packet_field(ALOCAL, tvb:range(0,8), ENC_BIG_ENDIAN) + execute ("add_pfield-time-bytes-local", treeAddPField ( tree, AUTC, tvb:range(0,8), ENC_BIG_ENDIAN) ) + addMatch( NSTime(), 8) + + execute ("add_pfield-time-bytes-local", treeAddPField ( tree, AUTC, tvb:range(8,8), ENC_BIG_ENDIAN) ) + addMatch( NSTime( 0x0000FF0F, 0x00FF000F), 8) + + execute ("add_pfield-time-bytes-local-le", treeAddPField ( tree, AUTC, tvb:range(0,8), ENC_LITTLE_ENDIAN) ) + addMatch( NSTime(), 8) + + execute ("add_pfield-time-bytes-local-le", treeAddPField ( tree, AUTC, tvb:range(8,8), ENC_LITTLE_ENDIAN) ) + addMatch( NSTime( 0x0FFF0000, 0x0F00FF00 ), 8) + + verifyFields("time.ABSOLUTE_UTC", autc_match_fields) + + verifyResults("add_pfield-time-bytes-local", autc_match_values) + +---------------------------------------- + testing(OTHER, "tree:add_packet_field Time string ENC_ISO_8601_DATE_TIME") + + resetResults() + autc_match_values = {} + + local datetimestring1 = "2013-03-01T22:14:48+00:00" -- this is 1362176088 seconds epoch time + local tvb1 = ByteArray.new(datetimestring1, true):tvb("Date_Time string 1") + local datetimestring2 = " 2013-03-01T17:14:48+05:00" -- this is 1362176088 seconds epoch time + local tvb2 = ByteArray.new(datetimestring2 .. " foobar", true):tvb("Date_Time string 2") + local datetimestring3 = " 2013-03-01T16:44+05:30" -- this is 1362176040 seconds epoch time + local tvb3 = ByteArray.new(datetimestring3, true):tvb("Date_Time string 3") + local datetimestring4 = "2013-03-02T01:44:00-03:30" -- this is 1362176040 seconds epoch time + local tvb4 = ByteArray.new(datetimestring4, true):tvb("Date_Time string 4") + local datetimestring5 = "2013-03-01T22:14:48Z" -- this is 1362176088 seconds epoch time + local tvb5 = ByteArray.new(datetimestring5, true):tvb("Date_Time string 5") + local datetimestring6 = "2013-03-01T22:14Z" -- this is 1362176040 seconds epoch time + local tvb6 = ByteArray.new(datetimestring6, true):tvb("Date_Time string 6") + + execute ("add_pfield-datetime-local", treeAddPField ( tree, AUTC, tvb1:range(), ENC_ISO_8601_DATE_TIME) ) + addMatch( NSTime( 1362176088, 0), string.len(datetimestring1)) + + execute ("add_pfield-datetime-local", treeAddPField ( tree, AUTC, tvb2:range(), ENC_ISO_8601_DATE_TIME) ) + addMatch( NSTime( 1362176088, 0), string.len(datetimestring2)) + + execute ("add_pfield-datetime-local", treeAddPField ( tree, AUTC, tvb3:range(), ENC_ISO_8601_DATE_TIME) ) + addMatch( NSTime( 1362176040, 0), string.len(datetimestring3)) + + execute ("add_pfield-datetime-local", treeAddPField ( tree, AUTC, tvb4:range(), ENC_ISO_8601_DATE_TIME) ) + addMatch( NSTime( 1362176040, 0), string.len(datetimestring4)) + + execute ("add_pfield-datetime-local", treeAddPField ( tree, AUTC, tvb5:range(), ENC_ISO_8601_DATE_TIME) ) + addMatch( NSTime( 1362176088, 0), string.len(datetimestring5)) + + execute ("add_pfield-datetime-local", treeAddPField ( tree, AUTC, tvb6:range(), ENC_ISO_8601_DATE_TIME) ) + addMatch( NSTime( 1362176040, 0), string.len(datetimestring6)) + + verifyFields("time.ABSOLUTE_UTC", autc_match_fields) + + verifyResults("add_pfield-datetime-local", autc_match_values) + +---------------------------------------- + testing(OTHER, "tree:add_packet_field Time string ENC_ISO_8601_DATE") + + resetResults() + autc_match_values = {} + + local datestring1 = "2013-03-01" -- this is 1362096000 seconds epoch time + local d_tvb1 = ByteArray.new(datestring1, true):tvb("Date string 1") + local datestring2 = " 2013-03-01" -- this is 1362096000 seconds epoch time + local d_tvb2 = ByteArray.new(datestring2 .. " foobar", true):tvb("Date string 2") + + execute ("add_pfield-date-local", treeAddPField ( tree, AUTC, d_tvb1:range(), ENC_ISO_8601_DATE) ) + addMatch( NSTime( 1362096000, 0), string.len(datestring1)) + + execute ("add_pfield-date-local", treeAddPField ( tree, AUTC, d_tvb2:range(), ENC_ISO_8601_DATE) ) + addMatch( NSTime( 1362096000, 0), string.len(datestring2)) + + verifyFields("time.ABSOLUTE_UTC", autc_match_fields) + + verifyResults("add_pfield-date-local", autc_match_values) + +---------------------------------------- + testing(OTHER, "tree:add_packet_field Time string ENC_ISO_8601_TIME") + + resetResults() + autc_match_values = {} + + local timestring1 = "22:14:48" -- this is 80088 seconds + local t_tvb1 = ByteArray.new(timestring1, true):tvb("Time string 1") + local timestring2 = " 22:14:48" -- this is 80088 seconds + local t_tvb2 = ByteArray.new(timestring2 .. " foobar", true):tvb("Time string 2") + + local now = os.date("!*t") + now.hour = 22 + now.min = 14 + now.sec = 48 + local timebase = os.time( now ) + timebase = timebase + timezone + print ("timebase = " .. tostring(timebase) .. ", timezone=" .. timezone) + + execute ("add_pfield-time-local", treeAddPField ( tree, AUTC, t_tvb1:range(), ENC_ISO_8601_TIME) ) + addMatch( NSTime( timebase, 0), string.len(timestring1)) + + execute ("add_pfield-time-local", treeAddPField ( tree, AUTC, t_tvb2:range(), ENC_ISO_8601_TIME) ) + addMatch( NSTime( timebase, 0), string.len(timestring2)) + + verifyFields("time.ABSOLUTE_UTC", autc_match_fields) + + verifyResults("add_pfield-time-local", autc_match_values) + +---------------------------------------- + testing(OTHER, "tree:add_packet_field Time string ENC_RFC_822") + + resetResults() + autc_match_values = {} + + local rfc822string1 = "Fri, 01 Mar 13 22:14:48 GMT" -- this is 1362176088 seconds epoch time + local rfc822_tvb1 = ByteArray.new(rfc822string1, true):tvb("RFC 822 Time string 1") + local rfc822string2 = " Fri, 01 Mar 13 22:14:48 GMT" -- this is 1362176088 seconds epoch time + local rfc822_tvb2 = ByteArray.new(rfc822string2 .. " foobar", true):tvb("RFC 822 Time string 2") + + execute ("add_pfield-time-local", treeAddPField ( tree, AUTC, rfc822_tvb1:range(), ENC_RFC_822) ) + addMatch( NSTime( 1362176088, 0), string.len(rfc822string1)) + + execute ("add_pfield-time-local", treeAddPField ( tree, AUTC, rfc822_tvb2:range(), ENC_RFC_822) ) + addMatch( NSTime( 1362176088, 0), string.len(rfc822string2)) + + verifyFields("time.ABSOLUTE_UTC", autc_match_fields) + + verifyResults("add_pfield-rfc822-local", autc_match_values) + +---------------------------------------- + testing(OTHER, "tree:add_packet_field Time string ENC_RFC_1123") + + resetResults() + autc_match_values = {} + + local rfc1123string1 = "Fri, 01 Mar 2013 22:14:48 GMT" -- this is 1362176088 seconds epoch time + local rfc1123_tvb1 = ByteArray.new(rfc1123string1, true):tvb("RFC 1123 Time string 1") + local rfc1123string2 = " Fri, 01 Mar 2013 22:14:48 GMT" -- this is 1362176088 seconds epoch time + local rfc1123_tvb2 = ByteArray.new(rfc1123string2 .. " foobar", true):tvb("RFC 1123 Time string 2") + + execute ("add_pfield-time-local", treeAddPField ( tree, AUTC, rfc1123_tvb1:range(), ENC_RFC_1123) ) + addMatch( NSTime( 1362176088, 0), string.len(rfc1123string1)) + + execute ("add_pfield-time-local", treeAddPField ( tree, AUTC, rfc1123_tvb2:range(), ENC_RFC_1123) ) + addMatch( NSTime( 1362176088, 0), string.len(rfc1123string2)) + + verifyFields("time.ABSOLUTE_UTC", autc_match_fields) + + verifyResults("add_pfield-rfc1123-local", autc_match_values) + +---------------------------------------- + + setPassed(FRAME) +end + +---------------------------------------- +-- we want to have our protocol dissection invoked for a specific UDP port, +-- so get the udp dissector table and add our protocol to it +DissectorTable.get("udp.port"):add(65333, test_proto) +DissectorTable.get("udp.port"):add(65346, test_proto) + +print("test_proto dissector registered") diff --git a/test/suite-wslua.sh b/test/suite-wslua.sh index 5afdc9a516..fdeb5f0f2f 100755 --- a/test/suite-wslua.sh +++ b/test/suite-wslua.sh @@ -364,6 +364,22 @@ wslua_step_struct_test() { fi } +wslua_step_tvb_test() { + if [ $HAVE_LUA -ne 0 ]; then + test_step_skipped + return + fi + + # Tshark catches lua script failures, so we have to parse the output. + $TSHARK -r $CAPTURE_DIR/dns_port.pcap -X lua_script:$TESTS_DIR/lua/tvb.lua > testout.txt 2>&1 + if grep -q "All tests passed!" testout.txt; then + test_step_ok + else + cat testout.txt + test_step_failed "didn't find pass marker" + fi +} + wslua_cleanup_step() { rm -f ./testout.txt rm -f ./testin.txt @@ -385,6 +401,7 @@ wslua_suite() { test_step_add "wslua proto/protofield" wslua_step_proto_test test_step_add "wslua script arguments" wslua_step_args_test test_step_add "wslua struct" wslua_step_struct_test + test_step_add "wslua tvb" wslua_step_tvb_test } # # Editor modelines - http://www.wireshark.org/tools/modelines.html diff --git a/tools/checkAPIs.pl b/tools/checkAPIs.pl index 17750194d8..1b87dd6cdb 100755 --- a/tools/checkAPIs.pl +++ b/tools/checkAPIs.pl @@ -1625,7 +1625,7 @@ sub check_proto_tree_add_XXX_encoding($$) $args =~ s/\(.*\)//sg; if ($args =~ /,\s*ENC_/xos) { - if (!($func =~ /proto_tree_add_(item|bitmask|bits_item|bits_ret_val)/xos) + if (!($func =~ /proto_tree_add_(time|item|bitmask|bits_item|bits_ret_val)/xos) ) { print STDERR "Error: ".$filename." uses $func with ENC_*.\n"; $errorCount++; diff --git a/wsutil/nstime.h b/wsutil/nstime.h index 8def5de250..3222ed18ba 100644 --- a/wsutil/nstime.h +++ b/wsutil/nstime.h @@ -36,7 +36,7 @@ extern "C" { */ /** data structure to hold time values with nanosecond resolution*/ -typedef struct { +typedef struct nstime_t { time_t secs; int nsecs; } nstime_t; |