[PATCH v2 3/8] lsusb: Add code to dump descriptor data using descriptor definition.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This adds a new way of dumping descriptors.  It takes the descriptor
data to be dumped, and a descriptor definition as input.

The descriptor definition takes the form of a NULL terminated array
of descriptor field definitions.

These definitions describe how the raw descriptor data buffer should
be interpreted.

Thus the knowledge of how to interpret a descriptor buffer is separate
from the shared code that renders the descriptor dump.  This has two
advantages:

1. The code for dumping descriptors is common, so the output is easy
   to keep consistent.  It is also consistent and thorough in its
   handling of insufficient descriptor data buffer, and junk data
   at the end of a descriptor.

2. It is easy to add support for new descriptors, since they are now
   simple definitions that resemble the tables in the USB specifications.

Signed-off-by: Michael Drake <michael.drake@xxxxxxxxxxxxxxx>
---
 desc-dump.c | 541 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 desc-dump.h |  64 +++++++
 2 files changed, 605 insertions(+)
 create mode 100644 desc-dump.c
 create mode 100644 desc-dump.h

diff --git a/desc-dump.c b/desc-dump.c
new file mode 100644
index 0000000..711889b
--- /dev/null
+++ b/desc-dump.c
@@ -0,0 +1,541 @@
+/*****************************************************************************/
+
+/*
+ *      desc-dump.c  --  USB descriptor dumping
+ *
+ *      Copyright (C) 2017 Michael Drake <michael.drake@xxxxxxxxxxxxxxx>
+ *
+ *      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[0] != '\0') {
+			if (type == DESC_BMCONTROL_1) {
+				if ((strings[count][0] != '\0') &&
+				    (bmcontrols >> count) & 0x1) {
+					printf("%*s%s Control\n",
+							indent * 2, "",
+							strings[count]);
+				}
+			} else {
+				control = (bmcontrols >> (count * 2)) & 0x3;
+				if ((strings[count][0] != '\0') && 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_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");
+	}
+}
diff --git a/desc-dump.h b/desc-dump.h
new file mode 100644
index 0000000..b823357
--- /dev/null
+++ b/desc-dump.h
@@ -0,0 +1,64 @@
+/*****************************************************************************/
+
+/*
+ *      desc-dump.h  --  USB descriptor dumping
+ *
+ *      Copyright (C) 2017 Michael Drake <michael.drake@xxxxxxxxxxxxxxx>
+ *
+ *      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.
+ *
+ */
+
+/*****************************************************************************/
+
+#ifndef _DESC_DUMP_H
+#define _DESC_DUMP_H
+
+/* ---------------------------------------------------------------------- */
+
+/**
+ * Buffer length value indicating that the buffer length should be
+ * read from the value of the first field in the buffer, as defined
+ * by the first field descriptor definition.
+ */
+#define DESC_BUF_LEN_FROM_BUF 0xffffffff
+
+/**
+ * Dump descriptor using a descriptor definition array.
+ *
+ * This function dumps the USB descriptor data given in the byte array,
+ * `buf`, according to the descriptor definition array given in `desc`.
+ *
+ * The first byte(s) of `buf` must correspond to the first field definition
+ * in the `desc` descriptor definition array.
+ *
+ * \param[in] dev     LibUSB device handle.
+ * \param[in] desc    Array of descriptor field definitions to use to interpret
+ *                    `buf`.  This array constitutes the descriptor definition.
+ *                    The final entry in the array must have a NULL field name,
+ *                    which is interpreted as the end of the array.
+ * \param[in] buf     Byte array containing the descriptor data to dump.
+ * \param[in] buf_len Byte length of `buf` or `DESC_BUF_LEN_FROM_BUF` to get
+ *                    the length from the value of the first field in the
+ *                    descriptor data.
+ * \param[in] indent  Indent level to use for descriptor dump.
+ */
+extern void desc_dump(
+		libusb_device_handle *dev,
+		const struct desc *desc,
+		const unsigned char *buf,
+		unsigned int buf_len,
+		unsigned int indent);
+
+
+/* ---------------------------------------------------------------------- */
+
+#endif /* _DESC_DUMP_H */
-- 
2.11.0

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux