1 /* packet-dcm.c
2 * Routines for DICOM dissection
3 * Copyright 2003, Rich Coe <richcoe2@gmail.com>
4 * Copyright 2008-2019, David Aggeler <david_aggeler@hispeed.ch>
5 *
6 * DICOM communication protocol: https://www.dicomstandard.org/current/
7 *
8 * Part 5: Data Structures and Encoding
9 * Part 6: Data Dictionary
10 * Part 7: Message Exchange
11 * Part 8: Network Communication Support for Message Exchange
12 * Part 10: Media Storage and File Format
13 *
14 * Wireshark - Network traffic analyzer
15 * By Gerald Combs <gerald@wireshark.org>
16 * Copyright 1998 Gerald Combs
17 *
18 * SPDX-License-Identifier: GPL-2.0-or-later
19 */
20
21
22 /*
23 *
24 * ToDo
25 *
26 * - Implement value multiplicity (VM) consistently in dissect_dcm_tag_value()
27 * - Syntax detection, in case an association request is missing in capture
28 * - Read private tags from configuration and parse in capture
29 *
30 * History
31 *
32 * Feb 2019 - David Aggeler
33 *
34 * - Fixed re-assembly and export (consolidated duplicate code)
35 * - Fixed random COL_INFO issues
36 * - Improved COL_INFO for C-FIND
37 * - Improved COL_INFO for multiple PDUs in one frame
38 *
39 * Feb 2019 - Rickard Holmberg
40 *
41 * - Updated DICOM definitions to 2019a
42 *
43 * Oct 2018 - Rickard Holmberg
44 *
45 * - Moved DICOM definitions to packet-dcm.h
46 * - Generate definitions from docbook with Phyton script
47 * - Updated DICOM definitions to 2018e
48 *
49 * June 2018 - David Aggeler
50 *
51 * - Fixed initial COL_INFO for associations. It used to 'append' instead of 'set'.
52 * - Changed initial length check from tvb_reported_length() to tvb_captured_length()
53 * - Heuristic Dissection:
54 * o Modified registration, so it can be clearly identified in the Enable/Disable Protocols dialog
55 * o Enabled by default
56 * o Return proper data type
57 *
58 * February 2018 - David Aggeler
59 *
60 * - Fixed Bug 14415. Some tag descriptions which are added to the parent item (32 tags).
61 * If one of those was empty a crash occurred. Mainly the RTPlan modality was affected.
62 * - Fixed length decoding for OD, OL, UC, UR
63 * - Fixed hf_dcm_assoc_item_type to be interpreted as 1 byte
64 * - Fixed pdu_type to be interpreted as 1 byte
65 * - Fixed decoding of AT type, where value length was wrongly reported in capture as 2 (instead of n*4)
66 *
67 * Misc. authors & dates
68 *
69 * - Fixed 'AT' value representation. The 'element' was equal to the 'group'.
70 * - Changed 'FL' value representations
71 *
72 * September 2013 - Pascal Quantin
73 *
74 * - Replace all ep_ and se_ allocation with wmem_ allocations
75 *
76 * February 2013 - Stefan Allers
77 *
78 * - Support for dissection of Extended Negotiation (Query/Retrieve)
79 * - Support for dissection of SCP/SCU Role Selection
80 * - Support for dissection of Async Operations Window Negotiation
81 * - Fixed: Improper calculation of length for Association Header
82 * - Missing UIDs (Transfer Syntax, SOP Class...) added acc. PS 3.x-2011
83 *
84 * Jul 11, 2010 - David Aggeler
85 *
86 * - Finally, better reassembly using fragment_add_seq_next().
87 * The previous mode is still supported.
88 * - Fixed sporadic decoding and export issues. Always decode
89 * association negotiation, since performance check (tree==NULL)
90 * is now only in dissect_dcm_pdv_fragmented().
91 * - Added one more PDV length check
92 * - Show Association Headers as individual items
93 * - Code cleanup. i.e. moved a few lookup functions to be closer to the dissection
94 *
95 * May 13, 2010 - David Aggeler (SVN 32815)
96 *
97 * - Fixed HF to separate signed & unsigned values and to have BASE_DEC all signed ones
98 * - Fixed private sequences with undefined length in ILE
99 * - Fixed some spellings in comments
100 *
101 * May 27, 2009 - David Aggeler (SVN 29060)
102 *
103 * - Fixed corrupt files on DICOM Export
104 * - Fixed memory limitation on DICOM Export
105 * - Removed minimum packet length for static port mode
106 * - Simplified checks for heuristic mode
107 * - Removed unused functions
108 *
109 * May 17, 2009 - David Aggeler (SVN 28392)
110 *
111 * - Spelling
112 * - Added expert_add_info() for status responses with warning & error level
113 * - Added command details in info column (optionally)
114 *
115 * Dec 19, 2008 to Mar 29, 2009 - Misc (SVN 27880)
116 *
117 * - Spellings, see SVN
118 *
119 * Oct 26, 2008 - David Aggeler (SVN 26662)
120 *
121 * - Support remaining DICOM/ARCNEMA tags
122 *
123 * Oct 3, 2008 - David Aggeler (SVN 26417)
124 *
125 * - DICOM Tags: Support all tags, except for group 1000, 7Fxx
126 * and tags (0020,3100 to 0020, 31FF).
127 * Luckily these ones are retired anyhow
128 * - DICOM Tags: Optionally show sequences, items and tags as subtree
129 * - DICOM Tags: Certain items do have a summary of a few contained tags
130 * - DICOM Tags: Support all defined VR representations
131 * - DICOM Tags: For Explicit Syntax, use VR in the capture
132 * - DICOM Tags: Lookup UIDs
133 * - DICOM Tags: Handle split at PDV start and end. RT Structures were affected by this.
134 * - DICOM Tags: Handle split in tag header
135 *
136 * - Added all status messages from PS 3.4 & PS 3.7
137 * - Fixed two more type warnings on solaris, i.e. (gchar *)tvb_get_ephemeral_string
138 * - Replaced all ep_alloc() with ep_alloc0() and se_alloc() with se_alloc0()
139 * - Replaced g_strdup with ep_strdup() or se_strdup()
140 * - Show multiple PDU description in COL_INFO, not just last one. Still not all, but more
141 * sophisticated logic for this column is probably overkill
142 * - Since DICOM is a 32 bit protocol with all length items specified unsigned
143 * all offset & position variables are now declared as guint32 for dissect_dcm_pdu and
144 * its nested functions. dissect_dcm_main() remained by purpose on int,
145 * since we request data consolidation, requiring a TRUE as return value
146 * - Decode DVTk streams when using defined ports (not in heuristic mode)
147 * - Changed to warning level 4 (for MSVC) and fixed the warnings
148 * - Code cleanup & removed last DISSECTOR_ASSERT()
149 *
150 * Jul 25, 2008 - David Aggeler (SVN 25834)
151 *
152 * - Replaced guchar with gchar, since it caused a lot of warnings on solaris.
153 * - Moved a little more form the include to this one to be consistent
154 *
155 * Jul 17, 2008 - David Aggeler
156 *
157 * - Export objects as part 10 compliant DICOM file. Finally, this major milestone has been reached.
158 * - PDVs are now a child of the PCTX rather than the ASSOC object.
159 * - Fixed PDV continuation for unknown tags (e.g. RT Structure Set)
160 * - Replaced proprietary trim() with g_strstrip()
161 * - Fixed strings that are displayed with /000 (padding of odd length)
162 * - Added expert_add_info() for invalid flags and presentation context IDs
163 *
164 * Jun 17, 2008 - David Aggeler
165 *
166 * - Support multiple PDVs per PDU
167 * - Better summary, in PDV, PDU header and in INFO Column, e.g. show commands like C-STORE
168 * - Fixed Association Reject (was working before my changes)
169 * - Fixed PDV Continuation with very small packets. Reduced minimum packet length
170 * from 10 to 2 Bytes for PDU Type 4
171 * - Fixed PDV Continuation. Last packet was not found correctly.
172 * - Fixed compilation warning (build 56 on solaris)
173 * - Fixed tree expansion (hf_dcm_xxx)
174 * - Added expert_add_info() for Association Reject
175 * - Added expert_add_info() for Association Abort
176 * - Added expert_add_info() for short PDVs (i.e. last fragment, but PDV is not completed yet)
177 * - Clarified and grouped data structures and its related code (dcmItem, dcmState) to have
178 * consistent _new() & _get() functions and to be according to coding conventions
179 * - Added more function declaration to be more consistent
180 * - All dissect_dcm_xx now have (almost) the same parameter order
181 * - Removed DISSECTOR_ASSERT() for packet data errors. Not designed to handle this.
182 * - Handle multiple DICOM Associations in a capture correctly, i.e. if presentation contexts are different.
183 *
184 * May 23, 2008 - David Aggeler
185 *
186 * - Added Class UID lookup, both in the association and in the transfer
187 * - Better hierarchy for items in Association request/response and therefore better overview
188 * This was a major rework. Abstract Syntax & Transfer Syntax are now children
189 * of a presentation context and therefore grouped. User Info is now grouped.
190 * - Re-assemble PDVs that span multiple PDUs, i.e fix continuation packets
191 * This caused significant changes to the data structures
192 * - Added preference with DICOM TCP ports, to prevent 'stealing' the conversation
193 * i.e. don't just rely on heuristic
194 * - Use pinfo->desegment_len instead of tcp_dissect_pdus()
195 * - Returns number of bytes parsed
196 * - For non DICOM packets, do not allocate any memory anymore,
197 * - Added one DISSECTOR_ASSERT() to prevent loop with len==0. More to come
198 * - Heuristic search is optional to save resources for non DICOM users
199 *
200 * - Output naming closer to DICOM Standard
201 * - Variable names closer to Standard
202 * - Protocol in now called DICOM not dcm anymore.
203 * - Fixed type of a few variables to guchar instead of guint8
204 * - Changed some of the length displays to decimal, because the hex value can
205 * already be seen in the packet and decimal is easier for length calculation
206 * in respect to TCP
207 *
208 * Apr 28, 2005 - Rich Coe
209 *
210 * - fix memory leak when Assoc packet is processed repeatedly in wireshark
211 * - removed unused partial packet flag
212 * - added better support for DICOM VR
213 * - sequences
214 * - report actual VR in packet display, if supplied by xfer syntax
215 * - show that we are not displaying entire tag string with '[...]',
216 * some tags can hold up to 2^32-1 chars
217 *
218 * - remove my goofy attempt at trying to get access to the fragmented packets
219 * - process all the data in the Assoc packet even if display is off
220 * - limit display of data in Assoc packet to defined size of the data even
221 * if reported size is larger
222 * - show the last tag in a packet as [incomplete] if we don't have all the data
223 * - added framework for reporting DICOM async negotiation (not finished)
224 * (I'm not aware of an implementation which currently supports this)
225 *
226 * Nov 9, 2004 - Rich Coe
227 *
228 * - Fixed the heuristic code -- sometimes a conversation already exists
229 * - Fixed the dissect code to display all the tags in the PDU
230 *
231 * Initial - Rich Coe
232 *
233 * - It currently displays most of the DICOM packets.
234 * - I've used it to debug Query/Retrieve, Storage, and Echo protocols.
235 * - Not all DICOM tags are currently displayed symbolically.
236 * Unknown tags are displayed as '(unknown)'
237 * More known tags might be added in the future.
238 * If the tag data contains a string, it will be displayed.
239 * Even if the tag contains Explicit VR, it is not currently used to
240 * symbolically display the data.
241 *
242 */
243
244 #include "config.h"
245
246 #include <epan/packet.h>
247 #include <epan/exceptions.h>
248 #include <epan/prefs.h>
249 #include <epan/expert.h>
250 #include <epan/tap.h>
251 #include <epan/reassemble.h>
252 #include <epan/export_object.h>
253
254 #include "packet-tcp.h"
255
256 #include "packet-dcm.h"
257
258 void proto_register_dcm(void);
259 void proto_reg_handoff_dcm(void);
260
261 #define DICOM_DEFAULT_RANGE "104"
262
263 /* Many thanks to http://medicalconnections.co.uk/ for the GUID */
264 #define WIRESHARK_IMPLEMENTATION_UID "1.2.826.0.1.3680043.8.427.10"
265 #define WIRESHARK_MEDIA_STORAGE_SOP_CLASS_UID "1.2.826.0.1.3680043.8.427.11.1"
266 #define WIRESHARK_MEDIA_STORAGE_SOP_INSTANCE_UID_PREFIX "1.2.826.0.1.3680043.8.427.11.2"
267 #define WIRESHARK_IMPLEMENTATION_VERSION "WIRESHARK"
268
269 static gboolean global_dcm_export_header = TRUE;
270 static guint global_dcm_export_minsize = 4096; /* Filter small objects in export */
271
272 static gboolean global_dcm_seq_subtree = TRUE;
273 static gboolean global_dcm_tag_subtree = FALSE; /* Only useful for debugging */
274 static gboolean global_dcm_cmd_details = TRUE; /* Show details in header and info column */
275 static gboolean global_dcm_reassemble = TRUE; /* Merge fragmented PDVs */
276
277 static wmem_map_t *dcm_tag_table = NULL;
278 static wmem_map_t *dcm_uid_table = NULL;
279 static wmem_map_t *dcm_status_table = NULL;
280
281 /* Initialize the protocol and registered fields */
282 static int proto_dcm = -1;
283
284 static int dicom_eo_tap = -1;
285
286 static int hf_dcm_pdu_type = -1;
287 static int hf_dcm_pdu_len = -1;
288 static int hf_dcm_assoc_version = -1;
289 static int hf_dcm_assoc_called = -1;
290 static int hf_dcm_assoc_calling = -1;
291 static int hf_dcm_assoc_reject_result = -1;
292 static int hf_dcm_assoc_reject_source = -1;
293 static int hf_dcm_assoc_reject_reason = -1;
294 static int hf_dcm_assoc_abort_source = -1;
295 static int hf_dcm_assoc_abort_reason = -1;
296 static int hf_dcm_assoc_item_type = -1;
297 static int hf_dcm_assoc_item_len = -1;
298 static int hf_dcm_actx = -1;
299 static int hf_dcm_pctx_id = -1;
300 static int hf_dcm_pctx_result = -1;
301 static int hf_dcm_pctx_abss_syntax = -1;
302 static int hf_dcm_pctx_xfer_syntax = -1;
303 static int hf_dcm_info = -1;
304 static int hf_dcm_info_uid = -1;
305 static int hf_dcm_info_version = -1;
306 static int hf_dcm_info_extneg = -1;
307 static int hf_dcm_info_extneg_sopclassuid_len = -1;
308 static int hf_dcm_info_extneg_sopclassuid = -1;
309 static int hf_dcm_info_extneg_relational_query = -1;
310 static int hf_dcm_info_extneg_date_time_matching = -1;
311 static int hf_dcm_info_extneg_fuzzy_semantic_matching = -1;
312 static int hf_dcm_info_extneg_timezone_query_adjustment = -1;
313 static int hf_dcm_info_rolesel = -1;
314 static int hf_dcm_info_rolesel_sopclassuid_len = -1;
315 static int hf_dcm_info_rolesel_sopclassuid = -1;
316 static int hf_dcm_info_rolesel_scurole = -1;
317 static int hf_dcm_info_rolesel_scprole = -1;
318 static int hf_dcm_info_async_neg = -1;
319 static int hf_dcm_info_async_neg_max_num_ops_inv = -1;
320 static int hf_dcm_info_async_neg_max_num_ops_per = -1;
321 static int hf_dcm_info_user_identify = -1;
322 static int hf_dcm_info_user_identify_type = -1;
323 static int hf_dcm_info_user_identify_response_requested = -1;
324 static int hf_dcm_info_user_identify_primary_field_length = -1;
325 static int hf_dcm_info_user_identify_primary_field = -1;
326 static int hf_dcm_info_user_identify_secondary_field_length = -1;
327 static int hf_dcm_info_user_identify_secondary_field = -1;
328 static int hf_dcm_info_unknown = -1;
329 static int hf_dcm_assoc_item_data = -1;
330 static int hf_dcm_pdu_maxlen = -1;
331 static int hf_dcm_pdv_len = -1;
332 static int hf_dcm_pdv_ctx = -1;
333 static int hf_dcm_pdv_flags = -1;
334 static int hf_dcm_data_tag = -1;
335 static int hf_dcm_tag = -1;
336 static int hf_dcm_tag_vr = -1;
337 static int hf_dcm_tag_vl = -1;
338 static int hf_dcm_tag_value_str = -1;
339 static int hf_dcm_tag_value_16u = -1;
340 static int hf_dcm_tag_value_16s = -1;
341 static int hf_dcm_tag_value_32s = -1;
342 static int hf_dcm_tag_value_32u = -1;
343 static int hf_dcm_tag_value_byte = -1;
344
345 /* Initialize the subtree pointers */
346 static gint ett_dcm = -1;
347 static gint ett_assoc = -1;
348 static gint ett_assoc_header = -1;
349 static gint ett_assoc_actx = -1;
350 static gint ett_assoc_pctx = -1;
351 static gint ett_assoc_pctx_abss = -1;
352 static gint ett_assoc_pctx_xfer = -1;
353 static gint ett_assoc_info = -1;
354 static gint ett_assoc_info_uid = -1;
355 static gint ett_assoc_info_version = -1;
356 static gint ett_assoc_info_extneg = -1;
357 static gint ett_assoc_info_rolesel = -1;
358 static gint ett_assoc_info_async_neg = -1;
359 static gint ett_assoc_info_user_identify = -1;
360 static gint ett_assoc_info_unknown = -1;
361 static gint ett_dcm_data = -1;
362 static gint ett_dcm_data_pdv = -1;
363 static gint ett_dcm_data_tag = -1;
364 static gint ett_dcm_data_seq = -1;
365 static gint ett_dcm_data_item = -1;
366
367 static expert_field ei_dcm_data_tag = EI_INIT;
368 static expert_field ei_dcm_multiple_transfer_syntax = EI_INIT;
369 static expert_field ei_dcm_pdv_len = EI_INIT;
370 static expert_field ei_dcm_pdv_flags = EI_INIT;
371 static expert_field ei_dcm_pdv_ctx = EI_INIT;
372 static expert_field ei_dcm_no_abstract_syntax = EI_INIT;
373 static expert_field ei_dcm_no_abstract_syntax_uid = EI_INIT;
374 static expert_field ei_dcm_status_msg = EI_INIT;
375 static expert_field ei_dcm_no_transfer_syntax = EI_INIT;
376 static expert_field ei_dcm_multiple_abstract_syntax = EI_INIT;
377 static expert_field ei_dcm_invalid_pdu_length = EI_INIT;
378 static expert_field ei_dcm_assoc_item_len = EI_INIT;
379 static expert_field ei_dcm_assoc_rejected = EI_INIT;
380 static expert_field ei_dcm_assoc_aborted = EI_INIT;
381
382 static dissector_handle_t dcm_handle;
383
384 static const value_string dcm_pdu_ids[] = {
385 { 1, "ASSOC Request" },
386 { 2, "ASSOC Accept" },
387 { 3, "ASSOC Reject" },
388 { 4, "Data" },
389 { 5, "RELEASE Request" },
390 { 6, "RELEASE Response" },
391 { 7, "ABORT" },
392 { 0, NULL }
393 };
394
395 static const value_string dcm_assoc_item_type[] = {
396 { 0x10, "Application Context" },
397 { 0x20, "Presentation Context" },
398 { 0x21, "Presentation Context Reply" },
399 { 0x30, "Abstract Syntax" },
400 { 0x40, "Transfer Syntax" },
401 { 0x50, "User Info" },
402 { 0x51, "Max Length" },
403 { 0x52, "Implementation Class UID" },
404 { 0x53, "Asynchronous Operations Window Negotiation" },
405 { 0x54, "SCP/SCU Role Selection" },
406 { 0x55, "Implementation Version" },
407 { 0x56, "SOP Class Extended Negotiation" },
408 { 0x58, "User Identity" },
409 { 0, NULL }
410 };
411
412 static const value_string user_identify_type_vals[] = {
413 { 1, "Username as a string in UTF-8" },
414 { 2, "Username as a string in UTF-8 and passcode" },
415 { 3, "Kerberos Service ticket" },
416 { 4, "SAML Assertion" },
417 { 0, NULL }
418 };
419
420 /* Used for DICOM Export Object feature */
421 typedef struct _dicom_eo_t {
422 guint32 pkt_num;
423 gchar *hostname;
424 gchar *filename;
425 gchar *content_type;
426 guint32 payload_len;
427 guint8 *payload_data;
428 } dicom_eo_t;
429
430 static tap_packet_status
dcm_eo_packet(void * tapdata,packet_info * pinfo,epan_dissect_t * edt _U_,const void * data)431 dcm_eo_packet(void *tapdata, packet_info *pinfo, epan_dissect_t *edt _U_,
432 const void *data)
433 {
434 export_object_list_t *object_list = (export_object_list_t *)tapdata;
435 const dicom_eo_t *eo_info = (const dicom_eo_t *)data;
436 export_object_entry_t *entry;
437
438 if (eo_info) { /* We have data waiting for us */
439 /*
440 Don't copy any data. dcm_export_create_object() is already g_malloc() the items
441 Still, the values will be freed when the export Object window is closed.
442 Therefore, strings and buffers must be copied
443 */
444 entry = g_new(export_object_entry_t, 1);
445
446 entry->pkt_num = pinfo->num;
447 entry->hostname = eo_info->hostname;
448 entry->content_type = eo_info->content_type;
449 entry->filename = g_path_get_basename(eo_info->filename);
450 entry->payload_len = eo_info->payload_len;
451 entry->payload_data = eo_info->payload_data;
452
453 object_list->add_entry(object_list->gui_data, entry);
454
455 return TAP_PACKET_REDRAW; /* State changed - window should be redrawn */
456 } else {
457 return TAP_PACKET_DONT_REDRAW; /* State unchanged - no window updates needed */
458 }
459 }
460
461
462 /* ************************************************************************* */
463 /* Fragment items */
464 /* ************************************************************************* */
465
466 /* Initialize the subtree pointers */
467 static gint ett_dcm_pdv = -1;
468
469 static gint ett_dcm_pdv_fragment = -1;
470 static gint ett_dcm_pdv_fragments = -1;
471
472 static int hf_dcm_pdv_fragments = -1;
473 static int hf_dcm_pdv_fragment = -1;
474 static int hf_dcm_pdv_fragment_overlap = -1;
475 static int hf_dcm_pdv_fragment_overlap_conflicts = -1;
476 static int hf_dcm_pdv_fragment_multiple_tails = -1;
477 static int hf_dcm_pdv_fragment_too_long_fragment = -1;
478 static int hf_dcm_pdv_fragment_error = -1;
479 static int hf_dcm_pdv_fragment_count = -1;
480 static int hf_dcm_pdv_reassembled_in = -1;
481 static int hf_dcm_pdv_reassembled_length = -1;
482
483 static const fragment_items dcm_pdv_fragment_items = {
484 /* Fragment subtrees */
485 &ett_dcm_pdv_fragment,
486 &ett_dcm_pdv_fragments,
487 /* Fragment fields */
488 &hf_dcm_pdv_fragments,
489 &hf_dcm_pdv_fragment,
490 &hf_dcm_pdv_fragment_overlap,
491 &hf_dcm_pdv_fragment_overlap_conflicts,
492 &hf_dcm_pdv_fragment_multiple_tails,
493 &hf_dcm_pdv_fragment_too_long_fragment,
494 &hf_dcm_pdv_fragment_error,
495 &hf_dcm_pdv_fragment_count,
496 &hf_dcm_pdv_reassembled_in,
497 &hf_dcm_pdv_reassembled_length,
498 /* Reassembled data field */
499 NULL,
500 /* Tag */
501 "Message fragments"
502 };
503
504 /* Structure to handle fragmented DICOM PDU packets */
505 static reassembly_table dcm_pdv_reassembly_table;
506
507 typedef struct dcm_open_tag {
508
509 /* Contains information about an open tag in a PDV, in case it was not complete.
510
511 This implementation differentiates between open headers (grm, elm, vr, vl) and
512 open values. This data structure will handle both cases.
513
514 Open headers are not shown in the packet where the tag starts, but only in the next PDV.
515 Open values are shown in the packet where the tag starts, with <Byte 1-n> as the value
516
517 The same PDV can close an open tag from a previous PDV at the beginning
518 and at the same have time open a new tag at the end. The closing part at the beginning
519 does not have its own persistent data.
520
521 Do not overwrite the values, once defined, to save some memory.
522
523 Since PDVs are always n*2 bytes, store each of the 2 Bytes in a variable.
524 This way, we don't need to call tvb_get_xxx on a self created buffer
525
526 */
527
528 gboolean is_header_fragmented;
529 gboolean is_value_fragmented;
530
531 guint32 len_decoded; /* Should only be < 16 bytes */
532
533 guint16 grp; /* Already decoded group */
534 guint16 elm; /* Already decoded element */
535 gchar *vr; /* Already decoded VR */
536
537 gboolean is_vl_long; /* If TRUE, Value Length is 4 Bytes, otherwise 2 */
538 guint16 vl_1; /* Partially decoded 1st two bytes of length */
539 guint16 vl_2; /* Partially decoded 2nd two bytes of length */
540
541 /* These ones are, where the value was truncated */
542 guint32 len_total; /* Tag length of 'over-sized' tags. Used for display */
543 guint32 len_remaining; /* Remaining tag bytes to 'decoded' as binary data after this PDV */
544
545 gchar *desc; /* Last decoded description */
546
547 } dcm_open_tag_t;
548
549 /*
550 Per Data PDV store data needed, to allow decoding of tags longer than a PDV
551 */
552 typedef struct dcm_state_pdv {
553
554 struct dcm_state_pdv *next, *prev;
555
556 guint32 packet_no; /* Wireshark packet number, where pdv starts */
557 guint32 offset; /* Offset in packet, where PDV header starts */
558
559 gchar *desc; /* PDV description. wmem_file_scope() */
560
561 guint8 pctx_id; /* Reference to used Presentation Context */
562
563 /* Following is derived from the transfer syntax in the parent PCTX, except for Command PDVs */
564 guint8 syntax;
565
566 /* Used and filled for Export Object only */
567 gpointer data; /* Copy of PDV data without any PDU/PDV header */
568 guint32 data_len; /* Length of this PDV buffer. If >0, memory has been allocated */
569
570 gchar *sop_class_uid; /* SOP Class UID. Set in 1st PDV of a DICOM object. wmem_file_scope() */
571 gchar *sop_instance_uid; /* SOP Instance UID. Set in 1st PDV of a DICOM object. wmem_file_scope() */
572 /* End Export use */
573
574 gboolean is_storage; /* True, if the Data PDV is on the context of a storage SOP Class */
575 gboolean is_flagvalid; /* The following two flags are initialized correctly */
576 gboolean is_command; /* This PDV is a command rather than a data package */
577 gboolean is_last_fragment; /* Last Fragment bit was set, i.e. termination of an object
578 This flag delimits different DICOM object in the same association */
579 gboolean is_corrupt; /* Early termination of long PDVs */
580
581 /* The following five attributes are only used for command PDVs */
582
583 gchar *command; /* Decoded command as text */
584 gchar *status; /* Decoded status as text */
585 gchar *comment; /* Error comment, if any */
586
587 gboolean is_warning; /* Command response is a cancel, warning, error */
588 gboolean is_pending; /* Command response is 'Current Match is supplied. Sub-operations are continuing' */
589
590 guint16 message_id; /* (0000,0110) Message ID */
591 guint16 message_id_resp; /* (0000,0120) Message ID being responded to */
592
593 guint16 no_remaining; /* (0000,1020) Number of remaining sub-operations */
594 guint16 no_completed; /* (0000,1021) Number of completed sub-operations */
595 guint16 no_failed; /* (0000,1022) Number of failed sub-operations */
596 guint16 no_warning; /* (0000,1023) Number of warning sub-operations */
597
598 dcm_open_tag_t open_tag; /* Container to store information about a fragmented tag */
599
600 guint8 reassembly_id;
601
602 } dcm_state_pdv_t;
603
604 /*
605 Per Presentation Context in an association store data needed, for subsequent decoding
606 */
607 typedef struct dcm_state_pctx {
608
609 struct dcm_state_pctx *next, *prev;
610
611 guint8 id; /* 0x20 Presentation Context ID */
612 gchar *abss_uid; /* 0x30 Abstract syntax */
613 gchar *abss_desc; /* 0x30 Abstract syntax decoded*/
614 gchar *xfer_uid; /* 0x40 Accepted Transfer syntax */
615 gchar *xfer_desc; /* 0x40 Accepted Transfer syntax decoded*/
616 guint8 syntax; /* Decoded transfer syntax */
617 #define DCM_ILE 0x01 /* implicit, little endian */
618 #define DCM_EBE 0x02 /* explicit, big endian */
619 #define DCM_ELE 0x03 /* explicit, little endian */
620 #define DCM_UNK 0xf0
621
622 guint8 reassembly_count;
623 dcm_state_pdv_t *first_pdv, *last_pdv; /* List of PDV objects */
624
625 } dcm_state_pctx_t;
626
627
628 typedef struct dcm_state_assoc {
629
630 struct dcm_state_assoc *next, *prev;
631
632 dcm_state_pctx_t *first_pctx, *last_pctx; /* List of Presentation context objects */
633
634 guint32 packet_no; /* Wireshark packet number, where association starts */
635
636 char *ae_called; /* Called AE title in A-ASSOCIATE RQ */
637 char *ae_calling; /* Calling AE title in A-ASSOCIATE RQ */
638 char *ae_called_resp; /* Called AE title in A-ASSOCIATE RP */
639 char *ae_calling_resp; /* Calling AE title in A-ASSOCIATE RP */
640
641 } dcm_state_assoc_t;
642
643 typedef struct dcm_state {
644
645 struct dcm_state_assoc *first_assoc, *last_assoc;
646
647 gboolean valid; /* this conversation is a DICOM conversation */
648
649 } dcm_state_t;
650
651
652 /* ---------------------------------------------------------------------
653 * DICOM Status Value Definitions
654 *
655 * Collected from PS 3.7 & 3.4
656 *
657 */
658
659 typedef struct dcm_status {
660 const guint16 value;
661 const gchar *description;
662 } dcm_status_t;
663
664 static dcm_status_t dcm_status_data[] = {
665
666 /* From PS 3.7 */
667
668 { 0x0000, "Success"},
669 { 0x0105, "No such attribute"},
670 { 0x0106, "Invalid attribute value"},
671 { 0x0107, "Attribute list error"},
672 { 0x0110, "Processing failure"},
673 { 0x0111, "Duplicate SOP instance"},
674 { 0x0112, "No Such object instance"},
675 { 0x0113, "No such event type"},
676 { 0x0114, "No such argument"},
677 { 0x0115, "Invalid argument value"},
678 { 0x0116, "Attribute Value Out of Range"},
679 { 0x0117, "Invalid object instance"},
680 { 0x0118, "No Such SOP class"},
681 { 0x0119, "Class-instance conflict"},
682 { 0x0120, "Missing attribute"},
683 { 0x0121, "Missing attribute value"},
684 { 0x0122, "Refused: SOP class not supported"},
685 { 0x0123, "No such action type"},
686 { 0x0210, "Duplicate invocation"},
687 { 0x0211, "Unrecognized operation"},
688 { 0x0212, "Mistyped argument"},
689 { 0x0213, "Resource limitation"},
690 { 0xFE00, "Cancel"},
691
692 /* from PS 3.4 */
693
694 { 0x0001, "Requested optional Attributes are not supported"},
695 { 0xA501, "Refused because General Purpose Scheduled Procedure Step Object may no longer be updated"},
696 { 0xA502, "Refused because the wrong Transaction UID is used"},
697 { 0xA503, "Refused because the General Purpose Scheduled Procedure Step SOP Instance is already in the 'IN PROGRESS' state"},
698 { 0xA504, "Refused because the related General Purpose Scheduled Procedure Step SOP Instance is not in the 'IN PROGRESS' state"},
699 { 0xA505, "Refused because Referenced General Purpose Scheduled Procedure Step Transaction UID does not match the Transaction UID of the N-ACTION request"},
700 { 0xA510, "Refused because an Initiate Media Creation action has already been received for this SOP Instance"},
701 { 0xA700, "Refused: Out of Resources"},
702 { 0xA701, "Refused: Out of Resources - Unable to calculate number of matches"},
703 { 0xA702, "Refused: Out of Resources - Unable to perform sub-operations"},
704 /*
705 { 0xA7xx, "Refused: Out of Resources"},
706 */
707 { 0xA801, "Refused: Move Destination unknown"},
708 /*
709 { 0xA9xx, "Error: Data Set does not match SOP Class"},
710 */
711 { 0xB000, "Sub-operations Complete - One or more Failures"},
712 { 0xB006, "Elements Discarded"},
713 { 0xB007, "Data Set does not match SOP Class"},
714 { 0xB101, "Specified Synchronization Frame of Reference UID does not match SCP Synchronization Frame of Reference"},
715 { 0xB102, "Study Instance UID coercion; Event logged under a different Study Instance UID"},
716 { 0xB104, "IDs inconsistent in matching a current study; Event logged"},
717 { 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"},
718 { 0xC000, "Error: Cannot understand/Unable to process"},
719 { 0xC100, "More than one match found"},
720 { 0xC101, "Procedural Logging not available for specified Study Instance UID"},
721 { 0xC102, "Event Information does not match Template"},
722 { 0xC103, "Cannot match event to a current study"},
723 { 0xC104, "IDs inconsistent in matching a current study; Event not logged"},
724 { 0xC200, "Unable to support requested template"},
725 { 0xC201, "Media creation request already completed"},
726 { 0xC202, "Media creation request already in progress and cannot be interrupted"},
727 { 0xC203, "Cancellation denied for unspecified reason"},
728 /*
729 { 0xCxxx, "Error: Cannot understand/Unable to Process"},
730 { 0xFE00, "Matching/Sub-operations terminated due to Cancel request"},
731 */
732 { 0xFF00, "Current Match is supplied. Sub-operations are continuing"},
733 { 0xFF01, "Matches are continuing - Warning that one or more Optional Keys were not supported for existence for this Identifier"}
734
735 };
736
737
738 /* following definitions are used to call dissect_dcm_assoc_item() */
739 #define DCM_ITEM_VALUE_TYPE_UID 1
740 #define DCM_ITEM_VALUE_TYPE_STRING 2
741 #define DCM_ITEM_VALUE_TYPE_UINT32 3
742
743 /* And from here on, only use unsigned 32 bit values. Offset is always positive number in respect to the tvb buffer start */
744 static guint32 dissect_dcm_pdu (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset);
745
746 static guint32 dissect_dcm_assoc_detail(tvbuff_t *tvb, packet_info *pinfo, proto_item *ti, dcm_state_assoc_t *assoc, guint32 offset, guint32 len);
747
748 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);
749
750 static void
dcm_init(void)751 dcm_init(void)
752 {
753 guint i;
754
755 /* Create three hash tables for quick lookups */
756 /* Add UID objects to hash table */
757 dcm_uid_table = wmem_map_new(wmem_file_scope(), wmem_str_hash, g_str_equal);
758 for (i = 0; i < array_length(dcm_uid_data); i++) {
759 wmem_map_insert(dcm_uid_table, (gpointer) dcm_uid_data[i].value,
760 (gpointer) &dcm_uid_data[i]);
761 }
762
763 /* Add Tag objects to hash table */
764 dcm_tag_table = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
765 for (i = 0; i < array_length(dcm_tag_data); i++) {
766 wmem_map_insert(dcm_tag_table, GUINT_TO_POINTER(dcm_tag_data[i].tag),
767 (gpointer) &dcm_tag_data[i]);
768 }
769
770 /* Add Status Values to hash table */
771 dcm_status_table = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
772 for (i = 0; i < array_length(dcm_status_data); i++) {
773 wmem_map_insert(dcm_status_table, GUINT_TO_POINTER((guint32)dcm_status_data[i].value),
774 (gpointer)&dcm_status_data[i]);
775 }
776 }
777
778 /*
779 Get or create conversation and DICOM data structure if desired.
780 Return new or existing DICOM structure, which is used to store context IDs and transfer syntax.
781 Return NULL in case of the structure couldn't be created.
782 */
783 static dcm_state_t *
dcm_state_get(packet_info * pinfo,gboolean create)784 dcm_state_get(packet_info *pinfo, gboolean create)
785 {
786
787 conversation_t *conv;
788 dcm_state_t *dcm_data;
789
790 conv = find_or_create_conversation(pinfo);
791 dcm_data = (dcm_state_t *)conversation_get_proto_data(conv, proto_dcm);
792
793 if (dcm_data == NULL && create) {
794
795 dcm_data = wmem_new0(wmem_file_scope(), dcm_state_t);
796 conversation_add_proto_data(conv, proto_dcm, dcm_data);
797
798 /* Mark it as DICOM conversation. Needed for the heuristic mode,
799 to prevent stealing subsequent packets by other dissectors
800 */
801 conversation_set_dissector(conv, dcm_handle);
802 }
803
804 return dcm_data;
805 }
806
807
808 static dcm_state_assoc_t *
dcm_state_assoc_new(dcm_state_t * dcm_data,guint32 packet_no)809 dcm_state_assoc_new(dcm_state_t *dcm_data, guint32 packet_no)
810 {
811 /* Create new association object and initialize the members */
812
813 dcm_state_assoc_t *assoc;
814
815 assoc = wmem_new0(wmem_file_scope(), dcm_state_assoc_t);
816 assoc->packet_no = packet_no; /* Identifier */
817
818 /* add to the end of the list */
819 if (dcm_data->last_assoc) {
820 dcm_data->last_assoc->next = assoc;
821 assoc->prev = dcm_data->last_assoc;
822 }
823 else {
824 dcm_data->first_assoc = assoc;
825 }
826 dcm_data->last_assoc = assoc;
827 return assoc;
828 }
829
830 /*
831 Find or create association object based on packet number. Return NULL, if association was not found.
832 */
833 static dcm_state_assoc_t *
dcm_state_assoc_get(dcm_state_t * dcm_data,guint32 packet_no,gboolean create)834 dcm_state_assoc_get(dcm_state_t *dcm_data, guint32 packet_no, gboolean create)
835 {
836
837 dcm_state_assoc_t *assoc = dcm_data->first_assoc;
838
839 while (assoc) {
840
841 if (assoc->next) {
842 /* we have more associations in the same stream */
843 if ((assoc->packet_no <= packet_no) && (packet_no < assoc->next->packet_no))
844 break;
845 }
846 else {
847 /* last or only associations in the same stream */
848 if (assoc->packet_no <= packet_no)
849 break;
850 }
851 assoc = assoc->next;
852 }
853
854 if (assoc == NULL && create) {
855 assoc = dcm_state_assoc_new(dcm_data, packet_no);
856 }
857 return assoc;
858 }
859
860 static dcm_state_pctx_t *
dcm_state_pctx_new(dcm_state_assoc_t * assoc,guint8 pctx_id)861 dcm_state_pctx_new(dcm_state_assoc_t *assoc, guint8 pctx_id)
862 {
863 /* Create new presentation context object and initialize the members */
864
865 dcm_state_pctx_t *pctx;
866
867 pctx = wmem_new0(wmem_file_scope(), dcm_state_pctx_t);
868 pctx->id = pctx_id;
869 pctx->syntax = DCM_UNK;
870
871 /* add to the end of the list list */
872 if (assoc->last_pctx) {
873 assoc->last_pctx->next = pctx;
874 pctx->prev = assoc->last_pctx;
875 }
876 else {
877 assoc->first_pctx = pctx;
878 }
879 assoc->last_pctx = pctx;
880
881 return pctx;
882 }
883
884 static dcm_state_pctx_t *
dcm_state_pctx_get(dcm_state_assoc_t * assoc,guint8 pctx_id,gboolean create)885 dcm_state_pctx_get(dcm_state_assoc_t *assoc, guint8 pctx_id, gboolean create)
886 {
887 /* Find or create presentation context object. Return NULL, if Context ID was not found */
888
889 dcm_state_pctx_t *pctx = assoc->first_pctx;
890 /*
891 static char notfound[] = "not found - click on ASSOC Request";
892 static dcm_state_pctx_t dunk = { NULL, NULL, FALSE, 0, notfound, notfound, notfound, notfound, DCM_UNK };
893 */
894 while (pctx) {
895 if (pctx->id == pctx_id)
896 break;
897 pctx = pctx->next;
898 }
899
900 if (pctx == NULL && create) {
901 pctx = dcm_state_pctx_new(assoc, pctx_id);
902 }
903
904 return pctx;
905 }
906
907
908 /*
909 Create new PDV object and initialize all members
910 */
911 static dcm_state_pdv_t*
dcm_state_pdv_new(dcm_state_pctx_t * pctx,guint32 packet_no,guint32 offset)912 dcm_state_pdv_new(dcm_state_pctx_t *pctx, guint32 packet_no, guint32 offset)
913 {
914 dcm_state_pdv_t *pdv;
915
916 pdv = wmem_new0(wmem_file_scope(), dcm_state_pdv_t);
917 pdv->syntax = DCM_UNK;
918 pdv->is_last_fragment = TRUE; /* Continuation PDVs are more tricky */
919 pdv->packet_no = packet_no;
920 pdv->offset = offset;
921
922 /* add to the end of the list */
923 if (pctx->last_pdv) {
924 pctx->last_pdv->next = pdv;
925 pdv->prev = pctx->last_pdv;
926 }
927 else {
928 pctx->first_pdv = pdv;
929 }
930 pctx->last_pdv = pdv;
931 return pdv;
932 }
933
934
935 static dcm_state_pdv_t*
dcm_state_pdv_get(dcm_state_pctx_t * pctx,guint32 packet_no,guint32 offset,gboolean create)936 dcm_state_pdv_get(dcm_state_pctx_t *pctx, guint32 packet_no, guint32 offset, gboolean create)
937 {
938 /* Find or create PDV object. Return NULL, if PDV was not found, based on packet number and offset */
939
940 dcm_state_pdv_t *pdv = pctx->first_pdv;
941
942 while (pdv) {
943 if ((pdv->packet_no == packet_no) && (pdv->offset == offset))
944 break;
945 pdv = pdv->next;
946 }
947
948 if (pdv == NULL && create) {
949 pdv = dcm_state_pdv_new(pctx, packet_no, offset);
950 }
951 return pdv;
952 }
953
954 static dcm_state_pdv_t*
dcm_state_pdv_get_obj_start(dcm_state_pdv_t * pdv_curr)955 dcm_state_pdv_get_obj_start(dcm_state_pdv_t *pdv_curr)
956 {
957
958 dcm_state_pdv_t *pdv_first=pdv_curr;
959
960 /* Get First PDV of the DICOM Object */
961 while (pdv_first->prev && !pdv_first->prev->is_last_fragment) {
962 pdv_first = pdv_first->prev;
963 }
964
965 return pdv_first;
966 }
967
968 static const value_string dcm_cmd_vals[] = {
969 { 0x0001, "C-STORE-RQ" },
970 { 0x0010, "C-GET-RQ" },
971 { 0x0020, "C-FIND-RQ" },
972 { 0x0021, "C-MOVE-RQ" },
973 { 0x0030, "C-ECHO-RQ" },
974 { 0x0100, "N-EVENT-REPORT-RQ" },
975 { 0x0110, "N-GET-RQ" },
976 { 0x0120, "N-SET-RQ" },
977 { 0x0130, "N-ACTION-RQ" },
978 { 0x0140, "N-CREATE-RQ" },
979 { 0x0150, "N-DELETE-RQ" },
980 { 0x8001, "C-STORE-RSP" },
981 { 0x8010, "C-GET-RSP" },
982 { 0x8020, "C-FIND-RSP" },
983 { 0x8021, "C-MOVE-RSP" },
984 { 0x8030, "C-ECHO-RSP" },
985 { 0x8100, "N-EVENT-REPORT-RSP" },
986 { 0x8110, "N-GET-RSP" },
987 { 0x8120, "N-SET-RSP" },
988 { 0x8130, "N-ACTION-RSP" },
989 { 0x8140, "N-CREATE-RSP" },
990 { 0x8150, "N-DELETE-RSP" },
991 { 0x0FFF, "C-CANCEL-RQ" },
992 { 0, NULL }
993 };
994
995
996 /*
997 Convert the two status bytes into a text based on lookup.
998
999 Classification
1000 0x0000 : SUCCESS
1001 0x0001 & Bxxx : WARNING
1002 0xFE00 : CANCEL
1003 0XFFxx : PENDING
1004 All other : FAILURE
1005 */
1006 static const gchar *
dcm_rsp2str(guint16 status_value)1007 dcm_rsp2str(guint16 status_value)
1008 {
1009
1010 dcm_status_t *status = NULL;
1011 const gchar *s = "";
1012
1013 /* Use specific text first */
1014 status = (dcm_status_t*) wmem_map_lookup(dcm_status_table, GUINT_TO_POINTER((guint32)status_value));
1015
1016 if (status) {
1017 s = status->description;
1018 }
1019 else {
1020
1021 if ((status_value & 0xFF00) == 0xA700) {
1022 /* 0xA7xx */
1023 s = "Refused: Out of Resources";
1024 }
1025 else if ((status_value & 0xFF00) == 0xA900) {
1026 /* 0xA9xx */
1027 s = "Error: Data Set does not match SOP Class";
1028 }
1029 else if ((status_value & 0xF000) == 0xC000) {
1030 /* 0xCxxx */
1031 s = "Error: Cannot understand/Unable to Process";
1032 }
1033 else {
1034 /* Encountered at least one case, with status_value == 0xD001 */
1035 s = "Unknown";
1036 }
1037 }
1038
1039 return s;
1040 }
1041
1042 static const gchar*
dcm_uid_or_desc(gchar * dcm_uid,gchar * dcm_desc)1043 dcm_uid_or_desc(gchar *dcm_uid, gchar *dcm_desc)
1044 {
1045 /* Return Description, UID or error */
1046
1047 return (dcm_desc == NULL ? (dcm_uid == NULL ? "Malformed Packet" : dcm_uid) : dcm_desc);
1048 }
1049
1050 static void
dcm_set_syntax(dcm_state_pctx_t * pctx,gchar * xfer_uid,const gchar * xfer_desc)1051 dcm_set_syntax(dcm_state_pctx_t *pctx, gchar *xfer_uid, const gchar *xfer_desc)
1052 {
1053 if ((pctx == NULL) || (xfer_uid == NULL) || (xfer_desc == NULL))
1054 return;
1055
1056 g_free(pctx->xfer_uid); /* free prev allocated xfer */
1057 g_free(pctx->xfer_desc); /* free prev allocated xfer */
1058
1059 pctx->syntax = 0;
1060 pctx->xfer_uid = g_strdup(xfer_uid);
1061 pctx->xfer_desc = g_strdup(xfer_desc);
1062
1063 /* this would be faster to skip the common parts, and have a FSA to
1064 * find the syntax.
1065 * Absent of coding that, this is in descending order of probability */
1066 if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2"))
1067 pctx->syntax = DCM_ILE; /* implicit little endian */
1068 else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.1"))
1069 pctx->syntax = DCM_ELE; /* explicit little endian */
1070 else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.2"))
1071 pctx->syntax = DCM_EBE; /* explicit big endian */
1072 else if (0 == strcmp(xfer_uid, "1.2.840.113619.5.2"))
1073 pctx->syntax = DCM_ILE; /* implicit little endian, big endian pixels, GE private */
1074 else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.4.70"))
1075 pctx->syntax = DCM_ELE; /* explicit little endian, jpeg */
1076 else if (0 == strncmp(xfer_uid, "1.2.840.10008.1.2.4", 18))
1077 pctx->syntax = DCM_ELE; /* explicit little endian, jpeg */
1078 else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.1.99"))
1079 pctx->syntax = DCM_ELE; /* explicit little endian, deflated */
1080 }
1081
1082 static void
dcm_guint16_to_le(guint8 * buffer,guint16 value)1083 dcm_guint16_to_le(guint8 *buffer, guint16 value)
1084 {
1085
1086 buffer[0]=(guint8) (value & 0x00FF);
1087 buffer[1]=(guint8)((value & 0xFF00) >> 8);
1088 }
1089
1090 static void
dcm_guint32_to_le(guint8 * buffer,guint32 value)1091 dcm_guint32_to_le(guint8 *buffer, guint32 value)
1092 {
1093
1094 buffer[0]=(guint8) (value & 0x000000FF);
1095 buffer[1]=(guint8)((value & 0x0000FF00) >> 8);
1096 buffer[2]=(guint8)((value & 0x00FF0000) >> 16);
1097 buffer[3]=(guint8)((value & 0xFF000000) >> 24);
1098
1099 }
1100
1101 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)1102 dcm_export_create_tag_base(guint8 *buffer, guint32 bufflen, guint32 offset,
1103 guint16 grp, guint16 elm, guint16 vr,
1104 const guint8 *value_buffer, guint32 value_len)
1105 {
1106 /* Only Explicit Little Endian is needed to create Metafile Header
1107 Generic function to write a TAG, VR, LEN & VALUE to a combined buffer
1108 The value (buffer, len) must be preprocessed by a VR specific function
1109 */
1110
1111 if (offset + 6 > bufflen) return bufflen;
1112
1113 dcm_guint16_to_le(buffer + offset, grp);
1114 offset += 2;
1115 dcm_guint16_to_le(buffer + offset, elm);
1116 offset += 2;
1117 memmove(buffer + offset, dcm_tag_vr_lookup[vr], 2);
1118 offset += 2;
1119
1120 switch (vr) {
1121 case DCM_VR_OB:
1122 case DCM_VR_OD:
1123 case DCM_VR_OF:
1124 case DCM_VR_OL:
1125 case DCM_VR_OW:
1126 case DCM_VR_SQ:
1127 case DCM_VR_UC:
1128 case DCM_VR_UR:
1129 case DCM_VR_UT:
1130 case DCM_VR_UN:
1131 /* DICOM likes it complicated. Special handling for these types */
1132
1133 if (offset + 6 > bufflen) return bufflen;
1134
1135 /* Add two reserved 0x00 bytes */
1136 dcm_guint16_to_le(buffer + offset, 0);
1137 offset += 2;
1138
1139 /* Length is a 4 byte field */
1140 dcm_guint32_to_le(buffer + offset, value_len);
1141 offset += 4;
1142
1143 break;
1144
1145 default:
1146 /* Length is a 2 byte field */
1147 if (offset + 2 > bufflen) return bufflen;
1148
1149 dcm_guint16_to_le(buffer + offset, (guint16)value_len);
1150 offset += 2;
1151 }
1152
1153 if (offset + value_len > bufflen) return bufflen;
1154
1155 memmove(buffer + offset, value_buffer, value_len);
1156 offset += value_len;
1157
1158 return offset;
1159 }
1160
1161 static guint32
dcm_export_create_tag_guint16(guint8 * buffer,guint32 bufflen,guint32 offset,guint16 grp,guint16 elm,guint16 vr,guint16 value)1162 dcm_export_create_tag_guint16(guint8 *buffer, guint32 bufflen, guint32 offset,
1163 guint16 grp, guint16 elm, guint16 vr, guint16 value)
1164 {
1165
1166 return dcm_export_create_tag_base(buffer, bufflen, offset, grp, elm, vr, (guint8*)&value, 2);
1167 }
1168
1169 static guint32
dcm_export_create_tag_guint32(guint8 * buffer,guint32 bufflen,guint32 offset,guint16 grp,guint16 elm,guint16 vr,guint32 value)1170 dcm_export_create_tag_guint32(guint8 *buffer, guint32 bufflen, guint32 offset,
1171 guint16 grp, guint16 elm, guint16 vr, guint32 value)
1172 {
1173
1174 return dcm_export_create_tag_base(buffer, bufflen, offset, grp, elm, vr, (guint8*)&value, 4);
1175 }
1176
1177 static guint32
dcm_export_create_tag_str(guint8 * buffer,guint32 bufflen,guint32 offset,guint16 grp,guint16 elm,guint16 vr,const gchar * value)1178 dcm_export_create_tag_str(guint8 *buffer, guint32 bufflen, guint32 offset,
1179 guint16 grp, guint16 elm, guint16 vr,
1180 const gchar *value)
1181 {
1182 guint32 len;
1183
1184 if (!value) {
1185 /* NULL object. E.g. happens if UID was not found/set. Don't create element*/
1186 return offset;
1187 }
1188
1189 len=(int)strlen(value);
1190
1191 if ((len & 0x01) == 1) {
1192 /* Odd length: since buffer is 0 initialized, pad with a 0x00 */
1193 len += 1;
1194 }
1195
1196 return dcm_export_create_tag_base(buffer, bufflen, offset, grp, elm, vr, (const guint8 *)value, len);
1197 }
1198
1199
1200 static guint8*
dcm_export_create_header(packet_info * pinfo,guint32 * dcm_header_len,const gchar * sop_class_uid,gchar * sop_instance_uid,gchar * xfer_uid)1201 dcm_export_create_header(packet_info *pinfo, guint32 *dcm_header_len, const gchar *sop_class_uid, gchar *sop_instance_uid, gchar *xfer_uid)
1202 {
1203 guint8 *dcm_header=NULL;
1204 guint32 offset=0;
1205 guint32 offset_header_len=0;
1206
1207 #define DCM_HEADER_MAX 512
1208
1209 dcm_header=(guint8 *)wmem_alloc0(pinfo->pool, DCM_HEADER_MAX); /* Slightly longer than needed */
1210 /* The subsequent functions rely on a 0 initialized buffer */
1211 offset=128;
1212
1213 memmove(dcm_header+offset, "DICM", 4);
1214 offset+=4;
1215
1216 offset_header_len=offset; /* remember for later */
1217
1218 offset+=12;
1219
1220 /*
1221 (0002,0000) File Meta Information Group Length UL
1222 (0002,0001) File Meta Information Version OB
1223 (0002,0002) Media Storage SOP Class UID UI
1224 (0002,0003) Media Storage SOP Instance UID UI
1225 (0002,0010) Transfer Syntax UID UI
1226 (0002,0012) Implementation Class UID UI
1227 (0002,0013) Implementation Version Name SH
1228 */
1229
1230 offset=dcm_export_create_tag_guint16(dcm_header, DCM_HEADER_MAX, offset,
1231 0x0002, 0x0001, DCM_VR_OB, 0x0100); /* will result on 00 01 since it is little endian */
1232
1233 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1234 0x0002, 0x0002, DCM_VR_UI, sop_class_uid);
1235
1236 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1237 0x0002, 0x0003, DCM_VR_UI, sop_instance_uid);
1238
1239 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1240 0x0002, 0x0010, DCM_VR_UI, xfer_uid);
1241
1242 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1243 0x0002, 0x0012, DCM_VR_UI, WIRESHARK_IMPLEMENTATION_UID);
1244
1245 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1246 0x0002, 0x0013, DCM_VR_SH, WIRESHARK_IMPLEMENTATION_VERSION);
1247
1248 /* Finally write the meta header length */
1249 dcm_export_create_tag_guint32(dcm_header, DCM_HEADER_MAX, offset_header_len,
1250 0x0002, 0x0000, DCM_VR_UL, offset-offset_header_len-12);
1251
1252 *dcm_header_len=offset;
1253
1254 return dcm_header;
1255
1256 }
1257
1258
1259 /*
1260 Concatenate related PDVs into one buffer and add it to the export object list.
1261
1262 Supports both modes:
1263
1264 - Multiple DICOM PDVs are reassembled with fragment_add_seq_next()
1265 and process_reassembled_data(). In this case all data will be in the last
1266 PDV, and all its predecessors will have zero data.
1267
1268 - DICOM PDVs are keep separate. Every PDV contains data.
1269 */
1270 static void
dcm_export_create_object(packet_info * pinfo,dcm_state_assoc_t * assoc,dcm_state_pdv_t * pdv)1271 dcm_export_create_object(packet_info *pinfo, dcm_state_assoc_t *assoc, dcm_state_pdv_t *pdv)
1272 {
1273
1274 dicom_eo_t *eo_info = NULL;
1275
1276 dcm_state_pdv_t *pdv_curr = NULL;
1277 dcm_state_pdv_t *pdv_same_pkt = NULL;
1278 dcm_state_pctx_t *pctx = NULL;
1279
1280 guint8 *pdv_combined = NULL;
1281 guint8 *pdv_combined_curr = NULL;
1282 guint8 *dcm_header = NULL;
1283 guint32 pdv_combined_len = 0;
1284 guint32 dcm_header_len = 0;
1285 guint16 cnt_same_pkt = 1;
1286 gchar *filename;
1287 const gchar *hostname;
1288
1289 const gchar *sop_class_uid;
1290 gchar *sop_instance_uid;
1291
1292 /* Calculate total PDV length, i.e. all packets until last PDV without continuation */
1293 pdv_curr = pdv;
1294 pdv_same_pkt = pdv;
1295 pdv_combined_len=pdv_curr->data_len;
1296
1297 while (pdv_curr->prev && !pdv_curr->prev->is_last_fragment) {
1298 pdv_curr = pdv_curr->prev;
1299 pdv_combined_len += pdv_curr->data_len;
1300 }
1301
1302 /* Count number of PDVs with the same Packet Number */
1303 while (pdv_same_pkt->prev && (pdv_same_pkt->prev->packet_no == pdv_same_pkt->packet_no)) {
1304 pdv_same_pkt = pdv_same_pkt->prev;
1305 cnt_same_pkt += 1;
1306 }
1307
1308 pctx=dcm_state_pctx_get(assoc, pdv_curr->pctx_id, FALSE);
1309
1310 if (assoc->ae_calling != NULL && strlen(assoc->ae_calling)>0 &&
1311 assoc->ae_called != NULL && strlen(assoc->ae_called)>0) {
1312 hostname = wmem_strdup_printf(pinfo->pool, "%s <-> %s", assoc->ae_calling, assoc->ae_called);
1313 }
1314 else {
1315 hostname = "AE title(s) unknown";
1316 }
1317
1318 if (pdv->is_storage &&
1319 pdv_curr->sop_class_uid && strlen(pdv_curr->sop_class_uid)>0 &&
1320 pdv_curr->sop_instance_uid && strlen(pdv_curr->sop_instance_uid)>0) {
1321
1322 sop_class_uid = wmem_strdup(pinfo->pool, pdv_curr->sop_class_uid);
1323 sop_instance_uid = wmem_strdup(pinfo->pool, pdv_curr->sop_instance_uid);
1324
1325 /* Make sure filename does not contain invalid character. Rather conservative.
1326 Even though this should be a valid DICOM UID, apply the same filter rules
1327 in case of bogus data.
1328 */
1329 filename = wmem_strdup_printf(pinfo->pool, "%06d-%d-%s.dcm", pinfo->num, cnt_same_pkt,
1330 g_strcanon(pdv_curr->sop_instance_uid, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-.", '-'));
1331 }
1332 else {
1333 /* No SOP Instance or SOP Class UID found in PDV. Use wireshark ones */
1334
1335 sop_class_uid = wmem_strdup(pinfo->pool, WIRESHARK_MEDIA_STORAGE_SOP_CLASS_UID);
1336 sop_instance_uid = wmem_strdup_printf(pinfo->pool, "%s.%d.%d",
1337 WIRESHARK_MEDIA_STORAGE_SOP_INSTANCE_UID_PREFIX, pinfo->num, cnt_same_pkt);
1338
1339 /* Make sure filename does not contain invalid character. Rather conservative.*/
1340 filename = wmem_strdup_printf(pinfo->pool, "%06d-%d-%s.dcm", pinfo->num, cnt_same_pkt,
1341 g_strcanon(pdv->desc, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-.", '-'));
1342
1343 }
1344
1345 if (global_dcm_export_header) {
1346 if (pctx && pctx->xfer_uid && strlen(pctx->xfer_uid)>0) {
1347 dcm_header=dcm_export_create_header(pinfo, &dcm_header_len, sop_class_uid, sop_instance_uid, pctx->xfer_uid);
1348 }
1349 else {
1350 /* We are running blind, i.e. no presentation context/syntax found.
1351 Don't invent one, so the meta header will miss
1352 the transfer syntax UID tag (even though it is mandatory)
1353 */
1354 dcm_header=dcm_export_create_header(pinfo, &dcm_header_len, sop_class_uid, sop_instance_uid, NULL);
1355 }
1356 }
1357
1358
1359 if (dcm_header_len + pdv_combined_len >= global_dcm_export_minsize) {
1360 /* Allocate the final size */
1361
1362 pdv_combined = (guint8 *)wmem_alloc0(wmem_file_scope(), dcm_header_len + pdv_combined_len);
1363
1364 pdv_combined_curr = pdv_combined;
1365
1366 if (dcm_header_len != 0) { /* Will be 0 when global_dcm_export_header is FALSE */
1367 memmove(pdv_combined, dcm_header, dcm_header_len);
1368 pdv_combined_curr += dcm_header_len;
1369 }
1370
1371 /* Copy PDV per PDV to target buffer */
1372 while (!pdv_curr->is_last_fragment) {
1373 memmove(pdv_combined_curr, pdv_curr->data, pdv_curr->data_len); /* this is a copy not move */
1374 pdv_combined_curr += pdv_curr->data_len;
1375 pdv_curr = pdv_curr->next;
1376 }
1377
1378 /* Last packet */
1379 memmove(pdv_combined_curr, pdv->data, pdv->data_len); /* this is a copy not a move */
1380
1381 /* Add to list */
1382 eo_info = wmem_new0(wmem_file_scope(), dicom_eo_t);
1383 eo_info->hostname = g_strdup(hostname);
1384 eo_info->filename = g_strdup(filename);
1385 eo_info->content_type = g_strdup(pdv->desc);
1386
1387 eo_info->payload_data = pdv_combined;
1388 eo_info->payload_len = dcm_header_len + pdv_combined_len;
1389
1390 tap_queue_packet(dicom_eo_tap, pinfo, eo_info);
1391 }
1392 }
1393
1394 /*
1395 For tags with fixed length items, calculate the value multiplicity (VM). String tags use a separator, which is not supported by this function.
1396 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)
1397 */
1398 static guint32
dcm_vm_item_count(guint32 value_length,guint32 item_length)1399 dcm_vm_item_count(guint32 value_length, guint32 item_length)
1400 {
1401
1402 /* This could all be formulated in a single line but it does not make it easier to read */
1403
1404 if (value_length == 0) {
1405 return 0;
1406 }
1407 else if (value_length <= item_length) {
1408 return 1; /* This is the special case of bad encoding */
1409 }
1410 else {
1411 return (value_length / item_length);
1412 }
1413
1414 }
1415
1416 /*
1417 Decode the association header
1418 */
1419 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)1420 dissect_dcm_assoc_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, dcm_state_assoc_t *assoc,
1421 guint8 pdu_type, guint32 pdu_len)
1422 {
1423
1424 proto_item *assoc_header_pitem;
1425 proto_tree *assoc_header_ptree; /* Tree for item details */
1426
1427 const gchar *buf_desc = NULL;
1428 const char *reject_result_desc = "";
1429 const char *reject_source_desc = "";
1430 const char *reject_reason_desc = "";
1431 const char *abort_source_desc = "";
1432 const char *abort_reason_desc = "";
1433
1434 char *ae_called;
1435 char *ae_calling;
1436 char *ae_called_resp;
1437 char *ae_calling_resp;
1438
1439 guint8 reject_result;
1440 guint8 reject_source;
1441 guint8 reject_reason;
1442 guint8 abort_source;
1443 guint8 abort_reason;
1444
1445 assoc_header_ptree = proto_tree_add_subtree(tree, tvb, offset, pdu_len, ett_assoc_header, &assoc_header_pitem, "Association Header");
1446
1447 switch (pdu_type) {
1448 case 1: /* Association Request */
1449
1450 proto_tree_add_item(assoc_header_ptree, hf_dcm_assoc_version, tvb, offset, 2, ENC_BIG_ENDIAN);
1451 offset += 2;
1452
1453 offset += 2; /* Two reserved bytes*/
1454
1455 /*
1456 * XXX - this is in "the ISO 646:1990-Basic G0 Set"; ISO/IEC 646:1991
1457 * claims to be the third edition of the standard, with the second
1458 * version being ISO 646:1983, so I'm not sure what happened to
1459 * ISO 646:1990. ISO/IEC 646:1991 speaks of "the basic 7-bit code
1460 * table", which leaves positions 2/3 (0x23) and 2/4 (0x24) as
1461 * being either NUMBER SIGN or POUND SIGN and either DOLLAR SIGN or
1462 * CURRENCY SIGN, respectively, and positions 4/0 (0x40), 5/11 (0x5b),
1463 * 5/12 (0x5c), 5/13 (0x5d), 5/14 (0x5e), 6/0 (0x60), 7/11 (0x7b),
1464 * 7/12 (0x7c), 7/13 (0x7d), and 7/14 (0x7e) as being "available for
1465 * national or application-oriented use", so I'm *guessing* that
1466 * "the ISO 646:1990-Basic G0 Set" means "those positions aren't
1467 * specified" and thus should probably be treated as not valid
1468 * in that "Basic" set.
1469 */
1470 proto_tree_add_item_ret_display_string(assoc_header_ptree, hf_dcm_assoc_called, tvb, offset, 16, ENC_ISO_646_BASIC|ENC_NA, pinfo->pool, &ae_called);
1471 assoc->ae_called = wmem_strdup(wmem_file_scope(), g_strstrip(ae_called));
1472 offset += 16;
1473
1474 proto_tree_add_item_ret_display_string(assoc_header_ptree, hf_dcm_assoc_calling, tvb, offset, 16, ENC_ISO_646_BASIC|ENC_NA, pinfo->pool, &ae_calling);
1475 assoc->ae_calling = wmem_strdup(wmem_file_scope(), g_strstrip(ae_calling));
1476 offset += 16;
1477
1478 offset += 32; /* 32 reserved bytes */
1479
1480 buf_desc = wmem_strdup_printf(pinfo->pool, "A-ASSOCIATE request %s --> %s",
1481 assoc->ae_calling, assoc->ae_called);
1482
1483 offset = dissect_dcm_assoc_detail(tvb, pinfo, assoc_header_ptree, assoc, offset, pdu_len-offset);
1484
1485 break;
1486 case 2: /* Association Accept */
1487
1488 proto_tree_add_item(assoc_header_ptree, hf_dcm_assoc_version, tvb, offset, 2, ENC_BIG_ENDIAN);
1489 offset += 2;
1490
1491 offset += 2; /* Two reserved bytes*/
1492
1493 proto_tree_add_item_ret_display_string(assoc_header_ptree, hf_dcm_assoc_called, tvb, offset, 16, ENC_ISO_646_BASIC|ENC_NA, pinfo->pool, &ae_called_resp);
1494 assoc->ae_called_resp = wmem_strdup(wmem_file_scope(), g_strstrip(ae_called_resp));
1495 offset += 16;
1496
1497 proto_tree_add_item_ret_display_string(assoc_header_ptree, hf_dcm_assoc_calling, tvb, offset, 16, ENC_ISO_646_BASIC|ENC_NA, pinfo->pool, &ae_calling_resp);
1498 assoc->ae_calling_resp = wmem_strdup(wmem_file_scope(), g_strstrip(ae_calling_resp));
1499 offset += 16;
1500
1501 offset += 32; /* 32 reserved bytes */
1502
1503 buf_desc = wmem_strdup_printf(pinfo->pool, "A-ASSOCIATE accept %s <-- %s",
1504 assoc->ae_calling_resp, assoc->ae_called_resp);
1505
1506 offset = dissect_dcm_assoc_detail(tvb, pinfo, assoc_header_ptree, assoc, offset, pdu_len-offset);
1507
1508 break;
1509 case 3: /* Association Reject */
1510
1511 offset += 1; /* One reserved byte */
1512
1513 reject_result = tvb_get_guint8(tvb, offset);
1514 reject_source = tvb_get_guint8(tvb, offset+1);
1515 reject_reason = tvb_get_guint8(tvb, offset+2);
1516
1517 switch (reject_result) {
1518 case 1: reject_result_desc = "Reject Permanent"; break;
1519 case 2: reject_result_desc = "Reject Transient"; break;
1520 default: break;
1521 }
1522
1523 switch (reject_source) {
1524 case 1:
1525 reject_source_desc = "User";
1526 switch (reject_reason) {
1527 case 1: reject_reason_desc = "No reason given"; break;
1528 case 2: reject_reason_desc = "Application context name not supported"; break;
1529 case 3: reject_reason_desc = "Calling AE title not recognized"; break;
1530 case 7: reject_reason_desc = "Called AE title not recognized"; break;
1531 }
1532 break;
1533 case 2:
1534 reject_source_desc = "Provider (ACSE)";
1535 switch (reject_reason) {
1536 case 1: reject_reason_desc = "No reason given"; break;
1537 case 2: reject_reason_desc = "Protocol version not supported"; break;
1538 }
1539 break;
1540 case 3:
1541 reject_source_desc = "Provider (Presentation)";
1542 switch (reject_reason) {
1543 case 1: reject_reason_desc = "Temporary congestion"; break;
1544 case 2: reject_reason_desc = "Local limit exceeded"; break;
1545 }
1546 break;
1547 }
1548
1549 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_reject_result, tvb,
1550 offset , 1, reject_result, "%s", reject_result_desc);
1551
1552 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_reject_source, tvb,
1553 offset+1, 1, reject_source, "%s", reject_source_desc);
1554
1555 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_reject_reason, tvb,
1556 offset+2, 1, reject_reason, "%s", reject_reason_desc);
1557
1558 offset += 3;
1559
1560 /* Provider aborted */
1561 buf_desc = wmem_strdup_printf(pinfo->pool, "A-ASSOCIATE reject %s <-- %s (%s)",
1562 assoc->ae_calling, assoc->ae_called, reject_reason_desc);
1563
1564 expert_add_info(pinfo, assoc_header_pitem, &ei_dcm_assoc_rejected);
1565
1566 break;
1567 case 5: /* RELEASE Request */
1568
1569 offset += 2; /* Two reserved bytes */
1570 buf_desc="A-RELEASE request";
1571
1572 break;
1573 case 6: /* RELEASE Response */
1574
1575 offset += 2; /* Two reserved bytes */
1576 buf_desc="A-RELEASE response";
1577
1578 break;
1579 case 7: /* ABORT */
1580
1581 offset += 2; /* Two reserved bytes */
1582
1583 abort_source = tvb_get_guint8(tvb, offset);
1584 abort_reason = tvb_get_guint8(tvb, offset+1);
1585
1586 switch (abort_source) {
1587 case 0:
1588 abort_source_desc = "User";
1589 abort_reason_desc = "N/A"; /* No details can be provided*/
1590 break;
1591 case 1:
1592 /* reserved */
1593 break;
1594 case 2:
1595 abort_source_desc = "Provider";
1596
1597 switch (abort_reason) {
1598 case 0: abort_reason_desc = "Not specified"; break;
1599 case 1: abort_reason_desc = "Unrecognized PDU"; break;
1600 case 2: abort_reason_desc = "Unexpected PDU"; break;
1601 case 4: abort_reason_desc = "Unrecognized PDU parameter"; break;
1602 case 5: abort_reason_desc = "Unexpected PDU parameter"; break;
1603 case 6: abort_reason_desc = "Invalid PDU parameter value"; break;
1604 }
1605
1606 break;
1607 }
1608
1609 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_abort_source,
1610 tvb, offset , 1, abort_source, "%s", abort_source_desc);
1611
1612 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_abort_reason,
1613 tvb, offset+1, 1, abort_reason, "%s", abort_reason_desc);
1614 offset += 2;
1615
1616 if (abort_source == 0) {
1617 /* User aborted */
1618 buf_desc = wmem_strdup_printf(pinfo->pool, "ABORT %s --> %s",
1619 assoc->ae_calling, assoc->ae_called);
1620 }
1621 else {
1622 /* Provider aborted, slightly more information */
1623 buf_desc = wmem_strdup_printf(pinfo->pool, "ABORT %s <-- %s (%s)",
1624 assoc->ae_calling, assoc->ae_called, abort_reason_desc);
1625 }
1626
1627 expert_add_info(pinfo, assoc_header_pitem, &ei_dcm_assoc_aborted);
1628
1629 break;
1630 }
1631
1632 proto_item_set_text(assoc_header_pitem, "%s", buf_desc);
1633 col_set_str(pinfo->cinfo, COL_INFO, buf_desc);
1634
1635 /* proto_item and proto_tree are one and the same */
1636 proto_item_append_text(tree, ", %s", buf_desc);
1637
1638 return offset;
1639 }
1640
1641 /*
1642 Decode one item in a association request or response. Lookup UIDs if requested.
1643 Create a subtree node with summary and three elements (item_type, item_len, value)
1644 */
1645 static void
dissect_dcm_assoc_item(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,guint32 offset,const gchar * pitem_prefix,int item_value_type,gchar ** item_value,const gchar ** item_description,int * hf_type,int * hf_len,int * hf_value,int ett_subtree)1646 dissect_dcm_assoc_item(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset,
1647 const gchar *pitem_prefix, int item_value_type,
1648 gchar **item_value, const gchar **item_description,
1649 int *hf_type, int *hf_len, int *hf_value, int ett_subtree)
1650 {
1651
1652 proto_tree *assoc_item_ptree; /* Tree for item details */
1653 proto_item *assoc_item_pitem;
1654 dcm_uid_t *uid = NULL;
1655
1656 guint32 item_number = 0;
1657
1658 guint8 item_type;
1659 guint16 item_len;
1660
1661 gchar *buf_desc = ""; /* Used for item text */
1662
1663 *item_value = NULL;
1664 *item_description = NULL;
1665
1666 item_type = tvb_get_guint8(tvb, offset);
1667 item_len = tvb_get_ntohs(tvb, offset+2);
1668
1669 assoc_item_ptree = proto_tree_add_subtree(tree, tvb, offset, item_len+4, ett_subtree, &assoc_item_pitem, pitem_prefix);
1670
1671 proto_tree_add_uint(assoc_item_ptree, *hf_type, tvb, offset, 1, item_type);
1672 proto_tree_add_uint(assoc_item_ptree, *hf_len, tvb, offset+2, 2, item_len);
1673
1674 switch (item_value_type) {
1675 case DCM_ITEM_VALUE_TYPE_UID:
1676 *item_value = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset+4, item_len, ENC_ASCII);
1677
1678 uid = (dcm_uid_t *)wmem_map_lookup(dcm_uid_table, (gpointer) *item_value);
1679 if (uid) {
1680 *item_description = uid->name;
1681 buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", *item_description, *item_value);
1682 }
1683 else {
1684 /* Unknown UID, or no UID at all */
1685 buf_desc = *item_value;
1686 }
1687
1688 proto_item_append_text(assoc_item_pitem, "%s", buf_desc);
1689 proto_tree_add_string(assoc_item_ptree, *hf_value, tvb, offset+4, item_len, buf_desc);
1690
1691 break;
1692
1693 case DCM_ITEM_VALUE_TYPE_STRING:
1694 *item_value = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset+4, item_len, ENC_ASCII);
1695 proto_item_append_text(assoc_item_pitem, "%s", *item_value);
1696 proto_tree_add_string(assoc_item_ptree, *hf_value, tvb, offset+4, item_len, *item_value);
1697
1698 break;
1699
1700 case DCM_ITEM_VALUE_TYPE_UINT32:
1701 item_number = tvb_get_ntohl(tvb, offset+4);
1702 *item_value = (gchar *)wmem_strdup_printf(wmem_file_scope(), "%d", item_number);
1703
1704 proto_item_append_text(assoc_item_pitem, "%s", *item_value);
1705 proto_tree_add_item(assoc_item_ptree, *hf_value, tvb, offset+4, 4, ENC_BIG_ENDIAN);
1706
1707 break;
1708
1709 default:
1710 break;
1711 }
1712 }
1713
1714 /*
1715 Decode the SOP Class Extended Negotiation Sub-Item Fields in a association request or response.
1716 Lookup UIDs if requested
1717 */
1718 static void
dissect_dcm_assoc_sopclass_extneg(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,guint32 offset)1719 dissect_dcm_assoc_sopclass_extneg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset)
1720 {
1721
1722 proto_tree *assoc_item_extneg_tree = NULL; /* Tree for item details */
1723 proto_item *assoc_item_extneg_item = NULL;
1724
1725 guint16 item_len = 0;
1726 guint16 sop_class_uid_len = 0;
1727 gint32 cnt = 0;
1728
1729 gchar *buf_desc = NULL; /* Used for item text */
1730 dcm_uid_t *sopclassuid=NULL;
1731 gchar *sopclassuid_str = NULL;
1732
1733 item_len = tvb_get_ntohs(tvb, offset+2);
1734 sop_class_uid_len = tvb_get_ntohs(tvb, offset+4);
1735
1736 assoc_item_extneg_item = proto_tree_add_item(tree, hf_dcm_info_extneg, tvb, offset, item_len+4, ENC_NA);
1737 proto_item_set_text(assoc_item_extneg_item, "Ext. Neg.: ");
1738 assoc_item_extneg_tree = proto_item_add_subtree(assoc_item_extneg_item, ett_assoc_info_extneg);
1739
1740 proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1741 proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN);
1742 proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_info_extneg_sopclassuid_len, tvb, offset+4, 2, ENC_BIG_ENDIAN);
1743
1744 sopclassuid_str = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset+6, sop_class_uid_len, ENC_ASCII);
1745 sopclassuid = (dcm_uid_t *)wmem_map_lookup(dcm_uid_table, (gpointer) sopclassuid_str);
1746
1747 if (sopclassuid) {
1748 buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", sopclassuid->name, sopclassuid->value);
1749 }
1750 else {
1751 buf_desc = sopclassuid_str;
1752 }
1753
1754 proto_item_append_text(assoc_item_extneg_item, "%s", buf_desc);
1755 proto_tree_add_string(assoc_item_extneg_tree, hf_dcm_info_extneg_sopclassuid, tvb, offset+6, sop_class_uid_len, buf_desc);
1756
1757 /* Count how many fields are following. */
1758 cnt = item_len - 2 - sop_class_uid_len;
1759
1760 /*
1761 * The next field contains Service Class specific information identified by the SOP Class UID.
1762 */
1763 if (0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) ||
1764 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) ||
1765 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_FIND_RETIRED) ||
1766 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_MOVE) ||
1767 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_MOVE) ||
1768 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_MOVE_RETIRED) ||
1769 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_GET) ||
1770 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_GET) ||
1771 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_GET_RETIRED))
1772 {
1773 if (cnt<=0)
1774 {
1775 return;
1776 }
1777
1778 /* Support for Relational queries. */
1779 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);
1780 --cnt;
1781 }
1782
1783 /* More sub-items are only allowed for the C-FIND SOP Classes. */
1784 if (0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) ||
1785 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) ||
1786 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_FIND_RETIRED))
1787 {
1788 if (cnt<=0)
1789 {
1790 return;
1791 }
1792
1793 /* Combined Date-Time matching. */
1794 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);
1795 --cnt;
1796
1797 if (cnt<=0)
1798 {
1799 return;
1800 }
1801
1802 /* Fuzzy semantic matching of person names. */
1803 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);
1804 --cnt;
1805
1806 if (cnt<=0)
1807 {
1808 return;
1809 }
1810
1811 /* Timezone query adjustment. */
1812 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);
1813 --cnt;
1814 }
1815 }
1816
1817 /*
1818 Decode user identities in the association
1819 */
1820 static void
dissect_dcm_assoc_user_identify(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,guint32 offset)1821 dissect_dcm_assoc_user_identify(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset)
1822 {
1823
1824 proto_tree *assoc_item_user_identify_tree = NULL; /* Tree for item details */
1825 proto_item *assoc_item_user_identify_item = NULL;
1826
1827 guint16 primary_field_length, secondary_field_length, item_len = 0;
1828 guint8 type;
1829
1830 item_len = tvb_get_ntohs(tvb, offset+2);
1831
1832 assoc_item_user_identify_item = proto_tree_add_item(tree, hf_dcm_info_user_identify, tvb, offset, item_len+4, ENC_NA);
1833 assoc_item_user_identify_tree = proto_item_add_subtree(assoc_item_user_identify_item, ett_assoc_info_user_identify);
1834
1835 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1836 offset += 2;
1837 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_assoc_item_len, tvb, offset, 2, ENC_BIG_ENDIAN);
1838 offset += 2;
1839
1840 type = tvb_get_guint8(tvb, offset);
1841 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1842 offset += 1;
1843
1844 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_response_requested, tvb, offset, 1, ENC_BIG_ENDIAN);
1845 offset += 1;
1846
1847 primary_field_length = tvb_get_ntohs(tvb, offset);
1848 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_primary_field_length, tvb, offset, 2, ENC_BIG_ENDIAN);
1849 offset += 2;
1850
1851 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);
1852 proto_item_append_text(assoc_item_user_identify_item, ": %s", tvb_get_string_enc(pinfo->pool, tvb, offset, primary_field_length, ENC_UTF_8|ENC_NA));
1853 offset += primary_field_length;
1854
1855 if (type == 2) {
1856 secondary_field_length = tvb_get_ntohs(tvb, offset);
1857 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_secondary_field_length, tvb, offset, 2, ENC_BIG_ENDIAN);
1858 offset += 2;
1859
1860 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);
1861 proto_item_append_text(assoc_item_user_identify_item, ", %s", tvb_get_string_enc(pinfo->pool, tvb, offset, secondary_field_length, ENC_UTF_8|ENC_NA));
1862 }
1863 }
1864
1865 /*
1866 Decode unknown item types in the association
1867 */
1868 static void
dissect_dcm_assoc_unknown(tvbuff_t * tvb,proto_tree * tree,guint32 offset)1869 dissect_dcm_assoc_unknown(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
1870 {
1871
1872 proto_tree *assoc_item_unknown_tree = NULL; /* Tree for item details */
1873 proto_item *assoc_item_unknown_item = NULL;
1874
1875 guint16 item_len = 0;
1876
1877 item_len = tvb_get_ntohs(tvb, offset+2);
1878
1879 assoc_item_unknown_item = proto_tree_add_item(tree, hf_dcm_info_unknown, tvb, offset, item_len+4, ENC_NA);
1880 assoc_item_unknown_tree = proto_item_add_subtree(assoc_item_unknown_item, ett_assoc_info_unknown);
1881
1882 proto_tree_add_item(assoc_item_unknown_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1883 proto_tree_add_item(assoc_item_unknown_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN);
1884 offset += 4;
1885
1886 proto_tree_add_item(assoc_item_unknown_tree, hf_dcm_assoc_item_data, tvb, offset, item_len, ENC_NA);
1887 }
1888
1889 /*
1890 Decode the SCP/SCU Role Selection Sub-Item Fields in a association request or response.
1891 Lookup UIDs if requested
1892 */
1893 static void
dissect_dcm_assoc_role_selection(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,guint32 offset)1894 dissect_dcm_assoc_role_selection(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset)
1895 {
1896
1897 proto_tree *assoc_item_rolesel_tree; /* Tree for item details */
1898 proto_item *assoc_item_rolesel_item;
1899
1900 guint16 item_len, sop_class_uid_len;
1901 guint8 scp_role, scu_role;
1902
1903 gchar *buf_desc; /* Used for item text */
1904 dcm_uid_t *sopclassuid;
1905 gchar *sopclassuid_str;
1906
1907 item_len = tvb_get_ntohs(tvb, offset+2);
1908 sop_class_uid_len = tvb_get_ntohs(tvb, offset+4);
1909
1910 assoc_item_rolesel_item = proto_tree_add_item(tree, hf_dcm_info_rolesel, tvb, offset, item_len+4, ENC_NA);
1911 proto_item_set_text(assoc_item_rolesel_item, "Role Selection: ");
1912 assoc_item_rolesel_tree = proto_item_add_subtree(assoc_item_rolesel_item, ett_assoc_info_rolesel);
1913
1914 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1915 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN);
1916 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_info_rolesel_sopclassuid_len, tvb, offset+4, 2, ENC_BIG_ENDIAN);
1917
1918 sopclassuid_str = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset+6, sop_class_uid_len, ENC_ASCII);
1919 sopclassuid = (dcm_uid_t *)wmem_map_lookup(dcm_uid_table, (gpointer) sopclassuid_str);
1920
1921 scu_role = tvb_get_guint8(tvb, offset+6+sop_class_uid_len);
1922 scp_role = tvb_get_guint8(tvb, offset+7+sop_class_uid_len);
1923
1924 if (scu_role) {
1925 proto_item_append_text(assoc_item_rolesel_item, "%s", "SCU-role: yes");
1926 }
1927 else {
1928 proto_item_append_text(assoc_item_rolesel_item, "%s", "SCU-role: no");
1929 }
1930
1931 if (scp_role) {
1932 proto_item_append_text(assoc_item_rolesel_item, ", %s", "SCP-role: yes");
1933 }
1934 else {
1935 proto_item_append_text(assoc_item_rolesel_item, ", %s", "SCP-role: no");
1936 }
1937
1938 if (sopclassuid) {
1939 buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", sopclassuid->name, sopclassuid->value);
1940 }
1941 else {
1942 buf_desc = sopclassuid_str;
1943 }
1944
1945 proto_tree_add_string(assoc_item_rolesel_tree, hf_dcm_info_rolesel_sopclassuid, tvb, offset+6, sop_class_uid_len, buf_desc);
1946
1947 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_info_rolesel_scurole, tvb, offset+6+sop_class_uid_len, 1, ENC_BIG_ENDIAN);
1948 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_info_rolesel_scprole, tvb, offset+7+sop_class_uid_len, 1, ENC_BIG_ENDIAN);
1949 }
1950
1951 /*
1952 Decode the Asynchronous operations (and sub-operations) Window Negotiation Sub-Item Fields in a association request or response.
1953 */
1954 static void
dissect_dcm_assoc_async_negotiation(tvbuff_t * tvb,proto_tree * tree,guint32 offset)1955 dissect_dcm_assoc_async_negotiation(tvbuff_t *tvb, proto_tree *tree, guint32 offset)
1956 {
1957
1958 proto_tree *assoc_item_asyncneg_tree; /* Tree for item details */
1959 proto_item *assoc_item_asyncneg_item;
1960
1961 guint16 item_len, max_num_ops_inv, max_num_ops_per = 0;
1962
1963 item_len = tvb_get_ntohs(tvb, offset+2);
1964
1965 assoc_item_asyncneg_item = proto_tree_add_item(tree, hf_dcm_info_async_neg, tvb, offset, item_len+4, ENC_NA);
1966 proto_item_set_text(assoc_item_asyncneg_item, "Async Negotiation: ");
1967 assoc_item_asyncneg_tree = proto_item_add_subtree(assoc_item_asyncneg_item, ett_assoc_info_async_neg);
1968
1969 proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1970 proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN);
1971 proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_info_async_neg_max_num_ops_inv, tvb, offset+4, 2, ENC_BIG_ENDIAN);
1972 proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_info_async_neg_max_num_ops_per, tvb, offset+6, 2, ENC_BIG_ENDIAN);
1973
1974 max_num_ops_inv = tvb_get_ntohs(tvb, offset+4);
1975 max_num_ops_per = tvb_get_ntohs(tvb, offset+6);
1976
1977 proto_item_append_text(assoc_item_asyncneg_item, "%s%d", "Maximum Number Operations Invoked: ", max_num_ops_inv);
1978 if (max_num_ops_inv==0) proto_item_append_text(assoc_item_asyncneg_item, "%s", " (unlimited)");
1979 proto_item_append_text(assoc_item_asyncneg_item, ", %s%d", "Maximum Number Operations Performed: ", max_num_ops_per);
1980 if (max_num_ops_per==0) proto_item_append_text(assoc_item_asyncneg_item, "%s", " (unlimited)");
1981 }
1982
1983 /*
1984 Decode a presentation context item in a Association Request or Response. In the response, set the accepted transfer syntax, if any.
1985 */
1986 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)1987 dissect_dcm_pctx(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
1988 dcm_state_assoc_t *assoc, guint32 offset, guint32 len,
1989 const gchar *pitem_prefix, gboolean is_assoc_request)
1990 {
1991
1992 proto_tree *pctx_ptree; /* Tree for presentation context details */
1993 proto_item *pctx_pitem;
1994
1995 dcm_state_pctx_t *pctx = NULL;
1996
1997 guint8 item_type = 0;
1998 guint16 item_len = 0;
1999
2000 guint8 pctx_id = 0; /* Presentation Context ID */
2001 guint8 pctx_result = 0;
2002
2003 const char *pctx_result_desc = "";
2004
2005 gchar *pctx_abss_uid = NULL; /* Abstract Syntax UID alias SOP Class UID */
2006 const gchar *pctx_abss_desc = NULL; /* Description of UID */
2007
2008 gchar *pctx_xfer_uid = NULL; /* Transfer Syntax UID */
2009 const gchar *pctx_xfer_desc = NULL; /* Description of UID */
2010
2011 gchar *buf_desc = ""; /* Used in info mode for item text */
2012
2013 guint32 endpos = 0;
2014 int cnt_abbs = 0; /* Number of Abstract Syntax Items */
2015 int cnt_xfer = 0; /* Number of Transfer Syntax Items */
2016
2017 endpos = offset + len;
2018
2019 item_type = tvb_get_guint8(tvb, offset-4);
2020 item_len = tvb_get_ntohs(tvb, offset-2);
2021
2022 pctx_ptree = proto_tree_add_subtree(tree, tvb, offset-4, item_len+4, ett_assoc_pctx, &pctx_pitem, pitem_prefix);
2023
2024 pctx_id = tvb_get_guint8(tvb, offset);
2025 pctx_result = tvb_get_guint8(tvb, 2 + offset); /* only set in responses, otherwise reserved and 0x00 */
2026
2027 /* Find or create DICOM context object */
2028 pctx = dcm_state_pctx_get(assoc, pctx_id, TRUE);
2029 if (pctx == NULL) { /* Internal error. Failed to create data structure */
2030 return;
2031 }
2032
2033 proto_tree_add_uint(pctx_ptree, hf_dcm_assoc_item_type, tvb, offset-4, 1, item_type); /* The type is only one byte long */
2034 proto_tree_add_uint(pctx_ptree, hf_dcm_assoc_item_len, tvb, offset-2, 2, item_len);
2035
2036 proto_tree_add_uint_format(pctx_ptree, hf_dcm_pctx_id, tvb, offset, 1, pctx_id, "Context ID: 0x%02x", pctx_id);
2037
2038 if (!is_assoc_request) {
2039 /* Association response. */
2040
2041 switch (pctx_result) {
2042 case 0: pctx_result_desc = "Accept"; break;
2043 case 1: pctx_result_desc = "User Reject"; break;
2044 case 2: pctx_result_desc = "No Reason"; break;
2045 case 3: pctx_result_desc = "Abstract Syntax Unsupported"; break;
2046 case 4: pctx_result_desc = "Transfer Syntax Unsupported"; break;
2047 }
2048
2049 proto_tree_add_uint_format(pctx_ptree, hf_dcm_pctx_result, tvb, offset+2, 1,
2050 pctx_result, "Result: %s (0x%x)", pctx_result_desc, pctx_result);
2051 }
2052
2053 offset += 4;
2054 while (offset < endpos) {
2055
2056 item_type = tvb_get_guint8(tvb, offset);
2057 item_len = tvb_get_ntohs(tvb, 2 + offset);
2058
2059 offset += 4;
2060 switch (item_type) {
2061 case 0x30: /* Abstract syntax */
2062
2063 /* Parse Item. Works also in info mode where dcm_pctx_tree is NULL */
2064 dissect_dcm_assoc_item(tvb, pinfo, pctx_ptree, offset-4,
2065 "Abstract Syntax: ", DCM_ITEM_VALUE_TYPE_UID, &pctx_abss_uid, &pctx_abss_desc,
2066 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_pctx_abss_syntax, ett_assoc_pctx_abss);
2067
2068 cnt_abbs += 1;
2069 offset += item_len;
2070 break;
2071
2072 case 0x40: /* Transfer syntax */
2073
2074 dissect_dcm_assoc_item(tvb, pinfo, pctx_ptree, offset-4,
2075 "Transfer Syntax: ", DCM_ITEM_VALUE_TYPE_UID, &pctx_xfer_uid, &pctx_xfer_desc,
2076 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_pctx_xfer_syntax, ett_assoc_pctx_xfer);
2077
2078 /*
2079 In a correct Association Response, only one Transfer syntax shall be present.
2080 Therefore, pctx_xfer_uid, pctx_xfer_desc are used for the accept scenario in the info mode
2081 */
2082
2083 if (!is_assoc_request && pctx_result == 0) {
2084 /* Association Response, Context Accepted */
2085 dcm_set_syntax(pctx, pctx_xfer_uid, pctx_xfer_desc);
2086 }
2087 cnt_xfer += 1;
2088 offset += item_len;
2089 break;
2090
2091 default:
2092 offset += item_len;
2093 break;
2094 }
2095 }
2096
2097 if (is_assoc_request) {
2098
2099 if (cnt_abbs<1) {
2100 expert_add_info(pinfo, pctx_pitem, &ei_dcm_no_abstract_syntax);
2101 return;
2102 }
2103 else if (cnt_abbs>1) {
2104 expert_add_info(pinfo, pctx_pitem, &ei_dcm_multiple_abstract_syntax);
2105 return;
2106 }
2107
2108 if (cnt_xfer==0) {
2109 expert_add_info(pinfo, pctx_pitem, &ei_dcm_no_transfer_syntax);
2110 return;
2111 }
2112
2113 if (pctx_abss_uid==NULL) {
2114 expert_add_info(pinfo, pctx_pitem, &ei_dcm_no_abstract_syntax_uid);
2115 return;
2116 }
2117
2118 }
2119 else {
2120
2121 if (cnt_xfer>1) {
2122 expert_add_info(pinfo, pctx_pitem, &ei_dcm_multiple_transfer_syntax);
2123 return;
2124 }
2125 }
2126
2127 if (pctx->abss_uid==NULL) {
2128 /* Permanent copy information into structure */
2129 pctx->abss_uid = wmem_strdup(wmem_file_scope(), pctx_abss_uid);
2130 pctx->abss_desc = wmem_strdup(wmem_file_scope(), pctx_abss_desc);
2131 }
2132
2133 /*
2134 Copy to buffer first, because proto_item_append_text()
2135 crashed for an unknown reason using 'ID 0x%02x, %s, %s'
2136 and in my opinion correctly set parameters.
2137 */
2138
2139 if (is_assoc_request) {
2140 if (pctx_abss_desc == NULL) {
2141 buf_desc = pctx_abss_uid;
2142 }
2143 else {
2144 buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", pctx_abss_desc, pctx_abss_uid);
2145 }
2146 }
2147 else
2148 {
2149 if (pctx_result==0) {
2150 /* Accepted */
2151 buf_desc = wmem_strdup_printf(pinfo->pool, "ID 0x%02x, %s, %s, %s",
2152 pctx_id, pctx_result_desc,
2153 dcm_uid_or_desc(pctx->xfer_uid, pctx->xfer_desc),
2154 dcm_uid_or_desc(pctx->abss_uid, pctx->abss_desc));
2155 }
2156 else {
2157 /* Rejected */
2158 buf_desc = wmem_strdup_printf(pinfo->pool, "ID 0x%02x, %s, %s",
2159 pctx_id, pctx_result_desc,
2160 dcm_uid_or_desc(pctx->abss_uid, pctx->abss_desc));
2161 }
2162 }
2163 proto_item_append_text(pctx_pitem, "%s", buf_desc);
2164
2165 }
2166
2167 /*
2168 Decode the user info item in a Association Request or Response
2169 */
2170 static void
dissect_dcm_userinfo(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,guint32 offset,guint32 len,const gchar * pitem_prefix)2171 dissect_dcm_userinfo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset, guint32 len, const gchar *pitem_prefix)
2172 {
2173
2174 proto_item *userinfo_pitem = NULL;
2175 proto_tree *userinfo_ptree = NULL; /* Tree for presentation context details */
2176
2177 guint8 item_type;
2178 guint16 item_len;
2179
2180 gboolean first_item=TRUE;
2181
2182 gchar *info_max_pdu=NULL;
2183 gchar *info_impl_uid=NULL;
2184 gchar *info_impl_version=NULL;
2185 const gchar *dummy=NULL;
2186
2187 guint32 endpos;
2188
2189 endpos = offset + len;
2190
2191 item_type = tvb_get_guint8(tvb, offset-4);
2192 item_len = tvb_get_ntohs(tvb, offset-2);
2193
2194 userinfo_pitem = proto_tree_add_item(tree, hf_dcm_info, tvb, offset-4, item_len+4, ENC_NA);
2195 proto_item_set_text(userinfo_pitem, "%s", pitem_prefix);
2196 userinfo_ptree = proto_item_add_subtree(userinfo_pitem, ett_assoc_info);
2197
2198 proto_tree_add_uint(userinfo_ptree, hf_dcm_assoc_item_type, tvb, offset-4, 1, item_type); /* The type is only one byte long */
2199 proto_tree_add_uint(userinfo_ptree, hf_dcm_assoc_item_len, tvb, offset-2, 2, item_len);
2200
2201 while (offset < endpos) {
2202
2203 item_type = tvb_get_guint8(tvb, offset);
2204 item_len = tvb_get_ntohs(tvb, 2 + offset);
2205
2206 offset += 4;
2207 switch (item_type) {
2208 case 0x51: /* Max length */
2209
2210 dissect_dcm_assoc_item(tvb, pinfo, userinfo_ptree, offset-4,
2211 "Max PDU Length: ", DCM_ITEM_VALUE_TYPE_UINT32, &info_max_pdu, &dummy,
2212 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_pdu_maxlen, ett_assoc_info_uid);
2213
2214 if (!first_item) {
2215 proto_item_append_text(userinfo_pitem, ", ");
2216 }
2217 proto_item_append_text(userinfo_pitem, "Max PDU Length %s", info_max_pdu);
2218 first_item=FALSE;
2219
2220 offset += item_len;
2221 break;
2222
2223 case 0x52: /* UID */
2224
2225 /* Parse Item. Works also in info mode where dcm_pctx_tree is NULL */
2226 dissect_dcm_assoc_item(tvb, pinfo, userinfo_ptree, offset-4,
2227 "Implementation UID: ", DCM_ITEM_VALUE_TYPE_STRING, &info_impl_uid, &dummy,
2228 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_info_uid, ett_assoc_info_uid);
2229
2230 if (!first_item) {
2231 proto_item_append_text(userinfo_pitem, ", ");
2232 }
2233 proto_item_append_text(userinfo_pitem, "Implementation UID %s", info_impl_uid);
2234 first_item=FALSE;
2235
2236 offset += item_len;
2237 break;
2238
2239 case 0x55: /* version */
2240
2241 dissect_dcm_assoc_item(tvb, pinfo, userinfo_ptree, offset-4,
2242 "Implementation Version: ", DCM_ITEM_VALUE_TYPE_STRING, &info_impl_version, &dummy,
2243 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_info_version, ett_assoc_info_version);
2244
2245 if (!first_item) {
2246 proto_item_append_text(userinfo_pitem, ", ");
2247 }
2248 proto_item_append_text(userinfo_pitem, "Version %s", info_impl_version);
2249 first_item=FALSE;
2250
2251 offset += item_len;
2252 break;
2253
2254 case 0x53: /* async negotiation */
2255
2256 dissect_dcm_assoc_async_negotiation(tvb, userinfo_ptree, offset-4);
2257
2258 offset += item_len;
2259 break;
2260
2261 case 0x54: /* scp/scu role selection */
2262
2263 dissect_dcm_assoc_role_selection(tvb, pinfo, userinfo_ptree, offset-4);
2264
2265 offset += item_len;
2266 break;
2267
2268 case 0x56: /* extended negotiation */
2269
2270 dissect_dcm_assoc_sopclass_extneg(tvb, pinfo, userinfo_ptree, offset-4);
2271
2272 offset += item_len;
2273 break;
2274
2275 case 0x58: /* User Identify */
2276
2277 dissect_dcm_assoc_user_identify(tvb, pinfo, userinfo_ptree, offset-4);
2278
2279 offset += item_len;
2280 break;
2281
2282 default:
2283
2284 dissect_dcm_assoc_unknown(tvb, userinfo_ptree, offset-4);
2285
2286 offset += item_len;
2287 break;
2288 }
2289 }
2290 }
2291
2292
2293 /*
2294 Create a subtree for association requests or responses
2295 */
2296 static guint32
dissect_dcm_assoc_detail(tvbuff_t * tvb,packet_info * pinfo,proto_item * ti,dcm_state_assoc_t * assoc,guint32 offset,guint32 len)2297 dissect_dcm_assoc_detail(tvbuff_t *tvb, packet_info *pinfo, proto_item *ti,
2298 dcm_state_assoc_t *assoc, guint32 offset, guint32 len)
2299 {
2300 proto_tree *assoc_tree = NULL; /* Tree for PDU details */
2301
2302 guint8 item_type;
2303 guint16 item_len;
2304
2305 guint32 endpos;
2306
2307 gchar *item_value = NULL;
2308 const gchar *item_description = NULL;
2309
2310 endpos = offset + len;
2311
2312 assoc_tree = proto_item_add_subtree(ti, ett_assoc);
2313 while (offset < endpos) {
2314
2315 item_type = tvb_get_guint8(tvb, offset);
2316 item_len = tvb_get_ntohs(tvb, 2 + offset);
2317
2318 if (item_len == 0) {
2319 expert_add_info(pinfo, ti, &ei_dcm_assoc_item_len);
2320 return endpos;
2321 }
2322
2323 offset += 4;
2324
2325 switch (item_type) {
2326 case 0x10: /* Application context */
2327 dissect_dcm_assoc_item(tvb, pinfo, assoc_tree, offset-4,
2328 "Application Context: ", DCM_ITEM_VALUE_TYPE_UID, &item_value, &item_description,
2329 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_actx, ett_assoc_actx);
2330
2331 offset += item_len;
2332 break;
2333
2334 case 0x20: /* Presentation context request */
2335 dissect_dcm_pctx(tvb, pinfo, assoc_tree, assoc, offset, item_len, "Presentation Context: ", TRUE);
2336 offset += item_len;
2337 break;
2338
2339 case 0x21: /* Presentation context reply */
2340 dissect_dcm_pctx(tvb, pinfo, assoc_tree, assoc, offset, item_len, "Presentation Context: ", FALSE);
2341 offset += item_len;
2342 break;
2343
2344 case 0x50: /* User Info */
2345 dissect_dcm_userinfo(tvb, pinfo, assoc_tree, offset, item_len, "User Info: ");
2346 offset += item_len;
2347 break;
2348
2349 default:
2350 offset += item_len;
2351 break;
2352 }
2353 }
2354
2355 return offset;
2356
2357 }
2358
2359 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)2360 dissect_dcm_pdv_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
2361 dcm_state_assoc_t *assoc, guint32 offset, dcm_state_pdv_t **pdv)
2362 {
2363 /* Dissect Context and Flags of a PDV and create new PDV structure */
2364
2365 proto_item *pdv_ctx_pitem = NULL;
2366 proto_item *pdv_flags_pitem = NULL;
2367
2368 dcm_state_pctx_t *pctx = NULL;
2369 dcm_state_pdv_t *pdv_first_data = NULL;
2370
2371 const gchar *desc_flag = NULL; /* Flag Description in tree */
2372 gchar *desc_header = NULL; /* Used for PDV description */
2373
2374 guint8 flags = 0, o_flags = 0;
2375 guint8 pctx_id = 0;
2376
2377 /* 1 Byte Context */
2378 pctx_id = tvb_get_guint8(tvb, offset);
2379 pctx = dcm_state_pctx_get(assoc, pctx_id, FALSE);
2380
2381 if (pctx && pctx->xfer_uid) {
2382 proto_tree_add_uint_format(tree, hf_dcm_pdv_ctx, tvb, offset, 1,
2383 pctx_id, "Context: 0x%02x (%s, %s)", pctx_id,
2384 dcm_uid_or_desc(pctx->xfer_uid, pctx->xfer_desc),
2385 dcm_uid_or_desc(pctx->abss_uid, pctx->abss_desc));
2386 }
2387 else {
2388 pdv_ctx_pitem=proto_tree_add_uint_format(tree, hf_dcm_pdv_ctx, tvb, offset, 1,
2389 pctx_id, "Context: 0x%02x not found. A-ASSOCIATE request not found in capture.", pctx_id);
2390
2391 expert_add_info(pinfo, pdv_ctx_pitem, &ei_dcm_pdv_ctx);
2392
2393 if (pctx == NULL) {
2394 /* only create presentation context, if it does not yet exist */
2395
2396 /* Create fake PCTX and guess Syntax ILE, ELE, EBE */
2397 pctx = dcm_state_pctx_new(assoc, pctx_id);
2398
2399 /* To be done: Guess Syntax */
2400 pctx->syntax = DCM_UNK;
2401 }
2402 }
2403 offset +=1;
2404
2405 /* Create PDV structure:
2406
2407 Since we can have multiple PDV per packet (offset) and
2408 multiple merged packets per PDV (the tvb raw_offset)
2409 we need both values to uniquely identify a PDV
2410 */
2411
2412 *pdv = dcm_state_pdv_get(pctx, pinfo->num, tvb_raw_offset(tvb)+offset, TRUE);
2413 if (*pdv == NULL) {
2414 return 0; /* Failed to allocate memory */
2415 }
2416
2417 /* 1 Byte Flag */
2418 /* PS3.8 E.2 Bits 2 through 7 are always set to 0 by the sender and never checked by the receiver. */
2419 o_flags = tvb_get_guint8(tvb, offset);
2420 flags = 0x3 & o_flags;
2421
2422 (*pdv)->pctx_id = pctx_id;
2423
2424 switch (flags) {
2425 case 0: /* 00 */
2426 if (0 != (0xfc & o_flags))
2427 desc_flag = "Data, More Fragments (Warning: Invalid)";
2428 else
2429 desc_flag = "Data, More Fragments";
2430
2431 (*pdv)->is_flagvalid = TRUE;
2432 (*pdv)->is_command = FALSE;
2433 (*pdv)->is_last_fragment = FALSE;
2434 (*pdv)->syntax = pctx->syntax; /* Inherit syntax for data PDVs*/
2435 break;
2436
2437 case 2: /* 10 */
2438 if (0 != (0xfc & o_flags))
2439 desc_flag = "Data, Last Fragment (Warning: Invalid)";
2440 else
2441 desc_flag = "Data, Last Fragment";
2442
2443 (*pdv)->is_flagvalid = TRUE;
2444 (*pdv)->is_command = FALSE;
2445 (*pdv)->is_last_fragment = TRUE;
2446 (*pdv)->syntax = pctx->syntax; /* Inherit syntax for data PDVs*/
2447 break;
2448
2449 case 1: /* 01 */
2450 if (0 != (0xfc & o_flags))
2451 desc_flag = "Command, More Fragments (Warning: Invalid)";
2452 else
2453 desc_flag = "Command, More Fragments";
2454 desc_header = wmem_strdup(wmem_file_scope(), "Command"); /* Will be overwritten with real command tag */
2455
2456 (*pdv)->is_flagvalid = TRUE;
2457 (*pdv)->is_command = TRUE;
2458 (*pdv)->is_last_fragment = FALSE;
2459 (*pdv)->syntax = DCM_ILE; /* Command tags are always little endian*/
2460 break;
2461
2462 case 3: /* 11 */
2463 if (0 != (0xfc & o_flags))
2464 desc_flag = "Command, Last Fragment (Warning: Invalid)";
2465 else
2466 desc_flag = "Command, Last Fragment";
2467 desc_header = wmem_strdup(wmem_file_scope(), "Command");
2468
2469 (*pdv)->is_flagvalid = TRUE;
2470 (*pdv)->is_command = TRUE;
2471 (*pdv)->is_last_fragment = TRUE;
2472 (*pdv)->syntax = DCM_ILE; /* Command tags are always little endian*/
2473 break;
2474
2475 default:
2476 desc_flag = "Invalid Flags";
2477 desc_header = wmem_strdup(wmem_file_scope(), desc_flag);
2478
2479 (*pdv)->is_flagvalid = FALSE;
2480 (*pdv)->is_command = FALSE;
2481 (*pdv)->is_last_fragment = FALSE;
2482 (*pdv)->syntax = DCM_UNK;
2483 }
2484
2485 if (!PINFO_FD_VISITED(pinfo)) {
2486 (*pdv)->reassembly_id = pctx->reassembly_count;
2487 if ((*pdv)->is_last_fragment) {
2488 pctx->reassembly_count++;
2489 }
2490 }
2491
2492 if (flags == 0 || flags == 2) {
2493 /* Data PDV */
2494 pdv_first_data = dcm_state_pdv_get_obj_start(*pdv);
2495
2496 if (pdv_first_data->prev && pdv_first_data->prev->is_command) {
2497 /* Every Data PDV sequence should be preceded by a Command PDV,
2498 so we should always hit this for a correct capture
2499 */
2500
2501 if (pctx->abss_desc && g_str_has_suffix(pctx->abss_desc, "Storage")) {
2502 /* Should be done far more intelligent, e.g. does not catch the (Retired) ones */
2503 if (flags == 0) {
2504 desc_header = wmem_strdup_printf(wmem_file_scope(), "%s Fragment", pctx->abss_desc);
2505 }
2506 else {
2507 desc_header = wmem_strdup(wmem_file_scope(), pctx->abss_desc);
2508 }
2509 (*pdv)->is_storage = TRUE;
2510 }
2511 else {
2512 /* Use previous command and append DATA*/
2513 desc_header = wmem_strdup_printf(wmem_file_scope(), "%s-DATA", pdv_first_data->prev->desc);
2514 }
2515 }
2516 else {
2517 desc_header = wmem_strdup(wmem_file_scope(), "DATA");
2518 }
2519 }
2520
2521 (*pdv)->desc = desc_header;
2522
2523 pdv_flags_pitem = proto_tree_add_uint_format(tree, hf_dcm_pdv_flags, tvb, offset, 1,
2524 flags, "Flags: 0x%02x (%s)", o_flags, desc_flag);
2525
2526 if (o_flags>3) {
2527 expert_add_info(pinfo, pdv_flags_pitem, &ei_dcm_pdv_flags);
2528 }
2529 offset +=1;
2530
2531 return offset;
2532 }
2533
2534 /*
2535 Based on the value representation, decode the value of one tag.
2536 Support VM>1 for most types, but not all. Returns new offset
2537 */
2538 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)2539 dissect_dcm_tag_value(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, dcm_state_pdv_t *pdv,
2540 guint32 offset, guint16 grp, guint16 elm,
2541 guint32 vl, guint32 vl_max, const gchar* vr, gchar **tag_value)
2542 {
2543
2544 proto_item *pitem = NULL;
2545 guint encoding = (pdv->syntax == DCM_EBE) ? ENC_BIG_ENDIAN : ENC_LITTLE_ENDIAN;
2546
2547
2548 /* Make sure we have all the bytes of the item; this should throw
2549 and exception if vl_max is so large that it causes the offset
2550 to overflow. */
2551 tvb_ensure_bytes_exist(tvb, offset, vl_max);
2552
2553 /* ---------------------------------------------------------------------------
2554 Potentially long types. Obey vl_max
2555 ---------------------------------------------------------------------------
2556 */
2557
2558 if ((strncmp(vr, "AE", 2) == 0) || (strncmp(vr, "AS", 2) == 0) || (strncmp(vr, "CS", 2) == 0) ||
2559 (strncmp(vr, "DA", 2) == 0) || (strncmp(vr, "DS", 2) == 0) || (strncmp(vr, "DT", 2) == 0) ||
2560 (strncmp(vr, "IS", 2) == 0) || (strncmp(vr, "LO", 2) == 0) || (strncmp(vr, "LT", 2) == 0) ||
2561 (strncmp(vr, "PN", 2) == 0) || (strncmp(vr, "SH", 2) == 0) || (strncmp(vr, "ST", 2) == 0) ||
2562 (strncmp(vr, "TM", 2) == 0) || (strncmp(vr, "UI", 2) == 0) || (strncmp(vr, "UT", 2) == 0) ) {
2563 /*
2564 15 ways to represent a string.
2565
2566 For LT, ST, UT the DICOM standard does not allow multi-value
2567 For the others, VM is built into 'automatically, because it uses '\' as separator
2568 */
2569
2570 gchar *vals;
2571 dcm_uid_t *uid = NULL;
2572 guint8 val8;
2573
2574 val8 = tvb_get_guint8(tvb, offset + vl_max - 1);
2575 if (val8 == 0x00) {
2576 /* Last byte of string is 0x00, i.e. padded */
2577 vals = tvb_format_text(pinfo->pool, tvb, offset, vl_max - 1);
2578 }
2579 else {
2580 vals = tvb_format_text(pinfo->pool, tvb, offset, vl_max);
2581 }
2582
2583 if ((strncmp(vr, "UI", 2) == 0)) {
2584 /* This is a UID. Attempt a lookup. Will only return something for classes of course */
2585
2586 uid = (dcm_uid_t *)wmem_map_lookup(dcm_uid_table, (gpointer) vals);
2587 if (uid) {
2588 *tag_value = wmem_strdup_printf(pinfo->pool, "%s (%s)", vals, uid->name);
2589 }
2590 else {
2591 *tag_value = vals;
2592 }
2593 }
2594 else {
2595 if (strlen(vals) > 50) {
2596 *tag_value = wmem_strdup_printf(pinfo->pool, "%-50.50s...", vals);
2597 }
2598 else {
2599 *tag_value = vals;
2600 }
2601 }
2602 proto_tree_add_string(tree, hf_dcm_tag_value_str, tvb, offset, vl_max, *tag_value);
2603
2604 if (grp == 0x0000 && elm == 0x0902) {
2605 /* The error comment */
2606 pdv->comment = wmem_strdup(wmem_file_scope(), g_strstrip(vals));
2607 }
2608 }
2609 else if ((strncmp(vr, "OB", 2) == 0) || (strncmp(vr, "OW", 2) == 0) ||
2610 (strncmp(vr, "OF", 2) == 0) || (strncmp(vr, "OD", 2) == 0)) {
2611
2612 /* Array of Bytes, Words, Float, or Doubles. Don't perform any decoding. VM=1. Multiple arrays are not possible */
2613
2614 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, vl_max, NULL, "%s", "(binary)");
2615
2616 *tag_value = wmem_strdup(pinfo->pool, "(binary)");
2617 }
2618 else if (strncmp(vr, "UN", 2) == 0) {
2619
2620 /* Usually the case for private tags in implicit syntax, since tag was not found and VR not specified.
2621 Not been able to create UN yet. No need to support VM > 1.
2622 */
2623
2624 guint8 val8;
2625 gchar *vals;
2626 guint32 i;
2627
2628 /* String detector, i.e. check if we only have alpha-numeric character */
2629 gboolean is_string = TRUE;
2630 gboolean is_padded = FALSE;
2631
2632 for (i = 0; i < vl_max ; i++) {
2633 val8 = tvb_get_guint8(tvb, offset + i);
2634
2635 if ((val8 == 0x09) || (val8 == 0x0A) || (val8 == 0x0D)) {
2636 /* TAB, LF, CR */
2637 }
2638 else if ((val8 >= 0x20) && (val8 <= 0x7E)) {
2639 /* No extended ASCII, 0-9, A-Z, a-z */
2640 }
2641 else if ((i == vl_max -1) && (val8 == 0x00)) {
2642 /* Last Byte can be null*/
2643 is_padded = TRUE;
2644 }
2645 else {
2646 /* Here's the code */
2647 is_string = FALSE;
2648 }
2649 }
2650
2651 if (is_string) {
2652 vals = tvb_format_text(pinfo->pool, tvb, offset, (is_padded ? vl_max - 1 : vl_max));
2653 proto_tree_add_string(tree, hf_dcm_tag_value_str, tvb, offset, vl_max, vals);
2654
2655 *tag_value = vals;
2656 }
2657 else {
2658 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, vl_max, NULL, "%s", "(binary)");
2659
2660 *tag_value = wmem_strdup(pinfo->pool, "(binary)");
2661 }
2662 }
2663 /* ---------------------------------------------------------------------------
2664 Smaller types. vl/vl_max are not used. Fixed item length from 2 to 8 bytes
2665 ---------------------------------------------------------------------------
2666 */
2667 else if (strncmp(vr, "AT", 2) == 0) {
2668
2669 /* Attribute Tag e.g. (0022,8866). 2*2 Bytes, Can have VM > 1 */
2670
2671 guint16 at_grp;
2672 guint16 at_elm;
2673 gchar *at_value = "";
2674
2675 /* 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
2676 So make it at least one loop so set it to at least 4.
2677 */
2678
2679 guint32 vm_item_len = 4;
2680 guint32 vm_item_count = dcm_vm_item_count(vl_max, vm_item_len);
2681
2682 guint32 i = 0;
2683 while (i < vm_item_count) {
2684 at_grp = tvb_get_guint16(tvb, offset+ i*vm_item_len, encoding);
2685 at_elm = tvb_get_guint16(tvb, offset+ i*vm_item_len+2, encoding);
2686
2687 proto_tree_add_uint_format_value(tree, hf_dcm_tag_value_32u, tvb, offset + i*vm_item_len, vm_item_len,
2688 (at_grp << 16) | at_elm, "%04x,%04x", at_grp, at_elm);
2689
2690 at_value = wmem_strdup_printf(pinfo->pool,"%s(%04x,%04x)", at_value, at_grp, at_elm);
2691
2692 i++;
2693 }
2694 *tag_value = at_value;
2695 }
2696 else if (strncmp(vr, "FL", 2) == 0) { /* Single Float. Can be VM > 1, but not yet supported */
2697
2698 gfloat valf = tvb_get_ieee_float(tvb, offset, encoding);
2699
2700 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, 4, NULL, "%f", valf);
2701
2702 *tag_value = wmem_strdup_printf(pinfo->pool, "%f", valf);
2703 }
2704 else if (strncmp(vr, "FD", 2) == 0) { /* Double Float. Can be VM > 1, but not yet supported */
2705
2706 gdouble vald = tvb_get_ieee_double(tvb, offset, encoding);
2707
2708 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, 8, NULL, "%f", vald);
2709
2710 *tag_value = wmem_strdup_printf(pinfo->pool, "%f", vald);
2711 }
2712 else if (strncmp(vr, "SL", 2) == 0) { /* Signed Long. Can be VM > 1, but not yet supported */
2713 gint32 val32;
2714
2715 proto_tree_add_item_ret_int(tree, hf_dcm_tag_value_32s, tvb, offset, 4, encoding, &val32);
2716
2717 *tag_value = wmem_strdup_printf(pinfo->pool, "%d", val32);
2718 }
2719 else if (strncmp(vr, "SS", 2) == 0) { /* Signed Short. Can be VM > 1, but not yet supported */
2720 gint32 val32;
2721
2722 proto_tree_add_item_ret_int(tree, hf_dcm_tag_value_16s, tvb, offset, 2, encoding, &val32);
2723
2724 *tag_value = wmem_strdup_printf(pinfo->pool, "%d", val32);
2725 }
2726 else if (strncmp(vr, "UL", 2) == 0) { /* Unsigned Long. Can be VM > 1, but not yet supported */
2727 guint32 val32;
2728
2729 proto_tree_add_item_ret_uint(tree, hf_dcm_tag_value_32u, tvb, offset, 4, encoding, &val32);
2730
2731 *tag_value = wmem_strdup_printf(pinfo->pool, "%u", val32);
2732 }
2733 else if (strncmp(vr, "US", 2) == 0) { /* Unsigned Short. Can be VM > 1, but not yet supported */
2734 const gchar *status_message = NULL;
2735 guint16 val16 = tvb_get_guint16(tvb, offset, encoding);
2736
2737 if (grp == 0x0000 && elm == 0x0100) {
2738 /* This is a command */
2739 pdv->command = wmem_strdup(wmem_file_scope(), val_to_str(val16, dcm_cmd_vals, " "));
2740 *tag_value = pdv->command;
2741 }
2742 else if (grp == 0x0000 && elm == 0x0900) {
2743 /* This is a status message. If value is not 0x0000, add an expert info */
2744
2745 status_message = dcm_rsp2str(val16);
2746 *tag_value = wmem_strdup_printf(pinfo->pool, "%s (0x%02x)", status_message, val16);
2747
2748 if ((val16 & 0xFF00) == 0xFF00) {
2749 /* C-FIND also has a 0xFF01 as a valid response */
2750 pdv->is_pending = TRUE;
2751 }
2752 else if (val16 != 0x0000) {
2753 /* Neither success nor pending */
2754 pdv->is_warning = TRUE;
2755 }
2756
2757 pdv->status = wmem_strdup(wmem_file_scope(), status_message);
2758
2759 }
2760 else {
2761 *tag_value = wmem_strdup_printf(pinfo->pool, "%u", val16);
2762 }
2763
2764 if (grp == 0x0000) {
2765 if (elm == 0x0110) { /* (0000,0110) Message ID */
2766 pdv->message_id = val16;
2767 }
2768 else if (elm == 0x0120) { /* (0000,0120) Message ID Being Responded To */
2769 pdv->message_id_resp = val16;
2770 }
2771 else if (elm == 0x1020) { /* (0000,1020) Number of Remaining Sub-operations */
2772 pdv->no_remaining = val16;
2773 }
2774 else if (elm == 0x1021) { /* (0000,1021) Number of Completed Sub-operations */
2775 pdv->no_completed = val16;
2776 }
2777 else if (elm == 0x1022) { /* (0000,1022) Number of Failed Sub-operations */
2778 pdv->no_failed = val16;
2779 }
2780 else if (elm == 0x1023) { /* (0000,1023) Number of Warning Sub-operations */
2781 pdv->no_warning = val16;
2782 }
2783 }
2784
2785 pitem = proto_tree_add_uint_format_value(tree, hf_dcm_tag_value_16u, tvb, offset, 2,
2786 val16, "%s", *tag_value);
2787
2788 if (pdv->is_warning && status_message) {
2789 expert_add_info(pinfo, pitem, &ei_dcm_status_msg);
2790 }
2791 }
2792 /* Invalid VR, can only occur with Explicit syntax */
2793 else {
2794 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, vl_max,
2795 NULL, "%s", (vl > vl_max ? "" : "(unknown VR)"));
2796
2797 *tag_value = wmem_strdup(pinfo->pool, "(unknown VR)");
2798 }
2799 offset += vl_max;
2800
2801 return offset;
2802
2803 }
2804
2805 /*
2806 Return true, if the required size does not fit at position 'offset'.
2807 */
2808 static gboolean
dcm_tag_is_open(dcm_state_pdv_t * pdv,guint32 startpos,guint32 offset,guint32 endpos,guint32 size_required)2809 dcm_tag_is_open(dcm_state_pdv_t *pdv, guint32 startpos, guint32 offset, guint32 endpos, guint32 size_required)
2810 {
2811
2812 if (offset + size_required > endpos) {
2813
2814 pdv->open_tag.is_header_fragmented = TRUE;
2815 pdv->open_tag.len_decoded = endpos - startpos;
2816
2817 return TRUE;
2818 }
2819 else {
2820 return FALSE;
2821 }
2822 }
2823
2824 static dcm_tag_t*
dcm_tag_lookup(guint16 grp,guint16 elm)2825 dcm_tag_lookup(guint16 grp, guint16 elm)
2826 {
2827
2828 static dcm_tag_t *tag_def = NULL;
2829
2830 static dcm_tag_t tag_unknown = { 0x00000000, "(unknown)", "UN", "1", 0, 0};
2831 static dcm_tag_t tag_private = { 0x00000000, "Private Tag", "UN", "1", 0, 0 };
2832 static dcm_tag_t tag_private_grp_len = { 0x00000000, "Private Tag Group Length", "UL", "1", 0, 0 };
2833 static dcm_tag_t tag_grp_length = { 0x00000000, "Group Length", "UL", "1", 0, 0 };
2834
2835 /* Try a direct hit first before doing a masked search */
2836 tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | elm));
2837
2838 if (tag_def == NULL) {
2839
2840 /* No match found */
2841 if ((grp & 0x0001) && (elm == 0x0000)) {
2842 tag_def = &tag_private_grp_len;
2843 }
2844 else if (grp & 0x0001) {
2845 tag_def = &tag_private;
2846 }
2847 else if (elm == 0x0000) {
2848 tag_def = &tag_grp_length;
2849 }
2850
2851 /* There are a few tags that require a mask to be found */
2852 else if (((grp & 0xFF00) == 0x5000) || ((grp & 0xFF00) == 0x6000) || ((grp & 0xFF00) == 0x7F00)) {
2853 /* Do a special for groups 0x50xx, 0x60xx and 0x7Fxx */
2854 tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER((((guint32)grp & 0xFF00) << 16) | elm));
2855 }
2856 else if ((grp == 0x0020) && ((elm & 0xFF00) == 0x3100)) {
2857 tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0xFF00)));
2858 }
2859 else if ((grp == 0x0028) && ((elm & 0xFF00) == 0x0400)) {
2860 /* This map was done to 0x041x */
2861 tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0xFF0F) | 0x0010));
2862 }
2863 else if ((grp == 0x0028) && ((elm & 0xFF00) == 0x0800)) {
2864 tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0xFF0F)));
2865 }
2866 else if (grp == 0x1000) {
2867 tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0x000F)));
2868 }
2869 else if (grp == 0x1010) {
2870 tag_def = (dcm_tag_t *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((guint32)grp << 16) | (elm & 0x0000)));
2871 }
2872
2873 if (tag_def == NULL) {
2874 /* Still no match found */
2875 tag_def = &tag_unknown;
2876 }
2877 }
2878
2879 return tag_def;
2880 }
2881
2882 static gchar*
dcm_tag_summary(packet_info * pinfo,guint16 grp,guint16 elm,guint32 vl,const gchar * tag_desc,const gchar * vr,gboolean is_retired,gboolean is_implicit)2883 dcm_tag_summary(packet_info *pinfo, guint16 grp, guint16 elm, guint32 vl, const gchar *tag_desc, const gchar *vr,
2884 gboolean is_retired, gboolean is_implicit)
2885 {
2886
2887 gchar *desc_mod;
2888 gchar *tag_vl;
2889 gchar *tag_sum;
2890
2891 if (is_retired) {
2892 desc_mod = wmem_strdup_printf(pinfo->pool, "(Retired) %-35.35s", tag_desc);
2893 }
2894 else {
2895 desc_mod = wmem_strdup_printf(pinfo->pool, "%-45.45s", tag_desc);
2896 }
2897
2898 if (vl == 0xFFFFFFFF) {
2899 tag_vl = wmem_strdup_printf(pinfo->pool, "%10.10s", "<udef>");
2900 }
2901 else {
2902 tag_vl = wmem_strdup_printf(pinfo->pool, "%10u", vl); /* Show as dec */
2903 }
2904
2905 if (is_implicit) tag_sum = wmem_strdup_printf(pinfo->pool, "(%04x,%04x) %s %s", grp, elm, tag_vl, desc_mod);
2906 else tag_sum = wmem_strdup_printf(pinfo->pool, "(%04x,%04x) %s %s [%s]", grp, elm, tag_vl, desc_mod, vr);
2907
2908 return tag_sum;
2909 }
2910
2911 /*
2912 Decode one tag. If it is a sequence or item start create a subtree. Returns new offset.
2913 http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html
2914 */
2915 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)2916 dissect_dcm_tag(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
2917 dcm_state_pdv_t *pdv, guint32 offset, guint32 endpos,
2918 gboolean is_first_tag, const gchar **tag_description,
2919 gboolean *end_of_seq_or_item)
2920 {
2921
2922
2923 proto_tree *tag_ptree = NULL; /* Tree for decoded tag details */
2924 proto_tree *seq_ptree = NULL; /* Possible subtree for sequences and items */
2925
2926 proto_item *tag_pitem = NULL;
2927 dcm_tag_t *tag_def = NULL;
2928
2929 gint ett;
2930
2931 const gchar *vr = NULL;
2932 gchar *tag_value = ""; /* Tag Value converted to a string */
2933 gchar *tag_summary;
2934
2935 guint32 vl = 0;
2936 guint16 vl_1 = 0;
2937 guint16 vl_2 = 0;
2938
2939 guint32 offset_tag = 0; /* Remember offsets for tree, since the tree */
2940 guint32 offset_vr = 0; /* header is created pretty late */
2941 guint32 offset_vl = 0;
2942
2943 guint32 vl_max = 0; /* Max Value Length to Parse */
2944
2945 guint16 grp = 0;
2946 guint16 elm = 0;
2947
2948 guint32 len_decoded_remaing = 0;
2949
2950 /* Decode the syntax a little more */
2951 guint32 encoding = (pdv->syntax == DCM_EBE) ? ENC_BIG_ENDIAN : ENC_LITTLE_ENDIAN;
2952 gboolean is_implicit = (pdv->syntax == DCM_ILE);
2953 gboolean is_vl_long = FALSE; /* True for 4 Bytes length fields */
2954
2955 gboolean is_sequence = FALSE; /* True for Sequence Tags */
2956 gboolean is_item = FALSE; /* True for Sequence Item Tags */
2957
2958 *tag_description = NULL; /* Reset description. It's wmem packet scope memory, so not really bad*/
2959
2960 offset_tag = offset;
2961
2962
2963 if (pdv->prev && is_first_tag) {
2964 len_decoded_remaing = pdv->prev->open_tag.len_decoded;
2965 }
2966
2967
2968 /* Since we may have a fragmented header, check for every attribute,
2969 whether we have already decoded left-overs from the previous PDV.
2970 Since we have implicit & explicit syntax, copying the open tag to
2971 a buffer without decoding, would have caused tvb_get_xxtohs()
2972 implementations on the copy.
2973
2974 An alternative approach would have been to resemble the PDVs first.
2975
2976 The attempts to reassemble without named sources (to be implemented)
2977 were very sensitive to missing packets. In such a case, no packet
2978 of a PDV chain was decoded, not even the start.
2979
2980 So for the time being, use this rather cumbersome approach.
2981
2982 For every two bytes (PDV length are always a factor of 2)
2983 check whether we have enough data in the buffer and store the value
2984 accordingly. In the next frame check, whether we have decoded this yet.
2985 */
2986
2987 /* Group */
2988 if (len_decoded_remaing >= 2) {
2989 grp = pdv->prev->open_tag.grp;
2990 len_decoded_remaing -= 2;
2991 }
2992 else {
2993
2994 if (dcm_tag_is_open(pdv, offset_tag, offset, endpos, 2))
2995 return endpos; /* Exit if needed */
2996
2997 grp = tvb_get_guint16(tvb, offset, encoding);
2998 offset += 2;
2999 pdv->open_tag.grp = grp;
3000 }
3001
3002 /* Element */
3003 if (len_decoded_remaing >= 2) {
3004 elm = pdv->prev->open_tag.elm;
3005 len_decoded_remaing -= 2;
3006 }
3007 else {
3008
3009 if (dcm_tag_is_open(pdv, offset_tag, offset, endpos, 2))
3010 return endpos; /* Exit if needed */
3011
3012 elm = tvb_get_guint16(tvb, offset, encoding);
3013 offset += 2;
3014 pdv->open_tag.elm = elm;
3015 }
3016
3017 /* Find the best matching tag */
3018 tag_def = dcm_tag_lookup(grp, elm);
3019
3020 /* Value Representation */
3021 offset_vr = offset;
3022 if ((grp == 0xFFFE) && (elm == 0xE000 || elm == 0xE00D || elm == 0xE0DD)) {
3023 /* Item start, Item Delimitation or Sequence Delimitation */
3024 vr = "UL";
3025 is_vl_long = TRUE; /* These tags always have a 4 byte length field */
3026 }
3027 else if (is_implicit) {
3028 /* Get VR from tag definition */
3029 vr = wmem_strdup(pinfo->pool, tag_def->vr);
3030 is_vl_long = TRUE; /* Implicit always has 4 byte length field */
3031 }
3032 else {
3033
3034 if (len_decoded_remaing >= 2) {
3035 vr = wmem_strdup(pinfo->pool, pdv->prev->open_tag.vr);
3036 len_decoded_remaing -= 2;
3037 }
3038 else {
3039
3040 /* Controlled exit, if VR does not fit. */
3041 if (dcm_tag_is_open(pdv, offset_tag, offset_vr, endpos, 2))
3042 return endpos;
3043
3044 vr = (gchar *)tvb_get_string_enc(pinfo->pool, tvb, offset, 2, ENC_ASCII);
3045 offset += 2;
3046
3047 g_free(pdv->open_tag.vr);
3048 pdv->open_tag.vr = g_strdup(vr); /* needs to survive within a session */
3049 }
3050
3051 if ((strcmp(vr, "OB") == 0) || (strcmp(vr, "OW") == 0) || (strcmp(vr, "OF") == 0) || (strcmp(vr, "OD") == 0) || (strcmp(vr, "OL") == 0) ||
3052 (strcmp(vr, "SQ") == 0) || (strcmp(vr, "UC") == 0) || (strcmp(vr, "UR") == 0) || (strcmp(vr, "UT") == 0) || (strcmp(vr, "UN") == 0)) {
3053 /* Part 5, Table 7.1-1 in the standard */
3054 /* Length is always 4 bytes: OB, OD, OF, OL, OW, SQ, UC, UR, UT or UN */
3055
3056 is_vl_long = TRUE;
3057
3058 /* Skip 2 Bytes */
3059 if (len_decoded_remaing >= 2) {
3060 len_decoded_remaing -= 2;
3061 }
3062 else {
3063 if (dcm_tag_is_open(pdv, offset_tag, offset_vr, endpos, 2))
3064 return endpos;
3065 offset += 2;
3066 }
3067 }
3068 else {
3069 is_vl_long = FALSE;
3070 }
3071 }
3072
3073
3074 /* Value Length. This is rather cumbersome code to get a 4 byte length, but in the
3075 fragmented case, we have 2*2 bytes. So always use that pattern
3076 */
3077
3078 offset_vl = offset;
3079 if (len_decoded_remaing >= 2) {
3080 vl_1 = pdv->prev->open_tag.vl_1;
3081 len_decoded_remaing -= 2;
3082 }
3083 else {
3084
3085 if (dcm_tag_is_open(pdv, offset_tag, offset_vl, endpos, 2))
3086 return endpos;
3087 vl_1 = tvb_get_guint16(tvb, offset, encoding);
3088 offset += 2;
3089 pdv->open_tag.vl_1 = vl_1;
3090 }
3091
3092 if (is_vl_long) {
3093
3094 if (len_decoded_remaing >= 2) {
3095 vl_2 = pdv->prev->open_tag.vl_2;
3096 }
3097 else {
3098
3099 if (dcm_tag_is_open(pdv, offset_tag, offset_vl+2, endpos, 2))
3100 return endpos;
3101 vl_2 = tvb_get_guint16(tvb, offset, encoding);
3102 offset += 2;
3103 pdv->open_tag.vl_2 = vl_2;
3104 }
3105
3106 if (encoding == ENC_LITTLE_ENDIAN) vl = (vl_2 << 16) + vl_1;
3107 else vl = (vl_1 << 16) + vl_2;
3108 }
3109 else {
3110 vl = vl_1;
3111 }
3112
3113 /* Now we have most of the information, except for sequences and items with undefined
3114 length :-/. But, whether we know the length or not, we now need to create the tree
3115 item and subtree, before we can loop into sequences and items
3116
3117 Display the information we collected so far. Don't wait until the value is parsed,
3118 because that parsing might cause an exception. If that happens within a sequence,
3119 the sequence tag would not show up with the value
3120
3121 Use different ett_ for Sequences & Items, so that fold/unfold state makes sense
3122 */
3123
3124 tag_summary = dcm_tag_summary(pinfo, grp, elm, vl, tag_def->description, vr, tag_def->is_retired, is_implicit);
3125 is_sequence = (strcmp(vr, "SQ") == 0) || (vl == 0xFFFFFFFF);
3126 is_item = ((grp == 0xFFFE) && (elm == 0xE000));
3127
3128 if ((is_sequence | is_item) && global_dcm_seq_subtree) {
3129 ett = is_sequence ? ett_dcm_data_seq : ett_dcm_data_item;
3130 }
3131 else {
3132 ett = ett_dcm_data_tag;
3133 }
3134
3135 if (vl == 0xFFFFFFFF) {
3136 /* 'Just' mark header as the length of the item */
3137 tag_ptree = proto_tree_add_subtree(tree, tvb, offset_tag, offset - offset_tag, ett, &tag_pitem, tag_summary);
3138 vl_max = 0; /* We don't know who long this sequence/item is */
3139 }
3140 else if (offset + vl <= endpos) {
3141 /* Show real length of item */
3142 tag_ptree = proto_tree_add_subtree(tree, tvb, offset_tag, offset + vl - offset_tag, ett, &tag_pitem, tag_summary);
3143 vl_max = vl;
3144 }
3145 else {
3146 /* Value is longer than what we have in the PDV, -> we do have a OPEN tag */
3147 tag_ptree = proto_tree_add_subtree(tree, tvb, offset_tag, endpos - offset_tag, ett, &tag_pitem, tag_summary);
3148 vl_max = endpos - offset;
3149 }
3150
3151 /* If you are going to touch the following 25 lines, make sure you reserve a few hours to go
3152 through both display options and check for proper tree display :-)
3153 */
3154 if (is_sequence | is_item) {
3155
3156 if (global_dcm_seq_subtree) {
3157 /* Use different ett_ for Sequences & Items, so that fold/unfold state makes sense */
3158 seq_ptree = tag_ptree;
3159 if (!global_dcm_tag_subtree) {
3160 tag_ptree = NULL;
3161 }
3162 }
3163 else {
3164 seq_ptree = tree;
3165 if (!global_dcm_tag_subtree) {
3166 tag_ptree = NULL;
3167 }
3168 }
3169 }
3170 else {
3171 /* For tags */
3172 if (!global_dcm_tag_subtree) {
3173 tag_ptree = NULL;
3174 }
3175 }
3176
3177 /* ---------------------------------------------------------------
3178 Tag details as separate items
3179 ---------------------------------------------------------------
3180 */
3181
3182 proto_tree_add_uint_format_value(tag_ptree, hf_dcm_tag, tvb, offset_tag, 4,
3183 (grp << 16) | elm, "%04x,%04x (%s)", grp, elm, tag_def->description);
3184
3185 /* Add VR to tag detail, except for sequence items */
3186 if (!is_item) {
3187 if (is_implicit) {
3188 /* Select header, since no VR is present in implicit syntax */
3189 proto_tree_add_string(tag_ptree, hf_dcm_tag_vr, tvb, offset_tag, 4, vr);
3190 }
3191 else {
3192 proto_tree_add_string(tag_ptree, hf_dcm_tag_vr, tvb, offset_vr, 2, vr);
3193 }
3194 }
3195
3196 /* Add length to tag detail */
3197 proto_tree_add_uint(tag_ptree, hf_dcm_tag_vl, tvb, offset_vl, (is_vl_long ? 4 : 2), vl);
3198
3199
3200 /* ---------------------------------------------------------------
3201 Finally the Tag Value
3202 ---------------------------------------------------------------
3203 */
3204 if ((is_sequence || is_item) && (vl > 0)) {
3205 /* Sequence or Item Start */
3206
3207 guint32 endpos_item = 0;
3208 gboolean local_end_of_seq_or_item = FALSE;
3209 gboolean is_first_desc = TRUE;
3210
3211 const gchar *item_description = NULL; /* Will be allocated as wmem packet scope memory in dissect_dcm_tag() */
3212
3213 if (vl == 0xFFFFFFFF) {
3214 /* Undefined length */
3215
3216 while ((!local_end_of_seq_or_item) && (!pdv->open_tag.is_header_fragmented) && (offset < endpos)) {
3217
3218 offset = dissect_dcm_tag(tvb, pinfo, seq_ptree, pdv, offset, endpos, FALSE, &item_description, &local_end_of_seq_or_item);
3219
3220 if (item_description && global_dcm_seq_subtree) {
3221 proto_item_append_text(tag_pitem, (is_first_desc ? " %s" : ", %s"), item_description);
3222 is_first_desc = FALSE;
3223 }
3224 }
3225 }
3226 else {
3227 /* Defined length */
3228 endpos_item = offset + vl_max;
3229
3230 while (offset < endpos_item) {
3231
3232 offset = dissect_dcm_tag(tvb, pinfo, seq_ptree, pdv, offset, endpos_item, FALSE, &item_description, &local_end_of_seq_or_item);
3233
3234 if (item_description && global_dcm_seq_subtree) {
3235 proto_item_append_text(tag_pitem, (is_first_desc ? " %s" : ", %s"), item_description);
3236 is_first_desc = FALSE;
3237 }
3238 }
3239 }
3240 } /* if ((is_sequence || is_item) && (vl > 0)) */
3241 else if ((grp == 0xFFFE) && (elm == 0xE00D)) {
3242 /* Item delimitation for items with undefined length */
3243 *end_of_seq_or_item = TRUE;
3244 }
3245 else if ((grp == 0xFFFE) && (elm == 0xE0DD)) {
3246 /* Sequence delimitation for sequences with undefined length */
3247 *end_of_seq_or_item = TRUE;
3248 }
3249 else if (vl == 0) {
3250 /* No value for this tag */
3251
3252 /* The following copy is needed. tag_value is post processed with g_strstrip()
3253 and that one will crash the whole application, when a constant is used.
3254 */
3255
3256 tag_value = wmem_strdup(pinfo->pool, "<Empty>");
3257 }
3258 else if (vl > vl_max) {
3259 /* Tag is longer than the PDV/PDU. Don't perform any decoding */
3260
3261 gchar *tag_desc;
3262
3263 proto_tree_add_bytes_format(tag_ptree, hf_dcm_tag_value_byte, tvb, offset, vl_max,
3264 NULL, "%-8.8sBytes %d - %d [start]", "Value:", 1, vl_max);
3265
3266 tag_value = wmem_strdup_printf(pinfo->pool, "<Bytes %d - %d, start>", 1, vl_max);
3267 offset += vl_max;
3268
3269 /* Save the needed data for reuse, and subsequent packets
3270 This will leak a little within the session.
3271
3272 But since we may have tags being closed and reopen in the same PDV
3273 we will always need to store this
3274 */
3275
3276 tag_desc = dcm_tag_summary(pinfo, grp, elm, vl, tag_def->description, vr, tag_def->is_retired, is_implicit);
3277
3278 if (pdv->open_tag.desc == NULL) {
3279 pdv->open_tag.is_value_fragmented = TRUE;
3280 pdv->open_tag.desc = wmem_strdup(wmem_file_scope(), tag_desc);
3281 pdv->open_tag.len_total = vl;
3282 pdv->open_tag.len_remaining = vl - vl_max;
3283 }
3284 }
3285 else {
3286 /* Regular value. Identify the type, decode and display */
3287
3288 offset = dissect_dcm_tag_value(tvb, pinfo, tag_ptree, pdv, offset, grp, elm, vl, vl_max, vr, &tag_value);
3289
3290 /* -------------------------------------------------------------
3291 We have decoded the value. Now store those tags of interest
3292 -------------------------------------------------------------
3293 */
3294
3295 /* Store SOP Class and Instance UID in first PDV of this object */
3296 if (grp == 0x0008 && elm == 0x0016) {
3297 dcm_state_pdv_get_obj_start(pdv)->sop_class_uid = wmem_strdup(wmem_file_scope(), tag_value);
3298 }
3299 else if (grp == 0x0008 && elm == 0x0018) {
3300 dcm_state_pdv_get_obj_start(pdv)->sop_instance_uid = wmem_strdup(wmem_file_scope(), tag_value);
3301 }
3302 else if (grp == 0x0000 && elm == 0x0100) {
3303 /* This is the command tag -> overwrite existing PDV description */
3304 pdv->desc = wmem_strdup(wmem_file_scope(), tag_value);
3305 }
3306 }
3307
3308
3309 /* -------------------------------------------------------------------
3310 Add the value to the already constructed item
3311 -------------------------------------------------------------------
3312 */
3313
3314 proto_item_append_text(tag_pitem, " %s", tag_value);
3315
3316 if (tag_def->add_to_summary) {
3317 *tag_description = wmem_strdup(pinfo->pool, g_strstrip(tag_value));
3318 }
3319
3320 return offset;
3321 }
3322
3323 /*
3324 'Decode' open tags from previous PDV. It mostly ends in 'continuation' or 'end' in the description.
3325 */
3326 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)3327 dissect_dcm_tag_open(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
3328 dcm_state_pdv_t *pdv, guint32 offset, guint32 endpos, gboolean *is_first_tag)
3329 {
3330
3331 proto_item *pitem = NULL;
3332
3333 guint32 tag_value_fragment_len = 0;
3334
3335 if ((pdv->prev) && (pdv->prev->open_tag.len_remaining > 0)) {
3336 /* Not first PDV in the given presentation context (Those don't have remaining data to parse :-) */
3337 /* And previous PDV has left overs, i.e. this is a continuation PDV */
3338
3339 if (endpos - offset >= pdv->prev->open_tag.len_remaining) {
3340 /*
3341 Remaining bytes are equal or more than we expect for the open tag
3342 Finally reach the end of this tag. Don't touch the open_tag structure
3343 of this PDV, as we may see a new open tag at the end
3344 */
3345 tag_value_fragment_len = pdv->prev->open_tag.len_remaining;
3346 pdv->is_corrupt = FALSE;
3347 }
3348 else if (pdv->is_flagvalid && pdv->is_last_fragment) {
3349 /*
3350 The tag is not yet complete, however, the flag indicates that it should be
3351 Therefore end this tag and issue an expert_add_info. Don't touch the
3352 open_tag structure of this PDV, as we may see a new open tag at the end
3353 */
3354 tag_value_fragment_len = endpos - offset;
3355 pdv->is_corrupt = TRUE;
3356 }
3357 else {
3358 /*
3359 * More to do for this tag
3360 */
3361 tag_value_fragment_len = endpos - offset;
3362
3363 /* Set data in current PDV structure */
3364 if (!pdv->open_tag.is_value_fragmented) {
3365 /* No need to do it twice or more */
3366
3367 pdv->open_tag.is_value_fragmented = TRUE;
3368 pdv->open_tag.len_total = pdv->prev->open_tag.len_total;
3369 pdv->open_tag.len_remaining = pdv->prev->open_tag.len_remaining - tag_value_fragment_len;
3370 pdv->open_tag.desc = wmem_strdup(wmem_file_scope(), pdv->prev->open_tag.desc);
3371
3372 }
3373 pdv->is_corrupt = FALSE;
3374 }
3375
3376 if (pdv->is_corrupt) {
3377 pitem = proto_tree_add_bytes_format(tree, hf_dcm_data_tag, tvb,
3378 offset, tag_value_fragment_len, NULL,
3379 "%s <incomplete>", pdv->prev->open_tag.desc);
3380
3381 expert_add_info(pinfo, pitem, &ei_dcm_data_tag);
3382
3383 }
3384 else {
3385 proto_tree_add_bytes_format(tree, hf_dcm_data_tag, tvb,
3386 offset, tag_value_fragment_len, NULL,
3387 "%s <Bytes %d - %d, %s>", pdv->prev->open_tag.desc,
3388 pdv->prev->open_tag.len_total - pdv->prev->open_tag.len_remaining + 1,
3389 pdv->prev->open_tag.len_total - pdv->prev->open_tag.len_remaining + tag_value_fragment_len,
3390 (pdv->prev->open_tag.len_remaining > tag_value_fragment_len ? "continuation" : "end") );
3391 }
3392
3393 offset += tag_value_fragment_len;
3394 *is_first_tag = FALSE;
3395 }
3396
3397 return offset;
3398 }
3399
3400 /*
3401 Decode the tag section inside a PDV. This can be a single combined dataset
3402 or DICOM natively split PDVs. Therefore it needs to resume previously opened tags.
3403 For data PDVs, only process tags when tree is set or listening to export objects tap.
3404 For command PDVs, process all tags.
3405 On export copy the content to the export buffer.
3406 */
3407 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)3408 dissect_dcm_pdv_body(
3409 tvbuff_t *tvb,
3410 packet_info *pinfo,
3411 proto_tree *tree,
3412 dcm_state_assoc_t *assoc,
3413 dcm_state_pdv_t *pdv,
3414 guint32 offset,
3415 guint32 pdv_body_len,
3416 gchar **pdv_description)
3417 {
3418 const gchar *tag_value = NULL;
3419 gboolean dummy = FALSE;
3420 guint32 startpos = offset;
3421 guint32 endpos = 0;
3422
3423 endpos = offset + pdv_body_len;
3424
3425 if (pdv->is_command || tree || have_tap_listener(dicom_eo_tap)) {
3426 /* Performance optimization starts here. Don't put any COL_INFO related stuff in here */
3427
3428 if (pdv->syntax == DCM_UNK) {
3429 /* Eventually, we will have a syntax detector. Until then, don't decode */
3430
3431 proto_tree_add_bytes_format(tree, hf_dcm_data_tag, tvb,
3432 offset, pdv_body_len, NULL,
3433 "(%04x,%04x) %-8x Unparsed data", 0, 0, pdv_body_len);
3434 }
3435 else {
3436
3437 gboolean is_first_tag = TRUE;
3438
3439 /* Treat the left overs */
3440 offset = dissect_dcm_tag_open(tvb, pinfo, tree, pdv, offset, endpos, &is_first_tag);
3441
3442 /* Decode all tags, sequences and items in this PDV recursively */
3443 while (offset < endpos) {
3444 offset = dissect_dcm_tag(tvb, pinfo, tree, pdv, offset, endpos, is_first_tag, &tag_value, &dummy);
3445 is_first_tag = FALSE;
3446 }
3447 }
3448 }
3449
3450 *pdv_description = pdv->desc;
3451
3452 if (pdv->is_command) {
3453
3454 if (pdv->is_warning) {
3455 if (pdv->comment) {
3456 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (%s, %s)", pdv->desc, pdv->status, pdv->comment);
3457 }
3458 else {
3459 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (%s)", pdv->desc, pdv->status);
3460 }
3461
3462 }
3463 else if (global_dcm_cmd_details) {
3464 /* Show command details in header */
3465
3466 if (pdv->message_id > 0) {
3467 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s ID=%d", pdv->desc, pdv->message_id);
3468 }
3469 else if (pdv->message_id_resp > 0) {
3470
3471 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s ID=%d", pdv->desc, pdv->message_id_resp);
3472
3473 if (pdv->no_completed > 0) {
3474 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s C=%d", *pdv_description, pdv->no_completed);
3475 }
3476 if (pdv->no_remaining > 0) {
3477 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s R=%d", *pdv_description, pdv->no_remaining);
3478 }
3479 if (pdv->no_warning > 0) {
3480 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s W=%d", *pdv_description, pdv->no_warning);
3481 }
3482 if (pdv->no_failed > 0) {
3483 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s F=%d", *pdv_description, pdv->no_failed);
3484 }
3485 if (!pdv->is_pending && pdv->status)
3486 {
3487 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (%s)", *pdv_description, pdv->status);
3488 }
3489 }
3490 }
3491 }
3492
3493 if (have_tap_listener(dicom_eo_tap)) {
3494
3495 if (pdv->data_len == 0) {
3496 /* Copy pure DICOM data to buffer, without PDV flags
3497 Packet scope for the memory allocation is too small, since we may have PDV in different tvb.
3498 Therefore check if this was already done.
3499 */
3500 pdv->data = wmem_alloc0(wmem_file_scope(), pdv_body_len);
3501 pdv->data_len = pdv_body_len;
3502 tvb_memcpy(tvb, pdv->data, startpos, pdv_body_len);
3503 }
3504 if ((pdv_body_len > 0) && (pdv->is_last_fragment)) {
3505 /* At the last segment, merge all related previous PDVs and copy to export buffer */
3506 dcm_export_create_object(pinfo, assoc, pdv);
3507 }
3508 }
3509
3510 return endpos;
3511 }
3512
3513 /*
3514 Handle one PDV inside a data PDU. When needed, perform the reassembly of PDV fragments.
3515 PDV fragments are different from TCP fragmentation.
3516 Create PDV object when needed.
3517 Return pdv_description to be used e.g. in COL_INFO.
3518 */
3519 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)3520 dissect_dcm_pdv_fragmented(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
3521 dcm_state_assoc_t *assoc, guint32 offset, guint32 pdv_len, gchar **pdv_description)
3522 {
3523
3524 conversation_t *conv = NULL;
3525
3526 dcm_state_pdv_t *pdv = NULL;
3527
3528 tvbuff_t *combined_tvb = NULL;
3529 fragment_head *head = NULL;
3530
3531 guint32 reassembly_id;
3532 guint32 pdv_body_len;
3533
3534 pdv_body_len = pdv_len-2;
3535
3536 /* Dissect Context ID, Find PDV object, Decode Command/Data flag and More Fragments flag */
3537 offset = dissect_dcm_pdv_header(tvb, pinfo, tree, assoc, offset, &pdv);
3538
3539 if (global_dcm_reassemble)
3540 {
3541 /* Combine the different PDVs. This is the default preference and useful in most scenarios.
3542 This will create one 'huge' PDV. E.g. a CT image will fits in one buffer.
3543 */
3544 conv = find_conversation_pinfo(pinfo, 0);
3545
3546 /* Try to create somewhat unique ID.
3547 Include the conversation index, to separate TCP session
3548 Include bits from the reassembly number in the current Presentation
3549 Context (that we track ourselves) in order to distinguish between
3550 PDV fragments from the same frame but different reassemblies.
3551 */
3552 DISSECTOR_ASSERT(conv);
3553
3554 /* The following expression seems to executed late in VS2017 in 'RelWithDebInf'.
3555 Therefore it may appear as 0 at first
3556 */
3557 reassembly_id = (((conv->conv_index) & 0x000FFFFF) << 12) +
3558 ((guint32)(pdv->pctx_id) << 4) + ((guint32)(pdv->reassembly_id & 0xF));
3559
3560 /* This one will chain the packets until 'is_last_fragment' */
3561 head = fragment_add_seq_next(
3562 &dcm_pdv_reassembly_table,
3563 tvb,
3564 offset,
3565 pinfo,
3566 reassembly_id,
3567 NULL,
3568 pdv_body_len,
3569 !(pdv->is_last_fragment));
3570
3571 if (head && (head->next == NULL)) {
3572 /* Was not really fragmented, therefore use 'conventional' decoding.
3573 process_reassembled_data() does not cope with two PDVs in the same frame, therefore catch it here
3574 */
3575
3576 offset = dissect_dcm_pdv_body(tvb, pinfo, tree, assoc, pdv, offset, pdv_body_len, pdv_description);
3577 }
3578 else
3579 {
3580 /* Will return a complete buffer, once last fragment is hit.
3581 The description is not used in packet-dcm. COL_INFO is set specifically in dissect_dcm_pdu()
3582 */
3583 combined_tvb = process_reassembled_data(
3584 tvb,
3585 offset,
3586 pinfo,
3587 "Reassembled PDV",
3588 head,
3589 &dcm_pdv_fragment_items,
3590 NULL,
3591 tree);
3592
3593 if (combined_tvb == NULL) {
3594 /* Just show this as a fragment */
3595
3596 if (head && head->reassembled_in != pinfo->num) {
3597
3598 if (pdv->desc) {
3599 /* We know the presentation context already */
3600 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (reassembled in #%u)", pdv->desc, head->reassembled_in);
3601 }
3602 else {
3603 /* Decoding of the presentation context did not occur yet or did not succeed */
3604 *pdv_description = wmem_strdup_printf(pinfo->pool, "PDV Fragment (reassembled in #%u)", head->reassembled_in);
3605 }
3606 }
3607 else {
3608 /* We don't know the last fragment yet (and/or we'll never see it).
3609 This can happen, e.g. when TCP packet arrive our of order.
3610 */
3611 *pdv_description = wmem_strdup(pinfo->pool, "PDV Fragment");
3612 }
3613
3614 offset += pdv_body_len;
3615 }
3616 else {
3617 /* Decode reassembled data. This needs to be += */
3618 offset += dissect_dcm_pdv_body(combined_tvb, pinfo, tree, assoc, pdv, 0, tvb_captured_length(combined_tvb), pdv_description);
3619 }
3620 }
3621 }
3622 else {
3623 /* Do not reassemble DICOM PDVs, i.e. decode PDVs one by one.
3624 This may be useful when troubleshooting PDU length issues,
3625 or to better understand the PDV split.
3626 The tag level decoding is more challenging, as leftovers need
3627 to be displayed adequately. Not a big deal for binary values.
3628 */
3629 offset = dissect_dcm_pdv_body(tvb, pinfo, tree, assoc, pdv, offset, pdv_body_len, pdv_description);
3630 }
3631
3632 return offset;
3633 }
3634
3635 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)3636 dissect_dcm_pdu_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
3637 dcm_state_assoc_t *assoc, guint32 offset, guint32 pdu_len, gchar **pdu_data_description)
3638 {
3639
3640 /* 04 P-DATA-TF
3641 1 1 reserved
3642 2 4 length
3643 - (1+) presentation data value (PDV) items
3644 6 4 length
3645 10 1 Presentation Context ID (odd ints 1 - 255)
3646 - PDV
3647 11 1 header
3648 0x01 if set, contains Message Command info, else Message Data
3649 0x02 if set, contains last fragment
3650 */
3651
3652 proto_tree *pdv_ptree; /* Tree for item details */
3653 proto_item *pdv_pitem, *pdvlen_item;
3654
3655 gchar *buf_desc = NULL; /* PDU description */
3656 gchar *pdv_description = NULL;
3657
3658 gboolean first_pdv = TRUE;
3659
3660 guint32 endpos = 0;
3661 guint32 pdv_len = 0;
3662
3663 endpos = offset + pdu_len;
3664
3665 /* Loop through multiple PDVs */
3666 while (offset < endpos) {
3667
3668 pdv_len = tvb_get_ntohl(tvb, offset);
3669
3670 pdv_ptree = proto_tree_add_subtree(tree, tvb, offset, pdv_len+4, ett_dcm_data_pdv, &pdv_pitem, "PDV");
3671
3672 pdvlen_item = proto_tree_add_item(pdv_ptree, hf_dcm_pdv_len, tvb, offset, 4, ENC_BIG_ENDIAN);
3673 offset += 4;
3674
3675 if ((pdv_len + 4 > pdu_len) || (pdv_len + 4 < pdv_len)) {
3676 expert_add_info_format(pinfo, pdvlen_item, &ei_dcm_pdv_len, "Invalid PDV length (too large)");
3677 return endpos;
3678 }
3679 else if (pdv_len <= 2) {
3680 expert_add_info_format(pinfo, pdvlen_item, &ei_dcm_pdv_len, "Invalid PDV length (too small)");
3681 return endpos;
3682 }
3683 else if (((pdv_len >> 1) << 1) != pdv_len) {
3684 expert_add_info_format(pinfo, pdvlen_item, &ei_dcm_pdv_len, "Invalid PDV length (not even)");
3685 return endpos;
3686 }
3687
3688 offset = dissect_dcm_pdv_fragmented(tvb, pinfo, pdv_ptree, assoc, offset, pdv_len, &pdv_description);
3689
3690 /* The following doesn't seem to work anymore */
3691 if (pdv_description) {
3692 if (first_pdv) {
3693 buf_desc = wmem_strdup(pinfo->pool, pdv_description);
3694 }
3695 else {
3696 buf_desc = wmem_strdup_printf(pinfo->pool, "%s, %s", buf_desc, pdv_description);
3697 }
3698 }
3699
3700 proto_item_append_text(pdv_pitem, ", %s", pdv_description);
3701 first_pdv=FALSE;
3702
3703 }
3704
3705 *pdu_data_description = buf_desc;
3706
3707 return offset;
3708 }
3709
3710
3711 /*
3712 Test for DICOM traffic.
3713
3714 - Minimum 10 Bytes
3715 - Look for the association request
3716 - Check PDU size vs TCP payload size
3717
3718 Since used in heuristic mode, be picky for performance reasons.
3719 We are called in static mode, once we decoded the association request and called conversation_set_dissector()
3720 They we can be more liberal on the packet selection
3721 */
3722 static gboolean
test_dcm(tvbuff_t * tvb)3723 test_dcm(tvbuff_t *tvb)
3724 {
3725
3726 guint8 pdu_type;
3727 guint32 pdu_len;
3728 guint16 vers;
3729
3730 /*
3731 Ensure that the tvb_captured_length is big enough before fetching the values.
3732 Otherwise it can trigger an exception during the heuristic check,
3733 preventing next heuristic dissectors from being called
3734
3735 tvb_reported_length() is the real size of the packet as transmitted on the wire
3736 tvb_captured_length() is the number of bytes captured (so you always have captured <= reported).
3737
3738 The 10 bytes represent an association request header including the 2 reserved bytes not used below
3739 In the captures at hand, the parsing result was equal.
3740 */
3741
3742 if (tvb_captured_length(tvb) < 8) {
3743 return FALSE;
3744 }
3745 if (tvb_reported_length(tvb) < 10) {
3746 return FALSE;
3747 }
3748
3749 pdu_type = tvb_get_guint8(tvb, 0);
3750 pdu_len = tvb_get_ntohl(tvb, 2);
3751 vers = tvb_get_ntohs(tvb, 6);
3752
3753 /* Exit, if not an association request at version 1 */
3754 if (!(pdu_type == 1 && vers == 1)) {
3755 return FALSE;
3756 }
3757
3758 /* Exit if TCP payload is bigger than PDU length (plus header)
3759 OK for PRESENTATION_DATA, questionable for ASSOCIATION requests
3760 */
3761 if (tvb_reported_length(tvb) > pdu_len + 6) {
3762 return FALSE;
3763 }
3764
3765 return TRUE;
3766 }
3767
3768 /*
3769 Main function to decode DICOM traffic. Supports reassembly of TCP packets.
3770 */
3771 static int
dissect_dcm_main(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,gboolean is_port_static)3772 dissect_dcm_main(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, gboolean is_port_static)
3773 {
3774
3775 guint8 pdu_type = 0;
3776 guint32 pdu_start = 0;
3777 guint32 pdu_len = 0;
3778 guint32 tlen = 0;
3779
3780 int offset = 0;
3781
3782 /*
3783 TCP packets are assembled well by wireshark in conjunction with the dissectors.
3784
3785 Therefore, we will only see properly aligned PDUs, at the beginning of the buffer.
3786 So if the buffer does not start with the PDU header, it's not DICOM traffic.
3787
3788 Do the byte checking as early as possible.
3789 The heuristic hook requires an association request
3790
3791 DICOM PDU are nice, but need to be managed
3792
3793 We can have any combination:
3794 - One or more DICOM PDU per TCP packet
3795 - PDU split over different TCP packets
3796 - And both together, i.e. some complete PDUs and then a fraction of a new PDU in a TCP packet
3797
3798 This function will handle multiple PDUs per TCP packet and will ask for more data,
3799 if the last PDU does not fit
3800
3801 It does not reassemble fragmented PDVs by purpose, since the Tag Value parsing needs to be done
3802 per Tag, and PDU recombination here would
3803 a) need to eliminate PDU/PDV/Ctx header (12 bytes)
3804 b) not show the true DICOM logic in transfer
3805
3806 The length check is tricky. If not a PDV continuation, 10 Bytes are required. For PDV continuation
3807 anything seems to be possible, depending on the buffer alignment of the sending process.
3808
3809 */
3810
3811 tlen = tvb_reported_length(tvb);
3812
3813 pdu_type = tvb_get_guint8(tvb, 0);
3814 if (pdu_type == 0 || pdu_type > 7) /* Wrong PDU type. 'Or' is slightly more efficient than 'and' */
3815 return 0; /* No bytes taken from the stack */
3816
3817 if (is_port_static) {
3818 /* Port is defined explicitly, or association request was previously found successfully.
3819 Be more tolerant on minimum packet size. Also accept < 6
3820 */
3821
3822 if (tlen < 6) {
3823 /* we need 6 bytes at least to get PDU length */
3824 pinfo->desegment_offset = offset;
3825 pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
3826 return tvb_captured_length(tvb);
3827 }
3828 }
3829
3830
3831 /* Passing this point, we should always have tlen >= 6 */
3832
3833 pdu_len = tvb_get_ntohl(tvb, 2);
3834 if (pdu_len < 4) /* The smallest PDUs are ASSOC Rejects & Release messages */
3835 return 0;
3836
3837 /* Mark it. This is a DICOM packet */
3838
3839 col_set_str(pinfo->cinfo, COL_PROTOCOL, "DICOM");
3840
3841 /* Process all PDUs in the buffer */
3842 while (pdu_start < tlen) {
3843 guint32 old_pdu_start;
3844
3845 if ((pdu_len+6) > (tlen-offset)) {
3846
3847 /* PDU is larger than the remaining packet (buffer), therefore request whole PDU
3848 The next time this function is called, tlen will be equal to pdu_len
3849 */
3850
3851 pinfo->desegment_offset = offset;
3852 pinfo->desegment_len = (pdu_len+6) - (tlen-offset);
3853 return tvb_captured_length(tvb);
3854 }
3855
3856 /* Process a whole PDU */
3857 offset=dissect_dcm_pdu(tvb, pinfo, tree, pdu_start);
3858
3859 /* Next PDU */
3860 old_pdu_start = pdu_start;
3861 pdu_start = pdu_start + pdu_len + 6;
3862 if (pdu_start <= old_pdu_start) {
3863 expert_add_info_format(pinfo, NULL, &ei_dcm_invalid_pdu_length, "Invalid PDU length (%u)", pdu_len);
3864 break;
3865 }
3866
3867 if (pdu_start < tlen - 6) {
3868 /* we got at least 6 bytes of the next PDU still in the buffer */
3869 pdu_len = tvb_get_ntohl(tvb, pdu_start+2);
3870 }
3871 else {
3872 pdu_len = 0;
3873 }
3874 }
3875 return offset;
3876 }
3877
3878 /*
3879 Callback function used to register
3880 */
3881 static int
dissect_dcm_static(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)3882 dissect_dcm_static(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
3883 {
3884 /* Less checking on ports that match */
3885 return dissect_dcm_main(tvb, pinfo, tree, TRUE);
3886 }
3887
3888 /*
3889 Test for an Association Request. Decode, when successful.
3890 */
3891 static gboolean
dissect_dcm_heuristic(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,void * data _U_)3892 dissect_dcm_heuristic(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
3893 {
3894
3895 /* This will be potentially called for every packet */
3896
3897 if (!test_dcm(tvb))
3898 return FALSE;
3899
3900 /*
3901 Conversation_set_dissector() is called inside dcm_state_get() once
3902 we have enough details. From there on, we will be 'static'
3903 */
3904
3905 if (dissect_dcm_main(tvb, pinfo, tree, FALSE) == 0) {
3906 /* there may have been another reason why it is not DICOM */
3907 return FALSE;
3908 }
3909
3910 return TRUE;
3911
3912 }
3913
3914 /*
3915 Only set a valued with col_set_str() if it does not yet exist.
3916 (In a multiple PDV scenario, col_set_str() actually appends for the subsequent calls)
3917 */
col_set_str_conditional(column_info * cinfo,const gint el,const gchar * str)3918 static void col_set_str_conditional(column_info *cinfo, const gint el, const gchar* str)
3919 {
3920 const char *col_string = col_get_text(cinfo, el);
3921
3922 if (col_string == NULL || !g_str_has_prefix(col_string, str))
3923 {
3924 col_add_str(cinfo, el, str);
3925 }
3926 }
3927
3928 /*
3929 CSV add a value to a column, if it does not exist yet
3930 */
col_append_str_conditional(column_info * cinfo,const gint el,const gchar * str)3931 static void col_append_str_conditional(column_info *cinfo, const gint el, const gchar* str)
3932 {
3933 const char *col_string = col_get_text(cinfo, el);
3934
3935 if (col_string == NULL || !g_strrstr(col_string, str))
3936 {
3937 col_append_fstr(cinfo, el, ", %s", str);
3938 }
3939 }
3940
3941 /*
3942 Dissect a single DICOM PDU. Can be an association or a data package. Creates a tree item.
3943 */
3944 static guint32
dissect_dcm_pdu(tvbuff_t * tvb,packet_info * pinfo,proto_tree * tree,guint32 offset)3945 dissect_dcm_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint32 offset)
3946 {
3947 proto_tree *dcm_ptree=NULL; /* Root DICOM tree and its item */
3948 proto_item *dcm_pitem=NULL;
3949
3950 dcm_state_t *dcm_data=NULL;
3951 dcm_state_assoc_t *assoc=NULL;
3952
3953 guint8 pdu_type=0;
3954 guint32 pdu_len=0;
3955
3956 gchar *pdu_data_description=NULL;
3957
3958 /* Get or create conversation. Used to store context IDs and xfer Syntax */
3959
3960 dcm_data = dcm_state_get(pinfo, TRUE);
3961 if (dcm_data == NULL) { /* Internal error. Failed to create main DICOM data structure */
3962 return offset;
3963 }
3964
3965 dcm_pitem = proto_tree_add_item(tree, proto_dcm, tvb, offset, -1, ENC_NA);
3966 dcm_ptree = proto_item_add_subtree(dcm_pitem, ett_dcm);
3967
3968 /* PDU type is only one byte, then one byte reserved */
3969 pdu_type = tvb_get_guint8(tvb, offset);
3970 proto_tree_add_item(dcm_ptree, hf_dcm_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN);
3971 offset += 2;
3972
3973 pdu_len = tvb_get_ntohl(tvb, offset);
3974 proto_tree_add_item(dcm_ptree, hf_dcm_pdu_len, tvb, offset, 4, ENC_BIG_ENDIAN);
3975 offset += 4;
3976
3977 /* Find previously detected association, else create a new one object*/
3978 assoc = dcm_state_assoc_get(dcm_data, pinfo->num, TRUE);
3979
3980 if (assoc == NULL) { /* Internal error. Failed to create association structure */
3981 return offset;
3982 }
3983
3984 if (pdu_type == 4) {
3985
3986 col_set_str_conditional(pinfo->cinfo, COL_INFO, "P-DATA");
3987
3988 /* Everything that needs to be shown in any UI column (like COL_INFO)
3989 needs to be calculated also with tree == null
3990 */
3991 offset = dissect_dcm_pdu_data(tvb, pinfo, dcm_ptree, assoc, offset, pdu_len, &pdu_data_description);
3992
3993 if (pdu_data_description) {
3994 proto_item_append_text(dcm_pitem, ", %s", pdu_data_description);
3995 col_append_str_conditional(pinfo->cinfo, COL_INFO, pdu_data_description);
3996 }
3997 }
3998 else {
3999
4000 /* Decode Association request, response, reject, abort details */
4001 offset = dissect_dcm_assoc_header(tvb, pinfo, dcm_ptree, offset, assoc, pdu_type, pdu_len);
4002 }
4003
4004 return offset; /* return the number of processed bytes */
4005 }
4006
4007
4008 /*
4009 Register the protocol with Wireshark
4010 */
4011 void
proto_register_dcm(void)4012 proto_register_dcm(void)
4013 {
4014 static hf_register_info hf[] = {
4015 { &hf_dcm_pdu_type, { "PDU Type", "dicom.pdu.type",
4016 FT_UINT8, BASE_HEX, VALS(dcm_pdu_ids), 0, NULL, HFILL } },
4017 { &hf_dcm_pdu_len, { "PDU Length", "dicom.pdu.len",
4018 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4019
4020 { &hf_dcm_assoc_version, { "Protocol Version", "dicom.assoc.version",
4021 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4022 { &hf_dcm_assoc_called, { "Called AE Title", "dicom.assoc.ae.called",
4023 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4024 { &hf_dcm_assoc_calling, { "Calling AE Title", "dicom.assoc.ae.calling",
4025 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4026 { &hf_dcm_assoc_reject_result, { "Result", "dicom.assoc.reject.result",
4027 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4028 { &hf_dcm_assoc_reject_source, { "Source", "dicom.assoc.reject.source",
4029 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4030 { &hf_dcm_assoc_reject_reason, { "Reason", "dicom.assoc.reject.reason",
4031 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4032 { &hf_dcm_assoc_abort_source, { "Source", "dicom.assoc.abort.source",
4033 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4034 { &hf_dcm_assoc_abort_reason, { "Reason", "dicom.assoc.abort.reason",
4035 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4036 { &hf_dcm_assoc_item_type, { "Item Type", "dicom.assoc.item.type",
4037 FT_UINT8, BASE_HEX, VALS(dcm_assoc_item_type), 0, NULL, HFILL } },
4038 { &hf_dcm_assoc_item_len, { "Item Length", "dicom.assoc.item.len",
4039 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4040
4041 { &hf_dcm_actx, { "Application Context", "dicom.actx",
4042 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4043 { &hf_dcm_pctx_id, { "Presentation Context ID", "dicom.pctx.id",
4044 FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } },
4045 { &hf_dcm_pctx_result, { "Presentation Context Result", "dicom.pctx.result",
4046 FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } },
4047 { &hf_dcm_pctx_abss_syntax, { "Abstract Syntax", "dicom.pctx.abss.syntax",
4048 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4049 { &hf_dcm_pctx_xfer_syntax, { "Transfer Syntax", "dicom.pctx.xfer.syntax",
4050 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4051 { &hf_dcm_info, { "User Info", "dicom.userinfo",
4052 FT_NONE, BASE_NONE, NULL, 0, "This field contains the ACSE User Information Item of the A-ASSOCIATErequest.", HFILL } },
4053 { &hf_dcm_info_uid, { "Implementation Class UID", "dicom.userinfo.uid",
4054 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4055 { &hf_dcm_info_version, { "Implementation Version", "dicom.userinfo.version",
4056 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4057 { &hf_dcm_info_extneg, { "Extended Negotiation", "dicom.userinfo.extneg",
4058 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 } },
4059 { &hf_dcm_info_extneg_sopclassuid_len, { "SOP Class UID Length", "dicom.userinfo.extneg.sopclassuid.len",
4060 FT_UINT16, BASE_DEC, NULL, 0, "This field contains the length of the SOP Class UID in the Extended Negotiation Sub-Item.", HFILL } },
4061 { &hf_dcm_info_extneg_sopclassuid, { "SOP Class UID", "dicom.userinfo.extneg.sopclassuid",
4062 FT_STRING, BASE_NONE, NULL, 0, "This field contains the SOP Class UID in the Extended Negotiation Sub-Item.", HFILL } },
4063 { &hf_dcm_info_extneg_relational_query, { "Relational-queries", "dicom.userinfo.extneg.relational",
4064 FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if relational queries are supported.", HFILL } },
4065 { &hf_dcm_info_extneg_date_time_matching, { "Combined Date-Time matching", "dicom.userinfo.extneg.datetimematching",
4066 FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if combined date-time matching is supported.", HFILL } },
4067 { &hf_dcm_info_extneg_fuzzy_semantic_matching, { "Fuzzy semantic matching", "dicom.userinfo.extneg.fuzzymatching",
4068 FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if fuzzy semantic matching of person names is supported.", HFILL } },
4069 { &hf_dcm_info_extneg_timezone_query_adjustment, { "Timezone query adjustment", "dicom.userinfo.extneg.timezone",
4070 FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if timezone query adjustment is supported.", HFILL } },
4071 { &hf_dcm_info_rolesel, { "SCP/SCU Role Selection", "dicom.userinfo.rolesel",
4072 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 } },
4073 { &hf_dcm_info_rolesel_sopclassuid_len, { "SOP Class UID Length", "dicom.userinfo.rolesel.sopclassuid.len",
4074 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 } },
4075 { &hf_dcm_info_rolesel_sopclassuid, { "SOP Class UID", "dicom.userinfo.rolesel.sopclassuid",
4076 FT_STRING, BASE_NONE, NULL, 0, "This field contains the SOP Class UID in the SCP/SCU Role Selection Sub-Item.", HFILL } },
4077 { &hf_dcm_info_rolesel_scurole, { "SCU-role", "dicom.userinfo.rolesel.scurole",
4078 FT_UINT8, BASE_HEX, NULL, 0, "This field contains the SCU-role as defined for the Association-requester.", HFILL } },
4079 { &hf_dcm_info_rolesel_scprole, { "SCP-role", "dicom.userinfo.rolesel.scprole",
4080 FT_UINT8, BASE_HEX, NULL, 0, "This field contains the SCP-role as defined for the Association-requester.", HFILL } },
4081 { &hf_dcm_info_async_neg, { "Asynchronous Operations (and sub-operations) Window Negotiation", "dicom.userinfo.asyncneg",
4082 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 } },
4083 { &hf_dcm_info_async_neg_max_num_ops_inv, { "Maximum-number-operations-invoked", "dicom.userinfo.asyncneg.maxnumopsinv",
4084 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 } },
4085 { &hf_dcm_info_async_neg_max_num_ops_per, { "Maximum-number-operations-performed", "dicom.userinfo.asyncneg.maxnumopsper",
4086 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 } },
4087 { &hf_dcm_info_unknown, { "Unknown", "dicom.userinfo.unknown",
4088 FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } },
4089 { &hf_dcm_assoc_item_data, { "Unknown Data", "dicom.userinfo.data",
4090 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } },
4091 { &hf_dcm_info_user_identify, { "User Identify", "dicom.userinfo.user_identify",
4092 FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } },
4093 { &hf_dcm_info_user_identify_type, { "Type", "dicom.userinfo.user_identify.type",
4094 FT_UINT8, BASE_DEC, VALS(user_identify_type_vals), 0, NULL, HFILL } },
4095 { &hf_dcm_info_user_identify_response_requested, { "Response Requested", "dicom.userinfo.user_identify.response_requested",
4096 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4097 { &hf_dcm_info_user_identify_primary_field_length, { "Primary Field Length", "dicom.userinfo.user_identify.primary_field_length",
4098 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4099 { &hf_dcm_info_user_identify_primary_field, { "Primary Field", "dicom.userinfo.user_identify.primary_field",
4100 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4101 { &hf_dcm_info_user_identify_secondary_field_length, { "Secondary Field Length", "dicom.userinfo.user_identify.secondary_field_length",
4102 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4103 { &hf_dcm_info_user_identify_secondary_field, { "Secondary Field", "dicom.userinfo.user_identify.secondary_field",
4104 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4105 { &hf_dcm_pdu_maxlen, { "Max PDU Length", "dicom.max_pdu_len",
4106 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4107 { &hf_dcm_pdv_len, { "PDV Length", "dicom.pdv.len",
4108 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4109 { &hf_dcm_pdv_ctx, { "PDV Context", "dicom.pdv.ctx",
4110 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4111 { &hf_dcm_pdv_flags, { "PDV Flags", "dicom.pdv.flags",
4112 FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } },
4113 { &hf_dcm_data_tag, { "Tag", "dicom.data.tag",
4114 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } },
4115
4116 { &hf_dcm_tag, { "Tag", "dicom.tag",
4117 FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } },
4118 { &hf_dcm_tag_vr, { "VR", "dicom.tag.vr",
4119 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4120 { &hf_dcm_tag_vl, { "Length", "dicom.tag.vl",
4121 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4122
4123 { &hf_dcm_tag_value_str, { "Value", "dicom.tag.value.str",
4124 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4125 { &hf_dcm_tag_value_16s, { "Value", "dicom.tag.value.16s",
4126 FT_INT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4127 { &hf_dcm_tag_value_16u, { "Value", "dicom.tag.value.16u",
4128 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4129 { &hf_dcm_tag_value_32s, { "Value", "dicom.tag.value.32s",
4130 FT_INT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4131 { &hf_dcm_tag_value_32u, { "Value", "dicom.tag.value.32u",
4132 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4133 { &hf_dcm_tag_value_byte, { "Value", "dicom.tag.value.byte",
4134 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } },
4135
4136 /* Fragment entries */
4137 { &hf_dcm_pdv_fragments,
4138 { "Message fragments", "dicom.pdv.fragments",
4139 FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4140 { &hf_dcm_pdv_fragment,
4141 { "Message fragment", "dicom.pdv.fragment",
4142 FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4143 { &hf_dcm_pdv_fragment_overlap,
4144 { "Message fragment overlap", "dicom.pdv.fragment.overlap",
4145 FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4146 { &hf_dcm_pdv_fragment_overlap_conflicts,
4147 { "Message fragment overlapping with conflicting data",
4148 "dicom.pdv.fragment.overlap.conflicts",
4149 FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4150 { &hf_dcm_pdv_fragment_multiple_tails,
4151 { "Message has multiple tail fragments",
4152 "dicom.pdv.fragment.multiple_tails",
4153 FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4154 { &hf_dcm_pdv_fragment_too_long_fragment,
4155 { "Message fragment too long", "dicom.pdv.fragment.too_long_fragment",
4156 FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4157 { &hf_dcm_pdv_fragment_error,
4158 { "Message defragmentation error", "dicom.pdv.fragment.error",
4159 FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4160 { &hf_dcm_pdv_fragment_count,
4161 { "Message fragment count", "dicom.pdv.fragment_count",
4162 FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } },
4163 { &hf_dcm_pdv_reassembled_in,
4164 { "Reassembled in", "dicom.pdv.reassembled.in",
4165 FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4166 { &hf_dcm_pdv_reassembled_length,
4167 { "Reassembled PDV length", "dicom.pdv.reassembled.length",
4168 FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } }
4169 };
4170
4171 /* Setup protocol subtree array */
4172 static gint *ett[] = {
4173 &ett_dcm,
4174 &ett_assoc,
4175 &ett_assoc_header,
4176 &ett_assoc_actx,
4177 &ett_assoc_pctx,
4178 &ett_assoc_pctx_abss,
4179 &ett_assoc_pctx_xfer,
4180 &ett_assoc_info,
4181 &ett_assoc_info_uid,
4182 &ett_assoc_info_version,
4183 &ett_assoc_info_extneg,
4184 &ett_assoc_info_rolesel,
4185 &ett_assoc_info_async_neg,
4186 &ett_assoc_info_user_identify,
4187 &ett_assoc_info_unknown,
4188 &ett_dcm_data,
4189 &ett_dcm_data_pdv,
4190 &ett_dcm_data_tag,
4191 &ett_dcm_data_seq,
4192 &ett_dcm_data_item,
4193 &ett_dcm_pdv, /* used for fragments */
4194 &ett_dcm_pdv_fragment,
4195 &ett_dcm_pdv_fragments
4196 };
4197
4198 static ei_register_info ei[] = {
4199 { &ei_dcm_assoc_rejected, { "dicom.assoc.reject", PI_RESPONSE_CODE, PI_WARN, "Association rejected", EXPFILL }},
4200 { &ei_dcm_assoc_aborted, { "dicom.assoc.abort", PI_RESPONSE_CODE, PI_WARN, "Association aborted", EXPFILL }},
4201 { &ei_dcm_no_abstract_syntax, { "dicom.no_abstract_syntax", PI_MALFORMED, PI_ERROR, "No Abstract Syntax provided for this Presentation Context", EXPFILL }},
4202 { &ei_dcm_multiple_abstract_syntax, { "dicom.multiple_abstract_syntax", PI_MALFORMED, PI_ERROR, "More than one Abstract Syntax provided for this Presentation Context", EXPFILL }},
4203 { &ei_dcm_no_transfer_syntax, { "dicom.no_transfer_syntax", PI_MALFORMED, PI_ERROR, "No Transfer Syntax provided for this Presentation Context", EXPFILL }},
4204 { &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 }},
4205 { &ei_dcm_multiple_transfer_syntax, { "dicom.multiple_transfer_syntax", PI_MALFORMED, PI_ERROR, "Only one Transfer Syntax allowed in a Association Response", EXPFILL }},
4206 { &ei_dcm_assoc_item_len, { "dicom.assoc.item.len.invalid", PI_MALFORMED, PI_ERROR, "Invalid Association Item Length", EXPFILL }},
4207 { &ei_dcm_pdv_ctx, { "dicom.pdv.ctx.invalid", PI_MALFORMED, PI_ERROR, "Invalid Presentation Context ID", EXPFILL }},
4208 { &ei_dcm_pdv_flags, { "dicom.pdv.flags.invalid", PI_MALFORMED, PI_ERROR, "Invalid Flags", EXPFILL }},
4209 { &ei_dcm_status_msg, { "dicom.status_msg", PI_RESPONSE_CODE, PI_WARN, "%s", EXPFILL }},
4210 { &ei_dcm_data_tag, { "dicom.data.tag.missing", PI_MALFORMED, PI_ERROR, "Early termination of tag. Data is missing", EXPFILL }},
4211 { &ei_dcm_pdv_len, { "dicom.pdv.len.invalid", PI_MALFORMED, PI_ERROR, "Invalid PDV length", EXPFILL }},
4212 { &ei_dcm_invalid_pdu_length, { "dicom.pdu_length.invalid", PI_MALFORMED, PI_ERROR, "Invalid PDU length", EXPFILL }},
4213 };
4214
4215 module_t *dcm_module;
4216 expert_module_t* expert_dcm;
4217
4218 /* Register the protocol name and description */
4219 proto_dcm = proto_register_protocol("DICOM", "DICOM", "dicom");
4220
4221 /* Required function calls to register the header fields and subtrees used */
4222 proto_register_field_array(proto_dcm, hf, array_length(hf));
4223 proto_register_subtree_array(ett, array_length(ett));
4224 expert_dcm = expert_register_protocol(proto_dcm);
4225 expert_register_field_array(expert_dcm, ei, array_length(ei));
4226
4227 /* Allow other dissectors to find this one by name. */
4228 dcm_handle = register_dissector("dicom", dissect_dcm_static, proto_dcm);
4229
4230 dcm_module = prefs_register_protocol(proto_dcm, NULL);
4231
4232 /* Used to migrate an older configuration file to a newer one */
4233 prefs_register_obsolete_preference(dcm_module, "heuristic");
4234
4235 prefs_register_bool_preference(dcm_module, "export_header",
4236 "Create Meta Header on Export",
4237 "Create DICOM File Meta Header according to PS 3.10 on export for PDUs. "
4238 "If the captured PDV does not contain a SOP Class UID and SOP Instance UID "
4239 "(e.g. for command PDVs), wireshark specific ones will be created.",
4240 &global_dcm_export_header);
4241
4242 prefs_register_uint_preference(dcm_module, "export_minsize",
4243 "Min. item size in bytes to export",
4244 "Do not show items below this size in the export list. "
4245 "Set it to 0, to see DICOM commands and responses in the list. "
4246 "Set it higher, to just export DICOM IODs (i.e. CT Images, RT Structures).", 10,
4247 &global_dcm_export_minsize);
4248
4249 prefs_register_bool_preference(dcm_module, "seq_tree",
4250 "Create subtrees for Sequences and Items",
4251 "Create a node for sequences and items, and show children in a hierarchy. "
4252 "De-select this option, if you prefer a flat display or e.g. "
4253 "when using TShark to create a text output.",
4254 &global_dcm_seq_subtree);
4255
4256 prefs_register_bool_preference(dcm_module, "tag_tree",
4257 "Create subtrees for DICOM Tags",
4258 "Create a node for a tag and show tag details as single elements. "
4259 "This can be useful to debug a tag and to allow display filters on these attributes. "
4260 "When using TShark to create a text output, it's better to have it disabled. ",
4261 &global_dcm_tag_subtree);
4262
4263 prefs_register_bool_preference(dcm_module, "cmd_details",
4264 "Show command details in header",
4265 "Show message ID and number of completed, remaining, warned or failed operations in header and info column.",
4266 &global_dcm_cmd_details);
4267
4268 prefs_register_bool_preference(dcm_module, "pdv_reassemble",
4269 "Merge fragmented PDVs",
4270 "Decode all DICOM tags in the last PDV. This will ensure the proper reassembly. "
4271 "De-select, to troubleshoot PDU length issues, or to understand PDV fragmentation. "
4272 "When not set, the decoding may fail and the exports may become corrupt.",
4273 &global_dcm_reassemble);
4274
4275 dicom_eo_tap = register_export_object(proto_dcm, dcm_eo_packet, NULL);
4276
4277 register_init_routine(&dcm_init);
4278
4279 /* Register processing of fragmented DICOM PDVs */
4280 reassembly_table_register(&dcm_pdv_reassembly_table, &addresses_reassembly_table_functions);
4281
4282 }
4283
4284 /*
4285 Register static TCP port range specified in preferences.
4286 Register heuristic search as well.
4287
4288 Statically defined ports take precedence over a heuristic one. I.e., if a foreign protocol claims a port,
4289 where DICOM is running on, we would never be called, by just having the heuristic registration.
4290
4291 This function is also called, when preferences change.
4292 */
4293 void
proto_reg_handoff_dcm(void)4294 proto_reg_handoff_dcm(void)
4295 {
4296 /* Adds a UI element to the preferences dialog. This is the static part. */
4297 dissector_add_uint_range_with_preference("tcp.port", DICOM_DEFAULT_RANGE, dcm_handle);
4298
4299 /*
4300 The following shows up as child protocol of 'DICOM' in 'Enable/Disable Protocols ...'
4301
4302 The registration procedure for dissectors is a two-stage procedure.
4303
4304 In stage 1, dissectors create tables in which other dissectors can register them. That's the stage in which proto_register_ routines are called.
4305 In stage 2, dissectors register themselves in tables created in stage 1. That's the stage in which proto_reg_handoff_ routines are called.
4306
4307 heur_dissector_add() needs to be called in proto_reg_handoff_dcm() function.
4308 */
4309
4310 heur_dissector_add("tcp", dissect_dcm_heuristic, "DICOM on any TCP port (heuristic)", "dicom_tcp", proto_dcm, HEURISTIC_ENABLE);
4311 }
4312
4313
4314 /*
4315
4316 PDU's
4317 01 ASSOC-RQ
4318 1 1 reserved
4319 2 4 length
4320 6 2 protocol version (0x0 0x1)
4321 8 2 reserved
4322 10 16 dest aetitle
4323 26 16 src aetitle
4324 42 32 reserved
4325 74 - presentation data value items
4326
4327 02 A-ASSOC-AC
4328 1 reserved
4329 4 length
4330 2 protocol version (0x0 0x1)
4331 2 reserved
4332 16 dest aetitle (not checked)
4333 16 src aetitle (not checked)
4334 32 reserved
4335 - presentation data value items
4336
4337 03 ASSOC-RJ
4338 1 reserved
4339 4 length (4)
4340 1 reserved
4341 1 result (1 reject perm, 2 reject transient)
4342 1 source (1 service user, 2 service provider, 3 service provider)
4343 1 reason
4344 1 == source
4345 1 no reason given
4346 2 application context name not supported
4347 3 calling aetitle not recognized
4348 7 called aetitle not recognized
4349 2 == source
4350 1 no reason given
4351 2 protocol version not supported
4352 3 == source
4353 1 temporary congestion
4354 2 local limit exceeded
4355
4356 04 P-DATA
4357 1 1 reserved
4358 2 4 length
4359 - (1+) presentation data value (PDV) items
4360 6 4 length
4361 10 1 Presentation Context ID (odd ints 1 - 255)
4362 - PDV
4363 11 1 header
4364 0x01 if set, contains Message Command info, else Message Data
4365 0x02 if set, contains last fragment
4366
4367 05 A-RELEASE-RQ
4368 1 reserved
4369 4 length (4)
4370 4 reserved
4371
4372 06 A-RELEASE-RP
4373 1 reserved
4374 4 length (4)
4375 4 reserved
4376
4377 07 A-ABORT
4378 1 reserved
4379 4 length (4)
4380 2 reserved
4381 1 source (0 = user, 1 = provider)
4382 1 reason if 1 == source (0 not spec, 1 unrecognized, 2 unexpected 4 unrecognized param, 5 unexpected param, 6 invalid param)
4383
4384
4385
4386 ITEM's
4387 10 Application Context
4388 1 reserved
4389 2 length
4390 - name
4391
4392 20 Presentation Context
4393 1 reserved
4394 2 length
4395 1 Presentation context id
4396 3 reserved
4397 - (1) abstract and (1+) transfer syntax sub-items
4398
4399 21 Presentation Context (Reply)
4400 1 reserved
4401 2 length
4402 1 ID (odd int's 1-255)
4403 1 reserved
4404 1 result (0 accept, 1 user-reject, 2 no-reason, 3 abstract not supported, 4- transfer syntax not supported)
4405 1 reserved
4406 - (1) type 40
4407
4408 30 Abstract syntax
4409 1 reserved
4410 2 length
4411 - name (<= 64)
4412
4413 40 Transfer syntax
4414 1 reserved
4415 2 length
4416 - name (<= 64)
4417
4418 50 user information
4419 1 reserved
4420 2 length
4421 - user data
4422
4423 51 max length
4424 1 reserved
4425 2 length (4)
4426 4 max PDU lengths
4427
4428 From 3.7 Annex D Association Negotiation
4429 ========================================
4430
4431 52 IMPLEMENTATION CLASS UID
4432 1 Item-type 52H
4433 1 Reserved
4434 2 Item-length
4435 n Implementation-class-uid
4436
4437 55 IMPLEMENTATION VERSION NAME
4438 1 Item-type 55H
4439 1 Reserved
4440 2 Item-length
4441 n Implementation-version-name
4442
4443 53 ASYNCHRONOUS OPERATIONS WINDOW
4444 1 Item-type 53H
4445 1 Reserved
4446 2 Item-length
4447 2 Maximum-number-operations-invoked
4448 2 Maximum-number-operations-performed
4449
4450 54 SCP/SCU ROLE SELECTION
4451 1 Item-type 54H
4452 1 Reserved
4453 2 Item-length (n)
4454 2 UID-length (m)
4455 m SOP-class-uid
4456 1 SCU-role
4457 0 - non support of the SCU role
4458 1 - support of the SCU role
4459 1 SCP-role
4460 0 - non support of the SCP role
4461 1 - support of the SCP role.
4462
4463 56 SOP CLASS EXTENDED NEGOTIATION
4464 1 Item-type 56H
4465 1 Reserved
4466 2 Item-Length (n)
4467 2 SOP-class-uid-length (m)
4468 m SOP-class-uid
4469 n-m Service-class-application-information
4470
4471 57 SOP CLASS COMMON EXTENDED NEGOTIATION
4472 1 Item-type 57H
4473 1 Sub-item-version
4474 2 Item-Length
4475 2 SOP-class-uid-length (m)
4476 7-x SOP-class-uid The SOP Class identifier encoded as a UID as defined in PS 3.5.
4477 (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.
4478 (x+3)-y Service-class-uid The Service Class identifier encoded as a UID as defined in PS 3.5.
4479 (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.
4480 (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.
4481 (z+1)-k Reserved Reserved for additional fields of the sub-item. Shall be zero-length for Version 0 of Sub-item definition.
4482
4483 Table D.3-13
4484 RELATED-GENERAL-SOP-CLASS-IDENTIFICATION SUB-FIELDS
4485 Bytes Sub-Field Name Description of Sub-Field
4486 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.
4487 3-n Related-general-sop-class-uid The Related General SOP Class identifier encoded as a UID as defined in PS 3.5.
4488
4489 58 User Identity Negotiation
4490 1 Item-type 58H
4491 1 Reserved
4492 2 Item-length
4493 1 User-Identity-Type Field value shall be in the range 1 to 4 with the following meanings:
4494 1 - Username as a string in UTF-8
4495 2 - Username as a string in UTF-8 and passcode
4496 3 - Kerberos Service ticket
4497 4 - SAML Assertion
4498 Other values are reserved for future standardization.
4499 1 Positive-response-requested Field value:
4500 0 - no response requested
4501 1 - positive response requested
4502 2 Primary-field-length The User-Identity-Length shall contain the length of the User-Identity value.
4503 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.
4504 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.
4505 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.
4506
4507 59 User Identity Negotiation Reply
4508 1 Item-type 59H
4509 1 Reserved
4510 2 Item-length
4511 5-6 Server-response-length This field shall contain the number of bytes in the Server-response. May be zero.
4512 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.
4513
4514 */
4515
4516 /*
4517 * Editor modelines - https://www.wireshark.org/tools/modelines.html
4518 *
4519 * Local variables:
4520 * c-basic-offset: 4
4521 * tab-width: 8
4522 * indent-tabs-mode: nil
4523 * End:
4524 *
4525 * vi: set shiftwidth=4 tabstop=8 expandtab:
4526 * :indentSize=4:tabSize=8:noTabs=true:
4527 */
4528