diff --git a/cmake/modules/UseAsn2Wrs.cmake b/cmake/modules/UseAsn2Wrs.cmake index ad65a9d92b..0abd33a82e 100644 --- a/cmake/modules/UseAsn2Wrs.cmake +++ b/cmake/modules/UseAsn2Wrs.cmake @@ -37,7 +37,9 @@ function(ASN2WRS) # Creates a dissector in the source directory and store the timestamp. add_custom_command( OUTPUT packet-${PROTOCOL_NAME}-stamp - COMMAND "${Python3_EXECUTABLE}" + COMMAND + "${CMAKE_COMMAND}" -E env PYTHONUTF8=1 + "${Python3_EXECUTABLE}" ${CMAKE_SOURCE_DIR}/tools/asn2wrs.py ${A2W_FLAGS} ${PROTO_OPT} diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index bf6cf70f21..4392eed039 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -437,6 +437,7 @@ set(WSLUA_MODULES ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_pinfo.c ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_address.c ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_column.c + ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_conversation.c ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_nstime.c ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_proto.c ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_dissector.c diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index 725cdf4aa9..1f9352a384 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -1893,6 +1893,7 @@ set(DISSECTOR_SRC ${CMAKE_CURRENT_SOURCE_DIR}/packet-shim6.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-sigcomp.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-signal-pdu.c + ${CMAKE_CURRENT_SOURCE_DIR}/packet-silabs-dch.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-simple.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-simulcrypt.c ${CMAKE_CURRENT_SOURCE_DIR}/packet-sinecap.c diff --git a/epan/dissectors/packet-cipsafety.c b/epan/dissectors/packet-cipsafety.c index 254e24b000..1fc5eaa3a7 100644 --- a/epan/dissectors/packet-cipsafety.c +++ b/epan/dissectors/packet-cipsafety.c @@ -4,7 +4,7 @@ * * This dissector includes items from: * CIP Volume 1: Common Industrial Protocol, Edition 3.24 - * CIP Volume 5: CIP Safety, Edition 2.22 + * CIP Volume 5: CIP Safety, Edition 2.26 * * Copyright 2011 * Michael Mann @@ -2036,7 +2036,7 @@ dissect_cip_safety_data( proto_tree *tree, proto_item *item, tvbuff_t *tvb, int if (multicast) { - dissect_base_format_time_correction_message(tree, tvb, (io_data_size * 2) + 5); + dissect_base_format_time_correction_message(tree, tvb, item_length - 6); } } break; diff --git a/epan/dissectors/packet-enip.c b/epan/dissectors/packet-enip.c index a43875ba16..baa7e5fa1a 100644 --- a/epan/dissectors/packet-enip.c +++ b/epan/dissectors/packet-enip.c @@ -1198,7 +1198,14 @@ enip_conv_info_t* get_conversation_info_one_direction(packet_info* pinfo, addres /* default some information if not included */ if ((connid_info->port == 0) || (connid_info->type == CONN_TYPE_MULTICAST)) { - connid_info->port = ENIP_IO_PORT; + if (pinfo->srcport == ENIP_SECURE_PORT || pinfo->destport == ENIP_SECURE_PORT) + { + connid_info->port = ENIP_SECURE_PORT; + } + else + { + connid_info->port = ENIP_IO_PORT; + } } ws_in6_addr ipv6_zero = {0}; @@ -1420,6 +1427,16 @@ enip_get_io_connid(packet_info *pinfo, uint32_t connid, enum enip_connid_type* p conversation_pt_to_conversation_type(pinfo->ptype), pinfo->destport, 0, NO_PORT_B); + // If we couldn't find anything, search based on the source port. Some types of IO traffic use the + // same src/dest port. Other types have a unique source port. + if (conversation == NULL) + { + conversation = find_conversation(pinfo->num, + &pinfo->src, &pinfo->dst, + conversation_pt_to_conversation_type(pinfo->ptype), + pinfo->srcport, 0, NO_PORT_B); + } + if (conversation == NULL) return NULL; diff --git a/epan/dissectors/packet-quic.c b/epan/dissectors/packet-quic.c index 6c9ffb84e2..3d5cc2c096 100644 --- a/epan/dissectors/packet-quic.c +++ b/epan/dissectors/packet-quic.c @@ -1476,6 +1476,69 @@ quic_connection_destroy(void *data, void *user_data _U_) /* QUIC Streams tracking and reassembly. {{{ */ static reassembly_table quic_reassembly_table; +typedef struct _quic_stream_key { + uint64_t stream_id; + uint32_t id; + uint32_t conn_number; + bool from_server; +} quic_stream_key; + +static unsigned +quic_stream_hash(const void *k) +{ + const quic_stream_key *key = (const quic_stream_key*)k; + unsigned hash_val; + + hash_val = key->id; + + return hash_val; +} + +static int +quic_stream_equal(const void *k1, const void *k2) +{ + const quic_stream_key* key1 = (const quic_stream_key*)k1; + const quic_stream_key* key2 = (const quic_stream_key*)k2; + + return (key1->id == key2->id) && + (key1->stream_id == key2->stream_id) && + (key1->conn_number == key2->conn_number) && + (key1->from_server == key2->from_server); +} + +static void * +quic_stream_persistent_key(const packet_info *pinfo _U_, const uint32_t id, + const void *data) +{ + const quic_stream_info* stream_info = (const quic_stream_info*)data; + DISSECTOR_ASSERT(stream_info != NULL); + quic_stream_key *key = g_slice_new(quic_stream_key); + + key->id = id; + key->stream_id = stream_info->stream_id; + key->conn_number = stream_info->quic_info->number; + key->from_server = stream_info->from_server; + + return (void *)key; +} + +static void +quic_stream_free_persistent_key(void *ptr) +{ + quic_stream_key *key = (quic_stream_key *)ptr; + g_slice_free(quic_stream_key, key); +} + +const reassembly_table_functions +quic_reassembly_table_functions = { + quic_stream_hash, + quic_stream_equal, + quic_stream_persistent_key, + quic_stream_persistent_key, + quic_stream_free_persistent_key, + quic_stream_free_persistent_key +}; + /** Perform sequence analysis for STREAM frames. */ static quic_stream_state * quic_get_stream_state(packet_info *pinfo, quic_info_data_t *quic_info, bool from_server, uint64_t stream_id) @@ -1611,15 +1674,7 @@ desegment_quic_stream(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, // result in a reassembly ID collision here. If that collision becomes // an issue, we would have to replace "msp->first_frame" with a new // field in "msp" that is initialized with "stream_info->stream_offset". -#if 0 - uint64_t reassembly_id_data[2]; - reassembly_id_data[0] = stream_info->stream_id; - reassembly_id_data[1] = msp ? msp->first_frame : pinfo->num; - reassembly_id = wmem_strong_hash((const uint8_t *)&reassembly_id_data, sizeof(reassembly_id_data)); -#else - // XXX for debug (visibility) purposes, do not use a hash but concatenate - reassembly_id = ((msp ? msp->first_frame : pinfo->num) << 16) | (uint32_t)stream_info->stream_id; -#endif + reassembly_id = msp ? msp->first_frame : pinfo->num; } if (msp && msp->seq <= seq && msp->nxtpdu > seq) { @@ -1642,7 +1697,7 @@ desegment_quic_stream(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, last_fragment_len = len; fh = fragment_add(&quic_reassembly_table, tvb, offset, - pinfo, reassembly_id, NULL, + pinfo, reassembly_id, stream_info, seq - msp->seq, len, nxtseq < msp->nxtpdu); if (fh) { @@ -1740,7 +1795,7 @@ desegment_quic_stream(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, * needs desegmentation). */ fragment_set_partial_reassembly(&quic_reassembly_table, - pinfo, reassembly_id, NULL); + pinfo, reassembly_id, stream_info); /* Update msp->nxtpdu to point to the new next * pdu boundary. @@ -1829,7 +1884,7 @@ desegment_quic_stream(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, /* add this segment as the first one for this new pdu */ fragment_add(&quic_reassembly_table, tvb, deseg_offset, - pinfo, reassembly_id, NULL, + pinfo, reassembly_id, stream_info, 0, nxtseq - deseg_seq, nxtseq < msp->nxtpdu); } @@ -1839,7 +1894,7 @@ desegment_quic_stream(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, * know what later frame the PDU is reassembled in. */ if (((struct tcp_multisegment_pdu *)wmem_tree_lookup32(stream->multisegment_pdus, deseg_seq))) { - fh = fragment_get(&quic_reassembly_table, pinfo, reassembly_id, NULL); + fh = fragment_get(&quic_reassembly_table, pinfo, reassembly_id, stream_info); } } } @@ -5749,11 +5804,12 @@ proto_register_quic(void) udp_port_to_display, follow_quic_tap_listener, get_quic_connections_count, quic_get_sub_stream_id); - // TODO implement custom reassembly functions that uses the QUIC Connection - // ID instead of address and port numbers. reassembly_table_register(&quic_reassembly_table, - &addresses_ports_reassembly_table_functions); + &quic_reassembly_table_functions); + // TODO do we need custom reassembly functions that use the QUIC Connection + // ID instead of address and port numbers here? It seems less likely that + // something will change the address or port than with STREAM frames. reassembly_table_register(&quic_crypto_reassembly_table, &tcp_reassembly_table_functions); diff --git a/epan/dissectors/packet-silabs-dch.c b/epan/dissectors/packet-silabs-dch.c new file mode 100644 index 0000000000..53eb9b7104 --- /dev/null +++ b/epan/dissectors/packet-silabs-dch.c @@ -0,0 +1,790 @@ +/* packet-silabs_dch.c + * Routines for Silicon Labs Debug Channel dissection + * Copyright 2023, Dhruv Chandwani + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Silabs Debug Channel is a protocol that is used for PTI (Packet Trace) data + * to flow from the Silicon Labs chips, via the adapter firmware into the PC of a developer. + * + * Packet Trace (PTI) is a functionality of Silicon Labs radio chips, which allows + * for the information about radio operation to be transferred to the observing + * hardware via a dedicated PTI pin (or two) on the Silicon Labs Radio Microcontroller. + * + * Silabs Debug Channel can also be used for other event-based data transfer, but PTI is the primary use case. + * + * Spec - https://github.com/SiliconLabs/java_packet_trace_library/blob/master/doc/debug-channel.md + */ + +/* Include files */ +#include "config.h" +#include +#include +#include /* Required dissection API header */ +#include /* Include only as needed */ +#include /* Include only as needed */ +#include "packet-tls.h" + +#define WS_LOG_DOMAIN "silabs_dch" + +/* Protocol identifier, populated by registration */ +static int proto_silabs_dch; + +// Field identifiers, populated by registration. +/* Silabs DCH */ +static int hf_dch; +static int hf_dch_version; +static int hf_dch_timestamp; +static int hf_dch_type; +static int hf_dch_flags; +static int hf_dch_sequence; + +/* Silabs EFR32 */ +static int hf_efr32; +static int hf_efr32_hwstart; +static int hf_efr32_hwend; +static int hf_efr32_rssi; +static int hf_efr32_syncword; +static int hf_efr32_radiocfg; +static int hf_efr32_radioicfg_ext; +static int hf_efr32_radiocfg_addedbytes; +static int hf_ef32_radiocfg_blephyid; +static int hf_efr32_radiocfg_regionid; +static int hf_efr32_radiocfg_phrlen; +static int hf_efr32_radiocfg_id; +static int hf_efr32_radioinfo; +static int hf_efr32_radioinfo_antenna; +static int hf_efr32_radioinfo_syncword; +static int hf_efr32_radioinfo_channel; +static int hf_efr32_status; +static int hf_efr32_status_errorcode; +static int hf_efr32_status_protocolid; +static int hf_efr32_appendedinfocfg; +static int hf_efr32_appendedinfocfg_txrx; +static int hf_efr32_appendedinfocfg_length; +static int hf_efr32_appendedinfocfg_version; + +/* Expert info fields - used for warnings/messages*/ +static expert_field ei_silabs_dch_unsupported_type = EI_INIT; +static expert_field ei_silabs_dch_unsupported_protocol = EI_INIT; +static expert_field ei_silabs_dch_invalid_appendedinfolen = EI_INIT; + +/* Bit-masks for the efr32 radio-cf field */ +#define EFR32_RADIOCFG_ADDEDBYTES_MASK 0xF8 +#define EFR32_RADIOCFG_BLEPHYID_MASK 0x03 +#define EFR32_RADIOCFG_REGIONID_MASK 0x1F +#define EFR32_RADIOCFG_PHRLEN_MASK 0x80 +#define EFR32_RADIOCFG_ID_MASK 0x07 + +/* Bit-masks for the efr32 radio-info field */ +#define EFR32_RADIOINFO_ANTENNA_MASK 0x80 +#define EFR32_RADIOINFO_SYNCWORD_MASK 0x40 +#define EFR32_RADIOINFO_CHANNEL_MASK 0x3F + +/* Bit-masks for the efr32 status field */ +#define EFR32_STATUS_ERRORCODE_MASK 0xF0 +#define EFR32_STATUS_PROTOCOLID_MASK 0x0F + +/* Bit-masks for the efr32 appended info cfg field */ +#define EFR32_APPENDEDINFOCFG_TXRX_MASK 0x40 +#define EFR32_APPENDEDINFOCFG_LENGTH_MASK 0x38 +#define EFR32_APPENDEDINFOCFG_VERSION_MASK 0x07 +// -------------------- + +/* Subtree identifiers, populated by registration */ +static int ett_silabs_dch; +static int ett_silabs_efr32; +static int ett_silabs_efr32_radiocfg; +static int ett_silabs_efr32_radioinfo; +static int ett_silabs_efr32_status; +static int ett_silabs_efr32_appendedinfo; +static int *ett[] = { + &ett_silabs_dch, + &ett_silabs_efr32, + &ett_silabs_efr32_radiocfg, + &ett_silabs_efr32_radioinfo, + &ett_silabs_efr32_status, + &ett_silabs_efr32_appendedinfo, +}; + +// Structs and other types +typedef struct +{ + const char *title; + int8_t crcLen; +} ProtocolInfo; + +typedef struct +{ + uint8_t length; + uint8_t version; + bool hasRssi; + bool hasSyncword; + bool hasRadioCfg; + uint8_t rssiLen; + uint8_t syncwordLen; + uint8_t radioCfgLen; + bool isInvalid; +} Efr32AppendedInfo; +// -------------------- + +/* Silabs Debug Channel Dissector Handle */ +static dissector_handle_t silabs_dch_handle; + +/* Handoff dissector handles */ +static dissector_handle_t ieee802154nofcs_handle; + +// Function declarations +/* Prototypes for required functions. */ +void proto_reg_handoff_silabs_dch(void); +void proto_register_silabs_dch(void); + +/* Dissector functions */ +static int dissect_silabs_dch(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_); +static int dissect_silabs_efr32(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree); + +/* Helper functions */ +static const Efr32AppendedInfo *get_efr32_appended_info(uint8_t appended_info_cfg); +static const ProtocolInfo *get_protocol_info(uint8_t protocol_id); +// -------------------- + +/* Debug channel message types*/ +static const value_string silabs_dch_message_types[] = { + {0x0000, "Time synchronization notice"}, + {0x0001, "Reset notice"}, + {0x0002, "Application printf"}, + {0x0003, "API trace"}, + {0x0004, "Assertion notice"}, + {0x0005, "Core dump"}, + {0x0006, "Phy Rx"}, + {0x0007, "API Rx"}, + {0x0008, "Phy Tx"}, + {0x0009, "API Tx"}, + {0x000A, "Sniffer packet"}, + {0x000B, "Adapter error"}, + {0x000C, "Statistics"}, + {0x000D, "Time sync test"}, + {0x000E, "Radio reboot count"}, + {0x0011, "Virtual UART Tx"}, + {0x0012, "Virtual UART Rx"}, + {0x0020, "2420 Tx packet"}, + {0x0021, "2420 Rx packet"}, + {0x0022, "250 Tx packet"}, + {0x0023, "250 Rx packet"}, + {0x0024, "350 Tx packet"}, + {0x0025, "350 Rx packet"}, + {0x0026, "Pro2+ Tx packet"}, + {0x0027, "Pro2+ Rx packet"}, + {0x0028, "Pro2+ debug packet"}, + {0x0029, "EFR Tx packet"}, + {0x002A, "EFR Rx packet"}, + {0x002B, "EFR additional PTI data"}, + {0x0030, "Flash read request"}, + {0x0031, "Flash read response"}, + {0x0032, "EEPROM read request"}, + {0x0033, "EEPROM read response"}, + {0x0034, "EEPROM write request"}, + {0x0035, "EEPROM write response"}, + {0x0036, "RAM read request"}, + {0x0037, "RAM read response"}, + {0x0038, "RAM write request"}, + {0x0039, "RAM write response"}, + {0x003A, "Info request"}, + {0x003B, "Node information"}, + {0x003C, "EmberZNet serial protocol"}, + {0x003D, "ASH protocol"}, + {0x003E, "DAG trace"}, + {0x003F, "Simulated NCP callback ready"}, + {0x0040, "Simulated wakeup signal to NCP"}, + {0x0041, "Simulated signal to host that NCP is awake"}, + {0x0042, "Ember ZNet stack version"}, + {0x0043, "Ember IP stack version"}, + {0x0044, "Current time information"}, + {0x0045, "Memory use information"}, + {0x0046, "Mustang API message"}, + {0x0047, "Latency"}, + {0x0048, "TMSP"}, + {0x0050, "AEM sample"}, + {0x0051, "AEM counters snapshot"}, + {0x0060, "AEM request"}, + {0x0061, "AEM response"}, + {0x0062, "AEM current packet"}, + {0x0063, "AEM current packet v2"}, + {0x0064, "PC sample packet"}, + {0x0065, "Exception packet"}, + {0x0066, "Logic analyzer data"}, + {0x0070, "CPU usage"}, + {0x0080, "Configuration over SWO"}, + {0xFFFE, "User command"}, + {0xFFFF, "User response"}, + {0, NULL}}; + +/* Efr32 HW start values*/ +static const value_string silabs_efr32_hwstart_values[] = { + {0xF8, "Rx Start"}, + {0xFC, "Tx Start"}, + {0xF0, "DMP Protocol Switch"}, + {0, NULL}}; + +/* Efr32 HW end values*/ +static const value_string silabs_efr32_hwend_values[] = { + {0xF9, "Rx Success"}, + {0xFA, "Rx Abort"}, + {0xFD, "Tx Success"}, + {0xFE, "Tx Abort"}, + {0, NULL}}; + +/* Efr32 protocol id values*/ +static const value_string silabs_efr32_status_protcolid_values[] = { + {0x00, "Custom"}, + {0x01, "EmberPHY (Zigbee/Thread)"}, + {0x02, "Thread on RAIL"}, + {0x03, "BLE"}, + {0x04, "Connect on RAIL"}, + {0x05, "Zigbee on RAIL"}, + {0x06, "Z-Wave on RAIL"}, + {0x07, "Wi-SUN on RAIL"}, + {0x08, "Custom on 802.15.4 built-in PHY"}, + {0x09, "Amazon SideWalk"}, + {0x0A, "Bluetooth Classic"}, + {0, NULL}}; + +/* Efr32 tx/rx values*/ +static const value_string silabs_efr32_appendedinfocfg_txrx_values[] = { + {0x00, "Tx"}, + {0x01, "Rx"}, + {0, NULL}}; + +/** + * Top-level dissector - dissects the debug channel header. + * + * @param tvb pointer to buffer containing raw packet. + * @param pinfo pointer to packet information fields + * @param tree pointer to data tree wireshark uses to display packet. + * @param data data passed (unused) + * @return offset after dissection + * + */ +static int dissect_silabs_dch(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) +{ + proto_item *dch_root = NULL; + proto_tree *dch_tree = NULL; + tvbuff_t *next_tvb; + int offset = 0; + uint32_t dch_version, dch_message_type; + const char *dch_message_type_str; + + // Create the top-level DCH subtree. + dch_root = proto_tree_add_item(tree, hf_dch, tvb, offset, -1, ENC_NA); + dch_tree = proto_item_add_subtree(dch_root, ett_silabs_dch); + + // Decode version, add it to DCH subtree. + proto_tree_add_item_ret_uint(dch_tree, hf_dch_version, tvb, offset, 2, ENC_LITTLE_ENDIAN, &dch_version); + offset += 2; + + // Decode timestamp, add it to DCH subtree. Timestamp is 8 bytes in later versions, but only 6 in version 2. + if (dch_version > 2) + { + proto_tree_add_item(dch_tree, hf_dch_timestamp, tvb, offset, 8, ENC_TIME_NSECS | ENC_LITTLE_ENDIAN); + offset += 8; + } + else + { + proto_tree_add_item(dch_tree, hf_dch_timestamp, tvb, offset, 6, ENC_TIME_USECS | ENC_LITTLE_ENDIAN); + offset += 6; + } + + // Decode message type, add it to DCH subtree. Use the message type descriptions + // from the array above. + proto_tree_add_item_ret_uint(dch_tree, hf_dch_type, tvb, offset, 2, ENC_LITTLE_ENDIAN, &dch_message_type); + dch_message_type_str = val_to_str_const(dch_message_type, silabs_dch_message_types, "Unknown"); + offset += 2; + col_add_fstr(pinfo->cinfo, COL_INFO, "Debug Message Type: %s", dch_message_type_str); + + // Decode flags (if exists) and sequence, honor differences between version 2 and version 3. + if (dch_version > 2) + { + proto_tree_add_item(dch_tree, hf_dch_flags, tvb, offset, 4, ENC_LITTLE_ENDIAN); + offset += 4; + proto_tree_add_item(dch_tree, hf_dch_sequence, tvb, offset, 2, ENC_LITTLE_ENDIAN); + offset += 2; + } + else + { + proto_tree_add_item(dch_tree, hf_dch_sequence, tvb, offset, 1, ENC_LITTLE_ENDIAN); + offset++; + } + + // get debug message payload + next_tvb = tvb_new_subset_remaining(tvb, offset); + + // Decode dch payload by dch message type + switch (dch_message_type) + { + case 0x02A: + case 0x029: + // hand debug message payload to efr32 dissector + offset = dissect_silabs_efr32(next_tvb, pinfo, tree); + break; + default: + proto_tree_add_expert_format(dch_tree, pinfo, &ei_silabs_dch_unsupported_type, tvb, offset, -1, "Debug message type - %s not supported yet", dch_message_type_str); + break; + } + + return offset; +} + +/** + * Dissector for Efr32 radio info + * + * @param tvb pointer to buffer containing raw packet. + * @param pinfo pointer to packet information fields + * @param tree pointer to data tree wireshark uses to display packet. + * @return offset after dissection + * + */ +static int dissect_silabs_efr32(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree) +{ + proto_item *efr32_root; + proto_item *efr32_tree; + tvbuff_t *next_tvb; + int offset = 0; + + // create the efr32 subtree + efr32_root = proto_tree_add_item(tree, hf_efr32, tvb, offset, -1, ENC_NA); + efr32_tree = proto_item_add_subtree(efr32_root, ett_silabs_efr32); + + // decode hw start, add to subtree + proto_tree_add_item(efr32_tree, hf_efr32_hwstart, tvb, offset, 1, ENC_LITTLE_ENDIAN); + offset += 1; + offset += 1; // efr32 has a packet length byte which we skip + + int last_index; + uint8_t protocol_id; + uint8_t appended_info_cfg; + uint8_t penultimate_byte; + const ProtocolInfo *protocol; + const Efr32AppendedInfo *appended_info; + + // decode radio appended info details + last_index = offset + tvb_reported_length_remaining(tvb, offset) - 1; + appended_info_cfg = tvb_get_uint8(tvb, last_index); + appended_info = get_efr32_appended_info(appended_info_cfg); + + // check if appended info len is invalid + if (appended_info->isInvalid) + { + proto_tree_add_expert_format(efr32_tree, pinfo, &ei_silabs_dch_invalid_appendedinfolen, tvb, offset, -1, "Invalid Appended Info Length"); + return offset; + } + + // decode protocol + penultimate_byte = tvb_get_uint8(tvb, last_index - 1); + protocol_id = 0x0000000F & penultimate_byte; + protocol = get_protocol_info(protocol_id); + + // determine OTA Payload (strip end byte, crc, appended info) + int crc_len = (protocol->crcLen != -1) ? protocol->crcLen : 2; // Dynamic crc length (indicated by -1) not supported yet, default to 2 + next_tvb = tvb_new_subset_length(tvb, offset, last_index + 1 - offset - appended_info->length - crc_len - 1); + + // hand ota payload to protocol dissector + switch (protocol_id) + { + case 2: // Thread on RAIL + case 5: // Zigbee on RAIL + offset += call_dissector(ieee802154nofcs_handle, next_tvb, pinfo, tree); + break; + default: + proto_tree_add_expert_format(efr32_tree, pinfo, &ei_silabs_dch_unsupported_protocol, tvb, offset, -1, "Protocol - %s not supported yet", protocol->title); + break; + // TODO: add support for the following protocols + // case 1: // EFR32 EmberPHY (Zigbee/Thread) + // case 3: // BLE + // case 4: // Connect on RAIL + // case 6: // Z-Wave on RAIL + // case 7: // Wi-SUN on RAIL + // case 9: // Amazon SideWalk + } + + // decode hw end, add to subtree + proto_tree_add_item(efr32_tree, hf_efr32_hwend, tvb, offset + protocol->crcLen, 1, ENC_LITTLE_ENDIAN); + offset += protocol->crcLen + 1; + + // decode rssi, add to subtree + if (appended_info->hasRssi) + { + proto_tree_add_item(efr32_tree, hf_efr32_rssi, tvb, offset, appended_info->rssiLen, ENC_LITTLE_ENDIAN); + offset += appended_info->rssiLen; + } + + // decode syncword, add to subtree + if (appended_info->hasSyncword) + { + proto_tree_add_item(efr32_tree, hf_efr32_syncword, tvb, offset, appended_info->syncwordLen, ENC_LITTLE_ENDIAN); + offset += appended_info->syncwordLen; + } + + // decode radio cfg, add to subtree + static int *const efr32_radiocfg_ble_fields[] = { + &hf_efr32_radiocfg_addedbytes, + &hf_ef32_radiocfg_blephyid, + NULL}; + static int *const efr32_radiocfg_zwave_fields[] = { + &hf_efr32_radiocfg_regionid, + NULL}; + static int *const efr32_radiocfg_fields[] = { + &hf_efr32_radiocfg_phrlen, + &hf_efr32_radiocfg_id, + NULL}; + if (appended_info->hasRadioCfg) + { + switch (protocol_id) + { + case 3: // BLE + proto_tree_add_bitmask(efr32_tree, tvb, offset, hf_efr32_radiocfg, ett_silabs_efr32_radiocfg, efr32_radiocfg_ble_fields, ENC_LITTLE_ENDIAN); + break; + case 6: // Z-Wave on RAIL + proto_tree_add_bitmask(efr32_tree, tvb, offset, hf_efr32_radiocfg, ett_silabs_efr32_radiocfg, efr32_radiocfg_zwave_fields, ENC_LITTLE_ENDIAN); + break; + case 1: + case 2: + case 5: + case 7: + case 8: + proto_tree_add_bitmask(efr32_tree, tvb, offset, hf_efr32_radiocfg, ett_silabs_efr32_radiocfg, efr32_radiocfg_fields, ENC_LITTLE_ENDIAN); + break; + default: + proto_tree_add_item(efr32_tree, hf_efr32_radiocfg, tvb, offset, appended_info->radioCfgLen, ENC_LITTLE_ENDIAN); + break; + } + if (appended_info->radioCfgLen == 2) + { + proto_tree_add_item(efr32_tree, hf_efr32_radioicfg_ext, tvb, offset, 1, ENC_LITTLE_ENDIAN); + } + offset += appended_info->radioCfgLen; + } + + // decode radio info, add to subtree + static int *const efr32_radioinfo_fields[] = { + &hf_efr32_radioinfo_antenna, + &hf_efr32_radioinfo_syncword, + &hf_efr32_radioinfo_channel, + NULL}; + proto_tree_add_bitmask(efr32_tree, tvb, offset, hf_efr32_radioinfo, ett_silabs_efr32_radioinfo, efr32_radioinfo_fields, ENC_LITTLE_ENDIAN); + offset += 1; + + // decode status, add to subtree + static int *const efr32_status_fields[] = { + &hf_efr32_status_errorcode, + &hf_efr32_status_protocolid, + NULL}; + proto_tree_add_bitmask(efr32_tree, tvb, offset, hf_efr32_status, ett_silabs_efr32_status, efr32_status_fields, ENC_LITTLE_ENDIAN); + offset += 1; + + // decode appended info cfg, add to subtree + static int *const efr32_appendedinfocfg_fields[] = { + &hf_efr32_appendedinfocfg_txrx, + &hf_efr32_appendedinfocfg_length, + &hf_efr32_appendedinfocfg_version, + NULL}; + proto_tree_add_bitmask(efr32_tree, tvb, offset, hf_efr32_appendedinfocfg, ett_silabs_efr32_appendedinfo, efr32_appendedinfocfg_fields, ENC_LITTLE_ENDIAN); + offset += 1; + + return offset; +} + +/** + * Get protocol info based on protocol id + * + * @param protocol_id protocol id + * @return pointer to ProtocolInfo struct + * + */ +static const ProtocolInfo *get_protocol_info(uint8_t protocol_id) +{ + ProtocolInfo *protocol_info = (ProtocolInfo *)wmem_alloc(wmem_packet_scope(), sizeof(ProtocolInfo)); + + switch (protocol_id) + { + case 0: + *protocol_info = (ProtocolInfo){"Custom", 2}; + break; + case 1: + *protocol_info = (ProtocolInfo){"EFR32 EmberPHY", 2}; + break; + case 2: + *protocol_info = (ProtocolInfo){"Thread on RAIL", 2}; + break; + case 3: + *protocol_info = (ProtocolInfo){"BLE", 3}; + break; + case 4: + *protocol_info = (ProtocolInfo){"Connect on RAIL", 2}; + break; + case 5: + *protocol_info = (ProtocolInfo){"ZigBee on RAIL", 2}; + break; + case 6: + *protocol_info = (ProtocolInfo){"Z-Wave on RAIL", -1}; + break; + case 7: + *protocol_info = (ProtocolInfo){"Wi-SUN", 4}; + break; + default: + *protocol_info = (ProtocolInfo){"Unknown", 0}; + break; + } + + return protocol_info; +} + +/** + * Get appended info details based on appended info cfg + * + * @param appended_info_cfg appended info cfg byte + * @return pointer to Efr32AppendedInfo struct + * + */ +static const Efr32AppendedInfo *get_efr32_appended_info(uint8_t appended_info_cfg) +{ + Efr32AppendedInfo *appended_info = (Efr32AppendedInfo *)wmem_alloc(wmem_packet_scope(), sizeof(Efr32AppendedInfo)); + bool isRx = (appended_info_cfg & 0x00000040) != 0; + uint8_t var_len = (appended_info_cfg & 0x00000038) >> 3; + appended_info->length = var_len + 3; + appended_info->isInvalid = false; + if (isRx) + { + switch (var_len) + { + case 1: + appended_info->hasSyncword = false; + appended_info->hasRssi = true; + appended_info->hasRadioCfg = false; + break; + case 2: + appended_info->hasSyncword = false; + appended_info->hasRssi = true; + appended_info->hasRadioCfg = true; + break; + case 3: + appended_info->hasSyncword = false; + appended_info->hasRssi = true; + appended_info->hasRadioCfg = true; + break; + case 5: + appended_info->hasSyncword = true; + appended_info->hasRssi = true; + appended_info->hasRadioCfg = false; + break; + case 6: + appended_info->hasSyncword = true; + appended_info->hasRssi = true; + appended_info->hasRadioCfg = true; + break; + case 7: + appended_info->hasSyncword = true; + appended_info->hasRssi = true; + appended_info->hasRadioCfg = true; + break; + default: + appended_info->hasSyncword = false; + appended_info->hasRssi = false; + appended_info->hasRadioCfg = false; + appended_info->isInvalid = true; + break; + } + } + else + { + switch (var_len) + { + case 0: + appended_info->hasSyncword = false; + appended_info->hasRssi = false; + appended_info->hasRadioCfg = false; + break; + case 1: + appended_info->hasSyncword = false; + appended_info->hasRssi = false; + appended_info->hasRadioCfg = true; + break; + case 2: + appended_info->hasSyncword = false; + appended_info->hasRssi = false; + appended_info->hasRadioCfg = true; + break; + case 4: + appended_info->hasSyncword = true; + appended_info->hasRssi = false; + appended_info->hasRadioCfg = false; + break; + case 5: + appended_info->hasSyncword = true; + appended_info->hasRssi = false; + appended_info->hasRadioCfg = true; + break; + case 6: + appended_info->hasSyncword = true; + appended_info->hasRssi = false; + appended_info->hasRadioCfg = true; + break; + default: + appended_info->hasSyncword = false; + appended_info->hasRssi = false; + appended_info->hasRadioCfg = false; + appended_info->isInvalid = true; + break; + } + } + appended_info->rssiLen = appended_info->hasRssi ? 1 : 0; + appended_info->syncwordLen = appended_info->hasSyncword ? 4 : 0; + if (appended_info->hasRadioCfg) + { + if (isRx) + { + appended_info->radioCfgLen = (var_len == 3 || var_len == 7) ? 2 : 1; + } + else + { + appended_info->radioCfgLen = (var_len == 2 || var_len == 6) ? 2 : 1; + } + } + + return appended_info; +} +// ******************************** + +/* Register the protocol with Wireshark. + * + * This format is required because a script is used to build the C function that + * calls all the protocol registration. + * + */ +void proto_register_silabs_dch(void) +{ + static hf_register_info hf[] = { + {&hf_dch, + {"Silabs Debug Channel", "silabs-dch", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_dch_version, + {"Version", "silabs-dch.version", FT_UINT16, BASE_DEC, NULL, 0x0, "Debug Channel Version", HFILL}}, + {&hf_dch_timestamp, + {"Timestamp", "silabs-dch.timestamp", FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0, "Debug Channel Timestamp", HFILL}}, + {&hf_dch_type, + {"Type", "silabs-dch.type", FT_UINT16, BASE_HEX, VALS(silabs_dch_message_types), 0x0, "Debug Message Type", HFILL}}, + {&hf_dch_flags, + {"Flags", "silabs-dch.flags", FT_UINT32, BASE_HEX, NULL, 0x0, "Debug Channel Flags", HFILL}}, + {&hf_dch_sequence, + {"Sequence", "silabs-dch.seq", FT_UINT16, BASE_DEC, NULL, 0x0, "Debug Channel Sequence", HFILL}}, + {&hf_efr32, + {"Silabs EFR32 Radio Info", "silabs-dch.efr32", FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL}}, + {&hf_efr32_hwstart, + {"HW Start", "silabs-dch.hwstart", FT_UINT8, BASE_HEX, VALS(silabs_efr32_hwstart_values), 0x0, "EFR32 HW Start", HFILL}}, + {&hf_efr32_hwend, + {"HW End", "silabs-dch.hwend", FT_UINT8, BASE_HEX, VALS(silabs_efr32_hwend_values), 0x0, "EFR32 HW End", HFILL}}, + {&hf_efr32_rssi, + {"RSSI", "silabs-dch.rssi", FT_UINT8, BASE_HEX, NULL, 0x0, "EFR32 RSSI", HFILL}}, + {&hf_efr32_syncword, + {"Syncword", "silabs-dch.syncword", FT_UINT32, BASE_HEX, NULL, 0x0, "EFR32 Syncword", HFILL}}, + {&hf_efr32_radiocfg, + {"Radio Config", "silabs-dch.radio_cfg", FT_UINT8, BASE_HEX, NULL, 0x0, "EFR32 Radio Config", HFILL}}, + {&hf_efr32_radioicfg_ext, + {"Radio Config Extension", "silabs-dch.radio_cfg_ext", FT_UINT8, BASE_HEX, NULL, 0x0, "EFR32 Radio Config Extension", HFILL}}, + {&hf_efr32_radiocfg_addedbytes, + {"Added Bytes", "silabs-dch.radio_cfg_addedbytes", FT_UINT8, BASE_HEX, NULL, EFR32_RADIOCFG_ADDEDBYTES_MASK, "EFR32 Radio Config Added Bytes", HFILL}}, + {&hf_ef32_radiocfg_blephyid, + {"BLE PHY Id", "silabs-dch.radio_cfg_blephyid", FT_UINT8, BASE_HEX, NULL, EFR32_RADIOCFG_BLEPHYID_MASK, "EFR32 Radio Config BLE PHY Id", HFILL}}, + {&hf_efr32_radiocfg_regionid, + {"Z-Wave Region ID", "silabs-dch.radio_cfg_regionid", FT_UINT8, BASE_HEX, NULL, EFR32_RADIOCFG_REGIONID_MASK, "EFR32 Radio Config Z-Wave Region ID", HFILL}}, + {&hf_efr32_radiocfg_phrlen, + {"PHR Length", "silabs-dch.radio_cfg_phrlen", FT_UINT8, BASE_DEC, NULL, EFR32_RADIOCFG_PHRLEN_MASK, "EFR32 Radio Config Phy Length", HFILL}}, + {&hf_efr32_radiocfg_id, + {"Radio Config Id", "silabs-dch.radio_cfg_id", FT_UINT8, BASE_HEX, NULL, EFR32_RADIOCFG_ID_MASK, "EFR32 Radio Config ID", HFILL}}, + {&hf_efr32_radioinfo, + {"Radio Info", "silabs-dch.radio_info", FT_UINT8, BASE_HEX, NULL, 0x0, "EFR32 Radio Info", HFILL}}, + {&hf_efr32_radioinfo_antenna, + {"Antenna Select", "silabs-dch.radio_info_antenna", FT_UINT8, BASE_HEX, NULL, EFR32_RADIOINFO_ANTENNA_MASK, "EFR32 Radio Info Antenna", HFILL}}, + {&hf_efr32_radioinfo_syncword, + {"Syncword Select", "silabs-dch.radio_info_syncword", FT_UINT8, BASE_HEX, NULL, EFR32_RADIOINFO_SYNCWORD_MASK, "EFR32 Radio Info Syncword", HFILL}}, + {&hf_efr32_radioinfo_channel, + {"Channel", "silabs-dch.radio_info_channel", FT_UINT8, BASE_DEC, NULL, EFR32_RADIOINFO_CHANNEL_MASK, "EFR32 Radio Info Channel", HFILL}}, + {&hf_efr32_status, + {"Status", "silabs-dch.status", FT_UINT8, BASE_HEX, NULL, 0x0, "EFR32 Status", HFILL}}, + {&hf_efr32_status_errorcode, + {"Error Code", "silabs-dch.status_errorcode", FT_UINT8, BASE_HEX, NULL, EFR32_STATUS_ERRORCODE_MASK, "EFR32 Status Error Code", HFILL}}, + {&hf_efr32_status_protocolid, + {"Protocol", "silabs-dch.status_protocolid", FT_UINT8, BASE_HEX, VALS(silabs_efr32_status_protcolid_values), EFR32_STATUS_PROTOCOLID_MASK, "EFR32 Status Protocol ID", HFILL}}, + {&hf_efr32_appendedinfocfg, + {"Appended Info Config", "silabs-dch.appended_info_cfg", FT_UINT8, BASE_HEX, NULL, 0x0, "EFR32 Appended Info Config", HFILL}}, + {&hf_efr32_appendedinfocfg_txrx, + {"Tx/Rx", "silabs-dch.appended_info_cfg_txrx", FT_UINT8, BASE_HEX, VALS(silabs_efr32_appendedinfocfg_txrx_values), EFR32_APPENDEDINFOCFG_TXRX_MASK, "EFR32 Appended Info Config Tx/Rx", HFILL}}, + {&hf_efr32_appendedinfocfg_length, + {"Appended Info Length", "silabs-dch.appended_info_cfg_length", FT_UINT8, BASE_DEC, NULL, EFR32_APPENDEDINFOCFG_LENGTH_MASK, "EFR32 Appended Info Config Length", HFILL}}, + {&hf_efr32_appendedinfocfg_version, + {"Appended Info Version", "silabs-dch.appended_info_cfg_version", FT_UINT8, BASE_DEC, NULL, EFR32_APPENDEDINFOCFG_VERSION_MASK, "EFR32 Appended Info Config Version", HFILL}}, + }; + + static ei_register_info ei[] = { + {&ei_silabs_dch_unsupported_type, + {"silabs-dch.unsupported_dch_msg_type", PI_COMMENTS_GROUP, PI_NOTE, "Unsupported Debug Channel Message Type", EXPFILL}}, + {&ei_silabs_dch_unsupported_protocol, + {"silabs-dch.unsupported_protocol", PI_COMMENTS_GROUP, PI_NOTE, "Unsupported EFR32 Protocol", EXPFILL}}, + {&ei_silabs_dch_invalid_appendedinfolen, + {"silabs-dch.invalid_appendedinfolen", PI_MALFORMED, PI_ERROR, "Invalid Appended Info Length", EXPFILL}}, + }; + + // Register the protocol + proto_silabs_dch = proto_register_protocol( + "Silabs Debug Channel", + "Silabs DCH", + "silabs-dch"); + + // Register field header + proto_register_field_array(proto_silabs_dch, hf, array_length(hf)); + + // Register subtrees + proto_register_subtree_array(ett, array_length(ett)); + + expert_module_t *expert_silabs_dch; + expert_silabs_dch = expert_register_protocol(proto_silabs_dch); + expert_register_field_array(expert_silabs_dch, ei, array_length(ei)); + + // Register the dissector, obtain a handle. + silabs_dch_handle = register_dissector( + "silabs_dch", + dissect_silabs_dch, + proto_silabs_dch); +} + +/** + * Register handoff link + * + */ +void proto_reg_handoff_silabs_dch(void) +{ + ieee802154nofcs_handle = find_dissector("wpan_nofcs"); + // Register top-level handoff, so that the toplevel WTAP will be + // decoded by the silabs dch dissector. + dissector_add_uint( + "wtap_encap", + WTAP_ENCAP_SILABS_DEBUG_CHANNEL, + silabs_dch_handle); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/epan/wslua/CMakeLists.txt b/epan/wslua/CMakeLists.txt index 9f35e1486a..e96605fe63 100644 --- a/epan/wslua/CMakeLists.txt +++ b/epan/wslua/CMakeLists.txt @@ -15,6 +15,7 @@ set(WSLUA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/wslua_byte_array.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua_capture_info.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua_column.c + ${CMAKE_CURRENT_SOURCE_DIR}/wslua_conversation.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua_dir.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua_dissector.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua_dumper.c diff --git a/epan/wslua/init_wslua.c b/epan/wslua/init_wslua.c index 5ca305dca0..31664cc1c0 100644 --- a/epan/wslua/init_wslua.c +++ b/epan/wslua/init_wslua.c @@ -1023,6 +1023,7 @@ wslua_allocf(void *ud _U_, void *ptr, size_t osize _U_, size_t nsize) #define WSLUA_BASE_TABLE "base" #define WSLUA_FTYPE_TABLE "ftypes" #define WSLUA_FRAMETYPE_TABLE "frametype" +#define WSLUA_CONV_TYPE_TABLE "convtypes" #define WSLUA_EXPERT_TABLE "expert" #define WSLUA_EXPERT_GROUP_TABLE "group" #define WSLUA_EXPERT_SEVERITY_TABLE "severity" @@ -1110,6 +1111,8 @@ wslua_add_introspection(void) lua_newtable(L); lua_setglobal(L, WSLUA_FRAMETYPE_TABLE); lua_newtable(L); + lua_setglobal(L, WSLUA_CONV_TYPE_TABLE); + lua_newtable(L); lua_pushstring(L, WSLUA_EXPERT_GROUP_TABLE); lua_newtable(L); lua_settable(L, -3); @@ -1157,6 +1160,13 @@ wslua_add_introspection(void) add_table_symbol(WSLUA_EPAN_ENUMS_TABLE, ep->symbol, ep->value); } + for (const wslua_conv_types_t* ct = wslua_inspect_convtype_enum(); ct->str != NULL; ct++) { + const char *name = strchr(ct->str, '.'); + if (name != NULL) { + add_table_symbol(WSLUA_CONV_TYPE_TABLE, name+1, ct->id); + } + } + /* Add empty tables to be populated. */ lua_newtable(L); lua_setglobal(L, WSLUA_WTAP_ENCAPS_TABLE); diff --git a/epan/wslua/wslua.h b/epan/wslua/wslua.h index 86263573a0..83b1ab9162 100644 --- a/epan/wslua/wslua.h +++ b/epan/wslua/wslua.h @@ -49,6 +49,7 @@ #include #include #include +#include #include @@ -226,6 +227,11 @@ typedef struct _wslua_proto_t { bool expired; } wslua_proto_t; +typedef struct _wslua_conv_data_t { + conversation_t* conv; + int data_ref; +} wslua_conv_data_t; + /* a "DissectorTable" object can be different things under the hood, * since its heuristic_new() can create a heur_dissector_list_t that * needs to be deregistered. */ @@ -351,6 +357,7 @@ struct _wslua_progdlg { typedef struct { const char* name; tap_extractor_t extractor; } tappable_t; typedef struct {const char* str; enum ftenum id; } wslua_ft_types_t; +typedef struct {const char* str; conversation_type id; } wslua_conv_types_t; typedef wslua_pref_t* Pref; typedef wslua_pref_t* Prefs; @@ -389,6 +396,7 @@ typedef tvbparse_elem_t* Node; typedef tvbparse_action_t* Shortcut; typedef struct _wslua_dir* Dir; typedef struct _wslua_private_table* PrivateTable; +typedef conversation_t* Conversation; typedef char* Struct; /* @@ -879,6 +887,8 @@ extern void wslua_deregister_menus(void); extern void wslua_init_wtap_filetypes(lua_State* L); +extern const wslua_conv_types_t* wslua_inspect_convtype_enum(void); + #endif /* diff --git a/epan/wslua/wslua_conversation.c b/epan/wslua/wslua_conversation.c new file mode 100644 index 0000000000..bde2bc483a --- /dev/null +++ b/epan/wslua/wslua_conversation.c @@ -0,0 +1,437 @@ +/* + * wslua_conversation.c + * + * Wireshark's interface to the Lua Programming Language + * + * (c) 2024, Alastair Knowles + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" + +/* WSLUA_CONTINUE_MODULE Pinfo */ + +#include "wslua.h" +#include + +static const wslua_conv_types_t convtype_enums[] = { + {"convtypes.NONE", CONVERSATION_NONE}, + {"convtypes.SCTP", CONVERSATION_SCTP}, + {"convtypes.TCP", CONVERSATION_TCP}, + {"convtypes.UDP", CONVERSATION_UDP}, + {"convtypes.DCCP", CONVERSATION_DCCP}, + {"convtypes.IPX", CONVERSATION_IPX}, + {"convtypes.NCP", CONVERSATION_NCP}, + {"convtypes.EXCHG", CONVERSATION_EXCHG}, + {"convtypes.DDP", CONVERSATION_DDP}, + {"convtypes.SBCCS", CONVERSATION_SBCCS}, + {"convtypes.IDP", CONVERSATION_IDP}, + {"convtypes.TIPC", CONVERSATION_TIPC}, + {"convtypes.USB", CONVERSATION_USB}, + {"convtypes.I2C", CONVERSATION_I2C}, + {"convtypes.IBQP", CONVERSATION_IBQP}, + {"convtypes.BLUETOOTH", CONVERSATION_BLUETOOTH}, + {"convtypes.TDMOP", CONVERSATION_TDMOP}, + {"convtypes.DVBCI", CONVERSATION_DVBCI}, + {"convtypes.ISO14443", CONVERSATION_ISO14443}, + {"convtypes.ISDN", CONVERSATION_ISDN}, + {"convtypes.H223", CONVERSATION_H223}, + {"convtypes.X25", CONVERSATION_X25}, + {"convtypes.IAX2", CONVERSATION_IAX2}, + {"convtypes.DLCI", CONVERSATION_DLCI}, + {"convtypes.ISUP", CONVERSATION_ISUP}, + {"convtypes.BICC", CONVERSATION_BICC}, + {"convtypes.GSMTAP", CONVERSATION_GSMTAP}, + {"convtypes.IUUP", CONVERSATION_IUUP}, + {"convtypes.DVBBBF", CONVERSATION_DVBBBF}, + {"convtypes.IWARP_MPA", CONVERSATION_IWARP_MPA}, + {"convtypes.BT_UTP", CONVERSATION_BT_UTP}, + {"convtypes.LOG", CONVERSATION_LOG}, + {"convtypes.LTP", CONVERSATION_LTP}, + {"convtypes.MCTP", CONVERSATION_MCTP}, + {"convtypes.NVME_MI", CONVERSATION_NVME_MI}, + {"convtypes.BP", CONVERSATION_BP}, + {"convtypes.SNMP", CONVERSATION_SNMP}, + {"convtypes.QUIC", CONVERSATION_QUIC}, + {"convtypes.IDN", CONVERSATION_IDN}, + {"convtypes.IP", CONVERSATION_IP}, + {"convtypes.IPV6", CONVERSATION_IPV6}, + {"convtypes.ETH", CONVERSATION_ETH}, + {"convtypes.ETH_NN", CONVERSATION_ETH_NN}, + {"convtypes.ETH_NV", CONVERSATION_ETH_NV}, + {"convtypes.ETH_IN", CONVERSATION_ETH_IN}, + {"convtypes.ETH_IV", CONVERSATION_ETH_IV}, + {"convtypes.VSPC_VMOTION", CONVERSATION_VSPC_VMOTION}, + {"convtypes.OPENVPN", CONVERSATION_OPENVPN}, + {"convtypes.PROXY", CONVERSATION_PROXY}, + {"convtypes.DNP3", CONVERSATION_DNP3}, + {NULL, CONVERSATION_NONE} +}; + +#define CONVTYPE_ENUM_LAST (sizeof(convtype_enums)/sizeof(wslua_conv_types_t)-1) + +const wslua_conv_types_t* wslua_inspect_convtype_enum(void) { + return convtype_enums; +} + +static conversation_type str_to_convtype_enum(const char* type) { + const wslua_conv_types_t* ts; + for (ts = convtype_enums; ts->str; ts++) { + if ( g_str_equal(ts->str,type) ) { + return ts->id; + } + } + return CONVERSATION_NONE; +} + +static conversation_type int_to_convtype_enum(int type) { + if (type < (int)convtype_enums[0].id || type > (int)convtype_enums[CONVTYPE_ENUM_LAST-1].id) { + type = CONVERSATION_NONE; + } + return (conversation_type)type; +} + +WSLUA_CLASS_DEFINE(Conversation,NOP); +/* Conversation object, used to attach conversation data or a conversation dissector */ + +WSLUA_METAMETHOD Conversation__eq(lua_State *L) { /* Compares two Conversation objects. */ + Conversation conv1 = checkConversation(L,1); + Conversation conv2 = checkConversation(L,2); + + lua_pushboolean(L, conv1 == conv2); + + WSLUA_RETURN(1); /* True if both objects refer to the same underlying conversation structure. False otherwise. */ +} + +WSLUA_METAMETHOD Conversation__tostring(lua_State *L) { + Conversation conv = checkConversation(L,1); + lua_pushfstring(L, "Conversation object (%p)", conv); + WSLUA_RETURN(1); /* A string representation of the object. */ +} + + +/* Gets registered as metamethod automatically by WSLUA_REGISTER_CLASS/META */ +static int Conversation__gc(lua_State* L _U_) { + /* Don't free. Conversation struct managed in Wireshark */ + return 0; +} + +WSLUA_CONSTRUCTOR Conversation_find(lua_State* L) { + /* Searches for a `Conversation` instance matching criteria. If one does not exist and 'create' + is true, one will be created, otherwise `nil` will be returned. Note that, although there are + 'first' and 'second' addresses and ports, a conversation does not distinguish between source + or destination. These are effectively matching criteria that wireshark uses to flag a packet as + belonging to the conversation. */ + +#define WSLUA_ARG_Conversation_find_FRAMENUM 1 /* The number of a frame within the conversation. If a new + conversation is created, this will be used as the first + frame of the conversation. */ +#define WSLUA_ARG_Conversation_find_CTYPE 2 /* Conversation Type. One of: `convtypes.NONE`, `convtypes.SCTP`, + `convtypes.TCP`, `convtypes.UDP`, `convtypes.DCCP`, `convtypes.IPX`, `convtypes.NCP`, + `convtypes.EXCHG`, `convtypes.DDP`, `convtypes.SBCCS`, `convtypes.IDP`, `convtypes.TIPC`, + `convtypes.USB`, `convtypes.I2C`, `convtypes.IBQP`, `convtypes.BLUETOOTH`, `convtypes.TDMOP`, + `convtypes.DVBCI`, `convtypes.ISO14443`, `convtypes.ISDN`, `convtypes.H223`, `convtypes.X25`, + `convtypes.IAX2`, `convtypes.DLCI`, `convtypes.ISUP`, `convtypes.BICC`, `convtypes.GSMTAP`, + `convtypes.IUUP`, `convtypes.DVBBBF`, `convtypes.IWARP_MPA`, `convtypes.BT_UTP`, `convtypes.LOG`, + `convtypes.LTP`, `convtypes.MCTP`, `convtypes.NVME_MI`, `convtypes.BP`, `convtypes.SNMP`, + `convtypes.QUIC`, `convtypes.IDN`, `convtypes.IP`, `convtypes.IPV6`, `convtypes.ETH`, + `convtypes.ETH_NN`, `convtypes.ETH_NV`, `convtypes.ETH_IN`, `convtypes.ETH_IV`, + `convtypes.VSPC_VMOTION`, `convtypes.OPENVPN`, `convtypes.PROXY`, `onvtypes.DNP3` */ + +#define WSLUA_ARG_Conversation_find_ADDR1 3 /* First <> of the conversation. */ +#define WSLUA_OPTARG_Conversation_find_ADDR2 4 /* Second <> of theconversation. (defaults to nil) */ +#define WSLUA_OPTARG_Conversation_find_PORT1 5 /* First port. A value of `nil` or `0` is treated as 'ignore' (default) */ +#define WSLUA_OPTARG_Conversation_find_PORT2 6 /* Second port. A value of `nil` or `0` is treated as 'ignore' (default) */ +#define WSLUA_OPTARG_Conversation_find_CREATE 7 /* Boolean. If conversation doesn't exist, create it (default true) */ + + uint32_t frameNum = wslua_checkuint32(L,WSLUA_ARG_Conversation_find_FRAMENUM); + Address addr1 = checkAddress(L,WSLUA_ARG_Conversation_find_ADDR1); + uint32_t port1 = wslua_optuint32(L, WSLUA_OPTARG_Conversation_find_PORT1, 0); + uint32_t port2 = wslua_optuint32(L, WSLUA_OPTARG_Conversation_find_PORT2, 0); + bool create = wslua_optbool(L,WSLUA_OPTARG_Conversation_find_CREATE,true); + + conversation_type ctype; + if (lua_isnumber(L,WSLUA_ARG_Conversation_find_CTYPE)) { + ctype = int_to_convtype_enum(wslua_checkint(L,WSLUA_ARG_Conversation_find_CTYPE)); + } else { + ctype = str_to_convtype_enum(luaL_checkstring(L,WSLUA_ARG_Conversation_find_CTYPE)); + } + + unsigned int options = 0; + + Address addr2 = NULL; + if (isAddress(L,WSLUA_OPTARG_Conversation_find_ADDR2)) { + addr2 = toAddress(L,WSLUA_OPTARG_Conversation_find_ADDR2); + } else { + /* Port A not given. Flag the option */ + options |= NO_ADDR_B; + } + + if (!port2) { + /* Port B not given. Flag the option */ + options |= NO_PORT_B; + } + + if (!port1) { + if (!port2) { + /* Neither port given. Flag that as an option */ + options |= NO_PORT_X | NO_PORTS; + } else { + /* Swap ports 1 & 2 if 2 was given, but 1 was not */ + port1 = port2; + port2 = 0; + } + } + + + Conversation conv = find_conversation(frameNum, addr1, addr2, ctype, port1, port2, options); + + if (conv == NULL && create) { + int optsNew = 0; + + if (options & NO_ADDR_B) { + optsNew |= NO_ADDR2; + } + + if (options & NO_PORT_B) { + optsNew |= NO_PORT2; + } + + if (options & NO_PORT_X) { + optsNew |= NO_PORTS; + } + + conv = conversation_new(frameNum, addr1, addr2, ctype, port1, port2, optsNew); + } + + if (conv) { + pushConversation(L, conv); + } else { + lua_pushnil(L); + } + + WSLUA_RETURN(1); /* The found or created <> instance. */ +} + +WSLUA_CONSTRUCTOR Conversation_find_by_id(lua_State* L) { + /* Searches for a `Conversation` object by id. If one does not exist and 'create' is true, one + will be created, otherwise `nil` will be returned. This is typically used if a protocol + encapsulates multiple 'sessions' or 'channels' in a single connection, and denotes this with + a 'channel id' or equivalent. */ +#define WSLUA_ARG_Conversation_find_by_id_FRAMENUM 1 /* The number of a frame within the conversation. If a new + conversation is created, this will be used as the first + frame of the conversation. */ +#define WSLUA_ARG_Conversation_find_by_id_CTYPE 2 /* Conversation Type. One of: `convtypes.NONE`, + `convtypes.SCTP`, `convtypes.TCP`, `convtypes.UDP`, `convtypes.DCCP`, `convtypes.IPX`, `convtypes.NCP`, + `convtypes.EXCHG`, `convtypes.DDP`, `convtypes.SBCCS`, `convtypes.IDP`, `convtypes.TIPC`, + `convtypes.USB`, `convtypes.I2C`, `convtypes.IBQP`, `convtypes.BLUETOOTH`, `convtypes.TDMOP`, + `convtypes.DVBCI`, `convtypes.ISO14443`, `convtypes.ISDN`, `convtypes.H223`, `convtypes.X25`, + `convtypes.IAX2`, `convtypes.DLCI`, `convtypes.ISUP`, `convtypes.BICC`, `convtypes.GSMTAP`, + `convtypes.IUUP`, `convtypes.DVBBBF`, `convtypes.IWARP_MPA`, `convtypes.BT_UTP`, `convtypes.LOG`, + `convtypes.LTP`, `convtypes.MCTP`, `convtypes.NVME_MI`, `convtypes.BP`, `convtypes.SNMP`, + `convtypes.QUIC`, `convtypes.IDN`, `convtypes.IP`, `convtypes.IPV6`, `convtypes.ETH`, + `convtypes.ETH_NN`, `convtypes.ETH_NV`, `convtypes.ETH_IN`, `convtypes.ETH_IV`, + `convtypes.VSPC_VMOTION`, `convtypes.OPENVPN`, `convtypes.PROXY`, `onvtypes.DNP3` */ +#define WSLUA_ARG_Conversation_find_by_id_ID 3 /* Conversation or session specific ID */ +#define WSLUA_OPTARG_Conversation_find_by_id_CREATE 4 /* Boolean. If conversation doesn't exist, create it (default true) */ + + uint32_t frameNum = wslua_checkuint32(L,WSLUA_ARG_Conversation_find_by_id_FRAMENUM); + uint32_t idNum = wslua_checkuint32(L,WSLUA_ARG_Conversation_find_by_id_ID); + bool create = wslua_optbool(L, WSLUA_OPTARG_Conversation_find_by_id_CREATE, true); + + conversation_type ctype; + if (lua_isnumber(L,WSLUA_ARG_Conversation_find_CTYPE)) { + ctype = int_to_convtype_enum(wslua_checkint(L,WSLUA_ARG_Conversation_find_CTYPE)); + } else { + ctype = str_to_convtype_enum(luaL_checkstring(L,WSLUA_ARG_Conversation_find_CTYPE)); + } + + Conversation conv = find_conversation_by_id(frameNum, ctype, idNum); + + if ((conv == NULL) && create) { + conv = conversation_new_by_id(frameNum, ctype, idNum); + } + + if (conv) { + pushConversation(L, conv); + } else { + lua_pushnil(L); + } + + WSLUA_RETURN(1); /* The found or created `Conversation` instance. */ +} + +WSLUA_CONSTRUCTOR Conversation_find_from_pinfo(lua_State* L) { + /* Searches for a `Conversation` object matching a pinfo. If one does not exist and 'create' is + true, one will be created, otherwise `nil` will be returned. Note that this is a shortcut for + `Conversation.find()`, where a pinfo structure is conveniently decomposed into individual + components for you. If you're not sure which `Conversation.find` method to use, most of the + time this will the the correct choice. */ +#define WSLUA_ARG_Conversation_find_from_pinfo_PINFO 1 /* A <> object. */ +#define WSLUA_OPTARG_Conversation_find_from_pinfo_CREATE 2 /* Boolean. If conversation doesn't exist, create it (default true) */ + + Pinfo p = checkPinfo(L,WSLUA_ARG_Conversation_find_from_pinfo_PINFO); + bool create = wslua_optbool(L,WSLUA_OPTARG_Conversation_find_from_pinfo_CREATE,true); + + Conversation conv; + + if (create) { + conv = find_or_create_conversation(p->ws_pinfo); + } else { + conv = find_conversation_pinfo(p->ws_pinfo, 0); + } + + if (conv) { + pushConversation(L, conv); + } else { + lua_pushnil(L); + } + + WSLUA_RETURN(1); /* The found or created `Conversation` instance. */ +} + +static bool wslua_conv_cleanup_cb(wmem_allocator_t *alloc, wmem_cb_event_t event, void* data) { + /* Conversation data is managed by wmem_file_scope(). This callback ensures the + * corresponding Lua table is correctly released. */ + (void)alloc; + if (event == WMEM_CB_FREE_EVENT) { + lua_State* L = wslua_state(); + wslua_conv_data_t *cd = (wslua_conv_data_t *)data; + if (L != NULL) { + luaL_unref(L, LUA_REGISTRYINDEX, cd->data_ref); + } + } + return false; +} + +WSLUA_METAMETHOD Conversation__newindex(lua_State* L) { + /* Sets protocol data for a specific protocol */ +#define WSLUA_ARG_Conversation__newindex_INDEX 2 /* The protocol index to set. Must be a <> */ +#define WSLUA_ARG_Conversation__newindex_VALUE 3 /* The protocol data to set (any valid lua object) */ + Conversation conv = checkConversation(L,1); + Proto proto = checkProto(L,WSLUA_ARG_Conversation__newindex_INDEX); + + luaL_checkany(L, WSLUA_ARG_Conversation__newindex_VALUE); + + wslua_conv_data_t *cd = conversation_get_proto_data(conv, proto->hfid); + + if (lua_isnoneornil(L, WSLUA_ARG_Conversation__newindex_VALUE)) { + if (cd != NULL) { + luaL_unref(L, LUA_REGISTRYINDEX, cd->data_ref); + cd->data_ref = LUA_NOREF; + } + return 0; + } + + lua_settop(L, WSLUA_ARG_Conversation__newindex_VALUE); + + if (cd == NULL) { + /* No conversation data set. Create new data saver now. */ + cd = wmem_alloc(wmem_file_scope(), sizeof(wslua_conv_data_t)); + wmem_register_callback(wmem_file_scope(), wslua_conv_cleanup_cb, cd); + + cd->conv = conv; + cd->data_ref = LUA_NOREF; + + conversation_add_proto_data(conv, proto->hfid, cd); + } + + if (cd->data_ref == LUA_NOREF) { + cd->data_ref = luaL_ref(L, LUA_REGISTRYINDEX); /* Automatically pushes 'top' */ + } else { + /* Update value */ + lua_rawseti(L, LUA_REGISTRYINDEX, cd->data_ref); + } + + return 0; +} + +WSLUA_METAMETHOD Conversation__index(lua_State* L) { + /* Get protocol data for a specific protocol */ +#define WSLUA_ARG_Conversation__index_INDEX 2 /* The protocol index to get. Must be a <> */ + Conversation conv = checkConversation(L,1); + Proto proto = checkProto(L,WSLUA_ARG_Conversation__index_INDEX); + + wslua_conv_data_t *cd = conversation_get_proto_data(conv, proto->hfid); + + if (cd == NULL || cd->data_ref == LUA_NOREF) { + lua_pushnil(L); + } else { + lua_rawgeti(L, LUA_REGISTRYINDEX, cd->data_ref); + } + + WSLUA_RETURN(1); /* Previously assigned conversation data, or `nil`. */ +} + +/* WSLUA_ATTRIBUTE Conversation_dissector WO Sets the dissector to be used for the conversation. + Accepted types are either a <> with assigned dissector, or a <>. */ +WSLUA_ATTR_SET Conversation_set_dissector(lua_State* L) { + Conversation conv = checkConversation(L,1); + Dissector handle; + + if (isProto(L,2)) { + Proto p = toProto(L,2); + handle = p->handle; + + if (!handle) { + luaL_error(L,"Proto %s has no registered dissector", p->name ? p->name : ""); + return 0; + } + } else if (isDissector(L,2)) { + handle = toDissector(L,2); + } else { + luaL_error(L,"Assigned data type must be either a Proto or Dissector"); + return 0; + } + + conversation_set_dissector(conv,handle); + + return 0; +}; + +/* This table is ultimately registered as a sub-table of the class' metatable, + * and if __index/__newindex is invoked then it calls the appropriate function + * from this table for getting/setting the members. + */ +WSLUA_ATTRIBUTES Conversation_attributes[] = { + WSLUA_ATTRIBUTE_WOREG(Conversation,dissector), + { NULL, NULL, NULL } +}; + +WSLUA_METHODS Conversation_methods[] = { + WSLUA_CLASS_FNREG(Conversation,find), + WSLUA_CLASS_FNREG(Conversation,find_by_id), + WSLUA_CLASS_FNREG(Conversation,find_from_pinfo), + { NULL, NULL } +}; + +WSLUA_META Conversation_meta[] = { + WSLUA_CLASS_MTREG(Conversation,eq), + WSLUA_CLASS_MTREG(Conversation,tostring), + WSLUA_CLASS_MTREG(Conversation,newindex), + WSLUA_CLASS_MTREG(Conversation,index), + { NULL, NULL } +}; + +int Conversation_register(lua_State* L) { + WSLUA_REGISTER_CLASS_WITH_ATTRS(Conversation); + + return 0; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/epan/wslua/wslua_internals.c b/epan/wslua/wslua_internals.c index 1b25aaed61..313bc18179 100644 --- a/epan/wslua/wslua_internals.c +++ b/epan/wslua/wslua_internals.c @@ -244,47 +244,64 @@ static int wslua_classmeta_index(lua_State *L) { */ static int wslua_instancemeta_index_impl(lua_State *L, bool is_getter) { - const char *fieldname = luaL_checkstring(L, 2); + const char *fieldname = lua_tostring(L, 2); /* NULL if not a string */ const int attr_idx = lua_upvalueindex(2); const int fallback_idx = lua_upvalueindex(3); const int methods_idx = lua_upvalueindex(4); - /* Check for getter/setter */ - if (lua_istable(L, attr_idx)) { - lua_rawgetfield(L, attr_idx, fieldname); - if (lua_iscfunction(L, -1)) { - lua_CFunction cfunc = lua_tocfunction(L, -1); - lua_pop(L, 1); /* Remove cfunction from stack */ - lua_remove(L, 2); /* Remove key from stack */ - /* - * Note: This re-uses the current closure as optimization, exposing - * its upvalues via pseudo-indices. The alternative is to create a - * new C closure (via lua_call), but this is more expensive. - * Callees should not rely on the availability of the upvalues. - */ - return (*cfunc)(L); + /* If index key is a string, it may be a class field/method */ + if (lua_isstring(L, 2)) { + + /* Check for getter/setter */ + if (lua_istable(L, attr_idx)) { + lua_rawgetfield(L, attr_idx, fieldname); + if (lua_iscfunction(L, -1)) { + lua_CFunction cfunc = lua_tocfunction(L, -1); + lua_pop(L, 1); /* Remove cfunction from stack */ + lua_remove(L, 2); /* Remove key from stack */ + /* + * Note: This re-uses the current closure as optimization, exposing + * its upvalues via pseudo-indices. The alternative is to create a + * new C closure (via lua_call), but this is more expensive. + * Callees should not rely on the availability of the upvalues. + */ + return (*cfunc)(L); + } } - } - /* If this is a getter, and the getter has methods, try them. */ - if (is_getter && lua_istable(L, methods_idx)) { - lua_rawgetfield(L, methods_idx, fieldname); - if (!lua_isnil(L, -1)) { - /* Return method from methods table. */ - return 1; + /* If this is a getter, and the getter has methods, try them. */ + if (is_getter && lua_istable(L, methods_idx)) { + lua_rawgetfield(L, methods_idx, fieldname); + if (!lua_isnil(L, -1)) { + /* Return method from methods table. */ + return 1; + } + lua_pop(L, 1); /* Remove nil from stack. */ } - lua_pop(L, 1); /* Remove nil from stack. */ } /* Use function from the class instance metatable (if any). */ if (lua_iscfunction(L, fallback_idx)) { lua_CFunction cfunc = lua_tocfunction(L, fallback_idx); - /* Note, unlike getters/setters functions, the key must be preserved! */ + /* Note, unlike getters/setters functions, the key must be preserved! It may not be a string */ return (*cfunc)(L); } const char *classname = luaL_checkstring(L, lua_upvalueindex(1)); - return luaL_error(L, "No such '%s' method/field for object type '%s'", fieldname, classname); + + if (lua_isstring(L, 2)) { + return luaL_error(L, "No such method/field '%s' for object type '%s'", fieldname, classname); + } + + /* Convert key to string, in case it isn't */ + luaL_tolstring(L, 2, NULL); + const char *keystr = lua_tostring(L, -1); + + /* Get typename */ + luaL_typename(L, 2); + const char *keytype = lua_tostring(L, -1); + + return luaL_error(L, "No such method/field '%s' (type %s) for object type '%s'", keystr, keytype, classname); } static int wslua_instancemeta_index(lua_State *L) diff --git a/epan/wslua/wslua_pinfo.c b/epan/wslua/wslua_pinfo.c index 1b0bb62135..de6cca6ae4 100644 --- a/epan/wslua/wslua_pinfo.c +++ b/epan/wslua/wslua_pinfo.c @@ -403,12 +403,16 @@ static int Pinfo_get_lo(lua_State *L) { return 1; } -/* WSLUA_ATTRIBUTE Pinfo_conversation WO Sets the packet conversation to the given Proto object. */ +/* WSLUA_ATTRIBUTE Pinfo_conversation RW + On read, returns a <> object (equivalent to ``Conversation.find_from_pinfo(pinfo, 0, True)``) + + On write, sets the <> for the current conversation (shortcut for ``pinfo.conversation.dissector = dissector``). Accepts either a <> object or a <> object with an assigned dissector */ static int Pinfo_set_conversation(lua_State *L) { Pinfo pinfo = checkPinfo(L,1); Proto proto = checkProto(L,2); conversation_t *conversation; + if (!proto->handle) { luaL_error(L,"Proto %s has no registered dissector", proto->name? proto->name:""); return 0; @@ -420,6 +424,14 @@ static int Pinfo_set_conversation(lua_State *L) { return 0; } +static int Pinfo_get_conversation(lua_State *L) { + Pinfo pinfo = checkPinfo(L,1); + Conversation conv = find_or_create_conversation(pinfo->ws_pinfo); + pushConversation(L, conv); + WSLUA_RETURN(1); +} + + /* Gets registered as metamethod automatically by WSLUA_REGISTER_CLASS/META */ static int Pinfo__gc(lua_State* L) { Pinfo pinfo = toPinfo(L,1); @@ -472,7 +484,7 @@ WSLUA_ATTRIBUTES Pinfo_attributes[] = { WSLUA_ATTRIBUTE_RWREG(Pinfo,in_error_pkt), WSLUA_ATTRIBUTE_ROREG(Pinfo,match_uint), WSLUA_ATTRIBUTE_ROREG(Pinfo,match_string), - WSLUA_ATTRIBUTE_WOREG(Pinfo,conversation), + WSLUA_ATTRIBUTE_RWREG(Pinfo,conversation), WSLUA_ATTRIBUTE_RWREG(Pinfo,p2p_dir), { NULL, NULL, NULL } }; diff --git a/epan/wslua/wslua_proto.c b/epan/wslua/wslua_proto.c index fa5c62cc31..012081f12d 100644 --- a/epan/wslua/wslua_proto.c +++ b/epan/wslua/wslua_proto.c @@ -58,7 +58,6 @@ void clear_outstanding_FuncSavers(void) { } } - WSLUA_CLASS_DEFINE(Proto,FAIL_ON_NULL("Proto")); /* A new protocol in Wireshark. diff --git a/resources/fonts/mplus1mn-bold-subset.ttf b/resources/fonts/mplus1mn-bold-subset.ttf new file mode 100644 index 0000000000..816e62f479 Binary files /dev/null and b/resources/fonts/mplus1mn-bold-subset.ttf differ diff --git a/resources/fonts/mplus1mn-bold_italic-subset.ttf b/resources/fonts/mplus1mn-bold_italic-subset.ttf new file mode 100644 index 0000000000..b8f402568e Binary files /dev/null and b/resources/fonts/mplus1mn-bold_italic-subset.ttf differ diff --git a/resources/fonts/mplus1mn-italic-subset.ttf b/resources/fonts/mplus1mn-italic-subset.ttf new file mode 100644 index 0000000000..63d3d3acf5 Binary files /dev/null and b/resources/fonts/mplus1mn-italic-subset.ttf differ diff --git a/resources/fonts/mplus1mn-regular-subset.ttf b/resources/fonts/mplus1mn-regular-subset.ttf new file mode 100644 index 0000000000..64dd8841a0 Binary files /dev/null and b/resources/fonts/mplus1mn-regular-subset.ttf differ diff --git a/resources/fonts/mplus1p-regular-fallback.ttf b/resources/fonts/mplus1p-regular-fallback.ttf new file mode 100644 index 0000000000..6746815e33 Binary files /dev/null and b/resources/fonts/mplus1p-regular-fallback.ttf differ diff --git a/resources/fonts/notoemoji-subset.ttf b/resources/fonts/notoemoji-subset.ttf new file mode 100644 index 0000000000..cac2b5c44c Binary files /dev/null and b/resources/fonts/notoemoji-subset.ttf differ diff --git a/resources/fonts/notoserif-bold-subset.ttf b/resources/fonts/notoserif-bold-subset.ttf new file mode 100644 index 0000000000..9e50e12e4d Binary files /dev/null and b/resources/fonts/notoserif-bold-subset.ttf differ diff --git a/resources/fonts/notoserif-bold_italic-subset.ttf b/resources/fonts/notoserif-bold_italic-subset.ttf new file mode 100644 index 0000000000..bedb61ff42 Binary files /dev/null and b/resources/fonts/notoserif-bold_italic-subset.ttf differ diff --git a/resources/fonts/notoserif-italic-subset.ttf b/resources/fonts/notoserif-italic-subset.ttf new file mode 100644 index 0000000000..0e3772590b Binary files /dev/null and b/resources/fonts/notoserif-italic-subset.ttf differ diff --git a/resources/fonts/notoserif-regular-subset.ttf b/resources/fonts/notoserif-regular-subset.ttf new file mode 100644 index 0000000000..8c0ee1ce33 Binary files /dev/null and b/resources/fonts/notoserif-regular-subset.ttf differ diff --git a/test/lua/conversation.lua b/test/lua/conversation.lua new file mode 100644 index 0000000000..04ac2810be --- /dev/null +++ b/test/lua/conversation.lua @@ -0,0 +1,152 @@ +---------------------------------------- +-- script-name: protofield.lua +-- test the ProtoField API +---------------------------------------- + +local testlib = require("testlib") + +local FIRSTPASS = "first-pass" +local FRAME = "frame" +local PER_FRAME = "per-frame" +local OTHER = "other" + +-- expected number of runs +local n_frames = 4 +local taptests = { + [FIRSTPASS]=14, + [FRAME]=n_frames, + [PER_FRAME]=n_frames*8-1, + [OTHER]=1, +} +testlib.init(taptests) + +------------- test script ------------ + +---------------------------------------- +local test_proto = Proto("test", "Test Proto") + +local numinits = 0 +function test_proto.init() + numinits = numinits + 1 + if numinits == 2 then + testlib.getResults() + end +end + +-- Helper functions for testing +local conv_reg = {} +function unique_conv(name, conv) + if conv == nil then + return false + end + + for k, v in pairs(conv_reg) do + if (name == k) or (conv == v) then + return false + end + end + + conv_reg[name] = conv + return true +end + +function existing_conv(conv_key, conv) + return conv_reg[conv_key] == conv +end + +-- Check functions, for pcall +function check_find(compare, fnum, ctype, addr_1, addr_2, port_1, port_2, create) + return compare == Conversation.find(fnum, ctype, addr_1, addr_2, port_1, port_2, create) +end + +function check_find_pinfo(compare, pinfo, create) + return compare == Conversation.find_from_pinfo(pinfo, create) +end + +function check_find_id(compare, frame_num, conv_type, conv_id, create) + return compare == Conversation.find_by_id(frame_num, conv_type, conv_id, create) +end + +-- Test variables, to be maintained throughout test +local frame_num = nil +local conv_type = convtypes.TCP +local conv_id = 62 +local addr1 = Address.ip('127.0.0.1') +local port1 = 65333 +local addr2 = nil +local port2 = nil + + +-- Test conversation handling +function test_proto.dissector(buf, pinfo, root) + testlib.countPacket(FRAME) + + -- First packet of dissector + if testlib.getPktCount(FRAME) == 1 then + frame_num = pinfo.number + + -- Check that create == false returns nil when conversation doesn't exist yet + testlib.test(FIRSTPASS,"Conversation.find(!p1,!p2) == nil",pcall(check_find,nil,frame_num,conv_type,addr1,addr2,nil,nil,false)) + testlib.test(FIRSTPASS,"Conversation.find(!p1, p2) == nil",pcall(check_find,nil,frame_num,conv_type,addr1,addr2,nil,port2,false)) + testlib.test(FIRSTPASS,"Conversation.find( p1,!p2) == nil",pcall(check_find,nil,frame_num,conv_type,addr1,addr2,port1,nil,false)) + testlib.test(FIRSTPASS,"Conversation.find( p1, p2) == nil",pcall(check_find,nil,frame_num,conv_type,addr1,addr2,port1,port2,false)) + + testlib.test(FIRSTPASS,"Conversation.find_from_pinfo() == nil",pcall(check_find_pinfo,nil,pinfo,false)) + testlib.test(FIRSTPASS,"Conversation.find_by_id() == nil",pcall(check_find_id,nil,frame_num,conv_type,conv_id,false)) + + -- Test conversation creation. Ensure each is unique (i.e. newly created) + testlib.test(FIRSTPASS,"Conversation.find_from_pinfo",unique_conv("pinfo", Conversation.find_from_pinfo(pinfo))) + testlib.test(FIRSTPASS,"Conversation.find_by_id",unique_conv("id",Conversation.find_by_id(frame_num,conv_type,conv_id))) + testlib.test(FIRSTPASS,"Conversation.find",unique_conv("full",Conversation.find(frame_num,conv_type,addr1,addr2,port1,port2))) + + local conv = Conversation.find_from_pinfo(pinfo) + + -- Check that nil is returned if no data has ever been assigned to the conversation + testlib.test(FIRSTPASS,"conv[proto] == nil", conv[test_proto] == nil) + + -- Check that a non-table value can be stored to and retrieved from the conversation + conv[test_proto] = 123 + testlib.test(FIRSTPASS,"conv[proto] number", type(conv[test_proto]) == "number") + testlib.test(FIRSTPASS,"conv[proto] number", conv[test_proto] == 123) + + -- Check that clearing conversation data works as expected + conv[test_proto] = nil + testlib.test(FIRSTPASS,"conv[proto] nil", conv[test_proto] == nil) + + -- Check that a table value can be stored to and retrieved from the conversation + local initial_table = {} + conv[test_proto] = initial_table + testlib.test(FIRSTPASS,"conv[proto] table", conv[test_proto] == initial_table) + + -- Check that pinfo.conversation is the same as Conversation.find_from_pinfo + testlib.test(OTHER, "pinfo.conversation == Conversation.find_from_pinfo", check_find_pinfo(pinfo.conversation, pinfo, true)) + end + + -- Ensure each frame that returned conversations are still the same + testlib.test(PER_FRAME,"existing conversation (pinfo)", existing_conv("pinfo", Conversation.find_from_pinfo(pinfo))) + testlib.test(PER_FRAME,"existing conversation (id)", existing_conv("id", Conversation.find_by_id(frame_num, conv_type, conv_id))) + testlib.test(PER_FRAME,"existing conversation (full)", existing_conv("full", Conversation.find(frame_num,conv_type,addr1,addr2,port1,port2))) + + local data = pinfo.conversation[test_proto] + + -- Check that data hasn't been cleared between frames, and is as expected + testlib.test(PER_FRAME,"stored data (non-nil)", data ~= nil) + testlib.test(PER_FRAME,"stored data (is table)", type(data) == "table") + testlib.test(PER_FRAME,"stored data (len == n-1)", #data == testlib.getPktCount(FRAME)-1) + if #data > 0 then + testlib.test(PER_FRAME,"stored data (end == n-1)", data[#data] == testlib.getPktCount(FRAME)-1) + end + + -- Store current frame number. Check number saved successfully. + data[#data+1] = testlib.getPktCount(FRAME) + testlib.test(PER_FRAME,"stored data", #data == testlib.getPktCount(FRAME)) + + -- Note: There's no need store in conv[test_proto]. Tables are passed as references + + -- Reached end of frame + testlib.pass(FRAME) +end + +-- Replace default "IP" handler, so that no conversation is created. (The built in IP dissector +-- automatically registers a pinfo conversation). +DissectorTable.get("ethertype"):add(2048, test_proto) diff --git a/test/lua/dissector.lua b/test/lua/dissector.lua index 836aa7b495..dc16287e3a 100644 --- a/test/lua/dissector.lua +++ b/test/lua/dissector.lua @@ -67,6 +67,7 @@ local default_settings = port = 65333, heur_enabled = true, heur_regmode = 1, + conv_regmode = 1, } -- for testing purposes, we want to be able to pass in changes to the defaults @@ -585,7 +586,20 @@ local function heur_dissect_dns(tvbuf,pktinfo,root) -- packets to/from the same address:port pair will just call our dissector -- function directly instead of this heuristic function -- this is a new attribute of pinfo in 1.11.3 - pktinfo.conversation = dns + if default_settings.conv_regmode == 1 then + -- This is the "shortcut" way to register conversation dissectors (added in 1.11.3) + -- For backwards compatibility, this has been kept. However, method '2' below should + -- probably be preferred. Slightly more explicit, but less ambiguous to what you're + -- actually setting. + pktinfo.conversation = dns + elseif default_settings.conv_regmode == 2 then + -- New, slightly more explicit method (added in 4.5.X) + -- This deconflicts the idea of a 'conversation' from a 'conversation dissector' + pktinfo.conversation.dissector = dns + elseif default_settings.conv_regmode == 3 then + -- Fully explicit method (added in 4.5.X) + Conversation.find_from_pinfo(pktinfo).dissector = dns + end return true end diff --git a/test/suite_wslua.py b/test/suite_wslua.py index 2be218c3b4..405e331767 100644 --- a/test/suite_wslua.py +++ b/test/suite_wslua.py @@ -49,17 +49,17 @@ def check_lua_script_real(lua_script, cap_file, check_passed, *args): @pytest.fixture def check_lua_script_verify(check_lua_script, result_file): - def check_lua_script_verify_real(lua_script, cap_file, check_stage_1=False, heur_regmode=None): + def check_lua_script_verify_real(lua_script, cap_file, check_stage_1=False, heur_regmode=None, conv_regmode=None): # First run tshark with the dissector script. - if heur_regmode is None: - tshark_proc = check_lua_script(lua_script, cap_file, check_stage_1, - '-V' - ) - else: - tshark_proc = check_lua_script(lua_script, cap_file, check_stage_1, - '-V', - '-X', 'lua_script1:heur_regmode={}'.format(heur_regmode) - ) + optargs = [] + + if heur_regmode is not None: + optargs += ['-X', 'lua_script1:heur_regmode={}'.format(heur_regmode)] + + if conv_regmode is not None: + optargs += ['-X', 'lua_script1:conv_regmode={}'.format(conv_regmode)] + + tshark_proc = check_lua_script(lua_script, cap_file, check_stage_1, '-V', *optargs) # then dump tshark's output to a verification file. verify_file = result_file('testin.txt') @@ -118,18 +118,30 @@ def test_wslua_util(self, check_lua_script): # Mode_1, mode_2, and mode_3, and fpm were all under wslua_step_dissector_test # in the Bash version. - def test_wslua_dissector_mode_1(self, check_lua_script_verify): - '''wslua dissector functions, mode 1''' + def test_wslua_heur_dissector_mode_1(self, check_lua_script_verify): + '''wslua heuristic dissector functions, mode 1''' check_lua_script_verify('dissector.lua', dns_port_pcap) - def test_wslua_dissector_mode_2(self, check_lua_script_verify): - '''wslua dissector functions, mode 2''' + def test_wslua_heur_dissector_mode_2(self, check_lua_script_verify): + '''wslua heuristic dissector functions, mode 2''' check_lua_script_verify('dissector.lua', dns_port_pcap, heur_regmode=2) - def test_wslua_dissector_mode_3(self, check_lua_script_verify): - '''wslua dissector functions, mode 3''' + def test_wslua_heur_dissector_mode_3(self, check_lua_script_verify): + '''wslua heuristic dissector functions, mode 3''' check_lua_script_verify('dissector.lua', dns_port_pcap, heur_regmode=3) + def test_wslua_conv_dissector_mode_1(self, check_lua_script_verify): + '''wslua conversation dissector functions, mode 1''' + check_lua_script_verify('dissector.lua', dns_port_pcap) + + def test_wslua_conv_dissector_mode_2(self, check_lua_script_verify): + '''wslua conversation dissector functions, mode 2''' + check_lua_script_verify('dissector.lua', dns_port_pcap, conv_regmode=2) + + def test_wslua_conv_dissector_mode_3(self, check_lua_script_verify): + '''wslua conversation dissector functions, mode 3''' + check_lua_script_verify('dissector.lua', dns_port_pcap, conv_regmode=3) + def test_wslua_dissector_fpm(self, check_lua_script): '''wslua dissector functions, fpm''' tshark_fpm_tcp_proc = check_lua_script('dissectFPM.lua', segmented_fpm_pcap, False, @@ -311,6 +323,10 @@ def test_wslua_add_packet_field(self, check_lua_script): '''wslua add_packet_field''' check_lua_script('add_packet_field.lua', dns_port_pcap, True) + def test_wslua_conversation(self, check_lua_script): + '''wslua conversation''' + check_lua_script('conversation.lua', dns_port_pcap, True) + class TestWsluaUnicode: def test_wslua_unicode(self, cmd_tshark, features, dirs, capture_file, unicode_env): '''Check handling of unicode paths.''' diff --git a/tools/macos-setup.sh b/tools/macos-setup.sh index 4616bfc4b6..0812347bea 100755 --- a/tools/macos-setup.sh +++ b/tools/macos-setup.sh @@ -200,7 +200,7 @@ OPENCORE_AMR_SHA256=483eb4061088e2b34b358e47540b5d495a96cd468e361050fae615b1809d OPUS_VERSION=1.4 # Falco libs (libsinsp and libscap) and their dependencies. Unset for now. -#FALCO_LIBS_VERSION=0.18.1 +FALCO_LIBS_VERSION=0.18.1 if [ "$FALCO_LIBS_VERSION" ] ; then FALCO_LIBS_SHA256=1812e8236c4cb51d3fe5dd066d71be99f25da7ed22d8feeeebeed09bdc26325f JSONCPP_VERSION=1.9.5 @@ -226,7 +226,7 @@ else PYTHON3_VERSION=3.12.1 fi BROTLI_VERSION=1.0.9 -# minizip +# minizip/minizipng MINIZIPNG_VERSION=4.0.7 ZLIB_VERSION=1.3 # Uncomment to enable automatic updates using Sparkle @@ -239,7 +239,7 @@ ZLIB_VERSION=1.3 # dependencies can become quite hairy: # https://github.com/Homebrew/homebrew-core/blob/master/Formula/a/asciidoctor.rb # Maybe we should install a JRE and use AsciidoctorJ instead? -ASCIIDOCTOR_VERSION=${ASCIIDOCTOR_VERSION-2.0.16} +ASCIIDOCTOR_VERSION=${ASCIIDOCTOR_VERSION-2.0.23} ASCIIDOCTORPDF_VERSION=${ASCIIDOCTORPDF_VERSION-1.6.1} # css_parser 1.13 and later require Ruby 2.7 @@ -248,6 +248,7 @@ CSS_PARSER_VERSION=${CSS_PARSER_VERSION-1.12.0} # GNU autotools. They're not supplied with the macOS versions we # support, and we currently use them for minizip. # +M4_VERSION=1.4.19 AUTOCONF_VERSION=2.72 AUTOMAKE_VERSION=1.17 LIBTOOL_VERSION=2.5.4 @@ -375,7 +376,7 @@ uninstall_pcre() { install_pcre2() { if [ "$PCRE2_VERSION" ] && [ ! -f "pcre2-$PCRE2_VERSION-done" ] ; then echo "Downloading, building, and installing pcre2:" - [ -f "pcre2-$PCRE2_VERSION.tar.bz2" ] || curl "${CURL_REMOTE_NAME_OPTS[@]}" "https://github.com/PhilipHazel/pcre2/releases/download/pcre2-$PCRE2_VERSION/pcre2-10.39.tar.bz2" + [ -f "pcre2-$PCRE2_VERSION.tar.bz2" ] || curl "${CURL_REMOTE_NAME_OPTS[@]}" "https://github.com/PhilipHazel/pcre2/releases/download/pcre2-$PCRE2_VERSION/pcre2-$PCRE2_VERSION.tar.bz2" $no_build && echo "Skipping installation" && return bzcat "pcre2-$PCRE2_VERSION.tar.bz2" | tar xf - cd "pcre2-$PCRE2_VERSION" @@ -410,6 +411,47 @@ uninstall_pcre2() { fi } +install_m4() { + if [ "$M4_VERSION" -a ! -f m4-$M4_VERSION-done ] ; then + echo "Downloading, building and installing GNU m4..." + [ -f m4-$M4_VERSION.tar.xz ] || curl "${CURL_REMOTE_NAME_OPTS[@]}" https://ftp.gnu.org/gnu/m4/m4-$M4_VERSION.tar.xz + $no_build && echo "Skipping installation" && return + xzcat m4-$M4_VERSION.tar.xz | tar xf - + cd m4-$M4_VERSION + ./configure "${CONFIGURE_OPTS[@]}" + make "${MAKE_BUILD_OPTS[@]}" + $DO_MAKE_INSTALL + cd .. + touch m4-$M4_VERSION-done + fi +} + +uninstall_m4() { + if [ -n "$installed_m4_version" ] ; then + # + # autoconf depends on this, so uninstall it. + # + uninstall_autoconf "$@" + + echo "Uninstalling GNU m4:" + cd m4-$installed_m4_version + $DO_MAKE_UNINSTALL + make distclean + cd .. + rm m4-$installed_m4_version-done + + if [ "$#" -eq 1 -a "$1" = "-r" ] ; then + # + # Get rid of the previously downloaded and unpacked version. + # + rm -rf m4-$installed_m4_version + rm -rf m4-$installed_m4_version.tar.xz + fi + + installed_m4_version="" + fi +} + install_autoconf() { if [ "$AUTOCONF_VERSION" -a ! -f autoconf-$AUTOCONF_VERSION-done ] ; then echo "Downloading, building and installing GNU autoconf..." @@ -3425,6 +3467,8 @@ install_all() { # install_xz + install_m4 + install_autoconf install_automake @@ -3469,12 +3513,14 @@ install_all() { install_gettext # - # GLib depends on pkg-config. + # GLib depends on pkg-config and libxml2. # By default, pkg-config depends on GLib; we break the dependency cycle # by configuring pkg-config to use its own internal version of GLib. # install_pkg_config + install_libxml2 + install_glib # @@ -3517,8 +3563,6 @@ install_all() { install_zlibng - install_libxml2 - install_lz4 install_sbc @@ -3549,10 +3593,10 @@ install_all() { install_brotli - install_minizip - install_minizip_ng + install_minizip + install_sparkle install_re2 @@ -3624,8 +3668,6 @@ uninstall_all() { uninstall_zlibng - uninstall_libxml2 - uninstall_lz4 uninstall_sbc @@ -3652,6 +3694,8 @@ uninstall_all() { uninstall_glib + uninstall_libxml2 + uninstall_pkg_config uninstall_gettext @@ -3681,6 +3725,8 @@ uninstall_all() { uninstall_autoconf + uninstall_m4 + uninstall_pcre # Legacy, remove diff --git a/tools/validate-commit.py b/tools/validate-commit.py index 8d92aae79e..f8438433de 100755 --- a/tools/validate-commit.py +++ b/tools/validate-commit.py @@ -24,7 +24,7 @@ parser = argparse.ArgumentParser() -parser.add_argument('commits', nargs='+', default=['HEAD'], +parser.add_argument('commits', nargs='*', default=['HEAD'], help='Commit ID to be checked (default %(default)s)') parser.add_argument('--commitmsg', help='commit-msg check', action='store') diff --git a/tshark.c b/tshark.c index 92bf3f46c9..bb6c8c948c 100644 --- a/tshark.c +++ b/tshark.c @@ -3634,6 +3634,8 @@ process_cap_file_first_pass(capture_file *cf, int max_packet_count, cf->provider.prev_dis = NULL; cf->provider.prev_cap = NULL; + wtap_rec_cleanup(&rec); + return status; } diff --git a/ui/qt/widgets/traffic_tab.cpp b/ui/qt/widgets/traffic_tab.cpp index ade8ef64cd..7b68d198cb 100644 --- a/ui/qt/widgets/traffic_tab.cpp +++ b/ui/qt/widgets/traffic_tab.cpp @@ -133,14 +133,20 @@ QTreeView * TrafficTab::createTree(int protoId) tree->setSelectionModel(ism); connect(ism, &QItemSelectionModel::currentChanged, this, &TrafficTab::doCurrentIndexChange); + // Initially resize to the header widths (inc. hidden/filtered columns). + for (int col = 0; col < tree->model()->columnCount(); col++) { + tree->resizeColumnToContents(col); + } + tree->applyRecentColumns(); tree->sortByColumn(0, Qt::AscendingOrder); connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, [tree]() { if (tree->model()->rowCount() > 0) { - for (int col = 0; col < tree->model()->columnCount(); col++) - tree->resizeColumnToContents(col); + for (int col = 0; col < tree->model()->columnCount(); col++) { + tree->widenColumnToContents(col); + } } }); connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, &TrafficTab::modelReset); diff --git a/ui/qt/widgets/traffic_tree.cpp b/ui/qt/widgets/traffic_tree.cpp index 2114ec2e7a..d7c056eb9f 100644 --- a/ui/qt/widgets/traffic_tree.cpp +++ b/ui/qt/widgets/traffic_tree.cpp @@ -821,6 +821,23 @@ void TrafficTree::resizeAction() resizeColumnToContents(col); } +void TrafficTree::widenColumnToContents(int col) +{ + if (!model()) + return; + + if (col < 0 || col >= model()->columnCount()) + return; + + int content_width = sizeHintForColumn(col); + if (!isHeaderHidden()) { + content_width = qMax(content_width, header()->sectionSizeHint(col)); + } + if (content_width > columnWidth(col)) { + setColumnWidth(col, content_width); + } +} + void TrafficTree::toggleSaveRawAction() { if (_exportRole == ATapDataModel::UNFORMATTED_DISPLAYDATA) diff --git a/ui/qt/widgets/traffic_tree.h b/ui/qt/widgets/traffic_tree.h index 00d9178397..bd38a8c329 100644 --- a/ui/qt/widgets/traffic_tree.h +++ b/ui/qt/widgets/traffic_tree.h @@ -137,6 +137,16 @@ class TrafficTree : public QTreeView QMenu * createCopyMenu(QWidget * parent = nullptr); void applyRecentColumns(); + /** + * @brief Increase column width if necessary to fit contents, but don't + * narrow it. + * + * This is used to ensure that the columns are wide enough for newly + * received data, but to avoid narrowing columns that have been manually + * widened, especially the Rel Start/Abs Start and Duration columns, + * since those contain a timeline graph. + */ + void widenColumnToContents(int column); virtual void setModel(QAbstractItemModel *model) override; diff --git a/ui/text_import.c b/ui/text_import.c index 0b79a23efe..fbef1be3f0 100644 --- a/ui/text_import.c +++ b/ui/text_import.c @@ -220,6 +220,7 @@ static const char *state_str[] = {"Init", static const char *token_str[] = {"", "Byte", + "Bytes", "Offset", "Directive", "Text", diff --git a/wiretap/log3gpp.c b/wiretap/log3gpp.c index 9963bc1aca..437acf53dc 100644 --- a/wiretap/log3gpp.c +++ b/wiretap/log3gpp.c @@ -648,7 +648,7 @@ bool parse_line(char* linebuff, int line_length, int *seconds, int *useconds, n++; /* Now skip ahead to find start of data (marked by '$') */ - for (; (n <= line_length) && (linebuff[n] != '$') && (prot_option_chars <= MAX_PROTOCOL_PAR_STRING); + for (; (n < line_length) && (linebuff[n] != '$') && (prot_option_chars < MAX_PROTOCOL_PAR_STRING); n++,prot_option_chars++) { protocol_parameters[prot_option_chars] = linebuff[n];