blob: 21e46d58fcaa3a8d38f2c359a2a9d2ce35c999aa [file] [log] [blame] [edit]
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include "aws_signing.h"
#include "credentials.h"
#include "crt.h"
#include "http_request_utils.h"
#include "java_class_ids.h"
#include "retry_utils.h"
#include <aws/common/string.h>
#include <aws/http/connection.h>
#include <aws/http/proxy.h>
#include <aws/http/request_response.h>
#include <aws/io/channel_bootstrap.h>
#include <aws/io/retry_strategy.h>
#include <aws/io/stream.h>
#include <aws/io/tls_channel_handler.h>
#include <aws/io/uri.h>
#include <aws/s3/s3_client.h>
#include <aws/s3/s3express_credentials_provider.h>
#include <http_proxy_options.h>
#include <http_proxy_options_environment_variable.h>
#include <jni.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 *' */
# pragma warning(disable : 4221)
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
# pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
# endif
#endif
struct s3_client_callback_data {
JavaVM *jvm;
jobject java_s3_client;
struct aws_signing_config_data signing_config_data;
jobject java_s3express_provider_factory;
};
struct s3_client_make_meta_request_callback_data {
JavaVM *jvm;
jobject java_s3_meta_request;
jobject java_s3_meta_request_response_handler_native_adapter;
struct aws_input_stream *input_stream;
struct aws_signing_config_data signing_config_data;
jthrowable java_exception;
};
static void s_on_s3_client_shutdown_complete_callback(void *user_data);
static void s_on_s3_meta_request_shutdown_complete_callback(void *user_data);
int aws_s3_tcp_keep_alive_options_from_java(
JNIEnv *env,
jobject jni_s3_tcp_keep_alive_options,
struct aws_s3_tcp_keep_alive_options *s3_tcp_keep_alive_options) {
uint16_t jni_keep_alive_interval_sec = (*env)->GetShortField(
env, jni_s3_tcp_keep_alive_options, s3_tcp_keep_alive_options_properties.keep_alive_interval_sec_field_id);
uint16_t jni_keep_alive_timeout_sec = (*env)->GetShortField(
env, jni_s3_tcp_keep_alive_options, s3_tcp_keep_alive_options_properties.keep_alive_timeout_sec_field_id);
uint16_t jni_keep_alive_max_failed_probes = (*env)->GetShortField(
env, jni_s3_tcp_keep_alive_options, s3_tcp_keep_alive_options_properties.keep_alive_max_failed_probes_field_id);
AWS_ZERO_STRUCT(*s3_tcp_keep_alive_options);
s3_tcp_keep_alive_options->keep_alive_interval_sec = jni_keep_alive_interval_sec;
s3_tcp_keep_alive_options->keep_alive_timeout_sec = jni_keep_alive_timeout_sec;
s3_tcp_keep_alive_options->keep_alive_max_failed_probes = jni_keep_alive_max_failed_probes;
return AWS_OP_SUCCESS;
}
struct s3_client_s3express_provider_java_impl {
JavaVM *jvm;
jobject java_s3express_provider;
};
struct s3_client_s3express_provider_callback_data {
void *log_id;
aws_on_get_credentials_callback_fn *get_cred_callback;
void *get_cred_user_data;
};
JNIEXPORT void JNICALL
Java_software_amazon_awssdk_crt_s3_S3ExpressCredentialsProvider_s3expressCredentialsProviderGetCredentialsCompleted(
JNIEnv *env,
jclass jni_class,
jlong nativeHandler,
jobject java_credentials) {
(void)jni_class;
struct s3_client_s3express_provider_callback_data *callback_data = (void *)nativeHandler;
struct aws_credentials *native_credentials = NULL;
int error_code = AWS_ERROR_SUCCESS;
if (!java_credentials || aws_jni_check_and_clear_exception(env)) {
/* TODO: a separate error code?? */
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Failed to get S3Express credentials from Java",
(void *)callback_data->log_id);
error_code = AWS_ERROR_HTTP_CALLBACK_FAILURE;
goto done;
}
native_credentials = aws_credentials_new_from_java_credentials(env, java_credentials);
if (!native_credentials) {
aws_jni_throw_runtime_exception(env, "Failed to create native credentials");
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Failed to create native credentials from Java",
(void *)callback_data->log_id);
goto done;
}
struct aws_byte_cursor session_token = aws_credentials_get_session_token(native_credentials);
if (session_token.len == 0) {
aws_jni_throw_runtime_exception(env, "S3ExpressCredentialsProvider - sessionToken must be non-null");
error_code = AWS_ERROR_HTTP_CALLBACK_FAILURE;
aws_credentials_release(native_credentials);
native_credentials = NULL;
goto done;
}
done:
callback_data->get_cred_callback(native_credentials, error_code, callback_data->get_cred_user_data);
aws_credentials_release(native_credentials);
aws_mem_release(aws_jni_get_allocator(), callback_data);
}
static int s_s3express_get_creds_java(
struct aws_s3express_credentials_provider *provider,
const struct aws_credentials *original_credentials,
const struct aws_credentials_properties_s3express *s3express_properties,
aws_on_get_credentials_callback_fn callback,
void *user_data) {
struct s3_client_s3express_provider_java_impl *impl = provider->impl;
int result = AWS_OP_ERR;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(impl->jvm);
if (!env) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return AWS_OP_SUCCESS;
}
jobject properties_object = NULL;
jobject original_credentials_object = NULL;
properties_object = (*env)->NewObject(
env,
s3express_credentials_properties_properties.s3express_credentials_properties_class,
s3express_credentials_properties_properties.constructor_method_id);
if ((*env)->ExceptionCheck(env) || properties_object == NULL) {
aws_jni_throw_runtime_exception(
env,
"S3ExpressCredentialsProvider.getS3ExpressCredentials: Failed to create S3ExpressCredentialsProperties "
"object.");
goto done;
}
original_credentials_object = aws_java_credentials_from_native_new(env, original_credentials);
if ((*env)->ExceptionCheck(env) || original_credentials_object == NULL) {
aws_jni_throw_runtime_exception(
env, "S3ExpressCredentialsProvider.getS3ExpressCredentials: Failed to create Credentials object.");
goto done;
}
jstring jni_host = aws_jni_string_from_cursor(env, &s3express_properties->host);
jstring jni_region = aws_jni_string_from_cursor(env, &s3express_properties->region);
(*env)->SetObjectField(env, properties_object, s3express_credentials_properties_properties.host_field_id, jni_host);
(*env)->SetObjectField(
env, properties_object, s3express_credentials_properties_properties.region_field_id, jni_region);
(*env)->DeleteLocalRef(env, jni_host);
(*env)->DeleteLocalRef(env, jni_region);
struct s3_client_s3express_provider_callback_data *callback_data =
aws_mem_calloc(aws_jni_get_allocator(), 1, sizeof(struct s3_client_s3express_provider_callback_data));
callback_data->get_cred_callback = callback;
callback_data->get_cred_user_data = user_data;
callback_data->log_id = impl->java_s3express_provider;
/* Invoke the java call */
(*env)->CallVoidMethod(
env,
impl->java_s3express_provider,
s3express_credentials_provider_properties.getS3ExpressCredentials,
properties_object,
original_credentials_object,
(jlong)callback_data);
if (aws_jni_check_and_clear_exception(env)) {
/* Check if any exception raised */
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: S3ExpressCredentialsProvider.getS3ExpressCredentials failed",
(void *)impl->java_s3express_provider);
aws_mem_release(aws_jni_get_allocator(), callback_data);
goto done;
}
result = AWS_OP_SUCCESS;
done:
if (properties_object) {
(*env)->DeleteLocalRef(env, properties_object);
}
if (original_credentials_object) {
(*env)->DeleteLocalRef(env, original_credentials_object);
}
aws_jni_release_thread_env(impl->jvm, env);
/********** JNI ENV RELEASE **********/
return result;
}
static void s_s3express_destroy_java(struct aws_s3express_credentials_provider *provider) {
struct s3_client_s3express_provider_java_impl *impl = provider->impl;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(impl->jvm);
if (!env) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return;
}
/* Invoke the java call */
(*env)->CallVoidMethod(
env, impl->java_s3express_provider, s3express_credentials_provider_properties.destroyProvider);
(*env)->DeleteGlobalRef(env, impl->java_s3express_provider);
aws_jni_release_thread_env(impl->jvm, env);
/********** JNI ENV RELEASE **********/
/* Once the java call returns, the java resource should be cleaned up already. We can finish up the shutdown
* process. Clean up the native part. */
aws_simple_completion_callback *callback = provider->shutdown_complete_callback;
void *user_data = provider->shutdown_user_data;
aws_mem_release(provider->allocator, provider);
callback(user_data);
}
static struct aws_s3express_credentials_provider_vtable s_java_s3express_vtable = {
.get_credentials = s_s3express_get_creds_java,
.destroy = s_s3express_destroy_java,
};
struct aws_s3express_credentials_provider *s_s3express_provider_jni_factory(
struct aws_allocator *allocator,
struct aws_s3_client *client,
aws_simple_completion_callback shutdown_complete_callback,
void *shutdown_user_data,
void *factory_user_data) {
(void)client;
struct s3_client_callback_data *client_data = factory_user_data;
struct aws_s3express_credentials_provider *provider = NULL;
struct s3_client_s3express_provider_java_impl *impl = NULL;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(client_data->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return NULL;
}
aws_mem_acquire_many(
allocator,
2,
&provider,
sizeof(struct aws_s3express_credentials_provider),
&impl,
sizeof(struct s3_client_s3express_provider_java_impl));
/* Call into the java factory to create the java impl */
AWS_FATAL_ASSERT(client_data->java_s3express_provider_factory != NULL);
jobject java_s3express_provider = (*env)->CallObjectMethod(
env,
client_data->java_s3express_provider_factory,
s3express_credentials_provider_factory_properties.createS3ExpressCredentialsProvider,
client_data->java_s3_client);
if (aws_jni_check_and_clear_exception(env)) {
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Failed to create Java S3Express Provider",
(void *)client_data->java_s3express_provider_factory);
aws_mem_release(allocator, provider);
provider = NULL;
goto done;
}
impl->java_s3express_provider = (*env)->NewGlobalRef(env, java_s3express_provider);
jint jvmresult = (*env)->GetJavaVM(env, &impl->jvm);
(void)jvmresult;
AWS_FATAL_ASSERT(jvmresult == 0);
aws_s3express_credentials_provider_init_base(provider, allocator, &s_java_s3express_vtable, impl);
provider->shutdown_complete_callback = shutdown_complete_callback;
provider->shutdown_user_data = shutdown_user_data;
/* We are done using the factory, when succeed, clean it up. TODO: we don't have to clean it up here */
(*env)->DeleteGlobalRef(env, client_data->java_s3express_provider_factory);
client_data->java_s3express_provider_factory = NULL;
done:
aws_jni_release_thread_env(client_data->jvm, env);
/********** JNI ENV RELEASE **********/
return provider;
}
JNIEXPORT jlong JNICALL Java_software_amazon_awssdk_crt_s3_S3Client_s3ClientNew(
JNIEnv *env,
jclass jni_class,
jobject s3_client_jobject,
jbyteArray jni_region,
jlong jni_client_bootstrap,
jlong jni_tls_ctx,
jobject java_signing_config,
jlong part_size_jlong,
jlong multipart_upload_threshold_jlong,
jdouble throughput_target_gbps,
jboolean enable_read_backpressure,
jlong initial_read_window_jlong,
int max_connections,
jobject jni_standard_retry_options,
jboolean compute_content_md5,
jint jni_proxy_connection_type,
jbyteArray jni_proxy_host,
jint jni_proxy_port,
jlong jni_proxy_tls_context,
jint jni_proxy_authorization_type,
jbyteArray jni_proxy_authorization_username,
jbyteArray jni_proxy_authorization_password,
jint jni_environment_variable_proxy_connection_type,
jlong jni_environment_variable_proxy_tls_connection_options,
jint jni_environment_variable_type,
int connect_timeout_ms,
jobject jni_s3_tcp_keep_alive_options,
jlong jni_monitoring_throughput_threshold_in_bytes_per_second,
jint jni_monitoring_failure_interval_in_seconds,
jboolean enable_s3express,
jobject java_s3express_provider_factory,
jlong jni_memory_limit_bytes_jlong) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_allocator *allocator = aws_jni_get_allocator();
struct aws_client_bootstrap *client_bootstrap = (struct aws_client_bootstrap *)jni_client_bootstrap;
if (!client_bootstrap) {
aws_jni_throw_illegal_argument_exception(env, "Invalid Client Bootstrap");
return (jlong)NULL;
}
uint64_t part_size = (uint64_t)part_size_jlong;
uint64_t multipart_upload_threshold = (uint64_t)multipart_upload_threshold_jlong;
uint64_t memory_limit_in_bytes = (uint64_t)jni_memory_limit_bytes_jlong;
size_t initial_read_window;
if (aws_size_t_from_java(env, &initial_read_window, initial_read_window_jlong, "Initial read window")) {
return (jlong)NULL;
}
struct aws_retry_strategy *retry_strategy = NULL;
if (jni_standard_retry_options != NULL) {
struct aws_standard_retry_options retry_options;
if (aws_standard_retry_options_from_java(env, jni_standard_retry_options, &retry_options)) {
return (jlong)NULL;
}
if (retry_options.backoff_retry_options.el_group == NULL) {
retry_options.backoff_retry_options.el_group = client_bootstrap->event_loop_group;
}
retry_strategy = aws_retry_strategy_new_standard(allocator, &retry_options);
if (retry_strategy == NULL) {
aws_jni_throw_runtime_exception(env, "Could not create retry strategy with standard-retry-options");
return (jlong)NULL;
}
}
struct aws_s3_tcp_keep_alive_options *s3_tcp_keep_alive_options = NULL;
if (jni_s3_tcp_keep_alive_options != NULL) {
s3_tcp_keep_alive_options = aws_mem_calloc(allocator, 1, sizeof(struct aws_s3_tcp_keep_alive_options));
if (aws_s3_tcp_keep_alive_options_from_java(env, jni_s3_tcp_keep_alive_options, s3_tcp_keep_alive_options)) {
aws_jni_throw_runtime_exception(env, "Could not create s3_tcp_keep_alive_options");
return (jlong)NULL;
}
}
struct aws_byte_cursor region = aws_jni_byte_cursor_from_jbyteArray_acquire(env, jni_region);
struct s3_client_callback_data *callback_data =
aws_mem_calloc(allocator, 1, sizeof(struct s3_client_callback_data));
struct aws_signing_config_aws signing_config;
struct aws_credentials *anonymous_credentials = NULL;
AWS_ZERO_STRUCT(signing_config);
if (java_signing_config != NULL) {
if (aws_build_signing_config(env, java_signing_config, &callback_data->signing_config_data, &signing_config)) {
aws_jni_throw_runtime_exception(env, "Invalid signingConfig");
aws_mem_release(allocator, callback_data);
return (jlong)NULL;
}
} else {
anonymous_credentials = aws_credentials_new_anonymous(allocator);
signing_config.credentials = anonymous_credentials;
}
if (java_s3express_provider_factory != NULL && enable_s3express) {
/* Create a JNI factory */
callback_data->java_s3express_provider_factory = (*env)->NewGlobalRef(env, java_s3express_provider_factory);
}
AWS_FATAL_ASSERT(callback_data);
callback_data->java_s3_client = (*env)->NewGlobalRef(env, s3_client_jobject);
jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
(void)jvmresult;
AWS_FATAL_ASSERT(jvmresult == 0);
struct aws_tls_connection_options *tls_options = NULL;
struct aws_tls_connection_options tls_options_storage;
AWS_ZERO_STRUCT(tls_options_storage);
if (jni_tls_ctx) {
struct aws_tls_ctx *tls_ctx = (void *)jni_tls_ctx;
tls_options = &tls_options_storage;
aws_tls_connection_options_init_from_ctx(tls_options, tls_ctx);
}
struct aws_s3_client_config client_config = {
.max_active_connections_override = max_connections,
.region = region,
.client_bootstrap = client_bootstrap,
.tls_connection_options = tls_options,
.signing_config = &signing_config,
.part_size = part_size,
.multipart_upload_threshold = multipart_upload_threshold,
.throughput_target_gbps = throughput_target_gbps,
.enable_read_backpressure = enable_read_backpressure,
.initial_read_window = initial_read_window,
.retry_strategy = retry_strategy,
.shutdown_callback = s_on_s3_client_shutdown_complete_callback,
.shutdown_callback_user_data = callback_data,
.compute_content_md5 = compute_content_md5 ? AWS_MR_CONTENT_MD5_ENABLED : AWS_MR_CONTENT_MD5_DISABLED,
.connect_timeout_ms = connect_timeout_ms,
.tcp_keep_alive_options = s3_tcp_keep_alive_options,
.enable_s3express = enable_s3express,
.s3express_provider_override_factory =
java_s3express_provider_factory ? s_s3express_provider_jni_factory : NULL,
.factory_user_data = callback_data,
.memory_limit_in_bytes = memory_limit_in_bytes,
};
struct aws_http_connection_monitoring_options monitoring_options;
AWS_ZERO_STRUCT(monitoring_options);
if (jni_monitoring_throughput_threshold_in_bytes_per_second >= 0 &&
jni_monitoring_failure_interval_in_seconds >= 2) {
monitoring_options.minimum_throughput_bytes_per_second =
jni_monitoring_throughput_threshold_in_bytes_per_second;
monitoring_options.allowable_throughput_failure_interval_seconds = jni_monitoring_failure_interval_in_seconds;
client_config.monitoring_options = &monitoring_options;
}
struct aws_http_proxy_options proxy_options;
AWS_ZERO_STRUCT(proxy_options);
struct aws_tls_connection_options proxy_tls_conn_options;
AWS_ZERO_STRUCT(proxy_tls_conn_options);
aws_http_proxy_options_jni_init(
env,
&proxy_options,
jni_proxy_connection_type,
&proxy_tls_conn_options,
jni_proxy_host,
jni_proxy_port,
jni_proxy_authorization_username,
jni_proxy_authorization_password,
jni_proxy_authorization_type,
(struct aws_tls_ctx *)jni_proxy_tls_context);
if (jni_proxy_host != NULL) {
client_config.proxy_options = &proxy_options;
}
struct proxy_env_var_settings proxy_ev_settings;
AWS_ZERO_STRUCT(proxy_ev_settings);
aws_http_proxy_environment_variable_setting_jni_init(
&proxy_ev_settings,
jni_environment_variable_proxy_connection_type,
jni_environment_variable_type,
(struct aws_tls_connection_options *)jni_environment_variable_proxy_tls_connection_options);
client_config.proxy_ev_settings = &proxy_ev_settings;
struct aws_s3_client *client = aws_s3_client_new(allocator, &client_config);
if (!client) {
aws_jni_throw_runtime_exception(env, "S3Client.aws_s3_client_new: creating aws_s3_client failed");
/* Clean up stuff */
aws_signing_config_data_clean_up(&callback_data->signing_config_data, env);
aws_mem_release(allocator, callback_data);
}
aws_credentials_release(anonymous_credentials);
aws_retry_strategy_release(retry_strategy);
aws_jni_byte_cursor_from_jbyteArray_release(env, jni_region, region);
aws_http_proxy_options_jni_clean_up(
env, &proxy_options, jni_proxy_host, jni_proxy_authorization_username, jni_proxy_authorization_password);
aws_mem_release(aws_jni_get_allocator(), s3_tcp_keep_alive_options);
return (jlong)client;
}
JNIEXPORT void JNICALL
Java_software_amazon_awssdk_crt_s3_S3Client_s3ClientDestroy(JNIEnv *env, jclass jni_class, jlong jni_s3_client) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_s3_client *client = (struct aws_s3_client *)jni_s3_client;
if (!client) {
aws_jni_throw_runtime_exception(env, "S3Client.s3_client_clean_up: Invalid/null client");
return;
}
aws_s3_client_release(client);
}
static void s_on_s3_client_shutdown_complete_callback(void *user_data) {
struct s3_client_callback_data *callback = (struct s3_client_callback_data *)user_data;
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(callback->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return;
}
AWS_LOGF_DEBUG(AWS_LS_S3_CLIENT, "S3 Client Shutdown Complete");
if (callback->java_s3_client != NULL) {
(*env)->CallVoidMethod(env, callback->java_s3_client, s3_client_properties.onShutdownComplete);
if (aws_jni_check_and_clear_exception(env)) {
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Ignored Exception from S3Client.onShutdownCompete callback",
(void *)callback->java_s3_client);
}
}
// We're done with this callback data, free it.
if (callback->java_s3express_provider_factory) {
/* Clean up factory data if still exist */
(*env)->DeleteGlobalRef(env, callback->java_s3express_provider_factory);
callback->java_s3express_provider_factory = NULL;
}
(*env)->DeleteGlobalRef(env, callback->java_s3_client);
aws_signing_config_data_clean_up(&callback->signing_config_data, env);
aws_jni_release_thread_env(callback->jvm, env);
/********** JNI ENV RELEASE **********/
aws_mem_release(aws_jni_get_allocator(), user_data);
}
static int s_on_s3_meta_request_body_callback(
struct aws_s3_meta_request *meta_request,
const struct aws_byte_cursor *body,
uint64_t range_start,
void *user_data) {
(void)body;
(void)range_start;
int return_value = AWS_OP_ERR;
uint64_t range_end = range_start + body->len;
struct s3_client_make_meta_request_callback_data *callback_data =
(struct s3_client_make_meta_request_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 AWS_OP_ERR;
}
jobject jni_payload = aws_jni_byte_array_from_cursor(env, body);
if (jni_payload == NULL) {
/* JVM is out of memory, but native code can still have memory available, handle it and don't crash. */
aws_jni_check_and_clear_exception(env);
aws_jni_release_thread_env(callback_data->jvm, env);
/********** JNI ENV RELEASE **********/
return aws_raise_error(AWS_ERROR_JAVA_CRT_JVM_OUT_OF_MEMORY);
}
jint body_response_result = 0;
if (callback_data->java_s3_meta_request_response_handler_native_adapter != NULL) {
body_response_result = (*env)->CallIntMethod(
env,
callback_data->java_s3_meta_request_response_handler_native_adapter,
s3_meta_request_response_handler_native_adapter_properties.onResponseBody,
jni_payload,
range_start,
range_end);
if (aws_jni_get_and_clear_exception(env, &(callback_data->java_exception))) {
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Received exception from S3MetaRequest.onResponseBody callback",
(void *)meta_request);
aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
goto cleanup;
}
/* The Java onResponseBody API lets users return a size for auto-incrementing the read window */
if (body_response_result > 0) {
aws_s3_meta_request_increment_read_window(meta_request, (uint64_t)body_response_result);
}
}
return_value = AWS_OP_SUCCESS;
cleanup:
(*env)->DeleteLocalRef(env, jni_payload);
aws_jni_release_thread_env(callback_data->jvm, env);
/********** JNI ENV RELEASE **********/
return return_value;
}
static int s_marshal_http_headers_to_buf(const struct aws_http_headers *headers, struct aws_byte_buf *out_headers_buf) {
/* calculate initial header capacity */
size_t headers_initial_capacity = 0;
for (size_t header_index = 0; header_index < aws_http_headers_count(headers); ++header_index) {
struct aws_http_header header;
aws_http_headers_get_index(headers, header_index, &header);
/* aws_marshal_http_headers_array_to_dynamic_buffer() impl drives this calculation */
headers_initial_capacity += header.name.len + header.value.len + 8;
}
struct aws_allocator *allocator = aws_jni_get_allocator();
if (aws_byte_buf_init(out_headers_buf, allocator, headers_initial_capacity)) {
return AWS_OP_ERR;
}
if (aws_marshal_http_headers_to_dynamic_buffer(out_headers_buf, headers)) {
aws_byte_buf_clean_up(out_headers_buf);
return AWS_OP_ERR;
}
return AWS_OP_SUCCESS;
}
static int s_on_s3_meta_request_headers_callback(
struct aws_s3_meta_request *meta_request,
const struct aws_http_headers *headers,
int response_status,
void *user_data) {
int return_value = AWS_OP_ERR;
struct s3_client_make_meta_request_callback_data *callback_data =
(struct s3_client_make_meta_request_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 AWS_OP_ERR;
}
jobject java_headers_buffer = NULL;
struct aws_byte_buf headers_buf;
AWS_ZERO_STRUCT(headers_buf);
if (s_marshal_http_headers_to_buf(headers, &headers_buf)) {
goto cleanup;
}
java_headers_buffer = aws_jni_direct_byte_buffer_from_raw_ptr(env, headers_buf.buffer, headers_buf.len);
if (java_headers_buffer == NULL) {
aws_jni_check_and_clear_exception(env);
aws_raise_error(AWS_ERROR_JAVA_CRT_JVM_OUT_OF_MEMORY);
goto cleanup;
}
if (callback_data->java_s3_meta_request_response_handler_native_adapter != NULL) {
(*env)->CallVoidMethod(
env,
callback_data->java_s3_meta_request_response_handler_native_adapter,
s3_meta_request_response_handler_native_adapter_properties.onResponseHeaders,
response_status,
java_headers_buffer);
if (aws_jni_get_and_clear_exception(env, &(callback_data->java_exception))) {
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Exception thrown from S3MetaRequest.onResponseHeaders callback",
(void *)meta_request);
aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
goto cleanup;
}
}
return_value = AWS_OP_SUCCESS;
cleanup:
aws_byte_buf_clean_up(&headers_buf);
if (java_headers_buffer) {
(*env)->DeleteLocalRef(env, java_headers_buffer);
}
aws_jni_release_thread_env(callback_data->jvm, env);
/********** JNI ENV RELEASE **********/
return return_value;
}
static void s_on_s3_meta_request_finish_callback(
struct aws_s3_meta_request *meta_request,
const struct aws_s3_meta_request_result *meta_request_result,
void *user_data) {
(void)meta_request;
struct s3_client_make_meta_request_callback_data *callback_data =
(struct s3_client_make_meta_request_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;
}
if (callback_data->java_s3_meta_request_response_handler_native_adapter != NULL) {
struct aws_byte_buf *error_response_body = meta_request_result->error_response_body;
struct aws_byte_cursor error_response_cursor;
AWS_ZERO_STRUCT(error_response_cursor);
if (error_response_body) {
error_response_cursor = aws_byte_cursor_from_buf(error_response_body);
}
jbyteArray jni_payload = aws_jni_byte_array_from_cursor(env, &error_response_cursor);
/* Only propagate java_exception if crt error code is callback failure */
jthrowable java_exception =
meta_request_result->error_code == AWS_ERROR_HTTP_CALLBACK_FAILURE ? callback_data->java_exception : NULL;
jobject java_error_headers_buffer = NULL;
struct aws_byte_buf headers_buf;
AWS_ZERO_STRUCT(headers_buf);
if (meta_request_result->error_response_headers) {
/* Ignore any errors and just report the original error without headers */
if (s_marshal_http_headers_to_buf(meta_request_result->error_response_headers, &headers_buf) ==
AWS_OP_SUCCESS) {
java_error_headers_buffer =
aws_jni_direct_byte_buffer_from_raw_ptr(env, headers_buf.buffer, headers_buf.len);
if (java_error_headers_buffer == NULL) {
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Ignored Exception from "
"S3MetaRequest.onFinished.aws_jni_direct_byte_buffer_from_raw_ptr",
(void *)meta_request);
aws_jni_check_and_clear_exception(env);
}
} else {
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Ignored Exception from S3MetaRequest.onFinished.s_marshal_http_headers_to_buf",
(void *)meta_request);
}
}
(*env)->CallVoidMethod(
env,
callback_data->java_s3_meta_request_response_handler_native_adapter,
s3_meta_request_response_handler_native_adapter_properties.onFinished,
meta_request_result->error_code,
meta_request_result->response_status,
jni_payload,
meta_request_result->validation_algorithm,
meta_request_result->did_validate,
java_exception,
java_error_headers_buffer);
if (aws_jni_check_and_clear_exception(env)) {
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Ignored Exception from S3MetaRequest.onFinished callback",
(void *)meta_request);
}
if (jni_payload) {
(*env)->DeleteLocalRef(env, jni_payload);
}
aws_byte_buf_clean_up(&headers_buf);
if (java_error_headers_buffer) {
(*env)->DeleteLocalRef(env, java_error_headers_buffer);
}
}
aws_jni_release_thread_env(callback_data->jvm, env);
/********** JNI ENV RELEASE **********/
}
static void s_on_s3_meta_request_progress_callback(
struct aws_s3_meta_request *meta_request,
const struct aws_s3_meta_request_progress *progress,
void *user_data) {
(void)meta_request;
struct s3_client_make_meta_request_callback_data *callback_data =
(struct s3_client_make_meta_request_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;
}
jobject progress_object = (*env)->NewObject(
env,
s3_meta_request_progress_properties.s3_meta_request_progress_class,
s3_meta_request_progress_properties.s3_meta_request_progress_constructor_method_id);
if ((*env)->ExceptionCheck(env) || progress_object == NULL) {
aws_jni_throw_runtime_exception(
env, "S3MetaRequestResponseHandler.onProgress: Failed to create S3MetaRequestProgress object.");
goto done;
}
(*env)->SetLongField(
env,
progress_object,
s3_meta_request_progress_properties.bytes_transferred_field_id,
progress->bytes_transferred);
(*env)->SetLongField(
env, progress_object, s3_meta_request_progress_properties.content_length_field_id, progress->content_length);
if (callback_data->java_s3_meta_request_response_handler_native_adapter != NULL) {
(*env)->CallVoidMethod(
env,
callback_data->java_s3_meta_request_response_handler_native_adapter,
s3_meta_request_response_handler_native_adapter_properties.onProgress,
progress_object);
if (aws_jni_check_and_clear_exception(env)) {
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Ignored Exception from S3MetaRequest.onProgress callback",
(void *)meta_request);
}
}
(*env)->DeleteLocalRef(env, progress_object);
done:
aws_jni_release_thread_env(callback_data->jvm, env);
/********** JNI ENV RELEASE **********/
}
static void s_s3_meta_request_callback_cleanup(
JNIEnv *env,
struct s3_client_make_meta_request_callback_data *callback_data) {
if (callback_data) {
(*env)->DeleteGlobalRef(env, callback_data->java_s3_meta_request);
(*env)->DeleteGlobalRef(env, callback_data->java_s3_meta_request_response_handler_native_adapter);
(*env)->DeleteGlobalRef(env, callback_data->java_exception);
aws_signing_config_data_clean_up(&callback_data->signing_config_data, env);
aws_mem_release(aws_jni_get_allocator(), callback_data);
}
}
static struct aws_s3_meta_request_resume_token *s_native_resume_token_from_java_new(
JNIEnv *env,
jobject resume_token_jni) {
if (resume_token_jni == NULL) {
return NULL;
}
struct aws_allocator *allocator = aws_jni_get_allocator();
jint native_type =
(*env)->GetIntField(env, resume_token_jni, s3_meta_request_resume_token_properties.native_type_field_id);
if (native_type != AWS_S3_META_REQUEST_TYPE_PUT_OBJECT) {
aws_jni_throw_illegal_argument_exception(
env, "ResumeToken: Operations other than PutObject are not supported for resume.");
return NULL;
}
jlong part_size_jni =
(*env)->GetLongField(env, resume_token_jni, s3_meta_request_resume_token_properties.part_size_field_id);
jlong total_num_parts_jni =
(*env)->GetLongField(env, resume_token_jni, s3_meta_request_resume_token_properties.total_num_parts_field_id);
jlong num_parts_completed_jni = (*env)->GetLongField(
env, resume_token_jni, s3_meta_request_resume_token_properties.num_parts_completed_field_id);
jstring upload_id_jni =
(*env)->GetObjectField(env, resume_token_jni, s3_meta_request_resume_token_properties.upload_id_field_id);
if (upload_id_jni == NULL) {
aws_jni_throw_illegal_argument_exception(env, "ResumeToken: UploadId must not be NULL.");
return NULL;
}
struct aws_string *upload_id = aws_jni_new_string_from_jstring(env, upload_id_jni);
struct aws_s3_upload_resume_token_options upload_options = {
.part_size = (uint64_t)part_size_jni,
.total_num_parts = (size_t)total_num_parts_jni,
.num_parts_completed = (size_t)num_parts_completed_jni,
.upload_id = aws_byte_cursor_from_string(upload_id),
};
struct aws_s3_meta_request_resume_token *resume_token =
aws_s3_meta_request_resume_token_new_upload(allocator, &upload_options);
aws_string_destroy(upload_id);
return resume_token;
}
JNIEXPORT jlong JNICALL Java_software_amazon_awssdk_crt_s3_S3Client_s3ClientMakeMetaRequest(
JNIEnv *env,
jclass jni_class,
jlong jni_s3_client,
jobject java_s3_meta_request_jobject,
jbyteArray jni_region,
jint meta_request_type,
jint checksum_location,
jint checksum_algorithm,
jboolean validate_response,
jintArray jni_marshalled_validate_algorithms,
jbyteArray jni_marshalled_message_data,
jobject jni_http_request_body_stream,
jbyteArray jni_request_filepath,
jobject java_signing_config,
jobject java_response_handler_jobject,
jbyteArray jni_endpoint,
jobject java_resume_token_jobject) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_allocator *allocator = aws_jni_get_allocator();
struct aws_s3_client *client = (struct aws_s3_client *)jni_s3_client;
struct aws_byte_cursor request_filepath;
AWS_ZERO_STRUCT(request_filepath);
struct aws_s3_meta_request_resume_token *resume_token =
s_native_resume_token_from_java_new(env, java_resume_token_jobject);
struct aws_s3_meta_request *meta_request = NULL;
bool success = false;
struct aws_byte_cursor region = aws_jni_byte_cursor_from_jbyteArray_acquire(env, jni_region);
struct aws_http_message *request_message = NULL;
struct s3_client_make_meta_request_callback_data *callback_data =
aws_mem_calloc(allocator, 1, sizeof(struct s3_client_make_meta_request_callback_data));
AWS_FATAL_ASSERT(callback_data);
struct aws_signing_config_aws signing_config;
AWS_ZERO_STRUCT(signing_config);
if (java_signing_config != NULL) {
if (aws_build_signing_config(env, java_signing_config, &callback_data->signing_config_data, &signing_config)) {
goto done;
}
}
jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
(void)jvmresult;
AWS_FATAL_ASSERT(jvmresult == 0);
callback_data->java_s3_meta_request = (*env)->NewGlobalRef(env, java_s3_meta_request_jobject);
AWS_FATAL_ASSERT(callback_data->java_s3_meta_request != NULL);
callback_data->java_s3_meta_request_response_handler_native_adapter =
(*env)->NewGlobalRef(env, java_response_handler_jobject);
AWS_FATAL_ASSERT(callback_data->java_s3_meta_request_response_handler_native_adapter != NULL);
request_message = aws_http_message_new_request(allocator);
AWS_FATAL_ASSERT(request_message);
AWS_FATAL_ASSERT(
AWS_OP_SUCCESS == aws_apply_java_http_request_changes_to_native_request(
env, jni_marshalled_message_data, jni_http_request_body_stream, request_message));
if (jni_request_filepath) {
request_filepath = aws_jni_byte_cursor_from_jbyteArray_acquire(env, jni_request_filepath);
if (request_filepath.ptr == NULL) {
goto done;
}
if (request_filepath.len == 0) {
aws_jni_throw_illegal_argument_exception(env, "Request file path cannot be empty");
goto done;
}
}
struct aws_uri endpoint;
AWS_ZERO_STRUCT(endpoint);
if (jni_endpoint != NULL) {
struct aws_byte_cursor endpoint_str = aws_jni_byte_cursor_from_jbyteArray_acquire(env, jni_endpoint);
int uri_parse = aws_uri_init_parse(&endpoint, allocator, &endpoint_str);
aws_jni_byte_cursor_from_jbyteArray_release(env, jni_endpoint, endpoint_str);
if (uri_parse) {
aws_jni_throw_runtime_exception(env, "S3Client.aws_s3_client_make_meta_request: failed to parse endpoint");
goto done;
}
}
struct aws_s3_checksum_config checksum_config = {
.location = checksum_location,
.checksum_algorithm = checksum_algorithm,
.validate_response_checksum = validate_response,
};
struct aws_array_list response_checksum_list;
AWS_ZERO_STRUCT(response_checksum_list);
if (jni_marshalled_validate_algorithms != NULL) {
jint *marshalled_algorithms = (*env)->GetIntArrayElements(env, jni_marshalled_validate_algorithms, NULL);
const size_t marshalled_len = (*env)->GetArrayLength(env, jni_marshalled_validate_algorithms);
aws_array_list_init_dynamic(&response_checksum_list, allocator, marshalled_len, sizeof(int));
for (size_t i = 0; i < marshalled_len; ++i) {
enum aws_s3_checksum_algorithm algorithm = (int)marshalled_algorithms[i];
aws_array_list_push_back(&response_checksum_list, &algorithm);
}
checksum_config.validate_checksum_algorithms = &response_checksum_list;
}
struct aws_s3_meta_request_options meta_request_options = {
.type = meta_request_type,
.checksum_config = &checksum_config,
.message = request_message,
.send_filepath = request_filepath,
.user_data = callback_data,
.signing_config = java_signing_config ? &signing_config : NULL,
.headers_callback = s_on_s3_meta_request_headers_callback,
.body_callback = s_on_s3_meta_request_body_callback,
.finish_callback = s_on_s3_meta_request_finish_callback,
.progress_callback = s_on_s3_meta_request_progress_callback,
.shutdown_callback = s_on_s3_meta_request_shutdown_complete_callback,
.endpoint = jni_endpoint != NULL ? &endpoint : NULL,
.resume_token = resume_token,
};
meta_request = aws_s3_client_make_meta_request(client, &meta_request_options);
/* We are done using the list, it can be safely cleaned up now. */
aws_array_list_clean_up(&response_checksum_list);
if (!meta_request) {
aws_jni_throw_runtime_exception(
env, "S3Client.aws_s3_client_make_meta_request: creating aws_s3_meta_request failed");
goto done;
}
success = true;
done:
aws_s3_meta_request_resume_token_release(resume_token);
aws_jni_byte_cursor_from_jbyteArray_release(env, jni_region, region);
aws_http_message_release(request_message);
aws_jni_byte_cursor_from_jbyteArray_release(env, jni_request_filepath, request_filepath);
aws_uri_clean_up(&endpoint);
if (success) {
return (jlong)meta_request;
}
s_s3_meta_request_callback_cleanup(env, callback_data);
return (jlong)0;
}
static void s_on_s3_meta_request_shutdown_complete_callback(void *user_data) {
struct s3_client_make_meta_request_callback_data *callback_data =
(struct s3_client_make_meta_request_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;
}
if (callback_data->java_s3_meta_request != NULL) {
(*env)->CallVoidMethod(env, callback_data->java_s3_meta_request, s3_meta_request_properties.onShutdownComplete);
if (aws_jni_check_and_clear_exception(env)) {
AWS_LOGF_ERROR(
AWS_LS_S3_META_REQUEST,
"id=%p: Ignored Exception from S3MetaRequest.onShutdownCompete callback",
(void *)callback_data->java_s3_meta_request);
}
}
// We're done with this callback data, free it.
JavaVM *jvm = callback_data->jvm;
s_s3_meta_request_callback_cleanup(env, callback_data);
aws_jni_release_thread_env(jvm, env);
/********** JNI ENV RELEASE **********/
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_s3_S3MetaRequest_s3MetaRequestDestroy(
JNIEnv *env,
jclass jni_class,
jlong jni_s3_meta_request) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_s3_meta_request *meta_request = (struct aws_s3_meta_request *)jni_s3_meta_request;
if (!meta_request) {
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
aws_jni_throw_runtime_exception(env, "S3MetaRequest.s3MetaRequestDestroy: Invalid/null meta request");
return;
}
aws_s3_meta_request_release(meta_request);
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_s3_S3MetaRequest_s3MetaRequestCancel(
JNIEnv *env,
jclass jni_class,
jlong jni_s3_meta_request) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_s3_meta_request *meta_request = (struct aws_s3_meta_request *)jni_s3_meta_request;
if (!meta_request) {
/* It's fine if this particular function does nothing when it's called
* after CrtResource is closed and the handle is NULL */
return;
}
aws_s3_meta_request_cancel(meta_request);
}
JNIEXPORT jobject JNICALL Java_software_amazon_awssdk_crt_s3_S3MetaRequest_s3MetaRequestPause(
JNIEnv *env,
jclass jni_class,
jlong jni_s3_meta_request) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_s3_meta_request *meta_request = (struct aws_s3_meta_request *)jni_s3_meta_request;
if (!meta_request) {
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
aws_jni_throw_illegal_argument_exception(env, "S3MetaRequest.s3MetaRequestPause: Invalid/null meta request");
return NULL;
}
struct aws_s3_meta_request_resume_token *resume_token = NULL;
if (aws_s3_meta_request_pause(meta_request, &resume_token)) {
aws_jni_throw_runtime_exception(env, "S3MetaRequest.s3MetaRequestPause: Failed to pause request");
return NULL;
}
jobject resume_token_jni = NULL;
if (resume_token != NULL) {
resume_token_jni = (*env)->NewObject(
env,
s3_meta_request_resume_token_properties.s3_meta_request_resume_token_class,
s3_meta_request_resume_token_properties.s3_meta_request_resume_token_constructor_method_id);
if ((*env)->ExceptionCheck(env) || resume_token_jni == NULL) {
aws_jni_throw_runtime_exception(env, "S3MetaRequest.s3MetaRequestPause: Failed to create ResumeToken.");
goto on_done;
}
enum aws_s3_meta_request_type type = aws_s3_meta_request_resume_token_type(resume_token);
if (type != AWS_S3_META_REQUEST_TYPE_PUT_OBJECT) {
aws_jni_throw_runtime_exception(env, "S3MetaRequest.s3MetaRequestPause: Failed to convert resume token.");
goto on_done;
}
(*env)->SetIntField(env, resume_token_jni, s3_meta_request_resume_token_properties.native_type_field_id, type);
(*env)->SetLongField(
env,
resume_token_jni,
s3_meta_request_resume_token_properties.part_size_field_id,
aws_s3_meta_request_resume_token_part_size(resume_token));
(*env)->SetLongField(
env,
resume_token_jni,
s3_meta_request_resume_token_properties.total_num_parts_field_id,
aws_s3_meta_request_resume_token_total_num_parts(resume_token));
(*env)->SetLongField(
env,
resume_token_jni,
s3_meta_request_resume_token_properties.num_parts_completed_field_id,
aws_s3_meta_request_resume_token_num_parts_completed(resume_token));
struct aws_byte_cursor upload_id_cur = aws_s3_meta_request_resume_token_upload_id(resume_token);
jstring upload_id_jni = aws_jni_string_from_cursor(env, &upload_id_cur);
(*env)->SetObjectField(
env, resume_token_jni, s3_meta_request_resume_token_properties.upload_id_field_id, upload_id_jni);
(*env)->DeleteLocalRef(env, upload_id_jni);
}
on_done:
aws_s3_meta_request_resume_token_release(resume_token);
return resume_token_jni;
}
JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_s3_S3MetaRequest_s3MetaRequestIncrementReadWindow(
JNIEnv *env,
jclass jni_class,
jlong jni_s3_meta_request,
jlong increment) {
(void)jni_class;
aws_cache_jni_ids(env);
struct aws_s3_meta_request *meta_request = (struct aws_s3_meta_request *)jni_s3_meta_request;
if (!meta_request) {
/* It's fine if this particular function does nothing when it's called
* after CrtResource is closed and the handle is NULL */
return;
}
if (increment < 0) {
aws_jni_throw_illegal_argument_exception(
env, "S3MetaRequest.s3MetaRequestIncrementReadWindow: Number cannot be negative");
return;
}
aws_s3_meta_request_increment_read_window(meta_request, (uint64_t)increment);
}
#if UINTPTR_MAX == 0xffffffff
# if defined(_MSC_VER)
# pragma warning(pop)
# else
# pragma GCC diagnostic pop
# endif
#endif