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

#include <aws/io/tls_channel_handler.h>

#include <aws/common/encoding.h>
#include <aws/common/math.h>
#include <aws/common/string.h>
#include <aws/common/task_scheduler.h>

#include <aws/io/channel.h>
#include <aws/io/file_utils.h>
#include <aws/io/logging.h>
#include <aws/io/private/pki_utils.h>
#include <aws/io/private/tls_channel_handler_private.h>
#include <aws/io/private/tls_channel_handler_shared.h>
#include <aws/io/statistics.h>

/* To use the SCH_CREDENTIALS structure, define SCHANNEL_USE_BLACKLISTS  */
#define SCHANNEL_USE_BLACKLISTS
#include <windows.h>

#include <subauth.h>

#include <schannel.h>
#include <security.h>
#include <sspi.h>

#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef _MSC_VER
#    pragma warning(disable : 4221) /* aggregate initializer using local variable addresses */
#    pragma warning(disable : 4204) /* non-constant aggregate initializer */
#    pragma warning(disable : 4306) /* Identifier is type cast to a larger pointer. */
#endif

#define KB_1 1024
#define READ_OUT_SIZE (16 * KB_1)
#define READ_IN_SIZE READ_OUT_SIZE
#define EST_HANDSHAKE_SIZE (7 * KB_1)
#define WINDOWS_BUILD_20348 20348

#define EST_TLS_RECORD_OVERHEAD 53 /* 5 byte header + 32 + 16 bytes for padding */

static bool s_use_schannel_creds = false;

void aws_tls_init_static_state(struct aws_allocator *alloc) {
    AWS_LOGF_INFO(AWS_LS_IO_TLS, "static: Initializing TLS using SecureChannel (SSPI).");
    (void)alloc;
}

void aws_tls_clean_up_static_state(void) {}

struct common_credential_params {
    DWORD dwFlags;
    PCCERT_CONTEXT *paCred;
    DWORD cCreds;
};

struct secure_channel_ctx {
    struct aws_tls_ctx ctx;
    struct aws_string *alpn_list;
    struct common_credential_params schannel_creds;
    SCHANNEL_CRED credentials;
    PCCERT_CONTEXT pcerts;
    HCERTSTORE cert_store;
    HCERTSTORE custom_trust_store;
    HCRYPTPROV crypto_provider;
    HCRYPTKEY private_key;
    bool verify_peer;
    bool should_free_pcerts;
    enum aws_tls_versions minimum_tls_version;
    bool disable_tls13;
};

struct secure_channel_handler {
    struct aws_channel_handler handler;
    struct aws_tls_channel_handler_shared shared_state;
    CtxtHandle sec_handle;
    CredHandle creds;
    /*
     * The SSPI API expects an array of len 1 of these where it's the leaf certificate associated with its private
     * key.
     */
    PCCERT_CONTEXT cert_context[1];
    HCERTSTORE cert_store;
    HCERTSTORE custom_ca_store;
    SecPkgContext_StreamSizes stream_sizes;
    unsigned long ctx_req;
    unsigned long ctx_ret_flags;
    struct aws_channel_slot *slot;
    struct aws_byte_buf protocol;
    struct aws_byte_buf server_name;
    TimeStamp sspi_timestamp;
    int (*s_connection_state_fn)(struct aws_channel_handler *handler);
    /*
     * Give a little bit of extra head room, for split records.
     */
    uint8_t buffered_read_in_data[READ_IN_SIZE + KB_1];
    struct aws_byte_buf buffered_read_in_data_buf;
    size_t estimated_incomplete_size;
    size_t read_extra;
    /* This is to accommodate the extra head room we added above.
       because we're allowing for splits, we may have more data decrypted
       than we can fit in this buffer if we don't make them match. */
    uint8_t buffered_read_out_data[READ_OUT_SIZE + KB_1];
    struct aws_byte_buf buffered_read_out_data_buf;
    struct aws_channel_task sequential_task_storage;
    aws_tls_on_negotiation_result_fn *on_negotiation_result;
    aws_tls_on_data_read_fn *on_data_read;
    aws_tls_on_error_fn *on_error;
    struct aws_string *alpn_list;
    void *user_data;
    bool advertise_alpn_message;
    bool negotiation_finished;
    bool verify_peer;
    struct aws_channel_task read_task;
    bool read_task_pending;
    enum aws_tls_handler_read_state read_state;
    int shutdown_error_code;
};

static size_t s_message_overhead(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;

    if (AWS_UNLIKELY(!sc_handler->stream_sizes.cbMaximumMessage)) {
        SECURITY_STATUS status =
            QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_STREAM_SIZES, &sc_handler->stream_sizes);

        if (status != SEC_E_OK) {
            return EST_TLS_RECORD_OVERHEAD;
        }
    }

    return sc_handler->stream_sizes.cbTrailer + sc_handler->stream_sizes.cbHeader;
}

/* Checks whether the current system is running Windows of a specific build number or later.
 *
 * This will continue to be a valid check in the future versions of Windows as the build number continues to increment
 * upwards. E.g., Windows 10 last build number is 19045, and Windows 11 starts at 22000. */
static bool s_is_windows_equal_or_above_build_number(DWORD build_number) {
    NTSTATUS status = STATUS_DLL_NOT_FOUND;

    OSVERSIONINFOEX osvi;
    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    osvi.dwBuildNumber = build_number;

    ULONGLONG dwlConditionMask = 0;
    BYTE op = VER_GREATER_EQUAL;
    dwlConditionMask = VerSetConditionMask(dwlConditionMask, VER_BUILDNUMBER, op);
    typedef NTSTATUS(WINAPI * pRtlGetVersionInfo)(
        OSVERSIONINFOEX * lpVersionInformation, ULONG TypeMask, ULONGLONG ConditionMask);

    pRtlGetVersionInfo f;
    f = (pRtlGetVersionInfo)GetProcAddress(GetModuleHandle("ntdll"), "RtlVerifyVersionInfo");

    if (f) {
        status = f(&osvi, VER_BUILDNUMBER, dwlConditionMask);
    } else {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "Could not load ntdll: Falling back to earlier windows version before build %d",
            build_number);
        status = STATUS_DLL_NOT_FOUND;
    }
    if (status == STATUS_SUCCESS) {
        AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "Checking Windows Version: running windows build %u or later", build_number);
        return true;
    } else if (status != STATUS_DLL_NOT_FOUND) {
        AWS_LOGF_DEBUG(
            AWS_LS_IO_TLS,
            "Checking Windows Version: Running earlier windows 10 version before build %u",
            build_number);
        return false;
    }
    return false;
}

bool aws_tls_is_alpn_available(void) {
/* if you built on an old version of windows, still no support, but if you did, we still
   want to check the OS version at runtime before agreeing to attempt alpn. */
#ifdef SECBUFFER_APPLICATION_PROTOCOLS
    AWS_LOGF_DEBUG(
        AWS_LS_IO_TLS,
        "static: This library was built with Windows 8.1 or later, "
        "probing OS to see what we're actually running on.");
    /* make sure we're on windows 8.1 or later. */
    OSVERSIONINFOEX os_version;
    DWORDLONG condition_mask = 0;
    VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
    VER_SET_CONDITION(condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
    VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
    VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);

    AWS_ZERO_STRUCT(os_version);
    os_version.dwMajorVersion = HIBYTE(_WIN32_WINNT_WIN8);
    os_version.dwMinorVersion = LOBYTE(_WIN32_WINNT_WIN8);
    os_version.wServicePackMajor = 0;
    os_version.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    if (VerifyVersionInfo(
            &os_version,
            VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
            condition_mask)) {
        AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: We're running on Windows 8.1 or later. ALPN is available.");
        return true;
    }

    AWS_LOGF_WARN(
        AWS_LS_IO_TLS,
        "static: Running on older version of windows, ALPN is not supported. "
        "Please update your OS to take advantage of modern features.");
#else
    AWS_LOGF_WARN(
        AWS_LS_IO_TLS,
        "static: This library was built using a Windows SDK prior to 8.1. "
        "Please build with a version of windows >= 8.1 to take advantage modern features. ALPN is not supported.");
#endif /*SECBUFFER_APPLICATION_PROTOCOLS */
    return false;
}

bool aws_tls_is_cipher_pref_supported(enum aws_tls_cipher_pref cipher_pref) {
    switch (cipher_pref) {
        case AWS_IO_TLS_CIPHER_PREF_SYSTEM_DEFAULT:
            return true;

        case AWS_IO_TLS_CIPHER_PREF_KMS_PQ_TLSv1_0_2019_06:
        default:
            return false;
    }
}

/* technically we could lower this, but lets be forgiving */
#define MAX_HOST_LENGTH 255

/* this only gets called if the user specified a custom ca. */
static int s_manually_verify_peer_cert(struct aws_channel_handler *handler) {
    AWS_LOGF_DEBUG(
        AWS_LS_IO_TLS,
        "id=%p: manually verifying certifcate chain because a custom CA is configured.",
        (void *)handler);
    struct secure_channel_handler *sc_handler = handler->impl;

    int result = AWS_OP_ERR;
    CERT_CONTEXT *peer_certificate = NULL;
    HCERTCHAINENGINE engine = NULL;
    PCCERT_CHAIN_CONTEXT cert_chain_ctx = NULL;

    /* get the peer's certificate so we can validate it.*/
    SECURITY_STATUS status =
        QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &peer_certificate);

    if (status != SEC_E_OK || !peer_certificate) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: failed to load peer's certificate with SECURITY_STATUS %d",
            (void *)handler,
            (int)status);
        return AWS_OP_ERR;
    }

    /* this next bit scours the custom trust store to try and load a chain to verify
       the leaf certificate against. */
    CERT_CHAIN_ENGINE_CONFIG engine_config;
    AWS_ZERO_STRUCT(engine_config);
    engine_config.cbSize = sizeof(engine_config);
    engine_config.hExclusiveRoot = sc_handler->custom_ca_store;

    if (!CertCreateCertificateChainEngine(&engine_config, &engine)) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: failed to load a certificate chain engine with SECURITY_STATUS %d. "
            "Most likely, the configured CA is corrupted.",
            (void *)handler,
            (int)status);
        goto done;
    }

    /*
     * TODO: Investigate CRL options further on a per-platform basis.  Add control APIs if appropriate.
     */
    DWORD get_chain_flags = 0;

    /* mimic chromium here since we intend for this to be used generally */
    const LPCSTR usage_identifiers[] = {
        szOID_PKIX_KP_SERVER_AUTH,
        szOID_SERVER_GATED_CRYPTO,
        szOID_SGC_NETSCAPE,
    };

    CERT_CHAIN_PARA chain_params;
    AWS_ZERO_STRUCT(chain_params);
    chain_params.cbSize = sizeof(chain_params);
    chain_params.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
    chain_params.RequestedUsage.Usage.cUsageIdentifier = AWS_ARRAY_SIZE(usage_identifiers);
    chain_params.RequestedUsage.Usage.rgpszUsageIdentifier = (LPSTR *)usage_identifiers;

    if (!CertGetCertificateChain(
            engine,
            peer_certificate,
            NULL,
            peer_certificate->hCertStore,
            &chain_params,
            get_chain_flags,
            NULL,
            &cert_chain_ctx)) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: unable to find certificate in chain with SECURITY_STATUS %d.",
            (void *)handler,
            (int)status);
        goto done;
    }

    struct aws_byte_buf host = aws_tls_handler_server_name(handler);
    if (host.len > MAX_HOST_LENGTH) {
        AWS_LOGF_ERROR(AWS_LS_IO_TLS, "id=%p: host name too long (%d).", (void *)handler, (int)host.len);
        goto done;
    }

    wchar_t whost[MAX_HOST_LENGTH + 1];
    AWS_ZERO_ARRAY(whost);

    int converted = MultiByteToWideChar(
        CP_UTF8, MB_ERR_INVALID_CHARS, (const char *)host.buffer, (int)host.len, whost, AWS_ARRAY_SIZE(whost));
    if ((size_t)converted != host.len) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: unable to convert host to wstr, %d -> %d, with last error 0x%x.",
            (void *)handler,
            (int)host.len,
            (int)converted,
            (int)GetLastError());
        goto done;
    }

    /* check if the chain was trusted */
    LPCSTR policyiod = CERT_CHAIN_POLICY_SSL;

    SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslpolicy;
    AWS_ZERO_STRUCT(sslpolicy);
    sslpolicy.cbSize = sizeof(sslpolicy);
    sslpolicy.dwAuthType = AUTHTYPE_SERVER;
    sslpolicy.fdwChecks = 0;
    sslpolicy.pwszServerName = whost;

    CERT_CHAIN_POLICY_PARA policypara;
    AWS_ZERO_STRUCT(policypara);
    policypara.cbSize = sizeof(policypara);
    policypara.dwFlags = 0;
    policypara.pvExtraPolicyPara = &sslpolicy;

    CERT_CHAIN_POLICY_STATUS policystatus;
    AWS_ZERO_STRUCT(policystatus);
    policystatus.cbSize = sizeof(policystatus);

    if (!CertVerifyCertificateChainPolicy(policyiod, cert_chain_ctx, &policypara, &policystatus)) {
        int error = GetLastError();
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS, "id=%p: CertVerifyCertificateChainPolicy() failed, error 0x%x", (void *)handler, (int)error);
        goto done;
    }

    if (policystatus.dwError) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: certificate verification failed, error 0x%x",
            (void *)handler,
            (int)policystatus.dwError);
        goto done;
    }

    /* if the chain was trusted, then we're good to go, if it was not
       we bail out. */
    CERT_SIMPLE_CHAIN *simple_chain = cert_chain_ctx->rgpChain[0];
    DWORD trust_mask = ~(DWORD)CERT_TRUST_IS_NOT_TIME_NESTED;
    trust_mask &= simple_chain->TrustStatus.dwErrorStatus;

    if (trust_mask != 0) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: peer certificate is un-trusted with SECURITY_STATUS %d.",
            (void *)handler,
            (int)trust_mask);
        goto done;
    }

    AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: peer certificate is trusted.", (void *)handler);
    result = AWS_OP_SUCCESS;

done:

    if (cert_chain_ctx != NULL) {
        CertFreeCertificateChain(cert_chain_ctx);
    }

    if (engine != NULL) {
        CertFreeCertificateChainEngine(engine);
    }

    if (peer_certificate != NULL) {
        CertFreeCertificateContext(peer_certificate);
    }

    return result;
}

static void s_invoke_negotiation_error(struct aws_channel_handler *handler, int err) {
    struct secure_channel_handler *sc_handler = handler->impl;

    aws_on_tls_negotiation_completed(&sc_handler->shared_state, err);

    if (sc_handler->on_negotiation_result) {
        sc_handler->on_negotiation_result(handler, sc_handler->slot, err, sc_handler->user_data);
    }
}

static void s_on_negotiation_success(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;

    /* if the user provided an ALPN handler to the channel, we need to let them know what their protocol is. */
    if (sc_handler->slot->adj_right && sc_handler->advertise_alpn_message && sc_handler->protocol.len) {
        struct aws_io_message *message = aws_channel_acquire_message_from_pool(
            sc_handler->slot->channel,
            AWS_IO_MESSAGE_APPLICATION_DATA,
            sizeof(struct aws_tls_negotiated_protocol_message));
        message->message_tag = AWS_TLS_NEGOTIATED_PROTOCOL_MESSAGE;
        struct aws_tls_negotiated_protocol_message *protocol_message =
            (struct aws_tls_negotiated_protocol_message *)message->message_data.buffer;

        protocol_message->protocol = sc_handler->protocol;
        message->message_data.len = sizeof(struct aws_tls_negotiated_protocol_message);
        if (aws_channel_slot_send_message(sc_handler->slot, message, AWS_CHANNEL_DIR_READ)) {
            aws_mem_release(message->allocator, message);
            aws_channel_shutdown(sc_handler->slot->channel, aws_last_error());
        }
    }

    aws_on_tls_negotiation_completed(&sc_handler->shared_state, AWS_ERROR_SUCCESS);

    if (sc_handler->on_negotiation_result) {
        sc_handler->on_negotiation_result(handler, sc_handler->slot, AWS_OP_SUCCESS, sc_handler->user_data);
    }
}

static int s_determine_sspi_error(int sspi_status) {
    switch (sspi_status) {
        case SEC_E_INSUFFICIENT_MEMORY:
            return AWS_ERROR_OOM;
        case SEC_I_CONTEXT_EXPIRED:
            return AWS_IO_TLS_ALERT_NOT_GRACEFUL;
        case SEC_E_WRONG_PRINCIPAL:
            return AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE;
            /*
            case SEC_E_INVALID_HANDLE:
            case SEC_E_INVALID_TOKEN:
            case SEC_E_LOGON_DENIED:
            case SEC_E_TARGET_UNKNOWN:
            case SEC_E_NO_AUTHENTICATING_AUTHORITY:
            case SEC_E_INTERNAL_ERROR:
            case SEC_E_NO_CREDENTIALS:
            case SEC_E_UNSUPPORTED_FUNCTION:
            case SEC_E_APPLICATION_PROTOCOL_MISMATCH:
            */
        default:
            return AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE;
    }
}

#define CHECK_ALPN_BUFFER_SIZE(s, i, b)                                                                                \
    if (s <= i) {                                                                                                      \
        aws_array_list_clean_up(&b);                                                                                   \
        return aws_raise_error(AWS_ERROR_SHORT_BUFFER);                                                                \
    }

/* construct ALPN extension data... apparently this works on big-endian machines? but I don't believe the docs
   if you're running ARM and you find ALPN isn't working, it's probably because I trusted the documentation
   and your bug is in here. Note, dotnet's corefx also acts like endianness isn't at play so if this is broken
   so is everyone's dotnet code. */
static int s_fillin_alpn_data(
    struct aws_channel_handler *handler,
    unsigned char *alpn_buffer_data,
    size_t buffer_size,
    size_t *written) {
    *written = 0;
    struct secure_channel_handler *sc_handler = handler->impl;
    AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "");

    struct aws_array_list alpn_buffers;
    struct aws_byte_cursor alpn_buffer_array[4];
    aws_array_list_init_static(&alpn_buffers, alpn_buffer_array, 4, sizeof(struct aws_byte_cursor));

    AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "Setting ALPN extension with string %s.", aws_string_c_str(sc_handler->alpn_list));
    struct aws_byte_cursor alpn_str_cur = aws_byte_cursor_from_string(sc_handler->alpn_list);
    if (aws_byte_cursor_split_on_char(&alpn_str_cur, ';', &alpn_buffers)) {
        return AWS_OP_ERR;
    }

    size_t protocols_count = aws_array_list_length(&alpn_buffers);

    size_t index = 0;
    CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint32_t), alpn_buffers)
    uint32_t *extension_length = (uint32_t *)&alpn_buffer_data[index];
    index += sizeof(uint32_t);
    CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint32_t), alpn_buffers)
    uint32_t *extension_name = (uint32_t *)&alpn_buffer_data[index];
    index += sizeof(uint32_t);
    CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint32_t), alpn_buffers)
    uint16_t *protocols_byte_length = (uint16_t *)&alpn_buffer_data[index];
    index += sizeof(uint16_t);
    CHECK_ALPN_BUFFER_SIZE(buffer_size, index + sizeof(uint16_t), alpn_buffers)

    *extension_length += sizeof(uint32_t) + sizeof(uint16_t);

    *extension_name = SecApplicationProtocolNegotiationExt_ALPN;
    /*now add the protocols*/
    for (size_t i = 0; i < protocols_count; ++i) {
        struct aws_byte_cursor *protocol_ptr = NULL;
        aws_array_list_get_at_ptr(&alpn_buffers, (void **)&protocol_ptr, i);
        AWS_ASSERT(protocol_ptr);
        *extension_length += (uint32_t)protocol_ptr->len + 1;
        *protocols_byte_length += (uint16_t)protocol_ptr->len + 1;
        CHECK_ALPN_BUFFER_SIZE(buffer_size, index + 1, alpn_buffers)
        alpn_buffer_data[index++] = (unsigned char)protocol_ptr->len;
        CHECK_ALPN_BUFFER_SIZE(buffer_size, index + protocol_ptr->len, alpn_buffers)
        memcpy(alpn_buffer_data + index, protocol_ptr->ptr, protocol_ptr->len);
        index += protocol_ptr->len;
    }

    aws_array_list_clean_up(&alpn_buffers);
    *written = *extension_length + sizeof(uint32_t);
    return AWS_OP_SUCCESS;
}

static int s_do_application_data_decrypt(struct aws_channel_handler *handler);

static int s_do_server_side_negotiation_step_2(struct aws_channel_handler *handler);

/** invoked during the first step of the server's negotiation. It receives the client hello,
    adds its alpn data if available, and if everything is good, sends out the server hello. */
static int s_do_server_side_negotiation_step_1(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;
    AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: server starting negotiation", (void *)handler);

    aws_on_drive_tls_negotiation(&sc_handler->shared_state);

    /* set this, and goto cleanup, if function encounters an error */
    int aws_error = 0;

    unsigned char alpn_buffer_data[128] = {0};
    SecBuffer input_bufs[] = {
        {
            .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer,
            .cbBuffer = (unsigned long)sc_handler->buffered_read_in_data_buf.len,
            .BufferType = SECBUFFER_TOKEN,
        },
        {
            .pvBuffer = NULL,
            .cbBuffer = 0,
            .BufferType = SECBUFFER_EMPTY,
        },
    };

    SecBufferDesc input_bufs_desc = {
        .ulVersion = SECBUFFER_VERSION,
        .cBuffers = 2,
        .pBuffers = input_bufs,
    };

    SecBuffer output_buffer = {
        .pvBuffer = NULL,
        .cbBuffer = 0,
        .BufferType = SECBUFFER_TOKEN,
    };

    SecBufferDesc output_buffer_desc = {
        .ulVersion = SECBUFFER_VERSION,
        .cBuffers = 1,
        .pBuffers = &output_buffer,
    };

#ifdef SECBUFFER_APPLICATION_PROTOCOLS
    if (sc_handler->alpn_list && aws_tls_is_alpn_available()) {
        AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: Setting ALPN to %s", handler, aws_string_c_str(sc_handler->alpn_list));
        size_t extension_length = 0;
        if (s_fillin_alpn_data(handler, alpn_buffer_data, sizeof(alpn_buffer_data), &extension_length)) {
            aws_error = aws_last_error();
            goto cleanup;
        }

        input_bufs[1].pvBuffer = alpn_buffer_data, input_bufs[1].cbBuffer = (unsigned long)extension_length,
        input_bufs[1].BufferType = SECBUFFER_APPLICATION_PROTOCOLS;
    }
#endif /* SECBUFFER_APPLICATION_PROTOCOLS*/

    sc_handler->ctx_req = ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY |
                          ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_STREAM;

    if (sc_handler->verify_peer) {
        AWS_LOGF_DEBUG(
            AWS_LS_IO_TLS,
            "id=%p: server configured to use mutual tls, expecting a certficate from client.",
            (void *)handler);
        sc_handler->ctx_req |= ASC_REQ_MUTUAL_AUTH;
    }

    /* process the client hello. */
    SECURITY_STATUS status = AcceptSecurityContext(
        &sc_handler->creds,
        NULL,
        &input_bufs_desc,
        sc_handler->ctx_req,
        0,
        &sc_handler->sec_handle,
        &output_buffer_desc,
        &sc_handler->ctx_ret_flags,
        NULL);

    if (!(status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK)) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: error during processing of the ClientHello. SECURITY_STATUS is %d",
            (void *)handler,
            (int)status);
        aws_error = s_determine_sspi_error(status);
        goto cleanup;
    }

    size_t data_to_write_len = output_buffer.cbBuffer;

    AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Sending ServerHello. Data size %zu", (void *)handler, data_to_write_len);
    /* send the server hello. */
    struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool(
        sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, data_to_write_len);

    AWS_FATAL_ASSERT(outgoing_message->message_data.capacity >= data_to_write_len);
    memcpy(outgoing_message->message_data.buffer, output_buffer.pvBuffer, output_buffer.cbBuffer);
    outgoing_message->message_data.len = output_buffer.cbBuffer;

    if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) {
        aws_error = aws_last_error();
        aws_mem_release(outgoing_message->allocator, outgoing_message);
        goto cleanup;
    }

    sc_handler->s_connection_state_fn = s_do_server_side_negotiation_step_2;

cleanup:
    FreeContextBuffer(output_buffer.pvBuffer);
    if (aws_error != 0) {
        s_invoke_negotiation_error(handler, aws_error);
        return aws_raise_error(aws_error);
    }
    return AWS_OP_SUCCESS;
}

/* cipher change, key exchange, mutual TLS stuff. */
static int s_do_server_side_negotiation_step_2(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;

    /* set this, and goto cleanup, if function encounters an error */
    int aws_error = 0;

    AWS_LOGF_TRACE(
        AWS_LS_IO_TLS, "id=%p: running step 2 of negotiation (cipher change, key exchange etc...)", (void *)handler);
    SecBuffer input_buffers[] = {
        [0] =
            {
                .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer,
                .cbBuffer = (unsigned long)sc_handler->buffered_read_in_data_buf.len,
                .BufferType = SECBUFFER_TOKEN,
            },
        [1] =
            {
                .pvBuffer = NULL,
                .cbBuffer = 0,
                .BufferType = SECBUFFER_EMPTY,
            },
    };

    SecBufferDesc input_buffers_desc = {
        .ulVersion = SECBUFFER_VERSION,
        .cBuffers = 2,
        .pBuffers = input_buffers,
    };

    SecBuffer output_buffers[3];
    AWS_ZERO_ARRAY(output_buffers);
    output_buffers[0].BufferType = SECBUFFER_TOKEN;
    output_buffers[1].BufferType = SECBUFFER_ALERT;

    SecBufferDesc output_buffers_desc = {
        .ulVersion = SECBUFFER_VERSION,
        .cBuffers = 3,
        .pBuffers = output_buffers,
    };

    sc_handler->read_extra = 0;
    sc_handler->estimated_incomplete_size = 0;

    SECURITY_STATUS status = AcceptSecurityContext(
        &sc_handler->creds,
        &sc_handler->sec_handle,
        &input_buffers_desc,
        sc_handler->ctx_req,
        0,
        NULL,
        &output_buffers_desc,
        &sc_handler->ctx_ret_flags,
        &sc_handler->sspi_timestamp);

    if (status != SEC_E_INCOMPLETE_MESSAGE && status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS, "id=%p: Error during negotiation. SECURITY_STATUS is %d", (void *)handler, (int)status);
        aws_error = s_determine_sspi_error(status);
        goto cleanup;
    }

    if (status == SEC_E_INCOMPLETE_MESSAGE) {
        AWS_LOGF_TRACE(
            AWS_LS_IO_TLS, "id=%p: Last processed buffer was incomplete, waiting on more data.", (void *)handler);
        sc_handler->estimated_incomplete_size = input_buffers[1].cbBuffer;
        aws_error = AWS_IO_READ_WOULD_BLOCK;
        goto cleanup;
    };
    /* any output buffers that were filled in with SECBUFFER_TOKEN need to be sent,
       SECBUFFER_EXTRA means we need to account for extra data and shift everything for the next run. */
    if (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK) {
        for (size_t i = 0; i < output_buffers_desc.cBuffers; ++i) {
            SecBuffer *buf_ptr = &output_buffers[i];

            if (buf_ptr->BufferType == SECBUFFER_TOKEN && buf_ptr->cbBuffer) {
                struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool(
                    sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, buf_ptr->cbBuffer);

                AWS_FATAL_ASSERT(outgoing_message->message_data.capacity >= buf_ptr->cbBuffer);
                memcpy(outgoing_message->message_data.buffer, buf_ptr->pvBuffer, buf_ptr->cbBuffer);
                outgoing_message->message_data.len = buf_ptr->cbBuffer;

                if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) {
                    aws_error = aws_last_error();
                    aws_mem_release(outgoing_message->allocator, outgoing_message);
                    goto cleanup;
                }
            }
        }

        if (input_buffers[1].BufferType == SECBUFFER_EXTRA && input_buffers[1].cbBuffer > 0) {
            AWS_LOGF_TRACE(
                AWS_LS_IO_TLS,
                "id=%p: Extra data recieved. Extra size is %lu",
                (void *)handler,
                input_buffers[1].cbBuffer);
            sc_handler->read_extra = input_buffers[1].cbBuffer;
        }
    }

    if (status == SEC_E_OK) {
        AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: handshake completed", (void *)handler);
        /* if a custom CA store was configured, we have to do the verification ourselves. */
        if (sc_handler->custom_ca_store) {
            AWS_LOGF_TRACE(
                AWS_LS_IO_TLS,
                "id=%p: Custom CA was configured, evaluating trust before completing connection",
                (void *)handler);

            if (s_manually_verify_peer_cert(handler)) {
                aws_error = AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE;
                goto cleanup;
            }
        }
        sc_handler->negotiation_finished = true;

        /* force query of the sizes so future calls to encrypt will be loaded. */
        s_message_overhead(handler);

        /*
           grab the negotiated protocol out of the session.
        */
#ifdef SECBUFFER_APPLICATION_PROTOCOLS
        if (sc_handler->alpn_list && aws_tls_is_alpn_available()) {
            SecPkgContext_ApplicationProtocol alpn_result;
            status = QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result);
            AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: ALPN is configured. Checking for negotiated protocol", handler);

            if (status == SEC_E_OK) {
                if (alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) {
                    aws_byte_buf_init(&sc_handler->protocol, handler->alloc, alpn_result.ProtocolIdSize + 1);
                    memset(sc_handler->protocol.buffer, 0, alpn_result.ProtocolIdSize + 1);
                    memcpy(sc_handler->protocol.buffer, alpn_result.ProtocolId, alpn_result.ProtocolIdSize);
                    sc_handler->protocol.len = alpn_result.ProtocolIdSize;
                    AWS_LOGF_DEBUG(
                        AWS_LS_IO_TLS, "id=%p: negotiated protocol %s", handler, (char *)sc_handler->protocol.buffer);
                } else {
                    /* this is not an error */
                    AWS_LOGF_INFO(
                        AWS_LS_IO_TLS, "id=%p: ALPN - no protocol was negotiated during TLS handshake", handler);
                }
            } else {
                AWS_LOGF_WARN(
                    AWS_LS_IO_TLS,
                    "id=%p: Error retrieving negotiated protocol. SECURITY_STATUS is %d",
                    handler,
                    (int)status);
                aws_error = s_determine_sspi_error(status);
                goto cleanup;
            }
        }
#endif
        sc_handler->s_connection_state_fn = s_do_application_data_decrypt;
        AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: TLS handshake completed successfully.", (void *)handler);
        s_on_negotiation_success(handler);
    }

cleanup:
    for (size_t i = 0; i < output_buffers_desc.cBuffers; ++i) {
        FreeContextBuffer(output_buffers[i].pvBuffer);
    }

    if (aws_error != 0) {
        if (aws_error != AWS_IO_READ_WOULD_BLOCK) {
            s_invoke_negotiation_error(handler, aws_error);
        }
        return aws_raise_error(aws_error);
    }
    return AWS_OP_SUCCESS;
}

static int s_do_client_side_negotiation_step_2(struct aws_channel_handler *handler);

/* send the client hello */
static int s_do_client_side_negotiation_step_1(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;
    AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: client starting negotiation", (void *)handler);

    aws_on_drive_tls_negotiation(&sc_handler->shared_state);

    /* set this, and goto cleanup, if function encounters an error */
    int aws_error = 0;

    unsigned char alpn_buffer_data[128] = {0};
    SecBuffer input_buf = {
        .pvBuffer = NULL,
        .cbBuffer = 0,
        .BufferType = SECBUFFER_EMPTY,
    };

    SecBufferDesc input_buf_desc = {
        .ulVersion = SECBUFFER_VERSION,
        .cBuffers = 1,
        .pBuffers = &input_buf,
    };

    SecBuffer output_buffer = {
        .pvBuffer = NULL,
        .cbBuffer = 0,
        .BufferType = SECBUFFER_EMPTY,
    };

    SecBufferDesc output_buffer_desc = {
        .ulVersion = SECBUFFER_VERSION,
        .cBuffers = 1,
        .pBuffers = &output_buffer,
    };

    SecBufferDesc *alpn_sspi_data = NULL;

    /* add alpn data to the client hello if it's supported. */
#ifdef SECBUFFER_APPLICATION_PROTOCOLS
    if (sc_handler->alpn_list && aws_tls_is_alpn_available()) {
        AWS_LOGF_DEBUG(
            AWS_LS_IO_TLS, "id=%p: Setting ALPN data as %s", handler, aws_string_c_str(sc_handler->alpn_list));
        size_t extension_length = 0;
        if (s_fillin_alpn_data(handler, alpn_buffer_data, sizeof(alpn_buffer_data), &extension_length)) {
            aws_error = aws_last_error();
            goto cleanup;
        }

        input_buf.pvBuffer = alpn_buffer_data, input_buf.cbBuffer = (unsigned long)extension_length,
        input_buf.BufferType = SECBUFFER_APPLICATION_PROTOCOLS;

        alpn_sspi_data = &input_buf_desc;
    }
#endif /* SECBUFFER_APPLICATION_PROTOCOLS*/

    sc_handler->ctx_req = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY |
                          ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;

    char server_name_cstr[256];
    AWS_ZERO_ARRAY(server_name_cstr);
    AWS_ASSERT(sc_handler->server_name.len < 256);
    memcpy(server_name_cstr, sc_handler->server_name.buffer, sc_handler->server_name.len);

    SECURITY_STATUS status = InitializeSecurityContextA(
        &sc_handler->creds,
        NULL,
        (SEC_CHAR *)server_name_cstr,
        sc_handler->ctx_req,
        0,
        0,
        alpn_sspi_data,
        0,
        &sc_handler->sec_handle,
        &output_buffer_desc,
        &sc_handler->ctx_ret_flags,
        &sc_handler->sspi_timestamp);

    if (status != SEC_I_CONTINUE_NEEDED) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS,
            "id=%p: Error sending client/receiving server handshake data. SECURITY_STATUS is %d",
            (void *)handler,
            (int)status);
        aws_error = s_determine_sspi_error(status);
        goto cleanup;
    }

    size_t data_to_write_len = output_buffer.cbBuffer;
    AWS_LOGF_TRACE(
        AWS_LS_IO_TLS, "id=%p: Sending client handshake data of size %zu", (void *)handler, data_to_write_len);

    struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool(
        sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, data_to_write_len);

    AWS_FATAL_ASSERT(outgoing_message->message_data.capacity >= data_to_write_len);
    memcpy(outgoing_message->message_data.buffer, output_buffer.pvBuffer, output_buffer.cbBuffer);
    outgoing_message->message_data.len = output_buffer.cbBuffer;

    if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) {
        aws_error = aws_last_error();
        aws_mem_release(outgoing_message->allocator, outgoing_message);
        goto cleanup;
    }

    sc_handler->s_connection_state_fn = s_do_client_side_negotiation_step_2;

cleanup:
    FreeContextBuffer(output_buffer.pvBuffer);
    if (aws_error != 0) {
        s_invoke_negotiation_error(handler, aws_error);
        return aws_raise_error(aws_error);
    }
    return AWS_OP_SUCCESS;
}

/* cipher exchange, key exchange etc.... */
static int s_do_client_side_negotiation_step_2(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;
    AWS_LOGF_TRACE(
        AWS_LS_IO_TLS,
        "id=%p: running step 2 of client-side negotiation (cipher change, key exchange etc...)",
        (void *)handler);

    /* set this, and goto cleanup, if function encounters an error */
    int aws_error = 0;

    SecBuffer input_buffers[] = {
        [0] =
            {
                .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer,
                .cbBuffer = (unsigned long)sc_handler->buffered_read_in_data_buf.len,
                .BufferType = SECBUFFER_TOKEN,
            },
        [1] =
            {
                .pvBuffer = NULL,
                .cbBuffer = 0,
                .BufferType = SECBUFFER_EMPTY,
            },
    };

    SecBufferDesc input_buffers_desc = {
        .ulVersion = SECBUFFER_VERSION,
        .cBuffers = 2,
        .pBuffers = input_buffers,
    };

    SecBuffer output_buffers[3];
    AWS_ZERO_ARRAY(output_buffers);
    output_buffers[0].BufferType = SECBUFFER_TOKEN;
    output_buffers[1].BufferType = SECBUFFER_ALERT;

    SecBufferDesc output_buffers_desc = {
        .ulVersion = SECBUFFER_VERSION,
        .cBuffers = 3,
        .pBuffers = output_buffers,
    };

    SECURITY_STATUS status = SEC_E_OK;

    sc_handler->read_extra = 0;
    sc_handler->estimated_incomplete_size = 0;

    char server_name_cstr[256];
    AWS_ZERO_ARRAY(server_name_cstr);
    AWS_FATAL_ASSERT(sc_handler->server_name.len < sizeof(server_name_cstr));
    memcpy(server_name_cstr, sc_handler->server_name.buffer, sc_handler->server_name.len);

    status = InitializeSecurityContextA(
        &sc_handler->creds,
        &sc_handler->sec_handle,
        (SEC_CHAR *)server_name_cstr,
        sc_handler->ctx_req,
        0,
        0,
        &input_buffers_desc,
        0,
        NULL,
        &output_buffers_desc,
        &sc_handler->ctx_ret_flags,
        &sc_handler->sspi_timestamp);
    if (status != SEC_E_INCOMPLETE_MESSAGE && status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_TLS, "id=%p: Error during negotiation. SECURITY_STATUS is %d", (void *)handler, (int)status);
        aws_error = s_determine_sspi_error(status);
        goto cleanup;
    }

    if (status == SEC_E_INCOMPLETE_MESSAGE) {
        sc_handler->estimated_incomplete_size = input_buffers[1].cbBuffer;
        AWS_LOGF_TRACE(
            AWS_LS_IO_TLS,
            "id=%p: Incomplete buffer recieved. Incomplete size is %zu. Waiting for more data.",
            (void *)handler,
            sc_handler->estimated_incomplete_size);
        aws_error = AWS_IO_READ_WOULD_BLOCK;
        goto cleanup;
    }

    if (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK) {
        for (size_t i = 0; i < output_buffers_desc.cBuffers; ++i) {
            SecBuffer *buf_ptr = &output_buffers[i];

            if (buf_ptr->BufferType == SECBUFFER_TOKEN && buf_ptr->cbBuffer) {
                struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool(
                    sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, buf_ptr->cbBuffer);

                AWS_FATAL_ASSERT(outgoing_message->message_data.capacity >= buf_ptr->cbBuffer);
                memcpy(outgoing_message->message_data.buffer, buf_ptr->pvBuffer, buf_ptr->cbBuffer);
                outgoing_message->message_data.len = buf_ptr->cbBuffer;

                if (aws_channel_slot_send_message(sc_handler->slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) {
                    aws_mem_release(outgoing_message->allocator, outgoing_message);
                    aws_error = aws_last_error();
                    goto cleanup;
                }
            }
        }

        if (input_buffers[1].BufferType == SECBUFFER_EXTRA && input_buffers[1].cbBuffer > 0) {
            AWS_LOGF_TRACE(
                AWS_LS_IO_TLS,
                "id=%p: Extra data recieved. Extra data size is %lu.",
                (void *)handler,
                input_buffers[1].cbBuffer);
            sc_handler->read_extra = input_buffers[1].cbBuffer;
        }
    }

    if (status == SEC_E_OK) {
        AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: handshake completed", handler);
        /* if a custom CA store was configured, we have to do the verification ourselves. */
        if (sc_handler->custom_ca_store) {
            AWS_LOGF_TRACE(
                AWS_LS_IO_TLS,
                "id=%p: Custom CA was configured, evaluating trust before completing connection",
                (void *)handler);
            if (s_manually_verify_peer_cert(handler)) {
                aws_error = AWS_IO_TLS_ERROR_NEGOTIATION_FAILURE;
                goto cleanup;
            }
        }
        sc_handler->negotiation_finished = true;
        /* force the sizes query, so future Encrypt message calls work.*/
        s_message_overhead(handler);

#ifdef SECBUFFER_APPLICATION_PROTOCOLS
        if (sc_handler->alpn_list && aws_tls_is_alpn_available()) {
            AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Retrieving negotiated protocol.", handler);
            SecPkgContext_ApplicationProtocol alpn_result;
            SECURITY_STATUS alpn_status =
                QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result);

            if (alpn_status == SEC_E_OK) {
                if (alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) {
                    aws_byte_buf_init(&sc_handler->protocol, handler->alloc, alpn_result.ProtocolIdSize + 1);
                    memset(sc_handler->protocol.buffer, 0, alpn_result.ProtocolIdSize + 1);
                    memcpy(sc_handler->protocol.buffer, alpn_result.ProtocolId, alpn_result.ProtocolIdSize);
                    sc_handler->protocol.len = alpn_result.ProtocolIdSize;
                    AWS_LOGF_DEBUG(
                        AWS_LS_IO_TLS, "id=%p: Negotiated protocol %s", handler, (char *)sc_handler->protocol.buffer);
                } else {
                    /* this is not an error */
                    AWS_LOGF_INFO(
                        AWS_LS_IO_TLS, "id=%p: ALPN - no negotiated protocol was returned by remote endpoint", handler);
                }
            } else {
                AWS_LOGF_WARN(
                    AWS_LS_IO_TLS,
                    "id=%p: Error retrieving negotiated protocol. SECURITY_STATUS is %d",
                    handler,
                    (int)alpn_status);
                aws_error = s_determine_sspi_error(alpn_status);
                goto cleanup;
            }
        }
#endif
        AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: TLS handshake completed successfully.", (void *)handler);
        sc_handler->s_connection_state_fn = s_do_application_data_decrypt;
        s_on_negotiation_success(handler);
    }

cleanup:
    for (size_t i = 0; i < output_buffers_desc.cBuffers; ++i) {
        FreeContextBuffer(output_buffers[i].pvBuffer);
    }

    if (aws_error != 0) {
        if (aws_error != AWS_IO_READ_WOULD_BLOCK) {
            s_invoke_negotiation_error(handler, aws_error);
        }
        return aws_raise_error(aws_error);
    }
    return AWS_OP_SUCCESS;
}

static int s_do_application_data_decrypt(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;

    /* I know this is an unnecessary initialization, it's initialized here to make linters happy.*/
    int error = AWS_OP_ERR;
    /* when we get an Extra buffer we have to move the pointer and replay the buffer, so we loop until we don't have
       any extra buffers left over, in the last phase, we then go ahead and send the output. This state function will
       always say BLOCKED_ON_READ, AWS_IO_TLS_ERROR_READ_FAILURE or SUCCESS. There will never be left over reads.*/
    do {
        error = AWS_OP_ERR;
        /* 4 buffers are needed, only one is input, the others get zeroed out for the output operation. */
        SecBuffer input_buffers[4];
        AWS_ZERO_ARRAY(input_buffers);

        size_t read_len = sc_handler->read_extra ? sc_handler->read_extra : sc_handler->buffered_read_in_data_buf.len;
        size_t offset = sc_handler->read_extra ? sc_handler->buffered_read_in_data_buf.len - sc_handler->read_extra : 0;
        sc_handler->read_extra = 0;

        input_buffers[0] = (SecBuffer){
            .cbBuffer = (unsigned long)(read_len),
            .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer + offset,
            .BufferType = SECBUFFER_DATA,
        };

        SecBufferDesc buffer_desc = {
            .ulVersion = SECBUFFER_VERSION,
            .cBuffers = 4,
            .pBuffers = input_buffers,
        };

        SECURITY_STATUS status = DecryptMessage(&sc_handler->sec_handle, &buffer_desc, 0, NULL);

        if (status == SEC_E_OK) {
            error = AWS_OP_SUCCESS;
            /* if SECBUFFER_DATA is the buffer type of the second buffer, we have decrypted data to process.
               If SECBUFFER_EXTRA is the type for the fourth buffer we need to keep track of it so we can shift
               everything before doing another decrypt operation.
               We don't care what's in the third buffer for TLS usage.*/
            if (input_buffers[1].BufferType == SECBUFFER_DATA) {
                size_t decrypted_length = input_buffers[1].cbBuffer;
                AWS_LOGF_TRACE(
                    AWS_LS_IO_TLS, "id=%p: Decrypted message with length %zu.", (void *)handler, decrypted_length);

                struct aws_byte_cursor to_append =
                    aws_byte_cursor_from_array(input_buffers[1].pvBuffer, decrypted_length);
                int append_failed = aws_byte_buf_append(&sc_handler->buffered_read_out_data_buf, &to_append);
                AWS_FATAL_ASSERT(!append_failed);
                /* if we have extra we have to move the pointer and do another Decrypt operation. */
                if (input_buffers[3].BufferType == SECBUFFER_EXTRA && input_buffers[3].cbBuffer > 0) {
                    sc_handler->read_extra = input_buffers[3].cbBuffer;
                    AWS_LOGF_TRACE(
                        AWS_LS_IO_TLS,
                        "id=%p: Extra (incomplete) message received with length %zu.",
                        (void *)handler,
                        sc_handler->read_extra);
                } else {
                    error = AWS_OP_SUCCESS;
                    /* this means we processed everything in the buffer. */
                    sc_handler->buffered_read_in_data_buf.len = 0;
                    AWS_LOGF_TRACE(
                        AWS_LS_IO_TLS,
                        "id=%p: Decrypt ended exactly on the end of the record, resetting buffer.",
                        (void *)handler);
                }
            }
        }
        /* With TLS1.3 on SChannel a call to DecryptMessage can return SEC_I_RENEGOTIATE, at this point a client must
         * call again InitializeSecurityContext with the data received from DecryptMessage until SEC_E_OK is received */
        else if (status == SEC_I_RENEGOTIATE) {
            AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Renegotiation received", (void *)handler);

            /* if SECBUFFER_DATA is the buffer type of the second buffer, we have decrypted data to process. */
            if (input_buffers[1].BufferType == SECBUFFER_DATA) {
                size_t decrypted_length = input_buffers[1].cbBuffer;
                AWS_LOGF_TRACE(
                    AWS_LS_IO_TLS, "id=%p: Decrypted message with length %zu.", (void *)handler, decrypted_length);

                struct aws_byte_cursor to_append =
                    aws_byte_cursor_from_array(input_buffers[1].pvBuffer, decrypted_length);
                int append_failed = aws_byte_buf_append(&sc_handler->buffered_read_out_data_buf, &to_append);
                AWS_FATAL_ASSERT(!append_failed);
            }

            /*
             * The following diagram visualizes a case, when DecryptMessage returns SEC_I_RENEGOTIATE while processing
             * extra data (i.e. we're at the second or later iteration of the loop). This case is not necessarily
             * possible, but it helps with understanding the logic of processing received data.
             *
             * At the second+ iteration, algorithm percepts sc_handler->buffered_read_in_data_buf like this:
             * +-----------------------+----------------+-----------------+
             * |        offset         | decrypted data | SECBUFFER_EXTRA |
             * +-----------------------+----------------+-----------------+
             * | data was decrypted on |                                  |
             * | previous iterations   |        current iteration         |
             *
             *  ----------------------------------------------------------   sc_handler->buffered_read_in_data_buf,
             *                                                                 a whole chunk of data read from a socket
             *  -----------------------                                      part of buffer that was decrypted on the
             *                                                                 previous step(s)
             *                          ----------------------------------   SECBUFFER_EXTRA from the previous step,
             *                                                                 with length read_len
             *
             *  We need to pass only the last segment, SECBUFFER_EXTRA, to InitializeSecurityContextA.
             */
            size_t extra_data_offset = offset;
            if (input_buffers[3].BufferType == SECBUFFER_EXTRA && input_buffers[3].cbBuffer > 0 &&
                input_buffers[3].cbBuffer < read_len) {
                extra_data_offset = offset + read_len - input_buffers[3].cbBuffer;
            }

            SecBuffer input_buffers2[] = {
                [0] =
                    {
                        .pvBuffer = sc_handler->buffered_read_in_data_buf.buffer + extra_data_offset,
                        .cbBuffer = (unsigned long)(sc_handler->buffered_read_in_data_buf.len - extra_data_offset),
                        .BufferType = SECBUFFER_TOKEN,
                    },
                [1] =
                    {
                        .pvBuffer = NULL,
                        .cbBuffer = 0,
                        .BufferType = SECBUFFER_EMPTY,
                    },
            };

            SecBufferDesc input_bufs_desc = {
                .ulVersion = SECBUFFER_VERSION,
                .cBuffers = 2,
                .pBuffers = input_buffers2,
            };

            SecBuffer output_buffers[3];
            AWS_ZERO_ARRAY(output_buffers);
            output_buffers[0].BufferType = SECBUFFER_TOKEN;
            output_buffers[1].BufferType = SECBUFFER_ALERT;
            output_buffers[2].BufferType = SECBUFFER_EMPTY;

            SecBufferDesc output_buffers_desc = {
                .ulVersion = SECBUFFER_VERSION,
                .cBuffers = 3,
                .pBuffers = output_buffers,
            };
            char server_name_cstr[256];
            AWS_ZERO_ARRAY(server_name_cstr);
            AWS_FATAL_ASSERT(sc_handler->server_name.len < sizeof(server_name_cstr));
            memcpy(server_name_cstr, sc_handler->server_name.buffer, sc_handler->server_name.len);

            status = InitializeSecurityContextA(
                &sc_handler->creds,
                &sc_handler->sec_handle,
                (SEC_CHAR *)server_name_cstr,
                sc_handler->ctx_req,
                0,
                0,
                &input_bufs_desc,
                0,
                NULL,
                &output_buffers_desc,
                &sc_handler->ctx_ret_flags,
                NULL);
            if (status == SEC_E_OK) {
                error = AWS_OP_SUCCESS;
                /* If renegotiating InitializeSecurityContextA returns SECBUFFER_EXTRA data, we should process it as
                 * usual, i.e. pass to DecryptMessage on the next iteration. */
                if (input_buffers2[1].BufferType == SECBUFFER_EXTRA) {
                    sc_handler->read_extra = input_buffers2[1].cbBuffer;
                }
                AWS_LOGF_TRACE(
                    AWS_LS_IO_TLS,
                    "id=%p: Successfully renegotiated; got %zu bytes of extra data to decrypt",
                    (void *)handler,
                    sc_handler->read_extra);

            } else {
                AWS_LOGF_ERROR(
                    AWS_LS_IO_TLS,
                    "id=%p: Error InitializeSecurityContext after renegotiation. status %d",
                    (void *)handler,
                    (int)status);
                error = AWS_OP_ERR;
                break;
            }
        }
        /* SEC_E_INCOMPLETE_MESSAGE means the message we tried to decrypt isn't a full record and we need to
           append our next read to it and try again. */
        else if (status == SEC_E_INCOMPLETE_MESSAGE) {
            sc_handler->estimated_incomplete_size = input_buffers[1].cbBuffer;
            AWS_LOGF_TRACE(
                AWS_LS_IO_TLS,
                "id=%p: (incomplete) message received. Expecting remaining portion of size %zu.",
                (void *)handler,
                sc_handler->estimated_incomplete_size);
            memmove(
                sc_handler->buffered_read_in_data_buf.buffer,
                sc_handler->buffered_read_in_data_buf.buffer + offset,
                read_len);
            sc_handler->buffered_read_in_data_buf.len = read_len;
            aws_raise_error(AWS_IO_READ_WOULD_BLOCK);
        }
        /* SEC_I_CONTEXT_EXPIRED means that the message sender has shut down the connection.  One such case
           where this can happen is an unaccepted certificate. */
        else if (status == SEC_I_CONTEXT_EXPIRED) {
            AWS_LOGF_TRACE(
                AWS_LS_IO_TLS,
                "id=%p: Alert received. Message sender has shut down the connection. SECURITY_STATUS is %d.",
                (void *)handler,
                (int)status);

            struct aws_channel_slot *slot = handler->slot;
            aws_channel_shutdown(slot->channel, AWS_OP_SUCCESS);
            error = AWS_OP_SUCCESS;
        } else {
            AWS_LOGF_ERROR(
                AWS_LS_IO_TLS, "id=%p: Error decrypting message. SECURITY_STATUS is %d.", (void *)handler, (int)status);
            aws_raise_error(AWS_IO_TLS_ERROR_READ_FAILURE);
        }
    } while (sc_handler->read_extra);

    return error;
}

static int s_process_pending_output_messages(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;
    if (sc_handler->read_state == AWS_TLS_HANDLER_READ_SHUT_DOWN_COMPLETE) {
        return AWS_OP_SUCCESS;
    }

    size_t downstream_window = SIZE_MAX;
    int error_code = 0;

    if (sc_handler->slot->adj_right) {
        downstream_window = aws_channel_slot_downstream_read_window(sc_handler->slot);
    }

    AWS_LOGF_TRACE(
        AWS_LS_IO_TLS,
        "id=%p: Processing incomming messages. Downstream window is %zu",
        (void *)handler,
        downstream_window);
    while (sc_handler->buffered_read_out_data_buf.len && downstream_window) {
        size_t requested_message_size = sc_handler->buffered_read_out_data_buf.len > downstream_window
                                            ? downstream_window
                                            : sc_handler->buffered_read_out_data_buf.len;
        AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Requested message size is %zu", (void *)handler, requested_message_size);

        if (sc_handler->slot->adj_right) {
            struct aws_io_message *read_out_msg = aws_channel_acquire_message_from_pool(
                sc_handler->slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, requested_message_size);

            size_t copy_size = read_out_msg->message_data.capacity < requested_message_size
                                   ? read_out_msg->message_data.capacity
                                   : requested_message_size;

            memcpy(read_out_msg->message_data.buffer, sc_handler->buffered_read_out_data_buf.buffer, copy_size);
            read_out_msg->message_data.len = copy_size;

            memmove(
                sc_handler->buffered_read_out_data_buf.buffer,
                sc_handler->buffered_read_out_data_buf.buffer + copy_size,
                sc_handler->buffered_read_out_data_buf.len - copy_size);
            sc_handler->buffered_read_out_data_buf.len -= copy_size;

            if (sc_handler->on_data_read) {
                sc_handler->on_data_read(handler, sc_handler->slot, &read_out_msg->message_data, sc_handler->user_data);
            }
            if (aws_channel_slot_send_message(sc_handler->slot, read_out_msg, AWS_CHANNEL_DIR_READ)) {
                aws_mem_release(read_out_msg->allocator, read_out_msg);
                error_code = aws_last_error();
                goto done;
            }

            if (sc_handler->slot->adj_right) {
                downstream_window = aws_channel_slot_downstream_read_window(sc_handler->slot);
            }
            AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Downstream window is %zu", (void *)handler, downstream_window);
        } else {
            if (sc_handler->on_data_read) {
                sc_handler->on_data_read(
                    handler, sc_handler->slot, &sc_handler->buffered_read_out_data_buf, sc_handler->user_data);
            }
            sc_handler->buffered_read_out_data_buf.len = 0;
        }
    }

    if (sc_handler->buffered_read_out_data_buf.len > 0) {
        /* Still have more data to be delivered */
        return AWS_OP_SUCCESS;
    }

done:
    if (sc_handler->read_state == AWS_TLS_HANDLER_READ_SHUTTING_DOWN) {
        sc_handler->read_state = AWS_TLS_HANDLER_READ_SHUT_DOWN_COMPLETE;
        /* Continue the shutdown process delayed before. */

        /* Propagate the original error code if it is set. */
        int shutdown_error_code = sc_handler->shutdown_error_code ? sc_handler->shutdown_error_code : error_code;

        aws_channel_slot_on_handler_shutdown_complete(
            sc_handler->slot, AWS_CHANNEL_DIR_READ, shutdown_error_code, false);
    }

    /* If there was an error, re-raise it, in case some other function call modified aws_last_error() */
    return error_code ? aws_raise_error(error_code) : AWS_OP_SUCCESS;
}

static void s_process_pending_output_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) {
    (void)task;
    struct aws_channel_handler *handler = arg;
    struct secure_channel_handler *sc_handler = handler->impl;
    sc_handler->read_task_pending = false;

    aws_channel_task_init(task, NULL, NULL, "secure_channel_handler_process_pending_output");
    if (status == AWS_TASK_STATUS_RUN_READY) {
        if (s_process_pending_output_messages(handler)) {
            aws_channel_shutdown(sc_handler->slot->channel, aws_last_error());
        }
    }
}

static int s_process_read_message(
    struct aws_channel_handler *handler,
    struct aws_channel_slot *slot,
    struct aws_io_message *message) {

    AWS_ASSERT(message);
    struct secure_channel_handler *sc_handler = handler->impl;

    if (sc_handler->read_state == AWS_TLS_HANDLER_READ_SHUT_DOWN_COMPLETE) {
        aws_mem_release(message->allocator, message);
        return AWS_OP_SUCCESS;
    }

    /* note, most of these functions log internally, so the log messages in this function are sparse. */
    AWS_LOGF_TRACE(
        AWS_LS_IO_TLS, "id=%p: processing incoming message of size %zu", (void *)handler, message->message_data.len);

    struct aws_byte_cursor message_cursor = aws_byte_cursor_from_buf(&message->message_data);

    /* The SSPI interface forces us to manage incomplete records manually. So when we had extra after
        the previous read, it needs to be shifted to the beginning of the current read, then the current
        read data is appended to it. If we had an incomplete record, we don't need to shift anything but
        we do need to append the current read data to the end of the incomplete record from the previous read.
        Keep going until we've processed everything in the message we were just passed.
        */
    int err = AWS_OP_SUCCESS;
    while (!err && message_cursor.len) {
        /* copy as much data as possible into buffered_read_in_data_buf */
        aws_byte_buf_write_to_capacity(&sc_handler->buffered_read_in_data_buf, &message_cursor);

        /* decrypt */
        bool record_is_incomplete = false;
        err = sc_handler->s_connection_state_fn(handler);
        if (err) {
            /* AWS_IO_READ_WOULD_BLOCK isn't fatal, it just means the record is incomplete */
            if (aws_last_error() == AWS_IO_READ_WOULD_BLOCK) {
                err = AWS_OP_SUCCESS;
                record_is_incomplete = true;
            } else {
                break;
            }
        }

        /* if any data was decrypted, try to send it downstream */
        if (sc_handler->buffered_read_out_data_buf.len) {
            err = s_process_pending_output_messages(handler);
            if (err) {
                break;
            }
        }

        if (record_is_incomplete) {
            /* if our buffer is full, but the record is still incomplete ... throw this one as a protocol error. */
            if (sc_handler->buffered_read_in_data_buf.len == sc_handler->buffered_read_in_data_buf.capacity) {
                err = aws_raise_error(AWS_IO_TLS_ERROR_WRITE_FAILURE);
                break;
            }

            /* prevent a deadlock due to downstream handlers wanting more data, but we have an incomplete
                record, and the amount they're requesting is less than the size of a tls record. */
            size_t downstream_window =
                sc_handler->slot->adj_right ? aws_channel_slot_downstream_read_window(sc_handler->slot) : SIZE_MAX;
            if (downstream_window > 0 && slot->window_size == 0) {
                err = aws_channel_slot_increment_read_window(slot, sc_handler->estimated_incomplete_size);
                if (err) {
                    break;
                }
            }
        } else {
            /* we had a complete record. handle any left over extra data from the decrypt operation here. */
            if (sc_handler->read_extra) {
                size_t move_pos = sc_handler->buffered_read_in_data_buf.len - sc_handler->read_extra;
                memmove(
                    sc_handler->buffered_read_in_data_buf.buffer,
                    sc_handler->buffered_read_in_data_buf.buffer + move_pos,
                    sc_handler->read_extra);
                sc_handler->buffered_read_in_data_buf.len = sc_handler->read_extra;
                sc_handler->read_extra = 0;
            } else {
                sc_handler->buffered_read_in_data_buf.len = 0;
            }
        }
    }

    if (err) {
        aws_channel_shutdown(slot->channel, aws_last_error());
        return AWS_OP_ERR;
    }

    aws_mem_release(message->allocator, message);
    return AWS_OP_SUCCESS;
}

static int s_process_write_message(
    struct aws_channel_handler *handler,
    struct aws_channel_slot *slot,
    struct aws_io_message *message) {

    struct secure_channel_handler *sc_handler = (struct secure_channel_handler *)handler->impl;
    AWS_ASSERT(sc_handler->negotiation_finished);
    SECURITY_STATUS status = SEC_E_OK;

    if (message) {
        AWS_LOGF_TRACE(
            AWS_LS_IO_TLS, "id=%p: processing ougoing message of size %zu", (void *)handler, message->message_data.len);

        struct aws_byte_cursor message_cursor = aws_byte_cursor_from_buf(&message->message_data);

        while (message_cursor.len) {
            AWS_LOGF_TRACE(
                AWS_LS_IO_TLS, "id=%p: processing message fragment of size %zu", (void *)handler, message_cursor.len);
            /* message size will be the lesser of either payload + record overhead or the max TLS record size.*/
            size_t upstream_overhead = aws_channel_slot_upstream_message_overhead(sc_handler->slot);
            upstream_overhead += sc_handler->stream_sizes.cbHeader + sc_handler->stream_sizes.cbTrailer;
            size_t requested_length = message_cursor.len + upstream_overhead;
            size_t to_write = sc_handler->stream_sizes.cbMaximumMessage < requested_length
                                  ? sc_handler->stream_sizes.cbMaximumMessage
                                  : requested_length;
            struct aws_io_message *outgoing_message =
                aws_channel_acquire_message_from_pool(slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, to_write);

            if (outgoing_message->message_data.capacity <= upstream_overhead) {
                aws_mem_release(outgoing_message->allocator, outgoing_message);
                return aws_raise_error(AWS_ERROR_INVALID_STATE);
            }

            /* what if message is larger than one record? */
            size_t original_message_fragment_to_process = outgoing_message->message_data.capacity - upstream_overhead;
            memcpy(
                outgoing_message->message_data.buffer + sc_handler->stream_sizes.cbHeader,
                message_cursor.ptr,
                original_message_fragment_to_process);

            if (original_message_fragment_to_process == message_cursor.len) {
                outgoing_message->on_completion = message->on_completion;
                outgoing_message->user_data = message->user_data;
            }

            SecBuffer buffers[4] = {
                [0] =
                    {
                        .BufferType = SECBUFFER_STREAM_HEADER,
                        .pvBuffer = outgoing_message->message_data.buffer,
                        .cbBuffer = sc_handler->stream_sizes.cbHeader,
                    },
                [1] =
                    {
                        .BufferType = SECBUFFER_DATA,
                        .pvBuffer = outgoing_message->message_data.buffer + sc_handler->stream_sizes.cbHeader,
                        .cbBuffer = (unsigned long)original_message_fragment_to_process,
                    },
                [2] =
                    {
                        .BufferType = SECBUFFER_STREAM_TRAILER,
                        .pvBuffer = outgoing_message->message_data.buffer + sc_handler->stream_sizes.cbHeader +
                                    original_message_fragment_to_process,
                        .cbBuffer = sc_handler->stream_sizes.cbTrailer,
                    },
                [3] =
                    {
                        .BufferType = SECBUFFER_EMPTY,
                        .pvBuffer = NULL,
                        .cbBuffer = 0,
                    },
            };

            SecBufferDesc buffer_desc = {
                .ulVersion = SECBUFFER_VERSION,
                .cBuffers = 4,
                .pBuffers = buffers,
            };

            status = EncryptMessage(&sc_handler->sec_handle, 0, &buffer_desc, 0);

            if (status == SEC_E_OK) {
                outgoing_message->message_data.len = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer;
                AWS_LOGF_TRACE(
                    AWS_LS_IO_TLS,
                    "id=%p:message fragment encrypted successfully: size is %zu",
                    (void *)handler,
                    outgoing_message->message_data.len);

                if (aws_channel_slot_send_message(slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) {
                    aws_mem_release(outgoing_message->allocator, outgoing_message);
                    return AWS_OP_ERR;
                }

                aws_byte_cursor_advance(&message_cursor, original_message_fragment_to_process);
            } else {
                AWS_LOGF_TRACE(
                    AWS_LS_IO_TLS,
                    "id=%p: Error encrypting message. SECURITY_STATUS is %d",
                    (void *)handler,
                    (int)status);
                return aws_raise_error(AWS_IO_TLS_ERROR_WRITE_FAILURE);
            }
        }

        aws_mem_release(message->allocator, message);
    }

    return AWS_OP_SUCCESS;
}

static int s_increment_read_window(struct aws_channel_handler *handler, struct aws_channel_slot *slot, size_t size) {
    (void)size;
    struct secure_channel_handler *sc_handler = handler->impl;
    if (sc_handler->read_state == AWS_TLS_HANDLER_READ_SHUT_DOWN_COMPLETE) {
        return AWS_OP_SUCCESS;
    }
    AWS_LOGF_TRACE(AWS_LS_IO_TLS, "id=%p: Increment read window message received %zu", (void *)handler, size);

    /* You can't query a context if negotiation isn't completed, since ciphers haven't been negotiated
     * and it couldn't possibly know the overhead size yet. */
    if (sc_handler->negotiation_finished && !sc_handler->stream_sizes.cbMaximumMessage) {
        SECURITY_STATUS status =
            QueryContextAttributes(&sc_handler->sec_handle, SECPKG_ATTR_STREAM_SIZES, &sc_handler->stream_sizes);

        if (status != SEC_E_OK) {
            AWS_LOGF_ERROR(
                AWS_LS_IO_TLS, "id=%p: QueryContextAttributes failed with error %d", (void *)handler, (int)status);
            aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
            aws_channel_shutdown(slot->channel, AWS_ERROR_SYS_CALL_FAILURE);
            return AWS_OP_ERR;
        }
    }

    size_t total_desired_size = size;
    size_t downstream_size = aws_channel_slot_downstream_read_window(slot);
    size_t current_window_size = slot->window_size;

    /* the only time this branch isn't taken is when a window update is propagated during tls negotiation.
     * in that case just pass it through. */
    if (sc_handler->stream_sizes.cbMaximumMessage) {
        size_t likely_records_count = (size_t)ceil((double)(downstream_size) / (double)(READ_IN_SIZE));
        size_t offset_size = aws_mul_size_saturating(
            likely_records_count, sc_handler->stream_sizes.cbTrailer + sc_handler->stream_sizes.cbHeader);
        total_desired_size = aws_add_size_saturating(offset_size, downstream_size);
    }

    if (total_desired_size > current_window_size) {
        size_t window_update_size = total_desired_size - current_window_size;
        AWS_LOGF_TRACE(
            AWS_LS_IO_TLS, "id=%p: Propagating read window increment of size %zu", (void *)handler, window_update_size);
        aws_channel_slot_increment_read_window(slot, window_update_size);
    }

    if (sc_handler->negotiation_finished && !sc_handler->read_task_pending) {
        /* TLS requires full records before it can decrypt anything. As a result we need to check everything we've
         * buffered instead of just waiting on a read from the socket, or we'll hit a deadlock.
         */
        sc_handler->read_task_pending = true;
        aws_channel_task_init(
            &sc_handler->read_task,
            s_process_pending_output_task,
            handler,
            "secure_channel_handler_process_pending_output_on_window_increment");
        aws_channel_schedule_task_now(slot->channel, &sc_handler->read_task);
    }
    return AWS_OP_SUCCESS;
}

static size_t s_initial_window_size(struct aws_channel_handler *handler) {
    (void)handler;

    /* set this to just enough for the handshake, once the handshake completes, the downstream
       handler will tell us the new window size. */
    return EST_HANDSHAKE_SIZE;
}

static void s_initialize_read_delay_shutdown(
    struct aws_channel_handler *handler,
    struct aws_channel_slot *slot,
    int error_code) {
    struct secure_channel_handler *sc_handler = handler->impl;
    /**
     * In case of if we have any queued data in the handler after negotiation and we start to shutdown,
     * make sure we pass those data down the pipeline before we complete the shutdown.
     */
    AWS_LOGF_DEBUG(
        AWS_LS_IO_TLS,
        "id=%p: TLS handler still have pending data to be delivered during shutdown. Wait until downstream "
        "reads the data.",
        (void *)handler);
    if (aws_channel_slot_downstream_read_window(slot) == 0) {
        AWS_LOGF_WARN(
            AWS_LS_IO_TLS,
            "id=%p: TLS shutdown delayed. Pending data cannot be processed until the flow-control window opens. "
            " Your application may hang if the read window never opens",
            (void *)handler);
    }
    sc_handler->read_state = AWS_TLS_HANDLER_READ_SHUTTING_DOWN;
    sc_handler->shutdown_error_code = error_code;
    if (!sc_handler->read_task_pending) {
        /* Kick off read, in case data arrives with TLS negotiation. Shutdown starts right after negotiation.
         * Nothing will kick off read in that case. */
        sc_handler->read_task_pending = true;
        aws_channel_task_init(
            &sc_handler->read_task,
            s_process_pending_output_task,
            handler,
            "secure_channel_handler_read_on_delay_shutdown");

        aws_channel_schedule_task_now(slot->channel, &sc_handler->read_task);
    }
}

static int s_handler_shutdown(
    struct aws_channel_handler *handler,
    struct aws_channel_slot *slot,
    enum aws_channel_direction dir,
    int error_code,
    bool abort_immediately) {
    struct secure_channel_handler *sc_handler = handler->impl;

    SecBuffer output_buffer = {
        .pvBuffer = NULL,
        .cbBuffer = 0,
        .BufferType = SECBUFFER_EMPTY,
    };

    SecBufferDesc output_buffer_desc = {
        .ulVersion = SECBUFFER_VERSION,
        .cBuffers = 1,
        .pBuffers = &output_buffer,
    };

    if (dir == AWS_CHANNEL_DIR_READ) {
        AWS_LOGF_DEBUG(
            AWS_LS_IO_TLS, "id=%p: shutting down read direction with error %d.", (void *)handler, error_code);
        if (!abort_immediately && sc_handler->negotiation_finished && sc_handler->buffered_read_out_data_buf.len &&
            slot->adj_right) {
            s_initialize_read_delay_shutdown(handler, slot, error_code);
            return AWS_OP_SUCCESS;
        }
        sc_handler->read_state = AWS_TLS_HANDLER_READ_SHUT_DOWN_COMPLETE;
    } else {
        /* Shutdown in write direction */
        if (!abort_immediately && error_code != AWS_IO_SOCKET_CLOSED) {
            AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: Shutting down the write direction", (void *)handler);

            /* send a TLS alert. */
            SECURITY_STATUS status;

            DWORD shutdown_code = SCHANNEL_SHUTDOWN;
            SecBuffer shutdown_buffer = {
                .pvBuffer = &shutdown_code,
                .cbBuffer = sizeof(shutdown_code),
                .BufferType = SECBUFFER_TOKEN,
            };

            SecBufferDesc shutdown_buffer_desc = {
                .ulVersion = SECBUFFER_VERSION,
                .cBuffers = 1,
                .pBuffers = &shutdown_buffer,
            };

            /* this updates the SSPI internal state machine. */
            status = ApplyControlToken(&sc_handler->sec_handle, &shutdown_buffer_desc);

            if (status != SEC_E_OK) {
                error_code = AWS_ERROR_SYS_CALL_FAILURE;
                goto cleanup;
            }

            struct aws_byte_buf server_name = aws_tls_handler_server_name(handler);
            char server_name_cstr[256];
            AWS_ZERO_ARRAY(server_name_cstr);
            AWS_FATAL_ASSERT(server_name.len < sizeof(server_name_cstr));
            memcpy(server_name_cstr, server_name.buffer, server_name.len);
            /* this acutally gives us an Alert record to send. */
            status = InitializeSecurityContextA(
                &sc_handler->creds,
                &sc_handler->sec_handle,
                (SEC_CHAR *)server_name_cstr,
                sc_handler->ctx_req,
                0,
                0,
                NULL,
                0,
                NULL,
                &output_buffer_desc,
                &sc_handler->ctx_ret_flags,
                NULL);

            if (status == SEC_E_OK || status == SEC_I_CONTEXT_EXPIRED) {
                struct aws_io_message *outgoing_message = aws_channel_acquire_message_from_pool(
                    slot->channel, AWS_IO_MESSAGE_APPLICATION_DATA, output_buffer.cbBuffer);

                if (outgoing_message->message_data.capacity < output_buffer.cbBuffer) {
                    aws_mem_release(outgoing_message->allocator, outgoing_message);
                    error_code = AWS_IO_TLS_ERROR_WRITE_FAILURE;
                    abort_immediately = true;
                    goto cleanup;
                }
                memcpy(outgoing_message->message_data.buffer, output_buffer.pvBuffer, output_buffer.cbBuffer);
                outgoing_message->message_data.len = output_buffer.cbBuffer;

                /* we don't really care if this succeeds or not, it's just sending the TLS alert. */
                if (aws_channel_slot_send_message(slot, outgoing_message, AWS_CHANNEL_DIR_WRITE)) {
                    aws_mem_release(outgoing_message->allocator, outgoing_message);
                    goto cleanup;
                }
            }
        }
    }

cleanup:
    FreeContextBuffer(output_buffer.pvBuffer);
    return aws_channel_slot_on_handler_shutdown_complete(slot, dir, error_code, abort_immediately);
}

static void s_do_negotiation_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) {
    (void)task;

    struct aws_channel_handler *handler = arg;
    struct secure_channel_handler *sc_handler = handler->impl;

    if (status == AWS_TASK_STATUS_RUN_READY) {
        int err = sc_handler->s_connection_state_fn(handler);
        if (err) {
            aws_channel_shutdown(sc_handler->slot->channel, aws_last_error());
        }
    }
}

static void s_secure_channel_handler_destroy(
    struct aws_allocator *allocator,
    struct secure_channel_handler *sc_handler) {

    if (sc_handler == NULL) {
        return;
    }

    if (sc_handler->protocol.buffer) {
        aws_byte_buf_clean_up(&sc_handler->protocol);
    }

    if (sc_handler->alpn_list) {
        aws_string_destroy(sc_handler->alpn_list);
    }

    if (sc_handler->server_name.buffer) {
        aws_byte_buf_clean_up(&sc_handler->server_name);
    }

    if (sc_handler->sec_handle.dwLower || sc_handler->sec_handle.dwUpper) {
        DeleteSecurityContext(&sc_handler->sec_handle);
    }

    if (sc_handler->creds.dwLower || sc_handler->creds.dwUpper) {
        DeleteSecurityContext(&sc_handler->creds);
    }

    aws_tls_channel_handler_shared_clean_up(&sc_handler->shared_state);

    aws_mem_release(allocator, sc_handler);
}

static void s_handler_destroy(struct aws_channel_handler *handler) {
    AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: destroying handler", (void *)handler);
    struct secure_channel_handler *sc_handler = handler->impl;

    s_secure_channel_handler_destroy(handler->alloc, sc_handler);
}

static void s_reset_statistics(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;

    aws_crt_statistics_tls_reset(&sc_handler->shared_state.stats);
}

static void s_gather_statistics(struct aws_channel_handler *handler, struct aws_array_list *stats) {
    struct secure_channel_handler *sc_handler = handler->impl;

    void *stats_base = &sc_handler->shared_state.stats;
    aws_array_list_push_back(stats, &stats_base);
}

int aws_tls_client_handler_start_negotiation(struct aws_channel_handler *handler) {
    AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "id=%p: Kicking off TLS negotiation", (void *)handler);

    struct secure_channel_handler *sc_handler = handler->impl;

    if (aws_channel_thread_is_callers_thread(sc_handler->slot->channel)) {
        int err = sc_handler->s_connection_state_fn(handler);
        if (err) {
            aws_channel_shutdown(sc_handler->slot->channel, aws_last_error());
        }
        return err;
    }

    aws_channel_task_init(
        &sc_handler->sequential_task_storage,
        s_do_negotiation_task,
        handler,
        "secure_channel_handler_start_negotation");
    aws_channel_schedule_task_now(sc_handler->slot->channel, &sc_handler->sequential_task_storage);
    return AWS_OP_SUCCESS;
}

struct aws_byte_buf aws_tls_handler_protocol(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;
    return sc_handler->protocol;
}

struct aws_byte_buf aws_tls_handler_server_name(struct aws_channel_handler *handler) {
    struct secure_channel_handler *sc_handler = handler->impl;
    return sc_handler->server_name;
}

static struct aws_channel_handler_vtable s_handler_vtable = {
    .destroy = s_handler_destroy,
    .process_read_message = s_process_read_message,
    .process_write_message = s_process_write_message,
    .shutdown = s_handler_shutdown,
    .increment_read_window = s_increment_read_window,
    .initial_window_size = s_initial_window_size,
    .message_overhead = s_message_overhead,
    .reset_statistics = s_reset_statistics,
    .gather_statistics = s_gather_statistics,
};

static struct aws_channel_handler *s_tls_handler_new_common(
    struct aws_allocator *alloc,
    struct aws_tls_connection_options *options,
    struct aws_channel_slot *slot,
    bool is_client_mode,
    struct secure_channel_handler *sc_handler) {

    struct secure_channel_ctx *sc_ctx = options->ctx->impl;
    sc_handler->advertise_alpn_message = options->advertise_alpn_message;
    sc_handler->on_data_read = options->on_data_read;
    sc_handler->on_error = options->on_error;
    sc_handler->on_negotiation_result = options->on_negotiation_result;
    sc_handler->user_data = options->user_data;

    if (!options->alpn_list && sc_ctx->alpn_list) {
        sc_handler->alpn_list = aws_string_new_from_string(alloc, sc_ctx->alpn_list);
        if (!sc_handler->alpn_list) {
            goto on_error;
        }
    } else if (options->alpn_list) {
        sc_handler->alpn_list = aws_string_new_from_string(alloc, options->alpn_list);
        if (!sc_handler->alpn_list) {
            goto on_error;
        }
    }
    sc_handler->handler.alloc = alloc;
    sc_handler->handler.impl = sc_handler;
    sc_handler->handler.vtable = &s_handler_vtable;
    sc_handler->handler.slot = slot;

    if (options->server_name) {
        AWS_LOGF_DEBUG(
            AWS_LS_IO_TLS,
            "id=%p: Setting SNI to %s",
            (void *)&sc_handler->handler,
            aws_string_c_str(options->server_name));
        struct aws_byte_cursor server_name_crsr = aws_byte_cursor_from_string(options->server_name);
        if (aws_byte_buf_init_copy_from_cursor(&sc_handler->server_name, alloc, server_name_crsr)) {
            goto on_error;
        }
    }

    sc_handler->slot = slot;

    if (is_client_mode) {
        sc_handler->s_connection_state_fn = s_do_client_side_negotiation_step_1;
    } else {
        sc_handler->s_connection_state_fn = s_do_server_side_negotiation_step_1;
    }

    sc_handler->custom_ca_store = sc_ctx->custom_trust_store;
    sc_handler->buffered_read_in_data_buf =
        aws_byte_buf_from_array(sc_handler->buffered_read_in_data, sizeof(sc_handler->buffered_read_in_data));
    sc_handler->buffered_read_in_data_buf.len = 0;
    sc_handler->buffered_read_out_data_buf =
        aws_byte_buf_from_array(sc_handler->buffered_read_out_data, sizeof(sc_handler->buffered_read_out_data));
    sc_handler->buffered_read_out_data_buf.len = 0;
    sc_handler->verify_peer = sc_ctx->verify_peer;

    return &sc_handler->handler;

on_error:

    s_secure_channel_handler_destroy(alloc, sc_handler);

    return NULL;
}

static DWORD s_get_disabled_protocols(
    enum aws_tls_versions minimum_tls_version,
    bool is_client_mode,
    bool disable_tls13) {
    DWORD bit_disabled_protocols = 0;
    if (is_client_mode) {
        switch (minimum_tls_version) {
            case AWS_IO_TLSv1_3:
#if defined(SP_PROT_TLS1_2_CLIENT)
                bit_disabled_protocols |= SP_PROT_TLS1_2_CLIENT;
#endif
            case AWS_IO_TLSv1_2:
                bit_disabled_protocols |= SP_PROT_TLS1_1_CLIENT;
            case AWS_IO_TLSv1_1:
                bit_disabled_protocols |= SP_PROT_TLS1_0_CLIENT;
            case AWS_IO_TLSv1:
                bit_disabled_protocols |= SP_PROT_SSL3_CLIENT;
            case AWS_IO_SSLv3:
                break;
            case AWS_IO_TLS_VER_SYS_DEFAULTS:
                bit_disabled_protocols = 0;
                break;
        }
#if defined(SP_PROT_TLS1_3_CLIENT)
        if (disable_tls13) {
            bit_disabled_protocols |= SP_PROT_TLS1_3_CLIENT;
        }
#endif
    } else {
        switch (minimum_tls_version) {
            case AWS_IO_TLSv1_3:
#if defined(SP_PROT_TLS1_2_SERVER)
                bit_disabled_protocols |= SP_PROT_TLS1_2_SERVER;
#endif
            case AWS_IO_TLSv1_2:
                bit_disabled_protocols |= SP_PROT_TLS1_1_SERVER;
            case AWS_IO_TLSv1_1:
                bit_disabled_protocols |= SP_PROT_TLS1_0_SERVER;
            case AWS_IO_TLSv1:
                bit_disabled_protocols |= SP_PROT_SSL3_SERVER;
            case AWS_IO_SSLv3:
                break;
            case AWS_IO_TLS_VER_SYS_DEFAULTS:
                bit_disabled_protocols = 0;
                break;
        }
#if defined(SP_PROT_TLS1_3_SERVER)
        if (disable_tls13) {
            bit_disabled_protocols |= SP_PROT_TLS1_3_SERVER;
        }
#endif
    }
    return bit_disabled_protocols;
}

static DWORD s_get_enabled_protocols(enum aws_tls_versions minimum_tls_version, bool is_client_mode) {
    DWORD bit_enabled_protocols = 0;
    if (is_client_mode) {
        switch (minimum_tls_version) {
            case AWS_IO_SSLv3:
                bit_enabled_protocols |= SP_PROT_SSL3_CLIENT;
            case AWS_IO_TLSv1:
                bit_enabled_protocols |= SP_PROT_TLS1_0_CLIENT;
            case AWS_IO_TLSv1_1:
                bit_enabled_protocols |= SP_PROT_TLS1_1_CLIENT;
            case AWS_IO_TLSv1_2:
#if defined(SP_PROT_TLS1_2_CLIENT)
                bit_enabled_protocols |= SP_PROT_TLS1_2_CLIENT;
#endif
            case AWS_IO_TLSv1_3:
                /* This function is used for SCHANNEL_CRED only, which doesn't support TLS 1.3. */
                break;
            case AWS_IO_TLS_VER_SYS_DEFAULTS:
                bit_enabled_protocols = 0;
                break;
        }
    } else {
        switch (minimum_tls_version) {
            case AWS_IO_SSLv3:
                bit_enabled_protocols |= SP_PROT_SSL3_SERVER;
            case AWS_IO_TLSv1:
                bit_enabled_protocols |= SP_PROT_TLS1_0_SERVER;
            case AWS_IO_TLSv1_1:
                bit_enabled_protocols |= SP_PROT_TLS1_1_SERVER;
            case AWS_IO_TLSv1_2:
#if defined(SP_PROT_TLS1_2_SERVER)
                bit_enabled_protocols |= SP_PROT_TLS1_2_SERVER;
#endif
            case AWS_IO_TLSv1_3:
                /* This function is used for SCHANNEL_CRED only, which doesn't support TLS 1.3. */
                break;
            case AWS_IO_TLS_VER_SYS_DEFAULTS:
                bit_enabled_protocols = 0;
                break;
        }
    }
    return bit_enabled_protocols;
}

#if NTDDI_VERSION >= 0x0A000006 /* Windows SDK 10.1.17763.0 or later */

/* The SCH_CREDENTIALS and few other structures became available starting with Windows SDK 10.1.17763.0. */
static struct aws_channel_handler *s_tls_handler_sch_credentials_new(
    struct aws_allocator *alloc,
    struct aws_tls_connection_options *options,
    struct aws_channel_slot *slot,
    bool is_client_mode) {

    AWS_ASSERT(options->ctx);

    struct secure_channel_handler *sc_handler = aws_mem_calloc(alloc, 1, sizeof(struct secure_channel_handler));
    if (!sc_handler) {
        return NULL;
    }
    struct secure_channel_ctx *sc_ctx = options->ctx->impl;

    SCH_CREDENTIALS credentials = {0};

    ZeroMemory(&credentials, sizeof(SCH_CREDENTIALS));

    TLS_PARAMETERS tls_params = {0};
    tls_params.grbitDisabledProtocols =
        s_get_disabled_protocols(sc_ctx->minimum_tls_version, is_client_mode, sc_ctx->disable_tls13);

    credentials.pTlsParameters = &tls_params;
    credentials.cTlsParameters = 1;
    credentials.dwSessionLifespan = 0; /* default 10 hours */
    credentials.dwVersion = SCH_CREDENTIALS_VERSION;
    credentials.dwCredFormat = 0;
    credentials.dwFlags = sc_ctx->schannel_creds.dwFlags;
    credentials.paCred = sc_ctx->schannel_creds.paCred;
    credentials.cCreds = sc_ctx->schannel_creds.cCreds;

    aws_tls_channel_handler_shared_init(&sc_handler->shared_state, &sc_handler->handler, options);

    unsigned long credential_use = SECPKG_CRED_INBOUND;
    if (is_client_mode) {
        credential_use = SECPKG_CRED_OUTBOUND;
    }

    SECURITY_STATUS status = AcquireCredentialsHandleA(
        NULL,
        UNISP_NAME,
        credential_use,
        NULL,
        &credentials,
        NULL,
        NULL,
        &sc_handler->creds,
        &sc_handler->sspi_timestamp);

    if (status != SEC_E_OK) {
        AWS_LOGF_ERROR(AWS_LS_IO_TLS, "Error on AcquireCredentialsHandle. SECURITY_STATUS is 0x%X", (int)status);
        int aws_error = s_determine_sspi_error(status);
        aws_raise_error(aws_error);
        goto on_error;
    }

    return s_tls_handler_new_common(alloc, options, slot, is_client_mode, sc_handler);

on_error:

    s_secure_channel_handler_destroy(alloc, sc_handler);

    return NULL;
}

#endif /* NTDDI_VERSION >= 0x0A000006 */

static struct aws_channel_handler *s_tls_handler_schannel_cred_new(
    struct aws_allocator *alloc,
    struct aws_tls_connection_options *options,
    struct aws_channel_slot *slot,
    bool is_client_mode) {
    AWS_ASSERT(options->ctx);

    struct secure_channel_handler *sc_handler = aws_mem_calloc(alloc, 1, sizeof(struct secure_channel_handler));
    if (!sc_handler) {
        return NULL;
    }

    struct secure_channel_ctx *sc_ctx = options->ctx->impl;

    /* Windows doesn't support TLS 1.3 with deprecated SCHANNEL_CRED. */
    if (sc_ctx->minimum_tls_version == AWS_IO_TLSv1_3) {
        AWS_LOGF_ERROR(AWS_LS_IO_TLS, "Minimum TLS version is set to 1.3, but SCHANNEL_CRED can't support it");
        return NULL;
    }

    SCHANNEL_CRED credentials = {0};
    credentials.dwVersion = SCHANNEL_CRED_VERSION;
    credentials.dwCredFormat = 0;
    credentials.dwFlags = sc_ctx->schannel_creds.dwFlags;
    credentials.paCred = sc_ctx->schannel_creds.paCred;
    credentials.cCreds = sc_ctx->schannel_creds.cCreds;
    sc_ctx->disable_tls13 = true;
    credentials.grbitEnabledProtocols = s_get_enabled_protocols(sc_ctx->minimum_tls_version, is_client_mode);

    aws_tls_channel_handler_shared_init(&sc_handler->shared_state, &sc_handler->handler, options);

    unsigned long credential_use = SECPKG_CRED_INBOUND;
    if (is_client_mode) {
        credential_use = SECPKG_CRED_OUTBOUND;
    }

    SECURITY_STATUS status = AcquireCredentialsHandleA(
        NULL,
        UNISP_NAME,
        credential_use,
        NULL,
        &credentials,
        NULL,
        NULL,
        &sc_handler->creds,
        &sc_handler->sspi_timestamp);

    if (status != SEC_E_OK) {
        AWS_LOGF_ERROR(AWS_LS_IO_TLS, "Error on AcquireCredentialsHandle. SECURITY_STATUS is %d", (int)status);
        int aws_error = s_determine_sspi_error(status);
        aws_raise_error(aws_error);
        goto on_error;
    }

    return s_tls_handler_new_common(alloc, options, slot, is_client_mode, sc_handler);

on_error:

    s_secure_channel_handler_destroy(alloc, sc_handler);

    return NULL;
}

void aws_windows_force_schannel_creds(bool use_schannel_creds) {
    s_use_schannel_creds = use_schannel_creds;
}

static struct aws_channel_handler *s_tls_handler_new(
    struct aws_allocator *alloc,
    struct aws_tls_connection_options *options,
    struct aws_channel_slot *slot,
    bool is_client_mode) {

#if NTDDI_VERSION >= 0x0A000006 /* Windows SDK 10.1.17763.0 or later */
    /* This check is used to determine the availability of TLS 1.3. According to Microsoft documentation
     * (https://learn.microsoft.com/en-us/windows/win32/secauthn/protocols-in-tls-ssl--schannel-ssp-), TLS 1.3 became
     * available in Windows Server 2022 (build number 20348) and Windows 11, version 21H2 (build number 22000).
     *
     * For more information see also the following links with Windows releases:
     * - https://learn.microsoft.com/en-us/windows/release-health/release-information
     * - https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
     * - https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info
     */
    if (s_is_windows_equal_or_above_build_number(WINDOWS_BUILD_20348) && !s_use_schannel_creds) {
        return s_tls_handler_sch_credentials_new(alloc, options, slot, is_client_mode);
    }
#endif
    return s_tls_handler_schannel_cred_new(alloc, options, slot, is_client_mode);
}

struct aws_channel_handler *aws_tls_client_handler_new(
    struct aws_allocator *allocator,
    struct aws_tls_connection_options *options,
    struct aws_channel_slot *slot) {

    return s_tls_handler_new(allocator, options, slot, true);
}

struct aws_channel_handler *aws_tls_server_handler_new(
    struct aws_allocator *allocator,
    struct aws_tls_connection_options *options,
    struct aws_channel_slot *slot) {

    return s_tls_handler_new(allocator, options, slot, false);
}

static void s_secure_channel_ctx_destroy(struct secure_channel_ctx *secure_channel_ctx) {
    if (secure_channel_ctx == NULL) {
        return;
    }

    if (secure_channel_ctx->private_key) {
        CryptDestroyKey(secure_channel_ctx->private_key);
    }

    if (secure_channel_ctx->crypto_provider) {
        CryptReleaseContext(secure_channel_ctx->crypto_provider, 0);
    }

    if (secure_channel_ctx->custom_trust_store) {
        aws_close_cert_store(secure_channel_ctx->custom_trust_store);
    }

    if (secure_channel_ctx->pcerts) {
        /**
         * Only free the private certificate context if the private key is NOT
         * from the certificate context because freeing the private key
         * using CryptDestroyKey frees the certificate context and then
         * trying to access it leads to a access violation.
         */
        if (secure_channel_ctx->should_free_pcerts == true) {
            CertFreeCertificateContext(secure_channel_ctx->pcerts);
        }
    }

    if (secure_channel_ctx->cert_store) {
        aws_close_cert_store(secure_channel_ctx->cert_store);
    }

    if (secure_channel_ctx->alpn_list) {
        aws_string_destroy(secure_channel_ctx->alpn_list);
    }

    aws_mem_release(secure_channel_ctx->ctx.alloc, secure_channel_ctx);
}

struct aws_tls_ctx *s_ctx_new(
    struct aws_allocator *alloc,
    const struct aws_tls_ctx_options *options,
    bool is_client_mode) {

    DWORD dw_flags = 0;
    PCCERT_CONTEXT *pa_cred = NULL;
    DWORD creds = 0;

    if (!aws_tls_is_cipher_pref_supported(options->cipher_pref)) {
        aws_raise_error(AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED);
        AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: TLS Cipher Preference is not supported: %d.", options->cipher_pref);
        return NULL;
    }

    struct secure_channel_ctx *secure_channel_ctx = aws_mem_calloc(alloc, 1, sizeof(struct secure_channel_ctx));
    if (!secure_channel_ctx) {
        return NULL;
    }

    secure_channel_ctx->ctx.alloc = alloc;
    secure_channel_ctx->ctx.impl = secure_channel_ctx;
    aws_ref_count_init(
        &secure_channel_ctx->ctx.ref_count,
        secure_channel_ctx,
        (aws_simple_completion_callback *)s_secure_channel_ctx_destroy);

    if (options->alpn_list) {
        secure_channel_ctx->alpn_list = aws_string_new_from_string(alloc, options->alpn_list);
        if (!secure_channel_ctx->alpn_list) {
            goto clean_up;
        }
    }

    secure_channel_ctx->verify_peer = options->verify_peer;
    secure_channel_ctx->should_free_pcerts = true;
    secure_channel_ctx->disable_tls13 = false;
    secure_channel_ctx->minimum_tls_version = options->minimum_tls_version;

    if (options->verify_peer && aws_tls_options_buf_is_set(&options->ca_file)) {
        AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: loading custom CA file.");
        dw_flags |= SCH_CRED_MANUAL_CRED_VALIDATION;

        struct aws_byte_cursor ca_blob_cur = aws_byte_cursor_from_buf(&options->ca_file);
        int error = aws_import_trusted_certificates(alloc, &ca_blob_cur, &secure_channel_ctx->custom_trust_store);

        if (error) {
            AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to import custom CA with error %d", aws_last_error());
            goto clean_up;
        }
    } else if (is_client_mode) {
        dw_flags |= SCH_CRED_AUTO_CRED_VALIDATION;
    }

    if (is_client_mode && !options->verify_peer) {
        AWS_LOGF_WARN(
            AWS_LS_IO_TLS,
            "static: x.509 validation has been disabled. "
            "If this is not running in a test environment, this is likely a security vulnerability.");
        dw_flags &= ~(SCH_CRED_AUTO_CRED_VALIDATION);
        dw_flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE |
                    SCH_CRED_NO_SERVERNAME_CHECK | SCH_CRED_MANUAL_CRED_VALIDATION;
    } else if (is_client_mode) {
        dw_flags |= SCH_CRED_REVOCATION_CHECK_CHAIN | SCH_CRED_IGNORE_REVOCATION_OFFLINE;
    }

    /* if someone wants to use broken algorithms like rc4/md5/des they'll need to ask for a special control */
    dw_flags |= SCH_USE_STRONG_CRYPTO;
    dw_flags |= SCH_CRED_NO_DEFAULT_CREDS;

    /* if using a system store. */
    if (options->system_certificate_path) {
        AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: assuming certificate is in a system store, loading now.");

        if (aws_load_cert_from_system_cert_store(
                options->system_certificate_path, &secure_channel_ctx->cert_store, &secure_channel_ctx->pcerts)) {
            AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to load %s", options->system_certificate_path);
            goto clean_up;
        }
        pa_cred = &secure_channel_ctx->pcerts;
        creds = 1;
        /* if using traditional PEM armored PKCS#7 and ASN Encoding public/private key pairs */
    } else if (aws_tls_options_buf_is_set(&options->certificate) && aws_tls_options_buf_is_set(&options->private_key)) {

        AWS_LOGF_DEBUG(AWS_LS_IO_TLS, "static: certificate and key have been set, setting them up now.");

        if (!aws_text_is_utf8(options->certificate.buffer, options->certificate.len)) {
            AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to import certificate, must be ASCII/UTF-8 encoded");
            aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE);
            goto clean_up;
        }

        if (!aws_text_is_utf8(options->private_key.buffer, options->private_key.len)) {
            AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: failed to import private key, must be ASCII/UTF-8 encoded");
            aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE);
            goto clean_up;
        }

        struct aws_byte_cursor cert_chain_cur = aws_byte_cursor_from_buf(&options->certificate);
        struct aws_byte_cursor pk_cur = aws_byte_cursor_from_buf(&options->private_key);
        int err = aws_import_key_pair_to_cert_context(
            alloc,
            &cert_chain_cur,
            &pk_cur,
            is_client_mode,
            &secure_channel_ctx->cert_store,
            &secure_channel_ctx->pcerts,
            &secure_channel_ctx->crypto_provider,
            &secure_channel_ctx->private_key,
            &secure_channel_ctx->disable_tls13);

        if (err) {
            AWS_LOGF_ERROR(
                AWS_LS_IO_TLS, "static: failed to import certificate and private key with error %d.", aws_last_error());
            goto clean_up;
        }

        if (secure_channel_ctx->disable_tls13 && options->minimum_tls_version == AWS_IO_TLSv1_3) {
            AWS_LOGF_ERROR(AWS_LS_IO_TLS, "static: minimum TLS version is set to 1.3, but it can't be supported");
            goto clean_up;
        }

        pa_cred = &secure_channel_ctx->pcerts;
        creds = 1;
        secure_channel_ctx->should_free_pcerts = false;
    }

    secure_channel_ctx->schannel_creds.dwFlags = dw_flags;
    secure_channel_ctx->schannel_creds.paCred = pa_cred;
    secure_channel_ctx->schannel_creds.cCreds = creds;

    return &secure_channel_ctx->ctx;

clean_up:
    s_secure_channel_ctx_destroy(secure_channel_ctx);
    return NULL;
}

struct aws_tls_ctx *aws_tls_server_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) {
    return s_ctx_new(alloc, options, false);
}

struct aws_tls_ctx *aws_tls_client_ctx_new(struct aws_allocator *alloc, const struct aws_tls_ctx_options *options) {
    return s_ctx_new(alloc, options, true);
}
