/src/wireshark/epan/dissectors/packet-usb-i1d3.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* packet-usb-i1d3.c |
2 | | * Dissects the X-Rite i1 Display Pro (and derivatives) USB protocol |
3 | | * Copyright 2016, Etienne Dechamps <etienne@edechamps.fr> |
4 | | * |
5 | | * Wireshark - Network traffic analyzer |
6 | | * By Gerald Combs <gerald@wireshark.org> |
7 | | * Copyright 1998 Gerald Combs |
8 | | * |
9 | | * SPDX-License-Identifier: GPL-2.0-or-later |
10 | | */ |
11 | | |
12 | | /* |
13 | | * This code dissects the USB protocol used for communicating with a |
14 | | * X-Rite i1 Display Pro colorimeter, as well as similar hardware such |
15 | | * as ColorMunki Display. |
16 | | * |
17 | | * Note that this protocol is proprietary and no public specification |
18 | | * exists. This code is largely based on Graeme Gill's reverse |
19 | | * engineering work for ArgyllCMS (see spectro/i1d3.c in the ArgyllCMS |
20 | | * source code). |
21 | | * |
22 | | * Because some aspects of the protocol are not yet fully understood, |
23 | | * this dissector might fail to properly parse some packets, especially |
24 | | * in unusual scenarios such as error conditions and the like. |
25 | | */ |
26 | | |
27 | | #include <config.h> |
28 | | #include <epan/conversation.h> |
29 | | #include <epan/packet.h> |
30 | | #include <epan/expert.h> |
31 | | #include <epan/tfs.h> |
32 | | #include <epan/unit_strings.h> |
33 | | |
34 | | #include <wsutil/array.h> |
35 | | |
36 | | void proto_register_usb_i1d3(void); |
37 | | void proto_reg_handoff_usb_i1d3(void); |
38 | | |
39 | | static dissector_handle_t usb_i1d3_dissector; |
40 | | |
41 | 0 | #define USB_I1D3_PACKET_LENGTH (64) |
42 | 0 | #define USB_I1D3_CLOCK_FREQUENCY (12e6) // 12 MHz |
43 | 0 | #define USB_I1D3_LED_OFFTIME_FACTOR (USB_I1D3_CLOCK_FREQUENCY / (1 << 19)) |
44 | 0 | #define USB_I1D3_LED_ONTIME_FACTOR (USB_I1D3_CLOCK_FREQUENCY / (1 << 19)) |
45 | 0 | #define USB_I1D3_LED_ONTIME_FADE_FACTOR (USB_I1D3_CLOCK_FREQUENCY / (1 << 23)) |
46 | | |
47 | | static int proto_usb_i1d3; |
48 | | static int ett_usb_i1d3; |
49 | | static int ett_usb_i1d3_measured_duration; |
50 | | static int ett_usb_i1d3_requested_edge_count; |
51 | | |
52 | | static int hf_usb_i1d3_challenge_response; |
53 | | static int hf_usb_i1d3_challenge_data; |
54 | | static int hf_usb_i1d3_challenge_decode_key; |
55 | | static int hf_usb_i1d3_challenge_encode_key; |
56 | | static int hf_usb_i1d3_command_code; |
57 | | static int hf_usb_i1d3_diffuser_position; |
58 | | static int hf_usb_i1d3_echoed_command_code; |
59 | | static int hf_usb_i1d3_firmdate; |
60 | | static int hf_usb_i1d3_firmver; |
61 | | static int hf_usb_i1d3_information; |
62 | | static int hf_usb_i1d3_measured_duration; |
63 | | static int hf_usb_i1d3_measured_duration_red; |
64 | | static int hf_usb_i1d3_measured_duration_green; |
65 | | static int hf_usb_i1d3_measured_duration_blue; |
66 | | static int hf_usb_i1d3_measured_edge_count; |
67 | | static int hf_usb_i1d3_measured_edge_count_red; |
68 | | static int hf_usb_i1d3_measured_edge_count_green; |
69 | | static int hf_usb_i1d3_measured_edge_count_blue; |
70 | | static int hf_usb_i1d3_led_mode; |
71 | | static int hf_usb_i1d3_led_offtime; |
72 | | static int hf_usb_i1d3_led_ontime; |
73 | | static int hf_usb_i1d3_led_pulse_count; |
74 | | static int hf_usb_i1d3_locked; |
75 | | static int hf_usb_i1d3_prodname; |
76 | | static int hf_usb_i1d3_prodtype; |
77 | | static int hf_usb_i1d3_request_in; |
78 | | static int hf_usb_i1d3_requested_edge_count; |
79 | | static int hf_usb_i1d3_requested_edge_count_red; |
80 | | static int hf_usb_i1d3_requested_edge_count_green; |
81 | | static int hf_usb_i1d3_requested_edge_count_blue; |
82 | | static int hf_usb_i1d3_requested_integration_time; |
83 | | static int hf_usb_i1d3_response_code; |
84 | | static int hf_usb_i1d3_response_in; |
85 | | static int hf_usb_i1d3_readextee_data; |
86 | | static int hf_usb_i1d3_readextee_offset; |
87 | | static int hf_usb_i1d3_readextee_length; |
88 | | static int hf_usb_i1d3_readintee_data; |
89 | | static int hf_usb_i1d3_readintee_offset; |
90 | | static int hf_usb_i1d3_readintee_length; |
91 | | static int hf_usb_i1d3_status; |
92 | | static int hf_usb_i1d3_unlock_result; |
93 | | |
94 | | static expert_field ei_usb_i1d3_echoed_command_code_mismatch; |
95 | | static expert_field ei_usb_i1d3_error; |
96 | | static expert_field ei_usb_i1d3_unexpected_response; |
97 | | static expert_field ei_usb_i1d3_unknown_command; |
98 | | static expert_field ei_usb_i1d3_unknown_diffuser_position; |
99 | | static expert_field ei_usb_i1d3_unlock_failed; |
100 | | static expert_field ei_usb_i1d3_unusual_length; |
101 | | |
102 | | // Derived from ArgyllCMS spectro/i1d3.c. |
103 | | typedef enum _usb_i1d3_command_code { |
104 | | USB_I1D3_GET_INFO = 0x0000, |
105 | | USB_I1D3_STATUS = 0x0001, |
106 | | USB_I1D3_PRODNAME = 0x0010, |
107 | | USB_I1D3_PRODTYPE = 0x0011, |
108 | | USB_I1D3_FIRMVER = 0x0012, |
109 | | USB_I1D3_FIRMDATE = 0x0013, |
110 | | USB_I1D3_LOCKED = 0x0020, |
111 | | USB_I1D3_MEASURE1 = 0x0100, |
112 | | USB_I1D3_MEASURE2 = 0x0200, |
113 | | USB_I1D3_READINTEE = 0x0800, |
114 | | USB_I1D3_READEXTEE = 0x1200, |
115 | | USB_I1D3_SETLED = 0x2100, |
116 | | USB_I1D3_RD_SENSOR = 0x9300, |
117 | | USB_I1D3_GET_DIFF = 0x9400, |
118 | | USB_I1D3_LOCKCHAL = 0x9900, |
119 | | USB_I1D3_LOCKRESP = 0x9a00, |
120 | | USB_I1D3_RELOCK = 0x9b00, |
121 | | } usb_i1d3_command_code; |
122 | | static const value_string usb_i1d3_command_code_strings[] = { |
123 | | {USB_I1D3_GET_INFO, "Get information"}, |
124 | | {USB_I1D3_STATUS, "Get status"}, |
125 | | {USB_I1D3_PRODNAME, "Get product name"}, |
126 | | {USB_I1D3_PRODTYPE, "Get product type"}, |
127 | | {USB_I1D3_FIRMVER, "Get firmware version"}, |
128 | | {USB_I1D3_FIRMDATE, "Get firmware date"}, |
129 | | {USB_I1D3_LOCKED, "Get locked status"}, |
130 | | {USB_I1D3_MEASURE1, "Make measurement (fixed integration time)"}, |
131 | | {USB_I1D3_MEASURE2, "Make measurement (fixed edge count)"}, |
132 | | {USB_I1D3_READINTEE, "Read internal EEPROM"}, |
133 | | {USB_I1D3_READEXTEE, "Read external EEPROM"}, |
134 | | {USB_I1D3_SETLED, "Set LED state"}, |
135 | | {USB_I1D3_RD_SENSOR, "Read analog sensor"}, |
136 | | {USB_I1D3_GET_DIFF, "Get diffuser position"}, |
137 | | {USB_I1D3_LOCKCHAL, "Request lock challenge"}, |
138 | | {USB_I1D3_LOCKRESP, "Unlock"}, |
139 | | {USB_I1D3_RELOCK, "Relock"}, |
140 | | {0, NULL} |
141 | | }; |
142 | | |
143 | | typedef enum _usb_i1d3_led_mode { |
144 | | USB_I1D3_LED_BLINK = 1, |
145 | | USB_I1D3_LED_BLINK_FADE_ON = 3, |
146 | | } usb_i1d3_led_mode; |
147 | | static const value_string usb_i1d3_led_mode_strings[] = { |
148 | | {USB_I1D3_LED_BLINK, "Blink"}, |
149 | | {USB_I1D3_LED_BLINK_FADE_ON, "Blink, fade on"}, |
150 | | {0, NULL} |
151 | | }; |
152 | | |
153 | | typedef enum _usb_i1d3_diffuser_position { |
154 | | USB_I1D3_DIFFUSER_DISPLAY = 0, |
155 | | USB_I1D3_DIFFUSER_AMBIENT = 1, |
156 | | } usb_i1d3_diffuser_position; |
157 | | static const value_string usb_i1d3_diffuser_position_strings[] = { |
158 | | {USB_I1D3_DIFFUSER_DISPLAY, "Display"}, |
159 | | {USB_I1D3_DIFFUSER_AMBIENT, "Ambient"}, |
160 | | {0, NULL} |
161 | | }; |
162 | | |
163 | | typedef struct _usb_i1d3_transaction_t { |
164 | | uint32_t request; |
165 | | uint32_t response; |
166 | | uint32_t command_code; |
167 | | uint32_t offset; |
168 | | uint32_t length; |
169 | | } usb_i1d3_transaction_t; |
170 | | |
171 | | typedef struct _usb_i1d3_conversation_t { |
172 | | wmem_map_t *request_to_transaction; |
173 | | wmem_map_t *response_to_transaction; |
174 | | uint32_t previous_packet; |
175 | | } usb_i1d3_conversation_t; |
176 | | |
177 | | static const unit_name_string units_edge_edges = { " edge", " edges" }; |
178 | | static const unit_name_string units_pulse_pulses = { " pulse", " pulses" }; |
179 | | |
180 | 0 | static usb_i1d3_conversation_t *usb_i1d3_get_conversation(packet_info *pinfo) { |
181 | 0 | conversation_t *conversation = find_or_create_conversation(pinfo); |
182 | 0 | usb_i1d3_conversation_t* i1d3_conversation = |
183 | 0 | (usb_i1d3_conversation_t *)conversation_get_proto_data( |
184 | 0 | conversation, proto_usb_i1d3); |
185 | 0 | if (!i1d3_conversation) { |
186 | 0 | i1d3_conversation = wmem_new0( |
187 | 0 | wmem_file_scope(), usb_i1d3_conversation_t); |
188 | 0 | i1d3_conversation->request_to_transaction = wmem_map_new( |
189 | 0 | wmem_file_scope(), g_direct_hash, g_direct_equal); |
190 | 0 | i1d3_conversation->response_to_transaction = wmem_map_new( |
191 | 0 | wmem_file_scope(), g_direct_hash, g_direct_equal); |
192 | 0 | conversation_add_proto_data( |
193 | 0 | conversation, proto_usb_i1d3, i1d3_conversation); |
194 | 0 | } |
195 | 0 | return i1d3_conversation; |
196 | 0 | } |
197 | | |
198 | | static usb_i1d3_transaction_t *usb_i1d3_create_transaction( |
199 | 0 | usb_i1d3_conversation_t *conversation, uint32_t request) { |
200 | 0 | usb_i1d3_transaction_t *transaction = wmem_new0( |
201 | 0 | wmem_file_scope(), usb_i1d3_transaction_t); |
202 | 0 | transaction->request = request; |
203 | 0 | wmem_map_insert( |
204 | 0 | conversation->request_to_transaction, |
205 | 0 | GUINT_TO_POINTER(transaction->request), (void *)transaction); |
206 | 0 | return transaction; |
207 | 0 | } |
208 | | |
209 | | static void dissect_usb_i1d3_command( |
210 | | tvbuff_t *tvb, packet_info *pinfo, |
211 | 0 | usb_i1d3_conversation_t *conversation, proto_tree *tree) { |
212 | | // Parsing the command code is a bit tricky: if the most significant |
213 | | // byte is non-zero, the command code is the most significant byte, |
214 | | // *and* the next byte is the first byte of the payload. |
215 | 0 | uint32_t command_code = tvb_get_ntohs(tvb, 0); |
216 | 0 | uint32_t command_code_msb = command_code & 0xff00; |
217 | 0 | int command_code_length = 2; |
218 | 0 | if (command_code_msb) { |
219 | 0 | command_code = command_code_msb; |
220 | 0 | command_code_length = 1; |
221 | 0 | } |
222 | 0 | proto_item *command_code_item = proto_tree_add_uint( |
223 | 0 | tree, hf_usb_i1d3_command_code, tvb, 0, command_code_length, |
224 | 0 | command_code); |
225 | |
|
226 | 0 | usb_i1d3_transaction_t *transaction; |
227 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
228 | 0 | transaction = usb_i1d3_create_transaction(conversation, pinfo->num); |
229 | 0 | transaction->command_code = command_code; |
230 | 0 | } else { |
231 | 0 | transaction = (usb_i1d3_transaction_t *)wmem_map_lookup( |
232 | 0 | conversation->request_to_transaction, |
233 | 0 | GUINT_TO_POINTER(pinfo->num)); |
234 | 0 | } |
235 | 0 | DISSECTOR_ASSERT(transaction); |
236 | |
|
237 | 0 | if (transaction->response != 0) { |
238 | 0 | proto_item *response_item = proto_tree_add_uint( |
239 | 0 | tree, hf_usb_i1d3_response_in, tvb, 0, 0, |
240 | 0 | transaction->response); |
241 | 0 | proto_item_set_generated(response_item); |
242 | 0 | } |
243 | |
|
244 | 0 | const char *command_code_string = try_val_to_str( |
245 | 0 | command_code, usb_i1d3_command_code_strings); |
246 | 0 | if (command_code_string) { |
247 | 0 | col_set_str(pinfo->cinfo, COL_INFO, command_code_string); |
248 | 0 | } else { |
249 | 0 | expert_add_info(pinfo, command_code_item, |
250 | 0 | &ei_usb_i1d3_unknown_command); |
251 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Unknown command"); |
252 | 0 | } |
253 | |
|
254 | 0 | switch (command_code) { |
255 | 0 | case USB_I1D3_LOCKRESP: { |
256 | | // TODO: verify that the challenge response is correct |
257 | 0 | proto_tree_add_item( |
258 | 0 | tree, hf_usb_i1d3_challenge_response, tvb, 24, 16, ENC_NA); |
259 | 0 | break; |
260 | 0 | } |
261 | | |
262 | 0 | case USB_I1D3_READINTEE: { |
263 | 0 | uint32_t offset, length; |
264 | 0 | proto_tree_add_item_ret_uint( |
265 | 0 | tree, hf_usb_i1d3_readintee_offset, tvb, |
266 | 0 | 1, 1, ENC_NA, &offset); |
267 | 0 | proto_tree_add_item_ret_uint( |
268 | 0 | tree, hf_usb_i1d3_readintee_length, tvb, |
269 | 0 | 2, 1, ENC_NA, &length); |
270 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s (offset: %u, length: %u)", |
271 | 0 | command_code_string, offset, length); |
272 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
273 | 0 | transaction->offset = offset; |
274 | 0 | transaction->length = length; |
275 | 0 | } |
276 | 0 | break; |
277 | 0 | } |
278 | | |
279 | 0 | case USB_I1D3_READEXTEE: { |
280 | 0 | uint32_t offset, length; |
281 | 0 | proto_tree_add_item_ret_uint( |
282 | 0 | tree, hf_usb_i1d3_readextee_offset, tvb, |
283 | 0 | 1, 2, ENC_BIG_ENDIAN, &offset); |
284 | 0 | proto_tree_add_item_ret_uint( |
285 | 0 | tree, hf_usb_i1d3_readextee_length, tvb, |
286 | 0 | 3, 1, ENC_NA, &length); |
287 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "%s (offset: %u, length: %u)", |
288 | 0 | command_code_string, offset, length); |
289 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
290 | 0 | transaction->offset = offset; |
291 | 0 | transaction->length = length; |
292 | 0 | } |
293 | 0 | break; |
294 | 0 | } |
295 | | |
296 | 0 | case USB_I1D3_MEASURE1: { |
297 | 0 | uint32_t integration_time; |
298 | 0 | proto_item *integration_time_item = proto_tree_add_item_ret_uint( |
299 | 0 | tree, hf_usb_i1d3_requested_integration_time, tvb, 1, 4, |
300 | 0 | ENC_LITTLE_ENDIAN, &integration_time); |
301 | 0 | double integration_time_seconds = |
302 | 0 | integration_time / USB_I1D3_CLOCK_FREQUENCY; |
303 | 0 | proto_item_append_text( |
304 | 0 | integration_time_item, |
305 | 0 | " [%.6f seconds]", integration_time_seconds); |
306 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, |
307 | 0 | "Measure for %.6fs", integration_time_seconds); |
308 | 0 | break; |
309 | 0 | } |
310 | 0 | case USB_I1D3_MEASURE2: { |
311 | 0 | proto_item *edge_count_item = proto_tree_add_item( |
312 | 0 | tree, hf_usb_i1d3_requested_edge_count, tvb, 1, 6, ENC_NA); |
313 | 0 | proto_tree *edge_count_tree = proto_item_add_subtree( |
314 | 0 | edge_count_item, ett_usb_i1d3_requested_edge_count); |
315 | 0 | uint32_t edge_count_red, edge_count_green, edge_count_blue; |
316 | 0 | proto_tree_add_item_ret_uint( |
317 | 0 | edge_count_tree, hf_usb_i1d3_requested_edge_count_red, tvb, |
318 | 0 | 1, 2, ENC_LITTLE_ENDIAN, &edge_count_red); |
319 | 0 | proto_tree_add_item_ret_uint( |
320 | 0 | edge_count_tree, hf_usb_i1d3_requested_edge_count_green, tvb, |
321 | 0 | 3, 2, ENC_LITTLE_ENDIAN, &edge_count_green); |
322 | 0 | proto_tree_add_item_ret_uint( |
323 | 0 | edge_count_tree, hf_usb_i1d3_requested_edge_count_blue, tvb, |
324 | 0 | 5, 2, ENC_LITTLE_ENDIAN, &edge_count_blue); |
325 | 0 | proto_item_append_text( |
326 | 0 | edge_count_item, ": R%u G%u B%u", |
327 | 0 | edge_count_red, edge_count_green, edge_count_blue); |
328 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "Measure R%u G%u B%u edges", |
329 | 0 | edge_count_red, edge_count_green, edge_count_blue); |
330 | 0 | break; |
331 | 0 | } |
332 | 0 | case USB_I1D3_SETLED: { |
333 | 0 | uint32_t led_mode, led_offtime, led_ontime, pulse_count; |
334 | 0 | proto_tree_add_item_ret_uint( |
335 | 0 | tree, hf_usb_i1d3_led_mode, tvb, 1, 1, ENC_NA, &led_mode); |
336 | 0 | proto_item *led_offtime_item = proto_tree_add_item_ret_uint( |
337 | 0 | tree, hf_usb_i1d3_led_offtime, tvb, 2, 1, ENC_NA, |
338 | 0 | &led_offtime); |
339 | 0 | double led_offtime_seconds = |
340 | 0 | led_offtime / USB_I1D3_LED_OFFTIME_FACTOR; |
341 | 0 | proto_item_append_text( |
342 | 0 | led_offtime_item, " [%.6f seconds]", led_offtime_seconds); |
343 | 0 | proto_item *led_ontime_item = proto_tree_add_item_ret_uint( |
344 | 0 | tree, hf_usb_i1d3_led_ontime, tvb, 3, 1, ENC_NA, |
345 | 0 | &led_ontime); |
346 | 0 | double led_ontime_seconds = |
347 | 0 | led_ontime / ((led_mode == USB_I1D3_LED_BLINK) ? |
348 | 0 | USB_I1D3_LED_ONTIME_FACTOR : |
349 | 0 | USB_I1D3_LED_ONTIME_FADE_FACTOR); |
350 | 0 | proto_item_append_text( |
351 | 0 | led_ontime_item, " [%.6f seconds]", led_ontime_seconds); |
352 | 0 | proto_item *pulse_count_item = proto_tree_add_item_ret_uint( |
353 | 0 | tree, hf_usb_i1d3_led_pulse_count, tvb, 4, 1, ENC_NA, |
354 | 0 | &pulse_count); |
355 | 0 | if (pulse_count == 0x80) { |
356 | 0 | proto_item_append_text(pulse_count_item, " [infinity]"); |
357 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, |
358 | 0 | "Pulse LED off (%.6fs) and on (%.6fs%s) " |
359 | 0 | "indefinitely", led_offtime_seconds, led_ontime_seconds, |
360 | 0 | (led_mode == USB_I1D3_LED_BLINK_FADE_ON) ? |
361 | 0 | " fading" : ""); |
362 | 0 | } else { |
363 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, |
364 | 0 | "Pulse LED off (%.6fs) and on (%.6fs%s) " |
365 | 0 | "%u times", led_offtime_seconds, led_ontime_seconds, |
366 | 0 | (led_mode == USB_I1D3_LED_BLINK_FADE_ON) ? |
367 | 0 | " fading" : "", pulse_count); |
368 | 0 | } |
369 | 0 | } |
370 | 0 | } |
371 | 0 | } |
372 | | |
373 | | static void dissect_usb_i1d3_response( |
374 | | tvbuff_t *tvb, packet_info *pinfo, |
375 | 0 | usb_i1d3_conversation_t *conversation, proto_tree *tree) { |
376 | | // The response packet does not contain any information about the command |
377 | | // it is a response to, so we need to reconstruct this information using the |
378 | | // previous packet that we saw. |
379 | | // |
380 | | // Note: currently, for simplicity's sake, this assumes that there is only |
381 | | // one inflight request at any given time - in other words, that there is no |
382 | | // pipelining going on. It is not clear if the device would even be able to |
383 | | // service more than one request at the same time in the first place. |
384 | 0 | usb_i1d3_transaction_t *transaction; |
385 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
386 | 0 | transaction = (usb_i1d3_transaction_t *)wmem_map_lookup( |
387 | 0 | conversation->request_to_transaction, |
388 | 0 | GUINT_TO_POINTER(conversation->previous_packet)); |
389 | 0 | if (transaction) { |
390 | 0 | DISSECTOR_ASSERT(transaction->response == 0); |
391 | 0 | transaction->response = pinfo->num; |
392 | 0 | wmem_map_insert( |
393 | 0 | conversation->response_to_transaction, |
394 | 0 | GUINT_TO_POINTER(transaction->response), |
395 | 0 | (void *)transaction); |
396 | 0 | } |
397 | 0 | } else { |
398 | | // After the first pass, we can't use previous_packet anymore since |
399 | | // there is no guarantee the dissector is called in order, so we use |
400 | | // the reverse mapping that we populated above. |
401 | 0 | transaction = (usb_i1d3_transaction_t *)wmem_map_lookup( |
402 | 0 | conversation->response_to_transaction, |
403 | 0 | GUINT_TO_POINTER(pinfo->num)); |
404 | 0 | } |
405 | 0 | if (transaction) { |
406 | 0 | DISSECTOR_ASSERT(transaction->response == pinfo->num); |
407 | 0 | DISSECTOR_ASSERT(transaction->request != 0); |
408 | 0 | } |
409 | |
|
410 | 0 | proto_item *request_item = proto_tree_add_uint( |
411 | 0 | tree, hf_usb_i1d3_request_in, tvb, 0, 0, |
412 | 0 | transaction ? transaction->request : 0); |
413 | 0 | proto_item_set_generated(request_item); |
414 | 0 | if (!transaction) { |
415 | 0 | expert_add_info(pinfo, request_item, &ei_usb_i1d3_unexpected_response); |
416 | 0 | } else { |
417 | 0 | proto_item *command_code_item = proto_tree_add_uint( |
418 | 0 | tree, hf_usb_i1d3_command_code, tvb, 0, 0, |
419 | 0 | transaction->command_code); |
420 | 0 | proto_item_set_generated(command_code_item); |
421 | 0 | } |
422 | |
|
423 | 0 | const char *command_string = transaction ? try_val_to_str( |
424 | 0 | transaction->command_code, usb_i1d3_command_code_strings) : NULL; |
425 | 0 | if (!command_string) command_string = "unknown"; |
426 | |
|
427 | 0 | uint32_t response_code; |
428 | 0 | proto_item *response_code_item = proto_tree_add_item_ret_uint( |
429 | 0 | tree, hf_usb_i1d3_response_code, tvb, 0, 1, ENC_NA, &response_code); |
430 | 0 | proto_item_append_text( |
431 | 0 | response_code_item, " (%s)", (response_code == 0) ? "OK" : "error"); |
432 | 0 | if (response_code != 0) { |
433 | 0 | col_add_fstr( |
434 | 0 | pinfo->cinfo, COL_INFO, "Error code %u (%s)", |
435 | 0 | response_code, command_string); |
436 | 0 | expert_add_info(pinfo, response_code_item, &ei_usb_i1d3_error); |
437 | 0 | return; |
438 | 0 | } |
439 | | |
440 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "OK (%s)", command_string); |
441 | |
|
442 | 0 | if (!transaction) return; |
443 | | |
444 | | // As mentioned in ArgyllCMS spectro/i1d3.c, the second byte is usually the |
445 | | // first byte of the command code, except for GET_DIFF. |
446 | 0 | if (transaction->command_code != USB_I1D3_GET_DIFF) { |
447 | 0 | uint32_t echoed_command_code; |
448 | 0 | proto_item *echoed_command_code_item = proto_tree_add_item_ret_uint( |
449 | 0 | tree, hf_usb_i1d3_echoed_command_code, tvb, 1, 1, ENC_NA, |
450 | 0 | &echoed_command_code); |
451 | 0 | uint8_t expected_command_code = transaction->command_code >> 8; |
452 | 0 | proto_item_append_text( |
453 | 0 | echoed_command_code_item, " [expected 0x%02x]", |
454 | 0 | expected_command_code); |
455 | 0 | if (echoed_command_code != expected_command_code) { |
456 | 0 | expert_add_info( |
457 | 0 | pinfo, echoed_command_code_item, |
458 | 0 | &ei_usb_i1d3_echoed_command_code_mismatch); |
459 | 0 | } |
460 | 0 | } |
461 | |
|
462 | 0 | switch (transaction->command_code) { |
463 | 0 | case USB_I1D3_GET_INFO: { |
464 | 0 | const uint8_t *information; |
465 | 0 | proto_tree_add_item_ret_string( |
466 | 0 | tree, hf_usb_i1d3_information, tvb, 2, -1, |
467 | 0 | ENC_ASCII | ENC_NA, pinfo->pool, &information); |
468 | 0 | col_add_fstr( |
469 | 0 | pinfo->cinfo, COL_INFO, "Information: %s", information); |
470 | 0 | break; |
471 | 0 | } |
472 | 0 | case USB_I1D3_STATUS: { |
473 | 0 | uint32_t status; |
474 | 0 | proto_item *status_item = proto_tree_add_item_ret_uint( |
475 | 0 | tree, hf_usb_i1d3_status, tvb, 2, 3, ENC_BIG_ENDIAN, |
476 | 0 | &status); |
477 | 0 | const char *status_string = |
478 | 0 | ((status & 0xff00ff) != 0 || (status & 0x00ff00) >= 5) ? |
479 | 0 | "OK" : "Bad"; |
480 | 0 | proto_item_append_text(status_item, " [%s]", status_string); |
481 | 0 | col_add_fstr( |
482 | 0 | pinfo->cinfo, COL_INFO, "Status: 0x%06x (%s)", |
483 | 0 | status, status_string); |
484 | 0 | break; |
485 | 0 | } |
486 | 0 | case USB_I1D3_PRODNAME: { |
487 | 0 | const uint8_t *prodname; |
488 | 0 | proto_tree_add_item_ret_string( |
489 | 0 | tree, hf_usb_i1d3_prodname, tvb, 2, -1, |
490 | 0 | ENC_ASCII | ENC_NA, pinfo->pool, &prodname); |
491 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "Product name: %s", prodname); |
492 | 0 | break; |
493 | 0 | } |
494 | 0 | case USB_I1D3_PRODTYPE: { |
495 | 0 | uint32_t prodtype; |
496 | 0 | proto_tree_add_item_ret_uint( |
497 | 0 | tree, hf_usb_i1d3_prodtype, tvb, 3, 2, ENC_BIG_ENDIAN, |
498 | 0 | &prodtype); |
499 | 0 | col_add_fstr( |
500 | 0 | pinfo->cinfo, COL_INFO, "Product type: 0x%04x", |
501 | 0 | prodtype); |
502 | 0 | break; |
503 | 0 | } |
504 | 0 | case USB_I1D3_FIRMVER: { |
505 | 0 | const uint8_t *firmver; |
506 | 0 | proto_tree_add_item_ret_string( |
507 | 0 | tree, hf_usb_i1d3_firmver, tvb, 2, -1, |
508 | 0 | ENC_ASCII | ENC_NA, pinfo->pool, &firmver); |
509 | 0 | col_add_fstr( |
510 | 0 | pinfo->cinfo, COL_INFO, "Firmware version: %s", firmver); |
511 | 0 | break; |
512 | 0 | } |
513 | 0 | case USB_I1D3_FIRMDATE: { |
514 | 0 | const uint8_t *firmdate; |
515 | 0 | proto_tree_add_item_ret_string( |
516 | 0 | tree, hf_usb_i1d3_firmdate, tvb, 2, -1, |
517 | 0 | ENC_ASCII | ENC_NA, pinfo->pool, &firmdate); |
518 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "Firmware date: %s", firmdate); |
519 | 0 | break; |
520 | 0 | } |
521 | 0 | case USB_I1D3_LOCKED: { |
522 | 0 | uint32_t locked; |
523 | 0 | proto_item *locked_item = proto_tree_add_item_ret_uint( |
524 | 0 | tree, hf_usb_i1d3_locked, tvb, 2, 2, ENC_BIG_ENDIAN, |
525 | 0 | &locked); |
526 | 0 | const char *locked_string = |
527 | 0 | ((locked & 0xff00) != 0 || (locked & 0x00ff) == 0) ? |
528 | 0 | "Unlocked" : "Locked"; |
529 | 0 | proto_item_append_text(locked_item, " [%s]", locked_string); |
530 | 0 | col_add_fstr( |
531 | 0 | pinfo->cinfo, COL_INFO, "Locked status: 0x%04x (%s)", |
532 | 0 | locked, locked_string); |
533 | 0 | break; |
534 | 0 | } |
535 | 0 | case USB_I1D3_MEASURE1: { |
536 | 0 | proto_item *edge_count_item = proto_tree_add_item( |
537 | 0 | tree, hf_usb_i1d3_measured_edge_count, tvb, 2, 12, ENC_NA); |
538 | 0 | proto_tree *edge_count_tree = proto_item_add_subtree( |
539 | 0 | edge_count_item, ett_usb_i1d3_requested_edge_count); |
540 | 0 | uint32_t edge_count_red, edge_count_green, edge_count_blue; |
541 | 0 | proto_tree_add_item_ret_uint( |
542 | 0 | edge_count_tree, hf_usb_i1d3_measured_edge_count_red, tvb, |
543 | 0 | 2, 4, ENC_LITTLE_ENDIAN, &edge_count_red); |
544 | 0 | proto_tree_add_item_ret_uint( |
545 | 0 | edge_count_tree, hf_usb_i1d3_measured_edge_count_green, tvb, |
546 | 0 | 6, 4, ENC_LITTLE_ENDIAN, &edge_count_green); |
547 | 0 | proto_tree_add_item_ret_uint( |
548 | 0 | edge_count_tree, hf_usb_i1d3_measured_edge_count_blue, tvb, |
549 | 0 | 10, 4, ENC_LITTLE_ENDIAN, &edge_count_blue); |
550 | 0 | proto_item_append_text( |
551 | 0 | edge_count_item, ": R%u G%u B%u", |
552 | 0 | edge_count_red, edge_count_green, edge_count_blue); |
553 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "Measured R%u G%u B%u edges", |
554 | 0 | edge_count_red, edge_count_green, edge_count_blue); |
555 | 0 | break; |
556 | 0 | } |
557 | 0 | case USB_I1D3_MEASURE2: { |
558 | 0 | proto_item *duration_item = proto_tree_add_item( |
559 | 0 | tree, hf_usb_i1d3_measured_duration, tvb, 2, 12, ENC_NA); |
560 | 0 | proto_tree *duration_tree = proto_item_add_subtree( |
561 | 0 | duration_item, ett_usb_i1d3_measured_duration); |
562 | 0 | uint32_t duration_red, duration_green, duration_blue; |
563 | 0 | proto_item *duration_red_item = proto_tree_add_item_ret_uint( |
564 | 0 | duration_tree, hf_usb_i1d3_measured_duration_red, |
565 | 0 | tvb, 2, 4, ENC_LITTLE_ENDIAN, &duration_red); |
566 | 0 | double duration_red_seconds = |
567 | 0 | duration_red / USB_I1D3_CLOCK_FREQUENCY; |
568 | 0 | proto_item_append_text( |
569 | 0 | duration_red_item, |
570 | 0 | " [%.6f seconds]", duration_red_seconds); |
571 | 0 | proto_item *duration_green_item = proto_tree_add_item_ret_uint( |
572 | 0 | duration_tree, hf_usb_i1d3_measured_duration_green, |
573 | 0 | tvb, 6, 4, ENC_LITTLE_ENDIAN, &duration_green); |
574 | 0 | double duration_green_seconds = |
575 | 0 | duration_green / USB_I1D3_CLOCK_FREQUENCY; |
576 | 0 | proto_item_append_text( |
577 | 0 | duration_green_item, |
578 | 0 | " [%.6f seconds]", duration_green_seconds); |
579 | 0 | proto_item *duration_blue_item = proto_tree_add_item_ret_uint( |
580 | 0 | duration_tree, hf_usb_i1d3_measured_duration_blue, |
581 | 0 | tvb, 10, 4, ENC_LITTLE_ENDIAN, &duration_blue); |
582 | 0 | double duration_blue_seconds = |
583 | 0 | duration_blue / USB_I1D3_CLOCK_FREQUENCY; |
584 | 0 | proto_item_append_text( |
585 | 0 | duration_blue_item, |
586 | 0 | " [%.6f seconds]", duration_blue_seconds); |
587 | 0 | proto_item_append_text( |
588 | 0 | duration_item, ": R%.6fs G%.6fs B%.6fs", |
589 | 0 | duration_red_seconds, duration_green_seconds, |
590 | 0 | duration_blue_seconds); |
591 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, |
592 | 0 | "Measured R%.6fs G%.6fs B%.6fs", |
593 | 0 | duration_red_seconds, duration_green_seconds, |
594 | 0 | duration_blue_seconds); |
595 | 0 | break; |
596 | 0 | } |
597 | 0 | case USB_I1D3_READINTEE: { |
598 | 0 | proto_item *offset_item = proto_tree_add_uint( |
599 | 0 | tree, hf_usb_i1d3_readintee_offset, tvb, 0, 0, |
600 | 0 | transaction->offset); |
601 | 0 | proto_item_set_generated(offset_item); |
602 | 0 | proto_item *length_item = proto_tree_add_uint( |
603 | 0 | tree, hf_usb_i1d3_readintee_length, tvb, 0, 0, |
604 | 0 | transaction->length); |
605 | 0 | proto_item_set_generated(length_item); |
606 | 0 | proto_tree_add_item( |
607 | 0 | tree, hf_usb_i1d3_readintee_data, tvb, |
608 | 0 | 4, transaction->length, ENC_NA); |
609 | 0 | col_add_fstr( |
610 | 0 | pinfo->cinfo, COL_INFO, |
611 | 0 | "Internal EEPROM data (offset: %u, length: %u)", |
612 | 0 | transaction->offset, transaction->length); |
613 | 0 | break; |
614 | 0 | } |
615 | 0 | case USB_I1D3_READEXTEE: { |
616 | 0 | proto_item *offset_item = proto_tree_add_uint( |
617 | 0 | tree, hf_usb_i1d3_readextee_offset, tvb, 0, 0, |
618 | 0 | transaction->offset); |
619 | 0 | proto_item_set_generated(offset_item); |
620 | 0 | proto_item *length_item = proto_tree_add_uint( |
621 | 0 | tree, hf_usb_i1d3_readextee_length, tvb, 0, 0, |
622 | 0 | transaction->length); |
623 | 0 | proto_item_set_generated(length_item); |
624 | 0 | proto_tree_add_item( |
625 | 0 | tree, hf_usb_i1d3_readextee_data, tvb, |
626 | 0 | 5, transaction->length, ENC_NA); |
627 | 0 | col_add_fstr( |
628 | 0 | pinfo->cinfo, COL_INFO, |
629 | 0 | "External EEPROM data (offset: %u, length: %u)", |
630 | 0 | transaction->offset, transaction->length); |
631 | 0 | break; |
632 | 0 | } |
633 | 0 | case USB_I1D3_GET_DIFF: { |
634 | 0 | uint32_t diffuser_position; |
635 | 0 | proto_item *diffuser_position_item = proto_tree_add_item_ret_uint( |
636 | 0 | tree, hf_usb_i1d3_diffuser_position, tvb, |
637 | 0 | 1, 1, ENC_NA, &diffuser_position); |
638 | 0 | const char *diffuser_position_string = try_val_to_str( |
639 | 0 | diffuser_position, usb_i1d3_diffuser_position_strings); |
640 | 0 | if (!diffuser_position_string) { |
641 | 0 | expert_add_info( |
642 | 0 | pinfo, diffuser_position_item, |
643 | 0 | &ei_usb_i1d3_unknown_diffuser_position); |
644 | 0 | } |
645 | 0 | col_add_fstr( |
646 | 0 | pinfo->cinfo, COL_INFO, "Diffuser position: %s", |
647 | 0 | diffuser_position_string ? |
648 | 0 | diffuser_position_string : "unknown"); |
649 | 0 | break; |
650 | 0 | } |
651 | 0 | case USB_I1D3_LOCKCHAL: { |
652 | 0 | proto_tree_add_item( |
653 | 0 | tree, hf_usb_i1d3_challenge_encode_key, tvb, 2, 1, ENC_NA); |
654 | 0 | proto_tree_add_item( |
655 | 0 | tree, hf_usb_i1d3_challenge_decode_key, tvb, 3, 1, ENC_NA); |
656 | 0 | proto_tree_add_item( |
657 | 0 | tree, hf_usb_i1d3_challenge_data, tvb, 35, 8, ENC_NA); |
658 | 0 | break; |
659 | 0 | } |
660 | 0 | case USB_I1D3_LOCKRESP: { |
661 | 0 | uint32_t unlock_result; |
662 | 0 | proto_item *unlock_result_item = proto_tree_add_item_ret_uint( |
663 | 0 | tree, hf_usb_i1d3_unlock_result, tvb, 2, 1, ENC_NA, |
664 | 0 | &unlock_result); |
665 | 0 | int unlock_successful = unlock_result == 0x77; |
666 | 0 | const char *unlock_result_string = unlock_successful ? |
667 | 0 | "Successfully unlocked" : "Failed to unlock"; |
668 | 0 | proto_item_append_text( |
669 | 0 | unlock_result_item, " [%s]", unlock_result_string); |
670 | 0 | if (!unlock_successful) { |
671 | 0 | expert_add_info( |
672 | 0 | pinfo, unlock_result_item, &ei_usb_i1d3_unlock_failed); |
673 | 0 | } |
674 | 0 | col_add_str(pinfo->cinfo, COL_INFO, unlock_result_string); |
675 | 0 | break; |
676 | 0 | } |
677 | 0 | } |
678 | 0 | } |
679 | | |
680 | | static int dissect_usb_i1d3( |
681 | | tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, |
682 | | void *data _U_) |
683 | 0 | { |
684 | 0 | if ((pinfo->p2p_dir == P2P_DIR_SENT && pinfo->destport == 0) || |
685 | 0 | (pinfo->p2p_dir == P2P_DIR_RECV && pinfo->srcport == 0)) { |
686 | | // The device describes itself as HID class, even though the actual |
687 | | // protocol doesn't seem to be based on HID at all. However that means |
688 | | // the device will receive (and respond) to some basic HID requests, |
689 | | // such as GET_DESCRIPTOR. These HID requests will go to endpoint 0, |
690 | | // while actual communication takes place on endpoint 1. Therefore, if |
691 | | // we get handed a packet going to/from endpoint 0, reject it and let |
692 | | // the HID dissector handle it. |
693 | 0 | return 0; |
694 | 0 | } |
695 | | |
696 | 0 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "i1d3"); |
697 | |
|
698 | 0 | proto_item *usb_i1d3_item = proto_tree_add_item( |
699 | 0 | tree, proto_usb_i1d3, tvb, 0, -1, ENC_NA); |
700 | 0 | proto_tree *usb_i1d3_tree = proto_item_add_subtree( |
701 | 0 | usb_i1d3_item, ett_usb_i1d3); |
702 | | |
703 | | // All i1d3 packets seen in the while are fixed-length, with padding added |
704 | | // as necessary. It is not clear if using a different length is valid or |
705 | | // not. |
706 | 0 | if (tvb_reported_length(tvb) != USB_I1D3_PACKET_LENGTH) { |
707 | 0 | expert_add_info(pinfo, usb_i1d3_item, &ei_usb_i1d3_unusual_length); |
708 | 0 | } |
709 | |
|
710 | 0 | col_clear(pinfo->cinfo, COL_INFO); |
711 | 0 | usb_i1d3_conversation_t *conversation = usb_i1d3_get_conversation(pinfo); |
712 | 0 | if (pinfo->p2p_dir == P2P_DIR_SENT) { |
713 | 0 | dissect_usb_i1d3_command(tvb, pinfo, conversation, usb_i1d3_tree); |
714 | 0 | } else if (pinfo->p2p_dir == P2P_DIR_RECV) { |
715 | 0 | dissect_usb_i1d3_response(tvb, pinfo, conversation, usb_i1d3_tree); |
716 | 0 | } else { |
717 | 0 | DISSECTOR_ASSERT(0); |
718 | 0 | } |
719 | 0 | conversation->previous_packet = pinfo->num; |
720 | |
|
721 | 0 | return tvb_captured_length(tvb); |
722 | 0 | } |
723 | | |
724 | | void proto_register_usb_i1d3(void) |
725 | 14 | { |
726 | 14 | proto_usb_i1d3 = proto_register_protocol("X-Rite i1 Display Pro (and derivatives) USB protocol", "X-Rite i1 Display Pro", "i1d3"); |
727 | | |
728 | 14 | static int *ett[] = { |
729 | 14 | &ett_usb_i1d3, |
730 | 14 | &ett_usb_i1d3_measured_duration, |
731 | 14 | &ett_usb_i1d3_requested_edge_count, |
732 | 14 | }; |
733 | 14 | static hf_register_info hf[] = { |
734 | 14 | { &hf_usb_i1d3_challenge_response, |
735 | 14 | { "Challenge response", "i1d3.challenge_response", |
736 | 14 | FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }, |
737 | 14 | }, |
738 | 14 | { &hf_usb_i1d3_challenge_data, |
739 | 14 | { "Challenge data", "i1d3.challenge_data", |
740 | 14 | FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }, |
741 | 14 | }, |
742 | 14 | { &hf_usb_i1d3_challenge_decode_key, |
743 | 14 | { "Challenge decode XOR value", "i1d3.challenge_decode_key", |
744 | 14 | FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }, |
745 | 14 | }, |
746 | 14 | { &hf_usb_i1d3_challenge_encode_key, |
747 | 14 | { "Challenge encode XOR value", "i1d3.challenge_encode_key", |
748 | 14 | FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }, |
749 | 14 | }, |
750 | 14 | { &hf_usb_i1d3_command_code, |
751 | 14 | { "Command code", "i1d3.command.code", FT_UINT16, BASE_HEX, |
752 | 14 | VALS(usb_i1d3_command_code_strings), 0, NULL, HFILL }, |
753 | 14 | }, |
754 | 14 | { &hf_usb_i1d3_diffuser_position, |
755 | 14 | { "Diffuser position", "i1d3.diffuser_position", FT_UINT8, BASE_DEC, |
756 | 14 | VALS(usb_i1d3_diffuser_position_strings), 0, NULL, HFILL }, |
757 | 14 | }, |
758 | 14 | { &hf_usb_i1d3_echoed_command_code, |
759 | 14 | { "Echoed command code", "i1d3.echoed_command.code", FT_UINT8, |
760 | 14 | BASE_HEX, NULL, 0, NULL, HFILL }, |
761 | 14 | }, |
762 | 14 | { &hf_usb_i1d3_firmdate, |
763 | 14 | { "Firmware date", "i1d3.firmdate", FT_STRINGZ, BASE_NONE, |
764 | 14 | NULL, 0, NULL, HFILL }, |
765 | 14 | }, |
766 | 14 | { &hf_usb_i1d3_firmver, |
767 | 14 | { "Firmware version", "i1d3.firmver", FT_STRINGZ, BASE_NONE, |
768 | 14 | NULL, 0, NULL, HFILL }, |
769 | 14 | }, |
770 | 14 | { &hf_usb_i1d3_information, |
771 | 14 | { "Information", "i1d3.information", FT_STRINGZ, BASE_NONE, |
772 | 14 | NULL, 0, NULL, HFILL }, |
773 | 14 | }, |
774 | 14 | { &hf_usb_i1d3_measured_duration, |
775 | 14 | { "Measured duration", "i1d3.measured_duration", |
776 | 14 | FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }, |
777 | 14 | }, |
778 | 14 | { &hf_usb_i1d3_measured_duration_red, |
779 | 14 | { "Red channel", |
780 | 14 | "i1d3.measured_duration.red", FT_UINT32, |
781 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_cycle_cycles), |
782 | 14 | 0, NULL, HFILL }, |
783 | 14 | }, |
784 | 14 | { &hf_usb_i1d3_measured_duration_green, |
785 | 14 | { "Green channel", |
786 | 14 | "i1d3.measured_duration.green", FT_UINT32, |
787 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_cycle_cycles), |
788 | 14 | 0, NULL, HFILL }, |
789 | 14 | }, |
790 | 14 | { &hf_usb_i1d3_measured_duration_blue, |
791 | 14 | { "Blue channel", |
792 | 14 | "i1d3.measured_duration.blue", FT_UINT32, |
793 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_cycle_cycles), |
794 | 14 | 0, NULL, HFILL }, |
795 | 14 | }, |
796 | 14 | { &hf_usb_i1d3_measured_edge_count, |
797 | 14 | { "Measured edge count", "i1d3.measured_edge_count", |
798 | 14 | FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }, |
799 | 14 | }, |
800 | 14 | { &hf_usb_i1d3_measured_edge_count_red, |
801 | 14 | { "Red channel", |
802 | 14 | "i1d3.measured_edge_count.red", FT_UINT32, |
803 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges), |
804 | 14 | 0, NULL, HFILL }, |
805 | 14 | }, |
806 | 14 | { &hf_usb_i1d3_measured_edge_count_green, |
807 | 14 | { "Green channel", |
808 | 14 | "i1d3.measured_edge_count.green", FT_UINT32, |
809 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges), |
810 | 14 | 0, NULL, HFILL }, |
811 | 14 | }, |
812 | 14 | { &hf_usb_i1d3_measured_edge_count_blue, |
813 | 14 | { "Blue channel", |
814 | 14 | "i1d3.measured_edge_count.blue", FT_UINT32, |
815 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges), |
816 | 14 | 0, NULL, HFILL }, |
817 | 14 | }, |
818 | 14 | { &hf_usb_i1d3_led_mode, |
819 | 14 | { "LED mode", "i1d3.led_mode", FT_UINT8, BASE_DEC, |
820 | 14 | VALS(usb_i1d3_led_mode_strings), 0, NULL, HFILL }, |
821 | 14 | }, |
822 | 14 | { &hf_usb_i1d3_led_offtime, |
823 | 14 | { "LED off time", "i1d3.led_offtime", FT_UINT8, BASE_DEC, |
824 | 14 | NULL, 0, NULL, HFILL }, |
825 | 14 | }, |
826 | 14 | { &hf_usb_i1d3_led_ontime, |
827 | 14 | { "LED on time", "i1d3.led_ontime", FT_UINT8, BASE_DEC, |
828 | 14 | NULL, 0, NULL, HFILL }, |
829 | 14 | }, |
830 | 14 | { &hf_usb_i1d3_led_pulse_count, |
831 | 14 | { "LED pulse count", "i1d3.led_pulse_count", FT_UINT8, |
832 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_pulse_pulses), |
833 | 14 | 0, NULL, HFILL }, |
834 | 14 | }, |
835 | 14 | { &hf_usb_i1d3_locked, |
836 | 14 | { "Lock status", "i1d3.locked", |
837 | 14 | FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL }, |
838 | 14 | }, |
839 | 14 | { &hf_usb_i1d3_prodname, |
840 | 14 | { "Product name", "i1d3.prodname", FT_STRINGZ, BASE_NONE, |
841 | 14 | NULL, 0, NULL, HFILL }, |
842 | 14 | }, |
843 | 14 | { &hf_usb_i1d3_prodtype, |
844 | 14 | { "Product type", "i1d3.prodtype", FT_UINT16, BASE_HEX, |
845 | 14 | NULL, 0, NULL, HFILL }, |
846 | 14 | }, |
847 | 14 | { &hf_usb_i1d3_request_in, |
848 | 14 | { "Request in frame", "i1d3.request_in", |
849 | 14 | FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), |
850 | 14 | 0, NULL, HFILL } |
851 | 14 | }, |
852 | 14 | { &hf_usb_i1d3_requested_edge_count, |
853 | 14 | { "Requested edge count", "i1d3.requested_edge_count", |
854 | 14 | FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }, |
855 | 14 | }, |
856 | 14 | { &hf_usb_i1d3_requested_edge_count_red, |
857 | 14 | { "Red channel", |
858 | 14 | "i1d3.requested_edge_count.red", FT_UINT16, |
859 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges), |
860 | 14 | 0, NULL, HFILL }, |
861 | 14 | }, |
862 | 14 | { &hf_usb_i1d3_requested_edge_count_green, |
863 | 14 | { "Green channel", |
864 | 14 | "i1d3.requested_edge_count.green", FT_UINT16, |
865 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges), |
866 | 14 | 0, NULL, HFILL }, |
867 | 14 | }, |
868 | 14 | { &hf_usb_i1d3_requested_edge_count_blue, |
869 | 14 | { "Blue channel", |
870 | 14 | "i1d3.requested_edge_count.blue", FT_UINT16, |
871 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges), |
872 | 14 | 0, NULL, HFILL }, |
873 | 14 | }, |
874 | 14 | { &hf_usb_i1d3_requested_integration_time, |
875 | 14 | { "Requested integration time", |
876 | 14 | "i1d3.requested_integration_time", FT_UINT32, |
877 | 14 | BASE_DEC|BASE_UNIT_STRING, UNS(&units_cycle_cycles), |
878 | 14 | 0, NULL, HFILL }, |
879 | 14 | }, |
880 | 14 | { &hf_usb_i1d3_response_code, |
881 | 14 | { "Response code", |
882 | 14 | "i1d3.response_code", FT_UINT8, BASE_HEX, |
883 | 14 | NULL, 0, NULL, HFILL }, |
884 | 14 | }, |
885 | 14 | { &hf_usb_i1d3_response_in, |
886 | 14 | { "Response in frame", "i1d3.response_in", |
887 | 14 | FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), |
888 | 14 | 0, NULL, HFILL } |
889 | 14 | }, |
890 | 14 | { &hf_usb_i1d3_readintee_data, |
891 | 14 | { "Internal EEPROM data", "i1d3.readintee_data", |
892 | 14 | FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }, |
893 | 14 | }, |
894 | 14 | { &hf_usb_i1d3_readintee_offset, |
895 | 14 | { "Internal EEPROM read offset", "i1d3.readintee_offset", |
896 | 14 | FT_UINT8, BASE_DEC|BASE_UNIT_STRING, UNS(&units_byte_bytes), |
897 | 14 | 0, NULL, HFILL }, |
898 | 14 | }, |
899 | 14 | { &hf_usb_i1d3_readintee_length, |
900 | 14 | { "Internal EEPROM read length", "i1d3.readintee_length", |
901 | 14 | FT_UINT8, BASE_DEC|BASE_UNIT_STRING, UNS(&units_byte_bytes), |
902 | 14 | 0, NULL, HFILL }, |
903 | 14 | }, |
904 | 14 | { &hf_usb_i1d3_readextee_data, |
905 | 14 | { "External EEPROM data", "i1d3.readextee_data", |
906 | 14 | FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }, |
907 | 14 | }, |
908 | 14 | { &hf_usb_i1d3_readextee_offset, |
909 | 14 | { "External EEPROM read offset", "i1d3.readextee_offset", |
910 | 14 | FT_UINT16, BASE_DEC|BASE_UNIT_STRING, UNS(&units_byte_bytes), |
911 | 14 | 0, NULL, HFILL }, |
912 | 14 | }, |
913 | 14 | { &hf_usb_i1d3_readextee_length, |
914 | 14 | { "External EEPROM read length", "i1d3.readextee_length", |
915 | 14 | FT_UINT8, BASE_DEC|BASE_UNIT_STRING, UNS(&units_byte_bytes), |
916 | 14 | 0, NULL, HFILL }, |
917 | 14 | }, |
918 | 14 | { &hf_usb_i1d3_status, |
919 | 14 | { "Status", "i1d3.status", |
920 | 14 | FT_UINT24, BASE_HEX, NULL, 0, NULL, HFILL }, |
921 | 14 | }, |
922 | 14 | { &hf_usb_i1d3_unlock_result, |
923 | 14 | { "Unlock result", "i1d3.unlock_result", |
924 | 14 | FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }, |
925 | 14 | }, |
926 | 14 | }; |
927 | 14 | static ei_register_info ei[] = { |
928 | 14 | { &ei_usb_i1d3_echoed_command_code_mismatch, |
929 | 14 | { "i1d3.echoed_command_code_mismatch", PI_PROTOCOL, PI_ERROR, |
930 | 14 | "Echoed command code does not match request", EXPFILL } |
931 | 14 | }, |
932 | 14 | { &ei_usb_i1d3_error, |
933 | 14 | { "i1d3.error", PI_RESPONSE_CODE, PI_NOTE, |
934 | 14 | "Error response code", EXPFILL } |
935 | 14 | }, |
936 | 14 | { &ei_usb_i1d3_unexpected_response, |
937 | 14 | { "i1d3.unexpected_response", PI_SEQUENCE, PI_WARN, |
938 | 14 | "Could not match response to a request", EXPFILL } |
939 | 14 | }, |
940 | 14 | { &ei_usb_i1d3_unknown_command, |
941 | 14 | { "i1d3.unknown_command", PI_MALFORMED, PI_ERROR, |
942 | 14 | "Unknown command code", EXPFILL } |
943 | 14 | }, |
944 | 14 | { &ei_usb_i1d3_unknown_diffuser_position, |
945 | 14 | { "i1d3.unknown_diffuser_position", PI_MALFORMED, PI_ERROR, |
946 | 14 | "Unknown diffuser position code", EXPFILL } |
947 | 14 | }, |
948 | 14 | { &ei_usb_i1d3_unlock_failed, |
949 | 14 | { "i1d3.unlock_failed", PI_RESPONSE_CODE, PI_NOTE, |
950 | 14 | "Failed to unlock device", EXPFILL } |
951 | 14 | }, |
952 | 14 | { &ei_usb_i1d3_unusual_length, |
953 | 14 | { "i1d3.unusual_length", PI_PROTOCOL, PI_WARN, |
954 | 14 | "Packet has unusual length", EXPFILL } |
955 | 14 | }, |
956 | 14 | }; |
957 | | |
958 | 14 | proto_register_subtree_array(ett, array_length(ett)); |
959 | 14 | proto_register_field_array(proto_usb_i1d3, hf, array_length(hf)); |
960 | 14 | expert_module_t *expert_usb_i1d3 = expert_register_protocol( |
961 | 14 | proto_usb_i1d3); |
962 | 14 | expert_register_field_array(expert_usb_i1d3, ei, array_length(ei)); |
963 | 14 | usb_i1d3_dissector = register_dissector("i1d3", |
964 | 14 | dissect_usb_i1d3, proto_usb_i1d3); |
965 | 14 | } |
966 | | |
967 | 14 | void proto_reg_handoff_usb_i1d3(void) { |
968 | 14 | dissector_add_for_decode_as("usb.device", usb_i1d3_dissector); |
969 | 14 | dissector_add_uint("usb.product", 0x7655020, usb_i1d3_dissector); |
970 | 14 | } |
971 | | |
972 | | /* |
973 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
974 | | * |
975 | | * Local variables: |
976 | | * c-basic-offset: 4 |
977 | | * tab-width: 8 |
978 | | * indent-tabs-mode: nil |
979 | | * End: |
980 | | * |
981 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
982 | | * :indentSize=4:tabSize=8:noTabs=true: |
983 | | */ |