From 92cd2c3ccb93ff67127d5a64fafa19b82ff7c624 Mon Sep 17 00:00:00 2001 From: Etienne Dechamps Date: Sun, 23 Oct 2016 11:32:29 +0100 Subject: X-Rite i1 Display Pro USB protocol dissector. This commit introduces a new dissector aimed at decoding the USB protocol used by X-Rite i1 Display Pro (and derivatives) colorimeter. It is based on reverse engineering work by Graeme Gill from the ArgyllCMS project. Change-Id: Icdfd0c3f75499d0df4360c6eb6856078de30ba56 Reviewed-on: https://code.wireshark.org/review/18901 Petri-Dish: Michael Mann Tested-by: Petri Dish Buildbot Reviewed-by: Michael Mann --- docbook/release-notes.asciidoc | 1 + epan/dissectors/CMakeLists.txt | 1 + epan/dissectors/Makefile.am | 1 + epan/dissectors/packet-usb-i1d3.c | 992 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 995 insertions(+) create mode 100644 epan/dissectors/packet-usb-i1d3.c diff --git a/docbook/release-notes.asciidoc b/docbook/release-notes.asciidoc index a75cda55f7..ed918310ed 100644 --- a/docbook/release-notes.asciidoc +++ b/docbook/release-notes.asciidoc @@ -74,6 +74,7 @@ vSocket Windows Cluster Management API (clusapi) GSMTAP based logging HomePNA +X-Rite i1 Display Pro (and derivatives) USB protocol --sort-and-group-- === Updated Protocol Support diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt index 275d0f84f0..c0625e0198 100644 --- a/epan/dissectors/CMakeLists.txt +++ b/epan/dissectors/CMakeLists.txt @@ -1317,6 +1317,7 @@ set(DISSECTOR_SRC packet-usb-dfu.c packet-usb-hid.c packet-usb-hub.c + packet-usb-i1d3.c packet-usb-masstorage.c packet-usb-video.c packet-usb.c diff --git a/epan/dissectors/Makefile.am b/epan/dissectors/Makefile.am index 1daedda589..6a5014b328 100644 --- a/epan/dissectors/Makefile.am +++ b/epan/dissectors/Makefile.am @@ -1339,6 +1339,7 @@ DISSECTOR_SRC = \ packet-usb-dfu.c \ packet-usb-hid.c \ packet-usb-hub.c \ + packet-usb-i1d3.c \ packet-usb-masstorage.c \ packet-usb-video.c \ packet-usb.c \ diff --git a/epan/dissectors/packet-usb-i1d3.c b/epan/dissectors/packet-usb-i1d3.c new file mode 100644 index 0000000000..f7326bfad3 --- /dev/null +++ b/epan/dissectors/packet-usb-i1d3.c @@ -0,0 +1,992 @@ +/* packet-usb-i1d3.c + * Dissects the X-Rite i1 Display Pro (and derivatives) USB protocol + * Copyright 2016, Etienne Dechamps + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * This code dissects the USB protocol used for communicating with a + * X-Rite i1 Display Pro colorimeter, as well as similar hardware such + * as ColorMunki Display. + * + * Note that this protocol is proprietary and no public specification + * exists. This code is largely based on Graeme Gill's reverse + * engineering work for ArgyllCMS (see spectro/i1d3.c in the ArgyllCMS + * source code). + * + * Because some aspects of the protocol are not yet fully understood, + * this dissector might fail to properly parse some packets, especially + * in unusual scenarios such as error conditions and the like. + */ + +#include +#include +#include +#include + +void proto_register_usb_i1d3(void); +void proto_reg_handoff_usb_i1d3(void); + +#define USB_I1D3_PACKET_LENGTH (64) +#define USB_I1D3_CLOCK_FREQUENCY (12e6) // 12 MHz +#define USB_I1D3_LED_OFFTIME_FACTOR (USB_I1D3_CLOCK_FREQUENCY / (1 << 19)) +#define USB_I1D3_LED_ONTIME_FACTOR (USB_I1D3_CLOCK_FREQUENCY / (1 << 19)) +#define USB_I1D3_LED_ONTIME_FADE_FACTOR (USB_I1D3_CLOCK_FREQUENCY / (1 << 23)) + +static int proto_usb_i1d3 = -1; +static int ett_usb_i1d3 = -1; +static int ett_usb_i1d3_measured_duration = -1; +static int ett_usb_i1d3_requested_edge_count = -1; + +static int hf_usb_i1d3_challenge_response = -1; +static int hf_usb_i1d3_challenge_data = -1; +static int hf_usb_i1d3_challenge_decode_key = -1; +static int hf_usb_i1d3_challenge_encode_key = -1; +static int hf_usb_i1d3_command_code = -1; +static int hf_usb_i1d3_diffuser_position = -1; +static int hf_usb_i1d3_echoed_command_code = -1; +static int hf_usb_i1d3_firmdate = -1; +static int hf_usb_i1d3_firmver = -1; +static int hf_usb_i1d3_information = -1; +static int hf_usb_i1d3_measured_duration = -1; +static int hf_usb_i1d3_measured_duration_red = -1; +static int hf_usb_i1d3_measured_duration_green = -1; +static int hf_usb_i1d3_measured_duration_blue = -1; +static int hf_usb_i1d3_measured_edge_count = -1; +static int hf_usb_i1d3_measured_edge_count_red = -1; +static int hf_usb_i1d3_measured_edge_count_green = -1; +static int hf_usb_i1d3_measured_edge_count_blue = -1; +static int hf_usb_i1d3_led_mode = -1; +static int hf_usb_i1d3_led_offtime = -1; +static int hf_usb_i1d3_led_ontime = -1; +static int hf_usb_i1d3_led_pulse_count = -1; +static int hf_usb_i1d3_locked = -1; +static int hf_usb_i1d3_prodname = -1; +static int hf_usb_i1d3_prodtype = -1; +static int hf_usb_i1d3_request_in = -1; +static int hf_usb_i1d3_requested_edge_count = -1; +static int hf_usb_i1d3_requested_edge_count_red = -1; +static int hf_usb_i1d3_requested_edge_count_green = -1; +static int hf_usb_i1d3_requested_edge_count_blue = -1; +static int hf_usb_i1d3_requested_integration_time = -1; +static int hf_usb_i1d3_response_code = -1; +static int hf_usb_i1d3_response_in = -1; +static int hf_usb_i1d3_readextee_data = -1; +static int hf_usb_i1d3_readextee_offset = -1; +static int hf_usb_i1d3_readextee_length = -1; +static int hf_usb_i1d3_readintee_data = -1; +static int hf_usb_i1d3_readintee_offset = -1; +static int hf_usb_i1d3_readintee_length = -1; +static int hf_usb_i1d3_status = -1; +static int hf_usb_i1d3_unlock_result = -1; + +static expert_field ei_usb_i1d3_echoed_command_code_mismatch = EI_INIT; +static expert_field ei_usb_i1d3_error = EI_INIT; +static expert_field ei_usb_i1d3_unexpected_response = EI_INIT; +static expert_field ei_usb_i1d3_unknown_command = EI_INIT; +static expert_field ei_usb_i1d3_unknown_diffuser_position = EI_INIT; +static expert_field ei_usb_i1d3_unlock_failed = EI_INIT; +static expert_field ei_usb_i1d3_unusual_length = EI_INIT; + +// Derived from ArgyllCMS spectro/i1d3.c. +typedef enum _usb_i1d3_command_code { + USB_I1D3_GET_INFO = 0x0000, + USB_I1D3_STATUS = 0x0001, + USB_I1D3_PRODNAME = 0x0010, + USB_I1D3_PRODTYPE = 0x0011, + USB_I1D3_FIRMVER = 0x0012, + USB_I1D3_FIRMDATE = 0x0013, + USB_I1D3_LOCKED = 0x0020, + USB_I1D3_MEASURE1 = 0x0100, + USB_I1D3_MEASURE2 = 0x0200, + USB_I1D3_READINTEE = 0x0800, + USB_I1D3_READEXTEE = 0x1200, + USB_I1D3_SETLED = 0x2100, + USB_I1D3_RD_SENSOR = 0x9300, + USB_I1D3_GET_DIFF = 0x9400, + USB_I1D3_LOCKCHAL = 0x9900, + USB_I1D3_LOCKRESP = 0x9a00, + USB_I1D3_RELOCK = 0x9b00, +} usb_i1d3_command_code; +static const value_string usb_i1d3_command_code_strings[] = { + {USB_I1D3_GET_INFO, "Get information"}, + {USB_I1D3_STATUS, "Get status"}, + {USB_I1D3_PRODNAME, "Get product name"}, + {USB_I1D3_PRODTYPE, "Get product type"}, + {USB_I1D3_FIRMVER, "Get firmware version"}, + {USB_I1D3_FIRMDATE, "Get firmware date"}, + {USB_I1D3_LOCKED, "Get locked status"}, + {USB_I1D3_MEASURE1, "Make measurement (fixed integration time)"}, + {USB_I1D3_MEASURE2, "Make measurement (fixed edge count)"}, + {USB_I1D3_READINTEE, "Read internal EEPROM"}, + {USB_I1D3_READEXTEE, "Read external EEPROM"}, + {USB_I1D3_SETLED, "Set LED state"}, + {USB_I1D3_RD_SENSOR, "Read analog sensor"}, + {USB_I1D3_GET_DIFF, "Get diffuser position"}, + {USB_I1D3_LOCKCHAL, "Request lock challenge"}, + {USB_I1D3_LOCKRESP, "Unlock"}, + {USB_I1D3_RELOCK, "Relock"}, + {0, NULL} +}; + +typedef enum _usb_i1d3_led_mode { + USB_I1D3_LED_BLINK = 1, + USB_I1D3_LED_BLINK_FADE_ON = 3, +} usb_i1d3_led_mode; +static const value_string usb_i1d3_led_mode_strings[] = { + {USB_I1D3_LED_BLINK, "Blink"}, + {USB_I1D3_LED_BLINK_FADE_ON, "Blink, fade on"}, + {0, NULL} +}; + +typedef enum _usb_i1d3_diffuser_position { + USB_I1D3_DIFFUSER_DISPLAY = 0, + USB_I1D3_DIFFUSER_AMBIENT = 1, +} usb_i1d3_diffuser_position; +static const value_string usb_i1d3_diffuser_position_strings[] = { + {USB_I1D3_DIFFUSER_DISPLAY, "Display"}, + {USB_I1D3_DIFFUSER_AMBIENT, "Ambient"}, + {0, NULL} +}; + +typedef struct _usb_i1d3_transaction_t { + guint32 request; + guint32 response; + guint32 command_code; + guint32 offset; + guint32 length; +} usb_i1d3_transaction_t; + +typedef struct _usb_i1d3_conversation_t { + wmem_map_t *request_to_transaction; + wmem_map_t *response_to_transaction; + guint32 previous_packet; +} usb_i1d3_conversation_t; + +static const unit_name_string units_cycle_cycles = { " cycle", " cycles" }; +static const unit_name_string units_edge_edges = { " edge", " edges" }; +static const unit_name_string units_pulse_pulses = { " pulse", " pulses" }; + +static usb_i1d3_conversation_t *usb_i1d3_get_conversation(packet_info *pinfo) { + conversation_t *conversation = find_or_create_conversation(pinfo); + usb_i1d3_conversation_t* i1d3_conversation = + (usb_i1d3_conversation_t *)conversation_get_proto_data( + conversation, proto_usb_i1d3); + if (!i1d3_conversation) { + i1d3_conversation = wmem_new0( + wmem_file_scope(), usb_i1d3_conversation_t); + i1d3_conversation->request_to_transaction = wmem_map_new( + wmem_file_scope(), g_direct_hash, g_direct_equal); + i1d3_conversation->response_to_transaction = wmem_map_new( + wmem_file_scope(), g_direct_hash, g_direct_equal); + conversation_add_proto_data( + conversation, proto_usb_i1d3, i1d3_conversation); + } + return i1d3_conversation; +} + +static usb_i1d3_transaction_t *usb_i1d3_create_transaction( + usb_i1d3_conversation_t *conversation, guint32 request) { + usb_i1d3_transaction_t *transaction = wmem_new0( + wmem_file_scope(), usb_i1d3_transaction_t); + transaction->request = request; + wmem_map_insert( + conversation->request_to_transaction, + GUINT_TO_POINTER(transaction->request), (void *)transaction); + return transaction; +} + +static void dissect_usb_i1d3_command( + tvbuff_t *tvb, packet_info *pinfo, + usb_i1d3_conversation_t *conversation, proto_tree *tree) { + // Parsing the command code is a bit tricky: if the most significant + // byte is non-zero, the command code is the most significant byte, + // *and* the next byte is the first byte of the payload. + guint32 command_code = tvb_get_ntohs(tvb, 0); + guint32 command_code_msb = command_code & 0xff00; + gint command_code_length = 2; + if (command_code_msb) { + command_code = command_code_msb; + command_code_length = 1; + } + proto_item *command_code_item = proto_tree_add_uint( + tree, hf_usb_i1d3_command_code, tvb, 0, command_code_length, + command_code); + + usb_i1d3_transaction_t *transaction; + if (!PINFO_FD_VISITED(pinfo)) { + transaction = usb_i1d3_create_transaction(conversation, pinfo->num); + transaction->command_code = command_code; + } else { + transaction = (usb_i1d3_transaction_t *)wmem_map_lookup( + conversation->request_to_transaction, + GUINT_TO_POINTER(pinfo->num)); + } + DISSECTOR_ASSERT(transaction); + + if (transaction->response != 0) { + proto_item *response_item = proto_tree_add_uint( + tree, hf_usb_i1d3_response_in, tvb, 0, 0, + transaction->response); + PROTO_ITEM_SET_GENERATED(response_item); + } + + const gchar *command_code_string = try_val_to_str( + command_code, usb_i1d3_command_code_strings); + if (command_code_string) { + col_set_str(pinfo->cinfo, COL_INFO, command_code_string); + } else { + expert_add_info(pinfo, command_code_item, + &ei_usb_i1d3_unknown_command); + col_set_str(pinfo->cinfo, COL_INFO, "Unknown command"); + } + + switch (command_code) { + case USB_I1D3_LOCKRESP: { + // TODO: verify that the challenge response is correct + proto_tree_add_item( + tree, hf_usb_i1d3_challenge_response, tvb, 24, 16, ENC_NA); + break; + } + + case USB_I1D3_READINTEE: { + guint32 offset, length; + proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_readintee_offset, tvb, + 1, 1, ENC_NA, &offset); + proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_readintee_length, tvb, + 2, 1, ENC_NA, &length); + col_add_fstr(pinfo->cinfo, COL_INFO, "%s (offset: %u, length: %u)", + command_code_string, offset, length); + if (!PINFO_FD_VISITED(pinfo)) { + transaction->offset = offset; + transaction->length = length; + } + break; + } + + case USB_I1D3_READEXTEE: { + guint32 offset, length; + proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_readextee_offset, tvb, + 1, 2, ENC_BIG_ENDIAN, &offset); + proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_readextee_length, tvb, + 3, 1, ENC_NA, &length); + col_add_fstr(pinfo->cinfo, COL_INFO, "%s (offset: %u, length: %u)", + command_code_string, offset, length); + if (!PINFO_FD_VISITED(pinfo)) { + transaction->offset = offset; + transaction->length = length; + } + break; + } + + case USB_I1D3_MEASURE1: { + guint32 integration_time; + proto_item *integration_time_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_requested_integration_time, tvb, 1, 4, + ENC_LITTLE_ENDIAN, &integration_time); + double integration_time_seconds = + integration_time / USB_I1D3_CLOCK_FREQUENCY; + proto_item_append_text( + integration_time_item, + " [%.6f seconds]", integration_time_seconds); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Measure for %.6fs", integration_time_seconds); + break; + } + case USB_I1D3_MEASURE2: { + proto_item *edge_count_item = proto_tree_add_item( + tree, hf_usb_i1d3_requested_edge_count, tvb, 1, 6, ENC_NA); + proto_tree *edge_count_tree = proto_item_add_subtree( + edge_count_item, ett_usb_i1d3_requested_edge_count); + guint32 edge_count_red, edge_count_green, edge_count_blue; + proto_tree_add_item_ret_uint( + edge_count_tree, hf_usb_i1d3_requested_edge_count_red, tvb, + 1, 2, ENC_LITTLE_ENDIAN, &edge_count_red); + proto_tree_add_item_ret_uint( + edge_count_tree, hf_usb_i1d3_requested_edge_count_green, tvb, + 3, 2, ENC_LITTLE_ENDIAN, &edge_count_green); + proto_tree_add_item_ret_uint( + edge_count_tree, hf_usb_i1d3_requested_edge_count_blue, tvb, + 5, 2, ENC_LITTLE_ENDIAN, &edge_count_blue); + proto_item_append_text( + edge_count_item, ": R%u G%u B%u", + edge_count_red, edge_count_green, edge_count_blue); + col_add_fstr(pinfo->cinfo, COL_INFO, "Measure R%u G%u B%u edges", + edge_count_red, edge_count_green, edge_count_blue); + break; + } + case USB_I1D3_SETLED: { + guint32 led_mode, led_offtime, led_ontime, pulse_count; + proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_led_mode, tvb, 1, 1, ENC_NA, &led_mode); + proto_item *led_offtime_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_led_offtime, tvb, 2, 1, ENC_NA, + &led_offtime); + double led_offtime_seconds = + led_offtime / USB_I1D3_LED_OFFTIME_FACTOR; + proto_item_append_text( + led_offtime_item, " [%.6f seconds]", led_offtime_seconds); + proto_item *led_ontime_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_led_ontime, tvb, 3, 1, ENC_NA, + &led_ontime); + double led_ontime_seconds = + led_ontime / ((led_mode == USB_I1D3_LED_BLINK) ? + USB_I1D3_LED_ONTIME_FACTOR : + USB_I1D3_LED_ONTIME_FADE_FACTOR); + proto_item_append_text( + led_ontime_item, " [%.6f seconds]", led_ontime_seconds); + proto_item *pulse_count_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_led_pulse_count, tvb, 4, 1, ENC_NA, + &pulse_count); + if (pulse_count == 0x80) { + proto_item_append_text(pulse_count_item, " [infinity]"); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Pulse LED off (%.6fs) and on (%.6fs%s) " + "indefinitely", led_offtime_seconds, led_ontime_seconds, + (led_mode == USB_I1D3_LED_BLINK_FADE_ON) ? + " fading" : ""); + } else { + col_add_fstr(pinfo->cinfo, COL_INFO, + "Pulse LED off (%.6fs) and on (%.6fs%s) " + "%u times", led_offtime_seconds, led_ontime_seconds, + (led_mode == USB_I1D3_LED_BLINK_FADE_ON) ? + " fading" : "", pulse_count); + } + } + } +} + +static void dissect_usb_i1d3_response( + tvbuff_t *tvb, packet_info *pinfo, + usb_i1d3_conversation_t *conversation, proto_tree *tree) { + // The response packet does not contain any information about the command + // it is a response to, so we need to reconstruct this information using the + // previous packet that we saw. + // + // Note: currently, for simplicity's sake, this assumes that there is only + // one inflight request at any given time - in other words, that there is no + // pipelining going on. It is not clear if the device would even be able to + // service more than one request at the same time in the first place. + usb_i1d3_transaction_t *transaction; + if (!PINFO_FD_VISITED(pinfo)) { + transaction = (usb_i1d3_transaction_t *)wmem_map_lookup( + conversation->request_to_transaction, + GUINT_TO_POINTER(conversation->previous_packet)); + if (transaction) { + DISSECTOR_ASSERT(transaction->response == 0); + transaction->response = pinfo->num; + wmem_map_insert( + conversation->response_to_transaction, + GUINT_TO_POINTER(transaction->response), + (void *)transaction); + } + } else { + // After the first pass, we can't use previous_packet anymore since + // there is no guarantee the dissector is called in order, so we use + // the reverse mapping that we populated above. + transaction = (usb_i1d3_transaction_t *)wmem_map_lookup( + conversation->response_to_transaction, + GUINT_TO_POINTER(pinfo->num)); + } + if (transaction) { + DISSECTOR_ASSERT(transaction->response == pinfo->num); + DISSECTOR_ASSERT(transaction->request != 0); + } + + proto_item *request_item = proto_tree_add_uint( + tree, hf_usb_i1d3_request_in, tvb, 0, 0, + transaction ? transaction->request : 0); + PROTO_ITEM_SET_GENERATED(request_item); + if (!transaction) { + expert_add_info(pinfo, request_item, &ei_usb_i1d3_unexpected_response); + } else { + proto_item *command_code_item = proto_tree_add_uint( + tree, hf_usb_i1d3_command_code, tvb, 0, 0, + transaction->command_code); + PROTO_ITEM_SET_GENERATED(command_code_item); + } + + const gchar *command_string = transaction ? try_val_to_str( + transaction->command_code, usb_i1d3_command_code_strings) : NULL; + if (!command_string) command_string = "unknown"; + + guint32 response_code; + proto_item *response_code_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_response_code, tvb, 0, 1, ENC_NA, &response_code); + proto_item_append_text( + response_code_item, " (%s)", (response_code == 0) ? "OK" : "error"); + if (response_code != 0) { + col_add_fstr( + pinfo->cinfo, COL_INFO, "Error code %u (%s)", + response_code, command_string); + expert_add_info(pinfo, response_code_item, &ei_usb_i1d3_error); + return; + } + + col_add_fstr(pinfo->cinfo, COL_INFO, "OK (%s)", command_string); + + if (!transaction) return; + + // As mentioned in ArgyllCMS spectro/i1d3.c, the second byte is usually the + // first byte of the command code, except for GET_DIFF. + if (transaction->command_code != USB_I1D3_GET_DIFF) { + guint32 echoed_command_code; + proto_item *echoed_command_code_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_echoed_command_code, tvb, 1, 1, ENC_NA, + &echoed_command_code); + guint8 expected_command_code = transaction->command_code >> 8; + proto_item_append_text( + echoed_command_code_item, " [expected 0x%02x]", + expected_command_code); + if (echoed_command_code != expected_command_code) { + expert_add_info( + pinfo, echoed_command_code_item, + &ei_usb_i1d3_echoed_command_code_mismatch); + } + } + + switch (transaction->command_code) { + case USB_I1D3_GET_INFO: { + const guint8 *information; + proto_tree_add_item_ret_string( + tree, hf_usb_i1d3_information, tvb, 2, -1, + ENC_ASCII | ENC_NA, wmem_packet_scope(), &information); + col_add_fstr( + pinfo->cinfo, COL_INFO, "Information: %s", information); + break; + } + case USB_I1D3_STATUS: { + guint32 status; + proto_item *status_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_status, tvb, 2, 3, ENC_BIG_ENDIAN, + &status); + const gchar *status_string = + ((status & 0xff00ff) != 0 || (status & 0x00ff00) >= 5) ? + "OK" : "Bad"; + proto_item_append_text(status_item, " [%s]", status_string); + col_add_fstr( + pinfo->cinfo, COL_INFO, "Status: 0x%06x (%s)", + status, status_string); + break; + } + case USB_I1D3_PRODNAME: { + const guint8 *prodname; + proto_tree_add_item_ret_string( + tree, hf_usb_i1d3_prodname, tvb, 2, -1, + ENC_ASCII | ENC_NA, wmem_packet_scope(), &prodname); + col_add_fstr(pinfo->cinfo, COL_INFO, "Product name: %s", prodname); + break; + } + case USB_I1D3_PRODTYPE: { + guint32 prodtype; + proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_prodtype, tvb, 3, 2, ENC_BIG_ENDIAN, + &prodtype); + col_add_fstr( + pinfo->cinfo, COL_INFO, "Product type: 0x%04x", + prodtype); + break; + } + case USB_I1D3_FIRMVER: { + const guint8 *firmver; + proto_tree_add_item_ret_string( + tree, hf_usb_i1d3_firmver, tvb, 2, -1, + ENC_ASCII | ENC_NA, wmem_packet_scope(), &firmver); + col_add_fstr( + pinfo->cinfo, COL_INFO, "Firmware version: %s", firmver); + break; + } + case USB_I1D3_FIRMDATE: { + const guint8 *firmdate; + proto_tree_add_item_ret_string( + tree, hf_usb_i1d3_firmdate, tvb, 2, -1, + ENC_ASCII | ENC_NA, wmem_packet_scope(), &firmdate); + col_add_fstr(pinfo->cinfo, COL_INFO, "Firmware date: %s", firmdate); + break; + } + case USB_I1D3_LOCKED: { + guint32 locked; + proto_item *locked_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_locked, tvb, 2, 2, ENC_BIG_ENDIAN, + &locked); + const gchar *locked_string = + ((locked & 0xff00) != 0 || (locked & 0x00ff) == 0) ? + "Unlocked" : "Locked"; + proto_item_append_text(locked_item, " [%s]", locked_string); + col_add_fstr( + pinfo->cinfo, COL_INFO, "Locked status: 0x%04x (%s)", + locked, locked_string); + break; + } + case USB_I1D3_MEASURE1: { + proto_item *edge_count_item = proto_tree_add_item( + tree, hf_usb_i1d3_measured_edge_count, tvb, 2, 12, ENC_NA); + proto_tree *edge_count_tree = proto_item_add_subtree( + edge_count_item, ett_usb_i1d3_requested_edge_count); + guint32 edge_count_red, edge_count_green, edge_count_blue; + proto_tree_add_item_ret_uint( + edge_count_tree, hf_usb_i1d3_measured_edge_count_red, tvb, + 2, 4, ENC_LITTLE_ENDIAN, &edge_count_red); + proto_tree_add_item_ret_uint( + edge_count_tree, hf_usb_i1d3_measured_edge_count_green, tvb, + 6, 4, ENC_LITTLE_ENDIAN, &edge_count_green); + proto_tree_add_item_ret_uint( + edge_count_tree, hf_usb_i1d3_measured_edge_count_blue, tvb, + 10, 4, ENC_LITTLE_ENDIAN, &edge_count_blue); + proto_item_append_text( + edge_count_item, ": R%u G%u B%u", + edge_count_red, edge_count_green, edge_count_blue); + col_add_fstr(pinfo->cinfo, COL_INFO, "Measured R%u G%u B%u edges", + edge_count_red, edge_count_green, edge_count_blue); + break; + } + case USB_I1D3_MEASURE2: { + proto_item *duration_item = proto_tree_add_item( + tree, hf_usb_i1d3_measured_duration, tvb, 2, 12, ENC_NA); + proto_tree *duration_tree = proto_item_add_subtree( + duration_item, ett_usb_i1d3_measured_duration); + guint32 duration_red, duration_green, duration_blue; + proto_item *duration_red_item = proto_tree_add_item_ret_uint( + duration_tree, hf_usb_i1d3_measured_duration_red, + tvb, 2, 4, ENC_LITTLE_ENDIAN, &duration_red); + double duration_red_seconds = + duration_red / USB_I1D3_CLOCK_FREQUENCY; + proto_item_append_text( + duration_red_item, + " [%.6f seconds]", duration_red_seconds); + proto_item *duration_green_item = proto_tree_add_item_ret_uint( + duration_tree, hf_usb_i1d3_measured_duration_green, + tvb, 6, 4, ENC_LITTLE_ENDIAN, &duration_green); + double duration_green_seconds = + duration_green / USB_I1D3_CLOCK_FREQUENCY; + proto_item_append_text( + duration_green_item, + " [%.6f seconds]", duration_green_seconds); + proto_item *duration_blue_item = proto_tree_add_item_ret_uint( + duration_tree, hf_usb_i1d3_measured_duration_blue, + tvb, 10, 4, ENC_LITTLE_ENDIAN, &duration_blue); + double duration_blue_seconds = + duration_blue / USB_I1D3_CLOCK_FREQUENCY; + proto_item_append_text( + duration_blue_item, + " [%.6f seconds]", duration_blue_seconds); + proto_item_append_text( + duration_item, ": R%.6fs G%.6fs B%.6fs", + duration_red_seconds, duration_green_seconds, + duration_blue_seconds); + col_add_fstr(pinfo->cinfo, COL_INFO, + "Measured R%.6fs G%.6fs B%.6fs", + duration_red_seconds, duration_green_seconds, + duration_blue_seconds); + break; + } + case USB_I1D3_READINTEE: { + proto_item *offset_item = proto_tree_add_uint( + tree, hf_usb_i1d3_readintee_offset, tvb, 0, 0, + transaction->offset); + PROTO_ITEM_SET_GENERATED(offset_item); + proto_item *length_item = proto_tree_add_uint( + tree, hf_usb_i1d3_readintee_length, tvb, 0, 0, + transaction->length); + PROTO_ITEM_SET_GENERATED(length_item); + proto_tree_add_item( + tree, hf_usb_i1d3_readintee_data, tvb, + 4, transaction->length, ENC_NA); + col_add_fstr( + pinfo->cinfo, COL_INFO, + "Internal EEPROM data (offset: %u, length: %u)", + transaction->offset, transaction->length); + break; + } + case USB_I1D3_READEXTEE: { + proto_item *offset_item = proto_tree_add_uint( + tree, hf_usb_i1d3_readextee_offset, tvb, 0, 0, + transaction->offset); + PROTO_ITEM_SET_GENERATED(offset_item); + proto_item *length_item = proto_tree_add_uint( + tree, hf_usb_i1d3_readextee_length, tvb, 0, 0, + transaction->length); + PROTO_ITEM_SET_GENERATED(length_item); + proto_tree_add_item( + tree, hf_usb_i1d3_readextee_data, tvb, + 5, transaction->length, ENC_NA); + col_add_fstr( + pinfo->cinfo, COL_INFO, + "External EEPROM data (offset: %u, length: %u)", + transaction->offset, transaction->length); + break; + } + case USB_I1D3_GET_DIFF: { + guint32 diffuser_position; + proto_item *diffuser_position_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_diffuser_position, tvb, + 1, 1, ENC_NA, &diffuser_position); + const char *diffuser_position_string = try_val_to_str( + diffuser_position, usb_i1d3_diffuser_position_strings); + if (!diffuser_position_string) { + expert_add_info( + pinfo, diffuser_position_item, + &ei_usb_i1d3_unknown_diffuser_position); + } + col_add_fstr( + pinfo->cinfo, COL_INFO, "Diffuser position: %s", + diffuser_position_string ? + diffuser_position_string : "unknown"); + break; + } + case USB_I1D3_LOCKCHAL: { + proto_tree_add_item( + tree, hf_usb_i1d3_challenge_encode_key, tvb, 2, 1, ENC_NA); + proto_tree_add_item( + tree, hf_usb_i1d3_challenge_decode_key, tvb, 3, 1, ENC_NA); + proto_tree_add_item( + tree, hf_usb_i1d3_challenge_data, tvb, 35, 8, ENC_NA); + break; + } + case USB_I1D3_LOCKRESP: { + guint32 unlock_result; + proto_item *unlock_result_item = proto_tree_add_item_ret_uint( + tree, hf_usb_i1d3_unlock_result, tvb, 2, 1, ENC_NA, + &unlock_result); + int unlock_successful = unlock_result == 0x77; + const gchar *unlock_result_string = unlock_successful ? + "Successfully unlocked" : "Failed to unlock"; + proto_item_append_text( + unlock_result_item, " [%s]", unlock_result_string); + if (!unlock_successful) { + expert_add_info( + pinfo, unlock_result_item, &ei_usb_i1d3_unlock_failed); + } + col_add_fstr(pinfo->cinfo, COL_INFO, "%s", unlock_result_string); + break; + } + } +} + +static int dissect_usb_i1d3( + tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + void *data _U_) +{ + if ((pinfo->p2p_dir == P2P_DIR_SENT && pinfo->destport == 0) || + (pinfo->p2p_dir == P2P_DIR_RECV && pinfo->srcport == 0)) { + // The device describes itself as HID class, even though the actual + // protocol doesn't seem to be based on HID at all. However that means + // the device will receive (and respond) to some basic HID requests, + // such as GET_DESCRIPTOR. These HID requests will go to endpoint 0, + // while actual communication takes place on endpoint 1. Therefore, if + // we get handed a packet going to/from endpoint 0, reject it and let + // the HID dissector handle it. + return 0; + } + + col_set_str(pinfo->cinfo, COL_PROTOCOL, "i1d3"); + + proto_item *usb_i1d3_item = proto_tree_add_item( + tree, proto_usb_i1d3, tvb, 0, -1, ENC_NA); + proto_tree *usb_i1d3_tree = proto_item_add_subtree( + usb_i1d3_item, ett_usb_i1d3); + + // All i1d3 packets seen in the while are fixed-length, with padding added + // as necessary. It is not clear if using a different length is valid or + // not. + if (tvb_reported_length(tvb) != USB_I1D3_PACKET_LENGTH) { + expert_add_info(pinfo, usb_i1d3_item, &ei_usb_i1d3_unusual_length); + } + + col_clear(pinfo->cinfo, COL_INFO); + usb_i1d3_conversation_t *conversation = usb_i1d3_get_conversation(pinfo); + if (pinfo->p2p_dir == P2P_DIR_SENT) { + dissect_usb_i1d3_command(tvb, pinfo, conversation, usb_i1d3_tree); + } else if (pinfo->p2p_dir == P2P_DIR_RECV) { + dissect_usb_i1d3_response(tvb, pinfo, conversation, usb_i1d3_tree); + } else { + DISSECTOR_ASSERT(0); + } + conversation->previous_packet = pinfo->num; + + return tvb_captured_length(tvb); +} + +void proto_register_usb_i1d3(void) +{ + proto_usb_i1d3 = proto_register_protocol( + "X-Rite i1 Display Pro (and derivatives) USB protocol", + "X-Rite i1 Display Pro", "i1d3"); + + static gint *ett[] = { + &ett_usb_i1d3, + &ett_usb_i1d3_measured_duration, + &ett_usb_i1d3_requested_edge_count, + }; + static hf_register_info hf[] = { + { &hf_usb_i1d3_challenge_response, + { "Challenge response", "i1d3.challenge_response", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_challenge_data, + { "Challenge data", "i1d3.challenge_data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_challenge_decode_key, + { "Challenge decode XOR value", "i1d3.challenge_decode_key", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_challenge_encode_key, + { "Challenge encode XOR value", "i1d3.challenge_encode_key", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_command_code, + { "Command code", "i1d3.command.code", FT_UINT16, BASE_HEX, + VALS(usb_i1d3_command_code_strings), 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_diffuser_position, + { "Diffuser position", "i1d3.diffuser_position", FT_UINT8, BASE_DEC, + VALS(usb_i1d3_diffuser_position_strings), 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_echoed_command_code, + { "Echoed command code", "i1d3.echoed_command.code", FT_UINT8, + BASE_HEX, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_firmdate, + { "Firmware date", "i1d3.firmdate", FT_STRINGZ, BASE_NONE, + NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_firmver, + { "Firmware version", "i1d3.firmver", FT_STRINGZ, BASE_NONE, + NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_information, + { "Information", "i1d3.information", FT_STRINGZ, BASE_NONE, + NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_measured_duration, + { "Measured duration", "i1d3.measured_duration", + FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_measured_duration_red, + { "Red channel", + "i1d3.measured_duration.red", FT_UINT32, + BASE_DEC|BASE_UNIT_STRING, &units_cycle_cycles, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_measured_duration_green, + { "Green channel", + "i1d3.measured_duration.green", FT_UINT32, + BASE_DEC|BASE_UNIT_STRING, &units_cycle_cycles, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_measured_duration_blue, + { "Blue channel", + "i1d3.measured_duration.blue", FT_UINT32, + BASE_DEC|BASE_UNIT_STRING, &units_cycle_cycles, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_measured_edge_count, + { "Measured edge count", "i1d3.measured_edge_count", + FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_measured_edge_count_red, + { "Red channel", + "i1d3.measured_edge_count.red", FT_UINT32, + BASE_DEC|BASE_UNIT_STRING, &units_edge_edges, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_measured_edge_count_green, + { "Green channel", + "i1d3.measured_edge_count.green", FT_UINT32, + BASE_DEC|BASE_UNIT_STRING, &units_edge_edges, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_measured_edge_count_blue, + { "Blue channel", + "i1d3.measured_edge_count.blue", FT_UINT32, + BASE_DEC|BASE_UNIT_STRING, &units_edge_edges, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_led_mode, + { "LED mode", "i1d3.led_mode", FT_UINT8, BASE_DEC, + VALS(usb_i1d3_led_mode_strings), 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_led_offtime, + { "LED off time", "i1d3.led_offtime", FT_UINT8, BASE_DEC, + NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_led_ontime, + { "LED on time", "i1d3.led_ontime", FT_UINT8, BASE_DEC, + NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_led_pulse_count, + { "LED pulse count", "i1d3.led_pulse_count", FT_UINT8, + BASE_DEC|BASE_UNIT_STRING, &units_pulse_pulses, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_locked, + { "Lock status", "i1d3.locked", + FT_UINT16, BASE_HEX, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_prodname, + { "Product name", "i1d3.prodname", FT_STRINGZ, BASE_NONE, + NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_prodtype, + { "Product type", "i1d3.prodtype", FT_UINT16, BASE_HEX, + NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_request_in, + { "Request in frame", "i1d3.request_in", + FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_REQUEST), + 0, NULL, HFILL } + }, + { &hf_usb_i1d3_requested_edge_count, + { "Requested edge count", "i1d3.requested_edge_count", + FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_requested_edge_count_red, + { "Red channel", + "i1d3.requested_edge_count.red", FT_UINT16, + BASE_DEC|BASE_UNIT_STRING, &units_edge_edges, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_requested_edge_count_green, + { "Green channel", + "i1d3.requested_edge_count.green", FT_UINT16, + BASE_DEC|BASE_UNIT_STRING, &units_edge_edges, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_requested_edge_count_blue, + { "Blue channel", + "i1d3.requested_edge_count.blue", FT_UINT16, + BASE_DEC|BASE_UNIT_STRING, &units_edge_edges, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_requested_integration_time, + { "Requested integration time", + "i1d3.requested_integration_time", FT_UINT32, + BASE_DEC|BASE_UNIT_STRING, &units_cycle_cycles, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_response_code, + { "Response code", + "i1d3.response_code", FT_UINT8, BASE_HEX, + NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_response_in, + { "Response in frame", "i1d3.response_in", + FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_RESPONSE), + 0, NULL, HFILL } + }, + { &hf_usb_i1d3_readintee_data, + { "Internal EEPROM data", "i1d3.readintee_data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_readintee_offset, + { "Internal EEPROM read offset", "i1d3.readintee_offset", + FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_readintee_length, + { "Internal EEPROM read length", "i1d3.readintee_length", + FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_readextee_data, + { "External EEPROM data", "i1d3.readextee_data", + FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_readextee_offset, + { "External EEPROM read offset", "i1d3.readextee_offset", + FT_UINT16, BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_readextee_length, + { "External EEPROM read length", "i1d3.readextee_length", + FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, + 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_status, + { "Status", "i1d3.status", + FT_UINT24, BASE_HEX, NULL, 0, NULL, HFILL }, + }, + { &hf_usb_i1d3_unlock_result, + { "Unlock result", "i1d3.unlock_result", + FT_UINT8, BASE_HEX, NULL, 0, NULL, HFILL }, + }, + }; + static ei_register_info ei[] = { + { &ei_usb_i1d3_echoed_command_code_mismatch, + { "i1d3.echoed_command_code_mismatch", PI_PROTOCOL, PI_ERROR, + "Echoed command code does not match request", EXPFILL } + }, + { &ei_usb_i1d3_error, + { "i1d3.error", PI_RESPONSE_CODE, PI_NOTE, + "Error response code", EXPFILL } + }, + { &ei_usb_i1d3_unexpected_response, + { "i1d3.unexpected_response", PI_SEQUENCE, PI_WARN, + "Could not match response to a request", EXPFILL } + }, + { &ei_usb_i1d3_unknown_command, + { "i1d3.unknown_command", PI_MALFORMED, PI_ERROR, + "Unknown command code", EXPFILL } + }, + { &ei_usb_i1d3_unknown_diffuser_position, + { "i1d3.unknown_diffuser_position", PI_MALFORMED, PI_ERROR, + "Unknown diffuser position code", EXPFILL } + }, + { &ei_usb_i1d3_unlock_failed, + { "i1d3.unlock_failed", PI_RESPONSE_CODE, PI_NOTE, + "Failed to unlock device", EXPFILL } + }, + { &ei_usb_i1d3_unusual_length, + { "i1d3.unusual_length", PI_PROTOCOL, PI_WARN, + "Packet has unusual length", EXPFILL } + }, + }; + + proto_register_subtree_array(ett, array_length(ett)); + proto_register_field_array(proto_usb_i1d3, hf, array_length(hf)); + expert_module_t *expert_usb_i1d3 = expert_register_protocol( + proto_usb_i1d3); + expert_register_field_array(expert_usb_i1d3, ei, array_length(ei)); +} + +void proto_reg_handoff_usb_i1d3(void) { + dissector_handle_t usb_i1d3_dissector = create_dissector_handle( + dissect_usb_i1d3, proto_usb_i1d3); + dissector_add_for_decode_as("usb.device", usb_i1d3_dissector); + dissector_add_uint("usb.product", 0x7655020, usb_i1d3_dissector); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ -- cgit v1.2.1