/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include "crt.h" #include "http_connection_manager.h" #include "http_request_response.h" #include "http_request_utils.h" #include "java_class_ids.h" #include #include #include #include #include #include #include #if _MSC_VER # pragma warning(disable : 4204) /* non-constant aggregate initializer */ #endif /* 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 jobject aws_java_http_stream_from_native_new(JNIEnv *env, void *opaque, int version) { jlong jni_native_ptr = (jlong)opaque; AWS_ASSERT(jni_native_ptr); jobject stream = NULL; switch (version) { case AWS_HTTP_VERSION_2: stream = (*env)->NewObject( env, http2_stream_properties.stream_class, http2_stream_properties.constructor, jni_native_ptr); break; case AWS_HTTP_VERSION_1_0: case AWS_HTTP_VERSION_1_1: stream = (*env)->NewObject( env, http_stream_properties.stream_class, http_stream_properties.constructor, jni_native_ptr); break; default: aws_jni_throw_runtime_exception(env, "Unsupported HTTP protocol."); aws_raise_error(AWS_ERROR_UNIMPLEMENTED); } return stream; } void aws_java_http_stream_from_native_delete(JNIEnv *env, jobject jHttpStream) { /* Delete our reference to the HttpStream Object from the JVM. */ (*env)->DeleteGlobalRef(env, jHttpStream); } /******************************************************************************* * http_stream_binding - Jni native represent of the Java HTTP stream object ******************************************************************************/ static void s_http_stream_binding_destroy(JNIEnv *env, struct http_stream_binding *binding) { if (binding->java_http_stream_base) { aws_java_http_stream_from_native_delete(env, binding->java_http_stream_base); } if (binding->java_http_response_stream_handler != NULL) { (*env)->DeleteGlobalRef(env, binding->java_http_response_stream_handler); } if (binding->native_request) { aws_http_message_release(binding->native_request); } aws_byte_buf_clean_up(&binding->headers_buf); aws_mem_release(aws_jni_get_allocator(), binding); } void *aws_http_stream_binding_acquire(struct http_stream_binding *binding) { if (binding == NULL) { return NULL; } aws_atomic_fetch_add(&binding->ref, 1); return binding; } void *aws_http_stream_binding_release(JNIEnv *env, struct http_stream_binding *binding) { if (binding == NULL) { return NULL; } size_t pre_ref = aws_atomic_fetch_sub(&binding->ref, 1); AWS_ASSERT(pre_ref > 0 && "stream binding refcount has gone negative"); if (pre_ref == 1) { s_http_stream_binding_destroy(env, binding); } return NULL; } // If error occurs, A Java exception is thrown and NULL is returned. struct http_stream_binding *aws_http_stream_binding_new(JNIEnv *env, jobject java_callback_handler) { struct aws_allocator *allocator = aws_jni_get_allocator(); struct http_stream_binding *binding = aws_mem_calloc(allocator, 1, sizeof(struct http_stream_binding)); AWS_FATAL_ASSERT(binding); // GetJavaVM() reference doesn't need a NewGlobalRef() call since it's global by default jint jvmresult = (*env)->GetJavaVM(env, &binding->jvm); (void)jvmresult; AWS_FATAL_ASSERT(jvmresult == 0); binding->java_http_response_stream_handler = (*env)->NewGlobalRef(env, java_callback_handler); AWS_FATAL_ASSERT(binding->java_http_response_stream_handler); AWS_FATAL_ASSERT(!aws_byte_buf_init(&binding->headers_buf, allocator, 1024)); aws_atomic_init_int(&binding->ref, 1); return binding; } int aws_java_http_stream_on_incoming_headers_fn( struct aws_http_stream *stream, enum aws_http_header_block block_type, const struct aws_http_header *header_array, size_t num_headers, void *user_data) { (void)block_type; struct http_stream_binding *binding = (struct http_stream_binding *)user_data; int resp_status = -1; int err_code = aws_http_stream_get_incoming_response_status(stream, &resp_status); if (err_code != AWS_OP_SUCCESS) { AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Invalid Incoming Response Status", (void *)stream); return AWS_OP_ERR; } binding->response_status = resp_status; if (aws_marshal_http_headers_array_to_dynamic_buffer(&binding->headers_buf, header_array, num_headers)) { AWS_LOGF_ERROR( AWS_LS_HTTP_STREAM, "id=%p: Failed to allocate buffer space for incoming headers", (void *)stream); return AWS_OP_ERR; } return AWS_OP_SUCCESS; } int aws_java_http_stream_on_incoming_header_block_done_fn( struct aws_http_stream *stream, enum aws_http_header_block block_type, void *user_data) { (void)stream; struct http_stream_binding *binding = (struct http_stream_binding *)user_data; /********** JNI ENV ACQUIRE **********/ JNIEnv *env = aws_jni_acquire_thread_env(binding->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; } int result = AWS_OP_ERR; jint jni_block_type = block_type; jobject jni_headers_buf = aws_jni_direct_byte_buffer_from_raw_ptr(env, binding->headers_buf.buffer, binding->headers_buf.len); (*env)->CallVoidMethod( env, binding->java_http_response_stream_handler, http_stream_response_handler_properties.onResponseHeaders, binding->java_http_stream_base, (jint)binding->response_status, (jint)block_type, jni_headers_buf); if (aws_jni_check_and_clear_exception(env)) { (*env)->DeleteLocalRef(env, jni_headers_buf); aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE); goto done; } /* instead of cleaning it up here, reset it in case another block is encountered */ aws_byte_buf_reset(&binding->headers_buf, false); (*env)->DeleteLocalRef(env, jni_headers_buf); (*env)->CallVoidMethod( env, binding->java_http_response_stream_handler, http_stream_response_handler_properties.onResponseHeadersDone, binding->java_http_stream_base, jni_block_type); if (aws_jni_check_and_clear_exception(env)) { aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE); goto done; } result = AWS_OP_SUCCESS; done: aws_jni_release_thread_env(binding->jvm, env); /********** JNI ENV RELEASE **********/ return result; } int aws_java_http_stream_on_incoming_body_fn( struct aws_http_stream *stream, const struct aws_byte_cursor *data, void *user_data) { struct http_stream_binding *binding = (struct http_stream_binding *)user_data; size_t total_window_increment = 0; /********** JNI ENV ACQUIRE **********/ JNIEnv *env = aws_jni_acquire_thread_env(binding->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; } int result = AWS_OP_ERR; jobject jni_payload = aws_jni_direct_byte_buffer_from_raw_ptr(env, data->ptr, data->len); jint window_increment = (*env)->CallIntMethod( env, binding->java_http_response_stream_handler, http_stream_response_handler_properties.onResponseBody, binding->java_http_stream_base, jni_payload); (*env)->DeleteLocalRef(env, jni_payload); if (aws_jni_check_and_clear_exception(env)) { AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Received Exception from onResponseBody", (void *)stream); aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE); goto done; } if (window_increment < 0) { AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Window Increment from onResponseBody < 0", (void *)stream); aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE); goto done; } total_window_increment += window_increment; if (total_window_increment > 0) { aws_http_stream_update_window(stream, total_window_increment); } result = AWS_OP_SUCCESS; done: aws_jni_release_thread_env(binding->jvm, env); /********** JNI ENV RELEASE **********/ return result; } void aws_java_http_stream_on_stream_complete_fn(struct aws_http_stream *stream, int error_code, void *user_data) { struct http_stream_binding *binding = (struct http_stream_binding *)user_data; /********** JNI ENV ACQUIRE **********/ JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm); if (env == NULL) { /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */ return; } /* Don't invoke Java callbacks if Java HttpStream failed to completely setup */ jint jErrorCode = error_code; (*env)->CallVoidMethod( env, binding->java_http_response_stream_handler, http_stream_response_handler_properties.onResponseComplete, binding->java_http_stream_base, jErrorCode); if (aws_jni_check_and_clear_exception(env)) { /* Close the Connection if the Java Callback throws an Exception */ aws_http_connection_close(aws_http_stream_get_connection(stream)); } aws_jni_release_thread_env(binding->jvm, env); /********** JNI ENV RELEASE **********/ } void aws_java_http_stream_on_stream_destroy_fn(void *user_data) { struct http_stream_binding *binding = (struct http_stream_binding *)user_data; /********** JNI ENV ACQUIRE **********/ JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm); if (env == NULL) { /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */ return; } /* Native stream destroyed, release the binding. */ aws_http_stream_binding_release(env, binding); aws_jni_release_thread_env(binding->jvm, env); /********** JNI ENV RELEASE **********/ } void aws_java_http_stream_on_stream_metrics_fn( struct aws_http_stream *stream, const struct aws_http_stream_metrics *metrics, void *user_data) { struct http_stream_binding *binding = (struct http_stream_binding *)user_data; /********** JNI ENV ACQUIRE **********/ JNIEnv *env = aws_jni_acquire_thread_env(binding->jvm); if (env == NULL) { /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */ return; } /* Convert metrics to Java HttpStreamMetrics obj */ jobject jni_metrics = (*env)->NewObject( env, http_stream_metrics_properties.http_stream_metrics_class, http_stream_metrics_properties.constructor_id, (jlong)metrics->send_start_timestamp_ns, (jlong)metrics->send_end_timestamp_ns, (jlong)metrics->sending_duration_ns, (jlong)metrics->receive_start_timestamp_ns, (jlong)metrics->receive_end_timestamp_ns, (jlong)metrics->receiving_duration_ns, /* Stream IDs are 31-bit unsigned integers, which fits into Java's regular (signed) 32-bit int */ (jint)metrics->stream_id); (*env)->CallVoidMethod( env, binding->java_http_response_stream_handler, http_stream_response_handler_properties.onMetrics, binding->java_http_stream_base, jni_metrics); /* Delete local reference to metrics object */ (*env)->DeleteLocalRef(env, jni_metrics); if (aws_jni_check_and_clear_exception(env)) { /* Close the Connection if the Java Callback throws an Exception */ aws_http_connection_close(aws_http_stream_get_connection(stream)); AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=%p: Received Exception from onMetrics", (void *)stream); aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE); } aws_jni_release_thread_env(binding->jvm, env); /********** JNI ENV RELEASE **********/ } jobjectArray aws_java_http_headers_from_native(JNIEnv *env, struct aws_http_headers *headers) { (void)headers; jobjectArray ret; const size_t header_count = aws_http_headers_count(headers); ret = (jobjectArray)(*env)->NewObjectArray( env, (jsize)header_count, http_header_properties.http_header_class, (void *)NULL); for (size_t index = 0; index < header_count; index += 1) { struct aws_http_header header; aws_http_headers_get_index(headers, index, &header); jbyteArray header_name = aws_jni_byte_array_from_cursor(env, &header.name); jbyteArray header_value = aws_jni_byte_array_from_cursor(env, &header.value); jobject java_http_header = (*env)->NewObject( env, http_header_properties.http_header_class, http_header_properties.constructor_method_id, header_name, header_value); (*env)->SetObjectArrayElement(env, ret, (jsize)index, java_http_header); } return (ret); } static jobject s_make_request_general( JNIEnv *env, jlong jni_connection, jbyteArray marshalled_request, jobject jni_http_request_body_stream, jobject jni_http_response_callback_handler, enum aws_http_version version) { struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection; struct aws_http_connection *native_conn = connection_binding->connection; if (!native_conn) { aws_jni_throw_null_pointer_exception(env, "HttpClientConnection.MakeRequest: Invalid aws_http_connection"); return (jobject)NULL; } if (!jni_http_response_callback_handler) { aws_jni_throw_illegal_argument_exception( env, "HttpClientConnection.MakeRequest: Invalid jni_http_response_callback_handler"); return (jobject)NULL; } /* initial refcount created for the Java object */ struct http_stream_binding *stream_binding = aws_http_stream_binding_new(env, jni_http_response_callback_handler); if (!stream_binding) { /* Exception already thrown */ return (jobject)NULL; } stream_binding->native_request = aws_http_request_new_from_java_http_request(env, marshalled_request, jni_http_request_body_stream); if (stream_binding->native_request == NULL) { /* Exception already thrown */ goto error; } struct aws_http_make_request_options request_options = { .self_size = sizeof(request_options), .request = stream_binding->native_request, /* Set Callbacks */ .on_response_headers = aws_java_http_stream_on_incoming_headers_fn, .on_response_header_block_done = aws_java_http_stream_on_incoming_header_block_done_fn, .on_response_body = aws_java_http_stream_on_incoming_body_fn, .on_complete = aws_java_http_stream_on_stream_complete_fn, .on_destroy = aws_java_http_stream_on_stream_destroy_fn, .on_metrics = aws_java_http_stream_on_stream_metrics_fn, .user_data = stream_binding, }; stream_binding->native_stream = aws_http_connection_make_request(native_conn, &request_options); if (stream_binding->native_stream == NULL) { AWS_LOGF_ERROR(AWS_LS_HTTP_CONNECTION, "Stream Request Failed. conn: %p", (void *)native_conn); aws_jni_throw_runtime_exception(env, "HttpClientConnection.MakeRequest: Unable to Execute Request"); goto error; } /* Stream created successfully, acquire on binding for the native stream lifetime. */ aws_http_stream_binding_acquire(stream_binding); jobject jHttpStreamBase = aws_java_http_stream_from_native_new(env, stream_binding, version); if (jHttpStreamBase == NULL) { goto error; } AWS_LOGF_TRACE( AWS_LS_HTTP_CONNECTION, "Opened new Stream on Connection. conn: %p, stream: %p", (void *)native_conn, (void *)stream_binding->native_stream); return jHttpStreamBase; error: aws_http_stream_release(stream_binding->native_stream); aws_http_stream_binding_release(env, stream_binding); return NULL; } JNIEXPORT jobject JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionMakeRequest( JNIEnv *env, jclass jni_class, jlong jni_connection, jbyteArray marshalled_request, jobject jni_http_request_body_stream, jobject jni_http_response_callback_handler) { (void)jni_class; aws_cache_jni_ids(env); return s_make_request_general( env, jni_connection, marshalled_request, jni_http_request_body_stream, jni_http_response_callback_handler, AWS_HTTP_VERSION_1_1); } JNIEXPORT jobject JNICALL Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionMakeRequest( JNIEnv *env, jclass jni_class, jlong jni_connection, jbyteArray marshalled_request, jobject jni_http_request_body_stream, jobject jni_http_response_callback_handler) { (void)jni_class; aws_cache_jni_ids(env); return s_make_request_general( env, jni_connection, marshalled_request, jni_http_request_body_stream, jni_http_response_callback_handler, AWS_HTTP_VERSION_2); } struct http_stream_chunked_callback_data { struct http_stream_binding *stream_cb_data; struct aws_byte_buf chunk_data; struct aws_input_stream *chunk_stream; jobject completion_callback; }; static void s_cleanup_chunked_callback_data( JNIEnv *env, struct http_stream_chunked_callback_data *chunked_callback_data) { aws_input_stream_destroy(chunked_callback_data->chunk_stream); aws_byte_buf_clean_up(&chunked_callback_data->chunk_data); (*env)->DeleteGlobalRef(env, chunked_callback_data->completion_callback); aws_mem_release(aws_jni_get_allocator(), chunked_callback_data); } static void s_write_chunk_complete(struct aws_http_stream *stream, int error_code, void *user_data) { (void)stream; struct http_stream_chunked_callback_data *chunked_callback_data = user_data; /********** JNI ENV ACQUIRE **********/ JNIEnv *env = aws_jni_acquire_thread_env(chunked_callback_data->stream_cb_data->jvm); if (env == NULL) { /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */ return; } (*env)->CallVoidMethod( env, chunked_callback_data->completion_callback, http_stream_write_chunk_completion_properties.callback, error_code); aws_jni_check_and_clear_exception(env); JavaVM *jvm = chunked_callback_data->stream_cb_data->jvm; s_cleanup_chunked_callback_data(env, chunked_callback_data); aws_jni_release_thread_env(jvm, env); /********** JNI ENV RELEASE **********/ } JNIEXPORT jint JNICALL Java_software_amazon_awssdk_crt_http_HttpStream_httpStreamWriteChunk( JNIEnv *env, jclass jni_class, jlong jni_cb_data, jbyteArray chunk_data, jboolean is_final_chunk, jobject completion_callback) { (void)jni_class; aws_cache_jni_ids(env); struct http_stream_binding *cb_data = (struct http_stream_binding *)jni_cb_data; struct aws_http_stream *stream = cb_data->native_stream; struct http_stream_chunked_callback_data *chunked_callback_data = aws_mem_calloc(aws_jni_get_allocator(), 1, sizeof(struct http_stream_chunked_callback_data)); chunked_callback_data->stream_cb_data = cb_data; chunked_callback_data->completion_callback = (*env)->NewGlobalRef(env, completion_callback); struct aws_byte_cursor chunk_cur = aws_jni_byte_cursor_from_jbyteArray_acquire(env, chunk_data); aws_byte_buf_init_copy_from_cursor(&chunked_callback_data->chunk_data, aws_jni_get_allocator(), chunk_cur); aws_jni_byte_cursor_from_jbyteArray_release(env, chunk_data, chunk_cur); struct aws_http1_chunk_options chunk_options = { .chunk_data_size = chunked_callback_data->chunk_data.len, .user_data = chunked_callback_data, .on_complete = s_write_chunk_complete, }; chunk_cur = aws_byte_cursor_from_buf(&chunked_callback_data->chunk_data); chunked_callback_data->chunk_stream = aws_input_stream_new_from_cursor(aws_jni_get_allocator(), &chunk_cur); chunk_options.chunk_data = chunked_callback_data->chunk_stream; if (aws_http1_stream_write_chunk(stream, &chunk_options)) { s_cleanup_chunked_callback_data(env, chunked_callback_data); return AWS_OP_ERR; } if (is_final_chunk) { struct aws_http1_chunk_options final_chunk_options = { .chunk_data_size = 0, }; if (aws_http1_stream_write_chunk(stream, &final_chunk_options)) { return AWS_OP_ERR; } } return AWS_OP_SUCCESS; } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpStreamBase_httpStreamBaseActivate( JNIEnv *env, jclass jni_class, jlong jni_stream_binding, jobject j_http_stream_base) { (void)jni_class; aws_cache_jni_ids(env); struct http_stream_binding *binding = (struct http_stream_binding *)jni_stream_binding; struct aws_http_stream *stream = binding->native_stream; if (stream == NULL) { aws_jni_throw_runtime_exception(env, "HttpStream is null."); return; } AWS_LOGF_TRACE(AWS_LS_HTTP_STREAM, "Activating Stream. stream: %p", (void *)stream); /* global ref this because now the callbacks will be firing, and they will release their reference when the * stream callback sequence completes. */ binding->java_http_stream_base = (*env)->NewGlobalRef(env, j_http_stream_base); if (aws_http_stream_activate(stream)) { (*env)->DeleteGlobalRef(env, binding->java_http_stream_base); aws_jni_throw_runtime_exception( env, "HttpStream activate failed with error %s\n", aws_error_str(aws_last_error())); } } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpStreamBase_httpStreamBaseRelease( JNIEnv *env, jclass jni_class, jlong jni_binding) { (void)jni_class; aws_cache_jni_ids(env); struct http_stream_binding *binding = (struct http_stream_binding *)jni_binding; struct aws_http_stream *stream = binding->native_stream; if (stream == NULL) { aws_jni_throw_runtime_exception(env, "HttpStream is null."); return; } AWS_LOGF_TRACE(AWS_LS_HTTP_STREAM, "Releasing Stream. stream: %p", (void *)stream); aws_http_stream_release(stream); aws_http_stream_binding_release(env, binding); } JNIEXPORT jint JNICALL Java_software_amazon_awssdk_crt_http_HttpStreamBase_httpStreamBaseGetResponseStatusCode( JNIEnv *env, jclass jni_class, jlong jni_binding) { (void)jni_class; aws_cache_jni_ids(env); struct http_stream_binding *binding = (struct http_stream_binding *)jni_binding; struct aws_http_stream *stream = binding->native_stream; if (stream == NULL) { aws_jni_throw_runtime_exception(env, "HttpStream is null."); return -1; } int status = -1; int err_code = aws_http_stream_get_incoming_response_status(stream, &status); if (err_code != AWS_OP_SUCCESS) { aws_jni_throw_runtime_exception(env, "Error Getting Response Status Code from HttpStream."); return -1; } return (jint)status; } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpStreamBase_httpStreamBaseIncrementWindow( JNIEnv *env, jclass jni_class, jlong jni_binding, jint window_update) { (void)jni_class; aws_cache_jni_ids(env); struct http_stream_binding *binding = (struct http_stream_binding *)jni_binding; struct aws_http_stream *stream = binding->native_stream; if (stream == NULL) { aws_jni_throw_runtime_exception(env, "HttpStream is null."); return; } if (window_update < 0) { aws_jni_throw_runtime_exception(env, "Window Update is < 0"); return; } AWS_LOGF_TRACE( AWS_LS_HTTP_STREAM, "Updating Stream Window. stream: %p, update: %d", (void *)stream, (int)window_update); aws_http_stream_update_window(stream, window_update); } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2Stream_http2StreamResetStream( JNIEnv *env, jclass jni_class, jlong jni_cb_data, jint error_code) { (void)jni_class; aws_cache_jni_ids(env); struct http_stream_binding *binding = (struct http_stream_binding *)jni_cb_data; struct aws_http_stream *stream = binding->native_stream; if (stream == NULL) { aws_jni_throw_null_pointer_exception(env, "Http2Stream is null."); return; } AWS_LOGF_TRACE(AWS_LS_HTTP_STREAM, "Resetting Stream. stream: %p", (void *)stream); if (aws_http2_stream_reset(stream, error_code)) { aws_jni_throw_runtime_exception( env, "reset stream failed with error %d(%s).", aws_last_error(), aws_error_debug_str(aws_last_error())); return; } } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionShutdown( JNIEnv *env, jclass jni_class, jlong jni_connection) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection; struct aws_http_connection *native_conn = connection_binding->connection; if (!native_conn) { aws_jni_throw_runtime_exception(env, "HttpClientConnection.Shutdown: Invalid aws_http_connection"); return; } aws_http_connection_close(native_conn); } JNIEXPORT jboolean JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionIsOpen( JNIEnv *env, jclass jni_class, jlong jni_connection) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection; struct aws_http_connection *native_conn = connection_binding->connection; if (!native_conn) { aws_jni_throw_runtime_exception(env, "HttpClientConnection.isOpen: Invalid aws_http_connection"); return false; } return aws_http_connection_is_open(native_conn); } JNIEXPORT jshort JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_httpClientConnectionGetVersion( JNIEnv *env, jclass jni_class, jlong jni_connection) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection; struct aws_http_connection *native_conn = connection_binding->connection; if (!native_conn) { aws_jni_throw_runtime_exception(env, "HttpClientConnection.getVersion: Invalid aws_http_connection"); return 0; } return (jshort)aws_http_connection_get_version(native_conn); } JNIEXPORT jboolean JNICALL Java_software_amazon_awssdk_crt_http_HttpClientConnection_isErrorRetryable( JNIEnv *env, jclass jni_class, jint error_code) { (void)jni_class; (void)env; aws_cache_jni_ids(env); switch (error_code) { case AWS_ERROR_HTTP_HEADER_NOT_FOUND: case AWS_ERROR_HTTP_INVALID_HEADER_FIELD: case AWS_ERROR_HTTP_INVALID_HEADER_NAME: case AWS_ERROR_HTTP_INVALID_HEADER_VALUE: case AWS_ERROR_HTTP_INVALID_METHOD: case AWS_ERROR_HTTP_INVALID_PATH: case AWS_ERROR_HTTP_INVALID_STATUS_CODE: case AWS_ERROR_HTTP_MISSING_BODY_STREAM: case AWS_ERROR_HTTP_INVALID_BODY_STREAM: case AWS_ERROR_HTTP_OUTGOING_STREAM_LENGTH_INCORRECT: case AWS_ERROR_HTTP_CALLBACK_FAILURE: case AWS_ERROR_HTTP_STREAM_MANAGER_SHUTTING_DOWN: case AWS_HTTP2_ERR_CANCEL: return false; default: return true; } } struct aws_http2_callback_data { JavaVM *jvm; jobject async_callback; }; static void s_cleanup_http2_callback_data(struct aws_http2_callback_data *callback_data, JNIEnv *env) { if (callback_data == NULL || env == NULL) { return; } if (callback_data->async_callback) { (*env)->DeleteGlobalRef(env, callback_data->async_callback); } aws_mem_release(aws_jni_get_allocator(), callback_data); } static struct aws_http2_callback_data *s_new_http2_callback_data( JNIEnv *env, struct aws_allocator *allocator, jobject async_callback) { struct aws_http2_callback_data *callback_data = aws_mem_calloc(allocator, 1, sizeof(struct aws_http2_callback_data)); jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm); AWS_FATAL_ASSERT(jvmresult == 0); callback_data->async_callback = async_callback ? (*env)->NewGlobalRef(env, async_callback) : NULL; AWS_FATAL_ASSERT(callback_data->async_callback != NULL); return callback_data; } static void s_on_settings_completed(struct aws_http_connection *http2_connection, int error_code, void *user_data) { (void)http2_connection; struct aws_http2_callback_data *callback_data = user_data; /********** JNI ENV ACQUIRE **********/ JavaVM *jvm = callback_data->jvm; JNIEnv *env = aws_jni_acquire_thread_env(jvm); if (env == NULL) { /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */ return; } if (error_code) { jobject crt_exception = aws_jni_new_crt_exception_from_error_code(env, error_code); (*env)->CallVoidMethod(env, callback_data->async_callback, async_callback_properties.on_failure, crt_exception); (*env)->DeleteLocalRef(env, crt_exception); } else { (*env)->CallVoidMethod(env, callback_data->async_callback, async_callback_properties.on_success); } AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env)); s_cleanup_http2_callback_data(callback_data, env); aws_jni_release_thread_env(jvm, env); /********** JNI ENV RELEASE **********/ } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionUpdateSettings( JNIEnv *env, jclass jni_class, jlong jni_connection, jobject java_async_callback, jlongArray java_marshalled_settings) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection; struct aws_http_connection *native_conn = connection_binding->connection; if (!native_conn) { aws_jni_throw_null_pointer_exception( env, "Http2ClientConnection.http2ClientConnectionUpdateSettings: Invalid aws_http_connection"); return; } if (!java_async_callback) { aws_jni_throw_illegal_argument_exception( env, "Http2ClientConnection.http2ClientConnectionUpdateSettings: Invalid async callback"); return; } struct aws_allocator *allocator = aws_jni_get_allocator(); struct aws_http2_callback_data *callback_data = s_new_http2_callback_data(env, allocator, java_async_callback); /* We marshalled each setting to two long integers, the long list will be number of settings times two */ const size_t len = (*env)->GetArrayLength(env, java_marshalled_settings); AWS_ASSERT(len % 2 == 0); const size_t settings_len = len / 2; struct aws_http2_setting *settings = settings_len ? aws_mem_calloc(allocator, settings_len, sizeof(struct aws_http2_setting)) : NULL; int success = false; jlong *marshalled_settings = (*env)->GetLongArrayElements(env, java_marshalled_settings, NULL); for (size_t i = 0; i < settings_len; i++) { jlong id = marshalled_settings[i * 2]; settings[i].id = id; jlong value = marshalled_settings[i * 2 + 1]; settings[i].value = (uint32_t)value; } if (aws_http2_connection_change_settings( native_conn, settings, settings_len, s_on_settings_completed, callback_data)) { aws_jni_throw_runtime_exception( env, "Http2ClientConnection.http2ClientConnectionUpdateSettings: failed to change settings"); goto done; } success = true; done: aws_mem_release(allocator, settings); (*env)->ReleaseLongArrayElements(env, java_marshalled_settings, (jlong *)marshalled_settings, JNI_ABORT); if (!success) { s_cleanup_http2_callback_data(callback_data, env); } return; } static void s_on_ping_completed( struct aws_http_connection *http2_connection, uint64_t round_trip_time_ns, int error_code, void *user_data) { (void)http2_connection; struct aws_http2_callback_data *callback_data = user_data; /********** JNI ENV ACQUIRE **********/ JavaVM *jvm = callback_data->jvm; JNIEnv *env = aws_jni_acquire_thread_env(jvm); if (env == NULL) { /* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */ return; } if (error_code) { jobject crt_exception = aws_jni_new_crt_exception_from_error_code(env, error_code); (*env)->CallVoidMethod(env, callback_data->async_callback, async_callback_properties.on_failure, crt_exception); (*env)->DeleteLocalRef(env, crt_exception); } else { jobject java_round_trip_time_ns = (*env)->NewObject( env, boxed_long_properties.long_class, boxed_long_properties.constructor, (jlong)round_trip_time_ns); (*env)->CallVoidMethod( env, callback_data->async_callback, async_callback_properties.on_success_with_object, java_round_trip_time_ns); (*env)->DeleteLocalRef(env, java_round_trip_time_ns); } AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env)); s_cleanup_http2_callback_data(callback_data, env); aws_jni_release_thread_env(jvm, env); /********** JNI ENV RELEASE **********/ } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionSendPing( JNIEnv *env, jclass jni_class, jlong jni_connection, jobject java_async_callback, jbyteArray ping_data) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection; struct aws_http_connection *native_conn = connection_binding->connection; if (!native_conn) { aws_jni_throw_null_pointer_exception( env, "Http2ClientConnection.http2ClientConnectionSendPing: Invalid aws_http_connection"); return; } if (!java_async_callback) { aws_jni_throw_illegal_argument_exception( env, "Http2ClientConnection.http2ClientConnectionSendPing: Invalid async callback"); return; } bool success = false; struct aws_allocator *allocator = aws_jni_get_allocator(); struct aws_byte_cursor *ping_cur_pointer = NULL; struct aws_byte_cursor ping_cur; AWS_ZERO_STRUCT(ping_cur); struct aws_http2_callback_data *callback_data = s_new_http2_callback_data(env, allocator, java_async_callback); if (ping_data) { ping_cur = aws_jni_byte_cursor_from_jbyteArray_acquire(env, ping_data); ping_cur_pointer = &ping_cur; } if (aws_http2_connection_ping(native_conn, ping_cur_pointer, s_on_ping_completed, callback_data)) { aws_jni_throw_runtime_exception(env, "Failed to send ping"); goto done; } success = true; done: if (ping_cur_pointer) { aws_jni_byte_cursor_from_jbyteArray_release(env, ping_data, ping_cur); } if (!success) { s_cleanup_http2_callback_data(callback_data, env); } return; } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionSendGoAway( JNIEnv *env, jclass jni_class, jlong jni_connection, jlong h2_error_code, jboolean allow_more_streams, jbyteArray debug_data) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection; struct aws_http_connection *native_conn = connection_binding->connection; struct aws_byte_cursor *debug_cur_pointer = NULL; struct aws_byte_cursor debug_cur; AWS_ZERO_STRUCT(debug_cur); if (!native_conn) { aws_jni_throw_runtime_exception( env, "Http2ClientConnection.http2ClientConnectionSendGoAway: Invalid aws_http_connection"); return; } if (debug_data) { debug_cur = aws_jni_byte_cursor_from_jbyteArray_acquire(env, debug_data); debug_cur_pointer = &debug_cur; } aws_http2_connection_send_goaway(native_conn, (uint32_t)h2_error_code, allow_more_streams, debug_cur_pointer); if (debug_cur_pointer) { aws_jni_byte_cursor_from_jbyteArray_release(env, debug_data, debug_cur); } return; } JNIEXPORT void JNICALL Java_software_amazon_awssdk_crt_http_Http2ClientConnection_http2ClientConnectionUpdateConnectionWindow( JNIEnv *env, jclass jni_class, jlong jni_connection, jlong increment_size) { (void)jni_class; aws_cache_jni_ids(env); struct aws_http_connection_binding *connection_binding = (struct aws_http_connection_binding *)jni_connection; struct aws_http_connection *native_conn = connection_binding->connection; if (!native_conn) { aws_jni_throw_runtime_exception( env, "Http2ClientConnection.http2ClientConnectionUpdateConnectionWindow: Invalid aws_http_connection"); return; } /* We did range check in Java already. */ aws_http2_connection_update_window(native_conn, (uint32_t)increment_size); return; } #if UINTPTR_MAX == 0xffffffff # if defined(_MSC_VER) # pragma warning(pop) # else # pragma GCC diagnostic pop # endif #endif