From 7f074364b62b0e7a6a8660e48c9803ebcaa1c8fc Mon Sep 17 00:00:00 2001 From: Hadriel Kaplan Date: Thu, 20 Feb 2014 21:12:25 -0500 Subject: [PATCH] Fix bug 9790: Lua: wslua allows duplicate field registration As discussed in bug 3513 and 9709, one can register more than one new ProtoFields for the same field name. Of course C-code can do that too, and does a LOT apparently, but if they're not similar ftypes then things can get scrweed up in display filters. So this change prevents duplicate field registration of dissimilar ftypes. The similarity is based on the discussion on the mailing list, and the listing in README.developer has been updated to refelect that as well. Also, this change adds a testscript for Proto/ProtoFields. Change-Id: I43bd323f785245941a21289647332a19adec2a9d Reviewed-on: https://code.wireshark.org/review/285 Reviewed-by: Evan Huus --- doc/README.dissector | 14 +- epan/ftypes/ftypes.c | 63 +++++ epan/ftypes/ftypes.h | 5 + epan/wslua/wslua_proto.c | 287 ++++++++++---------- test/lua/proto.lua | 566 +++++++++++++++++++++++++++++++++++++++ test/suite-wslua.sh | 28 ++ 6 files changed, 808 insertions(+), 155 deletions(-) create mode 100644 test/lua/proto.lua diff --git a/doc/README.dissector b/doc/README.dissector index cdd65e64ea..4db96d4623 100644 --- a/doc/README.dissector +++ b/doc/README.dissector @@ -1014,14 +1014,18 @@ the same abbreviation. For instance, the following is valid: This way a filter expression can match a header field, irrespective of the representation of it in the specific protocol context. This is interesting for protocols with variable-width header fields. -Note that the formats used must all belong to the same list as defined below: + +Note that the formats used must all belong to the same group as defined below: - FT_INT8, FT_INT16, FT_INT24 and FT_INT32 -- FT_UINT8, FT_UINT16, FT_UINT24, FT_UINT32, FT_IPXNET and FT_FRAMENUM -- FT_UINT64 and FT_EUI64 +- FT_UINT8, FT_UINT16, FT_UINT24, and FT_UINT32 - FT_STRING, FT_STRINGZ and FT_UINT_STRING - FT_FLOAT and FT_DOUBLE -- FT_BYTES, FT_UINT_BYTES, FT_AX25, FT_ETHER, FT_VINES, FT_OID and FT_REL_OID -- FT_ABSOLUTE_TIME and FT_RELATIVE_TIME +- FT_BYTES and FT_UINT_BYTES +- FT_OID and FT_REL_OID + +Any field not in a grouping above should *NOT* be used in duplicate field +abbrevations. The current code does not prevent it, but someday in the future +it might. The HFILL macro at the end of the struct will set reasonable default values for internally used fields. diff --git a/epan/ftypes/ftypes.c b/epan/ftypes/ftypes.c index 2ea1428746..a1958ad74e 100644 --- a/epan/ftypes/ftypes.c +++ b/epan/ftypes/ftypes.c @@ -69,6 +69,69 @@ ftype_register(enum ftenum ftype, ftype_t *ft) +/* from README.dissector: + Note that the formats used must all belong to the same list as defined below: + - FT_INT8, FT_INT16, FT_INT24 and FT_INT32 + - FT_UINT8, FT_UINT16, FT_UINT24, FT_UINT32, FT_IPXNET and FT_FRAMENUM + - FT_UINT64 and FT_EUI64 + - FT_STRING, FT_STRINGZ and FT_UINT_STRING + - FT_FLOAT and FT_DOUBLE + - FT_BYTES, FT_UINT_BYTES, FT_AX25, FT_ETHER, FT_VINES, FT_OID and FT_REL_OID + - FT_ABSOLUTE_TIME and FT_RELATIVE_TIME +*/ +static enum ftenum +same_ftype(const enum ftenum ftype) +{ + switch (ftype) { + case FT_INT8: + case FT_INT16: + case FT_INT24: + case FT_INT32: + return FT_INT32; + + case FT_UINT8: + case FT_UINT16: + case FT_UINT24: + case FT_UINT32: + return FT_UINT32; + + case FT_STRING: + case FT_STRINGZ: + case FT_UINT_STRING: + return FT_STRING; + + case FT_FLOAT: + case FT_DOUBLE: + return FT_DOUBLE; + + case FT_BYTES: + case FT_UINT_BYTES: + return FT_BYTES; + + case FT_OID: + case FT_REL_OID: + return FT_OID; + + /* XXX: the folowing are unqiue for now */ + case FT_INT64: + case FT_UINT64: + case FT_IPv4: + case FT_IPv6: + + /* everything else is unique */ + default: + return ftype; + } +} + +/* given two types, are they similar - for example can two + * duplicate fields be registered of these two types. */ +gboolean +ftype_similar_types(const enum ftenum ftype_a, const enum ftenum ftype_b) +{ + return (same_ftype(ftype_a) == same_ftype(ftype_b)); +} + /* Returns a string representing the name of the type. Useful * for glossary production. */ const char* diff --git a/epan/ftypes/ftypes.h b/epan/ftypes/ftypes.h index dcfeb0b3f3..a79dcf297c 100644 --- a/epan/ftypes/ftypes.h +++ b/epan/ftypes/ftypes.h @@ -108,6 +108,11 @@ ftypes_initialize(void); /* ---------------- FTYPE ----------------- */ +/* given two types, are they similar - for example can two + * duplicate fields be registered of these two types. */ +gboolean +ftype_similar_types(const enum ftenum ftype_a, const enum ftenum ftype_b); + /* Return a string representing the name of the type */ WS_DLL_PUBLIC const char* diff --git a/epan/wslua/wslua_proto.c b/epan/wslua/wslua_proto.c index 6d91c4f5d5..1764780be7 100644 --- a/epan/wslua/wslua_proto.c +++ b/epan/wslua/wslua_proto.c @@ -663,6 +663,30 @@ static true_false_string* true_false_string_from_table(lua_State* L, int idx) { return ret; } +static const gchar* check_field_name(lua_State* L, const int abbr_idx, const enum ftenum type) { + const gchar* abbr = luaL_checkstring(L,abbr_idx); + const header_field_info* hfinfo = NULL; + + if (!abbr[0]) { + luaL_argerror(L, abbr_idx, "Empty field name abbreviation"); + return NULL; + } + + if (proto_check_field_name(abbr)) { + luaL_argerror(L, abbr_idx, "Invalid char in abbrev"); + return NULL; + } + + hfinfo = proto_registrar_get_byname(abbr); + + if (hfinfo && !ftype_similar_types(type, hfinfo->type)) { + luaL_argerror(L, abbr_idx, "A field of an incompatible ftype with this abbrev already exists"); + return NULL; + } + + return abbr; +} + WSLUA_CONSTRUCTOR ProtoField_new(lua_State* L) { /* Creates a new field to be used in a protocol. */ #define WSLUA_ARG_ProtoField_new_NAME 1 /* Actual name of the field (the string that appears in the tree). */ #define WSLUA_ARG_ProtoField_new_ABBR 2 /* Filter name of the field (the string that is used in filters). */ @@ -680,7 +704,7 @@ WSLUA_CONSTRUCTOR ProtoField_new(lua_State* L) { /* Creates a new field to be us ProtoField f; int nargs = lua_gettop(L); const gchar* name = luaL_checkstring(L,WSLUA_ARG_ProtoField_new_NAME); - const gchar* abbr = luaL_checkstring(L,WSLUA_ARG_ProtoField_new_ABBR); + const gchar* abbr = NULL; enum ftenum type; value_string *vs32 = NULL; val64_string *vs64 = NULL; @@ -695,20 +719,14 @@ WSLUA_CONSTRUCTOR ProtoField_new(lua_State* L) { /* Creates a new field to be us type = get_ftenum(luaL_checkstring(L,WSLUA_ARG_ProtoField_new_TYPE)); } + abbr = check_field_name(L,WSLUA_ARG_ProtoField_new_ABBR,type); + if (lua_isnumber(L, WSLUA_OPTARG_ProtoField_new_BASE)) { base = luaL_optint(L, WSLUA_OPTARG_ProtoField_new_BASE, BASE_NONE); } else { base = string_to_base(luaL_optstring(L, WSLUA_OPTARG_ProtoField_new_BASE, "BASE_NONE")); } - if (!abbr || !abbr[0]) { - WSLUA_ARG_ERROR(ProtoField_new,ABBR,"Missing abbrev"); - } - - if (proto_check_field_name(abbr)) { - WSLUA_ARG_ERROR(ProtoField_new,ABBR,"Invalid char in abbrev"); - } - switch (type) { case FT_FRAMENUM: if (base != BASE_NONE) { @@ -828,7 +846,7 @@ WSLUA_CONSTRUCTOR ProtoField_new(lua_State* L) { /* Creates a new field to be us static int ProtoField_integer(lua_State* L, enum ftenum type) { ProtoField f; - const gchar* abbr = luaL_checkstring(L,1); + const gchar* abbr = check_field_name(L,1,type); const gchar* name = luaL_optstring(L,2,abbr); unsigned base = luaL_optint(L, 3, BASE_DEC); value_string* vs32 = NULL; @@ -859,16 +877,6 @@ static int ProtoField_integer(lua_State* L, enum ftenum type) { return 0; } - if (!abbr || !abbr[0]) { - luaL_argerror(L, 1, "Missing abbrev"); - return 0; - } - - if (proto_check_field_name(abbr)) { - luaL_argerror(L, 1, "Invalid char in abbrev"); - return 0; - } - f = g_new(wslua_field_t,1); f->hfid = -2; @@ -907,93 +915,93 @@ static int ProtoField_integer(lua_State* L, enum ftenum type) { /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_uint16 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_uint16_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_uint16_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_uint16_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_uint16_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_uint16_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_uint16_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_uint24 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_uint24_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_uint24_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_uint24_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_uint24_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_uint24_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_uint24_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_uint32 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_uint32_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_uint32_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_uint32_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_uint32_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_uint32_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_uint32_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_uint64 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_uint64_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_uint64_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_uint64_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_uint64_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_uint64_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_uint64_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_int8 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_int8_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_int8_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_int8_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_int8_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_int8_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_int8_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_int16 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_int16_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_int16_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_int16_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_int16_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_int16_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_int16_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_int24 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_int24_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_int24_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_int24_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_int24_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_int24_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_int24_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_int32 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_int32_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_int32_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_int32_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_int32_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_int32_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_int32_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_int64 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_int64_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_int64_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_int64_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_int64_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_int64_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_int64_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_framenum A frame number (for hyperlinks between frames) */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_BASE One of base.DEC, base.HEX or base.OCT */ -/* WSLUA_OPTARG_Protofield_uint8_VALUESTRING A table containing the text that corresponds to the values */ -/* WSLUA_OPTARG_Protofield_uint8_MASK Integer mask of this field */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_framenum_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_framenum_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_framenum_BASE One of base.DEC, base.HEX or base.OCT */ +/* WSLUA_OPTARG_Protofield_framenum_VALUESTRING A table containing the text that corresponds to the values */ +/* WSLUA_OPTARG_Protofield_framenum_MASK Integer mask of this field */ +/* WSLUA_OPTARG_Protofield_framenum_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ PROTOFIELD_INTEGER(uint8,FT_UINT8) @@ -1010,7 +1018,7 @@ PROTOFIELD_INTEGER(framenum,FT_FRAMENUM) static int ProtoField_boolean(lua_State* L, enum ftenum type) { ProtoField f; - const gchar* abbr = luaL_checkstring(L,1); + const gchar* abbr = check_field_name(L,1,type); const gchar* name = luaL_optstring(L,2,abbr); unsigned base = luaL_optint(L, 3, BASE_NONE); true_false_string* tfs = NULL; @@ -1029,16 +1037,6 @@ static int ProtoField_boolean(lua_State* L, enum ftenum type) { return 0; } - if (!abbr || !abbr[0]) { - luaL_argerror(L,1,"Missing abbrev"); - return 0; - } - - if (proto_check_field_name(abbr)) { - luaL_argerror(L,1,"Invalid char in abbrev"); - return 0; - } - if (lua_gettop(L) > 3 && !lua_isnil(L,4)) { tfs = true_false_string_from_table(L,4); } @@ -1079,26 +1077,21 @@ PROTOFIELD_BOOL(bool,FT_BOOLEAN) static int ProtoField_time(lua_State* L,enum ftenum type) { ProtoField f; - const gchar* abbr = luaL_checkstring(L,1); + const gchar* abbr = NULL; const gchar* name = luaL_optstring(L,2,abbr); unsigned base = luaL_optint(L,3,ABSOLUTE_TIME_LOCAL); - const gchar* blob = luaL_optstring(L,4,NULL); - - if (!abbr || !abbr[0]) { - luaL_argerror(L,1,"Missing abbrev"); - return 0; - } - - if (proto_check_field_name(abbr)) { - luaL_argerror(L,1,"Invalid char in abbrev"); - return 0; - } + const gchar* blob = NULL; if (type == FT_ABSOLUTE_TIME) { + abbr = check_field_name(L,1,type); + blob = luaL_optstring(L,4,NULL); if (base < ABSOLUTE_TIME_LOCAL || base > ABSOLUTE_TIME_DOY_UTC) { luaL_argerror(L, 3, "Base must be either LOCAL, UTC, or DOY_UTC"); return 0; } + } else { + abbr = check_field_name(L,1,type); + blob = luaL_optstring(L,3,NULL); } f = g_new(wslua_field_t,1); @@ -1141,20 +1134,10 @@ PROTOFIELD_TIME(absolute_time,FT_ABSOLUTE_TIME) static int ProtoField_other(lua_State* L,enum ftenum type) { ProtoField f; - const gchar* abbr = luaL_checkstring(L,1); + const gchar* abbr = check_field_name(L,1,type); const gchar* name = luaL_optstring(L,2,abbr); const gchar* blob = luaL_optstring(L,3,NULL); - if (!abbr || !abbr[0]) { - luaL_argerror(L,1,"Missing abbrev"); - return 0; - } - - if (proto_check_field_name(abbr)) { - luaL_argerror(L,1,"Invalid char in abbrev"); - return 0; - } - f = g_new(wslua_field_t,1); f->hfid = -2; @@ -1178,75 +1161,75 @@ static int ProtoField_other(lua_State* L,enum ftenum type) { #define PROTOFIELD_OTHER(lower,FT) static int ProtoField_##lower(lua_State* L) { return ProtoField_other(L,FT); } /* _WSLUA_CONSTRUCTOR_ ProtoField_ipv4 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_ipv4_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_ipv4_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_ipv4_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_ipv6 */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_ipv6_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_ipv6_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_ipv6_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_ether */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_ether_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_ether_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_ether_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_float */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_float_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_float_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_float_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_double */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_double_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_double_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_double_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_string */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_string_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_string_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_string_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_stringz */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_stringz_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_stringz_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_stringz_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_bytes */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_bytes_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_bytes_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_bytes_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_ubytes */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_ubytes_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_ubytes_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_ubytes_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_guid */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_guid_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_guid_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_guid_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_oid */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_oid_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_oid_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_oid_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ /* _WSLUA_CONSTRUCTOR_ ProtoField_bool */ -/* WSLUA_ARG_Protofield_uint8_ABBR Abbreviated name of the field (the string used in filters) */ -/* WSLUA_OPTARG_Protofield_uint8_NAME Actual name of the field (the string that appears in the tree) */ -/* WSLUA_OPTARG_Protofield_uint8_DESC Description of the field */ +/* WSLUA_ARG_Protofield_bool_ABBR Abbreviated name of the field (the string used in filters) */ +/* WSLUA_OPTARG_Protofield_bool_NAME Actual name of the field (the string that appears in the tree) */ +/* WSLUA_OPTARG_Protofield_bool_DESC Description of the field */ /* _WSLUA_RETURNS_ A protofield item to be added to a ProtoFieldArray */ PROTOFIELD_OTHER(ipv4,FT_IPv4) @@ -1355,6 +1338,10 @@ WSLUA_CONSTRUCTOR Proto_new(lua_State* L) { const gchar* name = luaL_checkstring(L,WSLUA_ARG_Proto_new_NAME); const gchar* desc = luaL_checkstring(L,WSLUA_ARG_Proto_new_DESC); + /* TODO: should really make a common function for all of wslua that does checkstring and non-empty at same time */ + if (!name[0] || !desc[0]) + luaL_argerror(L,WSLUA_ARG_Proto_new_NAME,"must not be an empty string"); + if ( name ) { gchar* loname_a; int proto_id; diff --git a/test/lua/proto.lua b/test/lua/proto.lua new file mode 100644 index 0000000000..1ade48bfe9 --- /dev/null +++ b/test/lua/proto.lua @@ -0,0 +1,566 @@ +---------------------------------------- +-- script-name: proto.lua +-- This is based on the dissector.lua example script, which is also used for testing. +-- Unlike that one, this one is purely for testing even more things, notably +-- the Proto/ProtoField 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 +-- note ip only runs 3 times because it gets removed +-- and bootp only runs twice because the filter makes it run +-- once and then it gets replaced with a different one for the second time +local taptests = { [FRAME]=2, [OTHER]=48 } +local function getResults() + print("\n-----------------------------\n") + for k,v in pairs(taptests) do + 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..", but got: "..tostring(passed[k])) + return false + end + end + print("All tests passed!\n\n") + return true +end + + +local function testing(type,...) + print("---- Testing "..type.." ---- "..tostring(...).." for packet # "..getPktCount(type).." ----") +end + +local function test(type,name, ...) + io.stdout:write("test "..type.."-->"..name.."-"..getTotal().."-"..getPktCount(type).."...") + if (...) == true then + setPassed(type) + io.stdout:write("passed\n") + return true + else + setFailed(type) + io.stdout:write("failed!\n") + 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_test = test +test = function (...) + return orig_test(OTHER,...) +end + +---------------------------------------- +-- creates a Proto object, but doesn't register it yet +testing(OTHER,"Proto creation") + +test("Proto.__call", pcall(callFunc,Proto,"foo","Foo Protocol")) +test("Proto.__call", pcall(callFunc,Proto,"foo1","Foo1 Protocol")) +test("Proto.__call", not pcall(callFunc,Proto,"","Bar Protocol")) +test("Proto.__call", not pcall(callFunc,Proto,nil,"Bar Protocol")) +test("Proto.__call", not pcall(callFunc,Proto,"bar","")) +test("Proto.__call", not pcall(callFunc,Proto,"bar",nil)) + + +local dns = Proto("mydns","MyDNS Protocol") + +test("Proto.__tostring", tostring(dns) == "Proto: MYDNS") + +---------------------------------------- +-- multiple ways to do the same thing: create a protocol field (but not register it yet) +-- the abbreviation should always have "." before the specific abbreviation, to avoid collisions +testing(OTHER,"ProtoField creation") + +local pfields = {} -- a table to hold fields, so we can pass them back/forth through pcall() +--- variable -- what dissector.lua did, so we almost match it +local pf_trasaction_id = 1 -- ProtoField.new("Transaction ID", "mydns.trans_id", ftypes.UINT16) +local pf_flags = 2 -- ProtoField.new("Flags", "mydns.flags", ftypes.UINT16, nil, base.HEX) +local pf_num_questions = 3 -- ProtoField.uint16("mydns.num_questions", "Number of Questions") +local pf_num_answers = 4 -- ProtoField.uint16("mydns.num_answers", "Number of Answer RRs") +local pf_num_authority_rr = 5 -- ProtoField.uint16("mydns.num_authority_rr", "Number of Authority RRs") +local pf_num_additional_rr = 6 -- ProtoField.uint16("mydns.num_additional_rr", "Number of Additional RRs") + +test("ProtoField.new",pcall(callObjFuncGetter, pfields,pf_trasaction_id, ProtoField,"new", "Transaction ID", "mydns.trans_id", ftypes.INT16,nil,"base.DEC")) +test("ProtoField.new",pcall(callObjFuncGetter, pfields,pf_flags, ProtoField,"new", "Flags", "mydns.flags", ftypes.UINT16, nil, "base.HEX")) + +-- tries to register a field that already exists (from the real dns proto dissector) but with incompatible type +test("ProtoField.new_duplicate_bad",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "Flags", "dns.flags", ftypes.INT16, nil, "base.HEX")) +test("ProtoField.int16_duplicate_bad",not pcall(callObjFuncGetter, pfields,10, ProtoField,"int16", "dns.id","Transaction ID")) +-- now compatible (but different type) +test("ProtoField.new_duplicate_ok",pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "Flags", "dns.flags", ftypes.UINT32, nil, "base.HEX")) +test("ProtoField.uint16_duplicate_ok",pcall(callObjFuncGetter, pfields,10, ProtoField,"uint16", "dns.id","Transaction ID")) + +-- invalid valuestring arg +test("ProtoField.new_invalid_valuestring",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "Transaction ID", "mydns.trans_id", ftypes.INT16,"howdy","base.DEC")) +-- invalid ftype +test("ProtoField.new_invalid_ftype",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "Transaction ID", "mydns.trans_id", 9999)) +-- invalid description +--test("ProtoField.new_invalid_description",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "", "mydns.trans_id", ftypes.INT16)) +test("ProtoField.new_invalid_description",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", nil, "mydns.trans_id", ftypes.INT16)) + +test("ProtoField.new_invalid_abbr",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "trans id", "", ftypes.INT16)) +test("ProtoField.new_invalid_abbr",not pcall(callObjFuncGetter, pfields,10, ProtoField,"new", "trans id", nil, ftypes.INT16)) + +test("ProtoField.int16",pcall(callObjFuncGetter, pfields,pf_num_questions, ProtoField,"int16", "mydns.num_questions", "Number of Questions")) +test("ProtoField.int16",pcall(callObjFuncGetter, pfields,pf_num_answers, ProtoField,"int16", "mydns.num_answers", "Number of Answer RRs",base.DEC)) +test("ProtoField.int16",pcall(callObjFuncGetter, pfields,pf_num_authority_rr, ProtoField,"int16", "mydns.num_authority_rr", "Number of Authority RRs",base.DEC)) +test("ProtoField.int16",pcall(callObjFuncGetter, pfields,pf_num_additional_rr, ProtoField,"int16", "mydns.num_additional_rr", "Number of Additional RRs")) + +-- now undo the table thingy +pf_trasaction_id = pfields[pf_trasaction_id] +pf_flags = pfields[pf_flags] +pf_num_questions = pfields[pf_num_questions] +pf_num_answers = pfields[pf_num_answers] +pf_num_authority_rr = pfields[pf_num_authority_rr] +pf_num_additional_rr = pfields[pf_num_additional_rr] + +-- within the flags field, we want to parse/show the bits separately +-- note the "base" argument becomes the size of the bitmask'ed field when ftypes.BOOLEAN is used +-- the "mask" argument is which bits we want to use for this field (e.g., base=16 and mask=0x8000 means we want the top bit of a 16-bit field) +-- again the following shows different ways of doing the same thing basically +local pf_flag_response = ProtoField.new("Response", "mydns.flags.response", ftypes.BOOLEAN, {"this is a response","this is a query"}, 16, 0x8000, "is the message a response?") +local pf_flag_opcode = ProtoField.new("Opcode", "mydns.flags.opcode", ftypes.UINT16, nil, base.DEC, 0x7800, "operation code") +local pf_flag_authoritative = ProtoField.new("Authoritative", "mydns.flags.authoritative", ftypes.BOOLEAN, nil, 16, 0x0400, "is the response authoritative?") +local pf_flag_truncated = ProtoField.bool("mydns.flags.truncated", "Truncated", 16, nil, 0x0200, "is the message truncated?") +local pf_flag_recursion_desired = ProtoField.bool("mydns.flags.recursion_desired", "Recursion desired", 16, {"yes","no"}, 0x0100, "do the query recursivley?") +local pf_flag_recursion_available = ProtoField.bool("mydns.flags.recursion_available", "Recursion available", 16, nil, 0x0080, "does the server support recursion?") +local pf_flag_z = ProtoField.uint16("mydns.flags.z", "World War Z - Reserved for future use", base.HEX, nil, 0x0040, "when is it the future?") +local pf_flag_authenticated = ProtoField.bool("mydns.flags.authenticated", "Authenticated", 16, {"yes","no"}, 0x0020, "did the server DNSSEC authenticate?") +local pf_flag_checking_disabled = ProtoField.bool("mydns.flags.checking_disabled", "Checking disabled", 16, nil, 0x0010) + +-- no, these aren't all the DNS response codes - this is just an example +local rcodes = { + [0] = "No Error", + [1] = "Format Error", + [2] = "Server Failure", + [3] = "Non-Existent Domain", + [9] = "Server Not Authoritative for zone" +} +-- the above rcodes table is used in this next ProtoField +local pf_flag_rcode = ProtoField.uint16("mydns.flags.rcode", "Response code", base.DEC, rcodes, 0x000F) +local pf_query = ProtoField.new("Query", "mydns.query", ftypes.BYTES) +local pf_query_name = ProtoField.new("Name", "mydns.query.name", ftypes.STRING) +local pf_query_name_len = ProtoField.new("Name Length", "mydns.query.name.len", ftypes.UINT8) +local pf_query_label_count = ProtoField.new("Label Count", "mydns.query.label.count", ftypes.UINT8) +local rrtypes = { [1] = "A (IPv4 host address)", [2] = "NS (authoritative name server)", [28] = "AAAA (for geeks only)" } +local pf_query_type = ProtoField.uint16("mydns.query.type", "Type", base.DEC, rrtypes) +-- again, not all class types are listed here +local classes = { + [0] = "Reserved", + [1] = "IN (Internet)", + [2] = "The 1%", + [5] = "First class", + [6] = "Business class", + [65535] = "Cattle class" +} +local pf_query_class = ProtoField.uint16("mydns.query.class", "Class", base.DEC, classes, nil, "keep it classy folks") + + +testing(OTHER,"Proto functions") + +---------------------------------------- +-- this actually registers the ProtoFields above, into our new Protocol +-- in a real script I wouldn't do it this way; I'd build a table of fields programaticaly +-- and then set dns.fields to it, so as to avoid forgetting a field +local myfields = { pf_trasaction_id, pf_flags, + pf_num_questions, pf_num_answers, pf_num_authority_rr, pf_num_additional_rr, + pf_flag_response, pf_flag_opcode, pf_flag_authoritative, + pf_flag_truncated, pf_flag_recursion_desired, pf_flag_recursion_available, + pf_flag_z, pf_flag_authenticated, pf_flag_checking_disabled, pf_flag_rcode, + pf_query, pf_query_name, pf_query_name_len, pf_query_label_count, pf_query_type, pf_query_class } + +--dns.fields = myfields +test("Proto.fields-set", pcall(setValue,dns,"fields",myfields)) +test("Proto.fields-get", pcall(getValue,dns,"fields")) +test("Proto.fields-get", #dns.fields == #myfields) + +local pf_foo = ProtoField.uint16("myfoo.com", "Fooishly", base.DEC, rcodes, 0x000F) + +local foo = Proto("myfoo","MyFOO Protocol") +local bar = Proto("mybar","MyBAR Protocol") + +test("Proto.fields-set", pcall(setValue,foo,"fields",pf_foo)) +test("Proto.fields-get", #foo.fields == 1) +test("Proto.fields-get", foo.fields[1] == pf_foo) + +test("Proto.fields-set", not pcall(setValue,bar,"fields","howdy")) +test("Proto.fields-set", not pcall(setValue,bar,"fields",nil)) +test("Proto.fields-get", #bar.fields == 0) + +test("Proto.name-get", foo.name == "MYFOO") +test("Proto.name-set", not pcall(setValue,foo,"name","howdy")) + +test("Proto.description-get", foo.description == "MyFOO Protocol") +test("Proto.description-set", not pcall(setValue,foo,"description","howdy")) + +test("Proto.prefs-get", typeof(foo.prefs) == "Prefs") +test("Proto.prefs-set", not pcall(setValue,foo,"prefs","howdy")) + +local function dummy() + setFailed(OTHER) + error("dummy function called!") + return +end + +-- can't get this because we haven't set it yet +test("Proto.dissector-get", not pcall(getValue,foo,"dissector")) +-- now set it +test("Proto.dissector-set", pcall(setValue,foo,"dissector",dummy)) +test("Proto.dissector-set", not pcall(setValue,foo,"dissector","howdy")) +test("Proto.dissector-get", pcall(getValue,foo,"dissector")) + +test("Proto.prefs_changed-set", pcall(setValue,foo,"prefs_changed",dummy)) +test("Proto.prefs_changed-get", not pcall(getValue,foo,"prefs_changed")) +test("Proto.prefs_changed-set", not pcall(setValue,foo,"prefs_changed","howdy")) + +local function dummy_init() + --orig_test(OTHER,"Proto.init-called",true) + return +end + +test("Proto.init-set", pcall(setValue,foo,"init",dummy_init)) +test("Proto.init-set", pcall(setValue,bar,"init",dummy_init)) + +test("Proto.init-get", not pcall(getValue,foo,"init")) +test("Proto.init-set", not pcall(setValue,foo,"init","howdy")) + +local numinits = 0 +function dns.init() + numinits = numinits + 1 + if numinits == 2 then + getResults() + end +end + +---------------------------------------- +-- we don't just want to display our protocol's fields, we want to access the value of some of them too! +-- There are several ways to do that. One is to just parse the buffer contents in Lua code to find +-- the values. But since ProtoFields actually do the parsing for us, and can be retrieved using Field +-- objects, it's kinda cool to do it that way. So let's create some Fields to extract the values. +-- The following creates the Field objects, but they're not 'registered' until after this script is loaded. +-- Also, these lines can't be before the 'dns.fields = ...' line above, because the Field.new() here is +-- referencing fields we're creating, and they're not "created" until that line above. +-- Furthermore, you cannot put these 'Field.new()' lines inside the dissector function. +-- Before Wireshark version 1.11, you couldn't even do this concept (of using fields you just created). +local questions_field = Field.new("mydns.num_questions") +local query_type_field = Field.new("mydns.query.type") +local query_class_field = Field.new("mydns.query.class") +local response_field = Field.new("mydns.flags.response") + +-- here's a little helper function to access the response_field value later. +-- Like any Field retrieval, you can't retrieve a field's value until its value has been +-- set, which won't happen until we actually use our ProtoFields in TreeItem:add() calls. +-- So this isResponse() function can't be used until after the pf_flag_response ProtoField +-- has been used inside the dissector. +-- Note that calling the Field object returns a FieldInfo object, and calling that +-- returns the value of the field - in this case a boolean true/false, since we set the +-- "mydns.flags.response" ProtoField to ftype.BOOLEAN way earlier when we created the +-- pf_flag_response ProtoField. Clear as mud? +-- +-- A shorter version of this function would be: +-- local function isResponse() return response_field()() end +-- but I though the below is easier to understand. +local function isResponse() + local response_fieldinfo = response_field() + return response_fieldinfo() +end + + +---------------------------------------- +---- some constants for later use ---- +-- the DNS header size +local DNS_HDR_LEN = 12 + +-- the smallest possible DNS query field size +-- has to be at least a label length octet, label character, label null terminator, 2-bytes type and 2-bytes class +local MIN_QUERY_LEN = 7 + +-- the UDP port number we want to associate with our protocol +local MYDNS_PROTO_UDP_PORT = 65333 + +---------------------------------------- +-- some forward "declarations" of helper functions we use in the dissector +-- I don't usually use this trick, but it'll help reading/grok'ing this script I think +-- if we don't focus on them. +local byteArray2String, getQueryName + + +---------------------------------------- +-- The following creates the callback function for the dissector. +-- It's the same as doing "dns.dissector = function (tvbuf,pkt,root)" +-- The 'tvbuf' is a Tvb object, 'pktinfo' is a Pinfo object, and 'root' is a TreeItem object. +-- Whenever Wireshark dissects a packet that our Proto is hooked into, it will call +-- this function and pass it these arguments for the packet it's dissecting. +function dns.dissector(tvbuf,pktinfo,root) + + incPktCount(FRAME) + + -- We want to check that the packet size is rational during dissection, so let's get the length of the + -- packet buffer (Tvb). + -- Because DNS has no additonal payload data other than itself, and it rides on UDP without padding, + -- we can use tvb:len() or tvb:reported_len() here; but I prefer tvb:reported_length_remaining() as it's safer. + local pktlen = tvbuf:reported_length_remaining() + + -- We start by adding our protocol to the dissection display tree. + -- A call to tree:add() returns the child created, so we can add more "under" it using that return value. + -- The second argument is how much of the buffer/packet this added tree item covers/represents - in this + -- case (DNS protocol) that's the remainder of the packet. + local tree = root:add(dns, tvbuf:range(0,pktlen)) + + -- now let's check it's not too short + if pktlen < DNS_HDR_LEN then + -- since we're going to add this protocol to a specific UDP port, we're going to + -- assume packets in this port are our protocol, so the packet being too short is an error + tree:add_expert_info(PI_MALFORMED, PI_ERROR, "packet too short") + return + end + + -- Now let's add our transaction id under our dns protocol tree we just created. + -- The transaction id starts at offset 0, for 2 bytes length. + tree:add(pf_trasaction_id, tvbuf:range(0,2)) + + -- We'd like to put the transaction id number in the GUI row for this packet, in its + -- INFO column/cell. Firt we need the transaction id value, though. Since we just + -- dissected it with the previous code line, we could now get it using a Field's + -- FieldInfo extractor, but instead we'll get it directly from the TvbRange just + -- to show how to do that. We'll use Field/FieldInfo extractors later on... + local transid = tvbuf:range(0,2):uint() + pktinfo.cols.info:set("(".. transid ..")") + + -- now let's add the flags, which are all in the packet bytes at offset 2 of length 2 + -- instead of calling this again and again, let's just use a variable + local flagrange = tvbuf:range(2,2) + + -- for our flags field, we want a sub-tree + local flag_tree = tree:add(pf_flags, flagrange) + -- I'm indenting this for calarity, because it's adding to the flag's child-tree + -- let's add the type of message (query vs. response) + flag_tree:add(pf_flag_response, flagrange) + + -- we now know if it's a response or query, so let's put that in the + -- GUI packet row, in the INFO column cell + -- this line of code uses a Lua trick for doing something similar to + -- the C/C++ 'test ? true : false' shorthand + pktinfo.cols.info:prepend(isResponse() and "Response " or "Query ") + + flag_tree:add(pf_flag_opcode, flagrange) + + if isResponse() then + flag_tree:add(pf_flag_authoritative, flagrange) + end + + flag_tree:add(pf_flag_truncated, flagrange) + + if isResponse() then + flag_tree:add(pf_flag_recursion_available, flagrange) + else + flag_tree:add(pf_flag_recursion_desired, flagrange) + end + + flag_tree:add(pf_flag_z, flagrange) + + if isResponse() then + flag_tree:add(pf_flag_authenticated, flagrange) + flag_tree:add(pf_flag_rcode, flagrange) + end + + flag_tree:add(pf_flag_checking_disabled, flagrange) + + -- now add more to the main mydns tree + tree:add(pf_num_questions, tvbuf:range(4,2)) + tree:add(pf_num_answers, tvbuf:range(6,2)) + -- another way to get a TvbRange is just to call the Tvb like this + tree:add(pf_num_authority_rr, tvbuf(8,2)) + -- or if we're crazy, we can create a sub-TvbRange, from a sub-TvbRange of the TvbRange + tree:add(pf_num_additional_rr, tvbuf:range(10,2):range()()) + + local num_queries = questions_field()() + local pos = DNS_HDR_LEN + + if num_queries > 0 then + -- let's create a sub-tree, using a plain text description (not a field from the packet) + local queries_tree = tree:add("Queries") + + local pktlen_remaining = pktlen - pos + + while num_queries > 0 and pktlen_remaining > 0 do + if pktlen_remaining < MIN_QUERY_LEN then + queries_tree:add_expert_info(PI_MALFORMED, PI_ERROR, "query field missing or too short") + return + end + + -- we don't know how long this query field in total is, so we have to parse it first before + -- adding it to the tree, because we want to identify the correct bytes it covers + local label_count, name, name_len = getQueryName(tvbuf:range(pos,pktlen_remaining)) + if not label_count then + q_tree:add_expert_info(PI_MALFORMED, PI_ERROR, name) + return + end + + -- now add the first query to the 'Queries' child tree we just created + -- we're going to change the string generated by this later, after we figure out the subsequent fields. + -- the whole query field is the query name field length we just got, plus the 20byte type and 2-byte class + local q_tree = queries_tree:add(pf_query, tvbuf:range(pos, name_len + 4)) + + q_tree:add(pf_query_name, tvbuf:range(pos, name_len), name) + pos = pos + name_len + + pktinfo.cols.info:append(" "..name) + + -- the following tree items are generated by us, not encoded in the packet per se, so mark them as such + q_tree:add(pf_query_name_len, name_len):set_generated() + q_tree:add(pf_query_label_count, label_count):set_generated() + + q_tree:add(pf_query_type, tvbuf:range(pos, 2)) + q_tree:add(pf_query_class, tvbuf:range(pos + 2, 2)) + pos = pos + 4 + + -- now change the query text + q_tree:set_text(name..": type "..query_type_field().display ..", class "..query_class_field().display) + + pktlen_remaining = pktlen_remaining - (name_len + 4) + num_queries = num_queries - 1 + end -- end of while loop + + if num_queries > 0 then + -- we didn't process them all + queries_tree:add_expert_info(PI_MALFORMED, PI_ERROR, num_queries .. " query field(s) missing") + return + end + end + + setPassed(FRAME) + + -- tell wireshark how much of tvbuff we dissected + return pos +end + +---------------------------------------- +-- we want to have our protocol disseciton invoked for a specific UDP port, +-- so get the udp dissecotr table and add our protocol to it +local udp_encap_table = DissectorTable.get("udp.port") +udp_encap_table:add(MYDNS_PROTO_UDP_PORT, dns) + +-- We're done! +-- our protocol (Proto) gets automatically registered after this script finishes loading +---------------------------------------- + +---------------------------------------- +-- a helper function used later +-- note that it doesn't use "local" because it's already been declared as a local +-- variable way earlier in this script (as a form of forward declaration) +byteArray2String = function (barray, begin, length) + local word = {} + for i = 1, length do + word[i] = string.char(barray:get_index(begin)) + begin = begin + 1 + end + return table.concat(word) +end + +---------------------------------------- +-- DNS query names are not just null-terminated strings; they're actually a sequence of +-- 'labels', with a length octet before each one. So "foobar.com" is actually the +-- string "\06foobar\03com\00". We could create a ProtoField for label_length and label_name +-- or whatever, but since this is an example script I'll show how to do it in raw code. +-- This function is given the TvbRange object from the dissector() function, and needs to +-- parse it. +-- On success, it returns three things: the number of labels, the name string, and how +-- many bytes it covered of the buffer (which is always 2 more than the name length in this case). +-- On failure, it returns nil and the error message. +getQueryName = function (tvbr) + local label_count = 0 + local name = "" + + local len_remaining = tvbr:len() + if len_remaining < 2 then + -- it's too short + return nil, "invalid name" + end + + local barray = tvbr:bytes() -- gets a ByteArray of the TvbRange + local pos = 0 -- unlike Lua, ByteArray uses 0-based indexing + + -- get the first octet/label-length + local label_len = barray:get_index(pos) + if label_len == 0 then + return nil, "invalid initial label length of 0" + end + + while label_len > 0 do + if label_len >= len_remaining then + return nil, "invalid label length of "..label_len + end + pos = pos + 1 -- move past label length octet + -- sadly, there's no current way to get a raw Lua string from a ByteArray (nor from Tvb for that matter) + -- so we need to do it one character at a a time + -- append the label and a dot to name string + name = name .. byteArray2String(barray, pos, label_len) .. "." + len_remaining = len_remaining - (label_len + 1) -- subtract label and its length octet + label_count = label_count + 1 + pos = pos + label_len -- move past label + label_len = barray:get_index(pos) + end + + -- we appended an extra dot, so get rid of it + name = name:sub(1, -2) + + return label_count, name, name:len() + 2 +end + diff --git a/test/suite-wslua.sh b/test/suite-wslua.sh index a6f274cba0..20b97f96ab 100755 --- a/test/suite-wslua.sh +++ b/test/suite-wslua.sh @@ -114,6 +114,33 @@ wslua_step_pinfo_test() { fi } + +wslua_step_proto_test() { + if [ $HAVE_LUA -ne 0 ]; then + test_step_skipped + return + fi + + # First run tshark with the dissector script. + $TSHARK -r $CAPTURE_DIR/dns_port.pcap -V -X lua_script:$TESTS_DIR/lua/proto.lua > testin.txt 2>&1 + grep -q "All tests passed!" testin.txt + if [ $? -ne 0 ]; then + cat ./testin.txt + test_step_failed "didn't find pass marker" + fi + + # then run tshark again with the verification script. (it internally reads in testin.txt) + $TSHARK -r $CAPTURE_DIR/dns_port.pcap -X lua_script:$TESTS_DIR/lua/verify_dissector.lua > testout.txt 2>&1 + if grep -q "All tests passed!" testout.txt; then + test_step_ok + else + echo + cat ./testin.txt + cat ./testout.txt + test_step_failed "didn't find pass marker" + fi +} + wslua_step_int64_test() { if [ $HAVE_LUA -ne 0 ]; then test_step_skipped @@ -222,6 +249,7 @@ wslua_suite() { test_step_add "wslua listener" wslua_step_listener_test test_step_add "wslua nstime" wslua_step_nstime_test test_step_add "wslua pinfo" wslua_step_pinfo_test + 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 }