# SPDX-License-Identifier: MPL-2.0
# pylint: disable=too-many-lines

import abc
import calendar
import collections
import datetime
import enum
import hashlib
import random
import attr


from cryptodatahub.common.exception import InvalidValue
from cryptodatahub.tls.algorithm import SslCipherKind, TlsCipherSuite, TlsCipherSuiteExtension, TlsCompressionMethod

from cryptoparser.common.base import (
    OneByteEnumParsable,
    Opaque,
    OpaqueParam,
    VariantParsable,
    Vector,
    VectorEnumCodeNumeric,
    VectorParamEnumCodeNumeric,
    VectorParamNumeric,
    VectorParamParsable,
    VectorParsable,
)
from cryptoparser.common.exception import NotEnoughData, InvalidType
from cryptoparser.common.parse import ParsableBase, ParserBinary, ComposerBinary, ComposerText

from cryptoparser.tls.extension import (
    TlsCertificateStatusType,
    TlsExtensionType,
    TlsExtensionsClient,
    TlsExtensionsServer,
    TlsSignatureAndHashAlgorithmVector,
)
from cryptoparser.tls.grease import TlsInvalidType, TlsInvalidTypeOneByte, TlsInvalidTypeTwoByte
from cryptoparser.tls.version import TlsProtocolVersion, TlsVersion
from cryptoparser.tls.ciphersuite import SslCipherKindFactory, TlsCipherSuiteFactory


class TlsContentType(enum.IntEnum):
    CHANGE_CIPHER_SPEC = 0x14
    ALERT = 0x15
    HANDSHAKE = 0x16
    APPLICATION_DATA = 0x17
    HEARTBEAT = 0x18


@attr.s
class SubprotocolParser:
    _subprotocol_type = attr.ib(validator=attr.validators.instance_of(enum.IntEnum))

    @classmethod
    @abc.abstractmethod
    def _get_subprotocol_parsers(cls):
        raise NotImplementedError()

    @classmethod
    def register_subprotocol_parser(cls, subprotocol_type, parsable_class):
        subprotocol_parsers = cls._get_subprotocol_parsers()
        subprotocol_parsers[subprotocol_type] = parsable_class

    def parse(self, parsable):
        subprotocol_parsers = self._get_subprotocol_parsers()

        if self._subprotocol_type in subprotocol_parsers:
            parsed_object, parsed_length = subprotocol_parsers[self._subprotocol_type].parse_immutable(parsable)
            return parsed_object, parsed_length

        raise InvalidValue(self._subprotocol_type, TlsSubprotocolMessageBase)


class TlsSubprotocolMessageBase(ParsableBase):
    @classmethod
    @abc.abstractmethod
    def _parse(cls, parsable):
        raise NotImplementedError()

    @abc.abstractmethod
    def compose(self):
        raise NotImplementedError()


class TlsAlertLevel(enum.IntEnum):
    WARNING = 0x01
    FATAL = 0x02


class TlsAlertDescription(enum.IntEnum):
    CLOSE_NOTIFY = 0x00
    UNEXPECTED_MESSAGE = 0x0a
    BAD_RECORD_MAC = 0x14
    RECORD_OVERFLOW = 0x16
    HANDSHAKE_FAILURE = 0x28
    BAD_CERTIFICATE = 0x2a
    UNSUPPORTED_CERTIFICATE = 0x2b
    CERTIFICATE_REVOKED = 0x2c
    CERTIFICATE_EXPIRED = 0x2d
    CERTIFICATE_UNKNOWN = 0x2e
    ILLEGAL_PARAMETER = 0x2f
    UNKNOWN_CA = 0x30
    ACCESS_DENIED = 0x30
    DECODE_ERROR = 0x32
    DECRYPT_ERROR = 0x33
    PROTOCOL_VERSION = 0x46
    INSUFFICIENT_SECURITY = 0x47
    INTERNAL_ERROR = 0x50
    INAPPROPRIATE_FALLBACK = 0x56
    USER_CANCELED = 0x5a
    MISSING_EXTENSION = 0x6d
    UNSUPPORTED_EXTENSION = 0x6e
    CERTIFICATE_UNOBTAINABLE = 0x6f
    UNRECOGNIZED_NAME = 0x70
    BAD_CERTIFICATE_STATUS_RESPONSE = 0x71
    BAD_CERTIFICATE_HASH_VALUE = 0x72
    UNKNOWN_PSK_IDENTITY = 0x73
    CERTIFICATE_REQUIRED = 0x74
    NO_APPLICATION_PROTOCOL = 0x78


@attr.s
class TlsAlertMessage(TlsSubprotocolMessageBase):
    _SIZE = 2

    level = attr.ib()
    description = attr.ib()

    @classmethod
    def _parse(cls, parsable):
        if len(parsable) < cls._SIZE:
            raise NotEnoughData(cls._SIZE - len(parsable))

        parser = ParserBinary(parsable)

        parser.parse_numeric('level', 1)
        parser.parse_numeric('description', 1)

        return TlsAlertMessage(parser['level'], parser['description']), parser.parsed_length

    def compose(self):
        composer = ComposerBinary()

        composer.compose_numeric(self.level, 1)
        composer.compose_numeric(self.description, 1)

        return composer.composed_bytes

    @level.validator
    def _validator_level(self, attribute, value):  # pylint: disable=unused-argument
        try:
            self.level = TlsAlertLevel(value)
        except ValueError as e:
            raise InvalidValue(value, TlsAlertLevel, 'level') from e

    @description.validator
    def _validator_description(self, attribute, value):  # pylint: disable=unused-argument
        try:
            self.description = TlsAlertDescription(value)
        except ValueError as e:
            raise InvalidValue(value, TlsAlertDescription) from e


class TlsChangeCipherSpecType(enum.IntEnum):
    CHANGE_CIPHER_SPEC = 0x01


@attr.s
class TlsChangeCipherSpecMessage(TlsSubprotocolMessageBase):
    _change_cipher_spec_type = attr.ib(
        default=TlsChangeCipherSpecType.CHANGE_CIPHER_SPEC,
        validator=attr.validators.in_(TlsChangeCipherSpecType)
    )

    @classmethod
    def _parse(cls, parsable):
        parser = ParserBinary(parsable)

        parser.parse_numeric('change_cipher_spec_type', 1, TlsChangeCipherSpecType)

        return TlsChangeCipherSpecMessage(parser['change_cipher_spec_type']), parser.parsed_length

    def compose(self):
        composer = ComposerBinary()

        composer.compose_numeric(self._change_cipher_spec_type, 1)

        return composer.composed_bytes


@attr.s
class TlsApplicationDataMessage(TlsSubprotocolMessageBase):
    data = attr.ib(attr.validators.instance_of(bytearray))

    @classmethod
    def _parse(cls, parsable):
        return TlsApplicationDataMessage(parsable), len(parsable)

    def compose(self):
        return self.data


class TlsHandshakeType(enum.IntEnum):
    HELLO_REQUEST = 0x00
    CLIENT_HELLO = 0x01
    SERVER_HELLO = 0x02
    HELLO_VERIFY_REQUEST = 0x03
    NEW_SESSION_TICKET = 0x04
    HELLO_RETRY_REQUEST = 0x06
    ENCRYPTED_EXTENSIONS = 0x08
    CERTIFICATE = 0x0b
    SERVER_KEY_EXCHANGE = 0x0c
    CERTIFICATE_REQUEST = 0x0d
    SERVER_HELLO_DONE = 0x0e
    CERTIFICATE_VERIFY = 0x0f
    CLIENT_KEY_EXCHANGE = 0x10
    CLIENT_CERTIFICATE_REQUEST = 0x11
    FINISHED = 0x14
    CLIENT_CERTIFICATE_URL = 0x15
    CERTIFICATE_STATUS = 0x16
    SUPPLEMENTAL_DATA = 0x17
    KEY_UPDATE = 0x18
    COMPRESSED_CERTIFICATE = 0x19
    EKT_KEY = 0x15
    MESSAGE_HASH = 254


@attr.s
class TlsHandshakeMessage(TlsSubprotocolMessageBase):
    """The payload of a handshake record.
    """
    _HEADER_SIZE = 4

    @classmethod
    @abc.abstractmethod
    def get_handshake_type(cls):
        raise NotImplementedError()

    @classmethod
    def _parse_handshake_header(cls, parsable):
        if len(parsable) < cls._HEADER_SIZE:
            raise NotEnoughData(cls._HEADER_SIZE - len(parsable))

        parser = ParserBinary(parsable)

        try:
            parser.parse_numeric('handshake_type', 1, TlsHandshakeType)
        except InvalidValue as e:
            raise e

        if parser['handshake_type'] != cls.get_handshake_type():
            raise InvalidType()

        try:
            parser.parse_bytes('payload', 3)
        except NotEnoughData as e:
            raise NotEnoughData(e.bytes_needed) from e

        return parser

    def _compose_header(self, payload_length):
        composer = ComposerBinary()

        composer.compose_numeric(self.get_handshake_type(), 1)
        composer.compose_numeric(payload_length, 3)

        return composer.composed_bytes


class TlsHandshakeHelloRandomBytes(Vector):
    @classmethod
    def _parse(cls, parsable):
        composer = ComposerBinary()
        vector_param = cls.get_param()
        composer.compose_numeric(vector_param.min_byte_num, vector_param.item_num_size)

        vector, parsed_length = super()._parse(composer.composed_bytes + parsable)

        return cls(vector), parsed_length - vector_param.item_num_size

    def compose(self):
        return super().compose()[self.get_param().item_num_size:]

    @classmethod
    def get_param(cls):
        return OpaqueParam(min_byte_num=28, max_byte_num=28)


@attr.s
class TlsHandshakeHelloRandom(ParsableBase):
    time = attr.ib(validator=attr.validators.instance_of(datetime.datetime))
    random = attr.ib(validator=attr.validators.instance_of(TlsHandshakeHelloRandomBytes))

    @time.default
    def _default_time(self):  # pylint: disable=no-self-use
        return datetime.datetime.now(datetime.timezone.utc)

    @random.default
    def _default_random(self):  # pylint: disable=no-self-use
        return TlsHandshakeHelloRandomBytes(
            bytearray.fromhex(f'{random.getrandbits(224):28x}'.zfill(56))
        )

    @classmethod
    def _parse(cls, parsable):
        parser = ParserBinary(parsable)

        parser.parse_timestamp('time', item_size=4)
        parser.parse_parsable('random', TlsHandshakeHelloRandomBytes)

        return TlsHandshakeHelloRandom(parser['time'], parser['random']), parser.parsed_length

    def compose(self):
        composer = ComposerBinary()

        composer.compose_numeric(int(calendar.timegm(self.time.utctimetuple())), 4)
        composer.compose_parsable(self.random)

        return composer.composed_bytes


@attr.s
class TlsHandshakeHello(TlsHandshakeMessage):
    @classmethod
    def _parse_hello_header(cls, parsable):
        parser = ParserBinary(parsable)

        parser.parse_parsable('protocol_version', TlsProtocolVersion)
        parser.parse_parsable('random', TlsHandshakeHelloRandom)
        parser.parse_parsable('session_id', TlsSessionIdVector)

        return parser

    def _compose_header(self, payload_length):
        composer = ComposerBinary()

        handshake_header_bytes = super()._compose_header(
            payload_length + composer.composed_length
        )

        return handshake_header_bytes + composer.composed_bytes

    @classmethod
    def _parse_extensions(cls, handshake_header_parser, parser, extensions_class):
        if parser.parsed_length >= len(handshake_header_parser['payload']):
            return None

        parser.parse_parsable('extensions', extensions_class)

        return parser

    @staticmethod
    def _compose_extensions(extensions):
        extension_bytes = bytearray()

        for extension in extensions:
            extension_bytes += extension.compose()

        payload_composer = ComposerBinary()
        if extensions:
            payload_composer.compose_numeric(len(extension_bytes), 2)

        return payload_composer.composed_bytes + extension_bytes

    @classmethod
    @abc.abstractmethod
    def _parse(cls, parsable):
        raise NotImplementedError()

    @abc.abstractmethod
    def compose(self):
        raise NotImplementedError()


class TlsCipherSuiteVector(VectorParsable):
    @classmethod
    def get_param(cls):
        return VectorParamEnumCodeNumeric(
            item_class=TlsCipherSuiteFactory,
            fallback_class=TlsInvalidTypeTwoByte,
            min_byte_num=2, max_byte_num=2 ** 16 - 2
        )


class TlsCompressionMethodFactory(OneByteEnumParsable):
    @classmethod
    def get_enum_class(cls):
        return TlsCompressionMethod

    @abc.abstractmethod
    def compose(self):
        raise NotImplementedError()


class TlsCompressionMethodVector(VectorEnumCodeNumeric):
    @classmethod
    def get_param(cls):
        return VectorParamEnumCodeNumeric(
            item_class=TlsCompressionMethodFactory,
            fallback_class=TlsInvalidTypeOneByte,
            min_byte_num=1,
            max_byte_num=2 ** 8 - 1,
        )


class TlsSessionIdVector(Vector):
    @classmethod
    def get_param(cls):
        return VectorParamNumeric(item_size=1, min_byte_num=0, max_byte_num=32)


@attr.s(frozen=True)
class TlsJA4Fingerprint:
    fingerprint = attr.ib(validator=attr.validators.instance_of(str))
    fingerprint_original = attr.ib(validator=attr.validators.instance_of(str))
    fingerprint_raw = attr.ib(validator=attr.validators.instance_of(str))
    fingerprint_raw_original = attr.ib(validator=attr.validators.instance_of(str))


@attr.s
class TlsHandshakeClientHello(TlsHandshakeHello):  # pylint: disable=too-many-instance-attributes
    cipher_suites = attr.ib(
        converter=TlsCipherSuiteVector,
        validator=attr.validators.instance_of(TlsCipherSuiteVector)
    )
    protocol_version = attr.ib(
        default=TlsProtocolVersion(TlsVersion.TLS1_2),
        validator=attr.validators.instance_of(TlsProtocolVersion),
    )
    random = attr.ib(
        default=TlsHandshakeHelloRandom(),
        validator=attr.validators.instance_of(TlsHandshakeHelloRandom),
    )
    session_id = attr.ib(
        default=TlsSessionIdVector(()),
        converter=TlsSessionIdVector,
        validator=attr.validators.instance_of(TlsSessionIdVector),
    )
    compression_methods = attr.ib(
        default=TlsCompressionMethodVector([TlsCompressionMethod.NULL, ]),
        converter=TlsCompressionMethodVector,
        validator=attr.validators.instance_of(TlsCompressionMethodVector),
    )
    extensions = attr.ib(
        default=TlsExtensionsClient(()),
        converter=TlsExtensionsClient,
        validator=attr.validators.instance_of(TlsExtensionsClient)
    )
    fallback_scsv = attr.ib(default=False, validator=attr.validators.instance_of(bool))
    empty_renegotiation_info_scsv = attr.ib(default=True, validator=attr.validators.instance_of(bool))

    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.CLIENT_HELLO

    @classmethod
    def _parse(cls, parsable):

        handshake_header_parser = cls._parse_handshake_header(parsable)

        parser = cls._parse_hello_header(handshake_header_parser['payload'])
        parser.parse_parsable('cipher_suites', TlsCipherSuiteVector)
        parser.parse_parsable('compression_methods', TlsCompressionMethodVector)

        extension_parser = cls._parse_extensions(handshake_header_parser, parser, TlsExtensionsClient)

        cipher_suites = []
        fallback_scsv = False
        empty_renegotiation_info_scsv = False
        for cipher_suite in parser['cipher_suites']:
            if cipher_suite.value.code == TlsCipherSuiteExtension.FALLBACK_SCSV.value.code:
                fallback_scsv = True
            elif cipher_suite.value.code == TlsCipherSuiteExtension.EMPTY_RENEGOTIATION_INFO_SCSV.value.code:
                empty_renegotiation_info_scsv = True
            else:
                cipher_suites.append(cipher_suite)

        return TlsHandshakeClientHello(
            cipher_suites,
            parser['protocol_version'],
            parser['random'],
            parser['session_id'],
            parser['compression_methods'],
            extensions=parser['extensions'] if extension_parser else TlsExtensionsClient([]),
            fallback_scsv=fallback_scsv,
            empty_renegotiation_info_scsv=empty_renegotiation_info_scsv,
        ), handshake_header_parser.parsed_length

    def compose(self):
        payload_composer = ComposerBinary()

        payload_composer.compose_parsable(self.protocol_version)
        payload_composer.compose_parsable(self.random)
        payload_composer.compose_parsable(self.session_id)
        if self.fallback_scsv:
            self.cipher_suites.append(TlsCipherSuiteExtension.FALLBACK_SCSV)
        if self.empty_renegotiation_info_scsv:
            self.cipher_suites.append(TlsCipherSuiteExtension.EMPTY_RENEGOTIATION_INFO_SCSV)
        payload_composer.compose_numeric(len(self.cipher_suites) * self.cipher_suites.get_param().item_num_size, 2)
        payload_composer.compose_numeric_array_enum_coded(self.cipher_suites)
        if self.fallback_scsv:
            del self.cipher_suites[-1]
        if self.empty_renegotiation_info_scsv:
            del self.cipher_suites[-1]
        payload_composer.compose_parsable(self.compression_methods)

        extension_bytes = self._compose_extensions(self.extensions)

        header_bytes = self._compose_header(payload_composer.composed_length + len(extension_bytes))

        return header_bytes + payload_composer.composed_bytes + extension_bytes

    def ja3(self):
        parser = ParserBinary(self.protocol_version.compose())
        parser.parse_numeric('tls_protocol_version', 2)

        cipher_suites = [cipher_suite.value.code for cipher_suite in self.cipher_suites]

        extension_types = []
        named_curves = []
        ec_point_formats = []
        for extension in self.extensions:
            if (not isinstance(extension.extension_type, TlsInvalidTypeTwoByte) or
                    extension.extension_type.value.value_type != TlsInvalidType.GREASE):
                extension_types.append(extension.extension_type.value.code)

            if extension.extension_type == TlsExtensionType.SUPPORTED_GROUPS:
                named_curves = [
                    named_curve.value.code
                    for named_curve in extension.elliptic_curves
                    if (not isinstance(named_curve, TlsInvalidTypeTwoByte) or
                        named_curve.value.value_type != TlsInvalidType.GREASE)
                ]
            elif extension.extension_type == TlsExtensionType.EC_POINT_FORMATS:
                ec_point_formats = [
                    point_format.value.code
                    for point_format in extension.point_formats
                    if (not isinstance(point_format, TlsInvalidTypeOneByte) or
                        point_format.value.value_type != TlsInvalidType.GREASE)
                ]

        composer = ComposerText()
        composer.compose_numeric(parser['tls_protocol_version'])
        for numeric_array in (cipher_suites, extension_types, named_curves, ec_point_formats):
            composer.compose_separator(',')
            composer.compose_numeric_array(numeric_array, '-')

        return composer.composed.decode('ascii')

    _JA4_VERSION_STRINGS = {
        TlsVersion.TLS1_3: '13',
        TlsVersion.TLS1_2: '12',
        TlsVersion.TLS1_1: '11',
        TlsVersion.TLS1: '10',
        TlsVersion.SSL3: 's3',
        TlsVersion.SSL2: 's2',
    }

    @staticmethod
    def _ja4_is_grease(item):
        return isinstance(item, TlsInvalidTypeTwoByte) and item.value.value_type == TlsInvalidType.GREASE

    @staticmethod
    def _ja4_sha256(data):
        return hashlib.sha256(data).hexdigest()[:12]

    @classmethod
    def _ja4_hashes(cls, cipher_hexes, extension_hexes, signature_algorithm_hexes):
        cipher_composer = ComposerText()
        cipher_composer.compose_string_array(cipher_hexes, ',')
        cipher_hash = cls._ja4_sha256(cipher_composer.composed) if cipher_hexes else '000000000000'

        extension_composer = ComposerText()
        extension_composer.compose_string_array(extension_hexes, ',')
        if signature_algorithm_hexes:
            extension_composer.compose_separator('_')
            extension_composer.compose_string_array(signature_algorithm_hexes, ',')
        extension_hash = cls._ja4_sha256(extension_composer.composed) if extension_hexes else '000000000000'

        return cipher_hash, extension_hash

    @staticmethod
    def _ja4_raw(header, cipher_hexes, extension_hexes, signature_algorithm_hexes):
        composer = ComposerText()
        composer.compose_string(header)
        for hexes in (cipher_hexes, extension_hexes, signature_algorithm_hexes):
            composer.compose_separator('_')
            composer.compose_string_array(hexes, ',')

        return composer.composed.decode('ascii')

    @staticmethod
    def _ja4_alpn_characters(value):
        return value[0] + value[-1]

    def _ja4_version_string(self):
        protocol_versions = []
        for extension in self.extensions:
            if extension.extension_type == TlsExtensionType.SUPPORTED_VERSIONS:
                protocol_versions = [
                    version
                    for version in extension.supported_versions
                    if not self._ja4_is_grease(version)
                ]
                break

        if not protocol_versions:
            protocol_versions = [self.protocol_version]

        highest_protocol_version = max(
            protocol_versions, key=lambda protocol_version: protocol_version.version.value.code
        )

        return self._JA4_VERSION_STRINGS.get(highest_protocol_version.version, '00')

    def _ja4_alpn_value(self):
        for extension in self.extensions:
            if extension.extension_type == TlsExtensionType.APPLICATION_LAYER_PROTOCOL_NEGOTIATION:
                return self._ja4_alpn_characters(list(extension.protocol_names)[0].value.code)

        return '00'

    def _ja4_signature_algorithm_hexes(self):
        for extension in self.extensions:
            if extension.extension_type == TlsExtensionType.SIGNATURE_ALGORITHMS:
                return [
                    f'{algorithm.value.code:04x}'
                    for algorithm in extension.hash_and_signature_algorithms
                    if not self._ja4_is_grease(algorithm)
                ]

        return []

    def ja4(self):
        cipher_hexes = [
            f'{cipher_suite.value.code:04x}'
            for cipher_suite in self.cipher_suites
            if not self._ja4_is_grease(cipher_suite)
        ]
        extension_hexes = [
            f'{extension.extension_type.value.code:04x}'
            for extension in self.extensions
            if not self._ja4_is_grease(extension.extension_type)
        ]
        signature_algorithm_hexes = self._ja4_signature_algorithm_hexes()

        sni = 'd' if any(
            extension.extension_type == TlsExtensionType.SERVER_NAME for extension in self.extensions
        ) else 'i'
        header = (
            f't{self._ja4_version_string()}{sni}'
            f'{min(len(cipher_hexes), 99):02d}{min(len(extension_hexes), 99):02d}'
            f'{self._ja4_alpn_value()}'
        )

        excluded_extension_hexes = (
            f'{TlsExtensionType.SERVER_NAME.value.code:04x}',
            f'{TlsExtensionType.APPLICATION_LAYER_PROTOCOL_NEGOTIATION.value.code:04x}',
        )
        sorted_cipher_hexes = sorted(cipher_hexes)
        sorted_extension_hexes = sorted(
            extension_hex
            for extension_hex in extension_hexes
            if extension_hex not in excluded_extension_hexes
        )

        cipher_hash, extension_hash = self._ja4_hashes(
            sorted_cipher_hexes, sorted_extension_hexes, signature_algorithm_hexes
        )
        cipher_hash_original, extension_hash_original = self._ja4_hashes(
            cipher_hexes, extension_hexes, signature_algorithm_hexes
        )

        return TlsJA4Fingerprint(
            fingerprint=f'{header}_{cipher_hash}_{extension_hash}',
            fingerprint_original=f'{header}_{cipher_hash_original}_{extension_hash_original}',
            fingerprint_raw=self._ja4_raw(
                header, sorted_cipher_hexes, sorted_extension_hexes, signature_algorithm_hexes
            ),
            fingerprint_raw_original=self._ja4_raw(
                header, cipher_hexes, extension_hexes, signature_algorithm_hexes
            ),
        )


@attr.s
class TlsHandshakeServerHello(TlsHandshakeHello):
    protocol_version = attr.ib(
        default=TlsProtocolVersion(TlsVersion.TLS1_2),
        validator=attr.validators.instance_of(TlsProtocolVersion),
    )
    random = attr.ib(
        default=TlsHandshakeHelloRandom(),
        validator=attr.validators.instance_of(TlsHandshakeHelloRandom),
    )
    session_id = attr.ib(
        default=TlsSessionIdVector(random.randint(0, 255) for i in range(32)),
        converter=TlsSessionIdVector,
        validator=attr.validators.instance_of(TlsSessionIdVector),
    )
    compression_method = attr.ib(
        default=TlsCompressionMethod.NULL,
        converter=TlsCompressionMethod,
        validator=attr.validators.in_(TlsCompressionMethod),
    )
    cipher_suite = attr.ib(default=None, validator=attr.validators.in_(TlsCipherSuite))
    extensions = attr.ib(
        default=TlsExtensionsServer([]),
        converter=TlsExtensionsServer,
        validator=attr.validators.instance_of(TlsExtensionsServer)
    )

    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.SERVER_HELLO

    @classmethod
    def _parse(cls, parsable):
        handshake_header_parser = cls._parse_handshake_header(parsable)

        parser = cls._parse_hello_header(handshake_header_parser['payload'])

        parser.parse_parsable('cipher_suite', TlsCipherSuiteFactory)
        parser.parse_parsable('compression_method', TlsCompressionMethodFactory)

        extension_parser = cls._parse_extensions(handshake_header_parser, parser, TlsExtensionsServer)

        return TlsHandshakeServerHello(
            protocol_version=parser['protocol_version'],
            random=parser['random'],
            session_id=parser['session_id'],
            compression_method=parser['compression_method'],
            cipher_suite=parser['cipher_suite'],
            extensions=parser['extensions'] if extension_parser else TlsExtensionsServer([]),
        ), handshake_header_parser.parsed_length

    def compose(self):
        payload_composer = ComposerBinary()

        payload_composer.compose_parsable(self.protocol_version)
        payload_composer.compose_parsable(self.random)
        payload_composer.compose_parsable(self.session_id)
        payload_composer.compose_numeric_enum_coded(self.cipher_suite)
        payload_composer.compose_numeric_enum_coded(self.compression_method)

        extension_bytes = self._compose_extensions(self.extensions)

        header_bytes = self._compose_header(payload_composer.composed_length + len(extension_bytes))

        return header_bytes + payload_composer.composed_bytes + extension_bytes


class TlsCertificateType(enum.IntEnum):
    X509 = 0
    RAW_PUBLIC_KEY = 2


@attr.s
class TlsCertificate(ParsableBase):
    certificate = attr.ib(validator=attr.validators.instance_of(bytes))

    @classmethod
    def _parse(cls, parsable):
        parser = ParserBinary(parsable)

        parser.parse_bytes('certificate', 3)

        return TlsCertificate(bytes(parser['certificate'])), parser.parsed_length

    def compose(self):
        composer = ComposerBinary()

        composer.compose_bytes(self.certificate, 3)

        return composer.composed_bytes


class TlsCertificates(VectorParsable):
    @classmethod
    def get_param(cls):
        return VectorParamParsable(
            item_class=TlsCertificate,
            fallback_class=None,
            min_byte_num=1, max_byte_num=2 ** 24 - 1
        )


@attr.s
class TlsHandshakeServerCertificate(TlsHandshakeMessage):
    certificate_chain = attr.ib(validator=attr.validators.instance_of(TlsCertificates))

    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.CERTIFICATE

    @classmethod
    def _parse(cls, parsable):
        handshake_header_parser = cls._parse_handshake_header(parsable)

        parser = ParserBinary(handshake_header_parser['payload'])

        parser.parse_parsable('certificates', TlsCertificates)

        return TlsHandshakeServerCertificate(
            parser['certificates']
        ), handshake_header_parser.parsed_length

    def compose(self):
        payload_composer = ComposerBinary()
        payload_composer.compose_parsable(self.certificate_chain)

        header_bytes = self._compose_header(payload_composer.composed_length)

        return header_bytes + payload_composer.composed_bytes


@attr.s
class TlsCertificateEntry(ParsableBase):
    certificate = attr.ib(validator=attr.validators.instance_of(TlsCertificate))
    extensions = attr.ib(validator=attr.validators.instance_of((bytes, bytearray)))

    def __attrs_post_init__(self):
        if len(self.extensions) > 2 ** 16 - 1:
            raise InvalidValue(len(self.extensions), self.__class__, 'extensions')

    @classmethod
    def _parse(cls, parsable):
        parser = ParserBinary(parsable)

        parser.parse_parsable('certificate', TlsCertificate)
        parser.parse_bytes('extensions', 2)

        return TlsCertificateEntry(
            parser['certificate'],
            bytes(parser['extensions']),
        ), parser.parsed_length

    def compose(self):
        composer = ComposerBinary()

        composer.compose_parsable(self.certificate)
        composer.compose_bytes(self.extensions, 2)

        return composer.composed_bytes


class TlsCertificateEntryVector(VectorParsable):
    @classmethod
    def get_param(cls):
        return VectorParamParsable(
            item_class=TlsCertificateEntry,
            fallback_class=None,
            min_byte_num=0,
            max_byte_num=2 ** 24 - 1,
        )


@attr.s
class TlsHandshakeCertificate(TlsHandshakeMessage):

    certificate_request_context = attr.ib(validator=attr.validators.instance_of((bytes, bytearray)))
    certificate_entries = attr.ib(validator=attr.validators.instance_of(TlsCertificateEntryVector))

    def __attrs_post_init__(self):
        if len(self.certificate_request_context) > 255:
            raise InvalidValue(len(self.certificate_request_context), self.__class__, 'certificate_request_context')

    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.CERTIFICATE

    @classmethod
    def _parse(cls, parsable):
        handshake_header_parser = cls._parse_handshake_header(parsable)

        parser = ParserBinary(handshake_header_parser['payload'])

        parser.parse_numeric('certificate_request_context_length', 1)
        parser.parse_raw(
            'certificate_request_context',
            parser['certificate_request_context_length'],
        )
        try:
            parser.parse_parsable('certificate_entries', TlsCertificateEntryVector)
        except (NotEnoughData, InvalidValue) as e:
            raise InvalidType() from e

        return TlsHandshakeCertificate(
            bytes(parser['certificate_request_context']),
            parser['certificate_entries'],
        ), handshake_header_parser.parsed_length

    def compose(self):
        payload_composer = ComposerBinary()

        payload_composer.compose_numeric(len(self.certificate_request_context), 1)
        payload_composer.compose_raw(self.certificate_request_context)
        payload_composer.compose_parsable(self.certificate_entries)

        header_bytes = self._compose_header(payload_composer.composed_length)

        return header_bytes + payload_composer.composed_bytes


class TlsHandshakeCertificateStatus(TlsHandshakeMessage):
    def __init__(self, status_type, status):
        super().__init__()

        self.status_type = status_type
        self.status = status

    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.CERTIFICATE_STATUS

    @classmethod
    def _parse(cls, parsable):
        handshake_header_parser = cls._parse_handshake_header(parsable)

        parser = ParserBinary(handshake_header_parser['payload'])

        parser.parse_numeric('status_type', 1, TlsCertificateStatusType)
        parser.parse_bytes('status', 3)

        return TlsHandshakeCertificateStatus(
            parser['status_type'],
            parser['status']
        ), handshake_header_parser.parsed_length

    def compose(self):
        body_composer = ComposerBinary()
        body_composer.compose_numeric(self.status_type, 1)

        body_composer.compose_bytes(self.status, 3)

        header_bytes = self._compose_header(body_composer.composed_length)

        return header_bytes + body_composer.composed_bytes


class TlsHandshakeServerHelloDone(TlsHandshakeMessage):
    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.SERVER_HELLO_DONE

    @classmethod
    def _parse(cls, parsable):
        handshake_header_parser = cls._parse_handshake_header(parsable)

        if handshake_header_parser['payload']:
            raise InvalidValue(bytes(handshake_header_parser['payload']), TlsHandshakeServerHelloDone, 'payload')

        return TlsHandshakeServerHelloDone(), handshake_header_parser.parsed_length

    def compose(self):
        return self._compose_header(0)


class TlsECCurveType(enum.IntEnum):
    EXPLICIT_PRIME = 1
    EXPLICIT_CHAR2 = 2
    NAMED_CURVE = 3


@attr.s
class TlsHandshakeServerKeyExchange(TlsHandshakeMessage):
    param_bytes = attr.ib(validator=attr.validators.instance_of(bytes))

    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.SERVER_KEY_EXCHANGE

    @classmethod
    def _parse(cls, parsable):
        handshake_header_parser = cls._parse_handshake_header(parsable)

        return TlsHandshakeServerKeyExchange(
            bytes(handshake_header_parser['payload'])
        ), handshake_header_parser.parsed_length

    def compose(self):
        return self._compose_header(len(self.param_bytes)) + bytes(self.param_bytes)


class TlsClientCertificateType(enum.IntEnum):
    RSA_SIGN = 0x01
    DSS_SIGN = 0x02
    RSA_FIXED_DH = 0x03
    DSS_FIXED_DH = 0x04
    ECDSA_SIGN = 0x40
    RSA_FIXED_ECDH = 0x41
    ECDSA_FIXED_ECDH = 0x42
    GOST_SIGN256 = 0x43
    GOST_SIGN512 = 0x44


class TlsClientCertificateTypeVector(Vector):
    @classmethod
    def get_param(cls):
        return VectorParamNumeric(
            item_size=1,
            min_byte_num=1,
            max_byte_num=2 ** 8 - 1,
            numeric_class=TlsClientCertificateType
        )


class TlsDistinguishedName(Opaque):
    @classmethod
    def get_param(cls):
        return OpaqueParam(min_byte_num=1, max_byte_num=2 ** 16 - 1)


class TlsDistinguishedNameVector(VectorParsable):
    @classmethod
    def get_param(cls):
        return VectorParamParsable(
            item_class=TlsDistinguishedName,
            fallback_class=None,
            min_byte_num=0, max_byte_num=2 ** 16 - 1
        )


@attr.s
class TlsHandshakeCertificateRequest(TlsHandshakeMessage):
    certificate_types = attr.ib(converter=TlsClientCertificateTypeVector)
    certificate_authorities = attr.ib(converter=TlsDistinguishedNameVector)
    supported_signature_algorithms = attr.ib(default=None)

    def __attrs_post_init__(self):
        if self.supported_signature_algorithms is not None:
            self.supported_signature_algorithms = TlsSignatureAndHashAlgorithmVector(
                self.supported_signature_algorithms
            )

        attr.validate(self)

    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.CERTIFICATE_REQUEST

    @classmethod
    def _parse(cls, parsable):
        handshake_header_parser = cls._parse_handshake_header(parsable)

        parser = ParserBinary(handshake_header_parser['payload'])

        parser.parse_parsable('certificate_types', TlsClientCertificateTypeVector)

        parser_remaining = ParserBinary(parser.unparsed)
        parser_remaining.parse_numeric('vector_length', 2)
        if parser_remaining['vector_length'] + 2 == parser.unparsed_length:
            supported_signature_algorithms = None
        else:
            parser.parse_parsable('supported_signature_algorithms', TlsSignatureAndHashAlgorithmVector)
            supported_signature_algorithms = parser['supported_signature_algorithms']

        parser.parse_parsable('certificate_authorities', TlsDistinguishedNameVector)

        msg = TlsHandshakeCertificateRequest(
            certificate_types=parser['certificate_types'],
            certificate_authorities=parser['certificate_authorities'],
            supported_signature_algorithms=supported_signature_algorithms,
        )

        return msg, handshake_header_parser.parsed_length

    def compose(self):
        payload_composer = ComposerBinary()

        payload_composer.compose_parsable(self.certificate_types)
        if self.supported_signature_algorithms is not None:
            payload_composer.compose_parsable(self.supported_signature_algorithms)
        payload_composer.compose_parsable(self.certificate_authorities)

        header_bytes = self._compose_header(payload_composer.composed_length)

        return header_bytes + payload_composer.composed_bytes


TLS_HANDSHAKE_HELLO_RETRY_REQUEST_RANDOM_BYTES = (
    b'\xcf\x21\xad\x74\xe5\x9a\x61\x11\xbe\x1d\x8c\x02\x1e\x65\xb8\x91' +
    b'\xc2\xa2\x11\x16\x7a\xbb\x8c\x5e\x07\x9e\x09\xe2\xc8\xa8\x33\x9c' +
    b''
)


TLS_HANDSHAKE_HELLO_RETRY_REQUEST_RANDOM = TlsHandshakeHelloRandom.parse_exact_size(
    TLS_HANDSHAKE_HELLO_RETRY_REQUEST_RANDOM_BYTES
)


@attr.s
class TlsHandshakeHelloRetryRequest(TlsHandshakeHello):
    cipher_suite = attr.ib(default=None, validator=attr.validators.in_(TlsCipherSuite))
    protocol_version = attr.ib(
        default=TlsProtocolVersion(TlsVersion.TLS1_3),
        validator=attr.validators.instance_of(TlsProtocolVersion),
    )
    random_bytes = attr.ib(
        default=TLS_HANDSHAKE_HELLO_RETRY_REQUEST_RANDOM,
        validator=attr.validators.instance_of(TlsHandshakeHelloRandom),
    )
    session_id = attr.ib(
        default=TlsSessionIdVector(random.randint(0, 255) for i in range(32)),
        converter=TlsSessionIdVector,
        validator=attr.validators.instance_of(TlsSessionIdVector),
    )
    compression_method = attr.ib(
        default=TlsCompressionMethod.NULL,
        converter=TlsCompressionMethod,
        validator=attr.validators.in_(TlsCompressionMethod),
    )
    extensions = attr.ib(
        default=TlsExtensionsServer([]),
        converter=TlsExtensionsServer,
        validator=attr.validators.instance_of(TlsExtensionsServer)
    )

    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.HELLO_RETRY_REQUEST

    @classmethod
    def _parse(cls, parsable):
        handshake_header_parser = cls._parse_handshake_header(parsable)
        parser = cls._parse_hello_header(handshake_header_parser['payload'])

        parser.parse_parsable('cipher_suite', TlsCipherSuiteFactory)

        parser.parse_parsable('compression_method', TlsCompressionMethodFactory)
        compression_method = parser['compression_method']
        session_id = parser['session_id']

        extension_parser = cls._parse_extensions(handshake_header_parser, parser, TlsExtensionsServer)

        return TlsHandshakeHelloRetryRequest(
            protocol_version=parser['protocol_version'],
            random_bytes=parser['random'],
            session_id=session_id,
            compression_method=compression_method,
            cipher_suite=parser['cipher_suite'],
            extensions=parser['extensions'] if extension_parser else TlsExtensionsClient([]),
        ), handshake_header_parser.parsed_length

    def compose(self):
        payload_composer = ComposerBinary()

        payload_composer.compose_parsable(self.protocol_version)
        payload_composer.compose_parsable(self.random_bytes)
        payload_composer.compose_parsable(self.session_id)
        payload_composer.compose_numeric_enum_coded(self.cipher_suite)
        payload_composer.compose_numeric_enum_coded(self.compression_method)

        extension_bytes = self._compose_extensions(self.extensions)

        header_bytes = self._compose_header(payload_composer.composed_length + len(extension_bytes))

        return header_bytes + payload_composer.composed_bytes + extension_bytes


@attr.s
class TlsHandshakeEncryptedExtensions(TlsHandshakeMessage):
    extension_data = attr.ib(validator=attr.validators.instance_of((bytes, bytearray)))

    @classmethod
    def get_handshake_type(cls):
        return TlsHandshakeType.ENCRYPTED_EXTENSIONS

    @classmethod
    def _parse(cls, parsable):
        parser = cls._parse_handshake_header(parsable)

        return TlsHandshakeEncryptedExtensions(
            parser['payload'],
        ), parser.parsed_length

    def compose(self):
        payload_composer = ComposerBinary()
        payload_composer.compose_raw(self.extension_data)

        header_bytes = self._compose_header(payload_composer.composed_length)

        return header_bytes + payload_composer.composed_bytes


class SslMessageBase(ParsableBase):
    @classmethod
    def get_message_type(cls):
        return NotImplementedError()  # pragma: no cover

    # pylint: disable=duplicate-code
    @classmethod
    @abc.abstractmethod
    def _parse(cls, parsable):
        raise NotImplementedError()

    # pylint: disable=duplicate-code
    @abc.abstractmethod
    def compose(self):
        raise NotImplementedError()


class SslMessageType(enum.IntEnum):
    ERROR = 0x00
    CLIENT_HELLO = 0x01
    CLIENT_MASTER_KEY = 0x02
    CLIENT_FINISHED = 0x03
    SERVER_HELLO = 0x04
    SERVER_VERIFY = 0x05
    SERVER_FINISHED = 0x06
    REQUEST_CERTIFICATE = 0x07
    CLIENT_CERTIFICATE = 0x08


class SslCertificateType(enum.IntEnum):
    X509_CERTIFICATE = 0x01


class SslAuthenticationType(enum.IntEnum):
    MD5_WITH_RSA_ENCRYPTION = 0x01


class SslErrorType(enum.IntEnum):
    NO_CIPHER_ERROR = 0x0001
    NO_CERTIFICATE_ERROR = 0x0002
    BAD_CERTIFICATE_ERROR = 0x0003
    UNSUPPORTED_CERTIFICATE_TYPE_ERROR = 0x0004


@attr.s
class SslErrorMessage(SslMessageBase):
    error_type = attr.ib(validator=attr.validators.in_(SslErrorType))

    @classmethod
    def get_message_type(cls):
        return SslMessageType.ERROR

    @classmethod
    def _parse(cls, parsable):
        parser = ParserBinary(parsable)

        parser.parse_numeric('error_type', 2, SslErrorType)

        return SslErrorMessage(parser['error_type']), parser.parsed_length

    def compose(self):
        composer = ComposerBinary()

        composer.compose_numeric(self.error_type, 2)

        return composer.composed_bytes


@attr.s
class SslHandshakeClientHello(SslMessageBase):
    cipher_kinds = attr.ib(validator=attr.validators.deep_iterable(member_validator=attr.validators.in_(SslCipherKind)))
    session_id = attr.ib(validator=attr.validators.instance_of(bytes))
    challenge = attr.ib(validator=attr.validators.instance_of(bytes))

    @session_id.default
    def _default_session_id(self):  # pylint: disable=no-self-use
        return b''

    @challenge.default
    def _default_challenge(self):  # pylint: disable=no-self-use
        return bytes(bytearray.fromhex(f'{random.getrandbits(128):16x}'.zfill(32)))

    @classmethod
    def get_message_type(cls):
        return SslMessageType.CLIENT_HELLO

    @classmethod
    def _parse(cls, parsable):
        parser = ParserBinary(parsable)

        parser.parse_parsable('version', TlsProtocolVersion)
        parser.parse_numeric('cipher_kinds_length', 2)
        parser.parse_numeric('session_id_length', 2)
        parser.parse_numeric('challenge_length', 2)
        parser.parse_parsable_array('cipher_kinds', parser['cipher_kinds_length'], SslCipherKindFactory)
        parser.parse_raw('session_id', parser['session_id_length'])
        parser.parse_raw('challenge', parser['challenge_length'])

        return SslHandshakeClientHello(
            cipher_kinds=parser['cipher_kinds'],
            session_id=bytes(parser['session_id']),
            challenge=bytes(parser['challenge']),
        ), parser.parsed_length

    def compose(self):
        composer = ComposerBinary()

        composer.compose_parsable(TlsProtocolVersion(TlsVersion.SSL2))

        composer.compose_numeric(len(self.cipher_kinds) * 3, 2)
        composer.compose_numeric(len(self.session_id), 2)
        composer.compose_numeric(len(self.challenge), 2)
        composer.compose_numeric_array_enum_coded(self.cipher_kinds)
        composer.compose_raw(self.session_id)
        composer.compose_raw(self.challenge)

        return composer.composed_bytes


@attr.s
class SslHandshakeServerHello(SslMessageBase):
    certificate = attr.ib(validator=attr.validators.instance_of(bytes))
    cipher_kinds = attr.ib(validator=attr.validators.deep_iterable(member_validator=attr.validators.in_(SslCipherKind)))
    connection_id = attr.ib(validator=attr.validators.instance_of(bytes))
    session_id_hit = attr.ib(default=False, validator=attr.validators.instance_of(bool))

    @connection_id.default
    def _default_connection_id(self):  # pylint: disable=no-self-use
        return b''

    @classmethod
    def get_message_type(cls):
        return SslMessageType.SERVER_HELLO

    @classmethod
    def _parse(cls, parsable):
        parser = ParserBinary(parsable)

        parser.parse_numeric('session_id_hit', 1)
        parser.parse_numeric('certificate_type', 1, SslCertificateType)
        parser.parse_parsable('version', TlsProtocolVersion)
        parser.parse_numeric('certificate_length', 2)
        parser.parse_numeric('cipher_kinds_length', 2)
        parser.parse_numeric('connection_id_length', 2)
        parser.parse_raw('certificate', parser['certificate_length'])
        parser.parse_parsable_array('cipher_kinds', parser['cipher_kinds_length'], SslCipherKindFactory)
        parser.parse_raw('connection_id', parser['connection_id_length'])

        return SslHandshakeServerHello(
            certificate=bytes(parser['certificate']),
            cipher_kinds=parser['cipher_kinds'],
            connection_id=bytes(parser['connection_id']),
            session_id_hit=bool(parser['session_id_hit']),
        ), parser.parsed_length

    def compose(self):
        composer = ComposerBinary()

        composer.compose_numeric(1 if self.session_id_hit else 0, 1)
        composer.compose_numeric(SslCertificateType.X509_CERTIFICATE, 1)
        composer.compose_parsable(TlsProtocolVersion(TlsVersion.SSL2))
        composer.compose_numeric(len(self.certificate), 2)
        composer.compose_numeric(len(self.cipher_kinds) * 3, 2)
        composer.compose_numeric(len(self.connection_id), 2)
        composer.compose_raw(self.certificate)
        composer.compose_numeric_array_enum_coded(self.cipher_kinds)
        composer.compose_raw(self.connection_id)

        return composer.composed_bytes


class TlsHandshakeMessageVariant(VariantParsable):
    _VARIANTS = collections.OrderedDict([
        (TlsHandshakeType.CLIENT_HELLO, [TlsHandshakeClientHello, ]),
        (TlsHandshakeType.SERVER_HELLO, [TlsHandshakeServerHello, ]),
        (TlsHandshakeType.ENCRYPTED_EXTENSIONS, [TlsHandshakeEncryptedExtensions, ]),
        (TlsHandshakeType.CERTIFICATE, [TlsHandshakeCertificate, TlsHandshakeServerCertificate, ]),
        (TlsHandshakeType.SERVER_KEY_EXCHANGE, [TlsHandshakeServerKeyExchange, ]),
        (TlsHandshakeType.CERTIFICATE_REQUEST, [TlsHandshakeCertificateRequest, ]),
        (TlsHandshakeType.CERTIFICATE_STATUS, [TlsHandshakeCertificateStatus, ]),
        (TlsHandshakeType.SERVER_HELLO_DONE, [TlsHandshakeServerHelloDone, ]),
        (TlsHandshakeType.HELLO_RETRY_REQUEST, [TlsHandshakeHelloRetryRequest, ]),
    ])

    @classmethod
    def _get_variants(cls):
        return cls._VARIANTS


class TlsSubprotocolMessageParser(SubprotocolParser):
    _SUBPROTOCOL_PARSERS = {
        TlsContentType.CHANGE_CIPHER_SPEC: TlsChangeCipherSpecMessage,
        TlsContentType.ALERT: TlsAlertMessage,
        TlsContentType.HANDSHAKE: TlsHandshakeMessageVariant,
    }

    @classmethod
    def _get_subprotocol_parsers(cls):
        return cls._SUBPROTOCOL_PARSERS


class SslSubprotocolMessageParser(SubprotocolParser):
    _SUBPROTOCOL_PARSERS = {
        SslMessageType.ERROR: SslErrorMessage,
        SslMessageType.CLIENT_HELLO: SslHandshakeClientHello,
        SslMessageType.SERVER_HELLO: SslHandshakeServerHello,
    }

    @classmethod
    def _get_subprotocol_parsers(cls):
        return cls._SUBPROTOCOL_PARSERS
