/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */
#include <aws/auth/auth.h>

#include <aws/auth/external/cJSON.h>
#include <aws/auth/private/aws_signing.h>

#include <aws/cal/cal.h>

#include <aws/http/http.h>

#include <aws/common/error.h>

#define AWS_DEFINE_ERROR_INFO_AUTH(CODE, STR) AWS_DEFINE_ERROR_INFO(CODE, STR, "aws-c-auth")

/* clang-format off */
static struct aws_error_info s_errors[] = {
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_PROFILE_PARSE_RECOVERABLE_ERROR,
        "Recoverable error while parsing an aws profile file"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_PROFILE_PARSE_FATAL_ERROR,
        "Fatal error while parsing an aws profile file"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_SIGNING_UNSUPPORTED_ALGORITHM,
        "Attempt to sign an http request with an unusupported version of the AWS signing protocol"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_SIGNING_MISMATCHED_CONFIGURATION,
        "Attempt to sign an http request with a signing configuration unrecognized by the invoked signer"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_SIGNING_NO_CREDENTIALS,
        "Attempt to sign an http request without credentials"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_SIGNING_ILLEGAL_REQUEST_QUERY_PARAM,
        "Attempt to sign an http request that includes a query param that signing may add"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_SIGNING_ILLEGAL_REQUEST_HEADER,
        "Attempt to sign an http request that includes a header that signing may add"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_SIGNING_INVALID_CONFIGURATION,
        "Attempt to sign an http request with an invalid signing configuration"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_INVALID_ENVIRONMENT,
        "Valid credentials could not be sourced from process environment"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_INVALID_DELEGATE,
        "Valid credentials could not be sourced from the provided vtable"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_PROFILE_SOURCE_FAILURE,
        "Valid credentials could not be sourced by a profile provider"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_IMDS_SOURCE_FAILURE,
        "Valid credentials could not be sourced by the IMDS provider"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_STS_SOURCE_FAILURE,
        "Valid credentials could not be sourced by the STS provider"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_HTTP_STATUS_FAILURE,
        "Unsuccessful status code returned from credentials-fetching http request"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_PROVIDER_PARSER_UNEXPECTED_RESPONSE,
        "Invalid response document encountered while querying credentials via http"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_ECS_SOURCE_FAILURE,
        "Valid credentials could not be sourced by the ECS provider"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_X509_SOURCE_FAILURE,
        "Valid credentials could not be sourced by the X509 provider"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_PROCESS_SOURCE_FAILURE,
        "Valid credentials could not be sourced by the process provider"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_CREDENTIALS_PROVIDER_STS_WEB_IDENTITY_SOURCE_FAILURE,
        "Valid credentials could not be sourced by the sts web identity provider"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_SIGNING_UNSUPPORTED_SIGNATURE_TYPE,
        "Attempt to sign using an unusupported signature type"),
    AWS_DEFINE_ERROR_INFO_AUTH(
        AWS_AUTH_SIGNING_MISSING_PREVIOUS_SIGNATURE,
        "Attempt to sign a streaming item without supplying a previous signature"),

};
/* clang-format on */

static struct aws_error_info_list s_error_list = {
    .error_list = s_errors,
    .count = sizeof(s_errors) / sizeof(struct aws_error_info),
};

static struct aws_log_subject_info s_auth_log_subject_infos[] = {
    DEFINE_LOG_SUBJECT_INFO(
        AWS_LS_AUTH_GENERAL,
        "AuthGeneral",
        "Subject for aws-c-auth logging that defies categorization."),
    DEFINE_LOG_SUBJECT_INFO(AWS_LS_AUTH_PROFILE, "AuthProfile", "Subject for config profile related logging."),
    DEFINE_LOG_SUBJECT_INFO(
        AWS_LS_AUTH_CREDENTIALS_PROVIDER,
        "AuthCredentialsProvider",
        "Subject for credentials provider related logging."),
    DEFINE_LOG_SUBJECT_INFO(AWS_LS_AUTH_SIGNING, "AuthSigning", "Subject for AWS request signing logging."),
};

static struct aws_log_subject_info_list s_auth_log_subject_list = {
    .subject_list = s_auth_log_subject_infos,
    .count = AWS_ARRAY_SIZE(s_auth_log_subject_infos),
};

static bool s_library_initialized = false;
static struct aws_allocator *s_library_allocator = NULL;

static void *s_cJSONAlloc(size_t sz) {
    return aws_mem_acquire(s_library_allocator, sz);
}

static void s_cJSONFree(void *ptr) {
    aws_mem_release(s_library_allocator, ptr);
}

void aws_auth_library_init(struct aws_allocator *allocator) {
    if (s_library_initialized) {
        return;
    }

    if (allocator) {
        s_library_allocator = allocator;
    } else {
        s_library_allocator = aws_default_allocator();
    }

    aws_cal_library_init(s_library_allocator);
    aws_http_library_init(s_library_allocator);

    aws_register_error_info(&s_error_list);
    aws_register_log_subject_info_list(&s_auth_log_subject_list);

    AWS_FATAL_ASSERT(aws_signing_init_signing_tables(allocator) == AWS_OP_SUCCESS);

    struct cJSON_Hooks allocation_hooks = {.malloc_fn = s_cJSONAlloc, .free_fn = s_cJSONFree};

    cJSON_InitHooks(&allocation_hooks);

    s_library_initialized = true;
}

void aws_auth_library_clean_up(void) {
    if (!s_library_initialized) {
        return;
    }

    s_library_initialized = false;

    aws_signing_clean_up_signing_tables();
    aws_unregister_log_subject_info_list(&s_auth_log_subject_list);
    aws_unregister_error_info(&s_error_list);
    aws_http_library_clean_up();
    aws_cal_library_clean_up();
    s_library_allocator = NULL;
}
