/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <openssl/md5.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/pkcs12.h>

#include "util/util_memory.h"
#include "iotcs_port_system.h"
#include "iotcs_port_thread.h"
#include "iotcs_port_crypto.h"
#include "trusted_assets_manager/iotcs_tam.h"
#include "util/util.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_PORT_CRYPTO
#include "log/log_template.h"

iotcs_result iotcs_port_crypto_encode_base64(char* output, size_t* output_length, const char* input, size_t input_length) {
    BIO* b64;
    BIO* bio;
    char* data;
    long data_length;

    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);
    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
    BIO_write(bio, input, input_length);
    BIO_flush(bio);
    data_length = BIO_get_mem_data(bio, &data);

    if ((size_t) data_length <= *output_length) {
        memcpy(output, data, data_length);
        *output_length = data_length;
        BIO_free_all(bio);
        return IOTCS_RESULT_OK;
    } else {
        LOG_ERR("Output buffer length isn't enough. Required - %d. Length - %d", data_length, *output_length);
        *output_length = 0;
        BIO_free_all(bio);
        return IOTCS_RESULT_FAIL;
    }
}

iotcs_result iotcs_port_crypto_decode_base64(char* output, size_t* output_length, const char* input, size_t input_length) {
    if (*output_length < (3 * input_length) >> 2) {
        *output_length = 0;
        return IOTCS_RESULT_FAIL;
    } else {
        BIO* b64;
        BIO* bio;

        b64 = BIO_new(BIO_f_base64());
        bio = BIO_new_mem_buf((void*) input, input_length);
        BIO_push(b64, bio);
        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
        BIO_write(b64, input, input_length);
        BIO_flush(b64);
        *output_length = BIO_read(b64, output, input_length);
        BIO_free_all(b64);
        return IOTCS_RESULT_OK;
    }
}

iotcs_result iotcs_port_crypto_init(void) {
    OpenSSL_add_all_algorithms();
    return IOTCS_RESULT_OK;
}

void iotcs_port_crypto_finalize(void) {
    EVP_cleanup();
    CRYPTO_cleanup_all_ex_data();
    iotcs_port_thread_cleanup();
    ERR_free_strings();
}

iotcs_result iotcs_port_md5(unsigned char *output_buffer, iotcs_port_get_next_string_func func, void *data) {
    MD5_CTX ctx;
    const char* string;
    if (MD5_Init(&ctx) != 1) {
        LOG_ERRS("MD5 initialization failed");
        return IOTCS_RESULT_FAIL;
    }

    while (NULL != (string = func(data))) {
        if (MD5_Update(&ctx, string, strlen(string)) != 1) {
            LOG_ERRS("MD5 update failed");
            return IOTCS_RESULT_FAIL;
        }
    }

    if (MD5_Final(output_buffer, &ctx) != 1) {
        LOG_ERRS("MD5 finalization failed");
        return IOTCS_RESULT_FAIL;
    }

    return IOTCS_RESULT_OK;
}
