text: /* eslint-disable node-core/require-common-first, node-core/required-modules */
  /* eslint-disable node-core/crypto-check */
  
  'use strict';
  const crypto = require('crypto');
  const net = require('net');
  
  exports.ccs = Buffer.from('140303000101', 'hex');
  
  class TestTLSSocket extends net.Socket {
    constructor(server_cert) {
      super();
      this.server_cert = server_cert;
      this.version = Buffer.from('0303', 'hex');
      this.handshake_list = [];
      // AES128-GCM-SHA256
      this.ciphers = Buffer.from('000002009c0', 'hex');
      this.pre_master_secret =
        Buffer.concat([this.version, crypto.randomBytes(46)]);
      this.master_secret = null;
      this.write_seq = 0;
      this.client_random = crypto.randomBytes(32);
  
      this.on('handshake', (msg) => {
        this.handshake_list.push(msg);
      });
  
      this.on('server_random', (server_random) => {
        this.master_secret = PRF12('sha256', this.pre_master_secret,
                                   'master secret',
                                   Buffer.concat([this.client_random,
                                                  server_random]),
                                   48);
        const key_block = PRF12('sha256', this.master_secret,
                                'key expansion',
                                Buffer.concat([server_random,
                                               this.client_random]),
                                40);
        this.client_writeKey = key_block.slice(0, 16);
        this.client_writeIV = key_block.slice(32, 36);
      });
    }
  
    createClientHello() {
      const compressions = Buffer.from('0100', 'hex'); // null
      const msg = addHandshakeHeader(0x01, Buffer.concat([
        this.version, this.client_random, this.ciphers, compressions
      ]));
      this.emit('handshake', msg);
      return addRecordHeader(0x16, msg);
    }
  
    createClientKeyExchange() {
      const encrypted_pre_master_secret = crypto.publicEncrypt({
        key: this.server_cert,
        padding: crypto.constants.RSA_PKCS1_PADDING
      }, this.pre_master_secret);
      const length = Buffer.alloc(2);
      length.writeUIntBE(encrypted_pre_master_secret.length, 0, 2);
      const msg = addHandshakeHeader(0x10, Buffer.concat([
        length, encrypted_pre_master_secret]));
      this.emit('handshake', msg);
      return addRecordHeader(0x16, msg);
    }
  
    createFinished() {
      const shasum = crypto.createHash('sha256');
      shasum.update(Buffer.concat(this.handshake_list));
      const message_hash = shasum.digest();
      const r = PRF12('sha256', this.master_secret,
                      'client finished', message_hash, 12);
      const msg = addHandshakeHeader(0x14, r);
      this.emit('handshake', msg);
      return addRecordHeader(0x16, msg);
    }
  
    createIllegalHandshake() {
      const illegal_handshake = Buffer.alloc(5);
      return addRecordHeader(0x16, illegal_handshake);
    }
  
    parseTLSFrame(buf) {
      let offset = 0;
      const record = buf.slice(offset, 5);
      const type = record[0];
      const length = record.slice(3, 5).readUInt16BE(0);
      offset += 5;
      let remaining = buf.slice(offset, offset + length);
      if (type === 0x16) {
        do {
          remaining = this.parseTLSHandshake(remaining);
        } while (remaining.length > 0);
      }
      offset += length;
      return buf.slice(offset);
    }
  
    parseTLSHandshake(buf) {
      let offset = 0;
      const handshake_type = buf[offset];
      if (handshake_type === 0x02) {
        const server_random = buf.slice(6, 6 + 32);
        this.emit('server_random', server_random);
      }
      offset += 1;
      const length = buf.readUIntBE(offset, 3);
      offset += 3;
      const handshake = buf.slice(0, offset + length);
      this.emit('handshake', handshake);
      offset += length;
      const remaining = buf.slice(offset);
      return remaining;
    }
  
    encrypt(plain) {
      const type = plain.slice(0, 1);
      const version = plain.slice(1, 3);
      const nonce = crypto.randomBytes(8);
      const iv = Buffer.concat([this.client_writeIV.slice(0, 4), nonce]);
      const bob = crypto.createCipheriv('aes-128-gcm', this.client_writeKey, iv);
      const write_seq = Buffer.alloc(8);
      write_seq.writeUInt32BE(this.write_seq++, 4);
      const aad = Buffer.concat([write_seq, plain.slice(0, 5)]);
      bob.setAAD(aad);
      const encrypted1 = bob.update(plain.slice(5));
      const encrypted = Buffer.concat([encrypted1, bob.final()]);
      const tag = bob.getAuthTag();
      const length = Buffer.alloc(2);
      length.writeUInt16BE(nonce.length + encrypted.length + tag.length, 0);
      return Buffer.concat([type, version, length, nonce, encrypted, tag]);
    }
  }
  
  function addRecordHeader(type, frame) {
    const record_layer = Buffer.from('0003030000', 'hex');
    record_layer[0] = type;
    record_layer.writeUInt16BE(frame.length, 3);
    return Buffer.concat([record_layer, frame]);
  }
  
  function addHandshakeHeader(type, msg) {
    const handshake_header = Buffer.alloc(4);
    handshake_header[0] = type;
    handshake_header.writeUIntBE(msg.length, 1, 3);
    return Buffer.concat([handshake_header, msg]);
  }
  
  function PRF12(algo, secret, label, seed, size) {
    const newSeed = Buffer.concat([Buffer.from(label, 'utf8'), seed]);
    return P_hash(algo, secret, newSeed, size);
  }
  
  function P_hash(algo, secret, seed, size) {
    const result = Buffer.alloc(size);
    let hmac = crypto.createHmac(algo, secret);
    hmac.update(seed);
    let a = hmac.digest();
    let j = 0;
    while (j < size) {
      hmac = crypto.createHmac(algo, secret);
      hmac.update(a);
      hmac.update(seed);
      const b = hmac.digest();
      let todo = b.length;
      if (j + todo > size) {
        todo = size - j;
      }
      b.copy(result, j, 0, todo);
      j += todo;
      hmac = crypto.createHmac(algo, secret);
      hmac.update(a);
      a = hmac.digest();
    }
    return result;
  }
  
  exports.TestTLSSocket = TestTLSSocket;
  