| [81bc7da] | 1 | /* eslint-disable prefer-destructuring */
|
|---|
| 2 | /* eslint-disable no-param-reassign */
|
|---|
| 3 |
|
|---|
| 4 | import * as common from './common';
|
|---|
| 5 | import * as constants4 from './v4/constants';
|
|---|
| 6 | import * as constants6 from './v6/constants';
|
|---|
| 7 | import * as helpers from './v6/helpers';
|
|---|
| 8 | import { Address4 } from './ipv4';
|
|---|
| 9 | import {
|
|---|
| 10 | ADDRESS_BOUNDARY,
|
|---|
| 11 | possibleElisions,
|
|---|
| 12 | simpleRegularExpression,
|
|---|
| 13 | } from './v6/regular-expressions';
|
|---|
| 14 | import { AddressError } from './address-error';
|
|---|
| 15 | import { testBit } from './common';
|
|---|
| 16 |
|
|---|
| 17 | function assert(condition: any): asserts condition {
|
|---|
| 18 | if (!condition) {
|
|---|
| 19 | throw new Error('Assertion failed.');
|
|---|
| 20 | }
|
|---|
| 21 | }
|
|---|
| 22 |
|
|---|
| 23 | function addCommas(number: string): string {
|
|---|
| 24 | const r = /(\d+)(\d{3})/;
|
|---|
| 25 |
|
|---|
| 26 | while (r.test(number)) {
|
|---|
| 27 | number = number.replace(r, '$1,$2');
|
|---|
| 28 | }
|
|---|
| 29 |
|
|---|
| 30 | return number;
|
|---|
| 31 | }
|
|---|
| 32 |
|
|---|
| 33 | function spanLeadingZeroes4(n: string): string {
|
|---|
| 34 | n = n.replace(/^(0{1,})([1-9]+)$/, '<span class="parse-error">$1</span>$2');
|
|---|
| 35 | n = n.replace(/^(0{1,})(0)$/, '<span class="parse-error">$1</span>$2');
|
|---|
| 36 |
|
|---|
| 37 | return n;
|
|---|
| 38 | }
|
|---|
| 39 |
|
|---|
| 40 | /*
|
|---|
| 41 | * A helper function to compact an array
|
|---|
| 42 | */
|
|---|
| 43 | function compact(address: string[], slice: number[]) {
|
|---|
| 44 | const s1 = [];
|
|---|
| 45 | const s2 = [];
|
|---|
| 46 | let i;
|
|---|
| 47 |
|
|---|
| 48 | for (i = 0; i < address.length; i++) {
|
|---|
| 49 | if (i < slice[0]) {
|
|---|
| 50 | s1.push(address[i]);
|
|---|
| 51 | } else if (i > slice[1]) {
|
|---|
| 52 | s2.push(address[i]);
|
|---|
| 53 | }
|
|---|
| 54 | }
|
|---|
| 55 |
|
|---|
| 56 | return s1.concat(['compact']).concat(s2);
|
|---|
| 57 | }
|
|---|
| 58 |
|
|---|
| 59 | function paddedHex(octet: string): string {
|
|---|
| 60 | return parseInt(octet, 16).toString(16).padStart(4, '0');
|
|---|
| 61 | }
|
|---|
| 62 |
|
|---|
| 63 | function unsignByte(b: number) {
|
|---|
| 64 | // eslint-disable-next-line no-bitwise
|
|---|
| 65 | return b & 0xff;
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | interface SixToFourProperties {
|
|---|
| 69 | prefix: string;
|
|---|
| 70 | gateway: string;
|
|---|
| 71 | }
|
|---|
| 72 |
|
|---|
| 73 | interface TeredoProperties {
|
|---|
| 74 | prefix: string;
|
|---|
| 75 | server4: string;
|
|---|
| 76 | client4: string;
|
|---|
| 77 | flags: string;
|
|---|
| 78 | coneNat: boolean;
|
|---|
| 79 | microsoft: {
|
|---|
| 80 | reserved: boolean;
|
|---|
| 81 | universalLocal: boolean;
|
|---|
| 82 | groupIndividual: boolean;
|
|---|
| 83 | nonce: string;
|
|---|
| 84 | };
|
|---|
| 85 | udpPort: string;
|
|---|
| 86 | }
|
|---|
| 87 |
|
|---|
| 88 | /**
|
|---|
| 89 | * Represents an IPv6 address
|
|---|
| 90 | * @class Address6
|
|---|
| 91 | * @param {string} address - An IPv6 address string
|
|---|
| 92 | * @param {number} [groups=8] - How many octets to parse
|
|---|
| 93 | * @example
|
|---|
| 94 | * var address = new Address6('2001::/32');
|
|---|
| 95 | */
|
|---|
| 96 | export class Address6 {
|
|---|
| 97 | address4?: Address4;
|
|---|
| 98 | address: string;
|
|---|
| 99 | addressMinusSuffix: string = '';
|
|---|
| 100 | elidedGroups?: number;
|
|---|
| 101 | elisionBegin?: number;
|
|---|
| 102 | elisionEnd?: number;
|
|---|
| 103 | groups: number;
|
|---|
| 104 | parsedAddress4?: string;
|
|---|
| 105 | parsedAddress: string[];
|
|---|
| 106 | parsedSubnet: string = '';
|
|---|
| 107 | subnet: string = '/128';
|
|---|
| 108 | subnetMask: number = 128;
|
|---|
| 109 | v4: boolean = false;
|
|---|
| 110 | zone: string = '';
|
|---|
| 111 |
|
|---|
| 112 | constructor(address: string, optionalGroups?: number) {
|
|---|
| 113 | if (optionalGroups === undefined) {
|
|---|
| 114 | this.groups = constants6.GROUPS;
|
|---|
| 115 | } else {
|
|---|
| 116 | this.groups = optionalGroups;
|
|---|
| 117 | }
|
|---|
| 118 |
|
|---|
| 119 | this.address = address;
|
|---|
| 120 |
|
|---|
| 121 | const subnet = constants6.RE_SUBNET_STRING.exec(address);
|
|---|
| 122 |
|
|---|
| 123 | if (subnet) {
|
|---|
| 124 | this.parsedSubnet = subnet[0].replace('/', '');
|
|---|
| 125 | this.subnetMask = parseInt(this.parsedSubnet, 10);
|
|---|
| 126 | this.subnet = `/${this.subnetMask}`;
|
|---|
| 127 |
|
|---|
| 128 | if (
|
|---|
| 129 | Number.isNaN(this.subnetMask) ||
|
|---|
| 130 | this.subnetMask < 0 ||
|
|---|
| 131 | this.subnetMask > constants6.BITS
|
|---|
| 132 | ) {
|
|---|
| 133 | throw new AddressError('Invalid subnet mask.');
|
|---|
| 134 | }
|
|---|
| 135 |
|
|---|
| 136 | address = address.replace(constants6.RE_SUBNET_STRING, '');
|
|---|
| 137 | } else if (/\//.test(address)) {
|
|---|
| 138 | throw new AddressError('Invalid subnet mask.');
|
|---|
| 139 | }
|
|---|
| 140 |
|
|---|
| 141 | const zone = constants6.RE_ZONE_STRING.exec(address);
|
|---|
| 142 |
|
|---|
| 143 | if (zone) {
|
|---|
| 144 | this.zone = zone[0];
|
|---|
| 145 |
|
|---|
| 146 | address = address.replace(constants6.RE_ZONE_STRING, '');
|
|---|
| 147 | }
|
|---|
| 148 |
|
|---|
| 149 | this.addressMinusSuffix = address;
|
|---|
| 150 |
|
|---|
| 151 | this.parsedAddress = this.parse(this.addressMinusSuffix);
|
|---|
| 152 | }
|
|---|
| 153 |
|
|---|
| 154 | static isValid(address: string): boolean {
|
|---|
| 155 | try {
|
|---|
| 156 | // eslint-disable-next-line no-new
|
|---|
| 157 | new Address6(address);
|
|---|
| 158 |
|
|---|
| 159 | return true;
|
|---|
| 160 | } catch (e) {
|
|---|
| 161 | return false;
|
|---|
| 162 | }
|
|---|
| 163 | }
|
|---|
| 164 |
|
|---|
| 165 | /**
|
|---|
| 166 | * Convert a BigInt to a v6 address object
|
|---|
| 167 | * @memberof Address6
|
|---|
| 168 | * @static
|
|---|
| 169 | * @param {bigint} bigInt - a BigInt to convert
|
|---|
| 170 | * @returns {Address6}
|
|---|
| 171 | * @example
|
|---|
| 172 | * var bigInt = BigInt('1000000000000');
|
|---|
| 173 | * var address = Address6.fromBigInt(bigInt);
|
|---|
| 174 | * address.correctForm(); // '::e8:d4a5:1000'
|
|---|
| 175 | */
|
|---|
| 176 | static fromBigInt(bigInt: bigint): Address6 {
|
|---|
| 177 | const hex = bigInt.toString(16).padStart(32, '0');
|
|---|
| 178 | const groups = [];
|
|---|
| 179 | let i;
|
|---|
| 180 |
|
|---|
| 181 | for (i = 0; i < constants6.GROUPS; i++) {
|
|---|
| 182 | groups.push(hex.slice(i * 4, (i + 1) * 4));
|
|---|
| 183 | }
|
|---|
| 184 |
|
|---|
| 185 | return new Address6(groups.join(':'));
|
|---|
| 186 | }
|
|---|
| 187 |
|
|---|
| 188 | /**
|
|---|
| 189 | * Convert a URL (with optional port number) to an address object
|
|---|
| 190 | * @memberof Address6
|
|---|
| 191 | * @static
|
|---|
| 192 | * @param {string} url - a URL with optional port number
|
|---|
| 193 | * @example
|
|---|
| 194 | * var addressAndPort = Address6.fromURL('http://[ffff::]:8080/foo/');
|
|---|
| 195 | * addressAndPort.address.correctForm(); // 'ffff::'
|
|---|
| 196 | * addressAndPort.port; // 8080
|
|---|
| 197 | */
|
|---|
| 198 | static fromURL(url: string) {
|
|---|
| 199 | let host: string;
|
|---|
| 200 | let port: string | number | null = null;
|
|---|
| 201 | let result: string[] | null;
|
|---|
| 202 |
|
|---|
| 203 | // If we have brackets parse them and find a port
|
|---|
| 204 | if (url.indexOf('[') !== -1 && url.indexOf(']:') !== -1) {
|
|---|
| 205 | result = constants6.RE_URL_WITH_PORT.exec(url);
|
|---|
| 206 |
|
|---|
| 207 | if (result === null) {
|
|---|
| 208 | return {
|
|---|
| 209 | error: 'failed to parse address with port',
|
|---|
| 210 | address: null,
|
|---|
| 211 | port: null,
|
|---|
| 212 | };
|
|---|
| 213 | }
|
|---|
| 214 |
|
|---|
| 215 | host = result[1];
|
|---|
| 216 | port = result[2];
|
|---|
| 217 | // If there's a URL extract the address
|
|---|
| 218 | } else if (url.indexOf('/') !== -1) {
|
|---|
| 219 | // Remove the protocol prefix
|
|---|
| 220 | url = url.replace(/^[a-z0-9]+:\/\//, '');
|
|---|
| 221 |
|
|---|
| 222 | // Parse the address
|
|---|
| 223 | result = constants6.RE_URL.exec(url);
|
|---|
| 224 |
|
|---|
| 225 | if (result === null) {
|
|---|
| 226 | return {
|
|---|
| 227 | error: 'failed to parse address from URL',
|
|---|
| 228 | address: null,
|
|---|
| 229 | port: null,
|
|---|
| 230 | };
|
|---|
| 231 | }
|
|---|
| 232 |
|
|---|
| 233 | host = result[1];
|
|---|
| 234 | // Otherwise just assign the URL to the host and let the library parse it
|
|---|
| 235 | } else {
|
|---|
| 236 | host = url;
|
|---|
| 237 | }
|
|---|
| 238 |
|
|---|
| 239 | // If there's a port convert it to an integer
|
|---|
| 240 | if (port) {
|
|---|
| 241 | port = parseInt(port, 10);
|
|---|
| 242 |
|
|---|
| 243 | // squelch out of range ports
|
|---|
| 244 | if (port < 0 || port > 65536) {
|
|---|
| 245 | port = null;
|
|---|
| 246 | }
|
|---|
| 247 | } else {
|
|---|
| 248 | // Standardize `undefined` to `null`
|
|---|
| 249 | port = null;
|
|---|
| 250 | }
|
|---|
| 251 |
|
|---|
| 252 | return {
|
|---|
| 253 | address: new Address6(host),
|
|---|
| 254 | port,
|
|---|
| 255 | };
|
|---|
| 256 | }
|
|---|
| 257 |
|
|---|
| 258 | /**
|
|---|
| 259 | * Create an IPv6-mapped address given an IPv4 address
|
|---|
| 260 | * @memberof Address6
|
|---|
| 261 | * @static
|
|---|
| 262 | * @param {string} address - An IPv4 address string
|
|---|
| 263 | * @returns {Address6}
|
|---|
| 264 | * @example
|
|---|
| 265 | * var address = Address6.fromAddress4('192.168.0.1');
|
|---|
| 266 | * address.correctForm(); // '::ffff:c0a8:1'
|
|---|
| 267 | * address.to4in6(); // '::ffff:192.168.0.1'
|
|---|
| 268 | */
|
|---|
| 269 | static fromAddress4(address: string): Address6 {
|
|---|
| 270 | const address4 = new Address4(address);
|
|---|
| 271 |
|
|---|
| 272 | const mask6 = constants6.BITS - (constants4.BITS - address4.subnetMask);
|
|---|
| 273 |
|
|---|
| 274 | return new Address6(`::ffff:${address4.correctForm()}/${mask6}`);
|
|---|
| 275 | }
|
|---|
| 276 |
|
|---|
| 277 | /**
|
|---|
| 278 | * Return an address from ip6.arpa form
|
|---|
| 279 | * @memberof Address6
|
|---|
| 280 | * @static
|
|---|
| 281 | * @param {string} arpaFormAddress - an 'ip6.arpa' form address
|
|---|
| 282 | * @returns {Adress6}
|
|---|
| 283 | * @example
|
|---|
| 284 | * 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.)
|
|---|
| 285 | * address.correctForm(); // '2001:0:ce49:7601:e866:efff:62c3:fffe'
|
|---|
| 286 | */
|
|---|
| 287 | static fromArpa(arpaFormAddress: string): Address6 {
|
|---|
| 288 | // remove ending ".ip6.arpa." or just "."
|
|---|
| 289 | let address = arpaFormAddress.replace(/(\.ip6\.arpa)?\.$/, '');
|
|---|
| 290 | const semicolonAmount = 7;
|
|---|
| 291 |
|
|---|
| 292 | // correct ip6.arpa form with ending removed will be 63 characters
|
|---|
| 293 | if (address.length !== 63) {
|
|---|
| 294 | throw new AddressError("Invalid 'ip6.arpa' form.");
|
|---|
| 295 | }
|
|---|
| 296 |
|
|---|
| 297 | const parts = address.split('.').reverse();
|
|---|
| 298 |
|
|---|
| 299 | for (let i = semicolonAmount; i > 0; i--) {
|
|---|
| 300 | const insertIndex = i * 4;
|
|---|
| 301 | parts.splice(insertIndex, 0, ':');
|
|---|
| 302 | }
|
|---|
| 303 |
|
|---|
| 304 | address = parts.join('');
|
|---|
| 305 |
|
|---|
| 306 | return new Address6(address);
|
|---|
| 307 | }
|
|---|
| 308 |
|
|---|
| 309 | /**
|
|---|
| 310 | * Return the Microsoft UNC transcription of the address
|
|---|
| 311 | * @memberof Address6
|
|---|
| 312 | * @instance
|
|---|
| 313 | * @returns {String} the Microsoft UNC transcription of the address
|
|---|
| 314 | */
|
|---|
| 315 | microsoftTranscription(): string {
|
|---|
| 316 | return `${this.correctForm().replace(/:/g, '-')}.ipv6-literal.net`;
|
|---|
| 317 | }
|
|---|
| 318 |
|
|---|
| 319 | /**
|
|---|
| 320 | * Return the first n bits of the address, defaulting to the subnet mask
|
|---|
| 321 | * @memberof Address6
|
|---|
| 322 | * @instance
|
|---|
| 323 | * @param {number} [mask=subnet] - the number of bits to mask
|
|---|
| 324 | * @returns {String} the first n bits of the address as a string
|
|---|
| 325 | */
|
|---|
| 326 | mask(mask: number = this.subnetMask): string {
|
|---|
| 327 | return this.getBitsBase2(0, mask);
|
|---|
| 328 | }
|
|---|
| 329 |
|
|---|
| 330 | /**
|
|---|
| 331 | * Return the number of possible subnets of a given size in the address
|
|---|
| 332 | * @memberof Address6
|
|---|
| 333 | * @instance
|
|---|
| 334 | * @param {number} [subnetSize=128] - the subnet size
|
|---|
| 335 | * @returns {String}
|
|---|
| 336 | */
|
|---|
| 337 | // TODO: probably useful to have a numeric version of this too
|
|---|
| 338 | possibleSubnets(subnetSize: number = 128): string {
|
|---|
| 339 | const availableBits = constants6.BITS - this.subnetMask;
|
|---|
| 340 | const subnetBits = Math.abs(subnetSize - constants6.BITS);
|
|---|
| 341 | const subnetPowers = availableBits - subnetBits;
|
|---|
| 342 |
|
|---|
| 343 | if (subnetPowers < 0) {
|
|---|
| 344 | return '0';
|
|---|
| 345 | }
|
|---|
| 346 |
|
|---|
| 347 | return addCommas((BigInt('2') ** BigInt(subnetPowers)).toString(10));
|
|---|
| 348 | }
|
|---|
| 349 |
|
|---|
| 350 | /**
|
|---|
| 351 | * Helper function getting start address.
|
|---|
| 352 | * @memberof Address6
|
|---|
| 353 | * @instance
|
|---|
| 354 | * @returns {bigint}
|
|---|
| 355 | */
|
|---|
| 356 | _startAddress(): bigint {
|
|---|
| 357 | return BigInt(`0b${this.mask() + '0'.repeat(constants6.BITS - this.subnetMask)}`);
|
|---|
| 358 | }
|
|---|
| 359 |
|
|---|
| 360 | /**
|
|---|
| 361 | * The first address in the range given by this address' subnet
|
|---|
| 362 | * Often referred to as the Network Address.
|
|---|
| 363 | * @memberof Address6
|
|---|
| 364 | * @instance
|
|---|
| 365 | * @returns {Address6}
|
|---|
| 366 | */
|
|---|
| 367 | startAddress(): Address6 {
|
|---|
| 368 | return Address6.fromBigInt(this._startAddress());
|
|---|
| 369 | }
|
|---|
| 370 |
|
|---|
| 371 | /**
|
|---|
| 372 | * The first host address in the range given by this address's subnet ie
|
|---|
| 373 | * the first address after the Network Address
|
|---|
| 374 | * @memberof Address6
|
|---|
| 375 | * @instance
|
|---|
| 376 | * @returns {Address6}
|
|---|
| 377 | */
|
|---|
| 378 | startAddressExclusive(): Address6 {
|
|---|
| 379 | const adjust = BigInt('1');
|
|---|
| 380 | return Address6.fromBigInt(this._startAddress() + adjust);
|
|---|
| 381 | }
|
|---|
| 382 |
|
|---|
| 383 | /**
|
|---|
| 384 | * Helper function getting end address.
|
|---|
| 385 | * @memberof Address6
|
|---|
| 386 | * @instance
|
|---|
| 387 | * @returns {bigint}
|
|---|
| 388 | */
|
|---|
| 389 | _endAddress(): bigint {
|
|---|
| 390 | return BigInt(`0b${this.mask() + '1'.repeat(constants6.BITS - this.subnetMask)}`);
|
|---|
| 391 | }
|
|---|
| 392 |
|
|---|
| 393 | /**
|
|---|
| 394 | * The last address in the range given by this address' subnet
|
|---|
| 395 | * Often referred to as the Broadcast
|
|---|
| 396 | * @memberof Address6
|
|---|
| 397 | * @instance
|
|---|
| 398 | * @returns {Address6}
|
|---|
| 399 | */
|
|---|
| 400 | endAddress(): Address6 {
|
|---|
| 401 | return Address6.fromBigInt(this._endAddress());
|
|---|
| 402 | }
|
|---|
| 403 |
|
|---|
| 404 | /**
|
|---|
| 405 | * The last host address in the range given by this address's subnet ie
|
|---|
| 406 | * the last address prior to the Broadcast Address
|
|---|
| 407 | * @memberof Address6
|
|---|
| 408 | * @instance
|
|---|
| 409 | * @returns {Address6}
|
|---|
| 410 | */
|
|---|
| 411 | endAddressExclusive(): Address6 {
|
|---|
| 412 | const adjust = BigInt('1');
|
|---|
| 413 | return Address6.fromBigInt(this._endAddress() - adjust);
|
|---|
| 414 | }
|
|---|
| 415 |
|
|---|
| 416 | /**
|
|---|
| 417 | * Return the scope of the address
|
|---|
| 418 | * @memberof Address6
|
|---|
| 419 | * @instance
|
|---|
| 420 | * @returns {String}
|
|---|
| 421 | */
|
|---|
| 422 | getScope(): string {
|
|---|
| 423 | let scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)];
|
|---|
| 424 |
|
|---|
| 425 | if (this.getType() === 'Global unicast' && scope !== 'Link local') {
|
|---|
| 426 | scope = 'Global';
|
|---|
| 427 | }
|
|---|
| 428 |
|
|---|
| 429 | return scope || 'Unknown';
|
|---|
| 430 | }
|
|---|
| 431 |
|
|---|
| 432 | /**
|
|---|
| 433 | * Return the type of the address
|
|---|
| 434 | * @memberof Address6
|
|---|
| 435 | * @instance
|
|---|
| 436 | * @returns {String}
|
|---|
| 437 | */
|
|---|
| 438 | getType(): string {
|
|---|
| 439 | for (const subnet of Object.keys(constants6.TYPES)) {
|
|---|
| 440 | if (this.isInSubnet(new Address6(subnet))) {
|
|---|
| 441 | return constants6.TYPES[subnet] as string;
|
|---|
| 442 | }
|
|---|
| 443 | }
|
|---|
| 444 |
|
|---|
| 445 | return 'Global unicast';
|
|---|
| 446 | }
|
|---|
| 447 |
|
|---|
| 448 | /**
|
|---|
| 449 | * Return the bits in the given range as a BigInt
|
|---|
| 450 | * @memberof Address6
|
|---|
| 451 | * @instance
|
|---|
| 452 | * @returns {bigint}
|
|---|
| 453 | */
|
|---|
| 454 | getBits(start: number, end: number): bigint {
|
|---|
| 455 | return BigInt(`0b${this.getBitsBase2(start, end)}`);
|
|---|
| 456 | }
|
|---|
| 457 |
|
|---|
| 458 | /**
|
|---|
| 459 | * Return the bits in the given range as a base-2 string
|
|---|
| 460 | * @memberof Address6
|
|---|
| 461 | * @instance
|
|---|
| 462 | * @returns {String}
|
|---|
| 463 | */
|
|---|
| 464 | getBitsBase2(start: number, end: number): string {
|
|---|
| 465 | return this.binaryZeroPad().slice(start, end);
|
|---|
| 466 | }
|
|---|
| 467 |
|
|---|
| 468 | /**
|
|---|
| 469 | * Return the bits in the given range as a base-16 string
|
|---|
| 470 | * @memberof Address6
|
|---|
| 471 | * @instance
|
|---|
| 472 | * @returns {String}
|
|---|
| 473 | */
|
|---|
| 474 | getBitsBase16(start: number, end: number): string {
|
|---|
| 475 | const length = end - start;
|
|---|
| 476 |
|
|---|
| 477 | if (length % 4 !== 0) {
|
|---|
| 478 | throw new Error('Length of bits to retrieve must be divisible by four');
|
|---|
| 479 | }
|
|---|
| 480 |
|
|---|
| 481 | return this.getBits(start, end)
|
|---|
| 482 | .toString(16)
|
|---|
| 483 | .padStart(length / 4, '0');
|
|---|
| 484 | }
|
|---|
| 485 |
|
|---|
| 486 | /**
|
|---|
| 487 | * Return the bits that are set past the subnet mask length
|
|---|
| 488 | * @memberof Address6
|
|---|
| 489 | * @instance
|
|---|
| 490 | * @returns {String}
|
|---|
| 491 | */
|
|---|
| 492 | getBitsPastSubnet(): string {
|
|---|
| 493 | return this.getBitsBase2(this.subnetMask, constants6.BITS);
|
|---|
| 494 | }
|
|---|
| 495 |
|
|---|
| 496 | /**
|
|---|
| 497 | * Return the reversed ip6.arpa form of the address
|
|---|
| 498 | * @memberof Address6
|
|---|
| 499 | * @param {Object} options
|
|---|
| 500 | * @param {boolean} options.omitSuffix - omit the "ip6.arpa" suffix
|
|---|
| 501 | * @instance
|
|---|
| 502 | * @returns {String}
|
|---|
| 503 | */
|
|---|
| 504 | reverseForm(options?: common.ReverseFormOptions): string {
|
|---|
| 505 | if (!options) {
|
|---|
| 506 | options = {};
|
|---|
| 507 | }
|
|---|
| 508 |
|
|---|
| 509 | const characters = Math.floor(this.subnetMask / 4);
|
|---|
| 510 |
|
|---|
| 511 | const reversed = this.canonicalForm()
|
|---|
| 512 | .replace(/:/g, '')
|
|---|
| 513 | .split('')
|
|---|
| 514 | .slice(0, characters)
|
|---|
| 515 | .reverse()
|
|---|
| 516 | .join('.');
|
|---|
| 517 |
|
|---|
| 518 | if (characters > 0) {
|
|---|
| 519 | if (options.omitSuffix) {
|
|---|
| 520 | return reversed;
|
|---|
| 521 | }
|
|---|
| 522 |
|
|---|
| 523 | return `${reversed}.ip6.arpa.`;
|
|---|
| 524 | }
|
|---|
| 525 |
|
|---|
| 526 | if (options.omitSuffix) {
|
|---|
| 527 | return '';
|
|---|
| 528 | }
|
|---|
| 529 |
|
|---|
| 530 | return 'ip6.arpa.';
|
|---|
| 531 | }
|
|---|
| 532 |
|
|---|
| 533 | /**
|
|---|
| 534 | * Return the correct form of the address
|
|---|
| 535 | * @memberof Address6
|
|---|
| 536 | * @instance
|
|---|
| 537 | * @returns {String}
|
|---|
| 538 | */
|
|---|
| 539 | correctForm(): string {
|
|---|
| 540 | let i;
|
|---|
| 541 | let groups = [];
|
|---|
| 542 |
|
|---|
| 543 | let zeroCounter = 0;
|
|---|
| 544 | const zeroes = [];
|
|---|
| 545 |
|
|---|
| 546 | for (i = 0; i < this.parsedAddress.length; i++) {
|
|---|
| 547 | const value = parseInt(this.parsedAddress[i], 16);
|
|---|
| 548 |
|
|---|
| 549 | if (value === 0) {
|
|---|
| 550 | zeroCounter++;
|
|---|
| 551 | }
|
|---|
| 552 |
|
|---|
| 553 | if (value !== 0 && zeroCounter > 0) {
|
|---|
| 554 | if (zeroCounter > 1) {
|
|---|
| 555 | zeroes.push([i - zeroCounter, i - 1]);
|
|---|
| 556 | }
|
|---|
| 557 |
|
|---|
| 558 | zeroCounter = 0;
|
|---|
| 559 | }
|
|---|
| 560 | }
|
|---|
| 561 |
|
|---|
| 562 | // Do we end with a string of zeroes?
|
|---|
| 563 | if (zeroCounter > 1) {
|
|---|
| 564 | zeroes.push([this.parsedAddress.length - zeroCounter, this.parsedAddress.length - 1]);
|
|---|
| 565 | }
|
|---|
| 566 |
|
|---|
| 567 | const zeroLengths = zeroes.map((n) => n[1] - n[0] + 1);
|
|---|
| 568 |
|
|---|
| 569 | if (zeroes.length > 0) {
|
|---|
| 570 | const index = zeroLengths.indexOf(Math.max(...zeroLengths) as number);
|
|---|
| 571 |
|
|---|
| 572 | groups = compact(this.parsedAddress, zeroes[index]);
|
|---|
| 573 | } else {
|
|---|
| 574 | groups = this.parsedAddress;
|
|---|
| 575 | }
|
|---|
| 576 |
|
|---|
| 577 | for (i = 0; i < groups.length; i++) {
|
|---|
| 578 | if (groups[i] !== 'compact') {
|
|---|
| 579 | groups[i] = parseInt(groups[i], 16).toString(16);
|
|---|
| 580 | }
|
|---|
| 581 | }
|
|---|
| 582 |
|
|---|
| 583 | let correct = groups.join(':');
|
|---|
| 584 |
|
|---|
| 585 | correct = correct.replace(/^compact$/, '::');
|
|---|
| 586 | correct = correct.replace(/(^compact)|(compact$)/, ':');
|
|---|
| 587 | correct = correct.replace(/compact/, '');
|
|---|
| 588 |
|
|---|
| 589 | return correct;
|
|---|
| 590 | }
|
|---|
| 591 |
|
|---|
| 592 | /**
|
|---|
| 593 | * Return a zero-padded base-2 string representation of the address
|
|---|
| 594 | * @memberof Address6
|
|---|
| 595 | * @instance
|
|---|
| 596 | * @returns {String}
|
|---|
| 597 | * @example
|
|---|
| 598 | * var address = new Address6('2001:4860:4001:803::1011');
|
|---|
| 599 | * address.binaryZeroPad();
|
|---|
| 600 | * // '0010000000000001010010000110000001000000000000010000100000000011
|
|---|
| 601 | * // 0000000000000000000000000000000000000000000000000001000000010001'
|
|---|
| 602 | */
|
|---|
| 603 | binaryZeroPad(): string {
|
|---|
| 604 | return this.bigInt().toString(2).padStart(constants6.BITS, '0');
|
|---|
| 605 | }
|
|---|
| 606 |
|
|---|
| 607 | // TODO: Improve the semantics of this helper function
|
|---|
| 608 | parse4in6(address: string): string {
|
|---|
| 609 | const groups = address.split(':');
|
|---|
| 610 | const lastGroup = groups.slice(-1)[0];
|
|---|
| 611 |
|
|---|
| 612 | const address4 = lastGroup.match(constants4.RE_ADDRESS);
|
|---|
| 613 |
|
|---|
| 614 | if (address4) {
|
|---|
| 615 | this.parsedAddress4 = address4[0];
|
|---|
| 616 | this.address4 = new Address4(this.parsedAddress4);
|
|---|
| 617 |
|
|---|
| 618 | for (let i = 0; i < this.address4.groups; i++) {
|
|---|
| 619 | if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) {
|
|---|
| 620 | throw new AddressError(
|
|---|
| 621 | "IPv4 addresses can't have leading zeroes.",
|
|---|
| 622 | address.replace(
|
|---|
| 623 | constants4.RE_ADDRESS,
|
|---|
| 624 | this.address4.parsedAddress.map(spanLeadingZeroes4).join('.'),
|
|---|
| 625 | ),
|
|---|
| 626 | );
|
|---|
| 627 | }
|
|---|
| 628 | }
|
|---|
| 629 |
|
|---|
| 630 | this.v4 = true;
|
|---|
| 631 |
|
|---|
| 632 | groups[groups.length - 1] = this.address4.toGroup6();
|
|---|
| 633 |
|
|---|
| 634 | address = groups.join(':');
|
|---|
| 635 | }
|
|---|
| 636 |
|
|---|
| 637 | return address;
|
|---|
| 638 | }
|
|---|
| 639 |
|
|---|
| 640 | // TODO: Make private?
|
|---|
| 641 | parse(address: string): string[] {
|
|---|
| 642 | address = this.parse4in6(address);
|
|---|
| 643 |
|
|---|
| 644 | const badCharacters = address.match(constants6.RE_BAD_CHARACTERS);
|
|---|
| 645 |
|
|---|
| 646 | if (badCharacters) {
|
|---|
| 647 | throw new AddressError(
|
|---|
| 648 | `Bad character${
|
|---|
| 649 | badCharacters.length > 1 ? 's' : ''
|
|---|
| 650 | } detected in address: ${badCharacters.join('')}`,
|
|---|
| 651 | address.replace(constants6.RE_BAD_CHARACTERS, '<span class="parse-error">$1</span>'),
|
|---|
| 652 | );
|
|---|
| 653 | }
|
|---|
| 654 |
|
|---|
| 655 | const badAddress = address.match(constants6.RE_BAD_ADDRESS);
|
|---|
| 656 |
|
|---|
| 657 | if (badAddress) {
|
|---|
| 658 | throw new AddressError(
|
|---|
| 659 | `Address failed regex: ${badAddress.join('')}`,
|
|---|
| 660 | address.replace(constants6.RE_BAD_ADDRESS, '<span class="parse-error">$1</span>'),
|
|---|
| 661 | );
|
|---|
| 662 | }
|
|---|
| 663 |
|
|---|
| 664 | let groups: string[] = [];
|
|---|
| 665 |
|
|---|
| 666 | const halves = address.split('::');
|
|---|
| 667 |
|
|---|
| 668 | if (halves.length === 2) {
|
|---|
| 669 | let first = halves[0].split(':');
|
|---|
| 670 | let last = halves[1].split(':');
|
|---|
| 671 |
|
|---|
| 672 | if (first.length === 1 && first[0] === '') {
|
|---|
| 673 | first = [];
|
|---|
| 674 | }
|
|---|
| 675 |
|
|---|
| 676 | if (last.length === 1 && last[0] === '') {
|
|---|
| 677 | last = [];
|
|---|
| 678 | }
|
|---|
| 679 |
|
|---|
| 680 | const remaining = this.groups - (first.length + last.length);
|
|---|
| 681 |
|
|---|
| 682 | if (!remaining) {
|
|---|
| 683 | throw new AddressError('Error parsing groups');
|
|---|
| 684 | }
|
|---|
| 685 |
|
|---|
| 686 | this.elidedGroups = remaining;
|
|---|
| 687 |
|
|---|
| 688 | this.elisionBegin = first.length;
|
|---|
| 689 | this.elisionEnd = first.length + this.elidedGroups;
|
|---|
| 690 |
|
|---|
| 691 | groups = groups.concat(first);
|
|---|
| 692 |
|
|---|
| 693 | for (let i = 0; i < remaining; i++) {
|
|---|
| 694 | groups.push('0');
|
|---|
| 695 | }
|
|---|
| 696 |
|
|---|
| 697 | groups = groups.concat(last);
|
|---|
| 698 | } else if (halves.length === 1) {
|
|---|
| 699 | groups = address.split(':');
|
|---|
| 700 |
|
|---|
| 701 | this.elidedGroups = 0;
|
|---|
| 702 | } else {
|
|---|
| 703 | throw new AddressError('Too many :: groups found');
|
|---|
| 704 | }
|
|---|
| 705 |
|
|---|
| 706 | groups = groups.map((group: string) => parseInt(group, 16).toString(16));
|
|---|
| 707 |
|
|---|
| 708 | if (groups.length !== this.groups) {
|
|---|
| 709 | throw new AddressError('Incorrect number of groups found');
|
|---|
| 710 | }
|
|---|
| 711 |
|
|---|
| 712 | return groups;
|
|---|
| 713 | }
|
|---|
| 714 |
|
|---|
| 715 | /**
|
|---|
| 716 | * Return the canonical form of the address
|
|---|
| 717 | * @memberof Address6
|
|---|
| 718 | * @instance
|
|---|
| 719 | * @returns {String}
|
|---|
| 720 | */
|
|---|
| 721 | canonicalForm(): string {
|
|---|
| 722 | return this.parsedAddress.map(paddedHex).join(':');
|
|---|
| 723 | }
|
|---|
| 724 |
|
|---|
| 725 | /**
|
|---|
| 726 | * Return the decimal form of the address
|
|---|
| 727 | * @memberof Address6
|
|---|
| 728 | * @instance
|
|---|
| 729 | * @returns {String}
|
|---|
| 730 | */
|
|---|
| 731 | decimal(): string {
|
|---|
| 732 | return this.parsedAddress.map((n) => parseInt(n, 16).toString(10).padStart(5, '0')).join(':');
|
|---|
| 733 | }
|
|---|
| 734 |
|
|---|
| 735 | /**
|
|---|
| 736 | * Return the address as a BigInt
|
|---|
| 737 | * @memberof Address6
|
|---|
| 738 | * @instance
|
|---|
| 739 | * @returns {bigint}
|
|---|
| 740 | */
|
|---|
| 741 | bigInt(): bigint {
|
|---|
| 742 | return BigInt(`0x${this.parsedAddress.map(paddedHex).join('')}`);
|
|---|
| 743 | }
|
|---|
| 744 |
|
|---|
| 745 | /**
|
|---|
| 746 | * Return the last two groups of this address as an IPv4 address string
|
|---|
| 747 | * @memberof Address6
|
|---|
| 748 | * @instance
|
|---|
| 749 | * @returns {Address4}
|
|---|
| 750 | * @example
|
|---|
| 751 | * var address = new Address6('2001:4860:4001::1825:bf11');
|
|---|
| 752 | * address.to4().correctForm(); // '24.37.191.17'
|
|---|
| 753 | */
|
|---|
| 754 | to4(): Address4 {
|
|---|
| 755 | const binary = this.binaryZeroPad().split('');
|
|---|
| 756 |
|
|---|
| 757 | return Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16));
|
|---|
| 758 | }
|
|---|
| 759 |
|
|---|
| 760 | /**
|
|---|
| 761 | * Return the v4-in-v6 form of the address
|
|---|
| 762 | * @memberof Address6
|
|---|
| 763 | * @instance
|
|---|
| 764 | * @returns {String}
|
|---|
| 765 | */
|
|---|
| 766 | to4in6(): string {
|
|---|
| 767 | const address4 = this.to4();
|
|---|
| 768 | const address6 = new Address6(this.parsedAddress.slice(0, 6).join(':'), 6);
|
|---|
| 769 |
|
|---|
| 770 | const correct = address6.correctForm();
|
|---|
| 771 |
|
|---|
| 772 | let infix = '';
|
|---|
| 773 |
|
|---|
| 774 | if (!/:$/.test(correct)) {
|
|---|
| 775 | infix = ':';
|
|---|
| 776 | }
|
|---|
| 777 |
|
|---|
| 778 | return correct + infix + address4.address;
|
|---|
| 779 | }
|
|---|
| 780 |
|
|---|
| 781 | /**
|
|---|
| 782 | * Return an object containing the Teredo properties of the address
|
|---|
| 783 | * @memberof Address6
|
|---|
| 784 | * @instance
|
|---|
| 785 | * @returns {Object}
|
|---|
| 786 | */
|
|---|
| 787 | inspectTeredo(): TeredoProperties {
|
|---|
| 788 | /*
|
|---|
| 789 | - Bits 0 to 31 are set to the Teredo prefix (normally 2001:0000::/32).
|
|---|
| 790 | - Bits 32 to 63 embed the primary IPv4 address of the Teredo server that
|
|---|
| 791 | is used.
|
|---|
| 792 | - Bits 64 to 79 can be used to define some flags. Currently only the
|
|---|
| 793 | higher order bit is used; it is set to 1 if the Teredo client is
|
|---|
| 794 | located behind a cone NAT, 0 otherwise. For Microsoft's Windows Vista
|
|---|
| 795 | and Windows Server 2008 implementations, more bits are used. In those
|
|---|
| 796 | implementations, the format for these 16 bits is "CRAAAAUG AAAAAAAA",
|
|---|
| 797 | where "C" remains the "Cone" flag. The "R" bit is reserved for future
|
|---|
| 798 | use. The "U" bit is for the Universal/Local flag (set to 0). The "G" bit
|
|---|
| 799 | is Individual/Group flag (set to 0). The A bits are set to a 12-bit
|
|---|
| 800 | randomly generated number chosen by the Teredo client to introduce
|
|---|
| 801 | additional protection for the Teredo node against IPv6-based scanning
|
|---|
| 802 | attacks.
|
|---|
| 803 | - Bits 80 to 95 contains the obfuscated UDP port number. This is the
|
|---|
| 804 | port number that is mapped by the NAT to the Teredo client with all
|
|---|
| 805 | bits inverted.
|
|---|
| 806 | - Bits 96 to 127 contains the obfuscated IPv4 address. This is the
|
|---|
| 807 | public IPv4 address of the NAT with all bits inverted.
|
|---|
| 808 | */
|
|---|
| 809 | const prefix = this.getBitsBase16(0, 32);
|
|---|
| 810 |
|
|---|
| 811 | const bitsForUdpPort: bigint = this.getBits(80, 96);
|
|---|
| 812 | // eslint-disable-next-line no-bitwise
|
|---|
| 813 | const udpPort = (bitsForUdpPort ^ BigInt('0xffff')).toString();
|
|---|
| 814 |
|
|---|
| 815 | const server4 = Address4.fromHex(this.getBitsBase16(32, 64));
|
|---|
| 816 |
|
|---|
| 817 | const bitsForClient4 = this.getBits(96, 128);
|
|---|
| 818 | // eslint-disable-next-line no-bitwise
|
|---|
| 819 | const client4 = Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16));
|
|---|
| 820 |
|
|---|
| 821 | const flagsBase2 = this.getBitsBase2(64, 80);
|
|---|
| 822 |
|
|---|
| 823 | const coneNat = testBit(flagsBase2, 15);
|
|---|
| 824 | const reserved = testBit(flagsBase2, 14);
|
|---|
| 825 | const groupIndividual = testBit(flagsBase2, 8);
|
|---|
| 826 | const universalLocal = testBit(flagsBase2, 9);
|
|---|
| 827 | const nonce = BigInt(`0b${flagsBase2.slice(2, 6) + flagsBase2.slice(8, 16)}`).toString(10);
|
|---|
| 828 |
|
|---|
| 829 | return {
|
|---|
| 830 | prefix: `${prefix.slice(0, 4)}:${prefix.slice(4, 8)}`,
|
|---|
| 831 | server4: server4.address,
|
|---|
| 832 | client4: client4.address,
|
|---|
| 833 | flags: flagsBase2,
|
|---|
| 834 | coneNat,
|
|---|
| 835 | microsoft: {
|
|---|
| 836 | reserved,
|
|---|
| 837 | universalLocal,
|
|---|
| 838 | groupIndividual,
|
|---|
| 839 | nonce,
|
|---|
| 840 | },
|
|---|
| 841 | udpPort,
|
|---|
| 842 | };
|
|---|
| 843 | }
|
|---|
| 844 |
|
|---|
| 845 | /**
|
|---|
| 846 | * Return an object containing the 6to4 properties of the address
|
|---|
| 847 | * @memberof Address6
|
|---|
| 848 | * @instance
|
|---|
| 849 | * @returns {Object}
|
|---|
| 850 | */
|
|---|
| 851 | inspect6to4(): SixToFourProperties {
|
|---|
| 852 | /*
|
|---|
| 853 | - Bits 0 to 15 are set to the 6to4 prefix (2002::/16).
|
|---|
| 854 | - Bits 16 to 48 embed the IPv4 address of the 6to4 gateway that is used.
|
|---|
| 855 | */
|
|---|
| 856 |
|
|---|
| 857 | const prefix = this.getBitsBase16(0, 16);
|
|---|
| 858 |
|
|---|
| 859 | const gateway = Address4.fromHex(this.getBitsBase16(16, 48));
|
|---|
| 860 |
|
|---|
| 861 | return {
|
|---|
| 862 | prefix: prefix.slice(0, 4),
|
|---|
| 863 | gateway: gateway.address,
|
|---|
| 864 | };
|
|---|
| 865 | }
|
|---|
| 866 |
|
|---|
| 867 | /**
|
|---|
| 868 | * Return a v6 6to4 address from a v6 v4inv6 address
|
|---|
| 869 | * @memberof Address6
|
|---|
| 870 | * @instance
|
|---|
| 871 | * @returns {Address6}
|
|---|
| 872 | */
|
|---|
| 873 | to6to4(): Address6 | null {
|
|---|
| 874 | if (!this.is4()) {
|
|---|
| 875 | return null;
|
|---|
| 876 | }
|
|---|
| 877 |
|
|---|
| 878 | const addr6to4 = [
|
|---|
| 879 | '2002',
|
|---|
| 880 | this.getBitsBase16(96, 112),
|
|---|
| 881 | this.getBitsBase16(112, 128),
|
|---|
| 882 | '',
|
|---|
| 883 | '/16',
|
|---|
| 884 | ].join(':');
|
|---|
| 885 |
|
|---|
| 886 | return new Address6(addr6to4);
|
|---|
| 887 | }
|
|---|
| 888 |
|
|---|
| 889 | /**
|
|---|
| 890 | * Return a byte array
|
|---|
| 891 | * @memberof Address6
|
|---|
| 892 | * @instance
|
|---|
| 893 | * @returns {Array}
|
|---|
| 894 | */
|
|---|
| 895 | toByteArray(): number[] {
|
|---|
| 896 | const valueWithoutPadding = this.bigInt().toString(16);
|
|---|
| 897 | const leadingPad = '0'.repeat(valueWithoutPadding.length % 2);
|
|---|
| 898 |
|
|---|
| 899 | const value = `${leadingPad}${valueWithoutPadding}`;
|
|---|
| 900 |
|
|---|
| 901 | const bytes = [];
|
|---|
| 902 | for (let i = 0, length = value.length; i < length; i += 2) {
|
|---|
| 903 | bytes.push(parseInt(value.substring(i, i + 2), 16));
|
|---|
| 904 | }
|
|---|
| 905 |
|
|---|
| 906 | return bytes;
|
|---|
| 907 | }
|
|---|
| 908 |
|
|---|
| 909 | /**
|
|---|
| 910 | * Return an unsigned byte array
|
|---|
| 911 | * @memberof Address6
|
|---|
| 912 | * @instance
|
|---|
| 913 | * @returns {Array}
|
|---|
| 914 | */
|
|---|
| 915 | toUnsignedByteArray(): number[] {
|
|---|
| 916 | return this.toByteArray().map(unsignByte);
|
|---|
| 917 | }
|
|---|
| 918 |
|
|---|
| 919 | /**
|
|---|
| 920 | * Convert a byte array to an Address6 object
|
|---|
| 921 | * @memberof Address6
|
|---|
| 922 | * @static
|
|---|
| 923 | * @returns {Address6}
|
|---|
| 924 | */
|
|---|
| 925 | static fromByteArray(bytes: Array<any>): Address6 {
|
|---|
| 926 | return this.fromUnsignedByteArray(bytes.map(unsignByte));
|
|---|
| 927 | }
|
|---|
| 928 |
|
|---|
| 929 | /**
|
|---|
| 930 | * Convert an unsigned byte array to an Address6 object
|
|---|
| 931 | * @memberof Address6
|
|---|
| 932 | * @static
|
|---|
| 933 | * @returns {Address6}
|
|---|
| 934 | */
|
|---|
| 935 | static fromUnsignedByteArray(bytes: Array<any>): Address6 {
|
|---|
| 936 | const BYTE_MAX = BigInt('256');
|
|---|
| 937 | let result = BigInt('0');
|
|---|
| 938 | let multiplier = BigInt('1');
|
|---|
| 939 |
|
|---|
| 940 | for (let i = bytes.length - 1; i >= 0; i--) {
|
|---|
| 941 | result += multiplier * BigInt(bytes[i].toString(10));
|
|---|
| 942 |
|
|---|
| 943 | multiplier *= BYTE_MAX;
|
|---|
| 944 | }
|
|---|
| 945 |
|
|---|
| 946 | return Address6.fromBigInt(result);
|
|---|
| 947 | }
|
|---|
| 948 |
|
|---|
| 949 | // #region Attributes
|
|---|
| 950 | /**
|
|---|
| 951 | * Returns true if the given address is in the subnet of the current address
|
|---|
| 952 | * @memberof Address6
|
|---|
| 953 | * @instance
|
|---|
| 954 | * @returns {boolean}
|
|---|
| 955 | */
|
|---|
| 956 | isInSubnet = common.isInSubnet;
|
|---|
| 957 |
|
|---|
| 958 | /**
|
|---|
| 959 | * Returns true if the address is correct, false otherwise
|
|---|
| 960 | * @memberof Address6
|
|---|
| 961 | * @instance
|
|---|
| 962 | * @returns {boolean}
|
|---|
| 963 | */
|
|---|
| 964 | isCorrect = common.isCorrect(constants6.BITS);
|
|---|
| 965 |
|
|---|
| 966 | /**
|
|---|
| 967 | * Returns true if the address is in the canonical form, false otherwise
|
|---|
| 968 | * @memberof Address6
|
|---|
| 969 | * @instance
|
|---|
| 970 | * @returns {boolean}
|
|---|
| 971 | */
|
|---|
| 972 | isCanonical(): boolean {
|
|---|
| 973 | return this.addressMinusSuffix === this.canonicalForm();
|
|---|
| 974 | }
|
|---|
| 975 |
|
|---|
| 976 | /**
|
|---|
| 977 | * Returns true if the address is a link local address, false otherwise
|
|---|
| 978 | * @memberof Address6
|
|---|
| 979 | * @instance
|
|---|
| 980 | * @returns {boolean}
|
|---|
| 981 | */
|
|---|
| 982 | isLinkLocal(): boolean {
|
|---|
| 983 | // Zeroes are required, i.e. we can't check isInSubnet with 'fe80::/10'
|
|---|
| 984 | if (
|
|---|
| 985 | this.getBitsBase2(0, 64) ===
|
|---|
| 986 | '1111111010000000000000000000000000000000000000000000000000000000'
|
|---|
| 987 | ) {
|
|---|
| 988 | return true;
|
|---|
| 989 | }
|
|---|
| 990 |
|
|---|
| 991 | return false;
|
|---|
| 992 | }
|
|---|
| 993 |
|
|---|
| 994 | /**
|
|---|
| 995 | * Returns true if the address is a multicast address, false otherwise
|
|---|
| 996 | * @memberof Address6
|
|---|
| 997 | * @instance
|
|---|
| 998 | * @returns {boolean}
|
|---|
| 999 | */
|
|---|
| 1000 | isMulticast(): boolean {
|
|---|
| 1001 | return this.getType() === 'Multicast';
|
|---|
| 1002 | }
|
|---|
| 1003 |
|
|---|
| 1004 | /**
|
|---|
| 1005 | * Returns true if the address is a v4-in-v6 address, false otherwise
|
|---|
| 1006 | * @memberof Address6
|
|---|
| 1007 | * @instance
|
|---|
| 1008 | * @returns {boolean}
|
|---|
| 1009 | */
|
|---|
| 1010 | is4(): boolean {
|
|---|
| 1011 | return this.v4;
|
|---|
| 1012 | }
|
|---|
| 1013 |
|
|---|
| 1014 | /**
|
|---|
| 1015 | * Returns true if the address is a Teredo address, false otherwise
|
|---|
| 1016 | * @memberof Address6
|
|---|
| 1017 | * @instance
|
|---|
| 1018 | * @returns {boolean}
|
|---|
| 1019 | */
|
|---|
| 1020 | isTeredo(): boolean {
|
|---|
| 1021 | return this.isInSubnet(new Address6('2001::/32'));
|
|---|
| 1022 | }
|
|---|
| 1023 |
|
|---|
| 1024 | /**
|
|---|
| 1025 | * Returns true if the address is a 6to4 address, false otherwise
|
|---|
| 1026 | * @memberof Address6
|
|---|
| 1027 | * @instance
|
|---|
| 1028 | * @returns {boolean}
|
|---|
| 1029 | */
|
|---|
| 1030 | is6to4(): boolean {
|
|---|
| 1031 | return this.isInSubnet(new Address6('2002::/16'));
|
|---|
| 1032 | }
|
|---|
| 1033 |
|
|---|
| 1034 | /**
|
|---|
| 1035 | * Returns true if the address is a loopback address, false otherwise
|
|---|
| 1036 | * @memberof Address6
|
|---|
| 1037 | * @instance
|
|---|
| 1038 | * @returns {boolean}
|
|---|
| 1039 | */
|
|---|
| 1040 | isLoopback(): boolean {
|
|---|
| 1041 | return this.getType() === 'Loopback';
|
|---|
| 1042 | }
|
|---|
| 1043 | // #endregion
|
|---|
| 1044 |
|
|---|
| 1045 | // #region HTML
|
|---|
| 1046 | /**
|
|---|
| 1047 | * @returns {String} the address in link form with a default port of 80
|
|---|
| 1048 | */
|
|---|
| 1049 | href(optionalPort?: number | string): string {
|
|---|
| 1050 | if (optionalPort === undefined) {
|
|---|
| 1051 | optionalPort = '';
|
|---|
| 1052 | } else {
|
|---|
| 1053 | optionalPort = `:${optionalPort}`;
|
|---|
| 1054 | }
|
|---|
| 1055 |
|
|---|
| 1056 | return `http://[${this.correctForm()}]${optionalPort}/`;
|
|---|
| 1057 | }
|
|---|
| 1058 |
|
|---|
| 1059 | /**
|
|---|
| 1060 | * @returns {String} a link suitable for conveying the address via a URL hash
|
|---|
| 1061 | */
|
|---|
| 1062 | link(options?: { className?: string; prefix?: string; v4?: boolean }): string {
|
|---|
| 1063 | if (!options) {
|
|---|
| 1064 | options = {};
|
|---|
| 1065 | }
|
|---|
| 1066 |
|
|---|
| 1067 | if (options.className === undefined) {
|
|---|
| 1068 | options.className = '';
|
|---|
| 1069 | }
|
|---|
| 1070 |
|
|---|
| 1071 | if (options.prefix === undefined) {
|
|---|
| 1072 | options.prefix = '/#address=';
|
|---|
| 1073 | }
|
|---|
| 1074 |
|
|---|
| 1075 | if (options.v4 === undefined) {
|
|---|
| 1076 | options.v4 = false;
|
|---|
| 1077 | }
|
|---|
| 1078 |
|
|---|
| 1079 | let formFunction = this.correctForm;
|
|---|
| 1080 |
|
|---|
| 1081 | if (options.v4) {
|
|---|
| 1082 | formFunction = this.to4in6;
|
|---|
| 1083 | }
|
|---|
| 1084 |
|
|---|
| 1085 | const form = formFunction.call(this);
|
|---|
| 1086 |
|
|---|
| 1087 | if (options.className) {
|
|---|
| 1088 | return `<a href="${options.prefix}${form}" class="${options.className}">${form}</a>`;
|
|---|
| 1089 | }
|
|---|
| 1090 |
|
|---|
| 1091 | return `<a href="${options.prefix}${form}">${form}</a>`;
|
|---|
| 1092 | }
|
|---|
| 1093 |
|
|---|
| 1094 | /**
|
|---|
| 1095 | * Groups an address
|
|---|
| 1096 | * @returns {String}
|
|---|
| 1097 | */
|
|---|
| 1098 | group(): string {
|
|---|
| 1099 | if (this.elidedGroups === 0) {
|
|---|
| 1100 | // The simple case
|
|---|
| 1101 | return helpers.simpleGroup(this.address).join(':');
|
|---|
| 1102 | }
|
|---|
| 1103 |
|
|---|
| 1104 | assert(typeof this.elidedGroups === 'number');
|
|---|
| 1105 | assert(typeof this.elisionBegin === 'number');
|
|---|
| 1106 |
|
|---|
| 1107 | // The elided case
|
|---|
| 1108 | const output = [];
|
|---|
| 1109 |
|
|---|
| 1110 | const [left, right] = this.address.split('::');
|
|---|
| 1111 |
|
|---|
| 1112 | if (left.length) {
|
|---|
| 1113 | output.push(...helpers.simpleGroup(left));
|
|---|
| 1114 | } else {
|
|---|
| 1115 | output.push('');
|
|---|
| 1116 | }
|
|---|
| 1117 |
|
|---|
| 1118 | const classes = ['hover-group'];
|
|---|
| 1119 |
|
|---|
| 1120 | for (let i = this.elisionBegin; i < this.elisionBegin + this.elidedGroups; i++) {
|
|---|
| 1121 | classes.push(`group-${i}`);
|
|---|
| 1122 | }
|
|---|
| 1123 |
|
|---|
| 1124 | output.push(`<span class="${classes.join(' ')}"></span>`);
|
|---|
| 1125 |
|
|---|
| 1126 | if (right.length) {
|
|---|
| 1127 | output.push(...helpers.simpleGroup(right, this.elisionEnd));
|
|---|
| 1128 | } else {
|
|---|
| 1129 | output.push('');
|
|---|
| 1130 | }
|
|---|
| 1131 |
|
|---|
| 1132 | if (this.is4()) {
|
|---|
| 1133 | assert(this.address4 instanceof Address4);
|
|---|
| 1134 |
|
|---|
| 1135 | output.pop();
|
|---|
| 1136 | output.push(this.address4.groupForV6());
|
|---|
| 1137 | }
|
|---|
| 1138 |
|
|---|
| 1139 | return output.join(':');
|
|---|
| 1140 | }
|
|---|
| 1141 | // #endregion
|
|---|
| 1142 |
|
|---|
| 1143 | // #region Regular expressions
|
|---|
| 1144 | /**
|
|---|
| 1145 | * Generate a regular expression string that can be used to find or validate
|
|---|
| 1146 | * all variations of this address
|
|---|
| 1147 | * @memberof Address6
|
|---|
| 1148 | * @instance
|
|---|
| 1149 | * @param {boolean} substringSearch
|
|---|
| 1150 | * @returns {string}
|
|---|
| 1151 | */
|
|---|
| 1152 | regularExpressionString(this: Address6, substringSearch: boolean = false): string {
|
|---|
| 1153 | let output: string[] = [];
|
|---|
| 1154 |
|
|---|
| 1155 | // TODO: revisit why this is necessary
|
|---|
| 1156 | const address6 = new Address6(this.correctForm());
|
|---|
| 1157 |
|
|---|
| 1158 | if (address6.elidedGroups === 0) {
|
|---|
| 1159 | // The simple case
|
|---|
| 1160 | output.push(simpleRegularExpression(address6.parsedAddress));
|
|---|
| 1161 | } else if (address6.elidedGroups === constants6.GROUPS) {
|
|---|
| 1162 | // A completely elided address
|
|---|
| 1163 | output.push(possibleElisions(constants6.GROUPS));
|
|---|
| 1164 | } else {
|
|---|
| 1165 | // A partially elided address
|
|---|
| 1166 | const halves = address6.address.split('::');
|
|---|
| 1167 |
|
|---|
| 1168 | if (halves[0].length) {
|
|---|
| 1169 | output.push(simpleRegularExpression(halves[0].split(':')));
|
|---|
| 1170 | }
|
|---|
| 1171 |
|
|---|
| 1172 | assert(typeof address6.elidedGroups === 'number');
|
|---|
| 1173 |
|
|---|
| 1174 | output.push(
|
|---|
| 1175 | possibleElisions(address6.elidedGroups, halves[0].length !== 0, halves[1].length !== 0),
|
|---|
| 1176 | );
|
|---|
| 1177 |
|
|---|
| 1178 | if (halves[1].length) {
|
|---|
| 1179 | output.push(simpleRegularExpression(halves[1].split(':')));
|
|---|
| 1180 | }
|
|---|
| 1181 |
|
|---|
| 1182 | output = [output.join(':')];
|
|---|
| 1183 | }
|
|---|
| 1184 |
|
|---|
| 1185 | if (!substringSearch) {
|
|---|
| 1186 | output = [
|
|---|
| 1187 | '(?=^|',
|
|---|
| 1188 | ADDRESS_BOUNDARY,
|
|---|
| 1189 | '|[^\\w\\:])(',
|
|---|
| 1190 | ...output,
|
|---|
| 1191 | ')(?=[^\\w\\:]|',
|
|---|
| 1192 | ADDRESS_BOUNDARY,
|
|---|
| 1193 | '|$)',
|
|---|
| 1194 | ];
|
|---|
| 1195 | }
|
|---|
| 1196 |
|
|---|
| 1197 | return output.join('');
|
|---|
| 1198 | }
|
|---|
| 1199 |
|
|---|
| 1200 | /**
|
|---|
| 1201 | * Generate a regular expression that can be used to find or validate all
|
|---|
| 1202 | * variations of this address.
|
|---|
| 1203 | * @memberof Address6
|
|---|
| 1204 | * @instance
|
|---|
| 1205 | * @param {boolean} substringSearch
|
|---|
| 1206 | * @returns {RegExp}
|
|---|
| 1207 | */
|
|---|
| 1208 | regularExpression(this: Address6, substringSearch: boolean = false): RegExp {
|
|---|
| 1209 | return new RegExp(this.regularExpressionString(substringSearch), 'i');
|
|---|
| 1210 | }
|
|---|
| 1211 | // #endregion
|
|---|
| 1212 | }
|
|---|