/**
 * TLS resources.
 *
 * @author n1474335 [n1474335@gmail.com]
 * @copyright Crown Copyright 2024
 * @license Apache-2.0
 */

import OperationError from "../errors/OperationError.mjs";
import Stream from "../lib/Stream.mjs";

/**
 * Parse a TLS Record
 * @param {Uint8Array} bytes
 * @returns {JSON}
 */
export function parseTLSRecord(bytes) {
    const s = new Stream(bytes);
    const b = s.clone();
    const r = {};

    // Content type
    r.contentType = {
        description: "Content Type",
        length: 1,
        data: b.getBytes(1),
        value: s.readInt(1)
    };
    if (r.contentType.value !== 0x16)
        throw new OperationError("Not handshake data.");

    // Version
    r.version = {
        description: "Protocol Version",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };

    // Length
    r.length = {
        description: "Record Length",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };
    if (s.length !== r.length.value + 5)
        throw new OperationError("Incorrect handshake length.");

    // Handshake
    r.handshake = {
        description: "Handshake",
        length: r.length.value,
        data: b.getBytes(r.length.value),
        value: parseHandshake(s.getBytes(r.length.value))
    };

    return r;
}

/**
 * Parse a TLS Handshake
 * @param {Uint8Array} bytes
 * @returns {JSON}
 */
function parseHandshake(bytes) {
    const s = new Stream(bytes);
    const b = s.clone();
    const h = {};

    // Handshake type
    h.handshakeType = {
        description: "Handshake Type",
        length: 1,
        data: b.getBytes(1),
        value: s.readInt(1)
    };

    // Handshake length
    h.handshakeLength = {
        description: "Handshake Length",
        length: 3,
        data: b.getBytes(3),
        value: s.readInt(3)
    };
    if (s.length !== h.handshakeLength.value + 4)
        throw new OperationError("Not enough data in Handshake message.");


    switch (h.handshakeType.value) {
        case 0x01:
            h.handshakeType.description = "Client Hello";
            parseClientHello(s, b, h);
            break;
        case 0x02:
            h.handshakeType.description = "Server Hello";
            parseServerHello(s, b, h);
            break;
        default:
            throw new OperationError("Not a known handshake message.");
    }

    return h;
}

/**
 * Parse a TLS Client Hello
 * @param {Stream} s
 * @param {Stream} b
 * @param {Object} h
 * @returns {JSON}
 */
function parseClientHello(s, b, h) {
    // Hello version
    h.helloVersion = {
        description: "Client Hello Version",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };

    // Random
    h.random = {
        description: "Client Random",
        length: 32,
        data: b.getBytes(32),
        value: s.getBytes(32)
    };

    // Session ID Length
    h.sessionIDLength = {
        description: "Session ID Length",
        length: 1,
        data: b.getBytes(1),
        value: s.readInt(1)
    };

    // Session ID
    h.sessionID = {
        description: "Session ID",
        length: h.sessionIDLength.value,
        data: b.getBytes(h.sessionIDLength.value),
        value: s.getBytes(h.sessionIDLength.value)
    };

    // Cipher Suites Length
    h.cipherSuitesLength = {
        description: "Cipher Suites Length",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };

    // Cipher Suites
    h.cipherSuites = {
        description: "Cipher Suites",
        length: h.cipherSuitesLength.value,
        data: b.getBytes(h.cipherSuitesLength.value),
        value: parseCipherSuites(s.getBytes(h.cipherSuitesLength.value))
    };

    // Compression Methods Length
    h.compressionMethodsLength = {
        description: "Compression Methods Length",
        length: 1,
        data: b.getBytes(1),
        value: s.readInt(1)
    };

    // Compression Methods
    h.compressionMethods = {
        description: "Compression Methods",
        length: h.compressionMethodsLength.value,
        data: b.getBytes(h.compressionMethodsLength.value),
        value: parseCompressionMethods(s.getBytes(h.compressionMethodsLength.value))
    };

    // Extensions Length
    h.extensionsLength = {
        description: "Extensions Length",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };

    // Extensions
    h.extensions = {
        description: "Extensions",
        length: h.extensionsLength.value,
        data: b.getBytes(h.extensionsLength.value),
        value: parseExtensions(s.getBytes(h.extensionsLength.value))
    };

    return h;
}

/**
 * Parse a TLS Server Hello
 * @param {Stream} s
 * @param {Stream} b
 * @param {Object} h
 * @returns {JSON}
 */
function parseServerHello(s, b, h) {
    // Hello version
    h.helloVersion = {
        description: "Server Hello Version",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };

    // Random
    h.random = {
        description: "Server Random",
        length: 32,
        data: b.getBytes(32),
        value: s.getBytes(32)
    };

    // Session ID Length
    h.sessionIDLength = {
        description: "Session ID Length",
        length: 1,
        data: b.getBytes(1),
        value: s.readInt(1)
    };

    // Session ID
    h.sessionID = {
        description: "Session ID",
        length: h.sessionIDLength.value,
        data: b.getBytes(h.sessionIDLength.value),
        value: s.getBytes(h.sessionIDLength.value)
    };

    // Cipher Suite
    h.cipherSuite = {
        description: "Selected Cipher Suite",
        length: 2,
        data: b.getBytes(2),
        value: CIPHER_SUITES_LOOKUP[s.readInt(2)] || "Unknown"
    };

    // Compression Method
    h.compressionMethod = {
        description: "Selected Compression Method",
        length: 1,
        data: b.getBytes(1),
        value: s.readInt(1) // TODO: Compression method name here
    };

    // Extensions Length
    h.extensionsLength = {
        description: "Extensions Length",
        length: 2,
        data: b.getBytes(2),
        value: s.readInt(2)
    };

    // Extensions
    h.extensions = {
        description: "Extensions",
        length: h.extensionsLength.value,
        data: b.getBytes(h.extensionsLength.value),
        value: parseExtensions(s.getBytes(h.extensionsLength.value))
    };
}

/**
 * Parse Cipher Suites
 * @param {Uint8Array} bytes
 * @returns {JSON}
 */
function parseCipherSuites(bytes) {
    const s = new Stream(bytes);
    const b = s.clone();
    const cs = [];

    while (s.hasMore()) {
        cs.push({
            description: "Cipher Suite",
            length: 2,
            data: b.getBytes(2),
            value: CIPHER_SUITES_LOOKUP[s.readInt(2)] || "Unknown"
        });
    }
    return cs;
}

/**
 * Parse Compression Methods
 * @param {Uint8Array} bytes
 * @returns {JSON}
 */
function parseCompressionMethods(bytes) {
    const s = new Stream(bytes);
    const b = s.clone();
    const cm = [];

    while (s.hasMore()) {
        cm.push({
            description: "Compression Method",
            length: 1,
            data: b.getBytes(1),
            value: s.readInt(1) // TODO: Compression method name here
        });
    }
    return cm;
}

/**
 * Parse Extensions
 * @param {Uint8Array} bytes
 * @returns {JSON}
 */
function parseExtensions(bytes) {
    const s = new Stream(bytes);
    const b = s.clone();

    const exts = [];
    while (s.hasMore()) {
        const ext = {};

        // Type
        ext.type = {
            description: "Extension Type",
            length: 2,
            data: b.getBytes(2),
            value: EXTENSION_LOOKUP[s.readInt(2)] || "unknown"
        };

        // Length
        ext.length = {
            description: "Extension Length",
            length: 2,
            data: b.getBytes(2),
            value: s.readInt(2)
        };

        // Value
        ext.value = {
            description: "Extension Value",
            length: ext.length.value,
            data: b.getBytes(ext.length.value),
            value: s.getBytes(ext.length.value)
        };

        exts.push(ext);
    }

    return exts;
}

/**
 * Extension type lookup table
 */
const EXTENSION_LOOKUP = {
    0: "server_name",
    1: "max_fragment_length",
    2: "client_certificate_url",
    3: "trusted_ca_keys",
    4: "truncated_hmac",
    5: "status_request",
    6: "user_mapping",
    7: "client_authz",
    8: "server_authz",
    9: "cert_type",
    10: "supported_groups",
    11: "ec_point_formats",
    12: "srp",
    13: "signature_algorithms",
    14: "use_srtp",
    15: "heartbeat",
    16: "application_layer_protocol_negotiation",
    17: "status_request_v2",
    18: "signed_certificate_timestamp",
    19: "client_certificate_type",
    20: "server_certificate_type",
    21: "padding",
    22: "encrypt_then_mac",
    23: "extended_master_secret",
    24: "token_binding",
    25: "cached_info",
    26: "tls_lts",
    27: "compress_certificate",
    28: "record_size_limit",
    29: "pwd_protect",
    30: "pwd_clear",
    31: "password_salt",
    32: "ticket_pinning",
    33: "tls_cert_with_extern_psk",
    34: "delegated_credential",
    35: "session_ticket",
    36: "TLMSP",
    37: "TLMSP_proxying",
    38: "TLMSP_delegate",
    39: "supported_ekt_ciphers",
    40: "Reserved",
    41: "pre_shared_key",
    42: "early_data",
    43: "supported_versions",
    44: "cookie",
    45: "psk_key_exchange_modes",
    46: "Reserved",
    47: "certificate_authorities",
    48: "oid_filters",
    49: "post_handshake_auth",
    50: "signature_algorithms_cert",
    51: "key_share",
    52: "transparency_info",
    53: "connection_id (deprecated)",
    54: "connection_id",
    55: "external_id_hash",
    56: "external_session_id",
    57: "quic_transport_parameters",
    58: "ticket_request",
    59: "dnssec_chain",
    60: "sequence_number_encryption_algorithms",
    61: "rrc",
    2570: "GREASE",
    6682: "GREASE",
    10794: "GREASE",
    14906: "GREASE",
    17513: "application_settings",
    19018: "GREASE",
    23130: "GREASE",
    27242: "GREASE",
    31354: "GREASE",
    35466: "GREASE",
    39578: "GREASE",
    43690: "GREASE",
    47802: "GREASE",
    51914: "GREASE",
    56026: "GREASE",
    60138: "GREASE",
    64250: "GREASE",
    64768: "ech_outer_extensions",
    65037: "encrypted_client_hello",
    65281: "renegotiation_info"
};

/**
 * Cipher suites lookup table
 */
const CIPHER_SUITES_LOOKUP = {
    0x0000: "TLS_NULL_WITH_NULL_NULL",
    0x0001: "TLS_RSA_WITH_NULL_MD5",
    0x0002: "TLS_RSA_WITH_NULL_SHA",
    0x0003: "TLS_RSA_EXPORT_WITH_RC4_40_MD5",
    0x0004: "TLS_RSA_WITH_RC4_128_MD5",
    0x0005: "TLS_RSA_WITH_RC4_128_SHA",
    0x0006: "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
    0x0007: "TLS_RSA_WITH_IDEA_CBC_SHA",
    0x0008: "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA",
    0x0009: "TLS_RSA_WITH_DES_CBC_SHA",
    0x000A: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
    0x000B: "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA",
    0x000C: "TLS_DH_DSS_WITH_DES_CBC_SHA",
    0x000D: "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA",
    0x000E: "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA",
    0x000F: "TLS_DH_RSA_WITH_DES_CBC_SHA",
    0x0010: "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA",
    0x0011: "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
    0x0012: "TLS_DHE_DSS_WITH_DES_CBC_SHA",
    0x0013: "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
    0x0014: "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
    0x0015: "TLS_DHE_RSA_WITH_DES_CBC_SHA",
    0x0016: "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
    0x0017: "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5",
    0x0018: "TLS_DH_anon_WITH_RC4_128_MD5",
    0x0019: "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
    0x001A: "TLS_DH_anon_WITH_DES_CBC_SHA",
    0x001B: "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA",
    0x001E: "TLS_KRB5_WITH_DES_CBC_SHA",
    0x001F: "TLS_KRB5_WITH_3DES_EDE_CBC_SHA",
    0x0020: "TLS_KRB5_WITH_RC4_128_SHA",
    0x0021: "TLS_KRB5_WITH_IDEA_CBC_SHA",
    0x0022: "TLS_KRB5_WITH_DES_CBC_MD5",
    0x0023: "TLS_KRB5_WITH_3DES_EDE_CBC_MD5",
    0x0024: "TLS_KRB5_WITH_RC4_128_MD5",
    0x0025: "TLS_KRB5_WITH_IDEA_CBC_MD5",
    0x0026: "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA",
    0x0027: "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA",
    0x0028: "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
    0x0029: "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5",
    0x002A: "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5",
    0x002B: "TLS_KRB5_EXPORT_WITH_RC4_40_MD5",
    0x002C: "TLS_PSK_WITH_NULL_SHA",
    0x002D: "TLS_DHE_PSK_WITH_NULL_SHA",
    0x002E: "TLS_RSA_PSK_WITH_NULL_SHA",
    0x002F: "TLS_RSA_WITH_AES_128_CBC_SHA",
    0x0030: "TLS_DH_DSS_WITH_AES_128_CBC_SHA",
    0x0031: "TLS_DH_RSA_WITH_AES_128_CBC_SHA",
    0x0032: "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
    0x0033: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
    0x0034: "TLS_DH_anon_WITH_AES_128_CBC_SHA",
    0x0035: "TLS_RSA_WITH_AES_256_CBC_SHA",
    0x0036: "TLS_DH_DSS_WITH_AES_256_CBC_SHA",
    0x0037: "TLS_DH_RSA_WITH_AES_256_CBC_SHA",
    0x0038: "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
    0x0039: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
    0x003A: "TLS_DH_anon_WITH_AES_256_CBC_SHA",
    0x003B: "TLS_RSA_WITH_NULL_SHA256",
    0x003C: "TLS_RSA_WITH_AES_128_CBC_SHA256",
    0x003D: "TLS_RSA_WITH_AES_256_CBC_SHA256",
    0x003E: "TLS_DH_DSS_WITH_AES_128_CBC_SHA256",
    0x003F: "TLS_DH_RSA_WITH_AES_128_CBC_SHA256",
    0x0040: "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
    0x0041: "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA",
    0x0042: "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA",
    0x0043: "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA",
    0x0044: "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA",
    0x0045: "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA",
    0x0046: "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA",
    0x0067: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
    0x0068: "TLS_DH_DSS_WITH_AES_256_CBC_SHA256",
    0x0069: "TLS_DH_RSA_WITH_AES_256_CBC_SHA256",
    0x006A: "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
    0x006B: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
    0x006C: "TLS_DH_anon_WITH_AES_128_CBC_SHA256",
    0x006D: "TLS_DH_anon_WITH_AES_256_CBC_SHA256",
    0x0084: "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA",
    0x0085: "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA",
    0x0086: "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA",
    0x0087: "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA",
    0x0088: "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA",
    0x0089: "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA",
    0x008A: "TLS_PSK_WITH_RC4_128_SHA",
    0x008B: "TLS_PSK_WITH_3DES_EDE_CBC_SHA",
    0x008C: "TLS_PSK_WITH_AES_128_CBC_SHA",
    0x008D: "TLS_PSK_WITH_AES_256_CBC_SHA",
    0x008E: "TLS_DHE_PSK_WITH_RC4_128_SHA",
    0x008F: "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA",
    0x0090: "TLS_DHE_PSK_WITH_AES_128_CBC_SHA",
    0x0091: "TLS_DHE_PSK_WITH_AES_256_CBC_SHA",
    0x0092: "TLS_RSA_PSK_WITH_RC4_128_SHA",
    0x0093: "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA",
    0x0094: "TLS_RSA_PSK_WITH_AES_128_CBC_SHA",
    0x0095: "TLS_RSA_PSK_WITH_AES_256_CBC_SHA",
    0x0096: "TLS_RSA_WITH_SEED_CBC_SHA",
    0x0097: "TLS_DH_DSS_WITH_SEED_CBC_SHA",
    0x0098: "TLS_DH_RSA_WITH_SEED_CBC_SHA",
    0x0099: "TLS_DHE_DSS_WITH_SEED_CBC_SHA",
    0x009A: "TLS_DHE_RSA_WITH_SEED_CBC_SHA",
    0x009B: "TLS_DH_anon_WITH_SEED_CBC_SHA",
    0x009C: "TLS_RSA_WITH_AES_128_GCM_SHA256",
    0x009D: "TLS_RSA_WITH_AES_256_GCM_SHA384",
    0x009E: "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
    0x009F: "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
    0x00A0: "TLS_DH_RSA_WITH_AES_128_GCM_SHA256",
    0x00A1: "TLS_DH_RSA_WITH_AES_256_GCM_SHA384",
    0x00A2: "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
    0x00A3: "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
    0x00A4: "TLS_DH_DSS_WITH_AES_128_GCM_SHA256",
    0x00A5: "TLS_DH_DSS_WITH_AES_256_GCM_SHA384",
    0x00A6: "TLS_DH_anon_WITH_AES_128_GCM_SHA256",
    0x00A7: "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
    0x00A8: "TLS_PSK_WITH_AES_128_GCM_SHA256",
    0x00A9: "TLS_PSK_WITH_AES_256_GCM_SHA384",
    0x00AA: "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256",
    0x00AB: "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384",
    0x00AC: "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256",
    0x00AD: "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384",
    0x00AE: "TLS_PSK_WITH_AES_128_CBC_SHA256",
    0x00AF: "TLS_PSK_WITH_AES_256_CBC_SHA384",
    0x00B0: "TLS_PSK_WITH_NULL_SHA256",
    0x00B1: "TLS_PSK_WITH_NULL_SHA384",
    0x00B2: "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256",
    0x00B3: "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384",
    0x00B4: "TLS_DHE_PSK_WITH_NULL_SHA256",
    0x00B5: "TLS_DHE_PSK_WITH_NULL_SHA384",
    0x00B6: "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256",
    0x00B7: "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384",
    0x00B8: "TLS_RSA_PSK_WITH_NULL_SHA256",
    0x00B9: "TLS_RSA_PSK_WITH_NULL_SHA384",
    0x00BA: "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256",
    0x00BB: "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256",
    0x00BC: "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
    0x00BD: "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256",
    0x00BE: "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
    0x00BF: "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256",
    0x00C0: "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256",
    0x00C1: "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256",
    0x00C2: "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256",
    0x00C3: "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256",
    0x00C4: "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256",
    0x00C5: "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256",
    0x00C6: "TLS_SM4_GCM_SM3",
    0x00C7: "TLS_SM4_CCM_SM3",
    0x00FF: "TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
    0x0A0A: "GREASE",
    0x1301: "TLS_AES_128_GCM_SHA256",
    0x1302: "TLS_AES_256_GCM_SHA384",
    0x1303: "TLS_CHACHA20_POLY1305_SHA256",
    0x1304: "TLS_AES_128_CCM_SHA256",
    0x1305: "TLS_AES_128_CCM_8_SHA256",
    0x1306: "TLS_AEGIS_256_SHA512",
    0x1307: "TLS_AEGIS_128L_SHA256",
    0x1A1A: "GREASE",
    0x2A2A: "GREASE",
    0x3A3A: "GREASE",
    0x4A4A: "GREASE",
    0x5600: "TLS_FALLBACK_SCSV",
    0x5A5A: "GREASE",
    0x6A6A: "GREASE",
    0x7A7A: "GREASE",
    0x8A8A: "GREASE",
    0x9A9A: "GREASE",
    0xAAAA: "GREASE",
    0xBABA: "GREASE",
    0xC001: "TLS_ECDH_ECDSA_WITH_NULL_SHA",
    0xC002: "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
    0xC003: "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
    0xC004: "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
    0xC005: "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
    0xC006: "TLS_ECDHE_ECDSA_WITH_NULL_SHA",
    0xC007: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
    0xC008: "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
    0xC009: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
    0xC00A: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
    0xC00B: "TLS_ECDH_RSA_WITH_NULL_SHA",
    0xC00C: "TLS_ECDH_RSA_WITH_RC4_128_SHA",
    0xC00D: "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
    0xC00E: "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
    0xC00F: "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
    0xC010: "TLS_ECDHE_RSA_WITH_NULL_SHA",
    0xC011: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
    0xC012: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
    0xC013: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    0xC014: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
    0xC015: "TLS_ECDH_anon_WITH_NULL_SHA",
    0xC016: "TLS_ECDH_anon_WITH_RC4_128_SHA",
    0xC017: "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
    0xC018: "TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
    0xC019: "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
    0xC01A: "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA",
    0xC01B: "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA",
    0xC01C: "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA",
    0xC01D: "TLS_SRP_SHA_WITH_AES_128_CBC_SHA",
    0xC01E: "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA",
    0xC01F: "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA",
    0xC020: "TLS_SRP_SHA_WITH_AES_256_CBC_SHA",
    0xC021: "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA",
    0xC022: "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA",
    0xC023: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
    0xC024: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
    0xC025: "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
    0xC026: "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
    0xC027: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    0xC028: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
    0xC029: "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
    0xC02A: "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
    0xC02B: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    0xC02C: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
    0xC02D: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
    0xC02E: "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
    0xC02F: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    0xC030: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
    0xC031: "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
    0xC032: "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
    0xC033: "TLS_ECDHE_PSK_WITH_RC4_128_SHA",
    0xC034: "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA",
    0xC035: "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA",
    0xC036: "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA",
    0xC037: "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256",
    0xC038: "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384",
    0xC039: "TLS_ECDHE_PSK_WITH_NULL_SHA",
    0xC03A: "TLS_ECDHE_PSK_WITH_NULL_SHA256",
    0xC03B: "TLS_ECDHE_PSK_WITH_NULL_SHA384",
    0xC03C: "TLS_RSA_WITH_ARIA_128_CBC_SHA256",
    0xC03D: "TLS_RSA_WITH_ARIA_256_CBC_SHA384",
    0xC03E: "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256",
    0xC03F: "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384",
    0xC040: "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256",
    0xC041: "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384",
    0xC042: "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256",
    0xC043: "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384",
    0xC044: "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256",
    0xC045: "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384",
    0xC046: "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256",
    0xC047: "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384",
    0xC048: "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256",
    0xC049: "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384",
    0xC04A: "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256",
    0xC04B: "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384",
    0xC04C: "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256",
    0xC04D: "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384",
    0xC04E: "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256",
    0xC04F: "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384",
    0xC050: "TLS_RSA_WITH_ARIA_128_GCM_SHA256",
    0xC051: "TLS_RSA_WITH_ARIA_256_GCM_SHA384",
    0xC052: "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256",
    0xC053: "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384",
    0xC054: "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256",
    0xC055: "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384",
    0xC056: "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256",
    0xC057: "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384",
    0xC058: "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256",
    0xC059: "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384",
    0xC05A: "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256",
    0xC05B: "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384",
    0xC05C: "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256",
    0xC05D: "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384",
    0xC05E: "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256",
    0xC05F: "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384",
    0xC060: "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256",
    0xC061: "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384",
    0xC062: "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256",
    0xC063: "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384",
    0xC064: "TLS_PSK_WITH_ARIA_128_CBC_SHA256",
    0xC065: "TLS_PSK_WITH_ARIA_256_CBC_SHA384",
    0xC066: "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256",
    0xC067: "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384",
    0xC068: "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256",
    0xC069: "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384",
    0xC06A: "TLS_PSK_WITH_ARIA_128_GCM_SHA256",
    0xC06B: "TLS_PSK_WITH_ARIA_256_GCM_SHA384",
    0xC06C: "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256",
    0xC06D: "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384",
    0xC06E: "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256",
    0xC06F: "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384",
    0xC070: "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256",
    0xC071: "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384",
    0xC072: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
    0xC073: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
    0xC074: "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
    0xC075: "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
    0xC076: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
    0xC077: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384",
    0xC078: "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
    0xC079: "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384",
    0xC07A: "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256",
    0xC07B: "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384",
    0xC07C: "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256",
    0xC07D: "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384",
    0xC07E: "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
    0xC07F: "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
    0xC080: "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256",
    0xC081: "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384",
    0xC082: "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256",
    0xC083: "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384",
    0xC084: "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256",
    0xC085: "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384",
    0xC086: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256",
    0xC087: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384",
    0xC088: "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256",
    0xC089: "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384",
    0xC08A: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256",
    0xC08B: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384",
    0xC08C: "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
    0xC08D: "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
    0xC08E: "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256",
    0xC08F: "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384",
    0xC090: "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256",
    0xC091: "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384",
    0xC092: "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256",
    0xC093: "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384",
    0xC094: "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256",
    0xC095: "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384",
    0xC096: "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
    0xC097: "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
    0xC098: "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256",
    0xC099: "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384",
    0xC09A: "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
    0xC09B: "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
    0xC09C: "TLS_RSA_WITH_AES_128_CCM",
    0xC09D: "TLS_RSA_WITH_AES_256_CCM",
    0xC09E: "TLS_DHE_RSA_WITH_AES_128_CCM",
    0xC09F: "TLS_DHE_RSA_WITH_AES_256_CCM",
    0xC0A0: "TLS_RSA_WITH_AES_128_CCM_8",
    0xC0A1: "TLS_RSA_WITH_AES_256_CCM_8",
    0xC0A2: "TLS_DHE_RSA_WITH_AES_128_CCM_8",
    0xC0A3: "TLS_DHE_RSA_WITH_AES_256_CCM_8",
    0xC0A4: "TLS_PSK_WITH_AES_128_CCM",
    0xC0A5: "TLS_PSK_WITH_AES_256_CCM",
    0xC0A6: "TLS_DHE_PSK_WITH_AES_128_CCM",
    0xC0A7: "TLS_DHE_PSK_WITH_AES_256_CCM",
    0xC0A8: "TLS_PSK_WITH_AES_128_CCM_8",
    0xC0A9: "TLS_PSK_WITH_AES_256_CCM_8",
    0xC0AA: "TLS_PSK_DHE_WITH_AES_128_CCM_8",
    0xC0AB: "TLS_PSK_DHE_WITH_AES_256_CCM_8",
    0xC0AC: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
    0xC0AD: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM",
    0xC0AE: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8",
    0xC0AF: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8",
    0xC0B0: "TLS_ECCPWD_WITH_AES_128_GCM_SHA256",
    0xC0B1: "TLS_ECCPWD_WITH_AES_256_GCM_SHA384",
    0xC0B2: "TLS_ECCPWD_WITH_AES_128_CCM_SHA256",
    0xC0B3: "TLS_ECCPWD_WITH_AES_256_CCM_SHA384",
    0xC0B4: "TLS_SHA256_SHA256",
    0xC0B5: "TLS_SHA384_SHA384",
    0xC100: "TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC",
    0xC101: "TLS_GOSTR341112_256_WITH_MAGMA_CTR_OMAC",
    0xC102: "TLS_GOSTR341112_256_WITH_28147_CNT_IMIT",
    0xC103: "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_L",
    0xC104: "TLS_GOSTR341112_256_WITH_MAGMA_MGM_L",
    0xC105: "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_S",
    0xC106: "TLS_GOSTR341112_256_WITH_MAGMA_MGM_S",
    0xCACA: "GREASE",
    0xCCA8: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
    0xCCA9: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
    0xCCAA: "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
    0xCCAB: "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256",
    0xCCAC: "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
    0xCCAD: "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
    0xCCAE: "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256",
    0xD001: "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256",
    0xD002: "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384",
    0xD003: "TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256",
    0xD005: "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256",
    0xDADA: "GREASE",
    0xEAEA: "GREASE",
    0xFAFA: "GREASE",
};

/**
 * GREASE values
 */
export const GREASE_VALUES = [
    0x0a0a,
    0x1a1a,
    0x2a2a,
    0x3a3a,
    0x4a4a,
    0x5a5a,
    0x6a6a,
    0x7a7a,
    0x8a8a,
    0x9a9a,
    0xaaaa,
    0xbaba,
    0xcaca,
    0xdada,
    0xeaea,
    0xfafa
];

/**
 * Parses the supported_versions extension and returns the highest supported version.
 * @param {Uint8Array} bytes
 * @returns {number}
 */
export function parseHighestSupportedVersion(bytes) {
    const s = new Stream(bytes);

    // The Server Hello supported_versions extension simply contains the chosen version
    if (s.length === 2) {
        return s.readInt(2);
    }

    // Length
    let i = s.readInt(1);

    let highestVersion = 0;
    while (s.hasMore() && i-- > 0) {
        const v = s.readInt(2);
        if (GREASE_VALUES.includes(v)) continue;
        if (v > highestVersion) highestVersion = v;
    }

    return highestVersion;
}

/**
 * Parses the application_layer_protocol_negotiation extension and returns the first value.
 * @param {Uint8Array} bytes
 * @returns {number}
 */
export function parseFirstALPNValue(bytes) {
    const s = new Stream(bytes);
    const alpnExtLen = s.readInt(2);
    if (alpnExtLen < 3) return "00";
    const strLen = s.readInt(1);
    if (strLen < 2) return "00";
    return s.readString(strLen);
}
