/* packet-usb-video.c
 *
 * Forked from packet-usb-masstorage.c 35224 2010-12-20 05:35:29Z guy
 * which was authored by Ronnie Sahlberg (2006)
 *
 * usb video dissector
 * Steven J. Magnani 2013
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

#include <epan/packet.h>
#include <epan/expert.h>
#include "packet-usb.h"

void proto_register_usb_vid(void);
void proto_reg_handoff_usb_vid(void);

/* References are to sections in USB Video Class specifications -
 * specifically V1.5, but versions have tended to keep
 * the same numbering (as of this writing).
 *
 * http://www.usb.org/developers/devclass_docs/USB_Video_Class_1_5.zip
 */

/* Table 2-1. Interrupt originators */
#define INT_VIDEOCONTROL               1
#define INT_VIDEOSTREAMING             2

#define INT_ORIGINATOR_MASK            0xF

/* Table 2-2. Video Control Status Packet bAttribute */
#define CONTROL_CHANGE_VALUE           0x00
#define CONTROL_CHANGE_INFO            0x01
#define CONTROL_CHANGE_FAILURE         0x02
#define CONTROL_CHANGE_MIN             0x03   /* UVC 1.5+ */
#define CONTROL_CHANGE_MAX             0x04   /* UVC 1.5+ */


/* A.2 Video Interface Subclass Codes */
#define SC_UNDEFINED                   0
#define SC_VIDEOCONTROL                1
#define SC_VIDEOSTREAMING              2
#define SC_VIDEO_INTERFACE_COLLECTION  3

/* A.4. Video Class-Specific Descriptor Types */
#define CS_INTERFACE       0x24
#define CS_ENDPOINT        0x25

/* A.5 Video Class-Specific VC Interface Descriptor Subtypes */
#define VC_HEADER           1
#define VC_INPUT_TERMINAL   2
#define VC_OUTPUT_TERMINAL  3
#define VC_SELECTOR_UNIT    4
#define VC_PROCESSING_UNIT  5
#define VC_EXTENSION_UNIT   6
#define VC_ENCODING_UNIT    7

/* A.6 Video Class-Specific VS Interface Descriptor Subtypes */
#define VS_UNDEFINED             0x00
#define VS_INPUT_HEADER          0x01
#define VS_OUTPUT_HEADER         0x02
#define VS_STILL_IMAGE_FRAME     0x03
#define VS_FORMAT_UNCOMPRESSED   0x04
#define VS_FRAME_UNCOMPRESSED    0x05
#define VS_FORMAT_MJPEG          0x06
#define VS_FRAME_MJPEG           0x07
#define VS_FORMAT_MPEG1          0x08     /* Pre-UVC 1.1 */
#define VS_FORMAT_MPEG2PS        0x09     /* Pre-UVC 1.1 */
#define VS_FORMAT_MPEG2TS        0x0A
#define VS_FORMAT_MPEG4SL        0x0B     /* Pre-UVC 1.1 */
#define VS_FORMAT_DV             0x0C
#define VS_COLORFORMAT           0x0D
#define VS_FORMAT_VENDOR         0x0E     /* Pre-UVC 1.1 */
#define VS_FRAME_VENDOR          0x0F     /* Pre-UVC 1.1 */
#define VS_FORMAT_FRAME_BASED    0x10
#define VS_FRAME_FRAME_BASED     0x11
#define VS_FORMAT_STREAM_BASED   0x12
#define VS_FORMAT_H264           0x13       /* UVC 1.5 */
#define VS_FRAME_H264            0x14       /* UVC 1.5 */
#define VS_FORMAT_H264_SIMULCAST 0x15       /* UVC 1.5 */
#define VS_FORMAT_VP8            0x16       /* UVC 1.5 */
#define VS_FRAME_VP8             0x17       /* UVC 1.5 */
#define VS_FORMAT_VP8_SIMULCAST  0x18       /* UVC 1.5 */

/* A.7 Video Class-Specific Endpoint Descriptor Subtypes */
#define EP_INTERRUPT           0x03

/* A.9.1 Video Control Interface Control Selectors */
#define VC_CONTROL_UNDEFINED                      0x00
#define VC_VIDEO_POWER_MODE_CONTROL               0x01
#define VC_REQUEST_ERROR_CODE_CONTROL             0x02
#define VC_REQUEST_INDICATE_HOST_CLOCK_CONTROL    0x03  /* Pre-UVC 1.1 */

/* A.9.3 Selector Unit Control Selectors */
#define SU_CONTROL_UNDEFINED                      0x00
#define SU_INPUT_SELECT_CONTROL                   0x01

/* A.9.4 Camera Terminal Control Selectors */
#define CT_CONTROL_UNDEFINED                      0x00
#define CT_SCANNING_MODE_CONTROL                  0x01
#define CT_AE_MODE_CONTROL                        0x02
#define CT_AE_PRIORITY_CONTROL                    0x03
#define CT_EXPOSURE_TIME_ABSOLUTE_CONTROL         0x04
#define CT_EXPOSURE_TIME_RELATIVE_CONTROL         0x05
#define CT_FOCUS_ABSOLUTE_CONTROL                 0x06
#define CT_FOCUS_RELATIVE_CONTROL                 0x07
#define CT_FOCUS_AUTO_CONTROL                     0x08
#define CT_IRIS_ABSOLUTE_CONTROL                  0x09
#define CT_IRIS_RELATIVE_CONTROL                  0x0A
#define CT_ZOOM_ABSOLUTE_CONTROL                  0x0B
#define CT_ZOOM_RELATIVE_CONTROL                  0x0C
#define CT_PANTILT_ABSOLUTE_CONTROL               0x0D
#define CT_PANTILT_RELATIVE_CONTROL               0x0E
#define CT_ROLL_ABSOLUTE_CONTROL                  0x0F
#define CT_ROLL_RELATIVE_CONTROL                  0x10
#define CT_PRIVACY_CONTROL                        0x11
#define CT_FOCUS_SIMPLE_CONTROL                   0x12  /* UVC 1.5 */
#define CT_WINDOW_CONTROL                         0x13  /* UVC 1.5 */
#define CT_REGION_OF_INTEREST_CONTROL             0x14  /* UVC 1.5 */

/* A.9.5 Processing Unit Control Selectors */
#define PU_CONTROL_UNDEFINED                      0x00
#define PU_BACKLIGHT_COMPENSATION_CONTROL         0x01
#define PU_BRIGHTNESS_CONTROL                     0x02
#define PU_CONTRAST_CONTROL                       0x03
#define PU_GAIN_CONTROL                           0x04
#define PU_POWER_LINE_FREQUENCY_CONTROL           0x05
#define PU_HUE_CONTROL                            0x06
#define PU_SATURATION_CONTROL                     0x07
#define PU_SHARPNESS_CONTROL                      0x08
#define PU_GAMMA_CONTROL                          0x09
#define PU_WHITE_BALANCE_TEMPERATURE_CONTROL      0x0A
#define PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL 0x0B
#define PU_WHITE_BALANCE_COMPONENT_CONTROL        0x0C
#define PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL   0x0D
#define PU_DIGITAL_MULTIPLIER_CONTROL             0x0E
#define PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL       0x0F
#define PU_HUE_AUTO_CONTROL                       0x10
#define PU_ANALOG_VIDEO_STANDARD_CONTROL          0x11
#define PU_ANALOG_LOCK_STATUS_CONTROL             0x12
#define PU_CONTRAST_AUTO_CONTROL                  0x13

/* A.9.7 VideoStreaming Interface Control Selectors */
#define VS_CONTROL_UNDEFINED                      0x00
#define VS_PROBE_CONTROL                          0x01
#define VS_COMMIT_CONTROL                         0x02
#define VS_STILL_PROBE_CONTROL                    0x03
#define VS_STILL_COMMIT_CONTROL                   0x04
#define VS_STILL_IMAGE_TRIGGER_CONTROL            0x05
#define VS_STREAM_ERROR_CODE_CONTROL              0x06
#define VS_GENERATE_KEY_FRAME_CONTROL             0x07
#define VS_UPDATE_FRAME_SEGMENT_CONTROL           0x08
#define VS_SYNCH_DELAY_CONTROL                    0x09

/* Appendix B Terminal Types */
#define TT_VENDOR_SPECIFIC          0x100
#define TT_STREAMING                0x101
#define ITT_VENDOR_SPECIFIC         0x200
#define ITT_CAMERA                  0x201
#define ITT_MEDIA_TRANSPORT_INPUT   0x202
#define OTT_VENDOR_SPECIFIC         0x300
#define OTT_DISPLAY                 0x301
#define OTT_MEDIA_TRANSPORT_OUTPUT  0x302
#define EXTERNAL_VENDOR_SPECIFIC    0x400
#define COMPOSITE_CONNECTOR         0x401
#define SVIDEO_CONNECTOR            0x402
#define COMPONENT_CONNECTOR         0x403

/* Table 2-2 Status Packet Format (VideoControl Interface as the Originator) */
#define CONTROL_INTERRUPT_EVENT_CONTROL_CHANGE  0

/* Table 4-7 Request Error Code Control bRequestErrorCode */
#define UVC_ERROR_NONE              0
#define UVC_ERROR_NOT_READY         1
#define UVC_ERROR_WRONG_STATE       2
#define UVC_ERROR_POWER             3
#define UVC_ERROR_OUT_OF_RANGE      4
#define UVC_ERROR_INVALID_UNIT      5
#define UVC_ERROR_INVALID_CONTROL   6
#define UVC_ERROR_INVALID_REQUEST   7
#define UVC_ERROR_INVALID_VALUE     8
#define UVC_ERROR_UNKNOWN           255

/* A.8 Video Class-Specific Request Codes */
#define USB_SETUP_SET_CUR           0x01
#define USB_SETUP_SET_CUR_ALL       0x11    /* UVC 1.5 */
#define USB_SETUP_GET_CUR           0x81
#define USB_SETUP_GET_MIN           0x82
#define USB_SETUP_GET_MAX           0x83
#define USB_SETUP_GET_RES           0x84
#define USB_SETUP_GET_LEN           0x85
#define USB_SETUP_GET_INFO          0x86
#define USB_SETUP_GET_DEF           0x87
#define USB_SETUP_GET_CUR_ALL       0x91    /* UVC 1.5 */
#define USB_SETUP_GET_MIN_ALL       0x92    /* UVC 1.5 */
#define USB_SETUP_GET_MAX_ALL       0x93    /* UVC 1.5 */
#define USB_SETUP_GET_RES_ALL       0x94    /* UVC 1.5 */
#define USB_SETUP_GET_DEF_ALL       0x97    /* UVC 1.5 */

/* dissector handles */
static dissector_handle_t usb_vid_control_handle;
static dissector_handle_t usb_vid_descriptor_handle;
static dissector_handle_t usb_vid_interrupt_handle;

/* protocols and header fields */
static int proto_usb_vid = -1;

static int hf_usb_vid_control_entity    = -1;
static int hf_usb_vid_control_interface = -1;
static int hf_usb_vid_control_selector  = -1;
static int hf_usb_vid_epdesc_subtype = -1;
static int hf_usb_vid_epdesc_max_transfer_sz = -1;
static int hf_usb_vid_control_ifdesc_subtype = -1;
static int hf_usb_vid_control_ifdesc_terminal_id = -1;
static int hf_usb_vid_control_ifdesc_terminal_type = -1;
static int hf_usb_vid_control_ifdesc_assoc_terminal = -1;
static int hf_usb_vid_streaming_ifdesc_subtype = -1;
static int hf_usb_vid_streaming_ifdesc_bNumFormats = -1;
static int hf_usb_vid_control_ifdesc_unit_id = -1;
static int hf_usb_vid_request = -1;
static int hf_usb_vid_length = -1;
static int hf_usb_vid_interrupt_bStatusType = -1;
static int hf_usb_vid_interrupt_bOriginator = -1;
static int hf_usb_vid_interrupt_bAttribute = -1;
static int hf_usb_vid_control_interrupt_bEvent = -1;
static int hf_usb_vid_control_ifdesc_bcdUVC = -1;
static int hf_usb_vid_ifdesc_wTotalLength = -1;
static int hf_usb_vid_control_ifdesc_dwClockFrequency = -1;
static int hf_usb_vid_control_ifdesc_bInCollection = -1;
static int hf_usb_vid_control_ifdesc_baInterfaceNr = -1;
static int hf_usb_vid_control_ifdesc_iTerminal = -1;
static int hf_usb_vid_control_ifdesc_src_id = -1;
static int hf_usb_vid_cam_objective_focal_len_min = -1;
static int hf_usb_vid_cam_objective_focal_len_max = -1;
static int hf_usb_vid_cam_ocular_focal_len = -1;
static int hf_usb_vid_bControlSize = -1;
static int hf_usb_vid_bmControl = -1;
static int hf_usb_vid_bmControl_bytes = -1;
static int hf_usb_vid_control_default = -1;
static int hf_usb_vid_control_min = -1;
static int hf_usb_vid_control_max = -1;
static int hf_usb_vid_control_res = -1;
static int hf_usb_vid_control_cur = -1;
static int hf_usb_vid_control_info = -1;
static int hf_usb_vid_control_info_D[7]   = { -1, -1, -1, -1, -1, -1, -1 };
static int hf_usb_vid_control_length = -1;
static int hf_usb_vid_cam_control_D[22]   = { -1, -1, -1, -1, -1, -1, -1, -1,
                                              -1, -1, -1, -1, -1, -1, -1, -1,
                                              -1, -1, -1, -1, -1, -1 };
static int hf_usb_vid_proc_control_D[19]  = { -1, -1, -1, -1, -1, -1, -1, -1,
                                              -1, -1, -1, -1, -1, -1, -1, -1,
                                              -1, -1, -1 };
static int hf_usb_vid_proc_standards_D[6] = { -1, -1, -1, -1, -1, -1 };
static int hf_usb_vid_exten_guid = -1;
static int hf_usb_vid_exten_num_controls = -1;
static int hf_usb_vid_num_inputs = -1;
static int hf_usb_vid_sources = -1;
static int hf_usb_vid_streaming_bmInfo = -1;
static int hf_usb_vid_streaming_info_D[1] = { -1 };
static int hf_usb_vid_streaming_terminal_link = -1;
static int hf_usb_vid_streaming_still_capture_method = -1;
static int hf_usb_vid_streaming_trigger_support = -1;
static int hf_usb_vid_streaming_trigger_usage = -1;
static int hf_usb_vid_streaming_control_D[6] = { -1, -1, -1, -1, -1, -1 };
static int hf_usb_vid_format_index = -1;
static int hf_usb_vid_format_num_frame_descriptors = -1;
static int hf_usb_vid_format_guid = -1;
static int hf_usb_vid_format_bits_per_pixel = -1;
static int hf_usb_vid_default_frame_index = -1;
static int hf_usb_vid_aspect_ratio_x = -1;
static int hf_usb_vid_aspect_ratio_y = -1;
static int hf_usb_vid_interlace_flags = -1;
static int hf_usb_vid_is_interlaced = -1;
static int hf_usb_vid_interlaced_fields = -1;
static int hf_usb_vid_field_1_first = -1;
static int hf_usb_vid_field_pattern = -1;
static int hf_usb_vid_copy_protect = -1;
static int hf_usb_vid_variable_size = -1;
static int hf_usb_vid_frame_index = -1;
static int hf_usb_vid_frame_capabilities = -1;
static int hf_usb_vid_frame_stills_supported = -1;
static int hf_usb_vid_frame_fixed_frame_rate = -1;
static int hf_usb_vid_frame_width = -1;
static int hf_usb_vid_frame_height = -1;
static int hf_usb_vid_frame_min_bit_rate = -1;
static int hf_usb_vid_frame_max_bit_rate = -1;
static int hf_usb_vid_frame_max_frame_sz = -1;
static int hf_usb_vid_frame_default_interval = -1;
static int hf_usb_vid_frame_bytes_per_line = -1;
static int hf_usb_vid_mjpeg_flags = -1;
static int hf_usb_vid_mjpeg_fixed_samples = -1;
static int hf_usb_vid_probe_hint = -1;
static int hf_usb_vid_probe_hint_D[5] = { -1, -1, -1, -1, -1 };
static int hf_usb_vid_frame_interval = -1;
static int hf_usb_vid_probe_key_frame_rate = -1;
static int hf_usb_vid_probe_p_frame_rate = -1;
static int hf_usb_vid_probe_comp_quality = -1;
static int hf_usb_vid_probe_comp_window = -1;
static int hf_usb_vid_probe_delay = -1;
static int hf_usb_vid_probe_max_frame_sz = -1;
static int hf_usb_vid_probe_max_payload_sz = -1;
static int hf_usb_vid_probe_clock_freq = -1;
static int hf_usb_vid_probe_framing = -1;
static int hf_usb_vid_probe_framing_D[2] = { -1, -1 };
static int hf_usb_vid_probe_preferred_ver = -1;
static int hf_usb_vid_probe_min_ver = -1;
static int hf_usb_vid_probe_max_ver = -1;
static int hf_usb_vid_frame_interval_type = -1;
static int hf_usb_vid_frame_min_interval = -1;
static int hf_usb_vid_frame_max_interval = -1;
static int hf_usb_vid_frame_step_interval = -1;
static int hf_usb_vid_color_primaries = -1;
static int hf_usb_vid_transfer_characteristics = -1;
static int hf_usb_vid_matrix_coefficients = -1;
static int hf_usb_vid_max_multiplier = -1;
static int hf_usb_vid_iProcessing = -1;
static int hf_usb_vid_iExtension = -1;
static int hf_usb_vid_iSelector = -1;
static int hf_usb_vid_proc_standards = -1;
static int hf_usb_vid_request_error = -1;
static int hf_usb_vid_descriptor_data = -1;
static int hf_usb_vid_control_data = -1;
static int hf_usb_vid_control_value = -1;
static int hf_usb_vid_value_data = -1;


/* Subtrees */
static gint ett_usb_vid = -1;
static gint ett_descriptor_video_endpoint = -1;
static gint ett_descriptor_video_control = -1;
static gint ett_descriptor_video_streaming = -1;
static gint ett_camera_controls = -1;
static gint ett_processing_controls = -1;
static gint ett_streaming_controls = -1;
static gint ett_streaming_info = -1;
static gint ett_interlace_flags = -1;
static gint ett_frame_capability_flags = -1;
static gint ett_mjpeg_flags = -1;
static gint ett_video_probe = -1;
static gint ett_probe_hint = -1;
static gint ett_probe_framing = -1;
static gint ett_video_standards = -1;
static gint ett_control_capabilities = -1;

static expert_field ei_usb_vid_subtype_unknown = EI_INIT;
static expert_field ei_usb_vid_bitmask_len = EI_INIT;

/* Lookup tables */
static const value_string vc_ep_descriptor_subtypes[] = {
    { EP_INTERRUPT, "Interrupt" },
    { 0, NULL }
};

static const value_string vid_descriptor_type_vals[] = {
    {CS_INTERFACE, "video class interface"},
    {CS_ENDPOINT, "video class endpoint"},
    {0,NULL}
};
static value_string_ext vid_descriptor_type_vals_ext =
    VALUE_STRING_EXT_INIT(vid_descriptor_type_vals);

static const value_string vc_if_descriptor_subtypes[] = {
    { VC_HEADER,              "Header" },
    { VC_INPUT_TERMINAL,      "Input Terminal" },
    { VC_OUTPUT_TERMINAL,     "Output Terminal" },
    { VC_SELECTOR_UNIT,       "Selector Unit" },
    { VC_PROCESSING_UNIT,     "Processing Unit" },
    { VC_EXTENSION_UNIT,      "Extension Unit" },
    { VC_ENCODING_UNIT,       "Encoding Unit" },
    { 0, NULL }
};
static value_string_ext vc_if_descriptor_subtypes_ext =
    VALUE_STRING_EXT_INIT(vc_if_descriptor_subtypes);

static const value_string cs_control_interface[] = {
    { VC_CONTROL_UNDEFINED,          "Undefined" },
    { VC_VIDEO_POWER_MODE_CONTROL,   "Video Power Mode" },
    { VC_REQUEST_ERROR_CODE_CONTROL, "Request Error Code" },
    { VC_REQUEST_INDICATE_HOST_CLOCK_CONTROL, "Request Indicate Host Clock" },
    { 0, NULL }
};
static value_string_ext cs_control_interface_ext =
    VALUE_STRING_EXT_INIT(cs_control_interface);

static const value_string cs_streaming_interface[] = {
    { VS_CONTROL_UNDEFINED,            "Undefined" },
    { VS_PROBE_CONTROL,                "Probe" },
    { VS_COMMIT_CONTROL,               "Commit" },
    { VS_STILL_PROBE_CONTROL,          "Still Probe" },
    { VS_STILL_COMMIT_CONTROL,         "Still Commit" },
    { VS_STILL_IMAGE_TRIGGER_CONTROL,  "Still Image Trigger" },
    { VS_STREAM_ERROR_CODE_CONTROL,    "Stream Error Code" },
    { VS_GENERATE_KEY_FRAME_CONTROL,   "Generate Key Frame" },
    { VS_UPDATE_FRAME_SEGMENT_CONTROL, "Update Frame Segment" },
    { VS_SYNCH_DELAY_CONTROL,          "Synch Delay" },
    { 0, NULL }
};
static value_string_ext cs_streaming_interface_ext =
    VALUE_STRING_EXT_INIT(cs_streaming_interface);

static const value_string cs_selector_unit[] = {
    { SU_CONTROL_UNDEFINED,              "Undefined" },
    { SU_INPUT_SELECT_CONTROL,           "Input Select" },
    { 0, NULL }
};
static value_string_ext cs_selector_unit_ext =
    VALUE_STRING_EXT_INIT(cs_selector_unit);

static const value_string cs_camera_terminal[] = {
    { CT_CONTROL_UNDEFINED,              "Undefined" },
    { CT_SCANNING_MODE_CONTROL,          "Scanning Mode" },
    { CT_AE_MODE_CONTROL,                "Auto-Exposure Mode" },
    { CT_AE_PRIORITY_CONTROL,            "Auto-Exposure Priority" },
    { CT_EXPOSURE_TIME_ABSOLUTE_CONTROL, "Exposure Time (Absolute)" },
    { CT_EXPOSURE_TIME_RELATIVE_CONTROL, "Exposure Time (Relative)" },
    { CT_FOCUS_ABSOLUTE_CONTROL,         "Focus (Absolute)" },
    { CT_FOCUS_RELATIVE_CONTROL,         "Focus (Relative)" },
    { CT_FOCUS_AUTO_CONTROL,             "Focus, Auto" },
    { CT_IRIS_ABSOLUTE_CONTROL,          "Iris (Absolute)" },
    { CT_IRIS_RELATIVE_CONTROL,          "Iris (Relative)" },
    { CT_ZOOM_ABSOLUTE_CONTROL,          "Zoom (Absolute)" },
    { CT_ZOOM_RELATIVE_CONTROL,          "Zoom (Relative)" },
    { CT_PANTILT_ABSOLUTE_CONTROL,       "PanTilt (Absolute)" },
    { CT_PANTILT_RELATIVE_CONTROL,       "PanTilt (Relative)" },
    { CT_ROLL_ABSOLUTE_CONTROL,          "Roll (Absolute)" },
    { CT_ROLL_RELATIVE_CONTROL,          "Roll (Relative)" },
    { CT_PRIVACY_CONTROL,                "Privacy" },
    { CT_FOCUS_SIMPLE_CONTROL,           "Focus (Simple)" },
    { CT_WINDOW_CONTROL,                 "Window" },
    { CT_REGION_OF_INTEREST_CONTROL,     "Region of Interest" },
    { 0, NULL }
};
static value_string_ext cs_camera_terminal_ext =
    VALUE_STRING_EXT_INIT(cs_camera_terminal);

static const value_string cs_processing_unit[] = {
    { PU_CONTROL_UNDEFINED,                     "Undefined" },
    { PU_BACKLIGHT_COMPENSATION_CONTROL,        "Backlight Compensation" },
    { PU_BRIGHTNESS_CONTROL,                    "Brightness" },
    { PU_CONTRAST_CONTROL,                      "Contrast" },
    { PU_GAIN_CONTROL,                          "Gain" },
    { PU_POWER_LINE_FREQUENCY_CONTROL,          "Power Line Frequency" },
    { PU_HUE_CONTROL,                           "Hue" },
    { PU_SATURATION_CONTROL,                    "Saturation" },
    { PU_SHARPNESS_CONTROL,                     "Sharpness" },
    { PU_GAMMA_CONTROL,                         "Gamma" },
    { PU_WHITE_BALANCE_TEMPERATURE_CONTROL,     "White Balance Temperature" },
    { PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL,"White Balance Temperature Auto" },
    { PU_WHITE_BALANCE_COMPONENT_CONTROL,       "White Balance Component" },
    { PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL,  "White Balance Component Auto" },
    { PU_DIGITAL_MULTIPLIER_CONTROL,            "Digital Multiplier" },
    { PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL,      "Digital Multiplier Limit" },
    { PU_HUE_AUTO_CONTROL,                      "Hue Auto" },
    { PU_ANALOG_VIDEO_STANDARD_CONTROL,         "Video Standard" },
    { PU_ANALOG_LOCK_STATUS_CONTROL,            "Analog Lock Status" },
    { PU_CONTRAST_AUTO_CONTROL,                 "Contrast Auto" },
    { 0, NULL }
};
static value_string_ext cs_processing_unit_ext =
    VALUE_STRING_EXT_INIT(cs_processing_unit);

static const value_string vc_terminal_types[] = {
    { TT_VENDOR_SPECIFIC,         "Vendor Specific", },
    { TT_STREAMING,               "Streaming" },
    { ITT_VENDOR_SPECIFIC,        "Vendor Specific Input" },
    { ITT_CAMERA,                 "Camera Input" },
    { ITT_MEDIA_TRANSPORT_INPUT,  "Media Transport Input" },
    { OTT_VENDOR_SPECIFIC,        "Vendor Specific Output" },
    { OTT_DISPLAY,                "Display Output" },
    { OTT_MEDIA_TRANSPORT_OUTPUT, "Media Transport Output" },
    { EXTERNAL_VENDOR_SPECIFIC,   "Vendor Specific External" },
    { COMPOSITE_CONNECTOR,        "Composite Connector" },
    { SVIDEO_CONNECTOR,           "SVideo Connector" },
    { COMPONENT_CONNECTOR,        "Component Connector" },
    { 0, NULL }
};
static value_string_ext vc_terminal_types_ext =
    VALUE_STRING_EXT_INIT(vc_terminal_types);

static const value_string vs_if_descriptor_subtypes[] = {
    { VS_UNDEFINED,             "Undefined" },
    { VS_INPUT_HEADER,          "Input Header" },
    { VS_OUTPUT_HEADER,         "Output Header" },
    { VS_STILL_IMAGE_FRAME,     "Still Image Frame" },
    { VS_FORMAT_UNCOMPRESSED,   "Format Uncompressed" },
    { VS_FRAME_UNCOMPRESSED,    "Frame Uncompressed" },
    { VS_FORMAT_MJPEG,          "Format MJPEG" },
    { VS_FRAME_MJPEG,           "Frame MJPEG" },
    { VS_FORMAT_MPEG1,          "Format MPEG1" },
    { VS_FORMAT_MPEG2PS,        "Format MPEG2-PS" },
    { VS_FORMAT_MPEG2TS,        "Format MPEG2-TS" },
    { VS_FORMAT_MPEG4SL,        "Format MPEG4-SL" },
    { VS_FORMAT_DV,             "Format DV" },
    { VS_COLORFORMAT,           "Colorformat" },
    { VS_FORMAT_VENDOR,         "Format Vendor" },
    { VS_FRAME_VENDOR,          "Frame Vendor" },
    { VS_FORMAT_FRAME_BASED,    "Format Frame-Based" },
    { VS_FRAME_FRAME_BASED,     "Frame Frame-Based" },
    { VS_FORMAT_STREAM_BASED,   "Format Stream Based" },
    { VS_FORMAT_H264,           "Format H.264" },
    { VS_FRAME_H264,            "Frame H.264" },
    { VS_FORMAT_H264_SIMULCAST, "Format H.264 Simulcast" },
    { VS_FORMAT_VP8,            "Format VP8" },
    { VS_FRAME_VP8,             "Frame VP8" },
    { VS_FORMAT_VP8_SIMULCAST,  "Format VP8 Simulcast" },
    { 0, NULL }
};
static value_string_ext vs_if_descriptor_subtypes_ext =
    VALUE_STRING_EXT_INIT(vs_if_descriptor_subtypes);

static const value_string interrupt_status_types[] = {
    { INT_VIDEOCONTROL,       "VideoControl Interface"   },
    { INT_VIDEOSTREAMING,     "VideoStreaming Interface" },
    { 0, NULL }
};

static const value_string control_change_types[] = {
    { CONTROL_CHANGE_VALUE,   "Value" },
    { CONTROL_CHANGE_INFO,    "Info" },
    { CONTROL_CHANGE_FAILURE, "Failure" },
    { CONTROL_CHANGE_MIN,     "Min" },
    { CONTROL_CHANGE_MAX,     "Max" },
    { 0, NULL }
};
static value_string_ext control_change_types_ext =
    VALUE_STRING_EXT_INIT(control_change_types);

static const value_string control_interrupt_events[] = {
    { CONTROL_INTERRUPT_EVENT_CONTROL_CHANGE,  "Control Change" },
    { 0, NULL }
};

/* Table 3-13 VS Interface Input Header Descriptor - bStillCaptureMethod field */
static const value_string vs_still_capture_methods[] = {
    { 0,  "None" },
    { 1,  "Uninterrupted streaming" },
    { 2,  "Suspended streaming" },
    { 3,  "Dedicated pipe" },
    { 0, NULL }
};
static value_string_ext vs_still_capture_methods_ext =
    VALUE_STRING_EXT_INIT(vs_still_capture_methods);

/* Table 3-13 VS Interface Input Header Descriptor - bTriggerUsage field */
static const value_string vs_trigger_usage[] = {
    { 0,  "Initiate still image capture" },
    { 1,  "General purpose button event" },
    { 0, NULL }
};

/* bmInterlaceFlags for format descriptors */
static const true_false_string is_interlaced_meaning = {
    "Interlaced",
    "Non-interlaced"
};

/* bmInterlaceFlags for format descriptors */
static const true_false_string interlaced_fields_meaning = {
    "1 field",
    "2 fields"
};

/* bmInterlaceFlags for format descriptors */
static const value_string field_pattern_meaning[] = {
    { 0,  "Field 1 only" },
    { 1,  "Field 2 only" },
    { 2,  "Regular pattern of fields 1 and 2" },
    { 3,  "Random pattern of fields 1 and 2" },
    {0, NULL},
};
static value_string_ext field_pattern_meaning_ext =
    VALUE_STRING_EXT_INIT(field_pattern_meaning);

/* bCopyProtect for format descriptors */
static const value_string copy_protect_meaning[] = {
    { 0,  "No restrictions" },
    { 1,  "Restrict duplication" },
    {0, NULL},
};

/* Table 4-46 Video Probe and Commit Controls - bmHint field */
static const true_false_string probe_hint_meaning = {
    "Constant",
    "Variable"
};

/* Table 3-19 Color Matching Descriptor - bColorPrimaries field */
static const value_string color_primaries_meaning[] = {
    { 0,  "Unspecified" },
    { 1,  "BT.709, sRGB" },
    { 2,  "BT.470-2 (M)" },
    { 3,  "BT.470-2 (B,G)" },
    { 4,  "SMPTE 170M" },
    { 5,  "SMPTE 240M" },
    {0, NULL},
};
static value_string_ext color_primaries_meaning_ext =
    VALUE_STRING_EXT_INIT(color_primaries_meaning);

/* Table 3-19 Color Matching Descriptor - bTransferCharacteristics field */
static const value_string color_transfer_characteristics[] = {
    { 0,  "Unspecified" },
    { 1,  "BT.709" },
    { 2,  "BT.470-2 (M)" },
    { 3,  "BT.470-2 (B,G)" },
    { 4,  "SMPTE 170M" },
    { 5,  "SMPTE 240M" },
    { 6,  "Linear (V=Lc)" },
    { 7,  "sRGB" },
    {0, NULL},
};
static value_string_ext color_transfer_characteristics_ext =
    VALUE_STRING_EXT_INIT(color_transfer_characteristics);

/* Table 3-19 Color Matching Descriptor - bMatrixCoefficients field */
static const value_string matrix_coefficients_meaning[] = {
    { 0,  "Unspecified" },
    { 1,  "BT.709" },
    { 2,  "FCC" },
    { 3,  "BT.470-2 (B,G)" },
    { 4,  "SMPTE 170M (BT.601)" },
    { 5,  "SMPTE 240M" },
    {0, NULL},
};
static value_string_ext matrix_coefficients_meaning_ext =
    VALUE_STRING_EXT_INIT(matrix_coefficients_meaning);

static const value_string request_error_codes[] = {
    { UVC_ERROR_NONE,             "No error" },
    { UVC_ERROR_NOT_READY,        "Not ready" },
    { UVC_ERROR_WRONG_STATE,      "Wrong state" },
    { UVC_ERROR_POWER,            "Insufficient power" } ,
    { UVC_ERROR_OUT_OF_RANGE,     "Out of range" },
    { UVC_ERROR_INVALID_UNIT,     "Invalid unit" },
    { UVC_ERROR_INVALID_CONTROL,  "Invalid control" },
    { UVC_ERROR_INVALID_REQUEST,  "Invalid request" },
    { UVC_ERROR_INVALID_VALUE,    "Invalid value within range" },
    { UVC_ERROR_UNKNOWN,          "Unknown" },
    {0, NULL},
};
static value_string_ext request_error_codes_ext =
    VALUE_STRING_EXT_INIT(request_error_codes);

/* There is one such structure per terminal or unit per interface */
typedef struct
{
    guint8  entityID;
    guint8  subtype;
    guint16 terminalType;
} video_entity_t;

/* video_entity_t's (units/terminals) associated with each video interface */
/* There is one such structure for each video conversation (interface) */
typedef struct _video_conv_info_t {
    wmem_tree_t* entities;      /* indexed by entity ID */
} video_conv_info_t;

/*****************************************************************************/
/*                            UTILITY FUNCTIONS                              */
/*****************************************************************************/

/**
 * Dissector for variable-length bmControl bitmask / bControlSize pair.
 *
 * Creates an item for bControlSize, and a subtree for the bmControl bitmask.
 *
 * @param tree            protocol tree to be the parent of the bitmask subtree
 * @param tvb             the tv_buff with the (remaining) packet data
 * @param offset          where in tvb to find bControlSize field
 * @param ett_subtree     index of the subtree to use for this bitmask
 * @param bm_items        NULL-terminated array of pointers that lists all the fields
 *                        of the bitmask
 *
 * @return   offset within tvb at which dissection should continue
 */
static int
dissect_bmControl(proto_tree *tree, tvbuff_t *tvb, int offset,
                  gint ett_subtree, int * const *bm_items)
{
    guint8 bm_size = 0;

    bm_size = tvb_get_guint8(tvb, offset);
    proto_tree_add_item(tree, hf_usb_vid_bControlSize, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    ++offset;

    if (bm_size > 0)
    {
        proto_tree_add_bitmask_len(tree, tvb, offset, bm_size, hf_usb_vid_bmControl,
                                   ett_subtree, bm_items, &ei_usb_vid_bitmask_len, ENC_LITTLE_ENDIAN);
        offset += bm_size;
    }

    return offset;
}

/*****************************************************************************/
/*                          VIDEO CONTROL DESCRIPTORS                        */
/*****************************************************************************/

/* Dissect a Camera Terminal descriptor */
static int
dissect_usb_video_camera_terminal(proto_tree *tree, tvbuff_t *tvb, int offset)
{
    static int * const control_bits[] = {
        &hf_usb_vid_cam_control_D[0],
        &hf_usb_vid_cam_control_D[1],
        &hf_usb_vid_cam_control_D[2],
        &hf_usb_vid_cam_control_D[3],
        &hf_usb_vid_cam_control_D[4],
        &hf_usb_vid_cam_control_D[5],
        &hf_usb_vid_cam_control_D[6],
        &hf_usb_vid_cam_control_D[7],
        &hf_usb_vid_cam_control_D[8],
        &hf_usb_vid_cam_control_D[9],
        &hf_usb_vid_cam_control_D[10],
        &hf_usb_vid_cam_control_D[11],
        &hf_usb_vid_cam_control_D[12],
        &hf_usb_vid_cam_control_D[13],
        &hf_usb_vid_cam_control_D[14],
        &hf_usb_vid_cam_control_D[15],
        &hf_usb_vid_cam_control_D[16],
        &hf_usb_vid_cam_control_D[17],
        &hf_usb_vid_cam_control_D[18],
        &hf_usb_vid_cam_control_D[19],
        &hf_usb_vid_cam_control_D[20],
        &hf_usb_vid_cam_control_D[21],
        NULL
    };

    DISSECTOR_ASSERT(array_length(control_bits) == (1+array_length(hf_usb_vid_cam_control_D)));

    proto_tree_add_item(tree, hf_usb_vid_cam_objective_focal_len_min,  tvb, offset, 2, ENC_LITTLE_ENDIAN);
    offset += 2;
    proto_tree_add_item(tree, hf_usb_vid_cam_objective_focal_len_max,  tvb, offset, 2, ENC_LITTLE_ENDIAN);
    offset += 2;
    proto_tree_add_item(tree, hf_usb_vid_cam_ocular_focal_len, tvb, offset, 2, ENC_LITTLE_ENDIAN);
    offset += 2;

    offset = dissect_bmControl(tree, tvb, offset, ett_camera_controls, control_bits);

    return offset;
}

/* Dissect a Processing Unit descriptor */
static int
dissect_usb_video_processing_unit(proto_tree *tree, tvbuff_t *tvb, int offset)
{
    static int * const control_bits[] = {
        &hf_usb_vid_proc_control_D[0],
        &hf_usb_vid_proc_control_D[1],
        &hf_usb_vid_proc_control_D[2],
        &hf_usb_vid_proc_control_D[3],
        &hf_usb_vid_proc_control_D[4],
        &hf_usb_vid_proc_control_D[5],
        &hf_usb_vid_proc_control_D[6],
        &hf_usb_vid_proc_control_D[7],
        &hf_usb_vid_proc_control_D[8],
        &hf_usb_vid_proc_control_D[9],
        &hf_usb_vid_proc_control_D[10],
        &hf_usb_vid_proc_control_D[11],
        &hf_usb_vid_proc_control_D[12],
        &hf_usb_vid_proc_control_D[13],
        &hf_usb_vid_proc_control_D[14],
        &hf_usb_vid_proc_control_D[15],
        &hf_usb_vid_proc_control_D[16],
        &hf_usb_vid_proc_control_D[17],
        &hf_usb_vid_proc_control_D[18],
        NULL
    };

    DISSECTOR_ASSERT(array_length(control_bits) == (1+array_length(hf_usb_vid_proc_control_D)));

    proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_src_id, tvb, offset,   1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_max_multiplier,        tvb, offset+1, 2, ENC_LITTLE_ENDIAN);
    offset += 3;

    offset = dissect_bmControl(tree, tvb, offset, ett_processing_controls, control_bits);

    proto_tree_add_item(tree, hf_usb_vid_iProcessing, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    ++offset;

    /* UVC 1.1 added bmVideoStandards */
    if (tvb_reported_length_remaining(tvb, offset) > 0)
    {
        static int * const standard_bits[] = {
            &hf_usb_vid_proc_standards_D[0],
            &hf_usb_vid_proc_standards_D[1],
            &hf_usb_vid_proc_standards_D[2],
            &hf_usb_vid_proc_standards_D[3],
            &hf_usb_vid_proc_standards_D[4],
            &hf_usb_vid_proc_standards_D[5],
            NULL
        };

        DISSECTOR_ASSERT(array_length(standard_bits) == (1+array_length(hf_usb_vid_proc_standards_D)));

        proto_tree_add_bitmask(tree, tvb, offset, hf_usb_vid_proc_standards,
                               ett_video_standards, standard_bits, ENC_NA);
        ++offset;
    }

    return offset;
}

/* Dissect a Selector Unit descriptor */
static int
dissect_usb_video_selector_unit(proto_tree *tree, tvbuff_t *tvb, int offset)
{
    guint8 num_inputs;

    num_inputs = tvb_get_guint8(tvb, offset);
    proto_tree_add_item(tree, hf_usb_vid_num_inputs, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    ++offset;

    if (num_inputs > 0)
    {
        proto_tree_add_item(tree, hf_usb_vid_sources, tvb, offset, num_inputs, ENC_NA);
        offset += num_inputs;
    }

    proto_tree_add_item(tree, hf_usb_vid_iSelector, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    ++offset;

    return offset;
}

/* Dissect an Extension Unit descriptor */
static int
dissect_usb_video_extension_unit(proto_tree *tree, tvbuff_t *tvb, int offset)
{
    guint8 num_inputs;
    guint8 control_size;

    proto_tree_add_item(tree, hf_usb_vid_exten_guid,         tvb, offset,    16, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_exten_num_controls, tvb, offset+16,  1, ENC_LITTLE_ENDIAN);
    offset += 17;

    num_inputs = tvb_get_guint8(tvb, offset);
    proto_tree_add_item(tree, hf_usb_vid_num_inputs,   tvb, offset,  1, ENC_LITTLE_ENDIAN);
    ++offset;

    if (num_inputs > 0)
    {
        proto_tree_add_item(tree, hf_usb_vid_sources, tvb, offset, num_inputs, ENC_NA);
        offset += num_inputs;
    }

    control_size = tvb_get_guint8(tvb, offset);
    proto_tree_add_item(tree, hf_usb_vid_bControlSize, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    ++offset;

    if (control_size > 0)
    {
        if (control_size <= proto_registrar_get_length(hf_usb_vid_bmControl))
        {
            proto_tree_add_item(tree, hf_usb_vid_bmControl, tvb, offset, control_size,
                                ENC_LITTLE_ENDIAN);
        }
        else
        {
            /* Too big to display as integer */
            /* @todo Display as FT_BYTES with a big-endian disclaimer?
             * See https://gitlab.com/wireshark/wireshark/-/issues/7933
             */
            proto_tree_add_bytes_format(tree, hf_usb_vid_bmControl_bytes, tvb, offset, control_size, NULL, "bmControl");
        }
        offset += control_size;
    }

    proto_tree_add_item(tree, hf_usb_vid_iExtension, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    ++offset;

    return offset;
}

/**
 * Dissector for video class control interface descriptors
 *
 * @param parent_tree     the protocol tree to be the parent of the descriptor subtree
 * @param tvb             the tv_buff with the (remaining) packet data
 *                        On entry the gaze is set to the descriptor length field.
 * @param descriptor_len  Length of the descriptor to dissect
 * @param pinfo           Information associated with the packet being dissected
 *
 * @return   offset within tvb at which dissection should continue
 */
static int
dissect_usb_video_control_interface_descriptor(proto_tree *parent_tree, tvbuff_t *tvb,
                                               guint8 descriptor_len, packet_info *pinfo, usb_conv_info_t *usb_conv_info)
{
    video_conv_info_t *video_conv_info = NULL;
    video_entity_t    *entity          = NULL;
    proto_item *item          = NULL;
    proto_item *subtype_item  = NULL;
    proto_tree *tree          = NULL;
    guint8      entity_id     = 0;
    guint16     terminal_type = 0;
    int         offset        = 0;
    guint8      subtype;

    subtype = tvb_get_guint8(tvb, offset+2);

    if (parent_tree)
    {
        const gchar *subtype_str;

        subtype_str = val_to_str_ext(subtype, &vc_if_descriptor_subtypes_ext, "Unknown (0x%x)");

        tree = proto_tree_add_subtree_format(parent_tree, tvb, offset, descriptor_len,
                                   ett_descriptor_video_control, &item, "VIDEO CONTROL INTERFACE DESCRIPTOR [%s]",
                                   subtype_str);
    }

    /* Common fields */
    dissect_usb_descriptor_header(tree, tvb, offset, &vid_descriptor_type_vals_ext);
    subtype_item = proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_subtype, tvb, offset+2, 1, ENC_LITTLE_ENDIAN);
    offset += 3;

    if (subtype == VC_HEADER)
    {
        guint8 num_vs_interfaces;

        proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_bcdUVC,            tvb, offset,   2, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_usb_vid_ifdesc_wTotalLength,              tvb, offset+2, 2, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_dwClockFrequency,  tvb, offset+4, 4, ENC_LITTLE_ENDIAN);

        num_vs_interfaces = tvb_get_guint8(tvb, offset+8);
        proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_bInCollection,     tvb, offset+8, 1, ENC_LITTLE_ENDIAN);

        if (num_vs_interfaces > 0)
        {
            proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_baInterfaceNr, tvb, offset+9, num_vs_interfaces, ENC_NA);
        }

        offset += 9 + num_vs_interfaces;
    }
    else if ((subtype == VC_INPUT_TERMINAL) || (subtype == VC_OUTPUT_TERMINAL))
    {
        /* Fields common to input and output terminals */
        entity_id     = tvb_get_guint8(tvb, offset);
        terminal_type = tvb_get_letohs(tvb, offset+1);

        proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_terminal_id,    tvb, offset,   1, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_terminal_type,  tvb, offset+1, 2, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_assoc_terminal, tvb, offset+3, 1, ENC_LITTLE_ENDIAN);
        offset += 4;

        if (subtype == VC_OUTPUT_TERMINAL)
        {
            proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_src_id, tvb, offset, 1, ENC_LITTLE_ENDIAN);
            ++offset;
        }

        proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_iTerminal, tvb, offset, 1, ENC_LITTLE_ENDIAN);
        ++offset;

        if (subtype == VC_INPUT_TERMINAL)
        {
            if (terminal_type == ITT_CAMERA)
            {
                offset = dissect_usb_video_camera_terminal(tree, tvb, offset);
            }
            else if (terminal_type == ITT_MEDIA_TRANSPORT_INPUT)
            {
                /* @todo */
            }
        }

        if (subtype == VC_OUTPUT_TERMINAL)
        {
            if (terminal_type == OTT_MEDIA_TRANSPORT_OUTPUT)
            {
                /* @todo */
            }
        }
    }
    else
    {
        /* Field common to extension / processing / selector / encoding units */
        entity_id = tvb_get_guint8(tvb, offset);
        proto_tree_add_item(tree, hf_usb_vid_control_ifdesc_unit_id, tvb, offset, 1, ENC_LITTLE_ENDIAN);
        ++offset;

        if (subtype == VC_PROCESSING_UNIT)
        {
            offset = dissect_usb_video_processing_unit(tree, tvb, offset);
        }
        else if (subtype == VC_SELECTOR_UNIT)
        {
            offset = dissect_usb_video_selector_unit(tree, tvb, offset);
        }
        else if (subtype == VC_EXTENSION_UNIT)
        {
            offset = dissect_usb_video_extension_unit(tree, tvb, offset);
        }
        else if (subtype == VC_ENCODING_UNIT)
        {
            /* @todo UVC 1.5 */
        }
        else
        {
            expert_add_info_format(pinfo, subtype_item, &ei_usb_vid_subtype_unknown,
                                   "Unknown VC subtype %u", subtype);
        }
    }

    /* Soak up descriptor bytes beyond those we know how to dissect */
    if (offset < descriptor_len)
    {
        proto_tree_add_item(tree, hf_usb_vid_descriptor_data, tvb, offset, descriptor_len-offset, ENC_NA);
        /* offset = descriptor_len; */
    }

    if (entity_id != 0)
        proto_item_append_text(item, " (Entity %d)", entity_id);

    if (subtype != VC_HEADER && usb_conv_info)
    {
        /* Switch to the usb_conv_info of the Video Control interface */
        usb_conv_info = get_usb_iface_conv_info(pinfo, usb_conv_info->interfaceNum);
        video_conv_info = (video_conv_info_t *)usb_conv_info->class_data;

        if (!video_conv_info)
        {
            video_conv_info = wmem_new(wmem_file_scope(), video_conv_info_t);
            video_conv_info->entities = wmem_tree_new(wmem_file_scope());
            usb_conv_info->class_data = video_conv_info;
            usb_conv_info->class_data_type = USB_CONV_VIDEO;
        } else if (usb_conv_info->class_data_type != USB_CONV_VIDEO) {
            /* Stop dissection if another USB type is in the conversation */
            return descriptor_len;
        }

        entity = (video_entity_t*) wmem_tree_lookup32(video_conv_info->entities, entity_id);
        if (!entity)
        {
            entity = wmem_new(wmem_file_scope(), video_entity_t);
            entity->entityID     = entity_id;
            entity->subtype      = subtype;
            entity->terminalType = terminal_type;

            wmem_tree_insert32(video_conv_info->entities, entity_id, entity);
        }
    }

    return descriptor_len;
}

/*****************************************************************************/
/*                        VIDEO STREAMING DESCRIPTORS                        */
/*****************************************************************************/

/* Dissect a Video Streaming Input Header descriptor */
static int
dissect_usb_video_streaming_input_header(proto_tree *tree, tvbuff_t *tvb, int offset)
{
    guint8 num_formats;
    guint8 bm_size;

    static int * const info_bits[] = {
        &hf_usb_vid_streaming_info_D[0],
        NULL
    };
    static int * const control_bits[] = {
        &hf_usb_vid_streaming_control_D[0],
        &hf_usb_vid_streaming_control_D[1],
        &hf_usb_vid_streaming_control_D[2],
        &hf_usb_vid_streaming_control_D[3],
        &hf_usb_vid_streaming_control_D[4],
        &hf_usb_vid_streaming_control_D[5],
        NULL
    };

    DISSECTOR_ASSERT(array_length(control_bits) == (1+array_length(hf_usb_vid_streaming_control_D)));

    num_formats = tvb_get_guint8(tvb, offset);
    proto_tree_add_item(tree, hf_usb_vid_streaming_ifdesc_bNumFormats, tvb, offset,   1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_ifdesc_wTotalLength,          tvb, offset+1, 2, ENC_LITTLE_ENDIAN);
    offset += 3;

    dissect_usb_endpoint_address(tree, tvb, offset);
    offset++;

    proto_tree_add_bitmask(tree, tvb, offset, hf_usb_vid_streaming_bmInfo,
                           ett_streaming_info, info_bits, ENC_NA);

    proto_tree_add_item(tree, hf_usb_vid_streaming_terminal_link,        tvb, offset+1, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_streaming_still_capture_method, tvb, offset+2, 1, ENC_LITTLE_ENDIAN);
    offset += 3;

    proto_tree_add_item(tree, hf_usb_vid_streaming_trigger_support,      tvb, offset,   1, ENC_NA);
    if (tvb_get_guint8(tvb, offset) > 0)
    {
        proto_tree_add_item(tree, hf_usb_vid_streaming_trigger_usage,    tvb, offset+1, 1, ENC_LITTLE_ENDIAN);
    }
    else
    {
        proto_tree_add_uint_format_value(tree, hf_usb_vid_streaming_trigger_usage, tvb, offset+1, 1, 0, "Not applicable");
    }

    offset += 2;

    /* NOTE: Can't use dissect_bmControl here because there's only one size
     *       field for (potentially) multiple bmControl fields
     */
    bm_size = tvb_get_guint8(tvb, offset);
    proto_tree_add_item(tree, hf_usb_vid_bControlSize, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    ++offset;

    if (bm_size > 0)
    {
        guint8 i;
        for (i=0; i<num_formats; ++i)
        {
            proto_tree_add_bitmask_len(tree, tvb, offset, bm_size, hf_usb_vid_bmControl,
                                       ett_streaming_controls, control_bits, &ei_usb_vid_bitmask_len,
                                       ENC_LITTLE_ENDIAN);
            offset += bm_size;
        }
    }

    return offset;
}

/**
 * Dissect a known Video Payload Format descriptor.
 *
 * @param tree     protocol tree to which fields should be added
 * @param tvb      the tv_buff with the (remaining) packet data
 * @param offset   where in tvb to begin dissection.
 *                 On entry this refers to the bFormatIndex field.
 * @param subtype  Type of format descriptor, from the
 *                 bDescriptorSubtype field
 *
 * @return   offset within tvb at which dissection should continue
 */
static int
dissect_usb_video_format(proto_tree *tree, tvbuff_t *tvb, int offset,
                         guint8 subtype)
{
    static int * const interlace_bits[] = {
        &hf_usb_vid_is_interlaced,
        &hf_usb_vid_interlaced_fields,
        &hf_usb_vid_field_1_first,
        &hf_usb_vid_field_pattern,
        NULL
    };

    proto_item *desc_item;
    guint8 format_index;

    /* Augment the descriptor root item with the index of this descriptor */
    format_index = tvb_get_guint8(tvb, offset);
    desc_item = proto_tree_get_parent(tree);
    proto_item_append_text(desc_item, "  (Format %u)", format_index);

    proto_tree_add_item(tree, hf_usb_vid_format_index,                  tvb, offset,    1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_format_num_frame_descriptors,  tvb, offset+1,  1, ENC_LITTLE_ENDIAN);
    offset += 2;

    if ((subtype == VS_FORMAT_UNCOMPRESSED) || (subtype == VS_FORMAT_FRAME_BASED))
    {
        /* Augment the descriptor root item with the format's four-character-code */
        proto_item_append_text(desc_item, ": %s", tvb_format_text(wmem_packet_scope(), tvb, offset, 4));

        proto_tree_add_item(tree, hf_usb_vid_format_guid, tvb, offset,   16, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_usb_vid_format_bits_per_pixel,        tvb, offset+16, 1, ENC_LITTLE_ENDIAN);
        offset += 17;
    }
    else if (subtype == VS_FORMAT_MJPEG)
    {
        static int * const  flags[] = {
            &hf_usb_vid_mjpeg_fixed_samples,
            NULL
        };

        proto_tree_add_bitmask(tree, tvb, offset, hf_usb_vid_mjpeg_flags, ett_mjpeg_flags, flags, ENC_NA);
        offset++;
    }
    else
    {
        /* We should only be called for known format descriptor subtypes */
        DISSECTOR_ASSERT_NOT_REACHED();
    }

    proto_tree_add_item(tree, hf_usb_vid_default_frame_index, tvb, offset,   1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_aspect_ratio_x,      tvb, offset+1, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_aspect_ratio_y,      tvb, offset+2, 1, ENC_LITTLE_ENDIAN);
    offset += 3;

#if 0
    /* @todo Display "N/A" if Camera Terminal does not support scanning mode control */
    if (something)
        proto_tree_add_uint_format_value(tree, hf_usb_vid_interlace_flags, tvb, offset, 1, tvb_get_guint8(tvb, offset), "Not applicable");
#endif

    proto_tree_add_bitmask(tree, tvb, offset, hf_usb_vid_interlace_flags,
                                ett_interlace_flags, interlace_bits, ENC_NA);
    offset++;

    proto_tree_add_item(tree, hf_usb_vid_copy_protect, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    offset++;

    if (subtype == VS_FORMAT_FRAME_BASED)
    {
        proto_tree_add_item(tree, hf_usb_vid_variable_size, tvb, offset, 1, ENC_NA);
        offset++;
    }

    return offset;
}

/**
 * Dissect a known Video Frame descriptor.
 *
 * @param tree     protocol tree to which fields should be added
 * @param tvb      the tv_buff with the (remaining) packet data
 * @param offset   where in tvb to begin dissection.
 *                 On entry this refers to the bFrameIndex field.
 * @param subtype  Type of frame descriptor, from the
 *                 bDescriptorSubtype field
 *
 * @return   offset within tvb at which dissection should continue
 */
static int
dissect_usb_video_frame(proto_tree *tree, tvbuff_t *tvb, int offset,
                        guint8 subtype)
{
    static int * const capability_bits[] = {
        &hf_usb_vid_frame_stills_supported,
        &hf_usb_vid_frame_fixed_frame_rate,
        NULL
    };
    proto_item *desc_item;
    guint8      bFrameIntervalType;
    guint8      frame_index;
    guint16     frame_width;
    guint16     frame_height;

    frame_index = tvb_get_guint8(tvb, offset);
    proto_tree_add_item(tree, hf_usb_vid_frame_index, tvb, offset, 1, ENC_LITTLE_ENDIAN);
    offset++;

    proto_tree_add_bitmask(tree, tvb, offset, hf_usb_vid_frame_capabilities,
                           ett_frame_capability_flags, capability_bits, ENC_NA);
    offset++;

    proto_tree_add_item(tree, hf_usb_vid_frame_width,        tvb, offset,    2, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_frame_height,       tvb, offset+2,  2, ENC_LITTLE_ENDIAN);

    /* Augment the descriptor root item with useful information */
    frame_width = tvb_get_letohs(tvb, offset);
    frame_height = tvb_get_letohs(tvb, offset+2);
    desc_item = proto_tree_get_parent(tree);
    proto_item_append_text(desc_item, "   (Index %2u): %4u x %4u", frame_index, frame_width, frame_height);

    proto_tree_add_item(tree, hf_usb_vid_frame_min_bit_rate, tvb, offset+4,  4, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_frame_max_bit_rate, tvb, offset+8,  4, ENC_LITTLE_ENDIAN);
    offset += 12;

    if (subtype != VS_FRAME_FRAME_BASED)
    {
        proto_tree_add_item(tree, hf_usb_vid_frame_max_frame_sz, tvb, offset, 4, ENC_LITTLE_ENDIAN);
        offset += 4;
    }

    proto_tree_add_item(tree, hf_usb_vid_frame_default_interval, tvb, offset, 4, ENC_LITTLE_ENDIAN);
    offset += 4;

    bFrameIntervalType = tvb_get_guint8(tvb, offset);
    if (bFrameIntervalType == 0)
    {
        proto_tree_add_uint_format_value(tree, hf_usb_vid_frame_interval_type, tvb, offset, 1,
                                         bFrameIntervalType, "Continuous (0)");
        offset++;

        if (subtype == VS_FRAME_FRAME_BASED)
        {
            proto_tree_add_item(tree, hf_usb_vid_frame_bytes_per_line, tvb, offset, 4, ENC_LITTLE_ENDIAN);
            offset += 4;
        }

        proto_tree_add_item(tree, hf_usb_vid_frame_min_interval,  tvb, offset,   4, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_usb_vid_frame_max_interval,  tvb, offset+4, 4, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_usb_vid_frame_step_interval, tvb, offset+8, 4, ENC_LITTLE_ENDIAN);
        offset += 12;
    }
    else
    {
        guint8 i;
        proto_tree_add_uint_format_value(tree, hf_usb_vid_frame_interval_type, tvb, offset, 1,
                                         bFrameIntervalType, "Discrete (%u choice%s)",
                                         bFrameIntervalType, (bFrameIntervalType > 1) ? "s" : "");
        offset++;

        if (subtype == VS_FRAME_FRAME_BASED)
        {
            proto_tree_add_item(tree, hf_usb_vid_frame_bytes_per_line, tvb, offset, 4, ENC_LITTLE_ENDIAN);
            offset += 4;
        }

        for (i=0; i<bFrameIntervalType; ++i)
        {
            proto_tree_add_item(tree, hf_usb_vid_frame_interval,  tvb, offset, 4, ENC_LITTLE_ENDIAN);
            offset += 4;
        }
    }

    return offset;
}

/* Dissect a Color Matching descriptor */
static int
dissect_usb_video_colorformat(proto_tree *tree, tvbuff_t *tvb, int offset)
{
    proto_tree_add_item(tree, hf_usb_vid_color_primaries,          tvb, offset,   1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_transfer_characteristics, tvb, offset+1, 1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_matrix_coefficients,      tvb, offset+2, 1, ENC_LITTLE_ENDIAN);
    offset +=3;

    return offset;
}

/**
 * Dissector for video class streaming interface descriptors.
 *
 * @param parent_tree     the protocol tree to be the parent of the descriptor subtree
 * @param tvb             the tv_buff with the (remaining) packet data
 *                        On entry the gaze is set to the descriptor length field.
 * @param descriptor_len  Length of the descriptor to dissect
 *
 * @return   offset within tvb at which dissection should continue
 */
static int
dissect_usb_video_streaming_interface_descriptor(proto_tree *parent_tree, tvbuff_t *tvb,
                                                 guint8 descriptor_len)
{
    proto_tree  *tree;
    int          offset = 0;
    const gchar *subtype_str;
    guint8       subtype;

    subtype = tvb_get_guint8(tvb, offset+2);

    subtype_str = val_to_str_ext(subtype, &vs_if_descriptor_subtypes_ext, "Unknown (0x%x)");
    tree = proto_tree_add_subtree_format(parent_tree, tvb, offset, descriptor_len,
            ett_descriptor_video_streaming, NULL, "VIDEO STREAMING INTERFACE DESCRIPTOR [%s]",
            subtype_str);

    dissect_usb_descriptor_header(tree, tvb, offset, &vid_descriptor_type_vals_ext);
    proto_tree_add_item(tree, hf_usb_vid_streaming_ifdesc_subtype, tvb, offset+2, 1, ENC_LITTLE_ENDIAN);
    offset += 3;

    switch (subtype)
    {
        case VS_INPUT_HEADER:
            offset = dissect_usb_video_streaming_input_header(tree, tvb, offset);
            break;

        case VS_FORMAT_UNCOMPRESSED:
        case VS_FORMAT_MJPEG:
        case VS_FORMAT_FRAME_BASED:
            offset = dissect_usb_video_format(tree, tvb, offset, subtype);
            break;

        /* @todo MPEG2, H.264, VP8, Still Image Frame */
        /* @todo Obsolete UVC-1.0 descriptors? */

        case VS_FRAME_UNCOMPRESSED:
        case VS_FRAME_MJPEG:
        case VS_FRAME_FRAME_BASED:
            offset = dissect_usb_video_frame(tree, tvb, offset, subtype);
            break;

        case VS_COLORFORMAT:
            offset = dissect_usb_video_colorformat(tree, tvb, offset);
            break;

        default:
            break;
    }

    /* Soak up descriptor bytes beyond those we know how to dissect */
    if (offset < descriptor_len)
        proto_tree_add_item(tree, hf_usb_vid_descriptor_data, tvb, offset, descriptor_len-offset, ENC_NA);

    return descriptor_len;
}

/*****************************************************************************/

/**
 * Dissector for video class-specific endpoint descriptor.
 *
 * @param parent_tree     the protocol tree to be the parent of the descriptor subtree
 * @param tvb             the tv_buff with the (remaining) packet data
 *                        On entry the gaze is set to the descriptor length field.
 * @param descriptor_len  Length of the descriptor to dissect
 *
 * @return   offset within tvb at which dissection should continue
 */
static int
dissect_usb_video_endpoint_descriptor(proto_tree *parent_tree, tvbuff_t *tvb,
                                      guint8 descriptor_len)
{
    proto_tree *tree   = NULL;
    int         offset = 0;
    guint8      subtype;

    subtype = tvb_get_guint8(tvb, offset+2);

    if (parent_tree)
    {
        const gchar* subtype_str;

        subtype_str = val_to_str(subtype, vc_ep_descriptor_subtypes, "Unknown (0x%x)");
        tree = proto_tree_add_subtree_format(parent_tree, tvb, offset, descriptor_len,
                ett_descriptor_video_endpoint, NULL, "VIDEO CONTROL ENDPOINT DESCRIPTOR [%s]",
                subtype_str);
    }

    dissect_usb_descriptor_header(tree, tvb, offset, &vid_descriptor_type_vals_ext);
    proto_tree_add_item(tree, hf_usb_vid_epdesc_subtype, tvb, offset+2, 1, ENC_LITTLE_ENDIAN);
    offset += 3;

    if (subtype == EP_INTERRUPT)
    {
        proto_tree_add_item(tree, hf_usb_vid_epdesc_max_transfer_sz, tvb, offset, 2, ENC_LITTLE_ENDIAN);
        offset += 2;
    }

    /* Soak up descriptor bytes beyond those we know how to dissect */
    if (offset < descriptor_len)
        proto_tree_add_item(tree, hf_usb_vid_descriptor_data, tvb, offset, descriptor_len-offset, ENC_NA);

    return descriptor_len;
}

/**
 * Registered dissector for video class-specific descriptors
 *
 * @param tvb    the tv_buff with the (remaining) packet data
 *               On entry the gaze is set to the descriptor length field.
 * @param pinfo  the packet info of this packet (additional info)
 * @param tree   the protocol tree to be built or NULL
 * @param data   Not used
 *
 * @return   0   no class specific dissector was found
 * @return  <0   not enough data
 * @return  >0   amount of data in the descriptor
 */
static int
dissect_usb_vid_descriptor(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
    int    offset = 0;
    guint8 descriptor_len;
    guint8 descriptor_type;
    gint   bytes_available;
    usb_conv_info_t  *usb_conv_info = (usb_conv_info_t *)data;

    tvbuff_t         *desc_tvb;

    descriptor_len  = tvb_get_guint8(tvb, offset);
    descriptor_type = tvb_get_guint8(tvb, offset+1);

    bytes_available = tvb_captured_length_remaining(tvb, offset);
    desc_tvb = tvb_new_subset_length_caplen(tvb, 0, bytes_available, descriptor_len);

    if (descriptor_type == CS_ENDPOINT)
    {
        offset = dissect_usb_video_endpoint_descriptor(tree, desc_tvb,
                                                       descriptor_len);
    }
    else if (descriptor_type == CS_INTERFACE)
    {
        if (usb_conv_info && usb_conv_info->interfaceSubclass == SC_VIDEOCONTROL)
        {
            offset = dissect_usb_video_control_interface_descriptor(tree, desc_tvb,
                                                                    descriptor_len,
                                                                    pinfo, usb_conv_info);
        }
        else if (usb_conv_info && usb_conv_info->interfaceSubclass == SC_VIDEOSTREAMING)
        {
            offset = dissect_usb_video_streaming_interface_descriptor(tree, desc_tvb,
                                                                      descriptor_len);
        }
    }
    /* else not something we recognize, just return offset = 0 */

    return offset;
}

/*****************************************************************************/
/*                            CONTROL TRANSFERS                              */
/*****************************************************************************/

/**
 * Dissect GET/SET transactions on the Video Probe and Commit controls.
 *
 * @param  parent_tree  protocol tree to which the probe/commit subtree should be added
 * @param  tvb          the tv_buff with the (remaining) packet data
 * @param  offset       where in tvb to begin dissection.
 *                      On entry this refers to the probe/commit bmHint field.
 *
 * @return offset within tvb at which dissection should continue
 */
static int
dissect_usb_vid_probe(proto_tree *parent_tree, tvbuff_t *tvb, int offset)
{
    proto_tree *tree;

    static int * const hint_bits[] = {
        &hf_usb_vid_probe_hint_D[0],
        &hf_usb_vid_probe_hint_D[1],
        &hf_usb_vid_probe_hint_D[2],
        &hf_usb_vid_probe_hint_D[3],
        &hf_usb_vid_probe_hint_D[4],
        NULL
    };

    DISSECTOR_ASSERT(array_length(hint_bits) == (1+array_length(hf_usb_vid_probe_hint_D)));

    tree = proto_tree_add_subtree(parent_tree, tvb, offset, -1, ett_video_probe, NULL, "Probe/Commit Info");

    proto_tree_add_bitmask(tree, tvb, offset, hf_usb_vid_probe_hint,
                           ett_probe_hint, hint_bits, ENC_LITTLE_ENDIAN);

    proto_tree_add_item(tree, hf_usb_vid_format_index,         tvb, offset+2,  1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_frame_index,          tvb, offset+3,  1, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_frame_interval,       tvb, offset+4,  4, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_probe_key_frame_rate, tvb, offset+8,  2, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_probe_p_frame_rate,   tvb, offset+10, 2, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_probe_comp_quality,   tvb, offset+12, 2, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_probe_comp_window,    tvb, offset+14, 2, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_probe_delay,          tvb, offset+16, 2, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_probe_max_frame_sz,   tvb, offset+18, 4, ENC_LITTLE_ENDIAN);
    proto_tree_add_item(tree, hf_usb_vid_probe_max_payload_sz, tvb, offset+22, 4, ENC_LITTLE_ENDIAN);
    offset += 26;

    /* UVC 1.1 fields */
    if (tvb_reported_length_remaining(tvb, offset) > 0)
    {
        static int * const framing_bits[] = {
            &hf_usb_vid_probe_framing_D[0],
            &hf_usb_vid_probe_framing_D[1],
            NULL
        };

        DISSECTOR_ASSERT(array_length(framing_bits) == (1+array_length(hf_usb_vid_probe_framing_D)));

        proto_tree_add_item(tree, hf_usb_vid_probe_clock_freq,     tvb, offset, 4, ENC_LITTLE_ENDIAN);
        offset += 4;

        proto_tree_add_bitmask(tree, tvb, offset, hf_usb_vid_probe_framing,
                               ett_probe_framing, framing_bits, ENC_NA);
        offset++;

        proto_tree_add_item(tree, hf_usb_vid_probe_preferred_ver, tvb, offset,   1, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_usb_vid_probe_min_ver,       tvb, offset+1, 1, ENC_LITTLE_ENDIAN);
        proto_tree_add_item(tree, hf_usb_vid_probe_max_ver,       tvb, offset+2, 1, ENC_LITTLE_ENDIAN);
        offset += 3;
    }

    return offset;
}

/**
 * Fetch the table that describes known control selectors for the specified unit/terminal.
 *
 * @param  entity_id      Unit or terminal of interest
 * @param  usb_conv_info  Information about the interface the entity is part of
 *
 * @return Table describing control selectors for the specified entity (may be NULL)
 */
static value_string_ext*
get_control_selector_values(guint8 entity_id, usb_conv_info_t *usb_conv_info)
{
    video_conv_info_t *video_conv_info;
    video_entity_t    *entity = NULL;
    value_string_ext  *selectors = NULL;

    if (usb_conv_info == NULL || usb_conv_info->class_data_type != USB_CONV_VIDEO) {
        return NULL;
    }

    video_conv_info = (video_conv_info_t *)usb_conv_info->class_data;
    if (video_conv_info)
        entity = (video_entity_t*) wmem_tree_lookup32(video_conv_info->entities, entity_id);

    if (entity_id == 0)
    {
        /* Interface Request*/
        switch (usb_conv_info->interfaceSubclass)
        {
            case SC_VIDEOCONTROL:
                selectors = &cs_control_interface_ext;
                break;

            case SC_VIDEOSTREAMING:
                selectors = &cs_streaming_interface_ext;
                break;

            default:
                break;
        }
    }
    else if (entity)
    {
        switch (entity->subtype)
        {
            case VC_INPUT_TERMINAL:
                if (entity->terminalType == ITT_CAMERA)
                {
                    selectors = &cs_camera_terminal_ext;
                }
                break;

            case VC_PROCESSING_UNIT:
                selectors = &cs_processing_unit_ext;
                break;

            case VC_SELECTOR_UNIT:
                selectors = &cs_selector_unit_ext;
                break;

            default:
                break;
        }
    }

    return selectors;
}

/**
 * Fetch the name of an entity's control.
 *
 * @param  entity_id      Unit or terminal of interest
 * @param  control_sel    Control of interest
 * @param  usb_conv_info  Information about the interface the entity is part of
 *
 * @return Table describing control selectors for the specified entity (may be NULL)
 */
static const gchar*
get_control_selector_name(guint8 entity_id, guint8 control_sel, usb_conv_info_t *usb_conv_info)
{
    const gchar      *control_name = NULL;
    value_string_ext *selectors = NULL;

    selectors = get_control_selector_values(entity_id, usb_conv_info);

    if (selectors)
        control_name = try_val_to_str_ext(control_sel, selectors);

    return control_name;
}

/* Dissect the response to a GET INFO request */
static int
dissect_usb_vid_control_info(proto_tree *tree, tvbuff_t *tvb, int offset)
{
    static int * const capability_bits[] = {
        &hf_usb_vid_control_info_D[0],
        &hf_usb_vid_control_info_D[1],
        &hf_usb_vid_control_info_D[2],
        &hf_usb_vid_control_info_D[3],
        &hf_usb_vid_control_info_D[4],
        &hf_usb_vid_control_info_D[5],
        &hf_usb_vid_control_info_D[6],
        NULL
    };

    DISSECTOR_ASSERT(array_length(capability_bits) == (1+array_length(hf_usb_vid_control_info_D)));

    proto_tree_add_bitmask(tree, tvb, offset, hf_usb_vid_control_info,
                           ett_control_capabilities, capability_bits, ENC_NA);

    return offset+1;
}

/* Dissect all remaining bytes in the tvb as a specified type of UVC value.
 * These are displayed as an unsigned integer where possible, otherwise just as
 * a text item.
 *
 * @param tree     the protocol tree to which an item will be added
 * @param tvb      the tv_buff with the (remaining) packet data
 * @param offset   How far into tvb the value data begins
 * @param request  Identifies type of value - either bRequest from a CONTROL
 *                 transfer (i.e., USB_SETUP_GET_MAX), or bValue from an
 *                 INTERRUPT transfer (i.e., CONTROL_CHANGE_MAX).
 */
static void
dissect_usb_vid_control_value(proto_tree *tree, tvbuff_t *tvb, int offset, guint8 request)
{
    gint        value_size;
    const char *fallback_name;
    int         hf;

    switch (request)
    {
        case USB_SETUP_GET_DEF:
            hf = hf_usb_vid_control_default;
            fallback_name = "Default Value";
            break;

        case USB_SETUP_GET_MIN:
        case CONTROL_CHANGE_MIN:
            hf = hf_usb_vid_control_min;
            fallback_name = "Min Value";
            break;

        case USB_SETUP_GET_MAX:
        case CONTROL_CHANGE_MAX:
            hf = hf_usb_vid_control_max;
            fallback_name = "Max Value";
            break;

        case USB_SETUP_GET_RES:
            hf = hf_usb_vid_control_res;
            fallback_name = "Resolution";
            break;

        case USB_SETUP_GET_CUR:
        case USB_SETUP_SET_CUR:
        case CONTROL_CHANGE_VALUE:
            hf = hf_usb_vid_control_cur;
            fallback_name = "Current Value";
            break;

        /* @todo UVC 1.5 USB_SETUP_x_ALL?
         *       They are poorly specified.
         */

        default:
            hf = -1;
            fallback_name = "Value";
            break;
    }

    value_size = tvb_reported_length_remaining(tvb, offset);

    if (hf != -1)
    {
        header_field_info *hfinfo;
        hfinfo = proto_registrar_get_nth(hf);
        DISSECTOR_ASSERT(FT_IS_INT(hfinfo->type) || FT_IS_UINT(hfinfo->type));
    }

    if ((hf != -1) && (value_size <= 4))
    {
        proto_tree_add_item(tree, hf, tvb, offset, value_size, ENC_LITTLE_ENDIAN);
    }
    else
    {
        /* @todo Display as FT_BYTES with a big-endian disclaimer?
         * See https://gitlab.com/wireshark/wireshark/-/issues/7933
         */
        proto_tree_add_bytes_format(tree, hf_usb_vid_control_value, tvb, offset, value_size, NULL, "%s", fallback_name);
    }
}

/**
 * Dissect video class GET/SET transactions.
 *
 * @param  pinfo           Information associated with the packet being dissected
 * @param  tree            protocol tree to which fields should be added
 * @param  tvb             the tv_buff with the (remaining) packet data
 * @param  offset          where in tvb to begin dissection.
 *                         On entry this refers to the bRequest field of the SETUP
 *                         transaction.
 * @param  is_request      true if the packet is host-to-device,
 *                         false if device-to-host
 * @param  usb_trans_info  Information specific to this request/response pair
 * @param  usb_conv_info   Information about the conversation with the host
 */
static int
dissect_usb_vid_get_set(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb,
                        int offset, gboolean is_request,
                        usb_trans_info_t *usb_trans_info,
                        usb_conv_info_t *usb_conv_info)
{
    const gchar *short_name = NULL;
    guint8       control_sel;
    guint8       entity_id;

    entity_id   = usb_trans_info->setup.wIndex >> 8;
    control_sel = usb_trans_info->setup.wValue >> 8;

    /* Display something informative in the INFO column */
    col_append_str(pinfo->cinfo, COL_INFO, " [");
    short_name = get_control_selector_name(entity_id, control_sel, usb_conv_info);

    if (short_name)
        col_append_str(pinfo->cinfo, COL_INFO, short_name);
    else
    {
        short_name = "Unknown";

        if (entity_id == 0)
        {
            col_append_fstr(pinfo->cinfo, COL_INFO, "Interface %u control 0x%x",
                            usb_conv_info->interfaceNum, control_sel);
        }
        else
        {
            col_append_fstr(pinfo->cinfo, COL_INFO, "Unit %u control 0x%x",
                            entity_id, control_sel);
        }
    }

    col_append_str(pinfo->cinfo, COL_INFO, "]");
    col_set_fence(pinfo->cinfo, COL_INFO);

    /* Add information on request context,
     * as GENERATED fields if not directly available (for filtering)
     */
    if (is_request)
    {
        /* Move gaze to control selector (MSB of wValue) */
        offset++;
        proto_tree_add_uint_format_value(tree, hf_usb_vid_control_selector, tvb,
                                     offset, 1, control_sel, "%s (0x%02x)", short_name, control_sel);
        offset++;

        proto_tree_add_item(tree, hf_usb_vid_control_interface, tvb, offset, 1, ENC_LITTLE_ENDIAN);
        offset++;

        proto_tree_add_item(tree, hf_usb_vid_control_entity, tvb, offset, 1, ENC_LITTLE_ENDIAN);
        offset++;

        proto_tree_add_item(tree, hf_usb_vid_length, tvb, offset, 2, ENC_LITTLE_ENDIAN);
        offset += 2;
    }
    else
    {
        proto_item *ti;

        ti = proto_tree_add_uint(tree, hf_usb_vid_control_interface, tvb, 0, 0,
                                 usb_trans_info->setup.wIndex & 0xFF);
        proto_item_set_generated(ti);

        ti = proto_tree_add_uint(tree, hf_usb_vid_control_entity, tvb, 0, 0, entity_id);
        proto_item_set_generated(ti);

        ti = proto_tree_add_uint_format_value(tree, hf_usb_vid_control_selector, tvb,
                                     0, 0, control_sel, "%s (0x%02x)", short_name, control_sel);
        proto_item_set_generated(ti);
    }

    if (!is_request || (usb_trans_info->setup.request == USB_SETUP_SET_CUR))
    {
        gint value_size = tvb_reported_length_remaining(tvb, offset);

        if (value_size != 0)
        {
            if ((entity_id == 0) && (usb_conv_info->interfaceSubclass == SC_VIDEOSTREAMING))
            {
                if ((control_sel == VS_PROBE_CONTROL) || (control_sel == VS_COMMIT_CONTROL))
                {
                    int old_offset = offset;
                    offset = dissect_usb_vid_probe(tree, tvb, offset);
                    value_size -= (offset - old_offset);
                }
            }
            else
            {
                if (usb_trans_info->setup.request == USB_SETUP_GET_INFO)
                {
                    dissect_usb_vid_control_info(tree, tvb, offset);
                    offset++;
                    value_size--;
                }
                else if (usb_trans_info->setup.request == USB_SETUP_GET_LEN)
                {
                    proto_tree_add_item(tree, hf_usb_vid_control_length, tvb, offset, 2, ENC_LITTLE_ENDIAN);
                    offset += 2;
                    value_size -= 2;
                }
                else if (   (usb_trans_info->setup.request == USB_SETUP_GET_CUR)
                         && (entity_id == 0)
                         && (usb_conv_info->interfaceSubclass == SC_VIDEOCONTROL)
                         && (control_sel == VC_REQUEST_ERROR_CODE_CONTROL))
                {
                    proto_tree_add_item(tree, hf_usb_vid_request_error, tvb, offset, 1, ENC_LITTLE_ENDIAN);
                    offset++;
                    value_size--;
                }
                else
                {
                    dissect_usb_vid_control_value(tree, tvb, offset, usb_trans_info->setup.request);
                    offset += value_size;
                    value_size = 0;
                }
            }

            if (value_size > 0)
            {
                proto_tree_add_item(tree, hf_usb_vid_control_data, tvb, offset, -1, ENC_NA);
                offset += value_size;
            }
        }
    }

    return offset;
}

/* Table for dispatch of video class SETUP transactions based on bRequest.
 * At the moment this is overkill since the same function handles all defined
 * requests.
 */
typedef int (*usb_setup_dissector)(packet_info *pinfo, proto_tree *tree,
        tvbuff_t *tvb, int offset,
        gboolean is_request,
        usb_trans_info_t *usb_trans_info,
        usb_conv_info_t *usb_conv_info);

typedef struct _usb_setup_dissector_table_t
{
    guint8 request;
    usb_setup_dissector dissector;
} usb_setup_dissector_table_t;

static const usb_setup_dissector_table_t setup_dissectors[] = {
        {USB_SETUP_SET_CUR,      dissect_usb_vid_get_set},
        {USB_SETUP_SET_CUR_ALL,  dissect_usb_vid_get_set},
        {USB_SETUP_GET_CUR,      dissect_usb_vid_get_set},
        {USB_SETUP_GET_MIN,      dissect_usb_vid_get_set},
        {USB_SETUP_GET_MAX,      dissect_usb_vid_get_set},
        {USB_SETUP_GET_RES,      dissect_usb_vid_get_set},
        {USB_SETUP_GET_LEN,      dissect_usb_vid_get_set},
        {USB_SETUP_GET_INFO,     dissect_usb_vid_get_set},
        {USB_SETUP_GET_DEF,      dissect_usb_vid_get_set},
        {USB_SETUP_GET_CUR_ALL,  dissect_usb_vid_get_set},
        {USB_SETUP_GET_MIN_ALL,  dissect_usb_vid_get_set},
        {USB_SETUP_GET_MAX_ALL,  dissect_usb_vid_get_set},
        {USB_SETUP_GET_RES_ALL,  dissect_usb_vid_get_set},
        {0, NULL}
};

static const value_string setup_request_names_vals[] = {
        {USB_SETUP_SET_CUR,      "SET CUR"},
        {USB_SETUP_SET_CUR_ALL,  "SET CUR ALL"},
        {USB_SETUP_GET_CUR,      "GET CUR"},
        {USB_SETUP_GET_MIN,      "GET MIN"},
        {USB_SETUP_GET_MAX,      "GET MAX"},
        {USB_SETUP_GET_RES,      "GET RES"},
        {USB_SETUP_GET_LEN,      "GET LEN"},
        {USB_SETUP_GET_INFO,     "GET INFO"},
        {USB_SETUP_GET_DEF,      "GET DEF"},
        {USB_SETUP_GET_CUR_ALL,  "GET CUR ALL"},
        {USB_SETUP_GET_MIN_ALL,  "GET MIN ALL"},
        {USB_SETUP_GET_MAX_ALL,  "GET MAX ALL"},
        {USB_SETUP_GET_RES_ALL,  "GET RES ALL"},
        {USB_SETUP_GET_DEF_ALL,  "GET DEF ALL"},
        {0, NULL}
};

/* Registered dissector for video class-specific control requests.
 * Dispatch to an appropriate dissector function.
 *
 * @param tvb    the tv_buff with the (remaining) packet data.
 *               On entry, the gaze is set to SETUP bRequest field.
 * @param pinfo  the packet info of this packet (additional info)
 * @param tree   the protocol tree to be built or NULL
 * @param data   Not used
 *
 * @return   0   no class specific dissector was found
 * @return  <0   not enough data
 * @return  >0   amount of data in the descriptor
 */
static int
dissect_usb_vid_control(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
    gboolean             is_request = (pinfo->srcport == NO_ENDPOINT);
    usb_conv_info_t     *usb_conv_info;
    usb_trans_info_t    *usb_trans_info;
    int                  offset     = 0;
    usb_setup_dissector  dissector  = NULL;
    const usb_setup_dissector_table_t *tmp;

    /* Reject the packet if data or usb_trans_info are NULL */
    if (data == NULL || ((usb_conv_info_t *)data)->usb_trans_info == NULL)
        return 0;
    usb_conv_info = (usb_conv_info_t *)data;
    usb_trans_info = usb_conv_info->usb_trans_info;

    /* See if we can find a class specific dissector for this request */
    for (tmp=setup_dissectors; tmp->dissector; tmp++)
    {
        if (tmp->request == usb_trans_info->setup.request)
        {
            dissector = tmp->dissector;
            break;
        }
    }
    /* No we could not find any class specific dissector for this request
     * return FALSE and let USB try any of the standard requests.
     */
    if (!dissector)
        return 0;

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "USBVIDEO");
    col_add_fstr(pinfo->cinfo, COL_INFO, "%s %s",
                val_to_str(usb_trans_info->setup.request, setup_request_names_vals, "Unknown type %x"),
                is_request?"Request ":"Response");

    if (is_request)
    {
        proto_tree_add_item(tree, hf_usb_vid_request, tvb, offset, 1, ENC_LITTLE_ENDIAN);
        offset += 1;
    }

    offset = dissector(pinfo, tree, tvb, offset, is_request, usb_trans_info, usb_conv_info);
    return offset;
}

/* Registered dissector for video class-specific URB_INTERRUPT
 *
 * @param tvb    the tv_buff with the (remaining) packet data
 * @param pinfo  the packet info of this packet (additional info)
 * @param tree   the protocol tree to be built or NULL
 * @param data   Unused API parameter
 *
 * @return   0   no class specific dissector was found
 * @return  <0   not enough data
 * @return  >0   amount of data in the descriptor
 */
static int
dissect_usb_vid_interrupt(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
    usb_conv_info_t *usb_conv_info;
    gint bytes_available;
    int  offset = 0;

    usb_conv_info   = (usb_conv_info_t *)data;
    bytes_available = tvb_reported_length_remaining(tvb, offset);

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "USBVIDEO");

    if (bytes_available > 0)
    {
        guint8 originating_interface;
        guint8 originating_entity;

        originating_interface = tvb_get_guint8(tvb, offset) & INT_ORIGINATOR_MASK;
        proto_tree_add_item(tree, hf_usb_vid_interrupt_bStatusType, tvb, offset, 1, ENC_LITTLE_ENDIAN);
        offset++;

        originating_entity = tvb_get_guint8(tvb, offset);
        proto_tree_add_item(tree, hf_usb_vid_interrupt_bOriginator, tvb, offset, 1, ENC_LITTLE_ENDIAN);
        offset++;

        if (originating_interface == INT_VIDEOCONTROL)
        {
            guint8 control_sel;
            guint8 attribute;
            const gchar *control_name;

            proto_tree_add_item(tree, hf_usb_vid_control_interrupt_bEvent, tvb, offset, 1, ENC_LITTLE_ENDIAN);
            offset++;

            control_sel = tvb_get_guint8(tvb, offset);
            control_name = get_control_selector_name(originating_entity, control_sel, usb_conv_info);
            if (!control_name)
                control_name = "Unknown";

            proto_tree_add_uint_format_value(tree, hf_usb_vid_control_selector, tvb,
                                             offset, 1, control_sel, "%s (0x%02x)",
                                             control_name, control_sel);
            offset++;

            attribute = tvb_get_guint8(tvb, offset);
            proto_tree_add_item(tree, hf_usb_vid_interrupt_bAttribute, tvb, offset, 1, ENC_LITTLE_ENDIAN);
            offset++;

            switch (attribute)
            {
                case CONTROL_CHANGE_FAILURE:
                    proto_tree_add_item(tree, hf_usb_vid_request_error, tvb, offset, 1, ENC_LITTLE_ENDIAN);
                    offset++;
                    break;

                case CONTROL_CHANGE_INFO:
                    offset = dissect_usb_vid_control_info(tree, tvb, offset);
                    break;

                case CONTROL_CHANGE_VALUE:
                case CONTROL_CHANGE_MIN:
                case CONTROL_CHANGE_MAX:
                    dissect_usb_vid_control_value(tree, tvb, offset, attribute);
                    offset += tvb_reported_length_remaining(tvb, offset);
                    break;

                default:
                    proto_tree_add_item(tree, hf_usb_vid_value_data, tvb, offset, -1, ENC_NA);
                    offset += tvb_reported_length_remaining(tvb, offset);
                    break;
            }
        }
        else if (originating_interface == INT_VIDEOSTREAMING)
        {
            /* @todo */
        }
    }
    else
        offset = -2;

    return offset;
}

void
proto_register_usb_vid(void)
{
    static hf_register_info hf[] = {
        /***** Setup *****/
            { &hf_usb_vid_request,
                    { "bRequest", "usbvideo.setup.bRequest", FT_UINT8, BASE_HEX, VALS(setup_request_names_vals), 0x0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_length,
                    { "wLength", "usbvideo.setup.wLength", FT_UINT16, BASE_DEC, NULL, 0x0,
                            NULL, HFILL }
            },

        /***** Request Error Control *****/
            { &hf_usb_vid_request_error,
                    { "bRequestErrorCode", "usbvideo.reqerror.code",
                            FT_UINT8, BASE_DEC | BASE_EXT_STRING,
                            &request_error_codes_ext, 0,
                            "Request Error Code", HFILL }
            },

        /***** Unit/Terminal Controls *****/
            { &hf_usb_vid_control_selector,
                    { "Control Selector", "usbvideo.control.selector", FT_UINT8, BASE_HEX, NULL, 0x0,
                            "ID of the control within its entity", HFILL }
            },

            { &hf_usb_vid_control_entity,
                    { "Entity", "usbvideo.control.entity", FT_UINT8, BASE_HEX, NULL, 0x0,
                            "Unit or terminal to which the control belongs", HFILL }
            },

            { &hf_usb_vid_control_interface,
                    { "Interface", "usbvideo.control.interface", FT_UINT8, BASE_HEX, NULL, 0x0,
                            "Interface to which the control belongs", HFILL }
            },

            { &hf_usb_vid_control_info,
                    { "Info (Capabilities/State)", "usbvideo.control.info",
                            FT_UINT8, BASE_HEX, NULL, 0,
                            "Control capabilities and current state", HFILL }
            },

            { &hf_usb_vid_control_info_D[0],
                    { "Supports GET", "usbvideo.control.info.D0",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<0),
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_info_D[1],
                    { "Supports SET", "usbvideo.control.info.D1",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<1),
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_info_D[2],
                    { "Disabled due to automatic mode", "usbvideo.control.info.D2",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<2),
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_info_D[3],
                    { "Autoupdate", "usbvideo.control.info.D3",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<3),
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_info_D[4],
                    { "Asynchronous", "usbvideo.control.info.D4",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<4),
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_info_D[5],
                    { "Disabled due to incompatibility with Commit state", "usbvideo.control.info.D5",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<5),
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_info_D[6],
                    { "Reserved", "usbvideo.control.info.D6",
                            FT_UINT8, BASE_HEX, NULL, (3<<6),
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_length,
                    { "Control Length", "usbvideo.control.len",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Control size in bytes", HFILL }
            },

            { &hf_usb_vid_control_default,
                    { "Default value", "usbvideo.control.value.default",
                            FT_UINT32, BASE_DEC_HEX, NULL, 0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_min,
                    { "Minimum value", "usbvideo.control.value.min",
                            FT_UINT32, BASE_DEC_HEX, NULL, 0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_max,
                    { "Maximum value", "usbvideo.control.value.max",
                            FT_UINT32, BASE_DEC_HEX, NULL, 0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_res,
                    { "Resolution", "usbvideo.control.value.res",
                            FT_UINT32, BASE_DEC_HEX, NULL, 0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_cur,
                    { "Current value", "usbvideo.control.value.cur",
                            FT_UINT32, BASE_DEC_HEX, NULL, 0,
                            NULL, HFILL }
            },

        /***** Terminal Descriptors *****/

            /* @todo Decide whether to unify .name fields */
            { &hf_usb_vid_control_ifdesc_iTerminal,
                    { "iTerminal", "usbvideo.terminal.name", FT_UINT8, BASE_DEC, NULL, 0x0,
                            "String Descriptor describing this terminal", HFILL }
            },

            /* @todo Decide whether to unify .terminal.id and .unit.id under .entityID */
            { &hf_usb_vid_control_ifdesc_terminal_id,
                    { "bTerminalID", "usbvideo.terminal.id", FT_UINT8, BASE_DEC, NULL, 0x0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_ifdesc_terminal_type,
                    { "wTerminalType", "usbvideo.terminal.type",
                            FT_UINT16, BASE_HEX | BASE_EXT_STRING, &vc_terminal_types_ext, 0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_ifdesc_assoc_terminal,
                    { "bAssocTerminal", "usbvideo.terminal.assocTerminal", FT_UINT8, BASE_DEC, NULL, 0x0,
                            "Associated Terminal", HFILL }
            },

        /***** Camera Terminal Descriptor *****/

            { &hf_usb_vid_cam_objective_focal_len_min,
                    { "wObjectiveFocalLengthMin", "usbvideo.camera.objectiveFocalLengthMin",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Minimum Focal Length for Optical Zoom", HFILL }
            },

            { &hf_usb_vid_cam_objective_focal_len_max,
                    { "wObjectiveFocalLengthMax", "usbvideo.camera.objectiveFocalLengthMax",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Minimum Focal Length for Optical Zoom", HFILL }
            },

            { &hf_usb_vid_cam_ocular_focal_len,
                    { "wOcularFocalLength", "usbvideo.camera.ocularFocalLength",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Ocular Focal Length for Optical Zoom", HFILL }
            },

            { &hf_usb_vid_cam_control_D[0],
                    { "Scanning Mode", "usbvideo.camera.control.D0",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<0),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[1],
                    { "Auto Exposure Mode", "usbvideo.camera.control.D1",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<1),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[2],
                    { "Auto Exposure Priority", "usbvideo.camera.control.D2",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<2),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[3],
                    { "Exposure Time (Absolute)", "usbvideo.camera.control.D3",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<3),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[4],
                    { "Exposure Time (Relative)", "usbvideo.camera.control.D4",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<4),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[5],
                    { "Focus (Absolute)", "usbvideo.camera.control.D5",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<5),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[6],
                    { "Focus (Relative)", "usbvideo.camera.control.D6",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<6),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[7],
                    { "Iris (Absolute)", "usbvideo.camera.control.D7",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<7),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[8],
                    { "Iris (Relative)", "usbvideo.camera.control.D8",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<8),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[9],
                    { "Zoom (Absolute)", "usbvideo.camera.control.D9",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<9),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[10],
                    { "Zoom (Relative)", "usbvideo.camera.control.D10",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<10),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[11],
                    { "PanTilt (Absolute)", "usbvideo.camera.control.D11",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<11),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[12],
                    { "PanTilt (Relative)", "usbvideo.camera.control.D12",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<12),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[13],
                    { "Roll (Absolute)", "usbvideo.camera.control.D13",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<13),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[14],
                    { "Roll (Relative)", "usbvideo.camera.control.D14",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<14),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[15],
                    { "D15", "usbvideo.camera.control.D15",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<15),
                            "Reserved", HFILL }
            },

            { &hf_usb_vid_cam_control_D[16],
                    { "D16", "usbvideo.camera.control.D16",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<16),
                            "Reserved", HFILL }
            },

            { &hf_usb_vid_cam_control_D[17],
                    { "Auto Focus", "usbvideo.camera.control.D17",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<17),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[18],
                    { "Privacy", "usbvideo.camera.control.D18",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<18),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[19],
                    { "Focus (Simple)", "usbvideo.camera.control.D19",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<19),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[20],
                    { "Window", "usbvideo.camera.control.D20",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<20),
                            NULL, HFILL }
            },

            { &hf_usb_vid_cam_control_D[21],
                    { "Region of Interest", "usbvideo.camera.control.D21",
                            FT_BOOLEAN,
                            array_length(hf_usb_vid_cam_control_D),
                            TFS(&tfs_yes_no), (1<<21),
                            NULL, HFILL }
            },

        /***** Unit Descriptors *****/

            { &hf_usb_vid_control_ifdesc_unit_id,
                    { "bUnitID", "usbvideo.unit.id", FT_UINT8, BASE_DEC, NULL, 0x0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_num_inputs,
                    { "bNrInPins", "usbvideo.unit.numInputs",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Number of input pins", HFILL }
            },

            { &hf_usb_vid_sources,
                    { "baSourceID", "usbvideo.unit.sources",
                            FT_BYTES, BASE_NONE, NULL, 0,
                            "Input entity IDs", HFILL }
            },


        /***** Processing Unit Descriptor *****/

            { &hf_usb_vid_iProcessing,
                    { "iProcessing", "usbvideo.processor.name", FT_UINT8, BASE_DEC, NULL, 0x0,
                            "String Descriptor describing this terminal", HFILL }
            },

            { &hf_usb_vid_proc_control_D[0],
                    { "Brightness", "usbvideo.processor.control.D0",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<0),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[1],
                    { "Contrast", "usbvideo.processor.control.D1",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<1),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[2],
                    { "Hue", "usbvideo.processor.control.D2",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<2),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[3],
                    { "Saturation", "usbvideo.processor.control.D3",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<3),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[4],
                    { "Sharpness", "usbvideo.processor.control.D4",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<4),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[5],
                    { "Gamma", "usbvideo.processor.control.D5",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<5),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[6],
                    { "White Balance Temperature", "usbvideo.processor.control.D6",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<6),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[7],
                    { "White Balance Component", "usbvideo.processor.control.D7",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<7),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[8],
                    { "Backlight Compensation", "usbvideo.processor.control.D8",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<8),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[9],
                    { "Gain", "usbvideo.processor.control.D9",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<9),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[10],
                    { "Power Line Frequency", "usbvideo.processor.control.D10",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<10),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[11],
                    { "Hue, Auto", "usbvideo.processor.control.D11",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<11),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[12],
                    { "White Balance Temperature, Auto", "usbvideo.processor.control.D12",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<12),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[13],
                    { "White Balance Component, Auto", "usbvideo.processor.control.D13",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<13),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[14],
                    { "Digital Multiplier", "usbvideo.processor.control.D14",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<14),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[15],
                    { "Digital Multiplier Limit", "usbvideo.processor.control.D15",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<15),
                            "Reserved", HFILL }
            },

            { &hf_usb_vid_proc_control_D[16],
                    { "Analog Video Standard", "usbvideo.processor.control.D16",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<16),
                            "Reserved", HFILL }
            },

            { &hf_usb_vid_proc_control_D[17],
                    { "Analog Video Lock Status", "usbvideo.processor.control.D17",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<17),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_control_D[18],
                    { "Contrast, Auto", "usbvideo.processor.control.D18",
                            FT_BOOLEAN, 24, TFS(&tfs_yes_no), (1<<18),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_standards,
                    { "bmVideoStandards", "usbvideo.processor.standards",
                            FT_UINT8, BASE_HEX, NULL, 0,
                            "Supported analog video standards", HFILL }
            },

            { &hf_usb_vid_proc_standards_D[0],
                    { "None", "usbvideo.processor.standards.D0",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<0),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_standards_D[1],
                    { "NTSC - 525/60", "usbvideo.processor.standards.D1",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<1),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_standards_D[2],
                    { "PAL - 625/50", "usbvideo.processor.standards.D2",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<2),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_standards_D[3],
                    { "SECAM - 625/50", "usbvideo.processor.standards.D3",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<3),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_standards_D[4],
                    { "NTSC - 625/50", "usbvideo.processor.standards.D4",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<4),
                            NULL, HFILL }
            },

            { &hf_usb_vid_proc_standards_D[5],
                    { "PAL - 525/60", "usbvideo.processor.standards.D5",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<5),
                            NULL, HFILL }
            },

            { &hf_usb_vid_max_multiplier,
                    { "wMaxMultiplier", "usbvideo.processor.maxMultiplier",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "100 x max digital multiplication", HFILL }
            },

        /***** Selector Unit Descriptor *****/

            { &hf_usb_vid_iSelector,
                    { "iSelector", "usbvideo.selector.name", FT_UINT8, BASE_DEC, NULL, 0x0,
                            "String Descriptor describing this terminal", HFILL }
            },

        /***** Extension Unit Descriptor *****/

            { &hf_usb_vid_iExtension,
                    { "iExtension", "usbvideo.extension.name", FT_UINT8, BASE_DEC, NULL, 0x0,
                            "String Descriptor describing this terminal", HFILL }
            },

            { &hf_usb_vid_exten_guid,
                    { "guid", "usbvideo.extension.guid",
                            FT_GUID, BASE_NONE, NULL, 0,
                            "Identifier", HFILL }
            },

            { &hf_usb_vid_exten_num_controls,
                    { "bNumControls", "usbvideo.extension.numControls",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Number of controls", HFILL }
            },

        /***** Probe/Commit *****/

            { &hf_usb_vid_probe_hint,
                    { "bmHint", "usbvideo.probe.hint",
                            FT_UINT16, BASE_HEX, NULL, 0,
                            "Fields to hold constant during negotiation", HFILL }
            },

            { &hf_usb_vid_probe_hint_D[0],
                    { "dwFrameInterval", "usbvideo.probe.hint.D0",
                            FT_BOOLEAN, 5, TFS(&probe_hint_meaning), (1<<0),
                            "Frame Rate", HFILL }
            },
            { &hf_usb_vid_probe_hint_D[1],
                    { "wKeyFrameRate", "usbvideo.probe.hint.D1",
                            FT_BOOLEAN, 5, TFS(&probe_hint_meaning), (1<<1),
                            "Key Frame Rate", HFILL }
            },
            { &hf_usb_vid_probe_hint_D[2],
                    { "wPFrameRate", "usbvideo.probe.hint.D2",
                            FT_BOOLEAN, 5, TFS(&probe_hint_meaning), (1<<2),
                            "P-Frame Rate", HFILL }
            },
            { &hf_usb_vid_probe_hint_D[3],
                    { "wCompQuality", "usbvideo.probe.hint.D3",
                            FT_BOOLEAN, 5, TFS(&probe_hint_meaning), (1<<3),
                            "Compression Quality", HFILL }
            },
            { &hf_usb_vid_probe_hint_D[4],
                    { "wCompWindowSize", "usbvideo.probe.hint.D4",
                            FT_BOOLEAN, 5, TFS(&probe_hint_meaning), (1<<4),
                            "Compression Window Size", HFILL }
            },

            { &hf_usb_vid_probe_key_frame_rate,
                    { "wKeyFrameRate", "usbvideo.probe.keyFrameRate",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Key frame rate", HFILL }
            },

            { &hf_usb_vid_probe_p_frame_rate,
                    { "wPFrameRate", "usbvideo.probe.pFrameRate",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "P frame rate", HFILL }
            },

            { &hf_usb_vid_probe_comp_quality,
                    { "wCompQuality", "usbvideo.probe.compQuality",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Compression quality [0-10000]", HFILL }
            },

            { &hf_usb_vid_probe_comp_window,
                    { "wCompWindow", "usbvideo.probe.compWindow",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Window size for average bit rate control", HFILL }
            },
            { &hf_usb_vid_probe_delay,
                    { "wDelay", "usbvideo.probe.delay",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Latency in ms from capture to USB", HFILL }
            },
            { &hf_usb_vid_probe_max_frame_sz,
                    { "dwMaxVideoFrameSize", "usbvideo.probe.maxVideoFrameSize",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            NULL, HFILL }
            },
            { &hf_usb_vid_probe_max_payload_sz,
                    { "dwMaxPayloadTransferSize", "usbvideo.probe.maxPayloadTransferSize",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            NULL, HFILL }
            },
            { &hf_usb_vid_probe_clock_freq,
                    { "dwClockFrequency", "usbvideo.probe.clockFrequency",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Device clock frequency in Hz", HFILL }
            },

            { &hf_usb_vid_probe_framing,
                    { "bmFramingInfo", "usbvideo.probe.framing",
                            FT_UINT8, BASE_HEX, NULL, 0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_probe_framing_D[0],
                    { "Frame ID required", "usbvideo.probe.framing.D0",
                            FT_BOOLEAN, 2, TFS(&tfs_yes_no), (1<<0),
                            NULL, HFILL }
            },
            { &hf_usb_vid_probe_framing_D[1],
                    { "EOF utilized", "usbvideo.probe.framing.D1",
                            FT_BOOLEAN, 2, TFS(&tfs_yes_no), (1<<1),
                            NULL, HFILL }
            },

            { &hf_usb_vid_probe_preferred_ver,
                    { "bPreferredVersion", "usbvideo.probe.preferredVersion",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Preferred payload format version", HFILL }
            },
            { &hf_usb_vid_probe_min_ver,
                    { "bMinVersion", "usbvideo.probe.minVersion",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Min supported payload format version", HFILL }
            },
            { &hf_usb_vid_probe_max_ver,
                    { "bMaxVersion", "usbvideo.probe.maxVer",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Max supported payload format version", HFILL }
            },

            { &hf_usb_vid_control_ifdesc_dwClockFrequency,
                    { "dwClockFrequency", "usbvideo.probe.clockFrequency",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Device clock frequency (Hz) for selected format", HFILL }
            },

        /***** Format Descriptors *****/

            { &hf_usb_vid_format_index,
                    { "bFormatIndex", "usbvideo.format.index",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Index of this format descriptor", HFILL }
            },

            { &hf_usb_vid_format_num_frame_descriptors,
                    { "bNumFrameDescriptors", "usbvideo.format.numFrameDescriptors",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Number of frame descriptors for this format", HFILL }
            },

            { &hf_usb_vid_format_guid,
                    { "guidFormat", "usbvideo.format.guid",
                            FT_GUID, BASE_NONE, NULL, 0,
                            "Stream encoding format", HFILL }
            },

            { &hf_usb_vid_format_bits_per_pixel,
                    { "bBitsPerPixel", "usbvideo.format.bitsPerPixel",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Bits per pixel", HFILL }
            },

            { &hf_usb_vid_default_frame_index,
                    { "bDefaultFrameIndex", "usbvideo.format.defaultFrameIndex",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Optimum frame index for this stream", HFILL }
            },

            { &hf_usb_vid_aspect_ratio_x,
                    { "bAspectRatioX", "usbvideo.format.aspectRatioX",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "X dimension of picture aspect ratio", HFILL }
            },

            { &hf_usb_vid_aspect_ratio_y,
                    { "bAspectRatioY", "usbvideo.format.aspectRatioY",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Y dimension of picture aspect ratio", HFILL }
            },

            { &hf_usb_vid_interlace_flags,
                    { "bmInterlaceFlags", "usbvideo.format.interlace",
                            FT_UINT8, BASE_HEX, NULL, 0x0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_is_interlaced,
                    { "Interlaced stream", "usbvideo.format.interlace.D0",
                            FT_BOOLEAN, 8, TFS(&is_interlaced_meaning), (1<<0),
                            NULL, HFILL }
            },

            { &hf_usb_vid_interlaced_fields,
                    { "Fields per frame", "usbvideo.format.interlace.D1",
                            FT_BOOLEAN, 8, TFS(&interlaced_fields_meaning), (1<<1),
                            NULL, HFILL }
            },

            { &hf_usb_vid_field_1_first,
                    { "Field 1 first", "usbvideo.format.interlace.D2",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<2),
                            NULL, HFILL }
            },

            { &hf_usb_vid_field_pattern,
                    { "Field pattern", "usbvideo.format.interlace.pattern",
                            FT_UINT8, BASE_DEC | BASE_EXT_STRING,
                            &field_pattern_meaning_ext, (3<<4),
                            NULL, HFILL }
            },

            { &hf_usb_vid_copy_protect,
                    { "bCopyProtect", "usbvideo.format.copyProtect",
                            FT_UINT8, BASE_DEC, VALS(copy_protect_meaning), 0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_variable_size,
                    { "Variable size", "usbvideo.format.variableSize",
                            FT_BOOLEAN, BASE_DEC, NULL, 0,
                            NULL, HFILL }
            },

        /***** MJPEG Format Descriptor *****/

            { &hf_usb_vid_mjpeg_flags,
                    { "bmFlags", "usbvideo.mjpeg.flags",
                            FT_UINT8, BASE_HEX, NULL, 0,
                            "Characteristics", HFILL }
            },

            { &hf_usb_vid_mjpeg_fixed_samples,
                    { "Fixed size samples", "usbvideo.mjpeg.fixed_size",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<0),
                            NULL, HFILL }
            },

        /***** Frame Descriptors *****/

            { &hf_usb_vid_frame_index,
                    { "bFrameIndex", "usbvideo.frame.index",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Index of this frame descriptor", HFILL }
            },

            { &hf_usb_vid_frame_capabilities,
                    { "bmCapabilities", "usbvideo.frame.capabilities",
                            FT_UINT8, BASE_HEX, NULL, 0,
                            "Capabilities", HFILL }
            },

            { &hf_usb_vid_frame_stills_supported,
                    { "Still image", "usbvideo.frame.stills",
                            FT_BOOLEAN, 8, TFS(&tfs_supported_not_supported), (1<<0),
                            NULL, HFILL }
            },

            { &hf_usb_vid_frame_interval,
                    { "dwFrameInterval", "usbvideo.frame.interval",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Frame interval multiple of 100 ns", HFILL }
            },

            { &hf_usb_vid_frame_fixed_frame_rate,
                    { "Fixed frame rate", "usbvideo.frame.fixedRate",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<1),
                            NULL, HFILL }
            },
            { &hf_usb_vid_frame_width,
                    { "wWidth", "usbvideo.frame.width",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Width of frame in pixels", HFILL }
            },
            { &hf_usb_vid_frame_height,
                    { "wHeight", "usbvideo.frame.height",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Height of frame in pixels", HFILL }
            },
            { &hf_usb_vid_frame_min_bit_rate,
                    { "dwMinBitRate", "usbvideo.frame.minBitRate",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Minimum bit rate in bps", HFILL }
            },
            { &hf_usb_vid_frame_max_bit_rate,
                    { "dwMaxBitRate", "usbvideo.frame.maxBitRate",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Maximum bit rate in bps", HFILL }
            },

            { &hf_usb_vid_frame_max_frame_sz,
                    { "dwMaxVideoFrameBufferSize", "usbvideo.frame.maxBuffer",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Maximum bytes per frame", HFILL }
            },
            { &hf_usb_vid_frame_default_interval,
                    { "dwDefaultFrameInterval", "usbvideo.frame.interval.default",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Suggested default", HFILL }
            },

            { &hf_usb_vid_frame_interval_type,
                    { "bFrameIntervalType", "usbvideo.frame.interval.type",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Frame rate control (continuous/discrete)", HFILL }
            },

            { &hf_usb_vid_frame_min_interval,
                    { "dwMinFrameInterval", "usbvideo.frame.interval.min",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Shortest frame interval (* 100 ns)", HFILL }
            },

            { &hf_usb_vid_frame_max_interval,
                    { "dwMaxFrameInterval", "usbvideo.frame.interval.max",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Longest frame interval (* 100 ns)", HFILL }
            },
            { &hf_usb_vid_frame_step_interval,
                    { "dwMinFrameInterval", "usbvideo.frame.interval.step",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Granularity of frame interval (* 100 ns)", HFILL }
            },

            { &hf_usb_vid_frame_bytes_per_line,
                    { "dwBytesPerLine", "usbvideo.frame.bytesPerLine",
                            FT_UINT32, BASE_DEC, NULL, 0,
                            "Fixed number of bytes per video line", HFILL }
            },

        /***** Colorformat Descriptor *****/

            { &hf_usb_vid_color_primaries,
                    { "bColorPrimaries", "usbvideo.color.primaries",
                            FT_UINT8, BASE_DEC | BASE_EXT_STRING,
                            &color_primaries_meaning_ext, 0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_transfer_characteristics,
                    { "bTransferCharacteristics", "usbvideo.color.transferCharacteristics",
                            FT_UINT8, BASE_DEC | BASE_EXT_STRING,
                            &color_transfer_characteristics_ext, 0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_matrix_coefficients,
                    { "bMatrixCoefficients", "usbvideo.color.matrixCoefficients",
                            FT_UINT8, BASE_DEC | BASE_EXT_STRING,
                            &matrix_coefficients_meaning_ext, 0,
                            NULL, HFILL }
            },

        /***** Video Control Header Descriptor *****/

            { &hf_usb_vid_control_ifdesc_bcdUVC,
                    { "bcdUVC", "usbvideo.bcdUVC",
                            FT_UINT16, BASE_HEX, NULL, 0,
                            "Video Device Class Specification release number", HFILL }
            },

            { &hf_usb_vid_control_ifdesc_bInCollection,
                    { "bInCollection", "usbvideo.numStreamingInterfaces",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Number of VideoStreaming interfaces", HFILL }
            },

            { &hf_usb_vid_control_ifdesc_baInterfaceNr,
                    { "baInterfaceNr", "usbvideo.streamingInterfaceNumbers",
                            FT_BYTES, BASE_NONE, NULL, 0,
                            "Interface numbers of VideoStreaming interfaces", HFILL }},

        /***** Video Streaming Input Header Descriptor *****/

            { &hf_usb_vid_streaming_ifdesc_bNumFormats,
                    { "bNumFormats", "usbvideo.streaming.numFormats",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Number of video payload format descriptors", HFILL }
            },

            { &hf_usb_vid_streaming_bmInfo,
                    { "bmInfo", "usbvideo.streaming.info",
                            FT_UINT8, BASE_HEX, NULL, 0,
                            "Capabilities", HFILL }
            },

            { &hf_usb_vid_streaming_info_D[0],
                    { "Dynamic Format Change", "usbvideo.streaming.info.D0",
                            FT_BOOLEAN, 8, TFS(&tfs_yes_no), (1<<0),
                            "Dynamic Format Change", HFILL }
            },

            { &hf_usb_vid_streaming_control_D[0],
                    { "wKeyFrameRate", "usbvideo.streaming.control.D0",
                            FT_BOOLEAN, 6, TFS(&tfs_yes_no), (1<<0),
                            "Probe and Commit support", HFILL }
            },

            { &hf_usb_vid_streaming_control_D[1],
                    { "wPFrameRate", "usbvideo.streaming.control.D1",
                            FT_BOOLEAN, 6, TFS(&tfs_yes_no), (1<<1),
                            "Probe and Commit support", HFILL }
            },

            { &hf_usb_vid_streaming_control_D[2],
                    { "wCompQuality", "usbvideo.streaming.control.D2",
                            FT_BOOLEAN, 6, TFS(&tfs_yes_no), (1<<2),
                            "Probe and Commit support", HFILL }
            },

            { &hf_usb_vid_streaming_control_D[3],
                    { "wCompWindowSize", "usbvideo.streaming.control.D3",
                            FT_BOOLEAN, 6, TFS(&tfs_yes_no), (1<<3),
                            "Probe and Commit support", HFILL }
            },

            { &hf_usb_vid_streaming_control_D[4],
                    { "Generate Key Frame", "usbvideo.streaming.control.D4",
                            FT_BOOLEAN, 6, TFS(&tfs_yes_no), (1<<4),
                            "Probe and Commit support", HFILL }
            },

            { &hf_usb_vid_streaming_control_D[5],
                    { "Update Frame Segment", "usbvideo.streaming.control.D5",
                            FT_BOOLEAN, 6, TFS(&tfs_yes_no), (1<<5),
                            "Probe and Commit support", HFILL }
            },

            { &hf_usb_vid_streaming_terminal_link,
                    { "bTerminalLink", "usbvideo.streaming.terminalLink", FT_UINT8, BASE_DEC, NULL, 0x0,
                            "Output terminal ID", HFILL }
            },

            { &hf_usb_vid_streaming_still_capture_method,
                    { "bStillCaptureMethod", "usbvideo.streaming.stillCaptureMethod",
                            FT_UINT8, BASE_DEC | BASE_EXT_STRING,
                            &vs_still_capture_methods_ext, 0,
                            "Method of Still Image Capture", HFILL }
            },

            { &hf_usb_vid_streaming_trigger_support,
                    { "HW Triggering", "usbvideo.streaming.triggerSupport",
                            FT_BOOLEAN, BASE_DEC, TFS(&tfs_supported_not_supported), 0,
                            "Is HW triggering supported", HFILL }
            },

            { &hf_usb_vid_streaming_trigger_usage,
                    { "bTriggerUsage", "usbvideo.streaming.triggerUsage",
                            FT_UINT8, BASE_DEC, VALS(vs_trigger_usage), 0,
                            "How host SW should respond to trigger", HFILL }
            },

        /***** Interrupt URB *****/

            { &hf_usb_vid_interrupt_bStatusType,
                    { "Status Type", "usbvideo.interrupt.statusType",
                            FT_UINT8, BASE_HEX, VALS(interrupt_status_types), 0xF,
                            NULL, HFILL }
            },

            { &hf_usb_vid_interrupt_bAttribute,
                    { "Change Type", "usbvideo.interrupt.attribute",
                            FT_UINT8, BASE_HEX | BASE_EXT_STRING,
                            &control_change_types_ext, 0,
                            "Type of control change", HFILL }
            },

            { &hf_usb_vid_interrupt_bOriginator,
                    { "Originator", "usbvideo.interrupt.originator",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "ID of the entity that reports this interrupt", HFILL }
            },

            { &hf_usb_vid_control_interrupt_bEvent,
                    { "Event", "usbvideo.interrupt.controlEvent",
                            FT_UINT8, BASE_HEX, VALS(control_interrupt_events), 0,
                            "Type of event", HFILL }
            },

        /***** Video Control Endpoint Descriptor *****/

            { &hf_usb_vid_epdesc_subtype,
                    { "Subtype", "usbvideo.ep.descriptorSubType",
                            FT_UINT8, BASE_DEC, VALS(vc_ep_descriptor_subtypes), 0,
                            "Descriptor Subtype", HFILL }
            },

            { &hf_usb_vid_epdesc_max_transfer_sz,
                    { "wMaxTransferSize", "usbvideo.ep.maxInterruptSize", FT_UINT16,
                      BASE_DEC, NULL, 0x0, "Max interrupt structure size", HFILL }
            },

        /***** Fields used in multiple contexts *****/

            { &hf_usb_vid_ifdesc_wTotalLength,
                    { "wTotalLength", "usbvideo.totalLength",
                            FT_UINT16, BASE_DEC, NULL, 0,
                            "Video interface descriptor size", HFILL }
            },

            { &hf_usb_vid_bControlSize,
                    { "bControlSize", "usbvideo.bmcontrolSize",
                            FT_UINT8, BASE_DEC, NULL, 0,
                            "Size of bmControls field", HFILL }
            },

            { &hf_usb_vid_bmControl,
                    { "bmControl", "usbvideo.availableControls",
                            FT_UINT32, BASE_HEX, NULL, 0,
                            "Available controls", HFILL }
            },

            { &hf_usb_vid_bmControl_bytes,
                    { "bmControl", "usbvideo.availableControls.bytes",
                            FT_BYTES, BASE_NONE, NULL, 0,
                            "Available controls", HFILL }
            },

            { &hf_usb_vid_control_ifdesc_src_id,
                    { "bSourceID", "usbvideo.sourceID", FT_UINT8, BASE_DEC, NULL, 0x0,
                            "Entity to which this terminal/unit is connected", HFILL }
            },

        /**********/

            { &hf_usb_vid_control_ifdesc_subtype,
                    { "Subtype", "usbvideo.control.descriptorSubType",
                            FT_UINT8, BASE_DEC | BASE_EXT_STRING,
                            &vc_if_descriptor_subtypes_ext, 0,
                            "Descriptor Subtype", HFILL }
            },

            { &hf_usb_vid_streaming_ifdesc_subtype,
                    { "Subtype", "usbvideo.streaming.descriptorSubType",
                            FT_UINT8, BASE_DEC | BASE_EXT_STRING,
                            &vs_if_descriptor_subtypes_ext, 0,
                            "Descriptor Subtype", HFILL }
            },

            { &hf_usb_vid_descriptor_data,
                    { "Descriptor data", "usbvideo.descriptor_data", FT_BYTES, BASE_NONE, NULL, 0x0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_data,
                    { "Control data", "usbvideo.control_data", FT_BYTES, BASE_NONE, NULL, 0x0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_control_value,
                    { "Control value", "usbvideo.control_value", FT_BYTES, BASE_NONE, NULL, 0x0,
                            NULL, HFILL }
            },

            { &hf_usb_vid_value_data,
                    { "Value data", "usbvideo.value_data", FT_BYTES, BASE_NONE, NULL, 0x0,
                            NULL, HFILL }
            },
    };

    static gint *usb_vid_subtrees[] = {
            &ett_usb_vid,
            &ett_descriptor_video_endpoint,
            &ett_descriptor_video_control,
            &ett_descriptor_video_streaming,
            &ett_camera_controls,
            &ett_processing_controls,
            &ett_streaming_controls,
            &ett_streaming_info,
            &ett_interlace_flags,
            &ett_frame_capability_flags,
            &ett_mjpeg_flags,
            &ett_video_probe,
            &ett_probe_hint,
            &ett_probe_framing,
            &ett_video_standards,
            &ett_control_capabilities
    };

    static ei_register_info ei[] = {
        { &ei_usb_vid_subtype_unknown, { "usbvideo.subtype.unknown", PI_UNDECODED, PI_WARN, "Unknown VC subtype", EXPFILL }},
        { &ei_usb_vid_bitmask_len, { "usbvideo.bitmask_len_error", PI_UNDECODED, PI_WARN, "Only least-significant bytes decoded", EXPFILL }},
    };

    expert_module_t* expert_usb_vid;

    proto_usb_vid = proto_register_protocol("USB Video", "USBVIDEO", "usbvideo");
    proto_register_field_array(proto_usb_vid, hf, array_length(hf));
    proto_register_subtree_array(usb_vid_subtrees, array_length(usb_vid_subtrees));
    expert_usb_vid = expert_register_protocol(proto_usb_vid);
    expert_register_field_array(expert_usb_vid, ei, array_length(ei));

    usb_vid_control_handle = register_dissector("usbvideo.control", dissect_usb_vid_control, proto_usb_vid);
    usb_vid_descriptor_handle = register_dissector("usbvideo.descriptor", dissect_usb_vid_descriptor, proto_usb_vid);
    usb_vid_interrupt_handle = register_dissector("usbvideo.interrupt", dissect_usb_vid_interrupt, proto_usb_vid);
}

void
proto_reg_handoff_usb_vid(void)
{
    dissector_add_uint("usb.control", IF_CLASS_VIDEO, usb_vid_control_handle);
    dissector_add_uint("usb.descriptor", IF_CLASS_VIDEO, usb_vid_descriptor_handle);
    dissector_add_uint("usb.interrupt", IF_CLASS_VIDEO, usb_vid_interrupt_handle);
}
/*
 * 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:
 */