/*
 * Copyright (c) 2006-2019, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2019-04-25     tyx          the first version
 */

#include <rtthread.h>
#include <rtdevice.h>
#include <hw_bignum.h>

static struct rt_hwcrypto_ctx *bignum_default;

rt_inline rt_err_t hwcrypto_bignum_dev_is_init(void)
{
    struct rt_hwcrypto_device *dev;

    if (bignum_default)
    {
        return RT_EOK;
    }
    dev = rt_hwcrypto_dev_default();
    if (dev == RT_NULL)
    {
        return -RT_ERROR;
    }
    return rt_hwcrypto_bignum_default(dev);
}

/**
 * @brief           Setting bignum default devices
 *
 * @return          RT_EOK on success.
 */
rt_err_t rt_hwcrypto_bignum_default(struct rt_hwcrypto_device *device)
{
    if (bignum_default)
    {
        rt_hwcrypto_ctx_destroy(bignum_default);
        bignum_default = RT_NULL;
    }
    if (device == RT_NULL)
    {
        return RT_EOK;
    }
    bignum_default = rt_hwcrypto_ctx_create(device, HWCRYPTO_TYPE_BIGNUM, sizeof(struct hwcrypto_bignum));
    if (bignum_default == RT_NULL)
    {
        return -RT_ERROR;
    }
    return RT_EOK;
}

/**
 * @brief           Init bignum obj
 * 
 * @param n         bignum obj
 */
void rt_hwcrypto_bignum_init(struct hw_bignum_mpi *n)
{
    if(n == RT_NULL)
        return;

    n->sign = 1;
    n->total = 0;
    n->p = RT_NULL;
}

/**
 * @brief           free a bignum obj
 *
 * @param           Pointer to bignum obj
 */
void rt_hwcrypto_bignum_free(struct hw_bignum_mpi *n)
{
    if (n)
    {
        rt_free(n->p);
        n->sign = 0;
        n->total = 0;
        n->p = RT_NULL;
    }
}

/**
 * @brief           Get length of bignum as an unsigned binary buffer
 * 
 * @param n         bignum obj
 * 
 * @return          binary buffer length
 */
int rt_hwcrypto_bignum_get_len(const struct hw_bignum_mpi *n)
{
    int tmp_len, total;

    if (n == RT_NULL || n->p == RT_NULL)
    {
        return 0;
    }
    tmp_len = 0;
    total = n->total;
    while ((total > 0) && (n->p[total - 1] == 0))
    {
        tmp_len++;
        total--;
    }
    return n->total - tmp_len;
}

/**
 * @brief           Export n into unsigned binary data, big endian
 * 
 * @param n         bignum obj
 * @param buf       Buffer for the binary number
 * @param len       Length of the buffer
 * 
 * @return          export bin length
 */
int rt_hwcrypto_bignum_export_bin(struct hw_bignum_mpi *n, rt_uint8_t *buf, int len)
{
    int cp_len, i, j;

    if (n == RT_NULL || buf == RT_NULL)
    {
        return 0;
    }
    rt_memset(buf, 0, len);
    cp_len = n->total > len ? len : n->total;
    for(i = cp_len, j = 0; i > 0; i--, j++)
    {
        buf[i - 1] = n->p[j];
    }

    return cp_len;
}

/**
 * @brief           Import n from unsigned binary data, big endian
 * 
 * @param n         bignum obj
 * @param buf       Buffer for the binary number
 * @param len       Length of the buffer
 * 
 * @return          RT_EOK on success.
 */
rt_err_t rt_hwcrypto_bignum_import_bin(struct hw_bignum_mpi *n, rt_uint8_t *buf, int len)
{
    int cp_len, i, j;
    void *temp_p;

    if (n == RT_NULL || buf == RT_NULL)
    {
        return 0;
    }
    if (n->total < len)
    {
        temp_p = rt_malloc(len);
        if (temp_p == RT_NULL)
        {
            return 0;
        }
        rt_memset(temp_p, 0, len);
        rt_free(n->p);
        n->p = temp_p;
        n->total = len;
    }
    cp_len = n->total > len ? len : n->total;

    for(i = cp_len, j = 0; i > 0; i--, j++)
    {
        n->p[j] = buf[i - 1];
    }

    return cp_len;
}

/**
 * @brief           x = a + b
 * 
 * @param a         bignum obj
 * @param b         bignum obj
 * @param c         bignum obj
 * 
 * @return          RT_EOK on success.
 */
rt_err_t rt_hwcrypto_bignum_add(struct hw_bignum_mpi *x,
                                const struct hw_bignum_mpi *a,
                                const struct hw_bignum_mpi *b)
{
    struct hwcrypto_bignum *bignum_ctx;

    if (hwcrypto_bignum_dev_is_init() != RT_EOK)
    {
        return -RT_ERROR;
    }
    bignum_ctx = (struct hwcrypto_bignum *)bignum_default;
    if (bignum_ctx->ops->add)
    {
        return bignum_ctx->ops->add(bignum_ctx, x, a, b);
    }
    return -RT_ERROR;
}

/**
 * @brief           x = a - b
 * 
 * @param a         bignum obj
 * @param b         bignum obj
 * @param c         bignum obj
 * 
 * @return          RT_EOK on success.
 */
rt_err_t rt_hwcrypto_bignum_sub(struct hw_bignum_mpi *x,
                                const struct hw_bignum_mpi *a,
                                const struct hw_bignum_mpi *b)
{
    struct hwcrypto_bignum *bignum_ctx;

    if (hwcrypto_bignum_dev_is_init() != RT_EOK)
    {
        return -RT_ERROR;
    }
    bignum_ctx = (struct hwcrypto_bignum *)bignum_default;
    if (bignum_ctx->ops->sub)
    {
        return bignum_ctx->ops->sub(bignum_ctx, x, a, b);
    }
    return -RT_ERROR;
}

/**
 * @brief           x = a * b
 * 
 * @param a         bignum obj
 * @param b         bignum obj
 * @param c         bignum obj
 * 
 * @return          RT_EOK on success.
 */
rt_err_t rt_hwcrypto_bignum_mul(struct hw_bignum_mpi *x,
                                const struct hw_bignum_mpi *a,
                                const struct hw_bignum_mpi *b)
{
    struct hwcrypto_bignum *bignum_ctx;

    if (hwcrypto_bignum_dev_is_init() != RT_EOK)
    {
        return -RT_ERROR;
    }
    bignum_ctx = (struct hwcrypto_bignum *)bignum_default;
    if (bignum_ctx->ops->mul)
    {
        return bignum_ctx->ops->mul(bignum_ctx, x, a, b);
    }
    return -RT_ERROR;
}

/**
 * @brief           x = a * b (mod c)
 * 
 * @param a         bignum obj
 * @param b         bignum obj
 * @param c         bignum obj
 * 
 * @return          RT_EOK on success.
 */
rt_err_t rt_hwcrypto_bignum_mulmod(struct hw_bignum_mpi *x,
                                   const struct hw_bignum_mpi *a,
                                   const struct hw_bignum_mpi *b,
                                   const struct hw_bignum_mpi *c)
{
    struct hwcrypto_bignum *bignum_ctx;

    if (hwcrypto_bignum_dev_is_init() != RT_EOK)
    {
        return -RT_ERROR;
    }
    bignum_ctx = (struct hwcrypto_bignum *)bignum_default;
    if (bignum_ctx->ops->mulmod)
    {
        return bignum_ctx->ops->mulmod(bignum_ctx, x, a, b, c);
    }
    return -RT_ERROR;
}

/**
 * @brief           x = a ^ b (mod c)
 * 
 * @param a         bignum obj
 * @param b         bignum obj
 * @param c         bignum obj
 * 
 * @return          RT_EOK on success.
 */
rt_err_t rt_hwcrypto_bignum_exptmod(struct hw_bignum_mpi *x,
                                    const struct hw_bignum_mpi *a,
                                    const struct hw_bignum_mpi *b,
                                    const struct hw_bignum_mpi *c)
{
    struct hwcrypto_bignum *bignum_ctx;

    if (hwcrypto_bignum_dev_is_init() != RT_EOK)
    {
        return -RT_ERROR;
    }
    bignum_ctx = (struct hwcrypto_bignum *)bignum_default;
    if (bignum_ctx->ops->exptmod)
    {
        return bignum_ctx->ops->exptmod(bignum_ctx, x, a, b, c);
    }
    return -RT_ERROR;
}
