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 { BigInteger } from 'jsbn' ; import { sprintf } from 'sprintf-js' ; function assert ( condition ) { if ( ! condition ) { throw new Error ( 'Assertion failed.' ) ; } } function addCommas ( number ) { const r = /(\d+)(\d{3})/ ; while ( r . test ( number ) ) { number = number . replace ( r , '$1,$2' ) ; } return number ; } function spanLeadingZeroes4 ( n ) { 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 , slice ) { 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 ) { return sprintf ( '%04x' , parseInt ( octet , 16 ) ) ; } function unsignByte ( b ) { 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 ; } class param { string } address param { number } groups example 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 , optionalGroups ? ) { 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 ) { try { new Address6 ( address ) ; return true ; } catch ( e ) { return false ; } } memberof static param { BigInteger } bigInteger returns { Address6 } example static fromBigInteger ( bigInteger ) { const hex = bigInteger . 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 ( ':' ) ) ; } memberof static param { string } url example static fromURL ( url ) { let host ; let port = null ; let result ; 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 , } ; } memberof static param { string } address returns { Address6 } example static fromAddress4 ( address ) { const address4 = new Address4 ( address ) ; const mask6 = constants6 . BITS - ( constants4 . BITS - address4 . subnetMask ) ; return new Address6 ( `::ffff:${ address4 . correctForm ( ) }/${ mask6 }` ) ; } memberof static param { string } arpaFormAddress returns { Adress6 } example static fromArpa ( arpaFormAddress ) { 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 ) ; } memberof instance returns { String } microsoftTranscription ( ) { return sprintf ( '%s.ipv6-literal.net' , this . correctForm ( ) . replace ( /:/g , '-' ) ) ; } memberof instance param { number } mask returns { String } mask ( mask = this . subnetMask ) { return this . getBitsBase2 ( 0 , mask ) ; } memberof instance param { number } size returns { String } possibleSubnets ( subnetSize = 128 ) { const availableBits = constants6 . BITS - this . subnetMask ; const subnetBits = Math . abs ( subnetSize - constants6 . BITS ) ; const subnetPowers = availableBits - subnetBits ; if ( subnetPowers < 0 ) { return '0' ; } return addCommas ( new BigInteger ( '2' , 10 ) . pow ( subnetPowers ) . toString ( 10 ) ) ; } memberof instance returns { BigInteger } _startAddress ( ) { return new BigInteger ( this . mask ( ) + '0' . repeat ( constants6 . BITS - this . subnetMask ) , 2 ) ; } memberof instance returns { Address6 } startAddress ( ) { return Address6 . fromBigInteger ( this . _startAddress ( ) ) ; } memberof instance returns { Address6 } startAddressExclusive ( ) { const adjust = new BigInteger ( '1' ) ; return Address6 . fromBigInteger ( this . _startAddress ( ) . add ( adjust ) ) ; } memberof instance returns { BigInteger } _endAddress ( ) { return new BigInteger ( this . mask ( ) + '1' . repeat ( constants6 . BITS - this . subnetMask ) , 2 ) ; } memberof instance returns { Address6 } endAddress ( ) { return Address6 . fromBigInteger ( this . _endAddress ( ) ) ; } memberof instance returns { Address6 } endAddressExclusive ( ) { const adjust = new BigInteger ( '1' ) ; return Address6 . fromBigInteger ( this . _endAddress ( ) . subtract ( adjust ) ) ; } memberof instance returns { String } getScope ( ) { let scope = constants6 . SCOPES [ this . getBits ( 12 , 16 ) . intValue ( ) ] ; if ( this . getType ( ) === 'Global unicast' && scope !== 'Link local' ) { scope = 'Global' ; } return scope || 'Unknown' ; } memberof instance returns { String } getType ( ) { for ( const subnet of Object . keys ( constants6 . TYPES ) ) { if ( this . isInSubnet ( new Address6 ( subnet ) ) ) { return constants6 . TYPES [ subnet ] as string ; } } return 'Global unicast' ; } memberof instance returns { BigInteger } getBits ( start , end ) { return new BigInteger ( this . getBitsBase2 ( start , end ) , 2 ) ; } memberof instance returns { String } getBitsBase2 ( start , end ) { return this . binaryZeroPad ( ) . slice ( start , end ) ; } memberof instance returns { String } getBitsBase16 ( start , end ) { 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' ) ; } memberof instance returns { String } getBitsPastSubnet ( ) { return this . getBitsBase2 ( this . subnetMask , constants6 . BITS ) ; } memberof param options param { boolean } options . omitSuffix instance returns { String } reverseForm ( options ? ) { 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 sprintf ( '%s.ip6.arpa.' , reversed ) ; } if ( options . omitSuffix ) { return '' ; } return 'ip6.arpa.' ; } memberof instance returns { String } correctForm ( ) { 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 ; } memberof instance returns { String } example binaryZeroPad ( ) { return this . bigInteger ( ) . toString ( 2 ) . padStart ( constants6 . BITS , '0' ) ; } parse4in6 ( address ) { 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 ) { address = this . parse4in6 ( address ) ; const badCharacters = address . match ( constants6 . RE_BAD_CHARACTERS ) ; if ( badCharacters ) { throw new AddressError ( sprintf ( 'Bad character%s detected in address: %s' , badCharacters . length > 1 ? 's' : '' , 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 ( sprintf ( 'Address failed regex: %s' , badAddress . join ( '' ) ) , address . replace ( constants6 . RE_BAD_ADDRESS , '<span class="parse-error">$1</span>' ) ) ; } let groups = [ ] ; 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 ) => sprintf ( '%x' , parseInt ( group , 16 ) ) ) ; if ( groups . length !== this . groups ) { throw new AddressError ( 'Incorrect number of groups found' ) ; } return groups ; } memberof instance returns { String } canonicalForm ( ) { return this . parsedAddress . map ( paddedHex ) . join ( ':' ) ; } memberof instance returns { String } decimal ( ) { return this . parsedAddress . map ( ( n ) => sprintf ( '%05d' , parseInt ( n , 16 ) ) ) . join ( ':' ) ; } memberof instance returns { BigInteger } bigInteger ( ) { return new BigInteger ( this . parsedAddress . map ( paddedHex ) . join ( '' ) , 16 ) ; } memberof instance returns { Address4 } example to4 ( ) { const binary = this . binaryZeroPad ( ) . split ( '' ) ; return Address4 . fromHex ( new BigInteger ( binary . slice ( 96 , 128 ) . join ( '' ) , 2 ) . toString ( 16 ) ) ; } memberof instance returns { String } to4in6 ( ) { 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 ; } memberof instance returns { Object } inspectTeredo ( ) { const prefix = this . getBitsBase16 ( 0 , 32 ) ; const udpPort = this . getBits ( 80 , 96 ) . xor ( new BigInteger ( 'ffff' , 16 ) ) . toString ( ) ; const server4 = Address4 . fromHex ( this . getBitsBase16 ( 32 , 64 ) ) ; const client4 = Address4 . fromHex ( this . getBits ( 96 , 128 ) . xor ( new BigInteger ( 'ffffffff' , 16 ) ) . toString ( 16 ) ) ; const flags = this . getBits ( 64 , 80 ) ; const flagsBase2 = this . getBitsBase2 ( 64 , 80 ) ; const coneNat = flags . testBit ( 15 ) ; const reserved = flags . testBit ( 14 ) ; const groupIndividual = flags . testBit ( 8 ) ; const universalLocal = flags . testBit ( 9 ) ; const nonce = new BigInteger ( flagsBase2 . slice ( 2 , 6 ) + flagsBase2 . slice ( 8 , 16 ) , 2 ) . toString ( 10 ) ; return { prefix : sprintf ( '%s:%s' , prefix . slice ( 0 , 4 ) , prefix . slice ( 4 , 8 ) ) , server4 : server4 . address , client4 : client4 . address , flags : flagsBase2 , coneNat , microsoft : { reserved , universalLocal , groupIndividual , nonce , } , udpPort , } ; } memberof instance returns { Object } inspect6to4 ( ) { const prefix = this . getBitsBase16 ( 0 , 16 ) ; const gateway = Address4 . fromHex ( this . getBitsBase16 ( 16 , 48 ) ) ; return { prefix : sprintf ( '%s' , prefix . slice ( 0 , 4 ) ) , gateway : gateway . address , } ; } memberof instance returns { Address6 } to6to4 ( ) { if ( ! this . is4 ( ) ) { return null ; } const addr6to4 = [ '2002' , this . getBitsBase16 ( 96 , 112 ) , this . getBitsBase16 ( 112 , 128 ) , '' , '/16' , ] . join ( ':' ) ; return new Address6 ( addr6to4 ) ; } memberof instance returns { Array } toByteArray ( ) { const byteArray = this . bigInteger ( ) . toByteArray ( ) ; if ( byteArray . length === 17 && byteArray [ 0 ] === 0 ) { return byteArray . slice ( 1 ) ; } return byteArray ; } memberof instance returns { Array } toUnsignedByteArray ( ) { return this . toByteArray ( ) . map ( unsignByte ) ; } memberof static returns { Address6 } static fromByteArray ( bytes ) { return this . fromUnsignedByteArray ( bytes . map ( unsignByte ) ) ; } memberof static returns { Address6 } static fromUnsignedByteArray ( bytes ) { const BYTE_MAX = new BigInteger ( '256' , 10 ) ; let result = new BigInteger ( '0' , 10 ) ; let multiplier = new BigInteger ( '1' , 10 ) ; for ( let i = bytes . length - 1 ; i >= 0 ; i -- ) { result = result . add ( multiplier . multiply ( new BigInteger ( bytes [ i ] . toString ( 10 ) , 10 ) ) ) ; multiplier = multiplier . multiply ( BYTE_MAX ) ; } return Address6 . fromBigInteger ( result ) ; } memberof instance returns { boolean } isInSubnet = common . isInSubnet ; memberof instance returns { boolean } isCorrect = common . isCorrect ( constants6 . BITS ) ; memberof instance returns { boolean } isCanonical ( ) { return this . addressMinusSuffix === this . canonicalForm ( ) ; } memberof instance returns { boolean } isLinkLocal ( ) { if ( this . getBitsBase2 ( 0 , 64 ) === '1111111010000000000000000000000000000000000000000000000000000000' ) { return true ; } return false ; } memberof instance returns { boolean } isMulticast ( ) { return this . getType ( ) === 'Multicast' ; } memberof instance returns { boolean } is4 ( ) { return this . v4 ; } memberof instance returns { boolean } isTeredo ( ) { return this . isInSubnet ( new Address6 ( '2001::/32' ) ) ; } memberof instance returns { boolean } is6to4 ( ) { return this . isInSubnet ( new Address6 ( '2002::/16' ) ) ; } memberof instance returns { boolean } isLoopback ( ) { return this . getType ( ) === 'Loopback' ; } returns { String } href ( optionalPort ? ) { if ( optionalPort === undefined ) { optionalPort = '' ; } else { optionalPort = sprintf ( ':%s' , optionalPort ) ; } return sprintf ( 'http://[%s]%s/' , this . correctForm ( ) , optionalPort ) ; } returns { String } link ( options ? ) { 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 ; } if ( options . className ) { return sprintf ( '<a href="%1$s%2$s" class="%3$s">%2$s</a>' , options . prefix , formFunction . call ( this ) , options . className ) ; } return sprintf ( '<a href="%1$s%2$s">%2$s</a>' , options . prefix , formFunction . call ( this ) ) ; } returns { String } group ( ) { 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 ( sprintf ( 'group-%d' , i ) ) ; } output . push ( sprintf ( '<span class="%s"></span>' , classes . join ( ' ' ) ) ) ; 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 ( ':' ) ; } memberof instance param { boolean } substringSearch returns { string } regularExpressionString ( this , substringSearch = false ) { let output = [ ] ; 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 ( '' ) ; } memberof instance param { boolean } substringSearch returns { RegExp } regularExpression ( this , substringSearch = false ) { return new RegExp ( this . regularExpressionString ( substringSearch ) , 'i' ) ; } }