/*
 * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.ssl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map;
import javax.net.ssl.SSLException;

enum SSLHandshake implements SSLConsumer, HandshakeProducer {
    @SuppressWarnings({"unchecked", "rawtypes"})
    HELLO_REQUEST ((byte)0x00, "hello_request",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                HelloRequest.handshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                HelloRequest.handshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    CLIENT_HELLO ((byte)0x01, "client_hello",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                ClientHello.handshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_13
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                ClientHello.handshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_13
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    SERVER_HELLO ((byte)0x02, "server_hello",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                ServerHello.handshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_13
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                ServerHello.t12HandshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_12
            ),
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                ServerHello.t13HandshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    HELLO_RETRY_REQUEST ((byte)0x02, "hello_retry_request",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                ServerHello.handshakeConsumer,      // Use ServerHello consumer
                ProtocolVersion.PROTOCOLS_TO_13
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                ServerHello.hrrHandshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    NEW_SESSION_TICKET          ((byte)0x04, "new_session_ticket",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                NewSessionTicket.handshakeConsumer,
                ProtocolVersion.PROTOCOLS_OF_13
        )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                NewSessionTicket.handshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_13
        )
        })),
    END_OF_EARLY_DATA           ((byte)0x05, "end_of_early_data"),

    @SuppressWarnings({"unchecked", "rawtypes"})
    ENCRYPTED_EXTENSIONS        ((byte)0x08, "encrypted_extensions",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                EncryptedExtensions.handshakeConsumer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                EncryptedExtensions.handshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    CERTIFICATE                 ((byte)0x0B, "certificate",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateMessage.t12HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_12
            ),
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateMessage.t13HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateMessage.t12HandshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_12
            ),
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateMessage.t13HandshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    SERVER_KEY_EXCHANGE         ((byte)0x0C, "server_key_exchange",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                ServerKeyExchange.handshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                ServerKeyExchange.handshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    CERTIFICATE_REQUEST         ((byte)0x0D, "certificate_request",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateRequest.t10HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_11
            ),
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateRequest.t12HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_OF_12
            ),
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateRequest.t13HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateRequest.t10HandshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_11
            ),
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateRequest.t12HandshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_12
            ),
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateRequest.t13HandshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    SERVER_HELLO_DONE           ((byte)0x0E, "server_hello_done",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                ServerHelloDone.handshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                ServerHelloDone.handshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    CERTIFICATE_VERIFY          ((byte)0x0F, "certificate_verify",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateVerify.s30HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_OF_30
            ),
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateVerify.t10HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_10_11
            ),
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateVerify.t12HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_OF_12
            ),
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateVerify.t13HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateVerify.s30HandshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_30
            ),
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateVerify.t10HandshakeProducer,
                ProtocolVersion.PROTOCOLS_10_11
            ),
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateVerify.t12HandshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_12
            ),
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateVerify.t13HandshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    CLIENT_KEY_EXCHANGE         ((byte)0x10, "client_key_exchange",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                ClientKeyExchange.handshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                ClientKeyExchange.handshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        })),

    @SuppressWarnings({"unchecked", "rawtypes"})
    FINISHED                    ((byte)0x14, "finished",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                Finished.t12HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_12
            ),
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                Finished.t13HandshakeConsumer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                Finished.t12HandshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_12
            ),
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                Finished.t13HandshakeProducer,
                ProtocolVersion.PROTOCOLS_OF_13
            )
        })),

    CERTIFICATE_URL             ((byte)0x15, "certificate_url"),

    @SuppressWarnings({"unchecked", "rawtypes"})
    CERTIFICATE_STATUS          ((byte)0x16, "certificate_status",
        (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                CertificateStatus.handshakeConsumer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        }),
        (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                CertificateStatus.handshakeProducer,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        }),
        (Map.Entry<HandshakeAbsence, ProtocolVersion[]>[])(new Map.Entry[] {
            new SimpleImmutableEntry<HandshakeAbsence, ProtocolVersion[]>(
                CertificateStatus.handshakeAbsence,
                ProtocolVersion.PROTOCOLS_TO_12
            )
        })),

    SUPPLEMENTAL_DATA           ((byte)0x17, "supplemental_data"),

    @SuppressWarnings({"unchecked", "rawtypes"})
    KEY_UPDATE                  ((byte)0x18, "key_update",
            (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(new Map.Entry[] {
                    new SimpleImmutableEntry<SSLConsumer, ProtocolVersion[]>(
                            KeyUpdate.handshakeConsumer,
                            ProtocolVersion.PROTOCOLS_OF_13
                    )
            }),
            (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(new Map.Entry[] {
                    new SimpleImmutableEntry<HandshakeProducer, ProtocolVersion[]>(
                            KeyUpdate.handshakeProducer,
                            ProtocolVersion.PROTOCOLS_OF_13
                    )
            })),
    MESSAGE_HASH                ((byte)0xFE, "message_hash"),
    NOT_APPLICABLE              ((byte)0xFF, "not_applicable");

    final byte id;
    final String name;
    final Map.Entry<SSLConsumer, ProtocolVersion[]>[] handshakeConsumers;
    final Map.Entry<HandshakeProducer, ProtocolVersion[]>[] handshakeProducers;
    final Map.Entry<HandshakeAbsence, ProtocolVersion[]>[] handshakeAbsences;

    @SuppressWarnings({"unchecked", "rawtypes"})
    SSLHandshake(byte id, String name) {
        this(id, name,
                (Map.Entry<SSLConsumer, ProtocolVersion[]>[])(
                        new Map.Entry[0]),
                (Map.Entry<HandshakeProducer, ProtocolVersion[]>[])(
                        new Map.Entry[0]),
                (Map.Entry<HandshakeAbsence, ProtocolVersion[]>[])(
                        new Map.Entry[0]));
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    SSLHandshake(byte id, String name,
        Map.Entry<SSLConsumer, ProtocolVersion[]>[] handshakeConsumers,
        Map.Entry<HandshakeProducer, ProtocolVersion[]>[] handshakeProducers) {

        this(id, name, handshakeConsumers, handshakeProducers,
                (Map.Entry<HandshakeAbsence, ProtocolVersion[]>[])(
                        new Map.Entry[0]));
    }

    SSLHandshake(byte id, String name,
        Map.Entry<SSLConsumer, ProtocolVersion[]>[] handshakeConsumers,
        Map.Entry<HandshakeProducer, ProtocolVersion[]>[] handshakeProducers,
        Map.Entry<HandshakeAbsence, ProtocolVersion[]>[] handshakeAbsence) {

        this.id = id;
        this.name = name;
        this.handshakeConsumers = handshakeConsumers;
        this.handshakeProducers = handshakeProducers;
        this.handshakeAbsences = handshakeAbsence;
    }

    @Override
    public void consume(ConnectionContext context,
            ByteBuffer message) throws IOException {
        SSLConsumer hc = getHandshakeConsumer(context);
        if (hc != null) {
            hc.consume(context, message);
        } else {
            throw new UnsupportedOperationException(
                    "Unsupported handshake consumer: " + this.name);
        }
    }

    private SSLConsumer getHandshakeConsumer(ConnectionContext context) {
        if (handshakeConsumers.length == 0) {
            return null;
        }

        // The consuming happens in handshake context only.
        HandshakeContext hc = (HandshakeContext)context;
        ProtocolVersion protocolVersion;
        if ((hc.negotiatedProtocol == null) ||
                (hc.negotiatedProtocol == ProtocolVersion.NONE)) {
            if (hc.conContext.isNegotiated &&
                    hc.conContext.protocolVersion != ProtocolVersion.NONE) {
                protocolVersion = hc.conContext.protocolVersion;
            } else {
                protocolVersion = hc.maximumActiveProtocol;
            }
        } else {
            protocolVersion = hc.negotiatedProtocol;
        }

        for (Map.Entry<SSLConsumer,
                ProtocolVersion[]> phe : handshakeConsumers) {
            for (ProtocolVersion pv : phe.getValue()) {
                if (protocolVersion == pv) {
                    return phe.getKey();
                }
            }
        }

        return null;
    }

    @Override
    public byte[] produce(ConnectionContext context,
            HandshakeMessage message) throws IOException {
        HandshakeProducer hp = getHandshakeProducer(context);
        if (hp != null) {
            return hp.produce(context, message);
        } else {
            throw new UnsupportedOperationException(
                    "Unsupported handshake producer: " + this.name);
        }
    }

    private HandshakeProducer getHandshakeProducer(
            ConnectionContext context) {
        if (handshakeConsumers.length == 0) {
            return null;
        }

        // The consuming happens in handshake context only.
        HandshakeContext hc = (HandshakeContext)context;
        ProtocolVersion protocolVersion;
        if ((hc.negotiatedProtocol == null) ||
                (hc.negotiatedProtocol == ProtocolVersion.NONE)) {
            if (hc.conContext.isNegotiated &&
                    hc.conContext.protocolVersion != ProtocolVersion.NONE) {
                protocolVersion = hc.conContext.protocolVersion;
            } else {
                protocolVersion = hc.maximumActiveProtocol;
            }
        } else {
            protocolVersion = hc.negotiatedProtocol;
        }

        for (Map.Entry<HandshakeProducer,
                ProtocolVersion[]> phe : handshakeProducers) {
            for (ProtocolVersion pv : phe.getValue()) {
                if (protocolVersion == pv) {
                    return phe.getKey();
                }
            }
        }

        return null;
    }

    @Override
    public String toString() {
        return name;
    }

    static String nameOf(byte id) {
        // If two handshake message share the same handshake type, returns
        // the first handshake message name.
        //
        // It is not a big issue at present as only ServerHello and
        // HellRetryRequest share a handshake type.
        for (SSLHandshake hs : SSLHandshake.values()) {
            if (hs.id == id) {
                return hs.name;
            }
        }

        return "UNKNOWN-HANDSHAKE-MESSAGE(" + id + ")";
    }

    static boolean isKnown(byte id) {
        for (SSLHandshake hs : SSLHandshake.values()) {
            if (hs.id == id && id != NOT_APPLICABLE.id) {
                return true;
            }
        }

        return false;
    }

    static final void kickstart(HandshakeContext context) throws IOException {
        if (context instanceof ClientHandshakeContext) {
            // For initial handshaking, including session resumption,
            // ClientHello message is used as the kickstart message.
            //
            // TLS 1.2 and older protocols support renegotiation on existing
            // connections.  A ClientHello messages is used to kickstart the
            // renegotiation.
            //
            // TLS 1.3 forbids renegotiation.  The post-handshake KeyUpdate
            // message is used to update the sending cryptographic keys.
            if (context.conContext.isNegotiated &&
                    context.conContext.protocolVersion.useTLS13PlusSpec()) {
                // Use KeyUpdate message for renegotiation.
                KeyUpdate.kickstartProducer.produce(context);
            } else {
                // Using ClientHello message for the initial handshaking
                // (including session resumption) or renegotiation.
                // SSLHandshake.CLIENT_HELLO.produce(context);
                ClientHello.kickstartProducer.produce(context);
            }
        } else {
            // The server side can delivering kickstart message after the
            // connection has established.
            //
            // TLS 1.2 and older protocols use HelloRequest to begin a
            // negotiation process anew.
            //
            // While TLS 1.3 uses the post-handshake KeyUpdate message
            // to update the sending cryptographic keys.
            if (context.conContext.protocolVersion.useTLS13PlusSpec()) {
                // Use KeyUpdate message for renegotiation.
                KeyUpdate.kickstartProducer.produce(context);
            } else {
                // SSLHandshake.HELLO_REQUEST.produce(context);
                HelloRequest.kickstartProducer.produce(context);
            }
        }
    }

    /**
     * A (transparent) specification of handshake message.
     */
    static abstract class HandshakeMessage {
        final HandshakeContext      handshakeContext;

        HandshakeMessage(HandshakeContext handshakeContext) {
            this.handshakeContext = handshakeContext;
        }

        abstract SSLHandshake handshakeType();
        abstract int messageLength();
        abstract void send(HandshakeOutStream hos) throws IOException;

        void write(HandshakeOutStream hos) throws IOException {
            int len = messageLength();
            if (len >= Record.OVERFLOW_OF_INT24) {
                throw new SSLException("Handshake message is overflow"
                        + ", type = " + handshakeType() + ", len = " + len);
            }
            hos.write(handshakeType().id);
            hos.putInt24(len);
            send(hos);
            hos.complete();
        }
    }
}
