/*
 * sixpair.c version 2007-04-18
 * Compile with: gcc -o sixpair sixpair.c -lusb
 */

#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <usb.h>

#define VENDOR 0x054c
#define PRODUCT 0x0268

#define USB_DIR_IN 0x80
#define USB_DIR_OUT 0

void fatal(char *msg) {
    perror(msg);
    exit(1);
}

void show_master(usb_dev_handle *devh, int itfnum) {
    printf("Current Bluetooth master: ");
    unsigned char msg[8];
    int res = usb_control_msg
              (devh, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
               0x01, 0x03f5, itfnum, (void*)msg, sizeof(msg), 5000);
    if ( res < 0 ) {
        perror("USB_REQ_GET_CONFIGURATION");
        return;
    }
    printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
           msg[2], msg[3], msg[4], msg[5], msg[6], msg[7]);
}

void set_master(usb_dev_handle *devh, int itfnum, int mac[6]) {
    printf("Setting master bd_addr to %02x:%02x:%02x:%02x:%02x:%02x\n",
           mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    char msg[8]= { 0x01, 0x00, mac[0],mac[1],mac[2],mac[3],mac[4],mac[5] };
    int res = usb_control_msg
              (devh,
               USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
               0x09,
               0x03f5, itfnum, msg, sizeof(msg),
               5000);
    if ( res < 0 ) fatal("USB_REQ_SET_CONFIGURATION");
}

void process_device(int argc, char **argv, struct usb_device *dev,
                    struct usb_config_descriptor *cfg, int itfnum) {
    int mac[6];

    usb_dev_handle *devh = usb_open(dev);
    if ( ! devh ) fatal("usb_open");

    usb_detach_kernel_driver_np(devh, itfnum);

    int res = usb_claim_interface(devh, itfnum);
    if ( res < 0 ) fatal("usb_claim_interface");

    show_master(devh, itfnum);

    if ( argc >= 2 ) {
        if ( sscanf(argv[1], "%x:%x:%x:%x:%x:%x",
                    &mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) != 6 ) {

            printf("usage: %s [<bd_addr of master>]\n", argv[0]);
            exit(1);
        }
    } else {
        FILE *f = popen("hcitool dev", "r");
        if ( !f ||
                fscanf(f, "%*s\n%*s %x:%x:%x:%x:%x:%x",
                       &mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) != 6 ) {
            printf("Unable to retrieve local bd_addr from `hcitool dev`.\n");
            printf("Please enable Bluetooth or specify an address manually.\n");
            exit(1);
        }
        pclose(f);
    }

    set_master(devh, itfnum, mac);

    usb_close(devh);
}

int main(int argc, char *argv[]) {

    usb_init();
    if ( usb_find_busses() < 0 ) fatal("usb_find_busses");
    if ( usb_find_devices() < 0 ) fatal("usb_find_devices");
    struct usb_bus *busses = usb_get_busses();
    if ( ! busses ) fatal("usb_get_busses");

    int found = 0;

    struct usb_bus *bus;
    for ( bus=busses; bus; bus=bus->next ) {
        struct usb_device *dev;
        for ( dev=bus->devices; dev; dev=dev->next) {
            struct usb_config_descriptor *cfg;
            for ( cfg = dev->config;
                    cfg < dev->config + dev->descriptor.bNumConfigurations;
                    ++cfg ) {
                int itfnum;
                for ( itfnum=0; itfnum<cfg->bNumInterfaces; ++itfnum ) {
                    struct usb_interface *itf = &cfg->interface[itfnum];
                    struct usb_interface_descriptor *alt;
                    for ( alt = itf->altsetting;
                            alt < itf->altsetting + itf->num_altsetting;
                            ++alt ) {
                        if ( dev->descriptor.idVendor == VENDOR &&
                                dev->descriptor.idProduct == PRODUCT &&
                                alt->bInterfaceClass == 3 ) {
                            process_device(argc, argv, dev, cfg, itfnum);
                            ++found;
                        }
                    }
                }
            }
        }
    }

    if ( ! found ) printf("No controller found on USB busses.\n");
    return 0;

}

