/*
 * This file is part of the bladeRF project:
 *   http://www.github.com/nuand/bladeRF
 *
 * Copyright (C) 2013-2016 Nuand LLC
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "host_config.h"
#include <stdlib.h>
#include <string.h>
#include "thread.h"
#include <errno.h>
#include <libusb.h>
#if 1 == BLADERF_OS_FREEBSD  
#include <limits.h>
#endif // BLADERF_OS_FREEBSD

#include "log.h"

#include "devinfo.h"
#include "backend/backend.h"
#include "backend/usb/usb.h"
#include "streaming/async.h"
#include "helpers/timeout.h"

#include "bladeRF.h"

#include "host_config.h"

#ifndef LIBUSB_HANDLE_EVENTS_TIMEOUT_NSEC
#   define LIBUSB_HANDLE_EVENTS_TIMEOUT_NSEC    (15 * 1000)
#endif

struct bladerf_lusb {
    libusb_device           *dev;
    libusb_device_handle    *handle;
    libusb_context          *context;
#if 1 == BLADERF_OS_WINDOWS
    HANDLE                  mutex;
#endif // BLADERF_OS_WINDOWS
};

typedef enum {
    TRANSFER_UNINITIALIZED = 0,
    TRANSFER_AVAIL,
    TRANSFER_IN_FLIGHT,
    TRANSFER_CANCEL_PENDING
} transfer_status;

struct lusb_stream_data {
    size_t num_transfers;               /* Total # of allocated transfers */
    size_t num_avail;                   /* # of currently available transfers */
    size_t i;                           /* Index to next transfer */
    struct libusb_transfer **transfers; /* Array of transfer metadata */
    transfer_status *transfer_status;   /* Status of each transfer */

   /* Warn the first time we get a transfer callback out of order.
    * This shouldn't happen normally, but we've seen it intermittently on
    * libusb 1.0.19 for Windows. Further investigation required...
    */
    bool out_of_order_event;
};

static inline struct bladerf_lusb * lusb_backend(struct bladerf *dev)
{
    struct bladerf_usb *usb;

    assert(dev && dev->backend_data);
    usb = (struct bladerf_usb *) dev->backend_data;

    assert(usb->driver);
    return (struct bladerf_lusb *) usb->driver;
}

/* Convert libusb error codes to libbladeRF error codes */
static int error_conv(int error)
{
    int ret;

    switch (error) {
        case 0:
            ret = 0;
            break;

        case LIBUSB_ERROR_IO:
            ret = BLADERF_ERR_IO;
            break;

        case LIBUSB_ERROR_INVALID_PARAM:
            ret = BLADERF_ERR_INVAL;
            break;

        case LIBUSB_ERROR_BUSY:
        case LIBUSB_ERROR_NO_DEVICE:
            ret = BLADERF_ERR_NODEV;
            break;

        case LIBUSB_ERROR_TIMEOUT:
            ret = BLADERF_ERR_TIMEOUT;
            break;

        case LIBUSB_ERROR_NO_MEM:
            ret = BLADERF_ERR_MEM;
            break;

        case LIBUSB_ERROR_NOT_SUPPORTED:
            ret = BLADERF_ERR_UNSUPPORTED;
            break;

        case LIBUSB_ERROR_ACCESS:
            ret = BLADERF_ERR_PERMISSION;
            break;

        case LIBUSB_ERROR_OVERFLOW:
        case LIBUSB_ERROR_PIPE:
        case LIBUSB_ERROR_INTERRUPTED:
        case LIBUSB_ERROR_NOT_FOUND:
        default:
            ret = BLADERF_ERR_UNEXPECTED;
    }

    return ret;
}

/* Returns libusb error codes */
static int get_devinfo(libusb_device *dev, struct bladerf_devinfo *info)
{
    int status = 0;
    libusb_device_handle *handle;
    struct libusb_device_descriptor desc;

    /* Populate device info */
    bladerf_init_devinfo(info);
    info->backend  = BLADERF_BACKEND_LIBUSB;
    info->usb_bus  = libusb_get_bus_number(dev);
    info->usb_addr = libusb_get_device_address(dev);

    status = libusb_open(dev, &handle);

    if (status == 0) {
        status = libusb_get_device_descriptor(dev, &desc);
        if (status != 0) {
            memset(info->serial, 0, BLADERF_SERIAL_LENGTH);
        } else {
            status = libusb_get_string_descriptor_ascii(
                handle, desc.iSerialNumber, (unsigned char *)&info->serial,
                BLADERF_SERIAL_LENGTH);

            /* Consider this to be non-fatal, otherwise firmware <= 1.1
             * wouldn't be able to get far enough to upgrade */
            if (status < 0) {
                log_debug("Failed to retrieve serial number\n");
                memset(info->serial, 0, BLADERF_SERIAL_LENGTH);
            }

            status = libusb_get_string_descriptor_ascii(
                handle, desc.iManufacturer,
                (unsigned char *)&info->manufacturer,
                BLADERF_DESCRIPTION_LENGTH);

            if (status < 0) {
                log_debug("Failed to retrieve manufacturer string\n");
                memset(info->manufacturer, 0, BLADERF_DESCRIPTION_LENGTH);
            }

            status = libusb_get_string_descriptor_ascii(
                handle, desc.iProduct, (unsigned char *)&info->product,
                BLADERF_DESCRIPTION_LENGTH);

            if (status < 0) {
                log_debug("Failed to retrieve product string\n");
                memset(info->product, 0, BLADERF_DESCRIPTION_LENGTH);
            }

            log_debug("Bus %03d Device %03d: %s %s, serial %s\n", info->usb_bus,
                      info->usb_addr, info->manufacturer, info->product,
                      info->serial);

            if (status > 0) {
                status = 0;
            }
        }

        libusb_close(handle);
    }

    return status;
}

static bool device_has_vid_pid(libusb_device *dev, uint16_t vid, uint16_t pid)
{
    int status;
    struct libusb_device_descriptor desc;
    bool match = false;

    status = libusb_get_device_descriptor(dev, &desc);
    if (status != 0) {
        log_debug("Couldn't get device descriptor: %s\n",
                  libusb_error_name(status));
    } else {
        match = (desc.idVendor == vid) && (desc.idProduct == pid);
    }

    return match;
}

static bool device_is_fx3_bootloader(libusb_device *dev)
{
    return device_has_vid_pid(dev, USB_CYPRESS_VENDOR_ID, USB_FX3_PRODUCT_ID) ||
           device_has_vid_pid(dev, USB_NUAND_VENDOR_ID, USB_NUAND_BLADERF_BOOT_PRODUCT_ID) ||
           device_has_vid_pid(dev, USB_NUAND_LEGACY_VENDOR_ID, USB_NUAND_BLADERF_LEGACY_BOOT_PRODUCT_ID);
}

static inline bool device_has_bladeRF_ids(libusb_device *dev)
{
    return device_has_vid_pid(dev, USB_NUAND_VENDOR_ID, USB_NUAND_BLADERF_PRODUCT_ID) ||
           device_has_vid_pid(dev, USB_NUAND_VENDOR_ID, USB_NUAND_BLADERF2_PRODUCT_ID) ||
           device_has_vid_pid(dev, USB_NUAND_LEGACY_VENDOR_ID, USB_NUAND_BLADERF_LEGACY_PRODUCT_ID);

}

static bool device_is_bladerf(libusb_device *dev)
{
    struct libusb_config_descriptor *cfg;
    int status;
    bool ret;

    if (!device_has_bladeRF_ids(dev)) {
        return false;
    }

    status = libusb_get_config_descriptor(dev, 0, &cfg);
    if (status != 0) {
        log_debug("Failed to get configuration descriptor: %s\n",
                  libusb_error_name(status));
        return false;
    }

    /* As of firmware v0.4, we expect there to be 4 altsettings on the
     * first interface. */
    if (cfg->interface->num_altsetting != 4) {
        const uint8_t bus = libusb_get_bus_number(dev);
        const uint8_t addr = libusb_get_device_address(dev);

        log_warning("A bladeRF running incompatible firmware appears to be "
                    "present on bus=%u, addr=%u. If this is true, a firmware "
                    "update via the device's bootloader is required.\n\n",
                    bus, addr);

        ret = false;
    } else {
        ret = true;
    }

    libusb_free_config_descriptor(cfg);
    return ret;
}

static bool device_is_probe_target(backend_probe_target probe_target,
                                   libusb_device *dev)
{
    bool is_probe_target = false;

    switch (probe_target) {
        case BACKEND_PROBE_BLADERF:
            is_probe_target = device_is_bladerf(dev);
            if (is_probe_target) {
                log_verbose("Found a bladeRF\n");
            }
            break;

        case BACKEND_PROBE_FX3_BOOTLOADER:
            is_probe_target = device_is_fx3_bootloader(dev);
            if (is_probe_target) {
                log_verbose("Found an FX3 bootloader.\n");
            }
            break;

        default:
            assert(!"Invalid probe target");
    }

    return is_probe_target;
}

static int lusb_probe(backend_probe_target probe_target,
                      struct bladerf_devinfo_list *info_list)
{
    int status, i, n;
    ssize_t count;
    libusb_device **list;
    struct bladerf_devinfo info;
    bool printed_access_warning = false;

    libusb_context *context;

    /* Initialize libusb for device tree walking */
    status = libusb_init(&context);
    if (status) {
        log_error("Could not initialize libusb: %s\n",
                  libusb_error_name(status));
        goto lusb_probe_done;
    }

    count = libusb_get_device_list(context, &list);
    /* Iterate through all the USB devices */
    for (i = 0, n = 0; i < count && status == 0; i++) {
        if (device_is_probe_target(probe_target, list[i])) {
            bool do_add = true;

            /* Open the USB device and get some information */
            status = get_devinfo(list[i], &info);
            if (status) {
                /* We may not be able to open the device if another driver
                 * (e.g., CyUSB3) is associated with it. Therefore, just log to
                 * the debug level and carry on. */
                log_debug("Could not open device: %s\n",
                          libusb_error_name(status));

                if (status == LIBUSB_ERROR_ACCESS) {
                    /* If it's an access error, odds are good this is happening
                     * because we've already got the device open. Pass the info
                     * we have back to the caller. */
                    do_add = true;

                    if (!printed_access_warning) {
                        printed_access_warning = true;
                        log_warning(
                            "Found a bladeRF via VID/PID, but could not open "
                            "it due to insufficient permissions, or because "
                            "the device is already open.\n");
                    }
                } else {
                    do_add = false;
                }

                /* Don't stop probing because one device was "problematic" */
                status = 0;
            }

            if (do_add) {
                info.instance = n++;

                status = bladerf_devinfo_list_add(info_list, &info);
                if (status) {
                    log_error("Could not add device to list: %s\n",
                              bladerf_strerror(status));
                } else {
                    log_verbose("Added instance %d to device list\n",
                                info.instance);
                }
            }
        }
    }

    libusb_free_device_list(list, 1);
    libusb_exit(context);

lusb_probe_done:
    return status;
}

#ifdef HAVE_LIBUSB_GET_VERSION
static inline void get_libusb_version(char *buf, size_t buf_len)
{
    const struct libusb_version *version;
    version = libusb_get_version();

    snprintf(buf, buf_len, "%d.%d.%d.%d%s", version->major, version->minor,
             version->micro, version->nano, version->rc);
}
#else
static void get_libusb_version(char *buf, size_t buf_len)
{
    snprintf(buf, buf_len, "<= 1.0.9");
}
#endif

static int create_device_mutex(const struct bladerf_devinfo *info,
                               struct bladerf_lusb *dev)
{
    int status = 0;

#if 1 == BLADERF_OS_WINDOWS
    const char prefix[] = "Global\\bladeRF-";
    const size_t mutex_name_len = strlen(prefix) + BLADERF_SERIAL_LENGTH;
    char *mutex_name = (char *)calloc(mutex_name_len, 1);
    DWORD last_error;

    if (NULL == mutex_name) {
        return BLADERF_ERR_MEM;
    }

    snprintf(mutex_name, mutex_name_len, "%s%s", prefix, info->serial);
    log_verbose("Mutex name: %s\n", mutex_name);

    SetLastError(0);
    dev->mutex = CreateMutex(NULL, true, mutex_name);
    last_error = GetLastError();
    if (NULL == dev->mutex || last_error != 0) {
        log_debug("Unable to create device mutex: mutex=%p, last_error=%ld\n",
                  dev->mutex, last_error);
        status = BLADERF_ERR_NODEV;
        ReleaseMutex(dev->mutex);
        CloseHandle(dev->mutex);
    }

    free(mutex_name);
#endif // BLADERF_OS_WINDOWS

    return status;
}

static int open_device(const struct bladerf_devinfo *info,
                       libusb_context *context,
                       libusb_device *libusb_dev_in,
                       struct bladerf_lusb **dev_out)
{
    int status;
    struct bladerf_lusb *dev;

    *dev_out = NULL;

    dev = (struct bladerf_lusb *) calloc(1, sizeof(dev[0]));
    if (dev == NULL) {
        log_debug("Failed to allocate handle for instance %d.\n",
                  info->instance);

        /* Report "no device" so we could try again with
         * another matching device */
        return BLADERF_ERR_NODEV;
    }

    dev->context = context;
    dev->dev = libusb_dev_in;

    status = libusb_open(libusb_dev_in, &dev->handle);
    if (status < 0) {
        log_debug("Failed to open device instance %d: %s\n",
                  info->instance, libusb_error_name(status));

        status = error_conv(status);
        goto out;
    }

    status = libusb_claim_interface(dev->handle, 0);
    if (status < 0) {
        log_debug("Failed to claim interface 0 for instance %d: %s\n",
                  info->instance, libusb_error_name(status));

        status = error_conv(status);
        goto out;
    }

    status = create_device_mutex(info, dev);
    if (status < 0) {
        log_debug("Failed to get device mutex for instance %d: %s\n",
                  info->instance, bladerf_strerror(status));

        status = error_conv(status);
        goto out;
    }

out:
    if (status != 0) {
        if (dev->handle != NULL) {
            libusb_close(dev->handle);
        }

        free(dev);
    } else {
        *dev_out = dev;
    }

    return status;
}

static int find_and_open_device(libusb_context *context,
                                const struct bladerf_devinfo *info_in,
                                struct bladerf_lusb **dev_out,
                                struct bladerf_devinfo *info_out)
{
    int status = BLADERF_ERR_NODEV;
    int i, n;
    ssize_t count;
    struct libusb_device **list;
    struct bladerf_devinfo curr_info;
    bool printed_access_warning = false;

    *dev_out = NULL;

    count = libusb_get_device_list(context, &list);
    if (count < 0) {
        if (count < INT_MIN) {
            /* Ensure we don't have a situation where we accidentally return 0
             * due to a narrowing conversion */
            return BLADERF_ERR_UNEXPECTED;
        } else {
            return error_conv((int) count);
        }
    }

    for (i = 0, n = 0; (i < count) && (*dev_out == NULL); i++) {
        if (device_is_bladerf(list[i])) {
            log_verbose("Found a bladeRF (idx=%d)\n", i);

            /* Open the USB device and get some information */
            status = get_devinfo(list[i], &curr_info);
            if (status < 0) {

                /* Give the user a helpful hint in case the have forgotten
                 * to update their udev rules */
                if (status == LIBUSB_ERROR_ACCESS && !printed_access_warning) {
                    printed_access_warning = true;
                    log_warning("Found a bladeRF via VID/PID, but could not "
                                "open it due to insufficient permissions.\n");
                } else {
                    log_debug("Could not open bladeRF device: %s\n",
                               libusb_error_name(status) );
                }

                status = BLADERF_ERR_NODEV;
                continue; /* Continue trying the next devices */
            } else {
                curr_info.instance = n++;
            }

            /* Check to see if this matches the info struct */
            if (bladerf_devinfo_matches(&curr_info, info_in)) {
                status = open_device(&curr_info, context, list[i], dev_out);
                if (status < 0) {
                    status = BLADERF_ERR_NODEV;
                    continue; /* Continue trying the next matching device */
                } else {
                    memcpy(info_out, &curr_info, sizeof(info_out[0]));
                }
            } else {
                status = BLADERF_ERR_NODEV;

                log_verbose("Devinfo doesn't match - skipping"
                        "(instance=%d, serial=%d, bus/addr=%d\n",
                        bladerf_instance_matches(&curr_info, info_in),
                        bladerf_serial_matches(&curr_info, info_in),
                        bladerf_bus_addr_matches(&curr_info, info_in));
            }
        }
    }

    if (status == 0) {
        /* Returning 0 indicates this function is providing a device */
        assert(*dev_out != NULL);
    }

    libusb_free_device_list(list, 1);
    return status;
}

#if ENABLE_USB_DEV_RESET_ON_OPEN
static int reset_and_reopen(libusb_context *context,
                            struct bladerf_lusb **dev,
                            struct bladerf_devinfo *info)
{
    int status;

    status = libusb_reset_device((*dev)->handle);
    if (status == 0) {
        log_verbose("USB port reset succeeded for bladeRF %s\n", info->serial);
        return 0;
    } else if (status == LIBUSB_ERROR_NO_DEVICE) {
        struct bladerf_devinfo new_info;

        /* The reset has caused the device to drop out and re-enumerate.
         *
         * We'll find it again via the info we gathered about it via its
         * serial number, which is now stored in the devinfo
         */
        log_verbose("Re-scan required after port reset for bladeRF %s\n",
                    info->serial);


        libusb_release_interface((*dev)->handle, 0);
        libusb_close((*dev)->handle);
#if 1 == BLADERF_OS_WINDOWS
        ReleaseMutex((*dev)->mutex);
        CloseHandle((*dev)->mutex);
#endif // BLADERF_OS_WINDOWS

        *dev = NULL;

        memcpy(&new_info, info, sizeof(new_info));
        new_info.usb_bus  = DEVINFO_BUS_ANY;
        new_info.usb_addr = DEVINFO_ADDR_ANY;

        status = find_and_open_device(context, &new_info, dev, info);

    } else {
        status = BLADERF_ERR_IO;
        log_verbose("Port reset failed for bladerf %s: %s\n",
                    info->serial, libusb_error_name(status));
    }

    return status;
}
#endif

static int lusb_open(void **driver,
                     struct bladerf_devinfo *info_in,
                     struct bladerf_devinfo *info_out)
{
    int status;
    struct bladerf_lusb *lusb = NULL;
    libusb_context *context;

    /* Initialize libusb for device tree walking */
    status = libusb_init(&context);
    if (status) {
        log_error("Could not initialize libusb: %s\n",
                  libusb_error_name(status));
        return error_conv(status);
    }

    /* We can only print this out when log output is enabled, or else we'll
     * get snagged by -Werror=unused-but-set-variable */
#   ifdef LOGGING_ENABLED
    {
        char buf[64];
        get_libusb_version(buf, sizeof(buf));
        log_verbose("Using libusb version: %s\n", buf);
    }
#   endif

    status = find_and_open_device(context, info_in, &lusb, info_out);
    if (status != 0) {
        libusb_exit(context);

        if (status == BLADERF_ERR_NODEV) {
            log_debug("No devices available on the libusb backend.\n");
        } else {
            log_debug("Failed to open bladeRF on libusb backend: %s\n",
                    bladerf_strerror(status));
        }
    } else {
        assert(lusb != NULL);

        /* Cosmin and Marian from Null Team (null.ro) and YateBTS(.com) found
         * that it is possible to recover from "Issue #95: Not enough bandwidth
         * for altsetting" by performing a USB port reset prior to actually
         * trying to use the device.
         */
#       if ENABLE_USB_DEV_RESET_ON_OPEN
        if (bladerf_usb_reset_device_on_open) {
            status = reset_and_reopen(context, &lusb, info_out);
        }
#       endif

        if (status == 0) {
            *driver = (void *) lusb;
        }
    }

    return status;
}

static int lusb_change_setting(void *driver, uint8_t setting)
{
    struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver;

    int status = libusb_set_interface_alt_setting(lusb->handle, 0, setting);

    return error_conv(status);
}

static void lusb_close(void *driver)
{
    int status;
    struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver;

    status = libusb_release_interface(lusb->handle, 0);
    if (status < 0) {
        log_error("Failed to release interface: %s\n",
                  libusb_error_name(status));
    }

    libusb_close(lusb->handle);
    libusb_exit(lusb->context);
#if 1 == BLADERF_OS_WINDOWS
    ReleaseMutex(lusb->mutex);
    CloseHandle(lusb->mutex);
#endif // BLADERF_OS_WINDOWS
    free(lusb);
}

static void lusb_close_bootloader(void *driver)
{
    int status;
    struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver;

    if (lusb != NULL) {
        if (lusb->handle != NULL) {
            status = libusb_release_interface(lusb->handle, 0);
            if (status < 0) {
                log_debug("Failed to release interface: %s\n",
                           libusb_error_name(status));
            }

            libusb_close(lusb->handle);
        }

        if (lusb->context != NULL) {
            libusb_exit(lusb->context);
        }

#if 1 == BLADERF_OS_WINDOWS
        ReleaseMutex(lusb->mutex);
        CloseHandle(lusb->mutex);
#endif // BLADERF_OS_WINDOWS

        free(lusb);
    }
}

static inline bool bus_matches(uint8_t bus, struct libusb_device *d)
{
    return (bus == DEVINFO_BUS_ANY) ||
           (bus == libusb_get_bus_number(d));
}

static inline bool addr_matches(uint8_t addr, struct libusb_device *d)
{
    return (addr == DEVINFO_BUS_ANY) ||
           (addr == libusb_get_device_address(d));
}

static int lusb_open_bootloader(void **driver, uint8_t bus, uint8_t addr)
{
    int status;
    struct libusb_device **dev_list = NULL;
    ssize_t dev_list_size, i;
    struct bladerf_lusb *lusb;

    *driver = NULL;

    lusb = calloc(1, sizeof(lusb[0]));
    if (lusb == NULL) {
        return BLADERF_ERR_MEM;
    }

    status = libusb_init(&lusb->context);
    if (status != 0) {
        log_debug("Failed to initialize libusb context: %s\n",
                  libusb_error_name(status));
        goto error;
    }

    dev_list_size = libusb_get_device_list(lusb->context, &dev_list);
    if (dev_list_size < 0) {
        log_debug("Failed to get device list: %s\n", libusb_error_name(status));
        status = (int) dev_list_size;
        goto error;
    }

    for (i = 0; i < dev_list_size; i++) {
        if (device_is_fx3_bootloader(dev_list[i]) &&
            bus_matches(bus, dev_list[i]) &&
            addr_matches(addr, dev_list[i])) {


            status = libusb_open(dev_list[i], &lusb->handle);
            if (status != 0) {
                log_debug("Failed to open device: %s\n",
                          libusb_error_name(status));
                goto error;
            } else {
                status = libusb_claim_interface(lusb->handle, 0);
                if (status < 0) {
                    log_debug("Failed to claim interface: %s\n",
                              libusb_error_name(status));

                    goto error;
                } else {
                    log_verbose("Opened bootloader at %u:%u\n",
                                libusb_get_bus_number(dev_list[i]),
                                libusb_get_device_address(dev_list[i]));
                    *driver = lusb;
                }
                break;
            }
        }
    }

error:
    if (dev_list != NULL) {
        libusb_free_device_list(dev_list, 1);
    }

    if (status != 0) {
        status = error_conv(status);
        lusb_close_bootloader(lusb);
    } else if (*driver == NULL) {
        status = BLADERF_ERR_NODEV;
        lusb_close_bootloader(lusb);
    }

    return status;
}

static int lusb_get_vid_pid(void *driver, uint16_t *vid,
                            uint16_t *pid)
{
    struct bladerf_lusb *lusb = (struct bladerf_lusb *)driver;
    struct libusb_device_descriptor desc;
    int status;

    status = libusb_get_device_descriptor(lusb->dev, &desc);
    if (status != 0) {
        log_debug("Couldn't get device descriptor: %s\n",
                  libusb_error_name(status));
         return BLADERF_ERR_IO;
    }

    *vid = desc.idVendor;
    *pid = desc.idProduct;

    return 0;
}

static int lusb_get_handle(void *driver, void **handle)
{
    struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver;
    *handle = lusb->handle;

    return 0;
}
static int lusb_get_speed(void *driver,
                          bladerf_dev_speed *device_speed)
{
    int speed;
    int status = 0;
    struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver;

    speed = libusb_get_device_speed(lusb->dev);
    if (speed == LIBUSB_SPEED_SUPER) {
        *device_speed = BLADERF_DEVICE_SPEED_SUPER;
    } else if (speed == LIBUSB_SPEED_HIGH) {
        *device_speed = BLADERF_DEVICE_SPEED_HIGH;
    } else {
        *device_speed = BLADERF_DEVICE_SPEED_UNKNOWN;

        if (speed == LIBUSB_SPEED_FULL) {
            log_debug("Full speed connection is not suppored.\n");
            status = BLADERF_ERR_UNSUPPORTED;
        } else if (speed == LIBUSB_SPEED_LOW) {
            log_debug("Low speed connection is not supported.\n");
            status = BLADERF_ERR_UNSUPPORTED;
        } else {
            log_debug("Unknown/unexpected device speed (%d)\n", speed);
            status = BLADERF_ERR_UNEXPECTED;
        }
    }

    return status;
}

static inline uint8_t bm_request_type(usb_target target_type,
                                      usb_request req_type,
                                      usb_direction direction)
{
    uint8_t ret = 0;

    switch (target_type) {
        case USB_TARGET_DEVICE:
            ret |= LIBUSB_RECIPIENT_DEVICE;
            break;

        case USB_TARGET_INTERFACE:
            ret |= LIBUSB_RECIPIENT_INTERFACE;
            break;

        case USB_TARGET_ENDPOINT:
            ret |= LIBUSB_RECIPIENT_ENDPOINT;
            break;

        default:
            ret |= LIBUSB_RECIPIENT_OTHER;

    }

    switch (req_type) {
        case USB_REQUEST_STANDARD:
            ret |= LIBUSB_REQUEST_TYPE_STANDARD;
            break;

        case USB_REQUEST_CLASS:
            ret |= LIBUSB_REQUEST_TYPE_CLASS;
            break;

        case USB_REQUEST_VENDOR:
            ret |= LIBUSB_REQUEST_TYPE_VENDOR;
            break;
    }

    switch (direction) {
        case USB_DIR_HOST_TO_DEVICE:
            ret |= LIBUSB_ENDPOINT_OUT;
            break;

        case USB_DIR_DEVICE_TO_HOST:
            ret |= LIBUSB_ENDPOINT_IN;
            break;
    }

    return ret;
}

static int lusb_control_transfer(void *driver,
                                 usb_target target_type, usb_request req_type,
                                 usb_direction dir, uint8_t request,
                                 uint16_t wvalue, uint16_t windex,
                                 void *buffer, uint32_t buffer_len,
                                 uint32_t timeout_ms)
{

    int status;
    struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver;
    const uint8_t bm_req_type = bm_request_type(target_type, req_type, dir);

    status = libusb_control_transfer(lusb->handle,
                                     bm_req_type, request,
                                     wvalue, windex,
                                     buffer, buffer_len,
                                     timeout_ms);

    if (status >= 0 && (uint32_t)status == buffer_len) {
        status = 0;
    } else {
        log_debug("%s failed: status = %d\n", __FUNCTION__, status);
    }

    return error_conv(status);
}

static int lusb_bulk_transfer(void *driver, uint8_t endpoint, void *buffer,
                              uint32_t buffer_len, uint32_t timeout_ms)
{
    int status;
    int n_transferred;
    struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver;

    status = libusb_bulk_transfer(lusb->handle, endpoint, buffer, buffer_len,
                                  &n_transferred, timeout_ms);

    status = error_conv(status);
    if (status == 0 && ((uint32_t)n_transferred != buffer_len)) {
        log_debug("Short bulk transfer: requested=%u, transferred=%u\n",
                  buffer_len, n_transferred);
        status = BLADERF_ERR_IO;
    }

    return status;
}

static int lusb_get_string_descriptor(void *driver, uint8_t index,
                                      void *buffer, uint32_t buffer_len)
{
    int status;
    struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver;

    status = libusb_get_string_descriptor_ascii(lusb->handle, index,
                                                (unsigned char*)buffer,
                                                buffer_len);

    if (status > 0 && (uint32_t)status < buffer_len)  {
        status = 0;
    } else {
        status = BLADERF_ERR_UNEXPECTED;
    }

    return status;
}

/* At the risk of being a little inefficient, just keep attempting to cancel
 * everything. If a transfer's no longer active, we'll just get a NOT_FOUND
 * error -- no big deal.  Just accepting that alleviates the need to track
 * the status of each transfer...
 */
static inline void cancel_all_transfers(struct bladerf_stream *stream)
{
    size_t i;
    int status;
    struct lusb_stream_data *stream_data = stream->backend_data;

    for (i = 0; i < stream_data->num_transfers; i++) {
        if (stream_data->transfer_status[i] == TRANSFER_IN_FLIGHT) {
            status = libusb_cancel_transfer(stream_data->transfers[i]);
            if (status < 0 && status != LIBUSB_ERROR_NOT_FOUND) {
                log_error("Error canceling transfer (%d): %s\n",
                        status, libusb_error_name(status));
            } else {
                stream_data->transfer_status[i] = TRANSFER_CANCEL_PENDING;
            }
        }
    }
}

static inline size_t transfer_idx(struct lusb_stream_data *stream_data,
                                  struct libusb_transfer *transfer)
{
    size_t i;
    for (i = 0; i < stream_data->num_transfers; i++) {
        if (stream_data->transfers[i] == transfer) {
            return i;
        }
    }

    return UINT_MAX;
}

static int submit_transfer(struct bladerf_stream *stream, void *buffer, size_t len);

static void LIBUSB_CALL lusb_stream_cb(struct libusb_transfer *transfer)
{
    struct bladerf_stream *stream = transfer->user_data;
    void *next_buffer             = NULL;
    struct bladerf_metadata metadata;
    struct lusb_stream_data *stream_data = stream->backend_data;
    size_t transfer_i;

    /* Currently unused - zero out for out own debugging sanity... */
    memset(&metadata, 0, sizeof(metadata));

    MUTEX_LOCK(&stream->lock);

    transfer_i = transfer_idx(stream_data, transfer);
    assert(stream_data->transfer_status[transfer_i] == TRANSFER_IN_FLIGHT ||
           stream_data->transfer_status[transfer_i] == TRANSFER_CANCEL_PENDING);

    if (transfer_i >= stream_data->num_transfers) {
        log_error("Unable to find transfer\n");
        stream->state = STREAM_SHUTTING_DOWN;
    } else {
        stream_data->transfer_status[transfer_i] = TRANSFER_AVAIL;
        stream_data->num_avail++;
        COND_SIGNAL(&stream->can_submit_buffer);
    }

    /* Check to see if the transfer has been cancelled or errored */
    if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
        /* Errored out for some reason .. */
        stream->state = STREAM_SHUTTING_DOWN;

        switch (transfer->status) {
            case LIBUSB_TRANSFER_CANCELLED:
                /* We expect this case when we begin tearing down the stream */
                break;

            case LIBUSB_TRANSFER_STALL:
                log_error("Hit stall for buffer %p\n\r", transfer->buffer);
                stream->error_code = BLADERF_ERR_IO;
                break;

            case LIBUSB_TRANSFER_ERROR:
                log_error("Got transfer error for buffer %p\n\r",
                          transfer->buffer);
                stream->error_code = BLADERF_ERR_IO;
                break;

            case LIBUSB_TRANSFER_OVERFLOW:
                log_error("Got transfer over for buffer %p, "
                          "transfer \"actual_length\" = %d\n\r",
                          transfer->buffer, transfer->actual_length);
                stream->error_code = BLADERF_ERR_IO;
                break;

            case LIBUSB_TRANSFER_TIMED_OUT:
                log_error("Transfer timed out for %s buffer %p\n\r",
                          (stream->layout & BLADERF_DIRECTION_MASK) == BLADERF_TX ? "TX" : "RX",
                          transfer->buffer);
                stream->error_code = BLADERF_ERR_TIMEOUT;
                break;

            case LIBUSB_TRANSFER_NO_DEVICE:
                stream->error_code = BLADERF_ERR_NODEV;
                break;

            default:
                log_error("Unexpected transfer status: %d\n\r", transfer->status);
                break;
        }
    }

    if (stream->state == STREAM_RUNNING) {
        if (stream->format == BLADERF_FORMAT_PACKET_META) {
            /* Call user callback requesting more data to transmit */
            next_buffer = stream->cb(
                stream->dev, stream, &metadata, transfer->buffer,
                bytes_to_samples(stream->format, transfer->actual_length), stream->user_data);
        } else {
            /* Sanity check for debugging purposes */
            if (transfer->length != transfer->actual_length) {
                log_warning("Received short transfer\n");
            }

            /* Call user callback requesting more data to transmit */
            next_buffer = stream->cb(
                stream->dev, stream, &metadata, transfer->buffer,
                bytes_to_samples(stream->format, transfer->actual_length), stream->user_data);
        }

        if (next_buffer == BLADERF_STREAM_SHUTDOWN) {
            stream->state = STREAM_SHUTTING_DOWN;
        } else if (next_buffer != BLADERF_STREAM_NO_DATA) {
            int status;
            if((stream->layout & BLADERF_DIRECTION_MASK) == BLADERF_TX
                  && stream->format == BLADERF_FORMAT_PACKET_META) {
               status = submit_transfer(stream, next_buffer, metadata.actual_count * sizeof(uint32_t));
            } else {
               status = submit_transfer(stream, next_buffer, async_stream_buf_bytes(stream));
            }
            if (status != 0) {
                /* If this fails, we probably have a serious problem...so just
                 * shut it down. */
                stream->state = STREAM_SHUTTING_DOWN;
            }
        }
    }


    /* Check to see if all the transfers have been cancelled,
     * and if so, clean up the stream */
    if (stream->state == STREAM_SHUTTING_DOWN) {
        /* We know we're done when all of our transfers have returned to their
         * "available" states */
        if (stream_data->num_avail == stream_data->num_transfers) {
            stream->state = STREAM_DONE;
        } else {
            cancel_all_transfers(stream);
        }
    }

    MUTEX_UNLOCK(&stream->lock);
}

static inline struct libusb_transfer *
get_next_available_transfer(struct lusb_stream_data *stream_data)
{
    unsigned int n;
    size_t i = stream_data->i;

    for (n = 0; n < stream_data->num_transfers; n++) {
        if (stream_data->transfer_status[i] == TRANSFER_AVAIL) {

            if (stream_data->i != i &&
                stream_data->out_of_order_event == false) {

                log_warning("Transfer callback occurred out of order. "
                            "(Warning only this time.)\n");
                stream_data->out_of_order_event = true;
            }

            stream_data->i = i;
            return stream_data->transfers[i];
        }

        i = (i + 1) % stream_data->num_transfers;
    }

    return NULL;
}

/* Precondition: A transfer is available. */
static int submit_transfer(struct bladerf_stream *stream, void *buffer, size_t len)
{
    int status;
    struct bladerf_lusb *lusb = lusb_backend(stream->dev);
    struct lusb_stream_data *stream_data = stream->backend_data;
    struct libusb_transfer *transfer;
    const size_t bytes_per_buffer = async_stream_buf_bytes(stream);
    size_t prev_idx;
    const unsigned char ep =
        (stream->layout & BLADERF_DIRECTION_MASK) == BLADERF_TX ? SAMPLE_EP_OUT : SAMPLE_EP_IN;

    transfer = get_next_available_transfer(stream_data);
    assert(transfer != NULL);

    assert(bytes_per_buffer <= INT_MAX);
    libusb_fill_bulk_transfer(transfer,
                              lusb->handle,
                              ep,
                              buffer,
                              (int)len,
                              lusb_stream_cb,
                              stream,
                              stream->transfer_timeout);

    prev_idx = stream_data->i;
    stream_data->transfer_status[stream_data->i] = TRANSFER_IN_FLIGHT;
    stream_data->i = (stream_data->i + 1) % stream_data->num_transfers;
    assert(stream_data->num_avail != 0);
    stream_data->num_avail--;

    /* FIXME We have an inherent issue here with lock ordering between
     *       stream->lock and libusb's underlying event lock, so we
     *       have to drop the stream->lock as a workaround.
     *
     *       This implies that a callback can potentially execute,
     *       including a callback for this transfer. Therefore, the transfer
     *       has been setup and its metadata logged.
     *
     *       Ultimately, we need to review our async scheme and associated
     *       lock schemes.
     */
    MUTEX_UNLOCK(&stream->lock);
    status = libusb_submit_transfer(transfer);
    MUTEX_LOCK(&stream->lock);

    if (status != 0) {
        log_error("Failed to submit transfer in %s: %s\n",
                  __FUNCTION__, libusb_error_name(status));

        /* We need to undo the metadata we updated prior to dropping
         * the lock and attempting to submit the transfer */
        assert(stream_data->transfer_status[prev_idx] == TRANSFER_IN_FLIGHT);
        stream_data->transfer_status[prev_idx] = TRANSFER_AVAIL;
        stream_data->num_avail++;
        if (stream_data->i == 0) {
            stream_data->i = stream_data->num_transfers - 1;
        } else {
            stream_data->i--;
        }
    }

    return error_conv(status);
}

static int lusb_init_stream(void *driver, struct bladerf_stream *stream,
                            size_t num_transfers)
{
    int status = 0;
    size_t i;
    struct lusb_stream_data *stream_data;

    /* Fill in backend stream information */
    stream_data = malloc(sizeof(struct lusb_stream_data));
    if (!stream_data) {
        return BLADERF_ERR_MEM;
    }

    /* Backend stream information */
    stream->backend_data = stream_data;
    stream_data->transfers = NULL;
    stream_data->transfer_status = NULL;
    stream_data->num_transfers = num_transfers;
    stream_data->num_avail = 0;
    stream_data->i = 0;
    stream_data->out_of_order_event = false;

    stream_data->transfers =
        malloc(num_transfers * sizeof(struct libusb_transfer *));

    if (stream_data->transfers == NULL) {
        log_error("Failed to allocate libusb tranfers\n");
        status = BLADERF_ERR_MEM;
        goto error;
    }

    stream_data->transfer_status =
        calloc(num_transfers, sizeof(transfer_status));

    if (stream_data->transfer_status == NULL) {
        log_error("Failed to allocated libusb transfer status array\n");
        status = BLADERF_ERR_MEM;
        goto error;
    }

    /* Create the libusb transfers */
    for (i = 0; i < stream_data->num_transfers; i++) {
        stream_data->transfers[i] = libusb_alloc_transfer(0);

        /* Upon error, start tearing down anything we've started allocating
         * and report that the stream is in a bad state */
        if (stream_data->transfers[i] == NULL) {

            /* Note: <= 0 not appropriate as we're dealing
             *       with an unsigned index */
            while (i > 0) {
                if (--i) {
                    libusb_free_transfer(stream_data->transfers[i]);
                    stream_data->transfers[i] = NULL;
                    stream_data->transfer_status[i] = TRANSFER_UNINITIALIZED;
                    stream_data->num_avail--;
                }
            }

            status = BLADERF_ERR_MEM;
            break;
        } else {
            stream_data->transfer_status[i] = TRANSFER_AVAIL;
            stream_data->num_avail++;
        }
    }

error:
    if (status != 0) {
        free(stream_data->transfer_status);
        free(stream_data->transfers);
        free(stream_data);
        stream->backend_data = NULL;
    }

    return status;
}

static int lusb_stream(void *driver, struct bladerf_stream *stream,
                       bladerf_channel_layout layout)
{
    size_t i;
    int status = 0;
    void *buffer;
    struct bladerf_metadata metadata;
    struct bladerf *dev = stream->dev;
    struct bladerf_lusb *lusb = (struct bladerf_lusb *) driver;
    struct lusb_stream_data *stream_data = stream->backend_data;
    struct timeval tv = { 0, LIBUSB_HANDLE_EVENTS_TIMEOUT_NSEC };

    /* Currently unused, so zero it out for a sanity check when debugging */
    memset(&metadata, 0, sizeof(metadata));

    MUTEX_LOCK(&stream->lock);

    /* Set up initial set of buffers */
    for (i = 0; i < stream_data->num_transfers; i++) {
        if ((layout & BLADERF_DIRECTION_MASK) == BLADERF_TX) {
            buffer = stream->cb(dev,
                                stream,
                                &metadata,
                                NULL,
                                stream->samples_per_buffer,
                                stream->user_data);

            if (buffer == BLADERF_STREAM_SHUTDOWN) {
                /* If we have transfers in flight and the user prematurely
                 * cancels the stream, we'll start shutting down */
                if (stream_data->num_avail != stream_data->num_transfers) {
                    stream->state = STREAM_SHUTTING_DOWN;
                } else {
                    /* No transfers have been shipped out yet so we can
                     * simply enter our "done" state */
                    stream->state = STREAM_DONE;
                }

                /* In either of the above we don't want to attempt to
                 * get any more buffers from the user */
                break;
            }
        } else {
            buffer = stream->buffers[i];
        }

        if (buffer != BLADERF_STREAM_NO_DATA) {
            if((layout & BLADERF_DIRECTION_MASK) == BLADERF_TX
                  && stream->format == BLADERF_FORMAT_PACKET_META) {
               status = submit_transfer(stream, buffer, metadata.actual_count * sizeof(uint32_t));
            } else {
               status = submit_transfer(stream, buffer, async_stream_buf_bytes(stream));
            }

            /* If we failed to submit any transfers, cancel everything in
             * flight.  We'll leave the stream in the running state so we can
             * have libusb fire off callbacks with the cancelled status*/
            if (status < 0) {
                stream->error_code = status;
                cancel_all_transfers(stream);
            }
        }
    }
    MUTEX_UNLOCK(&stream->lock);

    /* This loop is required so libusb can do callbacks and whatnot */
    while (stream->state != STREAM_DONE) {
        status = libusb_handle_events_timeout(lusb->context, &tv);

        if (status < 0 && status != LIBUSB_ERROR_INTERRUPTED) {
            log_warning("unexpected value from events processing: "
                        "%d: %s\n", status, libusb_error_name(status));
            status = error_conv(status);
        }
    }

    return status;
}

/* The top-level code will have aquired the stream->lock for us */
int lusb_submit_stream_buffer(void *driver, struct bladerf_stream *stream,
                              void *buffer, size_t *length,
                              unsigned int timeout_ms, bool nonblock)
{
    int status = 0;
    struct lusb_stream_data *stream_data = stream->backend_data;

    if (buffer == BLADERF_STREAM_SHUTDOWN) {
        if (stream_data->num_avail == stream_data->num_transfers) {
            stream->state = STREAM_DONE;
        } else {
            stream->state = STREAM_SHUTTING_DOWN;
        }

        return 0;
    }

    if (stream_data->num_avail == 0) {
        if (nonblock) {
            log_debug("Non-blocking buffer submission requested, but no "
                      "transfers are currently available.\n");

            return BLADERF_ERR_WOULD_BLOCK;
        }

        if (timeout_ms != 0) {
            while (stream_data->num_avail == 0 && status == THREAD_SUCCESS) {
                status = COND_TIMED_WAIT(&stream->can_submit_buffer,
                        &stream->lock,
                        timeout_ms);
            }
        } else {
            while (stream_data->num_avail == 0 && status == THREAD_SUCCESS) {
                status = COND_WAIT(&stream->can_submit_buffer,
                        &stream->lock);
            }
        }
    }

    if (status == THREAD_TIMEOUT) {
        log_debug("%s: Timed out waiting for a transfer to become available.\n",
                  __FUNCTION__);
        return BLADERF_ERR_TIMEOUT;
    } else if (status != 0) {
        return BLADERF_ERR_UNEXPECTED;
    } else {
        return submit_transfer(stream, buffer, *length);
    }
}

static int lusb_deinit_stream(void *driver, struct bladerf_stream *stream)
{
    size_t i;
    struct lusb_stream_data *stream_data = stream->backend_data;

    for (i = 0; i < stream_data->num_transfers; i++) {
        libusb_free_transfer(stream_data->transfers[i]);
        stream_data->transfers[i] = NULL;
        stream_data->transfer_status[i] = TRANSFER_UNINITIALIZED;
    }

    free(stream_data->transfers);
    free(stream_data->transfer_status);
    free(stream->backend_data);

    stream->backend_data = NULL;
    return 0;
}

static const struct usb_fns libusb_fns = {
    FIELD_INIT(.probe, lusb_probe),
    FIELD_INIT(.open, lusb_open),
    FIELD_INIT(.close, lusb_close),
    FIELD_INIT(.get_vid_pid, lusb_get_vid_pid),
    FIELD_INIT(.get_flash_id, NULL),
    FIELD_INIT(.get_handle, lusb_get_handle),
    FIELD_INIT(.get_speed, lusb_get_speed),
    FIELD_INIT(.change_setting, lusb_change_setting),
    FIELD_INIT(.control_transfer, lusb_control_transfer),
    FIELD_INIT(.bulk_transfer, lusb_bulk_transfer),
    FIELD_INIT(.get_string_descriptor, lusb_get_string_descriptor),
    FIELD_INIT(.init_stream, lusb_init_stream),
    FIELD_INIT(.stream, lusb_stream),
    FIELD_INIT(.submit_stream_buffer, lusb_submit_stream_buffer),
    FIELD_INIT(.deinit_stream, lusb_deinit_stream),
    FIELD_INIT(.open_bootloader, lusb_open_bootloader),
    FIELD_INIT(.close_bootloader, lusb_close_bootloader),
};

const struct usb_driver usb_driver_libusb = {
    FIELD_INIT(.fn, &libusb_fns),
    FIELD_INIT(.id, BLADERF_BACKEND_LIBUSB),
};
