/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2015 by Cástor Muñoz
 *
 * based on:
 *  ipoddfu_c by user890104
 *  xpwn/pwnmetheus2
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <string.h>
#ifdef WIN32
#include <windows.h>
#include <setupapi.h>
#endif
#ifdef USE_LIBUSBAPI
#include <libusb-1.0/libusb.h>
#endif
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#endif

#include "mks5lboot.h"


#ifdef WIN32
#define sleep_ms(ms) Sleep(ms)
#else
#include <time.h>
static void sleep_ms(unsigned int ms)
{
   struct timespec req;
   req.tv_sec = ms / 1000;
   req.tv_nsec = (ms % 1000) * 1000000;
   nanosleep(&req, NULL);
}
#endif

static void put_uint32le(unsigned char* p, uint32_t x)
{
    p[0] = x & 0xff;
    p[1] = (x >> 8) & 0xff;
    p[2] = (x >> 16) & 0xff;
    p[3] = (x >> 24) & 0xff;
}

/*
 * CRC32 functions
 * Based on public domain implementation by Finn Yannick Jacobs.
 *
 * Written and copyright 1999 by Finn Yannick Jacobs
 * No rights were reserved to this, so feel free to
 * manipulate or do with it, what you want or desire :)
 */

/* crc32table[] built by crc32_init() */
static uint32_t crc32table[256];

/* Calculate crc32 */
static uint32_t crc32(void *data, unsigned int len, uint32_t previousCrc32)
{
    uint32_t crc = ~previousCrc32;
    unsigned char *d = (unsigned char*) data;
    while (len--)
        crc = (crc >> 8) ^ crc32table[(crc & 0xFF) ^ *d++];
    return ~crc;
}

/* Calculate crc32table */
static void crc32_init()
{
    uint32_t poly = 0xEDB88320L;
    uint32_t crc;
    int i, j;
    for (i = 0; i < 256; ++i)
    {
        crc = i;
        for (j = 0; j < 8; ++j)
            crc = (crc >> 1) ^ ((crc & 1) ? poly : 0);
        crc32table[i] = crc;
    }
}

/* USB */
#define APPLE_VID   0x05AC

struct pid_info {
    int pid;
    int mode; /* 0->DFU, 1->WTF */
    char *desc;
};

struct pid_info known_pids[] =
{
    /* DFU */
    { 0x1220, 0, "Nano 2G" },
    { 0x1223, 0, "Nano 3G / Classic" },
    { 0x1224, 0, "Shuffle 3G" },
    { 0x1225, 0, "Nano 4G" },
    { 0x1231, 0, "Nano 5G" },
    { 0x1232, 0, "Nano 6G" },
    { 0x1233, 0, "Shuffle 4G" },
    { 0x1234, 0, "Nano 7G" },
    /* WTF */
    { 0x1240, 1, "Nano 2G" },
    { 0x1241, 1, "Classic 1G" },
    { 0x1242, 1, "Nano 3G" },
    { 0x1243, 1, "Nano 4G" },
    { 0x1245, 1, "Classic 2G" },
    { 0x1246, 1, "Nano 5G" },
    { 0x1247, 1, "Classic 3G" },
    { 0x1248, 1, "Nano 6G" },
    { 0x1249, 1, "Nano 7G" },
    { 0x124a, 1, "Nano 7G" },
    { 0x1250, 1, "Classic 4G" },
};
#define N_KNOWN_PIDS (sizeof(known_pids)/sizeof(struct pid_info))

struct usbControlSetup {
    uint8_t bmRequestType;
    uint8_t bRequest;
    uint16_t wValue;
    uint16_t wIndex;
    uint16_t wLength;
} __attribute__ ((packed));
#define USB_CS_SZ (sizeof(struct usbControlSetup))

struct usbStatusData {
    uint8_t bStatus;
    uint8_t bwPollTimeout0;
    uint8_t bwPollTimeout1;
    uint8_t bwPollTimeout2;
    uint8_t bState;
    uint8_t iString;
} __attribute__ ((packed));


/*
 * DFU API
 */
#define DFU_PKT_SZ  2048    /* must be pow2 <= wTransferSize (2048) */

/* DFU 1.1 specs */
typedef enum {
    appIDLE = 0,
    appDETACH = 1,
    dfuIDLE = 2,
    dfuDNLOAD_SYNC = 3,
    dfuDNBUSY = 4,
    dfuDNLOAD_IDLE = 5,
    dfuMANIFEST_SYNC = 6,
    dfuMANIFEST = 7,
    dfuMANIFEST_WAIT_RESET = 8,
    dfuUPLOAD_IDLE = 9,
    dfuERROR = 10
} DFUState;

typedef enum {
    errNONE = 0,
    errTARGET = 1,
    errFILE = 2,
    errWRITE = 3,
    errERASE = 4,
    errCHECK_ERASED = 5,
    errPROG = 6,
    errVERIFY = 7,
    errADDRESS = 8,
    errNOTDONE = 9,
    errFIRMWARE = 10,
    errVENDOR = 11,
    errUSBR = 12,
    errPOR = 13,
    errUNKNOWN = 14,
    errSTALLEDPKT = 15
} DFUStatus;

typedef enum {
    DFU_DETACH = 0,
    DFU_DNLOAD = 1,
    DFU_UPLOAD = 2,
    DFU_GETSTATUS = 3,
    DFU_CLRSTATUS = 4,
    DFU_GETSTATE = 5,
    DFU_ABORT = 6
} DFURequest;

typedef enum {
    DFUAPIFail = 0,
    DFUAPISuccess,
} dfuAPIResult;

struct dfuDev {
    struct dfuAPI *api;
    int found_pid;
    int detached;
    char descr[256];
    dfuAPIResult res;
    char err[256];
    /* API private */
#ifdef WIN32
    HANDLE fh;
    HANDLE ph;
    DWORD ec; /* winapi error code */
#endif
#ifdef USE_LIBUSBAPI
    libusb_context* ctx;
    libusb_device_handle* devh;
    int rc; /* libusb return code */
#endif
#ifdef __APPLE__
    IOUSBDeviceInterface** dev;
    kern_return_t kr;
#endif
};

struct dfuAPI {
    char *name;
    dfuAPIResult (*open_fn)(struct dfuDev*, int*);
    dfuAPIResult (*dfureq_fn)(struct dfuDev*, struct usbControlSetup*, void*);
    dfuAPIResult (*reset_fn)(struct dfuDev*);
    void (*close_fn)(struct dfuDev*);
};


/*
 * DFU API low-level (specific) functions
 */
static bool dfu_check_id(int vid, int pid, int *pid_list)
{
    int *p;
    if (vid != APPLE_VID)
        return 0;
    for (p = pid_list; *p; p++)
        if (*p == pid)
            return 1;
    return 0;
}

/* adds extra DFU request error info */
static void dfu_add_reqerrstr(struct dfuDev *dfuh, struct usbControlSetup *cs)
{
    snprintf(dfuh->err + strlen(dfuh->err),
        sizeof(dfuh->err) - strlen(dfuh->err), " (cs=%02x/%d/%d/%d/%d)",
        cs->bmRequestType, cs->bRequest, cs->wValue, cs->wIndex, cs->wLength);
}

#ifdef WIN32
static bool dfu_winapi_chkrc(struct dfuDev *dfuh, char *str, bool success)
{
    dfuh->res = (success) ? DFUAPISuccess : DFUAPIFail;
    if (!success) {
        dfuh->ec = GetLastError();
        snprintf(dfuh->err, sizeof(dfuh->err), "%s error %ld", str, dfuh->ec);
    }
    return success;
}

static dfuAPIResult dfu_winapi_request(struct dfuDev *dfuh,
                            struct usbControlSetup* cs, void* data)
{
    unsigned char buf[USB_CS_SZ + DFU_PKT_SZ];
    DWORD rdwr;
    bool rc;

    memcpy(buf, cs, USB_CS_SZ);

    if (cs->bmRequestType & 0x80)
    {
        rc = ReadFile(dfuh->ph, buf, USB_CS_SZ + cs->wLength, &rdwr, NULL);
        memcpy(data, buf+USB_CS_SZ, cs->wLength);
        dfu_winapi_chkrc(dfuh, "DFU request failed: ReadFile()", rc);
    }
    else
    {
        memcpy(buf+USB_CS_SZ, data, cs->wLength);
        rc = WriteFile(dfuh->ph, buf, USB_CS_SZ + cs->wLength, &rdwr, NULL);
        dfu_winapi_chkrc(dfuh, "DFU request failed: WriteFile()", rc);
    }
    if (!rc)
        dfu_add_reqerrstr(dfuh, cs);

    return dfuh->res;
}

static dfuAPIResult dfu_winapi_reset(struct dfuDev *dfuh)
{
    DWORD bytesReturned;
    bool rc = DeviceIoControl(dfuh->fh,
                0x22000c, NULL, 0, NULL, 0, &bytesReturned, NULL);
    dfu_winapi_chkrc(dfuh,
                "Could not reset USB device: DeviceIoControl()", rc);
    return dfuh->res;
}

static void dfu_winapi_close(struct dfuDev *dfuh)
{
    if (dfuh->fh != INVALID_HANDLE_VALUE) {
        CloseHandle(dfuh->fh);
        dfuh->fh = INVALID_HANDLE_VALUE;
    }
    if (dfuh->ph != INVALID_HANDLE_VALUE) {
        CloseHandle(dfuh->ph);
        dfuh->ph = INVALID_HANDLE_VALUE;
    }
}

static const GUID GUID_AAPLDFU =
    { 0xB8085869L, 0xFEB9, 0x404B, {0x8C, 0xB1, 0x1E, 0x5C, 0x14, 0xFA, 0x8C, 0x54}};

static dfuAPIResult dfu_winapi_open(struct dfuDev *dfuh, int *pid_list)
{
    const GUID *guid = &GUID_AAPLDFU;
    HDEVINFO devinfo = NULL;
    SP_DEVICE_INTERFACE_DETAIL_DATA_A* details = NULL;
    SP_DEVICE_INTERFACE_DATA iface;
    char *path = NULL;
    DWORD i, size;
    bool rc;

    dfuh->fh =
    dfuh->ph = INVALID_HANDLE_VALUE;
    dfuh->found_pid = 0;
    dfuh->res = DFUAPISuccess;
    dfuh->ec = 0;

    /* Get DFU path */
    devinfo = SetupDiGetClassDevsA(guid, NULL, NULL,
                            DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (!dfu_winapi_chkrc(dfuh, "SetupDiGetClassDevsA()",
                            (devinfo != INVALID_HANDLE_VALUE)))
        goto error;

    iface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

    for (i = 0; SetupDiEnumDeviceInterfaces(devinfo, NULL, guid, i, &iface); i++)
    {
        int vid, pid;

        SetupDiGetDeviceInterfaceDetailA(devinfo, &iface, NULL, 0, &size, NULL);

        if (details) free(details);
        details = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(size);
        details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
        rc = SetupDiGetDeviceInterfaceDetailA(devinfo, &iface, details, size, NULL, NULL);
        if (!dfu_winapi_chkrc(dfuh, "SetupDiGetDeviceInterfaceDetailA()", rc))
            goto error;

        CharUpperA(details->DevicePath);
        if (sscanf(details->DevicePath, "%*4cUSB#VID_%04x&PID_%04x%*s", &vid, &pid) != 2)
            continue;
        if (!dfu_check_id(vid, pid, pid_list))
            continue;

        if (path) free(path);
        path = malloc(size - sizeof(DWORD) + 16);
        memcpy(path, details->DevicePath, size - sizeof(DWORD));

        dfuh->fh = CreateFileA(path, GENERIC_READ|GENERIC_WRITE,
                    FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
        if (!dfu_winapi_chkrc(dfuh, "CreateFileA(fh)", (dfuh->fh != INVALID_HANDLE_VALUE)))
            goto error;

        strcat(path, "\\PIPE0");
        dfuh->ph = CreateFileA(path, GENERIC_READ|GENERIC_WRITE,
                    FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
        if (!dfu_winapi_chkrc(dfuh, "CreateFileA(ph)", (dfuh->ph != INVALID_HANDLE_VALUE)))
            goto error;

        /* ok */
        snprintf(dfuh->descr, sizeof(dfuh->descr), "%s", details->DevicePath);
        dfuh->found_pid = pid;
        goto bye;
    }

    if (!dfu_winapi_chkrc(dfuh, "SetupDiEnumDeviceInterfaces()",
                            (GetLastError() == ERROR_NO_MORE_ITEMS)))
       goto error;

    /* no devices found */

bye:
    if (path) free(path);
    if (details) free(details);
    if (devinfo) SetupDiDestroyDeviceInfoList(devinfo);
    return dfuh->res;

error:
    dfu_winapi_close(dfuh);
    goto bye;
}
#endif /* WIN32 */

#ifdef USE_LIBUSBAPI
static bool dfu_libusb_chkrc(struct dfuDev *dfuh, char *str)
{
    dfuh->res = (dfuh->rc < LIBUSB_SUCCESS) ? DFUAPIFail : DFUAPISuccess;
    if (dfuh->res == DFUAPIFail)
        snprintf(dfuh->err, sizeof(dfuh->err),
                    "%s: %s", str, libusb_error_name(dfuh->rc));
    return (dfuh->res == DFUAPISuccess);
}

static dfuAPIResult dfu_libusb_request(struct dfuDev *dfuh,
                            struct usbControlSetup *cs, void *data)
{
    dfuh->rc = libusb_control_transfer(dfuh->devh, cs->bmRequestType,
            cs->bRequest, cs->wValue, cs->wIndex, data, cs->wLength, 500);
    if (!dfu_libusb_chkrc(dfuh, "DFU request failed"))
        dfu_add_reqerrstr(dfuh, cs);
    return dfuh->res;
}

static dfuAPIResult dfu_libusb_reset(struct dfuDev *dfuh)
{
    dfuh->rc = libusb_reset_device(dfuh->devh);
    dfu_libusb_chkrc(dfuh, "Could not reset USB device");
    return dfuh->res;
}

static void dfu_libusb_close(struct dfuDev *dfuh)
{
    if (dfuh->devh) {
        libusb_release_interface(dfuh->devh, 0);
        if (dfuh->detached)
            libusb_attach_kernel_driver(dfuh->devh, 0);
        libusb_close(dfuh->devh);
        dfuh->devh = NULL;
    }
    if (dfuh->ctx) {
        libusb_exit(dfuh->ctx);
        dfuh->ctx = NULL;
    }
}

static dfuAPIResult dfu_libusb_open(struct dfuDev *dfuh, int *pid_list)
{
    struct libusb_device_descriptor desc;
    libusb_device **devs = NULL, *dev;
    int n_devs, i;

    dfuh->devh = NULL;
    dfuh->found_pid = 0;
    dfuh->detached = 0;
    dfuh->res = DFUAPISuccess;

    dfuh->rc = libusb_init(&(dfuh->ctx));
    if (!dfu_libusb_chkrc(dfuh, "Could not init USB library")) {
        dfuh->ctx = NULL; /* invalidate ctx (if any) */
        goto error;
    }

    n_devs =
    dfuh->rc = libusb_get_device_list(dfuh->ctx, &devs);
    if (!dfu_libusb_chkrc(dfuh, "Could not get USB device list"))
        goto error;

    for (i = 0; i < n_devs; ++i)
    {
        dev = devs[i];

        /* Note: since libusb-1.0.16 (LIBUSB_API_VERSION >= 0x01000102)
           this function always succeeds. */
        if (libusb_get_device_descriptor(dev, &desc) != LIBUSB_SUCCESS)
            continue; /* Unable to get device descriptor */

        if (!dfu_check_id(desc.idVendor, desc.idProduct, pid_list))
            continue;

        dfuh->rc = libusb_open(dev, &(dfuh->devh));
        if (!dfu_libusb_chkrc(dfuh, "Could not open USB device"))
            goto error;

        dfuh->rc = libusb_set_configuration(dfuh->devh, 1);
        if (!dfu_libusb_chkrc(dfuh, "Could not set USB configuration"))
            goto error;

        dfuh->rc = libusb_kernel_driver_active(dfuh->devh, 0);
        if (dfuh->rc != LIBUSB_ERROR_NOT_SUPPORTED) {
            if (!dfu_libusb_chkrc(dfuh, "Could not get USB driver status"))
                goto error;
            if (dfuh->rc == 1) {
                dfuh->rc = libusb_detach_kernel_driver(dfuh->devh, 0);
                if (!dfu_libusb_chkrc(dfuh, "Could not detach USB driver"))
                    goto error;
                dfuh->detached = 1;
            }
        }

        dfuh->rc = libusb_claim_interface(dfuh->devh, 0);
        if (!dfu_libusb_chkrc(dfuh, "Could not claim USB interface"))
            goto error;

        /* ok */
        snprintf(dfuh->descr, sizeof(dfuh->descr),
                "[%04x:%04x] at bus %d, device %d, USB ver. %04x",
                desc.idVendor, desc.idProduct, libusb_get_bus_number(dev),
                libusb_get_device_address(dev), desc.bcdUSB);
        dfuh->found_pid = desc.idProduct;
        break;
    }

bye:
    if (devs)
        libusb_free_device_list(devs, 1);
    if (!dfuh->found_pid)
        dfu_libusb_close(dfuh);
    return dfuh->res;

error:
    goto bye;
}
#endif /* USE_LIBUSBAPI */

#ifdef __APPLE__
static bool dfu_iokit_chkrc(struct dfuDev *dfuh, char *str)
{
    dfuh->res = (dfuh->kr == kIOReturnSuccess) ? DFUAPISuccess : DFUAPIFail;
    if (dfuh->res == DFUAPIFail)
        snprintf(dfuh->err, sizeof(dfuh->err),
                    "%s: error %08x", str, dfuh->kr);
    return (dfuh->res == DFUAPISuccess);
}

static dfuAPIResult dfu_iokit_request(struct dfuDev *dfuh,
                            struct usbControlSetup *cs, void *data)
{
    IOUSBDevRequest req;
    req.bmRequestType = cs->bmRequestType;
    req.bRequest = cs->bRequest;
    req.wValue = cs->wValue;
    req.wIndex = cs->wIndex;
    req.wLength = cs->wLength;
    req.pData = data;

    dfuh->kr = (*(dfuh->dev))->DeviceRequest(dfuh->dev, &req);
    if (!dfu_iokit_chkrc(dfuh, "DFU request failed"))
        dfu_add_reqerrstr(dfuh, cs);

    return dfuh->res;
}

static dfuAPIResult dfu_iokit_reset(struct dfuDev *dfuh)
{
    dfuh->kr = (*(dfuh->dev))->ResetDevice(dfuh->dev);
#if 0
    /* On 10.11+ ResetDevice() returns no error but does not perform
     * any reset, just a kernel log message.
     * USBDeviceReEnumerate() could be used as a workaround.
     */
    dfuh->kr = (*(dfuh->dev))->USBDeviceReEnumerate(dfuh->dev, 0);
#endif
    dfu_iokit_chkrc(dfuh, "Could not reset USB device");
    return dfuh->res;
}

static void dfu_iokit_close(struct dfuDev *dfuh)
{
    if (dfuh->dev) {
        (*(dfuh->dev))->USBDeviceClose(dfuh->dev);
        (*(dfuh->dev))->Release(dfuh->dev);
        dfuh->dev = NULL;
    }
}

static dfuAPIResult dfu_iokit_open(struct dfuDev *dfuh, int *pid_list)
{
    kern_return_t kr;
    CFMutableDictionaryRef usb_matching_dict = 0;
    io_object_t usbDevice;
    io_iterator_t usb_iterator = IO_OBJECT_NULL;
    IOCFPlugInInterface **plugInInterface = NULL;
    IOUSBDeviceInterface **dev = NULL;
    HRESULT result;
    SInt32 score;
    UInt16 vendor;
    UInt16 product;
    UInt16 release;

    dfuh->dev = NULL;
    dfuh->found_pid = 0;
    dfuh->res = DFUAPISuccess;

    usb_matching_dict = IOServiceMatching(kIOUSBDeviceClassName);
    dfuh->kr = IOServiceGetMatchingServices(
                kIOMasterPortDefault, usb_matching_dict, &usb_iterator);
    if (!dfu_iokit_chkrc(dfuh, "Could not get matching services"))
        goto error;

    while ((usbDevice = IOIteratorNext(usb_iterator)))
    {
        /* Create an intermediate plug-in */
        kr = IOCreatePlugInInterfaceForService(usbDevice,
                    kIOUSBDeviceUserClientTypeID,
                    kIOCFPlugInInterfaceID,
                    &plugInInterface,
                    &score);
        IOObjectRelease(usbDevice);

        if ((kIOReturnSuccess != kr) || !plugInInterface)
            continue; /* Unable to create a plugin */

        /* Now create the device interface */
        result = (*plugInInterface)->QueryInterface(plugInInterface,
                    CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
                    (LPVOID*)&dev);
        (*plugInInterface)->Release(plugInInterface);

        if (result || !dev)
            continue; /* Couldn't create a device interface */

        kr = (*dev)->GetDeviceVendor(dev, &vendor);
        kr = (*dev)->GetDeviceProduct(dev, &product);
        kr = (*dev)->GetDeviceReleaseNumber(dev, &release);

        if (!dfu_check_id(vendor, product, pid_list)) {
            (*dev)->Release(dev);
            continue;
        }

        /* Device found, open it */
        dfuh->kr = (*dev)->USBDeviceOpen(dev);
        if (!dfu_iokit_chkrc(dfuh, "Could not open USB device")) {
            (*dev)->Release(dev);
            goto error;
        }

        /* ok */
        dfuh->found_pid = product;
        dfuh->dev = dev;
        snprintf(dfuh->descr, sizeof(dfuh->descr),
                "[%04x:%04x] release: %d", vendor, product, release);
        break;
    }

bye:
    if (usb_iterator != IO_OBJECT_NULL)
        IOObjectRelease(usb_iterator);
    return dfuh->res;

error:
    goto bye;
}
#endif /* __APPLE__ */

/* list of suported APIs */
static struct dfuAPI api_list[] =
{
#ifdef WIN32
    { "winapi",
      dfu_winapi_open,
      dfu_winapi_request,
      dfu_winapi_reset,
      dfu_winapi_close },
#endif
#ifdef USE_LIBUSBAPI
    { "libusb",
      dfu_libusb_open,
      dfu_libusb_request,
      dfu_libusb_reset,
      dfu_libusb_close },
#endif
#ifdef __APPLE__
    { "IOKit",
      dfu_iokit_open,
      dfu_iokit_request,
      dfu_iokit_reset,
      dfu_iokit_close },
#endif
};
#define N_DFU_APIS (sizeof(api_list)/sizeof(struct dfuAPI))


/*
 * DFU API common functions
 */
static int DEBUG_DFUREQ = 0;

static dfuAPIResult dfuapi_request(struct dfuDev *dfuh,
                            struct usbControlSetup *cs, void *data)
{
    if (!DEBUG_DFUREQ)
        return dfuh->api->dfureq_fn(dfuh, cs, data);

    /* DEBUG */

    /* previous state */
    unsigned char ste = 0;
    struct usbControlSetup css = { 0xA1, DFU_GETSTATE, 0, 0, sizeof(ste) };
    if (dfuh->api->dfureq_fn(dfuh, &css, &ste) != DFUAPISuccess) {
        snprintf(dfuh->err + strlen(dfuh->err), sizeof(dfuh->err) -
                strlen(dfuh->err), " [DEBUG_DFUREQ ERROR: state=%d]", ste);
        goto error;
    }

    dfuh->api->dfureq_fn(dfuh, cs, data);
    fprintf(stderr, "[DEBUG]: REQ: ste=%d, cs=%2x/%d/%d/%d/%d -> %s",
                ste, cs->bmRequestType, cs->bRequest, cs->wValue,
                cs->wIndex, cs->wLength,
                (dfuh->res == DFUAPISuccess) ? "ok" : "ERROR");
    if (cs->bRequest == DFU_GETSTATE)
        fprintf(stderr, " (state=%d)", *((unsigned char*)(data)));
    if (cs->bRequest == DFU_GETSTATUS) {
        struct usbStatusData *sd = (struct usbStatusData*)data;
        fprintf(stderr, " (status=%d, polltmo=%d, state=%d)", sd->bStatus,
                (sd->bwPollTimeout2 << 16) | (sd->bwPollTimeout1 << 8) |
                (sd->bwPollTimeout0), sd->bState);
    }
    fputc('\n', stderr);
    fflush(stderr);

bye:
    return dfuh->res;
error:
    goto bye;
}

static dfuAPIResult dfuapi_req_getstatus(struct dfuDev *dfuh,
                            DFUStatus *status, int *poll_tmo /*ms*/,
                            DFUState *state)
{
    struct usbStatusData sd = { 0, 0, 0, 0, 0, 0 };
    struct usbControlSetup cs = { 0xA1, DFU_GETSTATUS, 0, 0, sizeof(sd) };
    dfuapi_request(dfuh, &cs, &sd);
    if (status) *status = sd.bStatus;
    if (state) *state = sd.bState;
    if (poll_tmo) *poll_tmo = (sd.bwPollTimeout2 << 16) |
                (sd.bwPollTimeout1 << 8) | (sd.bwPollTimeout0);
    return dfuh->res;
}

static dfuAPIResult dfuapi_req_getstate(struct dfuDev *dfuh, DFUState *state)
{
    unsigned char sts = 0;
    struct usbControlSetup cs = { 0xA1, DFU_GETSTATE, 0, 0, sizeof(sts) };
    dfuapi_request(dfuh, &cs, &sts);
    if (state) *state = sts;
    return dfuh->res;
}

static dfuAPIResult dfuapi_req_dnload(struct dfuDev* dfuh, uint16_t blknum,
                            uint16_t len, unsigned char *data)
{
    struct usbControlSetup cs = { 0x21, DFU_DNLOAD, blknum, 0, len };
    return dfuapi_request(dfuh, &cs, data);
}

/* not used */
#if 0
static dfuAPIResult dfuapi_req_upload(struct dfuDev* dfuh,
                uint16_t blknum, uint16_t len, unsigned char *data)
{
    struct usbControlSetup cs = { 0xA1, DFU_UPLOAD, blknum, 0, len };
    return dfuapi_request(dfuh, &cs, data);
}

static dfuAPIResult dfuapi_req_clrstatus(struct dfuDev* dfuh)
{
    struct usbControlSetup cs = { 0x21, DFU_CLRSTATUS, 0, 0, 0 };
    return dfuapi_request(dfuh, &cs, NULL);
}

static dfuAPIResult dfuapi_req_abort(struct dfuDev* dfuh)
{
    struct usbControlSetup cs = { 0x21, DFU_ABORT, 0, 0, 0 };
    return dfuapi_request(dfuh, &cs, NULL);
}

/* not implemented on DFU8702 */
static dfuAPIResult dfuapi_req_detach(struct dfuDev* dfuh, int tmo)
{
    struct usbControlSetup cs = { 0x21, DFU_DETACH, tmo, 0, 0 };
    return dfuapi_request(dfuh, &cs, NULL);
}
#endif

static dfuAPIResult dfuapi_reset(struct dfuDev *dfuh)
{
    return dfuh->api->reset_fn(dfuh);
}

static dfuAPIResult dfuapi_send_packet(struct dfuDev* dfuh, uint16_t blknum,
                            uint16_t len, unsigned char *data, DFUStatus *status,
                            int *poll_tmo, DFUState *state, DFUState *pre_state)
{
    if (dfuapi_req_dnload(dfuh, blknum, len, data) != DFUAPISuccess)
        goto error;

    /* device is in dfuDLSYNC state, waiting for a GETSTATUS request
     * to enter the next state, if she respond with dfuDLBUSY then
     * we must wait to resend the GETSTATUS request */

    if (dfuapi_req_getstatus(dfuh, status, poll_tmo, state) != DFUAPISuccess)
        goto error;

    if (*state == dfuDNBUSY) {
        if (*poll_tmo)
            sleep_ms(*poll_tmo);
        if (pre_state)
            if (dfuapi_req_getstate(dfuh, pre_state) != DFUAPISuccess)
                goto error;
        if (dfuapi_req_getstatus(dfuh, status, poll_tmo, state) != DFUAPISuccess)
            goto error;
    }

bye:
    return dfuh->res;
error:
    goto bye;
}

static void dfuapi_set_err(struct dfuDev *dfuh, char *str)
{
    dfuh->res = DFUAPIFail;
    strncpy(dfuh->err, str, sizeof(dfuh->err));
}

static dfuAPIResult dfuapi_open(struct dfuDev *dfuh, int pid)
{
    int pid_l[N_KNOWN_PIDS+1] = { 0 };
    struct dfuAPI *api;
    unsigned i, p;

    /* fill pid list */
    if (pid)
        pid_l[0] = pid;
    else
        for (p = 0; p < N_KNOWN_PIDS; p++)
            pid_l[p] = known_pids[p].pid;

    for (i = 0; i < N_DFU_APIS; i++)
    {
        api = &api_list[i];
        if (api->open_fn(dfuh, pid_l) != DFUAPISuccess)
            goto error;
        if (dfuh->found_pid) {
            /* ok */
            dfuh->api = api;
            printf("[INFO] %s: found %s\n", api->name, dfuh->descr);
            for (p = 0; p < N_KNOWN_PIDS; p++) {
                if (known_pids[p].pid == dfuh->found_pid) {
                    printf("[INFO] iPod %s, mode: %s\n", known_pids[p].desc,
                                known_pids[p].mode ? "WTF" : "DFU");
                    break;
                }
            }
            fflush(stdout);
            goto bye;
        }
        printf("[INFO] %s: no DFU devices found\n", api->name);
        fflush(stdout);
    }

    /* error */
    dfuapi_set_err(dfuh, "DFU device not found");

bye:
    return dfuh->res;
error:
    goto bye;
}

static void dfuapi_destroy(struct dfuDev *dfuh)
{
    if (dfuh) {
        if (dfuh->api)
            dfuh->api->close_fn(dfuh);
        free(dfuh);
    }
}

static struct dfuDev *dfuapi_create(void)
{
        return calloc(1, sizeof(struct dfuDev));
}


/*
 * app level functions
 */
static int ipoddfu_download_file(struct dfuDev* dfuh,
                            unsigned char *data, unsigned long size)
{
    unsigned int blknum, len, remaining;
    int poll_tmo;
    DFUStatus status;
    DFUState state;

    if (dfuapi_req_getstate(dfuh, &state) != DFUAPISuccess)
        goto error;

    if (state != dfuIDLE) {
        dfuapi_set_err(dfuh, "Could not start DFU download: not idle");
        goto error;
    }

    blknum = 0;
    remaining = size;
    while (remaining)
    {
        len = (remaining < DFU_PKT_SZ) ? remaining : DFU_PKT_SZ;

        if (dfuapi_send_packet(dfuh, blknum, len, data + blknum*DFU_PKT_SZ,
                    &status, &poll_tmo, &state, NULL) != DFUAPISuccess)
            goto error;

        if (state != dfuDNLOAD_IDLE) {
            dfuapi_set_err(dfuh, "DFU download aborted: unexpected state");
            goto error;
        }

        remaining -= len;
        blknum++;
    }

    /* send ZLP */
    DFUState pre_state = appIDLE; /* dummy state */
    if (dfuapi_send_packet(dfuh, blknum, 0, NULL,
                &status, &poll_tmo, &state, &pre_state) != DFUAPISuccess) {
        if (pre_state == dfuMANIFEST_SYNC)
            goto ok; /* pwnaged .dfu file */
        goto error;
    }

    if (state != dfuMANIFEST) {
        if (status == errFIRMWARE)
            dfuapi_set_err(dfuh, "DFU download failed: corrupt firmware");
        else
            dfuapi_set_err(dfuh, "DFU download failed: unexpected state");
        goto error;
    }

    /* wait for manifest stage */
    if (poll_tmo)
        sleep_ms(poll_tmo);

    if (dfuapi_req_getstatus(dfuh, &status, NULL, &state) != DFUAPISuccess)
        goto ok; /* 1223 .dfu file */

    /* XXX: next code never tested */

    if (state != dfuMANIFEST_WAIT_RESET) {
        if (status == errVERIFY)
            dfuapi_set_err(dfuh, "DFU manifest failed: wrong FW verification");
        else
            dfuapi_set_err(dfuh, "DFU manifest failed: unexpected state");
        goto error;
    }

    if (dfuapi_reset(dfuh) != DFUAPISuccess)
        goto error;

ok:
    return 1;
error:
    return 0;
}

/* exported functions */
int ipoddfu_send(int pid, unsigned char *data, int size,
                            char* errstr, int errstrsize)
{
    struct dfuDev *dfuh;
    unsigned char *buf;
    uint32_t checksum;
    int ret = 1; /* ok */

    dfuh = dfuapi_create();

    buf = malloc(size+4);
    if (!buf) {
        dfuapi_set_err(dfuh, "Could not allocate memory for DFU buffer");
        goto error;
    }

    if (memcmp(data, IM3_IDENT, 4)) {
        dfuapi_set_err(dfuh, "Bad DFU image data");
        goto error;
    }

    crc32_init();
    checksum = crc32(data, size, 0);
    memcpy(buf, data, size);
    put_uint32le(buf+size, ~checksum);

    if (dfuapi_open(dfuh, pid) != DFUAPISuccess)
        goto error;

    if (!ipoddfu_download_file(dfuh, buf, size+4))
        goto error;

bye:
    if (buf) free(buf);
    dfuapi_destroy(dfuh);
    return ret;

error:
    ret = 0;
    if (errstr)
        snprintf(errstr, errstrsize, "[ERR] %s", dfuh->err);
    goto bye;
}

/* search for the DFU device and gets its DFUState */
int ipoddfu_scan(int pid, int *state, int reset,
                            char* errstr, int errstrsize)
{
    struct dfuDev *dfuh;
    int ret = 1; /* ok */

    dfuh = dfuapi_create();

    if (dfuapi_open(dfuh, pid) != DFUAPISuccess)
        goto error;

    if (reset)
        if (dfuapi_reset(dfuh) != DFUAPISuccess)
            goto error;

    if (state) {
        DFUState sts;
        if (dfuapi_req_getstate(dfuh, &sts) != DFUAPISuccess)
            goto error;
        *state = (int)sts;
    }

bye:
    dfuapi_destroy(dfuh);
    return ret;

error:
    ret = 0;
    if (errstr)
        snprintf(errstr, errstrsize, "[ERR] %s", dfuh->err);
    goto bye;
}

void ipoddfu_debug(int debug)
{
    DEBUG_DFUREQ = debug;
}
