/*
 * Copyright (c) 2015, 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.util.Collections;
import java.util.List;
import java.util.LinkedList;
import java.util.HashMap;
import javax.net.ssl.SSLProtocolException;

import static sun.security.ssl.CipherSuite.KeyExchange;
import static sun.security.ssl.CipherSuite.KeyExchange.*;
import static sun.security.ssl.HandshakeStateManager.HandshakeState.*;
import static sun.security.ssl.HandshakeMessage.*;

/*
 * Handshake state manager.
 *
 * Messages flow for a full handshake:
 *
 *      -                                                         -
 *      |          HelloRequest       (No.0, RFC 5246) [*]        |
 *      |     <--------------------------------------------       |
 *      |                                                         |
 *      |          ClientHello        (No.1, RFC 5246)            |
 *      |     -------------------------------------------->       |
 *      |                                                         |
 *      |   -      HelloVerifyRequest (No.3, RFC 6347)      -     |
 *      | D | <-------------------------------------------- | D   |
 *      | T |                                               | T   |
 *      | L |      ClientHello        (No.1, RFC 5246)      | L   |
 *      | S | --------------------------------------------> | S   |
 *      |   -                                               -     |
 *      |                                                         |
 *   C  |          ServerHello        (No.2, RFC 5246)            |  S
 *   L  |          SupplementalData   (No.23, RFC4680) [*]        |  E
 *   I  |          Certificate        (No.11, RFC 5246) [*]       |  R
 *   E  |          CertificateStatus  (No.22, RFC 6066) [*]       |  V
 *   N  |          ServerKeyExchange  (No.12, RFC 5246) [*]       |  E
 *   T  |          CertificateRequest (No.13, RFC 5246) [*]       |  R
 *      |          ServerHelloDone    (No.14, RFC 5246)           |
 *      |     <--------------------------------------------       |
 *      |                                                         |
 *      |          SupplementalData   (No.23, RFC4680) [*]        |
 *      |          Certificate        (No.11, RFC 5246) [*] Or    |
 *      |              CertificateURL (No.21, RFC6066) [*]        |
 *      |          ClientKeyExchange  (No.16, RFC 5246)           |
 *      |          CertificateVerify  (No.15, RFC 5246) [*]       |
 *      |          [ChangeCipherSpec] (RFC 5246)                  |
 *      |          Finished           (No.20, RFC 5246)           |
 *      |     -------------------------------------------->       |
 *      |                                                         |
 *      |          NewSessionTicket   (No.4, RFC4507) [*]         |
 *      |          [ChangeCipherSpec] (RFC 5246)                  |
 *      |          Finished           (No.20, RFC 5246)           |
 *      |     <--------------------------------------------       |
 *      -                                                         -
 * [*] Indicates optional or situation-dependent messages that are not
 * always sent.
 *
 * Message flow for an abbreviated handshake:
 *      -                                                         -
 *      |          ClientHello        (No.1, RFC 5246)            |
 *      |     -------------------------------------------->       |
 *      |                                                         |
 *   C  |          ServerHello        (No.2, RFC 5246)            |  S
 *   L  |          NewSessionTicket   (No.4, RFC4507) [*]         |  E
 *   I  |          [ChangeCipherSpec] (RFC 5246)                  |  R
 *   E  |          Finished           (No.20, RFC 5246)           |  V
 *   N  |     <--------------------------------------------       |  E
 *   T  |                                                         |  R
 *      |          [ChangeCipherSpec] (RFC 5246)                  |
 *      |          Finished           (No.20, RFC 5246)           |
 *      |     -------------------------------------------->       |
 *      -                                                         -
 *
 *
 * State machine of handshake states:
 *
 *                   +--------------+
 *      START -----> | HelloRequest |
 *        |          +--------------+
 *        |               |
 *        v               v
 *     +---------------------+   -->  +---------------------+
 *     |    ClientHello      |        | HelloVerifyRequest  |
 *     +---------------------+   <--  +---------------------+
 *               |
 *               |
 * =========================================================================
 *               |
 *               v
 *     +---------------------+
 *     |    ServerHello      |  ----------------------------------+------+
 *     +---------------------+  -->  +-------------------------+  |      |
 *                    |              | Server SupplementalData |  |      |
 *                    |              +-------------------------+  |      |
 *                    |                |                          |      |
 *                    v                v                          |      |
 *                +---------------------+                         |      |
 *         +----  | Server Certificate  |                         |      |
 *         |      +---------------------+                         |      |
 *         |          |                                           |      |
 *         |          |   +--------------------+                  |      |
 *         |          +-> | CertificateStatus  |                  |      |
 *         |          |   +--------------------+                  v      |
 *         |          |      |          |     +--------------------+     |
 *         |          v      v          +-->  | ServerKeyExchange  |     |
 *         |  +---------------------+   |     +--------------------+     |
 *         |  | CertificateRequest  |   |         |                      |
 *         |  +---------------------+ <-+---------+                      |
 *         |            |               |         |                      |
 *         v            v               |         |                      |
 *     +---------------------+  <-------+         |                      |
 *     |  ServerHelloDone    |  <-----------------+                      |
 *     +---------------------+                                           |
 *       |         |                                                     |
 *       |         |                                                     |
 *       |         |                                                     |
 * =========================================================================
 *       |         |                                                     |
 *       |         v                                                     |
 *       |   +-------------------------+                                 |
 *       |   | Client SupplementalData | --------------+                 |
 *       |   +-------------------------+               |                 |
 *       |             |                               |                 |
 *       |             v                               |                 |
 *       |   +--------------------+                    |                 |
 *       +-> | Client Certificate | ALT.               |                 |
 *       |   +--------------------+----------------+   |                 |
 *       |                        | CertificateURL |   |                 |
 *       |                        +----------------+   |                 |
 *       v                                             |                 |
 *     +-------------------+  <------------------------+                 |
 *     | ClientKeyExchange |                                             |
 *     +-------------------+                                             |
 *          |           |                                                |
 *          |           v                                                |
 *          |      +-------------------+                                 |
 *          |      | CertificateVerify |                                 |
 *          |      +-------------------+                                 |
 *          |          |                                                 |
 *          v          v                                                 |
 *     +-------------------------+                                       |
 *     | Client ChangeCipherSpec |  <---------------+                    |
 *     +-------------------------+                  |                    |
 *               |                                  |                    |
 *               v                                  |                    |
 *     +-----------------+  (abbreviated)           |                    |
 *     | Client Finished |  -------------> END      |                    |
 *     +-----------------+  (Abbreviated handshake) |                    |
 *                      |                           |                    |
 *                      | (full)                    |                    |
 *                      |                           |                    |
 * ================================                 |                    |
 *                      |                           |                    |
 *                      |                   ================================
 *                      |                           |                    |
 *                      v                           |                    |
 *                 +------------------+             |    (abbreviated)   |
 *                 | NewSessionTicket | <--------------------------------+
 *                 +------------------+             |                    |
 *                      |                           |                    |
 *                      v                           |                    |
 *     +-------------------------+                  |    (abbreviated)   |
 *     | Server ChangeCipherSpec | <-------------------------------------+
 *     +-------------------------+                  |
 *               |                                  |
 *               v                                  |
 *     +-----------------+    (abbreviated)         |
 *     | Server Finished | -------------------------+
 *     +-----------------+
 *            | (full)
 *            v
 *        END (Full handshake)
 *
 *
 * The scenarios of the use of this class:
 * 1. Create an instance of HandshakeStateManager during the initializtion
 *    handshake.
 * 2. If receiving a handshake message, call HandshakeStateManager.check()
 *    to make sure that the message is of the expected handshake type.  And
 *    then call HandshakeStateManager.update() in case handshake states may
 *    be impacted by this new incoming handshake message.
 * 3. On delivering a handshake message, call HandshakeStateManager.update()
 *    in case handshake states may by thie new outgoing handshake message.
 * 4. On receiving and delivering ChangeCipherSpec message, call
 *    HandshakeStateManager.changeCipherSpec() to check the present sequence
 *    of this message, and update the states if necessary.
 */
final class HandshakeStateManager {
    // upcoming handshake states.
    private LinkedList<HandshakeState> upcomingStates;
    private LinkedList<HandshakeState> alternatives;

    private boolean isDTLS;

    private static final boolean debugIsOn;

    private static final HashMap<Byte, String> handshakeTypes;

    static {
        debugIsOn = (Handshaker.debug != null) &&
                Debug.isOn("handshake") && Debug.isOn("verbose");
        handshakeTypes = new HashMap<>(15);

        handshakeTypes.put(ht_hello_request,            "hello_request");
        handshakeTypes.put(ht_client_hello,             "client_hello");
        handshakeTypes.put(ht_server_hello,             "server_hello");
        handshakeTypes.put(ht_hello_verify_request,     "hello_verify_request");
        handshakeTypes.put(ht_new_session_ticket,       "session_ticket");
        handshakeTypes.put(ht_certificate,              "certificate");
        handshakeTypes.put(ht_server_key_exchange,      "server_key_exchange");
        handshakeTypes.put(ht_certificate_request,      "certificate_request");
        handshakeTypes.put(ht_server_hello_done,        "server_hello_done");
        handshakeTypes.put(ht_certificate_verify,       "certificate_verify");
        handshakeTypes.put(ht_client_key_exchange,      "client_key_exchange");
        handshakeTypes.put(ht_finished,                 "finished");
        handshakeTypes.put(ht_certificate_url,          "certificate_url");
        handshakeTypes.put(ht_certificate_status,       "certificate_status");
        handshakeTypes.put(ht_supplemental_data,        "supplemental_data");
    }

    HandshakeStateManager(boolean isDTLS) {
        this.upcomingStates = new LinkedList<>();
        this.alternatives = new LinkedList<>();
        this.isDTLS = isDTLS;
    }

    //
    // enumation of handshake type
    //
    static enum HandshakeState {
        HS_HELLO_REQUEST(
                "hello_request",
                HandshakeMessage.ht_hello_request),
        HS_CLIENT_HELLO(
                "client_hello",
                HandshakeMessage.ht_client_hello),
        HS_HELLO_VERIFY_REQUEST(
                "hello_verify_request",
                HandshakeMessage.ht_hello_verify_request),
        HS_SERVER_HELLO(
                "server_hello",
                HandshakeMessage.ht_server_hello),
        HS_SERVER_SUPPLEMENTAL_DATA(
                "server supplemental_data",
                HandshakeMessage.ht_supplemental_data, true),
        HS_SERVER_CERTIFICATE(
                "server certificate",
                HandshakeMessage.ht_certificate),
        HS_CERTIFICATE_STATUS(
                "certificate_status",
                HandshakeMessage.ht_certificate_status, true),
        HS_SERVER_KEY_EXCHANGE(
                "server_key_exchange",
                HandshakeMessage.ht_server_key_exchange, true),
        HS_CERTIFICATE_REQUEST(
                "certificate_request",
                HandshakeMessage.ht_certificate_request, true),
        HS_SERVER_HELLO_DONE(
                "server_hello_done",
                HandshakeMessage.ht_server_hello_done),
        HS_CLIENT_SUPPLEMENTAL_DATA(
                "client supplemental_data",
                HandshakeMessage.ht_supplemental_data, true),
        HS_CLIENT_CERTIFICATE(
                "client certificate",
                HandshakeMessage.ht_certificate, true),
        HS_CERTIFICATE_URL(
                "certificate_url",
                HandshakeMessage.ht_certificate_url, true),
        HS_CLIENT_KEY_EXCHANGE(
                "client_key_exchange",
                HandshakeMessage.ht_client_key_exchange),
        HS_CERTIFICATE_VERIFY(
                "certificate_verify",
                HandshakeMessage.ht_certificate_verify, true),
        HS_CLIENT_CHANGE_CIPHER_SPEC(
                "client change_cipher_spec",
                HandshakeMessage.ht_not_applicable),
        HS_CLEINT_FINISHED(
                "client finished",
                HandshakeMessage.ht_finished),
        HS_NEW_SESSION_TICKET(
                "session_ticket",
                HandshakeMessage.ht_new_session_ticket),
        HS_SERVER_CHANGE_CIPHER_SPEC(
                "server change_cipher_spec",
                HandshakeMessage.ht_not_applicable),
        HS_SERVER_FINISHED(
                "server finished",
                HandshakeMessage.ht_finished);

        final String description;
        final byte handshakeType;
        final boolean isOptional;

        HandshakeState(String description, byte handshakeType) {
            this.description = description;
            this.handshakeType = handshakeType;
            this.isOptional = false;
        }

        HandshakeState(String description,
                byte handshakeType, boolean isOptional) {

            this.description = description;
            this.handshakeType = handshakeType;
            this.isOptional = isOptional;
        }

        public String toString() {
            return description + "[" + handshakeType + "]" +
                    (isOptional ? "(optional)" : "");
        }
    }

    boolean isEmpty() {
        return upcomingStates.isEmpty();
    }

    List<Byte> check(byte handshakeType) throws SSLProtocolException {
        List<Byte> ignoredOptional = new LinkedList<>();
        String exceptionMsg =
                 "Handshake message sequence violation, " + handshakeType;

        if (debugIsOn) {
            System.out.println(
                    "check handshake state: " + toString(handshakeType));
        }

        if (upcomingStates.isEmpty()) {
            // Is it a kickstart message?
            if ((handshakeType != HandshakeMessage.ht_hello_request) &&
                (handshakeType != HandshakeMessage.ht_client_hello)) {

                throw new SSLProtocolException(
                    "Handshake message sequence violation, " + handshakeType);
            }

            // It is a kickstart message.
            return Collections.emptyList();
        }

        // Ignore the checking for HelloRequest messages as they
        // may be sent by the server at any time.
        if (handshakeType == HandshakeMessage.ht_hello_request) {
            return Collections.emptyList();
        }

        for (HandshakeState handshakeState : upcomingStates) {
            if (handshakeState.handshakeType == handshakeType) {
                // It's the expected next handshake type.
                return ignoredOptional;
            }

            if (handshakeState.isOptional) {
                ignoredOptional.add(handshakeState.handshakeType);
                continue;
            } else {
                for (HandshakeState alternative : alternatives) {
                    if (alternative.handshakeType == handshakeType) {
                        return ignoredOptional;
                    }

                    if (alternative.isOptional) {
                        continue;
                    } else {
                        throw new SSLProtocolException(exceptionMsg);
                    }
                }
            }

            throw new SSLProtocolException(exceptionMsg);
        }

        // Not an expected Handshake message.
        throw new SSLProtocolException(
                "Handshake message sequence violation, " + handshakeType);
    }

    void update(HandshakeMessage handshakeMessage,
            boolean isAbbreviated) throws SSLProtocolException {

        byte handshakeType = (byte)handshakeMessage.messageType();
        String exceptionMsg =
                 "Handshake message sequence violation, " + handshakeType;

        if (debugIsOn) {
            System.out.println(
                    "update handshake state: " + toString(handshakeType));
        }

        boolean hasPresentState = false;
        switch (handshakeType) {
        case HandshakeMessage.ht_hello_request:
            //
            // State machine:
            //     PRESENT: START
            //        TO  : ClientHello
            //

            // No old state to update.

            // Add the upcoming states.
            if (!upcomingStates.isEmpty()) {
                // A ClientHello message should be followed.
                upcomingStates.add(HS_CLIENT_HELLO);

            }   // Otherwise, ignore this HelloRequest message.

            break;

        case HandshakeMessage.ht_client_hello:
            //
            // State machine:
            //     PRESENT: START
            //              HS_CLIENT_HELLO
            //        TO  : HS_HELLO_VERIFY_REQUEST (DTLS)
            //              HS_SERVER_HELLO
            //

            // Check and update the present state.
            if (!upcomingStates.isEmpty()) {
                // The current state should be HS_CLIENT_HELLO.
                HandshakeState handshakeState = upcomingStates.pop();
                if (handshakeState != HS_CLIENT_HELLO) {
                    throw new SSLProtocolException(exceptionMsg);
                }
            }

            // Add the upcoming states.
            ClientHello clientHello = (ClientHello)handshakeMessage;
            if (isDTLS) {
                // Is it an initial ClientHello message?
                if (clientHello.cookie == null ||
                        clientHello.cookie.length == 0) {
                    // Is it an abbreviated handshake?
                    if (clientHello.sessionId.length() != 0) {
                        // A HelloVerifyRequest message or a ServerHello
                        // message may follow the abbreviated session
                        // resuming handshake request.
                        upcomingStates.add(HS_HELLO_VERIFY_REQUEST);
                        alternatives.add(HS_SERVER_HELLO);
                    } else {
                        // A HelloVerifyRequest message should follow
                        // the initial ClientHello message.
                        upcomingStates.add(HS_HELLO_VERIFY_REQUEST);
                    }
                } else {
                    // A HelloVerifyRequest may be followed if the cookie
                    // cannot be verified.
                    upcomingStates.add(HS_SERVER_HELLO);
                    alternatives.add(HS_HELLO_VERIFY_REQUEST);
                }
            } else {
                upcomingStates.add(HS_SERVER_HELLO);
            }

            break;

        case HandshakeMessage.ht_hello_verify_request:
            //
            // State machine:
            //     PRESENT: HS_HELLO_VERIFY_REQUEST
            //        TO  : HS_CLIENT_HELLO
            //
            // Note that this state may have an alternative option.

            // Check and update the present state.
            if (!upcomingStates.isEmpty()) {
                // The current state should be HS_HELLO_VERIFY_REQUEST.
                HandshakeState handshakeState = upcomingStates.pop();
                HandshakeState alternative = null;
                if (!alternatives.isEmpty()) {
                    alternative = alternatives.pop();
                }

                if ((handshakeState != HS_HELLO_VERIFY_REQUEST) &&
                        (alternative != HS_HELLO_VERIFY_REQUEST)) {

                    throw new SSLProtocolException(exceptionMsg);
                }
            } else {
                // No present state.
                throw new SSLProtocolException(exceptionMsg);
            }

            // Add the upcoming states.
            upcomingStates.add(HS_CLIENT_HELLO);

            break;

        case HandshakeMessage.ht_server_hello:
            //
            // State machine:
            //     PRESENT: HS_SERVER_HELLO
            //        TO  :
            //          Full handshake state stacks
            //              (ServerHello Flight)
            //              HS_SERVER_SUPPLEMENTAL_DATA [optional]
            //          --> HS_SERVER_CERTIFICATE [optional]
            //          --> HS_CERTIFICATE_STATUS [optional]
            //          --> HS_SERVER_KEY_EXCHANGE [optional]
            //          --> HS_CERTIFICATE_REQUEST [optional]
            //          --> HS_SERVER_HELLO_DONE
            //              (Client ClientKeyExchange Flight)
            //          --> HS_CLIENT_SUPPLEMENTAL_DATA [optional]
            //          --> HS_CLIENT_CERTIFICATE or
            //              HS_CERTIFICATE_URL
            //          --> HS_CLIENT_KEY_EXCHANGE
            //          --> HS_CERTIFICATE_VERIFY [optional]
            //          --> HS_CLIENT_CHANGE_CIPHER_SPEC
            //          --> HS_CLEINT_FINISHED
            //              (Server Finished Flight)
            //          --> HS_CLIENT_SUPPLEMENTAL_DATA [optional]
            //
            //          Abbreviated handshake state stacks
            //              (Server Finished Flight)
            //              HS_NEW_SESSION_TICKET
            //          --> HS_SERVER_CHANGE_CIPHER_SPEC
            //          --> HS_SERVER_FINISHED
            //              (Client Finished Flight)
            //          --> HS_CLIENT_CHANGE_CIPHER_SPEC
            //          --> HS_CLEINT_FINISHED
            //
            // Note that this state may have an alternative option.

            // Check and update the present state.
            if (!upcomingStates.isEmpty()) {
                // The current state should be HS_SERVER_HELLO
                HandshakeState handshakeState = upcomingStates.pop();
                HandshakeState alternative = null;
                if (!alternatives.isEmpty()) {
                    alternative = alternatives.pop();
                }

                if ((handshakeState != HS_SERVER_HELLO) &&
                        (alternative != HS_SERVER_HELLO)) {

                    throw new SSLProtocolException(exceptionMsg);
                }
            } else {
                // No present state.
                throw new SSLProtocolException(exceptionMsg);
            }

            // Add the upcoming states.
            ServerHello serverHello = (ServerHello)handshakeMessage;
            HelloExtensions hes = serverHello.extensions;


            // Not support SessionTicket extension yet.
            //
            // boolean hasSessionTicketExt =
            //     (hes.get(HandshakeMessage.ht_new_session_ticket) != null);

            if (isAbbreviated) {
                // Not support SessionTicket extension yet.
                //
                // // Mandatory NewSessionTicket message
                // if (hasSessionTicketExt) {
                //     upcomingStates.add(HS_NEW_SESSION_TICKET);
                // }

                // Mandatory server ChangeCipherSpec and Finished messages
                upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
                upcomingStates.add(HS_SERVER_FINISHED);

                // Mandatory client ChangeCipherSpec and Finished messages
                upcomingStates.add(HS_CLIENT_CHANGE_CIPHER_SPEC);
                upcomingStates.add(HS_CLEINT_FINISHED);
            } else {
                // Not support SupplementalData extension yet.
                //
                // boolean hasSupplementalDataExt =
                //     (hes.get(HandshakeMessage.ht_supplemental_data) != null);

                // Not support CertificateURL extension yet.
                //
                // boolean hasCertificateUrlExt =
                //     (hes.get(ExtensionType EXT_CLIENT_CERTIFICATE_URL)
                //          != null);

                // Not support SupplementalData extension yet.
                //
                // // Optional SupplementalData message
                // if (hasSupplementalDataExt) {
                //     upcomingStates.add(HS_SERVER_SUPPLEMENTAL_DATA);
                // }

                // Need server Certificate message or not?
                KeyExchange keyExchange = serverHello.cipherSuite.keyExchange;
                if ((keyExchange != K_KRB5) &&
                        (keyExchange != K_KRB5_EXPORT) &&
                        (keyExchange != K_DH_ANON) &&
                        (keyExchange != K_ECDH_ANON)) {
                    // Mandatory Certificate message
                    upcomingStates.add(HS_SERVER_CERTIFICATE);
                }

                // Optional CertificateStatus message
                if (hes.get(ExtensionType.EXT_STATUS_REQUEST) != null ||
                        hes.get(ExtensionType.EXT_STATUS_REQUEST_V2) != null) {
                    upcomingStates.add(HS_CERTIFICATE_STATUS);
                }

                // Need ServerKeyExchange message or not?
                if ((keyExchange == K_RSA_EXPORT) ||
                        (keyExchange == K_DHE_RSA) ||
                        (keyExchange == K_DHE_DSS) ||
                        (keyExchange == K_DH_ANON) ||
                        (keyExchange == K_ECDHE_RSA) ||
                        (keyExchange == K_ECDHE_ECDSA) ||
                        (keyExchange == K_ECDH_ANON)) {
                    // Optional ServerKeyExchange message
                    upcomingStates.add(HS_SERVER_KEY_EXCHANGE);
                }

                // Optional CertificateRequest message
                upcomingStates.add(HS_CERTIFICATE_REQUEST);

                // Mandatory ServerHelloDone message
                upcomingStates.add(HS_SERVER_HELLO_DONE);

                // Not support SupplementalData extension yet.
                //
                // // Optional SupplementalData message
                // if (hasSupplementalDataExt) {
                //     upcomingStates.add(HS_CLIENT_SUPPLEMENTAL_DATA);
                // }

                // Optional client Certificate message
                upcomingStates.add(HS_CLIENT_CERTIFICATE);

                // Not support CertificateURL extension yet.
                //
                // // Alternative CertificateURL message, optional too.
                // //
                // // Please put CertificateURL rather than Certificate
                // // message in the alternatives list.  So that we can
                // // simplify the process of this alternative pair later.
                // if (hasCertificateUrlExt) {
                //     alternatives.add(HS_CERTIFICATE_URL);
                // }

                // Mandatory ClientKeyExchange message
                upcomingStates.add(HS_CLIENT_KEY_EXCHANGE);

                // Optional CertificateVerify message
                upcomingStates.add(HS_CERTIFICATE_VERIFY);

                // Mandatory client ChangeCipherSpec and Finished messages
                upcomingStates.add(HS_CLIENT_CHANGE_CIPHER_SPEC);
                upcomingStates.add(HS_CLEINT_FINISHED);

                // Not support SessionTicket extension yet.
                //
                // // Mandatory NewSessionTicket message
                // if (hasSessionTicketExt) {
                //     upcomingStates.add(HS_NEW_SESSION_TICKET);
                // }

                // Mandatory server ChangeCipherSpec and Finished messages
                upcomingStates.add(HS_SERVER_CHANGE_CIPHER_SPEC);
                upcomingStates.add(HS_SERVER_FINISHED);
            }

            break;

        case HandshakeMessage.ht_certificate:
            //
            // State machine:
            //     PRESENT: HS_CERTIFICATE_URL or
            //              HS_CLIENT_CERTIFICATE
            //        TO  : HS_CLIENT_KEY_EXCHANGE
            //
            //     Or
            //
            //     PRESENT: HS_SERVER_CERTIFICATE
            //        TO  : HS_CERTIFICATE_STATUS [optional]
            //              HS_SERVER_KEY_EXCHANGE [optional]
            //              HS_CERTIFICATE_REQUEST [optional]
            //              HS_SERVER_HELLO_DONE
            //
            // Note that this state may have an alternative option.

            // Check and update the present state.
            while (!upcomingStates.isEmpty()) {
                HandshakeState handshakeState = upcomingStates.pop();
                if (handshakeState.handshakeType == handshakeType) {
                    hasPresentState = true;

                    // The current state should be HS_CLIENT_CERTIFICATE or
                    // HS_SERVER_CERTIFICATE.
                    //
                    // Note that we won't put HS_CLIENT_CERTIFICATE into
                    // the alternative list.
                    if ((handshakeState != HS_CLIENT_CERTIFICATE) &&
                            (handshakeState != HS_SERVER_CERTIFICATE)) {
                        throw new SSLProtocolException(exceptionMsg);
                    }

                    // Is it an expected client Certificate message?
                    boolean isClientMessage = false;
                    if (!upcomingStates.isEmpty()) {
                        // If the next expected message is ClientKeyExchange,
                        // this one should be an expected client Certificate
                        // message.
                        HandshakeState nextState = upcomingStates.getFirst();
                        if (nextState == HS_CLIENT_KEY_EXCHANGE) {
                            isClientMessage = true;
                        }
                    }

                    if (isClientMessage) {
                        if (handshakeState != HS_CLIENT_CERTIFICATE) {
                            throw new SSLProtocolException(exceptionMsg);
                        }

                        // Not support CertificateURL extension yet.
                        /*******************************************
                        // clear up the alternatives list
                        if (!alternatives.isEmpty()) {
                            HandshakeState alternative = alternatives.pop();

                            if (alternative != HS_CERTIFICATE_URL) {
                                throw new SSLProtocolException(exceptionMsg);
                            }
                        }
                        ********************************************/
                    } else {
                        if ((handshakeState != HS_SERVER_CERTIFICATE)) {
                            throw new SSLProtocolException(exceptionMsg);
                        }
                    }

                    break;
                } else if (!handshakeState.isOptional) {
                    throw new SSLProtocolException(exceptionMsg);
                }   // Otherwise, looking for next state track.
            }

            // No present state.
            if (!hasPresentState) {
                throw new SSLProtocolException(exceptionMsg);
            }

            // no new upcoming states.

            break;

        // Not support CertificateURL extension yet.
        /*************************************************/
        case HandshakeMessage.ht_certificate_url:
            //
            // State machine:
            //     PRESENT: HS_CERTIFICATE_URL or
            //              HS_CLIENT_CERTIFICATE
            //        TO  : HS_CLIENT_KEY_EXCHANGE
            //
            // Note that this state may have an alternative option.

            // Check and update the present state.
            while (!upcomingStates.isEmpty()) {
                // The current state should be HS_CLIENT_CERTIFICATE.
                //
                // Note that we won't put HS_CLIENT_CERTIFICATE into
                // the alternative list.
                HandshakeState handshakeState = upcomingStates.pop();
                if (handshakeState.handshakeType ==
                        HS_CLIENT_CERTIFICATE.handshakeType) {
                    hasPresentState = true;

                    // Look for HS_CERTIFICATE_URL state track.
                    if (!alternatives.isEmpty()) {
                        HandshakeState alternative = alternatives.pop();

                        if (alternative != HS_CERTIFICATE_URL) {
                            throw new SSLProtocolException(exceptionMsg);
                        }
                    } else {
                        // No alternative CertificateUR state track.
                        throw new SSLProtocolException(exceptionMsg);
                    }

                    if ((handshakeState != HS_CLIENT_CERTIFICATE)) {
                        throw new SSLProtocolException(exceptionMsg);
                    }

                    break;
                } else if (!handshakeState.isOptional) {
                    throw new SSLProtocolException(exceptionMsg);
                }   // Otherwise, looking for next state track.

            }

            // No present state.
            if (!hasPresentState) {
                // No present state.
                throw new SSLProtocolException(exceptionMsg);
            }

            // no new upcoming states.

            break;
        /*************************************************/

        default:
            // Check and update the present state.
            while (!upcomingStates.isEmpty()) {
                HandshakeState handshakeState = upcomingStates.pop();
                if (handshakeState.handshakeType == handshakeType) {
                    hasPresentState = true;
                    break;
                } else if (!handshakeState.isOptional) {
                    throw new SSLProtocolException(exceptionMsg);
                }   // Otherwise, looking for next state track.
            }

            // No present state.
            if (!hasPresentState) {
                throw new SSLProtocolException(exceptionMsg);
            }

            // no new upcoming states.
        }

        if (debugIsOn) {
            for (HandshakeState handshakeState : upcomingStates) {
                System.out.println(
                    "upcoming handshake states: " + handshakeState);
            }
            for (HandshakeState handshakeState : alternatives) {
                System.out.println(
                    "upcoming handshake alternative state: " + handshakeState);
            }
        }
    }

    void changeCipherSpec(boolean isInput,
            boolean isClient) throws SSLProtocolException {

        if (debugIsOn) {
            System.out.println(
                    "update handshake state: change_cipher_spec");
        }

        String exceptionMsg = "ChangeCipherSpec message sequence violation";

        HandshakeState expectedState;
        if ((isClient && isInput) || (!isClient && !isInput)) {
            expectedState = HS_SERVER_CHANGE_CIPHER_SPEC;
        } else {
            expectedState = HS_CLIENT_CHANGE_CIPHER_SPEC;
        }

        boolean hasPresentState = false;

        // Check and update the present state.
        while (!upcomingStates.isEmpty()) {
            HandshakeState handshakeState = upcomingStates.pop();
            if (handshakeState == expectedState) {
                hasPresentState = true;
                break;
            } else if (!handshakeState.isOptional) {
                throw new SSLProtocolException(exceptionMsg);
            }   // Otherwise, looking for next state track.
        }

        // No present state.
        if (!hasPresentState) {
            throw new SSLProtocolException(exceptionMsg);
        }

        // no new upcoming states.

        if (debugIsOn) {
            for (HandshakeState handshakeState : upcomingStates) {
                System.out.println(
                    "upcoming handshake states: " + handshakeState);
            }
            for (HandshakeState handshakeState : alternatives) {
                System.out.println(
                    "upcoming handshake alternative state: " + handshakeState);
            }
        }
    }

    private static String toString(byte handshakeType) {
        String s = handshakeTypes.get(handshakeType);
        if (s == null) {
            s = "unknown";
        }
        return (s + "[" + handshakeType + "]");
    }
}