Index: node_modules/ip-address/src/ipv6.ts
===================================================================
--- node_modules/ip-address/src/ipv6.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
+++ node_modules/ip-address/src/ipv6.ts	(revision 81bc7da776d8cda42939ef4ab350c88d13b4941f)
@@ -0,0 +1,1212 @@
+/* eslint-disable prefer-destructuring */
+/* eslint-disable no-param-reassign */
+
+import * as common from './common';
+import * as constants4 from './v4/constants';
+import * as constants6 from './v6/constants';
+import * as helpers from './v6/helpers';
+import { Address4 } from './ipv4';
+import {
+  ADDRESS_BOUNDARY,
+  possibleElisions,
+  simpleRegularExpression,
+} from './v6/regular-expressions';
+import { AddressError } from './address-error';
+import { testBit } from './common';
+
+function assert(condition: any): asserts condition {
+  if (!condition) {
+    throw new Error('Assertion failed.');
+  }
+}
+
+function addCommas(number: string): string {
+  const r = /(\d+)(\d{3})/;
+
+  while (r.test(number)) {
+    number = number.replace(r, '$1,$2');
+  }
+
+  return number;
+}
+
+function spanLeadingZeroes4(n: string): string {
+  n = n.replace(/^(0{1,})([1-9]+)$/, '<span class="parse-error">$1</span>$2');
+  n = n.replace(/^(0{1,})(0)$/, '<span class="parse-error">$1</span>$2');
+
+  return n;
+}
+
+/*
+ * A helper function to compact an array
+ */
+function compact(address: string[], slice: number[]) {
+  const s1 = [];
+  const s2 = [];
+  let i;
+
+  for (i = 0; i < address.length; i++) {
+    if (i < slice[0]) {
+      s1.push(address[i]);
+    } else if (i > slice[1]) {
+      s2.push(address[i]);
+    }
+  }
+
+  return s1.concat(['compact']).concat(s2);
+}
+
+function paddedHex(octet: string): string {
+  return parseInt(octet, 16).toString(16).padStart(4, '0');
+}
+
+function unsignByte(b: number) {
+  // eslint-disable-next-line no-bitwise
+  return b & 0xff;
+}
+
+interface SixToFourProperties {
+  prefix: string;
+  gateway: string;
+}
+
+interface TeredoProperties {
+  prefix: string;
+  server4: string;
+  client4: string;
+  flags: string;
+  coneNat: boolean;
+  microsoft: {
+    reserved: boolean;
+    universalLocal: boolean;
+    groupIndividual: boolean;
+    nonce: string;
+  };
+  udpPort: string;
+}
+
+/**
+ * Represents an IPv6 address
+ * @class Address6
+ * @param {string} address - An IPv6 address string
+ * @param {number} [groups=8] - How many octets to parse
+ * @example
+ * var address = new Address6('2001::/32');
+ */
+export class Address6 {
+  address4?: Address4;
+  address: string;
+  addressMinusSuffix: string = '';
+  elidedGroups?: number;
+  elisionBegin?: number;
+  elisionEnd?: number;
+  groups: number;
+  parsedAddress4?: string;
+  parsedAddress: string[];
+  parsedSubnet: string = '';
+  subnet: string = '/128';
+  subnetMask: number = 128;
+  v4: boolean = false;
+  zone: string = '';
+
+  constructor(address: string, optionalGroups?: number) {
+    if (optionalGroups === undefined) {
+      this.groups = constants6.GROUPS;
+    } else {
+      this.groups = optionalGroups;
+    }
+
+    this.address = address;
+
+    const subnet = constants6.RE_SUBNET_STRING.exec(address);
+
+    if (subnet) {
+      this.parsedSubnet = subnet[0].replace('/', '');
+      this.subnetMask = parseInt(this.parsedSubnet, 10);
+      this.subnet = `/${this.subnetMask}`;
+
+      if (
+        Number.isNaN(this.subnetMask) ||
+        this.subnetMask < 0 ||
+        this.subnetMask > constants6.BITS
+      ) {
+        throw new AddressError('Invalid subnet mask.');
+      }
+
+      address = address.replace(constants6.RE_SUBNET_STRING, '');
+    } else if (/\//.test(address)) {
+      throw new AddressError('Invalid subnet mask.');
+    }
+
+    const zone = constants6.RE_ZONE_STRING.exec(address);
+
+    if (zone) {
+      this.zone = zone[0];
+
+      address = address.replace(constants6.RE_ZONE_STRING, '');
+    }
+
+    this.addressMinusSuffix = address;
+
+    this.parsedAddress = this.parse(this.addressMinusSuffix);
+  }
+
+  static isValid(address: string): boolean {
+    try {
+      // eslint-disable-next-line no-new
+      new Address6(address);
+
+      return true;
+    } catch (e) {
+      return false;
+    }
+  }
+
+  /**
+   * Convert a BigInt to a v6 address object
+   * @memberof Address6
+   * @static
+   * @param {bigint} bigInt - a BigInt to convert
+   * @returns {Address6}
+   * @example
+   * var bigInt = BigInt('1000000000000');
+   * var address = Address6.fromBigInt(bigInt);
+   * address.correctForm(); // '::e8:d4a5:1000'
+   */
+  static fromBigInt(bigInt: bigint): Address6 {
+    const hex = bigInt.toString(16).padStart(32, '0');
+    const groups = [];
+    let i;
+
+    for (i = 0; i < constants6.GROUPS; i++) {
+      groups.push(hex.slice(i * 4, (i + 1) * 4));
+    }
+
+    return new Address6(groups.join(':'));
+  }
+
+  /**
+   * Convert a URL (with optional port number) to an address object
+   * @memberof Address6
+   * @static
+   * @param {string} url - a URL with optional port number
+   * @example
+   * var addressAndPort = Address6.fromURL('http://[ffff::]:8080/foo/');
+   * addressAndPort.address.correctForm(); // 'ffff::'
+   * addressAndPort.port; // 8080
+   */
+  static fromURL(url: string) {
+    let host: string;
+    let port: string | number | null = null;
+    let result: string[] | null;
+
+    // If we have brackets parse them and find a port
+    if (url.indexOf('[') !== -1 && url.indexOf(']:') !== -1) {
+      result = constants6.RE_URL_WITH_PORT.exec(url);
+
+      if (result === null) {
+        return {
+          error: 'failed to parse address with port',
+          address: null,
+          port: null,
+        };
+      }
+
+      host = result[1];
+      port = result[2];
+      // If there's a URL extract the address
+    } else if (url.indexOf('/') !== -1) {
+      // Remove the protocol prefix
+      url = url.replace(/^[a-z0-9]+:\/\//, '');
+
+      // Parse the address
+      result = constants6.RE_URL.exec(url);
+
+      if (result === null) {
+        return {
+          error: 'failed to parse address from URL',
+          address: null,
+          port: null,
+        };
+      }
+
+      host = result[1];
+      // Otherwise just assign the URL to the host and let the library parse it
+    } else {
+      host = url;
+    }
+
+    // If there's a port convert it to an integer
+    if (port) {
+      port = parseInt(port, 10);
+
+      // squelch out of range ports
+      if (port < 0 || port > 65536) {
+        port = null;
+      }
+    } else {
+      // Standardize `undefined` to `null`
+      port = null;
+    }
+
+    return {
+      address: new Address6(host),
+      port,
+    };
+  }
+
+  /**
+   * Create an IPv6-mapped address given an IPv4 address
+   * @memberof Address6
+   * @static
+   * @param {string} address - An IPv4 address string
+   * @returns {Address6}
+   * @example
+   * var address = Address6.fromAddress4('192.168.0.1');
+   * address.correctForm(); // '::ffff:c0a8:1'
+   * address.to4in6(); // '::ffff:192.168.0.1'
+   */
+  static fromAddress4(address: string): Address6 {
+    const address4 = new Address4(address);
+
+    const mask6 = constants6.BITS - (constants4.BITS - address4.subnetMask);
+
+    return new Address6(`::ffff:${address4.correctForm()}/${mask6}`);
+  }
+
+  /**
+   * Return an address from ip6.arpa form
+   * @memberof Address6
+   * @static
+   * @param {string} arpaFormAddress - an 'ip6.arpa' form address
+   * @returns {Adress6}
+   * @example
+   * var address = Address6.fromArpa(e.f.f.f.3.c.2.6.f.f.f.e.6.6.8.e.1.0.6.7.9.4.e.c.0.0.0.0.1.0.0.2.ip6.arpa.)
+   * address.correctForm(); // '2001:0:ce49:7601:e866:efff:62c3:fffe'
+   */
+  static fromArpa(arpaFormAddress: string): Address6 {
+    // remove ending ".ip6.arpa." or just "."
+    let address = arpaFormAddress.replace(/(\.ip6\.arpa)?\.$/, '');
+    const semicolonAmount = 7;
+
+    // correct ip6.arpa form with ending removed will be 63 characters
+    if (address.length !== 63) {
+      throw new AddressError("Invalid 'ip6.arpa' form.");
+    }
+
+    const parts = address.split('.').reverse();
+
+    for (let i = semicolonAmount; i > 0; i--) {
+      const insertIndex = i * 4;
+      parts.splice(insertIndex, 0, ':');
+    }
+
+    address = parts.join('');
+
+    return new Address6(address);
+  }
+
+  /**
+   * Return the Microsoft UNC transcription of the address
+   * @memberof Address6
+   * @instance
+   * @returns {String} the Microsoft UNC transcription of the address
+   */
+  microsoftTranscription(): string {
+    return `${this.correctForm().replace(/:/g, '-')}.ipv6-literal.net`;
+  }
+
+  /**
+   * Return the first n bits of the address, defaulting to the subnet mask
+   * @memberof Address6
+   * @instance
+   * @param {number} [mask=subnet] - the number of bits to mask
+   * @returns {String} the first n bits of the address as a string
+   */
+  mask(mask: number = this.subnetMask): string {
+    return this.getBitsBase2(0, mask);
+  }
+
+  /**
+   * Return the number of possible subnets of a given size in the address
+   * @memberof Address6
+   * @instance
+   * @param {number} [subnetSize=128] - the subnet size
+   * @returns {String}
+   */
+  // TODO: probably useful to have a numeric version of this too
+  possibleSubnets(subnetSize: number = 128): string {
+    const availableBits = constants6.BITS - this.subnetMask;
+    const subnetBits = Math.abs(subnetSize - constants6.BITS);
+    const subnetPowers = availableBits - subnetBits;
+
+    if (subnetPowers < 0) {
+      return '0';
+    }
+
+    return addCommas((BigInt('2') ** BigInt(subnetPowers)).toString(10));
+  }
+
+  /**
+   * Helper function getting start address.
+   * @memberof Address6
+   * @instance
+   * @returns {bigint}
+   */
+  _startAddress(): bigint {
+    return BigInt(`0b${this.mask() + '0'.repeat(constants6.BITS - this.subnetMask)}`);
+  }
+
+  /**
+   * The first address in the range given by this address' subnet
+   * Often referred to as the Network Address.
+   * @memberof Address6
+   * @instance
+   * @returns {Address6}
+   */
+  startAddress(): Address6 {
+    return Address6.fromBigInt(this._startAddress());
+  }
+
+  /**
+   * The first host address in the range given by this address's subnet ie
+   * the first address after the Network Address
+   * @memberof Address6
+   * @instance
+   * @returns {Address6}
+   */
+  startAddressExclusive(): Address6 {
+    const adjust = BigInt('1');
+    return Address6.fromBigInt(this._startAddress() + adjust);
+  }
+
+  /**
+   * Helper function getting end address.
+   * @memberof Address6
+   * @instance
+   * @returns {bigint}
+   */
+  _endAddress(): bigint {
+    return BigInt(`0b${this.mask() + '1'.repeat(constants6.BITS - this.subnetMask)}`);
+  }
+
+  /**
+   * The last address in the range given by this address' subnet
+   * Often referred to as the Broadcast
+   * @memberof Address6
+   * @instance
+   * @returns {Address6}
+   */
+  endAddress(): Address6 {
+    return Address6.fromBigInt(this._endAddress());
+  }
+
+  /**
+   * The last host address in the range given by this address's subnet ie
+   * the last address prior to the Broadcast Address
+   * @memberof Address6
+   * @instance
+   * @returns {Address6}
+   */
+  endAddressExclusive(): Address6 {
+    const adjust = BigInt('1');
+    return Address6.fromBigInt(this._endAddress() - adjust);
+  }
+
+  /**
+   * Return the scope of the address
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   */
+  getScope(): string {
+    let scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)];
+
+    if (this.getType() === 'Global unicast' && scope !== 'Link local') {
+      scope = 'Global';
+    }
+
+    return scope || 'Unknown';
+  }
+
+  /**
+   * Return the type of the address
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   */
+  getType(): string {
+    for (const subnet of Object.keys(constants6.TYPES)) {
+      if (this.isInSubnet(new Address6(subnet))) {
+        return constants6.TYPES[subnet] as string;
+      }
+    }
+
+    return 'Global unicast';
+  }
+
+  /**
+   * Return the bits in the given range as a BigInt
+   * @memberof Address6
+   * @instance
+   * @returns {bigint}
+   */
+  getBits(start: number, end: number): bigint {
+    return BigInt(`0b${this.getBitsBase2(start, end)}`);
+  }
+
+  /**
+   * Return the bits in the given range as a base-2 string
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   */
+  getBitsBase2(start: number, end: number): string {
+    return this.binaryZeroPad().slice(start, end);
+  }
+
+  /**
+   * Return the bits in the given range as a base-16 string
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   */
+  getBitsBase16(start: number, end: number): string {
+    const length = end - start;
+
+    if (length % 4 !== 0) {
+      throw new Error('Length of bits to retrieve must be divisible by four');
+    }
+
+    return this.getBits(start, end)
+      .toString(16)
+      .padStart(length / 4, '0');
+  }
+
+  /**
+   * Return the bits that are set past the subnet mask length
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   */
+  getBitsPastSubnet(): string {
+    return this.getBitsBase2(this.subnetMask, constants6.BITS);
+  }
+
+  /**
+   * Return the reversed ip6.arpa form of the address
+   * @memberof Address6
+   * @param {Object} options
+   * @param {boolean} options.omitSuffix - omit the "ip6.arpa" suffix
+   * @instance
+   * @returns {String}
+   */
+  reverseForm(options?: common.ReverseFormOptions): string {
+    if (!options) {
+      options = {};
+    }
+
+    const characters = Math.floor(this.subnetMask / 4);
+
+    const reversed = this.canonicalForm()
+      .replace(/:/g, '')
+      .split('')
+      .slice(0, characters)
+      .reverse()
+      .join('.');
+
+    if (characters > 0) {
+      if (options.omitSuffix) {
+        return reversed;
+      }
+
+      return `${reversed}.ip6.arpa.`;
+    }
+
+    if (options.omitSuffix) {
+      return '';
+    }
+
+    return 'ip6.arpa.';
+  }
+
+  /**
+   * Return the correct form of the address
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   */
+  correctForm(): string {
+    let i;
+    let groups = [];
+
+    let zeroCounter = 0;
+    const zeroes = [];
+
+    for (i = 0; i < this.parsedAddress.length; i++) {
+      const value = parseInt(this.parsedAddress[i], 16);
+
+      if (value === 0) {
+        zeroCounter++;
+      }
+
+      if (value !== 0 && zeroCounter > 0) {
+        if (zeroCounter > 1) {
+          zeroes.push([i - zeroCounter, i - 1]);
+        }
+
+        zeroCounter = 0;
+      }
+    }
+
+    // Do we end with a string of zeroes?
+    if (zeroCounter > 1) {
+      zeroes.push([this.parsedAddress.length - zeroCounter, this.parsedAddress.length - 1]);
+    }
+
+    const zeroLengths = zeroes.map((n) => n[1] - n[0] + 1);
+
+    if (zeroes.length > 0) {
+      const index = zeroLengths.indexOf(Math.max(...zeroLengths) as number);
+
+      groups = compact(this.parsedAddress, zeroes[index]);
+    } else {
+      groups = this.parsedAddress;
+    }
+
+    for (i = 0; i < groups.length; i++) {
+      if (groups[i] !== 'compact') {
+        groups[i] = parseInt(groups[i], 16).toString(16);
+      }
+    }
+
+    let correct = groups.join(':');
+
+    correct = correct.replace(/^compact$/, '::');
+    correct = correct.replace(/(^compact)|(compact$)/, ':');
+    correct = correct.replace(/compact/, '');
+
+    return correct;
+  }
+
+  /**
+   * Return a zero-padded base-2 string representation of the address
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   * @example
+   * var address = new Address6('2001:4860:4001:803::1011');
+   * address.binaryZeroPad();
+   * // '0010000000000001010010000110000001000000000000010000100000000011
+   * //  0000000000000000000000000000000000000000000000000001000000010001'
+   */
+  binaryZeroPad(): string {
+    return this.bigInt().toString(2).padStart(constants6.BITS, '0');
+  }
+
+  // TODO: Improve the semantics of this helper function
+  parse4in6(address: string): string {
+    const groups = address.split(':');
+    const lastGroup = groups.slice(-1)[0];
+
+    const address4 = lastGroup.match(constants4.RE_ADDRESS);
+
+    if (address4) {
+      this.parsedAddress4 = address4[0];
+      this.address4 = new Address4(this.parsedAddress4);
+
+      for (let i = 0; i < this.address4.groups; i++) {
+        if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) {
+          throw new AddressError(
+            "IPv4 addresses can't have leading zeroes.",
+            address.replace(
+              constants4.RE_ADDRESS,
+              this.address4.parsedAddress.map(spanLeadingZeroes4).join('.'),
+            ),
+          );
+        }
+      }
+
+      this.v4 = true;
+
+      groups[groups.length - 1] = this.address4.toGroup6();
+
+      address = groups.join(':');
+    }
+
+    return address;
+  }
+
+  // TODO: Make private?
+  parse(address: string): string[] {
+    address = this.parse4in6(address);
+
+    const badCharacters = address.match(constants6.RE_BAD_CHARACTERS);
+
+    if (badCharacters) {
+      throw new AddressError(
+        `Bad character${
+          badCharacters.length > 1 ? 's' : ''
+        } detected in address: ${badCharacters.join('')}`,
+        address.replace(constants6.RE_BAD_CHARACTERS, '<span class="parse-error">$1</span>'),
+      );
+    }
+
+    const badAddress = address.match(constants6.RE_BAD_ADDRESS);
+
+    if (badAddress) {
+      throw new AddressError(
+        `Address failed regex: ${badAddress.join('')}`,
+        address.replace(constants6.RE_BAD_ADDRESS, '<span class="parse-error">$1</span>'),
+      );
+    }
+
+    let groups: string[] = [];
+
+    const halves = address.split('::');
+
+    if (halves.length === 2) {
+      let first = halves[0].split(':');
+      let last = halves[1].split(':');
+
+      if (first.length === 1 && first[0] === '') {
+        first = [];
+      }
+
+      if (last.length === 1 && last[0] === '') {
+        last = [];
+      }
+
+      const remaining = this.groups - (first.length + last.length);
+
+      if (!remaining) {
+        throw new AddressError('Error parsing groups');
+      }
+
+      this.elidedGroups = remaining;
+
+      this.elisionBegin = first.length;
+      this.elisionEnd = first.length + this.elidedGroups;
+
+      groups = groups.concat(first);
+
+      for (let i = 0; i < remaining; i++) {
+        groups.push('0');
+      }
+
+      groups = groups.concat(last);
+    } else if (halves.length === 1) {
+      groups = address.split(':');
+
+      this.elidedGroups = 0;
+    } else {
+      throw new AddressError('Too many :: groups found');
+    }
+
+    groups = groups.map((group: string) => parseInt(group, 16).toString(16));
+
+    if (groups.length !== this.groups) {
+      throw new AddressError('Incorrect number of groups found');
+    }
+
+    return groups;
+  }
+
+  /**
+   * Return the canonical form of the address
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   */
+  canonicalForm(): string {
+    return this.parsedAddress.map(paddedHex).join(':');
+  }
+
+  /**
+   * Return the decimal form of the address
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   */
+  decimal(): string {
+    return this.parsedAddress.map((n) => parseInt(n, 16).toString(10).padStart(5, '0')).join(':');
+  }
+
+  /**
+   * Return the address as a BigInt
+   * @memberof Address6
+   * @instance
+   * @returns {bigint}
+   */
+  bigInt(): bigint {
+    return BigInt(`0x${this.parsedAddress.map(paddedHex).join('')}`);
+  }
+
+  /**
+   * Return the last two groups of this address as an IPv4 address string
+   * @memberof Address6
+   * @instance
+   * @returns {Address4}
+   * @example
+   * var address = new Address6('2001:4860:4001::1825:bf11');
+   * address.to4().correctForm(); // '24.37.191.17'
+   */
+  to4(): Address4 {
+    const binary = this.binaryZeroPad().split('');
+
+    return Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16));
+  }
+
+  /**
+   * Return the v4-in-v6 form of the address
+   * @memberof Address6
+   * @instance
+   * @returns {String}
+   */
+  to4in6(): string {
+    const address4 = this.to4();
+    const address6 = new Address6(this.parsedAddress.slice(0, 6).join(':'), 6);
+
+    const correct = address6.correctForm();
+
+    let infix = '';
+
+    if (!/:$/.test(correct)) {
+      infix = ':';
+    }
+
+    return correct + infix + address4.address;
+  }
+
+  /**
+   * Return an object containing the Teredo properties of the address
+   * @memberof Address6
+   * @instance
+   * @returns {Object}
+   */
+  inspectTeredo(): TeredoProperties {
+    /*
+    - Bits 0 to 31 are set to the Teredo prefix (normally 2001:0000::/32).
+    - Bits 32 to 63 embed the primary IPv4 address of the Teredo server that
+      is used.
+    - Bits 64 to 79 can be used to define some flags. Currently only the
+      higher order bit is used; it is set to 1 if the Teredo client is
+      located behind a cone NAT, 0 otherwise. For Microsoft's Windows Vista
+      and Windows Server 2008 implementations, more bits are used. In those
+      implementations, the format for these 16 bits is "CRAAAAUG AAAAAAAA",
+      where "C" remains the "Cone" flag. The "R" bit is reserved for future
+      use. The "U" bit is for the Universal/Local flag (set to 0). The "G" bit
+      is Individual/Group flag (set to 0). The A bits are set to a 12-bit
+      randomly generated number chosen by the Teredo client to introduce
+      additional protection for the Teredo node against IPv6-based scanning
+      attacks.
+    - Bits 80 to 95 contains the obfuscated UDP port number. This is the
+      port number that is mapped by the NAT to the Teredo client with all
+      bits inverted.
+    - Bits 96 to 127 contains the obfuscated IPv4 address. This is the
+      public IPv4 address of the NAT with all bits inverted.
+    */
+    const prefix = this.getBitsBase16(0, 32);
+
+    const bitsForUdpPort: bigint = this.getBits(80, 96);
+    // eslint-disable-next-line no-bitwise
+    const udpPort = (bitsForUdpPort ^ BigInt('0xffff')).toString();
+
+    const server4 = Address4.fromHex(this.getBitsBase16(32, 64));
+
+    const bitsForClient4 = this.getBits(96, 128);
+    // eslint-disable-next-line no-bitwise
+    const client4 = Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16));
+
+    const flagsBase2 = this.getBitsBase2(64, 80);
+
+    const coneNat = testBit(flagsBase2, 15);
+    const reserved = testBit(flagsBase2, 14);
+    const groupIndividual = testBit(flagsBase2, 8);
+    const universalLocal = testBit(flagsBase2, 9);
+    const nonce = BigInt(`0b${flagsBase2.slice(2, 6) + flagsBase2.slice(8, 16)}`).toString(10);
+
+    return {
+      prefix: `${prefix.slice(0, 4)}:${prefix.slice(4, 8)}`,
+      server4: server4.address,
+      client4: client4.address,
+      flags: flagsBase2,
+      coneNat,
+      microsoft: {
+        reserved,
+        universalLocal,
+        groupIndividual,
+        nonce,
+      },
+      udpPort,
+    };
+  }
+
+  /**
+   * Return an object containing the 6to4 properties of the address
+   * @memberof Address6
+   * @instance
+   * @returns {Object}
+   */
+  inspect6to4(): SixToFourProperties {
+    /*
+    - Bits 0 to 15 are set to the 6to4 prefix (2002::/16).
+    - Bits 16 to 48 embed the IPv4 address of the 6to4 gateway that is used.
+    */
+
+    const prefix = this.getBitsBase16(0, 16);
+
+    const gateway = Address4.fromHex(this.getBitsBase16(16, 48));
+
+    return {
+      prefix: prefix.slice(0, 4),
+      gateway: gateway.address,
+    };
+  }
+
+  /**
+   * Return a v6 6to4 address from a v6 v4inv6 address
+   * @memberof Address6
+   * @instance
+   * @returns {Address6}
+   */
+  to6to4(): Address6 | null {
+    if (!this.is4()) {
+      return null;
+    }
+
+    const addr6to4 = [
+      '2002',
+      this.getBitsBase16(96, 112),
+      this.getBitsBase16(112, 128),
+      '',
+      '/16',
+    ].join(':');
+
+    return new Address6(addr6to4);
+  }
+
+  /**
+   * Return a byte array
+   * @memberof Address6
+   * @instance
+   * @returns {Array}
+   */
+  toByteArray(): number[] {
+    const valueWithoutPadding = this.bigInt().toString(16);
+    const leadingPad = '0'.repeat(valueWithoutPadding.length % 2);
+
+    const value = `${leadingPad}${valueWithoutPadding}`;
+
+    const bytes = [];
+    for (let i = 0, length = value.length; i < length; i += 2) {
+      bytes.push(parseInt(value.substring(i, i + 2), 16));
+    }
+
+    return bytes;
+  }
+
+  /**
+   * Return an unsigned byte array
+   * @memberof Address6
+   * @instance
+   * @returns {Array}
+   */
+  toUnsignedByteArray(): number[] {
+    return this.toByteArray().map(unsignByte);
+  }
+
+  /**
+   * Convert a byte array to an Address6 object
+   * @memberof Address6
+   * @static
+   * @returns {Address6}
+   */
+  static fromByteArray(bytes: Array<any>): Address6 {
+    return this.fromUnsignedByteArray(bytes.map(unsignByte));
+  }
+
+  /**
+   * Convert an unsigned byte array to an Address6 object
+   * @memberof Address6
+   * @static
+   * @returns {Address6}
+   */
+  static fromUnsignedByteArray(bytes: Array<any>): Address6 {
+    const BYTE_MAX = BigInt('256');
+    let result = BigInt('0');
+    let multiplier = BigInt('1');
+
+    for (let i = bytes.length - 1; i >= 0; i--) {
+      result += multiplier * BigInt(bytes[i].toString(10));
+
+      multiplier *= BYTE_MAX;
+    }
+
+    return Address6.fromBigInt(result);
+  }
+
+  // #region Attributes
+  /**
+   * Returns true if the given address is in the subnet of the current address
+   * @memberof Address6
+   * @instance
+   * @returns {boolean}
+   */
+  isInSubnet = common.isInSubnet;
+
+  /**
+   * Returns true if the address is correct, false otherwise
+   * @memberof Address6
+   * @instance
+   * @returns {boolean}
+   */
+  isCorrect = common.isCorrect(constants6.BITS);
+
+  /**
+   * Returns true if the address is in the canonical form, false otherwise
+   * @memberof Address6
+   * @instance
+   * @returns {boolean}
+   */
+  isCanonical(): boolean {
+    return this.addressMinusSuffix === this.canonicalForm();
+  }
+
+  /**
+   * Returns true if the address is a link local address, false otherwise
+   * @memberof Address6
+   * @instance
+   * @returns {boolean}
+   */
+  isLinkLocal(): boolean {
+    // Zeroes are required, i.e. we can't check isInSubnet with 'fe80::/10'
+    if (
+      this.getBitsBase2(0, 64) ===
+      '1111111010000000000000000000000000000000000000000000000000000000'
+    ) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Returns true if the address is a multicast address, false otherwise
+   * @memberof Address6
+   * @instance
+   * @returns {boolean}
+   */
+  isMulticast(): boolean {
+    return this.getType() === 'Multicast';
+  }
+
+  /**
+   * Returns true if the address is a v4-in-v6 address, false otherwise
+   * @memberof Address6
+   * @instance
+   * @returns {boolean}
+   */
+  is4(): boolean {
+    return this.v4;
+  }
+
+  /**
+   * Returns true if the address is a Teredo address, false otherwise
+   * @memberof Address6
+   * @instance
+   * @returns {boolean}
+   */
+  isTeredo(): boolean {
+    return this.isInSubnet(new Address6('2001::/32'));
+  }
+
+  /**
+   * Returns true if the address is a 6to4 address, false otherwise
+   * @memberof Address6
+   * @instance
+   * @returns {boolean}
+   */
+  is6to4(): boolean {
+    return this.isInSubnet(new Address6('2002::/16'));
+  }
+
+  /**
+   * Returns true if the address is a loopback address, false otherwise
+   * @memberof Address6
+   * @instance
+   * @returns {boolean}
+   */
+  isLoopback(): boolean {
+    return this.getType() === 'Loopback';
+  }
+  // #endregion
+
+  // #region HTML
+  /**
+   * @returns {String} the address in link form with a default port of 80
+   */
+  href(optionalPort?: number | string): string {
+    if (optionalPort === undefined) {
+      optionalPort = '';
+    } else {
+      optionalPort = `:${optionalPort}`;
+    }
+
+    return `http://[${this.correctForm()}]${optionalPort}/`;
+  }
+
+  /**
+   * @returns {String} a link suitable for conveying the address via a URL hash
+   */
+  link(options?: { className?: string; prefix?: string; v4?: boolean }): string {
+    if (!options) {
+      options = {};
+    }
+
+    if (options.className === undefined) {
+      options.className = '';
+    }
+
+    if (options.prefix === undefined) {
+      options.prefix = '/#address=';
+    }
+
+    if (options.v4 === undefined) {
+      options.v4 = false;
+    }
+
+    let formFunction = this.correctForm;
+
+    if (options.v4) {
+      formFunction = this.to4in6;
+    }
+
+    const form = formFunction.call(this);
+
+    if (options.className) {
+      return `<a href="${options.prefix}${form}" class="${options.className}">${form}</a>`;
+    }
+
+    return `<a href="${options.prefix}${form}">${form}</a>`;
+  }
+
+  /**
+   * Groups an address
+   * @returns {String}
+   */
+  group(): string {
+    if (this.elidedGroups === 0) {
+      // The simple case
+      return helpers.simpleGroup(this.address).join(':');
+    }
+
+    assert(typeof this.elidedGroups === 'number');
+    assert(typeof this.elisionBegin === 'number');
+
+    // The elided case
+    const output = [];
+
+    const [left, right] = this.address.split('::');
+
+    if (left.length) {
+      output.push(...helpers.simpleGroup(left));
+    } else {
+      output.push('');
+    }
+
+    const classes = ['hover-group'];
+
+    for (let i = this.elisionBegin; i < this.elisionBegin + this.elidedGroups; i++) {
+      classes.push(`group-${i}`);
+    }
+
+    output.push(`<span class="${classes.join(' ')}"></span>`);
+
+    if (right.length) {
+      output.push(...helpers.simpleGroup(right, this.elisionEnd));
+    } else {
+      output.push('');
+    }
+
+    if (this.is4()) {
+      assert(this.address4 instanceof Address4);
+
+      output.pop();
+      output.push(this.address4.groupForV6());
+    }
+
+    return output.join(':');
+  }
+  // #endregion
+
+  // #region Regular expressions
+  /**
+   * Generate a regular expression string that can be used to find or validate
+   * all variations of this address
+   * @memberof Address6
+   * @instance
+   * @param {boolean} substringSearch
+   * @returns {string}
+   */
+  regularExpressionString(this: Address6, substringSearch: boolean = false): string {
+    let output: string[] = [];
+
+    // TODO: revisit why this is necessary
+    const address6 = new Address6(this.correctForm());
+
+    if (address6.elidedGroups === 0) {
+      // The simple case
+      output.push(simpleRegularExpression(address6.parsedAddress));
+    } else if (address6.elidedGroups === constants6.GROUPS) {
+      // A completely elided address
+      output.push(possibleElisions(constants6.GROUPS));
+    } else {
+      // A partially elided address
+      const halves = address6.address.split('::');
+
+      if (halves[0].length) {
+        output.push(simpleRegularExpression(halves[0].split(':')));
+      }
+
+      assert(typeof address6.elidedGroups === 'number');
+
+      output.push(
+        possibleElisions(address6.elidedGroups, halves[0].length !== 0, halves[1].length !== 0),
+      );
+
+      if (halves[1].length) {
+        output.push(simpleRegularExpression(halves[1].split(':')));
+      }
+
+      output = [output.join(':')];
+    }
+
+    if (!substringSearch) {
+      output = [
+        '(?=^|',
+        ADDRESS_BOUNDARY,
+        '|[^\\w\\:])(',
+        ...output,
+        ')(?=[^\\w\\:]|',
+        ADDRESS_BOUNDARY,
+        '|$)',
+      ];
+    }
+
+    return output.join('');
+  }
+
+  /**
+   * Generate a regular expression that can be used to find or validate all
+   * variations of this address.
+   * @memberof Address6
+   * @instance
+   * @param {boolean} substringSearch
+   * @returns {RegExp}
+   */
+  regularExpression(this: Address6, substringSearch: boolean = false): RegExp {
+    return new RegExp(this.regularExpressionString(substringSearch), 'i');
+  }
+  // #endregion
+}
