/*
 * Copyright (C) 2018 Alibaba Group Holding Limited
 */

#include "id2_plat.h"
#include "id2_priv.h"
#include "ali_crypto.h"

static const unsigned char encoding_table[] =
{
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
    'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
    'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
    'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
    'w', 'x', 'y', 'z', '0', '1', '2', '3',
    '4', '5', '6', '7', '8', '9', '+', '/'
};

static const unsigned char decoding_table[] =
{
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 62,  0,  0,  0, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61,  0,  0,  0,  0,  0,  0,
    0,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  0,  0,  0,  0,  0,
    0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
};

static const unsigned char mod_table[] = { 0, 2, 1 };

int id2_plat_get_random(uint8_t* buf, uint32_t len)
{
    ali_crypto_result result;
    uint32_t seed;

    seed = (uint32_t)ls_osa_get_time_ms();
    result = ali_seed((uint8_t*)&seed, sizeof(seed));
    if (result != ALI_CRYPTO_SUCCESS) {
        return -1;
    }

    result = ali_rand_gen(buf, len);
    if (result != ALI_CRYPTO_SUCCESS) {
        return -1;
    }

    return 0;
}

#if (CONFIG_ID2_HASH_TYPE == ID2_HASH_TYPE_SHA256)
int id2_plat_hash_sum(const uint8_t* in, uint32_t in_len,
                      uint8_t* out, uint32_t* out_len, digest_t type)
{
    int ret = 0;
    size_t ctx_size;
    void* ctx = NULL;
    ali_crypto_result result;

    if (type != DIGEST_TYPE_SHA256) {
        id2_log_error("not support this type, %d\n", type);
        return -1;
    }

    if (*out_len < 32) {
        id2_log_error("short buffer, %d\n", *out_len);
        return -1;
    }

     result = ali_sha256_get_ctx_size(&ctx_size);
     if (result != ALI_CRYPTO_SUCCESS) {
        id2_log_error("get ctx size fail, 0x%x\n", result);
        return -1;
     }

      ctx = ls_osa_malloc(ctx_size);
      if (ctx == NULL) {
          id2_log_error("out of mem, %d\n", (int)ctx_size);
          ret = -1;
          goto _out;
      }

      result = ali_sha256_init(ctx);
      if (result != ALI_CRYPTO_SUCCESS) {
          id2_log_error("hash init fail, 0x%x\n", result);
          ret = -1;
          goto _out;
      }
      result = ali_sha256_update(in, in_len, ctx);
      if (result != ALI_CRYPTO_SUCCESS) {
          id2_log_error("hash update fail, 0x%x\n", result);
          ret = -1;
          goto _out;
      }
      result = ali_sha256_final(out, ctx);
      if (result != ALI_CRYPTO_SUCCESS) {
          id2_log_error("hash final fail, 0x%x\n", result);
          ret = -1;
          goto _out;
      }

      *out_len = 32;

_out:
    ls_osa_free(ctx);

    return ret;
}
#elif (CONFIG_ID2_HASH_TYPE == ID2_HASH_TYPE_SM3)
int id2_plat_hash_sum(const uint8_t* in, uint32_t in_len,
                      uint8_t* out, uint32_t* out_len, digest_t type)
{
    int ret = 0;
    size_t ctx_size;
    void* ctx = NULL;
    ali_crypto_result result;

    if (*out_len < 32) {
        id2_log_error("short buffer, %d\n", *out_len);
        return -1;
    }

    switch (type) {
        case DIGEST_TYPE_SM3: {
            result = ali_sm3_get_ctx_size(&ctx_size);
            if (result != ALI_CRYPTO_SUCCESS) {
                id2_log_error("get ctx size fail, 0x%x\n", result);
                return -1;
            }

            ctx = ls_osa_malloc(ctx_size);
            if (ctx == NULL) {
                id2_log_error("out of mem, %d\n", (int)ctx_size);
                ret = -1;
                goto _out;
            }

            result = ali_sm3_init(ctx);
            if (result != ALI_CRYPTO_SUCCESS) {
                id2_log_error("hash init fail, 0x%x\n", result);
                ret = -1;
                goto _out;
            }
            result = ali_sm3_update(in, in_len, ctx);
            if (result != ALI_CRYPTO_SUCCESS) {
                id2_log_error("hash update fail, 0x%x\n", result);
                ret = -1;
                goto _out;
            }
            result = ali_sm3_final(out, ctx);
            if (result != ALI_CRYPTO_SUCCESS) {
                id2_log_error("hash final fail, 0x%x\n", result);
                ret = -1;
                goto _out;
            }

            break;
        }
#if defined(CONFIG_ID2_OTP)
        case DIGEST_TYPE_SHA256: {
            result = ali_sha256_get_ctx_size(&ctx_size);
            if (result != ALI_CRYPTO_SUCCESS) {
                id2_log_error("get ctx size fail, 0x%x\n", result);
                return -1;
            }

            ctx = ls_osa_malloc(ctx_size);
            if (ctx == NULL) {
                id2_log_error("out of mem, %d\n", (int)ctx_size);
                ret = -1;
                goto _out;
            }

            result = ali_sha256_init(ctx);
            if (result != ALI_CRYPTO_SUCCESS) {
                id2_log_error("hash init fail, 0x%x\n", result);
                ret = -1;
                goto _out;
            }
            result = ali_sha256_update(in, in_len, ctx);
            if (result != ALI_CRYPTO_SUCCESS) {
                id2_log_error("hash update fail, 0x%x\n", result);
                ret = -1;
                goto _out;
            }
            result = ali_sha256_final(out, ctx);
            if (result != ALI_CRYPTO_SUCCESS) {
                id2_log_error("hash final fail, 0x%x\n", result);
                ret = -1;
                goto _out;
            }

            break;
        }
#endif
        default:
            id2_log_error("not support this hash type, %d\n", type);
            return -1;
    }

    *out_len = 32;

_out:
    if (ctx != NULL) {
        ls_osa_free(ctx);
    }

    return ret;
}
#endif

int id2_plat_base64_encode(const uint8_t* input, uint32_t input_len,
                           uint8_t* output, uint32_t* output_len)
{
    int i, j;
    uint32_t o_len = 4 * ((input_len + 2) / 3);

    if (!input || !output || !output_len) {
        return -1;
    }

    if (output_len) {
        *output_len = o_len;
    }

    for (i = 0, j = 0; i < input_len;) {
        unsigned int octet_a = i < input_len ? input[i++] : 0;
        unsigned int octet_b = i < input_len ? input[i++] : 0;
        unsigned int octet_c = i < input_len ? input[i++] : 0;

        unsigned int triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;

        output[j++] = encoding_table[(triple >> 18) & 0x3F];
        output[j++] = encoding_table[(triple >> 12) & 0x3F];
        output[j++] = encoding_table[(triple >> 6) & 0x3F];
        output[j++] = encoding_table[(triple >> 0) & 0x3F];
    }

    for (i = 0; i < mod_table[input_len % 3]; i++) {
        output[o_len - 1 - i] = '=';
    }

    return 0;
}

int id2_plat_base64_decode(const uint8_t* input, uint32_t input_len,
                           uint8_t* output, uint32_t* output_len)
{
    int i, j;
    uint32_t o_len = input_len / 4 * 3;

    if (!input || !output || !output_len) {
        return -1;
    }

    if (input[input_len - 1] == '=') {
        o_len--;
    }
    if (input[input_len - 2] == '=') {
        o_len--;
    }

    if (output_len) {
        *output_len = o_len;
    }

    for (i = 0, j = 0; i < input_len;)
    {
        unsigned int sextet_a = decoding_table[input[i++]];
        unsigned int sextet_b = decoding_table[input[i++]];
        unsigned int sextet_c = decoding_table[input[i++]];
        unsigned int sextet_d = decoding_table[input[i++]];

        unsigned int triple = (sextet_a << 18)
                              + (sextet_b << 12)
                              + (sextet_c << 6)
                              + (sextet_d << 0);

        if (j < o_len) {
            output[j++] = (triple >> 16) & 0xFF;
        }
        if (j < o_len) {
            output[j++] = (triple >> 8) & 0xFF;
        }
        if (j < o_len) {
            output[j++] = (triple >> 0) & 0xFF;
        }
    }

    return 0;
}

