/*
 * Parses descriptors
 *
 * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com>
 *
 * This library is covered by the LGPL, read LICENSE for details.
 */

#include <stdio.h>
#include <string.h>
#include "usbi.h"

int usb_get_descriptor_by_endpoint(usb_dev_handle *udev, int ep,
                                   unsigned char type, unsigned char index, void *buf, int size)
{
    memset(buf, 0, size);

    return usb_control_msg(udev, ep | USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR,
                           (type << 8) + index, 0, buf, size, 1000);
}

int usb_get_descriptor(usb_dev_handle *udev, unsigned char type,
                       unsigned char index, void *buf, int size)
{
    memset(buf, 0, size);

    return usb_control_msg(udev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR,
                           (type << 8) + index, 0, buf, size, 1000);
}

int usb_parse_descriptor(unsigned char *source, char *description, void *dest)
{
    unsigned char *sp = source, *dp = dest;
    uint16_t w;
    uint32_t d;
    char *cp;

    for (cp = description; *cp; cp++)
    {
        switch (*cp)
        {
        case 'b':	/* 8-bit byte */
            *dp++ = *sp++;
            break;
        case 'w':	/* 16-bit word, convert from little endian to CPU */
            w = (sp[1] << 8) | sp[0];
            sp += 2;
            //dp += ((unsigned long)dp & 1);	/* Align to word boundary */
            *((uint16_t *)dp) = w;
            dp += 2;
            break;
        case 'd':	/* 32-bit dword, convert from little endian to CPU */
            d = (sp[3] << 24) | (sp[2] << 16) | (sp[1] << 8) | sp[0];
            sp += 4;
            //dp += ((unsigned long)dp & 2);	/* Align to dword boundary */
            *((uint32_t *)dp) = d;
            dp += 4;
            break;
            /* These two characters are undocumented and just a hack for Linux */
        case 'W':	/* 16-bit word, keep CPU endianess */
            //dp += ((unsigned long)dp & 1);	/* Align to word boundary */
            memcpy(dp, sp, 2);
            sp += 2;
            dp += 2;
            break;
        case 'D':	/* 32-bit dword, keep CPU endianess */
            //dp += ((unsigned long)dp & 2);	/* Align to dword boundary */
            memcpy(dp, sp, 4);
            sp += 4;
            dp += 4;
            break;
        }
    }

    return (int)(sp - source);
}

/*
 * This code looks surprisingly similar to the code I wrote for the Linux
 * kernel. It's not a coincidence :)
 */

static int usb_parse_endpoint(struct usb_endpoint_descriptor *endpoint, unsigned char *buffer, int size)
{
    struct usb_descriptor_header header;
    unsigned char *begin;
    int parsed = 0, len, numskipped;

    usb_parse_descriptor(buffer, "bb", &header);

    /* Everything should be fine being passed into here, but we sanity */
    /*  check JIC */
    if (header.bLength > size)
    {
        if (usb_debug >= 1)
            fprintf(stderr, "ran out of descriptors parsing\n");
        return -1;
    }

    if (header.bDescriptorType != USB_DT_ENDPOINT)
    {
        if (usb_debug >= 2)
            fprintf(stderr, "unexpected descriptor 0x%X, expecting endpoint descriptor, type 0x%X\n",
                    header.bDescriptorType, USB_DT_ENDPOINT);
        return parsed;
    }

    if (header.bLength >= ENDPOINT_AUDIO_DESC_LENGTH)
        usb_parse_descriptor(buffer, "bbbbwbbb", endpoint);
    else if (header.bLength >= ENDPOINT_DESC_LENGTH)
        usb_parse_descriptor(buffer, "bbbbwb", endpoint);

    buffer += header.bLength;
    size -= header.bLength;
    parsed += header.bLength;

    /* Skip over the rest of the Class Specific or Vendor Specific */
    /*  descriptors */
    begin = buffer;
    numskipped = 0;
    while (size >= DESC_HEADER_LENGTH)
    {
        usb_parse_descriptor(buffer, "bb", &header);

        if (header.bLength < 2)
        {
            if (usb_debug >= 1)
                fprintf(stderr, "invalid descriptor length of %d\n", header.bLength);
            return -1;
        }

        /* If we find another "proper" descriptor then we're done  */
        if ((header.bDescriptorType == USB_DT_ENDPOINT) ||
                (header.bDescriptorType == USB_DT_INTERFACE) ||
                (header.bDescriptorType == USB_DT_CONFIG) ||
                (header.bDescriptorType == USB_DT_DEVICE))
            break;

        if (usb_debug >= 1)
            fprintf(stderr, "skipping descriptor 0x%X\n", header.bDescriptorType);
        numskipped++;

        buffer += header.bLength;
        size -= header.bLength;
        parsed += header.bLength;
    }

    if (numskipped && usb_debug >= 2)
        fprintf(stderr, "skipped %d class/vendor specific endpoint descriptors\n", numskipped);

    /* Copy any unknown descriptors into a storage area for drivers */
    /*  to later parse */
    len = (int)(buffer - begin);
    if (!len)
    {
        endpoint->extra = NULL;
        endpoint->extralen = 0;
        return parsed;
    }

    endpoint->extra = malloc(len);
    if (!endpoint->extra)
    {
        if (usb_debug >= 1)
            fprintf(stderr, "couldn't allocate memory for endpoint extra descriptors\n");
        endpoint->extralen = 0;
        return parsed;
    }

    memcpy(endpoint->extra, begin, len);
    endpoint->extralen = len;

    return parsed;
}

static int usb_parse_interface(struct usb_interface *interface,
                               unsigned char *buffer, int size)
{
    int i, len, numskipped, retval, parsed = 0;
    struct usb_descriptor_header header;
    struct usb_interface_descriptor *ifp;
    unsigned char *begin;

    interface->num_altsetting = 0;

    while (size >= INTERFACE_DESC_LENGTH)
    {
        interface->altsetting = realloc(interface->altsetting, sizeof(struct usb_interface_descriptor) * (interface->num_altsetting + 1));
        if (!interface->altsetting)
        {
            if (usb_debug >= 1)
                fprintf(stderr, "couldn't malloc interface->altsetting\n");
            return -1;
        }

        ifp = interface->altsetting + interface->num_altsetting;
        interface->num_altsetting++;

        usb_parse_descriptor(buffer, "bbbbbbbbb", ifp);

        /* Skip over the interface */
        buffer += ifp->bLength;
        parsed += ifp->bLength;
        size -= ifp->bLength;

        begin = buffer;
        numskipped = 0;

        /* Skip over any interface, class or vendor descriptors */
        while (size >= DESC_HEADER_LENGTH)
        {
            usb_parse_descriptor(buffer, "bb", &header);

            if (header.bLength < 2)
            {
                if (usb_debug >= 1)
                    fprintf(stderr, "invalid descriptor length of %d\n", header.bLength);
                return -1;
            }

            /* If we find another "proper" descriptor then we're done */
            if ((header.bDescriptorType == USB_DT_INTERFACE) ||
                    (header.bDescriptorType == USB_DT_ENDPOINT) ||
                    (header.bDescriptorType == USB_DT_CONFIG) ||
                    (header.bDescriptorType == USB_DT_DEVICE))
                break;

            numskipped++;

            buffer += header.bLength;
            parsed += header.bLength;
            size -= header.bLength;
        }

        if (numskipped && usb_debug >= 2)
            fprintf(stderr, "skipped %d class/vendor specific interface descriptors\n", numskipped);

        /* Copy any unknown descriptors into a storage area for */
        /*  drivers to later parse */
        len = (int)(buffer - begin);
        if (!len)
        {
            ifp->extra = NULL;
            ifp->extralen = 0;
        }
        else
        {
            ifp->extra = malloc(len);
            if (!ifp->extra)
            {
                if (usb_debug >= 1)
                    fprintf(stderr, "couldn't allocate memory for interface extra descriptors\n");
                ifp->extralen = 0;
                return -1;
            }
            memcpy(ifp->extra, begin, len);
            ifp->extralen = len;
        }

        /* Did we hit an unexpected descriptor? */
        usb_parse_descriptor(buffer, "bb", &header);
        if ((size >= DESC_HEADER_LENGTH) &&
                ((header.bDescriptorType == USB_DT_CONFIG) ||
                 (header.bDescriptorType == USB_DT_DEVICE)))
            return parsed;

        if (ifp->bNumEndpoints > USB_MAXENDPOINTS)
        {
            if (usb_debug >= 1)
                fprintf(stderr, "too many endpoints\n");
            return -1;
        }

        if (ifp->bNumEndpoints > 0)
        {
            ifp->endpoint = (struct usb_endpoint_descriptor *)
                            malloc(ifp->bNumEndpoints *
                                   sizeof(struct usb_endpoint_descriptor));
            if (!ifp->endpoint)
            {
                if (usb_debug >= 1)
                    fprintf(stderr, "couldn't allocate memory for ifp->endpoint\n");
                return -1;
            }

            memset(ifp->endpoint, 0, ifp->bNumEndpoints *
                   sizeof(struct usb_endpoint_descriptor));

            for (i = 0; i < ifp->bNumEndpoints; i++)
            {
                usb_parse_descriptor(buffer, "bb", &header);

                if (header.bLength > size)
                {
                    if (usb_debug >= 1)
                        fprintf(stderr, "ran out of descriptors parsing\n");
                    return -1;
                }

                retval = usb_parse_endpoint(ifp->endpoint + i, buffer, size);
                if (retval < 0)
                    return retval;

                buffer += retval;
                parsed += retval;
                size -= retval;
            }
        }
        else
            ifp->endpoint = NULL;

        /* We check to see if it's an alternate to this one */
        ifp = (struct usb_interface_descriptor *)buffer;
        if (size < USB_DT_INTERFACE_SIZE ||
                ifp->bDescriptorType != USB_DT_INTERFACE ||
                !ifp->bAlternateSetting)
            return parsed;
    }

    return parsed;
}

int usb_parse_configuration(struct usb_config_descriptor *config,
                            unsigned char *buffer)
{
    int i, retval, size;
    struct usb_descriptor_header header;

    usb_parse_descriptor(buffer, "bbwbbbbb", config);
    size = config->wTotalLength;

    if (config->bNumInterfaces > USB_MAXINTERFACES)
    {
        if (usb_debug >= 1)
            fprintf(stderr, "too many interfaces\n");
        return -1;
    }

    config->interface = (struct usb_interface *)
                        malloc(config->bNumInterfaces *
                               sizeof(struct usb_interface));
    if (!config->interface)
    {
        if (usb_debug >= 1)
            fprintf(stderr, "out of memory\n");
        return -1;
    }

    memset(config->interface, 0, config->bNumInterfaces * sizeof(struct usb_interface));

    buffer += config->bLength;
    size -= config->bLength;

    config->extra = NULL;
    config->extralen = 0;

    for (i = 0; i < config->bNumInterfaces; i++)
    {
        int numskipped, len;
        unsigned char *begin;

        /* Skip over the rest of the Class Specific or Vendor */
        /*  Specific descriptors */
        begin = buffer;
        numskipped = 0;
        while (size >= DESC_HEADER_LENGTH)
        {
            usb_parse_descriptor(buffer, "bb", &header);

            if ((header.bLength > size) || (header.bLength < DESC_HEADER_LENGTH))
            {
                if (usb_debug >= 1)
                    fprintf(stderr, "invalid descriptor length of %d\n", header.bLength);
                return -1;
            }

            /* If we find another "proper" descriptor then we're done */
            if ((header.bDescriptorType == USB_DT_ENDPOINT) ||
                    (header.bDescriptorType == USB_DT_INTERFACE) ||
                    (header.bDescriptorType == USB_DT_CONFIG) ||
                    (header.bDescriptorType == USB_DT_DEVICE))
                break;

            if (usb_debug >= 2)
                fprintf(stderr, "skipping descriptor 0x%X\n", header.bDescriptorType);
            numskipped++;

            buffer += header.bLength;
            size -= header.bLength;
        }

        if (numskipped && usb_debug >= 2)
            fprintf(stderr, "skipped %d class/vendor specific endpoint descriptors\n", numskipped);

        /* Copy any unknown descriptors into a storage area for */
        /*  drivers to later parse */
        len = (int)(buffer - begin);
        if (len)
        {
            /* FIXME: We should realloc and append here */
            if (!config->extralen)
            {
                config->extra = malloc(len);
                if (!config->extra)
                {
                    if (usb_debug >= 1)
                        fprintf(stderr, "couldn't allocate memory for config extra descriptors\n");
                    config->extralen = 0;
                    return -1;
                }

                memcpy(config->extra, begin, len);
                config->extralen = len;
            }
        }

        retval = usb_parse_interface(config->interface + i, buffer, size);
        if (retval < 0)
            return retval;

        buffer += retval;
        size -= retval;
    }

    return size;
}

void usb_destroy_configuration(struct usb_device *dev)
{
    int c, i, j, k;

    if (!dev->config)
        return;

    for (c = 0; c < dev->descriptor.bNumConfigurations; c++)
    {
        struct usb_config_descriptor *cf = &dev->config[c];

        if (!cf->interface)
            continue;

        for (i = 0; i < cf->bNumInterfaces; i++)
        {
            struct usb_interface *ifp = &cf->interface[i];

            if (!ifp->altsetting)
                continue;

            for (j = 0; j < ifp->num_altsetting; j++)
            {
                struct usb_interface_descriptor *as = &ifp->altsetting[j];

                if (as->extra)
                    free(as->extra);

                if (!as->endpoint)
                    continue;

                for (k = 0; k < as->bNumEndpoints; k++)
                {
                    if (as->endpoint[k].extra)
                        free(as->endpoint[k].extra);
                }
                free(as->endpoint);
            }

            free(ifp->altsetting);
        }

        free(cf->interface);
    }

    free(dev->config);
}

void usb_fetch_and_parse_descriptors(usb_dev_handle *udev)
{
    struct usb_device *dev = udev->device;
    int i;

    if (dev->descriptor.bNumConfigurations > USB_MAXCONFIG)
    {
        if (usb_debug >= 1)
            fprintf(stderr, "Too many configurations (%d > %d)\n", dev->descriptor.bNumConfigurations, USB_MAXCONFIG);
        return;
    }

    if (dev->descriptor.bNumConfigurations < 1)
    {
        if (usb_debug >= 1)
            fprintf(stderr, "Not enough configurations (%d < %d)\n", dev->descriptor.bNumConfigurations, 1);
        return;
    }

    dev->config = (struct usb_config_descriptor *)malloc(dev->descriptor.bNumConfigurations * sizeof(struct usb_config_descriptor));
    if (!dev->config)
    {
        if (usb_debug >= 1)
            fprintf(stderr, "Unable to allocate memory for config descriptor\n");
        return;
    }

    memset(dev->config, 0, dev->descriptor.bNumConfigurations *
           sizeof(struct usb_config_descriptor));

    for (i = 0; i < dev->descriptor.bNumConfigurations; i++)
    {
        unsigned char buffer[USB_DT_CONFIG_SIZE], *bigbuffer;
        struct usb_config_descriptor config;
        int res;

        /* Get the first 8 bytes so we can figure out what the total length is */
        res = usb_get_descriptor(udev, USB_DT_CONFIG, (unsigned char)i, buffer, USB_DT_CONFIG_SIZE);
        if (res < USB_DT_CONFIG_SIZE)
        {
            if (usb_debug >= 1)
            {
                if (res < 0)
                    fprintf(stderr, "Unable to get descriptor (%d)\n", res);
                else
                    fprintf(stderr, "Config descriptor too short (expected %d, got %d)\n", USB_DT_CONFIG_SIZE, res);
            }

            goto err;
        }

        usb_parse_descriptor(buffer, "bbw", &config);

        bigbuffer = malloc(config.wTotalLength);
        if (!bigbuffer)
        {
            if (usb_debug >= 1)
                fprintf(stderr, "Unable to allocate memory for descriptors\n");
            goto err;
        }

        res = usb_get_descriptor(udev, USB_DT_CONFIG, (unsigned char)i, bigbuffer,
                                 config.wTotalLength);
        if (res < config.wTotalLength)
        {
            if (usb_debug >= 1)
            {
                if (res < 0)
                    fprintf(stderr, "Unable to get descriptor (%d)\n", res);
                else
                    fprintf(stderr, "Config descriptor too short (expected %d, got %d)\n", config.wTotalLength, res);
            }

            free(bigbuffer);
            goto err;
        }

        res = usb_parse_configuration(&dev->config[i], bigbuffer);
        if (usb_debug >= 2)
        {
            if (res > 0)
                fprintf(stderr, "Descriptor data still left\n");
            else if (res < 0)
                fprintf(stderr, "Unable to parse descriptors\n");
        }

        free(bigbuffer);
    }

    return;

err:
    free(dev->config);

    dev->config = NULL;
}

