Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / epan / dissectors / packet-usb-i1d3.c
blob769a2f2f1352b39f3e7cc9ff9c3275ffa836f801
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>
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
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.
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).
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.
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>
34 #include <wsutil/array.h>
36 void proto_register_usb_i1d3(void);
37 void proto_reg_handoff_usb_i1d3(void);
39 static dissector_handle_t usb_i1d3_dissector;
41 #define USB_I1D3_PACKET_LENGTH (64)
42 #define USB_I1D3_CLOCK_FREQUENCY (12e6) // 12 MHz
43 #define USB_I1D3_LED_OFFTIME_FACTOR (USB_I1D3_CLOCK_FREQUENCY / (1 << 19))
44 #define USB_I1D3_LED_ONTIME_FACTOR (USB_I1D3_CLOCK_FREQUENCY / (1 << 19))
45 #define USB_I1D3_LED_ONTIME_FADE_FACTOR (USB_I1D3_CLOCK_FREQUENCY / (1 << 23))
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;
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;
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;
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}
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}
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}
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;
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;
177 static const unit_name_string units_edge_edges = { " edge", " edges" };
178 static const unit_name_string units_pulse_pulses = { " pulse", " pulses" };
180 static usb_i1d3_conversation_t *usb_i1d3_get_conversation(packet_info *pinfo) {
181 conversation_t *conversation = find_or_create_conversation(pinfo);
182 usb_i1d3_conversation_t* i1d3_conversation =
183 (usb_i1d3_conversation_t *)conversation_get_proto_data(
184 conversation, proto_usb_i1d3);
185 if (!i1d3_conversation) {
186 i1d3_conversation = wmem_new0(
187 wmem_file_scope(), usb_i1d3_conversation_t);
188 i1d3_conversation->request_to_transaction = wmem_map_new(
189 wmem_file_scope(), g_direct_hash, g_direct_equal);
190 i1d3_conversation->response_to_transaction = wmem_map_new(
191 wmem_file_scope(), g_direct_hash, g_direct_equal);
192 conversation_add_proto_data(
193 conversation, proto_usb_i1d3, i1d3_conversation);
195 return i1d3_conversation;
198 static usb_i1d3_transaction_t *usb_i1d3_create_transaction(
199 usb_i1d3_conversation_t *conversation, uint32_t request) {
200 usb_i1d3_transaction_t *transaction = wmem_new0(
201 wmem_file_scope(), usb_i1d3_transaction_t);
202 transaction->request = request;
203 wmem_map_insert(
204 conversation->request_to_transaction,
205 GUINT_TO_POINTER(transaction->request), (void *)transaction);
206 return transaction;
209 static void dissect_usb_i1d3_command(
210 tvbuff_t *tvb, packet_info *pinfo,
211 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 uint32_t command_code = tvb_get_ntohs(tvb, 0);
216 uint32_t command_code_msb = command_code & 0xff00;
217 int command_code_length = 2;
218 if (command_code_msb) {
219 command_code = command_code_msb;
220 command_code_length = 1;
222 proto_item *command_code_item = proto_tree_add_uint(
223 tree, hf_usb_i1d3_command_code, tvb, 0, command_code_length,
224 command_code);
226 usb_i1d3_transaction_t *transaction;
227 if (!PINFO_FD_VISITED(pinfo)) {
228 transaction = usb_i1d3_create_transaction(conversation, pinfo->num);
229 transaction->command_code = command_code;
230 } else {
231 transaction = (usb_i1d3_transaction_t *)wmem_map_lookup(
232 conversation->request_to_transaction,
233 GUINT_TO_POINTER(pinfo->num));
235 DISSECTOR_ASSERT(transaction);
237 if (transaction->response != 0) {
238 proto_item *response_item = proto_tree_add_uint(
239 tree, hf_usb_i1d3_response_in, tvb, 0, 0,
240 transaction->response);
241 proto_item_set_generated(response_item);
244 const char *command_code_string = try_val_to_str(
245 command_code, usb_i1d3_command_code_strings);
246 if (command_code_string) {
247 col_set_str(pinfo->cinfo, COL_INFO, command_code_string);
248 } else {
249 expert_add_info(pinfo, command_code_item,
250 &ei_usb_i1d3_unknown_command);
251 col_set_str(pinfo->cinfo, COL_INFO, "Unknown command");
254 switch (command_code) {
255 case USB_I1D3_LOCKRESP: {
256 // TODO: verify that the challenge response is correct
257 proto_tree_add_item(
258 tree, hf_usb_i1d3_challenge_response, tvb, 24, 16, ENC_NA);
259 break;
262 case USB_I1D3_READINTEE: {
263 uint32_t offset, length;
264 proto_tree_add_item_ret_uint(
265 tree, hf_usb_i1d3_readintee_offset, tvb,
266 1, 1, ENC_NA, &offset);
267 proto_tree_add_item_ret_uint(
268 tree, hf_usb_i1d3_readintee_length, tvb,
269 2, 1, ENC_NA, &length);
270 col_add_fstr(pinfo->cinfo, COL_INFO, "%s (offset: %u, length: %u)",
271 command_code_string, offset, length);
272 if (!PINFO_FD_VISITED(pinfo)) {
273 transaction->offset = offset;
274 transaction->length = length;
276 break;
279 case USB_I1D3_READEXTEE: {
280 uint32_t offset, length;
281 proto_tree_add_item_ret_uint(
282 tree, hf_usb_i1d3_readextee_offset, tvb,
283 1, 2, ENC_BIG_ENDIAN, &offset);
284 proto_tree_add_item_ret_uint(
285 tree, hf_usb_i1d3_readextee_length, tvb,
286 3, 1, ENC_NA, &length);
287 col_add_fstr(pinfo->cinfo, COL_INFO, "%s (offset: %u, length: %u)",
288 command_code_string, offset, length);
289 if (!PINFO_FD_VISITED(pinfo)) {
290 transaction->offset = offset;
291 transaction->length = length;
293 break;
296 case USB_I1D3_MEASURE1: {
297 uint32_t integration_time;
298 proto_item *integration_time_item = proto_tree_add_item_ret_uint(
299 tree, hf_usb_i1d3_requested_integration_time, tvb, 1, 4,
300 ENC_LITTLE_ENDIAN, &integration_time);
301 double integration_time_seconds =
302 integration_time / USB_I1D3_CLOCK_FREQUENCY;
303 proto_item_append_text(
304 integration_time_item,
305 " [%.6f seconds]", integration_time_seconds);
306 col_add_fstr(pinfo->cinfo, COL_INFO,
307 "Measure for %.6fs", integration_time_seconds);
308 break;
310 case USB_I1D3_MEASURE2: {
311 proto_item *edge_count_item = proto_tree_add_item(
312 tree, hf_usb_i1d3_requested_edge_count, tvb, 1, 6, ENC_NA);
313 proto_tree *edge_count_tree = proto_item_add_subtree(
314 edge_count_item, ett_usb_i1d3_requested_edge_count);
315 uint32_t edge_count_red, edge_count_green, edge_count_blue;
316 proto_tree_add_item_ret_uint(
317 edge_count_tree, hf_usb_i1d3_requested_edge_count_red, tvb,
318 1, 2, ENC_LITTLE_ENDIAN, &edge_count_red);
319 proto_tree_add_item_ret_uint(
320 edge_count_tree, hf_usb_i1d3_requested_edge_count_green, tvb,
321 3, 2, ENC_LITTLE_ENDIAN, &edge_count_green);
322 proto_tree_add_item_ret_uint(
323 edge_count_tree, hf_usb_i1d3_requested_edge_count_blue, tvb,
324 5, 2, ENC_LITTLE_ENDIAN, &edge_count_blue);
325 proto_item_append_text(
326 edge_count_item, ": R%u G%u B%u",
327 edge_count_red, edge_count_green, edge_count_blue);
328 col_add_fstr(pinfo->cinfo, COL_INFO, "Measure R%u G%u B%u edges",
329 edge_count_red, edge_count_green, edge_count_blue);
330 break;
332 case USB_I1D3_SETLED: {
333 uint32_t led_mode, led_offtime, led_ontime, pulse_count;
334 proto_tree_add_item_ret_uint(
335 tree, hf_usb_i1d3_led_mode, tvb, 1, 1, ENC_NA, &led_mode);
336 proto_item *led_offtime_item = proto_tree_add_item_ret_uint(
337 tree, hf_usb_i1d3_led_offtime, tvb, 2, 1, ENC_NA,
338 &led_offtime);
339 double led_offtime_seconds =
340 led_offtime / USB_I1D3_LED_OFFTIME_FACTOR;
341 proto_item_append_text(
342 led_offtime_item, " [%.6f seconds]", led_offtime_seconds);
343 proto_item *led_ontime_item = proto_tree_add_item_ret_uint(
344 tree, hf_usb_i1d3_led_ontime, tvb, 3, 1, ENC_NA,
345 &led_ontime);
346 double led_ontime_seconds =
347 led_ontime / ((led_mode == USB_I1D3_LED_BLINK) ?
348 USB_I1D3_LED_ONTIME_FACTOR :
349 USB_I1D3_LED_ONTIME_FADE_FACTOR);
350 proto_item_append_text(
351 led_ontime_item, " [%.6f seconds]", led_ontime_seconds);
352 proto_item *pulse_count_item = proto_tree_add_item_ret_uint(
353 tree, hf_usb_i1d3_led_pulse_count, tvb, 4, 1, ENC_NA,
354 &pulse_count);
355 if (pulse_count == 0x80) {
356 proto_item_append_text(pulse_count_item, " [infinity]");
357 col_add_fstr(pinfo->cinfo, COL_INFO,
358 "Pulse LED off (%.6fs) and on (%.6fs%s) "
359 "indefinitely", led_offtime_seconds, led_ontime_seconds,
360 (led_mode == USB_I1D3_LED_BLINK_FADE_ON) ?
361 " fading" : "");
362 } else {
363 col_add_fstr(pinfo->cinfo, COL_INFO,
364 "Pulse LED off (%.6fs) and on (%.6fs%s) "
365 "%u times", led_offtime_seconds, led_ontime_seconds,
366 (led_mode == USB_I1D3_LED_BLINK_FADE_ON) ?
367 " fading" : "", pulse_count);
373 static void dissect_usb_i1d3_response(
374 tvbuff_t *tvb, packet_info *pinfo,
375 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.
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 usb_i1d3_transaction_t *transaction;
385 if (!PINFO_FD_VISITED(pinfo)) {
386 transaction = (usb_i1d3_transaction_t *)wmem_map_lookup(
387 conversation->request_to_transaction,
388 GUINT_TO_POINTER(conversation->previous_packet));
389 if (transaction) {
390 DISSECTOR_ASSERT(transaction->response == 0);
391 transaction->response = pinfo->num;
392 wmem_map_insert(
393 conversation->response_to_transaction,
394 GUINT_TO_POINTER(transaction->response),
395 (void *)transaction);
397 } 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 transaction = (usb_i1d3_transaction_t *)wmem_map_lookup(
402 conversation->response_to_transaction,
403 GUINT_TO_POINTER(pinfo->num));
405 if (transaction) {
406 DISSECTOR_ASSERT(transaction->response == pinfo->num);
407 DISSECTOR_ASSERT(transaction->request != 0);
410 proto_item *request_item = proto_tree_add_uint(
411 tree, hf_usb_i1d3_request_in, tvb, 0, 0,
412 transaction ? transaction->request : 0);
413 proto_item_set_generated(request_item);
414 if (!transaction) {
415 expert_add_info(pinfo, request_item, &ei_usb_i1d3_unexpected_response);
416 } else {
417 proto_item *command_code_item = proto_tree_add_uint(
418 tree, hf_usb_i1d3_command_code, tvb, 0, 0,
419 transaction->command_code);
420 proto_item_set_generated(command_code_item);
423 const char *command_string = transaction ? try_val_to_str(
424 transaction->command_code, usb_i1d3_command_code_strings) : NULL;
425 if (!command_string) command_string = "unknown";
427 uint32_t response_code;
428 proto_item *response_code_item = proto_tree_add_item_ret_uint(
429 tree, hf_usb_i1d3_response_code, tvb, 0, 1, ENC_NA, &response_code);
430 proto_item_append_text(
431 response_code_item, " (%s)", (response_code == 0) ? "OK" : "error");
432 if (response_code != 0) {
433 col_add_fstr(
434 pinfo->cinfo, COL_INFO, "Error code %u (%s)",
435 response_code, command_string);
436 expert_add_info(pinfo, response_code_item, &ei_usb_i1d3_error);
437 return;
440 col_add_fstr(pinfo->cinfo, COL_INFO, "OK (%s)", command_string);
442 if (!transaction) return;
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 if (transaction->command_code != USB_I1D3_GET_DIFF) {
447 uint32_t echoed_command_code;
448 proto_item *echoed_command_code_item = proto_tree_add_item_ret_uint(
449 tree, hf_usb_i1d3_echoed_command_code, tvb, 1, 1, ENC_NA,
450 &echoed_command_code);
451 uint8_t expected_command_code = transaction->command_code >> 8;
452 proto_item_append_text(
453 echoed_command_code_item, " [expected 0x%02x]",
454 expected_command_code);
455 if (echoed_command_code != expected_command_code) {
456 expert_add_info(
457 pinfo, echoed_command_code_item,
458 &ei_usb_i1d3_echoed_command_code_mismatch);
462 switch (transaction->command_code) {
463 case USB_I1D3_GET_INFO: {
464 const uint8_t *information;
465 proto_tree_add_item_ret_string(
466 tree, hf_usb_i1d3_information, tvb, 2, -1,
467 ENC_ASCII | ENC_NA, pinfo->pool, &information);
468 col_add_fstr(
469 pinfo->cinfo, COL_INFO, "Information: %s", information);
470 break;
472 case USB_I1D3_STATUS: {
473 uint32_t status;
474 proto_item *status_item = proto_tree_add_item_ret_uint(
475 tree, hf_usb_i1d3_status, tvb, 2, 3, ENC_BIG_ENDIAN,
476 &status);
477 const char *status_string =
478 ((status & 0xff00ff) != 0 || (status & 0x00ff00) >= 5) ?
479 "OK" : "Bad";
480 proto_item_append_text(status_item, " [%s]", status_string);
481 col_add_fstr(
482 pinfo->cinfo, COL_INFO, "Status: 0x%06x (%s)",
483 status, status_string);
484 break;
486 case USB_I1D3_PRODNAME: {
487 const uint8_t *prodname;
488 proto_tree_add_item_ret_string(
489 tree, hf_usb_i1d3_prodname, tvb, 2, -1,
490 ENC_ASCII | ENC_NA, pinfo->pool, &prodname);
491 col_add_fstr(pinfo->cinfo, COL_INFO, "Product name: %s", prodname);
492 break;
494 case USB_I1D3_PRODTYPE: {
495 uint32_t prodtype;
496 proto_tree_add_item_ret_uint(
497 tree, hf_usb_i1d3_prodtype, tvb, 3, 2, ENC_BIG_ENDIAN,
498 &prodtype);
499 col_add_fstr(
500 pinfo->cinfo, COL_INFO, "Product type: 0x%04x",
501 prodtype);
502 break;
504 case USB_I1D3_FIRMVER: {
505 const uint8_t *firmver;
506 proto_tree_add_item_ret_string(
507 tree, hf_usb_i1d3_firmver, tvb, 2, -1,
508 ENC_ASCII | ENC_NA, pinfo->pool, &firmver);
509 col_add_fstr(
510 pinfo->cinfo, COL_INFO, "Firmware version: %s", firmver);
511 break;
513 case USB_I1D3_FIRMDATE: {
514 const uint8_t *firmdate;
515 proto_tree_add_item_ret_string(
516 tree, hf_usb_i1d3_firmdate, tvb, 2, -1,
517 ENC_ASCII | ENC_NA, pinfo->pool, &firmdate);
518 col_add_fstr(pinfo->cinfo, COL_INFO, "Firmware date: %s", firmdate);
519 break;
521 case USB_I1D3_LOCKED: {
522 uint32_t locked;
523 proto_item *locked_item = proto_tree_add_item_ret_uint(
524 tree, hf_usb_i1d3_locked, tvb, 2, 2, ENC_BIG_ENDIAN,
525 &locked);
526 const char *locked_string =
527 ((locked & 0xff00) != 0 || (locked & 0x00ff) == 0) ?
528 "Unlocked" : "Locked";
529 proto_item_append_text(locked_item, " [%s]", locked_string);
530 col_add_fstr(
531 pinfo->cinfo, COL_INFO, "Locked status: 0x%04x (%s)",
532 locked, locked_string);
533 break;
535 case USB_I1D3_MEASURE1: {
536 proto_item *edge_count_item = proto_tree_add_item(
537 tree, hf_usb_i1d3_measured_edge_count, tvb, 2, 12, ENC_NA);
538 proto_tree *edge_count_tree = proto_item_add_subtree(
539 edge_count_item, ett_usb_i1d3_requested_edge_count);
540 uint32_t edge_count_red, edge_count_green, edge_count_blue;
541 proto_tree_add_item_ret_uint(
542 edge_count_tree, hf_usb_i1d3_measured_edge_count_red, tvb,
543 2, 4, ENC_LITTLE_ENDIAN, &edge_count_red);
544 proto_tree_add_item_ret_uint(
545 edge_count_tree, hf_usb_i1d3_measured_edge_count_green, tvb,
546 6, 4, ENC_LITTLE_ENDIAN, &edge_count_green);
547 proto_tree_add_item_ret_uint(
548 edge_count_tree, hf_usb_i1d3_measured_edge_count_blue, tvb,
549 10, 4, ENC_LITTLE_ENDIAN, &edge_count_blue);
550 proto_item_append_text(
551 edge_count_item, ": R%u G%u B%u",
552 edge_count_red, edge_count_green, edge_count_blue);
553 col_add_fstr(pinfo->cinfo, COL_INFO, "Measured R%u G%u B%u edges",
554 edge_count_red, edge_count_green, edge_count_blue);
555 break;
557 case USB_I1D3_MEASURE2: {
558 proto_item *duration_item = proto_tree_add_item(
559 tree, hf_usb_i1d3_measured_duration, tvb, 2, 12, ENC_NA);
560 proto_tree *duration_tree = proto_item_add_subtree(
561 duration_item, ett_usb_i1d3_measured_duration);
562 uint32_t duration_red, duration_green, duration_blue;
563 proto_item *duration_red_item = proto_tree_add_item_ret_uint(
564 duration_tree, hf_usb_i1d3_measured_duration_red,
565 tvb, 2, 4, ENC_LITTLE_ENDIAN, &duration_red);
566 double duration_red_seconds =
567 duration_red / USB_I1D3_CLOCK_FREQUENCY;
568 proto_item_append_text(
569 duration_red_item,
570 " [%.6f seconds]", duration_red_seconds);
571 proto_item *duration_green_item = proto_tree_add_item_ret_uint(
572 duration_tree, hf_usb_i1d3_measured_duration_green,
573 tvb, 6, 4, ENC_LITTLE_ENDIAN, &duration_green);
574 double duration_green_seconds =
575 duration_green / USB_I1D3_CLOCK_FREQUENCY;
576 proto_item_append_text(
577 duration_green_item,
578 " [%.6f seconds]", duration_green_seconds);
579 proto_item *duration_blue_item = proto_tree_add_item_ret_uint(
580 duration_tree, hf_usb_i1d3_measured_duration_blue,
581 tvb, 10, 4, ENC_LITTLE_ENDIAN, &duration_blue);
582 double duration_blue_seconds =
583 duration_blue / USB_I1D3_CLOCK_FREQUENCY;
584 proto_item_append_text(
585 duration_blue_item,
586 " [%.6f seconds]", duration_blue_seconds);
587 proto_item_append_text(
588 duration_item, ": R%.6fs G%.6fs B%.6fs",
589 duration_red_seconds, duration_green_seconds,
590 duration_blue_seconds);
591 col_add_fstr(pinfo->cinfo, COL_INFO,
592 "Measured R%.6fs G%.6fs B%.6fs",
593 duration_red_seconds, duration_green_seconds,
594 duration_blue_seconds);
595 break;
597 case USB_I1D3_READINTEE: {
598 proto_item *offset_item = proto_tree_add_uint(
599 tree, hf_usb_i1d3_readintee_offset, tvb, 0, 0,
600 transaction->offset);
601 proto_item_set_generated(offset_item);
602 proto_item *length_item = proto_tree_add_uint(
603 tree, hf_usb_i1d3_readintee_length, tvb, 0, 0,
604 transaction->length);
605 proto_item_set_generated(length_item);
606 proto_tree_add_item(
607 tree, hf_usb_i1d3_readintee_data, tvb,
608 4, transaction->length, ENC_NA);
609 col_add_fstr(
610 pinfo->cinfo, COL_INFO,
611 "Internal EEPROM data (offset: %u, length: %u)",
612 transaction->offset, transaction->length);
613 break;
615 case USB_I1D3_READEXTEE: {
616 proto_item *offset_item = proto_tree_add_uint(
617 tree, hf_usb_i1d3_readextee_offset, tvb, 0, 0,
618 transaction->offset);
619 proto_item_set_generated(offset_item);
620 proto_item *length_item = proto_tree_add_uint(
621 tree, hf_usb_i1d3_readextee_length, tvb, 0, 0,
622 transaction->length);
623 proto_item_set_generated(length_item);
624 proto_tree_add_item(
625 tree, hf_usb_i1d3_readextee_data, tvb,
626 5, transaction->length, ENC_NA);
627 col_add_fstr(
628 pinfo->cinfo, COL_INFO,
629 "External EEPROM data (offset: %u, length: %u)",
630 transaction->offset, transaction->length);
631 break;
633 case USB_I1D3_GET_DIFF: {
634 uint32_t diffuser_position;
635 proto_item *diffuser_position_item = proto_tree_add_item_ret_uint(
636 tree, hf_usb_i1d3_diffuser_position, tvb,
637 1, 1, ENC_NA, &diffuser_position);
638 const char *diffuser_position_string = try_val_to_str(
639 diffuser_position, usb_i1d3_diffuser_position_strings);
640 if (!diffuser_position_string) {
641 expert_add_info(
642 pinfo, diffuser_position_item,
643 &ei_usb_i1d3_unknown_diffuser_position);
645 col_add_fstr(
646 pinfo->cinfo, COL_INFO, "Diffuser position: %s",
647 diffuser_position_string ?
648 diffuser_position_string : "unknown");
649 break;
651 case USB_I1D3_LOCKCHAL: {
652 proto_tree_add_item(
653 tree, hf_usb_i1d3_challenge_encode_key, tvb, 2, 1, ENC_NA);
654 proto_tree_add_item(
655 tree, hf_usb_i1d3_challenge_decode_key, tvb, 3, 1, ENC_NA);
656 proto_tree_add_item(
657 tree, hf_usb_i1d3_challenge_data, tvb, 35, 8, ENC_NA);
658 break;
660 case USB_I1D3_LOCKRESP: {
661 uint32_t unlock_result;
662 proto_item *unlock_result_item = proto_tree_add_item_ret_uint(
663 tree, hf_usb_i1d3_unlock_result, tvb, 2, 1, ENC_NA,
664 &unlock_result);
665 int unlock_successful = unlock_result == 0x77;
666 const char *unlock_result_string = unlock_successful ?
667 "Successfully unlocked" : "Failed to unlock";
668 proto_item_append_text(
669 unlock_result_item, " [%s]", unlock_result_string);
670 if (!unlock_successful) {
671 expert_add_info(
672 pinfo, unlock_result_item, &ei_usb_i1d3_unlock_failed);
674 col_add_str(pinfo->cinfo, COL_INFO, unlock_result_string);
675 break;
680 static int dissect_usb_i1d3(
681 tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
682 void *data _U_)
684 if ((pinfo->p2p_dir == P2P_DIR_SENT && pinfo->destport == 0) ||
685 (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 return 0;
696 col_set_str(pinfo->cinfo, COL_PROTOCOL, "i1d3");
698 proto_item *usb_i1d3_item = proto_tree_add_item(
699 tree, proto_usb_i1d3, tvb, 0, -1, ENC_NA);
700 proto_tree *usb_i1d3_tree = proto_item_add_subtree(
701 usb_i1d3_item, ett_usb_i1d3);
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 if (tvb_reported_length(tvb) != USB_I1D3_PACKET_LENGTH) {
707 expert_add_info(pinfo, usb_i1d3_item, &ei_usb_i1d3_unusual_length);
710 col_clear(pinfo->cinfo, COL_INFO);
711 usb_i1d3_conversation_t *conversation = usb_i1d3_get_conversation(pinfo);
712 if (pinfo->p2p_dir == P2P_DIR_SENT) {
713 dissect_usb_i1d3_command(tvb, pinfo, conversation, usb_i1d3_tree);
714 } else if (pinfo->p2p_dir == P2P_DIR_RECV) {
715 dissect_usb_i1d3_response(tvb, pinfo, conversation, usb_i1d3_tree);
716 } else {
717 DISSECTOR_ASSERT(0);
719 conversation->previous_packet = pinfo->num;
721 return tvb_captured_length(tvb);
724 void proto_register_usb_i1d3(void)
726 proto_usb_i1d3 = proto_register_protocol("X-Rite i1 Display Pro (and derivatives) USB protocol", "X-Rite i1 Display Pro", "i1d3");
728 static int *ett[] = {
729 &ett_usb_i1d3,
730 &ett_usb_i1d3_measured_duration,
731 &ett_usb_i1d3_requested_edge_count,
733 static hf_register_info hf[] = {
734 { &hf_usb_i1d3_challenge_response,
735 { "Challenge response", "i1d3.challenge_response",
736 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL },
738 { &hf_usb_i1d3_challenge_data,
739 { "Challenge data", "i1d3.challenge_data",
740 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL },
742 { &hf_usb_i1d3_challenge_decode_key,
743 { "Challenge decode XOR value", "i1d3.challenge_decode_key",
744 FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL },
746 { &hf_usb_i1d3_challenge_encode_key,
747 { "Challenge encode XOR value", "i1d3.challenge_encode_key",
748 FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL },
750 { &hf_usb_i1d3_command_code,
751 { "Command code", "i1d3.command.code", FT_UINT16, BASE_HEX,
752 VALS(usb_i1d3_command_code_strings), 0, NULL, HFILL },
754 { &hf_usb_i1d3_diffuser_position,
755 { "Diffuser position", "i1d3.diffuser_position", FT_UINT8, BASE_DEC,
756 VALS(usb_i1d3_diffuser_position_strings), 0, NULL, HFILL },
758 { &hf_usb_i1d3_echoed_command_code,
759 { "Echoed command code", "i1d3.echoed_command.code", FT_UINT8,
760 BASE_HEX, NULL, 0, NULL, HFILL },
762 { &hf_usb_i1d3_firmdate,
763 { "Firmware date", "i1d3.firmdate", FT_STRINGZ, BASE_NONE,
764 NULL, 0, NULL, HFILL },
766 { &hf_usb_i1d3_firmver,
767 { "Firmware version", "i1d3.firmver", FT_STRINGZ, BASE_NONE,
768 NULL, 0, NULL, HFILL },
770 { &hf_usb_i1d3_information,
771 { "Information", "i1d3.information", FT_STRINGZ, BASE_NONE,
772 NULL, 0, NULL, HFILL },
774 { &hf_usb_i1d3_measured_duration,
775 { "Measured duration", "i1d3.measured_duration",
776 FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL },
778 { &hf_usb_i1d3_measured_duration_red,
779 { "Red channel",
780 "i1d3.measured_duration.red", FT_UINT32,
781 BASE_DEC|BASE_UNIT_STRING, UNS(&units_cycle_cycles),
782 0, NULL, HFILL },
784 { &hf_usb_i1d3_measured_duration_green,
785 { "Green channel",
786 "i1d3.measured_duration.green", FT_UINT32,
787 BASE_DEC|BASE_UNIT_STRING, UNS(&units_cycle_cycles),
788 0, NULL, HFILL },
790 { &hf_usb_i1d3_measured_duration_blue,
791 { "Blue channel",
792 "i1d3.measured_duration.blue", FT_UINT32,
793 BASE_DEC|BASE_UNIT_STRING, UNS(&units_cycle_cycles),
794 0, NULL, HFILL },
796 { &hf_usb_i1d3_measured_edge_count,
797 { "Measured edge count", "i1d3.measured_edge_count",
798 FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL },
800 { &hf_usb_i1d3_measured_edge_count_red,
801 { "Red channel",
802 "i1d3.measured_edge_count.red", FT_UINT32,
803 BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges),
804 0, NULL, HFILL },
806 { &hf_usb_i1d3_measured_edge_count_green,
807 { "Green channel",
808 "i1d3.measured_edge_count.green", FT_UINT32,
809 BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges),
810 0, NULL, HFILL },
812 { &hf_usb_i1d3_measured_edge_count_blue,
813 { "Blue channel",
814 "i1d3.measured_edge_count.blue", FT_UINT32,
815 BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges),
816 0, NULL, HFILL },
818 { &hf_usb_i1d3_led_mode,
819 { "LED mode", "i1d3.led_mode", FT_UINT8, BASE_DEC,
820 VALS(usb_i1d3_led_mode_strings), 0, NULL, HFILL },
822 { &hf_usb_i1d3_led_offtime,
823 { "LED off time", "i1d3.led_offtime", FT_UINT8, BASE_DEC,
824 NULL, 0, NULL, HFILL },
826 { &hf_usb_i1d3_led_ontime,
827 { "LED on time", "i1d3.led_ontime", FT_UINT8, BASE_DEC,
828 NULL, 0, NULL, HFILL },
830 { &hf_usb_i1d3_led_pulse_count,
831 { "LED pulse count", "i1d3.led_pulse_count", FT_UINT8,
832 BASE_DEC|BASE_UNIT_STRING, UNS(&units_pulse_pulses),
833 0, NULL, HFILL },
835 { &hf_usb_i1d3_locked,
836 { "Lock status", "i1d3.locked",
837 FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL },
839 { &hf_usb_i1d3_prodname,
840 { "Product name", "i1d3.prodname", FT_STRINGZ, BASE_NONE,
841 NULL, 0, NULL, HFILL },
843 { &hf_usb_i1d3_prodtype,
844 { "Product type", "i1d3.prodtype", FT_UINT16, BASE_HEX,
845 NULL, 0, NULL, HFILL },
847 { &hf_usb_i1d3_request_in,
848 { "Request in frame", "i1d3.request_in",
849 FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST),
850 0, NULL, HFILL }
852 { &hf_usb_i1d3_requested_edge_count,
853 { "Requested edge count", "i1d3.requested_edge_count",
854 FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL },
856 { &hf_usb_i1d3_requested_edge_count_red,
857 { "Red channel",
858 "i1d3.requested_edge_count.red", FT_UINT16,
859 BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges),
860 0, NULL, HFILL },
862 { &hf_usb_i1d3_requested_edge_count_green,
863 { "Green channel",
864 "i1d3.requested_edge_count.green", FT_UINT16,
865 BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges),
866 0, NULL, HFILL },
868 { &hf_usb_i1d3_requested_edge_count_blue,
869 { "Blue channel",
870 "i1d3.requested_edge_count.blue", FT_UINT16,
871 BASE_DEC|BASE_UNIT_STRING, UNS(&units_edge_edges),
872 0, NULL, HFILL },
874 { &hf_usb_i1d3_requested_integration_time,
875 { "Requested integration time",
876 "i1d3.requested_integration_time", FT_UINT32,
877 BASE_DEC|BASE_UNIT_STRING, UNS(&units_cycle_cycles),
878 0, NULL, HFILL },
880 { &hf_usb_i1d3_response_code,
881 { "Response code",
882 "i1d3.response_code", FT_UINT8, BASE_HEX,
883 NULL, 0, NULL, HFILL },
885 { &hf_usb_i1d3_response_in,
886 { "Response in frame", "i1d3.response_in",
887 FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE),
888 0, NULL, HFILL }
890 { &hf_usb_i1d3_readintee_data,
891 { "Internal EEPROM data", "i1d3.readintee_data",
892 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL },
894 { &hf_usb_i1d3_readintee_offset,
895 { "Internal EEPROM read offset", "i1d3.readintee_offset",
896 FT_UINT8, BASE_DEC|BASE_UNIT_STRING, UNS(&units_byte_bytes),
897 0, NULL, HFILL },
899 { &hf_usb_i1d3_readintee_length,
900 { "Internal EEPROM read length", "i1d3.readintee_length",
901 FT_UINT8, BASE_DEC|BASE_UNIT_STRING, UNS(&units_byte_bytes),
902 0, NULL, HFILL },
904 { &hf_usb_i1d3_readextee_data,
905 { "External EEPROM data", "i1d3.readextee_data",
906 FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL },
908 { &hf_usb_i1d3_readextee_offset,
909 { "External EEPROM read offset", "i1d3.readextee_offset",
910 FT_UINT16, BASE_DEC|BASE_UNIT_STRING, UNS(&units_byte_bytes),
911 0, NULL, HFILL },
913 { &hf_usb_i1d3_readextee_length,
914 { "External EEPROM read length", "i1d3.readextee_length",
915 FT_UINT8, BASE_DEC|BASE_UNIT_STRING, UNS(&units_byte_bytes),
916 0, NULL, HFILL },
918 { &hf_usb_i1d3_status,
919 { "Status", "i1d3.status",
920 FT_UINT24, BASE_HEX, NULL, 0, NULL, HFILL },
922 { &hf_usb_i1d3_unlock_result,
923 { "Unlock result", "i1d3.unlock_result",
924 FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL },
927 static ei_register_info ei[] = {
928 { &ei_usb_i1d3_echoed_command_code_mismatch,
929 { "i1d3.echoed_command_code_mismatch", PI_PROTOCOL, PI_ERROR,
930 "Echoed command code does not match request", EXPFILL }
932 { &ei_usb_i1d3_error,
933 { "i1d3.error", PI_RESPONSE_CODE, PI_NOTE,
934 "Error response code", EXPFILL }
936 { &ei_usb_i1d3_unexpected_response,
937 { "i1d3.unexpected_response", PI_SEQUENCE, PI_WARN,
938 "Could not match response to a request", EXPFILL }
940 { &ei_usb_i1d3_unknown_command,
941 { "i1d3.unknown_command", PI_MALFORMED, PI_ERROR,
942 "Unknown command code", EXPFILL }
944 { &ei_usb_i1d3_unknown_diffuser_position,
945 { "i1d3.unknown_diffuser_position", PI_MALFORMED, PI_ERROR,
946 "Unknown diffuser position code", EXPFILL }
948 { &ei_usb_i1d3_unlock_failed,
949 { "i1d3.unlock_failed", PI_RESPONSE_CODE, PI_NOTE,
950 "Failed to unlock device", EXPFILL }
952 { &ei_usb_i1d3_unusual_length,
953 { "i1d3.unusual_length", PI_PROTOCOL, PI_WARN,
954 "Packet has unusual length", EXPFILL }
958 proto_register_subtree_array(ett, array_length(ett));
959 proto_register_field_array(proto_usb_i1d3, hf, array_length(hf));
960 expert_module_t *expert_usb_i1d3 = expert_register_protocol(
961 proto_usb_i1d3);
962 expert_register_field_array(expert_usb_i1d3, ei, array_length(ei));
963 usb_i1d3_dissector = register_dissector("i1d3",
964 dissect_usb_i1d3, proto_usb_i1d3);
967 void proto_reg_handoff_usb_i1d3(void) {
968 dissector_add_for_decode_as("usb.device", usb_i1d3_dissector);
969 dissector_add_uint("usb.product", 0x7655020, usb_i1d3_dissector);
973 * Editor modelines - https://www.wireshark.org/tools/modelines.html
975 * Local variables:
976 * c-basic-offset: 4
977 * tab-width: 8
978 * indent-tabs-mode: nil
979 * End:
981 * vi: set shiftwidth=4 tabstop=8 expandtab:
982 * :indentSize=4:tabSize=8:noTabs=true: