Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/services/fxaccounts/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 112 kB image not shown  

Impressum FxAccountsPairingChannel.sys.mjs   Interaktion und
Portierbarkeitunbekannt

 
Spracherkennung für: .mjs vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

/*!
 * 
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * 
 * The following bundle is from an external repository at github.com/mozilla/fxa-pairing-channel,
 * it implements a shared library for two javascript environments to create an encrypted and authenticated
 * communication channel by sharing a secret key and by relaying messages through a websocket server.
 * 
 * It is used by the Firefox Accounts pairing flow, with one side of the channel being web
 * content from https://accounts.firefox.com and the other side of the channel being chrome native code.
 * 
 * This uses the event-target-shim node library published under the MIT license:
 * https://github.com/mysticatea/event-target-shim/blob/master/LICENSE
 * 
 * Bundle generated from https://github.com/mozilla/fxa-pairing-channel.git. Hash:c8ec3119920b4ffa833b, Chunkhash:378a5f51445e7aa7630e.
 * 
 */

// This header provides a little bit of plumbing to use `FxAccountsPairingChannel`
// from Firefox browser code, hence the presence of these privileged browser APIs.
// If you're trying to use this from ordinary web content you're in for a bad time.

import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";

// We cannot use WebSocket from chrome code without a window,
// see https://bugzilla.mozilla.org/show_bug.cgi?id=784686
const browser = Services.appShell.createWindowlessBrowser(true);
const {WebSocket} = browser.document.ownerGlobal;

export var FxAccountsPairingChannel =
/******/ (function(modules) { // webpackBootstrap
/******/  // The module cache
/******/  var installedModules = {};
/******/
/******/  // The require function
/******/  function __webpack_require__(moduleId) {
/******/
/******/   // Check if module is in cache
/******/   if(installedModules[moduleId]) {
/******/    return installedModules[moduleId].exports;
/******/   }
/******/   // Create a new module (and put it into the cache)
/******/   var module = installedModules[moduleId] = {
/******/    i: moduleId,
/******/    l: false,
/******/    exports: {}
/******/   };
/******/
/******/   // Execute the module function
/******/   modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/   // Flag the module as loaded
/******/   module.l = true;
/******/
/******/   // Return the exports of the module
/******/   return module.exports;
/******/  }
/******/
/******/
/******/  // expose the modules object (__webpack_modules__)
/******/  __webpack_require__.m = modules;
/******/
/******/  // expose the module cache
/******/  __webpack_require__.c = installedModules;
/******/
/******/  // define getter function for harmony exports
/******/  __webpack_require__.d = function(exports, name, getter) {
/******/   if(!__webpack_require__.o(exports, name)) {
/******/    Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/   }
/******/  };
/******/
/******/  // define __esModule on exports
/******/  __webpack_require__.r = function(exports) {
/******/   if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/    Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/   }
/******/   Object.defineProperty(exports, '__esModule', { value: true });
/******/  };
/******/
/******/  // create a fake namespace object
/******/  // mode & 1: value is a module id, require it
/******/  // mode & 2: merge all properties of value into the ns
/******/  // mode & 4: return value when already ns object
/******/  // mode & 8|1: behave like require
/******/  __webpack_require__.t = function(value, mode) {
/******/   if(mode & 1) value = __webpack_require__(value);
/******/   if(mode & 8) return value;
/******/   if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/   var ns = Object.create(null);
/******/   __webpack_require__.r(ns);
/******/   Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/   if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/   return ns;
/******/  };
/******/
/******/  // getDefaultExport function for compatibility with non-harmony modules
/******/  __webpack_require__.n = function(module) {
/******/   var getter = module && module.__esModule ?
/******/    function getDefault() { return module['default']; } :
/******/    function getModuleExports() { return module; };
/******/   __webpack_require__.d(getter, 'a', getter);
/******/   return getter;
/******/  };
/******/
/******/  // Object.prototype.hasOwnProperty.call
/******/  __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/  // __webpack_public_path__
/******/  __webpack_require__.p = "";
/******/
/******/
/******/  // Load entry module and return exports
/******/  return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);

// EXPORTS
__webpack_require__.d(__webpack_exports__, "PairingChannel", function() { return /* binding */ src_PairingChannel; });
__webpack_require__.d(__webpack_exports__, "base64urlToBytes", function() { return /* reexport */ base64urlToBytes; });
__webpack_require__.d(__webpack_exports__, "bytesToBase64url", function() { return /* reexport */ bytesToBase64url; });
__webpack_require__.d(__webpack_exports__, "bytesToHex", function() { return /* reexport */ bytesToHex; });
__webpack_require__.d(__webpack_exports__, "bytesToUtf8", function() { return /* reexport */ bytesToUtf8; });
__webpack_require__.d(__webpack_exports__, "hexToBytes", function() { return /* reexport */ hexToBytes; });
__webpack_require__.d(__webpack_exports__, "TLSCloseNotify", function() { return /* reexport */ TLSCloseNotify; });
__webpack_require__.d(__webpack_exports__, "TLSError", function() { return /* reexport */ TLSError; });
__webpack_require__.d(__webpack_exports__, "utf8ToBytes", function() { return /* reexport */ utf8ToBytes; });
__webpack_require__.d(__webpack_exports__, "_internals", function() { return /* binding */ _internals; });

// CONCATENATED MODULE: ./src/alerts.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable sorting/sort-object-props */
const ALERT_LEVEL = {
  WARNING: 1,
  FATAL: 2
};

const ALERT_DESCRIPTION = {
  CLOSE_NOTIFY: 0,
  UNEXPECTED_MESSAGE: 10,
  BAD_RECORD_MAC: 20,
  RECORD_OVERFLOW: 22,
  HANDSHAKE_FAILURE: 40,
  ILLEGAL_PARAMETER: 47,
  DECODE_ERROR: 50,
  DECRYPT_ERROR: 51,
  PROTOCOL_VERSION: 70,
  INTERNAL_ERROR: 80,
  MISSING_EXTENSION: 109,
  UNSUPPORTED_EXTENSION: 110,
  UNKNOWN_PSK_IDENTITY: 115,
  NO_APPLICATION_PROTOCOL: 120,
};
/* eslint-enable sorting/sort-object-props */

function alertTypeToName(type) {
  for (const name in ALERT_DESCRIPTION) {
    if (ALERT_DESCRIPTION[name] === type) {
      return `${name} (${type})`;
    }
  }
  return `UNKNOWN (${type})`;
}

class TLSAlert extends Error {
  constructor(description, level) {
    super(`TLS Alert: ${alertTypeToName(description)}`);
    this.description = description;
    this.level = level;
  }

  static fromBytes(bytes) {
    if (bytes.byteLength !== 2) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    switch (bytes[1]) {
      case ALERT_DESCRIPTION.CLOSE_NOTIFY:
        if (bytes[0] !== ALERT_LEVEL.WARNING) {
          // Close notifications should be fatal.
          throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
        }
        return new TLSCloseNotify();
      default:
        return new TLSError(bytes[1]);
    }
  }

  toBytes() {
    return new Uint8Array([this.level, this.description]);
  }
}

class TLSCloseNotify extends TLSAlert {
  constructor() {
    super(ALERT_DESCRIPTION.CLOSE_NOTIFY, ALERT_LEVEL.WARNING);
  }
}

class TLSError extends TLSAlert {
  constructor(description = ALERT_DESCRIPTION.INTERNAL_ERROR) {
    super(description, ALERT_LEVEL.FATAL);
  }
}

// CONCATENATED MODULE: ./src/utils.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */



//
// Various low-level utility functions.
//
// These are mostly conveniences for working with Uint8Arrays as
// the primitive "bytes" type.
//

const UTF8_ENCODER = new TextEncoder();
const UTF8_DECODER = new TextDecoder();

function noop() {}

function assert(cond, msg) {
  if (! cond) {
    throw new Error('assert failed: ' + msg);
  }
}

function assertIsBytes(value, msg = 'value must be a Uint8Array') {
  // Using `value instanceof Uint8Array` seems to fail in Firefox chrome code
  // for inscrutable reasons, so we do a less direct check.
  assert(ArrayBuffer.isView(value), msg);
  assert(value.BYTES_PER_ELEMENT === 1, msg);
  return value;
}

const EMPTY = new Uint8Array(0);

function zeros(n) {
  return new Uint8Array(n);
}

function arrayToBytes(value) {
  return new Uint8Array(value);
}

function bytesToHex(bytes) {
  return Array.prototype.map.call(bytes, byte => {
    let s = byte.toString(16);
    if (s.length === 1) {
      s = '0' + s;
    }
    return s;
  }).join('');
}

function hexToBytes(hexstr) {
  assert(hexstr.length % 2 === 0, 'hexstr.length must be even');
  return new Uint8Array(Array.prototype.map.call(hexstr, (c, n) => {
    if (n % 2 === 1) {
      return hexstr[n - 1] + c;
    } else {
      return '';
    }
  }).filter(s => {
    return !! s;
  }).map(s => {
    return parseInt(s, 16);
  }));
}

function bytesToUtf8(bytes) {
  return UTF8_DECODER.decode(bytes);
}

function utf8ToBytes(str) {
  return UTF8_ENCODER.encode(str);
}

function bytesToBase64url(bytes) {
  // XXX TODO: try to use something constant-time, in case calling code
  // uses it to encode secrets?
  const charCodes = String.fromCharCode.apply(String, bytes);
  return btoa(charCodes).replace(/\+/g, '-').replace(/\//g, '_');
}

function base64urlToBytes(str) {
  // XXX TODO: try to use something constant-time, in case calling code
  // uses it to decode secrets?
  str = atob(str.replace(/-/g, '+').replace(/_/g, '/'));
  const bytes = new Uint8Array(str.length);
  for (let i = 0; i < str.length; i++) {
    bytes[i] = str.charCodeAt(i);
  }
  return bytes;
}

function bytesAreEqual(v1, v2) {
  assertIsBytes(v1);
  assertIsBytes(v2);
  if (v1.length !== v2.length) {
    return false;
  }
  for (let i = 0; i < v1.length; i++) {
    if (v1[i] !== v2[i]) {
      return false;
    }
  }
  return true;
}

// The `BufferReader` and `BufferWriter` classes are helpers for dealing with the
// binary struct format that's used for various TLS message.  Think of them as a
// buffer with a pointer to the "current position" and a bunch of helper methods
// to read/write structured data and advance said pointer.

class utils_BufferWithPointer {
  constructor(buf) {
    this._buffer = buf;
    this._dataview = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
    this._pos = 0;
  }

  length() {
    return this._buffer.byteLength;
  }

  tell() {
    return this._pos;
  }

  seek(pos) {
    if (pos < 0) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    if (pos > this.length()) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    this._pos = pos;
  }

  incr(offset) {
    this.seek(this._pos + offset);
  }
}

// The `BufferReader` class helps you read structured data from a byte array.
// It offers methods for reading both primitive values, and the variable-length
// vector structures defined in https://tools.ietf.org/html/rfc8446#section-3.4.
//
// Such vectors are represented as a length followed by the concatenated
// bytes of each item, and the size of the length field is determined by
// the maximum allowed number of bytes in the vector.  For example
// to read a vector that may contain up to 65535 bytes, use `readVector16`.
//
// To read a variable-length vector of between 1 and 100 uint16 values,
// defined in the RFC like this:
//
//    uint16 items<2..200>;
//
// You would do something like this:
//
//    const items = []
//    buf.readVector8(buf => {
//      items.push(buf.readUint16())
//    })
//
// The various `read` will throw `DECODE_ERROR` if you attempt to read path
// the end of the buffer, or past the end of a variable-length list.
//
class utils_BufferReader extends utils_BufferWithPointer {

  hasMoreBytes() {
    return this.tell() < this.length();
  }

  readBytes(length) {
    // This avoids copies by returning a view onto the existing buffer.
    const start = this._buffer.byteOffset + this.tell();
    this.incr(length);
    return new Uint8Array(this._buffer.buffer, start, length);
  }

  _rangeErrorToAlert(cb) {
    try {
      return cb(this);
    } catch (err) {
      if (err instanceof RangeError) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
      throw err;
    }
  }

  readUint8() {
    return this._rangeErrorToAlert(() => {
      const n = this._dataview.getUint8(this._pos);
      this.incr(1);
      return n;
    });
  }

  readUint16() {
    return this._rangeErrorToAlert(() => {
      const n = this._dataview.getUint16(this._pos);
      this.incr(2);
      return n;
    });
  }

  readUint24() {
    return this._rangeErrorToAlert(() => {
      let n = this._dataview.getUint16(this._pos);
      n = (n << 8) | this._dataview.getUint8(this._pos + 2);
      this.incr(3);
      return n;
    });
  }

  readUint32() {
    return this._rangeErrorToAlert(() => {
      const n = this._dataview.getUint32(this._pos);
      this.incr(4);
      return n;
    });
  }

  _readVector(length, cb) {
    const contentsBuf = new utils_BufferReader(this.readBytes(length));
    const expectedEnd = this.tell();
    // Keep calling the callback until we've consumed the expected number of bytes.
    let n = 0;
    while (contentsBuf.hasMoreBytes()) {
      const prevPos = contentsBuf.tell();
      cb(contentsBuf, n);
      // Check that the callback made forward progress, otherwise we'll infinite loop.
      if (contentsBuf.tell() <= prevPos) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
      n += 1;
    }
    // Check that the callback correctly consumed the vector's entire contents.
    if (this.tell() !== expectedEnd) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
  }

  readVector8(cb) {
    const length = this.readUint8();
    return this._readVector(length, cb);
  }

  readVector16(cb) {
    const length = this.readUint16();
    return this._readVector(length, cb);
  }

  readVector24(cb) {
    const length = this.readUint24();
    return this._readVector(length, cb);
  }

  readVectorBytes8() {
    return this.readBytes(this.readUint8());
  }

  readVectorBytes16() {
    return this.readBytes(this.readUint16());
  }

  readVectorBytes24() {
    return this.readBytes(this.readUint24());
  }
}


class utils_BufferWriter extends utils_BufferWithPointer {
  constructor(size = 1024) {
    super(new Uint8Array(size));
  }

  _maybeGrow(n) {
    const curSize = this._buffer.byteLength;
    const newPos = this._pos + n;
    const shortfall = newPos - curSize;
    if (shortfall > 0) {
      // Classic grow-by-doubling, up to 4kB max increment.
      // This formula was not arrived at by any particular science.
      const incr = Math.min(curSize, 4 * 1024);
      const newbuf = new Uint8Array(curSize + Math.ceil(shortfall / incr) * incr);
      newbuf.set(this._buffer, 0);
      this._buffer = newbuf;
      this._dataview = new DataView(newbuf.buffer, newbuf.byteOffset, newbuf.byteLength);
    }
  }

  slice(start = 0, end = this.tell()) {
    if (end < 0) {
      end = this.tell() + end;
    }
    if (start < 0) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    if (end < 0) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    if (end > this.length()) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    return this._buffer.slice(start, end);
  }

  flush() {
    const slice = this.slice();
    this.seek(0);
    return slice;
  }

  writeBytes(data) {
    this._maybeGrow(data.byteLength);
    this._buffer.set(data, this.tell());
    this.incr(data.byteLength);
  }

  writeUint8(n) {
    this._maybeGrow(1);
    this._dataview.setUint8(this._pos, n);
    this.incr(1);
  }

  writeUint16(n) {
    this._maybeGrow(2);
    this._dataview.setUint16(this._pos, n);
    this.incr(2);
  }

  writeUint24(n) {
    this._maybeGrow(3);
    this._dataview.setUint16(this._pos, n >> 8);
    this._dataview.setUint8(this._pos + 2, n & 0xFF);
    this.incr(3);
  }

  writeUint32(n) {
    this._maybeGrow(4);
    this._dataview.setUint32(this._pos, n);
    this.incr(4);
  }

  // These are helpers for writing the variable-length vector structure
  // defined in https://tools.ietf.org/html/rfc8446#section-3.4.
  //
  // Such vectors are represented as a length followed by the concatenated
  // bytes of each item, and the size of the length field is determined by
  // the maximum allowed size of the vector.  For example to write a vector
  // that may contain up to 65535 bytes, use `writeVector16`.
  //
  // To write a variable-length vector of between 1 and 100 uint16 values,
  // defined in the RFC like this:
  //
  //    uint16 items<2..200>;
  //
  // You would do something like this:
  //
  //    buf.writeVector8(buf => {
  //      for (let item of items) {
  //          buf.writeUint16(item)
  //      }
  //    })
  //
  // The helper will automatically take care of writing the appropriate
  // length field once the callback completes.

  _writeVector(maxLength, writeLength, cb) {
    // Initially, write the length field as zero.
    const lengthPos = this.tell();
    writeLength(0);
    // Call the callback to write the vector items.
    const bodyPos = this.tell();
    cb(this);
    const length = this.tell() - bodyPos;
    if (length >= maxLength) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    // Backfill the actual length field.
    this.seek(lengthPos);
    writeLength(length);
    this.incr(length);
    return length;
  }

  writeVector8(cb) {
    return this._writeVector(Math.pow(2, 8), len => this.writeUint8(len), cb);
  }

  writeVector16(cb) {
    return this._writeVector(Math.pow(2, 16), len => this.writeUint16(len), cb);
  }

  writeVector24(cb) {
    return this._writeVector(Math.pow(2, 24), len => this.writeUint24(len), cb);
  }

  writeVectorBytes8(bytes) {
    return this.writeVector8(buf => {
      buf.writeBytes(bytes);
    });
  }

  writeVectorBytes16(bytes) {
    return this.writeVector16(buf => {
      buf.writeBytes(bytes);
    });
  }

  writeVectorBytes24(bytes) {
    return this.writeVector24(buf => {
      buf.writeBytes(bytes);
    });
  }
}

// CONCATENATED MODULE: ./src/crypto.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// Low-level crypto primitives.
//
// This file implements the AEAD encrypt/decrypt and hashing routines
// for the TLS_AES_128_GCM_SHA256 ciphersuite. They are (thankfully)
// fairly light-weight wrappers around what's available via the WebCrypto
// API.
//




const AEAD_SIZE_INFLATION = 16;
const KEY_LENGTH = 16;
const IV_LENGTH = 12;
const HASH_LENGTH = 32;

async function prepareKey(key, mode) {
  return crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [mode]);
}

async function encrypt(key, iv, plaintext, additionalData) {
  const ciphertext = await crypto.subtle.encrypt({
    additionalData,
    iv,
    name: 'AES-GCM',
    tagLength: AEAD_SIZE_INFLATION * 8
  }, key, plaintext);
  return new Uint8Array(ciphertext);
}

async function decrypt(key, iv, ciphertext, additionalData) {
  try {
    const plaintext = await crypto.subtle.decrypt({
      additionalData,
      iv,
      name: 'AES-GCM',
      tagLength: AEAD_SIZE_INFLATION * 8
    }, key, ciphertext);
    return new Uint8Array(plaintext);
  } catch (err) {
    // Yes, we really do throw 'decrypt_error' when failing to verify a HMAC,
    // and a 'bad_record_mac' error when failing to decrypt.
    throw new TLSError(ALERT_DESCRIPTION.BAD_RECORD_MAC);
  }
}

async function hash(message) {
  return new Uint8Array(await crypto.subtle.digest({ name: 'SHA-256' }, message));
}

async function hmac(keyBytes, message) {
  const key = await crypto.subtle.importKey('raw', keyBytes, {
    hash: { name: 'SHA-256' },
    name: 'HMAC',
  }, false, ['sign']);
  const sig = await crypto.subtle.sign({ name: 'HMAC' }, key, message);
  return new Uint8Array(sig);
}

async function verifyHmac(keyBytes, signature, message) {
  const key = await crypto.subtle.importKey('raw', keyBytes, {
    hash: { name: 'SHA-256' },
    name: 'HMAC',
  }, false, ['verify']);
  if (! (await crypto.subtle.verify({ name: 'HMAC' }, key, signature, message))) {
    // Yes, we really do throw 'decrypt_error' when failing to verify a HMAC,
    // and a 'bad_record_mac' error when failing to decrypt.
    throw new TLSError(ALERT_DESCRIPTION.DECRYPT_ERROR);
  }
}

async function hkdfExtract(salt, ikm) {
  // Ref https://tools.ietf.org/html/rfc5869#section-2.2
  return await hmac(salt, ikm);
}

async function hkdfExpand(prk, info, length) {
  // Ref https://tools.ietf.org/html/rfc5869#section-2.3
  const N = Math.ceil(length / HASH_LENGTH);
  if (N <= 0) {
    throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
  }
  if (N >= 255) {
    throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
  }
  const input = new utils_BufferWriter();
  const output = new utils_BufferWriter();
  let T = new Uint8Array(0);
  for (let i = 1; i <= N; i++) {
    input.writeBytes(T);
    input.writeBytes(info);
    input.writeUint8(i);
    T = await hmac(prk, input.flush());
    output.writeBytes(T);
  }
  return output.slice(0, length);
}

async function hkdfExpandLabel(secret, label, context, length) {
  //  struct {
  //    uint16 length = Length;
  //    opaque label < 7..255 > = "tls13 " + Label;
  //    opaque context < 0..255 > = Context;
  //  } HkdfLabel;
  const hkdfLabel = new utils_BufferWriter();
  hkdfLabel.writeUint16(length);
  hkdfLabel.writeVectorBytes8(utf8ToBytes('tls13 ' + label));
  hkdfLabel.writeVectorBytes8(context);
  return hkdfExpand(secret, hkdfLabel.flush(), length);
}

async function getRandomBytes(size) {
  const bytes = new Uint8Array(size);
  crypto.getRandomValues(bytes);
  return bytes;
}

// CONCATENATED MODULE: ./src/extensions.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// Extension parsing.
//
// This file contains some helpers for reading/writing the various kinds
// of Extension that might appear in a HandshakeMessage.
//
// "Extensions" are how TLS signals the presence of particular bits of optional
// functionality in the protocol. Lots of parts of TLS1.3 that don't seem like
// they're optional are implemented in terms of an extension, IIUC because that's
// what was needed for a clean deployment in amongst earlier versions of the protocol.
//





/* eslint-disable sorting/sort-object-props */
const EXTENSION_TYPE = {
  PRE_SHARED_KEY: 41,
  SUPPORTED_VERSIONS: 43,
  PSK_KEY_EXCHANGE_MODES: 45,
};
/* eslint-enable sorting/sort-object-props */

// Base class for generic reading/writing of extensions,
// which are all uniformly formatted as:
//
//   struct {
//     ExtensionType extension_type;
//     opaque extension_data<0..2^16-1>;
//   } Extension;
//
// Extensions always appear inside of a handshake message,
// and their internal structure may differ based on the
// type of that message.

class extensions_Extension {

  get TYPE_TAG() {
    throw new Error('not implemented');
  }

  static read(messageType, buf) {
    const type = buf.readUint16();
    let ext = {
      TYPE_TAG: type,
    };
    buf.readVector16(buf => {
      switch (type) {
        case EXTENSION_TYPE.PRE_SHARED_KEY:
          ext = extensions_PreSharedKeyExtension._read(messageType, buf);
          break;
        case EXTENSION_TYPE.SUPPORTED_VERSIONS:
          ext = extensions_SupportedVersionsExtension._read(messageType, buf);
          break;
        case EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES:
          ext = extensions_PskKeyExchangeModesExtension._read(messageType, buf);
          break;
        default:
          // Skip over unrecognised extensions.
          buf.incr(buf.length());
      }
      if (buf.hasMoreBytes()) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
    });
    return ext;
  }

  write(messageType, buf) {
    buf.writeUint16(this.TYPE_TAG);
    buf.writeVector16(buf => {
      this._write(messageType, buf);
    });
  }

  static _read(messageType, buf) {
    throw new Error('not implemented');
  }

  static _write(messageType, buf) {
    throw new Error('not implemented');
  }
}

// The PreSharedKey extension:
//
//  struct {
//    opaque identity<1..2^16-1>;
//    uint32 obfuscated_ticket_age;
//  } PskIdentity;
//  opaque PskBinderEntry<32..255>;
//  struct {
//    PskIdentity identities<7..2^16-1>;
//    PskBinderEntry binders<33..2^16-1>;
//  } OfferedPsks;
//  struct {
//    select(Handshake.msg_type) {
//      case client_hello: OfferedPsks;
//      case server_hello: uint16 selected_identity;
//    };
//  } PreSharedKeyExtension;

class extensions_PreSharedKeyExtension extends extensions_Extension {
  constructor(identities, binders, selectedIdentity) {
    super();
    this.identities = identities;
    this.binders = binders;
    this.selectedIdentity = selectedIdentity;
  }

  get TYPE_TAG() {
    return EXTENSION_TYPE.PRE_SHARED_KEY;
  }

  static _read(messageType, buf) {
    let identities = null, binders = null, selectedIdentity = null;
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        identities = []; binders = [];
        buf.readVector16(buf => {
          const identity = buf.readVectorBytes16();
          buf.readBytes(4); // Skip over the ticket age.
          identities.push(identity);
        });
        buf.readVector16(buf => {
          const binder = buf.readVectorBytes8();
          if (binder.byteLength < HASH_LENGTH) {
            throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
          }
          binders.push(binder);
        });
        if (identities.length !== binders.length) {
          throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
        }
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        selectedIdentity = buf.readUint16();
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(identities, binders, selectedIdentity);
  }

  _write(messageType, buf) {
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.writeVector16(buf => {
          this.identities.forEach(pskId => {
            buf.writeVectorBytes16(pskId);
            buf.writeUint32(0); // Zero for "tag age" field.
          });
        });
        buf.writeVector16(buf => {
          this.binders.forEach(pskBinder => {
            buf.writeVectorBytes8(pskBinder);
          });
        });
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        buf.writeUint16(this.selectedIdentity);
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
  }
}


// The SupportedVersions extension:
//
//  struct {
//    select(Handshake.msg_type) {
//      case client_hello:
//        ProtocolVersion versions < 2..254 >;
//      case server_hello:
//        ProtocolVersion selected_version;
//    };
//  } SupportedVersions;

class extensions_SupportedVersionsExtension extends extensions_Extension {
  constructor(versions, selectedVersion) {
    super();
    this.versions = versions;
    this.selectedVersion = selectedVersion;
  }

  get TYPE_TAG() {
    return EXTENSION_TYPE.SUPPORTED_VERSIONS;
  }

  static _read(messageType, buf) {
    let versions = null, selectedVersion = null;
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        versions = [];
        buf.readVector8(buf => {
          versions.push(buf.readUint16());
        });
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        selectedVersion = buf.readUint16();
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(versions, selectedVersion);
  }

  _write(messageType, buf) {
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.writeVector8(buf => {
          this.versions.forEach(version => {
            buf.writeUint16(version);
          });
        });
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        buf.writeUint16(this.selectedVersion);
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
  }
}


class extensions_PskKeyExchangeModesExtension extends extensions_Extension {
  constructor(modes) {
    super();
    this.modes = modes;
  }

  get TYPE_TAG() {
    return EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES;
  }

  static _read(messageType, buf) {
    const modes = [];
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.readVector8(buf => {
          modes.push(buf.readUint8());
        });
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(modes);
  }

  _write(messageType, buf) {
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.writeVector8(buf => {
          this.modes.forEach(mode => {
            buf.writeUint8(mode);
          });
        });
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
  }
}

// CONCATENATED MODULE: ./src/constants.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const VERSION_TLS_1_0 = 0x0301;
const VERSION_TLS_1_2 = 0x0303;
const VERSION_TLS_1_3 = 0x0304;
const TLS_AES_128_GCM_SHA256 = 0x1301;
const PSK_MODE_KE = 0;

// CONCATENATED MODULE: ./src/messages.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// Message parsing.
//
// Herein we have code for reading and writing the various Handshake
// messages involved in the TLS protocol.
//







/* eslint-disable sorting/sort-object-props */
const HANDSHAKE_TYPE = {
  CLIENT_HELLO: 1,
  SERVER_HELLO: 2,
  NEW_SESSION_TICKET: 4,
  ENCRYPTED_EXTENSIONS: 8,
  FINISHED: 20,
};
/* eslint-enable sorting/sort-object-props */

// Base class for generic reading/writing of handshake messages,
// which are all uniformly formatted as:
//
//  struct {
//    HandshakeType msg_type;    /* handshake type */
//    uint24 length;             /* bytes in message */
//    select(Handshake.msg_type) {
//        ... type specific cases here ...
//    };
//  } Handshake;

class messages_HandshakeMessage {

  get TYPE_TAG() {
    throw new Error('not implemented');
  }

  static fromBytes(bytes) {
    // Each handshake message has a type and length prefix, per
    // https://tools.ietf.org/html/rfc8446#appendix-B.3
    const buf = new utils_BufferReader(bytes);
    const msg = this.read(buf);
    if (buf.hasMoreBytes()) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    return msg;
  }

  toBytes() {
    const buf = new utils_BufferWriter();
    this.write(buf);
    return buf.flush();
  }

  static read(buf) {
    const type = buf.readUint8();
    let msg = null;
    buf.readVector24(buf => {
      switch (type) {
        case HANDSHAKE_TYPE.CLIENT_HELLO:
          msg = messages_ClientHello._read(buf);
          break;
        case HANDSHAKE_TYPE.SERVER_HELLO:
          msg = messages_ServerHello._read(buf);
          break;
        case HANDSHAKE_TYPE.NEW_SESSION_TICKET:
          msg = messages_NewSessionTicket._read(buf);
          break;
        case HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS:
          msg = EncryptedExtensions._read(buf);
          break;
        case HANDSHAKE_TYPE.FINISHED:
          msg = messages_Finished._read(buf);
          break;
      }
      if (buf.hasMoreBytes()) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
    });
    if (msg === null) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    return msg;
  }

  write(buf) {
    buf.writeUint8(this.TYPE_TAG);
    buf.writeVector24(buf => {
      this._write(buf);
    });
  }

  static _read(buf) {
    throw new Error('not implemented');
  }

  _write(buf) {
    throw new Error('not implemented');
  }

  // Some little helpers for reading a list of extensions,
  // which is uniformly represented as:
  //
  //   Extension extensions<8..2^16-1>;
  //
  // Recognized extensions are returned as a Map from extension type
  // to extension data object, with a special `lastSeenExtension`
  // property to make it easy to check which one came last.

  static _readExtensions(messageType, buf) {
    const extensions = new Map();
    buf.readVector16(buf => {
      const ext = extensions_Extension.read(messageType, buf);
      if (extensions.has(ext.TYPE_TAG)) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
      extensions.set(ext.TYPE_TAG, ext);
      extensions.lastSeenExtension = ext.TYPE_TAG;
    });
    return extensions;
  }

  _writeExtensions(buf, extensions) {
    buf.writeVector16(buf => {
      extensions.forEach(ext => {
        ext.write(this.TYPE_TAG, buf);
      });
    });
  }
}


// The ClientHello message:
//
// struct {
//   ProtocolVersion legacy_version = 0x0303;
//   Random random;
//   opaque legacy_session_id<0..32>;
//   CipherSuite cipher_suites<2..2^16-2>;
//   opaque legacy_compression_methods<1..2^8-1>;
//   Extension extensions<8..2^16-1>;
// } ClientHello;

class messages_ClientHello extends messages_HandshakeMessage {

  constructor(random, sessionId, extensions) {
    super();
    this.random = random;
    this.sessionId = sessionId;
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.CLIENT_HELLO;
  }

  static _read(buf) {
    // The legacy_version field may indicate an earlier version of TLS
    // for backwards compatibility, but must not predate TLS 1.0!
    if (buf.readUint16() < VERSION_TLS_1_0) {
      throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION);
    }
    // The random bytes provided by the peer.
    const random = buf.readBytes(32);
    // Read legacy_session_id, so the server can echo it.
    const sessionId = buf.readVectorBytes8();
    // We only support a single ciphersuite, but the peer may offer several.
    // Scan the list to confirm that the one we want is present.
    let found = false;
    buf.readVector16(buf => {
      const cipherSuite = buf.readUint16();
      if (cipherSuite === TLS_AES_128_GCM_SHA256) {
        found = true;
      }
    });
    if (! found) {
      throw new TLSError(ALERT_DESCRIPTION.HANDSHAKE_FAILURE);
    }
    // legacy_compression_methods must be a single zero byte for TLS1.3 ClientHellos.
    // It can be non-zero in previous versions of TLS, but we're not going to
    // make a successful handshake with such versions, so better to just bail out now.
    const legacyCompressionMethods = buf.readVectorBytes8();
    if (legacyCompressionMethods.byteLength !== 1) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    if (legacyCompressionMethods[0] !== 0x00) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    // Read and check the extensions.
    const extensions = this._readExtensions(HANDSHAKE_TYPE.CLIENT_HELLO, buf);
    if (! extensions.has(EXTENSION_TYPE.SUPPORTED_VERSIONS)) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    if (extensions.get(EXTENSION_TYPE.SUPPORTED_VERSIONS).versions.indexOf(VERSION_TLS_1_3) === -1) {
      throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION);
    }
    // Was the PreSharedKey extension the last one?
    if (extensions.has(EXTENSION_TYPE.PRE_SHARED_KEY)) {
      if (extensions.lastSeenExtension !== EXTENSION_TYPE.PRE_SHARED_KEY) {
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
      }
    }
    return new this(random, sessionId, extensions);
  }

  _write(buf) {
    buf.writeUint16(VERSION_TLS_1_2);
    buf.writeBytes(this.random);
    buf.writeVectorBytes8(this.sessionId);
    // Our single supported ciphersuite
    buf.writeVector16(buf => {
      buf.writeUint16(TLS_AES_128_GCM_SHA256);
    });
    // A single zero byte for legacy_compression_methods
    buf.writeVectorBytes8(new Uint8Array(1));
    this._writeExtensions(buf, this.extensions);
  }
}


// The ServerHello message:
//
//  struct {
//      ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */
//      Random random;
//      opaque legacy_session_id_echo<0..32>;
//      CipherSuite cipher_suite;
//      uint8 legacy_compression_method = 0;
//      Extension extensions < 6..2 ^ 16 - 1 >;
//  } ServerHello;

class messages_ServerHello extends messages_HandshakeMessage {

  constructor(random, sessionId, extensions) {
    super();
    this.random = random;
    this.sessionId = sessionId;
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.SERVER_HELLO;
  }

  static _read(buf) {
    // Fixed value for legacy_version.
    if (buf.readUint16() !== VERSION_TLS_1_2) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    // Random bytes from the server.
    const random = buf.readBytes(32);
    // It should have echoed our vector for legacy_session_id.
    const sessionId = buf.readVectorBytes8();
    // It should have selected our single offered ciphersuite.
    if (buf.readUint16() !== TLS_AES_128_GCM_SHA256) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    // legacy_compression_method must be zero.
    if (buf.readUint8() !== 0) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    const extensions = this._readExtensions(HANDSHAKE_TYPE.SERVER_HELLO, buf);
    if (! extensions.has(EXTENSION_TYPE.SUPPORTED_VERSIONS)) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    if (extensions.get(EXTENSION_TYPE.SUPPORTED_VERSIONS).selectedVersion !== VERSION_TLS_1_3) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(random, sessionId, extensions);
  }

  _write(buf) {
    buf.writeUint16(VERSION_TLS_1_2);
    buf.writeBytes(this.random);
    buf.writeVectorBytes8(this.sessionId);
    // Our single supported ciphersuite
    buf.writeUint16(TLS_AES_128_GCM_SHA256);
    // A single zero byte for legacy_compression_method
    buf.writeUint8(0);
    this._writeExtensions(buf, this.extensions);
  }
}


// The EncryptedExtensions message:
//
//  struct {
//    Extension extensions < 0..2 ^ 16 - 1 >;
//  } EncryptedExtensions;
//
// We don't actually send any EncryptedExtensions,
// but still have to send an empty message.

class EncryptedExtensions extends messages_HandshakeMessage {
  constructor(extensions) {
    super();
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS;
  }

  static _read(buf) {
    const extensions = this._readExtensions(HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS, buf);
    return new this(extensions);
  }

  _write(buf) {
    this._writeExtensions(buf, this.extensions);
  }
}


// The Finished message:
//
// struct {
//   opaque verify_data[Hash.length];
// } Finished;

class messages_Finished extends messages_HandshakeMessage {

  constructor(verifyData) {
    super();
    this.verifyData = verifyData;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.FINISHED;
  }

  static _read(buf) {
    const verifyData = buf.readBytes(HASH_LENGTH);
    return new this(verifyData);
  }

  _write(buf) {
    buf.writeBytes(this.verifyData);
  }
}


// The NewSessionTicket message:
//
//   struct {
//    uint32 ticket_lifetime;
//    uint32 ticket_age_add;
//    opaque ticket_nonce < 0..255 >;
//    opaque ticket < 1..2 ^ 16 - 1 >;
//    Extension extensions < 0..2 ^ 16 - 2 >;
//  } NewSessionTicket;
//
// We don't actually make use of these, but we need to be able
// to accept them and do basic validation.

class messages_NewSessionTicket extends messages_HandshakeMessage {
  constructor(ticketLifetime, ticketAgeAdd, ticketNonce, ticket, extensions) {
    super();
    this.ticketLifetime = ticketLifetime;
    this.ticketAgeAdd = ticketAgeAdd;
    this.ticketNonce = ticketNonce;
    this.ticket = ticket;
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.NEW_SESSION_TICKET;
  }

  static _read(buf) {
    const ticketLifetime = buf.readUint32();
    const ticketAgeAdd = buf.readUint32();
    const ticketNonce = buf.readVectorBytes8();
    const ticket = buf.readVectorBytes16();
    if (ticket.byteLength < 1) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    const extensions = this._readExtensions(HANDSHAKE_TYPE.NEW_SESSION_TICKET, buf);
    return new this(ticketLifetime, ticketAgeAdd, ticketNonce, ticket, extensions);
  }

  _write(buf) {
    buf.writeUint32(this.ticketLifetime);
    buf.writeUint32(this.ticketAgeAdd);
    buf.writeVectorBytes8(this.ticketNonce);
    buf.writeVectorBytes16(this.ticket);
    this._writeExtensions(buf, this.extensions);
  }
}

// CONCATENATED MODULE: ./src/states.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */








//
// State-machine for TLS Handshake Management.
//
// Internally, we manage the TLS connection by explicitly modelling the
// client and server state-machines from RFC8446.  You can think of
// these `State` objects as little plugins for the `Connection` class
// that provide different behaviours of `send` and `receive` depending
// on the state of the connection.
//

class states_State {

  constructor(conn) {
    this.conn = conn;
  }

  async initialize() {
    // By default, nothing to do when entering the state.
  }

  async sendApplicationData(bytes) {
    // By default, assume we're not ready to send yet and the caller
    // should be blocking on the connection promise before reaching here.
    throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
  }

  async recvApplicationData(bytes) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }

  async recvHandshakeMessage(msg) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }

  async recvAlertMessage(alert) {
    switch (alert.description) {
      case ALERT_DESCRIPTION.CLOSE_NOTIFY:
        this.conn._closeForRecv(alert);
        throw alert;
      default:
        return await this.handleErrorAndRethrow(alert);
    }
  }

  async recvChangeCipherSpec(bytes) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }

  async handleErrorAndRethrow(err) {
    let alert = err;
    if (! (alert instanceof TLSAlert)) {
      alert = new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    // Try to send error alert to the peer, but we may not
    // be able to if the outgoing connection was already closed.
    try {
      await this.conn._sendAlertMessage(alert);
    } catch (_) { }
    await this.conn._transition(ERROR, err);
    throw err;
  }

  async close() {
    const alert = new TLSCloseNotify();
    await this.conn._sendAlertMessage(alert);
    this.conn._closeForSend(alert);
  }

}

// A special "guard" state to prevent us from using
// an improperly-initialized Connection.

class UNINITIALIZED extends states_State {
  async initialize() {
    throw new Error('uninitialized state');
  }
  async sendApplicationData(bytes) {
    throw new Error('uninitialized state');
  }
  async recvApplicationData(bytes) {
    throw new Error('uninitialized state');
  }
  async recvHandshakeMessage(msg) {
    throw new Error('uninitialized state');
  }
  async recvChangeCipherSpec(bytes) {
    throw new Error('uninitialized state');
  }
  async handleErrorAndRethrow(err) {
    throw err;
  }
  async close() {
    throw new Error('uninitialized state');
  }
}

// A special "error" state for when something goes wrong.
// This state never transitions to another state, effectively
// terminating the connection.

class ERROR extends states_State {
  async initialize(err) {
    this.error = err;
    this.conn._setConnectionFailure(err);
    // Unceremoniously shut down the record layer on error.
    this.conn._recordlayer.setSendError(err);
    this.conn._recordlayer.setRecvError(err);
  }
  async sendApplicationData(bytes) {
    throw this.error;
  }
  async recvApplicationData(bytes) {
    throw this.error;
  }
  async recvHandshakeMessage(msg) {
    throw this.error;
  }
  async recvAlertMessage(err) {
    throw this.error;
  }
  async recvChangeCipherSpec(bytes) {
    throw this.error;
  }
  async handleErrorAndRethrow(err) {
    throw err;
  }
  async close() {
    throw this.error;
  }
}

// The "connected" state, for when the handshake is complete
// and we're ready to send application-level data.
// The logic for this is largely symmetric between client and server.

class states_CONNECTED extends states_State {
  async initialize() {
    this.conn._setConnectionSuccess();
  }
  async sendApplicationData(bytes) {
    await this.conn._sendApplicationData(bytes);
  }
  async recvApplicationData(bytes) {
    return bytes;
  }
  async recvChangeCipherSpec(bytes) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }
}

// A base class for states that occur in the middle of the handshake
// (that is, between ClientHello and Finished).  These states may receive
// CHANGE_CIPHER_SPEC records for b/w compat reasons, which must contain
// exactly a single 0x01 byte and must otherwise be ignored.

class states_MidHandshakeState extends states_State {
  async recvChangeCipherSpec(bytes) {
    if (this.conn._hasSeenChangeCipherSpec) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    if (bytes.byteLength !== 1 || bytes[0] !== 1) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    this.conn._hasSeenChangeCipherSpec = true;
  }
}

// These states implement (part of) the client state-machine from
// https://tools.ietf.org/html/rfc8446#appendix-A.1
//
// Since we're only implementing a small subset of TLS1.3,
// we only need a small subset of the handshake.  It basically goes:
//
//   * send ClientHello
//   * receive ServerHello
//   * receive EncryptedExtensions
//   * receive server Finished
//   * send client Finished
//
// We include some unused states for completeness, so that it's easier
// to check the implementation against the diagrams in the RFC.

class states_CLIENT_START extends states_State {
  async initialize() {
    const keyschedule = this.conn._keyschedule;
    await keyschedule.addPSK(this.conn.psk);
    // Construct a ClientHello message with our single PSK.
    // We can't know the PSK binder value yet, so we initially write zeros.
    const clientHello = new messages_ClientHello(
      // Client random salt.
      await getRandomBytes(32),
      // Random legacy_session_id; we *could* send an empty string here,
      // but sending a random one makes it easier to be compatible with
      // the data emitted by tlslite-ng for test-case generation.
      await getRandomBytes(32),
      [
        new extensions_SupportedVersionsExtension([VERSION_TLS_1_3]),
        new extensions_PskKeyExchangeModesExtension([PSK_MODE_KE]),
        new extensions_PreSharedKeyExtension([this.conn.pskId], [zeros(HASH_LENGTH)]),
      ],
    );
    const buf = new utils_BufferWriter();
    clientHello.write(buf);
    // Now that we know what the ClientHello looks like,
    // go back and calculate the appropriate PSK binder value.
    // We only support a single PSK, so the length of the binders field is the
    // length of the hash plus one for rendering it as a variable-length byte array,
    // plus two for rendering the variable-length list of PSK binders.
    const PSK_BINDERS_SIZE = HASH_LENGTH + 1 + 2;
    const truncatedTranscript = buf.slice(0, buf.tell() - PSK_BINDERS_SIZE);
    const pskBinder = await keyschedule.calculateFinishedMAC(keyschedule.extBinderKey, truncatedTranscript);
    buf.incr(-HASH_LENGTH);
    buf.writeBytes(pskBinder);
    await this.conn._sendHandshakeMessageBytes(buf.flush());
    await this.conn._transition(states_CLIENT_WAIT_SH, clientHello.sessionId);
  }
}

class states_CLIENT_WAIT_SH extends states_State {
  async initialize(sessionId) {
    this._sessionId = sessionId;
  }
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_ServerHello)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    if (! bytesAreEqual(msg.sessionId, this._sessionId)) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    const pskExt = msg.extensions.get(EXTENSION_TYPE.PRE_SHARED_KEY);
    if (! pskExt) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    // We expect only the SUPPORTED_VERSIONS and PRE_SHARED_KEY extensions.
    if (msg.extensions.size !== 2) {
      throw new TLSError(ALERT_DESCRIPTION.UNSUPPORTED_EXTENSION);
    }
    if (pskExt.selectedIdentity !== 0) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    await this.conn._keyschedule.addECDHE(null);
    await this.conn._setSendKey(this.conn._keyschedule.clientHandshakeTrafficSecret);
    await this.conn._setRecvKey(this.conn._keyschedule.serverHandshakeTrafficSecret);
    await this.conn._transition(states_CLIENT_WAIT_EE);
  }
}

class states_CLIENT_WAIT_EE extends states_MidHandshakeState {
  async recvHandshakeMessage(msg) {
    // We don't make use of any encrypted extensions, but we still
    // have to wait for the server to send the (empty) list of them.
    if (! (msg instanceof EncryptedExtensions)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    // We do not support any EncryptedExtensions.
    if (msg.extensions.size !== 0) {
      throw new TLSError(ALERT_DESCRIPTION.UNSUPPORTED_EXTENSION);
    }
    const keyschedule = this.conn._keyschedule;
    const serverFinishedTranscript = keyschedule.getTranscript();
    await this.conn._transition(states_CLIENT_WAIT_FINISHED, serverFinishedTranscript);
  }
}

class states_CLIENT_WAIT_FINISHED extends states_State {
  async initialize(serverFinishedTranscript) {
    this._serverFinishedTranscript = serverFinishedTranscript;
  }
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_Finished)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    // Verify server Finished MAC.
    const keyschedule = this.conn._keyschedule;
    await keyschedule.verifyFinishedMAC(keyschedule.serverHandshakeTrafficSecret, msg.verifyData, this._serverFinishedTranscript);
    // Send our own Finished message in return.
    // This must be encrypted with the handshake traffic key,
    // but must not appear in the transcript used to calculate the application keys.
    const clientFinishedMAC = await keyschedule.calculateFinishedMAC(keyschedule.clientHandshakeTrafficSecret);
    await keyschedule.finalize();
    await this.conn._sendHandshakeMessage(new messages_Finished(clientFinishedMAC));
    await this.conn._setSendKey(keyschedule.clientApplicationTrafficSecret);
    await this.conn._setRecvKey(keyschedule.serverApplicationTrafficSecret);
    await this.conn._transition(states_CLIENT_CONNECTED);
  }
}

class states_CLIENT_CONNECTED extends states_CONNECTED {
  async recvHandshakeMessage(msg) {
    // A connected client must be prepared to accept NewSessionTicket
    // messages.  We never use them, but other server implementations
    // might send them.
    if (! (msg instanceof messages_NewSessionTicket)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
  }
}

// These states implement (part of) the server state-machine from
// https://tools.ietf.org/html/rfc8446#appendix-A.2
//
// Since we're only implementing a small subset of TLS1.3,
// we only need a small subset of the handshake.  It basically goes:
//
//   * receive ClientHello
//   * send ServerHello
//   * send empty EncryptedExtensions
//   * send server Finished
//   * receive client Finished
//
// We include some unused states for completeness, so that it's easier
// to check the implementation against the diagrams in the RFC.

class states_SERVER_START extends states_State {
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_ClientHello)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    // In the spec, this is where we select connection parameters, and maybe
    // tell the client to try again if we can't find a compatible set.
    // Since we only support a fixed cipherset, the only thing to "negotiate"
    // is whether they provided an acceptable PSK.
    const pskExt = msg.extensions.get(EXTENSION_TYPE.PRE_SHARED_KEY);
    const pskModesExt = msg.extensions.get(EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES);
    if (! pskExt || ! pskModesExt) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    if (pskModesExt.modes.indexOf(PSK_MODE_KE) === -1) {
      throw new TLSError(ALERT_DESCRIPTION.HANDSHAKE_FAILURE);
    }
    const pskIndex = pskExt.identities.findIndex(pskId => bytesAreEqual(pskId, this.conn.pskId));
    if (pskIndex === -1) {
      throw new TLSError(ALERT_DESCRIPTION.UNKNOWN_PSK_IDENTITY);
    }
    await this.conn._keyschedule.addPSK(this.conn.psk);
    // Validate the PSK binder.
    const keyschedule = this.conn._keyschedule;
    const transcript = keyschedule.getTranscript();
    // Calculate size occupied by the PSK binders.
    let pskBindersSize = 2; // Vector16 representation overhead.
    for (const binder of pskExt.binders) {
      pskBindersSize += binder.byteLength + 1; // Vector8 representation overhead.
    }
    await keyschedule.verifyFinishedMAC(keyschedule.extBinderKey, pskExt.binders[pskIndex], transcript.slice(0, -pskBindersSize));
    await this.conn._transition(states_SERVER_NEGOTIATED, msg.sessionId, pskIndex);
  }
}

class states_SERVER_NEGOTIATED extends states_MidHandshakeState {
  async initialize(sessionId, pskIndex) {
    await this.conn._sendHandshakeMessage(new messages_ServerHello(
      // Server random
      await getRandomBytes(32),
      sessionId,
      [
        new extensions_SupportedVersionsExtension(null, VERSION_TLS_1_3),
        new extensions_PreSharedKeyExtension(null, null, pskIndex),
      ]
    ));
    // If the client sent a non-empty sessionId, the server *must* send a change-cipher-spec for b/w compat.
    if (sessionId.byteLength > 0) {
      await this.conn._sendChangeCipherSpec();
    }
    // We can now transition to the encrypted part of the handshake.
    const keyschedule = this.conn._keyschedule;
    await keyschedule.addECDHE(null);
    await this.conn._setSendKey(keyschedule.serverHandshakeTrafficSecret);
    await this.conn._setRecvKey(keyschedule.clientHandshakeTrafficSecret);
    // Send an empty EncryptedExtensions message.
    await this.conn._sendHandshakeMessage(new EncryptedExtensions([]));
    // Send the Finished message.
    const serverFinishedMAC = await keyschedule.calculateFinishedMAC(keyschedule.serverHandshakeTrafficSecret);
    await this.conn._sendHandshakeMessage(new messages_Finished(serverFinishedMAC));
    // We can now *send* using the application traffic key,
    // but have to wait to receive the client Finished before receiving under that key.
    // We need to remember the handshake state from before the client Finished
    // in order to successfully verify the client Finished.
    const clientFinishedTranscript = await keyschedule.getTranscript();
    const clientHandshakeTrafficSecret = keyschedule.clientHandshakeTrafficSecret;
    await keyschedule.finalize();
    await this.conn._setSendKey(keyschedule.serverApplicationTrafficSecret);
    await this.conn._transition(states_SERVER_WAIT_FINISHED, clientHandshakeTrafficSecret, clientFinishedTranscript);
  }
}

class states_SERVER_WAIT_FINISHED extends states_MidHandshakeState {
  async initialize(clientHandshakeTrafficSecret, clientFinishedTranscript) {
    this._clientHandshakeTrafficSecret = clientHandshakeTrafficSecret;
    this._clientFinishedTranscript = clientFinishedTranscript;
  }
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_Finished)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    const keyschedule = this.conn._keyschedule;
    await keyschedule.verifyFinishedMAC(this._clientHandshakeTrafficSecret, msg.verifyData, this._clientFinishedTranscript);
    this._clientHandshakeTrafficSecret = this._clientFinishedTranscript = null;
    await this.conn._setRecvKey(keyschedule.clientApplicationTrafficSecret);
    await this.conn._transition(states_CONNECTED);
  }
}

// CONCATENATED MODULE: ./src/keyschedule.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// TLS1.3 Key Schedule.
//
// In this file we implement the "key schedule" from
// https://tools.ietf.org/html/rfc8446#section-7.1, which
// defines how to calculate various keys as the handshake
// state progresses.







// The `KeySchedule` class progresses through three stages corresponding
// to the three phases of the TLS1.3 key schedule:
//
//   UNINITIALIZED
//       |
//       | addPSK()
//       v
//   EARLY_SECRET
//       |
//       | addECDHE()
//       v
//   HANDSHAKE_SECRET
//       |
//       | finalize()
//       v
//   MASTER_SECRET
//
// It will error out if the calling code attempts to add key material
// in the wrong order.

const STAGE_UNINITIALIZED = 0;
const STAGE_EARLY_SECRET = 1;
const STAGE_HANDSHAKE_SECRET = 2;
const STAGE_MASTER_SECRET = 3;

class keyschedule_KeySchedule {
  constructor() {
    this.stage = STAGE_UNINITIALIZED;
    // WebCrypto doesn't support a rolling hash construct, so we have to
    // keep the entire message transcript in memory.
    this.transcript = new utils_BufferWriter();
    // This tracks the main secret from with other keys are derived at each stage.
    this.secret = null;
    // And these are all the various keys we'll derive as the handshake progresses.
    this.extBinderKey = null;
    this.clientHandshakeTrafficSecret = null;
    this.serverHandshakeTrafficSecret = null;
    this.clientApplicationTrafficSecret = null;
    this.serverApplicationTrafficSecret = null;
  }

  async addPSK(psk) {
    // Use the selected PSK (if any) to calculate the "early secret".
    if (psk === null) {
      psk = zeros(HASH_LENGTH);
    }
    if (this.stage !== STAGE_UNINITIALIZED) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    this.stage = STAGE_EARLY_SECRET;
    this.secret = await hkdfExtract(zeros(HASH_LENGTH), psk);
    this.extBinderKey = await this.deriveSecret('ext binder', EMPTY);
    this.secret = await this.deriveSecret('derived', EMPTY);
  }

  async addECDHE(ecdhe) {
    // Mix in the ECDHE output (if any) to calculate the "handshake secret".
    if (ecdhe === null) {
      ecdhe = zeros(HASH_LENGTH);
    }
    if (this.stage !== STAGE_EARLY_SECRET) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    this.stage = STAGE_HANDSHAKE_SECRET;
    this.extBinderKey = null;
    this.secret = await hkdfExtract(this.secret, ecdhe);
    this.clientHandshakeTrafficSecret = await this.deriveSecret('c hs traffic');
    this.serverHandshakeTrafficSecret = await this.deriveSecret('s hs traffic');
    this.secret = await this.deriveSecret('derived', EMPTY);
  }

  async finalize() {
    if (this.stage !== STAGE_HANDSHAKE_SECRET) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    this.stage = STAGE_MASTER_SECRET;
    this.clientHandshakeTrafficSecret = null;
    this.serverHandshakeTrafficSecret = null;
    this.secret = await hkdfExtract(this.secret, zeros(HASH_LENGTH));
    this.clientApplicationTrafficSecret = await this.deriveSecret('c ap traffic');
    this.serverApplicationTrafficSecret = await this.deriveSecret('s ap traffic');
    this.secret = null;
  }

  addToTranscript(bytes) {
    this.transcript.writeBytes(bytes);
  }

  getTranscript() {
    return this.transcript.slice();
  }

  async deriveSecret(label, transcript = undefined) {
    transcript = transcript || this.getTranscript();
    return await hkdfExpandLabel(this.secret, label, await hash(transcript), HASH_LENGTH);
  }

  async calculateFinishedMAC(baseKey, transcript = undefined) {
    transcript = transcript || this.getTranscript();
    const finishedKey = await hkdfExpandLabel(baseKey, 'finished', EMPTY, HASH_LENGTH);
    return await hmac(finishedKey, await hash(transcript));
  }

  async verifyFinishedMAC(baseKey, mac, transcript = undefined) {
    transcript = transcript || this.getTranscript();
    const finishedKey = await hkdfExpandLabel(baseKey, 'finished', EMPTY, HASH_LENGTH);
    await verifyHmac(finishedKey, mac, await hash(transcript));
  }
}

// CONCATENATED MODULE: ./src/recordlayer.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// This file implements the "record layer" for TLS1.3, as defined in
// https://tools.ietf.org/html/rfc8446#section-5.
//
// The record layer is responsible for encrypting/decrypting bytes to be
// sent over the wire, including stateful management of sequence numbers
// for the incoming and outgoing stream.
//
// The main interface is the RecordLayer class, which takes a callback function
// sending data and can be used like so:
//
//    rl = new RecordLayer(async function send_encrypted_data(data) {
//      // application-specific sending logic here.
//    });
//
//    // Records are sent and received in plaintext by default,
//    // until you specify the key to use.
//    await rl.setSendKey(key)
//
//    // Send some data by specifying the record type and the bytes.
//    // Where allowed by the record type, it will be buffered until
//    // explicitly flushed, and then sent by calling the callback.
//    await rl.send(RECORD_TYPE.HANDSHAKE, <bytes for a handshake message>)
//    await rl.send(RECORD_TYPE.HANDSHAKE, <bytes for another handshake message>)
//    await rl.flush()
//
//    // Separate keys are used for sending and receiving.
//    rl.setRecvKey(key);
//
//    // When data is received, push it into the RecordLayer
//    // and pass a callback that will be called with a [type, bytes]
//    // pair for each message parsed from the data.
//    rl.recv(dataReceivedFromPeer, async (type, bytes) => {
//      switch (type) {
//        case RECORD_TYPE.APPLICATION_DATA:
//          // do something with application data
//        case RECORD_TYPE.HANDSHAKE:
//          // do something with a handshake message
//        default:
//          // etc...
//      }
//    });
//







/* eslint-disable sorting/sort-object-props */
const RECORD_TYPE = {
  CHANGE_CIPHER_SPEC: 20,
  ALERT: 21,
  HANDSHAKE: 22,
  APPLICATION_DATA: 23,
};
/* eslint-enable sorting/sort-object-props */

// Encrypting at most 2^24 records will force us to stay
// below data limits on AES-GCM encryption key use, and also
// means we can accurately represent the sequence number as
// a javascript double.
const MAX_SEQUENCE_NUMBER = Math.pow(2, 24);
const MAX_RECORD_SIZE = Math.pow(2, 14);
const MAX_ENCRYPTED_RECORD_SIZE = MAX_RECORD_SIZE + 256;
const RECORD_HEADER_SIZE = 5;

// These are some helper classes to manage the encryption/decryption state
// for a particular key.

class recordlayer_CipherState {
  constructor(key, iv) {
    this.key = key;
    this.iv = iv;
    this.seqnum = 0;
  }

  static async create(baseKey, mode) {
    // Derive key and iv per https://tools.ietf.org/html/rfc8446#section-7.3
    const key = await prepareKey(await hkdfExpandLabel(baseKey, 'key', EMPTY, KEY_LENGTH), mode);
    const iv = await hkdfExpandLabel(baseKey, 'iv', EMPTY, IV_LENGTH);
    return new this(key, iv);
  }

  nonce() {
    // Ref https://tools.ietf.org/html/rfc8446#section-5.3:
    // * left-pad the sequence number with zeros to IV_LENGTH
    // * xor with the provided iv
    // Our sequence numbers are always less than 2^24, so fit in a Uint32
    // in the last 4 bytes of the nonce.
    const nonce = this.iv.slice();
    const dv = new DataView(nonce.buffer, nonce.byteLength - 4, 4);
    dv.setUint32(0, dv.getUint32(0) ^ this.seqnum);
    this.seqnum += 1;
    if (this.seqnum > MAX_SEQUENCE_NUMBER) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    return nonce;
  }
}

class recordlayer_EncryptionState extends recordlayer_CipherState {
  static async create(key) {
    return super.create(key, 'encrypt');
  }

  async encrypt(plaintext, additionalData) {
    return await encrypt(this.key, this.nonce(), plaintext, additionalData);
  }
}

class recordlayer_DecryptionState extends recordlayer_CipherState {
  static async create(key) {
    return super.create(key, 'decrypt');
  }

  async decrypt(ciphertext, additionalData) {
    return await decrypt(this.key, this.nonce(), ciphertext, additionalData);
  }
}

// The main RecordLayer class.

class recordlayer_RecordLayer {
  constructor(sendCallback) {
    this.sendCallback = sendCallback;
    this._sendEncryptState = null;
    this._sendError = null;
    this._recvDecryptState = null;
    this._recvError = null;
    this._pendingRecordType = 0;
    this._pendingRecordBuf = null;
  }

  async setSendKey(key) {
    await this.flush();
    this._sendEncryptState = await recordlayer_EncryptionState.create(key);
  }

  async setRecvKey(key) {
    this._recvDecryptState = await recordlayer_DecryptionState.create(key);
  }

  async setSendError(err) {
    this._sendError = err;
  }

  async setRecvError(err) {
    this._recvError = err;
  }

  async send(type, data) {
    if (this._sendError !== null) {
      throw this._sendError;
    }
    // Forbid sending data that doesn't fit into a single record.
    // We do not support fragmentation over multiple records.
    if (data.byteLength > MAX_RECORD_SIZE) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    // Flush if we're switching to a different record type.
    if (this._pendingRecordType && this._pendingRecordType !== type) {
      await this.flush();
    }
    // Flush if we would overflow the max size of a record.
    if (this._pendingRecordBuf !== null) {
      if (this._pendingRecordBuf.tell() + data.byteLength > MAX_RECORD_SIZE) {
        await this.flush();
      }
    }
    // Start a new pending record if necessary.
    // We reserve space at the start of the buffer for the record header,
    // which is conveniently always a fixed size.
    if (this._pendingRecordBuf === null) {
      this._pendingRecordType = type;
      this._pendingRecordBuf = new utils_BufferWriter();
      this._pendingRecordBuf.incr(RECORD_HEADER_SIZE);
    }
    this._pendingRecordBuf.writeBytes(data);
  }

  async flush() {
    // If there's nothing to flush, bail out early.
    // Don't throw `_sendError` if we're not sending anything, because `flush()`
    // can be called when we're trying to transition into an error state.
    const buf = this._pendingRecordBuf;
    let type = this._pendingRecordType;
    if (! type) {
      if (buf !== null) {
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
      }
      return;
    }
    if (this._sendError !== null) {
      throw this._sendError;
    }
    // If we're encrypting, turn the existing buffer contents into a `TLSInnerPlaintext` by
    // appending the type. We don't do any zero-padding, although the spec allows it.
    let inflation = 0, innerPlaintext = null;
    if (this._sendEncryptState !== null) {
      buf.writeUint8(type);
      innerPlaintext = buf.slice(RECORD_HEADER_SIZE);
      inflation = AEAD_SIZE_INFLATION;
      type = RECORD_TYPE.APPLICATION_DATA;
    }
    // Write the common header for either `TLSPlaintext` or `TLSCiphertext` record.
    const length = buf.tell() - RECORD_HEADER_SIZE + inflation;
    buf.seek(0);
    buf.writeUint8(type);
    buf.writeUint16(VERSION_TLS_1_2);
    buf.writeUint16(length);
    // Followed by different payload depending on encryption status.
    if (this._sendEncryptState !== null) {
      const additionalData = buf.slice(0, RECORD_HEADER_SIZE);
      const ciphertext = await this._sendEncryptState.encrypt(innerPlaintext, additionalData);
      buf.writeBytes(ciphertext);
    } else {
      buf.incr(length);
    }
    this._pendingRecordBuf = null;
    this._pendingRecordType = 0;
    await this.sendCallback(buf.flush());
  }

  async recv(data) {
    if (this._recvError !== null) {
      throw this._recvError;
    }
    // For simplicity, we assume that the given data contains exactly one record.
    // Peers using this library will send one record at a time over the websocket
    // connection, and we can assume that the server-side websocket bridge will split
    // up any traffic into individual records if we ever start interoperating with
    // peers using a different TLS implementation.
    // Similarly, we assume that handshake messages will not be fragmented across
    // multiple records. This should be trivially true for the PSK-only mode used
    // by this library, but we may want to relax it in future for interoperability
    // with e.g. large ClientHello messages that contain lots of different options.
    const buf = new utils_BufferReader(data);
    // The data to read is either a TLSPlaintext or TLSCiphertext struct,
    // depending on whether record protection has been enabled yet:
    //
    //    struct {
    //        ContentType type;
    //        ProtocolVersion legacy_record_version;
    //        uint16 length;
    //        opaque fragment[TLSPlaintext.length];
    //    } TLSPlaintext;
    //
    //    struct {
    //        ContentType opaque_type = application_data; /* 23 */
    //        ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */
    //        uint16 length;
    //        opaque encrypted_record[TLSCiphertext.length];
    //    } TLSCiphertext;
    //
    let type = buf.readUint8();
    // The spec says legacy_record_version "MUST be ignored for all purposes",
    // but we know TLS1.3 implementations will only ever emit two possible values,
    // so it seems useful to bail out early if we receive anything else.
    const version = buf.readUint16();
    if (version !== VERSION_TLS_1_2) {
      // TLS1.0 is only acceptable on initial plaintext records.
      if (this._recvDecryptState !== null || version !== VERSION_TLS_1_0) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
    }
    const length = buf.readUint16();
    let plaintext;
    if (this._recvDecryptState === null || type === RECORD_TYPE.CHANGE_CIPHER_SPEC) {
      [type, plaintext] = await this._readPlaintextRecord(type, length, buf);
    } else {
      [type, plaintext] = await this._readEncryptedRecord(type, length, buf);
    }
    // Sanity-check that we received exactly one record.
    if (buf.hasMoreBytes()) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    return [type, plaintext];
  }

  // Helper to read an unencrypted `TLSPlaintext` struct

  async _readPlaintextRecord(type, length, buf) {
    if (length > MAX_RECORD_SIZE) {
      throw new TLSError(ALERT_DESCRIPTION.RECORD_OVERFLOW);
    }
    return [type, buf.readBytes(length)];
  }

  // Helper to read an encrypted `TLSCiphertext` struct,
  // decrypting it into plaintext.

  async _readEncryptedRecord(type, length, buf) {
    if (length > MAX_ENCRYPTED_RECORD_SIZE) {
      throw new TLSError(ALERT_DESCRIPTION.RECORD_OVERFLOW);
    }
    // The outer type for encrypted records is always APPLICATION_DATA.
    if (type !== RECORD_TYPE.APPLICATION_DATA) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    // Decrypt and decode the contained `TLSInnerPlaintext` struct:
    //
    //    struct {
    //        opaque content[TLSPlaintext.length];
    //        ContentType type;
    //        uint8 zeros[length_of_padding];
    //    } TLSInnerPlaintext;
    //
    // The additional data for the decryption is the `TLSCiphertext` record
    // header, which is a fixed size and immediately prior to current buffer position.
    buf.incr(-RECORD_HEADER_SIZE);
    const additionalData = buf.readBytes(RECORD_HEADER_SIZE);
    const ciphertext = buf.readBytes(length);
    const paddedPlaintext = await this._recvDecryptState.decrypt(ciphertext, additionalData);
    // We have to scan backwards over the zero padding at the end of the struct
    // in order to find the non-zero `type` byte.
    let i;
    for (i = paddedPlaintext.byteLength - 1; i >= 0; i--) {
      if (paddedPlaintext[i] !== 0) {
        break;
      }
    }
    if (i < 0) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    type = paddedPlaintext[i];
    // `change_cipher_spec` records must always be plaintext.
    if (type === RECORD_TYPE.CHANGE_CIPHER_SPEC) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    return [type, paddedPlaintext.slice(0, i)];
  }
}

// CONCATENATED MODULE: ./src/tlsconnection.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The top-level APIs offered by this module are `ClientConnection` and
// `ServerConnection` classes, which provide authenticated and encrypted
// communication via the "externally-provisioned PSK" mode of TLS1.3.
// They each take a callback to be used for sending data to the remote peer,
// and operate like this:
//
//    conn = await ClientConnection.create(psk, pskId, async function send_data_to_server(data) {
//      // application-specific sending logic here.
//    })
//
//    // Send data to the server by calling `send`,
//    // which will use the callback provided in the constructor.
//    // A single `send()` by the application may result in multiple
//    // invokations of the callback.
//
//    await conn.send('application-level data')
//
//    // When data is received from the server, push it into
//    // the connection and let it return any decrypted app-level data.
//    // There might not be any app-level data if it was a protocol control
//    //  message, and the receipt of the data might trigger additional calls
//    // to the send callback for protocol control purposes.
//
//    serverSocket.on('data', async encrypted_data => {
//      const plaintext = await conn.recv(data)
//      if (plaintext !== null) {
//        do_something_with_app_level_data(plaintext)
//      }
//    })
//
//    // It's good practice to explicitly close the connection
//    // when finished.  This will send a "closed" notification
//    // to the server.
//
//    await conn.close()
//
//    // When the peer sends a "closed" notification it will show up
//    // as a `TLSCloseNotify` exception from recv:
//
//    try {
//      data = await conn.recv(data);
//    } catch (err) {
//      if (! (err instanceof TLSCloseNotify) { throw err }
//      do_something_to_cleanly_close_data_connection();
//    }
//
// The `ServerConnection` API operates similarly; the distinction is mainly
// in which side is expected to send vs receieve during the protocol handshake.










class tlsconnection_Connection {
  constructor(psk, pskId, sendCallback) {
    this.psk = assertIsBytes(psk);
    this.pskId = assertIsBytes(pskId);
    this.connected = new Promise((resolve, reject) => {
      this._onConnectionSuccess = resolve;
      this._onConnectionFailure = reject;
    });
    this._state = new UNINITIALIZED(this);
    this._handshakeRecvBuffer = null;
    this._hasSeenChangeCipherSpec = false;
    this._recordlayer = new recordlayer_RecordLayer(sendCallback);
    this._keyschedule = new keyschedule_KeySchedule();
    this._lastPromise = Promise.resolve();
  }

  // Subclasses will override this with some async initialization logic.
  static async create(psk, pskId, sendCallback) {
    return new this(psk, pskId, sendCallback);
  }

  // These are the three public API methods that consumers can use
  // to send and receive data encrypted with TLS1.3.

  async send(data) {
    assertIsBytes(data);
    await this.connected;
    await this._synchronized(async () => {
      await this._state.sendApplicationData(data);
    });
  }

  async recv(data) {
    assertIsBytes(data);
    return await this._synchronized(async () => {
      // Decrypt the data using the record layer.
      // We expect to receive precisely one record at a time.
      const [type, bytes] = await this._recordlayer.recv(data);
      // Dispatch based on the type of the record.
      switch (type) {
        case RECORD_TYPE.CHANGE_CIPHER_SPEC:
          await this._state.recvChangeCipherSpec(bytes);
          return null;
        case RECORD_TYPE.ALERT:
          await this._state.recvAlertMessage(TLSAlert.fromBytes(bytes));
          return null;
        case RECORD_TYPE.APPLICATION_DATA:
          return await this._state.recvApplicationData(bytes);
        case RECORD_TYPE.HANDSHAKE:
          // Multiple handshake messages may be coalesced into a single record.
          // Store the in-progress record buffer on `this` so that we can guard
          // against handshake messages that span a change in keys.
          this._handshakeRecvBuffer = new utils_BufferReader(bytes);
          if (! this._handshakeRecvBuffer.hasMoreBytes()) {
            throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
          }
          do {
            // Each handshake messages has a type and length prefix, per
            // https://tools.ietf.org/html/rfc8446#appendix-B.3
            this._handshakeRecvBuffer.incr(1);
            const mlength = this._handshakeRecvBuffer.readUint24();
            this._handshakeRecvBuffer.incr(-4);
            const messageBytes = this._handshakeRecvBuffer.readBytes(mlength + 4);
            this._keyschedule.addToTranscript(messageBytes);
            await this._state.recvHandshakeMessage(messages_HandshakeMessage.fromBytes(messageBytes));
          } while (this._handshakeRecvBuffer.hasMoreBytes());
          this._handshakeRecvBuffer = null;
          return null;
        default:
          throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
      }
    });
  }

  async close() {
    await this._synchronized(async () => {
      await this._state.close();
    });
  }

  // Ensure that async functions execute one at a time,
  // by waiting for the previous call to `_synchronized()` to complete
  // before starting a new one.  This helps ensure that we complete
  // one state-machine transition before starting to do the next.
  // It's also a convenient place to catch and alert on errors.

  _synchronized(cb) {
    const nextPromise = this._lastPromise.then(() => {
      return cb();
    }).catch(async err => {
      if (err instanceof TLSCloseNotify) {
        throw err;
      }
      await this._state.handleErrorAndRethrow(err);
    });
    // We don't want to hold on to the return value or error,
    // just synchronize on the fact that it completed.
    this._lastPromise = nextPromise.then(noop, noop);
    return nextPromise;
  }

  // This drives internal transition of the state-machine,
  // ensuring that the new state is properly initialized.

  async _transition(State, ...args) {
    this._state = new State(this);
    await this._state.initialize(...args);
    await this._recordlayer.flush();
  }

  // These are helpers to allow the State to manipulate the recordlayer
  // and send out various types of data.

  async _sendApplicationData(bytes) {
    await this._recordlayer.send(RECORD_TYPE.APPLICATION_DATA, bytes);
    await this._recordlayer.flush();
  }

  async _sendHandshakeMessage(msg) {
    await this._sendHandshakeMessageBytes(msg.toBytes());
  }

  async _sendHandshakeMessageBytes(bytes) {
    this._keyschedule.addToTranscript(bytes);
    await this._recordlayer.send(RECORD_TYPE.HANDSHAKE, bytes);
    // Don't flush after each handshake message, since we can probably
    // coalesce multiple messages into a single record.
  }

  async _sendAlertMessage(err) {
    await this._recordlayer.send(RECORD_TYPE.ALERT, err.toBytes());
    await this._recordlayer.flush();
  }

  async _sendChangeCipherSpec() {
    await this._recordlayer.send(RECORD_TYPE.CHANGE_CIPHER_SPEC, new Uint8Array([0x01]));
    await this._recordlayer.flush();
  }

  async _setSendKey(key) {
    return await this._recordlayer.setSendKey(key);
  }

  async _setRecvKey(key) {
    // Handshake messages that change keys must be on a record boundary.
    if (this._handshakeRecvBuffer && this._handshakeRecvBuffer.hasMoreBytes()) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    return await this._recordlayer.setRecvKey(key);
  }

  _setConnectionSuccess() {
    if (this._onConnectionSuccess !== null) {
      this._onConnectionSuccess();
      this._onConnectionSuccess = null;
      this._onConnectionFailure = null;
    }
  }

  _setConnectionFailure(err) {
    if (this._onConnectionFailure !== null) {
      this._onConnectionFailure(err);
      this._onConnectionSuccess = null;
      this._onConnectionFailure = null;
    }
  }

  _closeForSend(alert) {
    this._recordlayer.setSendError(alert);
  }

  _closeForRecv(alert) {
    this._recordlayer.setRecvError(alert);
  }
}

class tlsconnection_ClientConnection extends tlsconnection_Connection {
  static async create(psk, pskId, sendCallback) {
    const instance = await super.create(psk, pskId, sendCallback);
    await instance._transition(states_CLIENT_START);
    return instance;
  }
}

class tlsconnection_ServerConnection extends tlsconnection_Connection {
  static async create(psk, pskId, sendCallback) {
    const instance = await super.create(psk, pskId, sendCallback);
    await instance._transition(states_SERVER_START);
    return instance;
  }
}

// CONCATENATED MODULE: ./node_modules/event-target-shim/dist/event-target-shim.mjs
/**
 * @author Toru Nagashima <https://github.com/mysticatea>
 * @copyright 2015 Toru Nagashima. All rights reserved.
 * See LICENSE file in root directory for full license.
 */
/**
 * @typedef {object} PrivateData
 * @property {EventTarget} eventTarget The event target.
 * @property {{type:string}} event The original event object.
 * @property {number} eventPhase The current event phase.
 * @property {EventTarget|null} currentTarget The current event target.
 * @property {boolean} canceled The flag to prevent default.
 * @property {boolean} stopped The flag to stop propagation.
 * @property {boolean} immediateStopped The flag to stop propagation immediately.
 * @property {Function|null} passiveListener The listener if the current listener is passive. Otherwise this is null.
 * @property {number} timeStamp The unix time.
 * @private
 */

/**
 * Private data for event wrappers.
 * @type {WeakMap<Event, PrivateData>}
 * @private
 */
const privateData = new WeakMap();

/**
 * Cache for wrapper classes.
 * @type {WeakMap<Object, Function>}
 * @private
 */
const wrappers = new WeakMap();

/**
 * Get private data.
 * @param {Event} event The event object to get private data.
 * @returns {PrivateData} The private data of the event.
 * @private
 */
function pd(event) {
    const retv = privateData.get(event);
    console.assert(
        retv != null,
        "'this' is expected an Event object, but got",
        event
    );
    return retv
}

/**
 * https://dom.spec.whatwg.org/#set-the-canceled-flag
 * @param data {PrivateData} private data.
 */
function setCancelFlag(data) {
    if (data.passiveListener != null) {
        if (
            typeof console !== "undefined" &&
            typeof console.error === "function"
        ) {
            console.error(
                "Unable to preventDefault inside passive event listener invocation.",
                data.passiveListener
            );
        }
        return
    }
    if (!data.event.cancelable) {
        return
    }

    data.canceled = true;
    if (typeof data.event.preventDefault === "function") {
        data.event.preventDefault();
    }
}

/**
 * @see https://dom.spec.whatwg.org/#interface-event
 * @private
 */
/**
 * The event wrapper.
 * @constructor
 * @param {EventTarget} eventTarget The event target of this dispatching.
 * @param {Event|{type:string}} event The original event to wrap.
 */
function Event(eventTarget, event) {
    privateData.set(this, {
        eventTarget,
        event,
        eventPhase: 2,
        currentTarget: eventTarget,
        canceled: false,
        stopped: false,
        immediateStopped: false,
        passiveListener: null,
        timeStamp: event.timeStamp || Date.now(),
    });

    // https://heycam.github.io/webidl/#Unforgeable
    Object.defineProperty(this, "isTrusted", { value: false, enumerable: true });

    // Define accessors
    const keys = Object.keys(event);
    for (let i = 0; i < keys.length; ++i) {
        const key = keys[i];
        if (!(key in this)) {
            Object.defineProperty(this, key, defineRedirectDescriptor(key));
        }
    }
}

// Should be enumerable, but class methods are not enumerable.
Event.prototype = {
    /**
     * The type of this event.
     * @type {string}
     */
    get type() {
        return pd(this).event.type
    },

    /**
     * The target of this event.
     * @type {EventTarget}
     */
    get target() {
        return pd(this).eventTarget
    },

    /**
     * The target of this event.
     * @type {EventTarget}
     */
    get currentTarget() {
        return pd(this).currentTarget
    },

    /**
     * @returns {EventTarget[]} The composed path of this event.
     */
    composedPath() {
        const currentTarget = pd(this).currentTarget;
        if (currentTarget == null) {
            return []
        }
        return [currentTarget]
    },

    /**
     * Constant of NONE.
     * @type {number}
     */
    get NONE() {
        return 0
    },

    /**
     * Constant of CAPTURING_PHASE.
     * @type {number}
     */
    get CAPTURING_PHASE() {
        return 1
    },

    /**
     * Constant of AT_TARGET.
     * @type {number}
     */
    get AT_TARGET() {
        return 2
    },

    /**
     * Constant of BUBBLING_PHASE.
     * @type {number}
     */
    get BUBBLING_PHASE() {
        return 3
    },

    /**
     * The target of this event.
     * @type {number}
     */
    get eventPhase() {
        return pd(this).eventPhase
    },

    /**
     * Stop event bubbling.
     * @returns {void}
     */
    stopPropagation() {
        const data = pd(this);

        data.stopped = true;
        if (typeof data.event.stopPropagation === "function") {
            data.event.stopPropagation();
        }
    },

    /**
     * Stop event bubbling.
     * @returns {void}
     */
    stopImmediatePropagation() {
        const data = pd(this);

        data.stopped = true;
        data.immediateStopped = true;
        if (typeof data.event.stopImmediatePropagation === "function") {
            data.event.stopImmediatePropagation();
        }
    },

    /**
     * The flag to be bubbling.
     * @type {boolean}
     */
    get bubbles() {
        return Boolean(pd(this).event.bubbles)
    },

    /**
     * The flag to be cancelable.
     * @type {boolean}
     */
    get cancelable() {
        return Boolean(pd(this).event.cancelable)
    },

    /**
     * Cancel this event.
     * @returns {void}
     */
    preventDefault() {
        setCancelFlag(pd(this));
    },

    /**
     * The flag to indicate cancellation state.
     * @type {boolean}
     */
    get defaultPrevented() {
        return pd(this).canceled
    },

    /**
     * The flag to be composed.
     * @type {boolean}
     */
    get composed() {
        return Boolean(pd(this).event.composed)
    },

    /**
     * The unix time of this event.
     * @type {number}
     */
    get timeStamp() {
        return pd(this).timeStamp
    },

    /**
     * The target of this event.
     * @type {EventTarget}
     * @deprecated
     */
    get srcElement() {
        return pd(this).eventTarget
    },

    /**
     * The flag to stop event bubbling.
     * @type {boolean}
     * @deprecated
     */
    get cancelBubble() {
        return pd(this).stopped
    },
    set cancelBubble(value) {
        if (!value) {
            return
        }
        const data = pd(this);

        data.stopped = true;
        if (typeof data.event.cancelBubble === "boolean") {
            data.event.cancelBubble = true;
        }
    },

    /**
     * The flag to indicate cancellation state.
     * @type {boolean}
     * @deprecated
     */
    get returnValue() {
        return !pd(this).canceled
    },
    set returnValue(value) {
        if (!value) {
            setCancelFlag(pd(this));
        }
    },

    /**
     * Initialize this event object. But do nothing under event dispatching.
     * @param {string} type The event type.
     * @param {boolean} [bubbles=false] The flag to be possible to bubble up.
     * @param {boolean} [cancelable=false] The flag to be possible to cancel.
     * @deprecated
     */
    initEvent() {
        // Do nothing.
    },
};

// `constructor` is not enumerable.
Object.defineProperty(Event.prototype, "constructor", {
    value: Event,
    configurable: true,
    writable: true,
});

// Ensure `event instanceof window.Event` is `true`.
if (typeof window !== "undefined" && typeof window.Event !== "undefined") {
    Object.setPrototypeOf(Event.prototype, window.Event.prototype);

    // Make association for wrappers.
    wrappers.set(window.Event.prototype, Event);
}

/**
 * Get the property descriptor to redirect a given property.
 * @param {string} key Property name to define property descriptor.
 * @returns {PropertyDescriptor} The property descriptor to redirect the property.
 * @private
 */
function defineRedirectDescriptor(key) {
    return {
        get() {
            return pd(this).event[key]
        },
        set(value) {
            pd(this).event[key] = value;
        },
        configurable: true,
        enumerable: true,
    }
}

/**
 * Get the property descriptor to call a given method property.
 * @param {string} key Property name to define property descriptor.
 * @returns {PropertyDescriptor} The property descriptor to call the method property.
 * @private
 */
function defineCallDescriptor(key) {
    return {
        value() {
            const event = pd(this).event;
            return event[key].apply(event, arguments)
        },
        configurable: true,
        enumerable: true,
    }
}

/**
 * Define new wrapper class.
 * @param {Function} BaseEvent The base wrapper class.
 * @param {Object} proto The prototype of the original event.
 * @returns {Function} The defined wrapper class.
 * @private
 */
function defineWrapper(BaseEvent, proto) {
    const keys = Object.keys(proto);
    if (keys.length === 0) {
        return BaseEvent
    }

    /** CustomEvent */
    function CustomEvent(eventTarget, event) {
        BaseEvent.call(this, eventTarget, event);
    }

    CustomEvent.prototype = Object.create(BaseEvent.prototype, {
        constructor: { value: CustomEvent, configurable: true, writable: true },
    });

    // Define accessors.
    for (let i = 0; i < keys.length; ++i) {
        const key = keys[i];
        if (!(key in BaseEvent.prototype)) {
            const descriptor = Object.getOwnPropertyDescriptor(proto, key);
            const isFunc = typeof descriptor.value === "function";
            Object.defineProperty(
                CustomEvent.prototype,
                key,
                isFunc
                    ? defineCallDescriptor(key)
                    : defineRedirectDescriptor(key)
            );
        }
    }

    return CustomEvent
}

/**
 * Get the wrapper class of a given prototype.
 * @param {Object} proto The prototype of the original event to get its wrapper.
 * @returns {Function} The wrapper class.
 * @private
 */
function getWrapper(proto) {
    if (proto == null || proto === Object.prototype) {
        return Event
    }

    let wrapper = wrappers.get(proto);
    if (wrapper == null) {
        wrapper = defineWrapper(getWrapper(Object.getPrototypeOf(proto)), proto);
        wrappers.set(proto, wrapper);
    }
    return wrapper
}

/**
 * Wrap a given event to management a dispatching.
 * @param {EventTarget} eventTarget The event target of this dispatching.
 * @param {Object} event The event to wrap.
 * @returns {Event} The wrapper instance.
 * @private
 */
function wrapEvent(eventTarget, event) {
    const Wrapper = getWrapper(Object.getPrototypeOf(event));
    return new Wrapper(eventTarget, event)
}

/**
 * Get the immediateStopped flag of a given event.
 * @param {Event} event The event to get.
 * @returns {boolean} The flag to stop propagation immediately.
 * @private
 */
function isStopped(event) {
    return pd(event).immediateStopped
}

/**
 * Set the current event phase of a given event.
 * @param {Event} event The event to set current target.
 * @param {number} eventPhase New event phase.
 * @returns {void}
 * @private
 */
function setEventPhase(event, eventPhase) {
    pd(event).eventPhase = eventPhase;
}

/**
 * Set the current target of a given event.
 * @param {Event} event The event to set current target.
 * @param {EventTarget|null} currentTarget New current target.
 * @returns {void}
 * @private
 */
function setCurrentTarget(event, currentTarget) {
    pd(event).currentTarget = currentTarget;
}

/**
 * Set a passive listener of a given event.
 * @param {Event} event The event to set current target.
 * @param {Function|null} passiveListener New passive listener.
 * @returns {void}
 * @private
 */
function setPassiveListener(event, passiveListener) {
    pd(event).passiveListener = passiveListener;
}

/**
 * @typedef {object} ListenerNode
 * @property {Function} listener
 * @property {1|2|3} listenerType
 * @property {boolean} passive
 * @property {boolean} once
 * @property {ListenerNode|null} next
 * @private
 */

/**
 * @type {WeakMap<object, Map<string, ListenerNode>>}
 * @private
 */
const listenersMap = new WeakMap();

// Listener types
const CAPTURE = 1;
const BUBBLE = 2;
const ATTRIBUTE = 3;

/**
 * Check whether a given value is an object or not.
 * @param {any} x The value to check.
 * @returns {boolean} `true` if the value is an object.
 */
function isObject(x) {
    return x !== null && typeof x === "object" //eslint-disable-line no-restricted-syntax
}

/**
 * Get listeners.
 * @param {EventTarget} eventTarget The event target to get.
 * @returns {Map<string, ListenerNode>} The listeners.
 * @private
 */
function getListeners(eventTarget) {
    const listeners = listenersMap.get(eventTarget);
    if (listeners == null) {
        throw new TypeError(
            "'this' is expected an EventTarget object, but got another value."
        )
    }
    return listeners
}

/**
 * Get the property descriptor for the event attribute of a given event.
 * @param {string} eventName The event name to get property descriptor.
 * @returns {PropertyDescriptor} The property descriptor.
 * @private
 */
function defineEventAttributeDescriptor(eventName) {
    return {
        get() {
            const listeners = getListeners(this);
            let node = listeners.get(eventName);
            while (node != null) {
                if (node.listenerType === ATTRIBUTE) {
                    return node.listener
                }
                node = node.next;
            }
            return null
        },

        set(listener) {
            if (typeof listener !== "function" && !isObject(listener)) {
                listener = null; // eslint-disable-line no-param-reassign
            }
            const listeners = getListeners(this);

            // Traverse to the tail while removing old value.
            let prev = null;
            let node = listeners.get(eventName);
            while (node != null) {
                if (node.listenerType === ATTRIBUTE) {
                    // Remove old value.
                    if (prev !== null) {
                        prev.next = node.next;
                    } else if (node.next !== null) {
                        listeners.set(eventName, node.next);
                    } else {
                        listeners.delete(eventName);
                    }
                } else {
                    prev = node;
                }

                node = node.next;
            }

            // Add new value.
            if (listener !== null) {
                const newNode = {
                    listener,
                    listenerType: ATTRIBUTE,
                    passive: false,
                    once: false,
                    next: null,
                };
                if (prev === null) {
                    listeners.set(eventName, newNode);
                } else {
                    prev.next = newNode;
                }
            }
        },
        configurable: true,
        enumerable: true,
    }
}

/**
 * Define an event attribute (e.g. `eventTarget.onclick`).
 * @param {Object} eventTargetPrototype The event target prototype to define an event attrbite.
 * @param {string} eventName The event name to define.
 * @returns {void}
 */
function defineEventAttribute(eventTargetPrototype, eventName) {
    Object.defineProperty(
        eventTargetPrototype,
        `on${eventName}`,
        defineEventAttributeDescriptor(eventName)
    );
}

/**
 * Define a custom EventTarget with event attributes.
 * @param {string[]} eventNames Event names for event attributes.
 * @returns {EventTarget} The custom EventTarget.
 * @private
 */
function defineCustomEventTarget(eventNames) {
    /** CustomEventTarget */
    function CustomEventTarget() {
        EventTarget.call(this);
    }

    CustomEventTarget.prototype = Object.create(EventTarget.prototype, {
        constructor: {
            value: CustomEventTarget,
            configurable: true,
            writable: true,
        },
    });

    for (let i = 0; i < eventNames.length; ++i) {
        defineEventAttribute(CustomEventTarget.prototype, eventNames[i]);
    }

    return CustomEventTarget
}

/**
 * EventTarget.
 *
 * - This is constructor if no arguments.
 * - This is a function which returns a CustomEventTarget constructor if there are arguments.
 *
 * For example:
 *
 *     class A extends EventTarget {}
 *     class B extends EventTarget("message") {}
 *     class C extends EventTarget("message", "error") {}
 *     class D extends EventTarget(["message", "error"]) {}
 */
function EventTarget() {
    /*eslint-disable consistent-return */
    if (this instanceof EventTarget) {
        listenersMap.set(this, new Map());
        return
    }
    if (arguments.length === 1 && Array.isArray(arguments[0])) {
        return defineCustomEventTarget(arguments[0])
    }
    if (arguments.length > 0) {
        const types = new Array(arguments.length);
        for (let i = 0; i < arguments.length; ++i) {
            types[i] = arguments[i];
        }
        return defineCustomEventTarget(types)
    }
    throw new TypeError("Cannot call a class as a function")
    /*eslint-enable consistent-return */
}

// Should be enumerable, but class methods are not enumerable.
EventTarget.prototype = {
    /**
     * Add a given listener to this event target.
     * @param {string} eventName The event name to add.
     * @param {Function} listener The listener to add.
     * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener.
     * @returns {void}
     */
    addEventListener(eventName, listener, options) {
        if (listener == null) {
            return
        }
        if (typeof listener !== "function" && !isObject(listener)) {
            throw new TypeError("'listener' should be a function or an object.")
        }

        const listeners = getListeners(this);
        const optionsIsObj = isObject(options);
        const capture = optionsIsObj
            ? Boolean(options.capture)
            : Boolean(options);
        const listenerType = capture ? CAPTURE : BUBBLE;
        const newNode = {
            listener,
            listenerType,
            passive: optionsIsObj && Boolean(options.passive),
            once: optionsIsObj && Boolean(options.once),
            next: null,
        };

        // Set it as the first node if the first node is null.
        let node = listeners.get(eventName);
        if (node === undefined) {
            listeners.set(eventName, newNode);
            return
        }

        // Traverse to the tail while checking duplication..
        let prev = null;
        while (node != null) {
            if (
                node.listener === listener &&
                node.listenerType === listenerType
            ) {
                // Should ignore duplication.
                return
            }
            prev = node;
            node = node.next;
        }

        // Add it.
        prev.next = newNode;
    },

    /**
     * Remove a given listener from this event target.
     * @param {string} eventName The event name to remove.
     * @param {Function} listener The listener to remove.
     * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener.
     * @returns {void}
     */
    removeEventListener(eventName, listener, options) {
        if (listener == null) {
            return
        }

        const listeners = getListeners(this);
        const capture = isObject(options)
            ? Boolean(options.capture)
            : Boolean(options);
        const listenerType = capture ? CAPTURE : BUBBLE;

        let prev = null;
        let node = listeners.get(eventName);
        while (node != null) {
            if (
                node.listener === listener &&
                node.listenerType === listenerType
            ) {
                if (prev !== null) {
                    prev.next = node.next;
                } else if (node.next !== null) {
                    listeners.set(eventName, node.next);
                } else {
                    listeners.delete(eventName);
                }
                return
            }

            prev = node;
            node = node.next;
        }
    },

    /**
     * Dispatch a given event.
     * @param {Event|{type:string}} event The event to dispatch.
     * @returns {boolean} `false` if canceled.
     */
    dispatchEvent(event) {
        if (event == null || typeof event.type !== "string") {
            throw new TypeError('"event.type" should be a string.')
        }

        // If listeners aren't registered, terminate.
        const listeners = getListeners(this);
        const eventName = event.type;
        let node = listeners.get(eventName);
        if (node == null) {
            return true
        }

        // Since we cannot rewrite several properties, so wrap object.
        const wrappedEvent = wrapEvent(this, event);

        // This doesn't process capturing phase and bubbling phase.
        // This isn't participating in a tree.
        let prev = null;
        while (node != null) {
            // Remove this listener if it's once
            if (node.once) {
                if (prev !== null) {
                    prev.next = node.next;
                } else if (node.next !== null) {
                    listeners.set(eventName, node.next);
                } else {
                    listeners.delete(eventName);
                }
            } else {
                prev = node;
            }

            // Call this listener
            setPassiveListener(
                wrappedEvent,
                node.passive ? node.listener : null
            );
            if (typeof node.listener === "function") {
                try {
                    node.listener.call(this, wrappedEvent);
                } catch (err) {
                    if (
                        typeof console !== "undefined" &&
                        typeof console.error === "function"
                    ) {
                        console.error(err);
                    }
                }
            } else if (
                node.listenerType !== ATTRIBUTE &&
                typeof node.listener.handleEvent === "function"
            ) {
                node.listener.handleEvent(wrappedEvent);
            }

            // Break if `event.stopImmediatePropagation` was called.
            if (isStopped(wrappedEvent)) {
                break
            }

            node = node.next;
        }
        setPassiveListener(wrappedEvent, null);
        setEventPhase(wrappedEvent, 0);
        setCurrentTarget(wrappedEvent, null);

        return !wrappedEvent.defaultPrevented
    },
};

// `constructor` is not enumerable.
Object.defineProperty(EventTarget.prototype, "constructor", {
    value: EventTarget,
    configurable: true,
    writable: true,
});

// Ensure `eventTarget instanceof window.EventTarget` is `true`.
if (
    typeof window !== "undefined" &&
    typeof window.EventTarget !== "undefined"
) {
    Object.setPrototypeOf(EventTarget.prototype, window.EventTarget.prototype);
}

/* harmony default export */ var event_target_shim = (EventTarget);


// CONCATENATED MODULE: ./src/index.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// A wrapper that combines a WebSocket to the channelserver
// with some client-side encryption for securing the channel.
//
// This code is responsible for the event handling and the consumer API.
// All the details of encrypting the messages are delegated to`./tlsconnection.js`.







const CLOSE_FLUSH_BUFFER_INTERVAL_MS = 200;
const CLOSE_FLUSH_BUFFER_MAX_TRIES = 5;

class src_PairingChannel extends EventTarget {
  constructor(channelId, channelKey, socket, connection) {
    super();
    this._channelId = channelId;
    this._channelKey = channelKey;
    this._socket = socket;
    this._connection = connection;
    this._selfClosed = false;
    this._peerClosed = false;
    this._setupListeners();
  }

  /**
   * Create a new pairing channel.
   *
   * This will open a channel on the channelserver, and generate a random client-side
   * encryption key. When the promise resolves, `this.channelId` and `this.channelKey`
   * can be transferred to another client to allow it to securely connect to the channel.
   *
   * @returns Promise<PairingChannel>
   */
  static create(channelServerURI) {
    const wsURI = new URL('/v1/ws/', channelServerURI).href;
    const channelKey = crypto.getRandomValues(new Uint8Array(32));
    // The one who creates the channel plays the role of 'server' in the underlying TLS exchange.
    return this._makePairingChannel(wsURI, tlsconnection_ServerConnection, channelKey);
  }

  /**
   * Connect to an existing pairing channel.
   *
   * This will connect to a channel on the channelserver previously established by
   * another client calling `create`. The `channelId` and `channelKey` must have been
   * obtained via some out-of-band mechanism (such as by scanning from a QR code).
   *
   * @returns Promise<PairingChannel>
   */
  static connect(channelServerURI, channelId, channelKey) {
    const wsURI = new URL(`/v1/ws/${channelId}`, channelServerURI).href;
    // The one who connects to an existing channel plays the role of 'client'
    // in the underlying TLS exchange.
    return this._makePairingChannel(wsURI, tlsconnection_ClientConnection, channelKey);
  }

  static _makePairingChannel(wsUri, ConnectionClass, psk) {
    const socket = new WebSocket(wsUri);
    return new Promise((resolve, reject) => {
      // eslint-disable-next-line prefer-const
      let stopListening;
      const onConnectionError = async () => {
        stopListening();
        reject(new Error('Error while creating the pairing channel'));
      };
      const onFirstMessage = async event => {
        stopListening();
        try {
          // The channelserver echos back the channel id, and we use it as an
          // additional input to the TLS handshake via the "psk id" field.
          const {channelid: channelId} = JSON.parse(event.data);
          const pskId = utf8ToBytes(channelId);
          const connection = await ConnectionClass.create(psk, pskId, data => {
            // Send data by forwarding it via the channelserver websocket.
            // The TLS connection gives us `data` as raw bytes, but channelserver
            // expects b64urlsafe strings, because it wraps them in a JSON object envelope.
            socket.send(bytesToBase64url(data));
          });
          const instance = new this(channelId, psk, socket, connection);
          resolve(instance);
        } catch (err) {
          reject(err);
        }
      };
      stopListening = () => {
        socket.removeEventListener('close', onConnectionError);
        socket.removeEventListener('error', onConnectionError);
        socket.removeEventListener('message', onFirstMessage);
      };
      socket.addEventListener('close', onConnectionError);
      socket.addEventListener('error', onConnectionError);
      socket.addEventListener('message', onFirstMessage);
    });
  }

  _setupListeners() {
    this._socket.addEventListener('message', async event => {
      try {
        // When we receive data from the channelserver, pump it through the TLS connection
        // to decrypt it, then echo it back out to consumers as an event.
        const channelServerEnvelope = JSON.parse(event.data);
        const payload = await this._connection.recv(base64urlToBytes(channelServerEnvelope.message));
        if (payload !== null) {
          const data = JSON.parse(bytesToUtf8(payload));
          this.dispatchEvent(new CustomEvent('message', {
            detail: {
              data,
              sender: channelServerEnvelope.sender,
            },
          }));
        }
      } catch (error) {
        let event;
        // The underlying TLS connection will signal a clean shutdown of the channel
        // by throwing a special error, because it doesn't really have a better
        // signally mechanism available.
        if (error instanceof TLSCloseNotify) {
          this._peerClosed = true;
          if (this._selfClosed) {
            this._shutdown();
          }
          event = new CustomEvent('close');
        } else {
          event = new CustomEvent('error', {
            detail: {
              error,
            }
          });
        }
        this.dispatchEvent(event);
      }
    });
    // Relay the WebSocket events.
    this._socket.addEventListener('error', () => {
      this._shutdown();
      // The dispatched event that we receive has no useful information.
      this.dispatchEvent(new CustomEvent('error', {
        detail: {
          error: new Error('WebSocket error.'),
        },
      }));
    });
    // In TLS, the peer has to explicitly send a close notification,
    // which we dispatch above.  Unexpected socket close is an error.
    this._socket.addEventListener('close', () => {
      this._shutdown();
      if (! this._peerClosed) {
        this.dispatchEvent(new CustomEvent('error', {
          detail: {
            error: new Error('WebSocket unexpectedly closed'),
          }
        }));
      }
    });
  }

  /**
   * @param {Object} data
   */
  async send(data) {
    const payload = utf8ToBytes(JSON.stringify(data));
    await this._connection.send(payload);
  }

  async close() {
    this._selfClosed = true;
    await this._connection.close();
    try {
      // Ensure all queued bytes have been sent before closing the connection.
      let tries = 0;
      while (this._socket.bufferedAmount > 0) {
        if (++tries > CLOSE_FLUSH_BUFFER_MAX_TRIES) {
          throw new Error('Could not flush the outgoing buffer in time.');
        }
        await new Promise(res => setTimeout(res, CLOSE_FLUSH_BUFFER_INTERVAL_MS));
      }
    } finally {
      // If the peer hasn't closed, we might still receive some data.
      if (this._peerClosed) {
        this._shutdown();
      }
    }
  }

  _shutdown() {
    if (this._socket) {
      this._socket.close();
      this._socket = null;
      this._connection = null;
    }
  }

  get closed() {
    return (! this._socket) || (this._socket.readyState === 3);
  }

  get channelId() {
    return this._channelId;
  }

  get channelKey() {
    return this._channelKey;
  }
}

// Re-export helpful utilities for calling code to use.


// For running tests using the built bundle,
// expose a bunch of implementation details.







const _internals = {
  arrayToBytes: arrayToBytes,
  BufferReader: utils_BufferReader,
  BufferWriter: utils_BufferWriter,
  bytesAreEqual: bytesAreEqual,
  bytesToHex: bytesToHex,
  bytesToUtf8: bytesToUtf8,
  ClientConnection: tlsconnection_ClientConnection,
  Connection: tlsconnection_Connection,
  DecryptionState: recordlayer_DecryptionState,
  EncryptedExtensions: EncryptedExtensions,
  EncryptionState: recordlayer_EncryptionState,
  Finished: messages_Finished,
  HASH_LENGTH: HASH_LENGTH,
  hexToBytes: hexToBytes,
  hkdfExpand: hkdfExpand,
  KeySchedule: keyschedule_KeySchedule,
  NewSessionTicket: messages_NewSessionTicket,
  RecordLayer: recordlayer_RecordLayer,
  ServerConnection: tlsconnection_ServerConnection,
  utf8ToBytes: utf8ToBytes,
  zeros: zeros,
};


/***/ })
/******/ ])["PairingChannel"];

[Seitenstruktur0.68Druckenetwas mehr zur Ethik2026-04-27]