2 * Routines for USB DFU dissection
4 * Copyright 2014, Michal Labedzki for Tieto Corporation
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
15 #include <epan/packet.h>
16 #include <epan/prefs.h>
17 #include <epan/expert.h>
18 #include "packet-usb.h"
20 static int proto_usb_dfu
;
22 static int hf_setup_command
;
23 static int hf_setup_unused
;
24 static int hf_setup_interface
;
25 static int hf_setup_length
;
26 static int hf_setup_timeout
;
27 static int hf_setup_block_number
;
28 static int hf_response
;
29 static int hf_command_in_frame
;
32 static int hf_poll_timeout
;
33 static int hf_iString
;
35 static int hf_usb_dfu_descriptor
;
36 static int hf_usb_dfu_descriptor_bmAttributes_reserved
;
37 static int hf_usb_dfu_descriptor_bmAttributes_WillDetach
;
38 static int hf_usb_dfu_descriptor_bmAttributes_ManifestationTolerant
;
39 static int hf_usb_dfu_descriptor_bmAttributes_CanUpload
;
40 static int hf_usb_dfu_descriptor_bmAttributes_CanDownload
;
41 static int hf_usb_dfu_descriptor_wDetachTimeOut
;
42 static int hf_usb_dfu_descriptor_wTransferSize
;
43 static int hf_usb_dfu_descriptor_bcdDFUVersion
;
45 static int ett_usb_dfu
;
46 static int ett_usb_dfu_descriptor
;
47 static int ett_command
;
49 static expert_field ei_unexpected_response
;
50 static expert_field ei_unknown_data
;
51 static expert_field ei_unexpected_data
;
52 static expert_field ei_descriptor_invalid_length
;
53 static expert_field ei_invalid_command_for_request_type
;
55 static dissector_handle_t usb_dfu_handle
;
56 static dissector_handle_t usf_dfu_descriptor_handle
;
59 static wmem_tree_t
*command_info
;
61 typedef struct _command_data
{
63 uint32_t device_address
;
67 uint32_t command_frame_number
;
72 static const value_string command_vals
[] = {
76 { 0x03, "Get Status" },
77 { 0x04, "Clear Status" },
78 { 0x05, "Get State" },
82 static value_string_ext(command_vals_ext
) = VALUE_STRING_EXT_INIT(command_vals
);
84 static const value_string state_vals
[] = {
88 { 3, "dfuDownloadSync" },
89 { 4, "dfuDownloadBusy" },
90 { 5, "dfuDownloadIdle" },
91 { 6, "dfuManifestSync" },
93 { 8, "dfuManifestWaitReset" },
94 { 9, "dfuUploadIdle" },
98 static value_string_ext(state_vals_ext
) = VALUE_STRING_EXT_INIT(state_vals
);
100 static const value_string status_vals
[] = {
102 { 0x01, "errTarget" },
104 { 0x03, "errWrite" },
105 { 0x04, "errErase" },
106 { 0x05, "errCheckErased" },
108 { 0x07, "errVerify" },
109 { 0x08, "errAddress" },
110 { 0x09, "errNotDone" },
111 { 0x0A, "errFirmware" },
112 { 0x0B, "errVendor" },
113 { 0x0C, "errUsbReset" },
114 { 0x0D, "errPowerOnReset" },
115 { 0x0E, "errUnknown" },
116 { 0x0F, "errStalledPkt" },
119 static value_string_ext(status_vals_ext
) = VALUE_STRING_EXT_INIT(status_vals
);
121 static const value_string descriptor_type_vals
[] = {
122 { 0x21, "DFU FUNCTIONAL" },
125 static value_string_ext(descriptor_type_vals_ext
) = VALUE_STRING_EXT_INIT(descriptor_type_vals
);
127 void proto_register_usb_dfu(void);
128 void proto_reg_handoff_usb_dfu(void);
132 dissect_usb_dfu_descriptor(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
)
134 proto_item
*main_item
;
135 proto_tree
*main_tree
;
136 proto_item
*length_item
;
138 uint8_t descriptor_length
;
139 uint8_t descriptor_type
;
140 urb_info_t
*urb
= (urb_info_t
*) data
;
142 if (!urb
|| !urb
->conv
) return offset
;
144 if (!(urb
->conv
->interfaceClass
== IF_CLASS_APPLICATION_SPECIFIC
&&
145 urb
->conv
->interfaceSubclass
== 0x01)) return offset
;
147 descriptor_length
= tvb_get_uint8(tvb
, offset
);
148 descriptor_type
= tvb_get_uint8(tvb
, offset
+ 1);
150 switch (descriptor_type
) {
152 main_item
= proto_tree_add_item(tree
, hf_usb_dfu_descriptor
, tvb
, offset
, -1, ENC_NA
);
153 main_tree
= proto_item_add_subtree(main_item
, ett_usb_dfu_descriptor
);
155 proto_item_append_text(main_item
, ": %s", val_to_str_ext_const(descriptor_type
, &descriptor_type_vals_ext
, "Unknown"));
157 length_item
= dissect_usb_descriptor_header(main_tree
, tvb
, offset
, &descriptor_type_vals_ext
);
158 if (descriptor_length
!= 7 && descriptor_length
!= 9)
159 expert_add_info(pinfo
, length_item
, &ei_descriptor_invalid_length
);
162 proto_tree_add_item(main_tree
, hf_usb_dfu_descriptor_bmAttributes_reserved
, tvb
, offset
, 1, ENC_LITTLE_ENDIAN
);
163 proto_tree_add_item(main_tree
, hf_usb_dfu_descriptor_bmAttributes_WillDetach
, tvb
, offset
, 1, ENC_NA
);
164 proto_tree_add_item(main_tree
, hf_usb_dfu_descriptor_bmAttributes_ManifestationTolerant
, tvb
, offset
, 1, ENC_NA
);
165 proto_tree_add_item(main_tree
, hf_usb_dfu_descriptor_bmAttributes_CanUpload
, tvb
, offset
, 1, ENC_NA
);
166 proto_tree_add_item(main_tree
, hf_usb_dfu_descriptor_bmAttributes_CanDownload
, tvb
, offset
, 1, ENC_NA
);
169 proto_tree_add_item(main_tree
, hf_usb_dfu_descriptor_wDetachTimeOut
, tvb
, offset
, 2, ENC_LITTLE_ENDIAN
);
172 proto_tree_add_item(main_tree
, hf_usb_dfu_descriptor_wTransferSize
, tvb
, offset
, 2, ENC_LITTLE_ENDIAN
);
175 if (descriptor_length
> 7) {
176 proto_tree_add_item(main_tree
, hf_usb_dfu_descriptor_bcdDFUVersion
, tvb
, offset
, 2, ENC_LITTLE_ENDIAN
);
187 dissect_usb_dfu(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
)
189 proto_item
*main_item
;
190 proto_tree
*main_tree
;
191 proto_item
*command_item
;
192 proto_item
*sub_item
;
193 proto_tree
*command_tree
;
197 int16_t command_response
= -1;
198 command_data_t
*command_data
= NULL
;
199 wmem_tree_t
*wmem_tree
;
200 wmem_tree_key_t key
[5];
202 uint32_t device_address
;
204 uint32_t k_device_address
;
205 uint32_t k_frame_number
;
206 int32_t block_number
= -1;
207 urb_info_t
*urb
= (urb_info_t
*)data
;
209 if (!urb
) return offset
;
211 bus_id
= urb
->bus_id
;
212 device_address
= urb
->device_address
;
215 k_device_address
= device_address
;
216 k_frame_number
= pinfo
->num
;
219 key
[0].key
= &k_bus_id
;
221 key
[1].key
= &k_device_address
;
223 main_item
= proto_tree_add_item(tree
, proto_usb_dfu
, tvb
, offset
, -1, ENC_NA
);
224 main_tree
= proto_item_add_subtree(main_item
, ett_usb_dfu
);
226 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "USB DFU");
228 p2p_dir_save
= pinfo
->p2p_dir
;
229 pinfo
->p2p_dir
= (urb
->is_request
) ? P2P_DIR_SENT
: P2P_DIR_RECV
;
231 switch (pinfo
->p2p_dir
) {
234 col_set_str(pinfo
->cinfo
, COL_INFO
, "Sent ");
238 col_set_str(pinfo
->cinfo
, COL_INFO
, "Rcvd ");
242 col_set_str(pinfo
->cinfo
, COL_INFO
, "Unknown direction ");
249 command_item
= proto_tree_add_item(main_tree
, hf_setup_command
, tvb
, offset
, 1, ENC_LITTLE_ENDIAN
);
250 command
= tvb_get_uint8(tvb
, offset
);
252 if (!((urb
->setup_requesttype
== 0x21 && (command
== 0x00 || command
== 0x01 || command
== 0x04 || command
== 0x06)) ||
253 (urb
->setup_requesttype
== 0xa1 && (command
== 0x02 || command
== 0x03 || command
== 0x05))))
254 expert_add_info(pinfo
, command_item
, &ei_invalid_command_for_request_type
);
257 col_append_fstr(pinfo
->cinfo
, COL_INFO
, "Command: %s",
258 val_to_str_ext_const(command
, &command_vals_ext
, "Unknown"));
260 if (command
== 0x00) { /* Detach */
261 proto_tree_add_item(main_tree
, hf_setup_timeout
, tvb
, offset
, 2, ENC_LITTLE_ENDIAN
);
262 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " Timeout=%u", tvb_get_letohs(tvb
, offset
));
263 } else if (command
== 0x01 || command
== 0x02) { /* Download || Upload */
264 proto_tree_add_item(main_tree
, hf_setup_block_number
, tvb
, offset
, 2, ENC_LITTLE_ENDIAN
);
265 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " Block Number=%u", tvb_get_letohs(tvb
, offset
));
266 block_number
= tvb_get_letohs(tvb
, offset
);
268 proto_tree_add_item(main_tree
, hf_setup_unused
, tvb
, offset
, 2, ENC_LITTLE_ENDIAN
);
272 proto_tree_add_item(main_tree
, hf_setup_interface
, tvb
, offset
, 2, ENC_LITTLE_ENDIAN
);
273 interface
= tvb_get_letohs(tvb
, offset
);
276 proto_tree_add_item(main_tree
, hf_setup_length
, tvb
, offset
, 2, ENC_LITTLE_ENDIAN
);
279 if (command
== 0x01) { /* Download */
280 proto_tree_add_item(main_tree
, hf_data
, tvb
, offset
, -1, ENC_NA
);
281 offset
= tvb_captured_length(tvb
);
284 if (tvb_reported_length_remaining(tvb
, offset
) > 0) {
285 proto_tree_add_expert(main_tree
, pinfo
, &ei_unexpected_data
, tvb
, offset
, tvb_captured_length_remaining(tvb
, offset
));
286 offset
= tvb_captured_length(tvb
);
289 /* Save request info (command_data) */
290 if (!pinfo
->fd
->visited
&& command
!= 21) {
292 key
[2].key
= &k_frame_number
;
296 command_data
= wmem_new(wmem_file_scope(), command_data_t
);
297 command_data
->bus_id
= bus_id
;
298 command_data
->device_address
= device_address
;
300 command_data
->command
= command
;
301 command_data
->interface
= interface
;
302 command_data
->command_frame_number
= pinfo
->num
;
303 command_data
->block_number
= block_number
;
305 wmem_tree_insert32_array(command_info
, key
, command_data
);
308 pinfo
->p2p_dir
= p2p_dir_save
;
313 /* Get request info (command_data) */
317 wmem_tree
= (wmem_tree_t
*) wmem_tree_lookup32_array(command_info
, key
);
319 command_data
= (command_data_t
*) wmem_tree_lookup32_le(wmem_tree
, pinfo
->num
);
321 command_response
= command_data
->command
;
322 block_number
= command_data
->block_number
;
327 col_append_str(pinfo
->cinfo
, COL_INFO
, "Response: Unknown");
329 proto_tree_add_expert(main_tree
, pinfo
, &ei_unknown_data
, tvb
, offset
, tvb_captured_length_remaining(tvb
, offset
));
331 pinfo
->p2p_dir
= p2p_dir_save
;
333 return tvb_captured_length(tvb
);
336 col_append_fstr(pinfo
->cinfo
, COL_INFO
, "Response: %s",
337 val_to_str_ext_const(command_response
, &command_vals_ext
, "Unknown"));
339 command_item
= proto_tree_add_uint(main_tree
, hf_response
, tvb
, offset
, 0, command_response
);
340 command_tree
= proto_item_add_subtree(command_item
, ett_command
);
341 proto_item_set_generated(command_item
);
343 command_item
= proto_tree_add_uint(main_tree
, hf_setup_interface
, tvb
, offset
, 0, command_data
->interface
);
344 proto_item_set_generated(command_item
);
346 command_item
= proto_tree_add_uint(main_tree
, hf_command_in_frame
, tvb
, offset
, 0, command_data
->command_frame_number
);
347 proto_item_set_generated(command_item
);
349 switch (command_response
) {
350 case 0x02: /* Upload */
351 if (block_number
!= -1) {
352 sub_item
= proto_tree_add_uint(main_tree
, hf_setup_block_number
, tvb
, offset
, 0, block_number
);
353 proto_item_set_generated(sub_item
);
354 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " Block Number=%u", block_number
);
357 proto_tree_add_item(main_tree
, hf_data
, tvb
, offset
, -1, ENC_NA
);
358 offset
= tvb_captured_length(tvb
);
361 case 0x03: /* Get Status */
362 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " = Status: %s, PollTimeout: %u ms, State: %s",
363 val_to_str_ext_const(tvb_get_uint8(tvb
, offset
), &status_vals_ext
, "Unknown"),
364 tvb_get_letoh24(tvb
, offset
+ 1),
365 val_to_str_ext_const(tvb_get_uint8(tvb
, offset
+ 4), &state_vals_ext
, "Unknown"));
367 proto_tree_add_item(main_tree
, hf_status
, tvb
, offset
, 1, ENC_LITTLE_ENDIAN
);
370 proto_tree_add_item(main_tree
, hf_poll_timeout
, tvb
, offset
, 3, ENC_LITTLE_ENDIAN
);
373 proto_tree_add_item(main_tree
, hf_state
, tvb
, offset
, 1, ENC_LITTLE_ENDIAN
);
376 proto_tree_add_item(main_tree
, hf_iString
, tvb
, offset
, 1, ENC_LITTLE_ENDIAN
);
380 case 0x05: /* Get State */
381 proto_tree_add_item(main_tree
, hf_state
, tvb
, offset
, 1, ENC_LITTLE_ENDIAN
);
383 col_append_fstr(pinfo
->cinfo
, COL_INFO
, " = %s",
384 val_to_str_ext_const(tvb_get_uint8(tvb
, offset
), &state_vals_ext
, "Unknown"));
389 case 0x00: /* Detach */
390 case 0x01: /* Download */
391 case 0x04: /* Clear Status */
392 case 0x06: /* Abort */
394 proto_tree_add_expert(command_tree
, pinfo
, &ei_unexpected_response
, tvb
, offset
, 0);
395 if (tvb_reported_length_remaining(tvb
, offset
) > 0) {
396 proto_tree_add_expert(main_tree
, pinfo
, &ei_unknown_data
, tvb
, offset
, -1);
397 offset
= tvb_captured_length(tvb
);
401 pinfo
->p2p_dir
= p2p_dir_save
;
407 proto_register_usb_dfu(void)
410 expert_module_t
*expert_module
;
412 static hf_register_info hf
[] = {
415 { "Command", "usbdfu.command",
416 FT_UINT8
, BASE_DEC
| BASE_EXT_STRING
, &command_vals_ext
, 0x0,
420 { "Response", "usbdfu.response",
421 FT_UINT8
, BASE_DEC
| BASE_EXT_STRING
, &command_vals_ext
, 0x0,
424 { &hf_command_in_frame
,
425 { "Command Frame", "usbdfu.command_frame",
426 FT_FRAMENUM
, BASE_NONE
, NULL
, 0x0,
430 { "Unused", "usbdfu.unused",
431 FT_UINT16
, BASE_HEX
, NULL
, 0x0,
434 { &hf_setup_interface
,
435 { "Interface", "usbdfu.interface",
436 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
440 { "Length", "usbdfu.length",
441 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
444 { &hf_setup_block_number
,
445 { "Block Number", "usbdfu.block_number",
446 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
450 { "Timeout", "usbdfu.timeout",
451 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
455 { "State", "usbdfu.state",
456 FT_UINT8
, BASE_DEC
| BASE_EXT_STRING
, &state_vals_ext
, 0x0,
460 { "Status", "usbdfu.status",
461 FT_UINT8
, BASE_HEX
| BASE_EXT_STRING
, &status_vals_ext
, 0x0,
465 { "iString", "usbdfu.iString",
466 FT_INT8
, BASE_DEC
, NULL
, 0x0,
470 { "Poll Timeout", "usbdfu.poll_timeout",
471 FT_UINT24
, BASE_DEC
, NULL
, 0x0,
475 { "Data", "usbdfu.data",
476 FT_NONE
, BASE_NONE
, NULL
, 0x0,
479 { &hf_usb_dfu_descriptor
,
480 { "DFU Descriptor", "usbdfu.descriptor",
481 FT_NONE
, BASE_NONE
, NULL
, 0x0,
484 { &hf_usb_dfu_descriptor_bmAttributes_reserved
,
485 { "Reserved", "usbdfu.descriptor.bmAttributes.reserved",
486 FT_UINT8
, BASE_HEX
, NULL
, 0xF0,
489 { &hf_usb_dfu_descriptor_bmAttributes_WillDetach
,
490 { "Will Detach", "usbdfu.descriptor.bmAttributes.WillDetach",
491 FT_BOOLEAN
, 8, NULL
, 0x08,
494 { &hf_usb_dfu_descriptor_bmAttributes_ManifestationTolerant
,
495 { "Manifestation Tolerant", "usbdfu.descriptor.bmAttributes.ManifestationTolerant",
496 FT_BOOLEAN
, 8, NULL
, 0x04,
499 { &hf_usb_dfu_descriptor_bmAttributes_CanUpload
,
500 { "Can Upload", "usbdfu.descriptor.bmAttributes.CanUpload",
501 FT_BOOLEAN
, 8, NULL
, 0x02,
504 { &hf_usb_dfu_descriptor_bmAttributes_CanDownload
,
505 { "Can Download", "usbdfu.descriptor.bmAttributes.CanDownload",
506 FT_BOOLEAN
, 8, NULL
, 0x01,
509 { &hf_usb_dfu_descriptor_wDetachTimeOut
,
510 { "wDetachTimeOut", "usbdfu.descriptor.wDetachTimeOut",
511 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
514 { &hf_usb_dfu_descriptor_wTransferSize
,
515 { "wTransferSize", "usbdfu.descriptor.wTransferSize",
516 FT_UINT16
, BASE_DEC
, NULL
, 0x0,
519 { &hf_usb_dfu_descriptor_bcdDFUVersion
,
520 { "bcdDFUVersion", "usbdfu.descriptor.bcdDFUVersion",
521 FT_UINT16
, BASE_HEX
, NULL
, 0x0,
526 static ei_register_info ei
[] = {
527 { &ei_unexpected_response
, { "usb_dfu.unexpected_response", PI_PROTOCOL
, PI_ERROR
, "Unexpected response for this command", EXPFILL
}},
528 { &ei_unknown_data
, { "usb_dfu.unknown_data", PI_PROTOCOL
, PI_NOTE
, "Unknown data", EXPFILL
}},
529 { &ei_unexpected_data
, { "usb_dfu.unexpected_data", PI_PROTOCOL
, PI_WARN
, "Unexpected data", EXPFILL
}},
530 { &ei_invalid_command_for_request_type
, { "usb_dfu.invalid_command_for_request_type", PI_PROTOCOL
, PI_WARN
, "Invalid command for this Request Type", EXPFILL
}},
531 { &ei_descriptor_invalid_length
, { "usb_dfu.descriptor.invalid_length", PI_PROTOCOL
, PI_WARN
, "Invalid Length", EXPFILL
}},
534 static int *ett
[] = {
536 &ett_usb_dfu_descriptor
,
540 command_info
= wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope());
542 proto_usb_dfu
= proto_register_protocol("USB Device Firmware Upgrade ", "USB DFU", "usbdfu");
543 proto_register_field_array(proto_usb_dfu
, hf
, array_length(hf
));
544 proto_register_subtree_array(ett
, array_length(ett
));
545 usb_dfu_handle
= register_dissector("usb_dfu", dissect_usb_dfu
, proto_usb_dfu
);
546 usf_dfu_descriptor_handle
= register_dissector("usb_dfu.descriptor", dissect_usb_dfu_descriptor
, proto_usb_dfu
);
548 expert_module
= expert_register_protocol(proto_usb_dfu
);
549 expert_register_field_array(expert_module
, ei
, array_length(ei
));
551 module
= prefs_register_protocol(proto_usb_dfu
, NULL
);
552 prefs_register_static_text_preference(module
, "version",
553 "USB DFU Specification 1.1",
554 "Version of protocol supported by this dissector.");
557 #define RUNTIME_KEY USB_PROTOCOL_KEY(IF_CLASS_APPLICATION_SPECIFIC, IF_SUBCLASS_APP_DFU, IF_PROTOCOL_DFU_RUNTIME)
558 #define DFU_MODE_KEY USB_PROTOCOL_KEY(IF_CLASS_APPLICATION_SPECIFIC, IF_SUBCLASS_APP_DFU, IF_PROTOCOL_DFU_MODE)
561 proto_reg_handoff_usb_dfu(void)
563 dissector_add_uint("usb.descriptor", IF_CLASS_APPLICATION_SPECIFIC
, usf_dfu_descriptor_handle
);
565 dissector_add_uint("usb.control", RUNTIME_KEY
, usb_dfu_handle
);
566 dissector_add_uint("usb.control", DFU_MODE_KEY
, usb_dfu_handle
);
568 dissector_add_uint("usb.product", (0x05ac << 16) | 0x1227, usb_dfu_handle
); /* Apple Inc. Mobile Device (DFU Mode) */
569 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x1db5, usb_dfu_handle
); /* IDBG in DFU mode */
570 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x6001, usb_dfu_handle
); /* Ubertooth Zero DFU */
571 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x6003, usb_dfu_handle
); /* Ubertooth One DFU */
572 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x600f, usb_dfu_handle
); /* Paparazzi Lisa/M (DFU) */
573 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x6011, usb_dfu_handle
); /* LeoLipo (DFU) */
574 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x6017, usb_dfu_handle
); /* Black Magic Debug Probe (DFU) */
575 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x6044, usb_dfu_handle
); /* Open Source USB CANBUS converter (DFU Mode) */
576 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x6064, usb_dfu_handle
); /* CPC FPGA (DFU) */
577 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x6069, usb_dfu_handle
); /* xser (DFU mode) */
578 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x6082, usb_dfu_handle
); /* Facecandy *USB DFU loader */
579 dissector_add_uint("usb.product", (0x1d50 << 16) | 0x6084, usb_dfu_handle
); /* arcin arcade controller (USB DFU loader) */
581 dissector_add_for_decode_as("usb.device", usb_dfu_handle
);
582 dissector_add_for_decode_as("usb.protocol", usb_dfu_handle
);
586 * Editor modelines - https://www.wireshark.org/tools/modelines.html
591 * indent-tabs-mode: nil
594 * vi: set shiftwidth=4 tabstop=8 expandtab:
595 * :indentSize=4:tabSize=8:noTabs=true: