Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / epan / dissectors / packet-dcm.c
bloba6048f45eb744496acebf4284a5f3fb98ddcac19
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>
6 * DICOM communication protocol: https://www.dicomstandard.org/current/
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
14 * Wireshark - Network traffic analyzer
15 * By Gerald Combs <gerald@wireshark.org>
16 * Copyright 1998 Gerald Combs
18 * SPDX-License-Identifier: GPL-2.0-or-later
24 * ToDo
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
30 * History
32 * Feb 2019 - David Aggeler
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
39 * Feb 2019 - Rickard Holmberg
41 * - Updated DICOM definitions to 2019a
43 * Oct 2018 - Rickard Holmberg
45 * - Moved DICOM definitions to packet-dcm.h
46 * - Generate definitions from docbook with Phyton script
47 * - Updated DICOM definitions to 2018e
49 * June 2018 - David Aggeler
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
58 * February 2018 - David Aggeler
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)
67 * Misc. authors & dates
69 * - Fixed 'AT' value representation. The 'element' was equal to the 'group'.
70 * - Changed 'FL' value representations
72 * September 2013 - Pascal Quantin
74 * - Replace all ep_ and se_ allocation with wmem_ allocations
76 * February 2013 - Stefan Allers
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
84 * Jul 11, 2010 - David Aggeler
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
95 * May 13, 2010 - David Aggeler (SVN 32815)
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
101 * May 27, 2009 - David Aggeler (SVN 29060)
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
109 * May 17, 2009 - David Aggeler (SVN 28392)
111 * - Spelling
112 * - Added expert_add_info() for status responses with warning & error level
113 * - Added command details in info column (optionally)
115 * Dec 19, 2008 to Mar 29, 2009 - Misc (SVN 27880)
117 * - Spellings, see SVN
119 * Oct 26, 2008 - David Aggeler (SVN 26662)
121 * - Support remaining DICOM/ARCNEMA tags
123 * Oct 3, 2008 - David Aggeler (SVN 26417)
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
136 * - Added all status messages from PS 3.4 & PS 3.7
137 * - Fixed two more type warnings on solaris, i.e. (char *)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 uint32_t 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()
150 * Jul 25, 2008 - David Aggeler (SVN 25834)
152 * - Replaced unsigned char with char, since it caused a lot of warnings on solaris.
153 * - Moved a little more form the include to this one to be consistent
155 * Jul 17, 2008 - David Aggeler
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
164 * Jun 17, 2008 - David Aggeler
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.
184 * May 23, 2008 - David Aggeler
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
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 unsigned char instead of uint8_t
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
208 * Apr 28, 2005 - Rich Coe
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
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)
226 * Nov 9, 2004 - Rich Coe
228 * - Fixed the heuristic code -- sometimes a conversation already exists
229 * - Fixed the dissect code to display all the tags in the PDU
231 * Initial - Rich Coe
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.
244 #include "config.h"
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>
254 #include <wsutil/str_util.h>
255 #include <wsutil/utf8_entities.h>
257 #include "packet-tcp.h"
259 #include "packet-dcm.h"
261 void proto_register_dcm(void);
262 void proto_reg_handoff_dcm(void);
264 #define DICOM_DEFAULT_RANGE "104"
266 /* Many thanks to http://medicalconnections.co.uk/ for the GUID */
267 #define WIRESHARK_IMPLEMENTATION_UID "1.2.826.0.1.3680043.8.427.10"
268 #define WIRESHARK_MEDIA_STORAGE_SOP_CLASS_UID "1.2.826.0.1.3680043.8.427.11.1"
269 #define WIRESHARK_MEDIA_STORAGE_SOP_INSTANCE_UID_PREFIX "1.2.826.0.1.3680043.8.427.11.2"
270 #define WIRESHARK_IMPLEMENTATION_VERSION "WIRESHARK"
272 static bool global_dcm_export_header = true;
273 static unsigned global_dcm_export_minsize = 4096; /* Filter small objects in export */
275 static bool global_dcm_seq_subtree = true;
276 static bool global_dcm_tag_subtree; /* Only useful for debugging */
277 static bool global_dcm_cmd_details = true; /* Show details in header and info column */
278 static bool global_dcm_reassemble = true; /* Merge fragmented PDVs */
280 static wmem_map_t *dcm_tag_table;
281 static wmem_map_t *dcm_uid_table;
282 static wmem_map_t *dcm_status_table;
284 /* Initialize the protocol and registered fields */
285 static int proto_dcm;
287 static int dicom_eo_tap;
289 static int hf_dcm_pdu_type;
290 static int hf_dcm_pdu_len;
291 static int hf_dcm_assoc_version;
292 static int hf_dcm_assoc_called;
293 static int hf_dcm_assoc_calling;
294 static int hf_dcm_assoc_reject_result;
295 static int hf_dcm_assoc_reject_source;
296 static int hf_dcm_assoc_reject_reason;
297 static int hf_dcm_assoc_abort_source;
298 static int hf_dcm_assoc_abort_reason;
299 static int hf_dcm_assoc_item_type;
300 static int hf_dcm_assoc_item_len;
301 static int hf_dcm_actx;
302 static int hf_dcm_pctx_id;
303 static int hf_dcm_pctx_result;
304 static int hf_dcm_pctx_abss_syntax;
305 static int hf_dcm_pctx_xfer_syntax;
306 static int hf_dcm_info;
307 static int hf_dcm_info_uid;
308 static int hf_dcm_info_version;
309 static int hf_dcm_info_extneg;
310 static int hf_dcm_info_extneg_sopclassuid_len;
311 static int hf_dcm_info_extneg_sopclassuid;
312 static int hf_dcm_info_extneg_relational_query;
313 static int hf_dcm_info_extneg_date_time_matching;
314 static int hf_dcm_info_extneg_fuzzy_semantic_matching;
315 static int hf_dcm_info_extneg_timezone_query_adjustment;
316 static int hf_dcm_info_rolesel;
317 static int hf_dcm_info_rolesel_sopclassuid_len;
318 static int hf_dcm_info_rolesel_sopclassuid;
319 static int hf_dcm_info_rolesel_scurole;
320 static int hf_dcm_info_rolesel_scprole;
321 static int hf_dcm_info_async_neg;
322 static int hf_dcm_info_async_neg_max_num_ops_inv;
323 static int hf_dcm_info_async_neg_max_num_ops_per;
324 static int hf_dcm_info_user_identify;
325 static int hf_dcm_info_user_identify_type;
326 static int hf_dcm_info_user_identify_response_requested;
327 static int hf_dcm_info_user_identify_primary_field_length;
328 static int hf_dcm_info_user_identify_primary_field;
329 static int hf_dcm_info_user_identify_secondary_field_length;
330 static int hf_dcm_info_user_identify_secondary_field;
331 static int hf_dcm_info_unknown;
332 static int hf_dcm_assoc_item_data;
333 static int hf_dcm_pdu_maxlen;
334 static int hf_dcm_pdv_len;
335 static int hf_dcm_pdv_ctx;
336 static int hf_dcm_pdv_flags;
337 static int hf_dcm_data_tag;
338 static int hf_dcm_tag;
339 static int hf_dcm_tag_vr;
340 static int hf_dcm_tag_vl;
341 static int hf_dcm_tag_value_str;
342 static int hf_dcm_tag_value_16u;
343 static int hf_dcm_tag_value_16s;
344 static int hf_dcm_tag_value_32s;
345 static int hf_dcm_tag_value_32u;
346 static int hf_dcm_tag_value_byte;
348 /* Initialize the subtree pointers */
349 static int ett_dcm;
350 static int ett_assoc;
351 static int ett_assoc_header;
352 static int ett_assoc_actx;
353 static int ett_assoc_pctx;
354 static int ett_assoc_pctx_abss;
355 static int ett_assoc_pctx_xfer;
356 static int ett_assoc_info;
357 static int ett_assoc_info_uid;
358 static int ett_assoc_info_version;
359 static int ett_assoc_info_extneg;
360 static int ett_assoc_info_rolesel;
361 static int ett_assoc_info_async_neg;
362 static int ett_assoc_info_user_identify;
363 static int ett_assoc_info_unknown;
364 static int ett_dcm_data;
365 static int ett_dcm_data_pdv;
366 static int ett_dcm_data_tag;
367 static int ett_dcm_data_seq;
368 static int ett_dcm_data_item;
370 static expert_field ei_dcm_data_tag;
371 static expert_field ei_dcm_multiple_transfer_syntax;
372 static expert_field ei_dcm_pdv_len;
373 static expert_field ei_dcm_pdv_flags;
374 static expert_field ei_dcm_pdv_ctx;
375 static expert_field ei_dcm_no_abstract_syntax;
376 static expert_field ei_dcm_no_abstract_syntax_uid;
377 static expert_field ei_dcm_status_msg;
378 static expert_field ei_dcm_no_transfer_syntax;
379 static expert_field ei_dcm_multiple_abstract_syntax;
380 static expert_field ei_dcm_invalid_pdu_length;
381 static expert_field ei_dcm_assoc_item_len;
382 static expert_field ei_dcm_assoc_rejected;
383 static expert_field ei_dcm_assoc_aborted;
385 static dissector_handle_t dcm_handle;
387 static const value_string dcm_pdu_ids[] = {
388 { 1, "ASSOC Request" },
389 { 2, "ASSOC Accept" },
390 { 3, "ASSOC Reject" },
391 { 4, "Data" },
392 { 5, "RELEASE Request" },
393 { 6, "RELEASE Response" },
394 { 7, "ABORT" },
395 { 0, NULL }
398 static const value_string dcm_assoc_item_type[] = {
399 { 0x10, "Application Context" },
400 { 0x20, "Presentation Context" },
401 { 0x21, "Presentation Context Reply" },
402 { 0x30, "Abstract Syntax" },
403 { 0x40, "Transfer Syntax" },
404 { 0x50, "User Info" },
405 { 0x51, "Max Length" },
406 { 0x52, "Implementation Class UID" },
407 { 0x53, "Asynchronous Operations Window Negotiation" },
408 { 0x54, "SCP/SCU Role Selection" },
409 { 0x55, "Implementation Version" },
410 { 0x56, "SOP Class Extended Negotiation" },
411 { 0x58, "User Identity" },
412 { 0, NULL }
415 static const value_string user_identify_type_vals[] = {
416 { 1, "Username as a string in UTF-8" },
417 { 2, "Username as a string in UTF-8 and passcode" },
418 { 3, "Kerberos Service ticket" },
419 { 4, "SAML Assertion" },
420 { 0, NULL }
423 /* Used for DICOM Export Object feature */
424 typedef struct _dicom_eo_t {
425 uint32_t pkt_num;
426 const char *hostname;
427 const char *filename;
428 const char *content_type;
429 uint32_t payload_len;
430 const uint8_t *payload_data;
431 } dicom_eo_t;
433 static tap_packet_status
434 dcm_eo_packet(void *tapdata, packet_info *pinfo, epan_dissect_t *edt _U_,
435 const void *data, tap_flags_t flags _U_)
437 export_object_list_t *object_list = (export_object_list_t *)tapdata;
438 const dicom_eo_t *eo_info = (const dicom_eo_t *)data;
439 export_object_entry_t *entry;
441 if (eo_info) { /* We have data waiting for us */
443 The values will be freed when the export Object window is closed.
444 Therefore, strings and buffers must be copied.
446 entry = g_new(export_object_entry_t, 1);
448 entry->pkt_num = pinfo->num;
449 entry->hostname = g_strdup(eo_info->hostname);
450 entry->content_type = g_strdup(eo_info->content_type);
451 /* g_path_get_basename() allocates a new string */
452 entry->filename = g_path_get_basename(eo_info->filename);
453 entry->payload_len = eo_info->payload_len;
454 entry->payload_data = (uint8_t *)g_memdup2(eo_info->payload_data, eo_info->payload_len);
456 object_list->add_entry(object_list->gui_data, entry);
458 return TAP_PACKET_REDRAW; /* State changed - window should be redrawn */
459 } else {
460 return TAP_PACKET_DONT_REDRAW; /* State unchanged - no window updates needed */
465 /* ************************************************************************* */
466 /* Fragment items */
467 /* ************************************************************************* */
469 /* Initialize the subtree pointers */
470 static int ett_dcm_pdv;
472 static int ett_dcm_pdv_fragment;
473 static int ett_dcm_pdv_fragments;
475 static int hf_dcm_pdv_fragments;
476 static int hf_dcm_pdv_fragment;
477 static int hf_dcm_pdv_fragment_overlap;
478 static int hf_dcm_pdv_fragment_overlap_conflicts;
479 static int hf_dcm_pdv_fragment_multiple_tails;
480 static int hf_dcm_pdv_fragment_too_long_fragment;
481 static int hf_dcm_pdv_fragment_error;
482 static int hf_dcm_pdv_fragment_count;
483 static int hf_dcm_pdv_reassembled_in;
484 static int hf_dcm_pdv_reassembled_length;
486 static const fragment_items dcm_pdv_fragment_items = {
487 /* Fragment subtrees */
488 &ett_dcm_pdv_fragment,
489 &ett_dcm_pdv_fragments,
490 /* Fragment fields */
491 &hf_dcm_pdv_fragments,
492 &hf_dcm_pdv_fragment,
493 &hf_dcm_pdv_fragment_overlap,
494 &hf_dcm_pdv_fragment_overlap_conflicts,
495 &hf_dcm_pdv_fragment_multiple_tails,
496 &hf_dcm_pdv_fragment_too_long_fragment,
497 &hf_dcm_pdv_fragment_error,
498 &hf_dcm_pdv_fragment_count,
499 &hf_dcm_pdv_reassembled_in,
500 &hf_dcm_pdv_reassembled_length,
501 /* Reassembled data field */
502 NULL,
503 /* Tag */
504 "Message fragments"
507 /* Structure to handle fragmented DICOM PDU packets */
508 static reassembly_table dcm_pdv_reassembly_table;
510 typedef struct dcm_open_tag {
512 /* Contains information about an open tag in a PDV, in case it was not complete.
514 This implementation differentiates between open headers (grm, elm, vr, vl) and
515 open values. This data structure will handle both cases.
517 Open headers are not shown in the packet where the tag starts, but only in the next PDV.
518 Open values are shown in the packet where the tag starts, with <Byte 1-n> as the value
520 The same PDV can close an open tag from a previous PDV at the beginning
521 and at the same have time open a new tag at the end. The closing part at the beginning
522 does not have its own persistent data.
524 Do not overwrite the values, once defined, to save some memory.
526 Since PDVs are always n*2 bytes, store each of the 2 Bytes in a variable.
527 This way, we don't need to call tvb_get_xxx on a self created buffer
531 bool is_header_fragmented;
532 bool is_value_fragmented;
534 uint32_t len_decoded; /* Should only be < 16 bytes */
536 uint16_t grp; /* Already decoded group */
537 uint16_t elm; /* Already decoded element */
538 char *vr; /* Already decoded VR */
540 bool is_vl_long; /* If true, Value Length is 4 Bytes, otherwise 2 */
541 uint16_t vl_1; /* Partially decoded 1st two bytes of length */
542 uint16_t vl_2; /* Partially decoded 2nd two bytes of length */
544 /* These ones are, where the value was truncated */
545 uint32_t len_total; /* Tag length of 'over-sized' tags. Used for display */
546 uint32_t len_remaining; /* Remaining tag bytes to 'decoded' as binary data after this PDV */
548 char *desc; /* Last decoded description */
550 } dcm_open_tag_t;
553 Per Data PDV store data needed, to allow decoding of tags longer than a PDV
555 typedef struct dcm_state_pdv {
557 struct dcm_state_pdv *next, *prev;
559 uint32_t packet_no; /* Wireshark packet number, where pdv starts */
560 uint32_t offset; /* Offset in packet, where PDV header starts */
562 char *desc; /* PDV description. wmem_file_scope() */
564 uint8_t pctx_id; /* Reference to used Presentation Context */
566 /* Following is derived from the transfer syntax in the parent PCTX, except for Command PDVs */
567 uint8_t syntax;
569 /* Used and filled for Export Object only */
570 void *data; /* Copy of PDV data without any PDU/PDV header */
571 uint32_t data_len; /* Length of this PDV buffer. If >0, memory has been allocated */
573 char *sop_class_uid; /* SOP Class UID. Set in 1st PDV of a DICOM object. wmem_file_scope() */
574 char *sop_instance_uid; /* SOP Instance UID. Set in 1st PDV of a DICOM object. wmem_file_scope() */
575 /* End Export use */
577 bool is_storage; /* True, if the Data PDV is on the context of a storage SOP Class */
578 bool is_flagvalid; /* The following two flags are initialized correctly */
579 bool is_command; /* This PDV is a command rather than a data package */
580 bool is_last_fragment; /* Last Fragment bit was set, i.e. termination of an object
581 This flag delimits different DICOM object in the same association */
582 bool is_corrupt; /* Early termination of long PDVs */
584 /* The following five attributes are only used for command PDVs */
586 char *command; /* Decoded command as text */
587 char *status; /* Decoded status as text */
588 char *comment; /* Error comment, if any */
590 bool is_warning; /* Command response is a cancel, warning, error */
591 bool is_pending; /* Command response is 'Current Match is supplied. Sub-operations are continuing' */
593 uint16_t message_id; /* (0000,0110) Message ID */
594 uint16_t message_id_resp; /* (0000,0120) Message ID being responded to */
596 uint16_t no_remaining; /* (0000,1020) Number of remaining sub-operations */
597 uint16_t no_completed; /* (0000,1021) Number of completed sub-operations */
598 uint16_t no_failed; /* (0000,1022) Number of failed sub-operations */
599 uint16_t no_warning; /* (0000,1023) Number of warning sub-operations */
601 dcm_open_tag_t open_tag; /* Container to store information about a fragmented tag */
603 uint8_t reassembly_id;
605 } dcm_state_pdv_t;
608 Per Presentation Context in an association store data needed, for subsequent decoding
610 typedef struct dcm_state_pctx {
612 struct dcm_state_pctx *next, *prev;
614 uint8_t id; /* 0x20 Presentation Context ID */
615 char *abss_uid; /* 0x30 Abstract syntax */
616 char *abss_desc; /* 0x30 Abstract syntax decoded*/
617 char *xfer_uid; /* 0x40 Accepted Transfer syntax */
618 char *xfer_desc; /* 0x40 Accepted Transfer syntax decoded*/
619 uint8_t syntax; /* Decoded transfer syntax */
620 #define DCM_ILE 0x01 /* implicit, little endian */
621 #define DCM_EBE 0x02 /* explicit, big endian */
622 #define DCM_ELE 0x03 /* explicit, little endian */
623 #define DCM_UNK 0xf0
625 uint8_t reassembly_count;
626 dcm_state_pdv_t *first_pdv, *last_pdv; /* List of PDV objects */
628 } dcm_state_pctx_t;
631 typedef struct dcm_state_assoc {
633 struct dcm_state_assoc *next, *prev;
635 dcm_state_pctx_t *first_pctx, *last_pctx; /* List of Presentation context objects */
637 uint32_t packet_no; /* Wireshark packet number, where association starts */
639 char *ae_called; /* Called AE title in A-ASSOCIATE RQ */
640 char *ae_calling; /* Calling AE title in A-ASSOCIATE RQ */
641 char *ae_called_resp; /* Called AE title in A-ASSOCIATE RP */
642 char *ae_calling_resp; /* Calling AE title in A-ASSOCIATE RP */
644 } dcm_state_assoc_t;
646 typedef struct dcm_state {
648 struct dcm_state_assoc *first_assoc, *last_assoc;
650 bool valid; /* this conversation is a DICOM conversation */
652 } dcm_state_t;
655 /* ---------------------------------------------------------------------
656 * DICOM Status Value Definitions
658 * Collected from PS 3.7 & 3.4
662 typedef struct dcm_status {
663 const uint16_t value;
664 const char *description;
665 } dcm_status_t;
667 static dcm_status_t const dcm_status_data[] = {
669 /* From PS 3.7 */
671 { 0x0000, "Success"},
672 { 0x0105, "No such attribute"},
673 { 0x0106, "Invalid attribute value"},
674 { 0x0107, "Attribute list error"},
675 { 0x0110, "Processing failure"},
676 { 0x0111, "Duplicate SOP instance"},
677 { 0x0112, "No Such object instance"},
678 { 0x0113, "No such event type"},
679 { 0x0114, "No such argument"},
680 { 0x0115, "Invalid argument value"},
681 { 0x0116, "Attribute Value Out of Range"},
682 { 0x0117, "Invalid object instance"},
683 { 0x0118, "No Such SOP class"},
684 { 0x0119, "Class-instance conflict"},
685 { 0x0120, "Missing attribute"},
686 { 0x0121, "Missing attribute value"},
687 { 0x0122, "Refused: SOP class not supported"},
688 { 0x0123, "No such action type"},
689 { 0x0210, "Duplicate invocation"},
690 { 0x0211, "Unrecognized operation"},
691 { 0x0212, "Mistyped argument"},
692 { 0x0213, "Resource limitation"},
693 { 0xFE00, "Cancel"},
695 /* from PS 3.4 */
697 { 0x0001, "Requested optional Attributes are not supported"},
698 { 0xA501, "Refused because General Purpose Scheduled Procedure Step Object may no longer be updated"},
699 { 0xA502, "Refused because the wrong Transaction UID is used"},
700 { 0xA503, "Refused because the General Purpose Scheduled Procedure Step SOP Instance is already in the 'IN PROGRESS' state"},
701 { 0xA504, "Refused because the related General Purpose Scheduled Procedure Step SOP Instance is not in the 'IN PROGRESS' state"},
702 { 0xA505, "Refused because Referenced General Purpose Scheduled Procedure Step Transaction UID does not match the Transaction UID of the N-ACTION request"},
703 { 0xA510, "Refused because an Initiate Media Creation action has already been received for this SOP Instance"},
704 { 0xA700, "Refused: Out of Resources"},
705 { 0xA701, "Refused: Out of Resources - Unable to calculate number of matches"},
706 { 0xA702, "Refused: Out of Resources - Unable to perform sub-operations"},
708 { 0xA7xx, "Refused: Out of Resources"},
710 { 0xA801, "Refused: Move Destination unknown"},
712 { 0xA9xx, "Error: Data Set does not match SOP Class"},
714 { 0xB000, "Sub-operations Complete - One or more Failures"},
715 { 0xB006, "Elements Discarded"},
716 { 0xB007, "Data Set does not match SOP Class"},
717 { 0xB101, "Specified Synchronization Frame of Reference UID does not match SCP Synchronization Frame of Reference"},
718 { 0xB102, "Study Instance UID coercion; Event logged under a different Study Instance UID"},
719 { 0xB104, "IDs inconsistent in matching a current study; Event logged"},
720 { 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"},
721 { 0xC000, "Error: Cannot understand/Unable to process"},
722 { 0xC100, "More than one match found"},
723 { 0xC101, "Procedural Logging not available for specified Study Instance UID"},
724 { 0xC102, "Event Information does not match Template"},
725 { 0xC103, "Cannot match event to a current study"},
726 { 0xC104, "IDs inconsistent in matching a current study; Event not logged"},
727 { 0xC200, "Unable to support requested template"},
728 { 0xC201, "Media creation request already completed"},
729 { 0xC202, "Media creation request already in progress and cannot be interrupted"},
730 { 0xC203, "Cancellation denied for unspecified reason"},
732 { 0xCxxx, "Error: Cannot understand/Unable to Process"},
733 { 0xFE00, "Matching/Sub-operations terminated due to Cancel request"},
735 { 0xFF00, "Current Match is supplied. Sub-operations are continuing"},
736 { 0xFF01, "Matches are continuing - Warning that one or more Optional Keys were not supported for existence for this Identifier"}
741 /* following definitions are used to call dissect_dcm_assoc_item() */
742 #define DCM_ITEM_VALUE_TYPE_UID 1
743 #define DCM_ITEM_VALUE_TYPE_STRING 2
744 #define DCM_ITEM_VALUE_TYPE_UINT32 3
746 /* And from here on, only use unsigned 32 bit values. Offset is always positive number in respect to the tvb buffer start */
747 static uint32_t dissect_dcm_pdu (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset);
749 static uint32_t dissect_dcm_assoc_detail(tvbuff_t *tvb, packet_info *pinfo, proto_item *ti, dcm_state_assoc_t *assoc, uint32_t offset, uint32_t len);
751 static uint32_t dissect_dcm_tag_value(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree, dcm_state_pdv_t * pdv, uint32_t offset, uint16_t grp, uint16_t elm, uint32_t vl, uint32_t vl_max, const char * vr, char ** tag_value);
753 static void
754 dcm_init(void)
756 unsigned i;
758 /* Create three hash tables for quick lookups */
759 /* Add UID objects to hash table */
760 dcm_uid_table = wmem_map_new(wmem_file_scope(), wmem_str_hash, g_str_equal);
761 for (i = 0; i < array_length(dcm_uid_data); i++) {
762 wmem_map_insert(dcm_uid_table, (void *) dcm_uid_data[i].value,
763 (void *) &dcm_uid_data[i]);
766 /* Add Tag objects to hash table */
767 dcm_tag_table = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
768 for (i = 0; i < array_length(dcm_tag_data); i++) {
769 wmem_map_insert(dcm_tag_table, GUINT_TO_POINTER(dcm_tag_data[i].tag),
770 (void *) &dcm_tag_data[i]);
773 /* Add Status Values to hash table */
774 dcm_status_table = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal);
775 for (i = 0; i < array_length(dcm_status_data); i++) {
776 wmem_map_insert(dcm_status_table, GUINT_TO_POINTER((uint32_t)dcm_status_data[i].value),
777 (void *)&dcm_status_data[i]);
782 Get or create conversation and DICOM data structure if desired.
783 Return new or existing DICOM structure, which is used to store context IDs and transfer syntax.
784 Return NULL in case of the structure couldn't be created.
786 static dcm_state_t *
787 dcm_state_get(packet_info *pinfo, bool create)
790 conversation_t *conv;
791 dcm_state_t *dcm_data;
793 conv = find_or_create_conversation(pinfo);
794 dcm_data = (dcm_state_t *)conversation_get_proto_data(conv, proto_dcm);
796 if (dcm_data == NULL && create) {
798 dcm_data = wmem_new0(wmem_file_scope(), dcm_state_t);
799 conversation_add_proto_data(conv, proto_dcm, dcm_data);
801 /* Mark it as DICOM conversation. Needed for the heuristic mode,
802 to prevent stealing subsequent packets by other dissectors
804 conversation_set_dissector(conv, dcm_handle);
807 return dcm_data;
811 static dcm_state_assoc_t *
812 dcm_state_assoc_new(dcm_state_t *dcm_data, uint32_t packet_no)
814 /* Create new association object and initialize the members */
816 dcm_state_assoc_t *assoc;
818 assoc = wmem_new0(wmem_file_scope(), dcm_state_assoc_t);
819 assoc->packet_no = packet_no; /* Identifier */
821 /* add to the end of the list */
822 if (dcm_data->last_assoc) {
823 dcm_data->last_assoc->next = assoc;
824 assoc->prev = dcm_data->last_assoc;
826 else {
827 dcm_data->first_assoc = assoc;
829 dcm_data->last_assoc = assoc;
830 return assoc;
834 Find or create association object based on packet number. Return NULL, if association was not found.
836 static dcm_state_assoc_t *
837 dcm_state_assoc_get(dcm_state_t *dcm_data, uint32_t packet_no, bool create)
840 dcm_state_assoc_t *assoc = dcm_data->first_assoc;
842 while (assoc) {
844 if (assoc->next) {
845 /* we have more associations in the same stream */
846 if ((assoc->packet_no <= packet_no) && (packet_no < assoc->next->packet_no))
847 break;
849 else {
850 /* last or only associations in the same stream */
851 if (assoc->packet_no <= packet_no)
852 break;
854 assoc = assoc->next;
857 if (assoc == NULL && create) {
858 assoc = dcm_state_assoc_new(dcm_data, packet_no);
860 return assoc;
863 static dcm_state_pctx_t *
864 dcm_state_pctx_new(dcm_state_assoc_t *assoc, uint8_t pctx_id)
866 /* Create new presentation context object and initialize the members */
868 dcm_state_pctx_t *pctx;
870 pctx = wmem_new0(wmem_file_scope(), dcm_state_pctx_t);
871 pctx->id = pctx_id;
872 pctx->syntax = DCM_UNK;
874 /* add to the end of the list list */
875 if (assoc->last_pctx) {
876 assoc->last_pctx->next = pctx;
877 pctx->prev = assoc->last_pctx;
879 else {
880 assoc->first_pctx = pctx;
882 assoc->last_pctx = pctx;
884 return pctx;
887 static dcm_state_pctx_t *
888 dcm_state_pctx_get(dcm_state_assoc_t *assoc, uint8_t pctx_id, bool create)
890 /* Find or create presentation context object. Return NULL, if Context ID was not found */
892 dcm_state_pctx_t *pctx = assoc->first_pctx;
894 static char notfound[] = "not found - click on ASSOC Request";
895 static dcm_state_pctx_t dunk = { NULL, NULL, false, 0, notfound, notfound, notfound, notfound, DCM_UNK };
897 while (pctx) {
898 if (pctx->id == pctx_id)
899 break;
900 pctx = pctx->next;
903 if (pctx == NULL && create) {
904 pctx = dcm_state_pctx_new(assoc, pctx_id);
907 return pctx;
912 Create new PDV object and initialize all members
914 static dcm_state_pdv_t*
915 dcm_state_pdv_new(dcm_state_pctx_t *pctx, uint32_t packet_no, uint32_t offset)
917 dcm_state_pdv_t *pdv;
919 pdv = wmem_new0(wmem_file_scope(), dcm_state_pdv_t);
920 pdv->syntax = DCM_UNK;
921 pdv->is_last_fragment = true; /* Continuation PDVs are more tricky */
922 pdv->packet_no = packet_no;
923 pdv->offset = offset;
925 /* add to the end of the list */
926 if (pctx->last_pdv) {
927 pctx->last_pdv->next = pdv;
928 pdv->prev = pctx->last_pdv;
930 else {
931 pctx->first_pdv = pdv;
933 pctx->last_pdv = pdv;
934 return pdv;
938 static dcm_state_pdv_t*
939 dcm_state_pdv_get(dcm_state_pctx_t *pctx, uint32_t packet_no, uint32_t offset, bool create)
941 /* Find or create PDV object. Return NULL, if PDV was not found, based on packet number and offset */
943 dcm_state_pdv_t *pdv = pctx->first_pdv;
945 while (pdv) {
946 if ((pdv->packet_no == packet_no) && (pdv->offset == offset))
947 break;
948 pdv = pdv->next;
951 if (pdv == NULL && create) {
952 pdv = dcm_state_pdv_new(pctx, packet_no, offset);
954 return pdv;
957 static dcm_state_pdv_t*
958 dcm_state_pdv_get_obj_start(dcm_state_pdv_t *pdv_curr)
961 dcm_state_pdv_t *pdv_first=pdv_curr;
963 /* Get First PDV of the DICOM Object */
964 while (pdv_first->prev && !pdv_first->prev->is_last_fragment) {
965 pdv_first = pdv_first->prev;
968 return pdv_first;
971 static const value_string dcm_cmd_vals[] = {
972 { 0x0001, "C-STORE-RQ" },
973 { 0x0010, "C-GET-RQ" },
974 { 0x0020, "C-FIND-RQ" },
975 { 0x0021, "C-MOVE-RQ" },
976 { 0x0030, "C-ECHO-RQ" },
977 { 0x0100, "N-EVENT-REPORT-RQ" },
978 { 0x0110, "N-GET-RQ" },
979 { 0x0120, "N-SET-RQ" },
980 { 0x0130, "N-ACTION-RQ" },
981 { 0x0140, "N-CREATE-RQ" },
982 { 0x0150, "N-DELETE-RQ" },
983 { 0x8001, "C-STORE-RSP" },
984 { 0x8010, "C-GET-RSP" },
985 { 0x8020, "C-FIND-RSP" },
986 { 0x8021, "C-MOVE-RSP" },
987 { 0x8030, "C-ECHO-RSP" },
988 { 0x8100, "N-EVENT-REPORT-RSP" },
989 { 0x8110, "N-GET-RSP" },
990 { 0x8120, "N-SET-RSP" },
991 { 0x8130, "N-ACTION-RSP" },
992 { 0x8140, "N-CREATE-RSP" },
993 { 0x8150, "N-DELETE-RSP" },
994 { 0x0FFF, "C-CANCEL-RQ" },
995 { 0, NULL }
1000 Convert the two status bytes into a text based on lookup.
1002 Classification
1003 0x0000 : SUCCESS
1004 0x0001 & Bxxx : WARNING
1005 0xFE00 : CANCEL
1006 0XFFxx : PENDING
1007 All other : FAILURE
1009 static const char *
1010 dcm_rsp2str(uint16_t status_value)
1013 dcm_status_t const *status = NULL;
1014 const char *s;
1016 /* Use specific text first */
1017 status = (dcm_status_t const *)wmem_map_lookup(dcm_status_table, GUINT_TO_POINTER((uint32_t)status_value));
1019 if (status) {
1020 s = status->description;
1022 else {
1024 if ((status_value & 0xFF00) == 0xA700) {
1025 /* 0xA7xx */
1026 s = "Refused: Out of Resources";
1028 else if ((status_value & 0xFF00) == 0xA900) {
1029 /* 0xA9xx */
1030 s = "Error: Data Set does not match SOP Class";
1032 else if ((status_value & 0xF000) == 0xC000) {
1033 /* 0xCxxx */
1034 s = "Error: Cannot understand/Unable to Process";
1036 else {
1037 /* Encountered at least one case, with status_value == 0xD001 */
1038 s = "Unknown";
1042 return s;
1045 static const char*
1046 dcm_uid_or_desc(char *dcm_uid, char *dcm_desc)
1048 /* Return Description, UID or error */
1050 return (dcm_desc == NULL ? (dcm_uid == NULL ? "Malformed Packet" : dcm_uid) : dcm_desc);
1053 static void
1054 dcm_set_syntax(dcm_state_pctx_t *pctx, char *xfer_uid, const char *xfer_desc)
1056 if ((pctx == NULL) || (xfer_uid == NULL) || (xfer_desc == NULL))
1057 return;
1059 wmem_free(wmem_file_scope(), pctx->xfer_uid); /* free prev allocated xfer */
1060 wmem_free(wmem_file_scope(), pctx->xfer_desc); /* free prev allocated xfer */
1062 pctx->syntax = 0;
1063 pctx->xfer_uid = wmem_strdup(wmem_file_scope(), xfer_uid);
1064 pctx->xfer_desc = wmem_strdup(wmem_file_scope(), xfer_desc);
1066 /* this would be faster to skip the common parts, and have a FSA to
1067 * find the syntax.
1068 * Absent of coding that, this is in descending order of probability */
1069 if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2"))
1070 pctx->syntax = DCM_ILE; /* implicit little endian */
1071 else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.1"))
1072 pctx->syntax = DCM_ELE; /* explicit little endian */
1073 else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.2"))
1074 pctx->syntax = DCM_EBE; /* explicit big endian */
1075 else if (0 == strcmp(xfer_uid, "1.2.840.113619.5.2"))
1076 pctx->syntax = DCM_ILE; /* implicit little endian, big endian pixels, GE private */
1077 else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.4.70"))
1078 pctx->syntax = DCM_ELE; /* explicit little endian, jpeg */
1079 else if (0 == strncmp(xfer_uid, "1.2.840.10008.1.2.4", 18))
1080 pctx->syntax = DCM_ELE; /* explicit little endian, jpeg */
1081 else if (0 == strcmp(xfer_uid, "1.2.840.10008.1.2.1.99"))
1082 pctx->syntax = DCM_ELE; /* explicit little endian, deflated */
1085 static void
1086 dcm_uint16_to_le(uint8_t *buffer, uint16_t value)
1089 buffer[0]=(uint8_t) (value & 0x00FF);
1090 buffer[1]=(uint8_t)((value & 0xFF00) >> 8);
1093 static void
1094 dcm_uint32_to_le(uint8_t *buffer, uint32_t value)
1097 buffer[0]=(uint8_t) (value & 0x000000FF);
1098 buffer[1]=(uint8_t)((value & 0x0000FF00) >> 8);
1099 buffer[2]=(uint8_t)((value & 0x00FF0000) >> 16);
1100 buffer[3]=(uint8_t)((value & 0xFF000000) >> 24);
1104 static uint32_t
1105 dcm_export_create_tag_base(uint8_t *buffer, uint32_t bufflen, uint32_t offset,
1106 uint16_t grp, uint16_t elm, uint16_t vr,
1107 const uint8_t *value_buffer, uint32_t value_len)
1109 /* Only Explicit Little Endian is needed to create Metafile Header
1110 Generic function to write a TAG, VR, LEN & VALUE to a combined buffer
1111 The value (buffer, len) must be preprocessed by a VR specific function
1114 if (offset + 6 > bufflen) return bufflen;
1116 dcm_uint16_to_le(buffer + offset, grp);
1117 offset += 2;
1118 dcm_uint16_to_le(buffer + offset, elm);
1119 offset += 2;
1120 memmove(buffer + offset, dcm_tag_vr_lookup[vr], 2);
1121 offset += 2;
1123 switch (vr) {
1124 case DCM_VR_OB:
1125 case DCM_VR_OD:
1126 case DCM_VR_OF:
1127 case DCM_VR_OL:
1128 case DCM_VR_OW:
1129 case DCM_VR_SQ:
1130 case DCM_VR_UC:
1131 case DCM_VR_UR:
1132 case DCM_VR_UT:
1133 case DCM_VR_UN:
1134 /* DICOM likes it complicated. Special handling for these types */
1136 if (offset + 6 > bufflen) return bufflen;
1138 /* Add two reserved 0x00 bytes */
1139 dcm_uint16_to_le(buffer + offset, 0);
1140 offset += 2;
1142 /* Length is a 4 byte field */
1143 dcm_uint32_to_le(buffer + offset, value_len);
1144 offset += 4;
1146 break;
1148 default:
1149 /* Length is a 2 byte field */
1150 if (offset + 2 > bufflen) return bufflen;
1152 dcm_uint16_to_le(buffer + offset, (uint16_t)value_len);
1153 offset += 2;
1156 if (offset + value_len > bufflen) return bufflen;
1158 memmove(buffer + offset, value_buffer, value_len);
1159 offset += value_len;
1161 return offset;
1164 static uint32_t
1165 dcm_export_create_tag_uint16(uint8_t *buffer, uint32_t bufflen, uint32_t offset,
1166 uint16_t grp, uint16_t elm, uint16_t vr, uint16_t value)
1169 return dcm_export_create_tag_base(buffer, bufflen, offset, grp, elm, vr, (uint8_t*)&value, 2);
1172 static uint32_t
1173 dcm_export_create_tag_uint32(uint8_t *buffer, uint32_t bufflen, uint32_t offset,
1174 uint16_t grp, uint16_t elm, uint16_t vr, uint32_t value)
1177 return dcm_export_create_tag_base(buffer, bufflen, offset, grp, elm, vr, (uint8_t*)&value, 4);
1180 static uint32_t
1181 dcm_export_create_tag_str(uint8_t *buffer, uint32_t bufflen, uint32_t offset,
1182 uint16_t grp, uint16_t elm, uint16_t vr,
1183 const char *value)
1185 uint32_t len;
1187 if (!value) {
1188 /* NULL object. E.g. happens if UID was not found/set. Don't create element*/
1189 return offset;
1192 len=(int)strlen(value);
1194 if ((len & 0x01) == 1) {
1195 /* Odd length: since buffer is 0 initialized, pad with a 0x00 */
1196 len += 1;
1199 return dcm_export_create_tag_base(buffer, bufflen, offset, grp, elm, vr, (const uint8_t *)value, len);
1203 static uint8_t*
1204 dcm_export_create_header(packet_info *pinfo, uint32_t *dcm_header_len, const char *sop_class_uid, char *sop_instance_uid, char *xfer_uid)
1206 uint8_t *dcm_header=NULL;
1207 uint32_t offset=0;
1208 uint32_t offset_header_len=0;
1210 #define DCM_HEADER_MAX 512
1212 dcm_header=(uint8_t *)wmem_alloc0(pinfo->pool, DCM_HEADER_MAX); /* Slightly longer than needed */
1213 /* The subsequent functions rely on a 0 initialized buffer */
1214 offset=128;
1216 memmove(dcm_header+offset, "DICM", 4);
1217 offset+=4;
1219 offset_header_len=offset; /* remember for later */
1221 offset+=12;
1224 (0002,0000) File Meta Information Group Length UL
1225 (0002,0001) File Meta Information Version OB
1226 (0002,0002) Media Storage SOP Class UID UI
1227 (0002,0003) Media Storage SOP Instance UID UI
1228 (0002,0010) Transfer Syntax UID UI
1229 (0002,0012) Implementation Class UID UI
1230 (0002,0013) Implementation Version Name SH
1233 offset=dcm_export_create_tag_uint16(dcm_header, DCM_HEADER_MAX, offset,
1234 0x0002, 0x0001, DCM_VR_OB, 0x0100); /* will result on 00 01 since it is little endian */
1236 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1237 0x0002, 0x0002, DCM_VR_UI, sop_class_uid);
1239 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1240 0x0002, 0x0003, DCM_VR_UI, sop_instance_uid);
1242 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1243 0x0002, 0x0010, DCM_VR_UI, xfer_uid);
1245 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1246 0x0002, 0x0012, DCM_VR_UI, WIRESHARK_IMPLEMENTATION_UID);
1248 offset=dcm_export_create_tag_str(dcm_header, DCM_HEADER_MAX, offset,
1249 0x0002, 0x0013, DCM_VR_SH, WIRESHARK_IMPLEMENTATION_VERSION);
1251 /* Finally write the meta header length */
1252 dcm_export_create_tag_uint32(dcm_header, DCM_HEADER_MAX, offset_header_len,
1253 0x0002, 0x0000, DCM_VR_UL, offset-offset_header_len-12);
1255 *dcm_header_len=offset;
1257 return dcm_header;
1263 Concatenate related PDVs into one buffer and add it to the export object list.
1265 Supports both modes:
1267 - Multiple DICOM PDVs are reassembled with fragment_add_seq_next()
1268 and process_reassembled_data(). In this case all data will be in the last
1269 PDV, and all its predecessors will have zero data.
1271 - DICOM PDVs are keep separate. Every PDV contains data.
1273 static void
1274 dcm_export_create_object(packet_info *pinfo, dcm_state_assoc_t *assoc, dcm_state_pdv_t *pdv)
1277 dicom_eo_t *eo_info = NULL;
1279 dcm_state_pdv_t *pdv_curr = NULL;
1280 dcm_state_pdv_t *pdv_same_pkt = NULL;
1281 dcm_state_pctx_t *pctx = NULL;
1283 uint8_t *pdv_combined = NULL;
1284 uint8_t *pdv_combined_curr = NULL;
1285 uint8_t *dcm_header = NULL;
1286 uint32_t pdv_combined_len = 0;
1287 uint32_t dcm_header_len = 0;
1288 uint16_t cnt_same_pkt = 1;
1289 char *filename;
1290 const char *hostname;
1292 const char *sop_class_uid;
1293 char *sop_instance_uid;
1295 /* Calculate total PDV length, i.e. all packets until last PDV without continuation */
1296 pdv_curr = pdv;
1297 pdv_same_pkt = pdv;
1298 pdv_combined_len=pdv_curr->data_len;
1300 while (pdv_curr->prev && !pdv_curr->prev->is_last_fragment) {
1301 pdv_curr = pdv_curr->prev;
1302 pdv_combined_len += pdv_curr->data_len;
1305 /* Count number of PDVs with the same Packet Number */
1306 while (pdv_same_pkt->prev && (pdv_same_pkt->prev->packet_no == pdv_same_pkt->packet_no)) {
1307 pdv_same_pkt = pdv_same_pkt->prev;
1308 cnt_same_pkt += 1;
1311 pctx=dcm_state_pctx_get(assoc, pdv_curr->pctx_id, false);
1313 if (assoc->ae_calling != NULL && strlen(assoc->ae_calling)>0 &&
1314 assoc->ae_called != NULL && strlen(assoc->ae_called)>0) {
1315 hostname = wmem_strdup_printf(pinfo->pool, "%s <-> %s", assoc->ae_calling, assoc->ae_called);
1317 else {
1318 hostname = "AE title(s) unknown";
1321 if (pdv->is_storage &&
1322 pdv_curr->sop_class_uid && strlen(pdv_curr->sop_class_uid)>0 &&
1323 pdv_curr->sop_instance_uid && strlen(pdv_curr->sop_instance_uid)>0) {
1325 sop_class_uid = wmem_strdup(pinfo->pool, pdv_curr->sop_class_uid);
1326 sop_instance_uid = wmem_strdup(pinfo->pool, pdv_curr->sop_instance_uid);
1328 /* Make sure filename does not contain invalid character. Rather conservative.
1329 Even though this should be a valid DICOM UID, apply the same filter rules
1330 in case of bogus data.
1332 filename = wmem_strdup_printf(pinfo->pool, "%06d-%d-%s.dcm", pinfo->num, cnt_same_pkt,
1333 g_strcanon(pdv_curr->sop_instance_uid, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-.", '-'));
1335 else {
1336 /* No SOP Instance or SOP Class UID found in PDV. Use wireshark ones */
1338 sop_class_uid = wmem_strdup(pinfo->pool, WIRESHARK_MEDIA_STORAGE_SOP_CLASS_UID);
1339 sop_instance_uid = wmem_strdup_printf(pinfo->pool, "%s.%d.%d",
1340 WIRESHARK_MEDIA_STORAGE_SOP_INSTANCE_UID_PREFIX, pinfo->num, cnt_same_pkt);
1342 /* Make sure filename does not contain invalid character. Rather conservative.*/
1343 filename = wmem_strdup_printf(pinfo->pool, "%06d-%d-%s.dcm", pinfo->num, cnt_same_pkt,
1344 g_strcanon(pdv->desc, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-.", '-'));
1348 if (global_dcm_export_header) {
1349 if (pctx && pctx->xfer_uid && strlen(pctx->xfer_uid)>0) {
1350 dcm_header=dcm_export_create_header(pinfo, &dcm_header_len, sop_class_uid, sop_instance_uid, pctx->xfer_uid);
1352 else {
1353 /* We are running blind, i.e. no presentation context/syntax found.
1354 Don't invent one, so the meta header will miss
1355 the transfer syntax UID tag (even though it is mandatory)
1357 dcm_header=dcm_export_create_header(pinfo, &dcm_header_len, sop_class_uid, sop_instance_uid, NULL);
1362 if (dcm_header_len + pdv_combined_len >= global_dcm_export_minsize) {
1363 /* Allocate the final size */
1365 pdv_combined = (uint8_t *)wmem_alloc0(pinfo->pool, dcm_header_len + pdv_combined_len);
1367 pdv_combined_curr = pdv_combined;
1369 if (dcm_header_len != 0) { /* Will be 0 when global_dcm_export_header is false */
1370 memmove(pdv_combined, dcm_header, dcm_header_len);
1371 pdv_combined_curr += dcm_header_len;
1374 /* Copy PDV per PDV to target buffer */
1375 while (!pdv_curr->is_last_fragment) {
1376 memmove(pdv_combined_curr, pdv_curr->data, pdv_curr->data_len); /* this is a copy not move */
1377 pdv_combined_curr += pdv_curr->data_len;
1378 pdv_curr = pdv_curr->next;
1381 /* Last packet */
1382 memmove(pdv_combined_curr, pdv->data, pdv->data_len); /* this is a copy not a move */
1384 /* Add to list */
1385 /* The tap will copy the values and free the copies; this only
1386 * needs packet lifetime. */
1387 eo_info = wmem_new0(pinfo->pool, dicom_eo_t);
1388 eo_info->hostname = hostname;
1389 eo_info->filename = filename;
1390 eo_info->content_type = pdv->desc;
1392 eo_info->payload_len = dcm_header_len + pdv_combined_len;
1393 eo_info->payload_data = pdv_combined;
1395 tap_queue_packet(dicom_eo_tap, pinfo, eo_info);
1400 For tags with fixed length items, calculate the value multiplicity (VM). String tags use a separator, which is not supported by this function.
1401 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)
1403 static uint32_t
1404 dcm_vm_item_count(uint32_t value_length, uint32_t item_length)
1407 /* This could all be formulated in a single line but it does not make it easier to read */
1409 if (value_length == 0) {
1410 return 0;
1412 else if (value_length <= item_length) {
1413 return 1; /* This is the special case of bad encoding */
1415 else {
1416 return (value_length / item_length);
1422 Decode the association header
1424 static uint32_t
1425 dissect_dcm_assoc_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset, dcm_state_assoc_t *assoc,
1426 uint8_t pdu_type, uint32_t pdu_len)
1429 proto_item *assoc_header_pitem;
1430 proto_tree *assoc_header_ptree; /* Tree for item details */
1432 const char *buf_desc = NULL;
1433 const char *reject_result_desc = "";
1434 const char *reject_source_desc = "";
1435 const char *reject_reason_desc = "";
1436 const char *abort_source_desc = "";
1437 const char *abort_reason_desc = "";
1439 char *ae_called;
1440 char *ae_calling;
1441 char *ae_called_resp;
1442 char *ae_calling_resp;
1444 uint8_t reject_result;
1445 uint8_t reject_source;
1446 uint8_t reject_reason;
1447 uint8_t abort_source;
1448 uint8_t abort_reason;
1450 assoc_header_ptree = proto_tree_add_subtree(tree, tvb, offset, pdu_len, ett_assoc_header, &assoc_header_pitem, "Association Header");
1452 switch (pdu_type) {
1453 case 1: /* Association Request */
1455 proto_tree_add_item(assoc_header_ptree, hf_dcm_assoc_version, tvb, offset, 2, ENC_BIG_ENDIAN);
1456 offset += 2;
1458 offset += 2; /* Two reserved bytes*/
1461 * XXX - this is in "the ISO 646:1990-Basic G0 Set"; ISO/IEC 646:1991
1462 * claims to be the third edition of the standard, with the second
1463 * version being ISO 646:1983, so I'm not sure what happened to
1464 * ISO 646:1990. ISO/IEC 646:1991 speaks of "the basic 7-bit code
1465 * table", which leaves positions 2/3 (0x23) and 2/4 (0x24) as
1466 * being either NUMBER SIGN or POUND SIGN and either DOLLAR SIGN or
1467 * CURRENCY SIGN, respectively, and positions 4/0 (0x40), 5/11 (0x5b),
1468 * 5/12 (0x5c), 5/13 (0x5d), 5/14 (0x5e), 6/0 (0x60), 7/11 (0x7b),
1469 * 7/12 (0x7c), 7/13 (0x7d), and 7/14 (0x7e) as being "available for
1470 * national or application-oriented use", so I'm *guessing* that
1471 * "the ISO 646:1990-Basic G0 Set" means "those positions aren't
1472 * specified" and thus should probably be treated as not valid
1473 * in that "Basic" set.
1475 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);
1476 assoc->ae_called = wmem_strdup(wmem_file_scope(), g_strstrip(ae_called));
1477 offset += 16;
1479 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);
1480 assoc->ae_calling = wmem_strdup(wmem_file_scope(), g_strstrip(ae_calling));
1481 offset += 16;
1483 offset += 32; /* 32 reserved bytes */
1485 buf_desc = wmem_strdup_printf(pinfo->pool, "A-ASSOCIATE request %s --> %s",
1486 assoc->ae_calling, assoc->ae_called);
1488 offset = dissect_dcm_assoc_detail(tvb, pinfo, assoc_header_ptree, assoc, offset, pdu_len-offset);
1490 break;
1491 case 2: /* Association Accept */
1493 proto_tree_add_item(assoc_header_ptree, hf_dcm_assoc_version, tvb, offset, 2, ENC_BIG_ENDIAN);
1494 offset += 2;
1496 offset += 2; /* Two reserved bytes*/
1498 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);
1499 assoc->ae_called_resp = wmem_strdup(wmem_file_scope(), g_strstrip(ae_called_resp));
1500 offset += 16;
1502 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);
1503 assoc->ae_calling_resp = wmem_strdup(wmem_file_scope(), g_strstrip(ae_calling_resp));
1504 offset += 16;
1506 offset += 32; /* 32 reserved bytes */
1508 buf_desc = wmem_strdup_printf(pinfo->pool, "A-ASSOCIATE accept %s <-- %s",
1509 assoc->ae_calling_resp, assoc->ae_called_resp);
1511 offset = dissect_dcm_assoc_detail(tvb, pinfo, assoc_header_ptree, assoc, offset, pdu_len-offset);
1513 break;
1514 case 3: /* Association Reject */
1516 offset += 1; /* One reserved byte */
1518 reject_result = tvb_get_uint8(tvb, offset);
1519 reject_source = tvb_get_uint8(tvb, offset+1);
1520 reject_reason = tvb_get_uint8(tvb, offset+2);
1522 switch (reject_result) {
1523 case 1: reject_result_desc = "Reject Permanent"; break;
1524 case 2: reject_result_desc = "Reject Transient"; break;
1525 default: break;
1528 switch (reject_source) {
1529 case 1:
1530 reject_source_desc = "User";
1531 switch (reject_reason) {
1532 case 1: reject_reason_desc = "No reason given"; break;
1533 case 2: reject_reason_desc = "Application context name not supported"; break;
1534 case 3: reject_reason_desc = "Calling AE title not recognized"; break;
1535 case 7: reject_reason_desc = "Called AE title not recognized"; break;
1537 break;
1538 case 2:
1539 reject_source_desc = "Provider (ACSE)";
1540 switch (reject_reason) {
1541 case 1: reject_reason_desc = "No reason given"; break;
1542 case 2: reject_reason_desc = "Protocol version not supported"; break;
1544 break;
1545 case 3:
1546 reject_source_desc = "Provider (Presentation)";
1547 switch (reject_reason) {
1548 case 1: reject_reason_desc = "Temporary congestion"; break;
1549 case 2: reject_reason_desc = "Local limit exceeded"; break;
1551 break;
1554 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_reject_result, tvb,
1555 offset , 1, reject_result, "%s", reject_result_desc);
1557 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_reject_source, tvb,
1558 offset+1, 1, reject_source, "%s", reject_source_desc);
1560 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_reject_reason, tvb,
1561 offset+2, 1, reject_reason, "%s", reject_reason_desc);
1563 offset += 3;
1565 /* Provider aborted */
1566 buf_desc = wmem_strdup_printf(pinfo->pool, "A-ASSOCIATE reject %s <-- %s (%s)",
1567 assoc->ae_calling, assoc->ae_called, reject_reason_desc);
1569 expert_add_info(pinfo, assoc_header_pitem, &ei_dcm_assoc_rejected);
1571 break;
1572 case 5: /* RELEASE Request */
1574 offset += 2; /* Two reserved bytes */
1575 buf_desc="A-RELEASE request";
1577 break;
1578 case 6: /* RELEASE Response */
1580 offset += 2; /* Two reserved bytes */
1581 buf_desc="A-RELEASE response";
1583 break;
1584 case 7: /* ABORT */
1586 offset += 2; /* Two reserved bytes */
1588 abort_source = tvb_get_uint8(tvb, offset);
1589 abort_reason = tvb_get_uint8(tvb, offset+1);
1591 switch (abort_source) {
1592 case 0:
1593 abort_source_desc = "User";
1594 abort_reason_desc = "N/A"; /* No details can be provided*/
1595 break;
1596 case 1:
1597 /* reserved */
1598 break;
1599 case 2:
1600 abort_source_desc = "Provider";
1602 switch (abort_reason) {
1603 case 0: abort_reason_desc = "Not specified"; break;
1604 case 1: abort_reason_desc = "Unrecognized PDU"; break;
1605 case 2: abort_reason_desc = "Unexpected PDU"; break;
1606 case 4: abort_reason_desc = "Unrecognized PDU parameter"; break;
1607 case 5: abort_reason_desc = "Unexpected PDU parameter"; break;
1608 case 6: abort_reason_desc = "Invalid PDU parameter value"; break;
1611 break;
1614 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_abort_source,
1615 tvb, offset , 1, abort_source, "%s", abort_source_desc);
1617 proto_tree_add_uint_format_value(assoc_header_ptree, hf_dcm_assoc_abort_reason,
1618 tvb, offset+1, 1, abort_reason, "%s", abort_reason_desc);
1619 offset += 2;
1621 if (abort_source == 0) {
1622 /* User aborted */
1623 buf_desc = wmem_strdup_printf(pinfo->pool, "ABORT %s --> %s",
1624 assoc->ae_calling, assoc->ae_called);
1626 else {
1627 /* Provider aborted, slightly more information */
1628 buf_desc = wmem_strdup_printf(pinfo->pool, "ABORT %s <-- %s (%s)",
1629 assoc->ae_calling, assoc->ae_called, abort_reason_desc);
1632 expert_add_info(pinfo, assoc_header_pitem, &ei_dcm_assoc_aborted);
1634 break;
1637 if (buf_desc) {
1638 proto_item_set_text(assoc_header_pitem, "%s", buf_desc);
1639 col_set_str(pinfo->cinfo, COL_INFO, buf_desc);
1641 /* proto_item and proto_tree are one and the same */
1642 proto_item_append_text(tree, ", %s", buf_desc);
1644 return offset;
1648 Decode one item in a association request or response. Lookup UIDs if requested.
1649 Create a subtree node with summary and three elements (item_type, item_len, value)
1651 static void
1652 dissect_dcm_assoc_item(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset,
1653 const char *pitem_prefix, int item_value_type,
1654 char **item_value, const char **item_description,
1655 int *hf_type, int *hf_len, int *hf_value, int ett_subtree)
1658 proto_tree *assoc_item_ptree; /* Tree for item details */
1659 proto_item *assoc_item_pitem;
1660 dcm_uid_t const *uid = NULL;
1662 uint32_t item_number = 0;
1664 uint8_t item_type;
1665 uint16_t item_len;
1667 char *buf_desc; /* Used for item text */
1669 *item_value = NULL;
1670 *item_description = NULL;
1672 item_type = tvb_get_uint8(tvb, offset);
1673 item_len = tvb_get_ntohs(tvb, offset+2);
1675 assoc_item_ptree = proto_tree_add_subtree(tree, tvb, offset, item_len+4, ett_subtree, &assoc_item_pitem, pitem_prefix);
1677 proto_tree_add_uint(assoc_item_ptree, *hf_type, tvb, offset, 1, item_type);
1678 proto_tree_add_uint(assoc_item_ptree, *hf_len, tvb, offset+2, 2, item_len);
1680 switch (item_value_type) {
1681 case DCM_ITEM_VALUE_TYPE_UID:
1682 *item_value = (char *)tvb_get_string_enc(pinfo->pool, tvb, offset+4, item_len, ENC_ASCII);
1684 uid = (dcm_uid_t const *)wmem_map_lookup(dcm_uid_table, (void *) *item_value);
1685 if (uid) {
1686 *item_description = uid->name;
1687 buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", *item_description, *item_value);
1689 else {
1690 /* Unknown UID, or no UID at all */
1691 buf_desc = *item_value;
1694 proto_item_append_text(assoc_item_pitem, "%s", buf_desc);
1695 proto_tree_add_string(assoc_item_ptree, *hf_value, tvb, offset+4, item_len, buf_desc);
1697 break;
1699 case DCM_ITEM_VALUE_TYPE_STRING:
1700 *item_value = (char *)tvb_get_string_enc(pinfo->pool, tvb, offset+4, item_len, ENC_ASCII);
1701 proto_item_append_text(assoc_item_pitem, "%s", *item_value);
1702 proto_tree_add_string(assoc_item_ptree, *hf_value, tvb, offset+4, item_len, *item_value);
1704 break;
1706 case DCM_ITEM_VALUE_TYPE_UINT32:
1707 item_number = tvb_get_ntohl(tvb, offset+4);
1708 *item_value = (char *)wmem_strdup_printf(wmem_file_scope(), "%d", item_number);
1710 proto_item_append_text(assoc_item_pitem, "%s", *item_value);
1711 proto_tree_add_item(assoc_item_ptree, *hf_value, tvb, offset+4, 4, ENC_BIG_ENDIAN);
1713 break;
1715 default:
1716 break;
1721 Decode the SOP Class Extended Negotiation Sub-Item Fields in a association request or response.
1722 Lookup UIDs if requested
1724 static void
1725 dissect_dcm_assoc_sopclass_extneg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset)
1728 proto_tree *assoc_item_extneg_tree = NULL; /* Tree for item details */
1729 proto_item *assoc_item_extneg_item = NULL;
1731 uint16_t item_len = 0;
1732 uint16_t sop_class_uid_len = 0;
1733 int32_t cnt = 0;
1735 char *buf_desc = NULL; /* Used for item text */
1736 dcm_uid_t const *sopclassuid=NULL;
1737 char *sopclassuid_str = NULL;
1739 item_len = tvb_get_ntohs(tvb, offset+2);
1740 sop_class_uid_len = tvb_get_ntohs(tvb, offset+4);
1742 assoc_item_extneg_item = proto_tree_add_item(tree, hf_dcm_info_extneg, tvb, offset, item_len+4, ENC_NA);
1743 proto_item_set_text(assoc_item_extneg_item, "Ext. Neg.: ");
1744 assoc_item_extneg_tree = proto_item_add_subtree(assoc_item_extneg_item, ett_assoc_info_extneg);
1746 proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1747 proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN);
1748 proto_tree_add_item(assoc_item_extneg_tree, hf_dcm_info_extneg_sopclassuid_len, tvb, offset+4, 2, ENC_BIG_ENDIAN);
1750 sopclassuid_str = (char *)tvb_get_string_enc(pinfo->pool, tvb, offset+6, sop_class_uid_len, ENC_ASCII);
1751 sopclassuid = (dcm_uid_t const *)wmem_map_lookup(dcm_uid_table, (void *) sopclassuid_str);
1753 if (sopclassuid) {
1754 buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", sopclassuid->name, sopclassuid->value);
1756 else {
1757 buf_desc = sopclassuid_str;
1760 proto_item_append_text(assoc_item_extneg_item, "%s", buf_desc);
1761 proto_tree_add_string(assoc_item_extneg_tree, hf_dcm_info_extneg_sopclassuid, tvb, offset+6, sop_class_uid_len, buf_desc);
1763 /* Count how many fields are following. */
1764 cnt = item_len - 2 - sop_class_uid_len;
1767 * The next field contains Service Class specific information identified by the SOP Class UID.
1769 if (0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) ||
1770 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) ||
1771 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_FIND_RETIRED) ||
1772 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_MOVE) ||
1773 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_MOVE) ||
1774 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_MOVE_RETIRED) ||
1775 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_GET) ||
1776 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_GET) ||
1777 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_GET_RETIRED))
1779 if (cnt<=0)
1781 return;
1784 /* Support for Relational queries. */
1785 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);
1786 --cnt;
1789 /* More sub-items are only allowed for the C-FIND SOP Classes. */
1790 if (0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENT_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) ||
1791 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_STUDY_ROOT_QUERYRETRIEVE_INFORMATION_MODEL_FIND) ||
1792 0 == strcmp(sopclassuid_str, DCM_UID_SOP_CLASS_PATIENTSTUDY_ONLY_QUERYRETRIEVE_INFORMATION_MODEL_FIND_RETIRED))
1794 if (cnt<=0)
1796 return;
1799 /* Combined Date-Time matching. */
1800 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);
1801 --cnt;
1803 if (cnt<=0)
1805 return;
1808 /* Fuzzy semantic matching of person names. */
1809 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);
1810 --cnt;
1812 if (cnt<=0)
1814 return;
1817 /* Timezone query adjustment. */
1818 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);
1819 --cnt;
1824 Decode user identities in the association
1826 static void
1827 dissect_dcm_assoc_user_identify(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset)
1830 proto_tree *assoc_item_user_identify_tree = NULL; /* Tree for item details */
1831 proto_item *assoc_item_user_identify_item = NULL;
1833 uint16_t primary_field_length, secondary_field_length, item_len = 0;
1834 uint8_t type;
1836 item_len = tvb_get_ntohs(tvb, offset+2);
1838 assoc_item_user_identify_item = proto_tree_add_item(tree, hf_dcm_info_user_identify, tvb, offset, item_len+4, ENC_NA);
1839 assoc_item_user_identify_tree = proto_item_add_subtree(assoc_item_user_identify_item, ett_assoc_info_user_identify);
1841 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1842 offset += 2;
1843 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_assoc_item_len, tvb, offset, 2, ENC_BIG_ENDIAN);
1844 offset += 2;
1846 type = tvb_get_uint8(tvb, offset);
1847 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1848 offset += 1;
1850 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_response_requested, tvb, offset, 1, ENC_BIG_ENDIAN);
1851 offset += 1;
1853 primary_field_length = tvb_get_ntohs(tvb, offset);
1854 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_primary_field_length, tvb, offset, 2, ENC_BIG_ENDIAN);
1855 offset += 2;
1857 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_primary_field, tvb, offset, primary_field_length, ENC_UTF_8);
1858 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));
1859 offset += primary_field_length;
1861 if (type == 2) {
1862 secondary_field_length = tvb_get_ntohs(tvb, offset);
1863 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_secondary_field_length, tvb, offset, 2, ENC_BIG_ENDIAN);
1864 offset += 2;
1866 proto_tree_add_item(assoc_item_user_identify_tree, hf_dcm_info_user_identify_secondary_field, tvb, offset, secondary_field_length, ENC_UTF_8);
1867 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));
1872 Decode unknown item types in the association
1874 static void
1875 dissect_dcm_assoc_unknown(tvbuff_t *tvb, proto_tree *tree, uint32_t offset)
1878 proto_tree *assoc_item_unknown_tree = NULL; /* Tree for item details */
1879 proto_item *assoc_item_unknown_item = NULL;
1881 uint16_t item_len = 0;
1883 item_len = tvb_get_ntohs(tvb, offset+2);
1885 assoc_item_unknown_item = proto_tree_add_item(tree, hf_dcm_info_unknown, tvb, offset, item_len+4, ENC_NA);
1886 assoc_item_unknown_tree = proto_item_add_subtree(assoc_item_unknown_item, ett_assoc_info_unknown);
1888 proto_tree_add_item(assoc_item_unknown_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1889 proto_tree_add_item(assoc_item_unknown_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN);
1890 offset += 4;
1892 proto_tree_add_item(assoc_item_unknown_tree, hf_dcm_assoc_item_data, tvb, offset, item_len, ENC_NA);
1896 Decode the SCP/SCU Role Selection Sub-Item Fields in a association request or response.
1897 Lookup UIDs if requested
1899 static void
1900 dissect_dcm_assoc_role_selection(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset)
1903 proto_tree *assoc_item_rolesel_tree; /* Tree for item details */
1904 proto_item *assoc_item_rolesel_item;
1906 uint16_t item_len, sop_class_uid_len;
1907 uint8_t scp_role, scu_role;
1909 char *buf_desc; /* Used for item text */
1910 dcm_uid_t const *sopclassuid;
1911 char *sopclassuid_str;
1913 item_len = tvb_get_ntohs(tvb, offset+2);
1914 sop_class_uid_len = tvb_get_ntohs(tvb, offset+4);
1916 assoc_item_rolesel_item = proto_tree_add_item(tree, hf_dcm_info_rolesel, tvb, offset, item_len+4, ENC_NA);
1917 proto_item_set_text(assoc_item_rolesel_item, "Role Selection: ");
1918 assoc_item_rolesel_tree = proto_item_add_subtree(assoc_item_rolesel_item, ett_assoc_info_rolesel);
1920 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1921 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN);
1922 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_info_rolesel_sopclassuid_len, tvb, offset+4, 2, ENC_BIG_ENDIAN);
1924 sopclassuid_str = (char *)tvb_get_string_enc(pinfo->pool, tvb, offset+6, sop_class_uid_len, ENC_ASCII);
1925 sopclassuid = (dcm_uid_t const *)wmem_map_lookup(dcm_uid_table, (void *) sopclassuid_str);
1927 scu_role = tvb_get_uint8(tvb, offset+6+sop_class_uid_len);
1928 scp_role = tvb_get_uint8(tvb, offset+7+sop_class_uid_len);
1930 if (scu_role) {
1931 proto_item_append_text(assoc_item_rolesel_item, "%s", "SCU-role: yes");
1933 else {
1934 proto_item_append_text(assoc_item_rolesel_item, "%s", "SCU-role: no");
1937 if (scp_role) {
1938 proto_item_append_text(assoc_item_rolesel_item, ", %s", "SCP-role: yes");
1940 else {
1941 proto_item_append_text(assoc_item_rolesel_item, ", %s", "SCP-role: no");
1944 if (sopclassuid) {
1945 buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", sopclassuid->name, sopclassuid->value);
1947 else {
1948 buf_desc = sopclassuid_str;
1951 proto_tree_add_string(assoc_item_rolesel_tree, hf_dcm_info_rolesel_sopclassuid, tvb, offset+6, sop_class_uid_len, buf_desc);
1953 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_info_rolesel_scurole, tvb, offset+6+sop_class_uid_len, 1, ENC_BIG_ENDIAN);
1954 proto_tree_add_item(assoc_item_rolesel_tree, hf_dcm_info_rolesel_scprole, tvb, offset+7+sop_class_uid_len, 1, ENC_BIG_ENDIAN);
1958 Decode the Asynchronous operations (and sub-operations) Window Negotiation Sub-Item Fields in a association request or response.
1960 static void
1961 dissect_dcm_assoc_async_negotiation(tvbuff_t *tvb, proto_tree *tree, uint32_t offset)
1964 proto_tree *assoc_item_asyncneg_tree; /* Tree for item details */
1965 proto_item *assoc_item_asyncneg_item;
1967 uint16_t item_len, max_num_ops_inv, max_num_ops_per = 0;
1969 item_len = tvb_get_ntohs(tvb, offset+2);
1971 assoc_item_asyncneg_item = proto_tree_add_item(tree, hf_dcm_info_async_neg, tvb, offset, item_len+4, ENC_NA);
1972 proto_item_set_text(assoc_item_asyncneg_item, "Async Negotiation: ");
1973 assoc_item_asyncneg_tree = proto_item_add_subtree(assoc_item_asyncneg_item, ett_assoc_info_async_neg);
1975 proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_assoc_item_type, tvb, offset, 1, ENC_BIG_ENDIAN);
1976 proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_assoc_item_len, tvb, offset+2, 2, ENC_BIG_ENDIAN);
1977 proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_info_async_neg_max_num_ops_inv, tvb, offset+4, 2, ENC_BIG_ENDIAN);
1978 proto_tree_add_item(assoc_item_asyncneg_tree, hf_dcm_info_async_neg_max_num_ops_per, tvb, offset+6, 2, ENC_BIG_ENDIAN);
1980 max_num_ops_inv = tvb_get_ntohs(tvb, offset+4);
1981 max_num_ops_per = tvb_get_ntohs(tvb, offset+6);
1983 proto_item_append_text(assoc_item_asyncneg_item, "%s%d", "Maximum Number Operations Invoked: ", max_num_ops_inv);
1984 if (max_num_ops_inv==0) proto_item_append_text(assoc_item_asyncneg_item, "%s", " (unlimited)");
1985 proto_item_append_text(assoc_item_asyncneg_item, ", %s%d", "Maximum Number Operations Performed: ", max_num_ops_per);
1986 if (max_num_ops_per==0) proto_item_append_text(assoc_item_asyncneg_item, "%s", " (unlimited)");
1990 Decode a presentation context item in a Association Request or Response. In the response, set the accepted transfer syntax, if any.
1992 static void
1993 dissect_dcm_pctx(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
1994 dcm_state_assoc_t *assoc, uint32_t offset, uint32_t len,
1995 const char *pitem_prefix, bool is_assoc_request)
1998 proto_tree *pctx_ptree; /* Tree for presentation context details */
1999 proto_item *pctx_pitem;
2001 dcm_state_pctx_t *pctx = NULL;
2003 uint8_t item_type = 0;
2004 uint16_t item_len = 0;
2006 uint8_t pctx_id = 0; /* Presentation Context ID */
2007 uint8_t pctx_result = 0;
2009 const char *pctx_result_desc = "";
2011 char *pctx_abss_uid = NULL; /* Abstract Syntax UID alias SOP Class UID */
2012 const char *pctx_abss_desc = NULL; /* Description of UID */
2014 char *pctx_xfer_uid = NULL; /* Transfer Syntax UID */
2015 const char *pctx_xfer_desc = NULL; /* Description of UID */
2017 char *buf_desc; /* Used in info mode for item text */
2019 uint32_t endpos = 0;
2020 int cnt_abbs = 0; /* Number of Abstract Syntax Items */
2021 int cnt_xfer = 0; /* Number of Transfer Syntax Items */
2023 endpos = offset + len;
2025 item_type = tvb_get_uint8(tvb, offset-4);
2026 item_len = tvb_get_ntohs(tvb, offset-2);
2028 pctx_ptree = proto_tree_add_subtree(tree, tvb, offset-4, item_len+4, ett_assoc_pctx, &pctx_pitem, pitem_prefix);
2030 pctx_id = tvb_get_uint8(tvb, offset);
2031 pctx_result = tvb_get_uint8(tvb, 2 + offset); /* only set in responses, otherwise reserved and 0x00 */
2033 /* Find or create DICOM context object */
2034 pctx = dcm_state_pctx_get(assoc, pctx_id, true);
2035 if (pctx == NULL) { /* Internal error. Failed to create data structure */
2036 return;
2039 proto_tree_add_uint(pctx_ptree, hf_dcm_assoc_item_type, tvb, offset-4, 1, item_type); /* The type is only one byte long */
2040 proto_tree_add_uint(pctx_ptree, hf_dcm_assoc_item_len, tvb, offset-2, 2, item_len);
2042 proto_tree_add_uint_format(pctx_ptree, hf_dcm_pctx_id, tvb, offset, 1, pctx_id, "Context ID: 0x%02x", pctx_id);
2044 if (!is_assoc_request) {
2045 /* Association response. */
2047 switch (pctx_result) {
2048 case 0: pctx_result_desc = "Accept"; break;
2049 case 1: pctx_result_desc = "User Reject"; break;
2050 case 2: pctx_result_desc = "No Reason"; break;
2051 case 3: pctx_result_desc = "Abstract Syntax Unsupported"; break;
2052 case 4: pctx_result_desc = "Transfer Syntax Unsupported"; break;
2055 proto_tree_add_uint_format(pctx_ptree, hf_dcm_pctx_result, tvb, offset+2, 1,
2056 pctx_result, "Result: %s (0x%x)", pctx_result_desc, pctx_result);
2059 offset += 4;
2060 while (offset < endpos) {
2062 item_type = tvb_get_uint8(tvb, offset);
2063 item_len = tvb_get_ntohs(tvb, 2 + offset);
2065 offset += 4;
2066 switch (item_type) {
2067 case 0x30: /* Abstract syntax */
2069 /* Parse Item. Works also in info mode where dcm_pctx_tree is NULL */
2070 dissect_dcm_assoc_item(tvb, pinfo, pctx_ptree, offset-4,
2071 "Abstract Syntax: ", DCM_ITEM_VALUE_TYPE_UID, &pctx_abss_uid, &pctx_abss_desc,
2072 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_pctx_abss_syntax, ett_assoc_pctx_abss);
2074 cnt_abbs += 1;
2075 offset += item_len;
2076 break;
2078 case 0x40: /* Transfer syntax */
2080 dissect_dcm_assoc_item(tvb, pinfo, pctx_ptree, offset-4,
2081 "Transfer Syntax: ", DCM_ITEM_VALUE_TYPE_UID, &pctx_xfer_uid, &pctx_xfer_desc,
2082 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_pctx_xfer_syntax, ett_assoc_pctx_xfer);
2085 In a correct Association Response, only one Transfer syntax shall be present.
2086 Therefore, pctx_xfer_uid, pctx_xfer_desc are used for the accept scenario in the info mode
2089 if (!is_assoc_request && pctx_result == 0) {
2090 /* Association Response, Context Accepted */
2091 dcm_set_syntax(pctx, pctx_xfer_uid, pctx_xfer_desc);
2093 cnt_xfer += 1;
2094 offset += item_len;
2095 break;
2097 default:
2098 offset += item_len;
2099 break;
2103 if (is_assoc_request) {
2105 if (cnt_abbs<1) {
2106 expert_add_info(pinfo, pctx_pitem, &ei_dcm_no_abstract_syntax);
2107 return;
2109 else if (cnt_abbs>1) {
2110 expert_add_info(pinfo, pctx_pitem, &ei_dcm_multiple_abstract_syntax);
2111 return;
2114 if (cnt_xfer==0) {
2115 expert_add_info(pinfo, pctx_pitem, &ei_dcm_no_transfer_syntax);
2116 return;
2119 if (pctx_abss_uid==NULL) {
2120 expert_add_info(pinfo, pctx_pitem, &ei_dcm_no_abstract_syntax_uid);
2121 return;
2125 else {
2127 if (cnt_xfer>1) {
2128 expert_add_info(pinfo, pctx_pitem, &ei_dcm_multiple_transfer_syntax);
2129 return;
2133 if (pctx->abss_uid==NULL) {
2134 /* Permanent copy information into structure */
2135 pctx->abss_uid = wmem_strdup(wmem_file_scope(), pctx_abss_uid);
2136 pctx->abss_desc = wmem_strdup(wmem_file_scope(), pctx_abss_desc);
2140 Copy to buffer first, because proto_item_append_text()
2141 crashed for an unknown reason using 'ID 0x%02x, %s, %s'
2142 and in my opinion correctly set parameters.
2145 if (is_assoc_request) {
2146 if (pctx_abss_desc == NULL) {
2147 buf_desc = pctx_abss_uid;
2149 else {
2150 buf_desc = wmem_strdup_printf(pinfo->pool, "%s (%s)", pctx_abss_desc, pctx_abss_uid);
2153 else
2155 if (pctx_result==0) {
2156 /* Accepted */
2157 buf_desc = wmem_strdup_printf(pinfo->pool, "ID 0x%02x, %s, %s, %s",
2158 pctx_id, pctx_result_desc,
2159 dcm_uid_or_desc(pctx->xfer_uid, pctx->xfer_desc),
2160 dcm_uid_or_desc(pctx->abss_uid, pctx->abss_desc));
2162 else {
2163 /* Rejected */
2164 buf_desc = wmem_strdup_printf(pinfo->pool, "ID 0x%02x, %s, %s",
2165 pctx_id, pctx_result_desc,
2166 dcm_uid_or_desc(pctx->abss_uid, pctx->abss_desc));
2169 proto_item_append_text(pctx_pitem, "%s", buf_desc);
2174 Decode the user info item in a Association Request or Response
2176 static void
2177 dissect_dcm_userinfo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset, uint32_t len, const char *pitem_prefix)
2180 proto_item *userinfo_pitem = NULL;
2181 proto_tree *userinfo_ptree = NULL; /* Tree for presentation context details */
2183 uint8_t item_type;
2184 uint16_t item_len;
2186 bool first_item=true;
2188 char *info_max_pdu=NULL;
2189 char *info_impl_uid=NULL;
2190 char *info_impl_version=NULL;
2191 const char *dummy=NULL;
2193 uint32_t endpos;
2195 endpos = offset + len;
2197 item_type = tvb_get_uint8(tvb, offset-4);
2198 item_len = tvb_get_ntohs(tvb, offset-2);
2200 userinfo_pitem = proto_tree_add_item(tree, hf_dcm_info, tvb, offset-4, item_len+4, ENC_NA);
2201 proto_item_set_text(userinfo_pitem, "%s", pitem_prefix);
2202 userinfo_ptree = proto_item_add_subtree(userinfo_pitem, ett_assoc_info);
2204 proto_tree_add_uint(userinfo_ptree, hf_dcm_assoc_item_type, tvb, offset-4, 1, item_type); /* The type is only one byte long */
2205 proto_tree_add_uint(userinfo_ptree, hf_dcm_assoc_item_len, tvb, offset-2, 2, item_len);
2207 while (offset < endpos) {
2209 item_type = tvb_get_uint8(tvb, offset);
2210 item_len = tvb_get_ntohs(tvb, 2 + offset);
2212 offset += 4;
2213 switch (item_type) {
2214 case 0x51: /* Max length */
2216 dissect_dcm_assoc_item(tvb, pinfo, userinfo_ptree, offset-4,
2217 "Max PDU Length: ", DCM_ITEM_VALUE_TYPE_UINT32, &info_max_pdu, &dummy,
2218 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_pdu_maxlen, ett_assoc_info_uid);
2220 if (!first_item) {
2221 proto_item_append_text(userinfo_pitem, ", ");
2223 proto_item_append_text(userinfo_pitem, "Max PDU Length %s", info_max_pdu);
2224 first_item=false;
2226 offset += item_len;
2227 break;
2229 case 0x52: /* UID */
2231 /* Parse Item. Works also in info mode where dcm_pctx_tree is NULL */
2232 dissect_dcm_assoc_item(tvb, pinfo, userinfo_ptree, offset-4,
2233 "Implementation UID: ", DCM_ITEM_VALUE_TYPE_STRING, &info_impl_uid, &dummy,
2234 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_info_uid, ett_assoc_info_uid);
2236 if (!first_item) {
2237 proto_item_append_text(userinfo_pitem, ", ");
2239 proto_item_append_text(userinfo_pitem, "Implementation UID %s", info_impl_uid);
2240 first_item=false;
2242 offset += item_len;
2243 break;
2245 case 0x55: /* version */
2247 dissect_dcm_assoc_item(tvb, pinfo, userinfo_ptree, offset-4,
2248 "Implementation Version: ", DCM_ITEM_VALUE_TYPE_STRING, &info_impl_version, &dummy,
2249 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_info_version, ett_assoc_info_version);
2251 if (!first_item) {
2252 proto_item_append_text(userinfo_pitem, ", ");
2254 proto_item_append_text(userinfo_pitem, "Version %s", info_impl_version);
2255 first_item=false;
2257 offset += item_len;
2258 break;
2260 case 0x53: /* async negotiation */
2262 dissect_dcm_assoc_async_negotiation(tvb, userinfo_ptree, offset-4);
2264 offset += item_len;
2265 break;
2267 case 0x54: /* scp/scu role selection */
2269 dissect_dcm_assoc_role_selection(tvb, pinfo, userinfo_ptree, offset-4);
2271 offset += item_len;
2272 break;
2274 case 0x56: /* extended negotiation */
2276 dissect_dcm_assoc_sopclass_extneg(tvb, pinfo, userinfo_ptree, offset-4);
2278 offset += item_len;
2279 break;
2281 case 0x58: /* User Identify */
2283 dissect_dcm_assoc_user_identify(tvb, pinfo, userinfo_ptree, offset-4);
2285 offset += item_len;
2286 break;
2288 default:
2290 dissect_dcm_assoc_unknown(tvb, userinfo_ptree, offset-4);
2292 offset += item_len;
2293 break;
2300 Create a subtree for association requests or responses
2302 static uint32_t
2303 dissect_dcm_assoc_detail(tvbuff_t *tvb, packet_info *pinfo, proto_item *ti,
2304 dcm_state_assoc_t *assoc, uint32_t offset, uint32_t len)
2306 proto_tree *assoc_tree = NULL; /* Tree for PDU details */
2308 uint8_t item_type;
2309 uint16_t item_len;
2311 uint32_t endpos;
2313 char *item_value = NULL;
2314 const char *item_description = NULL;
2316 endpos = offset + len;
2318 assoc_tree = proto_item_add_subtree(ti, ett_assoc);
2319 while (offset < endpos) {
2321 item_type = tvb_get_uint8(tvb, offset);
2322 item_len = tvb_get_ntohs(tvb, 2 + offset);
2324 if (item_len == 0) {
2325 expert_add_info(pinfo, ti, &ei_dcm_assoc_item_len);
2326 return endpos;
2329 offset += 4;
2331 switch (item_type) {
2332 case 0x10: /* Application context */
2333 dissect_dcm_assoc_item(tvb, pinfo, assoc_tree, offset-4,
2334 "Application Context: ", DCM_ITEM_VALUE_TYPE_UID, &item_value, &item_description,
2335 &hf_dcm_assoc_item_type, &hf_dcm_assoc_item_len, &hf_dcm_actx, ett_assoc_actx);
2337 offset += item_len;
2338 break;
2340 case 0x20: /* Presentation context request */
2341 dissect_dcm_pctx(tvb, pinfo, assoc_tree, assoc, offset, item_len, "Presentation Context: ", true);
2342 offset += item_len;
2343 break;
2345 case 0x21: /* Presentation context reply */
2346 dissect_dcm_pctx(tvb, pinfo, assoc_tree, assoc, offset, item_len, "Presentation Context: ", false);
2347 offset += item_len;
2348 break;
2350 case 0x50: /* User Info */
2351 dissect_dcm_userinfo(tvb, pinfo, assoc_tree, offset, item_len, "User Info: ");
2352 offset += item_len;
2353 break;
2355 default:
2356 offset += item_len;
2357 break;
2361 return offset;
2365 static uint32_t
2366 dissect_dcm_pdv_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
2367 dcm_state_assoc_t *assoc, uint32_t offset, dcm_state_pdv_t **pdv)
2369 /* Dissect Context and Flags of a PDV and create new PDV structure */
2371 proto_item *pdv_ctx_pitem = NULL;
2372 proto_item *pdv_flags_pitem = NULL;
2374 dcm_state_pctx_t *pctx = NULL;
2375 dcm_state_pdv_t *pdv_first_data = NULL;
2377 const char *desc_flag = NULL; /* Flag Description in tree */
2378 char *desc_header = NULL; /* Used for PDV description */
2380 uint8_t flags = 0, o_flags = 0;
2381 uint8_t pctx_id = 0;
2383 /* 1 Byte Context */
2384 pctx_id = tvb_get_uint8(tvb, offset);
2385 pctx = dcm_state_pctx_get(assoc, pctx_id, false);
2387 if (pctx && pctx->xfer_uid) {
2388 proto_tree_add_uint_format(tree, hf_dcm_pdv_ctx, tvb, offset, 1,
2389 pctx_id, "Context: 0x%02x (%s, %s)", pctx_id,
2390 dcm_uid_or_desc(pctx->xfer_uid, pctx->xfer_desc),
2391 dcm_uid_or_desc(pctx->abss_uid, pctx->abss_desc));
2393 else {
2394 pdv_ctx_pitem=proto_tree_add_uint_format(tree, hf_dcm_pdv_ctx, tvb, offset, 1,
2395 pctx_id, "Context: 0x%02x not found. A-ASSOCIATE request not found in capture.", pctx_id);
2397 expert_add_info(pinfo, pdv_ctx_pitem, &ei_dcm_pdv_ctx);
2399 if (pctx == NULL) {
2400 /* only create presentation context, if it does not yet exist */
2402 /* Create fake PCTX and guess Syntax ILE, ELE, EBE */
2403 pctx = dcm_state_pctx_new(assoc, pctx_id);
2405 /* To be done: Guess Syntax */
2406 pctx->syntax = DCM_UNK;
2409 offset +=1;
2411 /* Create PDV structure:
2413 Since we can have multiple PDV per packet (offset) and
2414 multiple merged packets per PDV (the tvb raw_offset)
2415 we need both values to uniquely identify a PDV
2418 *pdv = dcm_state_pdv_get(pctx, pinfo->num, tvb_raw_offset(tvb)+offset, true);
2419 if (*pdv == NULL) {
2420 return 0; /* Failed to allocate memory */
2423 /* 1 Byte Flag */
2424 /* PS3.8 E.2 Bits 2 through 7 are always set to 0 by the sender and never checked by the receiver. */
2425 o_flags = tvb_get_uint8(tvb, offset);
2426 flags = 0x3 & o_flags;
2428 (*pdv)->pctx_id = pctx_id;
2430 switch (flags) {
2431 case 0: /* 00 */
2432 if (0 != (0xfc & o_flags))
2433 desc_flag = "Data, More Fragments (Warning: Invalid)";
2434 else
2435 desc_flag = "Data, More Fragments";
2437 (*pdv)->is_flagvalid = true;
2438 (*pdv)->is_command = false;
2439 (*pdv)->is_last_fragment = false;
2440 (*pdv)->syntax = pctx->syntax; /* Inherit syntax for data PDVs*/
2441 break;
2443 case 2: /* 10 */
2444 if (0 != (0xfc & o_flags))
2445 desc_flag = "Data, Last Fragment (Warning: Invalid)";
2446 else
2447 desc_flag = "Data, Last Fragment";
2449 (*pdv)->is_flagvalid = true;
2450 (*pdv)->is_command = false;
2451 (*pdv)->is_last_fragment = true;
2452 (*pdv)->syntax = pctx->syntax; /* Inherit syntax for data PDVs*/
2453 break;
2455 case 1: /* 01 */
2456 if (0 != (0xfc & o_flags))
2457 desc_flag = "Command, More Fragments (Warning: Invalid)";
2458 else
2459 desc_flag = "Command, More Fragments";
2460 desc_header = wmem_strdup(wmem_file_scope(), "Command"); /* Will be overwritten with real command tag */
2462 (*pdv)->is_flagvalid = true;
2463 (*pdv)->is_command = true;
2464 (*pdv)->is_last_fragment = false;
2465 (*pdv)->syntax = DCM_ILE; /* Command tags are always little endian*/
2466 break;
2468 case 3: /* 11 */
2469 if (0 != (0xfc & o_flags))
2470 desc_flag = "Command, Last Fragment (Warning: Invalid)";
2471 else
2472 desc_flag = "Command, Last Fragment";
2473 desc_header = wmem_strdup(wmem_file_scope(), "Command");
2475 (*pdv)->is_flagvalid = true;
2476 (*pdv)->is_command = true;
2477 (*pdv)->is_last_fragment = true;
2478 (*pdv)->syntax = DCM_ILE; /* Command tags are always little endian*/
2479 break;
2481 default:
2482 desc_flag = "Invalid Flags";
2483 desc_header = wmem_strdup(wmem_file_scope(), desc_flag);
2485 (*pdv)->is_flagvalid = false;
2486 (*pdv)->is_command = false;
2487 (*pdv)->is_last_fragment = false;
2488 (*pdv)->syntax = DCM_UNK;
2491 if (!PINFO_FD_VISITED(pinfo)) {
2492 (*pdv)->reassembly_id = pctx->reassembly_count;
2493 if ((*pdv)->is_last_fragment) {
2494 pctx->reassembly_count++;
2498 if (flags == 0 || flags == 2) {
2499 /* Data PDV */
2500 pdv_first_data = dcm_state_pdv_get_obj_start(*pdv);
2502 if (pdv_first_data->prev && pdv_first_data->prev->is_command) {
2503 /* Every Data PDV sequence should be preceded by a Command PDV,
2504 so we should always hit this for a correct capture
2507 if (pctx->abss_desc && g_str_has_suffix(pctx->abss_desc, "Storage")) {
2508 /* Should be done far more intelligent, e.g. does not catch the (Retired) ones */
2509 if (flags == 0) {
2510 desc_header = wmem_strdup_printf(wmem_file_scope(), "%s Fragment", pctx->abss_desc);
2512 else {
2513 desc_header = wmem_strdup(wmem_file_scope(), pctx->abss_desc);
2515 (*pdv)->is_storage = true;
2517 else {
2518 /* Use previous command and append DATA*/
2519 desc_header = wmem_strdup_printf(wmem_file_scope(), "%s-DATA", pdv_first_data->prev->desc);
2522 else {
2523 desc_header = wmem_strdup(wmem_file_scope(), "DATA");
2527 (*pdv)->desc = desc_header;
2529 pdv_flags_pitem = proto_tree_add_uint_format(tree, hf_dcm_pdv_flags, tvb, offset, 1,
2530 flags, "Flags: 0x%02x (%s)", o_flags, desc_flag);
2532 if (o_flags>3) {
2533 expert_add_info(pinfo, pdv_flags_pitem, &ei_dcm_pdv_flags);
2535 offset +=1;
2537 return offset;
2541 Based on the value representation, decode the value of one tag.
2542 Support VM>1 for most types, but not all. Returns new offset
2544 static uint32_t
2545 dissect_dcm_tag_value(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, dcm_state_pdv_t *pdv,
2546 uint32_t offset, uint16_t grp, uint16_t elm,
2547 uint32_t vl, uint32_t vl_max, const char* vr, char **tag_value)
2550 proto_item *pitem = NULL;
2551 unsigned encoding = (pdv->syntax == DCM_EBE) ? ENC_BIG_ENDIAN : ENC_LITTLE_ENDIAN;
2554 /* Make sure we have all the bytes of the item; this should throw
2555 and exception if vl_max is so large that it causes the offset
2556 to overflow. */
2557 tvb_ensure_bytes_exist(tvb, offset, vl_max);
2559 /* ---------------------------------------------------------------------------
2560 Potentially long types. Obey vl_max
2561 ---------------------------------------------------------------------------
2564 if ((strncmp(vr, "AE", 2) == 0) || (strncmp(vr, "AS", 2) == 0) || (strncmp(vr, "CS", 2) == 0) ||
2565 (strncmp(vr, "DA", 2) == 0) || (strncmp(vr, "DS", 2) == 0) || (strncmp(vr, "DT", 2) == 0) ||
2566 (strncmp(vr, "IS", 2) == 0) || (strncmp(vr, "LO", 2) == 0) || (strncmp(vr, "LT", 2) == 0) ||
2567 (strncmp(vr, "PN", 2) == 0) || (strncmp(vr, "SH", 2) == 0) || (strncmp(vr, "ST", 2) == 0) ||
2568 (strncmp(vr, "TM", 2) == 0) || (strncmp(vr, "UI", 2) == 0) || (strncmp(vr, "UT", 2) == 0) ) {
2570 15 ways to represent a string.
2572 For LT, ST, UT the DICOM standard does not allow multi-value
2573 For the others, VM is built into 'automatically, because it uses '\' as separator
2576 char *vals;
2577 dcm_uid_t const *uid = NULL;
2578 uint8_t val8;
2580 val8 = tvb_get_uint8(tvb, offset + vl_max - 1);
2581 if (val8 == 0x00) {
2582 /* Last byte of string is 0x00, i.e. padded */
2583 vals = tvb_format_text(pinfo->pool, tvb, offset, vl_max - 1);
2585 else {
2586 vals = tvb_format_text(pinfo->pool, tvb, offset, vl_max);
2589 if (grp == 0x0000 && elm == 0x0902) {
2590 /* The error comment */
2591 pdv->comment = g_strstrip(wmem_strdup(wmem_file_scope(), vals));
2594 if ((strncmp(vr, "UI", 2) == 0)) {
2595 /* This is a UID. Attempt a lookup. Will only return something for classes of course */
2597 uid = (dcm_uid_t const *)wmem_map_lookup(dcm_uid_table, (void *) vals);
2598 if (uid) {
2599 *tag_value = wmem_strdup_printf(pinfo->pool, "%s (%s)", vals, uid->name);
2601 else {
2602 *tag_value = vals;
2605 else {
2606 if (strlen(vals) > 50) {
2607 *tag_value = wmem_strdup_printf(pinfo->pool, "%s%s", ws_utf8_truncate(vals, 50), UTF8_HORIZONTAL_ELLIPSIS);
2609 else {
2610 *tag_value = vals;
2613 proto_tree_add_string(tree, hf_dcm_tag_value_str, tvb, offset, vl_max, *tag_value);
2616 else if ((strncmp(vr, "OB", 2) == 0) || (strncmp(vr, "OW", 2) == 0) ||
2617 (strncmp(vr, "OF", 2) == 0) || (strncmp(vr, "OD", 2) == 0)) {
2619 /* Array of Bytes, Words, Float, or Doubles. Don't perform any decoding. VM=1. Multiple arrays are not possible */
2621 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, vl_max, NULL, "%s", "(binary)");
2623 *tag_value = wmem_strdup(pinfo->pool, "(binary)");
2625 else if (strncmp(vr, "UN", 2) == 0) {
2627 /* Usually the case for private tags in implicit syntax, since tag was not found and VR not specified.
2628 Not been able to create UN yet. No need to support VM > 1.
2631 uint8_t val8;
2632 char *vals;
2633 uint32_t i;
2635 /* String detector, i.e. check if we only have alpha-numeric character */
2636 bool is_string = true;
2637 bool is_padded = false;
2639 for (i = 0; i < vl_max ; i++) {
2640 val8 = tvb_get_uint8(tvb, offset + i);
2642 if ((val8 == 0x09) || (val8 == 0x0A) || (val8 == 0x0D)) {
2643 /* TAB, LF, CR */
2645 else if ((val8 >= 0x20) && (val8 <= 0x7E)) {
2646 /* No extended ASCII, 0-9, A-Z, a-z */
2648 else if ((i == vl_max -1) && (val8 == 0x00)) {
2649 /* Last Byte can be null*/
2650 is_padded = true;
2652 else {
2653 /* Here's the code */
2654 is_string = false;
2658 if (is_string) {
2659 vals = tvb_format_text(pinfo->pool, tvb, offset, (is_padded ? vl_max - 1 : vl_max));
2660 proto_tree_add_string(tree, hf_dcm_tag_value_str, tvb, offset, vl_max, vals);
2662 *tag_value = vals;
2664 else {
2665 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, vl_max, NULL, "%s", "(binary)");
2667 *tag_value = wmem_strdup(pinfo->pool, "(binary)");
2670 /* ---------------------------------------------------------------------------
2671 Smaller types. vl/vl_max are not used. Fixed item length from 2 to 8 bytes
2672 ---------------------------------------------------------------------------
2674 else if (strncmp(vr, "AT", 2) == 0) {
2676 /* Attribute Tag e.g. (0022,8866). 2*2 Bytes, Can have VM > 1 */
2678 uint16_t at_grp;
2679 uint16_t at_elm;
2680 char *at_value = "";
2682 /* 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
2683 So make it at least one loop so set it to at least 4.
2686 uint32_t vm_item_len = 4;
2687 uint32_t vm_item_count = dcm_vm_item_count(vl_max, vm_item_len);
2689 uint32_t i = 0;
2690 while (i < vm_item_count) {
2691 at_grp = tvb_get_uint16(tvb, offset+ i*vm_item_len, encoding);
2692 at_elm = tvb_get_uint16(tvb, offset+ i*vm_item_len+2, encoding);
2694 proto_tree_add_uint_format_value(tree, hf_dcm_tag_value_32u, tvb, offset + i*vm_item_len, vm_item_len,
2695 (at_grp << 16) | at_elm, "%04x,%04x", at_grp, at_elm);
2697 at_value = wmem_strdup_printf(pinfo->pool,"%s(%04x,%04x)", at_value, at_grp, at_elm);
2699 i++;
2701 *tag_value = at_value;
2703 else if (strncmp(vr, "FL", 2) == 0) { /* Single Float. Can be VM > 1, but not yet supported */
2705 float valf = tvb_get_ieee_float(tvb, offset, encoding);
2707 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, 4, NULL, "%f", valf);
2709 *tag_value = wmem_strdup_printf(pinfo->pool, "%f", valf);
2711 else if (strncmp(vr, "FD", 2) == 0) { /* Double Float. Can be VM > 1, but not yet supported */
2713 double vald = tvb_get_ieee_double(tvb, offset, encoding);
2715 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, 8, NULL, "%f", vald);
2717 *tag_value = wmem_strdup_printf(pinfo->pool, "%f", vald);
2719 else if (strncmp(vr, "SL", 2) == 0) { /* Signed Long. Can be VM > 1, but not yet supported */
2720 int32_t val32;
2722 proto_tree_add_item_ret_int(tree, hf_dcm_tag_value_32s, tvb, offset, 4, encoding, &val32);
2724 *tag_value = wmem_strdup_printf(pinfo->pool, "%d", val32);
2726 else if (strncmp(vr, "SS", 2) == 0) { /* Signed Short. Can be VM > 1, but not yet supported */
2727 int32_t val32;
2729 proto_tree_add_item_ret_int(tree, hf_dcm_tag_value_16s, tvb, offset, 2, encoding, &val32);
2731 *tag_value = wmem_strdup_printf(pinfo->pool, "%d", val32);
2733 else if (strncmp(vr, "UL", 2) == 0) { /* Unsigned Long. Can be VM > 1, but not yet supported */
2734 uint32_t val32;
2736 proto_tree_add_item_ret_uint(tree, hf_dcm_tag_value_32u, tvb, offset, 4, encoding, &val32);
2738 *tag_value = wmem_strdup_printf(pinfo->pool, "%u", val32);
2740 else if (strncmp(vr, "US", 2) == 0) { /* Unsigned Short. Can be VM > 1, but not yet supported */
2741 const char *status_message = NULL;
2742 uint16_t val16 = tvb_get_uint16(tvb, offset, encoding);
2744 if (grp == 0x0000 && elm == 0x0100) {
2745 /* This is a command */
2746 pdv->command = wmem_strdup(wmem_file_scope(), val_to_str_const(val16, dcm_cmd_vals, " "));
2747 *tag_value = pdv->command;
2749 else if (grp == 0x0000 && elm == 0x0900) {
2750 /* This is a status message. If value is not 0x0000, add an expert info */
2752 status_message = dcm_rsp2str(val16);
2753 *tag_value = wmem_strdup_printf(pinfo->pool, "%s (0x%02x)", status_message, val16);
2755 if ((val16 & 0xFF00) == 0xFF00) {
2756 /* C-FIND also has a 0xFF01 as a valid response */
2757 pdv->is_pending = true;
2759 else if (val16 != 0x0000) {
2760 /* Neither success nor pending */
2761 pdv->is_warning = true;
2764 pdv->status = wmem_strdup(wmem_file_scope(), status_message);
2767 else {
2768 *tag_value = wmem_strdup_printf(pinfo->pool, "%u", val16);
2771 if (grp == 0x0000) {
2772 if (elm == 0x0110) { /* (0000,0110) Message ID */
2773 pdv->message_id = val16;
2775 else if (elm == 0x0120) { /* (0000,0120) Message ID Being Responded To */
2776 pdv->message_id_resp = val16;
2778 else if (elm == 0x1020) { /* (0000,1020) Number of Remaining Sub-operations */
2779 pdv->no_remaining = val16;
2781 else if (elm == 0x1021) { /* (0000,1021) Number of Completed Sub-operations */
2782 pdv->no_completed = val16;
2784 else if (elm == 0x1022) { /* (0000,1022) Number of Failed Sub-operations */
2785 pdv->no_failed = val16;
2787 else if (elm == 0x1023) { /* (0000,1023) Number of Warning Sub-operations */
2788 pdv->no_warning = val16;
2792 pitem = proto_tree_add_uint_format_value(tree, hf_dcm_tag_value_16u, tvb, offset, 2,
2793 val16, "%s", *tag_value);
2795 if (pdv->is_warning && status_message) {
2796 expert_add_info(pinfo, pitem, &ei_dcm_status_msg);
2799 /* Invalid VR, can only occur with Explicit syntax */
2800 else {
2801 proto_tree_add_bytes_format_value(tree, hf_dcm_tag_value_byte, tvb, offset, vl_max,
2802 NULL, "%s", (vl > vl_max ? "" : "(unknown VR)"));
2804 *tag_value = wmem_strdup(pinfo->pool, "(unknown VR)");
2806 offset += vl_max;
2808 return offset;
2813 Return true, if the required size does not fit at position 'offset'.
2815 static bool
2816 dcm_tag_is_open(dcm_state_pdv_t *pdv, uint32_t startpos, uint32_t offset, uint32_t endpos, uint32_t size_required)
2819 if (offset + size_required > endpos) {
2821 pdv->open_tag.is_header_fragmented = true;
2822 pdv->open_tag.len_decoded = endpos - startpos;
2824 return true;
2826 else {
2827 return false;
2831 static dcm_tag_t const *
2832 dcm_tag_lookup(uint16_t grp, uint16_t elm)
2835 static dcm_tag_t const *tag_def = NULL;
2837 static dcm_tag_t const tag_unknown = { 0x00000000, "(unknown)", "UN", "1", 0, 0};
2838 static dcm_tag_t const tag_private = { 0x00000000, "Private Tag", "UN", "1", 0, 0 };
2839 static dcm_tag_t const tag_private_grp_len = { 0x00000000, "Private Tag Group Length", "UL", "1", 0, 0 };
2840 static dcm_tag_t const tag_grp_length = { 0x00000000, "Group Length", "UL", "1", 0, 0 };
2842 /* Try a direct hit first before doing a masked search */
2843 tag_def = (dcm_tag_t const *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((uint32_t)grp << 16) | elm));
2845 if (tag_def == NULL) {
2847 /* No match found */
2848 if ((grp & 0x0001) && (elm == 0x0000)) {
2849 tag_def = &tag_private_grp_len;
2851 else if (grp & 0x0001) {
2852 tag_def = &tag_private;
2854 else if (elm == 0x0000) {
2855 tag_def = &tag_grp_length;
2858 /* There are a few tags that require a mask to be found */
2859 else if (((grp & 0xFF00) == 0x5000) || ((grp & 0xFF00) == 0x6000) || ((grp & 0xFF00) == 0x7F00)) {
2860 /* Do a special for groups 0x50xx, 0x60xx and 0x7Fxx */
2861 tag_def = (dcm_tag_t const *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER((((uint32_t)grp & 0xFF00) << 16) | elm));
2863 else if ((grp == 0x0020) && ((elm & 0xFF00) == 0x3100)) {
2864 tag_def = (dcm_tag_t const *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((uint32_t)grp << 16) | (elm & 0xFF00)));
2866 else if ((grp == 0x0028) && ((elm & 0xFF00) == 0x0400)) {
2867 /* This map was done to 0x041x */
2868 tag_def = (dcm_tag_t const *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((uint32_t)grp << 16) | (elm & 0xFF0F) | 0x0010));
2870 else if ((grp == 0x0028) && ((elm & 0xFF00) == 0x0800)) {
2871 tag_def = (dcm_tag_t const *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((uint32_t)grp << 16) | (elm & 0xFF0F)));
2873 else if (grp == 0x1000) {
2874 tag_def = (dcm_tag_t const *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((uint32_t)grp << 16) | (elm & 0x000F)));
2876 else if (grp == 0x1010) {
2877 tag_def = (dcm_tag_t const *)wmem_map_lookup(dcm_tag_table, GUINT_TO_POINTER(((uint32_t)grp << 16) | (elm & 0x0000)));
2880 if (tag_def == NULL) {
2881 /* Still no match found */
2882 tag_def = &tag_unknown;
2886 return tag_def;
2889 static char*
2890 dcm_tag_summary(packet_info *pinfo, uint16_t grp, uint16_t elm, uint32_t vl, const char *tag_desc, const char *vr,
2891 bool is_retired, bool is_implicit)
2894 char *desc_mod;
2895 char *tag_vl;
2896 char *tag_sum;
2898 if (is_retired) {
2899 desc_mod = wmem_strdup_printf(pinfo->pool, "(Retired) %-35.35s", tag_desc);
2901 else {
2902 desc_mod = wmem_strdup_printf(pinfo->pool, "%-45.45s", tag_desc);
2905 if (vl == 0xFFFFFFFF) {
2906 tag_vl = wmem_strdup_printf(pinfo->pool, "%10.10s", "<udef>");
2908 else {
2909 tag_vl = wmem_strdup_printf(pinfo->pool, "%10u", vl); /* Show as dec */
2912 if (is_implicit) tag_sum = wmem_strdup_printf(pinfo->pool, "(%04x,%04x) %s %s", grp, elm, tag_vl, desc_mod);
2913 else tag_sum = wmem_strdup_printf(pinfo->pool, "(%04x,%04x) %s %s [%s]", grp, elm, tag_vl, desc_mod, vr);
2915 return tag_sum;
2919 Decode one tag. If it is a sequence or item start create a subtree. Returns new offset.
2920 http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html
2922 static uint32_t
2923 // NOLINTNEXTLINE(misc-no-recursion)
2924 dissect_dcm_tag(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
2925 dcm_state_pdv_t *pdv, uint32_t offset, uint32_t endpos,
2926 bool is_first_tag, const char **tag_description,
2927 bool *end_of_seq_or_item)
2931 proto_tree *tag_ptree = NULL; /* Tree for decoded tag details */
2932 proto_tree *seq_ptree = NULL; /* Possible subtree for sequences and items */
2934 proto_item *tag_pitem = NULL;
2935 dcm_tag_t const *tag_def = NULL;
2937 int ett;
2939 const char *vr = NULL;
2940 char *tag_value = ""; /* Tag Value converted to a string */
2941 char *tag_summary;
2943 uint32_t vl = 0;
2944 uint16_t vl_1 = 0;
2945 uint16_t vl_2 = 0;
2947 uint32_t offset_tag = 0; /* Remember offsets for tree, since the tree */
2948 uint32_t offset_vr = 0; /* header is created pretty late */
2949 uint32_t offset_vl = 0;
2951 uint32_t vl_max = 0; /* Max Value Length to Parse */
2953 uint16_t grp = 0;
2954 uint16_t elm = 0;
2956 uint32_t len_decoded_remaing = 0;
2958 /* Decode the syntax a little more */
2959 uint32_t encoding = (pdv->syntax == DCM_EBE) ? ENC_BIG_ENDIAN : ENC_LITTLE_ENDIAN;
2960 bool is_implicit = (pdv->syntax == DCM_ILE);
2961 bool is_vl_long = false; /* True for 4 Bytes length fields */
2963 bool is_sequence = false; /* True for Sequence Tags */
2964 bool is_item = false; /* True for Sequence Item Tags */
2966 *tag_description = NULL; /* Reset description. It's wmem packet scope memory, so not really bad*/
2968 offset_tag = offset;
2971 if (pdv->prev && is_first_tag) {
2972 len_decoded_remaing = pdv->prev->open_tag.len_decoded;
2976 /* Since we may have a fragmented header, check for every attribute,
2977 whether we have already decoded left-overs from the previous PDV.
2978 Since we have implicit & explicit syntax, copying the open tag to
2979 a buffer without decoding, would have caused tvb_get_xxtohs()
2980 implementations on the copy.
2982 An alternative approach would have been to resemble the PDVs first.
2984 The attempts to reassemble without named sources (to be implemented)
2985 were very sensitive to missing packets. In such a case, no packet
2986 of a PDV chain was decoded, not even the start.
2988 So for the time being, use this rather cumbersome approach.
2990 For every two bytes (PDV length are always a factor of 2)
2991 check whether we have enough data in the buffer and store the value
2992 accordingly. In the next frame check, whether we have decoded this yet.
2995 /* Group */
2996 if (len_decoded_remaing >= 2) {
2997 grp = pdv->prev->open_tag.grp;
2998 len_decoded_remaing -= 2;
3000 else {
3002 if (dcm_tag_is_open(pdv, offset_tag, offset, endpos, 2))
3003 return endpos; /* Exit if needed */
3005 grp = tvb_get_uint16(tvb, offset, encoding);
3006 offset += 2;
3007 pdv->open_tag.grp = grp;
3010 /* Element */
3011 if (len_decoded_remaing >= 2) {
3012 elm = pdv->prev->open_tag.elm;
3013 len_decoded_remaing -= 2;
3015 else {
3017 if (dcm_tag_is_open(pdv, offset_tag, offset, endpos, 2))
3018 return endpos; /* Exit if needed */
3020 elm = tvb_get_uint16(tvb, offset, encoding);
3021 offset += 2;
3022 pdv->open_tag.elm = elm;
3025 /* Find the best matching tag */
3026 tag_def = dcm_tag_lookup(grp, elm);
3028 /* Value Representation */
3029 offset_vr = offset;
3030 if ((grp == 0xFFFE) && (elm == 0xE000 || elm == 0xE00D || elm == 0xE0DD)) {
3031 /* Item start, Item Delimitation or Sequence Delimitation */
3032 vr = "UL";
3033 is_vl_long = true; /* These tags always have a 4 byte length field */
3035 else if (is_implicit) {
3036 /* Get VR from tag definition */
3037 vr = wmem_strdup(pinfo->pool, tag_def->vr);
3038 is_vl_long = true; /* Implicit always has 4 byte length field */
3040 else {
3042 if (len_decoded_remaing >= 2) {
3043 vr = wmem_strdup(pinfo->pool, pdv->prev->open_tag.vr);
3044 len_decoded_remaing -= 2;
3046 else {
3048 /* Controlled exit, if VR does not fit. */
3049 if (dcm_tag_is_open(pdv, offset_tag, offset_vr, endpos, 2))
3050 return endpos;
3052 vr = (char *)tvb_get_string_enc(pinfo->pool, tvb, offset, 2, ENC_ASCII);
3053 offset += 2;
3055 wmem_free(wmem_file_scope(), pdv->open_tag.vr);
3056 pdv->open_tag.vr = wmem_strdup(wmem_file_scope(), vr); /* needs to survive within a session */
3059 if ((strcmp(vr, "OB") == 0) || (strcmp(vr, "OW") == 0) || (strcmp(vr, "OF") == 0) || (strcmp(vr, "OD") == 0) || (strcmp(vr, "OL") == 0) ||
3060 (strcmp(vr, "SQ") == 0) || (strcmp(vr, "UC") == 0) || (strcmp(vr, "UR") == 0) || (strcmp(vr, "UT") == 0) || (strcmp(vr, "UN") == 0)) {
3061 /* Part 5, Table 7.1-1 in the standard */
3062 /* Length is always 4 bytes: OB, OD, OF, OL, OW, SQ, UC, UR, UT or UN */
3064 is_vl_long = true;
3066 /* Skip 2 Bytes */
3067 if (len_decoded_remaing >= 2) {
3068 len_decoded_remaing -= 2;
3070 else {
3071 if (dcm_tag_is_open(pdv, offset_tag, offset_vr, endpos, 2))
3072 return endpos;
3073 offset += 2;
3076 else {
3077 is_vl_long = false;
3082 /* Value Length. This is rather cumbersome code to get a 4 byte length, but in the
3083 fragmented case, we have 2*2 bytes. So always use that pattern
3086 offset_vl = offset;
3087 if (len_decoded_remaing >= 2) {
3088 vl_1 = pdv->prev->open_tag.vl_1;
3089 len_decoded_remaing -= 2;
3091 else {
3093 if (dcm_tag_is_open(pdv, offset_tag, offset_vl, endpos, 2))
3094 return endpos;
3095 vl_1 = tvb_get_uint16(tvb, offset, encoding);
3096 offset += 2;
3097 pdv->open_tag.vl_1 = vl_1;
3100 if (is_vl_long) {
3102 if (len_decoded_remaing >= 2) {
3103 vl_2 = pdv->prev->open_tag.vl_2;
3105 else {
3107 if (dcm_tag_is_open(pdv, offset_tag, offset_vl+2, endpos, 2))
3108 return endpos;
3109 vl_2 = tvb_get_uint16(tvb, offset, encoding);
3110 offset += 2;
3111 pdv->open_tag.vl_2 = vl_2;
3114 if (encoding == ENC_LITTLE_ENDIAN) vl = (vl_2 << 16) + vl_1;
3115 else vl = (vl_1 << 16) + vl_2;
3117 else {
3118 vl = vl_1;
3121 /* Now we have most of the information, except for sequences and items with undefined
3122 length :-/. But, whether we know the length or not, we now need to create the tree
3123 item and subtree, before we can loop into sequences and items
3125 Display the information we collected so far. Don't wait until the value is parsed,
3126 because that parsing might cause an exception. If that happens within a sequence,
3127 the sequence tag would not show up with the value
3129 Use different ett_ for Sequences & Items, so that fold/unfold state makes sense
3132 tag_summary = dcm_tag_summary(pinfo, grp, elm, vl, tag_def->description, vr, tag_def->is_retired, is_implicit);
3133 is_sequence = (strcmp(vr, "SQ") == 0) || (vl == 0xFFFFFFFF);
3134 is_item = ((grp == 0xFFFE) && (elm == 0xE000));
3136 if ((is_sequence | is_item) && global_dcm_seq_subtree) {
3137 ett = is_sequence ? ett_dcm_data_seq : ett_dcm_data_item;
3139 else {
3140 ett = ett_dcm_data_tag;
3143 if (vl == 0xFFFFFFFF) {
3144 /* 'Just' mark header as the length of the item */
3145 tag_ptree = proto_tree_add_subtree(tree, tvb, offset_tag, offset - offset_tag, ett, &tag_pitem, tag_summary);
3146 vl_max = 0; /* We don't know who long this sequence/item is */
3148 else if (offset + vl <= endpos) {
3149 /* Show real length of item */
3150 tag_ptree = proto_tree_add_subtree(tree, tvb, offset_tag, offset + vl - offset_tag, ett, &tag_pitem, tag_summary);
3151 vl_max = vl;
3153 else {
3154 /* Value is longer than what we have in the PDV, -> we do have a OPEN tag */
3155 tag_ptree = proto_tree_add_subtree(tree, tvb, offset_tag, endpos - offset_tag, ett, &tag_pitem, tag_summary);
3156 vl_max = endpos - offset;
3159 /* If you are going to touch the following 25 lines, make sure you reserve a few hours to go
3160 through both display options and check for proper tree display :-)
3162 if (is_sequence | is_item) {
3164 if (global_dcm_seq_subtree) {
3165 /* Use different ett_ for Sequences & Items, so that fold/unfold state makes sense */
3166 seq_ptree = tag_ptree;
3167 if (!global_dcm_tag_subtree) {
3168 tag_ptree = NULL;
3171 else {
3172 seq_ptree = tree;
3173 if (!global_dcm_tag_subtree) {
3174 tag_ptree = NULL;
3178 else {
3179 /* For tags */
3180 if (!global_dcm_tag_subtree) {
3181 tag_ptree = NULL;
3185 /* ---------------------------------------------------------------
3186 Tag details as separate items
3187 ---------------------------------------------------------------
3190 proto_tree_add_uint_format_value(tag_ptree, hf_dcm_tag, tvb, offset_tag, 4,
3191 (grp << 16) | elm, "%04x,%04x (%s)", grp, elm, tag_def->description);
3193 /* Add VR to tag detail, except for sequence items */
3194 if (!is_item) {
3195 if (is_implicit) {
3196 /* Select header, since no VR is present in implicit syntax */
3197 proto_tree_add_string(tag_ptree, hf_dcm_tag_vr, tvb, offset_tag, 4, vr);
3199 else {
3200 proto_tree_add_string(tag_ptree, hf_dcm_tag_vr, tvb, offset_vr, 2, vr);
3204 /* Add length to tag detail */
3205 proto_tree_add_uint(tag_ptree, hf_dcm_tag_vl, tvb, offset_vl, (is_vl_long ? 4 : 2), vl);
3208 /* ---------------------------------------------------------------
3209 Finally the Tag Value
3210 ---------------------------------------------------------------
3212 if ((is_sequence || is_item) && (vl > 0)) {
3213 /* Sequence or Item Start */
3215 uint32_t endpos_item = 0;
3216 bool local_end_of_seq_or_item = false;
3217 bool is_first_desc = true;
3219 const char *item_description = NULL; /* Will be allocated as wmem packet scope memory in dissect_dcm_tag() */
3221 if (vl == 0xFFFFFFFF) {
3222 /* Undefined length */
3224 increment_dissection_depth(pinfo);
3225 while ((!local_end_of_seq_or_item) && (!pdv->open_tag.is_header_fragmented) && (offset < endpos)) {
3227 offset = dissect_dcm_tag(tvb, pinfo, seq_ptree, pdv, offset, endpos, false, &item_description, &local_end_of_seq_or_item);
3229 if (item_description && global_dcm_seq_subtree) {
3230 proto_item_append_text(tag_pitem, (is_first_desc ? " %s" : ", %s"), item_description);
3231 is_first_desc = false;
3234 decrement_dissection_depth(pinfo);
3236 else {
3237 /* Defined length */
3238 endpos_item = offset + vl_max;
3240 increment_dissection_depth(pinfo);
3241 while (offset < endpos_item) {
3243 offset = dissect_dcm_tag(tvb, pinfo, seq_ptree, pdv, offset, endpos_item, false, &item_description, &local_end_of_seq_or_item);
3245 if (item_description && global_dcm_seq_subtree) {
3246 proto_item_append_text(tag_pitem, (is_first_desc ? " %s" : ", %s"), item_description);
3247 is_first_desc = false;
3250 decrement_dissection_depth(pinfo);
3252 } /* if ((is_sequence || is_item) && (vl > 0)) */
3253 else if ((grp == 0xFFFE) && (elm == 0xE00D)) {
3254 /* Item delimitation for items with undefined length */
3255 *end_of_seq_or_item = true;
3257 else if ((grp == 0xFFFE) && (elm == 0xE0DD)) {
3258 /* Sequence delimitation for sequences with undefined length */
3259 *end_of_seq_or_item = true;
3261 else if (vl == 0) {
3262 /* No value for this tag */
3264 /* The following copy is needed. tag_value is post processed with g_strstrip()
3265 and that one will crash the whole application, when a constant is used.
3268 tag_value = wmem_strdup(pinfo->pool, "<Empty>");
3270 else if (vl > vl_max) {
3271 /* Tag is longer than the PDV/PDU. Don't perform any decoding */
3273 char *tag_desc;
3275 proto_tree_add_bytes_format(tag_ptree, hf_dcm_tag_value_byte, tvb, offset, vl_max,
3276 NULL, "%-8.8sBytes %d - %d [start]", "Value:", 1, vl_max);
3278 tag_value = wmem_strdup_printf(pinfo->pool, "<Bytes %d - %d, start>", 1, vl_max);
3279 offset += vl_max;
3281 /* Save the needed data for reuse, and subsequent packets
3282 This will leak a little within the session.
3284 But since we may have tags being closed and reopen in the same PDV
3285 we will always need to store this
3288 tag_desc = dcm_tag_summary(pinfo, grp, elm, vl, tag_def->description, vr, tag_def->is_retired, is_implicit);
3290 if (pdv->open_tag.desc == NULL) {
3291 pdv->open_tag.is_value_fragmented = true;
3292 pdv->open_tag.desc = wmem_strdup(wmem_file_scope(), tag_desc);
3293 pdv->open_tag.len_total = vl;
3294 pdv->open_tag.len_remaining = vl - vl_max;
3297 else {
3298 /* Regular value. Identify the type, decode and display */
3300 increment_dissection_depth(pinfo);
3301 offset = dissect_dcm_tag_value(tvb, pinfo, tag_ptree, pdv, offset, grp, elm, vl, vl_max, vr, &tag_value);
3302 decrement_dissection_depth(pinfo);
3304 /* -------------------------------------------------------------
3305 We have decoded the value. Now store those tags of interest
3306 -------------------------------------------------------------
3309 /* Store SOP Class and Instance UID in first PDV of this object */
3310 if (grp == 0x0008 && elm == 0x0016) {
3311 dcm_state_pdv_get_obj_start(pdv)->sop_class_uid = wmem_strdup(wmem_file_scope(), tag_value);
3313 else if (grp == 0x0008 && elm == 0x0018) {
3314 dcm_state_pdv_get_obj_start(pdv)->sop_instance_uid = wmem_strdup(wmem_file_scope(), tag_value);
3316 else if (grp == 0x0000 && elm == 0x0100) {
3317 /* This is the command tag -> overwrite existing PDV description */
3318 pdv->desc = wmem_strdup(wmem_file_scope(), tag_value);
3323 /* -------------------------------------------------------------------
3324 Add the value to the already constructed item
3325 -------------------------------------------------------------------
3328 proto_item_append_text(tag_pitem, " %s", tag_value);
3330 if (tag_def->add_to_summary) {
3331 *tag_description = wmem_strdup(pinfo->pool, g_strstrip(tag_value));
3334 return offset;
3338 'Decode' open tags from previous PDV. It mostly ends in 'continuation' or 'end' in the description.
3340 static uint32_t
3341 dissect_dcm_tag_open(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
3342 dcm_state_pdv_t *pdv, uint32_t offset, uint32_t endpos, bool *is_first_tag)
3345 proto_item *pitem = NULL;
3347 uint32_t tag_value_fragment_len = 0;
3349 if ((pdv->prev) && (pdv->prev->open_tag.len_remaining > 0)) {
3350 /* Not first PDV in the given presentation context (Those don't have remaining data to parse :-) */
3351 /* And previous PDV has left overs, i.e. this is a continuation PDV */
3353 if (endpos - offset >= pdv->prev->open_tag.len_remaining) {
3355 Remaining bytes are equal or more than we expect for the open tag
3356 Finally reach the end of this tag. Don't touch the open_tag structure
3357 of this PDV, as we may see a new open tag at the end
3359 tag_value_fragment_len = pdv->prev->open_tag.len_remaining;
3360 pdv->is_corrupt = false;
3362 else if (pdv->is_flagvalid && pdv->is_last_fragment) {
3364 The tag is not yet complete, however, the flag indicates that it should be
3365 Therefore end this tag and issue an expert_add_info. Don't touch the
3366 open_tag structure of this PDV, as we may see a new open tag at the end
3368 tag_value_fragment_len = endpos - offset;
3369 pdv->is_corrupt = true;
3371 else {
3373 * More to do for this tag
3375 tag_value_fragment_len = endpos - offset;
3377 /* Set data in current PDV structure */
3378 if (!pdv->open_tag.is_value_fragmented) {
3379 /* No need to do it twice or more */
3381 pdv->open_tag.is_value_fragmented = true;
3382 pdv->open_tag.len_total = pdv->prev->open_tag.len_total;
3383 pdv->open_tag.len_remaining = pdv->prev->open_tag.len_remaining - tag_value_fragment_len;
3384 pdv->open_tag.desc = wmem_strdup(wmem_file_scope(), pdv->prev->open_tag.desc);
3387 pdv->is_corrupt = false;
3390 if (pdv->is_corrupt) {
3391 pitem = proto_tree_add_bytes_format(tree, hf_dcm_data_tag, tvb,
3392 offset, tag_value_fragment_len, NULL,
3393 "%s <incomplete>", pdv->prev->open_tag.desc);
3395 expert_add_info(pinfo, pitem, &ei_dcm_data_tag);
3398 else {
3399 proto_tree_add_bytes_format(tree, hf_dcm_data_tag, tvb,
3400 offset, tag_value_fragment_len, NULL,
3401 "%s <Bytes %d - %d, %s>", pdv->prev->open_tag.desc,
3402 pdv->prev->open_tag.len_total - pdv->prev->open_tag.len_remaining + 1,
3403 pdv->prev->open_tag.len_total - pdv->prev->open_tag.len_remaining + tag_value_fragment_len,
3404 (pdv->prev->open_tag.len_remaining > tag_value_fragment_len ? "continuation" : "end") );
3407 offset += tag_value_fragment_len;
3408 *is_first_tag = false;
3411 return offset;
3415 Decode the tag section inside a PDV. This can be a single combined dataset
3416 or DICOM natively split PDVs. Therefore it needs to resume previously opened tags.
3417 For data PDVs, only process tags when tree is set or listening to export objects tap.
3418 For command PDVs, process all tags.
3419 On export copy the content to the export buffer.
3421 static uint32_t
3422 dissect_dcm_pdv_body(
3423 tvbuff_t *tvb,
3424 packet_info *pinfo,
3425 proto_tree *tree,
3426 dcm_state_assoc_t *assoc,
3427 dcm_state_pdv_t *pdv,
3428 uint32_t offset,
3429 uint32_t pdv_body_len,
3430 char **pdv_description)
3432 const char *tag_value = NULL;
3433 bool dummy = false;
3434 uint32_t startpos = offset;
3435 uint32_t endpos = 0;
3437 endpos = offset + pdv_body_len;
3439 if (pdv->is_command || tree || have_tap_listener(dicom_eo_tap)) {
3440 /* Performance optimization starts here. Don't put any COL_INFO related stuff in here */
3442 if (pdv->syntax == DCM_UNK) {
3443 /* Eventually, we will have a syntax detector. Until then, don't decode */
3445 proto_tree_add_bytes_format(tree, hf_dcm_data_tag, tvb,
3446 offset, pdv_body_len, NULL,
3447 "(%04x,%04x) %-8x Unparsed data", 0, 0, pdv_body_len);
3449 else {
3451 bool is_first_tag = true;
3453 /* Treat the left overs */
3454 offset = dissect_dcm_tag_open(tvb, pinfo, tree, pdv, offset, endpos, &is_first_tag);
3456 /* Decode all tags, sequences and items in this PDV recursively */
3457 while (offset < endpos) {
3458 offset = dissect_dcm_tag(tvb, pinfo, tree, pdv, offset, endpos, is_first_tag, &tag_value, &dummy);
3459 is_first_tag = false;
3464 *pdv_description = pdv->desc;
3466 if (pdv->is_command) {
3468 if (pdv->is_warning) {
3469 if (pdv->comment) {
3470 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (%s, %s)", pdv->desc, pdv->status, pdv->comment);
3472 else {
3473 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (%s)", pdv->desc, pdv->status);
3477 else if (global_dcm_cmd_details) {
3478 /* Show command details in header */
3480 if (pdv->message_id > 0) {
3481 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s ID=%d", pdv->desc, pdv->message_id);
3483 else if (pdv->message_id_resp > 0) {
3485 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s ID=%d", pdv->desc, pdv->message_id_resp);
3487 if (pdv->no_completed > 0) {
3488 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s C=%d", *pdv_description, pdv->no_completed);
3490 if (pdv->no_remaining > 0) {
3491 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s R=%d", *pdv_description, pdv->no_remaining);
3493 if (pdv->no_warning > 0) {
3494 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s W=%d", *pdv_description, pdv->no_warning);
3496 if (pdv->no_failed > 0) {
3497 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s F=%d", *pdv_description, pdv->no_failed);
3499 if (!pdv->is_pending && pdv->status)
3501 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (%s)", *pdv_description, pdv->status);
3507 if (have_tap_listener(dicom_eo_tap)) {
3509 if (pdv->data_len == 0) {
3510 /* Copy pure DICOM data to buffer, without PDV flags
3511 Packet scope for the memory allocation is too small, since we may have PDV in different tvb.
3512 Therefore check if this was already done.
3514 pdv->data = wmem_alloc0(wmem_file_scope(), pdv_body_len);
3515 pdv->data_len = pdv_body_len;
3516 tvb_memcpy(tvb, pdv->data, startpos, pdv_body_len);
3518 if ((pdv_body_len > 0) && (pdv->is_last_fragment)) {
3519 /* At the last segment, merge all related previous PDVs and copy to export buffer */
3520 dcm_export_create_object(pinfo, assoc, pdv);
3524 return endpos;
3528 Handle one PDV inside a data PDU. When needed, perform the reassembly of PDV fragments.
3529 PDV fragments are different from TCP fragmentation.
3530 Create PDV object when needed.
3531 Return pdv_description to be used e.g. in COL_INFO.
3533 static uint32_t
3534 dissect_dcm_pdv_fragmented(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
3535 dcm_state_assoc_t *assoc, uint32_t offset, uint32_t pdv_len, char **pdv_description)
3538 conversation_t *conv = NULL;
3540 dcm_state_pdv_t *pdv = NULL;
3542 tvbuff_t *combined_tvb = NULL;
3543 fragment_head *head = NULL;
3545 uint32_t reassembly_id;
3546 uint32_t pdv_body_len;
3548 pdv_body_len = pdv_len-2;
3550 /* Dissect Context ID, Find PDV object, Decode Command/Data flag and More Fragments flag */
3551 offset = dissect_dcm_pdv_header(tvb, pinfo, tree, assoc, offset, &pdv);
3553 if (global_dcm_reassemble)
3555 /* Combine the different PDVs. This is the default preference and useful in most scenarios.
3556 This will create one 'huge' PDV. E.g. a CT image will fits in one buffer.
3558 conv = find_conversation_pinfo(pinfo, 0);
3560 /* Try to create somewhat unique ID.
3561 Include the conversation index, to separate TCP session
3562 Include bits from the reassembly number in the current Presentation
3563 Context (that we track ourselves) in order to distinguish between
3564 PDV fragments from the same frame but different reassemblies.
3566 DISSECTOR_ASSERT(conv);
3568 /* The following expression seems to executed late in VS2017 in 'RelWithDebInf'.
3569 Therefore it may appear as 0 at first
3571 reassembly_id = (((conv->conv_index) & 0x000FFFFF) << 12) +
3572 ((uint32_t)(pdv->pctx_id) << 4) + ((uint32_t)(pdv->reassembly_id & 0xF));
3574 /* This one will chain the packets until 'is_last_fragment' */
3575 head = fragment_add_seq_next(
3576 &dcm_pdv_reassembly_table,
3577 tvb,
3578 offset,
3579 pinfo,
3580 reassembly_id,
3581 NULL,
3582 pdv_body_len,
3583 !(pdv->is_last_fragment));
3585 if (head && (head->next == NULL)) {
3586 /* Was not really fragmented, therefore use 'conventional' decoding.
3587 process_reassembled_data() does not cope with two PDVs in the same frame, therefore catch it here
3590 offset = dissect_dcm_pdv_body(tvb, pinfo, tree, assoc, pdv, offset, pdv_body_len, pdv_description);
3592 else
3594 /* Will return a complete buffer, once last fragment is hit.
3595 The description is not used in packet-dcm. COL_INFO is set specifically in dissect_dcm_pdu()
3597 combined_tvb = process_reassembled_data(
3598 tvb,
3599 offset,
3600 pinfo,
3601 "Reassembled PDV",
3602 head,
3603 &dcm_pdv_fragment_items,
3604 NULL,
3605 tree);
3607 if (combined_tvb == NULL) {
3608 /* Just show this as a fragment */
3610 if (head && head->reassembled_in != pinfo->num) {
3612 if (pdv->desc) {
3613 /* We know the presentation context already */
3614 *pdv_description = wmem_strdup_printf(pinfo->pool, "%s (reassembled in #%u)", pdv->desc, head->reassembled_in);
3616 else {
3617 /* Decoding of the presentation context did not occur yet or did not succeed */
3618 *pdv_description = wmem_strdup_printf(pinfo->pool, "PDV Fragment (reassembled in #%u)", head->reassembled_in);
3621 else {
3622 /* We don't know the last fragment yet (and/or we'll never see it).
3623 This can happen, e.g. when TCP packet arrive our of order.
3625 *pdv_description = wmem_strdup(pinfo->pool, "PDV Fragment");
3628 offset += pdv_body_len;
3630 else {
3631 /* Decode reassembled data. This needs to be += */
3632 offset += dissect_dcm_pdv_body(combined_tvb, pinfo, tree, assoc, pdv, 0, tvb_captured_length(combined_tvb), pdv_description);
3636 else {
3637 /* Do not reassemble DICOM PDVs, i.e. decode PDVs one by one.
3638 This may be useful when troubleshooting PDU length issues,
3639 or to better understand the PDV split.
3640 The tag level decoding is more challenging, as leftovers need
3641 to be displayed adequately. Not a big deal for binary values.
3643 offset = dissect_dcm_pdv_body(tvb, pinfo, tree, assoc, pdv, offset, pdv_body_len, pdv_description);
3646 return offset;
3649 static uint32_t
3650 dissect_dcm_pdu_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
3651 dcm_state_assoc_t *assoc, uint32_t offset, uint32_t pdu_len, char **pdu_data_description)
3654 /* 04 P-DATA-TF
3655 1 1 reserved
3656 2 4 length
3657 - (1+) presentation data value (PDV) items
3658 6 4 length
3659 10 1 Presentation Context ID (odd ints 1 - 255)
3660 - PDV
3661 11 1 header
3662 0x01 if set, contains Message Command info, else Message Data
3663 0x02 if set, contains last fragment
3666 proto_tree *pdv_ptree; /* Tree for item details */
3667 proto_item *pdv_pitem, *pdvlen_item;
3669 char *buf_desc = NULL; /* PDU description */
3670 char *pdv_description = NULL;
3672 bool first_pdv = true;
3674 uint32_t endpos = 0;
3675 uint32_t pdv_len = 0;
3677 endpos = offset + pdu_len;
3679 /* Loop through multiple PDVs */
3680 while (offset < endpos) {
3682 pdv_len = tvb_get_ntohl(tvb, offset);
3684 pdv_ptree = proto_tree_add_subtree(tree, tvb, offset, pdv_len+4, ett_dcm_data_pdv, &pdv_pitem, "PDV");
3686 pdvlen_item = proto_tree_add_item(pdv_ptree, hf_dcm_pdv_len, tvb, offset, 4, ENC_BIG_ENDIAN);
3687 offset += 4;
3689 if ((pdv_len + 4 > pdu_len) || (pdv_len + 4 < pdv_len)) {
3690 expert_add_info_format(pinfo, pdvlen_item, &ei_dcm_pdv_len, "Invalid PDV length (too large)");
3691 return endpos;
3693 else if (pdv_len <= 2) {
3694 expert_add_info_format(pinfo, pdvlen_item, &ei_dcm_pdv_len, "Invalid PDV length (too small)");
3695 return endpos;
3697 else if (((pdv_len >> 1) << 1) != pdv_len) {
3698 expert_add_info_format(pinfo, pdvlen_item, &ei_dcm_pdv_len, "Invalid PDV length (not even)");
3699 return endpos;
3702 offset = dissect_dcm_pdv_fragmented(tvb, pinfo, pdv_ptree, assoc, offset, pdv_len, &pdv_description);
3704 /* The following doesn't seem to work anymore */
3705 if (pdv_description) {
3706 if (first_pdv) {
3707 buf_desc = wmem_strdup(pinfo->pool, pdv_description);
3709 else {
3710 buf_desc = wmem_strdup_printf(pinfo->pool, "%s, %s", buf_desc, pdv_description);
3714 proto_item_append_text(pdv_pitem, ", %s", pdv_description);
3715 first_pdv=false;
3719 *pdu_data_description = buf_desc;
3721 return offset;
3726 Test for DICOM traffic.
3728 - Minimum 10 Bytes
3729 - Look for the association request
3730 - Check PDU size vs TCP payload size
3732 Since used in heuristic mode, be picky for performance reasons.
3733 We are called in static mode, once we decoded the association request and called conversation_set_dissector()
3734 They we can be more liberal on the packet selection
3736 static bool
3737 test_dcm(tvbuff_t *tvb)
3740 uint8_t pdu_type;
3741 uint32_t pdu_len;
3742 uint16_t vers;
3745 Ensure that the tvb_captured_length is big enough before fetching the values.
3746 Otherwise it can trigger an exception during the heuristic check,
3747 preventing next heuristic dissectors from being called
3749 tvb_reported_length() is the real size of the packet as transmitted on the wire
3750 tvb_captured_length() is the number of bytes captured (so you always have captured <= reported).
3752 The 10 bytes represent an association request header including the 2 reserved bytes not used below
3753 In the captures at hand, the parsing result was equal.
3756 if (tvb_captured_length(tvb) < 8) {
3757 return false;
3759 if (tvb_reported_length(tvb) < 10) {
3760 return false;
3763 pdu_type = tvb_get_uint8(tvb, 0);
3764 pdu_len = tvb_get_ntohl(tvb, 2);
3765 vers = tvb_get_ntohs(tvb, 6);
3767 /* Exit, if not an association request at version 1 */
3768 if (!(pdu_type == 1 && vers == 1)) {
3769 return false;
3772 /* Exit if TCP payload is bigger than PDU length (plus header)
3773 OK for PRESENTATION_DATA, questionable for ASSOCIATION requests
3775 if (tvb_reported_length(tvb) > pdu_len + 6) {
3776 return false;
3779 return true;
3783 Main function to decode DICOM traffic. Supports reassembly of TCP packets.
3785 static int
3786 dissect_dcm_main(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, bool is_port_static)
3789 uint8_t pdu_type = 0;
3790 uint32_t pdu_start = 0;
3791 uint32_t pdu_len = 0;
3792 uint32_t tlen = 0;
3794 int offset = 0;
3797 TCP packets are assembled well by wireshark in conjunction with the dissectors.
3799 Therefore, we will only see properly aligned PDUs, at the beginning of the buffer.
3800 So if the buffer does not start with the PDU header, it's not DICOM traffic.
3802 Do the byte checking as early as possible.
3803 The heuristic hook requires an association request
3805 DICOM PDU are nice, but need to be managed
3807 We can have any combination:
3808 - One or more DICOM PDU per TCP packet
3809 - PDU split over different TCP packets
3810 - And both together, i.e. some complete PDUs and then a fraction of a new PDU in a TCP packet
3812 This function will handle multiple PDUs per TCP packet and will ask for more data,
3813 if the last PDU does not fit
3815 It does not reassemble fragmented PDVs by purpose, since the Tag Value parsing needs to be done
3816 per Tag, and PDU recombination here would
3817 a) need to eliminate PDU/PDV/Ctx header (12 bytes)
3818 b) not show the true DICOM logic in transfer
3820 The length check is tricky. If not a PDV continuation, 10 Bytes are required. For PDV continuation
3821 anything seems to be possible, depending on the buffer alignment of the sending process.
3825 tlen = tvb_reported_length(tvb);
3827 pdu_type = tvb_get_uint8(tvb, 0);
3828 if (pdu_type == 0 || pdu_type > 7) /* Wrong PDU type. 'Or' is slightly more efficient than 'and' */
3829 return 0; /* No bytes taken from the stack */
3831 if (is_port_static) {
3832 /* Port is defined explicitly, or association request was previously found successfully.
3833 Be more tolerant on minimum packet size. Also accept < 6
3836 if (tlen < 6) {
3837 /* we need 6 bytes at least to get PDU length */
3838 pinfo->desegment_offset = offset;
3839 pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
3840 return tvb_captured_length(tvb);
3845 /* Passing this point, we should always have tlen >= 6 */
3847 pdu_len = tvb_get_ntohl(tvb, 2);
3848 if (pdu_len < 4) /* The smallest PDUs are ASSOC Rejects & Release messages */
3849 return 0;
3851 /* Mark it. This is a DICOM packet */
3853 col_set_str(pinfo->cinfo, COL_PROTOCOL, "DICOM");
3855 /* Process all PDUs in the buffer */
3856 while (pdu_start < tlen) {
3857 uint32_t old_pdu_start;
3859 if ((pdu_len+6) > (tlen-offset)) {
3861 /* PDU is larger than the remaining packet (buffer), therefore request whole PDU
3862 The next time this function is called, tlen will be equal to pdu_len
3865 pinfo->desegment_offset = offset;
3866 pinfo->desegment_len = (pdu_len+6) - (tlen-offset);
3867 return tvb_captured_length(tvb);
3870 /* Process a whole PDU */
3871 offset=dissect_dcm_pdu(tvb, pinfo, tree, pdu_start);
3873 /* Next PDU */
3874 old_pdu_start = pdu_start;
3875 pdu_start = pdu_start + pdu_len + 6;
3876 if (pdu_start <= old_pdu_start) {
3877 expert_add_info_format(pinfo, NULL, &ei_dcm_invalid_pdu_length, "Invalid PDU length (%u)", pdu_len);
3878 break;
3881 if (pdu_start < tlen - 6) {
3882 /* we got at least 6 bytes of the next PDU still in the buffer */
3883 pdu_len = tvb_get_ntohl(tvb, pdu_start+2);
3885 else {
3886 pdu_len = 0;
3889 return offset;
3893 Callback function used to register
3895 static int
3896 dissect_dcm_static(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
3898 /* Less checking on ports that match */
3899 return dissect_dcm_main(tvb, pinfo, tree, true);
3903 Test for an Association Request. Decode, when successful.
3905 static bool
3906 dissect_dcm_heuristic(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
3909 /* This will be potentially called for every packet */
3911 if (!test_dcm(tvb))
3912 return false;
3915 Conversation_set_dissector() is called inside dcm_state_get() once
3916 we have enough details. From there on, we will be 'static'
3919 if (dissect_dcm_main(tvb, pinfo, tree, false) == 0) {
3920 /* there may have been another reason why it is not DICOM */
3921 return false;
3924 return true;
3929 Only set a valued with col_set_str() if it does not yet exist.
3930 (In a multiple PDV scenario, col_set_str() actually appends for the subsequent calls)
3932 static void col_set_str_conditional(column_info *cinfo, const int el, const char* str)
3934 const char *col_string = col_get_text(cinfo, el);
3936 if (col_string == NULL || !g_str_has_prefix(col_string, str))
3938 col_add_str(cinfo, el, str);
3943 CSV add a value to a column, if it does not exist yet
3945 static void col_append_str_conditional(column_info *cinfo, const int el, const char* str)
3947 const char *col_string = col_get_text(cinfo, el);
3949 if (col_string == NULL || !g_strrstr(col_string, str))
3951 col_append_fstr(cinfo, el, ", %s", str);
3956 Dissect a single DICOM PDU. Can be an association or a data package. Creates a tree item.
3958 static uint32_t
3959 dissect_dcm_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, uint32_t offset)
3961 proto_tree *dcm_ptree=NULL; /* Root DICOM tree and its item */
3962 proto_item *dcm_pitem=NULL;
3964 dcm_state_t *dcm_data=NULL;
3965 dcm_state_assoc_t *assoc=NULL;
3967 uint8_t pdu_type=0;
3968 uint32_t pdu_len=0;
3970 char *pdu_data_description=NULL;
3972 /* Get or create conversation. Used to store context IDs and xfer Syntax */
3974 dcm_data = dcm_state_get(pinfo, true);
3975 if (dcm_data == NULL) { /* Internal error. Failed to create main DICOM data structure */
3976 return offset;
3979 dcm_pitem = proto_tree_add_item(tree, proto_dcm, tvb, offset, -1, ENC_NA);
3980 dcm_ptree = proto_item_add_subtree(dcm_pitem, ett_dcm);
3982 /* PDU type is only one byte, then one byte reserved */
3983 pdu_type = tvb_get_uint8(tvb, offset);
3984 proto_tree_add_item(dcm_ptree, hf_dcm_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN);
3985 offset += 2;
3987 pdu_len = tvb_get_ntohl(tvb, offset);
3988 proto_tree_add_item(dcm_ptree, hf_dcm_pdu_len, tvb, offset, 4, ENC_BIG_ENDIAN);
3989 offset += 4;
3991 /* Find previously detected association, else create a new one object*/
3992 assoc = dcm_state_assoc_get(dcm_data, pinfo->num, true);
3994 if (assoc == NULL) { /* Internal error. Failed to create association structure */
3995 return offset;
3998 if (pdu_type == 4) {
4000 col_set_str_conditional(pinfo->cinfo, COL_INFO, "P-DATA");
4002 /* Everything that needs to be shown in any UI column (like COL_INFO)
4003 needs to be calculated also with tree == null
4005 offset = dissect_dcm_pdu_data(tvb, pinfo, dcm_ptree, assoc, offset, pdu_len, &pdu_data_description);
4007 if (pdu_data_description) {
4008 proto_item_append_text(dcm_pitem, ", %s", pdu_data_description);
4009 col_append_str_conditional(pinfo->cinfo, COL_INFO, pdu_data_description);
4012 else {
4014 /* Decode Association request, response, reject, abort details */
4015 offset = dissect_dcm_assoc_header(tvb, pinfo, dcm_ptree, offset, assoc, pdu_type, pdu_len);
4018 return offset; /* return the number of processed bytes */
4023 Register the protocol with Wireshark
4025 void
4026 proto_register_dcm(void)
4028 static hf_register_info hf[] = {
4029 { &hf_dcm_pdu_type, { "PDU Type", "dicom.pdu.type",
4030 FT_UINT8, BASE_HEX, VALS(dcm_pdu_ids), 0, NULL, HFILL } },
4031 { &hf_dcm_pdu_len, { "PDU Length", "dicom.pdu.len",
4032 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4034 { &hf_dcm_assoc_version, { "Protocol Version", "dicom.assoc.version",
4035 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4036 { &hf_dcm_assoc_called, { "Called AE Title", "dicom.assoc.ae.called",
4037 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4038 { &hf_dcm_assoc_calling, { "Calling AE Title", "dicom.assoc.ae.calling",
4039 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4040 { &hf_dcm_assoc_reject_result, { "Result", "dicom.assoc.reject.result",
4041 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4042 { &hf_dcm_assoc_reject_source, { "Source", "dicom.assoc.reject.source",
4043 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4044 { &hf_dcm_assoc_reject_reason, { "Reason", "dicom.assoc.reject.reason",
4045 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4046 { &hf_dcm_assoc_abort_source, { "Source", "dicom.assoc.abort.source",
4047 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4048 { &hf_dcm_assoc_abort_reason, { "Reason", "dicom.assoc.abort.reason",
4049 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4050 { &hf_dcm_assoc_item_type, { "Item Type", "dicom.assoc.item.type",
4051 FT_UINT8, BASE_HEX, VALS(dcm_assoc_item_type), 0, NULL, HFILL } },
4052 { &hf_dcm_assoc_item_len, { "Item Length", "dicom.assoc.item.len",
4053 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4055 { &hf_dcm_actx, { "Application Context", "dicom.actx",
4056 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4057 { &hf_dcm_pctx_id, { "Presentation Context ID", "dicom.pctx.id",
4058 FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } },
4059 { &hf_dcm_pctx_result, { "Presentation Context Result", "dicom.pctx.result",
4060 FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } },
4061 { &hf_dcm_pctx_abss_syntax, { "Abstract Syntax", "dicom.pctx.abss.syntax",
4062 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4063 { &hf_dcm_pctx_xfer_syntax, { "Transfer Syntax", "dicom.pctx.xfer.syntax",
4064 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4065 { &hf_dcm_info, { "User Info", "dicom.userinfo",
4066 FT_NONE, BASE_NONE, NULL, 0, "This field contains the ACSE User Information Item of the A-ASSOCIATErequest.", HFILL } },
4067 { &hf_dcm_info_uid, { "Implementation Class UID", "dicom.userinfo.uid",
4068 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4069 { &hf_dcm_info_version, { "Implementation Version", "dicom.userinfo.version",
4070 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4071 { &hf_dcm_info_extneg, { "Extended Negotiation", "dicom.userinfo.extneg",
4072 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 } },
4073 { &hf_dcm_info_extneg_sopclassuid_len, { "SOP Class UID Length", "dicom.userinfo.extneg.sopclassuid.len",
4074 FT_UINT16, BASE_DEC, NULL, 0, "This field contains the length of the SOP Class UID in the Extended Negotiation Sub-Item.", HFILL } },
4075 { &hf_dcm_info_extneg_sopclassuid, { "SOP Class UID", "dicom.userinfo.extneg.sopclassuid",
4076 FT_STRING, BASE_NONE, NULL, 0, "This field contains the SOP Class UID in the Extended Negotiation Sub-Item.", HFILL } },
4077 { &hf_dcm_info_extneg_relational_query, { "Relational-queries", "dicom.userinfo.extneg.relational",
4078 FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if relational queries are supported.", HFILL } },
4079 { &hf_dcm_info_extneg_date_time_matching, { "Combined Date-Time matching", "dicom.userinfo.extneg.datetimematching",
4080 FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if combined date-time matching is supported.", HFILL } },
4081 { &hf_dcm_info_extneg_fuzzy_semantic_matching, { "Fuzzy semantic matching", "dicom.userinfo.extneg.fuzzymatching",
4082 FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if fuzzy semantic matching of person names is supported.", HFILL } },
4083 { &hf_dcm_info_extneg_timezone_query_adjustment, { "Timezone query adjustment", "dicom.userinfo.extneg.timezone",
4084 FT_UINT8, BASE_HEX, NULL, 0, "This field indicates, if timezone query adjustment is supported.", HFILL } },
4085 { &hf_dcm_info_rolesel, { "SCP/SCU Role Selection", "dicom.userinfo.rolesel",
4086 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 } },
4087 { &hf_dcm_info_rolesel_sopclassuid_len, { "SOP Class UID Length", "dicom.userinfo.rolesel.sopclassuid.len",
4088 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 } },
4089 { &hf_dcm_info_rolesel_sopclassuid, { "SOP Class UID", "dicom.userinfo.rolesel.sopclassuid",
4090 FT_STRING, BASE_NONE, NULL, 0, "This field contains the SOP Class UID in the SCP/SCU Role Selection Sub-Item.", HFILL } },
4091 { &hf_dcm_info_rolesel_scurole, { "SCU-role", "dicom.userinfo.rolesel.scurole",
4092 FT_UINT8, BASE_HEX, NULL, 0, "This field contains the SCU-role as defined for the Association-requester.", HFILL } },
4093 { &hf_dcm_info_rolesel_scprole, { "SCP-role", "dicom.userinfo.rolesel.scprole",
4094 FT_UINT8, BASE_HEX, NULL, 0, "This field contains the SCP-role as defined for the Association-requester.", HFILL } },
4095 { &hf_dcm_info_async_neg, { "Asynchronous Operations (and sub-operations) Window Negotiation", "dicom.userinfo.asyncneg",
4096 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 } },
4097 { &hf_dcm_info_async_neg_max_num_ops_inv, { "Maximum-number-operations-invoked", "dicom.userinfo.asyncneg.maxnumopsinv",
4098 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 } },
4099 { &hf_dcm_info_async_neg_max_num_ops_per, { "Maximum-number-operations-performed", "dicom.userinfo.asyncneg.maxnumopsper",
4100 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 } },
4101 { &hf_dcm_info_unknown, { "Unknown", "dicom.userinfo.unknown",
4102 FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } },
4103 { &hf_dcm_assoc_item_data, { "Unknown Data", "dicom.userinfo.data",
4104 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } },
4105 { &hf_dcm_info_user_identify, { "User Identify", "dicom.userinfo.user_identify",
4106 FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } },
4107 { &hf_dcm_info_user_identify_type, { "Type", "dicom.userinfo.user_identify.type",
4108 FT_UINT8, BASE_DEC, VALS(user_identify_type_vals), 0, NULL, HFILL } },
4109 { &hf_dcm_info_user_identify_response_requested, { "Response Requested", "dicom.userinfo.user_identify.response_requested",
4110 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4111 { &hf_dcm_info_user_identify_primary_field_length, { "Primary Field Length", "dicom.userinfo.user_identify.primary_field_length",
4112 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4113 { &hf_dcm_info_user_identify_primary_field, { "Primary Field", "dicom.userinfo.user_identify.primary_field",
4114 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4115 { &hf_dcm_info_user_identify_secondary_field_length, { "Secondary Field Length", "dicom.userinfo.user_identify.secondary_field_length",
4116 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4117 { &hf_dcm_info_user_identify_secondary_field, { "Secondary Field", "dicom.userinfo.user_identify.secondary_field",
4118 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4119 { &hf_dcm_pdu_maxlen, { "Max PDU Length", "dicom.max_pdu_len",
4120 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4121 { &hf_dcm_pdv_len, { "PDV Length", "dicom.pdv.len",
4122 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4123 { &hf_dcm_pdv_ctx, { "PDV Context", "dicom.pdv.ctx",
4124 FT_UINT8, BASE_DEC, NULL, 0, NULL, HFILL } },
4125 { &hf_dcm_pdv_flags, { "PDV Flags", "dicom.pdv.flags",
4126 FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL } },
4127 { &hf_dcm_data_tag, { "Tag", "dicom.data.tag",
4128 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } },
4130 { &hf_dcm_tag, { "Tag", "dicom.tag",
4131 FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } },
4132 { &hf_dcm_tag_vr, { "VR", "dicom.tag.vr",
4133 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4134 { &hf_dcm_tag_vl, { "Length", "dicom.tag.vl",
4135 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4137 { &hf_dcm_tag_value_str, { "Value", "dicom.tag.value.str",
4138 FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL } },
4139 { &hf_dcm_tag_value_16s, { "Value", "dicom.tag.value.16s",
4140 FT_INT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4141 { &hf_dcm_tag_value_16u, { "Value", "dicom.tag.value.16u",
4142 FT_UINT16, BASE_DEC, NULL, 0, NULL, HFILL } },
4143 { &hf_dcm_tag_value_32s, { "Value", "dicom.tag.value.32s",
4144 FT_INT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4145 { &hf_dcm_tag_value_32u, { "Value", "dicom.tag.value.32u",
4146 FT_UINT32, BASE_DEC, NULL, 0, NULL, HFILL } },
4147 { &hf_dcm_tag_value_byte, { "Value", "dicom.tag.value.byte",
4148 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL } },
4150 /* Fragment entries */
4151 { &hf_dcm_pdv_fragments,
4152 { "Message fragments", "dicom.pdv.fragments",
4153 FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4154 { &hf_dcm_pdv_fragment,
4155 { "Message fragment", "dicom.pdv.fragment",
4156 FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4157 { &hf_dcm_pdv_fragment_overlap,
4158 { "Message fragment overlap", "dicom.pdv.fragment.overlap",
4159 FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4160 { &hf_dcm_pdv_fragment_overlap_conflicts,
4161 { "Message fragment overlapping with conflicting data",
4162 "dicom.pdv.fragment.overlap.conflicts",
4163 FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4164 { &hf_dcm_pdv_fragment_multiple_tails,
4165 { "Message has multiple tail fragments",
4166 "dicom.pdv.fragment.multiple_tails",
4167 FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4168 { &hf_dcm_pdv_fragment_too_long_fragment,
4169 { "Message fragment too long", "dicom.pdv.fragment.too_long_fragment",
4170 FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4171 { &hf_dcm_pdv_fragment_error,
4172 { "Message defragmentation error", "dicom.pdv.fragment.error",
4173 FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4174 { &hf_dcm_pdv_fragment_count,
4175 { "Message fragment count", "dicom.pdv.fragment_count",
4176 FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } },
4177 { &hf_dcm_pdv_reassembled_in,
4178 { "Reassembled in", "dicom.pdv.reassembled.in",
4179 FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } },
4180 { &hf_dcm_pdv_reassembled_length,
4181 { "Reassembled PDV length", "dicom.pdv.reassembled.length",
4182 FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } }
4185 /* Setup protocol subtree array */
4186 static int *ett[] = {
4187 &ett_dcm,
4188 &ett_assoc,
4189 &ett_assoc_header,
4190 &ett_assoc_actx,
4191 &ett_assoc_pctx,
4192 &ett_assoc_pctx_abss,
4193 &ett_assoc_pctx_xfer,
4194 &ett_assoc_info,
4195 &ett_assoc_info_uid,
4196 &ett_assoc_info_version,
4197 &ett_assoc_info_extneg,
4198 &ett_assoc_info_rolesel,
4199 &ett_assoc_info_async_neg,
4200 &ett_assoc_info_user_identify,
4201 &ett_assoc_info_unknown,
4202 &ett_dcm_data,
4203 &ett_dcm_data_pdv,
4204 &ett_dcm_data_tag,
4205 &ett_dcm_data_seq,
4206 &ett_dcm_data_item,
4207 &ett_dcm_pdv, /* used for fragments */
4208 &ett_dcm_pdv_fragment,
4209 &ett_dcm_pdv_fragments
4212 static ei_register_info ei[] = {
4213 { &ei_dcm_assoc_rejected, { "dicom.assoc.reject", PI_RESPONSE_CODE, PI_WARN, "Association rejected", EXPFILL }},
4214 { &ei_dcm_assoc_aborted, { "dicom.assoc.abort", PI_RESPONSE_CODE, PI_WARN, "Association aborted", EXPFILL }},
4215 { &ei_dcm_no_abstract_syntax, { "dicom.no_abstract_syntax", PI_MALFORMED, PI_ERROR, "No Abstract Syntax provided for this Presentation Context", EXPFILL }},
4216 { &ei_dcm_multiple_abstract_syntax, { "dicom.multiple_abstract_syntax", PI_MALFORMED, PI_ERROR, "More than one Abstract Syntax provided for this Presentation Context", EXPFILL }},
4217 { &ei_dcm_no_transfer_syntax, { "dicom.no_transfer_syntax", PI_MALFORMED, PI_ERROR, "No Transfer Syntax provided for this Presentation Context", EXPFILL }},
4218 { &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 }},
4219 { &ei_dcm_multiple_transfer_syntax, { "dicom.multiple_transfer_syntax", PI_MALFORMED, PI_ERROR, "Only one Transfer Syntax allowed in a Association Response", EXPFILL }},
4220 { &ei_dcm_assoc_item_len, { "dicom.assoc.item.len.invalid", PI_MALFORMED, PI_ERROR, "Invalid Association Item Length", EXPFILL }},
4221 { &ei_dcm_pdv_ctx, { "dicom.pdv.ctx.invalid", PI_MALFORMED, PI_ERROR, "Invalid Presentation Context ID", EXPFILL }},
4222 { &ei_dcm_pdv_flags, { "dicom.pdv.flags.invalid", PI_MALFORMED, PI_ERROR, "Invalid Flags", EXPFILL }},
4223 { &ei_dcm_status_msg, { "dicom.status_msg", PI_RESPONSE_CODE, PI_WARN, "Status Message", EXPFILL }},
4224 { &ei_dcm_data_tag, { "dicom.data.tag.missing", PI_MALFORMED, PI_ERROR, "Early termination of tag. Data is missing", EXPFILL }},
4225 { &ei_dcm_pdv_len, { "dicom.pdv.len.invalid", PI_MALFORMED, PI_ERROR, "Invalid PDV length", EXPFILL }},
4226 { &ei_dcm_invalid_pdu_length, { "dicom.pdu_length.invalid", PI_MALFORMED, PI_ERROR, "Invalid PDU length", EXPFILL }},
4229 module_t *dcm_module;
4230 expert_module_t* expert_dcm;
4232 /* Register the protocol name and description */
4233 proto_dcm = proto_register_protocol("DICOM", "DICOM", "dicom");
4235 /* Required function calls to register the header fields and subtrees used */
4236 proto_register_field_array(proto_dcm, hf, array_length(hf));
4237 proto_register_subtree_array(ett, array_length(ett));
4238 expert_dcm = expert_register_protocol(proto_dcm);
4239 expert_register_field_array(expert_dcm, ei, array_length(ei));
4241 /* Allow other dissectors to find this one by name. */
4242 dcm_handle = register_dissector("dicom", dissect_dcm_static, proto_dcm);
4244 dcm_module = prefs_register_protocol(proto_dcm, NULL);
4246 /* Used to migrate an older configuration file to a newer one */
4247 prefs_register_obsolete_preference(dcm_module, "heuristic");
4249 prefs_register_bool_preference(dcm_module, "export_header",
4250 "Create Meta Header on Export",
4251 "Create DICOM File Meta Header according to PS 3.10 on export for PDUs. "
4252 "If the captured PDV does not contain a SOP Class UID and SOP Instance UID "
4253 "(e.g. for command PDVs), wireshark specific ones will be created.",
4254 &global_dcm_export_header);
4256 prefs_register_uint_preference(dcm_module, "export_minsize",
4257 "Min. item size in bytes to export",
4258 "Do not show items below this size in the export list. "
4259 "Set it to 0, to see DICOM commands and responses in the list. "
4260 "Set it higher, to just export DICOM IODs (i.e. CT Images, RT Structures).", 10,
4261 &global_dcm_export_minsize);
4263 prefs_register_bool_preference(dcm_module, "seq_tree",
4264 "Create subtrees for Sequences and Items",
4265 "Create a node for sequences and items, and show children in a hierarchy. "
4266 "De-select this option, if you prefer a flat display or e.g. "
4267 "when using TShark to create a text output.",
4268 &global_dcm_seq_subtree);
4270 prefs_register_bool_preference(dcm_module, "tag_tree",
4271 "Create subtrees for DICOM Tags",
4272 "Create a node for a tag and show tag details as single elements. "
4273 "This can be useful to debug a tag and to allow display filters on these attributes. "
4274 "When using TShark to create a text output, it's better to have it disabled. ",
4275 &global_dcm_tag_subtree);
4277 prefs_register_bool_preference(dcm_module, "cmd_details",
4278 "Show command details in header",
4279 "Show message ID and number of completed, remaining, warned or failed operations in header and info column.",
4280 &global_dcm_cmd_details);
4282 prefs_register_bool_preference(dcm_module, "pdv_reassemble",
4283 "Merge fragmented PDVs",
4284 "Decode all DICOM tags in the last PDV. This will ensure the proper reassembly. "
4285 "De-select, to troubleshoot PDU length issues, or to understand PDV fragmentation. "
4286 "When not set, the decoding may fail and the exports may become corrupt.",
4287 &global_dcm_reassemble);
4289 dicom_eo_tap = register_export_object(proto_dcm, dcm_eo_packet, NULL);
4291 register_init_routine(&dcm_init);
4293 /* Register processing of fragmented DICOM PDVs */
4294 reassembly_table_register(&dcm_pdv_reassembly_table, &addresses_reassembly_table_functions);
4299 Register static TCP port range specified in preferences.
4300 Register heuristic search as well.
4302 Statically defined ports take precedence over a heuristic one. I.e., if a foreign protocol claims a port,
4303 where DICOM is running on, we would never be called, by just having the heuristic registration.
4305 This function is also called, when preferences change.
4307 void
4308 proto_reg_handoff_dcm(void)
4310 /* Adds a UI element to the preferences dialog. This is the static part. */
4311 dissector_add_uint_range_with_preference("tcp.port", DICOM_DEFAULT_RANGE, dcm_handle);
4314 The following shows up as child protocol of 'DICOM' in 'Enable/Disable Protocols ...'
4316 The registration procedure for dissectors is a two-stage procedure.
4318 In stage 1, dissectors create tables in which other dissectors can register them. That's the stage in which proto_register_ routines are called.
4319 In stage 2, dissectors register themselves in tables created in stage 1. That's the stage in which proto_reg_handoff_ routines are called.
4321 heur_dissector_add() needs to be called in proto_reg_handoff_dcm() function.
4324 heur_dissector_add("tcp", dissect_dcm_heuristic, "DICOM on any TCP port (heuristic)", "dicom_tcp", proto_dcm, HEURISTIC_ENABLE);
4330 PDU's
4331 01 ASSOC-RQ
4332 1 1 reserved
4333 2 4 length
4334 6 2 protocol version (0x0 0x1)
4335 8 2 reserved
4336 10 16 dest aetitle
4337 26 16 src aetitle
4338 42 32 reserved
4339 74 - presentation data value items
4341 02 A-ASSOC-AC
4342 1 reserved
4343 4 length
4344 2 protocol version (0x0 0x1)
4345 2 reserved
4346 16 dest aetitle (not checked)
4347 16 src aetitle (not checked)
4348 32 reserved
4349 - presentation data value items
4351 03 ASSOC-RJ
4352 1 reserved
4353 4 length (4)
4354 1 reserved
4355 1 result (1 reject perm, 2 reject transient)
4356 1 source (1 service user, 2 service provider, 3 service provider)
4357 1 reason
4358 1 == source
4359 1 no reason given
4360 2 application context name not supported
4361 3 calling aetitle not recognized
4362 7 called aetitle not recognized
4363 2 == source
4364 1 no reason given
4365 2 protocol version not supported
4366 3 == source
4367 1 temporary congestion
4368 2 local limit exceeded
4370 04 P-DATA
4371 1 1 reserved
4372 2 4 length
4373 - (1+) presentation data value (PDV) items
4374 6 4 length
4375 10 1 Presentation Context ID (odd ints 1 - 255)
4376 - PDV
4377 11 1 header
4378 0x01 if set, contains Message Command info, else Message Data
4379 0x02 if set, contains last fragment
4381 05 A-RELEASE-RQ
4382 1 reserved
4383 4 length (4)
4384 4 reserved
4386 06 A-RELEASE-RP
4387 1 reserved
4388 4 length (4)
4389 4 reserved
4391 07 A-ABORT
4392 1 reserved
4393 4 length (4)
4394 2 reserved
4395 1 source (0 = user, 1 = provider)
4396 1 reason if 1 == source (0 not spec, 1 unrecognized, 2 unexpected 4 unrecognized param, 5 unexpected param, 6 invalid param)
4400 ITEM's
4401 10 Application Context
4402 1 reserved
4403 2 length
4404 - name
4406 20 Presentation Context
4407 1 reserved
4408 2 length
4409 1 Presentation context id
4410 3 reserved
4411 - (1) abstract and (1+) transfer syntax sub-items
4413 21 Presentation Context (Reply)
4414 1 reserved
4415 2 length
4416 1 ID (odd int's 1-255)
4417 1 reserved
4418 1 result (0 accept, 1 user-reject, 2 no-reason, 3 abstract not supported, 4- transfer syntax not supported)
4419 1 reserved
4420 - (1) type 40
4422 30 Abstract syntax
4423 1 reserved
4424 2 length
4425 - name (<= 64)
4427 40 Transfer syntax
4428 1 reserved
4429 2 length
4430 - name (<= 64)
4432 50 user information
4433 1 reserved
4434 2 length
4435 - user data
4437 51 max length
4438 1 reserved
4439 2 length (4)
4440 4 max PDU lengths
4442 From 3.7 Annex D Association Negotiation
4443 ========================================
4445 52 IMPLEMENTATION CLASS UID
4446 1 Item-type 52H
4447 1 Reserved
4448 2 Item-length
4449 n Implementation-class-uid
4451 55 IMPLEMENTATION VERSION NAME
4452 1 Item-type 55H
4453 1 Reserved
4454 2 Item-length
4455 n Implementation-version-name
4457 53 ASYNCHRONOUS OPERATIONS WINDOW
4458 1 Item-type 53H
4459 1 Reserved
4460 2 Item-length
4461 2 Maximum-number-operations-invoked
4462 2 Maximum-number-operations-performed
4464 54 SCP/SCU ROLE SELECTION
4465 1 Item-type 54H
4466 1 Reserved
4467 2 Item-length (n)
4468 2 UID-length (m)
4469 m SOP-class-uid
4470 1 SCU-role
4471 0 - non support of the SCU role
4472 1 - support of the SCU role
4473 1 SCP-role
4474 0 - non support of the SCP role
4475 1 - support of the SCP role.
4477 56 SOP CLASS EXTENDED NEGOTIATION
4478 1 Item-type 56H
4479 1 Reserved
4480 2 Item-Length (n)
4481 2 SOP-class-uid-length (m)
4482 m SOP-class-uid
4483 n-m Service-class-application-information
4485 57 SOP CLASS COMMON EXTENDED NEGOTIATION
4486 1 Item-type 57H
4487 1 Sub-item-version
4488 2 Item-Length
4489 2 SOP-class-uid-length (m)
4490 7-x SOP-class-uid The SOP Class identifier encoded as a UID as defined in PS 3.5.
4491 (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.
4492 (x+3)-y Service-class-uid The Service Class identifier encoded as a UID as defined in PS 3.5.
4493 (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.
4494 (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.
4495 (z+1)-k Reserved Reserved for additional fields of the sub-item. Shall be zero-length for Version 0 of Sub-item definition.
4497 Table D.3-13
4498 RELATED-GENERAL-SOP-CLASS-IDENTIFICATION SUB-FIELDS
4499 Bytes Sub-Field Name Description of Sub-Field
4500 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.
4501 3-n Related-general-sop-class-uid The Related General SOP Class identifier encoded as a UID as defined in PS 3.5.
4503 58 User Identity Negotiation
4504 1 Item-type 58H
4505 1 Reserved
4506 2 Item-length
4507 1 User-Identity-Type Field value shall be in the range 1 to 4 with the following meanings:
4508 1 - Username as a string in UTF-8
4509 2 - Username as a string in UTF-8 and passcode
4510 3 - Kerberos Service ticket
4511 4 - SAML Assertion
4512 Other values are reserved for future standardization.
4513 1 Positive-response-requested Field value:
4514 0 - no response requested
4515 1 - positive response requested
4516 2 Primary-field-length The User-Identity-Length shall contain the length of the User-Identity value.
4517 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.
4518 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.
4519 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.
4521 59 User Identity Negotiation Reply
4522 1 Item-type 59H
4523 1 Reserved
4524 2 Item-length
4525 5-6 Server-response-length This field shall contain the number of bytes in the Server-response. May be zero.
4526 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.
4531 * Editor modelines - https://www.wireshark.org/tools/modelines.html
4533 * Local variables:
4534 * c-basic-offset: 4
4535 * tab-width: 8
4536 * indent-tabs-mode: nil
4537 * End:
4539 * vi: set shiftwidth=4 tabstop=8 expandtab:
4540 * :indentSize=4:tabSize=8:noTabs=true: