/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

#include "api/s2n.h"
#include "tls/extensions/s2n_client_psk.h"
#include "tls/extensions/s2n_early_data_indication.h"
#include "tls/s2n_cipher_suites.h"
#include "tls/s2n_early_data.h"
#include "tls/s2n_protocol_preferences.h"
#include "tls/s2n_tls13.h"
#include "utils/s2n_safety.h"

/* S2N determines the handshake type after the ServerHello, but that will be
 * too late to handle the early data + middlebox compatibility case:
 *
 *= https://tools.ietf.org/rfc/rfc8446#appendix-D.4
 *# -  If not offering early data, the client sends a dummy
 *#    change_cipher_spec record (see the third paragraph of Section 5)
 *#    immediately before its second flight.  This may either be before
 *#    its second ClientHello or before its encrypted handshake flight.
 *#    If offering early data, the record is placed immediately after the
 *#    first ClientHello.
 *
 * We need to set the handshake type flags in question during the ClientHello.
 * This will require special [INITIAL | MIDDLEBOX_COMPAT | EARLY_CLIENT_CCS]
 * entries in the handshake arrays.
 */
static S2N_RESULT s2n_setup_middlebox_compat_for_early_data(struct s2n_connection *conn)
{
    RESULT_ENSURE_REF(conn);

    if (s2n_is_middlebox_compat_enabled(conn)) {
        RESULT_GUARD(s2n_handshake_type_set_tls13_flag(conn, MIDDLEBOX_COMPAT));
        RESULT_GUARD(s2n_handshake_type_set_tls13_flag(conn, EARLY_CLIENT_CCS));
    }
    return S2N_RESULT_OK;
}

static S2N_RESULT s2n_early_data_config_is_possible(struct s2n_connection *conn)
{
    RESULT_ENSURE_REF(conn);

    struct s2n_psk *first_psk = NULL;
    RESULT_GUARD(s2n_array_get(&conn->psk_params.psk_list, 0, (void **) &first_psk));
    RESULT_ENSURE_REF(first_psk);

    struct s2n_early_data_config *early_data_config = &first_psk->early_data_config;

    /* Must support early data */
    RESULT_ENSURE_GT(early_data_config->max_early_data_size, 0);

    /* Early data must require a protocol than we could negotiate */
    RESULT_ENSURE_GTE(s2n_connection_get_protocol_version(conn), early_data_config->protocol_version);
    RESULT_ENSURE_GTE(s2n_connection_get_protocol_version(conn), S2N_TLS13);

    const struct s2n_cipher_preferences *cipher_preferences = NULL;
    RESULT_GUARD_POSIX(s2n_connection_get_cipher_preferences(conn, &cipher_preferences));
    RESULT_ENSURE_REF(cipher_preferences);

    /* Early data must require a supported cipher */
    bool match = false;
    for (uint8_t i = 0; i < cipher_preferences->count; i++) {
        if (cipher_preferences->suites[i] == early_data_config->cipher_suite) {
            match = true;
            break;
        }
    }
    RESULT_ENSURE_EQ(match, true);

    /* If early data specifies an application protocol, it must be supported by protocol preferences */
    if (early_data_config->application_protocol.size > 0) {
        struct s2n_blob *application_protocols = NULL;
        RESULT_GUARD_POSIX(s2n_connection_get_protocol_preferences(conn, &application_protocols));
        RESULT_ENSURE_REF(application_protocols);

        match = false;
        RESULT_GUARD(s2n_protocol_preferences_contain(application_protocols, &early_data_config->application_protocol, &match));
        RESULT_ENSURE_EQ(match, true);
    }

    return S2N_RESULT_OK;
}

static bool s2n_client_early_data_indication_should_send(struct s2n_connection *conn)
{
    return s2n_result_is_ok(s2n_early_data_config_is_possible(conn))
            && conn && conn->early_data_expected
            /**
             *= https://tools.ietf.org/rfc/rfc8446#section-4.2.10
             *# A client MUST NOT include the
             *# "early_data" extension in its followup ClientHello.
             **/
            && !s2n_is_hello_retry_handshake(conn)
            /**
             *= https://tools.ietf.org/rfc/rfc8446#section-4.2.10
             *# When a PSK is used and early data is allowed for that PSK, the client
             *# can send Application Data in its first flight of messages.  If the
             *# client opts to do so, it MUST supply both the "pre_shared_key" and
             *# "early_data" extensions.
             */
            && s2n_client_psk_extension.should_send(conn);
}

static int s2n_client_early_data_indication_is_missing(struct s2n_connection *conn)
{
    if (conn->early_data_state != S2N_EARLY_DATA_REJECTED) {
        POSIX_GUARD_RESULT(s2n_connection_set_early_data_state(conn, S2N_EARLY_DATA_NOT_REQUESTED));
    }
    return S2N_SUCCESS;
}

/**
 * The client version of this extension is empty, so we don't read/write any data.
 *
 *= https://tools.ietf.org/rfc/rfc8446#section-4.2.10
 *# The "extension_data" field of this extension contains an
 *# "EarlyDataIndication" value.
 *#
 *#     struct {} Empty;
 *#
 *#     struct {
 *#         select (Handshake.msg_type) {
 **
 *= https://tools.ietf.org/rfc/rfc8446#section-4.2.10
 *#             case client_hello:         Empty;
 **
 *= https://tools.ietf.org/rfc/rfc8446#section-4.2.10
 *#         };
 *#     } EarlyDataIndication;
 **/

static int s2n_client_early_data_indication_send(struct s2n_connection *conn, struct s2n_stuffer *out)
{
    POSIX_ENSURE_REF(conn);
    POSIX_ENSURE_REF(conn->secure);

    POSIX_GUARD_RESULT(s2n_setup_middlebox_compat_for_early_data(conn));
    POSIX_GUARD_RESULT(s2n_connection_set_early_data_state(conn, S2N_EARLY_DATA_REQUESTED));

    /* Set the cipher suite for early data */
    struct s2n_psk *first_psk = NULL;
    POSIX_GUARD_RESULT(s2n_array_get(&conn->psk_params.psk_list, 0, (void **) &first_psk));
    POSIX_ENSURE_REF(first_psk);
    conn->secure->cipher_suite = first_psk->early_data_config.cipher_suite;

    return S2N_SUCCESS;
}

static int s2n_client_early_data_indiction_recv(struct s2n_connection *conn, struct s2n_stuffer *in)
{
    /**
     *= https://tools.ietf.org/rfc/rfc8446#section-4.2.10
     *# A client MUST NOT include the
     *# "early_data" extension in its followup ClientHello.
     */
    POSIX_ENSURE(conn->handshake.message_number == 0, S2N_ERR_UNSUPPORTED_EXTENSION);

    /* Although technically we could NOT set the [MIDDLEBOX_COMPAT | EARLY_CLIENT_CCS] handshake type
     * for the server because the server ignores the Client CCS message state, doing so would mean that
     * the client and server state machines would be out of sync and potentially cause confusion.
     */
    POSIX_GUARD_RESULT(s2n_setup_middlebox_compat_for_early_data(conn));

    POSIX_GUARD_RESULT(s2n_connection_set_early_data_state(conn, S2N_EARLY_DATA_REQUESTED));
    return S2N_SUCCESS;
}

const s2n_extension_type s2n_client_early_data_indication_extension = {
    .iana_value = TLS_EXTENSION_EARLY_DATA,
    .is_response = false,
    .send = s2n_client_early_data_indication_send,
    .recv = s2n_client_early_data_indiction_recv,
    .should_send = s2n_client_early_data_indication_should_send,
    .if_missing = s2n_client_early_data_indication_is_missing,
};