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

#include <mqtt5_packets.h>

#include <aws/mqtt/v5/mqtt5_client.h>
#include <crt.h>

#include <java_class_ids.h>

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

/*******************************************************************************
 * STRUCT DEFINITIONS
 ******************************************************************************/

struct aws_mqtt5_packet_connect_view_java_jni {
    struct aws_mqtt5_packet_connect_view packet;

    struct aws_byte_buf client_id_buf;
    struct aws_byte_cursor client_id_cursor;
    struct aws_byte_buf username_buf;
    struct aws_byte_cursor username_cursor;
    struct aws_byte_buf password_buf;
    struct aws_byte_cursor password_cursor;
    uint32_t session_expiry_interval_seconds;
    uint8_t request_response_information;
    uint8_t request_problem_information;
    uint16_t receive_maximum;
    uint16_t topic_alias_maximum;
    uint32_t maximum_packet_size_bytes;
    uint32_t will_delay_interval_seconds;
    uint16_t keep_alive_interval_seconds;
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list jni_user_properties_holder;
    /* Contains aws_mqtt5_user_property pointers */
    struct aws_array_list jni_user_properties_struct_holder;
    struct aws_mqtt5_packet_publish_view_java_jni *will_publish_packet;
};

struct aws_mqtt5_packet_disconnect_view_java_jni {
    struct aws_mqtt5_packet_disconnect_view packet;

    struct aws_byte_buf reason_string_buf;
    struct aws_byte_cursor reason_string_cursor;
    struct aws_byte_buf server_reference_buf;
    struct aws_byte_cursor server_reference_cursor;
    uint32_t session_expiry_interval_seconds;
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list jni_user_properties_holder;
    /* Contains aws_mqtt5_user_property pointers */
    struct aws_array_list jni_user_properties_struct_holder;
};

struct aws_mqtt5_packet_publish_view_java_jni {
    struct aws_mqtt5_packet_publish_view packet;

    struct aws_byte_buf payload_buf;
    struct aws_byte_cursor payload_cursor;
    struct aws_byte_buf topic_buf;
    struct aws_byte_cursor topic_cursor;
    enum aws_mqtt5_payload_format_indicator payload_format;
    uint32_t message_expiry_interval_seconds;
    uint16_t topic_alias;
    struct aws_byte_buf response_topic_buf;
    struct aws_byte_cursor response_topic_cursor;
    struct aws_byte_buf correlation_data_buf;
    struct aws_byte_cursor correlation_data_cursor;
    struct aws_byte_buf content_type_buf;
    struct aws_byte_cursor content_type_cursor;
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list jni_user_properties_holder;
    /* Contains aws_mqtt5_user_property pointers */
    struct aws_array_list jni_user_properties_struct_holder;
};

struct aws_mqtt5_packet_subscribe_view_java_jni {
    struct aws_mqtt5_packet_subscribe_view packet;

    /* Contains aws_mqtt5_subscription_view pointers */
    struct aws_array_list topic_filters;
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list jni_subscription_topic_filters;
    uint32_t subscription_identifier;
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list jni_user_properties_holder;
    /* Contains aws_mqtt5_user_property pointers */
    struct aws_array_list jni_user_properties_struct_holder;
};

struct aws_mqtt5_packet_unsubscribe_view_java_jni {
    struct aws_mqtt5_packet_unsubscribe_view packet;

    /* Contains aws_byte_cursor pointers */
    struct aws_array_list topic_filters;
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list jni_topic_filters;
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list jni_user_properties_holder;
    /* Contains aws_mqtt5_user_property pointers */
    struct aws_array_list jni_user_properties_struct_holder;
};

struct buffer_and_cursor_array_holder_struct {
    struct aws_byte_cursor cursor;
    struct aws_byte_buf buffer;
};

/*******************************************************************************
 * HELPER FUNCTIONS
 ******************************************************************************/

static int s_populate_user_properties(
    JNIEnv *env,
    jobject jni_user_properties_list,
    size_t java_packet_native_user_property_count,
    const struct aws_mqtt5_user_property **java_packet_native_user_properties,
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list *java_packet_user_properties_holder,
    /* Contains aws_mqtt5_user_property pointers */
    struct aws_array_list *java_packet_user_properties_struct_holder) {

    if (jni_user_properties_list) {
        for (size_t i = 0; i < java_packet_native_user_property_count; i++) {
            jobject jni_property =
                (*env)->CallObjectMethod(env, jni_user_properties_list, boxed_list_properties.list_get_id, (jint)i);
            if (!jni_property || aws_jni_check_and_clear_exception(env)) {
                AWS_LOGF_ERROR(
                    AWS_LS_MQTT5_CLIENT,
                    "Could not populate user properties due to being unable to get property in list from Java");
                return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
            }

            jstring jni_property_key =
                (jstring)(*env)->GetObjectField(env, jni_property, mqtt5_user_property_properties.property_key_id);
            if (aws_jni_check_and_clear_exception(env) || !jni_property_key) {
                AWS_LOGF_ERROR(
                    AWS_LS_MQTT5_CLIENT,
                    "Could not populate user properties due to exception when getting property key");
                return aws_raise_error(AWS_ERROR_INVALID_STATE);
            }
            jstring jni_property_value =
                (jstring)(*env)->GetObjectField(env, jni_property, mqtt5_user_property_properties.property_value_id);
            if (aws_jni_check_and_clear_exception(env) || !jni_property_value) {
                AWS_LOGF_ERROR(
                    AWS_LS_MQTT5_CLIENT,
                    "Could not populate user properties due to exception when getting property value");
                return aws_raise_error(AWS_ERROR_INVALID_STATE);
            }

            if (!jni_property_key) {
                AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "Error reading a user property: Key in user property was NULL!");
                return aws_raise_error(AWS_ERROR_INVALID_STATE);
            }
            if (!jni_property_value) {
                AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "Error reading a user property: Key in user property was NULL!");
                return aws_raise_error(AWS_ERROR_INVALID_STATE);
            }

            // Get a temporary cursor from JNI, copy it, and then destroy the JNI version, leaving the byte_buffer copy.
            // This gets around JNI stuff going out of scope.
            struct buffer_and_cursor_array_holder_struct holder_property_key;
            struct aws_byte_cursor tmp_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, jni_property_key);
            aws_byte_buf_init_copy_from_cursor(&holder_property_key.buffer, aws_jni_get_allocator(), tmp_cursor);
            holder_property_key.cursor = aws_byte_cursor_from_buf(&holder_property_key.buffer);
            aws_jni_byte_cursor_from_jstring_release(env, jni_property_key, tmp_cursor);
            jni_property_key = NULL;

            // Get a temporary cursor from JNI, copy it, and then destroy the JNI version, leaving the byte_buffer copy.
            // This gets around JNI stuff going out of scope.
            struct buffer_and_cursor_array_holder_struct holder_property_value;
            tmp_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, jni_property_value);
            aws_byte_buf_init_copy_from_cursor(&holder_property_value.buffer, aws_jni_get_allocator(), tmp_cursor);
            holder_property_value.cursor = aws_byte_cursor_from_buf(&holder_property_value.buffer);
            aws_jni_byte_cursor_from_jstring_release(env, jni_property_value, tmp_cursor);
            jni_property_value = NULL;

            aws_array_list_push_back(java_packet_user_properties_holder, (void *)&holder_property_key);
            aws_array_list_push_back(java_packet_user_properties_holder, (void *)&holder_property_value);

            struct aws_mqtt5_user_property jni_property_struct = {
                .name = holder_property_key.cursor,
                .value = holder_property_value.cursor,
            };
            aws_array_list_push_back(java_packet_user_properties_struct_holder, (void *)&jni_property_struct);
        }
        *java_packet_native_user_properties =
            (struct aws_mqtt5_user_property *)java_packet_user_properties_struct_holder->data;
    }
    return AWS_OP_SUCCESS;
}

static int s_allocate_user_properties_array_holders(
    struct aws_allocator *allocator,
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list *holder_array,
    /* Contains aws_mqtt5_user_property pointers */
    struct aws_array_list *user_property_array,
    size_t init_entries) {

    if (init_entries > 0) {
        if (aws_array_list_init_dynamic(
                holder_array, allocator, 2 * init_entries, sizeof(struct buffer_and_cursor_array_holder_struct)) !=
                AWS_OP_SUCCESS ||
            aws_array_list_init_dynamic(
                user_property_array, allocator, 2 * init_entries, sizeof(struct aws_mqtt5_user_property)) !=
                AWS_OP_SUCCESS) {
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
    }
    return AWS_OP_SUCCESS;
}

static void s_cleanup_two_aws_array(
    JNIEnv *env,
    struct aws_array_list *user_properties_holder,
    struct aws_array_list *user_properties_struct_holder) {

    (void)env;
    if (aws_array_list_is_valid(user_properties_holder)) {
        /**
         * Note that this ONLY frees the array holders for the non-struct array.
         * We want to keep the struct one in memory since it belongs to a packet or similar.
         * If both need to be freed, then we assume whomever is calling this will handle it.
         */
        for (size_t i = 0; i < aws_array_list_length(user_properties_holder); i++) {
            struct buffer_and_cursor_array_holder_struct holder;
            aws_array_list_get_at(user_properties_holder, &holder, i);
            if (aws_byte_buf_is_valid(&holder.buffer)) {
                aws_byte_buf_clean_up(&holder.buffer);
            }
        }
        aws_array_list_clean_up(user_properties_holder);
    }
    if (aws_array_list_is_valid(user_properties_struct_holder)) {
        aws_array_list_clean_up(user_properties_struct_holder);
    }
}

int aws_get_uint16_from_jobject(
    JNIEnv *env,
    jobject object,
    jfieldID object_field,
    char *object_name,
    char *field_name,
    uint16_t *result,
    bool optional,
    bool *was_value_set) {

    if (was_value_set != NULL) {
        *was_value_set = false;
    }

    jobject jlong_obj = (*env)->GetObjectField(env, object, object_field);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, field_name);
        return aws_raise_error(AWS_ERROR_INVALID_STATE);
    }
    if (jlong_obj) {
        jlong jlong_value = (*env)->CallLongMethod(env, jlong_obj, boxed_long_properties.long_value_method_id);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, field_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
        int64_t jlong_value_check = (int64_t)jlong_value;
        if (jlong_value_check < 0) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: %s is less than 0", object_name, field_name);
            return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
        } else if (jlong_value_check > UINT16_MAX) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT, "%s create_from_java: %s is more than UINT16_MAX", object_name, field_name);
            return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
        }
        *result = (uint16_t)jlong_value;

        if (was_value_set != NULL) {
            *was_value_set = true;
        }

        if (!optional) {
            return AWS_OP_SUCCESS;
        }
    }

    if (optional) {
        return AWS_OP_SUCCESS;
    } else {
        return AWS_OP_ERR;
    }
}

int aws_get_uint32_from_jobject(
    JNIEnv *env,
    jobject object,
    jfieldID object_field,
    char *object_name,
    char *field_name,
    uint32_t *result,
    bool optional,
    bool *was_value_set) {

    if (was_value_set != NULL) {
        *was_value_set = false;
    }

    jobject jlong_obj = (*env)->GetObjectField(env, object, object_field);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, field_name);
        return aws_raise_error(AWS_ERROR_INVALID_STATE);
    }
    if (jlong_obj) {
        jlong jlong_value = (*env)->CallLongMethod(env, jlong_obj, boxed_long_properties.long_value_method_id);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, field_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
        int64_t jlong_value_check = (int64_t)jlong_value;
        if (jlong_value_check < 0) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: %s is less than 0", object_name, field_name);
            return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
        } else if (jlong_value_check > UINT32_MAX) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT, "%s create_from_java: %s is more than UINT32_MAX", object_name, field_name);
            return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
        }
        *result = (uint32_t)jlong_value_check;

        if (was_value_set != NULL) {
            *was_value_set = true;
        }

        if (!optional) {
            return AWS_OP_SUCCESS;
        }
    }

    if (optional) {
        return AWS_OP_SUCCESS;
    } else {
        return AWS_OP_ERR;
    }
}

int aws_get_uint64_from_jobject(
    JNIEnv *env,
    jobject object,
    jfieldID object_field,
    char *object_name,
    char *field_name,
    uint64_t *result,
    bool optional,
    bool *was_value_set) {

    if (was_value_set != NULL) {
        *was_value_set = false;
    }

    jobject jlong_obj = (*env)->GetObjectField(env, object, object_field);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, field_name);
        return aws_raise_error(AWS_ERROR_INVALID_STATE);
    }
    if (jlong_obj) {
        jlong jlong_value = (*env)->CallLongMethod(env, jlong_obj, boxed_long_properties.long_value_method_id);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, field_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
        int64_t jlong_value_check = (int64_t)jlong_value;
        if (jlong_value_check < 0) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: %s is less than 0", object_name, field_name);
            return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
        }
        *result = (uint64_t)jlong_value_check;

        if (was_value_set != NULL) {
            *was_value_set = true;
        }

        if (!optional) {
            return AWS_OP_SUCCESS;
        }
    }

    if (optional) {
        return AWS_OP_SUCCESS;
    } else {
        return AWS_OP_ERR;
    }
}

int aws_get_string_from_jobject(
    JNIEnv *env,
    jobject object,
    jfieldID object_field,
    char *object_name,
    char *field_name,
    struct aws_byte_buf *result_buf,
    struct aws_byte_cursor *result_cursor,
    bool is_optional,
    bool *was_value_set) {

    if (was_value_set != NULL) {
        *was_value_set = false;
    }

    jstring jstring_value = (jstring)(*env)->GetObjectField(env, object, object_field);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, field_name);
        return aws_raise_error(AWS_ERROR_INVALID_STATE);
    }
    if (jstring_value) {
        // Get the data, copy it, and then release the JNI stuff
        struct aws_byte_cursor tmp_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, jstring_value);
        aws_byte_buf_init_copy_from_cursor(result_buf, aws_jni_get_allocator(), tmp_cursor);
        *result_cursor = aws_byte_cursor_from_buf(result_buf);
        aws_jni_byte_cursor_from_jstring_release(env, jstring_value, tmp_cursor);

        if (was_value_set != NULL) {
            *was_value_set = true;
        }

        if (!is_optional) {
            return AWS_OP_SUCCESS;
        }
    }
    if (is_optional) {
        return AWS_OP_SUCCESS;
    } else {
        return AWS_OP_ERR;
    }
}

int aws_get_byte_array_from_jobject(
    JNIEnv *env,
    jobject object,
    jfieldID object_field,
    char *object_name,
    char *field_name,
    struct aws_byte_buf *result_buf,
    struct aws_byte_cursor *result_cursor,
    bool optional,
    bool *was_value_set) {

    if (was_value_set != NULL) {
        *was_value_set = false;
    }

    jbyteArray jbyte_array_value = (jbyteArray)(*env)->GetObjectField(env, object, object_field);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, field_name);
        return aws_raise_error(AWS_ERROR_INVALID_STATE);
    }
    if (jbyte_array_value) {
        // Get the data, copy it, and then release the JNI stuff
        struct aws_byte_cursor tmp_cursor = aws_jni_byte_cursor_from_jbyteArray_acquire(env, jbyte_array_value);
        aws_byte_buf_init_copy_from_cursor(result_buf, aws_jni_get_allocator(), tmp_cursor);
        *result_cursor = aws_byte_cursor_from_buf(result_buf);
        aws_jni_byte_cursor_from_jbyteArray_release(env, jbyte_array_value, tmp_cursor);

        if (was_value_set != NULL) {
            *was_value_set = true;
        }

        if (!optional) {
            return AWS_OP_SUCCESS;
        }
    }
    if (optional) {
        return AWS_OP_SUCCESS;
    } else {
        return AWS_OP_ERR;
    }
}

int aws_get_boolean_from_jobject(
    JNIEnv *env,
    jobject object,
    jfieldID object_field,
    char *object_name,
    char *field_name,
    uint8_t *result_boolean_int,
    bool optional,
    bool *was_value_set) {

    if (was_value_set != NULL) {
        *was_value_set = false;
    }

    jobject jboolean_obj = (*env)->GetObjectField(env, object, object_field);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, field_name);
        return aws_raise_error(AWS_ERROR_INVALID_STATE);
    }
    if (jboolean_obj) {
        jboolean jboolean_value =
            (*env)->CallBooleanMethod(env, jboolean_obj, boxed_boolean_properties.boolean_get_value_id);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT,
                "%s create_from_java: Error getting native value from %s",
                object_name,
                field_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
        *result_boolean_int = (uint8_t)jboolean_value;
        if (was_value_set != NULL) {
            *was_value_set = true;
        }
        if (!optional) {
            return AWS_OP_SUCCESS;
        }
    }

    if (optional) {
        return AWS_OP_SUCCESS;
    } else {
        return AWS_OP_ERR;
    }
}

int aws_get_enum_from_jobject(
    JNIEnv *env,
    jobject object,
    jmethodID object_enum_field,
    char *object_name,
    char *enum_name,
    jmethodID enum_value_field,
    uint32_t *enum_value_destination,
    bool optional,
    bool *was_value_set) {

    if (was_value_set != NULL) {
        *was_value_set = false;
    }

    if (enum_value_destination == NULL) {
        AWS_LOGF_ERROR(
            AWS_LS_MQTT5_CLIENT,
            "%s create_from_java: Error getting %s due to null destination",
            object_name,
            enum_name);
        return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
    }

    jobject jni_retain_handling_type = (*env)->CallObjectMethod(env, object, object_enum_field);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting %s", object_name, enum_name);
        return aws_raise_error(AWS_ERROR_INVALID_STATE);
    }
    if (jni_retain_handling_type) {
        jint enum_value = (*env)->CallIntMethod(env, jni_retain_handling_type, enum_value_field);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting native value from %s", object_name, enum_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
        if (enum_value < 0) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT,
                "%s create_from_java: Native value from %s is less than 0",
                object_name,
                enum_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        } else if ((int32_t)enum_value > UINT16_MAX) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT,
                "%s create_from_java: Native value from %s is more than UINT16_MAX",
                object_name,
                enum_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
        *enum_value_destination = (int32_t)enum_value;
        if (was_value_set != NULL) {
            *was_value_set = true;
        }
        if (!optional) {
            return AWS_OP_SUCCESS;
        }
    }
    if (optional) {
        return AWS_OP_SUCCESS;
    } else {
        return AWS_OP_ERR;
    }
}

static int s_get_user_properties_from_packet_optional(
    JNIEnv *env,
    jobject packet,
    jfieldID packet_field,
    char *packet_name,
    size_t *packet_user_property_count,
    /* Contains buffer_and_cursor_array_holder_struct pointers */
    struct aws_array_list *jni_user_properties_holder,
    /* Contains aws_mqtt5_user_property pointers */
    struct aws_array_list *jni_user_properties_struct_holder,
    const struct aws_mqtt5_user_property **packet_properties) {

    struct aws_allocator *allocator = aws_jni_get_allocator();

    jobject jni_list = (*env)->GetObjectField(env, packet, packet_field);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting user properties list", packet_name);
        return aws_raise_error(AWS_ERROR_INVALID_STATE);
    }
    if (jni_list) {
        jint jni_user_properties_size = (*env)->CallIntMethod(env, jni_list, boxed_list_properties.list_size_id);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting user properties list size", packet_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
        *packet_user_property_count = (size_t)jni_user_properties_size;
        if (AWS_OP_SUCCESS != s_allocate_user_properties_array_holders(
                                  allocator,
                                  jni_user_properties_holder,
                                  jni_user_properties_struct_holder,
                                  *packet_user_property_count)) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT, "%s create_from_java: Could not create user properties array", packet_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
        int populate_result = s_populate_user_properties(
            env,
            jni_list,
            *packet_user_property_count,
            packet_properties,
            jni_user_properties_holder,
            jni_user_properties_struct_holder);
        if (populate_result != AWS_OP_SUCCESS) {
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
    }
    return AWS_OP_SUCCESS;
}

static int s_get_qos_from_packet(
    JNIEnv *env,
    jobject packet,
    jmethodID packet_field,
    char *packet_name,
    enum aws_mqtt5_qos *packet_qos,
    bool optional,
    bool *was_value_set) {

    if (was_value_set != NULL) {
        *was_value_set = false;
    }

    jobject jni_qos = (*env)->CallObjectMethod(env, packet, packet_field);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting QoS", packet_name);
        return AWS_OP_ERR;
    }
    if (jni_qos) {
        jint jni_qos_value = (*env)->CallIntMethod(env, jni_qos, mqtt5_packet_qos_properties.qos_get_value_id);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT, "%s create_from_java: Error getting native value from QoS", packet_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
        *packet_qos = (enum aws_mqtt5_qos)jni_qos_value;
        if (was_value_set != NULL) {
            *was_value_set = true;
        }
        if (!optional) {
            return AWS_OP_SUCCESS;
        }
    } else {
        if (!optional) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "%s create_from_java: QoS not found", packet_name);
            return aws_raise_error(AWS_ERROR_INVALID_STATE);
        }
    }
    if (optional) {
        return AWS_OP_SUCCESS;
    } else {
        return AWS_OP_ERR;
    }
}

static char s_connect_packet_string[] = "ConnectPacket";
static char s_disconnect_packet_string[] = "DisconnectPacket";
static char s_publish_packet_string[] = "PublishPacket";
static char s_subscribe_packet_string[] = "SubscribePacket";
static char s_unsubscribe_packet_string[] = "UnsubscribePacket";

/*******************************************************************************
 * CONNECT PACKET FUNCTIONS
 ******************************************************************************/

void aws_mqtt5_packet_connect_view_java_destroy(
    JNIEnv *env,
    struct aws_allocator *allocator,
    struct aws_mqtt5_packet_connect_view_java_jni *java_packet) {
    if (!java_packet) {
        return;
    }
    AWS_LOGF_DEBUG(AWS_LS_MQTT5_CLIENT, "id=%p: Destroying ConnectPacket", (void *)java_packet);

    if (aws_byte_buf_is_valid(&java_packet->client_id_buf)) {
        aws_byte_buf_clean_up(&java_packet->client_id_buf);
    }
    if (aws_byte_buf_is_valid(&java_packet->username_buf)) {
        aws_byte_buf_clean_up(&java_packet->username_buf);
    }
    if (aws_byte_buf_is_valid(&java_packet->password_buf)) {
        aws_byte_buf_clean_up(&java_packet->password_buf);
    }
    if (java_packet->will_publish_packet) {
        aws_mqtt5_packet_publish_view_java_destroy(env, allocator, java_packet->will_publish_packet);
    }

    s_cleanup_two_aws_array(
        env, &java_packet->jni_user_properties_holder, &java_packet->jni_user_properties_struct_holder);
    aws_mem_release(allocator, java_packet);
}

/**
 * Creates a JNI connack packet from the given Java connack packet and returns it. It creates a new packet but it does
 * NOT free it. You will need to call aws_mqtt5_packet_connect_view_java_destroy when you are done with it.
 */
struct aws_mqtt5_packet_connect_view_java_jni *aws_mqtt5_packet_connect_view_create_from_java(
    JNIEnv *env,
    struct aws_allocator *allocator,
    jobject java_connect_packet) {
    struct aws_mqtt5_packet_connect_view_java_jni *java_packet =
        aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_packet_connect_view_java_jni));
    if (java_packet == NULL) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "ConnectPacket create_from_java: Creating new ConnectPacket failed");
        return NULL;
    }

    /* Needed to track if optionals are set or not */
    bool was_value_set = false;

    if (aws_get_uint16_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_keep_alive_interval_seconds_field_id,
            s_connect_packet_string,
            "keep alive interval seconds",
            &java_packet->keep_alive_interval_seconds,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.keep_alive_interval_seconds = java_packet->keep_alive_interval_seconds;
    }

    if (aws_get_string_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_client_id_field_id,
            s_connect_packet_string,
            "client ID",
            &java_packet->client_id_buf,
            &java_packet->client_id_cursor,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.client_id = java_packet->client_id_cursor;
    }

    if (aws_get_string_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_username_field_id,
            s_connect_packet_string,
            "username",
            &java_packet->username_buf,
            &java_packet->username_cursor,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.username = &java_packet->username_cursor;
    }

    if (aws_get_byte_array_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_password_field_id,
            s_connect_packet_string,
            "password",
            &java_packet->password_buf,
            &java_packet->password_cursor,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.password = &java_packet->password_cursor;
    }

    if (aws_get_uint32_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_session_expiry_interval_seconds_field_id,
            s_connect_packet_string,
            "session expiry interval seconds",
            &java_packet->session_expiry_interval_seconds,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.session_expiry_interval_seconds = &java_packet->session_expiry_interval_seconds;
    }

    if (aws_get_boolean_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_request_response_information_field_id,
            s_connect_packet_string,
            "request response information",
            &java_packet->request_response_information,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.request_response_information = &java_packet->request_response_information;
    }

    if (aws_get_boolean_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_request_problem_information_field_id,
            s_connect_packet_string,
            "request problem information",
            &java_packet->request_problem_information,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.request_problem_information = &java_packet->request_problem_information;
    }

    if (aws_get_uint16_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_receive_maximum_field_id,
            s_connect_packet_string,
            "receive maximum",
            &java_packet->receive_maximum,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.receive_maximum = &java_packet->receive_maximum;
    }

    if (aws_get_uint32_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_maximum_packet_size_bytes_field_id,
            s_connect_packet_string,
            "maximum packet size",
            &java_packet->maximum_packet_size_bytes,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.maximum_packet_size_bytes = &java_packet->maximum_packet_size_bytes;
    }

    if (aws_get_uint32_from_jobject(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_will_delay_interval_seconds_field_id,
            s_connect_packet_string,
            "will delay interval",
            &java_packet->will_delay_interval_seconds,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.will_delay_interval_seconds = &java_packet->will_delay_interval_seconds;
    }

    jobject jni_will_packet =
        (*env)->GetObjectField(env, java_connect_packet, mqtt5_connect_packet_properties.connect_will_field_id);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "ConnectPacket create_from_java: Error getting will packet");
        goto on_error;
    }
    if (jni_will_packet) {
        java_packet->will_publish_packet =
            aws_mqtt5_packet_publish_view_create_from_java(env, allocator, jni_will_packet);
        if (java_packet->will_publish_packet == NULL) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "ConnectPacket create_from_java: Error getting will packet");
            goto on_error;
        }
        java_packet->packet.will = &java_packet->will_publish_packet->packet;
    }

    if (s_get_user_properties_from_packet_optional(
            env,
            java_connect_packet,
            mqtt5_connect_packet_properties.connect_user_properties_field_id,
            s_connect_packet_string,
            &java_packet->packet.user_property_count,
            &java_packet->jni_user_properties_holder,
            &java_packet->jni_user_properties_struct_holder,
            &java_packet->packet.user_properties) == AWS_OP_ERR) {
        goto on_error;
    }

    return java_packet;

on_error:

    /* Clean up */
    aws_mqtt5_packet_connect_view_java_destroy(env, allocator, java_packet);
    return NULL;
}

struct aws_mqtt5_packet_connect_view *aws_mqtt5_packet_connect_view_get_packet(
    struct aws_mqtt5_packet_connect_view_java_jni *java_packet) {
    if (java_packet) {
        return &java_packet->packet;
    } else {
        return NULL;
    }
}

/*******************************************************************************
 * PACKET DISCONNECT FUNCTIONS
 ******************************************************************************/

void aws_mqtt5_packet_disconnect_view_java_destroy(
    JNIEnv *env,
    struct aws_allocator *allocator,
    struct aws_mqtt5_packet_disconnect_view_java_jni *java_packet) {
    if (!java_packet) {
        return;
    }
    AWS_LOGF_DEBUG(AWS_LS_MQTT5_CLIENT, "id=%p: Destroying DisconnectPacket", (void *)java_packet);

    if (aws_byte_buf_is_valid(&java_packet->reason_string_buf)) {
        aws_byte_buf_clean_up(&java_packet->reason_string_buf);
    }
    if (aws_byte_buf_is_valid(&java_packet->server_reference_buf)) {
        aws_byte_buf_clean_up(&java_packet->server_reference_buf);
    }

    s_cleanup_two_aws_array(
        env, &java_packet->jni_user_properties_holder, &java_packet->jni_user_properties_struct_holder);
    aws_mem_release(allocator, java_packet);
}

struct aws_mqtt5_packet_disconnect_view_java_jni *aws_mqtt5_packet_disconnect_view_create_from_java(
    JNIEnv *env,
    struct aws_allocator *allocator,
    jobject java_disconnect_packet) {

    struct aws_mqtt5_packet_disconnect_view_java_jni *java_packet =
        aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_packet_disconnect_view_java_jni));
    if (java_packet == NULL) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "DisconnectPacket create_from_java: Creating new DisconnectPacket failed");
        return NULL;
    }

    /* Needed to track if optionals are set or not */
    bool was_value_set = false;

    uint32_t reason_code_enum;
    if (aws_get_enum_from_jobject(
            env,
            java_disconnect_packet,
            mqtt5_disconnect_packet_properties.disconnect_get_reason_code_id,
            s_disconnect_packet_string,
            "reason code",
            mqtt5_disconnect_reason_code_properties.code_get_value_id,
            &reason_code_enum,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.reason_code = (enum aws_mqtt5_disconnect_reason_code)reason_code_enum;
    }

    if (aws_get_uint32_from_jobject(
            env,
            java_disconnect_packet,
            mqtt5_disconnect_packet_properties.disconnect_session_expiry_interval_seconds_field_id,
            s_disconnect_packet_string,
            "session expiry interval seconds",
            &java_packet->session_expiry_interval_seconds,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.session_expiry_interval_seconds = &java_packet->session_expiry_interval_seconds;
    }

    if (aws_get_string_from_jobject(
            env,
            java_disconnect_packet,
            mqtt5_disconnect_packet_properties.disconnect_reason_string_field_id,
            s_disconnect_packet_string,
            "reason string",
            &java_packet->reason_string_buf,
            &java_packet->reason_string_cursor,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.reason_string = &java_packet->reason_string_cursor;
    }

    if (aws_get_string_from_jobject(
            env,
            java_disconnect_packet,
            mqtt5_disconnect_packet_properties.disconnect_session_server_reference_field_id,
            s_disconnect_packet_string,
            "server reference",
            &java_packet->server_reference_buf,
            &java_packet->server_reference_cursor,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.server_reference = &java_packet->server_reference_cursor;
    }

    if (s_get_user_properties_from_packet_optional(
            env,
            java_disconnect_packet,
            mqtt5_disconnect_packet_properties.disconnect_user_properties_field_id,
            s_disconnect_packet_string,
            &java_packet->packet.user_property_count,
            &java_packet->jni_user_properties_holder,
            &java_packet->jni_user_properties_struct_holder,
            &java_packet->packet.user_properties) == AWS_OP_ERR) {
        goto on_error;
    }

    return java_packet;

on_error:

    /* Clean up */
    aws_mqtt5_packet_disconnect_view_java_destroy(env, allocator, java_packet);
    return NULL;
}

struct aws_mqtt5_packet_disconnect_view *aws_mqtt5_packet_disconnect_view_get_packet(
    struct aws_mqtt5_packet_disconnect_view_java_jni *java_packet) {
    if (java_packet) {
        return &java_packet->packet;
    } else {
        return NULL;
    }
}

/*******************************************************************************
 * PUBLISH PACKET FUNCTIONS
 ******************************************************************************/

void aws_mqtt5_packet_publish_view_java_destroy(
    JNIEnv *env,
    struct aws_allocator *allocator,
    struct aws_mqtt5_packet_publish_view_java_jni *java_packet) {
    if (!java_packet) {
        return;
    }
    AWS_LOGF_DEBUG(AWS_LS_MQTT5_CLIENT, "id=%p: Destroying PublishPacket", (void *)java_packet);

    if (aws_byte_buf_is_valid(&java_packet->payload_buf)) {
        aws_byte_buf_clean_up(&java_packet->payload_buf);
    }
    if (aws_byte_buf_is_valid(&java_packet->topic_buf)) {
        aws_byte_buf_clean_up(&java_packet->topic_buf);
    }
    if (aws_byte_buf_is_valid(&java_packet->response_topic_buf)) {
        aws_byte_buf_clean_up(&java_packet->response_topic_buf);
    }
    if (aws_byte_buf_is_valid(&java_packet->correlation_data_buf)) {
        aws_byte_buf_clean_up(&java_packet->correlation_data_buf);
    }
    if (aws_byte_buf_is_valid(&java_packet->content_type_buf)) {
        aws_byte_buf_clean_up(&java_packet->content_type_buf);
    }

    s_cleanup_two_aws_array(
        env, &java_packet->jni_user_properties_holder, &java_packet->jni_user_properties_struct_holder);
    aws_mem_release(allocator, java_packet);
}

struct aws_mqtt5_packet_publish_view_java_jni *aws_mqtt5_packet_publish_view_create_from_java(
    JNIEnv *env,
    struct aws_allocator *allocator,
    jobject java_publish_packet) {

    struct aws_mqtt5_packet_publish_view_java_jni *java_packet =
        aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_packet_publish_view_java_jni));
    if (java_packet == NULL) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "PublishPacket create_from_java: Creating new PublishPacket failed");
        return NULL;
    }

    /* Needed to track if optionals are set or not */
    bool was_value_set = false;

    if (aws_get_byte_array_from_jobject(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_payload_field_id,
            s_publish_packet_string,
            "payload",
            &java_packet->correlation_data_buf,
            &java_packet->payload_cursor,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.payload = java_packet->payload_cursor;
    }

    if (s_get_qos_from_packet(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_get_qos_id,
            s_publish_packet_string,
            &java_packet->packet.qos,
            false,
            NULL) == AWS_OP_ERR) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "PublishPacket create_from_java: QOS not found");
        goto on_error;
    }

    uint8_t packet_retain;
    if (aws_get_boolean_from_jobject(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_retain_field_id,
            s_publish_packet_string,
            "retain",
            &packet_retain,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.retain = (bool)packet_retain;
    }

    if (aws_get_string_from_jobject(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_topic_field_id,
            s_publish_packet_string,
            "topic",
            &java_packet->topic_buf,
            &java_packet->topic_cursor,
            false,
            NULL) == AWS_OP_ERR) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "PublishPacket create_from_java: No topic found");
        goto on_error;
    }
    java_packet->packet.topic = java_packet->topic_cursor;

    uint32_t format_enum;
    if (aws_get_enum_from_jobject(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_get_payload_format_id,
            s_publish_packet_string,
            "payload format",
            mqtt5_payload_format_indicator_properties.format_get_value_id,
            &format_enum,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->payload_format = (enum aws_mqtt5_payload_format_indicator)format_enum;
        java_packet->packet.payload_format = &java_packet->payload_format;
    }

    if (aws_get_uint32_from_jobject(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_message_expiry_interval_seconds_field_id,
            s_publish_packet_string,
            "message expiry interval seconds",
            &java_packet->message_expiry_interval_seconds,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.message_expiry_interval_seconds = &java_packet->message_expiry_interval_seconds;
    }

    if (aws_get_uint16_from_jobject(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_topic_alias_field_id,
            s_publish_packet_string,
            "topic alias",
            &java_packet->topic_alias,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.topic_alias = &java_packet->topic_alias;
    }

    if (aws_get_string_from_jobject(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_response_topic_field_id,
            s_publish_packet_string,
            "response topic",
            &java_packet->response_topic_buf,
            &java_packet->response_topic_cursor,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.response_topic = &java_packet->response_topic_cursor;
    }

    if (aws_get_byte_array_from_jobject(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_correlation_data_field_id,
            s_publish_packet_string,
            "correlation data",
            &java_packet->correlation_data_buf,
            &java_packet->correlation_data_cursor,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.correlation_data = &java_packet->correlation_data_cursor;
    }

    if (aws_get_string_from_jobject(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_content_type_field_id,
            s_publish_packet_string,
            "content type",
            &java_packet->content_type_buf,
            &java_packet->content_type_cursor,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.content_type = &java_packet->content_type_cursor;
    }

    if (s_get_user_properties_from_packet_optional(
            env,
            java_publish_packet,
            mqtt5_publish_packet_properties.publish_user_properties_field_id,
            s_publish_packet_string,
            &java_packet->packet.user_property_count,
            &java_packet->jni_user_properties_holder,
            &java_packet->jni_user_properties_struct_holder,
            &java_packet->packet.user_properties) == AWS_OP_ERR) {
        goto on_error;
    }

    return java_packet;

on_error:

    /* Clean up */
    aws_mqtt5_packet_publish_view_java_destroy(env, allocator, java_packet);
    return NULL;
}

struct aws_mqtt5_packet_publish_view *aws_mqtt5_packet_publish_view_get_packet(
    struct aws_mqtt5_packet_publish_view_java_jni *java_packet) {
    if (java_packet) {
        return &java_packet->packet;
    } else {
        return NULL;
    }
}

/*******************************************************************************
 * SUBSCRIBE PACKET FUNCTIONS
 ******************************************************************************/

void aws_mqtt5_packet_subscribe_view_java_destroy(
    JNIEnv *env,
    struct aws_allocator *allocator,
    struct aws_mqtt5_packet_subscribe_view_java_jni *java_packet) {
    if (!java_packet) {
        return;
    }
    AWS_LOGF_DEBUG(AWS_LS_MQTT5_CLIENT, "id=%p: Destroying SubscribePacket", (void *)java_packet);

    s_cleanup_two_aws_array(
        env, &java_packet->jni_user_properties_holder, &java_packet->jni_user_properties_struct_holder);
    s_cleanup_two_aws_array(env, &java_packet->jni_subscription_topic_filters, &java_packet->topic_filters);
    aws_mem_release(allocator, java_packet);
}

struct aws_mqtt5_packet_subscribe_view_java_jni *aws_mqtt5_packet_subscribe_view_create_from_java(
    JNIEnv *env,
    struct aws_allocator *allocator,
    jobject java_subscribe_packet) {

    jobject jni_subscriptions = (*env)->GetObjectField(
        env, java_subscribe_packet, mqtt5_subscribe_packet_properties.subscribe_subscriptions_field_id);
    if (aws_jni_check_and_clear_exception(env)) {
        return NULL;
    }
    if (!jni_subscriptions) {
        AWS_LOGF_ERROR(
            AWS_LS_MQTT5_CLIENT,
            "SubscribePacket create_from_java: Creating new SubscribePacket failed due to no subscriptions!");
        return NULL;
    }
    jint jni_subscriptions_size = (*env)->CallIntMethod(env, jni_subscriptions, boxed_list_properties.list_size_id);
    if (aws_jni_check_and_clear_exception(env)) {
        return NULL;
    }
    size_t subscriptions_filter_size = (size_t)jni_subscriptions_size;

    if (subscriptions_filter_size <= 0) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "SubscribePacket create_from_java: subscriptions count is 0");
        return NULL;
    }

    struct aws_mqtt5_packet_subscribe_view_java_jni *java_packet =
        aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_packet_subscribe_view_java_jni));
    if (java_packet == NULL) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "SubscribePacket create_from_java: Creating new SubscribePacket failed");
        return NULL;
    }
    int array_init = aws_array_list_init_dynamic(
        &java_packet->topic_filters, allocator, subscriptions_filter_size, sizeof(struct aws_mqtt5_subscription_view));
    if (array_init != AWS_OP_SUCCESS) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "SubscribePacket create_from_java: Creating new SubscribePacket failed");
        goto on_error;
    }
    int jni_array_init = aws_array_list_init_dynamic(
        &java_packet->jni_subscription_topic_filters,
        allocator,
        subscriptions_filter_size,
        sizeof(struct buffer_and_cursor_array_holder_struct));
    if (jni_array_init != AWS_OP_SUCCESS) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "SubscribePacket create_from_java: Creating new SubscribePacket failed");
        goto on_error;
    }

    /* Needed to track if optionals are set or not */
    bool was_value_set = false;

    if (aws_get_uint32_from_jobject(
            env,
            java_subscribe_packet,
            mqtt5_subscribe_packet_properties.subscribe_subscription_identifier_field_id,
            s_subscribe_packet_string,
            "subscription identifier",
            &java_packet->subscription_identifier,
            true,
            &was_value_set) == AWS_OP_ERR) {
        goto on_error;
    }
    if (was_value_set) {
        java_packet->packet.subscription_identifier = &java_packet->subscription_identifier;
    }

    java_packet->packet.subscription_count = subscriptions_filter_size;
    for (size_t i = 0; i < subscriptions_filter_size; i++) {
        /* Populate */
        struct aws_mqtt5_subscription_view subscription_view;
        struct buffer_and_cursor_array_holder_struct holder;

        jobject jni_packet_subscribe_subscription =
            (*env)->CallObjectMethod(env, jni_subscriptions, boxed_list_properties.list_get_id, (jint)i);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "SubscribePacket create_from_java: Error getting topic filters");
            goto on_error;
        }

        jstring jni_topic_filter = (jstring)(*env)->CallObjectMethod(
            env, jni_packet_subscribe_subscription, mqtt5_subscription_properties.subscribe_get_topic_filter_id);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT, "SubscribePacket create_from_java: Error getting subscription topic filter");
            goto on_error;
        }
        if (jni_topic_filter) {
            // Get a temporary cursor from JNI, copy it, and then destroy the JNI version, leaving the byte_buffer copy.
            // This gets around JNI stuff going out of scope.
            struct aws_byte_cursor tmp_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, jni_topic_filter);
            aws_byte_buf_init_copy_from_cursor(&holder.buffer, aws_jni_get_allocator(), tmp_cursor);
            holder.cursor = aws_byte_cursor_from_buf(&holder.buffer);
            aws_jni_byte_cursor_from_jstring_release(env, jni_topic_filter, tmp_cursor);
            subscription_view.topic_filter = holder.cursor;
            jni_topic_filter = NULL;
        } else {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT, "SubscribePacket create_from_java: subscription topic filter is required");
            goto on_error;
        }

        if (s_get_qos_from_packet(
                env,
                jni_packet_subscribe_subscription,
                mqtt5_subscription_properties.subscribe_get_qos_id,
                s_subscribe_packet_string,
                &subscription_view.qos,
                false,
                NULL) == AWS_OP_ERR) {
            AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "SubscribePacket create_from_java: subscription QoS is required");
            goto on_error;
        }

        uint8_t subscription_no_local;
        if (aws_get_boolean_from_jobject(
                env,
                jni_packet_subscribe_subscription,
                mqtt5_subscription_properties.subscribe_no_local_field_id,
                s_subscribe_packet_string,
                "no local",
                &subscription_no_local,
                true,
                &was_value_set) != AWS_OP_SUCCESS) {
            goto on_error;
        }
        if (was_value_set) {
            subscription_view.no_local = (bool)subscription_no_local;
        }

        uint8_t retain_as_published;
        if (aws_get_boolean_from_jobject(
                env,
                jni_packet_subscribe_subscription,
                mqtt5_subscription_properties.subscribe_retain_as_published_field_id,
                s_subscribe_packet_string,
                "retain as published",
                &retain_as_published,
                true,
                &was_value_set) != AWS_OP_SUCCESS) {
            goto on_error;
        }
        if (was_value_set) {
            subscription_view.retain_as_published = (bool)retain_as_published;
        }

        uint32_t retain_enum;
        if (aws_get_enum_from_jobject(
                env,
                jni_packet_subscribe_subscription,
                mqtt5_subscription_properties.subscribe_get_retain_handling_type_id,
                s_subscribe_packet_string,
                "subscription retain handling type",
                mqtt5_retain_handling_type_properties.retain_get_value_id,
                &retain_enum,
                true,
                &was_value_set) == AWS_OP_ERR) {
            goto on_error;
        }
        if (was_value_set) {
            subscription_view.retain_handling_type = (enum aws_mqtt5_retain_handling_type)retain_enum;
        }

        aws_array_list_push_back(&java_packet->topic_filters, (void *)&subscription_view);
        aws_array_list_push_back(&java_packet->jni_subscription_topic_filters, (void *)&holder);
    }
    java_packet->packet.subscriptions = (struct aws_mqtt5_subscription_view *)java_packet->topic_filters.data;

    if (s_get_user_properties_from_packet_optional(
            env,
            java_subscribe_packet,
            mqtt5_subscribe_packet_properties.subscribe_user_properties_field_id,
            s_subscribe_packet_string,
            &java_packet->packet.user_property_count,
            &java_packet->jni_user_properties_holder,
            &java_packet->jni_user_properties_struct_holder,
            &java_packet->packet.user_properties) == AWS_OP_ERR) {
        goto on_error;
    }

    return java_packet;

on_error:

    /* Clean up */
    aws_mqtt5_packet_subscribe_view_java_destroy(env, allocator, java_packet);
    return NULL;
}

struct aws_mqtt5_packet_subscribe_view *aws_mqtt5_packet_subscribe_view_get_packet(
    struct aws_mqtt5_packet_subscribe_view_java_jni *java_packet) {
    if (java_packet) {
        return &java_packet->packet;
    } else {
        return NULL;
    }
}

/*******************************************************************************
 * UNSUBSCRIBE PACKET FUNCTIONS
 ******************************************************************************/

void aws_mqtt5_packet_unsubscribe_view_java_destroy(
    JNIEnv *env,
    struct aws_allocator *allocator,
    struct aws_mqtt5_packet_unsubscribe_view_java_jni *java_packet) {
    if (!java_packet) {
        return;
    }
    AWS_LOGF_DEBUG(AWS_LS_MQTT5_CLIENT, "id=%p: Destroying UnsubscribePacket", (void *)java_packet);

    s_cleanup_two_aws_array(
        env, &java_packet->jni_user_properties_holder, &java_packet->jni_user_properties_struct_holder);
    s_cleanup_two_aws_array(env, &java_packet->jni_topic_filters, &java_packet->topic_filters);
    aws_mem_release(allocator, java_packet);
}

struct aws_mqtt5_packet_unsubscribe_view_java_jni *aws_mqtt5_packet_unsubscribe_view_create_from_java(
    JNIEnv *env,
    struct aws_allocator *allocator,
    jobject java_unsubscribe_packet) {

    jobject jni_topic_filters = (*env)->GetObjectField(
        env, java_unsubscribe_packet, mqtt5_unsubscribe_packet_properties.unsubscribe_subscriptions_field_id);
    if (aws_jni_check_and_clear_exception(env)) {
        return NULL;
    }
    if (!jni_topic_filters) {
        AWS_LOGF_ERROR(
            AWS_LS_MQTT5_CLIENT,
            "UnsubscribePacket create_from_java: Creating new UnsubscribePacket failed due to no topic filters");
        return NULL;
    }

    size_t topic_filter_size = 0;
    jint jni_topic_filter_size = (*env)->CallIntMethod(env, jni_topic_filters, boxed_list_properties.list_size_id);
    if (aws_jni_check_and_clear_exception(env)) {
        AWS_LOGF_ERROR(
            AWS_LS_MQTT5_CLIENT,
            "UnsubscribePacket create_from_java: Created new UnsubscribePacket failed due to no topic filters");
        return NULL;
    }
    int32_t jni_topic_filter_size_check = (int32_t)jni_topic_filter_size;
    if (jni_topic_filter_size_check < 0) {
        AWS_LOGF_ERROR(AWS_LS_MQTT5_CLIENT, "UnsubscribePacket create_from_java: No topic filters found");
        return NULL;
    } else {
        topic_filter_size = (size_t)jni_topic_filter_size;
    }

    struct aws_mqtt5_packet_unsubscribe_view_java_jni *java_packet =
        aws_mem_calloc(allocator, 1, sizeof(struct aws_mqtt5_packet_unsubscribe_view_java_jni));
    if (java_packet == NULL) {
        AWS_LOGF_ERROR(
            AWS_LS_MQTT5_CLIENT, "UnsubscribePacket create_from_java: Creating new UnsubscribePacket failed");
        return NULL;
    }
    int array_init = aws_array_list_init_dynamic(
        &java_packet->topic_filters, allocator, topic_filter_size, sizeof(struct aws_byte_cursor));
    if (array_init != AWS_OP_SUCCESS) {
        AWS_LOGF_ERROR(
            AWS_LS_MQTT5_CLIENT, "UnsubscribePacket create_from_java: Creating new UnsubscribePacket failed");
        goto on_error;
    }
    int jni_array_init = aws_array_list_init_dynamic(
        &java_packet->jni_topic_filters,
        allocator,
        topic_filter_size,
        sizeof(struct buffer_and_cursor_array_holder_struct));
    if (jni_array_init != AWS_OP_SUCCESS) {
        AWS_LOGF_ERROR(
            AWS_LS_MQTT5_CLIENT, "UnsubscribePacket create_from_java: Creating new UnsubscribePacket failed");
        goto on_error;
    }

    java_packet->packet.topic_filter_count = topic_filter_size;

    for (size_t i = 0; i < topic_filter_size; i++) {
        /* Populate */
        struct buffer_and_cursor_array_holder_struct holder;

        jstring jni_topic_filter =
            (jstring)(*env)->CallObjectMethod(env, jni_topic_filters, boxed_list_properties.list_get_id, (jint)i);
        if (aws_jni_check_and_clear_exception(env)) {
            AWS_LOGF_ERROR(
                AWS_LS_MQTT5_CLIENT, "UnsubscribePacket create_from_java: Error getting subscription topic filter");
            goto on_error;
        }
        if (jni_topic_filter) {
            // Get a temporary cursor from JNI, copy it, and then destroy the JNI version, leaving the byte_buffer copy.
            // This gets around JNI stuff going out of scope.
            struct aws_byte_cursor tmp_cursor = aws_jni_byte_cursor_from_jstring_acquire(env, jni_topic_filter);
            aws_byte_buf_init_copy_from_cursor(&holder.buffer, aws_jni_get_allocator(), tmp_cursor);
            holder.cursor = aws_byte_cursor_from_buf(&holder.buffer);
            aws_jni_byte_cursor_from_jstring_release(env, jni_topic_filter, tmp_cursor);
            jni_topic_filter = NULL;
        }
        aws_array_list_push_back(&java_packet->topic_filters, (void *)&holder.cursor);
        aws_array_list_push_back(&java_packet->jni_topic_filters, (void *)&holder);
    }
    java_packet->packet.topic_filters = (struct aws_byte_cursor *)java_packet->topic_filters.data;

    if (s_get_user_properties_from_packet_optional(
            env,
            java_unsubscribe_packet,
            mqtt5_unsubscribe_packet_properties.unsubscribe_user_properties_field_id,
            s_unsubscribe_packet_string,
            &java_packet->packet.user_property_count,
            &java_packet->jni_user_properties_holder,
            &java_packet->jni_user_properties_struct_holder,
            &java_packet->packet.user_properties) == AWS_OP_ERR) {
        goto on_error;
    }

    return java_packet;

on_error:

    /* Clean up */
    aws_mqtt5_packet_unsubscribe_view_java_destroy(env, allocator, java_packet);
    return NULL;
}

struct aws_mqtt5_packet_unsubscribe_view *aws_mqtt5_packet_unsubscribe_view_get_packet(
    struct aws_mqtt5_packet_unsubscribe_view_java_jni *java_packet) {
    if (java_packet) {
        return &java_packet->packet;
    } else {
        return NULL;
    }
}

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