/*
 * Copyright (c) 2019-2026 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.
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include "fido.h"
#include "fido/bio.h"
#include "fido/es256.h"

#define CMD_ENROLL_BEGIN	0x01
#define CMD_ENROLL_NEXT		0x02
#define CMD_ENROLL_CANCEL	0x03
#define CMD_ENUM		0x04
#define CMD_SET_NAME		0x05
#define CMD_ENROLL_REMOVE	0x06
#define CMD_GET_INFO		0x07

static int
bio_prepare_hmac(uint8_t cmd, cbor_item_t **argv, size_t argc,
    cbor_item_t **param, fido_blob_t *hmac_data)
{
	const uint8_t	 prefix[2] = { 0x01 /* modality */, cmd };
	int		 ok = -1;
	size_t		 cbor_alloc_len;
	size_t		 cbor_len;
	unsigned char	*cbor = NULL;

	if (argv == NULL || param == NULL)
		return (fido_blob_set(hmac_data, prefix, sizeof(prefix)));

	if ((*param = cbor_flatten_vector(argv, argc)) == NULL) {
		fido_log_debug("%s: cbor_flatten_vector", __func__);
		goto fail;
	}

	if ((cbor_len = cbor_serialize_alloc(*param, &cbor,
	    &cbor_alloc_len)) == 0 || cbor_len > SIZE_MAX - sizeof(prefix)) {
		fido_log_debug("%s: cbor_serialize_alloc", __func__);
		goto fail;
	}

	if ((hmac_data->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) {
		fido_log_debug("%s: malloc", __func__);
		goto fail;
	}

	memcpy(hmac_data->ptr, prefix, sizeof(prefix));
	memcpy(hmac_data->ptr + sizeof(prefix), cbor, cbor_len);
	hmac_data->len = cbor_len + sizeof(prefix);

	ok = 0;
fail:
	free(cbor);

	return (ok);
}

static uint8_t
bio_get_cmd(const fido_dev_t *dev)
{
	if (dev->flags & (FIDO_DEV_BIO_SET|FIDO_DEV_BIO_UNSET))
		return (CTAP_CBOR_BIO_ENROLL);

	return (CTAP_CBOR_BIO_ENROLL_PRE);
}

static int
bio_get_uv_token(fido_dev_t *dev, const char *pin, uint8_t cmd,
    fido_blob_t *token, int *ms)
{
	es256_pk_t	*pk = NULL;
	fido_blob_t	*ecdh = NULL;
	int		 r;

	if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
		fido_log_debug("%s: fido_do_ecdh", __func__);
		return r;
	}

	r = fido_dev_get_uv_token(dev, cmd, pin, ecdh, pk, NULL, token, ms);
	if (r != FIDO_OK)
		fido_log_debug("%s: fido_dev_get_uv_token", __func__);

	es256_pk_free(&pk);
	fido_blob_free(&ecdh);

	return r;
}

static int
bio_tx(fido_dev_t *dev, uint8_t subcmd, cbor_item_t **sub_argv, size_t sub_argc,
    const char *pin, const fido_blob_t *token, int *ms)
{
	cbor_item_t	*argv[5];
	fido_blob_t	 token_store;
	fido_blob_t	 f;
	fido_blob_t	 hmac;
	const uint8_t	 cmd = bio_get_cmd(dev);
	int		 r = FIDO_ERR_INTERNAL;

	memset(&f, 0, sizeof(f));
	memset(&hmac, 0, sizeof(hmac));
	memset(&argv, 0, sizeof(argv));
	memset(&token_store, 0, sizeof(token_store));

	/* modality, subCommand */
	if ((argv[0] = cbor_build_uint8(1)) == NULL ||
	    (argv[1] = cbor_build_uint8(subcmd)) == NULL) {
		fido_log_debug("%s: cbor encode", __func__);
		goto fail;
	}

	if (pin && !token) {
		if ((r = bio_get_uv_token(dev, pin, cmd, &token_store,
		    ms)) != FIDO_OK)
			goto fail;

		token = &token_store;
	}

	if (token) {
		if (bio_prepare_hmac(subcmd, sub_argv, sub_argc, &argv[2],
		    &hmac) < 0) {
			fido_log_debug("%s: bio_prepare_hmac", __func__);
			goto fail;
		}

		if ((argv[3] = cbor_encode_pin_opt(dev)) == NULL ||
		    (argv[4] = cbor_encode_pin_auth(dev, token, &hmac)) == NULL) {
			fido_log_debug("%s: encode pin", __func__);
			goto fail;
		}
	}

	/* framing and transmission */
	if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 ||
	    fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
		fido_log_debug("%s: fido_tx", __func__);
		r = FIDO_ERR_TX;
		goto fail;
	}

	r = FIDO_OK;
fail:
	cbor_vector_free(argv, nitems(argv));
	fido_blob_reset(&token_store);
	free(f.ptr);
	free(hmac.ptr);

	return (r);
}

static void
bio_reset_template(fido_bio_template_t *t)
{
	free(t->name);
	t->name = NULL;
	fido_blob_reset(&t->id);
}

static void
bio_reset_template_array(fido_bio_template_array_t *ta)
{
	for (size_t i = 0; i < ta->n_alloc; i++)
		bio_reset_template(&ta->ptr[i]);

	free(ta->ptr);
	ta->ptr = NULL;
	memset(ta, 0, sizeof(*ta));
}

static int
decode_template(const cbor_item_t *key, const cbor_item_t *val, void *arg)
{
	fido_bio_template_t *t = arg;

	if (cbor_isa_uint(key) == false ||
	    cbor_int_get_width(key) != CBOR_INT_8) {
		fido_log_debug("%s: cbor type", __func__);
		return (0); /* ignore */
	}

	switch (cbor_get_uint8(key)) {
	case 1: /* id */
		return (fido_blob_decode(val, &t->id));
	case 2: /* name */
		return (cbor_string_copy(val, &t->name));
	}

	return (0); /* ignore */
}

static int
decode_template_array(const cbor_item_t *item, void *arg)
{
	fido_bio_template_array_t *ta = arg;

	if (cbor_isa_map(item) == false ||
	    cbor_map_is_definite(item) == false) {
		fido_log_debug("%s: cbor type", __func__);
		return (-1);
	}

	if (ta->n_rx >= ta->n_alloc) {
		fido_log_debug("%s: n_rx >= n_alloc", __func__);
		return (-1);
	}

	if (cbor_map_iter(item, &ta->ptr[ta->n_rx], decode_template) < 0) {
		fido_log_debug("%s: decode_template", __func__);
		return (-1);
	}

	ta->n_rx++;

	return (0);
}

static int
bio_parse_template_array(const cbor_item_t *key, const cbor_item_t *val,
    void *arg)
{
	fido_bio_template_array_t *ta = arg;

	if (cbor_isa_uint(key) == false ||
	    cbor_int_get_width(key) != CBOR_INT_8 ||
	    cbor_get_uint8(key) != 7) {
		fido_log_debug("%s: cbor type", __func__);
		return (0); /* ignore */
	}

	if (cbor_isa_array(val) == false ||
	    cbor_array_is_definite(val) == false) {
		fido_log_debug("%s: cbor type", __func__);
		return (-1);
	}

	if (ta->ptr != NULL || ta->n_alloc != 0 || ta->n_rx != 0) {
		fido_log_debug("%s: ptr != NULL || n_alloc != 0 || n_rx != 0",
		    __func__);
		return (-1);
	}

	if ((ta->ptr = calloc(cbor_array_size(val), sizeof(*ta->ptr))) == NULL)
		return (-1);

	ta->n_alloc = cbor_array_size(val);

	if (cbor_array_iter(val, ta, decode_template_array) < 0) {
		fido_log_debug("%s: decode_template_array", __func__);
		return (-1);
	}

	return (0);
}

static int
bio_rx_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta, int *ms)
{
	unsigned char	*msg;
	int		 msglen;
	int		 r;

	bio_reset_template_array(ta);

	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
		r = FIDO_ERR_INTERNAL;
		goto out;
	}

	if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
		fido_log_debug("%s: fido_rx", __func__);
		r = FIDO_ERR_RX;
		goto out;
	}

	if ((r = cbor_parse_reply(msg, (size_t)msglen, ta,
	    bio_parse_template_array)) != FIDO_OK) {
		fido_log_debug("%s: bio_parse_template_array" , __func__);
		goto out;
	}

	r = FIDO_OK;
out:
	freezero(msg, FIDO_MAXMSG);

	return (r);
}

static int
bio_get_template_array_wait(fido_dev_t *dev, fido_bio_template_array_t *ta,
    const char *pin, int *ms)
{
	int r;

	if ((r = bio_tx(dev, CMD_ENUM, NULL, 0, pin, fido_dev_puat_blob(dev),
	    ms)) != FIDO_OK || (r = bio_rx_template_array(dev, ta, ms)) != FIDO_OK)
		return (r);

	return (FIDO_OK);
}

int
fido_bio_dev_get_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta,
    const char *pin)
{
	int ms = dev->timeout_ms;

	if (pin == NULL && fido_dev_puat_blob(dev) == NULL)
		return (FIDO_ERR_INVALID_ARGUMENT);

	return (bio_get_template_array_wait(dev, ta, pin, &ms));
}

static int
bio_set_template_name_wait(fido_dev_t *dev, const fido_bio_template_t *t,
    const char *pin, int *ms)
{
	cbor_item_t	*argv[2];
	int		 r = FIDO_ERR_INTERNAL;

	memset(&argv, 0, sizeof(argv));

	if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
	    (argv[1] = cbor_build_string(t->name)) == NULL) {
		fido_log_debug("%s: cbor encode", __func__);
		goto fail;
	}

	if ((r = bio_tx(dev, CMD_SET_NAME, argv, 2, pin, fido_dev_puat_blob(dev),
	    ms)) != FIDO_OK ||
	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
		fido_log_debug("%s: tx/rx", __func__);
		goto fail;
	}

	r = FIDO_OK;
fail:
	cbor_vector_free(argv, nitems(argv));

	return (r);
}

int
fido_bio_dev_set_template_name(fido_dev_t *dev, const fido_bio_template_t *t,
    const char *pin)
{
	int ms = dev->timeout_ms;

	if ((pin == NULL && fido_dev_puat_blob(dev) == NULL) || t->name == NULL)
		return (FIDO_ERR_INVALID_ARGUMENT);

	return (bio_set_template_name_wait(dev, t, pin, &ms));
}

static void
bio_reset_enroll(fido_bio_enroll_t *e)
{
	e->remaining_samples = 0;
	e->last_status = 0;
	fido_blob_reset(&e->token);
}

static int
bio_parse_enroll_status(const cbor_item_t *key, const cbor_item_t *val,
    void *arg)
{
	fido_bio_enroll_t *e = arg;
	uint64_t x;

	if (cbor_isa_uint(key) == false ||
	    cbor_int_get_width(key) != CBOR_INT_8) {
		fido_log_debug("%s: cbor type", __func__);
		return (0); /* ignore */
	}

	switch (cbor_get_uint8(key)) {
	case 5:
		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
			fido_log_debug("%s: cbor_decode_uint64", __func__);
			return (-1);
		}
		e->last_status = (uint8_t)x;
		break;
	case 6:
		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
			fido_log_debug("%s: cbor_decode_uint64", __func__);
			return (-1);
		}
		e->remaining_samples = (uint8_t)x;
		break;
	default:
		return (0); /* ignore */
	}

	return (0);
}

static int
bio_parse_template_id(const cbor_item_t *key, const cbor_item_t *val,
    void *arg)
{
	fido_blob_t *id = arg;

	if (cbor_isa_uint(key) == false ||
	    cbor_int_get_width(key) != CBOR_INT_8 ||
	    cbor_get_uint8(key) != 4) {
		fido_log_debug("%s: cbor type", __func__);
		return (0); /* ignore */
	}

	return (fido_blob_decode(val, id));
}

static int
bio_rx_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
    fido_bio_enroll_t *e, int *ms)
{
	unsigned char	*msg;
	int		 msglen;
	int		 r;

	bio_reset_template(t);

	e->remaining_samples = 0;
	e->last_status = 0;

	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
		r = FIDO_ERR_INTERNAL;
		goto out;
	}

	if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
		fido_log_debug("%s: fido_rx", __func__);
		r = FIDO_ERR_RX;
		goto out;
	}

	if ((r = cbor_parse_reply(msg, (size_t)msglen, e,
	    bio_parse_enroll_status)) != FIDO_OK) {
		fido_log_debug("%s: bio_parse_enroll_status", __func__);
		goto out;
	}

	if ((r = cbor_parse_reply(msg, (size_t)msglen, &t->id,
	    bio_parse_template_id)) != FIDO_OK) {
		fido_log_debug("%s: bio_parse_template_id", __func__);
		goto out;
	}

	r = FIDO_OK;
out:
	freezero(msg, FIDO_MAXMSG);

	return (r);
}

static int
bio_enroll_begin_wait(fido_dev_t *dev, fido_bio_template_t *t,
    fido_bio_enroll_t *e, uint32_t timo_ms, int *ms)
{
	cbor_item_t	*argv[3];
	const uint8_t	 cmd = CMD_ENROLL_BEGIN;
	int		 r = FIDO_ERR_INTERNAL;

	memset(&argv, 0, sizeof(argv));

	if ((argv[2] = cbor_build_uint(timo_ms)) == NULL) {
		fido_log_debug("%s: cbor encode", __func__);
		goto fail;
	}

	if ((r = bio_tx(dev, cmd, argv, 3, NULL, &e->token, ms)) != FIDO_OK ||
	    (r = bio_rx_enroll_begin(dev, t, e, ms)) != FIDO_OK) {
		fido_log_debug("%s: tx/rx", __func__);
		goto fail;
	}

	r = FIDO_OK;
fail:
	cbor_vector_free(argv, nitems(argv));

	return (r);
}

int
fido_bio_dev_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
    fido_bio_enroll_t *e, uint32_t timo_ms, const char *pin)
{
	const fido_blob_t	*token;
	int			 ms = dev->timeout_ms;
	int			 r;

	token = fido_dev_puat_blob(dev);

	if ((pin == NULL && token == NULL) || !fido_blob_is_empty(&e->token))
		return (FIDO_ERR_INVALID_ARGUMENT);

	if (token)
		r = fido_blob_set(&e->token, token->ptr, token->len);
	else
		r = bio_get_uv_token(dev, pin, CTAP_CBOR_BIO_ENROLL_PRE, &e->token, &ms);

	if (r != FIDO_OK)
		return (r);

	return (bio_enroll_begin_wait(dev, t, e, timo_ms, &ms));
}

static int
bio_rx_enroll_continue(fido_dev_t *dev, fido_bio_enroll_t *e, int *ms)
{
	unsigned char	*msg;
	int		 msglen;
	int		 r;

	e->remaining_samples = 0;
	e->last_status = 0;

	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
		r = FIDO_ERR_INTERNAL;
		goto out;
	}

	if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
		fido_log_debug("%s: fido_rx", __func__);
		r = FIDO_ERR_RX;
		goto out;
	}

	if ((r = cbor_parse_reply(msg, (size_t)msglen, e,
	    bio_parse_enroll_status)) != FIDO_OK) {
		fido_log_debug("%s: bio_parse_enroll_status", __func__);
		goto out;
	}

	r = FIDO_OK;
out:
	freezero(msg, FIDO_MAXMSG);

	return (r);
}

static int
bio_enroll_continue_wait(fido_dev_t *dev, const fido_bio_template_t *t,
    fido_bio_enroll_t *e, uint32_t timo_ms, int *ms)
{
	cbor_item_t	*argv[3];
	const uint8_t	 cmd = CMD_ENROLL_NEXT;
	int		 r = FIDO_ERR_INTERNAL;

	memset(&argv, 0, sizeof(argv));

	if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
	    (argv[2] = cbor_build_uint(timo_ms)) == NULL) {
		fido_log_debug("%s: cbor encode", __func__);
		goto fail;
	}

	if ((r = bio_tx(dev, cmd, argv, 3, NULL, &e->token, ms)) != FIDO_OK ||
	    (r = bio_rx_enroll_continue(dev, e, ms)) != FIDO_OK) {
		fido_log_debug("%s: tx/rx", __func__);
		goto fail;
	}

	r = FIDO_OK;
fail:
	cbor_vector_free(argv, nitems(argv));

	return (r);
}

int
fido_bio_dev_enroll_continue(fido_dev_t *dev, const fido_bio_template_t *t,
    fido_bio_enroll_t *e, uint32_t timo_ms)
{
	int ms = dev->timeout_ms;

	if (fido_blob_is_empty(&e->token))
		return (FIDO_ERR_INVALID_ARGUMENT);

	return (bio_enroll_continue_wait(dev, t, e, timo_ms, &ms));
}

static int
bio_enroll_cancel_wait(fido_dev_t *dev, int *ms)
{
	const uint8_t	cmd = CMD_ENROLL_CANCEL;
	int		r;

	if ((r = bio_tx(dev, cmd, NULL, 0, NULL, NULL, ms)) != FIDO_OK ||
	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
		fido_log_debug("%s: tx/rx", __func__);
		return (r);
	}

	return (FIDO_OK);
}

int
fido_bio_dev_enroll_cancel(fido_dev_t *dev)
{
	int ms = dev->timeout_ms;

	return (bio_enroll_cancel_wait(dev, &ms));
}

static int
bio_enroll_remove_wait(fido_dev_t *dev, const fido_bio_template_t *t,
    const char *pin, int *ms)
{
	cbor_item_t	*argv[1];
	const uint8_t	 cmd = CMD_ENROLL_REMOVE;
	int		 r = FIDO_ERR_INTERNAL;

	memset(&argv, 0, sizeof(argv));

	if ((argv[0] = fido_blob_encode(&t->id)) == NULL) {
		fido_log_debug("%s: cbor encode", __func__);
		goto fail;
	}

	if ((r = bio_tx(dev, cmd, argv, 1, pin, fido_dev_puat_blob(dev),
	    ms)) != FIDO_OK ||
	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
		fido_log_debug("%s: tx/rx", __func__);
		goto fail;
	}

	r = FIDO_OK;
fail:
	cbor_vector_free(argv, nitems(argv));

	return (r);
}

int
fido_bio_dev_enroll_remove(fido_dev_t *dev, const fido_bio_template_t *t,
    const char *pin)
{
	int ms = dev->timeout_ms;

	return (bio_enroll_remove_wait(dev, t, pin, &ms));
}

static void
bio_reset_info(fido_bio_info_t *i)
{
	i->type = 0;
	i->max_samples = 0;
}

static int
bio_parse_info(const cbor_item_t *key, const cbor_item_t *val, void *arg)
{
	fido_bio_info_t	*i = arg;
	uint64_t	 x;

	if (cbor_isa_uint(key) == false ||
	    cbor_int_get_width(key) != CBOR_INT_8) {
		fido_log_debug("%s: cbor type", __func__);
		return (0); /* ignore */
	}

	switch (cbor_get_uint8(key)) {
	case 2:
		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
			fido_log_debug("%s: cbor_decode_uint64", __func__);
			return (-1);
		}
		i->type = (uint8_t)x;
		break;
	case 3:
		if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
			fido_log_debug("%s: cbor_decode_uint64", __func__);
			return (-1);
		}
		i->max_samples = (uint8_t)x;
		break;
	default:
		return (0); /* ignore */
	}

	return (0);
}

static int
bio_rx_info(fido_dev_t *dev, fido_bio_info_t *i, int *ms)
{
	unsigned char	*msg;
	int		 msglen;
	int		 r;

	bio_reset_info(i);

	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
		r = FIDO_ERR_INTERNAL;
		goto out;
	}

	if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
		fido_log_debug("%s: fido_rx", __func__);
		r = FIDO_ERR_RX;
		goto out;
	}

	if ((r = cbor_parse_reply(msg, (size_t)msglen, i,
	    bio_parse_info)) != FIDO_OK) {
		fido_log_debug("%s: bio_parse_info" , __func__);
		goto out;
	}

	r = FIDO_OK;
out:
	freezero(msg, FIDO_MAXMSG);

	return (r);
}

static int
bio_get_info_wait(fido_dev_t *dev, fido_bio_info_t *i, int *ms)
{
	int r;

	if ((r = bio_tx(dev, CMD_GET_INFO, NULL, 0, NULL, NULL,
	    ms)) != FIDO_OK ||
	    (r = bio_rx_info(dev, i, ms)) != FIDO_OK) {
		fido_log_debug("%s: tx/rx", __func__);
		return (r);
	}

	return (FIDO_OK);
}

int
fido_bio_dev_get_info(fido_dev_t *dev, fido_bio_info_t *i)
{
	int ms = dev->timeout_ms;

	return (bio_get_info_wait(dev, i, &ms));
}

const char *
fido_bio_template_name(const fido_bio_template_t *t)
{
	return (t->name);
}

const unsigned char *
fido_bio_template_id_ptr(const fido_bio_template_t *t)
{
	return (t->id.ptr);
}

size_t
fido_bio_template_id_len(const fido_bio_template_t *t)
{
	return (t->id.len);
}

size_t
fido_bio_template_array_count(const fido_bio_template_array_t *ta)
{
	return (ta->n_rx);
}

fido_bio_template_array_t *
fido_bio_template_array_new(void)
{
	return (calloc(1, sizeof(fido_bio_template_array_t)));
}

fido_bio_template_t *
fido_bio_template_new(void)
{
	return (calloc(1, sizeof(fido_bio_template_t)));
}

void
fido_bio_template_array_free(fido_bio_template_array_t **tap)
{
	fido_bio_template_array_t *ta;

	if (tap == NULL || (ta = *tap) == NULL)
		return;

	bio_reset_template_array(ta);
	free(ta);
	*tap = NULL;
}

void
fido_bio_template_free(fido_bio_template_t **tp)
{
	fido_bio_template_t *t;

	if (tp == NULL || (t = *tp) == NULL)
		return;

	bio_reset_template(t);
	free(t);
	*tp = NULL;
}

int
fido_bio_template_set_name(fido_bio_template_t *t, const char *name)
{
	free(t->name);
	t->name = NULL;

	if (name && (t->name = strdup(name)) == NULL)
		return (FIDO_ERR_INTERNAL);

	return (FIDO_OK);
}

int
fido_bio_template_set_id(fido_bio_template_t *t, const unsigned char *ptr,
    size_t len)
{
	fido_blob_reset(&t->id);

	if (ptr && fido_blob_set(&t->id, ptr, len) < 0)
		return (FIDO_ERR_INTERNAL);

	return (FIDO_OK);
}

const fido_bio_template_t *
fido_bio_template(const fido_bio_template_array_t *ta, size_t idx)
{
	if (idx >= ta->n_alloc)
		return (NULL);

	return (&ta->ptr[idx]);
}

fido_bio_enroll_t *
fido_bio_enroll_new(void)
{
	return (calloc(1, sizeof(fido_bio_enroll_t)));
}

fido_bio_info_t *
fido_bio_info_new(void)
{
	return (calloc(1, sizeof(fido_bio_info_t)));
}

uint8_t
fido_bio_info_type(const fido_bio_info_t *i)
{
	return (i->type);
}

uint8_t
fido_bio_info_max_samples(const fido_bio_info_t *i)
{
	return (i->max_samples);
}

void
fido_bio_enroll_free(fido_bio_enroll_t **ep)
{
	fido_bio_enroll_t *e;

	if (ep == NULL || (e = *ep) == NULL)
		return;

	bio_reset_enroll(e);

	free(e);
	*ep = NULL;
}

void
fido_bio_info_free(fido_bio_info_t **ip)
{
	fido_bio_info_t *i;

	if (ip == NULL || (i = *ip) == NULL)
		return;

	free(i);
	*ip = NULL;
}

uint8_t
fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *e)
{
	return (e->remaining_samples);
}

uint8_t
fido_bio_enroll_last_status(const fido_bio_enroll_t *e)
{
	return (e->last_status);
}
