mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usbutils.git
synced 2024-11-15 06:53:43 +08:00
969054759a
This squashes "warning: comparison between pointer and zero character" This was an empty string check that was checking the pointer rather than the first character. The check was done correctly before the string was used, so here we yank the correct check up, to the upper level, replacing the ineffectual/broken one. Signed-off-by: Michael Drake <michael.drake@codethink.co.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
546 lines
16 KiB
C
546 lines
16 KiB
C
/*****************************************************************************/
|
|
|
|
/*
|
|
* desc-dump.c -- USB descriptor dumping
|
|
*
|
|
* Copyright (C) 2017 Michael Drake <michael.drake@codethink.co.uk>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
/*****************************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include <libusb.h>
|
|
|
|
#include "desc-defs.h"
|
|
#include "desc-dump.h"
|
|
#include "usbmisc.h"
|
|
#include "names.h"
|
|
|
|
/**
|
|
* Print a description of a bmControls field value, using a given string array.
|
|
*
|
|
* Handles the DESC_BMCONTROL_1 and DESC_BMCONTROL_2 field types. The former
|
|
* is one bit per string, and the latter is 2 bits per string, with the
|
|
* additional bit specifying whether the control is read-only.
|
|
*
|
|
* \param[in] bmcontrols The value to dump a human-readable representation of.
|
|
* \param[in] strings Array of human-readable strings, must be NULL terminated.
|
|
* \param[in] type The type of the value in bmcontrols.
|
|
* \param[in] indent The current indent level.
|
|
*/
|
|
static void desc_bmcontrol_dump(
|
|
unsigned long long bmcontrols,
|
|
const char * const * strings,
|
|
enum desc_type type,
|
|
unsigned int indent)
|
|
{
|
|
static const char * const setting[] = {
|
|
"read-only",
|
|
"ILLEGAL VALUE (0b10)",
|
|
"read/write"
|
|
};
|
|
unsigned int count = 0;
|
|
unsigned int control;
|
|
|
|
assert((type == DESC_BMCONTROL_1) ||
|
|
(type == DESC_BMCONTROL_2));
|
|
|
|
while (strings[count] != NULL) {
|
|
if (strings[count][0] != '\0') {
|
|
if (type == DESC_BMCONTROL_1) {
|
|
if ((bmcontrols >> count) & 0x1) {
|
|
printf("%*s%s Control\n",
|
|
indent * 2, "",
|
|
strings[count]);
|
|
}
|
|
} else {
|
|
control = (bmcontrols >> (count * 2)) & 0x3;
|
|
if (control) {
|
|
printf("%*s%s Control (%s)\n",
|
|
indent * 2, "",
|
|
strings[count],
|
|
setting[control-1]);
|
|
}
|
|
}
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read N bytes from descriptor data buffer into a value.
|
|
*
|
|
* Only supports values of up to 8 bytes.
|
|
*
|
|
* \param[in] buf Buffer containing the bytes to read.
|
|
* \param[in] offset Offset in buffer to start reading bytes from.
|
|
* \param[in] bytes Number of bytes to read.
|
|
* \return Value contained within the given bytes.
|
|
*/
|
|
static unsigned long long get_n_bytes_as_ull(
|
|
const unsigned char *buf,
|
|
unsigned int offset,
|
|
unsigned int bytes)
|
|
{
|
|
unsigned long long ret = 0;
|
|
|
|
if (bytes > 8) {
|
|
fprintf(stderr, "Bad descriptor definition; Field size > 8.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
buf += offset;
|
|
|
|
switch (bytes) {
|
|
case 8: ret |= ((unsigned long long)buf[7]) << 56; /* fall-through */
|
|
case 7: ret |= ((unsigned long long)buf[6]) << 48; /* fall-through */
|
|
case 6: ret |= ((unsigned long long)buf[5]) << 40; /* fall-through */
|
|
case 5: ret |= ((unsigned long long)buf[4]) << 32; /* fall-through */
|
|
case 4: ret |= ((unsigned long long)buf[3]) << 24; /* fall-through */
|
|
case 3: ret |= ((unsigned long long)buf[2]) << 16; /* fall-through */
|
|
case 2: ret |= ((unsigned long long)buf[1]) << 8; /* fall-through */
|
|
case 1: ret |= ((unsigned long long)buf[0]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Dump a number as hex to stdout.
|
|
*
|
|
* \param[in] buf Descriptor buffer to get values to render from.
|
|
* \param[in] width Character width to right-align value inside.
|
|
* \param[in] offset Offset in buffer to start of value to render.
|
|
* \param[in] bytes Byte length of value to render.
|
|
*/
|
|
static void hex_renderer(
|
|
const unsigned char *buf,
|
|
unsigned int width,
|
|
unsigned int offset,
|
|
unsigned int bytes)
|
|
{
|
|
unsigned int align = (width >= bytes * 2) ? width - bytes * 2 : 0;
|
|
printf(" %*s0x%0*llx", align, "", bytes * 2,
|
|
get_n_bytes_as_ull(buf, offset, bytes));
|
|
}
|
|
|
|
/**
|
|
* Dump a number to stdout.
|
|
*
|
|
* Single-byte numbers a rendered as decimal, otherwise hexadecimal is used.
|
|
*
|
|
* \param[in] buf Descriptor buffer to get values to render from.
|
|
* \param[in] width Character width to right-align value inside.
|
|
* \param[in] offset Offset in buffer to start of value to render.
|
|
* \param[in] bytes Byte length of value to render.
|
|
*/
|
|
static void number_renderer(
|
|
const unsigned char *buf,
|
|
unsigned int width,
|
|
unsigned int offset,
|
|
unsigned int bytes)
|
|
{
|
|
if (bytes == 1) {
|
|
/* Render small numbers as decimal */
|
|
printf(" %*u", width, buf[offset]);
|
|
} else {
|
|
/* Otherwise render as hexadecimal */
|
|
hex_renderer(buf, width, offset, bytes);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render a field's value to stdout.
|
|
*
|
|
* The manner of rendering the value is dependant on the value type.
|
|
*
|
|
* \param[in] dev LibUSB device handle.
|
|
* \param[in] current Descriptor definition field to render.
|
|
* \param[in] current_size Descriptor definition field to render.
|
|
* \param[in] buf Byte array containing the descriptor date to dump.
|
|
* \param[in] indent Current indent level.
|
|
* \param[in] offset Offset to current value in `buf`.
|
|
*/
|
|
static void value_renderer(
|
|
libusb_device_handle *dev,
|
|
const struct desc *current,
|
|
unsigned int current_size,
|
|
const unsigned char *buf,
|
|
unsigned int indent,
|
|
size_t offset)
|
|
{
|
|
/** Maximum amount of characters to right align numerical values by. */
|
|
const unsigned int size_chars = 4;
|
|
|
|
switch (current->type) {
|
|
case DESC_NUMBER: /* fall-through */
|
|
case DESC_CONSTANT:
|
|
number_renderer(buf, size_chars, offset, current_size);
|
|
printf("\n");
|
|
break;
|
|
case DESC_NUMBER_POSTFIX:
|
|
number_renderer(buf, size_chars, offset, current_size);
|
|
printf("%s\n", current->number_postfix);
|
|
break;
|
|
case DESC_NUMBER_STRINGS: {
|
|
unsigned int i;
|
|
unsigned long long value = get_n_bytes_as_ull(buf, offset, current_size);
|
|
number_renderer(buf, size_chars, offset, current_size);
|
|
for (i = 0; i <= value; i++) {
|
|
if (current->number_strings[i] == NULL) {
|
|
break;
|
|
}
|
|
if (value == i) {
|
|
printf(" %s", current->number_strings[i]);
|
|
}
|
|
}
|
|
printf("\n");
|
|
break;
|
|
}
|
|
case DESC_BCD: {
|
|
unsigned int i;
|
|
printf(" %2x", buf[offset + current_size - 1]);
|
|
for (i = 1; i < current_size; i++) {
|
|
printf(".%02x", buf[offset + current_size - 1 - i]);
|
|
}
|
|
printf("\n");
|
|
break;
|
|
}
|
|
case DESC_BITMAP:
|
|
hex_renderer(buf, size_chars, offset, current_size);
|
|
printf("\n");
|
|
break;
|
|
case DESC_BMCONTROL_1: /* fall-through */
|
|
case DESC_BMCONTROL_2:
|
|
hex_renderer(buf, size_chars, offset, current_size);
|
|
printf("\n");
|
|
desc_bmcontrol_dump(
|
|
get_n_bytes_as_ull(buf, offset, current_size),
|
|
current->bmcontrol, current->type, indent + 1);
|
|
break;
|
|
case DESC_BITMAP_STRINGS: {
|
|
unsigned int i;
|
|
unsigned long long value = get_n_bytes_as_ull(buf, offset, current_size);
|
|
hex_renderer(buf, size_chars, offset, current_size);
|
|
printf("\n");
|
|
for (i = 0; i < current->bitmap_strings.count; i++) {
|
|
if (current->bitmap_strings.strings[i] == NULL) {
|
|
continue;
|
|
}
|
|
if (((value >> i) & 0x1) == 0) {
|
|
continue;
|
|
}
|
|
printf("%*s%s\n", (indent + 1) * 2, "",
|
|
current->bitmap_strings.strings[i]);
|
|
}
|
|
break;
|
|
}
|
|
case DESC_STR_DESC_INDEX: {
|
|
char *string;
|
|
number_renderer(buf, size_chars, offset, current_size);
|
|
string = get_dev_string(dev, buf[offset]);
|
|
if (string) {
|
|
printf(" %s\n", string);
|
|
free(string);
|
|
} else {
|
|
printf("\n");
|
|
}
|
|
break;
|
|
}
|
|
case DESC_CS_STR_DESC_ID:
|
|
number_renderer(buf, size_chars, offset, current_size);
|
|
/* TODO: Add support for UAC3 class-specific String descriptor */
|
|
printf("\n");
|
|
break;
|
|
case DESC_TERMINAL_STR:
|
|
number_renderer(buf, size_chars, offset, current_size);
|
|
printf(" %s\n", names_audioterminal(
|
|
get_n_bytes_as_ull(buf, offset, current_size)));
|
|
break;
|
|
case DESC_SNOWFLAKE:
|
|
number_renderer(buf, size_chars, offset, current_size);
|
|
current->snowflake(
|
|
get_n_bytes_as_ull(buf, offset, current_size),
|
|
indent + 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the size of a descriptor field in bytes.
|
|
*
|
|
* Normally the size is provided in the entry's size parameter, but some
|
|
* fields have a variable size, with the actual size being stored in as
|
|
* the value of another field.
|
|
*
|
|
* \param[in] buf Descriptor data.
|
|
* \param[in] desc First field in the descriptor definition array.
|
|
* \param[in] entry The descriptor definition field to get size for.
|
|
* \return Size of the field in bytes.
|
|
*/
|
|
static unsigned int get_entry_size(
|
|
const unsigned char *buf,
|
|
const struct desc *desc,
|
|
const struct desc *entry)
|
|
{
|
|
const struct desc *current;
|
|
unsigned int size = entry->size;
|
|
|
|
if (entry->size_field != NULL) {
|
|
/* Variable field length, given by `size_field`'s value. */
|
|
size_t offset = 0;
|
|
|
|
/* Search descriptor definition array for the field who's value
|
|
* gives the size of the entry we're interested in. */
|
|
for (current = desc; current->field != NULL; current++) {
|
|
if (strcmp(current->field, entry->size_field) == 0) {
|
|
/* Found the field who's value gives us the
|
|
* size of, so read that field's value out of
|
|
* the descriptor data buffer. */
|
|
size = get_n_bytes_as_ull(buf, offset,
|
|
current->size);
|
|
break;
|
|
}
|
|
|
|
/* Keep track of our offset in the descriptor data
|
|
* as we look for the field we want. */
|
|
offset += get_entry_size(buf, desc, current);
|
|
}
|
|
}
|
|
|
|
if (size == 0) {
|
|
fprintf(stderr, "Bad descriptor definition; "
|
|
"'%s' field has zero size.\n", entry->field);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* Get the number of entries needed by an descriptor definition array field.
|
|
*
|
|
* The number of entries is either calculated from length_field parameters,
|
|
* which indicate which other field(s) contain values representing the
|
|
* array length, or the array length is calculated from the buf_len parameter,
|
|
* which should ultimately have been derived from the bLength field in the raw
|
|
* descriptor data.
|
|
*
|
|
* \param[in] buf Descriptor data.
|
|
* \param[in] buf_len Byte length of `buf`.
|
|
* \param[in] desc First field in the descriptor definition.
|
|
* \param[in] array_entry Array field to get entry count for.
|
|
* \return Number of entries in array.
|
|
*/
|
|
static unsigned int get_array_entry_count(
|
|
const unsigned char *buf,
|
|
unsigned int buf_len,
|
|
const struct desc *desc,
|
|
const struct desc *array_entry)
|
|
{
|
|
const struct desc *current;
|
|
unsigned int entries = 0;
|
|
|
|
if (array_entry->array.length_field1) {
|
|
/* We can get the array size from the length_field1. */
|
|
size_t offset = 0;
|
|
for (current = desc; current->field != NULL; current++) {
|
|
if (strcmp(current->field, array_entry->array.length_field1) == 0) {
|
|
entries = get_n_bytes_as_ull(buf, offset, current->size);
|
|
break;
|
|
}
|
|
|
|
offset += get_entry_size(buf, desc, current);
|
|
}
|
|
offset = 0; /* skip first three common 1-byte fields */
|
|
if (array_entry->array.length_field2 != NULL) {
|
|
/* There's a second field specifying length. The two
|
|
* lengths are multiplied. */
|
|
for (current = desc; current->field != NULL; current++) {
|
|
if (strcmp(current->field, array_entry->array.length_field2) == 0) {
|
|
entries *= get_n_bytes_as_ull(buf, offset, current->size);
|
|
break;
|
|
}
|
|
|
|
offset += get_entry_size(buf, desc, current);
|
|
}
|
|
}
|
|
|
|
/* If the bits flag is set, then the entry count so far
|
|
* was a bit count, and we need to get a byte count. */
|
|
if (array_entry->array.bits) {
|
|
entries = (entries / 8) + (entries & 0x7) ? 1 : 0;
|
|
}
|
|
} else {
|
|
/* Inferred array length. We haven't been given a field to get
|
|
* length from; start with the descriptor's byte-length, and
|
|
* subtract the sizes of all the other fields. */
|
|
unsigned int size = buf_len;
|
|
|
|
for (current = desc; current->field != NULL; current++) {
|
|
if (current == array_entry)
|
|
continue;
|
|
|
|
if (current->array.array) {
|
|
unsigned int count;
|
|
/* We can't deal with two inferred-length arrays
|
|
* in one descriptor definition, because its
|
|
* an unresolvable ambiguity. If this
|
|
* happens it's a flaw in the descriptor
|
|
* definition. */
|
|
if (current->array.length_field1 == NULL) {
|
|
return 0xffffffff;
|
|
}
|
|
count = get_array_entry_count(buf, buf_len,
|
|
desc, current);
|
|
if (count == 0xffffffff) {
|
|
fprintf(stderr, "Bad descriptor definition; "
|
|
"multiple inferred-length arrays.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
size -= get_entry_size(buf, desc, current) *
|
|
count;
|
|
} else {
|
|
size -= get_entry_size(buf, desc, current);
|
|
}
|
|
}
|
|
|
|
entries = size / array_entry->size;
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
/**
|
|
* Get the number of characters needed to dump an array index
|
|
*
|
|
* \param[in] array_entries Number of entries in array.
|
|
* \return number of characters required to render largest possible index.
|
|
*/
|
|
static unsigned int get_char_count_for_array_index(unsigned int array_entries)
|
|
{
|
|
/* Arrays are zero-indexed, so largest index is array_entries - 1. */
|
|
if (array_entries > 100) {
|
|
/* [NNN] */
|
|
return 5;
|
|
} else if (array_entries > 10) {
|
|
/* [NN] */
|
|
return 4;
|
|
}
|
|
|
|
/* [N] */
|
|
return 3;
|
|
}
|
|
|
|
/* Function documented in desc-dump.h */
|
|
void desc_dump(
|
|
libusb_device_handle *dev,
|
|
const struct desc *desc,
|
|
const unsigned char *buf,
|
|
unsigned int buf_len,
|
|
unsigned int indent)
|
|
{
|
|
unsigned int entry;
|
|
unsigned int entries;
|
|
unsigned int needed_chars;
|
|
unsigned int current_size;
|
|
unsigned int field_len = 18;
|
|
const struct desc *current;
|
|
size_t offset = 0;
|
|
|
|
/* Find the buffer length, if we've been instructed to read it from
|
|
* the first field. */
|
|
if ((buf_len == DESC_BUF_LEN_FROM_BUF) && (desc != NULL)) {
|
|
buf_len = get_n_bytes_as_ull(buf, offset, desc->size);
|
|
}
|
|
|
|
/* Increase `field_len` to be sufficient for character length of
|
|
* longest field name for this descriptor. */
|
|
for (current = desc; current->field != NULL; current++) {
|
|
needed_chars = 0;
|
|
if (current->array.array) {
|
|
entries = get_array_entry_count(buf, buf_len,
|
|
desc, current);
|
|
needed_chars = get_char_count_for_array_index(entries);
|
|
}
|
|
if (strlen(current->field) + needed_chars > field_len) {
|
|
field_len = strlen(current->field) + needed_chars;
|
|
}
|
|
}
|
|
|
|
/* Step through each field, and dump it. */
|
|
for (current = desc; current->field != NULL; current++) {
|
|
entries = 1;
|
|
if (current->array.array) {
|
|
/* Array type fields may have more than one entry. */
|
|
entries = get_array_entry_count(buf, buf_len,
|
|
desc, current);
|
|
}
|
|
|
|
current_size = get_entry_size(buf, desc, current);
|
|
|
|
for (entry = 0; entry < entries; entry++) {
|
|
/* Check there's enough data in buf for this entry. */
|
|
if (offset + current_size > buf_len) {
|
|
unsigned int i;
|
|
printf("%*sWarning: Length insufficient for "
|
|
"descriptor type.\n",
|
|
(indent - 1) * 2, "");
|
|
for (i = offset; i < buf_len; i++) {
|
|
printf("%02x ", buf[i]);
|
|
}
|
|
printf("\n");
|
|
return;
|
|
}
|
|
/* Dump the field name */
|
|
if (current->array.array) {
|
|
needed_chars = field_len -
|
|
get_char_count_for_array_index(
|
|
entries) -
|
|
strlen(current->field);
|
|
printf("%*s%s(%u)%*s", indent * 2, "",
|
|
current->field, entry,
|
|
needed_chars, "");
|
|
} else {
|
|
printf("%*s%-*s", indent * 2, "",
|
|
field_len, current->field);
|
|
}
|
|
/* Dump the value */
|
|
value_renderer(dev, current, current_size, buf,
|
|
indent, offset);
|
|
/* Advance offset in buffer */
|
|
offset += current_size;
|
|
}
|
|
}
|
|
|
|
/* Check for junk at end of descriptor. */
|
|
if (offset < buf_len) {
|
|
unsigned int i;
|
|
printf("%*sWarning: Junk at end of descriptor (%zu bytes):\n",
|
|
(indent - 1) * 2, "", buf_len - offset);
|
|
printf("%*s", indent * 2, "");
|
|
for (i = offset; i < buf_len; i++) {
|
|
printf("%02x ", buf[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|