#ifndef AWS_S3_CLIENT_H
#define AWS_S3_CLIENT_H

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

#include <aws/auth/signing_config.h>
#include <aws/common/ref_count.h>
#include <aws/io/retry_strategy.h>
#include <aws/s3/s3.h>
#include <aws/s3/s3_buffer_pool.h>

AWS_PUSH_SANE_WARNING_LEVEL

struct aws_allocator;

struct aws_http_stream;
struct aws_http_message;
struct aws_http_headers;
struct aws_tls_connection_options;
struct aws_input_stream;
struct aws_hash_table;

struct aws_s3_client;
struct aws_s3_request;
struct aws_s3_meta_request;
struct aws_s3_meta_request_result;
struct aws_s3_meta_request_resume_token;
struct aws_uri;
struct aws_string;

struct aws_s3_request_metrics;
struct aws_s3express_credentials_provider;
struct aws_credentials_properties_s3express;

/**
 * A Meta Request represents a group of generated requests that are being done on behalf of the
 * original request. For example, one large GetObject request can be transformed into a series
 * of ranged GetObject requests that are executed in parallel to improve throughput.
 *
 * The aws_s3_meta_request_type is a hint of transformation to be applied.
 */
enum aws_s3_meta_request_type {

    /**
     * The Default meta request type sends any request to S3 as-is (with no transformation). For example,
     * it can be used to pass a CreateBucket request.
     */
    AWS_S3_META_REQUEST_TYPE_DEFAULT,

    /**
     * The GetObject request will be split into a series of ranged GetObject requests that are
     * executed in parallel to improve throughput, when possible.
     */
    AWS_S3_META_REQUEST_TYPE_GET_OBJECT,

    /**
     * The PutObject request will be split into MultiPart uploads that are executed in parallel
     * to improve throughput, when possible.
     * Note: put object supports both known and unknown body length. The client
     * relies on Content-Length header to determine length of the body.
     * Request with unknown content length are always sent using multipart
     * upload regardless of final number of parts and do have the following limitations:
     * - multipart threshold is ignored and all request are made through mpu,
     *   even if they only need one part
     * - pause/resume is not supported
     * - meta request will throw error if checksum header is provider (due to
     *   general limitation of checksum not being usable if meta request is
     *   getting split)
     */
    AWS_S3_META_REQUEST_TYPE_PUT_OBJECT,

    /**
     * The CopyObject meta request performs a multi-part copy
     * using multiple S3 UploadPartCopy requests in parallel, or bypasses
     * a CopyObject request to S3 if the object size is not large enough for
     * a multipart upload.
     * Note: copy support is still in development and has following limitations:
     * 1. host header must use virtual host addressing style (path style is not
     *   supported) and both source and dest buckets must have dns compliant name
     * 2. only {bucket}/{key} format is supported for source and passing arn as
     *   source will not work
     * 3. source bucket is assumed to be in the same region as dest
     * 4. source bucket and dest bucket must both be either directory buckets or regular buckets.
     *
     * Provide the `meta_request_options.copy_source_uri` to bypass limitation 1 & 2.
     */
    AWS_S3_META_REQUEST_TYPE_COPY_OBJECT,

    AWS_S3_META_REQUEST_TYPE_MAX,
};

/**
 * The type of a single S3 HTTP request. Used by metrics.
 * A meta-request can make multiple S3 HTTP requests under the hood.
 *
 * For example, AWS_S3_META_REQUEST_TYPE_PUT_OBJECT for a large file will
 * do multipart upload, resulting in 3+ HTTP requests:
 * AWS_S3_REQUEST_TYPE_CREATE_MULTIPART_UPLOAD, one or more AWS_S3_REQUEST_TYPE_UPLOAD_PART,
 * and finally AWS_S3_REQUEST_TYPE_COMPLETE_MULTIPART_UPLOAD.
 *
 * aws_s3_request_type_operation_name() returns the S3 operation name
 * for types that map (e.g. AWS_S3_REQUEST_TYPE_HEAD_OBJECT -> "HeadObject"),
 * or empty string for types that don't map (e.g. AWS_S3_REQUEST_TYPE_UNKNOWN -> "").
 */
enum aws_s3_request_type {
    /* The actual type of the single S3 HTTP request is unknown */
    AWS_S3_REQUEST_TYPE_UNKNOWN,

    /* S3 APIs */
    AWS_S3_REQUEST_TYPE_HEAD_OBJECT,
    AWS_S3_REQUEST_TYPE_GET_OBJECT,
    AWS_S3_REQUEST_TYPE_LIST_PARTS,
    AWS_S3_REQUEST_TYPE_CREATE_MULTIPART_UPLOAD,
    AWS_S3_REQUEST_TYPE_UPLOAD_PART,
    AWS_S3_REQUEST_TYPE_ABORT_MULTIPART_UPLOAD,
    AWS_S3_REQUEST_TYPE_COMPLETE_MULTIPART_UPLOAD,
    AWS_S3_REQUEST_TYPE_UPLOAD_PART_COPY,
    AWS_S3_REQUEST_TYPE_COPY_OBJECT,
    AWS_S3_REQUEST_TYPE_PUT_OBJECT,
    AWS_S3_REQUEST_TYPE_CREATE_SESSION,

    /* Max enum value */
    AWS_S3_REQUEST_TYPE_MAX,

    /** @deprecated Use AWS_S3_REQUEST_TYPE_UNKNOWN if the actual S3 HTTP request type is unknown */
    AWS_S3_REQUEST_TYPE_DEFAULT = AWS_S3_REQUEST_TYPE_UNKNOWN,
};

/**
 * Invoked to provide response headers received during execution of the meta request, both for
 * success and error HTTP status codes.
 *
 * Return AWS_OP_SUCCESS to continue processing the request.
 *
 * Return aws_raise_error(E) to cancel the request.
 * The error you raise will be reflected in `aws_s3_meta_request_result.error_code`.
 * If you're not sure which error to raise, use AWS_ERROR_S3_CANCELED.
 */
typedef int(aws_s3_meta_request_headers_callback_fn)(
    struct aws_s3_meta_request *meta_request,
    const struct aws_http_headers *headers,
    int response_status,
    void *user_data);

/**
 * Invoked to provide the response body as it is received.
 *
 * Note: If you set `enable_read_backpressure` true on the S3 client,
 * you must maintain the flow-control window.
 * The flow-control window shrinks as you receive body data via this callback.
 * Whenever the flow-control window reaches 0 you will stop downloading data.
 * Use aws_s3_meta_request_increment_read_window() to increment the window and keep data flowing.
 * Maintain a larger window to keep up a high download throughput,
 * parts cannot download in parallel unless the window is large enough to hold multiple parts.
 * Maintain a smaller window to limit the amount of data buffered in memory.
 *
 * If `manual_window_management` is false, you do not need to maintain the flow-control window.
 * No back-pressure is applied and data arrives as fast as possible.
 *
 * Return AWS_OP_SUCCESS to continue processing the request.
 *
 * Return aws_raise_error(E) to cancel the request.
 * The error you raise will be reflected in `aws_s3_meta_request_result.error_code`.
 * If you're not sure which error to raise, use AWS_ERROR_S3_CANCELED.
 */
typedef int(aws_s3_meta_request_receive_body_callback_fn)(

    /* The meta request that the callback is being issued for. */
    struct aws_s3_meta_request *meta_request,

    /* The body data for this chunk of the object. */
    const struct aws_byte_cursor *body,

    /* The byte index of the object that this refers to. For example, for an HTTP message that has a range header, the
       first chunk received will have a range_start that matches the range header's range-start.*/
    uint64_t range_start,

    /* User data specified by aws_s3_meta_request_options.*/
    void *user_data);

/**
 * Invoked when the entire meta request execution is complete.
 */
typedef void(aws_s3_meta_request_finish_fn)(
    struct aws_s3_meta_request *meta_request,
    const struct aws_s3_meta_request_result *meta_request_result,
    void *user_data);

struct aws_s3_meta_request_receive_body_extra_info {
    /* The byte index of the object that this refers to. For example, for an HTTP message that has a range header, the
       first chunk received will have a range_start that matches the range header's range-start.*/
    uint64_t range_start;

    /* Associated buffer
     */
    struct aws_s3_buffer_ticket *ticket;
};

typedef int(aws_s3_meta_request_receive_body_callback_ex_fn)(

    /* The meta request that the callback is being issued for. */
    struct aws_s3_meta_request *meta_request,

    /* The body data for this chunk of the object. */
    const struct aws_byte_cursor *body,

    /* Extra information associated with the delivered body */
    const struct aws_s3_meta_request_receive_body_extra_info info,

    /* User data specified by aws_s3_meta_request_options.*/
    void *user_data);

/**
 * Information sent in the meta_request progress callback.
 */
struct aws_s3_meta_request_progress {

    /* Bytes transferred since the previous progress update */
    uint64_t bytes_transferred;

    /* Length of the entire meta request operation */
    uint64_t content_length;
};

/**
 * Invoked to report progress of a meta-request.
 * For PutObject, progress refers to bytes uploaded.
 * For CopyObject, progress refers to bytes copied.
 * For GetObject, progress refers to bytes downloaded.
 * For anything else, progress refers to response body bytes received.
 */
typedef void(aws_s3_meta_request_progress_fn)(
    struct aws_s3_meta_request *meta_request,
    const struct aws_s3_meta_request_progress *progress,
    void *user_data);

/**
 * Invoked to report the telemetry of the meta request once a single request finishes.
 * Note: *metrics is only valid for the duration of the callback. If you need to keep it around, use
 * `aws_s3_request_metrics_acquire`
 */
typedef void(aws_s3_meta_request_telemetry_fn)(
    struct aws_s3_meta_request *meta_request,
    struct aws_s3_request_metrics *metrics,
    void *user_data);

typedef void(aws_s3_meta_request_shutdown_fn)(void *user_data);

typedef void(aws_s3_client_shutdown_complete_callback_fn)(void *user_data);

/**
 * Optional callback, for you to provide the full object checksum after the object was read.
 * Client will NOT check the checksum provided before sending it to the server.
 *
 * @param meta_request  pointer to the aws_s3_meta_request of the upload.
 * @param user_data     pointer to the user_data set.
 *
 * @return A new string with the full object checksum, as it is sent in a PutObject request (base64-encoded):
 *         https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html#API_PutObject_RequestSyntax
 *         If an error occurs, call aws_raise_error(E) with a proper error code and return NULL.
 */
typedef struct aws_string *(aws_s3_meta_request_full_object_checksum_fn)(struct aws_s3_meta_request *meta_request,
                                                                         void *user_data);

enum aws_s3_meta_request_tls_mode {
    AWS_MR_TLS_ENABLED,
    AWS_MR_TLS_DISABLED,
};

enum aws_s3_meta_request_compute_content_md5 {
    AWS_MR_CONTENT_MD5_DISABLED,
    AWS_MR_CONTENT_MD5_ENABLED,
};

enum aws_s3_checksum_algorithm {
    AWS_SCA_NONE = 0,
    AWS_SCA_INIT,
    AWS_SCA_CRC32C = AWS_SCA_INIT,
    AWS_SCA_CRC32,
    AWS_SCA_SHA1,
    AWS_SCA_SHA256,
    AWS_SCA_CRC64NVME,
    AWS_SCA_END = AWS_SCA_CRC64NVME,
};

enum aws_s3_checksum_location {
    AWS_SCL_NONE = 0,
    AWS_SCL_HEADER,
    AWS_SCL_TRAILER,
};

enum aws_s3_recv_file_option {
    /**
     * Create a new file if it doesn't exist, otherwise replace the existing file.
     */
    AWS_S3_RECV_FILE_CREATE_OR_REPLACE = 0,
    /**
     * Always create a new file. If the file already exists, AWS_ERROR_S3_RECV_FILE_ALREADY_EXISTS will be raised.
     */
    AWS_S3_RECV_FILE_CREATE_NEW,
    /**
     * Create a new file if it doesn't exist, otherwise append to the existing file.
     */
    AWS_S3_RECV_FILE_CREATE_OR_APPEND,

    /**
     * Write to an existing file at the specified position, defined by the `recv_file_position`.
     * If the file does not exist, AWS_ERROR_S3_RECV_FILE_NOT_FOUND will be raised.
     * If `recv_file_position` is not configured, start overwriting data at the beginning of the
     * file (byte 0).
     */
    AWS_S3_RECV_FILE_WRITE_TO_POSITION,
};
/**
 * Info about a single part, for you to review before the upload completes.
 */
struct aws_s3_upload_part_review {
    /* Size in bytes of this part */
    uint64_t size;

    /* Checksum string, as sent in the UploadPart request (usually base64-encoded):
     * https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPart.html#API_UploadPart_RequestSyntax
     * This is empty if no checksum is used. */
    struct aws_byte_cursor checksum;
};

/**
 * Info for you to review before an upload completes.
 *
 * WARNING: This feature is experimental/unstable.
 * At this time, review is only available for multipart upload
 * (when Content-Length is above the `multipart_upload_threshold`,
 * or Content-Length not specified).
 */
struct aws_s3_upload_review {
    /* The checksum algorithm used. */
    enum aws_s3_checksum_algorithm checksum_algorithm;

    /* Number of parts uploaded. */
    size_t part_count;

    /* Array of info about each part uploaded (array is `part_count` in length) */
    struct aws_s3_upload_part_review *part_array;
};

/**
 * Optional callback, for you to review an upload before it completes.
 * For example, you can review each part's checksum and fail the upload if
 * you do not agree with them.
 *
 * @param meta_request pointer to the aws_s3_meta_request of the upload.
 * @param info Detailed info about the upload.
 *
 * Return AWS_OP_SUCCESS to continue processing the request.
 *
 * Return aws_raise_error(E) to cancel the request.
 * The error you raise will be reflected in `aws_s3_meta_request_result.error_code`.
 * If you're not sure which error to raise, use AWS_ERROR_S3_CANCELED.
 *
 * WARNING: This feature is experimental/unstable.
 * At this time, the callback is only invoked for multipart upload
 * (when Content-Length is above the `multipart_upload_threshold`,
 * or Content-Length not specified).
 */
typedef int(aws_s3_meta_request_upload_review_fn)(
    struct aws_s3_meta_request *meta_request,
    const struct aws_s3_upload_review *review,
    void *user_data);

/**
 * The factory function for S3 client to create a S3 Express credentials provider.
 * The S3 client will be the only owner of the S3 Express credentials provider.
 *
 * During S3 client destruction, S3 client will start the destruction of the provider, and wait the
 * on_provider_shutdown_callback to be invoked before the S3 client finish destruction.
 *
 * Note to implement the factory properly:
 * - Make sure `on_provider_shutdown_callback` will be invoked after the provider finish shutdown, otherwise,
 * leak will happen.
 * - The provider must not acquire a reference to the client; otherwise, a circular reference will cause a deadlock.
 * - The `client` provided CANNOT be used within the factory function call or the destructor.
 *
 * @param allocator    memory allocator to create the provider.
 * @param client    The S3 client uses and owns the provider.
 * @param on_provider_shutdown_callback    The callback to be invoked when the provider finishes shutdown.
 * @param shutdown_user_data    The user data to invoke shutdown callback with
 * @param user_data    The user data with the factory
 *
 * @return The aws_s3express_credentials_provider.
 */
typedef struct aws_s3express_credentials_provider *(
    aws_s3express_provider_factory_fn)(struct aws_allocator *allocator,
                                       struct aws_s3_client *client,
                                       aws_simple_completion_callback on_provider_shutdown_callback,
                                       void *shutdown_user_data,
                                       void *factory_user_data);

/* Keepalive properties are TCP only.
 * If interval or timeout are zero, then default values are used.
 */
struct aws_s3_tcp_keep_alive_options {

    uint16_t keep_alive_interval_sec;
    uint16_t keep_alive_timeout_sec;

    /* If set, sets the number of keep alive probes allowed to fail before the connection is considered
     * lost. If zero OS defaults are used. On Windows, this option is meaningless until Windows 10 1703.*/
    uint16_t keep_alive_max_failed_probes;
};

/* Options for a new client. */
struct aws_s3_client_config {

    /* When set, this will cap the number of active connections. When 0, the client will determine this value based on
     * throughput_target_gbps. (Recommended) */
    uint32_t max_active_connections_override;

    /* Region that the client default to. */
    struct aws_byte_cursor region;

    /* Client bootstrap used for common staples such as event loop group, host resolver, etc.. s*/
    struct aws_client_bootstrap *client_bootstrap;

    /* How tls should be used while performing the request
     * If this is ENABLED:
     *     If tls_connection_options is not-null, then those tls options will be used
     *     If tls_connection_options is NULL, then default tls options will be used
     * If this is DISABLED:
     *     No tls options will be used, regardless of tls_connection_options value.
     */
    enum aws_s3_meta_request_tls_mode tls_mode;

    /* TLS Options to be used for each connection, if tls_mode is ENABLED. When compiling with BYO_CRYPTO, and tls_mode
     * is ENABLED, this is required. Otherwise, this is optional.
     */
    const struct aws_tls_connection_options *tls_connection_options;

    /**
     * Required.
     * Configure the signing for the requests made from the client.
     * - Credentials or credentials provider is required. Other configs are all optional, and will be default to what
     *      needs to sign the request for S3, only overrides when Non-zero/Not-empty is set.
     * - To skip signing, you can config it with anonymous credentials.
     * - S3 Client will derive the right config for signing process based on this.
     *
     * Notes:
     * - For AWS_SIGNING_ALGORITHM_V4_S3EXPRESS, S3 client will use the credentials in the config to derive the
     * S3 Express credentials that are used in the signing process.
     * - For other auth algorithm, client may make modifications to signing config before passing it on to signer.
     *
     * TODO: deprecate this structure from auth, introduce a new S3 specific one.
     */
    const struct aws_signing_config_aws *signing_config;

    /**
     * Optional.
     * Size of parts the object will be downloaded or uploaded in, in bytes.
     * This only affects AWS_S3_META_REQUEST_TYPE_GET_OBJECT and AWS_S3_META_REQUEST_TYPE_PUT_OBJECT.
     * If not set, this defaults to 8 MiB.
     * The client will adjust the part size for AWS_S3_META_REQUEST_TYPE_PUT_OBJECT if needed for service limits (max
     * number of parts per upload is 10,000, minimum upload part size is 5 MiB).
     *
     * You can also set this per meta-request, via `aws_s3_meta_request_options.part_size`.
     */
    uint64_t part_size;

    /* If the part size needs to be adjusted for service limits, this is the maximum size it will be adjusted to. On 32
     * bit machine, it will be forced to SIZE_MAX, which is around 4GiB. The server limit is 5GiB, but object size limit
     * is 5TiB for now. We should be good enough for all the cases. */
    uint64_t max_part_size;

    /**
     * Optional.
     * The size threshold in bytes for when to use multipart uploads.
     * Uploads larger than this will use the multipart upload strategy.
     * Uploads smaller or equal to this will use a single HTTP request.
     * This only affects AWS_S3_META_REQUEST_TYPE_PUT_OBJECT.
     * If set, this should be at least `part_size`.
     * If not set, maximal of `part_size` and 5 MiB will be used.
     *
     * You can also set this per meta-request, via `aws_s3_meta_request_options.multipart_upload_threshold`.
     */
    uint64_t multipart_upload_threshold;

    /* Throughput target in gigabits per second (Gbps) that we are trying to reach. */
    double throughput_target_gbps;

    /* How much memory can we use. This will be capped to SIZE_MAX */
    uint64_t memory_limit_in_bytes;

    /* Retry strategy to use. If NULL, a default retry strategy will be used. */
    struct aws_retry_strategy *retry_strategy;

    /**
     * TODO: move MD5 config to checksum config.
     * For multi-part upload, content-md5 will be calculated if the AWS_MR_CONTENT_MD5_ENABLED is specified
     *     or initial request has content-md5 header.
     * For single-part upload, keep the content-md5 in the initial request unchanged. */
    enum aws_s3_meta_request_compute_content_md5 compute_content_md5;

    /* Callback and associated user data for when the client has completed its shutdown process. */
    aws_s3_client_shutdown_complete_callback_fn *shutdown_callback;
    void *shutdown_callback_user_data;

    /**
     * Optional.
     * Proxy configuration for http connection.
     * If the connection_type is AWS_HPCT_HTTP_LEGACY, it will be converted to AWS_HPCT_HTTP_TUNNEL if tls_mode is
     * ENABLED. Otherwise, it will be converted to AWS_HPCT_HTTP_FORWARD.
     */
    const struct aws_http_proxy_options *proxy_options;

    /**
     * Optional.
     * Configuration for fetching proxy configuration from environment.
     * By Default proxy_ev_settings.aws_http_proxy_env_var_type is set to AWS_HPEV_ENABLE which means read proxy
     * configuration from environment.
     * Only works when proxy_options is not set. If both are set, configuration from proxy_options is used.
     */
    const struct proxy_env_var_settings *proxy_ev_settings;

    /**
     * Optional.
     * If set to 0, default value is used.
     */
    uint32_t connect_timeout_ms;

    /**
     * Optional.
     * Set keepalive to periodically transmit messages for detecting a disconnected peer.
     */
    const struct aws_s3_tcp_keep_alive_options *tcp_keep_alive_options;

    /**
     * Optional.
     * Configuration options for connection monitoring.
     * If the transfer speed falls below the specified minimum_throughput_bytes_per_second, the operation is aborted.
     * If set to NULL, default values are used.
     */
    const struct aws_http_connection_monitoring_options *monitoring_options;

    /**
     * Enable backpressure and prevent response data from downloading faster than you can handle it.
     *
     * If false (default), no backpressure is applied and data will download as fast as possible.
     *
     * If true, each meta request has a flow-control window that shrinks as
     * response body data is downloaded (headers do not affect the window).
     * `initial_read_window` determines the starting size of each meta request's window.
     * You will stop downloading data whenever the flow-control window reaches 0
     * You must call aws_s3_meta_request_increment_read_window() to keep data flowing.
     *
     * WARNING: This feature is experimental.
     * Currently, backpressure is only applied to GetObject requests which are split into multiple parts,
     * and you may still receive some data after the window reaches 0.
     */
    bool enable_read_backpressure;

    /**
     * The starting size of each meta request's flow-control window, in bytes.
     * Ignored unless `enable_read_backpressure` is true.
     */
    size_t initial_read_window;

    /**
     * To enable S3 Express support or not.
     */
    bool enable_s3express;

    /**
     * Optional.
     * Only used when `enable_s3express` is set.
     *
     * If set, client will invoke the factory to get the provider to use, when needed.
     *
     * If not set, client will create a default S3 Express provider under the hood.
     *
     * NOTE: THE FOLLOWING BEHAVIOR IS EXPERIMENTAL AND UNSTABLE
     * Default S3 Express provider will pass the headers allowed in `g_s3_create_session_allowed_headers` to the
     * CreateSession call.
     */
    aws_s3express_provider_factory_fn *s3express_provider_override_factory;
    void *factory_user_data;

    /**
     * THIS IS AN EXPERIMENTAL AND UNSTABLE API
     * (Optional)
     * An array of network interface names. The client will distribute the
     * connections across network interface names provided in this array. If any interface name is invalid, goes down,
     * or has any issues like network access, you will see connection failures.
     *
     * This option is only supported on Linux, MacOS, and platforms that have either SO_BINDTODEVICE or IP_BOUND_IF. It
     * is not supported on Windows. `AWS_ERROR_PLATFORM_NOT_SUPPORTED` will be raised on unsupported platforms. On
     * Linux, SO_BINDTODEVICE is used and requires kernel version >= 5.7 or root privileges.
     */
    const struct aws_byte_cursor *network_interface_names_array;
    size_t num_network_interface_names;

    /**
     * Experimental (touch at your own risk).
     * (Optional)
     * Factory function to create buffer pool.
     * if not set creates default implementation of the buffer pool.
     */
    aws_s3_buffer_pool_factory_fn *buffer_pool_factory_fn;

    /* User data thats passed into pool factory. */
    void *buffer_pool_user_data;
};

struct aws_s3_checksum_config {

    /****************************** PUT Object specific *******************************/
    /**
     * The location of client added checksum header.
     *
     * If AWS_SCL_NONE. No request payload checksum will be added.
     *
     * If AWS_SCL_HEADER, the client will calculate the checksum and add it to the headers.
     *
     * If AWS_SCL_TRAILER, the payload will be aws_chunked encoded, The client will calculate the checksum and add it to
     * the trailer. Note the payload of the original request cannot be aws-chunked encoded already, this will cause an
     * error.
     */
    enum aws_s3_checksum_location location;

    /**
     * The checksum algorithm used.
     * Must be set if location is not AWS_SCL_NONE.
     */
    enum aws_s3_checksum_algorithm checksum_algorithm;

    /**
     * Optional.
     * Provide the full object checksum. This callback is invoked once, after the entire body has been read.
     * sent.
     *
     * NOTE:
     *  - Do not set this callback if the HTTP message already has a checksum header (e.g. x-amz-checksum-crc32). Doing
     *      so will raise AWS_ERROR_INVALID_ARGUMENT.
     *  - checksum_algorithm must be set to the algorithm you will use.
     *
     * WARNING: This feature is experimental/unstable.
     * At this time, full object checksum callback is only available for multipart upload
     * (when Content-Length is above the `multipart_upload_threshold`,
     * or Content-Length not specified). Otherwise, it will be ignored.
     */
    aws_s3_meta_request_full_object_checksum_fn *full_object_checksum_callback;
    void *user_data;

    /****************************** GET Object specific *******************************/
    /**
     * Enable checksum mode header will be attached to GET requests, this will tell s3 to send back checksums headers if
     * they exist. Calculate the corresponding checksum on the response bodies. The meta request will finish with a did
     * validate field and set the error code to AWS_ERROR_S3_RESPONSE_CHECKSUM_MISMATCH if the calculated
     * checksum, and checksum found in the response header do not match.
     */
    bool validate_response_checksum;

    /**
     * Optional array of `enum aws_s3_checksum_algorithm`.
     *
     * Ignored when validate_response_checksum is not set.
     * If not set all the algorithms will be selected as default behavior.
     * Owned by the caller.
     *
     * The list of algorithms for user to pick up when validate the checksum. Client will pick up the algorithm from the
     * list with the priority based on performance, and the algorithm sent by server. The priority based on performance
     * is [CRC64NVME, CRC32C, CRC32, SHA1, SHA256].
     *
     * If the response checksum was validated by client, the result will indicate which algorithm was picked.
     */
    const struct aws_array_list *validate_checksum_algorithms;
};

/**
 * Options for a new meta request, ie, file transfer that will be handled by the high performance client.
 *
 * There are several ways to pass the request's body data:
 * 1) If the data is already in memory, set the body-stream on `message`.
 * 2) If the data is on disk, set `send_filepath` for best performance.
 * 3) If the data is available, but copying each chunk is asynchronous, set `send_async_stream`.
 * 4) If you're not sure when each chunk of data will be available, use `send_using_async_writes`.
 */
struct aws_s3_meta_request_options {
    /* The type of meta request we will be trying to accelerate. */
    enum aws_s3_meta_request_type type;

    /**
     * The S3 operation name (e.g. "CreateBucket").
     * This MUST be set if type is AWS_S3_META_REQUEST_TYPE_DEFAULT;
     * it is automatically populated for other meta-request types.
     * The canonical operation names are listed here:
     * https://docs.aws.amazon.com/AmazonS3/latest/API/API_Operations_Amazon_Simple_Storage_Service.html
     *
     * This name is used to fill out details in metrics and error reports.
     * It also drives some operation-specific behavior.
     * If you pass the wrong name, you risk getting the wrong behavior.
     *
     * For example, every operation except "GetObject" has its response checked
     * for error, even if the HTTP status-code was 200 OK
     * (see https://repost.aws/knowledge-center/s3-resolve-200-internalerror).
     * If you used AWS_S3_META_REQUEST_TYPE_DEFAULT to do GetObject, but mis-named
     * it "Download", and the object looked like XML with an error code,
     * then the meta-request would fail. You may log the full response body,
     * and leak sensitive data.
     */
    struct aws_byte_cursor operation_name;

    /**
     * Configure the signing for each request created for this meta request. If NULL, options in the client will be
     *  used.
     * - The credentials will be obtained based on the precedence of:
     *      1. `credentials` from `signing_config` in `aws_s3_meta_request_options`
     *      2. `credentials_provider` from `signing_config` in `aws_s3_meta_request_options`
     *      3. `credentials` from `signing_config` cached in the client
     *      4. `credentials_provider` cached in the client
     * - To skip signing, you can config it with anonymous credentials.
     * - S3 Client will derive the right config for signing process based on this.
     *
     * Notes:
     * - For AWS_SIGNING_ALGORITHM_V4_S3EXPRESS, S3 client will use the credentials in the config to derive the
     * S3 Express credentials that are used in the signing process.
     * - For other auth algorithm, client may make modifications to signing config before passing it on to signer.
     **/
    const struct aws_signing_config_aws *signing_config;

    /* Initial HTTP message that defines what operation we are doing.
     * Do not set the message's body-stream if the body is being passed by other means (see note above) */
    struct aws_http_message *message;

    /**
     * Optional.
     * If set, the received data will be written into this file.
     * the `body_callback` will NOT be invoked.
     * This gives a better performance when receiving data to write to a file.
     * See `aws_s3_recv_file_option` for the configuration on the receive file.
     */
    struct aws_byte_cursor recv_filepath;

    /**
     * Optional.
     * Default to AWS_S3_RECV_FILE_CREATE_OR_REPLACE.
     * This only works with recv_filepath set.
     * See `aws_s3_recv_file_option`.
     */
    enum aws_s3_recv_file_option recv_file_option;
    /**
     * Optional.
     * The specified position to start writing at for the recv file when `recv_file_option` is set to
     * AWS_S3_RECV_FILE_WRITE_TO_POSITION, ignored otherwise.
     */
    uint64_t recv_file_position;
    /**
     * Set it to be true to delete the receive file on failure, otherwise, the file will be left as-is.
     * This only works with recv_filepath set.
     */
    bool recv_file_delete_on_failure;

    /**
     * Optional.
     * If set, this file is sent as the request body.
     * This gives the best performance when sending data from a file.
     * Do not set if the body is being passed by other means (see note above).
     */
    struct aws_byte_cursor send_filepath;

    /**
     * Optional - EXPERIMENTAL/UNSTABLE
     * If set, the request body comes from this async stream.
     * Use this when outgoing data will be produced in asynchronous chunks.
     * The S3 client will read from the stream whenever it's ready to upload another chunk.
     *
     * WARNING: The S3 client can deadlock if many async streams are "stalled",
     * never completing their async read. If you're not sure when (if ever)
     * data will be ready, use `send_using_async_writes` instead.
     *
     * Do not set if the body is being passed by other means (see note above).
     */
    struct aws_async_input_stream *send_async_stream;

    /**
     * Optional - EXPERIMENTAL/UNSTABLE
     * Set this to send request body data using the async aws_s3_meta_request_poll_write()
     * or aws_s3_meta_request_write() functions.
     * Use this when outgoing data will be produced in asynchronous chunks,
     * and you're not sure when (if ever) each chunk will be ready.
     *
     * This only works with AWS_S3_META_REQUEST_TYPE_PUT_OBJECT.
     *
     * Do not set if the body is being passed by other means (see note above).
     */
    bool send_using_async_writes;

    /**
     * Optional.
     * if set, the flexible checksum will be performed by client based on the config.
     *
     * Notes: checksum can also be added through the http message provided.
     *      The checksum in http header will override corresponding the checksum config.
     */
    const struct aws_s3_checksum_config *checksum_config;

    /**
     * Optional.
     * Size of parts the object will be downloaded or uploaded in, in bytes.
     * This only affects AWS_S3_META_REQUEST_TYPE_GET_OBJECT and AWS_S3_META_REQUEST_TYPE_PUT_OBJECT.
     * If not set, the value from `aws_s3_client_config.part_size` is used, which defaults to 8MiB.
     *
     * The client will adjust the part size for AWS_S3_META_REQUEST_TYPE_PUT_OBJECT if needed for service limits (max
     * number of parts per upload is 10,000, minimum upload part size is 5 MiB).
     */
    uint64_t part_size;

    /**
     * Optional.
     * The size threshold in bytes for when to use multipart uploads.
     * Uploads larger than this will use the multipart upload strategy.
     * Uploads smaller or equal to this will use a single HTTP request.
     * This only affects AWS_S3_META_REQUEST_TYPE_PUT_OBJECT.
     * If set, this should be at least `part_size`.
     * If not set, `part_size` adjusted by client will be used as the threshold.
     * If both `part_size` and `multipart_upload_threshold` are not set,
     * the values from `aws_s3_client_config` are used.
     */
    uint64_t multipart_upload_threshold;

    /* User data for all callbacks. */
    void *user_data;

    /**
     * Optional.
     * Invoked to provide response headers received during execution of the meta request.
     * Note: this callback will not be fired for cases when resuming an
     * operation that was already completed (ex. pausing put object after it
     * uploaded all data and then resuming it)
     * See `aws_s3_meta_request_headers_callback_fn`.
     */
    aws_s3_meta_request_headers_callback_fn *headers_callback;

    /**
     * Invoked to provide the response body as it is received.
     * See `aws_s3_meta_request_receive_body_callback_fn`.
     */
    aws_s3_meta_request_receive_body_callback_fn *body_callback;

    /**
     * Invoked to provide the response body as it is received.
     * Provides extra information as compared to regular callback.
     * Note: setting both body_callback and body_callback_ex is not
     * considered valid and meta request creation will fail.
     * See `aws_s3_meta_request_receive_body_callback_ex_fn`.
     */
    aws_s3_meta_request_receive_body_callback_ex_fn *body_callback_ex;

    /**
     * Invoked when the entire meta request execution is complete.
     * See `aws_s3_meta_request_finish_fn`.
     */
    aws_s3_meta_request_finish_fn *finish_callback;

    /* Callback for when the meta request has completely cleaned up. */
    aws_s3_meta_request_shutdown_fn *shutdown_callback;

    /**
     * Invoked to report progress of the meta request execution.
     * See `aws_s3_meta_request_progress_fn`.
     */
    aws_s3_meta_request_progress_fn *progress_callback;

    /**
     * Optional.
     * To get telemetry metrics when a single request finishes.
     * If set the request will keep track of the metrics from `aws_s3_request_metrics`, and fire the callback when the
     * request finishes receiving response.
     * See `aws_s3_meta_request_telemetry_fn`
     */
    aws_s3_meta_request_telemetry_fn *telemetry_callback;

    /**
     * Optional.
     * Callback for reviewing an upload before it completes.
     * WARNING: experimental/unstable
     * See `aws_s3_meta_request_upload_review_fn`
     */
    aws_s3_meta_request_upload_review_fn *upload_review_callback;

    /**
     * Optional.
     * Endpoint override for request. Can be used to override scheme and port of
     * the endpoint.
     * There is some overlap between Host header and Endpoint and corner cases
     * are handled as follows:
     * - Only Host header is set - Host is used to construct endpoint. https is
     *   default with corresponding port
     * - Only endpoint is set - Host header is created from endpoint. Port and
     *   Scheme from endpoint is used.
     * - Both Host and Endpoint is set - Host header must match Authority of
     *   Endpoint uri. Port and Scheme from endpoint is used.
     */
    const struct aws_uri *endpoint;

    /**
     * Optional.
     * For meta requests that support pause/resume (e.g. PutObject), serialized resume token returned by
     * aws_s3_meta_request_pause() can be provided here.
     * Note: If PutObject request specifies a checksum algorithm, client will calculate checksums while skipping parts
     * from the buffer and compare them them to previously uploaded part checksums.
     */
    struct aws_s3_meta_request_resume_token *resume_token;

    /*
     * Optional.
     * Total object size hint, in bytes.
     * The optimal strategy for downloading a file depends on its size.
     * Set this hint to help the S3 client choose the best strategy for this particular file.
     * This is just used as an estimate, so it's okay to provide an approximate value if the exact size is unknown.
     */
    const uint64_t *object_size_hint;

    /*
     * (Optional)
     * If performing a copy operation, provide the source URI here to bypass limitations 1 and 2 of the copy operation.
     * This will be ignored for other operations.
     */
    struct aws_byte_cursor copy_source_uri;
};

/* Result details of a meta request.
 *
 * If error_code is AWS_ERROR_SUCCESS, then response_status will match the response_status passed earlier by the header
 * callback and error_response_headers and error_response_body will be NULL.
 *
 * If error_code is equal to AWS_ERROR_S3_INVALID_RESPONSE_STATUS, then error_response_headers, error_response_body, and
 * response_status will be populated by the failed request.
 *
 * For all other error codes, response_status will be 0, and the error_response variables will be NULL.
 */
struct aws_s3_meta_request_result {

    /* If meta request failed due to an HTTP error response from S3, these are the headers.
     * NULL if meta request failed for another reason. */
    struct aws_http_headers *error_response_headers;

    /* If meta request failed due to an HTTP error response from S3, this the body.
     * NULL if meta request failed for another reason, or if the response had no body (such as a HEAD response). */
    struct aws_byte_buf *error_response_body;

    /* If meta request failed due to an HTTP error response from S3,
     * this is the name of the S3 operation it was responding to.
     * For example, if a AWS_S3_META_REQUEST_TYPE_PUT_OBJECT fails this could be
     * "PutObject, "CreateMultipartUpload", "UploadPart", "CompleteMultipartUpload", or others.
     * For AWS_S3_META_REQUEST_TYPE_DEFAULT, this is the same value passed to
     * aws_s3_meta_request_options.operation_name.
     * NULL if the meta request failed for another reason. */
    struct aws_string *error_response_operation_name;

    /* Response status of the failed request or of the entire meta request. */
    int response_status;

    /* Only set for GET request.
     * Was the server side checksum compared against a calculated checksum of the response body. This may be false
     * even if validate_get_response_checksum was set because the object was uploaded without a checksum, or was
     * uploaded as a multipart object.
     *
     * If the object to get is multipart object, the part checksum MAY be validated if the part size to get matches the
     * part size uploaded. In that case, if any part mismatch the checksum received, the meta request will fail with
     * checksum mismatch. However, even if the parts checksum were validated, this will NOT be set to true, as the
     * checksum for the whole meta request was NOT validated.
     **/
    bool did_validate;

    /* algorithm used to validate checksum */
    enum aws_s3_checksum_algorithm validation_algorithm;

    /* Final error code of the meta request. */
    int error_code;
};

AWS_EXTERN_C_BEGIN

AWS_S3_API
struct aws_s3_client *aws_s3_client_new(
    struct aws_allocator *allocator,
    const struct aws_s3_client_config *client_config);

/**
 * Add a reference, keeping this object alive.
 * The reference must be released when you are done with it, or it's memory will never be cleaned up.
 * You must not pass in NULL.
 * Always returns the same pointer that was passed in.
 */
AWS_S3_API
struct aws_s3_client *aws_s3_client_acquire(struct aws_s3_client *client);

/**
 * Release a reference.
 * When the reference count drops to 0, this object will be cleaned up.
 * It's OK to pass in NULL (nothing happens).
 * Always returns NULL.
 */
AWS_S3_API
struct aws_s3_client *aws_s3_client_release(struct aws_s3_client *client);

AWS_S3_API
struct aws_s3_meta_request *aws_s3_client_make_meta_request(
    struct aws_s3_client *client,
    const struct aws_s3_meta_request_options *options);

/**
 * The result of an `aws_s3_meta_request_poll_write()` call.
 * Think of this like Rust's `Poll<Result<size_t, int>>`, or C++'s `optional<expected<size_t, int>>`.
 */
struct aws_s3_meta_request_poll_write_result {
    bool is_pending;
    int error_code;
    size_t bytes_processed;
};

/**
 * Attempt to write data.
 *
 * You must set `aws_s3_meta_request_options.send_using_async_writes` to use this function.
 *
 * This is a non-blocking poll-style async function, similar to Rust's:
 * https://docs.rs/futures/latest/futures/io/trait.AsyncWrite.html#tymethod.poll_write
 * If you prefer completion-style async functions, and your data can outlive
 * the callstack, use aws_s3_meta_request_write() instead.
 *
 * Check the returned `result` struct to see what happened:
 * 1)   If `result.is_pending == true` then no work was done.
 *      The waker callback will be invoked when you can call poll_write() again.
 *      Do not call poll_write() again before the waker is invoked.
 *
 * 2)   Else if `result.error_code != 0` then poll_write() did not succeed
 *      and you should not call it again. The meta request is guaranteed to finish soon
 *      (you don't need to worry about canceling the meta request yourself after a failed write).
 *      A common error code is AWS_ERROR_S3_REQUEST_HAS_COMPLETED, indicating
 *      the meta request completed for reasons unrelated to the poll_write() call
 *      (e.g. CreateMultipartUpload received a 403 Forbidden response).
 *      AWS_ERROR_INVALID_STATE usually indicates that you're calling poll_write()
 *      incorrectly (e.g. not waiting for waker callback from previous poll_write() call).
 *
 * 3)   Else `result.bytes_processed` tells you how much data was processed.
 *      `bytes_processed` may be less than the `data.len` you passed in.
 *      Continue calling poll_write() with the remaining data until everything is processed.
 *      `result.bytes_processed` won't be 0 unless you passed in `data.len` of 0.
 *
 * @param meta_request  Meta request
 *
 * @param data          The data to send. The data can be any size.
 *                      `result.bytes_processed` indicates how many bytes were
 *                      processed by this call.
 *
 * @param eof           Pass true to signal EOF (end of file).
 *                      If poll_write() doesn't process all your data
 *                      (`result.is_pending` or `result.byte_processed < data.len`)
 *                      then EOF was ignored, and you need to pass it again
 *                      to subsequent poll_write() calls.
 *
 * @param waker         Waker callback.
 *                      If `result.is_pending == true`, then the waker will be called
 *                      exactly once when it's a good time to call poll_write() again.
 *                      If `result.is_pending == false`, the waker will never be called.
 *
 * @param user_data     Pointer to be passed to the waker callback.
 *
 * WARNING: This feature is experimental.
 */
AWS_S3_API
struct aws_s3_meta_request_poll_write_result aws_s3_meta_request_poll_write(
    struct aws_s3_meta_request *meta_request,
    struct aws_byte_cursor data,
    bool eof,
    aws_simple_completion_callback *waker,
    void *user_data);

/**
 * Write the next chunk of data.
 *
 * You must set `aws_s3_meta_request_options.send_using_async_writes` to use this function.
 *
 * This function is asynchronous, and returns a future (see <aws/io/future.h>).
 * You may not call write() again until the future completes.
 *
 * If the future completes with an error code, then write() did not succeed
 * and you should not call it again. If the future contains any error code,
 * the meta request is guaranteed to finish soon (you don't need to worry about
 * canceling the meta request yourself after a failed write).
 * A common error code is AWS_ERROR_S3_REQUEST_HAS_COMPLETED, indicating
 * the meta request completed for reasons unrelated to the write() call
 * (e.g. CreateMultipartUpload received a 403 Forbidden response).
 * AWS_ERROR_INVALID_STATE usually indicates that you're calling write()
 * incorrectly (e.g. not waiting for previous write to complete).
 *
 * You MUST keep the data in memory until the future completes.
 * If you cannot do this, use aws_s3_meta_request_poll_write() instead.
 *
 * You can wait any length of time between calls to write().
 * If there's not enough data to upload a part, the data will be copied
 * to a buffer and the future will immediately complete.
 *
 * @param meta_request  Meta request
 *
 * @param data          The data to send. The data can be any size.
 *
 * @param eof           Pass true to signal EOF (end of file).
 *                      Do not call write() again after passing true.
 *
 * This function never returns NULL.
 *
 * WARNING: This feature is experimental.
 */
AWS_S3_API
struct aws_future_void *aws_s3_meta_request_write(
    struct aws_s3_meta_request *meta_request,
    struct aws_byte_cursor data,
    bool eof);

/**
 * Increment the flow-control window, so that response data continues downloading.
 *
 * If the client was created with `enable_read_backpressure` set true,
 * each meta request has a flow-control window that shrinks as response
 * body data is downloaded (headers do not affect the size of the window).
 * The client's `initial_read_window` determines the starting size of each meta request's window.
 * If a meta request's flow-control window reaches 0, no further data will be downloaded.
 * If the `initial_read_window` is 0, the request will not start until the window is incremented.
 * Maintain a larger window to keep up a high download throughput,
 * parts cannot download in parallel unless the window is large enough to hold multiple parts.
 * Maintain a smaller window to limit the amount of data buffered in memory.
 *
 * If `enable_read_backpressure` is false this call will have no effect,
 * no backpressure is being applied and data is being downloaded as fast as possible.
 *
 * WARNING: This feature is experimental.
 * Currently, backpressure is only applied to GetObject requests which are split into multiple parts,
 * and you may still receive some data after the window reaches 0.
 */
AWS_S3_API
void aws_s3_meta_request_increment_read_window(struct aws_s3_meta_request *meta_request, uint64_t bytes);

AWS_S3_API
void aws_s3_meta_request_cancel(struct aws_s3_meta_request *meta_request);

/**
 * Note: pause is currently only supported on upload requests.
 * In order to pause an ongoing upload, call aws_s3_meta_request_pause() that
 * will return resume token. Token can be used to query the state of operation
 * at the pausing time.
 * To resume an upload that was paused, supply resume token in the meta
 * request options structure member aws_s3_meta_request_options.resume_token.
 * The upload can be resumed either from the same client or a different one.
 * Corner cases for resume upload are as follows:
 * - upload is not MPU - fail with AWS_ERROR_UNSUPPORTED_OPERATION
 * - pausing before MPU is created - NULL resume token returned. NULL resume
 *   token is equivalent to restarting upload
 * - pausing in the middle of part transfer - return resume token. scheduling of
 *   new part uploads stops.
 * - pausing after completeMPU started - return resume token. if s3 cannot find
 *   find associated MPU id when resuming with that token and num of parts
 *   uploaded equals to total num parts, then operation is a no op. Otherwise
 *   operation fails.
 * Note: for no op case the call will succeed and finish/shutdown request callbacks will
 *   fire, but on headers callback will not fire.
 * Note: similar to cancel pause does not cancel requests already in flight and
 * and parts might complete after pause is requested.
 * @param meta_request pointer to the aws_s3_meta_request of the upload to be paused
 * @param resume_token resume token
 * @return either AWS_OP_ERR or AWS_OP_SUCCESS
 */
AWS_S3_API
int aws_s3_meta_request_pause(
    struct aws_s3_meta_request *meta_request,
    struct aws_s3_meta_request_resume_token **out_resume_token);

/*
 * Options to construct upload resume token.
 * Note: fields correspond to getters on the token below and it up to the caller
 * to persist those in whichever way they choose.
 */
struct aws_s3_upload_resume_token_options {
    struct aws_byte_cursor upload_id; /* Required */
    uint64_t part_size;               /* Required. Must be less than SIZE_MAX */
    size_t total_num_parts;           /* Required */

    /**
     * Optional.
     *
     * Note: during resume num_parts_uploaded is used for sanity checking against
     * uploads on s3 side.
     * In cases where upload id does not exist (already resumed using this token
     * or pause called after upload completes, etc...) and num_parts_uploaded
     * equals to total num parts, resume will become a noop.
     */
    size_t num_parts_completed;
};

/**
 * Create upload resume token from persisted data.
 * Note: Data required for resume token varies per operation.
 */
AWS_S3_API
struct aws_s3_meta_request_resume_token *aws_s3_meta_request_resume_token_new_upload(
    struct aws_allocator *allocator,
    const struct aws_s3_upload_resume_token_options *options);

/*
 * Increment resume token ref count.
 */
AWS_S3_API
struct aws_s3_meta_request_resume_token *aws_s3_meta_request_resume_token_acquire(
    struct aws_s3_meta_request_resume_token *resume_token);

/*
 * Decrement resume token ref count.
 */
AWS_S3_API
struct aws_s3_meta_request_resume_token *aws_s3_meta_request_resume_token_release(
    struct aws_s3_meta_request_resume_token *resume_token);

/*
 * Type of resume token.
 */
AWS_S3_API
enum aws_s3_meta_request_type aws_s3_meta_request_resume_token_type(
    struct aws_s3_meta_request_resume_token *resume_token);

/*
 * Part size associated with operation.
 */
AWS_S3_API
uint64_t aws_s3_meta_request_resume_token_part_size(struct aws_s3_meta_request_resume_token *resume_token);

/*
 * Total num parts associated with operation.
 */
AWS_S3_API
size_t aws_s3_meta_request_resume_token_total_num_parts(struct aws_s3_meta_request_resume_token *resume_token);

/*
 * Num parts completed.
 */
AWS_S3_API
size_t aws_s3_meta_request_resume_token_num_parts_completed(struct aws_s3_meta_request_resume_token *resume_token);

/*
 * Upload id associated with operation.
 * Only valid for tokens returned from upload operation. For all other operations
 * this will return empty.
 */
AWS_S3_API
struct aws_byte_cursor aws_s3_meta_request_resume_token_upload_id(
    struct aws_s3_meta_request_resume_token *resume_token);

/**
 * Add a reference, keeping this object alive.
 * The reference must be released when you are done with it, or it's memory will never be cleaned up.
 * You must not pass in NULL.
 * Always returns the same pointer that was passed in.
 */
AWS_S3_API
struct aws_s3_meta_request *aws_s3_meta_request_acquire(struct aws_s3_meta_request *meta_request);

/**
 * Release a reference.
 * When the reference count drops to 0, this object will be cleaned up.
 * It's OK to pass in NULL (nothing happens).
 * Always returns NULL.
 */
AWS_S3_API
struct aws_s3_meta_request *aws_s3_meta_request_release(struct aws_s3_meta_request *meta_request);

/**
 * Initialize the configuration for a default S3 signing.
 */
AWS_S3_API
void aws_s3_init_default_signing_config(
    struct aws_signing_config_aws *signing_config,
    const struct aws_byte_cursor region,
    struct aws_credentials_provider *credentials_provider);

/**
 * Return operation name for aws_s3_request_type,
 * or empty string if the type doesn't map to an actual operation.
 * For example:
 * AWS_S3_REQUEST_TYPE_HEAD_OBJECT -> "HeadObject"
 * AWS_S3_REQUEST_TYPE_UNKNOWN -> ""
 * AWS_S3_REQUEST_TYPE_MAX -> ""
 */
AWS_S3_API
const char *aws_s3_request_type_operation_name(enum aws_s3_request_type type);

/**
 * Add a reference, keeping this object alive.
 * The reference must be released when you are done with it, or it's memory will never be cleaned up.
 * Always returns the same pointer that was passed in.
 */
AWS_S3_API
struct aws_s3_request_metrics *aws_s3_request_metrics_acquire(struct aws_s3_request_metrics *metrics);

/**
 * Release a reference.
 * When the reference count drops to 0, this object will be cleaned up.
 * It's OK to pass in NULL (nothing happens).
 * Always returns NULL.
 */
AWS_S3_API
struct aws_s3_request_metrics *aws_s3_request_metrics_release(struct aws_s3_request_metrics *metrics);

/*************************************  Getters for s3 request metrics ************************************************/
/**
 * Get the request ID from aws_s3_request_metrics.
 * If unavailable, AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised.
 * If available, out_request_id will be set to a string. Be warned this string's lifetime is tied to the metrics
 * object.
 **/
AWS_S3_API
int aws_s3_request_metrics_get_request_id(
    const struct aws_s3_request_metrics *metrics,
    const struct aws_string **out_request_id);

/* Get the start time from aws_s3_request_metrics, which is when S3 client prepare the request to be sent. Always
 * available. Timestamp are from `aws_high_res_clock_get_ticks`  */
AWS_S3_API
void aws_s3_request_metrics_get_start_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_start_time);

/* Get the end time from aws_s3_request_metrics. Always available */
AWS_S3_API
void aws_s3_request_metrics_get_end_timestamp_ns(const struct aws_s3_request_metrics *metrics, uint64_t *out_end_time);

/* Get the total duration time from aws_s3_request_metrics. Always available */
AWS_S3_API
void aws_s3_request_metrics_get_total_duration_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_total_duration);

/* Get the time stamp when the request started to be encoded. Timestamps are from `aws_high_res_clock_get_ticks`
 * AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if the request ended before it gets sent. */
AWS_S3_API
int aws_s3_request_metrics_get_send_start_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_send_start_time);

/* Get the time stamp when the request finished to be encoded. Timestamps are from `aws_high_res_clock_get_ticks`
 * AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_send_end_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_send_end_time);

/* The time duration for the request from start encoding to finish encoding (send_end_timestamp_ns -
 * send_start_timestamp_ns).
 * AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_sending_duration_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_sending_duration);

/* Get the time stamp when the response started to be received from the network channel. Timestamps are from
 * `aws_high_res_clock_get_ticks` AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_receive_start_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_receive_start_time);

/* Get the time stamp when the response finished to be received from the network channel. Timestamps are from
 * `aws_high_res_clock_get_ticks` AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_receive_end_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_receive_end_time);

/* Get the time stamp when the signing for the request started. Timestamps are from
 * `aws_high_res_clock_get_ticks` AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_sign_start_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_signing_start_time);

/* Get the time stamp when the signing for the request ended. Timestamps are from
 * `aws_high_res_clock_get_ticks` AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_sign_end_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_signing_end_time);

/* The time duration for the request singing (sign_end_timestamp_ns -
 * sign_start_timestamp_ns).
 * AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_signing_duration_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_signing_duration);

/* Get the time stamp when the memory acquisition for the request started. Timestamps are from
 * `aws_high_res_clock_get_ticks` AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_mem_acquire_start_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_mem_acquire_start_time);

/* Get the time stamp when the memory acquisition for the request ended. Timestamps are from
 * `aws_high_res_clock_get_ticks` AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_mem_acquire_end_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_mem_acquire_end_time);

/* The time duration for the request memory acquisition (mem_acquire_end_timestamp_ns -
 * mem_acquire_start_timestamp_ns).
 * AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_mem_acquire_duration_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_mem_acquire_duration);

/* Get the time stamp when the delivery of the request response started. Timestamps are from
 * `aws_high_res_clock_get_ticks` AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_delivery_start_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_delivery_start_time);

/* Get the time stamp when the delivery of the request response ended. Timestamps are from
 * `aws_high_res_clock_get_ticks` AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_delivery_end_timestamp_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_delivery_end_time);

/* The time duration for the request delivery(delivery_end_timestamp_ns -
 * delivery_start_timestamp_ns).
 * AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_delivery_duration_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_delivery_duration);

/* The time duration for the request from start receiving to finish receiving (receive_end_timestamp_ns -
 * receive_start_timestamp_ns).
 * AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_receiving_duration_ns(
    const struct aws_s3_request_metrics *metrics,
    uint64_t *out_receiving_duration);

/* Get the response status code for the request. AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data not
 * available. */
AWS_S3_API
int aws_s3_request_metrics_get_response_status_code(
    const struct aws_s3_request_metrics *metrics,
    int *out_response_status);

/* Get the HTTP Headers of the response received for the request. AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised
 * if data not available. */
AWS_S3_API
int aws_s3_request_metrics_get_response_headers(
    const struct aws_s3_request_metrics *metrics,
    struct aws_http_headers **out_response_headers);

/**
 * Get the path and query of the request.
 * If unavailable, AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised.
 * If available, out_request_path_query will be set to a string. Be warned this string's lifetime is tied to the metrics
 * object.
 */
AWS_S3_API
void aws_s3_request_metrics_get_request_path_query(
    const struct aws_s3_request_metrics *metrics,
    const struct aws_string **out_request_path_query);

/**
 * Get the host_address of the request.
 * If unavailable, AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised.
 * If available, out_host_address will be set to a string. Be warned this string's lifetime is tied to the metrics
 * object.
 */
AWS_S3_API
void aws_s3_request_metrics_get_host_address(
    const struct aws_s3_request_metrics *metrics,
    const struct aws_string **out_host_address);

/**
 * Get the IP address of the request connected to.
 * If unavailable, AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised.
 * If available, out_ip_address will be set to a string. Be warned this string's lifetime is tied to the metrics object.
 */
AWS_S3_API
int aws_s3_request_metrics_get_ip_address(
    const struct aws_s3_request_metrics *metrics,
    const struct aws_string **out_ip_address);

/* Get the id of connection that request was made from. AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if data
 * not available */
AWS_S3_API
int aws_s3_request_metrics_get_connection_id(const struct aws_s3_request_metrics *metrics, size_t *out_connection_id);

/* Get the thread ID of the thread that request was made from. AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised if
 * data not available */
AWS_S3_API
int aws_s3_request_metrics_get_thread_id(const struct aws_s3_request_metrics *metrics, aws_thread_id_t *out_thread_id);

/* Get the stream-id, which is the idex when the stream was activated. AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be
 * raised if data not available */
AWS_S3_API
int aws_s3_request_metrics_get_request_stream_id(const struct aws_s3_request_metrics *metrics, uint32_t *out_stream_id);

/**
 * Get the S3 operation name of the request (e.g. "HeadObject").
 * If unavailable, AWS_ERROR_S3_METRIC_DATA_NOT_AVAILABLE will be raised.
 * If available, out_operation_name will be set to a string.
 * Be warned this string's lifetime is tied to the metrics object.
 */
AWS_S3_API
int aws_s3_request_metrics_get_operation_name(
    const struct aws_s3_request_metrics *metrics,
    const struct aws_string **out_operation_name);

/* Get the request type from request metrics.
 * If you just need a string, aws_s3_request_metrics_get_operation_name() is more reliable. */
AWS_S3_API
void aws_s3_request_metrics_get_request_type(
    const struct aws_s3_request_metrics *metrics,
    enum aws_s3_request_type *out_request_type);

/* Get the AWS CRT error code from request metrics. */
AWS_S3_API
int aws_s3_request_metrics_get_error_code(const struct aws_s3_request_metrics *metrics);

AWS_EXTERN_C_END
AWS_POP_SANE_WARNING_LEVEL

#endif /* AWS_S3_CLIENT_H */
