/** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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