source: node_modules/ip-address/src/ipv6.ts@ 81bc7da

finki-main main
Last change on this file since 81bc7da was 81bc7da, checked in by Klimentina Efremova <klimentina08642@…>, 3 months ago

Initial commit

  • Property mode set to 100644
File size: 30.1 KB
Line 
1/* eslint-disable prefer-destructuring */
2/* eslint-disable no-param-reassign */
3
4import * as common from './common';
5import * as constants4 from './v4/constants';
6import * as constants6 from './v6/constants';
7import * as helpers from './v6/helpers';
8import { Address4 } from './ipv4';
9import {
10 ADDRESS_BOUNDARY,
11 possibleElisions,
12 simpleRegularExpression,
13} from './v6/regular-expressions';
14import { AddressError } from './address-error';
15import { testBit } from './common';
16
17function assert(condition: any): asserts condition {
18 if (!condition) {
19 throw new Error('Assertion failed.');
20 }
21}
22
23function 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
33function 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 */
43function 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
59function paddedHex(octet: string): string {
60 return parseInt(octet, 16).toString(16).padStart(4, '0');
61}
62
63function unsignByte(b: number) {
64 // eslint-disable-next-line no-bitwise
65 return b & 0xff;
66}
67
68interface SixToFourProperties {
69 prefix: string;
70 gateway: string;
71}
72
73interface 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 */
96export 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}
Note: See TracBrowser for help on using the repository browser.