|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
|
new Address6(address); |
|
|
|
|
|
return true; |
|
|
} catch (e) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(':')); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static fromURL(url: string) { |
|
|
let host: string; |
|
|
let port: string | number | null = null; |
|
|
let result: string[] | null; |
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
|
} else if (url.indexOf('/') !== -1) { |
|
|
|
|
|
url = url.replace(/^[a-z0-9]+:\/\//, ''); |
|
|
|
|
|
|
|
|
result = constants6.RE_URL.exec(url); |
|
|
|
|
|
if (result === null) { |
|
|
return { |
|
|
error: 'failed to parse address from URL', |
|
|
address: null, |
|
|
port: null, |
|
|
}; |
|
|
} |
|
|
|
|
|
host = result[1]; |
|
|
|
|
|
} else { |
|
|
host = url; |
|
|
} |
|
|
|
|
|
|
|
|
if (port) { |
|
|
port = parseInt(port, 10); |
|
|
|
|
|
|
|
|
if (port < 0 || port > 65536) { |
|
|
port = null; |
|
|
} |
|
|
} else { |
|
|
|
|
|
port = null; |
|
|
} |
|
|
|
|
|
return { |
|
|
address: new Address6(host), |
|
|
port, |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static fromArpa(arpaFormAddress: string): Address6 { |
|
|
|
|
|
let address = arpaFormAddress.replace(/(\.ip6\.arpa)?\.$/, ''); |
|
|
const semicolonAmount = 7; |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
microsoftTranscription(): string { |
|
|
return `${this.correctForm().replace(/:/g, '-')}.ipv6-literal.net`; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mask(mask: number = this.subnetMask): string { |
|
|
return this.getBitsBase2(0, mask); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_startAddress(): bigint { |
|
|
return BigInt(`0b${this.mask() + '0'.repeat(constants6.BITS - this.subnetMask)}`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
startAddress(): Address6 { |
|
|
return Address6.fromBigInt(this._startAddress()); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
startAddressExclusive(): Address6 { |
|
|
const adjust = BigInt('1'); |
|
|
return Address6.fromBigInt(this._startAddress() + adjust); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_endAddress(): bigint { |
|
|
return BigInt(`0b${this.mask() + '1'.repeat(constants6.BITS - this.subnetMask)}`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
endAddress(): Address6 { |
|
|
return Address6.fromBigInt(this._endAddress()); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
endAddressExclusive(): Address6 { |
|
|
const adjust = BigInt('1'); |
|
|
return Address6.fromBigInt(this._endAddress() - adjust); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getBits(start: number, end: number): bigint { |
|
|
return BigInt(`0b${this.getBitsBase2(start, end)}`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getBitsBase2(start: number, end: number): string { |
|
|
return this.binaryZeroPad().slice(start, end); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getBitsPastSubnet(): string { |
|
|
return this.getBitsBase2(this.subnetMask, constants6.BITS); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
binaryZeroPad(): string { |
|
|
return this.bigInt().toString(2).padStart(constants6.BITS, '0'); |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
canonicalForm(): string { |
|
|
return this.parsedAddress.map(paddedHex).join(':'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
decimal(): string { |
|
|
return this.parsedAddress.map((n) => parseInt(n, 16).toString(10).padStart(5, '0')).join(':'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bigInt(): bigint { |
|
|
return BigInt(`0x${this.parsedAddress.map(paddedHex).join('')}`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
to4(): Address4 { |
|
|
const binary = this.binaryZeroPad().split(''); |
|
|
|
|
|
return Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inspectTeredo(): TeredoProperties { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const prefix = this.getBitsBase16(0, 32); |
|
|
|
|
|
const bitsForUdpPort: bigint = this.getBits(80, 96); |
|
|
|
|
|
const udpPort = (bitsForUdpPort ^ BigInt('0xffff')).toString(); |
|
|
|
|
|
const server4 = Address4.fromHex(this.getBitsBase16(32, 64)); |
|
|
|
|
|
const bitsForClient4 = this.getBits(96, 128); |
|
|
|
|
|
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, |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inspect6to4(): SixToFourProperties { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const prefix = this.getBitsBase16(0, 16); |
|
|
|
|
|
const gateway = Address4.fromHex(this.getBitsBase16(16, 48)); |
|
|
|
|
|
return { |
|
|
prefix: prefix.slice(0, 4), |
|
|
gateway: gateway.address, |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toUnsignedByteArray(): number[] { |
|
|
return this.toByteArray().map(unsignByte); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static fromByteArray(bytes: Array<any>): Address6 { |
|
|
return this.fromUnsignedByteArray(bytes.map(unsignByte)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isInSubnet = common.isInSubnet; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isCorrect = common.isCorrect(constants6.BITS); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isCanonical(): boolean { |
|
|
return this.addressMinusSuffix === this.canonicalForm(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isLinkLocal(): boolean { |
|
|
|
|
|
if ( |
|
|
this.getBitsBase2(0, 64) === |
|
|
'1111111010000000000000000000000000000000000000000000000000000000' |
|
|
) { |
|
|
return true; |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isMulticast(): boolean { |
|
|
return this.getType() === 'Multicast'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
is4(): boolean { |
|
|
return this.v4; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isTeredo(): boolean { |
|
|
return this.isInSubnet(new Address6('2001::/32')); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
is6to4(): boolean { |
|
|
return this.isInSubnet(new Address6('2002::/16')); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isLoopback(): boolean { |
|
|
return this.getType() === 'Loopback'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
href(optionalPort?: number | string): string { |
|
|
if (optionalPort === undefined) { |
|
|
optionalPort = ''; |
|
|
} else { |
|
|
optionalPort = `:${optionalPort}`; |
|
|
} |
|
|
|
|
|
return `http://[${this.correctForm()}]${optionalPort}/`; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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>`; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
group(): string { |
|
|
if (this.elidedGroups === 0) { |
|
|
|
|
|
return helpers.simpleGroup(this.address).join(':'); |
|
|
} |
|
|
|
|
|
assert(typeof this.elidedGroups === 'number'); |
|
|
assert(typeof this.elisionBegin === 'number'); |
|
|
|
|
|
|
|
|
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(':'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
regularExpressionString(this: Address6, substringSearch: boolean = false): string { |
|
|
let output: string[] = []; |
|
|
|
|
|
|
|
|
const address6 = new Address6(this.correctForm()); |
|
|
|
|
|
if (address6.elidedGroups === 0) { |
|
|
|
|
|
output.push(simpleRegularExpression(address6.parsedAddress)); |
|
|
} else if (address6.elidedGroups === constants6.GROUPS) { |
|
|
|
|
|
output.push(possibleElisions(constants6.GROUPS)); |
|
|
} else { |
|
|
|
|
|
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(''); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
regularExpression(this: Address6, substringSearch: boolean = false): RegExp { |
|
|
return new RegExp(this.regularExpressionString(substringSearch), 'i'); |
|
|
} |
|
|
|
|
|
} |
|
|
|