parse_report_descriptor(report_descriptor_t *rdesc)
{
    hid_field_t field;
    guint8 *data = rdesc->desc_body;
    unsigned int tag, type, size;
    guint8 prefix;
    guint32 defined = 0, usage = 0, usage_min = 0, usage_max = 0;
    wmem_allocator_t *scope = wmem_file_scope();
    gboolean first_item = TRUE;

    memset(&field, 0, sizeof(field));
    field.usages = wmem_array_new(scope, sizeof(guint32));
    rdesc->fields_in = wmem_array_new(scope, sizeof(hid_field_t));
    rdesc->fields_out = wmem_array_new(scope, sizeof(hid_field_t));

    int i = 0;
    while (i < rdesc->desc_length)
    {
        prefix = data[i];
        tag = (prefix & 0b11110000) >> 4;
        type = (prefix & 0b00001100) >> 2;
        size = prefix & 0b00000011;

        if (size == 3)  /* HID spec: 6.2.2.2 - Short Items */
            size = 4;

        switch (type)
        {
            case USBHID_ITEMTYPE_MAIN:
                switch (tag)
                {
                    case USBHID_MAINITEM_TAG_INPUT:
                        field.properties = hid_unpack_value(data, i, size);

                        if ((defined & HID_REQUIRED_MASK) != HID_REQUIRED_MASK)
                            goto err;

                        /* new field */
                        wmem_array_append_one(rdesc->fields_in, field);

                        field.usages = wmem_array_new(scope, sizeof(guint32));
                        first_item = FALSE;

                        /* only keep the global items */
                        defined &= HID_GLOBAL_MASK;
                        break;

                    case USBHID_MAINITEM_TAG_OUTPUT:
                        field.properties = hid_unpack_value(data, i, size);

                        if ((defined & HID_REQUIRED_MASK) != HID_REQUIRED_MASK)
                            goto err;

                        /* new field */
                        wmem_array_append_one(rdesc->fields_out, field);

                        field.usages = wmem_array_new(scope, sizeof(guint32));
                        first_item = FALSE;

                        defined &= HID_GLOBAL_MASK;
                        break;

                    case USBHID_MAINITEM_TAG_FEATURE:
                        /*
                        field.properties = hid_unpack_value(data, i, size);
                        TODO
                        */
                        break;

                    case USBHID_MAINITEM_TAG_COLLECTION:
                        /* clear usages */
                        wmem_free(scope, field.usages);
                        field.usages = wmem_array_new(scope, sizeof(guint32));
                        break;

                    default:
                        break;
                }
                break;

            case USBHID_ITEMTYPE_GLOBAL:
                switch (tag)
                {
                    case USBHID_GLOBALITEM_TAG_USAGE_PAGE:
                        field.usage_page = hid_unpack_value(data, i, size);
                        defined |= HID_USAGE_PAGE;
                        break;

                    case USBHID_GLOBALITEM_TAG_LOG_MIN:
                        if (hid_unpack_signed(data, i, size, &field.logical_min))
                            goto err;
                        defined |= HID_LOGICAL_MIN;
                        break;

                    case USBHID_GLOBALITEM_TAG_LOG_MAX:
                        if (hid_unpack_signed(data, i, size, &field.logical_max))
                            goto err;
                        defined |= HID_LOGICAL_MAX;
                        break;

                    case USBHID_GLOBALITEM_TAG_REPORT_SIZE:
                        field.report_size = hid_unpack_value(data, i, size);
                        defined |= HID_REPORT_SIZE;
                        break;

                    case USBHID_GLOBALITEM_TAG_REPORT_ID:
                        if (!first_item && !rdesc->uses_report_id)
                            goto err;

                        rdesc->uses_report_id = TRUE;

                        field.report_id = hid_unpack_value(data, i, size);
                        defined |= HID_REPORT_ID;
                        break;

                    case USBHID_GLOBALITEM_TAG_REPORT_COUNT:
                        field.report_count = hid_unpack_value(data, i, size);
                        defined |= HID_REPORT_COUNT;
                        break;

                    case USBHID_GLOBALITEM_TAG_PUSH:
                    case USBHID_GLOBALITEM_TAG_POP:
                        /* TODO */
                        goto err;

                    default:
                        break;
                }
                break;

            case USBHID_ITEMTYPE_LOCAL:
                switch (tag)
                {
                    case USBHID_LOCALITEM_TAG_USAGE:
                        if (!(defined & HID_USAGE_PAGE))
                            return FALSE;

                        usage = hid_unpack_value(data, i, size);

                        wmem_array_append_one(field.usages, usage);
                        break;

                    case USBHID_LOCALITEM_TAG_USAGE_MIN:
                        usage_min = hid_unpack_value(data, i, size);
                        defined |= HID_USAGE_MIN;
                        break;

                    case USBHID_LOCALITEM_TAG_USAGE_MAX:
                        if (!(defined & HID_USAGE_MIN))
                            return FALSE;

                        usage_max = hid_unpack_value(data, i, size);
                        if (usage_min >= usage_max) {
                            goto err;
                        }

                        wmem_array_grow(field.usages, usage_max - usage_min);
                        for (guint32 j = usage_min; j < usage_max; j++)
                            wmem_array_append_one(field.usages, j);

                        defined ^= HID_USAGE_MIN;
                        break;

                    default: /* TODO */
                        goto err;
                }
                break;

            default: /* reserved */
                goto err;
        }

        i += size + 1;
    }

    return TRUE;

err:
    for (unsigned int j = 0; j < wmem_array_get_count(rdesc->fields_in); j++)
        wmem_free(scope, ((hid_field_t*) wmem_array_index(rdesc->fields_in, j))->usages);

    for (unsigned int j = 0; j < wmem_array_get_count(rdesc->fields_out); j++)
        wmem_free(scope, ((hid_field_t*) wmem_array_index(rdesc->fields_out, j))->usages);

    wmem_free(scope, rdesc->fields_in);
    wmem_free(scope, rdesc->fields_out);
    return FALSE;
}
parse_report_descriptor(report_descriptor_t *rdesc)
{
    hid_field_t field;
    guint8 *data = rdesc->desc_body;
    unsigned int tag, type, size;
    guint8 prefix;
    guint32 defined = 0, usage = 0, usage_min = 0, usage_max = 0;
    wmem_allocator_t *scope = wmem_file_scope();
    gboolean first_item = TRUE;

    memset(&field, 0, sizeof(field));
    field.usages = wmem_array_new(scope, sizeof(guint32));
    rdesc->fields_in = wmem_array_new(scope, sizeof(hid_field_t));
    rdesc->fields_out = wmem_array_new(scope, sizeof(hid_field_t));

    int i = 0;
    while (i < rdesc->desc_length)
    {
        prefix = data[i];
        tag = (prefix & 0b11110000) >> 4;
        type = (prefix & 0b00001100) >> 2;
        size = prefix & 0b00000011;

        if (size == 3)   
            size = 4;

        switch (type)
        {
            case USBHID_ITEMTYPE_MAIN:
                switch (tag)
                {
                    case USBHID_MAINITEM_TAG_INPUT:
                        field.properties = hid_unpack_value(data, i, size);

                        if ((defined & HID_REQUIRED_MASK) != HID_REQUIRED_MASK)
                            goto err;

                         
                        wmem_array_append_one(rdesc->fields_in, field);

                        field.usages = wmem_array_new(scope, sizeof(guint32));
                        first_item = FALSE;

                         
                        defined &= HID_GLOBAL_MASK;
                        break;

                    case USBHID_MAINITEM_TAG_OUTPUT:
                        field.properties = hid_unpack_value(data, i, size);

                        if ((defined & HID_REQUIRED_MASK) != HID_REQUIRED_MASK)
                            goto err;

                         
                        wmem_array_append_one(rdesc->fields_out, field);

                        field.usages = wmem_array_new(scope, sizeof(guint32));
                        first_item = FALSE;

                        defined &= HID_GLOBAL_MASK;
                        break;

                    case USBHID_MAINITEM_TAG_FEATURE:
                         
                        break;

                    case USBHID_MAINITEM_TAG_COLLECTION:
                         
                        wmem_free(scope, field.usages);
                        field.usages = wmem_array_new(scope, sizeof(guint32));
                        break;

                    default:
                        break;
                }
                break;

            case USBHID_ITEMTYPE_GLOBAL:
                switch (tag)
                {
                    case USBHID_GLOBALITEM_TAG_USAGE_PAGE:
                        field.usage_page = hid_unpack_value(data, i, size);
                        defined |= HID_USAGE_PAGE;
                        break;

                    case USBHID_GLOBALITEM_TAG_LOG_MIN:
                        if (hid_unpack_signed(data, i, size, &field.logical_min))
                            goto err;
                        defined |= HID_LOGICAL_MIN;
                        break;

                    case USBHID_GLOBALITEM_TAG_LOG_MAX:
                        if (hid_unpack_signed(data, i, size, &field.logical_max))
                            goto err;
                        defined |= HID_LOGICAL_MAX;
                        break;

                    case USBHID_GLOBALITEM_TAG_REPORT_SIZE:
                        field.report_size = hid_unpack_value(data, i, size);
                        defined |= HID_REPORT_SIZE;
                        break;

                    case USBHID_GLOBALITEM_TAG_REPORT_ID:
                        if (!first_item && !rdesc->uses_report_id)
                            goto err;

                        rdesc->uses_report_id = TRUE;

                        field.report_id = hid_unpack_value(data, i, size);
                        defined |= HID_REPORT_ID;
                        break;

                    case USBHID_GLOBALITEM_TAG_REPORT_COUNT:
                        field.report_count = hid_unpack_value(data, i, size);
                        defined |= HID_REPORT_COUNT;
                        break;

                    case USBHID_GLOBALITEM_TAG_PUSH:
                    case USBHID_GLOBALITEM_TAG_POP:
                         
                        goto err;

                    default:
                        break;
                }
                break;

            case USBHID_ITEMTYPE_LOCAL:
                switch (tag)
                {
                    case USBHID_LOCALITEM_TAG_USAGE:
                        if (!(defined & HID_USAGE_PAGE))
                            return FALSE;

                        usage = hid_unpack_value(data, i, size);

                        wmem_array_append_one(field.usages, usage);
                        break;

                    case USBHID_LOCALITEM_TAG_USAGE_MIN:
                        usage_min = hid_unpack_value(data, i, size);
                        defined |= HID_USAGE_MIN;
                        break;

                    case USBHID_LOCALITEM_TAG_USAGE_MAX:
                        if (!(defined & HID_USAGE_MIN))
                            return FALSE;

                        usage_max = hid_unpack_value(data, i, size);
                        if (usage_min >= usage_max) {
                            goto err;
                        }

                        wmem_array_grow(field.usages, usage_max - usage_min);
                        for (guint32 j = usage_min; j < usage_max; j++)
                            wmem_array_append_one(field.usages, j);

                        defined ^= HID_USAGE_MIN;
                        break;

                    default:  
                        goto err;
                }
                break;

            default:  
                goto err;
        }

        i += size + 1;
    }

    return TRUE;

err:
    for (unsigned int j = 0; j < wmem_array_get_count(rdesc->fields_in); j++)
        wmem_free(scope, ((hid_field_t*) wmem_array_index(rdesc->fields_in, j))->usages);

    for (unsigned int j = 0; j < wmem_array_get_count(rdesc->fields_out); j++)
        wmem_free(scope, ((hid_field_t*) wmem_array_index(rdesc->fields_out, j))->usages);

    wmem_free(scope, rdesc->fields_in);
    wmem_free(scope, rdesc->fields_out);
    return FALSE;
}