root/lib/libusb/libusb20_desc.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef LIBUSB_GLOBAL_INCLUDE_FILE
#include LIBUSB_GLOBAL_INCLUDE_FILE
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/queue.h>
#endif

#include "libusb20.h"
#include "libusb20_desc.h"
#include "libusb20_int.h"

static const uint32_t libusb20_me_encode_empty[2];      /* dummy */

LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_DEVICE_DESC);
LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_ENDPOINT_DESC);
LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_INTERFACE_DESC);
LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_CONFIG_DESC);
LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_CONTROL_SETUP);
LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_SS_ENDPT_COMP_DESC);
LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_USB_20_DEVCAP_DESC);
LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_SS_USB_DEVCAP_DESC);
LIBUSB20_MAKE_STRUCT_FORMAT(LIBUSB20_BOS_DESCRIPTOR);

/*------------------------------------------------------------------------*
 *      libusb20_parse_config_desc
 *
 * Return values:
 * NULL: Out of memory.
 * Else: A valid config structure pointer which must be passed to "free()"
 *------------------------------------------------------------------------*/
struct libusb20_config *
libusb20_parse_config_desc(const void *config_desc)
{
        struct libusb20_config *lub_config;
        struct libusb20_interface *lub_interface;
        struct libusb20_interface *lub_alt_interface;
        struct libusb20_interface *last_if;
        struct libusb20_endpoint *lub_endpoint;
        struct libusb20_endpoint *last_ep;

        struct libusb20_me_struct pcdesc;
        const uint8_t *ptr;
        uint32_t size;
        uint16_t niface_no_alt;
        uint16_t niface;
        uint16_t nendpoint;
        uint16_t iface_no;

        ptr = config_desc;
        if (ptr[1] != LIBUSB20_DT_CONFIG) {
                return (NULL);          /* not config descriptor */
        }

        /*
         * The first "bInterfaceNumber" cannot start at 0xFFFF
         * because the field is 8-bit.
         */
        niface_no_alt = 0;
        nendpoint = 0;
        niface = 0;
        iface_no = 0xFFFF;
        ptr = NULL;

        /* get "wTotalLength" and setup "pcdesc" */
        pcdesc.ptr = LIBUSB20_ADD_BYTES(config_desc, 0);
        pcdesc.len =
            ((const uint8_t *)config_desc)[2] |
            (((const uint8_t *)config_desc)[3] << 8);
        pcdesc.type = LIBUSB20_ME_IS_RAW;

        /* descriptor pre-scan */
        while ((ptr = libusb20_desc_foreach(&pcdesc, ptr))) {
                if (ptr[1] == LIBUSB20_DT_ENDPOINT) {
                        nendpoint++;
                } else if ((ptr[1] == LIBUSB20_DT_INTERFACE) && (ptr[0] >= 4)) {
                        niface++;
                        /* check "bInterfaceNumber" */
                        if (ptr[2] != iface_no) {
                                iface_no = ptr[2];
                                niface_no_alt++;
                        }
                }
        }

        /* sanity checking */
        if (niface >= 256) {
                return (NULL);          /* corrupt */
        }
        if (nendpoint >= 256) {
                return (NULL);          /* corrupt */
        }
        size = sizeof(*lub_config) +
            (niface * sizeof(*lub_interface)) +
            (nendpoint * sizeof(*lub_endpoint)) +
            pcdesc.len;

        lub_config = malloc(size);
        if (lub_config == NULL) {
                return (NULL);          /* out of memory */
        }
        /* make sure memory is initialised */
        memset(lub_config, 0, size);

        lub_interface = (void *)(lub_config + 1);
        lub_alt_interface = (void *)(lub_interface + niface_no_alt);
        lub_endpoint = (void *)(lub_interface + niface);

        /*
         * Make a copy of the config descriptor, so that the caller can free
         * the initial config descriptor pointer!
         */
        memcpy((void *)(lub_endpoint + nendpoint), config_desc, pcdesc.len);

        ptr = (const void *)(lub_endpoint + nendpoint);
        pcdesc.ptr = LIBUSB20_ADD_BYTES(ptr, 0);

        /* init config structure */

        LIBUSB20_INIT(LIBUSB20_CONFIG_DESC, &lub_config->desc);

        if (libusb20_me_decode(ptr, ptr[0], &lub_config->desc)) {
                /* ignore */
        }
        lub_config->num_interface = 0;
        lub_config->interface = lub_interface;
        lub_config->extra.ptr = LIBUSB20_ADD_BYTES(ptr, ptr[0]);
        lub_config->extra.len = -ptr[0];
        lub_config->extra.type = LIBUSB20_ME_IS_RAW;

        /* reset states */
        niface = 0;
        iface_no = 0xFFFF;
        ptr = NULL;
        lub_interface--;
        lub_endpoint--;
        last_if = NULL;
        last_ep = NULL;

        /* descriptor pre-scan */
        while ((ptr = libusb20_desc_foreach(&pcdesc, ptr))) {
                if (ptr[1] == LIBUSB20_DT_ENDPOINT) {
                        if (last_if) {
                                lub_endpoint++;
                                last_ep = lub_endpoint;
                                last_if->num_endpoints++;

                                LIBUSB20_INIT(LIBUSB20_ENDPOINT_DESC, &last_ep->desc);

                                if (libusb20_me_decode(ptr, ptr[0], &last_ep->desc)) {
                                        /* ignore */
                                }
                                last_ep->extra.ptr = LIBUSB20_ADD_BYTES(ptr, ptr[0]);
                                last_ep->extra.len = 0;
                                last_ep->extra.type = LIBUSB20_ME_IS_RAW;
                        } else {
                                lub_config->extra.len += ptr[0];
                        }

                } else if ((ptr[1] == LIBUSB20_DT_INTERFACE) && (ptr[0] >= 4)) {
                        if (ptr[2] != iface_no) {
                                /* new interface */
                                iface_no = ptr[2];
                                lub_interface++;
                                lub_config->num_interface++;
                                last_if = lub_interface;
                                niface++;
                        } else {
                                /* one more alternate setting */
                                lub_interface->num_altsetting++;
                                last_if = lub_alt_interface;
                                lub_alt_interface++;
                        }

                        LIBUSB20_INIT(LIBUSB20_INTERFACE_DESC, &last_if->desc);

                        if (libusb20_me_decode(ptr, ptr[0], &last_if->desc)) {
                                /* ignore */
                        }

                        /* detect broken USB descriptors when USB debugging is enabled */
                        if (last_if->desc.bInterfaceNumber != (uint8_t)(niface - 1)) {
                                const char *str = getenv("LIBUSB_DEBUG");
                                if (str != NULL && str[0] != '\0' && str[0] != '0') {
                                        printf("LIBUSB_DEBUG: bInterfaceNumber(%u) is not sequential(%u)\n",
                                            last_if->desc.bInterfaceNumber, niface - 1);
                                }
                        }
                        last_if->extra.ptr = LIBUSB20_ADD_BYTES(ptr, ptr[0]);
                        last_if->extra.len = 0;
                        last_if->extra.type = LIBUSB20_ME_IS_RAW;
                        last_if->endpoints = lub_endpoint + 1;
                        last_if->altsetting = lub_alt_interface;
                        last_if->num_altsetting = 0;
                        last_if->num_endpoints = 0;
                        last_ep = NULL;
                } else {
                        /* unknown descriptor */
                        if (last_if) {
                                if (last_ep) {
                                        last_ep->extra.len += ptr[0];
                                } else {
                                        last_if->extra.len += ptr[0];
                                }
                        } else {
                                lub_config->extra.len += ptr[0];
                        }
                }
        }
        return (lub_config);
}

/*------------------------------------------------------------------------*
 *      libusb20_desc_foreach
 *
 * Safe traversal of USB descriptors.
 *
 * Return values:
 * NULL: End of descriptors
 * Else: Pointer to next descriptor
 *------------------------------------------------------------------------*/
const uint8_t *
libusb20_desc_foreach(const struct libusb20_me_struct *pdesc,
    const uint8_t *psubdesc)
{
        const uint8_t *start;
        const uint8_t *end;
        const uint8_t *desc_next;

        /* be NULL safe */
        if (pdesc == NULL)
                return (NULL);

        start = (const uint8_t *)pdesc->ptr;
        end = LIBUSB20_ADD_BYTES(start, pdesc->len);

        /* get start of next descriptor */
        if (psubdesc == NULL)
                psubdesc = start;
        else
                psubdesc = psubdesc + psubdesc[0];

        /* check that the next USB descriptor is within the range */
        if ((psubdesc < start) || (psubdesc >= end))
                return (NULL);          /* out of range, or EOD */

        /* check start of the second next USB descriptor, if any */
        desc_next = psubdesc + psubdesc[0];
        if ((desc_next < start) || (desc_next > end))
                return (NULL);          /* out of range */

        /* check minimum descriptor length */
        if (psubdesc[0] < 3)
                return (NULL);          /* too short descriptor */

        return (psubdesc);              /* return start of next descriptor */
}

/*------------------------------------------------------------------------*
 *      libusb20_me_get_1 - safety wrapper to read out one byte
 *------------------------------------------------------------------------*/
uint8_t
libusb20_me_get_1(const struct libusb20_me_struct *ie, uint16_t offset)
{
        if (offset < ie->len) {
                return (*((uint8_t *)LIBUSB20_ADD_BYTES(ie->ptr, offset)));
        }
        return (0);
}

/*------------------------------------------------------------------------*
 *      libusb20_me_get_2 - safety wrapper to read out one word
 *------------------------------------------------------------------------*/
uint16_t
libusb20_me_get_2(const struct libusb20_me_struct *ie, uint16_t offset)
{
        return (libusb20_me_get_1(ie, offset) |
            (libusb20_me_get_1(ie, offset + 1) << 8));
}

/*------------------------------------------------------------------------*
 *      libusb20_me_encode - encode a message structure
 *
 * Description of parameters:
 * "len" - maximum length of output buffer
 * "ptr" - pointer to output buffer. If NULL, no data will be written
 * "pd" - source structure
 *
 * Return values:
 * 0..65535 - Number of bytes used, limited by the "len" input parameter.
 *------------------------------------------------------------------------*/
uint16_t
libusb20_me_encode(void *ptr, uint16_t len, const void *pd)
{
        const uint8_t *pf;              /* pointer to format data */
        uint8_t *buf;                   /* pointer to output buffer */

        uint32_t pd_offset;             /* decoded structure offset */
        uint16_t len_old;               /* old length */
        uint16_t pd_count;              /* decoded element count */
        uint8_t me;                     /* message element */

        /* initialise */

        len_old = len;
        buf = ptr;
        pd_offset = sizeof(void *);
        pf = (*((struct libusb20_me_format *const *)pd))->format;

        /* scan */

        while (1) {

                /* get information element */

                me = (pf[0]) & LIBUSB20_ME_MASK;
                pd_count = pf[1] | (pf[2] << 8);
                pf += 3;

                /* encode the message element */

                switch (me) {
                case LIBUSB20_ME_INT8:
                        while (pd_count--) {
                                uint8_t temp;

                                if (len < 1)    /* overflow */
                                        goto done;
                                if (buf) {
                                        temp = *((const uint8_t *)
                                            LIBUSB20_ADD_BYTES(pd, pd_offset));
                                        buf[0] = temp;
                                        buf += 1;
                                }
                                pd_offset += 1;
                                len -= 1;
                        }
                        break;

                case LIBUSB20_ME_INT16:
                        pd_offset = -((-pd_offset) & ~1);       /* align */
                        while (pd_count--) {
                                uint16_t temp;

                                if (len < 2)    /* overflow */
                                        goto done;

                                if (buf) {
                                        temp = *((const uint16_t *)
                                            LIBUSB20_ADD_BYTES(pd, pd_offset));
                                        buf[1] = (temp >> 8) & 0xFF;
                                        buf[0] = temp & 0xFF;
                                        buf += 2;
                                }
                                pd_offset += 2;
                                len -= 2;
                        }
                        break;

                case LIBUSB20_ME_INT32:
                        pd_offset = -((-pd_offset) & ~3);       /* align */
                        while (pd_count--) {
                                uint32_t temp;

                                if (len < 4)    /* overflow */
                                        goto done;
                                if (buf) {
                                        temp = *((const uint32_t *)
                                            LIBUSB20_ADD_BYTES(pd, pd_offset));
                                        buf[3] = (temp >> 24) & 0xFF;
                                        buf[2] = (temp >> 16) & 0xFF;
                                        buf[1] = (temp >> 8) & 0xFF;
                                        buf[0] = temp & 0xFF;
                                        buf += 4;
                                }
                                pd_offset += 4;
                                len -= 4;
                        }
                        break;

                case LIBUSB20_ME_INT64:
                        pd_offset = -((-pd_offset) & ~7);       /* align */
                        while (pd_count--) {
                                uint64_t temp;

                                if (len < 8)    /* overflow */
                                        goto done;
                                if (buf) {

                                        temp = *((const uint64_t *)
                                            LIBUSB20_ADD_BYTES(pd, pd_offset));
                                        buf[7] = (temp >> 56) & 0xFF;
                                        buf[6] = (temp >> 48) & 0xFF;
                                        buf[5] = (temp >> 40) & 0xFF;
                                        buf[4] = (temp >> 32) & 0xFF;
                                        buf[3] = (temp >> 24) & 0xFF;
                                        buf[2] = (temp >> 16) & 0xFF;
                                        buf[1] = (temp >> 8) & 0xFF;
                                        buf[0] = temp & 0xFF;
                                        buf += 8;
                                }
                                pd_offset += 8;
                                len -= 8;
                        }
                        break;

                case LIBUSB20_ME_STRUCT:
                        pd_offset = -((-pd_offset) &
                            ~(LIBUSB20_ME_STRUCT_ALIGN - 1));   /* align */
                        while (pd_count--) {
                                void *src_ptr;
                                uint16_t src_len;
                                struct libusb20_me_struct *ps;

                                ps = LIBUSB20_ADD_BYTES(pd, pd_offset);

                                switch (ps->type) {
                                case LIBUSB20_ME_IS_RAW:
                                        src_len = ps->len;
                                        src_ptr = ps->ptr;
                                        break;

                                case LIBUSB20_ME_IS_ENCODED:
                                        if (ps->len == 0) {
                                                /*
                                                 * Length is encoded
                                                 * in the data itself
                                                 * and should be
                                                 * correct:
                                                 */
                                                ps->len = 0xFFFF;
                                        }
                                        src_len = libusb20_me_get_1(pd, 0);
                                        src_ptr = LIBUSB20_ADD_BYTES(ps->ptr, 1);
                                        if (src_len == 0xFF) {
                                                /* length is escaped */
                                                src_len = libusb20_me_get_2(pd, 1);
                                                src_ptr =
                                                    LIBUSB20_ADD_BYTES(ps->ptr, 3);
                                        }
                                        break;

                                case LIBUSB20_ME_IS_DECODED:
                                        /* reserve 3 length bytes */
                                        src_len = libusb20_me_encode(NULL,
                                            0xFFFF - 3, ps->ptr);
                                        src_ptr = NULL;
                                        break;

                                default:        /* empty structure */
                                        src_len = 0;
                                        src_ptr = NULL;
                                        break;
                                }

                                if (src_len > 0xFE) {
                                        if (src_len > (0xFFFF - 3))
                                                /* overflow */
                                                goto done;

                                        if (len < (src_len + 3))
                                                /* overflow */
                                                goto done;

                                        if (buf) {
                                                buf[0] = 0xFF;
                                                buf[1] = (src_len & 0xFF);
                                                buf[2] = (src_len >> 8) & 0xFF;
                                                buf += 3;
                                        }
                                        len -= (src_len + 3);
                                } else {
                                        if (len < (src_len + 1))
                                                /* overflow */
                                                goto done;

                                        if (buf) {
                                                buf[0] = (src_len & 0xFF);
                                                buf += 1;
                                        }
                                        len -= (src_len + 1);
                                }

                                /* check for buffer and non-zero length */

                                if (buf && src_len) {
                                        if (ps->type == LIBUSB20_ME_IS_DECODED) {
                                                /*
                                                 * Repeat encode
                                                 * procedure - we have
                                                 * room for the
                                                 * complete structure:
                                                 */
                                                (void) libusb20_me_encode(buf,
                                                    0xFFFF - 3, ps->ptr);
                                        } else {
                                                bcopy(src_ptr, buf, src_len);
                                        }
                                        buf += src_len;
                                }
                                pd_offset += sizeof(struct libusb20_me_struct);
                        }
                        break;

                default:
                        goto done;
                }
        }
done:
        return (len_old - len);
}

/*------------------------------------------------------------------------*
 *      libusb20_me_decode - decode a message into a decoded structure
 *
 * Description of parameters:
 * "ptr" - message pointer
 * "len" - message length
 * "pd" - pointer to decoded structure
 *
 * Returns:
 * "0..65535" - number of bytes decoded, limited by "len"
 *------------------------------------------------------------------------*/
uint16_t
libusb20_me_decode(const void *ptr, uint16_t len, void *pd)
{
        const uint8_t *pf;              /* pointer to format data */
        const uint8_t *buf;             /* pointer to input buffer */

        uint32_t pd_offset;             /* decoded structure offset */
        uint16_t len_old;               /* old length */
        uint16_t pd_count;              /* decoded element count */
        uint8_t me;                     /* message element */

        /* initialise */

        len_old = len;
        buf = ptr;
        pd_offset = sizeof(void *);
        pf = (*((struct libusb20_me_format **)pd))->format;

        /* scan */

        while (1) {

                /* get information element */

                me = (pf[0]) & LIBUSB20_ME_MASK;
                pd_count = pf[1] | (pf[2] << 8);
                pf += 3;

                /* decode the message element by type */

                switch (me) {
                case LIBUSB20_ME_INT8:
                        while (pd_count--) {
                                uint8_t temp;

                                if (len < 1) {
                                        len = 0;
                                        temp = 0;
                                } else {
                                        len -= 1;
                                        temp = buf[0];
                                        buf++;
                                }
                                *((uint8_t *)LIBUSB20_ADD_BYTES(pd,
                                    pd_offset)) = temp;
                                pd_offset += 1;
                        }
                        break;

                case LIBUSB20_ME_INT16:
                        pd_offset = -((-pd_offset) & ~1);       /* align */
                        while (pd_count--) {
                                uint16_t temp;

                                if (len < 2) {
                                        len = 0;
                                        temp = 0;
                                } else {
                                        len -= 2;
                                        temp = buf[1] << 8;
                                        temp |= buf[0];
                                        buf += 2;
                                }
                                *((uint16_t *)LIBUSB20_ADD_BYTES(pd,
                                    pd_offset)) = temp;
                                pd_offset += 2;
                        }
                        break;

                case LIBUSB20_ME_INT32:
                        pd_offset = -((-pd_offset) & ~3);       /* align */
                        while (pd_count--) {
                                uint32_t temp;

                                if (len < 4) {
                                        len = 0;
                                        temp = 0;
                                } else {
                                        len -= 4;
                                        temp = buf[3] << 24;
                                        temp |= buf[2] << 16;
                                        temp |= buf[1] << 8;
                                        temp |= buf[0];
                                        buf += 4;
                                }

                                *((uint32_t *)LIBUSB20_ADD_BYTES(pd,
                                    pd_offset)) = temp;
                                pd_offset += 4;
                        }
                        break;

                case LIBUSB20_ME_INT64:
                        pd_offset = -((-pd_offset) & ~7);       /* align */
                        while (pd_count--) {
                                uint64_t temp;

                                if (len < 8) {
                                        len = 0;
                                        temp = 0;
                                } else {
                                        len -= 8;
                                        temp = ((uint64_t)buf[7]) << 56;
                                        temp |= ((uint64_t)buf[6]) << 48;
                                        temp |= ((uint64_t)buf[5]) << 40;
                                        temp |= ((uint64_t)buf[4]) << 32;
                                        temp |= buf[3] << 24;
                                        temp |= buf[2] << 16;
                                        temp |= buf[1] << 8;
                                        temp |= buf[0];
                                        buf += 8;
                                }

                                *((uint64_t *)LIBUSB20_ADD_BYTES(pd,
                                    pd_offset)) = temp;
                                pd_offset += 8;
                        }
                        break;

                case LIBUSB20_ME_STRUCT:
                        pd_offset = -((-pd_offset) &
                            ~(LIBUSB20_ME_STRUCT_ALIGN - 1));   /* align */
                        while (pd_count--) {
                                uint16_t temp;
                                struct libusb20_me_struct *ps;

                                ps = LIBUSB20_ADD_BYTES(pd, pd_offset);

                                if (ps->type == LIBUSB20_ME_IS_ENCODED) {
                                        /*
                                         * Pre-store a de-constified
                                         * pointer to the raw
                                         * structure:
                                         */
                                        ps->ptr = LIBUSB20_ADD_BYTES(buf, 0);

                                        /*
                                         * Get the correct number of
                                         * length bytes:
                                         */
                                        if (len != 0) {
                                                if (buf[0] == 0xFF) {
                                                        ps->len = 3;
                                                } else {
                                                        ps->len = 1;
                                                }
                                        } else {
                                                ps->len = 0;
                                        }
                                }
                                /* get the structure length */

                                if (len != 0) {
                                        if (buf[0] == 0xFF) {
                                                if (len < 3) {
                                                        len = 0;
                                                        temp = 0;
                                                } else {
                                                        len -= 3;
                                                        temp = buf[1] |
                                                            (buf[2] << 8);
                                                        buf += 3;
                                                }
                                        } else {
                                                len -= 1;
                                                temp = buf[0];
                                                buf += 1;
                                        }
                                } else {
                                        len = 0;
                                        temp = 0;
                                }
                                /* check for invalid length */

                                if (temp > len) {
                                        len = 0;
                                        temp = 0;
                                }
                                /* check wanted structure type */

                                switch (ps->type) {
                                case LIBUSB20_ME_IS_ENCODED:
                                        /* check for zero length */
                                        if (temp == 0) {
                                                /*
                                                 * The pointer must
                                                 * be valid:
                                                 */
                                                ps->ptr = LIBUSB20_ADD_BYTES(
                                                    libusb20_me_encode_empty, 0);
                                                ps->len = 1;
                                        } else {
                                                ps->len += temp;
                                        }
                                        break;

                                case LIBUSB20_ME_IS_RAW:
                                        /* update length and pointer */
                                        ps->len = temp;
                                        ps->ptr = LIBUSB20_ADD_BYTES(buf, 0);
                                        break;

                                case LIBUSB20_ME_IS_EMPTY:
                                case LIBUSB20_ME_IS_DECODED:
                                        /* check for non-zero length */
                                        if (temp != 0) {
                                                /* update type */
                                                ps->type = LIBUSB20_ME_IS_DECODED;
                                                ps->len = 0;
                                                /*
                                                 * Recursivly decode
                                                 * the next structure
                                                 */
                                                (void) libusb20_me_decode(buf,
                                                    temp, ps->ptr);
                                        } else {
                                                /* update type */
                                                ps->type = LIBUSB20_ME_IS_EMPTY;
                                                ps->len = 0;
                                        }
                                        break;

                                default:
                                        /*
                                         * nothing to do - should
                                         * not happen
                                         */
                                        ps->ptr = NULL;
                                        ps->len = 0;
                                        break;
                                }
                                buf += temp;
                                len -= temp;
                                pd_offset += sizeof(struct libusb20_me_struct);
                        }
                        break;

                default:
                        goto done;
                }
        }
done:
        return (len_old - len);
}