"""TLS handshake layer base classes and utilities."""

import os
import time
import struct
from abc import ABC, abstractmethod
from ja3requests.protocol.tls.debug import debug


class Random:
    """
    struct {
        uint32 gmt_unix_time;
        opaque random_bytes[28];
    } Random

    gmt_unix_time
         The current time and date in standard UNIX 32-bit format
         (seconds since the midnight starting Jan 1, 1970, UTC, ignoring
         leap seconds) according to the sender's internal clock.  Clocks
         are not required to be set correctly by the basic TLS protocol;
         higher-level or application protocols may define additional
         requirements.  Note that, for historical reasons, the data
         element is named using GMT, the predecessor of the current
         worldwide time base, UTC.

    random_bytes
        28 bytes generated by a secure random number generator.
    """

    def __init__(self):
        self.gmt_unix_time = self.get_unix_time()
        self.random_bytes = os.urandom(28)

    def __bytes__(self):
        return self.bytes()

    def bytes(self):
        """Return the random bytes as a concatenation of unix time and random data."""
        if self.gmt_unix_time and self.random_bytes:
            return self.gmt_unix_time.to_bytes(4, byteorder="big") + self.random_bytes
        return os.urandom(32)

    @staticmethod
    def get_unix_time():
        """Return the current UNIX time as a 32-bit integer."""
        current_time_unix = int(time.time())
        current_time_unix_32bit = current_time_unix % (2**32)

        return current_time_unix_32bit


class HandShake(ABC):
    """
    The TLS record layer receives uninterpreted data from higher layers
    in non-empty blocks of arbitrary size.
    The record layer fragments information blocks into TLSPlaintext
    records carrying data in chunks of 2^14 bytes or less.  Client
    message boundaries are not preserved in the record layer (i.e.,
    multiple client messages of the same ContentType MAY be coalesced
    into a single TLSPlaintext record, or a single message MAY be
    fragmented across several records).

    struct {
      uint8 major;
      uint8 minor;
    } ProtocolVersion;

    enum {
      change_cipher_spec(20), alert(21), handshake(22),
      application_data(23), (255)
    } ContentType;

    struct {
      ContentType type;
      ProtocolVersion version;
      uint16 length;
      opaque fragment[TLSPlaintext.length];
    } TLSPlaintext;

    The TLS Handshake Protocol is one of the defined higher-level clients
    of the TLS Record Protocol.  This protocol is used to negotiate the
    secure attributes of a session.  Handshake messages are supplied to
    the TLS record layer, where they are encapsulated within one or more
    TLSPlaintext structures, which are processed and transmitted as
    specified by the current active session state.

    enum {
        hello_request(0), client_hello(1), server_hello(2),
        certificate(11), server_key_exchange (12),
        certificate_request(13), server_hello_done(14),
        certificate_verify(15), client_key_exchange(16),
        finished(20)
        (255)
    } HandshakeType;

    struct {
        HandshakeType msg_type;
        uint24 length;
        select (HandshakeType) {
            case hello_request:         HelloRequest;
            case client_hello:          ClientHello;
            case server_hello:          ServerHello;
            case certificate:            Certificate;
            case server_key_exchange:   ServerKeyExchange;
            case certificate_request:    CertificateRequest;
            case server_hello_done:     ServerHelloDone;
            case certificate_verify:     CertificateVerify;
            case client_key_exchange:   ClientKeyExchange;
            case finished:               Finished;
        } body;
    } Handshake;

    The ClientHello message includes a random structure, which is used later in the protocol.

    opaque SessionID<0..32>;
    uint8 CipherSuite[2];    /* Cryptographic suite selector */
    enum { null(0), (255) } CompressionMethod;

    struct {
        ExtensionType extension_type;
        opaque extension_data<0..2^16-1>;
    } Extension;

    enum {
        signature_algorithms(13), (65535)
    } ExtensionType;
    -  "extension_type" identifies the particular extension type.
    -  "extension_data" contains information specific to the particular extension type.

    struct {
        uint8 major;
        uint8 minor;
    } ProtocolVersion;

    ProtocolVersion version = { 3, 2 };     /* TLS v1.1*/
    ProtocolVersion version = { 3, 3 };     /* TLS v1.2*/

    struct {
        ProtocolVersion client_version;
        Random random;
        SessionID session_id;
        CipherSuite cipher_suites<2..2^16-2>;
        CompressionMethod compression_methods<1..2^8-1>;
        select (extensions_present) {
            case false:
                struct {};
            case true:
                Extension extensions<0..2^16-1>;
        };
    } ClientHello;

    cipher_suites
        This is a list of the cryptographic options supported by the
        client, with the client's first preference first.  If the
        session_id field is not empty (implying a session resumption
        request), this vector MUST include at least the cipher_suite from
        that session.

        A cipher suite defines a cipher specification supported in TLS
        Version 1.2.
            CipherSuite TLS_NULL_WITH_NULL_NULL               = { 0x00,0x00 };

        The following CipherSuite definitions require that the server provide
        an RSA certificate that can be used for key exchange.  The server may
        request any signature-capable certificate in the certificate request
        message.
            CipherSuite TLS_RSA_WITH_NULL_MD5                 = { 0x00,0x01 };
            CipherSuite TLS_RSA_WITH_NULL_SHA                 = { 0x00,0x02 };
            CipherSuite TLS_RSA_WITH_NULL_SHA256              = { 0x00,0x3B };
            CipherSuite TLS_RSA_WITH_RC4_128_MD5              = { 0x00,0x04 };
            CipherSuite TLS_RSA_WITH_RC4_128_SHA              = { 0x00,0x05 };
            CipherSuite TLS_RSA_WITH_3DES_EDE_CBC_SHA         = { 0x00,0x0A };
            CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA          = { 0x00,0x2F };
            CipherSuite TLS_RSA_WITH_AES_256_CBC_SHA          = { 0x00,0x35 };
            CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA256       = { 0x00,0x3C };
            CipherSuite TLS_RSA_WITH_AES_256_CBC_SHA256       = { 0x00,0x3D };

        The following cipher suite definitions are used for server-
        authenticated (and optionally client-authenticated) Diffie-Hellman.
        DH denotes cipher suites in which the server's certificate contains
        the Diffie-Hellman parameters signed by the certificate authority
        (CA).  DHE denotes ephemeral Diffie-Hellman, where the Diffie-Hellman
        parameters are signed by a signature-capable certificate, which has
        been signed by the CA.  The signing algorithm used by the server is
        specified after the DHE component of the CipherSuite name.  The
        server can request any signature-capable certificate from the client
        for client authentication, or it may request a Diffie-Hellman
        certificate.  Any Diffie-Hellman certificate provided by the client
        must use the parameters (group and generator) described by the
        server.
            CipherSuite TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA      = { 0x00,0x0D };
            CipherSuite TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA      = { 0x00,0x10 };
            CipherSuite TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA     = { 0x00,0x13 };
            CipherSuite TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA     = { 0x00,0x16 };
            CipherSuite TLS_DH_DSS_WITH_AES_128_CBC_SHA       = { 0x00,0x30 };
            CipherSuite TLS_DH_RSA_WITH_AES_128_CBC_SHA       = { 0x00,0x31 };
            CipherSuite TLS_DHE_DSS_WITH_AES_128_CBC_SHA      = { 0x00,0x32 };
            CipherSuite TLS_DHE_RSA_WITH_AES_128_CBC_SHA      = { 0x00,0x33 };
            CipherSuite TLS_DH_DSS_WITH_AES_256_CBC_SHA       = { 0x00,0x36 };
            CipherSuite TLS_DH_RSA_WITH_AES_256_CBC_SHA       = { 0x00,0x37 };
            CipherSuite TLS_DHE_DSS_WITH_AES_256_CBC_SHA      = { 0x00,0x38 };
            CipherSuite TLS_DHE_RSA_WITH_AES_256_CBC_SHA      = { 0x00,0x39 };
            CipherSuite TLS_DH_DSS_WITH_AES_128_CBC_SHA256    = { 0x00,0x3E };
            CipherSuite TLS_DH_RSA_WITH_AES_128_CBC_SHA256    = { 0x00,0x3F };
            CipherSuite TLS_DHE_DSS_WITH_AES_128_CBC_SHA256   = { 0x00,0x40 };
            CipherSuite TLS_DHE_RSA_WITH_AES_128_CBC_SHA256   = { 0x00,0x67 };
            CipherSuite TLS_DH_DSS_WITH_AES_256_CBC_SHA256    = { 0x00,0x68 };
            CipherSuite TLS_DH_RSA_WITH_AES_256_CBC_SHA256    = { 0x00,0x69 };
            CipherSuite TLS_DHE_DSS_WITH_AES_256_CBC_SHA256   = { 0x00,0x6A };
            CipherSuite TLS_DHE_RSA_WITH_AES_256_CBC_SHA256   = { 0x00,0x6B };

        The following cipher suites are used for completely anonymous
        Diffie-Hellman communications in which neither party is
        authenticated.  Note that this mode is vulnerable to man-in-the-
        middle attacks.  Using this mode therefore is of limited use: These
        cipher suites MUST NOT be used by TLS 1.2 implementations unless the
        application layer has specifically requested to allow anonymous key
        exchange.  (Anonymous key exchange may sometimes be acceptable, for
        example, to support opportunistic encryption when no set-up for
        authentication is in place, or when TLS is used as part of more
        complex security protocols that have other means to ensure
        authentication.)
            CipherSuite TLS_DH_anon_WITH_RC4_128_MD5          = { 0x00,0x18 };
            CipherSuite TLS_DH_anon_WITH_3DES_EDE_CBC_SHA     = { 0x00,0x1B };
            CipherSuite TLS_DH_anon_WITH_AES_128_CBC_SHA      = { 0x00,0x34 };
            CipherSuite TLS_DH_anon_WITH_AES_256_CBC_SHA      = { 0x00,0x3A };
            CipherSuite TLS_DH_anon_WITH_AES_128_CBC_SHA256   = { 0x00,0x6C };
            CipherSuite TLS_DH_anon_WITH_AES_256_CBC_SHA256   = { 0x00,0x6D };

        Note: The cipher suite values { 0x00, 0x1C } and { 0x00, 0x1D } are
        reserved to avoid collision with Fortezza-based cipher suites in
        SSL 3.
    """

    def __init__(self):
        self._version = None
        self._random = None
        self._session_id = None
        self._cipher_suites = None
        self._compression_methods = None
        self._extensions = None
        self.t = []

    def __len__(self):

        return self.length()

    @property
    @abstractmethod
    def handshake_type(self) -> bytes:
        """Return the handshake type identifier byte."""
        raise NotImplementedError(
            "handshake_type method must be implemented by subclass."
        )

    @property
    def version(self) -> bytes:
        """
        client_version
            The version of the TLS protocol by which the client wishes to
            communicate during this session.  This SHOULD be the latest
            (highest valued) version supported by the client.  For this
            version of the specification, the version will be 3.3.
        :return:
        """

        return self._version

    @version.setter
    def version(self, attr: bytes):

        self._version = attr

    @property
    def random(self) -> bytes:
        """
        random
            A client-generated random structure.
        :return:
        """
        if not self._random:
            self._random = bytes(Random())

        return self._random

    @random.setter
    def random(self, attr: bytes):

        self._random = attr

    @property
    def session_id(self) -> bytes:
        """
        session_id
            The ID of a session the client wishes to use for this connection.
            This field is empty if no session_id is available, or if the
            client wishes to generate new security parameters.
        :return:
        """
        if not self._session_id:
            self._session_id = struct.pack("B", 0)
            # self._session_id = os.urandom(32)

        return self._session_id

    @session_id.setter
    def session_id(self, attr: bytes):

        self._session_id = attr

    @property
    def cipher_suites(self) -> bytes:
        """
        cipher_suites
            This is a list of the cryptographic options supported by the
            client, with the client's first preference first.  If the session_id
            field is not empty (implying a session resumption request),
            this vector MUST include at least the cipher_suite from that session.
        :return:
        """
        if not self._cipher_suites:
            self._cipher_suites = int.to_bytes(0x1301, 2, byteorder="big")

        return self._cipher_suites

    @cipher_suites.setter
    def cipher_suites(self, attr: bytes):

        self._cipher_suites = attr

    @property
    def compression_methods(self) -> bytes:
        """
        compression_methods
            This is a list of the compression methods supported by the client,
            sorted by client preference.  If the session_id field is not empty
            (implying a session resumption request), it MUST include the
            compression_method from that session.  This vector MUST contain,
            and all implementations MUST support, CompressionMethod.null.Thus,
            a client and server will always be able to agree on a compression method.
        :return:
        """

        if not self._compression_methods:
            self._compression_methods = struct.pack("B", 0)
            # self._compression_methods = struct.pack("B", 255)

        return self._compression_methods

    @compression_methods.setter
    def compression_methods(self, attr: bytes):

        self._compression_methods = attr

    @property
    def extensions(self) -> bytes:
        """
        extensions
            Clients MAY request extended functionality from servers by sending
            data in the extensions field.
        :return:
        """

        return self._extensions

    @extensions.setter
    def extensions(self, attr: bytes):

        self._extensions = attr

    def content(self):  # pylint: disable=too-many-branches
        """Build the handshake content from all configured fields."""
        content = b""
        if self.version:
            content += self.version
            if "version" not in self.t:
                debug(f"version: {self.version}", level=2)
                self.t.append("version")

        if self.random:
            content += self.random
            if "random" not in self.t:
                debug(f"random: {self.random}", level=2)
                self.t.append("random")

        if self.session_id:
            if self.session_id == b'\x00':
                content += self.session_id
            else:
                content += struct.pack("B", len(self.session_id))
                content += self.session_id

            if "session_id" not in self.t:
                debug(f"session id: {self.session_id}", level=2)
                self.t.append("session_id")

        if self.cipher_suites:
            content += struct.pack(
                "!H", len(self.cipher_suites)
            )  # 2 bytes for cipher suites length
            content += self.cipher_suites
            if "cipher_suites" not in self.t:
                debug(f"cipher suites: {self.cipher_suites}", level=2)
                self.t.append("cipher_suites")

        if self.compression_methods:
            content += struct.pack("B", len(self.compression_methods))
            content += self.compression_methods
            if "compression_methods" not in self.t:
                debug(f"compression methods: {self.compression_methods}", level=2)
                self.t.append("compression_methods")

        if self.extensions:
            content += self.extensions
            if "extensions" not in self.t:
                debug(f"extensions: {self.extensions}", level=2)
                self.t.append("extensions")

        return content

    def length(self) -> bytes:
        """
        uint24 length;             /* bytes in message */
        :return:
        """

        content = self.content()
        return struct.pack("!I", len(content))[1:]

    @property
    def handshake_message(self):
        """
        Raw handshake message without TLS record layer wrapper
        Used for handshake hash calculation
        """
        return self.handshake_type + self.length() + self.content()

    @property
    def message(self):
        """
        Complete TLS record containing the handshake message
        """
        handshake_msg = self.handshake_message
        message = (
            b'\x16'
            + b'\x03\x01'  # Use TLS 1.0 version for record layer (for compatibility)
            + struct.pack("!H", len(handshake_msg))
            + handshake_msg
        )
        return message


if __name__ == '__main__':
    print(Random().bytes())
