root/lib/libfido2/src/hid.c
/*
 * Copyright (c) 2018 Yubico AB. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

#include "fido.h"

static int
get_key_len(uint8_t tag, uint8_t *key, size_t *key_len)
{
        *key = tag & 0xfc;
        if ((*key & 0xf0) == 0xf0) {
                fido_log_debug("%s: *key=0x%02x", __func__, *key);
                return (-1);
        }

        *key_len = tag & 0x3;
        if (*key_len == 3) {
                *key_len = 4;
        }

        return (0);
}

static int
get_key_val(const void *body, size_t key_len, uint32_t *val)
{
        const uint8_t *ptr = body;

        switch (key_len) {
        case 0:
                *val = 0;
                break;
        case 1:
                *val = ptr[0];
                break;
        case 2:
                *val = (uint32_t)((ptr[1] << 8) | ptr[0]);
                break;
        default:
                fido_log_debug("%s: key_len=%zu", __func__, key_len);
                return (-1);
        }

        return (0);
}

int
fido_hid_get_usage(const uint8_t *report_ptr, size_t report_len,
    uint32_t *usage_page)
{
        const uint8_t   *ptr = report_ptr;
        size_t           len = report_len;

        while (len > 0) {
                const uint8_t tag = ptr[0];
                ptr++;
                len--;

                uint8_t  key;
                size_t   key_len;
                uint32_t key_val;

                if (get_key_len(tag, &key, &key_len) < 0 || key_len > len ||
                    get_key_val(ptr, key_len, &key_val) < 0) {
                        return (-1);
                }

                if (key == 0x4) {
                        *usage_page = key_val;
                }

                ptr += key_len;
                len -= key_len;
        }

        return (0);
}

int
fido_hid_get_report_len(const uint8_t *report_ptr, size_t report_len,
    size_t *report_in_len, size_t *report_out_len)
{
        const uint8_t   *ptr = report_ptr;
        size_t           len = report_len;
        uint32_t         report_size = 0;

        while (len > 0) {
                const uint8_t tag = ptr[0];
                ptr++;
                len--;

                uint8_t  key;
                size_t   key_len;
                uint32_t key_val;

                if (get_key_len(tag, &key, &key_len) < 0 || key_len > len ||
                    get_key_val(ptr, key_len, &key_val) < 0) {
                        return (-1);
                }

                if (key == 0x94) {
                        report_size = key_val;
                } else if (key == 0x80) {
                        *report_in_len = (size_t)report_size;
                } else if (key == 0x90) {
                        *report_out_len = (size_t)report_size;
                }

                ptr += key_len;
                len -= key_len;
        }

        return (0);
}

fido_dev_info_t *
fido_dev_info_new(size_t n)
{
        return (calloc(n, sizeof(fido_dev_info_t)));
}

static void
fido_dev_info_reset(fido_dev_info_t *di)
{
        free(di->path);
        free(di->manufacturer);
        free(di->product);
        memset(di, 0, sizeof(*di));
}

void
fido_dev_info_free(fido_dev_info_t **devlist_p, size_t n)
{
        fido_dev_info_t *devlist;

        if (devlist_p == NULL || (devlist = *devlist_p) == NULL)
                return;

        for (size_t i = 0; i < n; i++)
                fido_dev_info_reset(&devlist[i]);

        free(devlist);

        *devlist_p = NULL;
}

const fido_dev_info_t *
fido_dev_info_ptr(const fido_dev_info_t *devlist, size_t i)
{
        return (&devlist[i]);
}

int
fido_dev_info_set(fido_dev_info_t *devlist, size_t i,
    const char *path, const char *manufacturer, const char *product,
    const fido_dev_io_t *io, const fido_dev_transport_t *transport)
{
        char *path_copy = NULL, *manu_copy = NULL, *prod_copy = NULL;
        int r;

        if (path == NULL || manufacturer == NULL || product == NULL ||
            io == NULL) {
                r = FIDO_ERR_INVALID_ARGUMENT;
                goto out;
        }

        if ((path_copy = strdup(path)) == NULL ||
            (manu_copy = strdup(manufacturer)) == NULL ||
            (prod_copy = strdup(product)) == NULL) {
                r = FIDO_ERR_INTERNAL;
                goto out;
        }

        fido_dev_info_reset(&devlist[i]);
        devlist[i].path = path_copy;
        devlist[i].manufacturer = manu_copy;
        devlist[i].product = prod_copy;
        devlist[i].io = *io;
        if (transport)
                devlist[i].transport = *transport;
        r = FIDO_OK;
out:
        if (r != FIDO_OK) {
                free(prod_copy);
                free(manu_copy);
                free(path_copy);
        }
        return (r);
}

const char *
fido_dev_info_path(const fido_dev_info_t *di)
{
        return (di->path);
}

int16_t
fido_dev_info_vendor(const fido_dev_info_t *di)
{
        return (di->vendor_id);
}

int16_t
fido_dev_info_product(const fido_dev_info_t *di)
{
        return (di->product_id);
}

const char *
fido_dev_info_manufacturer_string(const fido_dev_info_t *di)
{
        return (di->manufacturer);
}

const char *
fido_dev_info_product_string(const fido_dev_info_t *di)
{
        return (di->product);
}