forked from osmocom/wireshark
![Etienne Dechamps](/assets/img/avatar_default.png)
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 <mmann78@netscape.net> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Michael Mann <mmann78@netscape.net>
993 lines
42 KiB
C
993 lines
42 KiB
C
/* packet-usb-i1d3.c
|
|
* Dissects the X-Rite i1 Display Pro (and derivatives) USB protocol
|
|
* Copyright 2016, Etienne Dechamps <etienne@edechamps.fr>
|
|
*
|
|
* Wireshark - Network traffic analyzer
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
* 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 <config.h>
|
|
#include <epan/conversation.h>
|
|
#include <epan/packet.h>
|
|
#include <epan/expert.h>
|
|
|
|
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:
|
|
*/
|