/*
 * Copyright (c) 2014, Peter Thorson. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the WebSocket++ Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#ifndef WEBSOCKETPP_CLOSE_HPP
#define WEBSOCKETPP_CLOSE_HPP

/** \file
 * A package of types and methods for manipulating WebSocket close codes.
 */

#include <websocketpp/error.hpp>
#include <websocketpp/common/network.hpp>
#include <websocketpp/common/stdint.hpp>
#include <websocketpp/utf8_validator.hpp>

#include <string>

namespace websocketpp {
/// A package of types and methods for manipulating WebSocket close codes.
namespace close {
/// A package of types and methods for manipulating WebSocket close status'
namespace status {
    /// The type of a close code value.
    typedef uint16_t value;

    /// A blank value for internal use.
    static value const blank = 0;

    /// Close the connection without a WebSocket close handshake.
    /**
     * This special value requests that the WebSocket connection be closed
     * without performing the WebSocket closing handshake. This does not comply
     * with RFC6455, but should be safe to do if necessary. This could be useful
     * for clients that need to disconnect quickly and cannot afford the
     * complete handshake.
     */
    static value const omit_handshake = 1;

    /// Close the connection with a forced TCP drop.
    /**
     * This special value requests that the WebSocket connection be closed by
     * forcibly dropping the TCP connection. This will leave the other side of
     * the connection with a broken connection and some expensive timeouts. this
     * should not be done except in extreme cases or in cases of malicious
     * remote endpoints.
     */
    static value const force_tcp_drop = 2;

    /// Normal closure, meaning that the purpose for which the connection was
    /// established has been fulfilled.
    static value const normal = 1000;

    /// The endpoint was "going away", such as a server going down or a browser
    /// navigating away from a page.
    static value const going_away = 1001;

    /// A protocol error occurred.
    static value const protocol_error = 1002;

    /// The connection was terminated because an endpoint received a type of
    /// data it cannot accept.
    /**
     * (e.g., an endpoint that understands only text data MAY send this if it
     * receives a binary message).
     */
    static value const unsupported_data = 1003;

    /// A dummy value to indicate that no status code was received.
    /**
     * This value is illegal on the wire.
     */
    static value const no_status = 1005;

    /// A dummy value to indicate that the connection was closed abnormally.
    /**
     * In such a case there was no close frame to extract a value from. This
     * value is illegal on the wire.
     */
    static value const abnormal_close = 1006;

    /// An endpoint received message data inconsistent with its type.
    /**
     * For example: Invalid UTF8 bytes in a text message.
     */
    static value const invalid_payload = 1007;

    /// An endpoint received a message that violated its policy.
    /**
     * This is a generic status code that can be returned when there is no other
     * more suitable status code (e.g., 1003 or 1009) or if there is a need to
     * hide specific details about the policy.
     */
    static value const policy_violation = 1008;

    /// An endpoint received a message too large to process.
    static value const message_too_big = 1009;

    /// A client expected the server to accept a required extension request
    /**
     * The list of extensions that are needed SHOULD appear in the /reason/ part
     * of the Close frame. Note that this status code is not used by the server,
     * because it can fail the WebSocket handshake instead.
     */
    static value const extension_required = 1010;

    /// An endpoint encountered an unexpected condition that prevented it from
    /// fulfilling the request.
    static value const internal_endpoint_error = 1011;

    /// Indicates that the service is restarted. A client may reconnect and if
    /// if it chooses to do so, should reconnect using a randomized delay of
    /// 5-30s
    static value const service_restart = 1012;

    /// Indicates that the service is experiencing overload. A client should
    /// only connect to a different IP (when there are multiple for the target)
    /// or reconnect to the same IP upon user action.
    static value const try_again_later = 1013;

    /// Indicates that the server was acting as a gateway or proxy and received
    /// an invalid response from the upstream server. This is similar to 502
    /// HTTP Status Code.
    static value const bad_gateway = 1014;

    /// An endpoint failed to perform a TLS handshake
    /**
     * Designated for use in applications expecting a status code to indicate
     * that the connection was closed due to a failure to perform a TLS
     * handshake (e.g., the server certificate can't be verified). This value is
     * illegal on the wire.
     */
    static value const tls_handshake = 1015;
    
    /// A generic subprotocol error
    /**
     * Indicates that a subprotocol error occurred. Typically this involves
     * receiving a message that is not formatted as a valid message for the
     * subprotocol in use.
     */
    static value const subprotocol_error = 3000;
    
    /// A invalid subprotocol data
    /**
     * Indicates that data was received that violated the specification of the
     * subprotocol in use.
     */
    static value const invalid_subprotocol_data = 3001;

    /// First value in range reserved for future protocol use
    static value const rsv_start = 1016;
    /// Last value in range reserved for future protocol use
    static value const rsv_end = 2999;

    /// Test whether a close code is in a reserved range
    /**
     * @param [in] code The code to test
     * @return Whether or not code is reserved
     */
    inline bool reserved(value code) {
        return ((code >= rsv_start && code <= rsv_end) ||
                code == 1004);
    }

    /// First value in range that is always invalid on the wire
    static value const invalid_low = 999;
    /// Last value in range that is always invalid on the wire
    static value const invalid_high = 5000;

    /// Test whether a close code is invalid on the wire
    /**
     * @param [in] code The code to test
     * @return Whether or not code is invalid on the wire
     */
    inline bool invalid(value code) {
        return (code <= invalid_low || code >= invalid_high ||
                code == no_status || code == abnormal_close ||
                code == tls_handshake);
    }

    /// Determine if the code represents an unrecoverable error
    /**
     * There is a class of errors for which once they are discovered normal
     * WebSocket functionality can no longer occur. This function determines
     * if a given code is one of these values. This information is used to
     * determine if the system has the capability of waiting for a close
     * acknowledgement or if it should drop the TCP connection immediately
     * after sending its close frame.
     *
     * @param [in] code The value to test.
     * @return True if the code represents an unrecoverable error
     */
    inline bool terminal(value code) {
        return (code == protocol_error || code == invalid_payload ||
                code == policy_violation || code == message_too_big ||
                 code == internal_endpoint_error);
    }
    
    /// Return a human readable interpretation of a WebSocket close code
    /**
     * See https://tools.ietf.org/html/rfc6455#section-7.4 for more details.
     *
     * @since 0.3.0
     *
     * @param [in] code The code to look up.
     * @return A human readable interpretation of the code.
     */
    inline std::string get_string(value code) {
        switch (code) {
            case normal:
                return "Normal close";
            case going_away:
                return "Going away";
            case protocol_error:
                return "Protocol error";
            case unsupported_data:
                return "Unsupported data";
            case no_status:
                return "No status set";
            case abnormal_close:
                return "Abnormal close";
            case invalid_payload:
                return "Invalid payload";
            case policy_violation:
                return "Policy violoation";
            case message_too_big:
                return "Message too big";
            case extension_required:
                return "Extension required";
            case internal_endpoint_error:
                return "Internal endpoint error";
            case service_restart:
                return "Service restart";
            case try_again_later:
                return "Try again later";
            case bad_gateway:
                return "Bad gateway";
            case tls_handshake:
                return "TLS handshake failure";
            case subprotocol_error:
                return "Generic subprotocol error";
            case invalid_subprotocol_data:
                return "Invalid subprotocol data";
            default:
                return "Unknown";
        }
    }
} // namespace status

/// Type used to convert close statuses between integer and wire representations
union code_converter {
    uint16_t i;
    char c[2];
};

/// Extract a close code value from a close payload
/**
 * If there is no close value (ie string is empty) status::no_status is
 * returned. If a code couldn't be extracted (usually do to a short or
 * otherwise mangled payload) status::protocol_error is returned and the ec
 * value is flagged as an error. Note that this case is different than the case
 * where protocol error is received over the wire.
 *
 * If the value is in an invalid or reserved range ec is set accordingly.
 *
 * @param [in] payload Close frame payload value received over the wire.
 * @param [out] ec Set to indicate what error occurred, if any.
 * @return The extracted value
 */
inline status::value extract_code(std::string const & payload, lib::error_code
    & ec)
{
    ec = lib::error_code();

    if (payload.size() == 0) {
        return status::no_status;
    } else if (payload.size() == 1) {
        ec = make_error_code(error::bad_close_code);
        return status::protocol_error;
    }

    code_converter val;

    val.c[0] = payload[0];
    val.c[1] = payload[1];

    status::value code(ntohs(val.i));

    if (status::invalid(code)) {
        ec = make_error_code(error::invalid_close_code);
    }

    if (status::reserved(code)) {
        ec = make_error_code(error::reserved_close_code);
    }

    return code;
}

/// Extract the reason string from a close payload
/**
 * The string should be a valid UTF8 message. error::invalid_utf8 will be set if
 * the function extracts a reason that is not valid UTF8.
 *
 * @param [in] payload The payload string to extract a reason from.
 * @param [out] ec Set to indicate what error occurred, if any.
 * @return The reason string.
 */
inline std::string extract_reason(std::string const & payload, lib::error_code
    & ec)
{
    std::string reason;
    ec = lib::error_code();

    if (payload.size() > 2) {
        reason.append(payload.begin()+2,payload.end());
    }

    if (!websocketpp::utf8_validator::validate(reason)) {
        ec = make_error_code(error::invalid_utf8);
    }

    return reason;
}

} // namespace close
} // namespace websocketpp

#endif // WEBSOCKETPP_CLOSE_HPP