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