forked from osmocom/wireshark
4528 lines
171 KiB
C
4528 lines
171 KiB
C
/* 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 "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;
|
|
gchar *hostname;
|
|
gchar *filename;
|
|
gchar *content_type;
|
|
guint32 payload_len;
|
|
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)
|
|
{
|
|
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 */
|
|
/*
|
|
Don't copy any data. dcm_export_create_object() is already g_malloc() the items
|
|
Still, 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 = eo_info->hostname;
|
|
entry->content_type = eo_info->content_type;
|
|
entry->filename = g_path_get_basename(eo_info->filename);
|
|
entry->payload_len = eo_info->payload_len;
|
|
entry->payload_data = eo_info->payload_data;
|
|
|
|
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;
|
|
|
|
g_free(pctx->xfer_uid); /* free prev allocated xfer */
|
|
g_free(pctx->xfer_desc); /* free prev allocated xfer */
|
|
|
|
pctx->syntax = 0;
|
|
pctx->xfer_uid = g_strdup(xfer_uid);
|
|
pctx->xfer_desc = g_strdup(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(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(wmem_packet_scope(), 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(wmem_packet_scope(), "%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(wmem_packet_scope(), pdv_curr->sop_class_uid);
|
|
sop_instance_uid = wmem_strdup(wmem_packet_scope(), 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(wmem_packet_scope(), "%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(wmem_packet_scope(), WIRESHARK_MEDIA_STORAGE_SOP_CLASS_UID);
|
|
sop_instance_uid = wmem_strdup_printf(wmem_packet_scope(), "%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(wmem_packet_scope(), "%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(&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(&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(wmem_file_scope(), 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 */
|
|
eo_info = wmem_new0(wmem_file_scope(), dicom_eo_t);
|
|
eo_info->hostname = g_strdup(hostname);
|
|
eo_info->filename = g_strdup(filename);
|
|
eo_info->content_type = g_strdup(pdv->desc);
|
|
|
|
eo_info->payload_data = pdv_combined;
|
|
eo_info->payload_len = dcm_header_len + pdv_combined_len;
|
|
|
|
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, wmem_packet_scope(), &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, wmem_packet_scope(), &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, wmem_packet_scope(), &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, wmem_packet_scope(), &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;
|
|
}
|
|
|
|
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, 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(wmem_packet_scope(), 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(wmem_packet_scope(), "%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(wmem_packet_scope(), 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, 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(wmem_packet_scope(), 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(wmem_packet_scope(), "%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, 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|ENC_NA);
|
|
proto_item_append_text(assoc_item_user_identify_item, ": %s", tvb_get_string_enc(wmem_packet_scope(), 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|ENC_NA);
|
|
proto_item_append_text(assoc_item_user_identify_item, ", %s", tvb_get_string_enc(wmem_packet_scope(), 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, 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(wmem_packet_scope(), 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(wmem_packet_scope(), "%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, 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, 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(wmem_packet_scope(), "%s (%s)", pctx_abss_desc, pctx_abss_uid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pctx_result==0) {
|
|
/* Accepted */
|
|
buf_desc = wmem_strdup_printf(wmem_packet_scope(), "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(wmem_packet_scope(), "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, 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, 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, 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, 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, userinfo_ptree, offset-4);
|
|
|
|
offset += item_len;
|
|
break;
|
|
|
|
case 0x56: /* extended negotiation */
|
|
|
|
dissect_dcm_assoc_sopclass_extneg(tvb, userinfo_ptree, offset-4);
|
|
|
|
offset += item_len;
|
|
break;
|
|
|
|
case 0x58: /* User Identify */
|
|
|
|
dissect_dcm_assoc_user_identify(tvb, 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, 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, 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(tvb, offset, vl_max - 1);
|
|
}
|
|
else {
|
|
vals = tvb_format_text(tvb, offset, vl_max);
|
|
}
|
|
|
|
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(wmem_packet_scope(), "%s (%s)", vals, uid->name);
|
|
}
|
|
else {
|
|
*tag_value = vals;
|
|
}
|
|
}
|
|
else {
|
|
if (strlen(vals) > 50) {
|
|
*tag_value = wmem_strdup_printf(wmem_packet_scope(), "%-50.50s...", vals);
|
|
}
|
|
else {
|
|
*tag_value = vals;
|
|
}
|
|
}
|
|
proto_tree_add_string(tree, hf_dcm_tag_value_str, tvb, offset, vl_max, *tag_value);
|
|
|
|
if (grp == 0x0000 && elm == 0x0902) {
|
|
/* The error comment */
|
|
pdv->comment = wmem_strdup(wmem_file_scope(), g_strstrip(vals));
|
|
}
|
|
}
|
|
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(wmem_packet_scope(), "(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(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(wmem_packet_scope(), "(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(wmem_packet_scope(),"%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(wmem_packet_scope(), "%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(wmem_packet_scope(), "%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(wmem_packet_scope(), "%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(wmem_packet_scope(), "%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(wmem_packet_scope(), "%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(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(wmem_packet_scope(), "%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(wmem_packet_scope(), "%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(wmem_packet_scope(), "(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(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(wmem_packet_scope(), "(Retired) %-35.35s", tag_desc);
|
|
}
|
|
else {
|
|
desc_mod = wmem_strdup_printf(wmem_packet_scope(), "%-45.45s", tag_desc);
|
|
}
|
|
|
|
if (vl == 0xFFFFFFFF) {
|
|
tag_vl = wmem_strdup_printf(wmem_packet_scope(), "%10.10s", "<udef>");
|
|
}
|
|
else {
|
|
tag_vl = wmem_strdup_printf(wmem_packet_scope(), "%10u", vl); /* Show as dec */
|
|
}
|
|
|
|
if (is_implicit) tag_sum = wmem_strdup_printf(wmem_packet_scope(), "(%04x,%04x) %s %s", grp, elm, tag_vl, desc_mod);
|
|
else tag_sum = wmem_strdup_printf(wmem_packet_scope(), "(%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(wmem_packet_scope(), tag_def->vr);
|
|
is_vl_long = TRUE; /* Implicit always has 4 byte length field */
|
|
}
|
|
else {
|
|
|
|
if (len_decoded_remaing >= 2) {
|
|
vr = wmem_strdup(wmem_packet_scope(), 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(wmem_packet_scope(), tvb, offset, 2, ENC_ASCII);
|
|
offset += 2;
|
|
|
|
g_free(pdv->open_tag.vr);
|
|
pdv->open_tag.vr = g_strdup(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(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(wmem_packet_scope(), "<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(wmem_packet_scope(), "<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(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(wmem_packet_scope(), 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(wmem_packet_scope(), "%s (%s, %s)", pdv->desc, pdv->status, pdv->comment);
|
|
}
|
|
else {
|
|
*pdv_description = wmem_strdup_printf(wmem_packet_scope(), "%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(wmem_packet_scope(), "%s ID=%d", pdv->desc, pdv->message_id);
|
|
}
|
|
else if (pdv->message_id_resp > 0) {
|
|
|
|
*pdv_description = wmem_strdup_printf(wmem_packet_scope(), "%s ID=%d", pdv->desc, pdv->message_id_resp);
|
|
|
|
if (pdv->no_completed > 0) {
|
|
*pdv_description = wmem_strdup_printf(wmem_packet_scope(), "%s C=%d", *pdv_description, pdv->no_completed);
|
|
}
|
|
if (pdv->no_remaining > 0) {
|
|
*pdv_description = wmem_strdup_printf(wmem_packet_scope(), "%s R=%d", *pdv_description, pdv->no_remaining);
|
|
}
|
|
if (pdv->no_warning > 0) {
|
|
*pdv_description = wmem_strdup_printf(wmem_packet_scope(), "%s W=%d", *pdv_description, pdv->no_warning);
|
|
}
|
|
if (pdv->no_failed > 0) {
|
|
*pdv_description = wmem_strdup_printf(wmem_packet_scope(), "%s F=%d", *pdv_description, pdv->no_failed);
|
|
}
|
|
if (!pdv->is_pending && pdv->status)
|
|
{
|
|
*pdv_description = wmem_strdup_printf(wmem_packet_scope(), "%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(wmem_packet_scope(), "%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(wmem_packet_scope(), "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(wmem_packet_scope(), "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(wmem_packet_scope(), pdv_description);
|
|
}
|
|
else {
|
|
buf_desc = wmem_strdup_printf(wmem_packet_scope(), "%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.id",
|
|
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:
|
|
*/
|