diff options
Diffstat (limited to '')
-rw-r--r-- | epan/dissectors/packet-dcm.c | 4534 |
1 files changed, 4534 insertions, 0 deletions
diff --git a/epan/dissectors/packet-dcm.c b/epan/dissectors/packet-dcm.c new file mode 100644 index 00000000..fab18c61 --- /dev/null +++ b/epan/dissectors/packet-dcm.c @@ -0,0 +1,4534 @@ +/* packet-dcm.c + * Routines for DICOM dissection + * Copyright 2003, Rich Coe <richcoe2@gmail.com> + * Copyright 2008-2019, David Aggeler <david_aggeler@hispeed.ch> + * + * DICOM communication protocol: https://www.dicomstandard.org/current/ + * + * Part 5: Data Structures and Encoding + * Part 6: Data Dictionary + * Part 7: Message Exchange + * Part 8: Network Communication Support for Message Exchange + * Part 10: Media Storage and File Format + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + + +/* + * + * ToDo + * + * - Implement value multiplicity (VM) consistently in dissect_dcm_tag_value() + * - Syntax detection, in case an association request is missing in capture + * - Read private tags from configuration and parse in capture + * + * History + * + * Feb 2019 - David Aggeler + * + * - Fixed re-assembly and export (consolidated duplicate code) + * - Fixed random COL_INFO issues + * - Improved COL_INFO for C-FIND + * - Improved COL_INFO for multiple PDUs in one frame + * + * Feb 2019 - Rickard Holmberg + * + * - Updated DICOM definitions to 2019a + * + * Oct 2018 - Rickard Holmberg + * + * - Moved DICOM definitions to packet-dcm.h + * - Generate definitions from docbook with Phyton script + * - Updated DICOM definitions to 2018e + * + * June 2018 - David Aggeler + * + * - Fixed initial COL_INFO for associations. It used to 'append' instead of 'set'. + * - Changed initial length check from tvb_reported_length() to tvb_captured_length() + * - Heuristic Dissection: + * o Modified registration, so it can be clearly identified in the Enable/Disable Protocols dialog + * o Enabled by default + * o Return proper data type + * + * February 2018 - David Aggeler + * + * - Fixed Bug 14415. Some tag descriptions which are added to the parent item (32 tags). + * If one of those was empty a crash occurred. Mainly the RTPlan modality was affected. + * - Fixed length decoding for OD, OL, UC, UR + * - Fixed hf_dcm_assoc_item_type to be interpreted as 1 byte + * - Fixed pdu_type to be interpreted as 1 byte + * - Fixed decoding of AT type, where value length was wrongly reported in capture as 2 (instead of n*4) + * + * Misc. authors & dates + * + * - Fixed 'AT' value representation. The 'element' was equal to the 'group'. + * - Changed 'FL' value representations + * + * September 2013 - Pascal Quantin + * + * - Replace all ep_ and se_ allocation with wmem_ allocations + * + * February 2013 - Stefan Allers + * + * - Support for dissection of Extended Negotiation (Query/Retrieve) + * - Support for dissection of SCP/SCU Role Selection + * - Support for dissection of Async Operations Window Negotiation + * - Fixed: Improper calculation of length for Association Header + * - Missing UIDs (Transfer Syntax, SOP Class...) added acc. PS 3.x-2011 + * + * Jul 11, 2010 - David Aggeler + * + * - Finally, better reassembly using fragment_add_seq_next(). + * The previous mode is still supported. + * - Fixed sporadic decoding and export issues. Always decode + * association negotiation, since performance check (tree==NULL) + * is now only in dissect_dcm_pdv_fragmented(). + * - Added one more PDV length check + * - Show Association Headers as individual items + * - Code cleanup. i.e. moved a few lookup functions to be closer to the dissection + * + * May 13, 2010 - David Aggeler (SVN 32815) + * + * - Fixed HF to separate signed & unsigned values and to have BASE_DEC all signed ones + * - Fixed private sequences with undefined length in ILE + * - Fixed some spellings in comments + * + * May 27, 2009 - David Aggeler (SVN 29060) + * + * - Fixed corrupt files on DICOM Export + * - Fixed memory limitation on DICOM Export + * - Removed minimum packet length for static port mode + * - Simplified checks for heuristic mode + * - Removed unused functions + * + * May 17, 2009 - David Aggeler (SVN 28392) + * + * - Spelling + * - Added expert_add_info() for status responses with warning & error level + * - Added command details in info column (optionally) + * + * Dec 19, 2008 to Mar 29, 2009 - Misc (SVN 27880) + * + * - Spellings, see SVN + * + * Oct 26, 2008 - David Aggeler (SVN 26662) + * + * - Support remaining DICOM/ARCNEMA tags + * + * Oct 3, 2008 - David Aggeler (SVN 26417) + * + * - DICOM Tags: Support all tags, except for group 1000, 7Fxx + * and tags (0020,3100 to 0020, 31FF). + * Luckily these ones are retired anyhow + * - DICOM Tags: Optionally show sequences, items and tags as subtree + * - DICOM Tags: Certain items do have a summary of a few contained tags + * - DICOM Tags: Support all defined VR representations + * - DICOM Tags: For Explicit Syntax, use VR in the capture + * - DICOM Tags: Lookup UIDs + * - DICOM Tags: Handle split at PDV start and end. RT Structures were affected by this. + * - DICOM Tags: Handle split in tag header + * + * - Added all status messages from PS 3.4 & PS 3.7 + * - Fixed two more type warnings on solaris, i.e. (gchar *)tvb_get_ephemeral_string + * - Replaced all ep_alloc() with ep_alloc0() and se_alloc() with se_alloc0() + * - Replaced g_strdup with ep_strdup() or se_strdup() + * - Show multiple PDU description in COL_INFO, not just last one. Still not all, but more + * sophisticated logic for this column is probably overkill + * - Since DICOM is a 32 bit protocol with all length items specified unsigned + * all offset & position variables are now declared as guint32 for dissect_dcm_pdu and + * its nested functions. dissect_dcm_main() remained by purpose on int, + * since we request data consolidation, requiring a TRUE as return value + * - Decode DVTk streams when using defined ports (not in heuristic mode) + * - Changed to warning level 4 (for MSVC) and fixed the warnings + * - Code cleanup & removed last DISSECTOR_ASSERT() + * + * Jul 25, 2008 - David Aggeler (SVN 25834) + * + * - Replaced guchar with gchar, since it caused a lot of warnings on solaris. + * - Moved a little more form the include to this one to be consistent + * + * Jul 17, 2008 - David Aggeler + * + * - Export objects as part 10 compliant DICOM file. Finally, this major milestone has been reached. + * - PDVs are now a child of the PCTX rather than the ASSOC object. + * - Fixed PDV continuation for unknown tags (e.g. RT Structure Set) + * - Replaced proprietary trim() with g_strstrip() + * - Fixed strings that are displayed with /000 (padding of odd length) + * - Added expert_add_info() for invalid flags and presentation context IDs + * + * Jun 17, 2008 - David Aggeler + * + * - Support multiple PDVs per PDU + * - Better summary, in PDV, PDU header and in INFO Column, e.g. show commands like C-STORE + * - Fixed Association Reject (was working before my changes) + * - Fixed PDV Continuation with very small packets. Reduced minimum packet length + * from 10 to 2 Bytes for PDU Type 4 + * - Fixed PDV Continuation. Last packet was not found correctly. + * - Fixed compilation warning (build 56 on solaris) + * - Fixed tree expansion (hf_dcm_xxx) + * - Added expert_add_info() for Association Reject + * - Added expert_add_info() for Association Abort + * - Added expert_add_info() for short PDVs (i.e. last fragment, but PDV is not completed yet) + * - Clarified and grouped data structures and its related code (dcmItem, dcmState) to have + * consistent _new() & _get() functions and to be according to coding conventions + * - Added more function declaration to be more consistent + * - All dissect_dcm_xx now have (almost) the same parameter order + * - Removed DISSECTOR_ASSERT() for packet data errors. Not designed to handle this. + * - Handle multiple DICOM Associations in a capture correctly, i.e. if presentation contexts are different. + * + * May 23, 2008 - David Aggeler + * + * - Added Class UID lookup, both in the association and in the transfer + * - Better hierarchy for items in Association request/response and therefore better overview + * This was a major rework. Abstract Syntax & Transfer Syntax are now children + * of a presentation context and therefore grouped. User Info is now grouped. + * - Re-assemble PDVs that span multiple PDUs, i.e fix continuation packets + * This caused significant changes to the data structures + * - Added preference with DICOM TCP ports, to prevent 'stealing' the conversation + * i.e. don't just rely on heuristic + * - Use pinfo->desegment_len instead of tcp_dissect_pdus() + * - Returns number of bytes parsed + * - For non DICOM packets, do not allocate any memory anymore, + * - Added one DISSECTOR_ASSERT() to prevent loop with len==0. More to come + * - Heuristic search is optional to save resources for non DICOM users + * + * - Output naming closer to DICOM Standard + * - Variable names closer to Standard + * - Protocol in now called DICOM not dcm anymore. + * - Fixed type of a few variables to guchar instead of guint8 + * - Changed some of the length displays to decimal, because the hex value can + * already be seen in the packet and decimal is easier for length calculation + * in respect to TCP + * + * Apr 28, 2005 - Rich Coe + * + * - fix memory leak when Assoc packet is processed repeatedly in wireshark + * - removed unused partial packet flag + * - added better support for DICOM VR + * - sequences + * - report actual VR in packet display, if supplied by xfer syntax + * - show that we are not displaying entire tag string with '[...]', + * some tags can hold up to 2^32-1 chars + * + * - remove my goofy attempt at trying to get access to the fragmented packets + * - process all the data in the Assoc packet even if display is off + * - limit display of data in Assoc packet to defined size of the data even + * if reported size is larger + * - show the last tag in a packet as [incomplete] if we don't have all the data + * - added framework for reporting DICOM async negotiation (not finished) + * (I'm not aware of an implementation which currently supports this) + * + * Nov 9, 2004 - Rich Coe + * + * - Fixed the heuristic code -- sometimes a conversation already exists + * - Fixed the dissect code to display all the tags in the PDU + * + * Initial - Rich Coe + * + * - It currently displays most of the DICOM packets. + * - I've used it to debug Query/Retrieve, Storage, and Echo protocols. + * - Not all DICOM tags are currently displayed symbolically. + * Unknown tags are displayed as '(unknown)' + * More known tags might be added in the future. + * If the tag data contains a string, it will be displayed. + * Even if the tag contains Explicit VR, it is not currently used to + * symbolically display the data. + * + */ + +#include "config.h" + +#include <epan/packet.h> +#include <epan/exceptions.h> +#include <epan/prefs.h> +#include <epan/expert.h> +#include <epan/tap.h> +#include <epan/reassemble.h> +#include <epan/export_object.h> + +#include <wsutil/str_util.h> +#include <wsutil/utf8_entities.h> + +#include "packet-tcp.h" + +#include "packet-dcm.h" + +void proto_register_dcm(void); +void proto_reg_handoff_dcm(void); + +#define DICOM_DEFAULT_RANGE "104" + +/* Many thanks to http://medicalconnections.co.uk/ for the GUID */ +#define WIRESHARK_IMPLEMENTATION_UID "1.2.826.0.1.3680043.8.427.10" +#define WIRESHARK_MEDIA_STORAGE_SOP_CLASS_UID "1.2.826.0.1.3680043.8.427.11.1" +#define WIRESHARK_MEDIA_STORAGE_SOP_INSTANCE_UID_PREFIX "1.2.826.0.1.3680043.8.427.11.2" +#define WIRESHARK_IMPLEMENTATION_VERSION "WIRESHARK" + +static gboolean global_dcm_export_header = TRUE; +static guint global_dcm_export_minsize = 4096; /* Filter small objects in export */ + +static gboolean global_dcm_seq_subtree = TRUE; +static gboolean global_dcm_tag_subtree = FALSE; /* Only useful for debugging */ +static gboolean global_dcm_cmd_details = TRUE; /* Show details in header and info column */ +static gboolean global_dcm_reassemble = TRUE; /* Merge fragmented PDVs */ + +static wmem_map_t *dcm_tag_table = NULL; +static wmem_map_t *dcm_uid_table = NULL; +static wmem_map_t *dcm_status_table = NULL; + +/* Initialize the protocol and registered fields */ +static int proto_dcm = -1; + +static int dicom_eo_tap = -1; + +static int hf_dcm_pdu_type = -1; +static int hf_dcm_pdu_len = -1; +static int hf_dcm_assoc_version = -1; +static int hf_dcm_assoc_called = -1; +static int hf_dcm_assoc_calling = -1; +static int hf_dcm_assoc_reject_result = -1; +static int hf_dcm_assoc_reject_source = -1; +static int hf_dcm_assoc_reject_reason = -1; +static int hf_dcm_assoc_abort_source = -1; +static int hf_dcm_assoc_abort_reason = -1; +static int hf_dcm_assoc_item_type = -1; +static int hf_dcm_assoc_item_len = -1; +static int hf_dcm_actx = -1; +static int hf_dcm_pctx_id = -1; +static int hf_dcm_pctx_result = -1; +static int hf_dcm_pctx_abss_syntax = -1; +static int hf_dcm_pctx_xfer_syntax = -1; +static int hf_dcm_info = -1; +static int hf_dcm_info_uid = -1; +static int hf_dcm_info_version = -1; +static int hf_dcm_info_extneg = -1; +static int hf_dcm_info_extneg_sopclassuid_len = -1; +static int hf_dcm_info_extneg_sopclassuid = -1; +static int hf_dcm_info_extneg_relational_query = -1; +static int hf_dcm_info_extneg_date_time_matching = -1; +static int hf_dcm_info_extneg_fuzzy_semantic_matching = -1; +static int hf_dcm_info_extneg_timezone_query_adjustment = -1; +static int hf_dcm_info_rolesel = -1; +static int hf_dcm_info_rolesel_sopclassuid_len = -1; +static int hf_dcm_info_rolesel_sopclassuid = -1; +static int hf_dcm_info_rolesel_scurole = -1; +static int hf_dcm_info_rolesel_scprole = -1; +static int hf_dcm_info_async_neg = -1; +static int hf_dcm_info_async_neg_max_num_ops_inv = -1; +static int hf_dcm_info_async_neg_max_num_ops_per = -1; +static int hf_dcm_info_user_identify = -1; +static int hf_dcm_info_user_identify_type = -1; +static int hf_dcm_info_user_identify_response_requested = -1; +static int hf_dcm_info_user_identify_primary_field_length = -1; +static int hf_dcm_info_user_identify_primary_field = -1; +static int hf_dcm_info_user_identify_secondary_field_length = -1; +static int hf_dcm_info_user_identify_secondary_field = -1; +static int hf_dcm_info_unknown = -1; +static int hf_dcm_assoc_item_data = -1; +static int hf_dcm_pdu_maxlen = -1; +static int hf_dcm_pdv_len = -1; +static int hf_dcm_pdv_ctx = -1; +static int hf_dcm_pdv_flags = -1; +static int hf_dcm_data_tag = -1; +static int hf_dcm_tag = -1; +static int hf_dcm_tag_vr = -1; +static int hf_dcm_tag_vl = -1; +static int hf_dcm_tag_value_str = -1; +static int hf_dcm_tag_value_16u = -1; +static int hf_dcm_tag_value_16s = -1; +static int hf_dcm_tag_value_32s = -1; +static int hf_dcm_tag_value_32u = -1; +static int hf_dcm_tag_value_byte = -1; + +/* Initialize the subtree pointers */ +static gint ett_dcm = -1; +static gint ett_assoc = -1; +static gint ett_assoc_header = -1; +static gint ett_assoc_actx = -1; +static gint ett_assoc_pctx = -1; +static gint ett_assoc_pctx_abss = -1; +static gint ett_assoc_pctx_xfer = -1; +static gint ett_assoc_info = -1; +static gint ett_assoc_info_uid = -1; +static gint ett_assoc_info_version = -1; +static gint ett_assoc_info_extneg = -1; +static gint ett_assoc_info_rolesel = -1; +static gint ett_assoc_info_async_neg = -1; +static gint ett_assoc_info_user_identify = -1; +static gint ett_assoc_info_unknown = -1; +static gint ett_dcm_data = -1; +static gint ett_dcm_data_pdv = -1; +static gint ett_dcm_data_tag = -1; +static gint ett_dcm_data_seq = -1; +static gint ett_dcm_data_item = -1; + +static expert_field ei_dcm_data_tag = EI_INIT; +static expert_field ei_dcm_multiple_transfer_syntax = EI_INIT; +static expert_field ei_dcm_pdv_len = EI_INIT; +static expert_field ei_dcm_pdv_flags = EI_INIT; +static expert_field ei_dcm_pdv_ctx = EI_INIT; +static expert_field ei_dcm_no_abstract_syntax = EI_INIT; +static expert_field ei_dcm_no_abstract_syntax_uid = EI_INIT; +static expert_field ei_dcm_status_msg = EI_INIT; +static expert_field ei_dcm_no_transfer_syntax = EI_INIT; +static expert_field ei_dcm_multiple_abstract_syntax = EI_INIT; +static expert_field ei_dcm_invalid_pdu_length = EI_INIT; +static expert_field ei_dcm_assoc_item_len = EI_INIT; +static expert_field ei_dcm_assoc_rejected = EI_INIT; +static expert_field ei_dcm_assoc_aborted = EI_INIT; + +static dissector_handle_t dcm_handle; + +static const value_string dcm_pdu_ids[] = { + { 1, "ASSOC Request" }, + { 2, "ASSOC Accept" }, + { 3, "ASSOC Reject" }, + { 4, "Data" }, + { 5, "RELEASE Request" }, + { 6, "RELEASE Response" }, + { 7, "ABORT" }, + { 0, NULL } +}; + +static const value_string dcm_assoc_item_type[] = { + { 0x10, "Application Context" }, + { 0x20, "Presentation Context" }, + { 0x21, "Presentation Context Reply" }, + { 0x30, "Abstract Syntax" }, + { 0x40, "Transfer Syntax" }, + { 0x50, "User Info" }, + { 0x51, "Max Length" }, + { 0x52, "Implementation Class UID" }, + { 0x53, "Asynchronous Operations Window Negotiation" }, + { 0x54, "SCP/SCU Role Selection" }, + { 0x55, "Implementation Version" }, + { 0x56, "SOP Class Extended Negotiation" }, + { 0x58, "User Identity" }, + { 0, NULL } +}; + +static const value_string user_identify_type_vals[] = { + { 1, "Username as a string in UTF-8" }, + { 2, "Username as a string in UTF-8 and passcode" }, + { 3, "Kerberos Service ticket" }, + { 4, "SAML Assertion" }, + { 0, NULL } +}; + +/* Used for DICOM Export Object feature */ +typedef struct _dicom_eo_t { + guint32 pkt_num; + const gchar *hostname; + const gchar *filename; + const gchar *content_type; + guint32 payload_len; + const guint8 *payload_data; +} dicom_eo_t; + +static tap_packet_status +dcm_eo_packet(void *tapdata, packet_info *pinfo, epan_dissect_t *edt _U_, + const void *data, tap_flags_t flags _U_) +{ + export_object_list_t *object_list = (export_object_list_t *)tapdata; + const dicom_eo_t *eo_info = (const dicom_eo_t *)data; + export_object_entry_t *entry; + + if (eo_info) { /* We have data waiting for us */ + /* + The values will be freed when the export Object window is closed. + Therefore, strings and buffers must be copied. + */ + entry = g_new(export_object_entry_t, 1); + + entry->pkt_num = pinfo->num; + entry->hostname = g_strdup(eo_info->hostname); + entry->content_type = g_strdup(eo_info->content_type); + /* g_path_get_basename() allocates a new string */ + entry->filename = g_path_get_basename(eo_info->filename); + entry->payload_len = eo_info->payload_len; + entry->payload_data = (guint8 *)g_memdup2(eo_info->payload_data, eo_info->payload_len); + + object_list->add_entry(object_list->gui_data, entry); + + return TAP_PACKET_REDRAW; /* State changed - window should be redrawn */ + } else { + return TAP_PACKET_DONT_REDRAW; /* State unchanged - no window updates needed */ + } +} + + +/* ************************************************************************* */ +/* Fragment items */ +/* ************************************************************************* */ + +/* Initialize the subtree pointers */ +static gint ett_dcm_pdv = -1; + +static gint ett_dcm_pdv_fragment = -1; +static gint ett_dcm_pdv_fragments = -1; + +static int hf_dcm_pdv_fragments = -1; +static int hf_dcm_pdv_fragment = -1; +static int hf_dcm_pdv_fragment_overlap = -1; +static int hf_dcm_pdv_fragment_overlap_conflicts = -1; +static int hf_dcm_pdv_fragment_multiple_tails = -1; +static int hf_dcm_pdv_fragment_too_long_fragment = -1; +static int hf_dcm_pdv_fragment_error = -1; +static int hf_dcm_pdv_fragment_count = -1; +static int hf_dcm_pdv_reassembled_in = -1; +static int hf_dcm_pdv_reassembled_length = -1; + +static const fragment_items dcm_pdv_fragment_items = { + /* Fragment subtrees */ + &ett_dcm_pdv_fragment, + &ett_dcm_pdv_fragments, + /* Fragment fields */ + &hf_dcm_pdv_fragments, + &hf_dcm_pdv_fragment, + &hf_dcm_pdv_fragment_overlap, + &hf_dcm_pdv_fragment_overlap_conflicts, + &hf_dcm_pdv_fragment_multiple_tails, + &hf_dcm_pdv_fragment_too_long_fragment, + &hf_dcm_pdv_fragment_error, + &hf_dcm_pdv_fragment_count, + &hf_dcm_pdv_reassembled_in, + &hf_dcm_pdv_reassembled_length, + /* Reassembled data field */ + NULL, + /* Tag */ + "Message fragments" +}; + +/* Structure to handle fragmented DICOM PDU packets */ +static reassembly_table dcm_pdv_reassembly_table; + +typedef struct dcm_open_tag { + + /* Contains information about an open tag in a PDV, in case it was not complete. + + This implementation differentiates between open headers (grm, elm, vr, vl) and + open values. This data structure will handle both cases. + + Open headers are not shown in the packet where the tag starts, but only in the next PDV. + Open values are shown in the packet where the tag starts, with <Byte 1-n> as the value + + The same PDV can close an open tag from a previous PDV at the beginning + and at the same have time open a new tag at the end. The closing part at the beginning + does not have its own persistent data. + + Do not overwrite the values, once defined, to save some memory. + + Since PDVs are always n*2 bytes, store each of the 2 Bytes in a variable. + This way, we don't need to call tvb_get_xxx on a self created buffer + + */ + + gboolean is_header_fragmented; + gboolean is_value_fragmented; + + guint32 len_decoded; /* Should only be < 16 bytes */ + + guint16 grp; /* Already decoded group */ + guint16 elm; /* Already decoded element */ + gchar *vr; /* Already decoded VR */ + + gboolean is_vl_long; /* If TRUE, Value Length is 4 Bytes, otherwise 2 */ + guint16 vl_1; /* Partially decoded 1st two bytes of length */ + guint16 vl_2; /* Partially decoded 2nd two bytes of length */ + + /* These ones are, where the value was truncated */ + guint32 len_total; /* Tag length of 'over-sized' tags. Used for display */ + guint32 len_remaining; /* Remaining tag bytes to 'decoded' as binary data after this PDV */ + + gchar *desc; /* Last decoded description */ + +} dcm_open_tag_t; + +/* + Per Data PDV store data needed, to allow decoding of tags longer than a PDV +*/ +typedef struct dcm_state_pdv { + + struct dcm_state_pdv *next, *prev; + + guint32 packet_no; /* Wireshark packet number, where pdv starts */ + guint32 offset; /* Offset in packet, where PDV header starts */ + + gchar *desc; /* PDV description. wmem_file_scope() */ + + guint8 pctx_id; /* Reference to used Presentation Context */ + + /* Following is derived from the transfer syntax in the parent PCTX, except for Command PDVs */ + guint8 syntax; + + /* Used and filled for Export Object only */ + gpointer data; /* Copy of PDV data without any PDU/PDV header */ + guint32 data_len; /* Length of this PDV buffer. If >0, memory has been allocated */ + + gchar *sop_class_uid; /* SOP Class UID. Set in 1st PDV of a DICOM object. wmem_file_scope() */ + gchar *sop_instance_uid; /* SOP Instance UID. Set in 1st PDV of a DICOM object. wmem_file_scope() */ + /* End Export use */ + + gboolean is_storage; /* True, if the Data PDV is on the context of a storage SOP Class */ + gboolean is_flagvalid; /* The following two flags are initialized correctly */ + gboolean is_command; /* This PDV is a command rather than a data package */ + gboolean is_last_fragment; /* Last Fragment bit was set, i.e. termination of an object + This flag delimits different DICOM object in the same association */ + gboolean is_corrupt; /* Early termination of long PDVs */ + + /* The following five attributes are only used for command PDVs */ + + gchar *command; /* Decoded command as text */ + gchar *status; /* Decoded status as text */ + gchar *comment; /* Error comment, if any */ + + gboolean is_warning; /* Command response is a cancel, warning, error */ + gboolean is_pending; /* Command response is 'Current Match is supplied. Sub-operations are continuing' */ + + guint16 message_id; /* (0000,0110) Message ID */ + guint16 message_id_resp; /* (0000,0120) Message ID being responded to */ + + guint16 no_remaining; /* (0000,1020) Number of remaining sub-operations */ + guint16 no_completed; /* (0000,1021) Number of completed sub-operations */ + guint16 no_failed; /* (0000,1022) Number of failed sub-operations */ + guint16 no_warning; /* (0000,1023) Number of warning sub-operations */ + + dcm_open_tag_t open_tag; /* Container to store information about a fragmented tag */ + + guint8 reassembly_id; + +} dcm_state_pdv_t; + +/* +Per Presentation Context in an association store data needed, for subsequent decoding +*/ +typedef struct dcm_state_pctx { + + struct dcm_state_pctx *next, *prev; + + guint8 id; /* 0x20 Presentation Context ID */ + gchar *abss_uid; /* 0x30 Abstract syntax */ + gchar *abss_desc; /* 0x30 Abstract syntax decoded*/ + gchar *xfer_uid; /* 0x40 Accepted Transfer syntax */ + gchar *xfer_desc; /* 0x40 Accepted Transfer syntax decoded*/ + guint8 syntax; /* Decoded transfer syntax */ +#define DCM_ILE 0x01 /* implicit, little endian */ +#define DCM_EBE 0x02 /* explicit, big endian */ +#define DCM_ELE 0x03 /* explicit, little endian */ +#define DCM_UNK 0xf0 + + guint8 reassembly_count; + dcm_state_pdv_t *first_pdv, *last_pdv; /* List of PDV objects */ + +} dcm_state_pctx_t; + + +typedef struct dcm_state_assoc { + + struct dcm_state_assoc *next, *prev; + + dcm_state_pctx_t *first_pctx, *last_pctx; /* List of Presentation context objects */ + + guint32 packet_no; /* Wireshark packet number, where association starts */ + + char *ae_called; /* Called AE title in A-ASSOCIATE RQ */ + char *ae_calling; /* Calling AE title in A-ASSOCIATE RQ */ + char *ae_called_resp; /* Called AE title in A-ASSOCIATE RP */ + char *ae_calling_resp; /* Calling AE title in A-ASSOCIATE RP */ + +} dcm_state_assoc_t; + +typedef struct dcm_state { + + struct dcm_state_assoc *first_assoc, *last_assoc; + + gboolean valid; /* this conversation is a DICOM conversation */ + +} dcm_state_t; + + +/* --------------------------------------------------------------------- + * DICOM Status Value Definitions + * + * Collected from PS 3.7 & 3.4 + * +*/ + +typedef struct dcm_status { + const guint16 value; + const gchar *description; +} dcm_status_t; + +static dcm_status_t dcm_status_data[] = { + + /* From PS 3.7 */ + + { 0x0000, "Success"}, + { 0x0105, "No such attribute"}, + { 0x0106, "Invalid attribute value"}, + { 0x0107, "Attribute list error"}, + { 0x0110, "Processing failure"}, + { 0x0111, "Duplicate SOP instance"}, + { 0x0112, "No Such object instance"}, + { 0x0113, "No such event type"}, + { 0x0114, "No such argument"}, + { 0x0115, "Invalid argument value"}, + { 0x0116, "Attribute Value Out of Range"}, + { 0x0117, "Invalid object instance"}, + { 0x0118, "No Such SOP class"}, + { 0x0119, "Class-instance conflict"}, + { 0x0120, "Missing attribute"}, + { 0x0121, "Missing attribute value"}, + { 0x0122, "Refused: SOP class not supported"}, + { 0x0123, "No such action type"}, + { 0x0210, "Duplicate invocation"}, + { 0x0211, "Unrecognized operation"}, + { 0x0212, "Mistyped argument"}, + { 0x0213, "Resource limitation"}, + { 0xFE00, "Cancel"}, + + /* from PS 3.4 */ + + { 0x0001, "Requested optional Attributes are not supported"}, + { 0xA501, "Refused because General Purpose Scheduled Procedure Step Object may no longer be updated"}, + { 0xA502, "Refused because the wrong Transaction UID is used"}, + { 0xA503, "Refused because the General Purpose Scheduled Procedure Step SOP Instance is already in the 'IN PROGRESS' state"}, + { 0xA504, "Refused because the related General Purpose Scheduled Procedure Step SOP Instance is not in the 'IN PROGRESS' state"}, + { 0xA505, "Refused because Referenced General Purpose Scheduled Procedure Step Transaction UID does not match the Transaction UID of the N-ACTION request"}, + { 0xA510, "Refused because an Initiate Media Creation action has already been received for this SOP Instance"}, + { 0xA700, "Refused: Out of Resources"}, + { 0xA701, "Refused: Out of Resources - Unable to calculate number of matches"}, + { 0xA702, "Refused: Out of Resources - Unable to perform sub-operations"}, + /* + { 0xA7xx, "Refused: Out of Resources"}, + */ + { 0xA801, "Refused: Move Destination unknown"}, + /* + { 0xA9xx, "Error: Data Set does not match SOP Class"}, + */ + { 0xB000, "Sub-operations Complete - One or more Failures"}, + { 0xB006, "Elements Discarded"}, + { 0xB007, "Data Set does not match SOP Class"}, + { 0xB101, "Specified Synchronization Frame of Reference UID does not match SCP Synchronization Frame of Reference"}, + { 0xB102, "Study Instance UID coercion; Event logged under a different Study Instance UID"}, + { 0xB104, "IDs inconsistent in matching a current study; Event logged"}, + { 0xB605, "Requested Min Density or Max Density outside of printer's operating range. The printer will use its respective minimum or maximum density value instead"}, + { 0xC000, "Error: Cannot understand/Unable to process"}, + { 0xC100, "More than one match found"}, + { 0xC101, "Procedural Logging not available for specified Study Instance UID"}, + { 0xC102, "Event Information does not match Template"}, + { 0xC103, "Cannot match event to a current study"}, + { 0xC104, "IDs inconsistent in matching a current study; Event not logged"}, + { 0xC200, "Unable to support requested template"}, + { 0xC201, "Media creation request already completed"}, + { 0xC202, "Media creation request already in progress and cannot be interrupted"}, + { 0xC203, "Cancellation denied for unspecified reason"}, + /* + { 0xCxxx, "Error: Cannot understand/Unable to Process"}, + { 0xFE00, "Matching/Sub-operations terminated due to Cancel request"}, + */ + { 0xFF00, "Current Match is supplied. Sub-operations are continuing"}, + { 0xFF01, "Matches are continuing - Warning that one or more Optional Keys were not supported for existence for this Identifier"} + +}; + + +/* following definitions are used to call dissect_dcm_assoc_item() */ +#define DCM_ITEM_VALUE_TYPE_UID 1 +#define DCM_ITEM_VALUE_TYPE_STRING 2 +#define DCM_ITEM_VALUE_TYPE_UINT32 3 + +/* And from here on, only use unsigned 32 bit values. Offset is always positive number in respect to the tvb buffer start */ +static guint32 dissect_dcm_pdu (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset); + +static guint32 dissect_dcm_assoc_detail(tvbuff_t *tvb, packet_info *pinfo, proto_item *ti, dcm_state_assoc_t *assoc, guint32 offset, guint32 len); + +static guint32 dissect_dcm_tag_value(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, dcm_state_pdv_t * pdv, guint32 offset, guint16 grp, guint16 elm, guint32 vl, guint32 vl_max, const gchar * vr, gchar ** tag_value); + +static void +dcm_init(void) +{ + guint i; + + /* Create three hash tables for quick lookups */ + /* Add UID objects to hash table */ + dcm_uid_table = wmem_map_new(wmem_file_scope(), wmem_str_hash, g_str_equal); + for (i = 0; i < array_length(dcm_uid_data); i++) { + wmem_map_insert(dcm_uid_table, (gpointer) dcm_uid_data[i].value, + (gpointer) &dcm_uid_data[i]); + } + + /* Add Tag objects to hash table */ + dcm_tag_table = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal); + for (i = 0; i < array_length(dcm_tag_data); i++) { + wmem_map_insert(dcm_tag_table, GUINT_TO_POINTER(dcm_tag_data[i].tag), + (gpointer) &dcm_tag_data[i]); + } + + /* Add Status Values to hash table */ + dcm_status_table = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal); + for (i = 0; i < array_length(dcm_status_data); i++) { + wmem_map_insert(dcm_status_table, GUINT_TO_POINTER((guint32)dcm_status_data[i].value), + (gpointer)&dcm_status_data[i]); + } +} + +/* +Get or create conversation and DICOM data structure if desired. +Return new or existing DICOM structure, which is used to store context IDs and transfer syntax. +Return NULL in case of the structure couldn't be created. +*/ +static dcm_state_t * +dcm_state_get(packet_info *pinfo, gboolean create) +{ + + conversation_t *conv; + dcm_state_t *dcm_data; + + conv = find_or_create_conversation(pinfo); + dcm_data = (dcm_state_t *)conversation_get_proto_data(conv, proto_dcm); + + if (dcm_data == NULL && create) { + + dcm_data = wmem_new0(wmem_file_scope(), dcm_state_t); + conversation_add_proto_data(conv, proto_dcm, dcm_data); + + /* Mark it as DICOM conversation. Needed for the heuristic mode, + to prevent stealing subsequent packets by other dissectors + */ + conversation_set_dissector(conv, dcm_handle); + } + + return dcm_data; +} + + +static dcm_state_assoc_t * +dcm_state_assoc_new(dcm_state_t *dcm_data, guint32 packet_no) +{ + /* Create new association object and initialize the members */ + + dcm_state_assoc_t *assoc; + + assoc = wmem_new0(wmem_file_scope(), dcm_state_assoc_t); + assoc->packet_no = packet_no; /* Identifier */ + + /* add to the end of the list */ + if (dcm_data->last_assoc) { + dcm_data->last_assoc->next = assoc; + assoc->prev = dcm_data->last_assoc; + } + else { + dcm_data->first_assoc = assoc; + } + dcm_data->last_assoc = assoc; + return assoc; +} + +/* +Find or create association object based on packet number. Return NULL, if association was not found. +*/ +static dcm_state_assoc_t * +dcm_state_assoc_get(dcm_state_t *dcm_data, guint32 packet_no, gboolean create) +{ + + dcm_state_assoc_t *assoc = dcm_data->first_assoc; + + while (assoc) { + + if (assoc->next) { + /* we have more associations in the same stream */ + if ((assoc->packet_no <= packet_no) && (packet_no < assoc->next->packet_no)) + break; + } + else { + /* last or only associations in the same stream */ + if (assoc->packet_no <= packet_no) + break; + } + assoc = assoc->next; + } + + if (assoc == NULL && create) { + assoc = dcm_state_assoc_new(dcm_data, packet_no); + } + return assoc; +} + +static dcm_state_pctx_t * +dcm_state_pctx_new(dcm_state_assoc_t *assoc, guint8 pctx_id) +{ + /* Create new presentation context object and initialize the members */ + + dcm_state_pctx_t *pctx; + + pctx = wmem_new0(wmem_file_scope(), dcm_state_pctx_t); + pctx->id = pctx_id; + pctx->syntax = DCM_UNK; + + /* add to the end of the list list */ + if (assoc->last_pctx) { + assoc->last_pctx->next = pctx; + pctx->prev = assoc->last_pctx; + } + else { + assoc->first_pctx = pctx; + } + assoc->last_pctx = pctx; + + return pctx; +} + +static dcm_state_pctx_t * +dcm_state_pctx_get(dcm_state_assoc_t *assoc, guint8 pctx_id, gboolean create) +{ + /* Find or create presentation context object. Return NULL, if Context ID was not found */ + + dcm_state_pctx_t *pctx = assoc->first_pctx; + /* + static char notfound[] = "not found - click on ASSOC Request"; + static dcm_state_pctx_t dunk = { NULL, NULL, FALSE, 0, notfound, notfound, notfound, notfound, DCM_UNK }; + */ + while (pctx) { + if (pctx->id == pctx_id) + break; + pctx = pctx->next; + } + + if (pctx == NULL && create) { + pctx = dcm_state_pctx_new(assoc, pctx_id); + } + + return pctx; +} + + +/* +Create new PDV object and initialize all members +*/ +static dcm_state_pdv_t* +dcm_state_pdv_new(dcm_state_pctx_t *pctx, guint32 packet_no, guint32 offset) +{ + dcm_state_pdv_t *pdv; + + pdv = wmem_new0(wmem_file_scope(), dcm_state_pdv_t); + pdv->syntax = DCM_UNK; + pdv->is_last_fragment = TRUE; /* Continuation PDVs are more tricky */ + pdv->packet_no = packet_no; + pdv->offset = offset; + + /* add to the end of the list */ + if (pctx->last_pdv) { + pctx->last_pdv->next = pdv; + pdv->prev = pctx->last_pdv; + } + else { + pctx->first_pdv = pdv; + } + pctx->last_pdv = pdv; + return pdv; +} + + +static dcm_state_pdv_t* +dcm_state_pdv_get(dcm_state_pctx_t *pctx, guint32 packet_no, guint32 offset, gboolean create) +{ + /* Find or create PDV object. Return NULL, if PDV was not found, based on packet number and offset */ + + dcm_state_pdv_t *pdv = pctx->first_pdv; + + while (pdv) { + if ((pdv->packet_no == packet_no) && (pdv->offset == offset)) + break; + pdv = pdv->next; + } + + if (pdv == NULL && create) { + pdv = dcm_state_pdv_new(pctx, packet_no, offset); + } + return pdv; +} + +static dcm_state_pdv_t* +dcm_state_pdv_get_obj_start(dcm_state_pdv_t *pdv_curr) +{ + + dcm_state_pdv_t *pdv_first=pdv_curr; + + /* Get First PDV of the DICOM Object */ + while (pdv_first->prev && !pdv_first->prev->is_last_fragment) { + pdv_first = pdv_first->prev; + } + + return pdv_first; +} + +static const value_string dcm_cmd_vals[] = { + { 0x0001, "C-STORE-RQ" }, + { 0x0010, "C-GET-RQ" }, + { 0x0020, "C-FIND-RQ" }, + { 0x0021, "C-MOVE-RQ" }, + { 0x0030, "C-ECHO-RQ" }, + { 0x0100, "N-EVENT-REPORT-RQ" }, + { 0x0110, "N-GET-RQ" }, + { 0x0120, "N-SET-RQ" }, + { 0x0130, "N-ACTION-RQ" }, + { 0x0140, "N-CREATE-RQ" }, + { 0x0150, "N-DELETE-RQ" }, + { 0x8001, "C-STORE-RSP" }, + { 0x8010, "C-GET-RSP" }, + { 0x8020, "C-FIND-RSP" }, + { 0x8021, "C-MOVE-RSP" }, + { 0x8030, "C-ECHO-RSP" }, + { 0x8100, "N-EVENT-REPORT-RSP" }, + { 0x8110, "N-GET-RSP" }, + { 0x8120, "N-SET-RSP" }, + { 0x8130, "N-ACTION-RSP" }, + { 0x8140, "N-CREATE-RSP" }, + { 0x8150, "N-DELETE-RSP" }, + { 0x0FFF, "C-CANCEL-RQ" }, + { 0, NULL } +}; + + +/* +Convert the two status bytes into a text based on lookup. + +Classification +0x0000 : SUCCESS +0x0001 & Bxxx : WARNING +0xFE00 : CANCEL +0XFFxx : PENDING +All other : FAILURE +*/ +static const gchar * +dcm_rsp2str(guint16 status_value) +{ + + dcm_status_t *status = NULL; + const gchar *s; + + /* Use specific text first */ + status = (dcm_status_t*) wmem_map_lookup(dcm_status_table, GUINT_TO_POINTER((guint32)status_value)); + + if (status) { + s = status->description; + } + else { + + if ((status_value & 0xFF00) == 0xA700) { + /* 0xA7xx */ + s = "Refused: Out of Resources"; + } + else if ((status_value & 0xFF00) == 0xA900) { + /* 0xA9xx */ + s = "Error: Data Set does not match SOP Class"; + } + else if ((status_value & 0xF000) == 0xC000) { + /* 0xCxxx */ + s = "Error: Cannot understand/Unable to Process"; + } + else { + /* Encountered at least one case, with status_value == 0xD001 */ + s = "Unknown"; + } + } + + return s; +} + +static const gchar* +dcm_uid_or_desc(gchar *dcm_uid, gchar *dcm_desc) +{ + /* Return Description, UID or error */ + + return (dcm_desc == NULL ? (dcm_uid == NULL ? "Malformed Packet" : dcm_uid) : dcm_desc); +} + +static void +dcm_set_syntax(dcm_state_pctx_t *pctx, gchar *xfer_uid, const gchar *xfer_desc) +{ + if ((pctx == NULL) || (xfer_uid == NULL) || (xfer_desc == NULL)) + return; + + wmem_free(wmem_file_scope(), pctx->xfer_uid); /* free prev allocated xfer */ + wmem_free(wmem_file_scope(), pctx->xfer_desc); /* free prev allocated xfer */ + + pctx->syntax = 0; + pctx->xfer_uid = wmem_strdup(wmem_file_scope(), xfer_uid); + pctx->xfer_desc = wmem_strdup(wmem_file_scope(), xfer_desc); + + /* this would be faster to skip the common parts, and have a FSA to + * find the syntax. + * Absent of coding that, this is in descending order of probability */ + if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2")) + pctx->syntax = DCM_ILE; /* implicit little endian */ + else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.1")) + pctx->syntax = DCM_ELE; /* explicit little endian */ + else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.2")) + pctx->syntax = DCM_EBE; /* explicit big endian */ + else if (0 == strcmp(xfer_uid, "1.2.840.113619.5.2")) + pctx->syntax = DCM_ILE; /* implicit little endian, big endian pixels, GE private */ + else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.4.70")) + pctx->syntax = DCM_ELE; /* explicit little endian, jpeg */ + else if (0 == strncmp(xfer_uid, "1.2.840.10008.1.2.4", 18)) + pctx->syntax = DCM_ELE; /* explicit little endian, jpeg */ + else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.1.99")) + pctx->syntax = DCM_ELE; /* explicit little endian, deflated */ +} + +static void +dcm_guint16_to_le(guint8 *buffer, guint16 value) +{ + + buffer[0]=(guint8) (value & 0x00FF); + buffer[1]=(guint8)((value & 0xFF00) >> 8); +} + +static void +dcm_guint32_to_le(guint8 *buffer, guint32 value) +{ + + buffer[0]=(guint8) (value & 0x000000FF); + buffer[1]=(guint8)((value & 0x0000FF00) >> 8); + buffer[2]=(guint8)((value & 0x00FF0000) >> 16); + buffer[3]=(guint8)((value & 0xFF000000) >> 24); + +} + +static guint32 +dcm_export_create_tag_base(guint8 *buffer, guint32 bufflen, guint32 offset, + guint16 grp, guint16 elm, guint16 vr, + const guint8 *value_buffer, guint32 value_len) +{ + /* Only Explicit Little Endian is needed to create Metafile Header + Generic function to write a TAG, VR, LEN & VALUE to a combined buffer + The value (buffer, len) must be preprocessed by a VR specific function + */ + + if (offset + 6 > bufflen) return bufflen; + + dcm_guint16_to_le(buffer + offset, grp); + offset += 2; + dcm_guint16_to_le(buffer + offset, elm); + offset += 2; + memmove(buffer + offset, dcm_tag_vr_lookup[vr], 2); + offset += 2; + + switch (vr) { + case DCM_VR_OB: + case DCM_VR_OD: + case DCM_VR_OF: + case DCM_VR_OL: + case DCM_VR_OW: + case DCM_VR_SQ: + case DCM_VR_UC: + case DCM_VR_UR: + case DCM_VR_UT: + case DCM_VR_UN: + /* DICOM likes it complicated. Special handling for these types */ + + if (offset + 6 > bufflen) return bufflen; + + /* Add two reserved 0x00 bytes */ + dcm_guint16_to_le(buffer + offset, 0); + offset += 2; + + /* Length is a 4 byte field */ + dcm_guint32_to_le(buffer + offset, value_len); + offset += 4; + + break; + + default: + /* Length is a 2 byte field */ + if (offset + 2 > bufflen) return bufflen; + + dcm_guint16_to_le(buffer + offset, (guint16)value_len); + offset += 2; + } + + if (offset + value_len > bufflen) return bufflen; + + memmove(buffer + offset, value_buffer, value_len); + offset += value_len; + + return offset; +} + +static guint32 +dcm_export_create_tag_guint16(guint8 *buffer, guint32 bufflen, guint32 offset, + guint16 grp, guint16 elm, guint16 vr, guint16 value) +{ + + return dcm_export_create_tag_base(buffer, bufflen, offset, grp, elm, vr, (guint8*)&value, 2); +} + +static guint32 +dcm_export_create_tag_guint32(guint8 *buffer, guint32 bufflen, guint32 offset, + guint16 grp, guint16 elm, guint16 vr, guint32 value) +{ + + return dcm_export_create_tag_base(buffer, bufflen, offset, grp, elm, vr, (guint8*)&value, 4); +} + +static guint32 +dcm_export_create_tag_str(guint8 *buffer, guint32 bufflen, guint32 offset, + guint16 grp, guint16 elm, guint16 vr, + const gchar *value) +{ + guint32 len; + + if (!value) { + /* NULL object. E.g. happens if UID was not found/set. Don't create element*/ + return offset; + } + + len=(int)strlen(value); + + if ((len & 0x01) == 1) { + /* Odd length: since buffer is 0 initialized, pad with a 0x00 */ + len += 1; + } + + return dcm_export_create_tag_base(buffer, bufflen, offset, grp, elm, vr, (const guint8 *)value, len); +} + + +static guint8* +dcm_export_create_header(packet_info *pinfo, guint32 *dcm_header_len, const gchar *sop_class_uid, gchar *sop_instance_uid, gchar *xfer_uid) +{ + guint8 *dcm_header=NULL; + guint32 offset=0; + guint32 offset_header_len=0; + +#define DCM_HEADER_MAX 512 + + dcm_header=(guint8 *)wmem_alloc0(pinfo->pool, DCM_HEADER_MAX); /* Slightly longer than needed */ + /* The subsequent functions rely on a 0 initialized buffer */ + offset=128; + + memmove(dcm_header+offset, "DICM", 4); + offset+=4; + + offset_header_len=offset; /* remember for later */ + + offset+=12; + + /* + (0002,0000) File Meta Information Group Length UL + (0002,0001) File Meta Information Version OB + (0002,0002) Media Storage SOP Class UID UI + (0002,0003) Media Storage SOP Instance UID UI + (0002,0010) Transfer Syntax UID UI + (0002,0012) Implementation Class UID UI + (0002,0013) Implementation Version Name SH + */ + + offset=dcm_export_create_tag_guint16(dcm_header, DCM_HEADER_MAX, offset, + 0x0002, 0x0001, DCM_VR_OB, 0x0100); /* will result on 00 01 since it is little endian */ + + offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset, + 0x0002, 0x0002, DCM_VR_UI, sop_class_uid); + + offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset, + 0x0002, 0x0003, DCM_VR_UI, sop_instance_uid); + + offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset, + 0x0002, 0x0010, DCM_VR_UI, xfer_uid); + + offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset, + 0x0002, 0x0012, DCM_VR_UI, WIRESHARK_IMPLEMENTATION_UID); + + offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset, + 0x0002, 0x0013, DCM_VR_SH, WIRESHARK_IMPLEMENTATION_VERSION); + + /* Finally write the meta header length */ + dcm_export_create_tag_guint32(dcm_header, DCM_HEADER_MAX, offset_header_len, + 0x0002, 0x0000, DCM_VR_UL, offset-offset_header_len-12); + + *dcm_header_len=offset; + + return dcm_header; + +} + + +/* +Concatenate related PDVs into one buffer and add it to the export object list. + +Supports both modes: + +- Multiple DICOM PDVs are reassembled with fragment_add_seq_next() + and process_reassembled_data(). In this case all data will be in the last + PDV, and all its predecessors will have zero data. + +- DICOM PDVs are keep separate. Every PDV contains data. +*/ +static void +dcm_export_create_object(packet_info *pinfo, dcm_state_assoc_t *assoc, dcm_state_pdv_t *pdv) +{ + + dicom_eo_t *eo_info = NULL; + + dcm_state_pdv_t *pdv_curr = NULL; + dcm_state_pdv_t *pdv_same_pkt = NULL; + dcm_state_pctx_t *pctx = NULL; + + guint8 *pdv_combined = NULL; + guint8 *pdv_combined_curr = NULL; + guint8 *dcm_header = NULL; + guint32 pdv_combined_len = 0; + guint32 dcm_header_len = 0; + guint16 cnt_same_pkt = 1; + gchar *filename; + const gchar *hostname; + + const gchar *sop_class_uid; + gchar *sop_instance_uid; + + /* Calculate total PDV length, i.e. all packets until last PDV without continuation */ + pdv_curr = pdv; + pdv_same_pkt = pdv; + pdv_combined_len=pdv_curr->data_len; + + while (pdv_curr->prev && !pdv_curr->prev->is_last_fragment) { + pdv_curr = pdv_curr->prev; + pdv_combined_len += pdv_curr->data_len; + } + + /* Count number of PDVs with the same Packet Number */ + while (pdv_same_pkt->prev && (pdv_same_pkt->prev->packet_no == pdv_same_pkt->packet_no)) { + pdv_same_pkt = pdv_same_pkt->prev; + cnt_same_pkt += 1; + } + + pctx=dcm_state_pctx_get(assoc, pdv_curr->pctx_id, FALSE); + + if (assoc->ae_calling != NULL && strlen(assoc->ae_calling)>0 && + assoc->ae_called != NULL && strlen(assoc->ae_called)>0) { + hostname = wmem_strdup_printf(pinfo->pool, "%s <-> %s", assoc->ae_calling, assoc->ae_called); + } + else { + hostname = "AE title(s) unknown"; + } + + if (pdv->is_storage && + pdv_curr->sop_class_uid && strlen(pdv_curr->sop_class_uid)>0 && + pdv_curr->sop_instance_uid && strlen(pdv_curr->sop_instance_uid)>0) { + + sop_class_uid = wmem_strdup(pinfo->pool, pdv_curr->sop_class_uid); + sop_instance_uid = wmem_strdup(pinfo->pool, pdv_curr->sop_instance_uid); + + /* Make sure filename does not contain invalid character. Rather conservative. + Even though this should be a valid DICOM UID, apply the same filter rules + in case of bogus data. + */ + filename = wmem_strdup_printf(pinfo->pool, "%06d-%d-%s.dcm", pinfo->num, cnt_same_pkt, + g_strcanon(pdv_curr->sop_instance_uid, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-.", '-')); + } + else { + /* No SOP Instance or SOP Class UID found in PDV. Use wireshark ones */ + + sop_class_uid = wmem_strdup(pinfo->pool, WIRESHARK_MEDIA_STORAGE_SOP_CLASS_UID); + sop_instance_uid = wmem_strdup_printf(pinfo->pool, "%s.%d.%d", + WIRESHARK_MEDIA_STORAGE_SOP_INSTANCE_UID_PREFIX, pinfo->num, cnt_same_pkt); + + /* Make sure filename does not contain invalid character. Rather conservative.*/ + filename = wmem_strdup_printf(pinfo->pool, "%06d-%d-%s.dcm", pinfo->num, cnt_same_pkt, + g_strcanon(pdv->desc, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-.", '-')); + + } + + if (global_dcm_export_header) { + if (pctx && pctx->xfer_uid && strlen(pctx->xfer_uid)>0) { + dcm_header=dcm_export_create_header(pinfo, &dcm_header_len, sop_class_uid, sop_instance_uid, pctx->xfer_uid); + } + else { + /* We are running blind, i.e. no presentation context/syntax found. + Don't invent one, so the meta header will miss + the transfer syntax UID tag (even though it is mandatory) + */ + dcm_header=dcm_export_create_header(pinfo, &dcm_header_len, sop_class_uid, sop_instance_uid, NULL); + } + } + + + if (dcm_header_len + pdv_combined_len >= global_dcm_export_minsize) { + /* Allocate the final size */ + + pdv_combined = (guint8 *)wmem_alloc0(pinfo->pool, dcm_header_len + pdv_combined_len); + + pdv_combined_curr = pdv_combined; + + if (dcm_header_len != 0) { /* Will be 0 when global_dcm_export_header is FALSE */ + memmove(pdv_combined, dcm_header, dcm_header_len); + pdv_combined_curr += dcm_header_len; + } + + /* Copy PDV per PDV to target buffer */ + while (!pdv_curr->is_last_fragment) { + memmove(pdv_combined_curr, pdv_curr->data, pdv_curr->data_len); /* this is a copy not move */ + pdv_combined_curr += pdv_curr->data_len; + pdv_curr = pdv_curr->next; + } + + /* Last packet */ + memmove(pdv_combined_curr, pdv->data, pdv->data_len); /* this is a copy not a move */ + + /* Add to list */ + /* The tap will copy the values and free the copies; this only + * needs packet lifetime. */ + eo_info = wmem_new0(pinfo->pool, dicom_eo_t); + eo_info->hostname = hostname; + eo_info->filename = filename; + eo_info->content_type = pdv->desc; + + eo_info->payload_len = dcm_header_len + pdv_combined_len; + eo_info->payload_data = pdv_combined; + + tap_queue_packet(dicom_eo_tap, pinfo, eo_info); + } +} + +/* +For tags with fixed length items, calculate the value multiplicity (VM). String tags use a separator, which is not supported by this function. +Support item count from 0 to n. and handles bad encoding (e.g. an 'AT' tag was reported to be 2 bytes instead of 4 bytes) +*/ +static guint32 +dcm_vm_item_count(guint32 value_length, guint32 item_length) +{ + + /* This could all be formulated in a single line but it does not make it easier to read */ + + if (value_length == 0) { + return 0; + } + else if (value_length <= item_length) { + return 1; /* This is the special case of bad encoding */ + } + else { + return (value_length / item_length); + } + +} + +/* +Decode the association header + */ +static guint32 +dissect_dcm_assoc_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, dcm_state_assoc_t *assoc, + guint8 pdu_type, guint32 pdu_len) +{ + + proto_item *assoc_header_pitem; + proto_tree *assoc_header_ptree; /* Tree for item details */ + + const gchar *buf_desc = NULL; + const char *reject_result_desc = ""; + const char *reject_source_desc = ""; + const char *reject_reason_desc = ""; + const char *abort_source_desc = ""; + const char *abort_reason_desc = ""; + + char *ae_called; + char *ae_calling; + char *ae_called_resp; + char *ae_calling_resp; + + guint8 reject_result; + guint8 reject_source; + guint8 reject_reason; + guint8 abort_source; + guint8 abort_reason; + + assoc_header_ptree = proto_tree_add_subtree(tree, tvb, offset, pdu_len, ett_assoc_header, &assoc_header_pitem, "Association Header"); + + switch (pdu_type) { + case 1: /* Association Request */ + + proto_tree_add_item(assoc_header_ptree, hf_dcm_assoc_version, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + + offset += 2; /* Two reserved bytes*/ + + /* + * XXX - this is in "the ISO 646:1990-Basic G0 Set"; ISO/IEC 646:1991 + * claims to be the third edition of the standard, with the second + * version being ISO 646:1983, so I'm not sure what happened to + * ISO 646:1990. ISO/IEC 646:1991 speaks of "the basic 7-bit code + * table", which leaves positions 2/3 (0x23) and 2/4 (0x24) as + * being either NUMBER SIGN or POUND SIGN and either DOLLAR SIGN or + * CURRENCY SIGN, respectively, and positions 4/0 (0x40), 5/11 (0x5b), + * 5/12 (0x5c), 5/13 (0x5d), 5/14 (0x5e), 6/0 (0x60), 7/11 (0x7b), + * 7/12 (0x7c), 7/13 (0x7d), and 7/14 (0x7e) as being "available for + * national or application-oriented use", so I'm *guessing* that + * "the ISO 646:1990-Basic G0 Set" means "those positions aren't + * specified" and thus should probably be treated as not valid + * in that "Basic" set. + */ + proto_tree_add_item_ret_display_string(assoc_header_ptree, hf_dcm_assoc_called, tvb, offset, 16, ENC_ISO_646_BASIC|ENC_NA, pinfo->pool, &ae_called); + assoc->ae_called = wmem_strdup(wmem_file_scope(), g_strstrip(ae_called)); + offset += 16; + + proto_tree_add_item_ret_display_string(assoc_header_ptree, hf_dcm_assoc_calling, tvb, offset, 16, ENC_ISO_646_BASIC|ENC_NA, pinfo->pool, &ae_calling); + assoc->ae_calling = wmem_strdup(wmem_file_scope(), g_strstrip(ae_calling)); + offset += 16; + + offset += 32; /* 32 reserved bytes */ + + buf_desc = wmem_strdup_printf(pinfo->pool, "A-ASSOCIATE request %s --> %s", + assoc->ae_calling, assoc->ae_called); + + offset = dissect_dcm_assoc_detail(tvb, pinfo, assoc_header_ptree, assoc, offset, pdu_len-offset); + + break; + case 2: /* Association Accept */ + + proto_tree_add_item(assoc_header_ptree, hf_dcm_assoc_version, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + + offset += 2; /* Two reserved bytes*/ + + proto_tree_add_item_ret_display_string(assoc_header_ptree, hf_dcm_assoc_called, tvb, offset, 16, ENC_ISO_646_BASIC|ENC_NA, pinfo->pool, &ae_called_resp); + assoc->ae_called_resp = wmem_strdup(wmem_file_scope(), g_strstrip(ae_called_resp)); + offset += 16; + + proto_tree_add_item_ret_display_string(assoc_header_ptree, hf_dcm_assoc_calling, tvb, offset, 16, ENC_ISO_646_BASIC|ENC_NA, pinfo->pool, &ae_calling_resp); + assoc->ae_calling_resp = wmem_strdup(wmem_file_scope(), g_strstrip(ae_calling_resp)); + offset += 16; + + offset += 32; /* 32 reserved bytes */ + + buf_desc = wmem_strdup_printf(pinfo->pool, "A-ASSOCIATE accept %s <-- %s", + assoc->ae_calling_resp, assoc->ae_called_resp); + + offset = dissect_dcm_assoc_detail(tvb, pinfo, assoc_header_ptree, assoc, offset, pdu_len-offset); + + break; + case 3: /* Association Reject */ + + offset += 1; /* One reserved byte */ + + reject_result = tvb_get_guint8(tvb, offset); + reject_source = tvb_get_guint8(tvb, offset+1); + reject_reason = tvb_get_guint8(tvb, offset+2); + + switch (reject_result) { + case 1: reject_result_desc = "Reject Permanent"; break; + case 2: reject_result_desc = "Reject Transient"; break; + default: break; + } + + switch (reject_source) { + case 1: + reject_source_desc = "User"; + switch (reject_reason) { + case 1: reject_reason_desc = "No reason given"; break; + case 2: reject_reason_desc = "Application context name not supported"; break; + case 3: reject_reason_desc = "Calling AE title not recognized"; break; + case 7: reject_reason_desc = "Called AE title not recognized"; break; + } + break; + case 2: + reject_source_desc = "Provider (ACSE)"; + switch (reject_reason) { + case 1: reject_reason_desc = "No reason given"; break; + case 2: reject_reason_desc = "Protocol version not supported"; break; + } + break; + case 3: + reject_source_desc = "Provider (Presentation)"; + switch (reject_reason) { + case 1: reject_reason_desc = "Temporary congestion"; break; + case 2: reject_reason_desc = "Local limit exceeded"; break; + } + break; + } + + proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_reject_result, tvb, + offset , 1, reject_result, "%s", reject_result_desc); + + proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_reject_source, tvb, + offset+1, 1, reject_source, "%s", reject_source_desc); + + proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_reject_reason, tvb, + offset+2, 1, reject_reason, "%s", reject_reason_desc); + + offset += 3; + + /* Provider aborted */ + buf_desc = wmem_strdup_printf(pinfo->pool, "A-ASSOCIATE reject %s <-- %s (%s)", + assoc->ae_calling, assoc->ae_called, reject_reason_desc); + + expert_add_info(pinfo, assoc_header_pitem, &ei_dcm_assoc_rejected); + + break; + case 5: /* RELEASE Request */ + + offset += 2; /* Two reserved bytes */ + buf_desc="A-RELEASE request"; + + break; + case 6: /* RELEASE Response */ + + offset += 2; /* Two reserved bytes */ + buf_desc="A-RELEASE response"; + + break; + case 7: /* ABORT */ + + offset += 2; /* Two reserved bytes */ + + abort_source = tvb_get_guint8(tvb, offset); + abort_reason = tvb_get_guint8(tvb, offset+1); + + switch (abort_source) { + case 0: + abort_source_desc = "User"; + abort_reason_desc = "N/A"; /* No details can be provided*/ + break; + case 1: + /* reserved */ + break; + case 2: + abort_source_desc = "Provider"; + + switch (abort_reason) { + case 0: abort_reason_desc = "Not specified"; break; + case 1: abort_reason_desc = "Unrecognized PDU"; break; + case 2: abort_reason_desc = "Unexpected PDU"; break; + case 4: abort_reason_desc = "Unrecognized PDU parameter"; break; + case 5: abort_reason_desc = "Unexpected PDU parameter"; break; + case 6: abort_reason_desc = "Invalid PDU parameter value"; break; + } + + break; + } + + proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_abort_source, + tvb, offset , 1, abort_source, "%s", abort_source_desc); + + proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_abort_reason, + tvb, offset+1, 1, abort_reason, "%s", abort_reason_desc); + offset += 2; + + if (abort_source == 0) { + /* User aborted */ + buf_desc = wmem_strdup_printf(pinfo->pool, "ABORT %s --> %s", + assoc->ae_calling, assoc->ae_called); + } + else { + /* Provider aborted, slightly more information */ + buf_desc = wmem_strdup_printf(pinfo->pool, "ABORT %s <-- %s (%s)", + assoc->ae_calling, assoc->ae_called, abort_reason_desc); + } + + expert_add_info(pinfo, assoc_header_pitem, &ei_dcm_assoc_aborted); + + break; + } + + if (buf_desc) { + proto_item_set_text(assoc_header_pitem, "%s", buf_desc); + col_set_str(pinfo->cinfo, COL_INFO, buf_desc); + + /* proto_item and proto_tree are one and the same */ + proto_item_append_text(tree, ", %s", buf_desc); + } + return offset; +} + +/* +Decode one item in a association request or response. Lookup UIDs if requested. +Create a subtree node with summary and three elements (item_type, item_len, value) +*/ +static void +dissect_dcm_assoc_item(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, + const gchar *pitem_prefix, int item_value_type, + gchar **item_value, const gchar **item_description, + int *hf_type, int *hf_len, int *hf_value, int ett_subtree) +{ + + proto_tree *assoc_item_ptree; /* Tree for item details */ + proto_item *assoc_item_pitem; + dcm_uid_t *uid = NULL; + + guint32 item_number = 0; + + guint8 item_type; + guint16 item_len; + + gchar *buf_desc; /* Used for item text */ + + *item_value = NULL; + *item_description = NULL; + + item_type = tvb_get_guint8(tvb, offset); + item_len = tvb_get_ntohs(tvb, offset+2); + + assoc_item_ptree = proto_tree_add_subtree(tree, tvb, offset, item_len+4, ett_subtree, &assoc_item_pitem, pitem_prefix); + + proto_tree_add_uint(assoc_item_ptree, *hf_type, tvb, offset, 1, item_type); + proto_tree_add_uint(assoc_item_ptree, *hf_len, tvb, offset+2, 2, item_len); + + switch (item_value_type) { + case DCM_ITEM_VALUE_TYPE_UID: + *item_value = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset+4, item_len, ENC_ASCII); + + uid = (dcm_uid_t *)wmem_map_lookup(dcm_uid_table, (gpointer) *item_value); + if (uid) { + *item_description = uid->name; + buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", *item_description, *item_value); + } + else { + /* Unknown UID, or no UID at all */ + buf_desc = *item_value; + } + + proto_item_append_text(assoc_item_pitem, "%s", buf_desc); + proto_tree_add_string(assoc_item_ptree, *hf_value, tvb, offset+4, item_len, buf_desc); + + break; + + case DCM_ITEM_VALUE_TYPE_STRING: + *item_value = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset+4, item_len, ENC_ASCII); + proto_item_append_text(assoc_item_pitem, "%s", *item_value); + proto_tree_add_string(assoc_item_ptree, *hf_value, tvb, offset+4, item_len, *item_value); + + break; + + case DCM_ITEM_VALUE_TYPE_UINT32: + item_number = tvb_get_ntohl(tvb, offset+4); + *item_value = (gchar *)wmem_strdup_printf(wmem_file_scope(), "%d", item_number); + + proto_item_append_text(assoc_item_pitem, "%s", *item_value); + proto_tree_add_item(assoc_item_ptree, *hf_value, tvb, offset+4, 4, ENC_BIG_ENDIAN); + + break; + + default: + break; + } +} + +/* +Decode the SOP Class Extended Negotiation Sub-Item Fields in a association request or response. +Lookup UIDs if requested +*/ +static void +dissect_dcm_assoc_sopclass_extneg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset) +{ + + proto_tree *assoc_item_extneg_tree = NULL; /* Tree for item details */ + proto_item *assoc_item_extneg_item = NULL; + + guint16 item_len = 0; + guint16 sop_class_uid_len = 0; + gint32 cnt = 0; + + gchar *buf_desc = NULL; /* Used for item text */ + dcm_uid_t *sopclassuid=NULL; + gchar *sopclassuid_str = NULL; + + item_len = tvb_get_ntohs(tvb, offset+2); + sop_class_uid_len = tvb_get_ntohs(tvb, offset+4); + + assoc_item_extneg_item = proto_tree_add_item(tree, hf_dcm_info_extneg, tvb, offset, item_len+4, ENC_NA); + proto_item_set_text(assoc_item_extneg_item, "Ext. Neg.: "); + assoc_item_extneg_tree = proto_item_add_subtree(assoc_item_extneg_item, ett_assoc_info_extneg); + + proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN); + proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN); + proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_info_extneg_sopclassuid_len, tvb, offset+4, 2, ENC_BIG_ENDIAN); + + sopclassuid_str = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset+6, sop_class_uid_len, ENC_ASCII); + sopclassuid = (dcm_uid_t *)wmem_map_lookup(dcm_uid_table, (gpointer) sopclassuid_str); + + if (sopclassuid) { + buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", sopclassuid->name, sopclassuid->value); + } + else { + buf_desc = sopclassuid_str; + } + + proto_item_append_text(assoc_item_extneg_item, "%s", buf_desc); + proto_tree_add_string(assoc_item_extneg_tree, hf_dcm_info_extneg_sopclassuid, tvb, offset+6, sop_class_uid_len, buf_desc); + + /* Count how many fields are following. */ + cnt = item_len - 2 - sop_class_uid_len; + + /* + * The next field contains Service Class specific information identified by the SOP Class UID. + */ + if (0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_FIND_RETIRED) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_MOVE) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_MOVE) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_MOVE_RETIRED) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_GET) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_GET) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_GET_RETIRED)) + { + if (cnt<=0) + { + return; + } + + /* Support for Relational queries. */ + proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_info_extneg_relational_query, tvb, offset+6+sop_class_uid_len, 1, ENC_BIG_ENDIAN); + --cnt; + } + + /* More sub-items are only allowed for the C-FIND SOP Classes. */ + if (0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) || + 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_FIND_RETIRED)) + { + if (cnt<=0) + { + return; + } + + /* Combined Date-Time matching. */ + proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_info_extneg_date_time_matching, tvb, offset+7+sop_class_uid_len, 1, ENC_BIG_ENDIAN); + --cnt; + + if (cnt<=0) + { + return; + } + + /* Fuzzy semantic matching of person names. */ + proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_info_extneg_fuzzy_semantic_matching, tvb, offset+8+sop_class_uid_len, 1, ENC_BIG_ENDIAN); + --cnt; + + if (cnt<=0) + { + return; + } + + /* Timezone query adjustment. */ + proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_info_extneg_timezone_query_adjustment, tvb, offset+9+sop_class_uid_len, 1, ENC_BIG_ENDIAN); + --cnt; + } +} + +/* +Decode user identities in the association +*/ +static void +dissect_dcm_assoc_user_identify(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset) +{ + + proto_tree *assoc_item_user_identify_tree = NULL; /* Tree for item details */ + proto_item *assoc_item_user_identify_item = NULL; + + guint16 primary_field_length, secondary_field_length, item_len = 0; + guint8 type; + + item_len = tvb_get_ntohs(tvb, offset+2); + + assoc_item_user_identify_item = proto_tree_add_item(tree, hf_dcm_info_user_identify, tvb, offset, item_len+4, ENC_NA); + assoc_item_user_identify_tree = proto_item_add_subtree(assoc_item_user_identify_item, ett_assoc_info_user_identify); + + proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN); + offset += 2; + proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_assoc_item_len, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + + type = tvb_get_guint8(tvb, offset); + proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_type, tvb, offset, 1, ENC_BIG_ENDIAN); + offset += 1; + + proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_response_requested, tvb, offset, 1, ENC_BIG_ENDIAN); + offset += 1; + + primary_field_length = tvb_get_ntohs(tvb, offset); + proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_primary_field_length, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + + proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_primary_field, tvb, offset, primary_field_length, ENC_UTF_8); + proto_item_append_text(assoc_item_user_identify_item, ": %s", tvb_get_string_enc(pinfo->pool, tvb, offset, primary_field_length, ENC_UTF_8|ENC_NA)); + offset += primary_field_length; + + if (type == 2) { + secondary_field_length = tvb_get_ntohs(tvb, offset); + proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_secondary_field_length, tvb, offset, 2, ENC_BIG_ENDIAN); + offset += 2; + + proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_secondary_field, tvb, offset, secondary_field_length, ENC_UTF_8); + proto_item_append_text(assoc_item_user_identify_item, ", %s", tvb_get_string_enc(pinfo->pool, tvb, offset, secondary_field_length, ENC_UTF_8|ENC_NA)); + } +} + +/* +Decode unknown item types in the association +*/ +static void +dissect_dcm_assoc_unknown(tvbuff_t *tvb, proto_tree *tree, guint32 offset) +{ + + proto_tree *assoc_item_unknown_tree = NULL; /* Tree for item details */ + proto_item *assoc_item_unknown_item = NULL; + + guint16 item_len = 0; + + item_len = tvb_get_ntohs(tvb, offset+2); + + assoc_item_unknown_item = proto_tree_add_item(tree, hf_dcm_info_unknown, tvb, offset, item_len+4, ENC_NA); + assoc_item_unknown_tree = proto_item_add_subtree(assoc_item_unknown_item, ett_assoc_info_unknown); + + proto_tree_add_item(assoc_item_unknown_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN); + proto_tree_add_item(assoc_item_unknown_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN); + offset += 4; + + proto_tree_add_item(assoc_item_unknown_tree, hf_dcm_assoc_item_data, tvb, offset, item_len, ENC_NA); +} + +/* +Decode the SCP/SCU Role Selection Sub-Item Fields in a association request or response. +Lookup UIDs if requested +*/ +static void +dissect_dcm_assoc_role_selection(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset) +{ + + proto_tree *assoc_item_rolesel_tree; /* Tree for item details */ + proto_item *assoc_item_rolesel_item; + + guint16 item_len, sop_class_uid_len; + guint8 scp_role, scu_role; + + gchar *buf_desc; /* Used for item text */ + dcm_uid_t *sopclassuid; + gchar *sopclassuid_str; + + item_len = tvb_get_ntohs(tvb, offset+2); + sop_class_uid_len = tvb_get_ntohs(tvb, offset+4); + + assoc_item_rolesel_item = proto_tree_add_item(tree, hf_dcm_info_rolesel, tvb, offset, item_len+4, ENC_NA); + proto_item_set_text(assoc_item_rolesel_item, "Role Selection: "); + assoc_item_rolesel_tree = proto_item_add_subtree(assoc_item_rolesel_item, ett_assoc_info_rolesel); + + proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN); + proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN); + proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_info_rolesel_sopclassuid_len, tvb, offset+4, 2, ENC_BIG_ENDIAN); + + sopclassuid_str = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset+6, sop_class_uid_len, ENC_ASCII); + sopclassuid = (dcm_uid_t *)wmem_map_lookup(dcm_uid_table, (gpointer) sopclassuid_str); + + scu_role = tvb_get_guint8(tvb, offset+6+sop_class_uid_len); + scp_role = tvb_get_guint8(tvb, offset+7+sop_class_uid_len); + + if (scu_role) { + proto_item_append_text(assoc_item_rolesel_item, "%s", "SCU-role: yes"); + } + else { + proto_item_append_text(assoc_item_rolesel_item, "%s", "SCU-role: no"); + } + + if (scp_role) { + proto_item_append_text(assoc_item_rolesel_item, ", %s", "SCP-role: yes"); + } + else { + proto_item_append_text(assoc_item_rolesel_item, ", %s", "SCP-role: no"); + } + + if (sopclassuid) { + buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", sopclassuid->name, sopclassuid->value); + } + else { + buf_desc = sopclassuid_str; + } + + proto_tree_add_string(assoc_item_rolesel_tree, hf_dcm_info_rolesel_sopclassuid, tvb, offset+6, sop_class_uid_len, buf_desc); + + proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_info_rolesel_scurole, tvb, offset+6+sop_class_uid_len, 1, ENC_BIG_ENDIAN); + proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_info_rolesel_scprole, tvb, offset+7+sop_class_uid_len, 1, ENC_BIG_ENDIAN); +} + +/* +Decode the Asynchronous operations (and sub-operations) Window Negotiation Sub-Item Fields in a association request or response. +*/ +static void +dissect_dcm_assoc_async_negotiation(tvbuff_t *tvb, proto_tree *tree, guint32 offset) +{ + + proto_tree *assoc_item_asyncneg_tree; /* Tree for item details */ + proto_item *assoc_item_asyncneg_item; + + guint16 item_len, max_num_ops_inv, max_num_ops_per = 0; + + item_len = tvb_get_ntohs(tvb, offset+2); + + assoc_item_asyncneg_item = proto_tree_add_item(tree, hf_dcm_info_async_neg, tvb, offset, item_len+4, ENC_NA); + proto_item_set_text(assoc_item_asyncneg_item, "Async Negotiation: "); + assoc_item_asyncneg_tree = proto_item_add_subtree(assoc_item_asyncneg_item, ett_assoc_info_async_neg); + + proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN); + proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN); + proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_info_async_neg_max_num_ops_inv, tvb, offset+4, 2, ENC_BIG_ENDIAN); + proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_info_async_neg_max_num_ops_per, tvb, offset+6, 2, ENC_BIG_ENDIAN); + + max_num_ops_inv = tvb_get_ntohs(tvb, offset+4); + max_num_ops_per = tvb_get_ntohs(tvb, offset+6); + + proto_item_append_text(assoc_item_asyncneg_item, "%s%d", "Maximum Number Operations Invoked: ", max_num_ops_inv); + if (max_num_ops_inv==0) proto_item_append_text(assoc_item_asyncneg_item, "%s", " (unlimited)"); + proto_item_append_text(assoc_item_asyncneg_item, ", %s%d", "Maximum Number Operations Performed: ", max_num_ops_per); + if (max_num_ops_per==0) proto_item_append_text(assoc_item_asyncneg_item, "%s", " (unlimited)"); +} + +/* +Decode a presentation context item in a Association Request or Response. In the response, set the accepted transfer syntax, if any. +*/ +static void +dissect_dcm_pctx(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + dcm_state_assoc_t *assoc, guint32 offset, guint32 len, + const gchar *pitem_prefix, gboolean is_assoc_request) +{ + + proto_tree *pctx_ptree; /* Tree for presentation context details */ + proto_item *pctx_pitem; + + dcm_state_pctx_t *pctx = NULL; + + guint8 item_type = 0; + guint16 item_len = 0; + + guint8 pctx_id = 0; /* Presentation Context ID */ + guint8 pctx_result = 0; + + const char *pctx_result_desc = ""; + + gchar *pctx_abss_uid = NULL; /* Abstract Syntax UID alias SOP Class UID */ + const gchar *pctx_abss_desc = NULL; /* Description of UID */ + + gchar *pctx_xfer_uid = NULL; /* Transfer Syntax UID */ + const gchar *pctx_xfer_desc = NULL; /* Description of UID */ + + gchar *buf_desc; /* Used in info mode for item text */ + + guint32 endpos = 0; + int cnt_abbs = 0; /* Number of Abstract Syntax Items */ + int cnt_xfer = 0; /* Number of Transfer Syntax Items */ + + endpos = offset + len; + + item_type = tvb_get_guint8(tvb, offset-4); + item_len = tvb_get_ntohs(tvb, offset-2); + + pctx_ptree = proto_tree_add_subtree(tree, tvb, offset-4, item_len+4, ett_assoc_pctx, &pctx_pitem, pitem_prefix); + + pctx_id = tvb_get_guint8(tvb, offset); + pctx_result = tvb_get_guint8(tvb, 2 + offset); /* only set in responses, otherwise reserved and 0x00 */ + + /* Find or create DICOM context object */ + pctx = dcm_state_pctx_get(assoc, pctx_id, TRUE); + if (pctx == NULL) { /* Internal error. Failed to create data structure */ + return; + } + + proto_tree_add_uint(pctx_ptree, hf_dcm_assoc_item_type, tvb, offset-4, 1, item_type); /* The type is only one byte long */ + proto_tree_add_uint(pctx_ptree, hf_dcm_assoc_item_len, tvb, offset-2, 2, item_len); + + proto_tree_add_uint_format(pctx_ptree, hf_dcm_pctx_id, tvb, offset, 1, pctx_id, "Context ID: 0x%02x", pctx_id); + + if (!is_assoc_request) { + /* Association response. */ + + switch (pctx_result) { + case 0: pctx_result_desc = "Accept"; break; + case 1: pctx_result_desc = "User Reject"; break; + case 2: pctx_result_desc = "No Reason"; break; + case 3: pctx_result_desc = "Abstract Syntax Unsupported"; break; + case 4: pctx_result_desc = "Transfer Syntax Unsupported"; break; + } + + proto_tree_add_uint_format(pctx_ptree, hf_dcm_pctx_result, tvb, offset+2, 1, + pctx_result, "Result: %s (0x%x)", pctx_result_desc, pctx_result); + } + + offset += 4; + while (offset < endpos) { + + item_type = tvb_get_guint8(tvb, offset); + item_len = tvb_get_ntohs(tvb, 2 + offset); + + offset += 4; + switch (item_type) { + case 0x30: /* Abstract syntax */ + + /* Parse Item. Works also in info mode where dcm_pctx_tree is NULL */ + dissect_dcm_assoc_item(tvb, pinfo, pctx_ptree, offset-4, + "Abstract Syntax: ", DCM_ITEM_VALUE_TYPE_UID, &pctx_abss_uid, &pctx_abss_desc, + &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_pctx_abss_syntax, ett_assoc_pctx_abss); + + cnt_abbs += 1; + offset += item_len; + break; + + case 0x40: /* Transfer syntax */ + + dissect_dcm_assoc_item(tvb, pinfo, pctx_ptree, offset-4, + "Transfer Syntax: ", DCM_ITEM_VALUE_TYPE_UID, &pctx_xfer_uid, &pctx_xfer_desc, + &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_pctx_xfer_syntax, ett_assoc_pctx_xfer); + + /* + In a correct Association Response, only one Transfer syntax shall be present. + Therefore, pctx_xfer_uid, pctx_xfer_desc are used for the accept scenario in the info mode + */ + + if (!is_assoc_request && pctx_result == 0) { + /* Association Response, Context Accepted */ + dcm_set_syntax(pctx, pctx_xfer_uid, pctx_xfer_desc); + } + cnt_xfer += 1; + offset += item_len; + break; + + default: + offset += item_len; + break; + } + } + + if (is_assoc_request) { + + if (cnt_abbs<1) { + expert_add_info(pinfo, pctx_pitem, &ei_dcm_no_abstract_syntax); + return; + } + else if (cnt_abbs>1) { + expert_add_info(pinfo, pctx_pitem, &ei_dcm_multiple_abstract_syntax); + return; + } + + if (cnt_xfer==0) { + expert_add_info(pinfo, pctx_pitem, &ei_dcm_no_transfer_syntax); + return; + } + + if (pctx_abss_uid==NULL) { + expert_add_info(pinfo, pctx_pitem, &ei_dcm_no_abstract_syntax_uid); + return; + } + + } + else { + + if (cnt_xfer>1) { + expert_add_info(pinfo, pctx_pitem, &ei_dcm_multiple_transfer_syntax); + return; + } + } + + if (pctx->abss_uid==NULL) { + /* Permanent copy information into structure */ + pctx->abss_uid = wmem_strdup(wmem_file_scope(), pctx_abss_uid); + pctx->abss_desc = wmem_strdup(wmem_file_scope(), pctx_abss_desc); + } + + /* + Copy to buffer first, because proto_item_append_text() + crashed for an unknown reason using 'ID 0x%02x, %s, %s' + and in my opinion correctly set parameters. + */ + + if (is_assoc_request) { + if (pctx_abss_desc == NULL) { + buf_desc = pctx_abss_uid; + } + else { + buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", pctx_abss_desc, pctx_abss_uid); + } + } + else + { + if (pctx_result==0) { + /* Accepted */ + buf_desc = wmem_strdup_printf(pinfo->pool, "ID 0x%02x, %s, %s, %s", + pctx_id, pctx_result_desc, + dcm_uid_or_desc(pctx->xfer_uid, pctx->xfer_desc), + dcm_uid_or_desc(pctx->abss_uid, pctx->abss_desc)); + } + else { + /* Rejected */ + buf_desc = wmem_strdup_printf(pinfo->pool, "ID 0x%02x, %s, %s", + pctx_id, pctx_result_desc, + dcm_uid_or_desc(pctx->abss_uid, pctx->abss_desc)); + } + } + proto_item_append_text(pctx_pitem, "%s", buf_desc); + +} + +/* +Decode the user info item in a Association Request or Response +*/ +static void +dissect_dcm_userinfo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, guint32 len, const gchar *pitem_prefix) +{ + + proto_item *userinfo_pitem = NULL; + proto_tree *userinfo_ptree = NULL; /* Tree for presentation context details */ + + guint8 item_type; + guint16 item_len; + + gboolean first_item=TRUE; + + gchar *info_max_pdu=NULL; + gchar *info_impl_uid=NULL; + gchar *info_impl_version=NULL; + const gchar *dummy=NULL; + + guint32 endpos; + + endpos = offset + len; + + item_type = tvb_get_guint8(tvb, offset-4); + item_len = tvb_get_ntohs(tvb, offset-2); + + userinfo_pitem = proto_tree_add_item(tree, hf_dcm_info, tvb, offset-4, item_len+4, ENC_NA); + proto_item_set_text(userinfo_pitem, "%s", pitem_prefix); + userinfo_ptree = proto_item_add_subtree(userinfo_pitem, ett_assoc_info); + + proto_tree_add_uint(userinfo_ptree, hf_dcm_assoc_item_type, tvb, offset-4, 1, item_type); /* The type is only one byte long */ + proto_tree_add_uint(userinfo_ptree, hf_dcm_assoc_item_len, tvb, offset-2, 2, item_len); + + while (offset < endpos) { + + item_type = tvb_get_guint8(tvb, offset); + item_len = tvb_get_ntohs(tvb, 2 + offset); + + offset += 4; + switch (item_type) { + case 0x51: /* Max length */ + + dissect_dcm_assoc_item(tvb, pinfo, userinfo_ptree, offset-4, + "Max PDU Length: ", DCM_ITEM_VALUE_TYPE_UINT32, &info_max_pdu, &dummy, + &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_pdu_maxlen, ett_assoc_info_uid); + + if (!first_item) { + proto_item_append_text(userinfo_pitem, ", "); + } + proto_item_append_text(userinfo_pitem, "Max PDU Length %s", info_max_pdu); + first_item=FALSE; + + offset += item_len; + break; + + case 0x52: /* UID */ + + /* Parse Item. Works also in info mode where dcm_pctx_tree is NULL */ + dissect_dcm_assoc_item(tvb, pinfo, userinfo_ptree, offset-4, + "Implementation UID: ", DCM_ITEM_VALUE_TYPE_STRING, &info_impl_uid, &dummy, + &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_info_uid, ett_assoc_info_uid); + + if (!first_item) { + proto_item_append_text(userinfo_pitem, ", "); + } + proto_item_append_text(userinfo_pitem, "Implementation UID %s", info_impl_uid); + first_item=FALSE; + + offset += item_len; + break; + + case 0x55: /* version */ + + dissect_dcm_assoc_item(tvb, pinfo, userinfo_ptree, offset-4, + "Implementation Version: ", DCM_ITEM_VALUE_TYPE_STRING, &info_impl_version, &dummy, + &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_info_version, ett_assoc_info_version); + + if (!first_item) { + proto_item_append_text(userinfo_pitem, ", "); + } + proto_item_append_text(userinfo_pitem, "Version %s", info_impl_version); + first_item=FALSE; + + offset += item_len; + break; + + case 0x53: /* async negotiation */ + + dissect_dcm_assoc_async_negotiation(tvb, userinfo_ptree, offset-4); + + offset += item_len; + break; + + case 0x54: /* scp/scu role selection */ + + dissect_dcm_assoc_role_selection(tvb, pinfo, userinfo_ptree, offset-4); + + offset += item_len; + break; + + case 0x56: /* extended negotiation */ + + dissect_dcm_assoc_sopclass_extneg(tvb, pinfo, userinfo_ptree, offset-4); + + offset += item_len; + break; + + case 0x58: /* User Identify */ + + dissect_dcm_assoc_user_identify(tvb, pinfo, userinfo_ptree, offset-4); + + offset += item_len; + break; + + default: + + dissect_dcm_assoc_unknown(tvb, userinfo_ptree, offset-4); + + offset += item_len; + break; + } + } +} + + +/* +Create a subtree for association requests or responses +*/ +static guint32 +dissect_dcm_assoc_detail(tvbuff_t *tvb, packet_info *pinfo, proto_item *ti, + dcm_state_assoc_t *assoc, guint32 offset, guint32 len) +{ + proto_tree *assoc_tree = NULL; /* Tree for PDU details */ + + guint8 item_type; + guint16 item_len; + + guint32 endpos; + + gchar *item_value = NULL; + const gchar *item_description = NULL; + + endpos = offset + len; + + assoc_tree = proto_item_add_subtree(ti, ett_assoc); + while (offset < endpos) { + + item_type = tvb_get_guint8(tvb, offset); + item_len = tvb_get_ntohs(tvb, 2 + offset); + + if (item_len == 0) { + expert_add_info(pinfo, ti, &ei_dcm_assoc_item_len); + return endpos; + } + + offset += 4; + + switch (item_type) { + case 0x10: /* Application context */ + dissect_dcm_assoc_item(tvb, pinfo, assoc_tree, offset-4, + "Application Context: ", DCM_ITEM_VALUE_TYPE_UID, &item_value, &item_description, + &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_actx, ett_assoc_actx); + + offset += item_len; + break; + + case 0x20: /* Presentation context request */ + dissect_dcm_pctx(tvb, pinfo, assoc_tree, assoc, offset, item_len, "Presentation Context: ", TRUE); + offset += item_len; + break; + + case 0x21: /* Presentation context reply */ + dissect_dcm_pctx(tvb, pinfo, assoc_tree, assoc, offset, item_len, "Presentation Context: ", FALSE); + offset += item_len; + break; + + case 0x50: /* User Info */ + dissect_dcm_userinfo(tvb, pinfo, assoc_tree, offset, item_len, "User Info: "); + offset += item_len; + break; + + default: + offset += item_len; + break; + } + } + + return offset; + +} + +static guint32 +dissect_dcm_pdv_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + dcm_state_assoc_t *assoc, guint32 offset, dcm_state_pdv_t **pdv) +{ + /* Dissect Context and Flags of a PDV and create new PDV structure */ + + proto_item *pdv_ctx_pitem = NULL; + proto_item *pdv_flags_pitem = NULL; + + dcm_state_pctx_t *pctx = NULL; + dcm_state_pdv_t *pdv_first_data = NULL; + + const gchar *desc_flag = NULL; /* Flag Description in tree */ + gchar *desc_header = NULL; /* Used for PDV description */ + + guint8 flags = 0, o_flags = 0; + guint8 pctx_id = 0; + + /* 1 Byte Context */ + pctx_id = tvb_get_guint8(tvb, offset); + pctx = dcm_state_pctx_get(assoc, pctx_id, FALSE); + + if (pctx && pctx->xfer_uid) { + proto_tree_add_uint_format(tree, hf_dcm_pdv_ctx, tvb, offset, 1, + pctx_id, "Context: 0x%02x (%s, %s)", pctx_id, + dcm_uid_or_desc(pctx->xfer_uid, pctx->xfer_desc), + dcm_uid_or_desc(pctx->abss_uid, pctx->abss_desc)); + } + else { + pdv_ctx_pitem=proto_tree_add_uint_format(tree, hf_dcm_pdv_ctx, tvb, offset, 1, + pctx_id, "Context: 0x%02x not found. A-ASSOCIATE request not found in capture.", pctx_id); + + expert_add_info(pinfo, pdv_ctx_pitem, &ei_dcm_pdv_ctx); + + if (pctx == NULL) { + /* only create presentation context, if it does not yet exist */ + + /* Create fake PCTX and guess Syntax ILE, ELE, EBE */ + pctx = dcm_state_pctx_new(assoc, pctx_id); + + /* To be done: Guess Syntax */ + pctx->syntax = DCM_UNK; + } + } + offset +=1; + + /* Create PDV structure: + + Since we can have multiple PDV per packet (offset) and + multiple merged packets per PDV (the tvb raw_offset) + we need both values to uniquely identify a PDV + */ + + *pdv = dcm_state_pdv_get(pctx, pinfo->num, tvb_raw_offset(tvb)+offset, TRUE); + if (*pdv == NULL) { + return 0; /* Failed to allocate memory */ + } + + /* 1 Byte Flag */ + /* PS3.8 E.2 Bits 2 through 7 are always set to 0 by the sender and never checked by the receiver. */ + o_flags = tvb_get_guint8(tvb, offset); + flags = 0x3 & o_flags; + + (*pdv)->pctx_id = pctx_id; + + switch (flags) { + case 0: /* 00 */ + if (0 != (0xfc & o_flags)) + desc_flag = "Data, More Fragments (Warning: Invalid)"; + else + desc_flag = "Data, More Fragments"; + + (*pdv)->is_flagvalid = TRUE; + (*pdv)->is_command = FALSE; + (*pdv)->is_last_fragment = FALSE; + (*pdv)->syntax = pctx->syntax; /* Inherit syntax for data PDVs*/ + break; + + case 2: /* 10 */ + if (0 != (0xfc & o_flags)) + desc_flag = "Data, Last Fragment (Warning: Invalid)"; + else + desc_flag = "Data, Last Fragment"; + + (*pdv)->is_flagvalid = TRUE; + (*pdv)->is_command = FALSE; + (*pdv)->is_last_fragment = TRUE; + (*pdv)->syntax = pctx->syntax; /* Inherit syntax for data PDVs*/ + break; + + case 1: /* 01 */ + if (0 != (0xfc & o_flags)) + desc_flag = "Command, More Fragments (Warning: Invalid)"; + else + desc_flag = "Command, More Fragments"; + desc_header = wmem_strdup(wmem_file_scope(), "Command"); /* Will be overwritten with real command tag */ + + (*pdv)->is_flagvalid = TRUE; + (*pdv)->is_command = TRUE; + (*pdv)->is_last_fragment = FALSE; + (*pdv)->syntax = DCM_ILE; /* Command tags are always little endian*/ + break; + + case 3: /* 11 */ + if (0 != (0xfc & o_flags)) + desc_flag = "Command, Last Fragment (Warning: Invalid)"; + else + desc_flag = "Command, Last Fragment"; + desc_header = wmem_strdup(wmem_file_scope(), "Command"); + + (*pdv)->is_flagvalid = TRUE; + (*pdv)->is_command = TRUE; + (*pdv)->is_last_fragment = TRUE; + (*pdv)->syntax = DCM_ILE; /* Command tags are always little endian*/ + break; + + default: + desc_flag = "Invalid Flags"; + desc_header = wmem_strdup(wmem_file_scope(), desc_flag); + + (*pdv)->is_flagvalid = FALSE; + (*pdv)->is_command = FALSE; + (*pdv)->is_last_fragment = FALSE; + (*pdv)->syntax = DCM_UNK; + } + + if (!PINFO_FD_VISITED(pinfo)) { + (*pdv)->reassembly_id = pctx->reassembly_count; + if ((*pdv)->is_last_fragment) { + pctx->reassembly_count++; + } + } + + if (flags == 0 || flags == 2) { + /* Data PDV */ + pdv_first_data = dcm_state_pdv_get_obj_start(*pdv); + + if (pdv_first_data->prev && pdv_first_data->prev->is_command) { + /* Every Data PDV sequence should be preceded by a Command PDV, + so we should always hit this for a correct capture + */ + + if (pctx->abss_desc && g_str_has_suffix(pctx->abss_desc, "Storage")) { + /* Should be done far more intelligent, e.g. does not catch the (Retired) ones */ + if (flags == 0) { + desc_header = wmem_strdup_printf(wmem_file_scope(), "%s Fragment", pctx->abss_desc); + } + else { + desc_header = wmem_strdup(wmem_file_scope(), pctx->abss_desc); + } + (*pdv)->is_storage = TRUE; + } + else { + /* Use previous command and append DATA*/ + desc_header = wmem_strdup_printf(wmem_file_scope(), "%s-DATA", pdv_first_data->prev->desc); + } + } + else { + desc_header = wmem_strdup(wmem_file_scope(), "DATA"); + } + } + + (*pdv)->desc = desc_header; + + pdv_flags_pitem = proto_tree_add_uint_format(tree, hf_dcm_pdv_flags, tvb, offset, 1, + flags, "Flags: 0x%02x (%s)", o_flags, desc_flag); + + if (o_flags>3) { + expert_add_info(pinfo, pdv_flags_pitem, &ei_dcm_pdv_flags); + } + offset +=1; + + return offset; +} + +/* +Based on the value representation, decode the value of one tag. +Support VM>1 for most types, but not all. Returns new offset +*/ +static guint32 +dissect_dcm_tag_value(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, dcm_state_pdv_t *pdv, + guint32 offset, guint16 grp, guint16 elm, + guint32 vl, guint32 vl_max, const gchar* vr, gchar **tag_value) +{ + + proto_item *pitem = NULL; + guint encoding = (pdv->syntax == DCM_EBE) ? ENC_BIG_ENDIAN : ENC_LITTLE_ENDIAN; + + + /* Make sure we have all the bytes of the item; this should throw + and exception if vl_max is so large that it causes the offset + to overflow. */ + tvb_ensure_bytes_exist(tvb, offset, vl_max); + + /* --------------------------------------------------------------------------- + Potentially long types. Obey vl_max + --------------------------------------------------------------------------- + */ + + if ((strncmp(vr, "AE", 2) == 0) || (strncmp(vr, "AS", 2) == 0) || (strncmp(vr, "CS", 2) == 0) || + (strncmp(vr, "DA", 2) == 0) || (strncmp(vr, "DS", 2) == 0) || (strncmp(vr, "DT", 2) == 0) || + (strncmp(vr, "IS", 2) == 0) || (strncmp(vr, "LO", 2) == 0) || (strncmp(vr, "LT", 2) == 0) || + (strncmp(vr, "PN", 2) == 0) || (strncmp(vr, "SH", 2) == 0) || (strncmp(vr, "ST", 2) == 0) || + (strncmp(vr, "TM", 2) == 0) || (strncmp(vr, "UI", 2) == 0) || (strncmp(vr, "UT", 2) == 0) ) { + /* + 15 ways to represent a string. + + For LT, ST, UT the DICOM standard does not allow multi-value + For the others, VM is built into 'automatically, because it uses '\' as separator + */ + + gchar *vals; + dcm_uid_t *uid = NULL; + guint8 val8; + + val8 = tvb_get_guint8(tvb, offset + vl_max - 1); + if (val8 == 0x00) { + /* Last byte of string is 0x00, i.e. padded */ + vals = tvb_format_text(pinfo->pool, tvb, offset, vl_max - 1); + } + else { + vals = tvb_format_text(pinfo->pool, tvb, offset, vl_max); + } + + if (grp == 0x0000 && elm == 0x0902) { + /* The error comment */ + pdv->comment = g_strstrip(wmem_strdup(wmem_file_scope(), vals)); + } + + if ((strncmp(vr, "UI", 2) == 0)) { + /* This is a UID. Attempt a lookup. Will only return something for classes of course */ + + uid = (dcm_uid_t *)wmem_map_lookup(dcm_uid_table, (gpointer) vals); + if (uid) { + *tag_value = wmem_strdup_printf(pinfo->pool, "%s (%s)", vals, uid->name); + } + else { + *tag_value = vals; + } + } + else { + if (strlen(vals) > 50) { + *tag_value = wmem_strdup_printf(pinfo->pool, "%s%s", ws_utf8_truncate(vals, 50), UTF8_HORIZONTAL_ELLIPSIS); + } + else { + *tag_value = vals; + } + } + proto_tree_add_string(tree, hf_dcm_tag_value_str, tvb, offset, vl_max, *tag_value); + + } + else if ((strncmp(vr, "OB", 2) == 0) || (strncmp(vr, "OW", 2) == 0) || + (strncmp(vr, "OF", 2) == 0) || (strncmp(vr, "OD", 2) == 0)) { + + /* Array of Bytes, Words, Float, or Doubles. Don't perform any decoding. VM=1. Multiple arrays are not possible */ + + proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, vl_max, NULL, "%s", "(binary)"); + + *tag_value = wmem_strdup(pinfo->pool, "(binary)"); + } + else if (strncmp(vr, "UN", 2) == 0) { + + /* Usually the case for private tags in implicit syntax, since tag was not found and VR not specified. + Not been able to create UN yet. No need to support VM > 1. + */ + + guint8 val8; + gchar *vals; + guint32 i; + + /* String detector, i.e. check if we only have alpha-numeric character */ + gboolean is_string = TRUE; + gboolean is_padded = FALSE; + + for (i = 0; i < vl_max ; i++) { + val8 = tvb_get_guint8(tvb, offset + i); + + if ((val8 == 0x09) || (val8 == 0x0A) || (val8 == 0x0D)) { + /* TAB, LF, CR */ + } + else if ((val8 >= 0x20) && (val8 <= 0x7E)) { + /* No extended ASCII, 0-9, A-Z, a-z */ + } + else if ((i == vl_max -1) && (val8 == 0x00)) { + /* Last Byte can be null*/ + is_padded = TRUE; + } + else { + /* Here's the code */ + is_string = FALSE; + } + } + + if (is_string) { + vals = tvb_format_text(pinfo->pool, tvb, offset, (is_padded ? vl_max - 1 : vl_max)); + proto_tree_add_string(tree, hf_dcm_tag_value_str, tvb, offset, vl_max, vals); + + *tag_value = vals; + } + else { + proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, vl_max, NULL, "%s", "(binary)"); + + *tag_value = wmem_strdup(pinfo->pool, "(binary)"); + } + } + /* --------------------------------------------------------------------------- + Smaller types. vl/vl_max are not used. Fixed item length from 2 to 8 bytes + --------------------------------------------------------------------------- + */ + else if (strncmp(vr, "AT", 2) == 0) { + + /* Attribute Tag e.g. (0022,8866). 2*2 Bytes, Can have VM > 1 */ + + guint16 at_grp; + guint16 at_elm; + gchar *at_value = ""; + + /* In on capture the reported length for this tag was 2 bytes. And since vl_max is unsigned long, -3 caused it to be 2^32-1 + So make it at least one loop so set it to at least 4. + */ + + guint32 vm_item_len = 4; + guint32 vm_item_count = dcm_vm_item_count(vl_max, vm_item_len); + + guint32 i = 0; + while (i < vm_item_count) { + at_grp = tvb_get_guint16(tvb, offset+ i*vm_item_len, encoding); + at_elm = tvb_get_guint16(tvb, offset+ i*vm_item_len+2, encoding); + + proto_tree_add_uint_format_value(tree, hf_dcm_tag_value_32u, tvb, offset + i*vm_item_len, vm_item_len, + (at_grp << 16) | at_elm, "%04x,%04x", at_grp, at_elm); + + at_value = wmem_strdup_printf(pinfo->pool,"%s(%04x,%04x)", at_value, at_grp, at_elm); + + i++; + } + *tag_value = at_value; + } + else if (strncmp(vr, "FL", 2) == 0) { /* Single Float. Can be VM > 1, but not yet supported */ + + gfloat valf = tvb_get_ieee_float(tvb, offset, encoding); + + proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, 4, NULL, "%f", valf); + + *tag_value = wmem_strdup_printf(pinfo->pool, "%f", valf); + } + else if (strncmp(vr, "FD", 2) == 0) { /* Double Float. Can be VM > 1, but not yet supported */ + + gdouble vald = tvb_get_ieee_double(tvb, offset, encoding); + + proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, 8, NULL, "%f", vald); + + *tag_value = wmem_strdup_printf(pinfo->pool, "%f", vald); + } + else if (strncmp(vr, "SL", 2) == 0) { /* Signed Long. Can be VM > 1, but not yet supported */ + gint32 val32; + + proto_tree_add_item_ret_int(tree, hf_dcm_tag_value_32s, tvb, offset, 4, encoding, &val32); + + *tag_value = wmem_strdup_printf(pinfo->pool, "%d", val32); + } + else if (strncmp(vr, "SS", 2) == 0) { /* Signed Short. Can be VM > 1, but not yet supported */ + gint32 val32; + + proto_tree_add_item_ret_int(tree, hf_dcm_tag_value_16s, tvb, offset, 2, encoding, &val32); + + *tag_value = wmem_strdup_printf(pinfo->pool, "%d", val32); + } + else if (strncmp(vr, "UL", 2) == 0) { /* Unsigned Long. Can be VM > 1, but not yet supported */ + guint32 val32; + + proto_tree_add_item_ret_uint(tree, hf_dcm_tag_value_32u, tvb, offset, 4, encoding, &val32); + + *tag_value = wmem_strdup_printf(pinfo->pool, "%u", val32); + } + else if (strncmp(vr, "US", 2) == 0) { /* Unsigned Short. Can be VM > 1, but not yet supported */ + const gchar *status_message = NULL; + guint16 val16 = tvb_get_guint16(tvb, offset, encoding); + + if (grp == 0x0000 && elm == 0x0100) { + /* This is a command */ + pdv->command = wmem_strdup(wmem_file_scope(), val_to_str_const(val16, dcm_cmd_vals, " ")); + *tag_value = pdv->command; + } + else if (grp == 0x0000 && elm == 0x0900) { + /* This is a status message. If value is not 0x0000, add an expert info */ + + status_message = dcm_rsp2str(val16); + *tag_value = wmem_strdup_printf(pinfo->pool, "%s (0x%02x)", status_message, val16); + + if ((val16 & 0xFF00) == 0xFF00) { + /* C-FIND also has a 0xFF01 as a valid response */ + pdv->is_pending = TRUE; + } + else if (val16 != 0x0000) { + /* Neither success nor pending */ + pdv->is_warning = TRUE; + } + + pdv->status = wmem_strdup(wmem_file_scope(), status_message); + + } + else { + *tag_value = wmem_strdup_printf(pinfo->pool, "%u", val16); + } + + if (grp == 0x0000) { + if (elm == 0x0110) { /* (0000,0110) Message ID */ + pdv->message_id = val16; + } + else if (elm == 0x0120) { /* (0000,0120) Message ID Being Responded To */ + pdv->message_id_resp = val16; + } + else if (elm == 0x1020) { /* (0000,1020) Number of Remaining Sub-operations */ + pdv->no_remaining = val16; + } + else if (elm == 0x1021) { /* (0000,1021) Number of Completed Sub-operations */ + pdv->no_completed = val16; + } + else if (elm == 0x1022) { /* (0000,1022) Number of Failed Sub-operations */ + pdv->no_failed = val16; + } + else if (elm == 0x1023) { /* (0000,1023) Number of Warning Sub-operations */ + pdv->no_warning = val16; + } + } + + pitem = proto_tree_add_uint_format_value(tree, hf_dcm_tag_value_16u, tvb, offset, 2, + val16, "%s", *tag_value); + + if (pdv->is_warning && status_message) { + expert_add_info(pinfo, pitem, &ei_dcm_status_msg); + } + } + /* Invalid VR, can only occur with Explicit syntax */ + else { + proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, vl_max, + NULL, "%s", (vl > vl_max ? "" : "(unknown VR)")); + + *tag_value = wmem_strdup(pinfo->pool, "(unknown VR)"); + } + offset += vl_max; + + return offset; + +} + +/* +Return true, if the required size does not fit at position 'offset'. +*/ +static gboolean +dcm_tag_is_open(dcm_state_pdv_t *pdv, guint32 startpos, guint32 offset, guint32 endpos, guint32 size_required) +{ + + if (offset + size_required > endpos) { + + pdv->open_tag.is_header_fragmented = TRUE; + pdv->open_tag.len_decoded = endpos - startpos; + + return TRUE; + } + else { + return FALSE; + } +} + +static dcm_tag_t* +dcm_tag_lookup(guint16 grp, guint16 elm) +{ + + static dcm_tag_t *tag_def = NULL; + + static dcm_tag_t tag_unknown = { 0x00000000, "(unknown)", "UN", "1", 0, 0}; + static dcm_tag_t tag_private = { 0x00000000, "Private Tag", "UN", "1", 0, 0 }; + static dcm_tag_t tag_private_grp_len = { 0x00000000, "Private Tag Group Length", "UL", "1", 0, 0 }; + static dcm_tag_t tag_grp_length = { 0x00000000, "Group Length", "UL", "1", 0, 0 }; + + /* Try a direct hit first before doing a masked search */ + tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | elm)); + + if (tag_def == NULL) { + + /* No match found */ + if ((grp & 0x0001) && (elm == 0x0000)) { + tag_def = &tag_private_grp_len; + } + else if (grp & 0x0001) { + tag_def = &tag_private; + } + else if (elm == 0x0000) { + tag_def = &tag_grp_length; + } + + /* There are a few tags that require a mask to be found */ + else if (((grp & 0xFF00) == 0x5000) || ((grp & 0xFF00) == 0x6000) || ((grp & 0xFF00) == 0x7F00)) { + /* Do a special for groups 0x50xx, 0x60xx and 0x7Fxx */ + tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER((((guint32)grp & 0xFF00) << 16) | elm)); + } + else if ((grp == 0x0020) && ((elm & 0xFF00) == 0x3100)) { + tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0xFF00))); + } + else if ((grp == 0x0028) && ((elm & 0xFF00) == 0x0400)) { + /* This map was done to 0x041x */ + tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0xFF0F) | 0x0010)); + } + else if ((grp == 0x0028) && ((elm & 0xFF00) == 0x0800)) { + tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0xFF0F))); + } + else if (grp == 0x1000) { + tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0x000F))); + } + else if (grp == 0x1010) { + tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0x0000))); + } + + if (tag_def == NULL) { + /* Still no match found */ + tag_def = &tag_unknown; + } + } + + return tag_def; +} + +static gchar* +dcm_tag_summary(packet_info *pinfo, guint16 grp, guint16 elm, guint32 vl, const gchar *tag_desc, const gchar *vr, + gboolean is_retired, gboolean is_implicit) +{ + + gchar *desc_mod; + gchar *tag_vl; + gchar *tag_sum; + + if (is_retired) { + desc_mod = wmem_strdup_printf(pinfo->pool, "(Retired) %-35.35s", tag_desc); + } + else { + desc_mod = wmem_strdup_printf(pinfo->pool, "%-45.45s", tag_desc); + } + + if (vl == 0xFFFFFFFF) { + tag_vl = wmem_strdup_printf(pinfo->pool, "%10.10s", "<udef>"); + } + else { + tag_vl = wmem_strdup_printf(pinfo->pool, "%10u", vl); /* Show as dec */ + } + + if (is_implicit) tag_sum = wmem_strdup_printf(pinfo->pool, "(%04x,%04x) %s %s", grp, elm, tag_vl, desc_mod); + else tag_sum = wmem_strdup_printf(pinfo->pool, "(%04x,%04x) %s %s [%s]", grp, elm, tag_vl, desc_mod, vr); + + return tag_sum; +} + +/* +Decode one tag. If it is a sequence or item start create a subtree. Returns new offset. +http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html +*/ +static guint32 +dissect_dcm_tag(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + dcm_state_pdv_t *pdv, guint32 offset, guint32 endpos, + gboolean is_first_tag, const gchar **tag_description, + gboolean *end_of_seq_or_item) +{ + + + proto_tree *tag_ptree = NULL; /* Tree for decoded tag details */ + proto_tree *seq_ptree = NULL; /* Possible subtree for sequences and items */ + + proto_item *tag_pitem = NULL; + dcm_tag_t *tag_def = NULL; + + gint ett; + + const gchar *vr = NULL; + gchar *tag_value = ""; /* Tag Value converted to a string */ + gchar *tag_summary; + + guint32 vl = 0; + guint16 vl_1 = 0; + guint16 vl_2 = 0; + + guint32 offset_tag = 0; /* Remember offsets for tree, since the tree */ + guint32 offset_vr = 0; /* header is created pretty late */ + guint32 offset_vl = 0; + + guint32 vl_max = 0; /* Max Value Length to Parse */ + + guint16 grp = 0; + guint16 elm = 0; + + guint32 len_decoded_remaing = 0; + + /* Decode the syntax a little more */ + guint32 encoding = (pdv->syntax == DCM_EBE) ? ENC_BIG_ENDIAN : ENC_LITTLE_ENDIAN; + gboolean is_implicit = (pdv->syntax == DCM_ILE); + gboolean is_vl_long = FALSE; /* True for 4 Bytes length fields */ + + gboolean is_sequence = FALSE; /* True for Sequence Tags */ + gboolean is_item = FALSE; /* True for Sequence Item Tags */ + + *tag_description = NULL; /* Reset description. It's wmem packet scope memory, so not really bad*/ + + offset_tag = offset; + + + if (pdv->prev && is_first_tag) { + len_decoded_remaing = pdv->prev->open_tag.len_decoded; + } + + + /* Since we may have a fragmented header, check for every attribute, + whether we have already decoded left-overs from the previous PDV. + Since we have implicit & explicit syntax, copying the open tag to + a buffer without decoding, would have caused tvb_get_xxtohs() + implementations on the copy. + + An alternative approach would have been to resemble the PDVs first. + + The attempts to reassemble without named sources (to be implemented) + were very sensitive to missing packets. In such a case, no packet + of a PDV chain was decoded, not even the start. + + So for the time being, use this rather cumbersome approach. + + For every two bytes (PDV length are always a factor of 2) + check whether we have enough data in the buffer and store the value + accordingly. In the next frame check, whether we have decoded this yet. + */ + + /* Group */ + if (len_decoded_remaing >= 2) { + grp = pdv->prev->open_tag.grp; + len_decoded_remaing -= 2; + } + else { + + if (dcm_tag_is_open(pdv, offset_tag, offset, endpos, 2)) + return endpos; /* Exit if needed */ + + grp = tvb_get_guint16(tvb, offset, encoding); + offset += 2; + pdv->open_tag.grp = grp; + } + + /* Element */ + if (len_decoded_remaing >= 2) { + elm = pdv->prev->open_tag.elm; + len_decoded_remaing -= 2; + } + else { + + if (dcm_tag_is_open(pdv, offset_tag, offset, endpos, 2)) + return endpos; /* Exit if needed */ + + elm = tvb_get_guint16(tvb, offset, encoding); + offset += 2; + pdv->open_tag.elm = elm; + } + + /* Find the best matching tag */ + tag_def = dcm_tag_lookup(grp, elm); + + /* Value Representation */ + offset_vr = offset; + if ((grp == 0xFFFE) && (elm == 0xE000 || elm == 0xE00D || elm == 0xE0DD)) { + /* Item start, Item Delimitation or Sequence Delimitation */ + vr = "UL"; + is_vl_long = TRUE; /* These tags always have a 4 byte length field */ + } + else if (is_implicit) { + /* Get VR from tag definition */ + vr = wmem_strdup(pinfo->pool, tag_def->vr); + is_vl_long = TRUE; /* Implicit always has 4 byte length field */ + } + else { + + if (len_decoded_remaing >= 2) { + vr = wmem_strdup(pinfo->pool, pdv->prev->open_tag.vr); + len_decoded_remaing -= 2; + } + else { + + /* Controlled exit, if VR does not fit. */ + if (dcm_tag_is_open(pdv, offset_tag, offset_vr, endpos, 2)) + return endpos; + + vr = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset, 2, ENC_ASCII); + offset += 2; + + wmem_free(wmem_file_scope(), pdv->open_tag.vr); + pdv->open_tag.vr = wmem_strdup(wmem_file_scope(), vr); /* needs to survive within a session */ + } + + if ((strcmp(vr, "OB") == 0) || (strcmp(vr, "OW") == 0) || (strcmp(vr, "OF") == 0) || (strcmp(vr, "OD") == 0) || (strcmp(vr, "OL") == 0) || + (strcmp(vr, "SQ") == 0) || (strcmp(vr, "UC") == 0) || (strcmp(vr, "UR") == 0) || (strcmp(vr, "UT") == 0) || (strcmp(vr, "UN") == 0)) { + /* Part 5, Table 7.1-1 in the standard */ + /* Length is always 4 bytes: OB, OD, OF, OL, OW, SQ, UC, UR, UT or UN */ + + is_vl_long = TRUE; + + /* Skip 2 Bytes */ + if (len_decoded_remaing >= 2) { + len_decoded_remaing -= 2; + } + else { + if (dcm_tag_is_open(pdv, offset_tag, offset_vr, endpos, 2)) + return endpos; + offset += 2; + } + } + else { + is_vl_long = FALSE; + } + } + + + /* Value Length. This is rather cumbersome code to get a 4 byte length, but in the + fragmented case, we have 2*2 bytes. So always use that pattern + */ + + offset_vl = offset; + if (len_decoded_remaing >= 2) { + vl_1 = pdv->prev->open_tag.vl_1; + len_decoded_remaing -= 2; + } + else { + + if (dcm_tag_is_open(pdv, offset_tag, offset_vl, endpos, 2)) + return endpos; + vl_1 = tvb_get_guint16(tvb, offset, encoding); + offset += 2; + pdv->open_tag.vl_1 = vl_1; + } + + if (is_vl_long) { + + if (len_decoded_remaing >= 2) { + vl_2 = pdv->prev->open_tag.vl_2; + } + else { + + if (dcm_tag_is_open(pdv, offset_tag, offset_vl+2, endpos, 2)) + return endpos; + vl_2 = tvb_get_guint16(tvb, offset, encoding); + offset += 2; + pdv->open_tag.vl_2 = vl_2; + } + + if (encoding == ENC_LITTLE_ENDIAN) vl = (vl_2 << 16) + vl_1; + else vl = (vl_1 << 16) + vl_2; + } + else { + vl = vl_1; + } + + /* Now we have most of the information, except for sequences and items with undefined + length :-/. But, whether we know the length or not, we now need to create the tree + item and subtree, before we can loop into sequences and items + + Display the information we collected so far. Don't wait until the value is parsed, + because that parsing might cause an exception. If that happens within a sequence, + the sequence tag would not show up with the value + + Use different ett_ for Sequences & Items, so that fold/unfold state makes sense + */ + + tag_summary = dcm_tag_summary(pinfo, grp, elm, vl, tag_def->description, vr, tag_def->is_retired, is_implicit); + is_sequence = (strcmp(vr, "SQ") == 0) || (vl == 0xFFFFFFFF); + is_item = ((grp == 0xFFFE) && (elm == 0xE000)); + + if ((is_sequence | is_item) && global_dcm_seq_subtree) { + ett = is_sequence ? ett_dcm_data_seq : ett_dcm_data_item; + } + else { + ett = ett_dcm_data_tag; + } + + if (vl == 0xFFFFFFFF) { + /* 'Just' mark header as the length of the item */ + tag_ptree = proto_tree_add_subtree(tree, tvb, offset_tag, offset - offset_tag, ett, &tag_pitem, tag_summary); + vl_max = 0; /* We don't know who long this sequence/item is */ + } + else if (offset + vl <= endpos) { + /* Show real length of item */ + tag_ptree = proto_tree_add_subtree(tree, tvb, offset_tag, offset + vl - offset_tag, ett, &tag_pitem, tag_summary); + vl_max = vl; + } + else { + /* Value is longer than what we have in the PDV, -> we do have a OPEN tag */ + tag_ptree = proto_tree_add_subtree(tree, tvb, offset_tag, endpos - offset_tag, ett, &tag_pitem, tag_summary); + vl_max = endpos - offset; + } + + /* If you are going to touch the following 25 lines, make sure you reserve a few hours to go + through both display options and check for proper tree display :-) + */ + if (is_sequence | is_item) { + + if (global_dcm_seq_subtree) { + /* Use different ett_ for Sequences & Items, so that fold/unfold state makes sense */ + seq_ptree = tag_ptree; + if (!global_dcm_tag_subtree) { + tag_ptree = NULL; + } + } + else { + seq_ptree = tree; + if (!global_dcm_tag_subtree) { + tag_ptree = NULL; + } + } + } + else { + /* For tags */ + if (!global_dcm_tag_subtree) { + tag_ptree = NULL; + } + } + + /* --------------------------------------------------------------- + Tag details as separate items + --------------------------------------------------------------- + */ + + proto_tree_add_uint_format_value(tag_ptree, hf_dcm_tag, tvb, offset_tag, 4, + (grp << 16) | elm, "%04x,%04x (%s)", grp, elm, tag_def->description); + + /* Add VR to tag detail, except for sequence items */ + if (!is_item) { + if (is_implicit) { + /* Select header, since no VR is present in implicit syntax */ + proto_tree_add_string(tag_ptree, hf_dcm_tag_vr, tvb, offset_tag, 4, vr); + } + else { + proto_tree_add_string(tag_ptree, hf_dcm_tag_vr, tvb, offset_vr, 2, vr); + } + } + + /* Add length to tag detail */ + proto_tree_add_uint(tag_ptree, hf_dcm_tag_vl, tvb, offset_vl, (is_vl_long ? 4 : 2), vl); + + + /* --------------------------------------------------------------- + Finally the Tag Value + --------------------------------------------------------------- + */ + if ((is_sequence || is_item) && (vl > 0)) { + /* Sequence or Item Start */ + + guint32 endpos_item = 0; + gboolean local_end_of_seq_or_item = FALSE; + gboolean is_first_desc = TRUE; + + const gchar *item_description = NULL; /* Will be allocated as wmem packet scope memory in dissect_dcm_tag() */ + + if (vl == 0xFFFFFFFF) { + /* Undefined length */ + + while ((!local_end_of_seq_or_item) && (!pdv->open_tag.is_header_fragmented) && (offset < endpos)) { + + offset = dissect_dcm_tag(tvb, pinfo, seq_ptree, pdv, offset, endpos, FALSE, &item_description, &local_end_of_seq_or_item); + + if (item_description && global_dcm_seq_subtree) { + proto_item_append_text(tag_pitem, (is_first_desc ? " %s" : ", %s"), item_description); + is_first_desc = FALSE; + } + } + } + else { + /* Defined length */ + endpos_item = offset + vl_max; + + while (offset < endpos_item) { + + offset = dissect_dcm_tag(tvb, pinfo, seq_ptree, pdv, offset, endpos_item, FALSE, &item_description, &local_end_of_seq_or_item); + + if (item_description && global_dcm_seq_subtree) { + proto_item_append_text(tag_pitem, (is_first_desc ? " %s" : ", %s"), item_description); + is_first_desc = FALSE; + } + } + } + } /* if ((is_sequence || is_item) && (vl > 0)) */ + else if ((grp == 0xFFFE) && (elm == 0xE00D)) { + /* Item delimitation for items with undefined length */ + *end_of_seq_or_item = TRUE; + } + else if ((grp == 0xFFFE) && (elm == 0xE0DD)) { + /* Sequence delimitation for sequences with undefined length */ + *end_of_seq_or_item = TRUE; + } + else if (vl == 0) { + /* No value for this tag */ + + /* The following copy is needed. tag_value is post processed with g_strstrip() + and that one will crash the whole application, when a constant is used. + */ + + tag_value = wmem_strdup(pinfo->pool, "<Empty>"); + } + else if (vl > vl_max) { + /* Tag is longer than the PDV/PDU. Don't perform any decoding */ + + gchar *tag_desc; + + proto_tree_add_bytes_format(tag_ptree, hf_dcm_tag_value_byte, tvb, offset, vl_max, + NULL, "%-8.8sBytes %d - %d [start]", "Value:", 1, vl_max); + + tag_value = wmem_strdup_printf(pinfo->pool, "<Bytes %d - %d, start>", 1, vl_max); + offset += vl_max; + + /* Save the needed data for reuse, and subsequent packets + This will leak a little within the session. + + But since we may have tags being closed and reopen in the same PDV + we will always need to store this + */ + + tag_desc = dcm_tag_summary(pinfo, grp, elm, vl, tag_def->description, vr, tag_def->is_retired, is_implicit); + + if (pdv->open_tag.desc == NULL) { + pdv->open_tag.is_value_fragmented = TRUE; + pdv->open_tag.desc = wmem_strdup(wmem_file_scope(), tag_desc); + pdv->open_tag.len_total = vl; + pdv->open_tag.len_remaining = vl - vl_max; + } + } + else { + /* Regular value. Identify the type, decode and display */ + + offset = dissect_dcm_tag_value(tvb, pinfo, tag_ptree, pdv, offset, grp, elm, vl, vl_max, vr, &tag_value); + + /* ------------------------------------------------------------- + We have decoded the value. Now store those tags of interest + ------------------------------------------------------------- + */ + + /* Store SOP Class and Instance UID in first PDV of this object */ + if (grp == 0x0008 && elm == 0x0016) { + dcm_state_pdv_get_obj_start(pdv)->sop_class_uid = wmem_strdup(wmem_file_scope(), tag_value); + } + else if (grp == 0x0008 && elm == 0x0018) { + dcm_state_pdv_get_obj_start(pdv)->sop_instance_uid = wmem_strdup(wmem_file_scope(), tag_value); + } + else if (grp == 0x0000 && elm == 0x0100) { + /* This is the command tag -> overwrite existing PDV description */ + pdv->desc = wmem_strdup(wmem_file_scope(), tag_value); + } + } + + + /* ------------------------------------------------------------------- + Add the value to the already constructed item + ------------------------------------------------------------------- + */ + + proto_item_append_text(tag_pitem, " %s", tag_value); + + if (tag_def->add_to_summary) { + *tag_description = wmem_strdup(pinfo->pool, g_strstrip(tag_value)); + } + + return offset; +} + +/* +'Decode' open tags from previous PDV. It mostly ends in 'continuation' or 'end' in the description. +*/ +static guint32 +dissect_dcm_tag_open(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + dcm_state_pdv_t *pdv, guint32 offset, guint32 endpos, gboolean *is_first_tag) +{ + + proto_item *pitem = NULL; + + guint32 tag_value_fragment_len = 0; + + if ((pdv->prev) && (pdv->prev->open_tag.len_remaining > 0)) { + /* Not first PDV in the given presentation context (Those don't have remaining data to parse :-) */ + /* And previous PDV has left overs, i.e. this is a continuation PDV */ + + if (endpos - offset >= pdv->prev->open_tag.len_remaining) { + /* + Remaining bytes are equal or more than we expect for the open tag + Finally reach the end of this tag. Don't touch the open_tag structure + of this PDV, as we may see a new open tag at the end + */ + tag_value_fragment_len = pdv->prev->open_tag.len_remaining; + pdv->is_corrupt = FALSE; + } + else if (pdv->is_flagvalid && pdv->is_last_fragment) { + /* + The tag is not yet complete, however, the flag indicates that it should be + Therefore end this tag and issue an expert_add_info. Don't touch the + open_tag structure of this PDV, as we may see a new open tag at the end + */ + tag_value_fragment_len = endpos - offset; + pdv->is_corrupt = TRUE; + } + else { + /* + * More to do for this tag + */ + tag_value_fragment_len = endpos - offset; + + /* Set data in current PDV structure */ + if (!pdv->open_tag.is_value_fragmented) { + /* No need to do it twice or more */ + + pdv->open_tag.is_value_fragmented = TRUE; + pdv->open_tag.len_total = pdv->prev->open_tag.len_total; + pdv->open_tag.len_remaining = pdv->prev->open_tag.len_remaining - tag_value_fragment_len; + pdv->open_tag.desc = wmem_strdup(wmem_file_scope(), pdv->prev->open_tag.desc); + + } + pdv->is_corrupt = FALSE; + } + + if (pdv->is_corrupt) { + pitem = proto_tree_add_bytes_format(tree, hf_dcm_data_tag, tvb, + offset, tag_value_fragment_len, NULL, + "%s <incomplete>", pdv->prev->open_tag.desc); + + expert_add_info(pinfo, pitem, &ei_dcm_data_tag); + + } + else { + proto_tree_add_bytes_format(tree, hf_dcm_data_tag, tvb, + offset, tag_value_fragment_len, NULL, + "%s <Bytes %d - %d, %s>", pdv->prev->open_tag.desc, + pdv->prev->open_tag.len_total - pdv->prev->open_tag.len_remaining + 1, + pdv->prev->open_tag.len_total - pdv->prev->open_tag.len_remaining + tag_value_fragment_len, + (pdv->prev->open_tag.len_remaining > tag_value_fragment_len ? "continuation" : "end") ); + } + + offset += tag_value_fragment_len; + *is_first_tag = FALSE; + } + + return offset; +} + +/* +Decode the tag section inside a PDV. This can be a single combined dataset +or DICOM natively split PDVs. Therefore it needs to resume previously opened tags. +For data PDVs, only process tags when tree is set or listening to export objects tap. +For command PDVs, process all tags. +On export copy the content to the export buffer. +*/ +static guint32 +dissect_dcm_pdv_body( + tvbuff_t *tvb, + packet_info *pinfo, + proto_tree *tree, + dcm_state_assoc_t *assoc, + dcm_state_pdv_t *pdv, + guint32 offset, + guint32 pdv_body_len, + gchar **pdv_description) +{ + const gchar *tag_value = NULL; + gboolean dummy = FALSE; + guint32 startpos = offset; + guint32 endpos = 0; + + endpos = offset + pdv_body_len; + + if (pdv->is_command || tree || have_tap_listener(dicom_eo_tap)) { + /* Performance optimization starts here. Don't put any COL_INFO related stuff in here */ + + if (pdv->syntax == DCM_UNK) { + /* Eventually, we will have a syntax detector. Until then, don't decode */ + + proto_tree_add_bytes_format(tree, hf_dcm_data_tag, tvb, + offset, pdv_body_len, NULL, + "(%04x,%04x) %-8x Unparsed data", 0, 0, pdv_body_len); + } + else { + + gboolean is_first_tag = TRUE; + + /* Treat the left overs */ + offset = dissect_dcm_tag_open(tvb, pinfo, tree, pdv, offset, endpos, &is_first_tag); + + /* Decode all tags, sequences and items in this PDV recursively */ + while (offset < endpos) { + offset = dissect_dcm_tag(tvb, pinfo, tree, pdv, offset, endpos, is_first_tag, &tag_value, &dummy); + is_first_tag = FALSE; + } + } + } + + *pdv_description = pdv->desc; + + if (pdv->is_command) { + + if (pdv->is_warning) { + if (pdv->comment) { + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (%s, %s)", pdv->desc, pdv->status, pdv->comment); + } + else { + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (%s)", pdv->desc, pdv->status); + } + + } + else if (global_dcm_cmd_details) { + /* Show command details in header */ + + if (pdv->message_id > 0) { + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s ID=%d", pdv->desc, pdv->message_id); + } + else if (pdv->message_id_resp > 0) { + + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s ID=%d", pdv->desc, pdv->message_id_resp); + + if (pdv->no_completed > 0) { + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s C=%d", *pdv_description, pdv->no_completed); + } + if (pdv->no_remaining > 0) { + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s R=%d", *pdv_description, pdv->no_remaining); + } + if (pdv->no_warning > 0) { + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s W=%d", *pdv_description, pdv->no_warning); + } + if (pdv->no_failed > 0) { + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s F=%d", *pdv_description, pdv->no_failed); + } + if (!pdv->is_pending && pdv->status) + { + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (%s)", *pdv_description, pdv->status); + } + } + } + } + + if (have_tap_listener(dicom_eo_tap)) { + + if (pdv->data_len == 0) { + /* Copy pure DICOM data to buffer, without PDV flags + Packet scope for the memory allocation is too small, since we may have PDV in different tvb. + Therefore check if this was already done. + */ + pdv->data = wmem_alloc0(wmem_file_scope(), pdv_body_len); + pdv->data_len = pdv_body_len; + tvb_memcpy(tvb, pdv->data, startpos, pdv_body_len); + } + if ((pdv_body_len > 0) && (pdv->is_last_fragment)) { + /* At the last segment, merge all related previous PDVs and copy to export buffer */ + dcm_export_create_object(pinfo, assoc, pdv); + } + } + + return endpos; +} + +/* +Handle one PDV inside a data PDU. When needed, perform the reassembly of PDV fragments. +PDV fragments are different from TCP fragmentation. +Create PDV object when needed. +Return pdv_description to be used e.g. in COL_INFO. +*/ +static guint32 +dissect_dcm_pdv_fragmented(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + dcm_state_assoc_t *assoc, guint32 offset, guint32 pdv_len, gchar **pdv_description) +{ + + conversation_t *conv = NULL; + + dcm_state_pdv_t *pdv = NULL; + + tvbuff_t *combined_tvb = NULL; + fragment_head *head = NULL; + + guint32 reassembly_id; + guint32 pdv_body_len; + + pdv_body_len = pdv_len-2; + + /* Dissect Context ID, Find PDV object, Decode Command/Data flag and More Fragments flag */ + offset = dissect_dcm_pdv_header(tvb, pinfo, tree, assoc, offset, &pdv); + + if (global_dcm_reassemble) + { + /* Combine the different PDVs. This is the default preference and useful in most scenarios. + This will create one 'huge' PDV. E.g. a CT image will fits in one buffer. + */ + conv = find_conversation_pinfo(pinfo, 0); + + /* Try to create somewhat unique ID. + Include the conversation index, to separate TCP session + Include bits from the reassembly number in the current Presentation + Context (that we track ourselves) in order to distinguish between + PDV fragments from the same frame but different reassemblies. + */ + DISSECTOR_ASSERT(conv); + + /* The following expression seems to executed late in VS2017 in 'RelWithDebInf'. + Therefore it may appear as 0 at first + */ + reassembly_id = (((conv->conv_index) & 0x000FFFFF) << 12) + + ((guint32)(pdv->pctx_id) << 4) + ((guint32)(pdv->reassembly_id & 0xF)); + + /* This one will chain the packets until 'is_last_fragment' */ + head = fragment_add_seq_next( + &dcm_pdv_reassembly_table, + tvb, + offset, + pinfo, + reassembly_id, + NULL, + pdv_body_len, + !(pdv->is_last_fragment)); + + if (head && (head->next == NULL)) { + /* Was not really fragmented, therefore use 'conventional' decoding. + process_reassembled_data() does not cope with two PDVs in the same frame, therefore catch it here + */ + + offset = dissect_dcm_pdv_body(tvb, pinfo, tree, assoc, pdv, offset, pdv_body_len, pdv_description); + } + else + { + /* Will return a complete buffer, once last fragment is hit. + The description is not used in packet-dcm. COL_INFO is set specifically in dissect_dcm_pdu() + */ + combined_tvb = process_reassembled_data( + tvb, + offset, + pinfo, + "Reassembled PDV", + head, + &dcm_pdv_fragment_items, + NULL, + tree); + + if (combined_tvb == NULL) { + /* Just show this as a fragment */ + + if (head && head->reassembled_in != pinfo->num) { + + if (pdv->desc) { + /* We know the presentation context already */ + *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (reassembled in #%u)", pdv->desc, head->reassembled_in); + } + else { + /* Decoding of the presentation context did not occur yet or did not succeed */ + *pdv_description = wmem_strdup_printf(pinfo->pool, "PDV Fragment (reassembled in #%u)", head->reassembled_in); + } + } + else { + /* We don't know the last fragment yet (and/or we'll never see it). + This can happen, e.g. when TCP packet arrive our of order. + */ + *pdv_description = wmem_strdup(pinfo->pool, "PDV Fragment"); + } + + offset += pdv_body_len; + } + else { + /* Decode reassembled data. This needs to be += */ + offset += dissect_dcm_pdv_body(combined_tvb, pinfo, tree, assoc, pdv, 0, tvb_captured_length(combined_tvb), pdv_description); + } + } + } + else { + /* Do not reassemble DICOM PDVs, i.e. decode PDVs one by one. + This may be useful when troubleshooting PDU length issues, + or to better understand the PDV split. + The tag level decoding is more challenging, as leftovers need + to be displayed adequately. Not a big deal for binary values. + */ + offset = dissect_dcm_pdv_body(tvb, pinfo, tree, assoc, pdv, offset, pdv_body_len, pdv_description); + } + + return offset; +} + +static guint32 +dissect_dcm_pdu_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + dcm_state_assoc_t *assoc, guint32 offset, guint32 pdu_len, gchar **pdu_data_description) +{ + + /* 04 P-DATA-TF + 1 1 reserved + 2 4 length + - (1+) presentation data value (PDV) items + 6 4 length + 10 1 Presentation Context ID (odd ints 1 - 255) + - PDV + 11 1 header + 0x01 if set, contains Message Command info, else Message Data + 0x02 if set, contains last fragment + */ + + proto_tree *pdv_ptree; /* Tree for item details */ + proto_item *pdv_pitem, *pdvlen_item; + + gchar *buf_desc = NULL; /* PDU description */ + gchar *pdv_description = NULL; + + gboolean first_pdv = TRUE; + + guint32 endpos = 0; + guint32 pdv_len = 0; + + endpos = offset + pdu_len; + + /* Loop through multiple PDVs */ + while (offset < endpos) { + + pdv_len = tvb_get_ntohl(tvb, offset); + + pdv_ptree = proto_tree_add_subtree(tree, tvb, offset, pdv_len+4, ett_dcm_data_pdv, &pdv_pitem, "PDV"); + + pdvlen_item = proto_tree_add_item(pdv_ptree, hf_dcm_pdv_len, tvb, offset, 4, ENC_BIG_ENDIAN); + offset += 4; + + if ((pdv_len + 4 > pdu_len) || (pdv_len + 4 < pdv_len)) { + expert_add_info_format(pinfo, pdvlen_item, &ei_dcm_pdv_len, "Invalid PDV length (too large)"); + return endpos; + } + else if (pdv_len <= 2) { + expert_add_info_format(pinfo, pdvlen_item, &ei_dcm_pdv_len, "Invalid PDV length (too small)"); + return endpos; + } + else if (((pdv_len >> 1) << 1) != pdv_len) { + expert_add_info_format(pinfo, pdvlen_item, &ei_dcm_pdv_len, "Invalid PDV length (not even)"); + return endpos; + } + + offset = dissect_dcm_pdv_fragmented(tvb, pinfo, pdv_ptree, assoc, offset, pdv_len, &pdv_description); + + /* The following doesn't seem to work anymore */ + if (pdv_description) { + if (first_pdv) { + buf_desc = wmem_strdup(pinfo->pool, pdv_description); + } + else { + buf_desc = wmem_strdup_printf(pinfo->pool, "%s, %s", buf_desc, pdv_description); + } + } + + proto_item_append_text(pdv_pitem, ", %s", pdv_description); + first_pdv=FALSE; + + } + + *pdu_data_description = buf_desc; + + return offset; +} + + +/* +Test for DICOM traffic. + +- Minimum 10 Bytes +- Look for the association request +- Check PDU size vs TCP payload size + +Since used in heuristic mode, be picky for performance reasons. +We are called in static mode, once we decoded the association request and called conversation_set_dissector() +They we can be more liberal on the packet selection +*/ +static gboolean +test_dcm(tvbuff_t *tvb) +{ + + guint8 pdu_type; + guint32 pdu_len; + guint16 vers; + + /* + Ensure that the tvb_captured_length is big enough before fetching the values. + Otherwise it can trigger an exception during the heuristic check, + preventing next heuristic dissectors from being called + + tvb_reported_length() is the real size of the packet as transmitted on the wire + tvb_captured_length() is the number of bytes captured (so you always have captured <= reported). + + The 10 bytes represent an association request header including the 2 reserved bytes not used below + In the captures at hand, the parsing result was equal. + */ + + if (tvb_captured_length(tvb) < 8) { + return FALSE; + } + if (tvb_reported_length(tvb) < 10) { + return FALSE; + } + + pdu_type = tvb_get_guint8(tvb, 0); + pdu_len = tvb_get_ntohl(tvb, 2); + vers = tvb_get_ntohs(tvb, 6); + + /* Exit, if not an association request at version 1 */ + if (!(pdu_type == 1 && vers == 1)) { + return FALSE; + } + + /* Exit if TCP payload is bigger than PDU length (plus header) + OK for PRESENTATION_DATA, questionable for ASSOCIATION requests + */ + if (tvb_reported_length(tvb) > pdu_len + 6) { + return FALSE; + } + + return TRUE; +} + +/* +Main function to decode DICOM traffic. Supports reassembly of TCP packets. +*/ +static int +dissect_dcm_main(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gboolean is_port_static) +{ + + guint8 pdu_type = 0; + guint32 pdu_start = 0; + guint32 pdu_len = 0; + guint32 tlen = 0; + + int offset = 0; + + /* + TCP packets are assembled well by wireshark in conjunction with the dissectors. + + Therefore, we will only see properly aligned PDUs, at the beginning of the buffer. + So if the buffer does not start with the PDU header, it's not DICOM traffic. + + Do the byte checking as early as possible. + The heuristic hook requires an association request + + DICOM PDU are nice, but need to be managed + + We can have any combination: + - One or more DICOM PDU per TCP packet + - PDU split over different TCP packets + - And both together, i.e. some complete PDUs and then a fraction of a new PDU in a TCP packet + + This function will handle multiple PDUs per TCP packet and will ask for more data, + if the last PDU does not fit + + It does not reassemble fragmented PDVs by purpose, since the Tag Value parsing needs to be done + per Tag, and PDU recombination here would + a) need to eliminate PDU/PDV/Ctx header (12 bytes) + b) not show the true DICOM logic in transfer + + The length check is tricky. If not a PDV continuation, 10 Bytes are required. For PDV continuation + anything seems to be possible, depending on the buffer alignment of the sending process. + + */ + + tlen = tvb_reported_length(tvb); + + pdu_type = tvb_get_guint8(tvb, 0); + if (pdu_type == 0 || pdu_type > 7) /* Wrong PDU type. 'Or' is slightly more efficient than 'and' */ + return 0; /* No bytes taken from the stack */ + + if (is_port_static) { + /* Port is defined explicitly, or association request was previously found successfully. + Be more tolerant on minimum packet size. Also accept < 6 + */ + + if (tlen < 6) { + /* we need 6 bytes at least to get PDU length */ + pinfo->desegment_offset = offset; + pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT; + return tvb_captured_length(tvb); + } + } + + + /* Passing this point, we should always have tlen >= 6 */ + + pdu_len = tvb_get_ntohl(tvb, 2); + if (pdu_len < 4) /* The smallest PDUs are ASSOC Rejects & Release messages */ + return 0; + + /* Mark it. This is a DICOM packet */ + + col_set_str(pinfo->cinfo, COL_PROTOCOL, "DICOM"); + + /* Process all PDUs in the buffer */ + while (pdu_start < tlen) { + guint32 old_pdu_start; + + if ((pdu_len+6) > (tlen-offset)) { + + /* PDU is larger than the remaining packet (buffer), therefore request whole PDU + The next time this function is called, tlen will be equal to pdu_len + */ + + pinfo->desegment_offset = offset; + pinfo->desegment_len = (pdu_len+6) - (tlen-offset); + return tvb_captured_length(tvb); + } + + /* Process a whole PDU */ + offset=dissect_dcm_pdu(tvb, pinfo, tree, pdu_start); + + /* Next PDU */ + old_pdu_start = pdu_start; + pdu_start = pdu_start + pdu_len + 6; + if (pdu_start <= old_pdu_start) { + expert_add_info_format(pinfo, NULL, &ei_dcm_invalid_pdu_length, "Invalid PDU length (%u)", pdu_len); + break; + } + + if (pdu_start < tlen - 6) { + /* we got at least 6 bytes of the next PDU still in the buffer */ + pdu_len = tvb_get_ntohl(tvb, pdu_start+2); + } + else { + pdu_len = 0; + } + } + return offset; +} + +/* +Callback function used to register +*/ +static int +dissect_dcm_static(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) +{ + /* Less checking on ports that match */ + return dissect_dcm_main(tvb, pinfo, tree, TRUE); +} + +/* +Test for an Association Request. Decode, when successful. +*/ +static gboolean +dissect_dcm_heuristic(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) +{ + + /* This will be potentially called for every packet */ + + if (!test_dcm(tvb)) + return FALSE; + + /* + Conversation_set_dissector() is called inside dcm_state_get() once + we have enough details. From there on, we will be 'static' + */ + + if (dissect_dcm_main(tvb, pinfo, tree, FALSE) == 0) { + /* there may have been another reason why it is not DICOM */ + return FALSE; + } + + return TRUE; + +} + +/* +Only set a valued with col_set_str() if it does not yet exist. +(In a multiple PDV scenario, col_set_str() actually appends for the subsequent calls) +*/ +static void col_set_str_conditional(column_info *cinfo, const gint el, const gchar* str) +{ + const char *col_string = col_get_text(cinfo, el); + + if (col_string == NULL || !g_str_has_prefix(col_string, str)) + { + col_add_str(cinfo, el, str); + } +} + +/* +CSV add a value to a column, if it does not exist yet +*/ +static void col_append_str_conditional(column_info *cinfo, const gint el, const gchar* str) +{ + const char *col_string = col_get_text(cinfo, el); + + if (col_string == NULL || !g_strrstr(col_string, str)) + { + col_append_fstr(cinfo, el, ", %s", str); + } +} + +/* +Dissect a single DICOM PDU. Can be an association or a data package. Creates a tree item. +*/ +static guint32 +dissect_dcm_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset) +{ + proto_tree *dcm_ptree=NULL; /* Root DICOM tree and its item */ + proto_item *dcm_pitem=NULL; + + dcm_state_t *dcm_data=NULL; + dcm_state_assoc_t *assoc=NULL; + + guint8 pdu_type=0; + guint32 pdu_len=0; + + gchar *pdu_data_description=NULL; + + /* Get or create conversation. Used to store context IDs and xfer Syntax */ + + dcm_data = dcm_state_get(pinfo, TRUE); + if (dcm_data == NULL) { /* Internal error. Failed to create main DICOM data structure */ + return offset; + } + + dcm_pitem = proto_tree_add_item(tree, proto_dcm, tvb, offset, -1, ENC_NA); + dcm_ptree = proto_item_add_subtree(dcm_pitem, ett_dcm); + + /* PDU type is only one byte, then one byte reserved */ + pdu_type = tvb_get_guint8(tvb, offset); + proto_tree_add_item(dcm_ptree, hf_dcm_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN); + offset += 2; + + pdu_len = tvb_get_ntohl(tvb, offset); + proto_tree_add_item(dcm_ptree, hf_dcm_pdu_len, tvb, offset, 4, ENC_BIG_ENDIAN); + offset += 4; + + /* Find previously detected association, else create a new one object*/ + assoc = dcm_state_assoc_get(dcm_data, pinfo->num, TRUE); + + if (assoc == NULL) { /* Internal error. Failed to create association structure */ + return offset; + } + + if (pdu_type == 4) { + + col_set_str_conditional(pinfo->cinfo, COL_INFO, "P-DATA"); + + /* Everything that needs to be shown in any UI column (like COL_INFO) + needs to be calculated also with tree == null + */ + offset = dissect_dcm_pdu_data(tvb, pinfo, dcm_ptree, assoc, offset, pdu_len, &pdu_data_description); + + if (pdu_data_description) { + proto_item_append_text(dcm_pitem, ", %s", pdu_data_description); + col_append_str_conditional(pinfo->cinfo, COL_INFO, pdu_data_description); + } + } + else { + + /* Decode Association request, response, reject, abort details */ + offset = dissect_dcm_assoc_header(tvb, pinfo, dcm_ptree, offset, assoc, pdu_type, pdu_len); + } + + return offset; /* return the number of processed bytes */ +} + + +/* +Register the protocol with Wireshark +*/ +void +proto_register_dcm(void) +{ + static hf_register_info hf[] = { + { &hf_dcm_pdu_type, { "PDU Type", "dicom.pdu.type", + FT_UINT8, BASE_HEX, VALS(dcm_pdu_ids), 0, NULL, HFILL } }, + { &hf_dcm_pdu_len, { "PDU Length", "dicom.pdu.len", + FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } }, + + { &hf_dcm_assoc_version, { "Protocol Version", "dicom.assoc.version", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_assoc_called, { "Called AE Title", "dicom.assoc.ae.called", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_assoc_calling, { "Calling AE Title", "dicom.assoc.ae.calling", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_assoc_reject_result, { "Result", "dicom.assoc.reject.result", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_assoc_reject_source, { "Source", "dicom.assoc.reject.source", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_assoc_reject_reason, { "Reason", "dicom.assoc.reject.reason", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_assoc_abort_source, { "Source", "dicom.assoc.abort.source", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_assoc_abort_reason, { "Reason", "dicom.assoc.abort.reason", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_assoc_item_type, { "Item Type", "dicom.assoc.item.type", + FT_UINT8, BASE_HEX, VALS(dcm_assoc_item_type), 0, NULL, HFILL } }, + { &hf_dcm_assoc_item_len, { "Item Length", "dicom.assoc.item.len", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + + { &hf_dcm_actx, { "Application Context", "dicom.actx", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_pctx_id, { "Presentation Context ID", "dicom.pctx.id", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_dcm_pctx_result, { "Presentation Context Result", "dicom.pctx.result", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_dcm_pctx_abss_syntax, { "Abstract Syntax", "dicom.pctx.abss.syntax", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_pctx_xfer_syntax, { "Transfer Syntax", "dicom.pctx.xfer.syntax", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_info, { "User Info", "dicom.userinfo", + FT_NONE, BASE_NONE, NULL, 0, "This field contains the ACSE User Information Item of the A-ASSOCIATErequest.", HFILL } }, + { &hf_dcm_info_uid, { "Implementation Class UID", "dicom.userinfo.uid", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_info_version, { "Implementation Version", "dicom.userinfo.version", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_info_extneg, { "Extended Negotiation", "dicom.userinfo.extneg", + FT_NONE, BASE_NONE, NULL, 0, "This field contains the optional SOP Class Extended Negotiation Sub-Item of the ACSE User Information Item of the A-ASSOCIATE-RQ/RSP.", HFILL } }, + { &hf_dcm_info_extneg_sopclassuid_len, { "SOP Class UID Length", "dicom.userinfo.extneg.sopclassuid.len", + FT_UINT16, BASE_DEC, NULL, 0, "This field contains the length of the SOP Class UID in the Extended Negotiation Sub-Item.", HFILL } }, + { &hf_dcm_info_extneg_sopclassuid, { "SOP Class UID", "dicom.userinfo.extneg.sopclassuid", + FT_STRING, BASE_NONE, NULL, 0, "This field contains the SOP Class UID in the Extended Negotiation Sub-Item.", HFILL } }, + { &hf_dcm_info_extneg_relational_query, { "Relational-queries", "dicom.userinfo.extneg.relational", + FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if relational queries are supported.", HFILL } }, + { &hf_dcm_info_extneg_date_time_matching, { "Combined Date-Time matching", "dicom.userinfo.extneg.datetimematching", + FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if combined date-time matching is supported.", HFILL } }, + { &hf_dcm_info_extneg_fuzzy_semantic_matching, { "Fuzzy semantic matching", "dicom.userinfo.extneg.fuzzymatching", + FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if fuzzy semantic matching of person names is supported.", HFILL } }, + { &hf_dcm_info_extneg_timezone_query_adjustment, { "Timezone query adjustment", "dicom.userinfo.extneg.timezone", + FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if timezone query adjustment is supported.", HFILL } }, + { &hf_dcm_info_rolesel, { "SCP/SCU Role Selection", "dicom.userinfo.rolesel", + FT_NONE, BASE_NONE, NULL, 0, "This field contains the optional SCP/SCU Role Selection Sub-Item of the ACSE User Information Item of the A-ASSOCIATE-RQ/RSP.", HFILL } }, + { &hf_dcm_info_rolesel_sopclassuid_len, { "SOP Class UID Length", "dicom.userinfo.rolesel.sopclassuid.len", + FT_UINT16, BASE_DEC, NULL, 0, "This field contains the length of the SOP Class UID in the SCP/SCU Role Selection Sub-Item.", HFILL } }, + { &hf_dcm_info_rolesel_sopclassuid, { "SOP Class UID", "dicom.userinfo.rolesel.sopclassuid", + FT_STRING, BASE_NONE, NULL, 0, "This field contains the SOP Class UID in the SCP/SCU Role Selection Sub-Item.", HFILL } }, + { &hf_dcm_info_rolesel_scurole, { "SCU-role", "dicom.userinfo.rolesel.scurole", + FT_UINT8, BASE_HEX, NULL, 0, "This field contains the SCU-role as defined for the Association-requester.", HFILL } }, + { &hf_dcm_info_rolesel_scprole, { "SCP-role", "dicom.userinfo.rolesel.scprole", + FT_UINT8, BASE_HEX, NULL, 0, "This field contains the SCP-role as defined for the Association-requester.", HFILL } }, + { &hf_dcm_info_async_neg, { "Asynchronous Operations (and sub-operations) Window Negotiation", "dicom.userinfo.asyncneg", + FT_NONE, BASE_NONE, NULL, 0, "This field contains the optional Asynchronous Operations (and sub-operations) Window Negotiation Sub-Item of the ACSE User Information Item of the A-ASSOCIATE-RQ/RSP.", HFILL } }, + { &hf_dcm_info_async_neg_max_num_ops_inv, { "Maximum-number-operations-invoked", "dicom.userinfo.asyncneg.maxnumopsinv", + FT_UINT16, BASE_DEC, NULL, 0, "This field contains the maximum-number-operations-invoked in the Asynchronous Operations (and sub-operations) Window Negotiation Sub-Item.", HFILL } }, + { &hf_dcm_info_async_neg_max_num_ops_per, { "Maximum-number-operations-performed", "dicom.userinfo.asyncneg.maxnumopsper", + FT_UINT16, BASE_DEC, NULL, 0, "This field contains the maximum-number-operations-performed in the Asynchronous Operations (and sub-operations) Window Negotiation Sub-Item.", HFILL } }, + { &hf_dcm_info_unknown, { "Unknown", "dicom.userinfo.unknown", + FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_assoc_item_data, { "Unknown Data", "dicom.userinfo.data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_info_user_identify, { "User Identify", "dicom.userinfo.user_identify", + FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_info_user_identify_type, { "Type", "dicom.userinfo.user_identify.type", + FT_UINT8, BASE_DEC, VALS(user_identify_type_vals), 0, NULL, HFILL } }, + { &hf_dcm_info_user_identify_response_requested, { "Response Requested", "dicom.userinfo.user_identify.response_requested", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_info_user_identify_primary_field_length, { "Primary Field Length", "dicom.userinfo.user_identify.primary_field_length", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_info_user_identify_primary_field, { "Primary Field", "dicom.userinfo.user_identify.primary_field", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_info_user_identify_secondary_field_length, { "Secondary Field Length", "dicom.userinfo.user_identify.secondary_field_length", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_info_user_identify_secondary_field, { "Secondary Field", "dicom.userinfo.user_identify.secondary_field", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_pdu_maxlen, { "Max PDU Length", "dicom.max_pdu_len", + FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_pdv_len, { "PDV Length", "dicom.pdv.len", + FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_pdv_ctx, { "PDV Context", "dicom.pdv.ctx", + FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_pdv_flags, { "PDV Flags", "dicom.pdv.flags", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_dcm_data_tag, { "Tag", "dicom.data.tag", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } }, + + { &hf_dcm_tag, { "Tag", "dicom.tag", + FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } }, + { &hf_dcm_tag_vr, { "VR", "dicom.tag.vr", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_tag_vl, { "Length", "dicom.tag.vl", + FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } }, + + { &hf_dcm_tag_value_str, { "Value", "dicom.tag.value.str", + FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } }, + { &hf_dcm_tag_value_16s, { "Value", "dicom.tag.value.16s", + FT_INT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_tag_value_16u, { "Value", "dicom.tag.value.16u", + FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_tag_value_32s, { "Value", "dicom.tag.value.32s", + FT_INT32, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_tag_value_32u, { "Value", "dicom.tag.value.32u", + FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } }, + { &hf_dcm_tag_value_byte, { "Value", "dicom.tag.value.byte", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } }, + + /* Fragment entries */ + { &hf_dcm_pdv_fragments, + { "Message fragments", "dicom.pdv.fragments", + FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL } }, + { &hf_dcm_pdv_fragment, + { "Message fragment", "dicom.pdv.fragment", + FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } }, + { &hf_dcm_pdv_fragment_overlap, + { "Message fragment overlap", "dicom.pdv.fragment.overlap", + FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } }, + { &hf_dcm_pdv_fragment_overlap_conflicts, + { "Message fragment overlapping with conflicting data", + "dicom.pdv.fragment.overlap.conflicts", + FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } }, + { &hf_dcm_pdv_fragment_multiple_tails, + { "Message has multiple tail fragments", + "dicom.pdv.fragment.multiple_tails", + FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } }, + { &hf_dcm_pdv_fragment_too_long_fragment, + { "Message fragment too long", "dicom.pdv.fragment.too_long_fragment", + FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } }, + { &hf_dcm_pdv_fragment_error, + { "Message defragmentation error", "dicom.pdv.fragment.error", + FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } }, + { &hf_dcm_pdv_fragment_count, + { "Message fragment count", "dicom.pdv.fragment_count", + FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } }, + { &hf_dcm_pdv_reassembled_in, + { "Reassembled in", "dicom.pdv.reassembled.in", + FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } }, + { &hf_dcm_pdv_reassembled_length, + { "Reassembled PDV length", "dicom.pdv.reassembled.length", + FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } } + }; + +/* Setup protocol subtree array */ + static gint *ett[] = { + &ett_dcm, + &ett_assoc, + &ett_assoc_header, + &ett_assoc_actx, + &ett_assoc_pctx, + &ett_assoc_pctx_abss, + &ett_assoc_pctx_xfer, + &ett_assoc_info, + &ett_assoc_info_uid, + &ett_assoc_info_version, + &ett_assoc_info_extneg, + &ett_assoc_info_rolesel, + &ett_assoc_info_async_neg, + &ett_assoc_info_user_identify, + &ett_assoc_info_unknown, + &ett_dcm_data, + &ett_dcm_data_pdv, + &ett_dcm_data_tag, + &ett_dcm_data_seq, + &ett_dcm_data_item, + &ett_dcm_pdv, /* used for fragments */ + &ett_dcm_pdv_fragment, + &ett_dcm_pdv_fragments + }; + + static ei_register_info ei[] = { + { &ei_dcm_assoc_rejected, { "dicom.assoc.reject", PI_RESPONSE_CODE, PI_WARN, "Association rejected", EXPFILL }}, + { &ei_dcm_assoc_aborted, { "dicom.assoc.abort", PI_RESPONSE_CODE, PI_WARN, "Association aborted", EXPFILL }}, + { &ei_dcm_no_abstract_syntax, { "dicom.no_abstract_syntax", PI_MALFORMED, PI_ERROR, "No Abstract Syntax provided for this Presentation Context", EXPFILL }}, + { &ei_dcm_multiple_abstract_syntax, { "dicom.multiple_abstract_syntax", PI_MALFORMED, PI_ERROR, "More than one Abstract Syntax provided for this Presentation Context", EXPFILL }}, + { &ei_dcm_no_transfer_syntax, { "dicom.no_transfer_syntax", PI_MALFORMED, PI_ERROR, "No Transfer Syntax provided for this Presentation Context", EXPFILL }}, + { &ei_dcm_no_abstract_syntax_uid, { "dicom.no_abstract_syntax_uid", PI_MALFORMED, PI_ERROR, "No Abstract Syntax UID found for this Presentation Context", EXPFILL }}, + { &ei_dcm_multiple_transfer_syntax, { "dicom.multiple_transfer_syntax", PI_MALFORMED, PI_ERROR, "Only one Transfer Syntax allowed in a Association Response", EXPFILL }}, + { &ei_dcm_assoc_item_len, { "dicom.assoc.item.len.invalid", PI_MALFORMED, PI_ERROR, "Invalid Association Item Length", EXPFILL }}, + { &ei_dcm_pdv_ctx, { "dicom.pdv.ctx.invalid", PI_MALFORMED, PI_ERROR, "Invalid Presentation Context ID", EXPFILL }}, + { &ei_dcm_pdv_flags, { "dicom.pdv.flags.invalid", PI_MALFORMED, PI_ERROR, "Invalid Flags", EXPFILL }}, + { &ei_dcm_status_msg, { "dicom.status_msg", PI_RESPONSE_CODE, PI_WARN, "%s", EXPFILL }}, + { &ei_dcm_data_tag, { "dicom.data.tag.missing", PI_MALFORMED, PI_ERROR, "Early termination of tag. Data is missing", EXPFILL }}, + { &ei_dcm_pdv_len, { "dicom.pdv.len.invalid", PI_MALFORMED, PI_ERROR, "Invalid PDV length", EXPFILL }}, + { &ei_dcm_invalid_pdu_length, { "dicom.pdu_length.invalid", PI_MALFORMED, PI_ERROR, "Invalid PDU length", EXPFILL }}, + }; + + module_t *dcm_module; + expert_module_t* expert_dcm; + + /* Register the protocol name and description */ + proto_dcm = proto_register_protocol("DICOM", "DICOM", "dicom"); + + /* Required function calls to register the header fields and subtrees used */ + proto_register_field_array(proto_dcm, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + expert_dcm = expert_register_protocol(proto_dcm); + expert_register_field_array(expert_dcm, ei, array_length(ei)); + + /* Allow other dissectors to find this one by name. */ + dcm_handle = register_dissector("dicom", dissect_dcm_static, proto_dcm); + + dcm_module = prefs_register_protocol(proto_dcm, NULL); + + /* Used to migrate an older configuration file to a newer one */ + prefs_register_obsolete_preference(dcm_module, "heuristic"); + + prefs_register_bool_preference(dcm_module, "export_header", + "Create Meta Header on Export", + "Create DICOM File Meta Header according to PS 3.10 on export for PDUs. " + "If the captured PDV does not contain a SOP Class UID and SOP Instance UID " + "(e.g. for command PDVs), wireshark specific ones will be created.", + &global_dcm_export_header); + + prefs_register_uint_preference(dcm_module, "export_minsize", + "Min. item size in bytes to export", + "Do not show items below this size in the export list. " + "Set it to 0, to see DICOM commands and responses in the list. " + "Set it higher, to just export DICOM IODs (i.e. CT Images, RT Structures).", 10, + &global_dcm_export_minsize); + + prefs_register_bool_preference(dcm_module, "seq_tree", + "Create subtrees for Sequences and Items", + "Create a node for sequences and items, and show children in a hierarchy. " + "De-select this option, if you prefer a flat display or e.g. " + "when using TShark to create a text output.", + &global_dcm_seq_subtree); + + prefs_register_bool_preference(dcm_module, "tag_tree", + "Create subtrees for DICOM Tags", + "Create a node for a tag and show tag details as single elements. " + "This can be useful to debug a tag and to allow display filters on these attributes. " + "When using TShark to create a text output, it's better to have it disabled. ", + &global_dcm_tag_subtree); + + prefs_register_bool_preference(dcm_module, "cmd_details", + "Show command details in header", + "Show message ID and number of completed, remaining, warned or failed operations in header and info column.", + &global_dcm_cmd_details); + + prefs_register_bool_preference(dcm_module, "pdv_reassemble", + "Merge fragmented PDVs", + "Decode all DICOM tags in the last PDV. This will ensure the proper reassembly. " + "De-select, to troubleshoot PDU length issues, or to understand PDV fragmentation. " + "When not set, the decoding may fail and the exports may become corrupt.", + &global_dcm_reassemble); + + dicom_eo_tap = register_export_object(proto_dcm, dcm_eo_packet, NULL); + + register_init_routine(&dcm_init); + + /* Register processing of fragmented DICOM PDVs */ + reassembly_table_register(&dcm_pdv_reassembly_table, &addresses_reassembly_table_functions); + +} + +/* +Register static TCP port range specified in preferences. +Register heuristic search as well. + +Statically defined ports take precedence over a heuristic one. I.e., if a foreign protocol claims a port, +where DICOM is running on, we would never be called, by just having the heuristic registration. + +This function is also called, when preferences change. +*/ +void +proto_reg_handoff_dcm(void) +{ + /* Adds a UI element to the preferences dialog. This is the static part. */ + dissector_add_uint_range_with_preference("tcp.port", DICOM_DEFAULT_RANGE, dcm_handle); + + /* + The following shows up as child protocol of 'DICOM' in 'Enable/Disable Protocols ...' + + The registration procedure for dissectors is a two-stage procedure. + + In stage 1, dissectors create tables in which other dissectors can register them. That's the stage in which proto_register_ routines are called. + In stage 2, dissectors register themselves in tables created in stage 1. That's the stage in which proto_reg_handoff_ routines are called. + + heur_dissector_add() needs to be called in proto_reg_handoff_dcm() function. + */ + + heur_dissector_add("tcp", dissect_dcm_heuristic, "DICOM on any TCP port (heuristic)", "dicom_tcp", proto_dcm, HEURISTIC_ENABLE); +} + + +/* + +PDU's +01 ASSOC-RQ + 1 1 reserved + 2 4 length + 6 2 protocol version (0x0 0x1) + 8 2 reserved +10 16 dest aetitle +26 16 src aetitle +42 32 reserved +74 - presentation data value items + +02 A-ASSOC-AC + 1 reserved + 4 length + 2 protocol version (0x0 0x1) + 2 reserved + 16 dest aetitle (not checked) + 16 src aetitle (not checked) + 32 reserved + - presentation data value items + +03 ASSOC-RJ + 1 reserved + 4 length (4) + 1 reserved + 1 result (1 reject perm, 2 reject transient) + 1 source (1 service user, 2 service provider, 3 service provider) + 1 reason + 1 == source + 1 no reason given + 2 application context name not supported + 3 calling aetitle not recognized + 7 called aetitle not recognized + 2 == source + 1 no reason given + 2 protocol version not supported + 3 == source + 1 temporary congestion + 2 local limit exceeded + +04 P-DATA + 1 1 reserved + 2 4 length + - (1+) presentation data value (PDV) items + 6 4 length +10 1 Presentation Context ID (odd ints 1 - 255) + - PDV +11 1 header + 0x01 if set, contains Message Command info, else Message Data + 0x02 if set, contains last fragment + +05 A-RELEASE-RQ + 1 reserved + 4 length (4) + 4 reserved + +06 A-RELEASE-RP + 1 reserved + 4 length (4) + 4 reserved + +07 A-ABORT + 1 reserved + 4 length (4) + 2 reserved + 1 source (0 = user, 1 = provider) + 1 reason if 1 == source (0 not spec, 1 unrecognized, 2 unexpected 4 unrecognized param, 5 unexpected param, 6 invalid param) + + + +ITEM's +10 Application Context + 1 reserved + 2 length + - name + +20 Presentation Context + 1 reserved + 2 length + 1 Presentation context id + 3 reserved + - (1) abstract and (1+) transfer syntax sub-items + +21 Presentation Context (Reply) + 1 reserved + 2 length + 1 ID (odd int's 1-255) + 1 reserved + 1 result (0 accept, 1 user-reject, 2 no-reason, 3 abstract not supported, 4- transfer syntax not supported) + 1 reserved + - (1) type 40 + +30 Abstract syntax + 1 reserved + 2 length + - name (<= 64) + +40 Transfer syntax + 1 reserved + 2 length + - name (<= 64) + +50 user information + 1 reserved + 2 length + - user data + +51 max length + 1 reserved + 2 length (4) + 4 max PDU lengths + +From 3.7 Annex D Association Negotiation +======================================== + +52 IMPLEMENTATION CLASS UID + 1 Item-type 52H + 1 Reserved + 2 Item-length + n Implementation-class-uid + +55 IMPLEMENTATION VERSION NAME + 1 Item-type 55H + 1 Reserved + 2 Item-length + n Implementation-version-name + +53 ASYNCHRONOUS OPERATIONS WINDOW + 1 Item-type 53H + 1 Reserved + 2 Item-length + 2 Maximum-number-operations-invoked + 2 Maximum-number-operations-performed + +54 SCP/SCU ROLE SELECTION + 1 Item-type 54H + 1 Reserved + 2 Item-length (n) + 2 UID-length (m) + m SOP-class-uid + 1 SCU-role + 0 - non support of the SCU role + 1 - support of the SCU role + 1 SCP-role + 0 - non support of the SCP role + 1 - support of the SCP role. + +56 SOP CLASS EXTENDED NEGOTIATION + 1 Item-type 56H + 1 Reserved + 2 Item-Length (n) + 2 SOP-class-uid-length (m) + m SOP-class-uid + n-m Service-class-application-information + +57 SOP CLASS COMMON EXTENDED NEGOTIATION + 1 Item-type 57H + 1 Sub-item-version + 2 Item-Length + 2 SOP-class-uid-length (m) + 7-x SOP-class-uid The SOP Class identifier encoded as a UID as defined in PS 3.5. + (x+1)-(x+2) Service-class-uid-length The Service-class-uid-length shall be the number of bytes in the Service-class-uid field. It shall be encoded as an unsigned binary number. + (x+3)-y Service-class-uid The Service Class identifier encoded as a UID as defined in PS 3.5. + (y+1)-(y+2) Related-general-sop-class-identification-length The Related-general-sop-class-identification-length shall be the number of bytes in the Related-general-sop-class-identification field. Shall be zero if no Related General SOP Classes are identified. + (y+3)-z Related-general-sop-class-identification The Related-general-sop-class-identification is a sequence of pairs of length and UID sub-fields. Each pair of sub-fields shall be formatted in accordance with Table D.3-13. + (z+1)-k Reserved Reserved for additional fields of the sub-item. Shall be zero-length for Version 0 of Sub-item definition. + + Table D.3-13 + RELATED-GENERAL-SOP-CLASS-IDENTIFICATION SUB-FIELDS + Bytes Sub-Field Name Description of Sub-Field + 1-2 Related-general-sop-class-uid-length The Related-general-sop-class-uid-length shall be the number of bytes in the Related-general-sop-class-uid sub-field. It shall be encoded as an unsigned binary number. + 3-n Related-general-sop-class-uid The Related General SOP Class identifier encoded as a UID as defined in PS 3.5. + +58 User Identity Negotiation + 1 Item-type 58H + 1 Reserved + 2 Item-length + 1 User-Identity-Type Field value shall be in the range 1 to 4 with the following meanings: + 1 - Username as a string in UTF-8 + 2 - Username as a string in UTF-8 and passcode + 3 - Kerberos Service ticket + 4 - SAML Assertion + Other values are reserved for future standardization. + 1 Positive-response-requested Field value: + 0 - no response requested + 1 - positive response requested + 2 Primary-field-length The User-Identity-Length shall contain the length of the User-Identity value. + 9-n Primary-field This field shall convey the user identity, either the username as a series of characters, or the Kerberos Service ticket encoded in accordance with RFC-1510. + n+1-n+2 Secondary-field-length This field shall be non-zero only if User-Identity-Type has the value 2. It shall contain the length of the secondary-field. + n+3-m Secondary-field This field shall be present only if User-Identity-Type has the value 2. It shall contain the Passcode value. + +59 User Identity Negotiation Reply + 1 Item-type 59H + 1 Reserved + 2 Item-length + 5-6 Server-response-length This field shall contain the number of bytes in the Server-response. May be zero. + 7-n Server-response This field shall contain the Kerberos Server ticket, encoded in accordance with RFC-1510, if the User-Identity-Type value in the A-ASSOCIATE-RQ was 3. This field shall contain the SAML response if the User-Identity-Type value in the A-ASSOCIATE-RQ was 4. This field shall be zero length if the value of the User-Identity-Type in the A-ASSOCIATE-RQ was 1 or 2. + + */ + +/* + * 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: + */ |