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
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.
28 #include <epan/conversation.h>
29 #include <epan/packet.h>
30 #include <epan/expert.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"},
143 typedef enum _usb_i1d3_led_mode
{
144 USB_I1D3_LED_BLINK
= 1,
145 USB_I1D3_LED_BLINK_FADE_ON
= 3,
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"},
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"},
163 typedef struct _usb_i1d3_transaction_t
{
166 uint32_t command_code
;
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
;
204 conversation
->request_to_transaction
,
205 GUINT_TO_POINTER(transaction
->request
), (void *)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
,
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
;
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
);
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
258 tree
, hf_usb_i1d3_challenge_response
, tvb
, 24, 16, ENC_NA
);
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
;
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
;
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
);
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
);
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
,
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
,
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
,
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
) ?
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
));
390 DISSECTOR_ASSERT(transaction
->response
== 0);
391 transaction
->response
= pinfo
->num
;
393 conversation
->response_to_transaction
,
394 GUINT_TO_POINTER(transaction
->response
),
395 (void *)transaction
);
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
));
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
);
415 expert_add_info(pinfo
, request_item
, &ei_usb_i1d3_unexpected_response
);
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) {
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
);
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
) {
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
);
469 pinfo
->cinfo
, COL_INFO
, "Information: %s", information
);
472 case USB_I1D3_STATUS
: {
474 proto_item
*status_item
= proto_tree_add_item_ret_uint(
475 tree
, hf_usb_i1d3_status
, tvb
, 2, 3, ENC_BIG_ENDIAN
,
477 const char *status_string
=
478 ((status
& 0xff00ff) != 0 || (status
& 0x00ff00) >= 5) ?
480 proto_item_append_text(status_item
, " [%s]", status_string
);
482 pinfo
->cinfo
, COL_INFO
, "Status: 0x%06x (%s)",
483 status
, status_string
);
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
);
494 case USB_I1D3_PRODTYPE
: {
496 proto_tree_add_item_ret_uint(
497 tree
, hf_usb_i1d3_prodtype
, tvb
, 3, 2, ENC_BIG_ENDIAN
,
500 pinfo
->cinfo
, COL_INFO
, "Product type: 0x%04x",
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
);
510 pinfo
->cinfo
, COL_INFO
, "Firmware version: %s", firmver
);
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
);
521 case USB_I1D3_LOCKED
: {
523 proto_item
*locked_item
= proto_tree_add_item_ret_uint(
524 tree
, hf_usb_i1d3_locked
, tvb
, 2, 2, ENC_BIG_ENDIAN
,
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
);
531 pinfo
->cinfo
, COL_INFO
, "Locked status: 0x%04x (%s)",
532 locked
, locked_string
);
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
);
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(
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(
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(
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
);
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
);
607 tree
, hf_usb_i1d3_readintee_data
, tvb
,
608 4, transaction
->length
, ENC_NA
);
610 pinfo
->cinfo
, COL_INFO
,
611 "Internal EEPROM data (offset: %u, length: %u)",
612 transaction
->offset
, transaction
->length
);
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
);
625 tree
, hf_usb_i1d3_readextee_data
, tvb
,
626 5, transaction
->length
, ENC_NA
);
628 pinfo
->cinfo
, COL_INFO
,
629 "External EEPROM data (offset: %u, length: %u)",
630 transaction
->offset
, transaction
->length
);
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
) {
642 pinfo
, diffuser_position_item
,
643 &ei_usb_i1d3_unknown_diffuser_position
);
646 pinfo
->cinfo
, COL_INFO
, "Diffuser position: %s",
647 diffuser_position_string
?
648 diffuser_position_string
: "unknown");
651 case USB_I1D3_LOCKCHAL
: {
653 tree
, hf_usb_i1d3_challenge_encode_key
, tvb
, 2, 1, ENC_NA
);
655 tree
, hf_usb_i1d3_challenge_decode_key
, tvb
, 3, 1, ENC_NA
);
657 tree
, hf_usb_i1d3_challenge_data
, tvb
, 35, 8, ENC_NA
);
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
,
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
) {
672 pinfo
, unlock_result_item
, &ei_usb_i1d3_unlock_failed
);
674 col_add_str(pinfo
->cinfo
, COL_INFO
, unlock_result_string
);
680 static int dissect_usb_i1d3(
681 tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
,
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.
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
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
);
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
[] = {
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
,
780 "i1d3.measured_duration.red", FT_UINT32
,
781 BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_cycle_cycles
),
784 { &hf_usb_i1d3_measured_duration_green
,
786 "i1d3.measured_duration.green", FT_UINT32
,
787 BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_cycle_cycles
),
790 { &hf_usb_i1d3_measured_duration_blue
,
792 "i1d3.measured_duration.blue", FT_UINT32
,
793 BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_cycle_cycles
),
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
,
802 "i1d3.measured_edge_count.red", FT_UINT32
,
803 BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_edge_edges
),
806 { &hf_usb_i1d3_measured_edge_count_green
,
808 "i1d3.measured_edge_count.green", FT_UINT32
,
809 BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_edge_edges
),
812 { &hf_usb_i1d3_measured_edge_count_blue
,
814 "i1d3.measured_edge_count.blue", FT_UINT32
,
815 BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_edge_edges
),
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
),
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
),
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
,
858 "i1d3.requested_edge_count.red", FT_UINT16
,
859 BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_edge_edges
),
862 { &hf_usb_i1d3_requested_edge_count_green
,
864 "i1d3.requested_edge_count.green", FT_UINT16
,
865 BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_edge_edges
),
868 { &hf_usb_i1d3_requested_edge_count_blue
,
870 "i1d3.requested_edge_count.blue", FT_UINT16
,
871 BASE_DEC
|BASE_UNIT_STRING
, UNS(&units_edge_edges
),
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
),
880 { &hf_usb_i1d3_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
),
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
),
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
),
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
),
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
),
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(
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
978 * indent-tabs-mode: nil
981 * vi: set shiftwidth=4 tabstop=8 expandtab:
982 * :indentSize=4:tabSize=8:noTabs=true: