/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

#include "crt.h"

#include "aws_signing.h"
#include "credentials.h"
#include "http_request_utils.h"
#include "java_class_ids.h"

#include <jni.h>
#include <string.h>

#include <aws/auth/credentials.h>
#include <aws/auth/signable.h>
#include <aws/auth/signing.h>
#include <aws/auth/signing_result.h>
#include <aws/cal/ecc.h>
#include <aws/common/string.h>
#include <aws/http/request_response.h>
#include <aws/io/stream.h>

/* on 32-bit platforms, casting pointers to longs throws a warning we don't need */
#if UINTPTR_MAX == 0xffffffff
#    if defined(_MSC_VER)
#        pragma warning(push)
#        pragma warning(disable : 4305) /* 'type cast': truncation from 'jlong' to 'jni_tls_ctx_options *' */
#    else
#        pragma GCC diagnostic push
#        pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
#        pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
#    endif
#endif

struct s_aws_sign_request_callback_data {
    JavaVM *jvm;
    jobject java_signing_result_future;
    jobject java_original_request;
    jobject java_original_chunk_body;
    jbyteArray java_previous_signature;
    struct aws_http_headers *trailing_headers;
    struct aws_input_stream *chunk_body_stream;
    struct aws_http_message *native_request;
    struct aws_signable *original_message_signable;
    struct aws_byte_cursor previous_signature;

    struct aws_signing_config_data signing_config_data;
};

void aws_signing_config_data_clean_up(struct aws_signing_config_data *data, JNIEnv *env) {

    aws_string_destroy(data->region);
    aws_string_destroy(data->service);
    aws_string_destroy(data->signed_body_value);

    if (data->java_sign_header_predicate) {
        (*env)->DeleteGlobalRef(env, data->java_sign_header_predicate);
    }
    if (data->java_credentials_provider) {
        (*env)->DeleteGlobalRef(env, data->java_credentials_provider);
    }
    aws_credentials_release(data->credentials);
}

static void s_cleanup_callback_data(struct s_aws_sign_request_callback_data *callback_data, JNIEnv *env) {
    if (callback_data == NULL || env == NULL) {
        return;
    }

    if (callback_data->java_signing_result_future != NULL) {
        (*env)->DeleteGlobalRef(env, callback_data->java_signing_result_future);
    }

    if (callback_data->java_original_request != NULL) {
        (*env)->DeleteGlobalRef(env, callback_data->java_original_request);
    }

    if (callback_data->java_original_chunk_body != NULL) {
        (*env)->DeleteGlobalRef(env, callback_data->java_original_chunk_body);
    }

    if (callback_data->native_request) {
        aws_http_message_release(callback_data->native_request);
    }

    if (callback_data->original_message_signable) {
        aws_signable_destroy(callback_data->original_message_signable);
    }

    if (callback_data->chunk_body_stream != NULL) {
        aws_input_stream_destroy(callback_data->chunk_body_stream);
    }
    if (callback_data->trailing_headers != NULL) {
        aws_http_headers_release(callback_data->trailing_headers);
    }

    if (callback_data->previous_signature.len > 0 && callback_data->java_previous_signature != NULL) {
        aws_jni_byte_cursor_from_jbyteArray_release(
            env, callback_data->java_previous_signature, callback_data->previous_signature);
    }

    if (callback_data->java_previous_signature != NULL) {
        (*env)->DeleteGlobalRef(env, callback_data->java_previous_signature);
    }

    aws_signing_config_data_clean_up(&callback_data->signing_config_data, env);

    aws_mem_release(aws_jni_get_allocator(), callback_data);
}

static jobject s_create_signed_java_http_request(
    JNIEnv *env,
    struct aws_http_message *native_request,
    jobject java_original_request) {
    jobject jni_body_stream =
        (*env)->GetObjectField(env, java_original_request, http_request_properties.body_stream_field_id);

    jobject http_request = aws_java_http_request_from_native(env, native_request, jni_body_stream);

    if (jni_body_stream != NULL) {
        (*env)->DeleteLocalRef(env, jni_body_stream);
    }

    return http_request;
}

static void s_complete_signing_exceptionally(
    JNIEnv *env,
    struct s_aws_sign_request_callback_data *callback_data,
    int error_code) {

    if (error_code == AWS_ERROR_SUCCESS) {
        error_code = AWS_ERROR_UNKNOWN;
    }

    jobject crt_exception = aws_jni_new_crt_exception_from_error_code(env, error_code);

    (*env)->CallBooleanMethod(
        env,
        callback_data->java_signing_result_future,
        completable_future_properties.complete_exceptionally_method_id,
        crt_exception);

    aws_jni_check_and_clear_exception(env);
    (*env)->DeleteLocalRef(env, crt_exception);
}

static void s_aws_complete_signing_result(
    JNIEnv *env,
    struct aws_signing_result *result,
    struct s_aws_sign_request_callback_data *callback_data,
    jobject java_signed_request) {
    jbyteArray java_signature = NULL;
    jobject java_signing_result = NULL;

    struct aws_string *signature = NULL;
    aws_signing_result_get_property(result, g_aws_signature_property_name, &signature);

    /* Anonymous requests don't have a signature because they are not signed. */
    if (signature != NULL) {
        struct aws_byte_cursor signature_cursor = aws_byte_cursor_from_string(signature);
        java_signature = aws_jni_byte_array_from_cursor(env, &signature_cursor);
    }

    java_signing_result = (*env)->NewObject(
        env, aws_signing_result_properties.aws_signing_result_class, aws_signing_result_properties.constructor);
    if ((*env)->ExceptionCheck(env) || java_signing_result == NULL) {
        s_complete_signing_exceptionally(env, callback_data, AWS_ERROR_UNKNOWN);
        goto done;
    }

    (*env)->SetObjectField(
        env, java_signing_result, aws_signing_result_properties.signed_request_field_id, java_signed_request);
    (*env)->SetObjectField(env, java_signing_result, aws_signing_result_properties.signature_field_id, java_signature);

    (*env)->CallBooleanMethod(
        env,
        callback_data->java_signing_result_future,
        completable_future_properties.complete_method_id,
        java_signing_result);

    /* I have no idea what we should do here... but the JVM really doesn't like us NOT calling this function after
       we cross the barrier. */
    AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env));

done:

    if (java_signature != NULL) {
        (*env)->DeleteLocalRef(env, java_signature);
    }

    if (java_signing_result != NULL) {
        (*env)->DeleteLocalRef(env, java_signing_result);
    }

    if (java_signed_request != NULL) {
        (*env)->DeleteLocalRef(env, java_signed_request);
    }
}

static void s_aws_request_signing_complete(struct aws_signing_result *result, int error_code, void *userdata) {

    struct s_aws_sign_request_callback_data *callback_data = userdata;

    /********** JNI ENV ACQUIRE **********/
    JNIEnv *env = aws_jni_acquire_thread_env(callback_data->jvm);
    if (env == NULL) {
        /* If we can't get an environment, then the JVM is probably shutting down.  Don't crash. */
        return;
    }

    if (result == NULL || error_code != AWS_ERROR_SUCCESS) {
        s_complete_signing_exceptionally(env, callback_data, error_code);
        goto done;
    }

    if (aws_apply_signing_result_to_http_request(callback_data->native_request, aws_jni_get_allocator(), result)) {
        s_complete_signing_exceptionally(env, callback_data, aws_last_error());
        goto done;
    }

    jobject java_signed_request =
        s_create_signed_java_http_request(env, callback_data->native_request, callback_data->java_original_request);
    if (java_signed_request == NULL) {
        s_complete_signing_exceptionally(env, callback_data, aws_last_error());
        goto done;
    }

    s_aws_complete_signing_result(env, result, callback_data, java_signed_request);

done:;

    JavaVM *jvm = callback_data->jvm;
    s_cleanup_callback_data(callback_data, env);

    aws_jni_release_thread_env(jvm, env);
    /********** JNI ENV RELEASE **********/
}

static void s_aws_chunk_like_signing_complete(struct aws_signing_result *result, int error_code, void *userdata) {

    struct s_aws_sign_request_callback_data *callback_data = userdata;

    /********** JNI ENV ACQUIRE **********/
    JNIEnv *env = aws_jni_acquire_thread_env(callback_data->jvm);
    if (env == NULL) {
        /* If we can't get an environment, then the JVM is probably shutting down.  Don't crash. */
        return;
    }

    if (result == NULL || error_code != AWS_ERROR_SUCCESS) {
        s_complete_signing_exceptionally(env, callback_data, error_code);
        goto done;
    }

    s_aws_complete_signing_result(env, result, callback_data, NULL);

done:;

    JavaVM *jvm = callback_data->jvm;
    s_cleanup_callback_data(callback_data, env);

    aws_jni_release_thread_env(jvm, env);
    /********** JNI ENV RELEASE **********/
}

static void s_aws_chunk_signing_complete(struct aws_signing_result *result, int error_code, void *userdata) {
    s_aws_chunk_like_signing_complete(result, error_code, userdata);
}

static void s_aws_trailing_headers_signing_complete(struct aws_signing_result *result, int error_code, void *userdata) {
    s_aws_chunk_like_signing_complete(result, error_code, userdata);
}

static bool s_should_sign_header(const struct aws_byte_cursor *name, void *user_data) {
    struct aws_signing_config_data *callback_data = user_data;

    /********** JNI ENV ACQUIRE **********/
    JNIEnv *env = aws_jni_acquire_thread_env(callback_data->jvm);
    if (env == NULL) {
        /* If we can't get an environment, then the JVM is probably shutting down.  Don't crash. */
        return false;
    }

    jstring header_name = aws_jni_string_from_cursor(env, name);

    bool result = (*env)->CallBooleanMethod(
        env, callback_data->java_sign_header_predicate, predicate_properties.test_method_id, (jobject)header_name);
    AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env));

    (*env)->DeleteLocalRef(env, header_name);

    aws_jni_release_thread_env(callback_data->jvm, env);
    /********** JNI ENV RELEASE **********/

    return result;
}

int aws_build_signing_config(
    JNIEnv *env,
    jobject java_config,
    struct aws_signing_config_data *config_data,
    struct aws_signing_config_aws *config) {

    jint jvmresult = (*env)->GetJavaVM(env, &config_data->jvm);
    AWS_FATAL_ASSERT(jvmresult == 0);

    config->config_type = AWS_SIGNING_CONFIG_AWS;
    config->algorithm = (enum aws_signing_algorithm)(*env)->GetIntField(
        env, java_config, aws_signing_config_properties.algorithm_field_id);
    config->signature_type = (enum aws_signature_type)(*env)->GetIntField(
        env, java_config, aws_signing_config_properties.signature_type_field_id);

    jstring region = (jstring)(*env)->GetObjectField(env, java_config, aws_signing_config_properties.region_field_id);
    if (region == NULL) {
        AWS_ZERO_STRUCT(config->region);
    } else {
        config_data->region = aws_jni_new_string_from_jstring(env, region);
        config->region = aws_byte_cursor_from_string(config_data->region);
    }

    jstring service = (jstring)(*env)->GetObjectField(env, java_config, aws_signing_config_properties.service_field_id);
    if (service == NULL) {
        AWS_ZERO_STRUCT(config->service);
    } else {
        config_data->service = aws_jni_new_string_from_jstring(env, service);
        config->service = aws_byte_cursor_from_string(config_data->service);
    }

    int64_t epoch_time_millis = (*env)->GetLongField(env, java_config, aws_signing_config_properties.time_field_id);
    aws_date_time_init_epoch_millis(&config->date, (uint64_t)epoch_time_millis);

    jobject sign_header_predicate =
        (*env)->GetObjectField(env, java_config, aws_signing_config_properties.should_sign_header_field_id);
    if (sign_header_predicate != NULL) {
        config_data->java_sign_header_predicate = (*env)->NewGlobalRef(env, sign_header_predicate);
        AWS_FATAL_ASSERT(config_data->java_sign_header_predicate != NULL);

        config->should_sign_header = s_should_sign_header;
        config->should_sign_header_ud = config_data;
    }

    config->flags.use_double_uri_encode =
        (*env)->GetBooleanField(env, java_config, aws_signing_config_properties.use_double_uri_encode_field_id);
    config->flags.should_normalize_uri_path =
        (*env)->GetBooleanField(env, java_config, aws_signing_config_properties.should_normalize_uri_path_field_id);
    config->flags.omit_session_token =
        (*env)->GetBooleanField(env, java_config, aws_signing_config_properties.omit_session_token_field_id);

    jstring signed_body_value =
        (jstring)(*env)->GetObjectField(env, java_config, aws_signing_config_properties.signed_body_value_field_id);
    if (signed_body_value == NULL) {
        AWS_ZERO_STRUCT(config->signed_body_value);
    } else {
        config_data->signed_body_value = aws_jni_new_string_from_jstring(env, signed_body_value);
        config->signed_body_value = aws_byte_cursor_from_string(config_data->signed_body_value);
    }

    config->signed_body_header =
        (*env)->GetIntField(env, java_config, aws_signing_config_properties.signed_body_header_field_id);

    jobject provider =
        (*env)->GetObjectField(env, java_config, aws_signing_config_properties.credentials_provider_field_id);
    if (provider != NULL) {
        config->credentials_provider =
            (void *)(*env)->CallLongMethod(env, provider, crt_resource_properties.get_native_handle_method_id);
        /* Keep the java object alive */
        config_data->java_credentials_provider = (*env)->NewGlobalRef(env, provider);
        AWS_FATAL_ASSERT(config_data->java_credentials_provider != NULL);
        aws_jni_check_and_clear_exception(env);
    }

    jobject credentials = (*env)->GetObjectField(env, java_config, aws_signing_config_properties.credentials_field_id);
    if (credentials != NULL) {
        config_data->credentials = aws_credentials_new_from_java_credentials(env, credentials);
        config->credentials = config_data->credentials;
    }

    config->expiration_in_seconds =
        (uint64_t)(*env)->GetLongField(env, java_config, aws_signing_config_properties.expiration_in_seconds_field_id);

    if (aws_jni_check_and_clear_exception(env)) {
        return aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
    }

    return AWS_OP_SUCCESS;
}

JNIEXPORT
void JNICALL Java_software_amazon_awssdk_crt_auth_signing_AwsSigner_awsSignerSignRequest(
    JNIEnv *env,
    jclass jni_class,
    jobject java_http_request,
    jbyteArray marshalled_request,
    jobject java_signing_config,
    jobject java_signing_result_future) {

    (void)jni_class;
    aws_cache_jni_ids(env);

    struct aws_allocator *allocator = aws_jni_get_allocator();
    struct s_aws_sign_request_callback_data *callback_data =
        aws_mem_calloc(allocator, 1, sizeof(struct s_aws_sign_request_callback_data));
    if (callback_data == NULL) {
        aws_jni_throw_runtime_exception(env, "Failed to allocated sign request callback data");
        return;
    }

    jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
    AWS_FATAL_ASSERT(jvmresult == 0);

    callback_data->java_signing_result_future = (*env)->NewGlobalRef(env, java_signing_result_future);
    AWS_FATAL_ASSERT(callback_data->java_signing_result_future != NULL);

    callback_data->java_original_request = (*env)->NewGlobalRef(env, java_http_request);
    AWS_FATAL_ASSERT(callback_data->java_original_request != NULL);

    /* Build a native aws_signing_config_aws object */
    struct aws_signing_config_aws signing_config;
    AWS_ZERO_STRUCT(signing_config);

    if (aws_build_signing_config(env, java_signing_config, &callback_data->signing_config_data, &signing_config)) {
        aws_jni_throw_runtime_exception(env, "Failed to create signing configuration");
        goto on_error;
    }

    jobject java_http_request_body_stream =
        (*env)->GetObjectField(env, java_http_request, http_request_properties.body_stream_field_id);

    callback_data->native_request =
        aws_http_request_new_from_java_http_request(env, marshalled_request, java_http_request_body_stream);
    if (callback_data->native_request == NULL) {
        aws_jni_throw_runtime_exception(env, "Failed to create native http request from Java HttpRequest");
        goto on_error;
    }

    callback_data->original_message_signable = aws_signable_new_http_request(allocator, callback_data->native_request);
    if (callback_data->original_message_signable == NULL) {
        aws_jni_throw_runtime_exception(env, "Failed to create signable from http request");
        goto on_error;
    }

    /* Sign the native request */
    if (aws_sign_request_aws(
            allocator,
            callback_data->original_message_signable,
            (struct aws_signing_config_base *)&signing_config,
            s_aws_request_signing_complete,
            callback_data)) {
        aws_jni_throw_runtime_exception(env, "Failed to initiate signing process for HttpRequest");
        goto on_error;
    }

    return;

on_error:

    s_cleanup_callback_data(callback_data, env);
}

JNIEXPORT
void JNICALL Java_software_amazon_awssdk_crt_auth_signing_AwsSigner_awsSignerSignChunk(
    JNIEnv *env,
    jclass jni_class,
    jobject java_chunk_body_stream,
    jbyteArray java_previous_signature,
    jobject java_signing_config,
    jobject java_signing_result_future) {

    (void)jni_class;
    aws_cache_jni_ids(env);

    struct aws_allocator *allocator = aws_jni_get_allocator();
    struct s_aws_sign_request_callback_data *callback_data =
        aws_mem_calloc(allocator, 1, sizeof(struct s_aws_sign_request_callback_data));
    if (callback_data == NULL) {
        aws_jni_throw_runtime_exception(env, "Failed to allocate chunk signing callback data");
        return;
    }

    jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
    AWS_FATAL_ASSERT(jvmresult == 0);

    callback_data->java_signing_result_future = (*env)->NewGlobalRef(env, java_signing_result_future);
    AWS_FATAL_ASSERT(callback_data->java_signing_result_future != NULL);

    if (java_chunk_body_stream != NULL) {
        callback_data->java_original_chunk_body = (*env)->NewGlobalRef(env, java_chunk_body_stream);
        AWS_FATAL_ASSERT(callback_data->java_original_chunk_body != NULL);

        callback_data->chunk_body_stream = aws_input_stream_new_from_java_http_request_body_stream(
            aws_jni_get_allocator(), env, java_chunk_body_stream);
        if (callback_data->chunk_body_stream == NULL) {
            aws_jni_throw_runtime_exception(env, "Error building chunk body stream");
            goto on_error;
        }
    }

    /* Build a native aws_signing_config_aws object */
    struct aws_signing_config_aws signing_config;
    AWS_ZERO_STRUCT(signing_config);
    if (aws_build_signing_config(env, java_signing_config, &callback_data->signing_config_data, &signing_config)) {
        aws_jni_throw_runtime_exception(env, "Failed to create signing configuration");
        goto on_error;
    }

    callback_data->java_previous_signature = (*env)->NewGlobalRef(env, java_previous_signature);
    callback_data->previous_signature = aws_jni_byte_cursor_from_jbyteArray_acquire(env, java_previous_signature);

    callback_data->original_message_signable =
        aws_signable_new_chunk(allocator, callback_data->chunk_body_stream, callback_data->previous_signature);
    if (callback_data->original_message_signable == NULL) {
        aws_jni_throw_runtime_exception(env, "Failed to create signable from chunk data");
        goto on_error;
    }

    /* Sign the native request */
    if (aws_sign_request_aws(
            allocator,
            callback_data->original_message_signable,
            (struct aws_signing_config_base *)&signing_config,
            s_aws_chunk_signing_complete,
            callback_data)) {
        aws_jni_throw_runtime_exception(env, "Failed to initiate signing process for Chunk");
        goto on_error;
    }

    return;

on_error:

    s_cleanup_callback_data(callback_data, env);
}

JNIEXPORT
void JNICALL Java_software_amazon_awssdk_crt_auth_signing_AwsSigner_awsSignerSignTrailingHeaders(
    JNIEnv *env,
    jclass jni_class,
    jbyteArray marshalled_headers,
    jbyteArray java_previous_signature,
    jobject java_signing_config,
    jobject java_signing_result_future) {

    (void)jni_class;
    aws_cache_jni_ids(env);

    struct aws_allocator *allocator = aws_jni_get_allocator();
    struct s_aws_sign_request_callback_data *callback_data =
        aws_mem_calloc(allocator, 1, sizeof(struct s_aws_sign_request_callback_data));
    /* we no longer worry about allocation failures */

    jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
    AWS_FATAL_ASSERT(jvmresult == 0);

    callback_data->java_signing_result_future = (*env)->NewGlobalRef(env, java_signing_result_future);
    AWS_FATAL_ASSERT(callback_data->java_signing_result_future != NULL);

    callback_data->trailing_headers = aws_http_headers_new_from_java_http_headers(env, marshalled_headers);
    if (callback_data->trailing_headers == NULL) {
        goto on_error;
    }

    /* Build a native aws_signing_config_aws object */
    struct aws_signing_config_aws signing_config;
    AWS_ZERO_STRUCT(signing_config);

    if (aws_build_signing_config(env, java_signing_config, &callback_data->signing_config_data, &signing_config)) {
        aws_jni_throw_runtime_exception(env, "Failed to create signing configuration");
        goto on_error;
    }

    callback_data->java_previous_signature = (*env)->NewGlobalRef(env, java_previous_signature);
    callback_data->previous_signature = aws_jni_byte_cursor_from_jbyteArray_acquire(env, java_previous_signature);

    callback_data->original_message_signable = aws_signable_new_trailing_headers(
        allocator, callback_data->trailing_headers, callback_data->previous_signature);
    if (callback_data->original_message_signable == NULL) {
        aws_jni_throw_runtime_exception(env, "Failed to create signable from trailing headers data");
        goto on_error;
    }

    /* Sign the native request */
    if (aws_sign_request_aws(
            allocator,
            callback_data->original_message_signable,
            (struct aws_signing_config_base *)&signing_config,
            s_aws_trailing_headers_signing_complete,
            callback_data)) {
        aws_jni_throw_runtime_exception(env, "Failed to initiate signing process for trailing headers");
        goto on_error;
    }

    return;

on_error:

    s_cleanup_callback_data(callback_data, env);
}

JNIEXPORT
bool JNICALL Java_software_amazon_awssdk_crt_auth_signing_AwsSigningUtils_awsSigningUtilsVerifyEcdsaSignature(
    JNIEnv *env,
    jclass jni_class,
    jobject java_http_request,
    jbyteArray java_marshalled_request,
    jstring java_expected_canonical_request,
    jobject java_signing_config,
    jbyteArray java_signature,
    jstring java_verifier_pub_x,
    jstring java_verifier_pub_y) {

    (void)jni_class;
    aws_cache_jni_ids(env);

    bool success = false;

    struct aws_string *expected_canonical_request = NULL;
    struct aws_byte_cursor signature_cursor;
    AWS_ZERO_STRUCT(signature_cursor);
    struct aws_string *pub_x = NULL;
    struct aws_string *pub_y = NULL;

    struct aws_allocator *allocator = aws_jni_get_allocator();
    struct s_aws_sign_request_callback_data *callback_data =
        aws_mem_calloc(allocator, 1, sizeof(struct s_aws_sign_request_callback_data));
    if (callback_data == NULL) {
        goto done;
    }

    if (java_signature == NULL) {
        goto done;
    }

    signature_cursor = aws_jni_byte_cursor_from_jbyteArray_acquire(env, java_signature);
    if (signature_cursor.len == 0) {
        goto done;
    }

    jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
    AWS_FATAL_ASSERT(jvmresult == 0);

    /* Build a native aws_signing_config_aws object */
    struct aws_signing_config_aws signing_config;
    AWS_ZERO_STRUCT(signing_config);

    if (aws_build_signing_config(env, java_signing_config, &callback_data->signing_config_data, &signing_config)) {
        goto done;
    }

    jobject java_http_request_body_stream =
        (*env)->GetObjectField(env, java_http_request, http_request_properties.body_stream_field_id);

    callback_data->native_request =
        aws_http_request_new_from_java_http_request(env, java_marshalled_request, java_http_request_body_stream);
    if (callback_data->native_request == NULL) {
        goto done;
    }

    callback_data->original_message_signable = aws_signable_new_http_request(allocator, callback_data->native_request);
    if (callback_data->original_message_signable == NULL) {
        goto done;
    }

    expected_canonical_request = aws_jni_new_string_from_jstring(env, java_expected_canonical_request);
    pub_x = aws_jni_new_string_from_jstring(env, java_verifier_pub_x);
    pub_y = aws_jni_new_string_from_jstring(env, java_verifier_pub_y);

    if (aws_verify_sigv4a_signing(
            allocator,
            callback_data->original_message_signable,
            (struct aws_signing_config_base *)&signing_config,
            aws_byte_cursor_from_string(expected_canonical_request),
            signature_cursor,
            aws_byte_cursor_from_string(pub_x),
            aws_byte_cursor_from_string(pub_y))) {
        aws_jni_throw_runtime_exception(env, aws_error_str(aws_last_error()));
        goto done;
    }

    success = true;

done:

    s_cleanup_callback_data(callback_data, env);

    aws_string_destroy(expected_canonical_request);
    if (signature_cursor.len > 0) {
        aws_jni_byte_cursor_from_jbyteArray_release(env, java_signature, signature_cursor);
    }
    aws_string_destroy(pub_x);
    aws_string_destroy(pub_y);

    return success;
}

JNIEXPORT
bool JNICALL Java_software_amazon_awssdk_crt_auth_signing_AwsSigningUtils_awsSigningUtilsVerifyRawSha256EcdsaSignature(
    JNIEnv *env,
    jclass jni_class,
    jbyteArray java_string_to_sign,
    jbyteArray java_signature,
    jstring java_verifier_pub_x,
    jstring java_verifier_pub_y) {

    (void)jni_class;
    aws_cache_jni_ids(env);

    bool success = false;
    struct aws_allocator *allocator = aws_jni_get_allocator();

    struct aws_byte_cursor string_to_sign_cursor;
    AWS_ZERO_STRUCT(string_to_sign_cursor);
    struct aws_byte_cursor signature_cursor;
    AWS_ZERO_STRUCT(signature_cursor);

    struct aws_ecc_key_pair *ecc_key = NULL;
    struct aws_string *pub_x = NULL;
    struct aws_string *pub_y = NULL;

    if (java_string_to_sign == NULL || java_signature == NULL || java_verifier_pub_x == NULL ||
        java_verifier_pub_y == NULL) {
        goto done;
    }

    pub_x = aws_jni_new_string_from_jstring(env, java_verifier_pub_x);
    pub_y = aws_jni_new_string_from_jstring(env, java_verifier_pub_y);
    if (pub_x == NULL || pub_y == NULL) {
        goto done;
    }

    ecc_key = aws_ecc_key_new_from_hex_coordinates(
        allocator, AWS_CAL_ECDSA_P256, aws_byte_cursor_from_string(pub_x), aws_byte_cursor_from_string(pub_y));
    if (ecc_key == NULL) {
        goto done;
    }

    string_to_sign_cursor = aws_jni_byte_cursor_from_jbyteArray_acquire(env, java_string_to_sign);
    signature_cursor = aws_jni_byte_cursor_from_jbyteArray_acquire(env, java_signature);

    if (aws_validate_v4a_authorization_value(allocator, ecc_key, string_to_sign_cursor, signature_cursor)) {
        goto done;
    }

    success = true;

done:

    if (string_to_sign_cursor.len > 0) {
        aws_jni_byte_cursor_from_jbyteArray_release(env, java_string_to_sign, string_to_sign_cursor);
    }

    if (signature_cursor.len > 0) {
        aws_jni_byte_cursor_from_jbyteArray_release(env, java_signature, signature_cursor);
    }

    aws_string_destroy(pub_x);
    aws_string_destroy(pub_y);

    aws_ecc_key_pair_release(ecc_key);

    return success;
}

#if UINTPTR_MAX == 0xffffffff
#    if defined(_MSC_VER)
#        pragma warning(pop)
#    else
#        pragma GCC diagnostic pop
#    endif
#endif
