From d3265f0cbd005d1dac3bede38cc76d1fb85f4dd8 Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Tue, 4 Aug 2026 16:03:56 -0500 Subject: [PATCH 1/9] Add .DS_Store to git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2cc5f6a..e22975d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea node_modules package-lock.json +.DS_Store \ No newline at end of file From abb3f934d38ea6c9351a61f0a35e077808c9bd75 Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Tue, 4 Aug 2026 16:04:39 -0500 Subject: [PATCH 2/9] Update package.json --- package.json | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 231cd9a..bf3a4e8 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,25 @@ { "name": "ipaddr.js", "description": "A library for manipulating IPv4 and IPv6 addresses in JavaScript.", - "type": "commonjs", - "version": "2.5.0", + "type": "module", + "version": "3.4.0", "author": "whitequark ", "directories": { "lib": "./lib" }, + "dependencies": {}, "devDependencies": { - "@eslint/js": "^10.0.1", - "eslint": "^10.3.0", - "globals": "^17.6.0" + "eslint": "^9.19.0", + "uglify-es": "*" }, "scripts": { "lint": "npx eslint lib", "lintfix": "npx eslint --fix lib test", - "test": "node --test", - "test:coverage": "node --test --experimental-test-coverage" + "test": "node --test" }, "files": [ - "lib" + "lib/ipaddr.js", + "lib/ipaddr.d.ts" ], "keywords": [ "ip", @@ -31,6 +31,9 @@ "url": "git+https://github.com/whitequark/ipaddr.js.git" }, "main": "./lib/ipaddr.js", + "exports": { + ".": "./lib/ipaddr.js" + }, "engines": { "node": ">= 10" }, From 0a3830603cf6760b1e157859427b55dbcc33310c Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Tue, 4 Aug 2026 16:05:21 -0500 Subject: [PATCH 3/9] Basic rewrite to ESM --- lib/ipaddr.js | 1817 +++++++++++++++++++++---------------------- test/ipaddr.test.js | 920 +++++++++++----------- 2 files changed, 1361 insertions(+), 1376 deletions(-) diff --git a/lib/ipaddr.js b/lib/ipaddr.js index f821948..b570970 100644 --- a/lib/ipaddr.js +++ b/lib/ipaddr.js @@ -1,1098 +1,1085 @@ -(function (root) { - 'use strict'; - // A list of regular expressions that match arbitrary IPv4 addresses, - // for which a number of weird notations exist. - // Note that an address like 0010.0xa5.1.1 is considered legal. - const ipv4Part = '(0?\\d+|0x[a-f0-9]+)'; - const ipv4Regexes = { - fourOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, 'i'), - threeOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, 'i'), - twoOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}$`, 'i'), - longValue: new RegExp(`^${ipv4Part}$`, 'i') - }; +// A list of regular expressions that match arbitrary IPv4 addresses, +// for which a number of weird notations exist. +// Note that an address like 0010.0xa5.1.1 is considered legal. +const ipv4Part = '(0?\\d+|0x[a-f0-9]+)'; +const ipv4Regexes = { + fourOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, 'i'), + threeOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, 'i'), + twoOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}$`, 'i'), + longValue: new RegExp(`^${ipv4Part}$`, 'i') +}; + +// Regular Expression for checking Octal numbers +const octalRegex = new RegExp(`^0[0-7]+$`, 'i'); +const hexRegex = new RegExp(`^0x[a-f0-9]+$`, 'i'); + +const zoneIndex = '%[0-9a-z]{1,}'; + +// IPv6-matching regular expressions. +// For IPv6, the task is simpler: it is enough to match the colon-delimited +// hexadecimal IPv6 and a transitional variant with dotted-decimal IPv4 at +// the end. +const ipv6Part = '(?:[0-9a-f]+::?)+'; +const ipv6Regexes = { + zoneIndex: new RegExp(zoneIndex, 'i'), + 'native': new RegExp(`^(::)?(${ipv6Part})?([0-9a-f]+)?(::)?(${zoneIndex})?$`, 'i'), + deprecatedTransitional: new RegExp(`^(?:::)(${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?)$`, 'i'), + transitional: new RegExp(`^((?:${ipv6Part})|(?:::)(?:${ipv6Part})?)${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?$`, 'i') +}; + +// Expand :: in an IPv6 address or address part consisting of `parts` groups. +function expandIPv6 (string, parts) { + // More than one '::' means invalid address + if (string.indexOf('::') !== string.lastIndexOf('::')) { + return null; + } - // Regular Expression for checking Octal numbers - const octalRegex = new RegExp(`^0[0-7]+$`, 'i'); - const hexRegex = new RegExp(`^0x[a-f0-9]+$`, 'i'); - - const zoneIndex = '%[0-9a-z]{1,}'; - - // IPv6-matching regular expressions. - // For IPv6, the task is simpler: it is enough to match the colon-delimited - // hexadecimal IPv6 and a transitional variant with dotted-decimal IPv4 at - // the end. - const ipv6Part = '(?:[0-9a-f]+::?)+'; - const ipv6Regexes = { - zoneIndex: new RegExp(zoneIndex, 'i'), - 'native': new RegExp(`^(::)?(${ipv6Part})?([0-9a-f]+)?(::)?(${zoneIndex})?$`, 'i'), - deprecatedTransitional: new RegExp(`^(?:::)(${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?)$`, 'i'), - transitional: new RegExp(`^((?:${ipv6Part})|(?:::)(?:${ipv6Part})?)${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?$`, 'i') - }; + let colonCount = 0; + let lastColon = -1; + let zoneId = (string.match(ipv6Regexes.zoneIndex) || [])[0]; + let replacement, replacementCount; - // Expand :: in an IPv6 address or address part consisting of `parts` groups. - function expandIPv6 (string, parts) { - // More than one '::' means invalid address - if (string.indexOf('::') !== string.lastIndexOf('::')) { - return null; - } + // Remove zone index and save it for later + if (zoneId) { + zoneId = zoneId.substring(1); + string = string.replace(/%.+$/, ''); + } - let colonCount = 0; - let lastColon = -1; - let zoneId = (string.match(ipv6Regexes.zoneIndex) || [])[0]; - let replacement, replacementCount; + // How many parts do we already have? + while ((lastColon = string.indexOf(':', lastColon + 1)) >= 0) { + colonCount++; + } - // Remove zone index and save it for later - if (zoneId) { - zoneId = zoneId.substring(1); - string = string.replace(/%.+$/, ''); - } + // 0::0 is two parts more than :: + if (string.substr(0, 2) === '::') { + colonCount--; + } - // How many parts do we already have? - while ((lastColon = string.indexOf(':', lastColon + 1)) >= 0) { - colonCount++; - } + if (string.substr(-2, 2) === '::') { + colonCount--; + } - // 0::0 is two parts more than :: - if (string.substr(0, 2) === '::') { - colonCount--; - } + // An address must not contain more separators than available parts, + // and :: must compress at least one part. + if (colonCount >= parts) { + return null; + } - if (string.substr(-2, 2) === '::') { - colonCount--; - } + // replacement = ':' + '0:' * (parts - colonCount) + replacementCount = parts - colonCount; + replacement = ':'; + while (replacementCount--) { + replacement += '0:'; + } - // An address must not contain more separators than available parts, - // and :: must compress at least one part. - if (colonCount >= parts) { - return null; - } + // Insert the missing zeroes + string = string.replace('::', replacement); - // replacement = ':' + '0:' * (parts - colonCount) - replacementCount = parts - colonCount; - replacement = ':'; - while (replacementCount--) { - replacement += '0:'; - } + // Trim any garbage which may be hanging around if :: was at the edge in + // the source string + if (string[0] === ':') { + string = string.slice(1); + } - // Insert the missing zeroes - string = string.replace('::', replacement); + if (string[string.length - 1] === ':') { + string = string.slice(0, -1); + } - // Trim any garbage which may be hanging around if :: was at the edge in - // the source string - if (string[0] === ':') { - string = string.slice(1); - } + parts = (function () { + const ref = string.split(':'); + const results = []; - if (string[string.length - 1] === ':') { - string = string.slice(0, -1); + for (let i = 0; i < ref.length; i++) { + results.push(ref[i].length > 4 ? NaN : parseInt(ref[i], 16)); } - parts = (function () { - const ref = string.split(':'); - const results = []; - - for (let i = 0; i < ref.length; i++) { - results.push(ref[i].length > 4 ? NaN : parseInt(ref[i], 16)); - } + return results; + })(); - return results; - })(); + return { + parts: parts, + zoneId: zoneId + }; +} - return { - parts: parts, - zoneId: zoneId - }; +// A generic CIDR (Classless Inter-Domain Routing) RFC1518 range matcher. +function matchCIDR (first, second, partSize, cidrBits) { + if (first.length !== second.length) { + throw new Error('ipaddr: cannot match CIDR for objects with different lengths'); } - // A generic CIDR (Classless Inter-Domain Routing) RFC1518 range matcher. - function matchCIDR (first, second, partSize, cidrBits) { - if (first.length !== second.length) { - throw new Error('ipaddr: cannot match CIDR for objects with different lengths'); + let part = 0; + let shift; + + while (cidrBits > 0) { + shift = partSize - cidrBits; + if (shift < 0) { + shift = 0; } - let part = 0; - let shift; + if (first[part] >> shift !== second[part] >> shift) { + return false; + } - while (cidrBits > 0) { - shift = partSize - cidrBits; - if (shift < 0) { - shift = 0; - } + cidrBits -= partSize; + part += 1; + } - if (first[part] >> shift !== second[part] >> shift) { - return false; - } + return true; +} - cidrBits -= partSize; - part += 1; - } +function parseIntAuto (string) { + // Hexadecimal base 16 (0x#) + if (hexRegex.test(string)) { + return parseInt(string, 16); + } + // While octal representation is discouraged by ECMAScript 3 + // and forbidden by ECMAScript 5, we silently allow it to + // work only if the rest of the string has numbers less than 8. + if (string[0] === '0' && !isNaN(parseInt(string[1], 10))) { + if (octalRegex.test(string)) { + return parseInt(string, 8); + } + throw new Error(`ipaddr: cannot parse ${string} as octal`); + } + // Always include the base 10 radix! + return parseInt(string, 10); +} - return true; +function padPart (part, length) { + while (part.length < length) { + part = `0${part}`; } - function parseIntAuto (string) { - // Hexadecimal base 16 (0x#) - if (hexRegex.test(string)) { - return parseInt(string, 16); - } - // While octal representation is discouraged by ECMAScript 3 - // and forbidden by ECMAScript 5, we silently allow it to - // work only if the rest of the string has numbers less than 8. - if (string[0] === '0' && !isNaN(parseInt(string[1], 10))) { - if (octalRegex.test(string)) { - return parseInt(string, 8); + return part; +} + +// An IPv4 address (RFC791). +export const IPv4 = (function () { + // Constructs a new IPv4 address from an array of four octets + // in network order (MSB first) + // Verifies the input. + function IPv4 (octets) { + if (octets.length !== 4) { + throw new Error('ipaddr: ipv4 octet count should be 4'); } - throw new Error(`ipaddr: cannot parse ${string} as octal`); - } - // Always include the base 10 radix! - return parseInt(string, 10); - } - function padPart (part, length) { - while (part.length < length) { - part = `0${part}`; + let i, octet; + + for (i = 0; i < octets.length; i++) { + octet = octets[i]; + if (!((0 <= octet && octet <= 255))) { + throw new Error('ipaddr: ipv4 octet should fit in 8 bits'); + } } - return part; + this.octets = octets; } - const ipaddr = {}; - - // An IPv4 address (RFC791). - ipaddr.IPv4 = (function () { - // Constructs a new IPv4 address from an array of four octets - // in network order (MSB first) - // Verifies the input. - function IPv4 (octets) { - if (octets.length !== 4) { - throw new Error('ipaddr: ipv4 octet count should be 4'); - } + // Special IPv4 address ranges. + // See also https://en.wikipedia.org/wiki/Reserved_IP_addresses + IPv4.prototype.SpecialRanges = { + unspecified: [[new IPv4([0, 0, 0, 0]), 8]], + broadcast: [[new IPv4([255, 255, 255, 255]), 32]], + // RFC3171 + multicast: [[new IPv4([224, 0, 0, 0]), 4]], + // RFC3927 + linkLocal: [[new IPv4([169, 254, 0, 0]), 16]], + // RFC5735 + loopback: [[new IPv4([127, 0, 0, 0]), 8]], + // RFC6598 + carrierGradeNat: [[new IPv4([100, 64, 0, 0]), 10]], + // RFC1918 + 'private': [ + [new IPv4([10, 0, 0, 0]), 8], + [new IPv4([172, 16, 0, 0]), 12], + [new IPv4([192, 168, 0, 0]), 16] + ], + // Reserved and testing-only ranges; RFCs 5735, 5737, 2544, 1700 + reserved: [ + [new IPv4([192, 0, 0, 0]), 24], + [new IPv4([192, 0, 2, 0]), 24], + [new IPv4([192, 88, 99, 0]), 24], + [new IPv4([198, 18, 0, 0]), 15], + [new IPv4([198, 51, 100, 0]), 24], + [new IPv4([203, 0, 113, 0]), 24], + [new IPv4([240, 0, 0, 0]), 4] + ], + // RFC7534, RFC7535 + as112: [ + [new IPv4([192, 175, 48, 0]), 24], + [new IPv4([192, 31, 196, 0]), 24], + ], + // RFC7450 + amt: [ + [new IPv4([192, 52, 193, 0]), 24], + ], + }; - let i, octet; + // The 'kind' method exists on both IPv4 and IPv6 classes. + IPv4.prototype.kind = function () { + return 'ipv4'; + }; - for (i = 0; i < octets.length; i++) { - octet = octets[i]; - if (!((0 <= octet && octet <= 255))) { - throw new Error('ipaddr: ipv4 octet should fit in 8 bits'); - } - } + // Checks if this address matches other one within given CIDR range. + IPv4.prototype.match = function (other, cidrRange) { + let ref; + if (cidrRange === undefined) { + ref = other; + other = ref[0]; + cidrRange = ref[1]; + } - this.octets = octets; + if (other.kind() !== 'ipv4') { + throw new Error('ipaddr: cannot match ipv4 address with non-ipv4 one'); } - // Special IPv4 address ranges. - // See also https://en.wikipedia.org/wiki/Reserved_IP_addresses - IPv4.prototype.SpecialRanges = { - unspecified: [[new IPv4([0, 0, 0, 0]), 8]], - broadcast: [[new IPv4([255, 255, 255, 255]), 32]], - // RFC3171 - multicast: [[new IPv4([224, 0, 0, 0]), 4]], - // RFC3927 - linkLocal: [[new IPv4([169, 254, 0, 0]), 16]], - // RFC5735 - loopback: [[new IPv4([127, 0, 0, 0]), 8]], - // RFC6598 - carrierGradeNat: [[new IPv4([100, 64, 0, 0]), 10]], - // RFC1918 - 'private': [ - [new IPv4([10, 0, 0, 0]), 8], - [new IPv4([172, 16, 0, 0]), 12], - [new IPv4([192, 168, 0, 0]), 16] - ], - // Reserved and testing-only ranges; RFCs 5735, 5737, 2544, 1700 - reserved: [ - [new IPv4([192, 0, 0, 0]), 24], - [new IPv4([192, 0, 2, 0]), 24], - [new IPv4([192, 88, 99, 0]), 24], - [new IPv4([198, 18, 0, 0]), 15], - [new IPv4([198, 51, 100, 0]), 24], - [new IPv4([203, 0, 113, 0]), 24], - [new IPv4([240, 0, 0, 0]), 4] - ], - // RFC7534, RFC7535 - as112: [ - [new IPv4([192, 175, 48, 0]), 24], - [new IPv4([192, 31, 196, 0]), 24], - ], - // RFC7450 - amt: [ - [new IPv4([192, 52, 193, 0]), 24], - ], - }; + return matchCIDR(this.octets, other.octets, 8, cidrRange); + }; - // The 'kind' method exists on both IPv4 and IPv6 classes. - IPv4.prototype.kind = function () { - return 'ipv4'; + // returns a number of leading ones in IPv4 address, making sure that + // the rest is a solid sequence of 0's (valid netmask) + // returns either the CIDR length or null if mask is not valid + IPv4.prototype.prefixLengthFromSubnetMask = function () { + let cidr = 0; + // non-zero encountered stop scanning for zeroes + let stop = false; + // number of zeroes in octet + const zerotable = { + 0: 8, + 128: 7, + 192: 6, + 224: 5, + 240: 4, + 248: 3, + 252: 2, + 254: 1, + 255: 0 }; + let i, octet, zeros; - // Checks if this address matches other one within given CIDR range. - IPv4.prototype.match = function (other, cidrRange) { - let ref; - if (cidrRange === undefined) { - ref = other; - other = ref[0]; - cidrRange = ref[1]; - } + for (i = 3; i >= 0; i -= 1) { + octet = this.octets[i]; + if (octet in zerotable) { + zeros = zerotable[octet]; + if (stop && zeros !== 0) { + return null; + } - if (other.kind() !== 'ipv4') { - throw new Error('ipaddr: cannot match ipv4 address with non-ipv4 one'); + if (zeros !== 8) { + stop = true; + } + + cidr += zeros; + } else { + return null; } + } - return matchCIDR(this.octets, other.octets, 8, cidrRange); - }; + return 32 - cidr; + }; - // returns a number of leading ones in IPv4 address, making sure that - // the rest is a solid sequence of 0's (valid netmask) - // returns either the CIDR length or null if mask is not valid - IPv4.prototype.prefixLengthFromSubnetMask = function () { - let cidr = 0; - // non-zero encountered stop scanning for zeroes - let stop = false; - // number of zeroes in octet - const zerotable = { - 0: 8, - 128: 7, - 192: 6, - 224: 5, - 240: 4, - 248: 3, - 252: 2, - 254: 1, - 255: 0 - }; - let i, octet, zeros; - - for (i = 3; i >= 0; i -= 1) { - octet = this.octets[i]; - if (octet in zerotable) { - zeros = zerotable[octet]; - if (stop && zeros !== 0) { - return null; - } - - if (zeros !== 8) { - stop = true; - } - - cidr += zeros; - } else { - return null; - } - } + // Checks if the address corresponds to one of the special ranges. + IPv4.prototype.range = function () { + return subnetMatch(this, this.SpecialRanges); + }; - return 32 - cidr; - }; + // Returns an array of byte-sized values in network order (MSB first) + IPv4.prototype.toByteArray = function () { + return this.octets.slice(0); + }; - // Checks if the address corresponds to one of the special ranges. - IPv4.prototype.range = function () { - return ipaddr.subnetMatch(this, this.SpecialRanges); - }; + // Converts this IPv4 address to an IPv4-mapped IPv6 address. + IPv4.prototype.toIPv4MappedAddress = function () { + return IPv6.parse(`::ffff:${this.toString()}`); + }; - // Returns an array of byte-sized values in network order (MSB first) - IPv4.prototype.toByteArray = function () { - return this.octets.slice(0); - }; + // Symmetrical method strictly for aligning with the IPv6 methods. + IPv4.prototype.toNormalizedString = function () { + return this.toString(); + }; - // Converts this IPv4 address to an IPv4-mapped IPv6 address. - IPv4.prototype.toIPv4MappedAddress = function () { - return ipaddr.IPv6.parse(`::ffff:${this.toString()}`); - }; + // Returns the address in convenient, decimal-dotted format. + IPv4.prototype.toString = function () { + return this.octets.join('.'); + }; - // Symmetrical method strictly for aligning with the IPv6 methods. - IPv4.prototype.toNormalizedString = function () { - return this.toString(); - }; + return IPv4; +})(); + +// A utility function to return broadcast address given the IPv4 interface and prefix length in CIDR notation +IPv4.broadcastAddressFromCIDR = function (string) { + + try { + const cidr = this.parseCIDR(string); + const ipInterfaceOctets = cidr[0].toByteArray(); + const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); + const octets = []; + let i = 0; + while (i < 4) { + // Broadcast address is bitwise OR between ip interface and inverted mask + octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255); + i++; + } - // Returns the address in convenient, decimal-dotted format. - IPv4.prototype.toString = function () { - return this.octets.join('.'); - }; + return new this(octets); + } catch (e) { + throw new Error('ipaddr: the address does not have IPv4 CIDR format', { cause: e }); + } +}; - return IPv4; - })(); +// Checks if a given string is formatted like IPv4 address. +IPv4.isIPv4 = function (string) { + return this.parser(string) !== null; +}; - // A utility function to return broadcast address given the IPv4 interface and prefix length in CIDR notation - ipaddr.IPv4.broadcastAddressFromCIDR = function (string) { +// Checks if a given string is a valid IPv4 address. +IPv4.isValid = function (string) { + try { + new this(this.parser(string)); + return true; + } catch { + return false; + } +}; - try { - const cidr = this.parseCIDR(string); - const ipInterfaceOctets = cidr[0].toByteArray(); - const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); - const octets = []; - let i = 0; - while (i < 4) { - // Broadcast address is bitwise OR between ip interface and inverted mask - octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255); - i++; - } +// Checks if a given string is a valid IPv4 address in CIDR notation. +IPv4.isValidCIDR = function (string) { + try { + this.parseCIDR(string); + return true; + } catch { + return false; + } +}; - return new this(octets); - } catch (e) { - throw new Error('ipaddr: the address does not have IPv4 CIDR format', { cause: e }); - } - }; +// Checks if a given string is a full four-part IPv4 Address. +IPv4.isValidFourPartDecimal = function (string) { + if (IPv4.isValid(string) && string.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/)) { + return true; + } else { + return false; + } +}; - // Checks if a given string is formatted like IPv4 address. - ipaddr.IPv4.isIPv4 = function (string) { - return this.parser(string) !== null; - }; +// Checks if a given string is a full four-part IPv4 Address with CIDR prefix. +IPv4.isValidCIDRFourPartDecimal = function (string) { + const match = string.match(/^(.+)\/(\d+)$/); - // Checks if a given string is a valid IPv4 address. - ipaddr.IPv4.isValid = function (string) { - try { - new this(this.parser(string)); - return true; - } catch { - return false; - } - }; + if (!IPv4.isValidCIDR(string) || !match) { + return false; + } - // Checks if a given string is a valid IPv4 address in CIDR notation. - ipaddr.IPv4.isValidCIDR = function (string) { - try { - this.parseCIDR(string); - return true; - } catch { - return false; + return IPv4.isValidFourPartDecimal(match[1]); +}; + +// A utility function to return network address given the IPv4 interface and prefix length in CIDR notation +IPv4.networkAddressFromCIDR = function (string) { + let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; + + try { + cidr = this.parseCIDR(string); + ipInterfaceOctets = cidr[0].toByteArray(); + subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); + octets = []; + i = 0; + while (i < 4) { + // Network address is bitwise AND between ip interface and mask + octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10)); + i++; } - }; - // Checks if a given string is a full four-part IPv4 Address. - ipaddr.IPv4.isValidFourPartDecimal = function (string) { - if (ipaddr.IPv4.isValid(string) && string.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/)) { - return true; - } else { - return false; - } - }; + return new this(octets); + } catch (e) { + throw new Error('ipaddr: the address does not have IPv4 CIDR format', { cause: e }); + } +}; - // Checks if a given string is a full four-part IPv4 Address with CIDR prefix. - ipaddr.IPv4.isValidCIDRFourPartDecimal = function (string) { - const match = string.match(/^(.+)\/(\d+)$/); +// Tries to parse and validate a string with IPv4 address. +// Throws an error if it fails. +IPv4.parse = function (string) { + const parts = this.parser(string); - if (!ipaddr.IPv4.isValidCIDR(string) || !match) { - return false; + if (parts === null) { + throw new Error('ipaddr: string is not formatted like an IPv4 Address'); + } + + return new this(parts); +}; + +// Parses the string as an IPv4 Address with CIDR Notation. +IPv4.parseCIDR = function (string) { + let match; + + if ((match = string.match(/^(.+)\/(\d+)$/))) { + const maskLength = parseInt(match[2]); + if (maskLength >= 0 && maskLength <= 32) { + const parsed = [this.parse(match[1]), maskLength]; + Object.defineProperty(parsed, 'toString', { + value: function () { + return this.join('/'); + } + }); + return parsed; } + } - return ipaddr.IPv4.isValidFourPartDecimal(match[1]); - }; + throw new Error('ipaddr: string is not formatted like an IPv4 CIDR range'); +}; - // A utility function to return network address given the IPv4 interface and prefix length in CIDR notation - ipaddr.IPv4.networkAddressFromCIDR = function (string) { - let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; +// Classful variants (like a.b, where a is an octet, and b is a 24-bit +// value representing last three octets; this corresponds to a class C +// address) are omitted due to classless nature of modern Internet. +IPv4.parser = function (string) { + let match, part, value; - try { - cidr = this.parseCIDR(string); - ipInterfaceOctets = cidr[0].toByteArray(); - subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); - octets = []; - i = 0; - while (i < 4) { - // Network address is bitwise AND between ip interface and mask - octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10)); - i++; + // parseInt recognizes all that octal & hexadecimal weirdness for us + if ((match = string.match(ipv4Regexes.fourOctet))) { + return (function () { + const ref = match.slice(1, 6); + const results = []; + + for (let i = 0; i < ref.length; i++) { + part = ref[i]; + results.push(parseIntAuto(part)); } - return new this(octets); - } catch (e) { - throw new Error('ipaddr: the address does not have IPv4 CIDR format', { cause: e }); + return results; + })(); + } else if ((match = string.match(ipv4Regexes.longValue))) { + value = parseIntAuto(match[1]); + if (value > 0xffffffff || value < 0) { + throw new Error('ipaddr: address outside defined range'); } - }; - - // Tries to parse and validate a string with IPv4 address. - // Throws an error if it fails. - ipaddr.IPv4.parse = function (string) { - const parts = this.parser(string); - if (parts === null) { - throw new Error('ipaddr: string is not formatted like an IPv4 Address'); - } + return ((function () { + const results = []; + let shift; - return new this(parts); - }; + for (shift = 0; shift <= 24; shift += 8) { + results.push((value >> shift) & 0xff); + } - // Parses the string as an IPv4 Address with CIDR Notation. - ipaddr.IPv4.parseCIDR = function (string) { - let match; + return results; + })()).reverse(); + } else if ((match = string.match(ipv4Regexes.twoOctet))) { + return (function () { + const ref = match.slice(1, 4); + const results = []; - if ((match = string.match(/^(.+)\/(\d+)$/))) { - const maskLength = parseInt(match[2]); - if (maskLength >= 0 && maskLength <= 32) { - const parsed = [this.parse(match[1]), maskLength]; - Object.defineProperty(parsed, 'toString', { - value: function () { - return this.join('/'); - } - }); - return parsed; + value = parseIntAuto(ref[1]); + if (value > 0xffffff || value < 0) { + throw new Error('ipaddr: address outside defined range'); } - } - throw new Error('ipaddr: string is not formatted like an IPv4 CIDR range'); - }; + results.push(parseIntAuto(ref[0])); + results.push((value >> 16) & 0xff); + results.push((value >> 8) & 0xff); + results.push( value & 0xff); - // Classful variants (like a.b, where a is an octet, and b is a 24-bit - // value representing last three octets; this corresponds to a class C - // address) are omitted due to classless nature of modern Internet. - ipaddr.IPv4.parser = function (string) { - let match, part, value; - - // parseInt recognizes all that octal & hexadecimal weirdness for us - if ((match = string.match(ipv4Regexes.fourOctet))) { - return (function () { - const ref = match.slice(1, 6); - const results = []; - - for (let i = 0; i < ref.length; i++) { - part = ref[i]; - results.push(parseIntAuto(part)); - } + return results; + })(); + } else if ((match = string.match(ipv4Regexes.threeOctet))) { + return (function () { + const ref = match.slice(1, 5); + const results = []; - return results; - })(); - } else if ((match = string.match(ipv4Regexes.longValue))) { - value = parseIntAuto(match[1]); - if (value > 0xffffffff || value < 0) { + value = parseIntAuto(ref[2]); + if (value > 0xffff || value < 0) { throw new Error('ipaddr: address outside defined range'); } - return ((function () { - const results = []; - let shift; + results.push(parseIntAuto(ref[0])); + results.push(parseIntAuto(ref[1])); + results.push((value >> 8) & 0xff); + results.push( value & 0xff); - for (shift = 0; shift <= 24; shift += 8) { - results.push((value >> shift) & 0xff); - } + return results; + })(); + } else { + return null; + } +}; - return results; - })()).reverse(); - } else if ((match = string.match(ipv4Regexes.twoOctet))) { - return (function () { - const ref = match.slice(1, 4); - const results = []; +// A utility function to return subnet mask in IPv4 format given the prefix length +IPv4.subnetMaskFromPrefixLength = function (prefix) { + prefix = parseInt(prefix); + if (Number.isNaN(prefix) || prefix < 0 || prefix > 32) { + throw new Error('ipaddr: invalid IPv4 prefix length'); + } - value = parseIntAuto(ref[1]); - if (value > 0xffffff || value < 0) { - throw new Error('ipaddr: address outside defined range'); - } + const octets = [0, 0, 0, 0]; + let j = 0; + const filledOctetCount = Math.floor(prefix / 8); - results.push(parseIntAuto(ref[0])); - results.push((value >> 16) & 0xff); - results.push((value >> 8) & 0xff); - results.push( value & 0xff); - - return results; - })(); - } else if ((match = string.match(ipv4Regexes.threeOctet))) { - return (function () { - const ref = match.slice(1, 5); - const results = []; - - value = parseIntAuto(ref[2]); - if (value > 0xffff || value < 0) { - throw new Error('ipaddr: address outside defined range'); - } + while (j < filledOctetCount) { + octets[j] = 255; + j++; + } - results.push(parseIntAuto(ref[0])); - results.push(parseIntAuto(ref[1])); - results.push((value >> 8) & 0xff); - results.push( value & 0xff); + if (filledOctetCount < 4) { + octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8); + } - return results; - })(); + return new this(octets); +}; + +// An IPv6 address (RFC2460) +export const IPv6 = (function () { + // Constructs an IPv6 address from an array of eight 16 - bit parts + // or sixteen 8 - bit parts in network order(MSB first). + // Throws an error if the input is invalid. + function IPv6 (parts, zoneId) { + let i, part; + + if (parts.length === 16) { + this.parts = []; + for (i = 0; i <= 14; i += 2) { + this.parts.push((parts[i] << 8) | parts[i + 1]); + } + } else if (parts.length === 8) { + this.parts = parts; } else { - return null; + throw new Error('ipaddr: ipv6 part count should be 8 or 16'); } - }; - // A utility function to return subnet mask in IPv4 format given the prefix length - ipaddr.IPv4.subnetMaskFromPrefixLength = function (prefix) { - prefix = parseInt(prefix); - if (Number.isNaN(prefix) || prefix < 0 || prefix > 32) { - throw new Error('ipaddr: invalid IPv4 prefix length'); + for (i = 0; i < this.parts.length; i++) { + part = this.parts[i]; + if (!((0 <= part && part <= 0xffff))) { + throw new Error('ipaddr: ipv6 part should fit in 16 bits'); + } } - const octets = [0, 0, 0, 0]; - let j = 0; - const filledOctetCount = Math.floor(prefix / 8); - - while (j < filledOctetCount) { - octets[j] = 255; - j++; + if (zoneId) { + this.zoneId = zoneId; } + } - if (filledOctetCount < 4) { - octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8); - } + // Special IPv6 ranges + IPv6.prototype.SpecialRanges = { + // RFC4291, here and after + unspecified: [new IPv6([0, 0, 0, 0, 0, 0, 0, 0]), 128], + linkLocal: [new IPv6([0xfe80, 0, 0, 0, 0, 0, 0, 0]), 10], + multicast: [new IPv6([0xff00, 0, 0, 0, 0, 0, 0, 0]), 8], + loopback: [new IPv6([0, 0, 0, 0, 0, 0, 0, 1]), 128], + uniqueLocal: [new IPv6([0xfc00, 0, 0, 0, 0, 0, 0, 0]), 7], + ipv4Mapped: [new IPv6([0, 0, 0, 0, 0, 0xffff, 0, 0]), 96], + // RFC3879 + deprecatedSiteLocal: [new IPv6([0xfec0, 0, 0, 0, 0, 0, 0, 0]), 10], + // RFC6666 + discard: [new IPv6([0x100, 0, 0, 0, 0, 0, 0, 0]), 64], + // RFC6145 + rfc6145: [new IPv6([0, 0, 0, 0, 0xffff, 0, 0, 0]), 96], + rfc6052: [ + // RFC6052 + [new IPv6([0x64, 0xff9b, 0, 0, 0, 0, 0, 0]), 96], + // RFC8215 + [new IPv6([0x64, 0xff9b, 0x1, 0, 0, 0, 0, 0]), 48], + ], + // RFC3056 + '6to4': [new IPv6([0x2002, 0, 0, 0, 0, 0, 0, 0]), 16], + // RFC6052, RFC6146 + teredo: [new IPv6([0x2001, 0, 0, 0, 0, 0, 0, 0]), 32], + // RFC5180 + benchmarking: [new IPv6([0x2001, 0x2, 0, 0, 0, 0, 0, 0]), 48], + // RFC7450 + amt: [new IPv6([0x2001, 0x3, 0, 0, 0, 0, 0, 0]), 32], + as112v6: [ + // RFC7535 + [new IPv6([0x2001, 0x4, 0x112, 0, 0, 0, 0, 0]), 48], + // RFC7534 + [new IPv6([0x2620, 0x4f, 0x8000, 0, 0, 0, 0, 0]), 48], + ], + // RFC4843 + deprecatedOrchid: [new IPv6([0x2001, 0x10, 0, 0, 0, 0, 0, 0]), 28], + // RFC7343 + orchid2: [new IPv6([0x2001, 0x20, 0, 0, 0, 0, 0, 0]), 28], + // RFC9374 + droneRemoteIdProtocolEntityTags: [new IPv6([0x2001, 0x30, 0, 0, 0, 0, 0, 0]), 28], + // RFC9602 + segmentRouting: [new IPv6([0x5f00, 0, 0, 0, 0, 0, 0, 0]), 16], + reserved: [ + // RFC3849 + [new IPv6([0x2001, 0, 0, 0, 0, 0, 0, 0]), 23], + // RFC2928 + [new IPv6([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]), 32], + // RFC9637 + [new IPv6([0x3fff, 0, 0, 0, 0, 0, 0, 0]), 20], + ], + }; - return new this(octets); + // Checks if this address is an IPv4-mapped IPv6 address. + IPv6.prototype.isIPv4MappedAddress = function () { + return this.range() === 'ipv4Mapped'; }; - // An IPv6 address (RFC2460) - ipaddr.IPv6 = (function () { - // Constructs an IPv6 address from an array of eight 16 - bit parts - // or sixteen 8 - bit parts in network order(MSB first). - // Throws an error if the input is invalid. - function IPv6 (parts, zoneId) { - let i, part; - - if (parts.length === 16) { - this.parts = []; - for (i = 0; i <= 14; i += 2) { - this.parts.push((parts[i] << 8) | parts[i + 1]); - } - } else if (parts.length === 8) { - this.parts = parts; - } else { - throw new Error('ipaddr: ipv6 part count should be 8 or 16'); - } + // The 'kind' method exists on both IPv4 and IPv6 classes. + IPv6.prototype.kind = function () { + return 'ipv6'; + }; - for (i = 0; i < this.parts.length; i++) { - part = this.parts[i]; - if (!((0 <= part && part <= 0xffff))) { - throw new Error('ipaddr: ipv6 part should fit in 16 bits'); - } - } + // Checks if this address matches other one within given CIDR range. + IPv6.prototype.match = function (other, cidrRange) { + let ref; - if (zoneId) { - this.zoneId = zoneId; - } + if (cidrRange === undefined) { + ref = other; + other = ref[0]; + cidrRange = ref[1]; } - // Special IPv6 ranges - IPv6.prototype.SpecialRanges = { - // RFC4291, here and after - unspecified: [new IPv6([0, 0, 0, 0, 0, 0, 0, 0]), 128], - linkLocal: [new IPv6([0xfe80, 0, 0, 0, 0, 0, 0, 0]), 10], - multicast: [new IPv6([0xff00, 0, 0, 0, 0, 0, 0, 0]), 8], - loopback: [new IPv6([0, 0, 0, 0, 0, 0, 0, 1]), 128], - uniqueLocal: [new IPv6([0xfc00, 0, 0, 0, 0, 0, 0, 0]), 7], - ipv4Mapped: [new IPv6([0, 0, 0, 0, 0, 0xffff, 0, 0]), 96], - // RFC3879 - deprecatedSiteLocal: [new IPv6([0xfec0, 0, 0, 0, 0, 0, 0, 0]), 10], - // RFC6666 - discard: [new IPv6([0x100, 0, 0, 0, 0, 0, 0, 0]), 64], - // RFC6145 - rfc6145: [new IPv6([0, 0, 0, 0, 0xffff, 0, 0, 0]), 96], - rfc6052: [ - // RFC6052 - [new IPv6([0x64, 0xff9b, 0, 0, 0, 0, 0, 0]), 96], - // RFC8215 - [new IPv6([0x64, 0xff9b, 0x1, 0, 0, 0, 0, 0]), 48], - ], - // RFC3056 - '6to4': [new IPv6([0x2002, 0, 0, 0, 0, 0, 0, 0]), 16], - // RFC6052, RFC6146 - teredo: [new IPv6([0x2001, 0, 0, 0, 0, 0, 0, 0]), 32], - // RFC5180 - benchmarking: [new IPv6([0x2001, 0x2, 0, 0, 0, 0, 0, 0]), 48], - // RFC7450 - amt: [new IPv6([0x2001, 0x3, 0, 0, 0, 0, 0, 0]), 32], - as112v6: [ - // RFC7535 - [new IPv6([0x2001, 0x4, 0x112, 0, 0, 0, 0, 0]), 48], - // RFC7534 - [new IPv6([0x2620, 0x4f, 0x8000, 0, 0, 0, 0, 0]), 48], - ], - // RFC4843 - deprecatedOrchid: [new IPv6([0x2001, 0x10, 0, 0, 0, 0, 0, 0]), 28], - // RFC7343 - orchid2: [new IPv6([0x2001, 0x20, 0, 0, 0, 0, 0, 0]), 28], - // RFC9374 - droneRemoteIdProtocolEntityTags: [new IPv6([0x2001, 0x30, 0, 0, 0, 0, 0, 0]), 28], - // RFC9602 - segmentRouting: [new IPv6([0x5f00, 0, 0, 0, 0, 0, 0, 0]), 16], - reserved: [ - // RFC3849 - [new IPv6([0x2001, 0, 0, 0, 0, 0, 0, 0]), 23], - // RFC2928 - [new IPv6([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]), 32], - // RFC9637 - [new IPv6([0x3fff, 0, 0, 0, 0, 0, 0, 0]), 20], - ], - }; - - // Checks if this address is an IPv4-mapped IPv6 address. - IPv6.prototype.isIPv4MappedAddress = function () { - return this.range() === 'ipv4Mapped'; - }; - - // The 'kind' method exists on both IPv4 and IPv6 classes. - IPv6.prototype.kind = function () { - return 'ipv6'; - }; - - // Checks if this address matches other one within given CIDR range. - IPv6.prototype.match = function (other, cidrRange) { - let ref; - - if (cidrRange === undefined) { - ref = other; - other = ref[0]; - cidrRange = ref[1]; - } + if (other.kind() !== 'ipv6') { + throw new Error('ipaddr: cannot match ipv6 address with non-ipv6 one'); + } - if (other.kind() !== 'ipv6') { - throw new Error('ipaddr: cannot match ipv6 address with non-ipv6 one'); - } + return matchCIDR(this.parts, other.parts, 16, cidrRange); + }; - return matchCIDR(this.parts, other.parts, 16, cidrRange); + // returns a number of leading ones in IPv6 address, making sure that + // the rest is a solid sequence of 0's (valid netmask) + // returns either the CIDR length or null if mask is not valid + IPv6.prototype.prefixLengthFromSubnetMask = function () { + let cidr = 0; + // non-zero encountered stop scanning for zeroes + let stop = false; + // number of zeroes in octet + const zerotable = { + 0: 16, + 32768: 15, + 49152: 14, + 57344: 13, + 61440: 12, + 63488: 11, + 64512: 10, + 65024: 9, + 65280: 8, + 65408: 7, + 65472: 6, + 65504: 5, + 65520: 4, + 65528: 3, + 65532: 2, + 65534: 1, + 65535: 0 }; + let part, zeros; - // returns a number of leading ones in IPv6 address, making sure that - // the rest is a solid sequence of 0's (valid netmask) - // returns either the CIDR length or null if mask is not valid - IPv6.prototype.prefixLengthFromSubnetMask = function () { - let cidr = 0; - // non-zero encountered stop scanning for zeroes - let stop = false; - // number of zeroes in octet - const zerotable = { - 0: 16, - 32768: 15, - 49152: 14, - 57344: 13, - 61440: 12, - 63488: 11, - 64512: 10, - 65024: 9, - 65280: 8, - 65408: 7, - 65472: 6, - 65504: 5, - 65520: 4, - 65528: 3, - 65532: 2, - 65534: 1, - 65535: 0 - }; - let part, zeros; - - for (let i = 7; i >= 0; i -= 1) { - part = this.parts[i]; - if (part in zerotable) { - zeros = zerotable[part]; - if (stop && zeros !== 0) { - return null; - } - - if (zeros !== 16) { - stop = true; - } - - cidr += zeros; - } else { + for (let i = 7; i >= 0; i -= 1) { + part = this.parts[i]; + if (part in zerotable) { + zeros = zerotable[part]; + if (stop && zeros !== 0) { return null; } - } - - return 128 - cidr; - }; + if (zeros !== 16) { + stop = true; + } - // Checks if the address corresponds to one of the special ranges. - IPv6.prototype.range = function () { - return ipaddr.subnetMatch(this, this.SpecialRanges); - }; - - // Returns an array of byte-sized values in network order (MSB first) - IPv6.prototype.toByteArray = function () { - let part; - const bytes = []; - const ref = this.parts; - for (let i = 0; i < ref.length; i++) { - part = ref[i]; - bytes.push(part >> 8); - bytes.push(part & 0xff); + cidr += zeros; + } else { + return null; } + } - return bytes; - }; - - // Returns the address in expanded format with all zeroes included, like - // 2001:0db8:0008:0066:0000:0000:0000:0001 - IPv6.prototype.toFixedLengthString = function () { - const addr = ((function () { - const results = []; - for (let i = 0; i < this.parts.length; i++) { - results.push(padPart(this.parts[i].toString(16), 4)); - } + return 128 - cidr; + }; - return results; - }).call(this)).join(':'); - let suffix = ''; + // Checks if the address corresponds to one of the special ranges. + IPv6.prototype.range = function () { + return subnetMatch(this, this.SpecialRanges); + }; - if (this.zoneId) { - suffix = `%${this.zoneId}`; - } + // Returns an array of byte-sized values in network order (MSB first) + IPv6.prototype.toByteArray = function () { + let part; + const bytes = []; + const ref = this.parts; + for (let i = 0; i < ref.length; i++) { + part = ref[i]; + bytes.push(part >> 8); + bytes.push(part & 0xff); + } - return addr + suffix; - }; + return bytes; + }; - // Converts this address to IPv4 address if it is an IPv4-mapped IPv6 address. - // Throws an error otherwise. - IPv6.prototype.toIPv4Address = function () { - if (!this.isIPv4MappedAddress()) { - throw new Error('ipaddr: trying to convert a generic ipv6 address to ipv4'); + // Returns the address in expanded format with all zeroes included, like + // 2001:0db8:0008:0066:0000:0000:0000:0001 + IPv6.prototype.toFixedLengthString = function () { + const addr = ((function () { + const results = []; + for (let i = 0; i < this.parts.length; i++) { + results.push(padPart(this.parts[i].toString(16), 4)); } - const ref = this.parts.slice(-2); - const high = ref[0]; - const low = ref[1]; - - return new ipaddr.IPv4([high >> 8, high & 0xff, low >> 8, low & 0xff]); - }; + return results; + }).call(this)).join(':'); - // Returns the address in expanded format with all zeroes included, like - // 2001:db8:8:66:0:0:0:1 - // - // Deprecated: use toFixedLengthString() instead. - IPv6.prototype.toNormalizedString = function () { - const addr = ((function () { - const results = []; + let suffix = ''; - for (let i = 0; i < this.parts.length; i++) { - results.push(this.parts[i].toString(16)); - } + if (this.zoneId) { + suffix = `%${this.zoneId}`; + } - return results; - }).call(this)).join(':'); + return addr + suffix; + }; - let suffix = ''; + // Converts this address to IPv4 address if it is an IPv4-mapped IPv6 address. + // Throws an error otherwise. + IPv6.prototype.toIPv4Address = function () { + if (!this.isIPv4MappedAddress()) { + throw new Error('ipaddr: trying to convert a generic ipv6 address to ipv4'); + } - if (this.zoneId) { - suffix = `%${this.zoneId}`; - } + const ref = this.parts.slice(-2); + const high = ref[0]; + const low = ref[1]; - return addr + suffix; - }; + return new IPv4([high >> 8, high & 0xff, low >> 8, low & 0xff]); + }; - // Returns the address in compact, human-readable format like - // 2001:db8:8:66::1 - // in line with RFC 5952 (see https://tools.ietf.org/html/rfc5952#section-4) - IPv6.prototype.toRFC5952String = function () { - const regex = /((^|:)(0(:|$)){2,})/g; - // The zone identifier (RFC 4007) is not part of the address; match - // against the address alone so a trailing zero run right before the - // "%" suffix still gets compressed (RFC 5952, 4.2.2). - let suffix = ''; - if (this.zoneId) { - suffix = `%${this.zoneId}`; - } - const normalized = this.toNormalizedString(); - const string = normalized.slice(0, normalized.length - suffix.length); - let bestMatchIndex = 0; - let bestMatchLength = -1; - let bestMatchGroups = -1; - let match; - - while ((match = regex.exec(string))) { - // Compare by the number of zero groups, not the matched length: - // a run that is not at the start carries a leading ":" in the - // match, so an equal-length run later in the address would - // otherwise be preferred over the leftmost one (RFC 5952, 4.2.3). - const groups = (match[0].match(/0/g) || []).length; - if (groups > bestMatchGroups) { - bestMatchGroups = groups; - bestMatchIndex = match.index; - bestMatchLength = match[0].length; - } - } + // Returns the address in expanded format with all zeroes included, like + // 2001:db8:8:66:0:0:0:1 + // + // Deprecated: use toFixedLengthString() instead. + IPv6.prototype.toNormalizedString = function () { + const addr = ((function () { + const results = []; - if (bestMatchLength < 0) { - return string + suffix; + for (let i = 0; i < this.parts.length; i++) { + results.push(this.parts[i].toString(16)); } - return `${string.substring(0, bestMatchIndex)}::${string.substring(bestMatchIndex + bestMatchLength)}${suffix}`; - }; - - // Returns the address in compact, human-readable format like - // 2001:db8:8:66::1 - // Calls toRFC5952String under the hood. - IPv6.prototype.toString = function () { - return this.toRFC5952String(); - }; - - return IPv6; - - })(); + return results; + }).call(this)).join(':'); - // A utility function to return broadcast address given the IPv6 interface and prefix length in CIDR notation - ipaddr.IPv6.broadcastAddressFromCIDR = function (string) { - try { - const cidr = this.parseCIDR(string); - const ipInterfaceOctets = cidr[0].toByteArray(); - const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); - const octets = []; - let i = 0; - while (i < 16) { - // Broadcast address is bitwise OR between ip interface and inverted mask - octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255); - i++; - } + let suffix = ''; - return new this(octets); - } catch (e) { - throw new Error('ipaddr: the address does not have IPv6 CIDR format', { cause: e }); + if (this.zoneId) { + suffix = `%${this.zoneId}`; } - }; - // Checks if a given string is formatted like IPv6 address. - ipaddr.IPv6.isIPv6 = function (string) { - return this.parser(string) !== null; + return addr + suffix; }; - // Checks to see if string is a valid IPv6 Address - ipaddr.IPv6.isValid = function (string) { + // Returns the address in compact, human-readable format like + // 2001:db8:8:66::1 + // in line with RFC 5952 (see https://tools.ietf.org/html/rfc5952#section-4) + IPv6.prototype.toRFC5952String = function () { + const regex = /((^|:)(0(:|$)){2,})/g; + // The zone identifier (RFC 4007) is not part of the address; match + // against the address alone so a trailing zero run right before the + // "%" suffix still gets compressed (RFC 5952, 4.2.2). + let suffix = ''; + if (this.zoneId) { + suffix = `%${this.zoneId}`; + } + const normalized = this.toNormalizedString(); + const string = normalized.slice(0, normalized.length - suffix.length); + let bestMatchIndex = 0; + let bestMatchLength = -1; + let bestMatchGroups = -1; + let match; - // Since IPv6.isValid is always called first, this shortcut - // provides a substantial performance gain. - if (typeof string === 'string' && string.indexOf(':') === -1) { - return false; + while ((match = regex.exec(string))) { + // Compare by the number of zero groups, not the matched length: + // a run that is not at the start carries a leading ":" in the + // match, so an equal-length run later in the address would + // otherwise be preferred over the leftmost one (RFC 5952, 4.2.3). + const groups = (match[0].match(/0/g) || []).length; + if (groups > bestMatchGroups) { + bestMatchGroups = groups; + bestMatchIndex = match.index; + bestMatchLength = match[0].length; + } } - try { - const addr = this.parser(string); - new this(addr.parts, addr.zoneId); - return true; - } catch { - return false; + if (bestMatchLength < 0) { + return string + suffix; } + + return `${string.substring(0, bestMatchIndex)}::${string.substring(bestMatchIndex + bestMatchLength)}${suffix}`; }; - // Checks if a given string is a valid IPv6 address in CIDR notation. - ipaddr.IPv6.isValidCIDR = function (string) { + // Returns the address in compact, human-readable format like + // 2001:db8:8:66::1 + // Calls toRFC5952String under the hood. + IPv6.prototype.toString = function () { + return this.toRFC5952String(); + }; - // See note in IPv6.isValid - if (typeof string === 'string' && string.indexOf(':') === -1) { - return false; + return IPv6; + +})(); + +// A utility function to return broadcast address given the IPv6 interface and prefix length in CIDR notation +IPv6.broadcastAddressFromCIDR = function (string) { + try { + const cidr = this.parseCIDR(string); + const ipInterfaceOctets = cidr[0].toByteArray(); + const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); + const octets = []; + let i = 0; + while (i < 16) { + // Broadcast address is bitwise OR between ip interface and inverted mask + octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255); + i++; } - try { - this.parseCIDR(string); - return true; - } catch { - return false; - } - }; + return new this(octets); + } catch (e) { + throw new Error('ipaddr: the address does not have IPv6 CIDR format', { cause: e }); + } +}; - // A utility function to return network address given the IPv6 interface and prefix length in CIDR notation - ipaddr.IPv6.networkAddressFromCIDR = function (string) { - let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; +// Checks if a given string is formatted like IPv6 address. +IPv6.isIPv6 = function (string) { + return this.parser(string) !== null; +}; - try { - cidr = this.parseCIDR(string); - ipInterfaceOctets = cidr[0].toByteArray(); - subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); - octets = []; - i = 0; - while (i < 16) { - // Network address is bitwise AND between ip interface and mask - octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10)); - i++; - } +// Checks to see if string is a valid IPv6 Address +IPv6.isValid = function (string) { - return new this(octets); - } catch (e) { - throw new Error('ipaddr: the address does not have IPv6 CIDR format', { cause: e }); - } - }; + // Since IPv6.isValid is always called first, this shortcut + // provides a substantial performance gain. + if (typeof string === 'string' && string.indexOf(':') === -1) { + return false; + } - // Tries to parse and validate a string with IPv6 address. - // Throws an error if it fails. - ipaddr.IPv6.parse = function (string) { + try { const addr = this.parser(string); + new this(addr.parts, addr.zoneId); + return true; + } catch { + return false; + } +}; - if (addr === null) { - throw new Error('ipaddr: string is not formatted like an IPv6 Address'); - } +// Checks if a given string is a valid IPv6 address in CIDR notation. +IPv6.isValidCIDR = function (string) { - return new this(addr.parts, addr.zoneId); - }; + // See note in IPv6.isValid + if (typeof string === 'string' && string.indexOf(':') === -1) { + return false; + } - ipaddr.IPv6.parseCIDR = function (string) { - let maskLength, match, parsed; - - if ((match = string.match(/^(.+)\/(\d+)$/))) { - maskLength = parseInt(match[2]); - if (maskLength >= 0 && maskLength <= 128) { - parsed = [this.parse(match[1]), maskLength]; - Object.defineProperty(parsed, 'toString', { - value: function () { - return this.join('/'); - } - }); - return parsed; - } + try { + this.parseCIDR(string); + return true; + } catch { + return false; + } +}; + +// A utility function to return network address given the IPv6 interface and prefix length in CIDR notation +IPv6.networkAddressFromCIDR = function (string) { + let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; + + try { + cidr = this.parseCIDR(string); + ipInterfaceOctets = cidr[0].toByteArray(); + subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); + octets = []; + i = 0; + while (i < 16) { + // Network address is bitwise AND between ip interface and mask + octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10)); + i++; } - throw new Error('ipaddr: string is not formatted like an IPv6 CIDR range'); - }; + return new this(octets); + } catch (e) { + throw new Error('ipaddr: the address does not have IPv6 CIDR format', { cause: e }); + } +}; - // Parse an IPv6 address. - ipaddr.IPv6.parser = function (string) { - let addr, i, match, octet, octets, zoneId; +// Tries to parse and validate a string with IPv6 address. +// Throws an error if it fails. +IPv6.parse = function (string) { + const addr = this.parser(string); - if ((match = string.match(ipv6Regexes.deprecatedTransitional))) { - return this.parser(`::ffff:${match[1]}`); - } - if (ipv6Regexes.native.test(string)) { - return expandIPv6(string, 8); - } - if ((match = string.match(ipv6Regexes.transitional))) { - zoneId = match[6] || ''; - addr = match[1] - if (!match[1].endsWith('::')) { - addr = addr.slice(0, -1) - } - addr = expandIPv6(addr + zoneId, 6); - if (addr && addr.parts) { - octets = [ - parseInt(match[2]), - parseInt(match[3]), - parseInt(match[4]), - parseInt(match[5]) - ]; - for (i = 0; i < octets.length; i++) { - octet = octets[i]; - if (!((0 <= octet && octet <= 255))) { - return null; - } - } + if (addr === null) { + throw new Error('ipaddr: string is not formatted like an IPv6 Address'); + } - addr.parts.push(octets[0] << 8 | octets[1]); - addr.parts.push(octets[2] << 8 | octets[3]); - return { - parts: addr.parts, - zoneId: addr.zoneId - }; - } - } + return new this(addr.parts, addr.zoneId); +}; - return null; - }; +IPv6.parseCIDR = function (string) { + let maskLength, match, parsed; - // A utility function to return subnet mask in IPv6 format given the prefix length - ipaddr.IPv6.subnetMaskFromPrefixLength = function (prefix) { - prefix = parseInt(prefix); - if (Number.isNaN(prefix) || prefix < 0 || prefix > 128) { - throw new Error('ipaddr: invalid IPv6 prefix length'); + if ((match = string.match(/^(.+)\/(\d+)$/))) { + maskLength = parseInt(match[2]); + if (maskLength >= 0 && maskLength <= 128) { + parsed = [this.parse(match[1]), maskLength]; + Object.defineProperty(parsed, 'toString', { + value: function () { + return this.join('/'); + } + }); + return parsed; } + } - const octets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - let j = 0; - const filledOctetCount = Math.floor(prefix / 8); + throw new Error('ipaddr: string is not formatted like an IPv6 CIDR range'); +}; - while (j < filledOctetCount) { - octets[j] = 255; - j++; +// Parse an IPv6 address. +IPv6.parser = function (string) { + let addr, i, match, octet, octets, zoneId; + + if ((match = string.match(ipv6Regexes.deprecatedTransitional))) { + return this.parser(`::ffff:${match[1]}`); + } + if (ipv6Regexes.native.test(string)) { + return expandIPv6(string, 8); + } + if ((match = string.match(ipv6Regexes.transitional))) { + zoneId = match[6] || ''; + addr = match[1] + if (!match[1].endsWith('::')) { + addr = addr.slice(0, -1) } + addr = expandIPv6(addr + zoneId, 6); + if (addr && addr.parts) { + octets = [ + parseInt(match[2]), + parseInt(match[3]), + parseInt(match[4]), + parseInt(match[5]) + ]; + for (i = 0; i < octets.length; i++) { + octet = octets[i]; + if (!((0 <= octet && octet <= 255))) { + return null; + } + } - if (filledOctetCount < 16) { - octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8); + addr.parts.push(octets[0] << 8 | octets[1]); + addr.parts.push(octets[2] << 8 | octets[3]); + return { + parts: addr.parts, + zoneId: addr.zoneId + }; } + } - return new this(octets); - }; + return null; +}; - // Try to parse an array in network order (MSB first) for IPv4 and IPv6 - ipaddr.fromByteArray = function (bytes) { - const length = bytes.length; +// A utility function to return subnet mask in IPv6 format given the prefix length +IPv6.subnetMaskFromPrefixLength = function (prefix) { + prefix = parseInt(prefix); + if (Number.isNaN(prefix) || prefix < 0 || prefix > 128) { + throw new Error('ipaddr: invalid IPv6 prefix length'); + } - if (length === 4) { - return new ipaddr.IPv4(bytes); - } else if (length === 16) { - return new ipaddr.IPv6(bytes); - } else { - throw new Error('ipaddr: the binary input is neither an IPv6 nor IPv4 address'); - } - }; + const octets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let j = 0; + const filledOctetCount = Math.floor(prefix / 8); - // Checks if the address is valid IP address - ipaddr.isValid = function (string) { - return ipaddr.IPv6.isValid(string) || ipaddr.IPv4.isValid(string); - }; + while (j < filledOctetCount) { + octets[j] = 255; + j++; + } - // Checks if the address is valid IP address in CIDR notation - ipaddr.isValidCIDR = function (string) { - return ipaddr.IPv6.isValidCIDR(string) || ipaddr.IPv4.isValidCIDR(string); - }; + if (filledOctetCount < 16) { + octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8); + } + return new this(octets); +}; - // Attempts to parse an IP Address, first through IPv6 then IPv4. - // Throws an error if it could not be parsed. - ipaddr.parse = function (string) { - if (ipaddr.IPv6.isValid(string)) { - return ipaddr.IPv6.parse(string); - } else if (ipaddr.IPv4.isValid(string)) { - return ipaddr.IPv4.parse(string); - } else { - throw new Error('ipaddr: the address has neither IPv6 nor IPv4 format'); - } - }; +// Try to parse an array in network order (MSB first) for IPv4 and IPv6 +export const fromByteArray = function (bytes) { + const length = bytes.length; - // Attempt to parse CIDR notation, first through IPv6 then IPv4. - // Throws an error if it could not be parsed. - ipaddr.parseCIDR = function (string) { + if (length === 4) { + return new IPv4(bytes); + } else if (length === 16) { + return new IPv6(bytes); + } else { + throw new Error('ipaddr: the binary input is neither an IPv6 nor IPv4 address'); + } +}; + +// Checks if the address is valid IP address +export const isValid = function (string) { + return IPv6.isValid(string) || IPv4.isValid(string); +}; + +// Checks if the address is valid IP address in CIDR notation +export const isValidCIDR = function (string) { + return IPv6.isValidCIDR(string) || IPv4.isValidCIDR(string); +}; + + +// Attempts to parse an IP Address, first through IPv6 then IPv4. +// Throws an error if it could not be parsed. +export const parse = function (string) { + if (IPv6.isValid(string)) { + return IPv6.parse(string); + } else if (IPv4.isValid(string)) { + return IPv4.parse(string); + } else { + throw new Error('ipaddr: the address has neither IPv6 nor IPv4 format'); + } +}; + +// Attempt to parse CIDR notation, first through IPv6 then IPv4. +// Throws an error if it could not be parsed. +export const parseCIDR = function (string) { + try { + return IPv6.parseCIDR(string); + } catch { try { - return ipaddr.IPv6.parseCIDR(string); - } catch { - try { - return ipaddr.IPv4.parseCIDR(string); - } catch (e) { - throw new Error('ipaddr: the address has neither IPv6 nor IPv4 CIDR format', { cause: e }); - } + return IPv4.parseCIDR(string); + } catch (e) { + throw new Error('ipaddr: the address has neither IPv6 nor IPv4 CIDR format', { cause: e }); } - }; + } +}; - // Parse an address and return plain IPv4 address if it is an IPv4-mapped address - ipaddr.process = function (string) { - const addr = this.parse(string); +// Parse an address and return plain IPv4 address if it is an IPv4-mapped address +export const process = function (string) { + const addr = parse(string); - if (addr.kind() === 'ipv6' && addr.isIPv4MappedAddress()) { - return addr.toIPv4Address(); - } else { - return addr; - } - }; + if (addr.kind() === 'ipv6' && addr.isIPv4MappedAddress()) { + return addr.toIPv4Address(); + } else { + return addr; + } +}; - // An utility function to ease named range matching. See examples below. - // rangeList can contain both IPv4 and IPv6 subnet entries and will not throw errors - // on matching IPv4 addresses to IPv6 ranges or vice versa. - ipaddr.subnetMatch = function (address, rangeList, defaultName) { - let i, rangeName, rangeSubnets, subnet; +// An utility function to ease named range matching. See examples below. +// rangeList can contain both IPv4 and IPv6 subnet entries and will not throw errors +// on matching IPv4 addresses to IPv6 ranges or vice versa. +export const subnetMatch = function (address, rangeList, defaultName) { + let i, rangeName, rangeSubnets, subnet; - if (defaultName === undefined || defaultName === null) { - defaultName = 'unicast'; - } + if (defaultName === undefined || defaultName === null) { + defaultName = 'unicast'; + } - for (rangeName in rangeList) { - if (Object.prototype.hasOwnProperty.call(rangeList, rangeName)) { - rangeSubnets = rangeList[rangeName]; - // ECMA5 Array.isArray isn't available everywhere - if (rangeSubnets[0] && !(rangeSubnets[0] instanceof Array)) { - rangeSubnets = [rangeSubnets]; - } + for (rangeName in rangeList) { + if (Object.prototype.hasOwnProperty.call(rangeList, rangeName)) { + rangeSubnets = rangeList[rangeName]; + // ECMA5 Array.isArray isn't available everywhere + if (rangeSubnets[0] && !(rangeSubnets[0] instanceof Array)) { + rangeSubnets = [rangeSubnets]; + } - for (i = 0; i < rangeSubnets.length; i++) { - subnet = rangeSubnets[i]; - if (address.kind() === subnet[0].kind() && address.match.apply(address, subnet)) { - return rangeName; - } + for (i = 0; i < rangeSubnets.length; i++) { + subnet = rangeSubnets[i]; + if (address.kind() === subnet[0].kind() && address.match.apply(address, subnet)) { + return rangeName; } } } - - return defaultName; - }; - - // Export for both the CommonJS and browser-like environment - if (typeof module !== 'undefined' && module.exports) { - module.exports = ipaddr; - - } else { - root.ipaddr = ipaddr; } -}(this)); + return defaultName; +}; + diff --git a/test/ipaddr.test.js b/test/ipaddr.test.js index fd724c5..ae12d1b 100644 --- a/test/ipaddr.test.js +++ b/test/ipaddr.test.js @@ -1,276 +1,274 @@ -const assert = require('node:assert') -const { describe, it } = require('node:test') +import { ok, doesNotThrow, throws, equal, deepEqual } from 'node:assert'; +import { describe, it } from 'node:test'; -const { IPv6, IPv4 } = require('../lib/ipaddr'); - -const ipaddr = require('../lib/ipaddr'); +import { IPv4, IPv6, parseCIDR, parse, process, isValid, subnetMatch, fromByteArray } from '../lib/ipaddr.js'; describe('ipaddr', () => { it('should define main classes', () => { - assert.ok(ipaddr.IPv4, 'defines IPv4 class'); - assert.ok(ipaddr.IPv6, 'defines IPv6 class'); + ok(IPv4, 'defines IPv4 class'); + ok(IPv6, 'defines IPv6 class'); }) it('can construct IPv4 from octets', () => { - assert.doesNotThrow(() => { - new ipaddr.IPv4([192, 168, 1, 2]); + doesNotThrow(() => { + new IPv4([192, 168, 1, 2]); }); }) it('refuses to construct invalid IPv4', () => { - assert.throws(() => { - new ipaddr.IPv4([300, 1, 2, 3]); + throws(() => { + new IPv4([300, 1, 2, 3]); }); - assert.throws(() => { - new ipaddr.IPv4([8, 8, 8]); + throws(() => { + new IPv4([8, 8, 8]); }); }) it('converts IPv4 to string correctly', () => { - let addr = new ipaddr.IPv4([192, 168, 1, 1]); - assert.equal(addr.toString(), '192.168.1.1'); - assert.equal(addr.toNormalizedString(), '192.168.1.1'); + let addr = new IPv4([192, 168, 1, 1]); + equal(addr.toString(), '192.168.1.1'); + equal(addr.toNormalizedString(), '192.168.1.1'); }) it('converts IPv4 CIDR to string correctly', () => { - assert.equal(IPv4.parseCIDR('192.168.1.1/24').toString(), '192.168.1.1/24'); + equal(IPv4.parseCIDR('192.168.1.1/24').toString(), '192.168.1.1/24'); }) it('returns correct kind for IPv4', () => { - let addr = new ipaddr.IPv4([1, 2, 3, 4]); - assert.equal(addr.kind(), 'ipv4'); + let addr = new IPv4([1, 2, 3, 4]); + equal(addr.kind(), 'ipv4'); }) it('allows to access IPv4 octets', () => { - let addr = new ipaddr.IPv4([42, 0, 0, 0]); - assert.equal(addr.octets[0], 42); + let addr = new IPv4([42, 0, 0, 0]); + equal(addr.octets[0], 42); }) it('checks IPv4 address format', () => { - assert.equal(ipaddr.IPv4.isIPv4('192.168.007.0xa'), true); - assert.equal(ipaddr.IPv4.isIPv4('1024.0.0.1'), true); - assert.equal(ipaddr.IPv4.isIPv4('8.0xa.wtf.6'), false); + equal(IPv4.isIPv4('192.168.007.0xa'), true); + equal(IPv4.isIPv4('1024.0.0.1'), true); + equal(IPv4.isIPv4('8.0xa.wtf.6'), false); }) it('validates IPv4 addresses', () => { - assert.equal(ipaddr.IPv4.isValid('192.168.007.0xa'), true); - assert.equal(ipaddr.IPv4.isValid('1024.0.0.1'), false); - assert.equal(ipaddr.IPv4.isValid('8.0xa.wtf.6'), false); + equal(IPv4.isValid('192.168.007.0xa'), true); + equal(IPv4.isValid('1024.0.0.1'), false); + equal(IPv4.isValid('8.0xa.wtf.6'), false); }) it('validates IPv4 addresses in CIDR notation', () => { - assert.equal(ipaddr.IPv4.isValidCIDR('192.168.1.1/24'), true); - assert.equal(ipaddr.IPv4.isValidCIDR('10.5.0.1'), false); - assert.equal(ipaddr.IPv4.isValidCIDR('0.0.0.0/-1'), false); - assert.equal(ipaddr.IPv4.isValidCIDR('192.168.1.1/999'), false); + equal(IPv4.isValidCIDR('192.168.1.1/24'), true); + equal(IPv4.isValidCIDR('10.5.0.1'), false); + equal(IPv4.isValidCIDR('0.0.0.0/-1'), false); + equal(IPv4.isValidCIDR('192.168.1.1/999'), false); }) it('parses IPv4 in several weird formats', () => { - assert.deepEqual(ipaddr.IPv4.parse('192.168.1.1').octets, [192, 168, 1, 1]); - assert.deepEqual(ipaddr.IPv4.parse('0xc0.168.1.1').octets, [192, 168, 1, 1]); - assert.deepEqual(ipaddr.IPv4.parse('192.0250.1.1').octets, [192, 168, 1, 1]); - assert.deepEqual(ipaddr.IPv4.parse('0xc0a80101').octets, [192, 168, 1, 1]); - assert.deepEqual(ipaddr.IPv4.parse('030052000401').octets, [192, 168, 1, 1]); - assert.deepEqual(ipaddr.IPv4.parse('3232235777').octets, [192, 168, 1, 1]); - assert.deepEqual(ipaddr.IPv4.parse('127.42.258').octets, [127, 42, 1, 2]); - assert.deepEqual(ipaddr.IPv4.parse('127.66051').octets, [127, 1, 2, 3]); - assert.deepEqual(ipaddr.IPv4.parse('10.1.1.0xff').octets, [10, 1, 1, 255]); + deepEqual(IPv4.parse('192.168.1.1').octets, [192, 168, 1, 1]); + deepEqual(IPv4.parse('0xc0.168.1.1').octets, [192, 168, 1, 1]); + deepEqual(IPv4.parse('192.0250.1.1').octets, [192, 168, 1, 1]); + deepEqual(IPv4.parse('0xc0a80101').octets, [192, 168, 1, 1]); + deepEqual(IPv4.parse('030052000401').octets, [192, 168, 1, 1]); + deepEqual(IPv4.parse('3232235777').octets, [192, 168, 1, 1]); + deepEqual(IPv4.parse('127.42.258').octets, [127, 42, 1, 2]); + deepEqual(IPv4.parse('127.66051').octets, [127, 1, 2, 3]); + deepEqual(IPv4.parse('10.1.1.0xff').octets, [10, 1, 1, 255]); }) it('barfs at invalid IPv4', () => { - assert.throws(() => { - ipaddr.IPv4.parse('10.0.0.wtf'); + throws(() => { + IPv4.parse('10.0.0.wtf'); }); - assert.throws(() => { - ipaddr.IPv4.parse('8.0x1ffffff'); + throws(() => { + IPv4.parse('8.0x1ffffff'); }); - assert.throws(() => { - ipaddr.IPv4.parse('8.8.0x1ffff'); + throws(() => { + IPv4.parse('8.8.0x1ffff'); }); - assert.throws(() => { - ipaddr.IPv4.parse('10.048.1.1'); + throws(() => { + IPv4.parse('10.048.1.1'); }); }) it('matches IPv4 CIDR correctly', () => { - let addr = new ipaddr.IPv4([10, 5, 0, 1]); - assert.equal(addr.match(ipaddr.IPv4.parse('0.0.0.0'), 0), true); - assert.equal(addr.match(ipaddr.IPv4.parse('11.0.0.0'), 8), false); - assert.equal(addr.match(ipaddr.IPv4.parse('10.0.0.0'), 8), true); - assert.equal(addr.match(ipaddr.IPv4.parse('10.0.0.1'), 8), true); - assert.equal(addr.match(ipaddr.IPv4.parse('10.0.0.10'), 8), true); - assert.equal(addr.match(ipaddr.IPv4.parse('10.5.5.0'), 16), true); - assert.equal(addr.match(ipaddr.IPv4.parse('10.4.5.0'), 16), false); - assert.equal(addr.match(ipaddr.IPv4.parse('10.4.5.0'), 15), true); - assert.equal(addr.match(ipaddr.IPv4.parse('10.5.0.2'), 32), false); - assert.equal(addr.match(addr, 32), true); + let addr = new IPv4([10, 5, 0, 1]); + equal(addr.match(IPv4.parse('0.0.0.0'), 0), true); + equal(addr.match(IPv4.parse('11.0.0.0'), 8), false); + equal(addr.match(IPv4.parse('10.0.0.0'), 8), true); + equal(addr.match(IPv4.parse('10.0.0.1'), 8), true); + equal(addr.match(IPv4.parse('10.0.0.10'), 8), true); + equal(addr.match(IPv4.parse('10.5.5.0'), 16), true); + equal(addr.match(IPv4.parse('10.4.5.0'), 16), false); + equal(addr.match(IPv4.parse('10.4.5.0'), 15), true); + equal(addr.match(IPv4.parse('10.5.0.2'), 32), false); + equal(addr.match(addr, 32), true); }) it('parses CIDR reversible', () => { - assert.equal(ipaddr.parseCIDR('1.2.3.4/24').toString(), '1.2.3.4/24'); - assert.equal(ipaddr.parseCIDR('::1%zone/24').toString(), '::1%zone/24'); + equal(parseCIDR('1.2.3.4/24').toString(), '1.2.3.4/24'); + equal(parseCIDR('::1%zone/24').toString(), '::1%zone/24'); }) it('parses IPv4 CIDR correctly', () => { - let addr = new ipaddr.IPv4([10, 5, 0, 1]); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('0.0.0.0/0')), true); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('11.0.0.0/8')), false); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('10.0.0.0/8')), true); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('10.0.0.1/8')), true); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('10.0.0.10/8')), true); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('10.5.5.0/16')), true); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('10.4.5.0/16')), false); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('10.4.5.0/15')), true); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('10.5.0.2/32')), false); - assert.equal(addr.match(ipaddr.IPv4.parseCIDR('10.5.0.1/32')), true); - assert.throws(() => { - ipaddr.IPv4.parseCIDR('10.5.0.1'); + let addr = new IPv4([10, 5, 0, 1]); + equal(addr.match(IPv4.parseCIDR('0.0.0.0/0')), true); + equal(addr.match(IPv4.parseCIDR('11.0.0.0/8')), false); + equal(addr.match(IPv4.parseCIDR('10.0.0.0/8')), true); + equal(addr.match(IPv4.parseCIDR('10.0.0.1/8')), true); + equal(addr.match(IPv4.parseCIDR('10.0.0.10/8')), true); + equal(addr.match(IPv4.parseCIDR('10.5.5.0/16')), true); + equal(addr.match(IPv4.parseCIDR('10.4.5.0/16')), false); + equal(addr.match(IPv4.parseCIDR('10.4.5.0/15')), true); + equal(addr.match(IPv4.parseCIDR('10.5.0.2/32')), false); + equal(addr.match(IPv4.parseCIDR('10.5.0.1/32')), true); + throws(() => { + IPv4.parseCIDR('10.5.0.1'); }); - assert.throws(() => { - ipaddr.IPv4.parseCIDR('0.0.0.0/-1'); + throws(() => { + IPv4.parseCIDR('0.0.0.0/-1'); }); - assert.throws(() => { - ipaddr.IPv4.parseCIDR('0.0.0.0/33'); + throws(() => { + IPv4.parseCIDR('0.0.0.0/33'); }); }) it('detects reserved IPv4 networks', () => { - assert.equal(ipaddr.IPv4.parse('0.0.0.0').range(), 'unspecified'); - assert.equal(ipaddr.IPv4.parse('0.1.0.0').range(), 'unspecified'); - assert.equal(ipaddr.IPv4.parse('10.1.0.1').range(), 'private'); - assert.equal(ipaddr.IPv4.parse('100.64.0.0').range(), 'carrierGradeNat'); - assert.equal(ipaddr.IPv4.parse('100.127.255.255').range(), 'carrierGradeNat'); - assert.equal(ipaddr.IPv4.parse('192.52.193.1').range(), 'amt'); - assert.equal(ipaddr.IPv4.parse('192.168.2.1').range(), 'private'); - assert.equal(ipaddr.IPv4.parse('192.175.48.0').range(), 'as112'); - assert.equal(ipaddr.IPv4.parse('224.100.0.1').range(), 'multicast'); - assert.equal(ipaddr.IPv4.parse('169.254.15.0').range(), 'linkLocal'); - assert.equal(ipaddr.IPv4.parse('127.1.1.1').range(), 'loopback'); - assert.equal(ipaddr.IPv4.parse('255.255.255.255').range(), 'broadcast'); - assert.equal(ipaddr.IPv4.parse('240.1.2.3').range(), 'reserved'); - assert.equal(ipaddr.IPv4.parse('8.8.8.8').range(), 'unicast'); + equal(IPv4.parse('0.0.0.0').range(), 'unspecified'); + equal(IPv4.parse('0.1.0.0').range(), 'unspecified'); + equal(IPv4.parse('10.1.0.1').range(), 'private'); + equal(IPv4.parse('100.64.0.0').range(), 'carrierGradeNat'); + equal(IPv4.parse('100.127.255.255').range(), 'carrierGradeNat'); + equal(IPv4.parse('192.52.193.1').range(), 'amt'); + equal(IPv4.parse('192.168.2.1').range(), 'private'); + equal(IPv4.parse('192.175.48.0').range(), 'as112'); + equal(IPv4.parse('224.100.0.1').range(), 'multicast'); + equal(IPv4.parse('169.254.15.0').range(), 'linkLocal'); + equal(IPv4.parse('127.1.1.1').range(), 'loopback'); + equal(IPv4.parse('255.255.255.255').range(), 'broadcast'); + equal(IPv4.parse('240.1.2.3').range(), 'reserved'); + equal(IPv4.parse('8.8.8.8').range(), 'unicast'); }) it('checks the conventional IPv4 address format', () => { - assert.equal(ipaddr.IPv4.isValidFourPartDecimal('0.0.0.0'), true); - assert.equal(ipaddr.IPv4.isValidFourPartDecimal('127.0.0.1'), true); - assert.equal(ipaddr.IPv4.isValidFourPartDecimal('192.168.1.1'), true); - assert.equal(ipaddr.IPv4.isValidFourPartDecimal('0xc0.168.1.1'), false); + equal(IPv4.isValidFourPartDecimal('0.0.0.0'), true); + equal(IPv4.isValidFourPartDecimal('127.0.0.1'), true); + equal(IPv4.isValidFourPartDecimal('192.168.1.1'), true); + equal(IPv4.isValidFourPartDecimal('0xc0.168.1.1'), false); }) it('checks the conventional IPv4 CIDR format', () => { - assert.equal(ipaddr.IPv4.isValidCIDRFourPartDecimal('192.168.1.1/24'), true); - assert.equal(ipaddr.IPv4.isValidCIDRFourPartDecimal('0.0.0.0/0'), true); - assert.equal(ipaddr.IPv4.isValidCIDRFourPartDecimal('0xc0.168.1.1/24'), false); - assert.equal(ipaddr.IPv4.isValidCIDRFourPartDecimal('192.168.1.1/33'), false); - assert.equal(ipaddr.IPv4.isValidCIDRFourPartDecimal('192.168.1/24'), false); + equal(IPv4.isValidCIDRFourPartDecimal('192.168.1.1/24'), true); + equal(IPv4.isValidCIDRFourPartDecimal('0.0.0.0/0'), true); + equal(IPv4.isValidCIDRFourPartDecimal('0xc0.168.1.1/24'), false); + equal(IPv4.isValidCIDRFourPartDecimal('192.168.1.1/33'), false); + equal(IPv4.isValidCIDRFourPartDecimal('192.168.1/24'), false); }) it('refuses to construct IPv4 address with trailing and leading zeros', () => { - assert.equal(ipaddr.IPv4.isValidFourPartDecimal('000000192.168.100.2'), false); - assert.equal(ipaddr.IPv4.isValidFourPartDecimal('192.0000168.100.2'), false); - assert.equal(ipaddr.IPv4.isValidFourPartDecimal('192.168.100.00000002'), false); - assert.equal(ipaddr.IPv4.isValidFourPartDecimal('192.168.100.20000000'), false); + equal(IPv4.isValidFourPartDecimal('000000192.168.100.2'), false); + equal(IPv4.isValidFourPartDecimal('192.0000168.100.2'), false); + equal(IPv4.isValidFourPartDecimal('192.168.100.00000002'), false); + equal(IPv4.isValidFourPartDecimal('192.168.100.20000000'), false); }) it('can construct IPv6 from 16bit parts', () => { - assert.doesNotThrow(() => { - new ipaddr.IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); + doesNotThrow(() => { + new IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); }); }) it('can construct IPv6 from 8bit parts', () => { - assert.doesNotThrow(() => { - new ipaddr.IPv6([0x20, 0x01, 0xd, 0xb8, 0xf5, 0x3a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + doesNotThrow(() => { + new IPv6([0x20, 0x01, 0xd, 0xb8, 0xf5, 0x3a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); }); - assert.deepEqual( - new ipaddr.IPv6([0x20, 0x01, 0xd, 0xb8, 0xf5, 0x3a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]), - new ipaddr.IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]) + deepEqual( + new IPv6([0x20, 0x01, 0xd, 0xb8, 0xf5, 0x3a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]), + new IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]) ); }) it('refuses to construct invalid IPv6', () => { - assert.throws(() => { - new ipaddr.IPv6([0xfffff, 0, 0, 0, 0, 0, 0, 1]); + throws(() => { + new IPv6([0xfffff, 0, 0, 0, 0, 0, 0, 1]); }); - assert.throws(() => { - new ipaddr.IPv6([0xfffff, 0, 0, 0, 0, 0, 1]); + throws(() => { + new IPv6([0xfffff, 0, 0, 0, 0, 0, 1]); }); - assert.throws(() => { - new ipaddr.IPv6([0xffff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + throws(() => { + new IPv6([0xffff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); }); }) it('converts IPv6 to string correctly', () => { - let addr = new ipaddr.IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); - assert.equal(addr.toNormalizedString(), '2001:db8:f53a:0:0:0:0:1'); - assert.equal(addr.toFixedLengthString(), '2001:0db8:f53a:0000:0000:0000:0000:0001'); - assert.equal(addr.toString(), '2001:db8:f53a::1'); - assert.equal(new ipaddr.IPv6([0, 0, 0, 0, 0, 0, 0, 0]).toString(), '::'); - assert.equal(new ipaddr.IPv6([0, 0, 0, 0, 0, 0, 0, 1]).toString(), '::1'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]).toString(), '2001:db8::'); - assert.equal(new ipaddr.IPv6([0, 0xff, 0, 0, 0, 0, 0, 0]).toString(), '0:ff::'); - assert.equal(new ipaddr.IPv6([0, 0, 0, 0, 0, 0, 0xff, 0]).toString(), '::ff:0'); - assert.equal(new ipaddr.IPv6([0, 0, 0xff, 0, 0, 0, 0, 0]).toString(), '0:0:ff::'); - assert.equal(new ipaddr.IPv6([0, 0, 0, 0, 0, 0xff, 0, 0]).toString(), '::ff:0:0'); - assert.equal(new ipaddr.IPv6([0, 0, 0, 0xff, 0xff, 0, 0, 0]).toString(), '::ff:ff:0:0:0'); + let addr = new IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); + equal(addr.toNormalizedString(), '2001:db8:f53a:0:0:0:0:1'); + equal(addr.toFixedLengthString(), '2001:0db8:f53a:0000:0000:0000:0000:0001'); + equal(addr.toString(), '2001:db8:f53a::1'); + equal(new IPv6([0, 0, 0, 0, 0, 0, 0, 0]).toString(), '::'); + equal(new IPv6([0, 0, 0, 0, 0, 0, 0, 1]).toString(), '::1'); + equal(new IPv6([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]).toString(), '2001:db8::'); + equal(new IPv6([0, 0xff, 0, 0, 0, 0, 0, 0]).toString(), '0:ff::'); + equal(new IPv6([0, 0, 0, 0, 0, 0, 0xff, 0]).toString(), '::ff:0'); + equal(new IPv6([0, 0, 0xff, 0, 0, 0, 0, 0]).toString(), '0:0:ff::'); + equal(new IPv6([0, 0, 0, 0, 0, 0xff, 0, 0]).toString(), '::ff:0:0'); + equal(new IPv6([0, 0, 0, 0xff, 0xff, 0, 0, 0]).toString(), '::ff:ff:0:0:0'); // RFC 5952, 4.2.3: on equal-length runs the leftmost is compressed, // including when that run is at the start of the address. - assert.equal(new ipaddr.IPv6([0, 0, 0xff, 0xff, 0, 0, 0xff, 0xff]).toString(), '::ff:ff:0:0:ff:ff'); - assert.equal(new ipaddr.IPv6([0, 0, 1, 0, 0, 1, 0, 0]).toString(), '::1:0:0:1:0:0'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0, 0, 1, 0, 0, 1]).toString(), '2001:db8::1:0:0:1'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0xdef, 0x123b, 0x456c, 0x78d]).toString(), '2001:db8:ff:abc:def:123b:456c:78d'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0, 0x123b, 0x456c, 0x78d]).toString(), '2001:db8:ff:abc:0:123b:456c:78d'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0, 0, 0x456c, 0x78d]).toString(), '2001:db8:ff:abc::456c:78d'); + equal(new IPv6([0, 0, 0xff, 0xff, 0, 0, 0xff, 0xff]).toString(), '::ff:ff:0:0:ff:ff'); + equal(new IPv6([0, 0, 1, 0, 0, 1, 0, 0]).toString(), '::1:0:0:1:0:0'); + equal(new IPv6([0x2001, 0xdb8, 0, 0, 1, 0, 0, 1]).toString(), '2001:db8::1:0:0:1'); + equal(new IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0xdef, 0x123b, 0x456c, 0x78d]).toString(), '2001:db8:ff:abc:def:123b:456c:78d'); + equal(new IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0, 0x123b, 0x456c, 0x78d]).toString(), '2001:db8:ff:abc:0:123b:456c:78d'); + equal(new IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0, 0, 0x456c, 0x78d]).toString(), '2001:db8:ff:abc::456c:78d'); }) it('converts IPv6 CIDR to string correctly', () => { - assert.equal(IPv6.parseCIDR('0:0:0:0:0:0:0:0/64').toString(), '::/64'); - assert.equal(IPv6.parseCIDR('0:0:0:ff:ff:0:0:0/64').toString(), '::ff:ff:0:0:0/64'); - assert.equal(IPv6.parseCIDR('2001:db8:ff:abc:def:123b:456c:78d/64').toString(), '2001:db8:ff:abc:def:123b:456c:78d/64'); + equal(IPv6.parseCIDR('0:0:0:0:0:0:0:0/64').toString(), '::/64'); + equal(IPv6.parseCIDR('0:0:0:ff:ff:0:0:0/64').toString(), '::ff:ff:0:0:0/64'); + equal(IPv6.parseCIDR('2001:db8:ff:abc:def:123b:456c:78d/64').toString(), '2001:db8:ff:abc:def:123b:456c:78d/64'); }) it('converts IPv6 to RFC 5952 string correctly', () => { // see https://tools.ietf.org/html/rfc5952#section-4 - let addr = new ipaddr.IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); - assert.equal(addr.toRFC5952String(), '2001:db8:f53a::1'); - assert.equal(new ipaddr.IPv6([0, 0, 0, 0, 0, 0, 0, 0]).toRFC5952String(), '::'); - assert.equal(new ipaddr.IPv6([0, 0, 0, 0, 0, 0, 0, 1]).toRFC5952String(), '::1'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]).toRFC5952String(), '2001:db8::'); + let addr = new IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); + equal(addr.toRFC5952String(), '2001:db8:f53a::1'); + equal(new IPv6([0, 0, 0, 0, 0, 0, 0, 0]).toRFC5952String(), '::'); + equal(new IPv6([0, 0, 0, 0, 0, 0, 0, 1]).toRFC5952String(), '::1'); + equal(new IPv6([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]).toRFC5952String(), '2001:db8::'); // longest set of zeroes gets collapsed (section 4.2.3) - assert.equal(new ipaddr.IPv6([0, 0xff, 0, 0, 0, 0, 0, 0]).toRFC5952String(), '0:ff::'); - assert.equal(new ipaddr.IPv6([0, 0, 0, 0, 0, 0, 0xff, 0]).toRFC5952String(), '::ff:0'); - assert.equal(new ipaddr.IPv6([0, 0, 0xff, 0, 0, 0, 0, 0]).toRFC5952String(), '0:0:ff::'); - assert.equal(new ipaddr.IPv6([0, 0, 0, 0, 0, 0xff, 0, 0]).toRFC5952String(), '::ff:0:0'); + equal(new IPv6([0, 0xff, 0, 0, 0, 0, 0, 0]).toRFC5952String(), '0:ff::'); + equal(new IPv6([0, 0, 0, 0, 0, 0, 0xff, 0]).toRFC5952String(), '::ff:0'); + equal(new IPv6([0, 0, 0xff, 0, 0, 0, 0, 0]).toRFC5952String(), '0:0:ff::'); + equal(new IPv6([0, 0, 0, 0, 0, 0xff, 0, 0]).toRFC5952String(), '::ff:0:0'); - assert.equal(new ipaddr.IPv6([0x2001, 0, 0, 0, 0xff, 0, 0, 0]).toRFC5952String(), '2001::ff:0:0:0'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0xdef, 0x123b, 0x456c, 0x78d]).toRFC5952String(), '2001:db8:ff:abc:def:123b:456c:78d'); + equal(new IPv6([0x2001, 0, 0, 0, 0xff, 0, 0, 0]).toRFC5952String(), '2001::ff:0:0:0'); + equal(new IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0xdef, 0x123b, 0x456c, 0x78d]).toRFC5952String(), '2001:db8:ff:abc:def:123b:456c:78d'); // don't shorten single 0s (section 4.2.2) - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0, 0x123b, 0x456c, 0x78d]).toRFC5952String(), '2001:db8:ff:abc:0:123b:456c:78d'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0x78d, 0x123b, 0x456c, 0]).toRFC5952String(), '2001:db8:ff:abc:78d:123b:456c:0'); - assert.equal(new ipaddr.IPv6([0, 0xdb8, 0xff, 0xabc, 0x78d, 0x123b, 0x456c, 0x2001]).toRFC5952String(), '0:db8:ff:abc:78d:123b:456c:2001'); + equal(new IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0, 0x123b, 0x456c, 0x78d]).toRFC5952String(), '2001:db8:ff:abc:0:123b:456c:78d'); + equal(new IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0x78d, 0x123b, 0x456c, 0]).toRFC5952String(), '2001:db8:ff:abc:78d:123b:456c:0'); + equal(new IPv6([0, 0xdb8, 0xff, 0xabc, 0x78d, 0x123b, 0x456c, 0x2001]).toRFC5952String(), '0:db8:ff:abc:78d:123b:456c:2001'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0, 0, 0x456c, 0x78d]).toRFC5952String(), '2001:db8:ff:abc::456c:78d'); + equal(new IPv6([0x2001, 0xdb8, 0xff, 0xabc, 0, 0, 0x456c, 0x78d]).toRFC5952String(), '2001:db8:ff:abc::456c:78d'); }) it('returns IPv6 zoneIndex', () => { - let addr = new ipaddr.IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1], 'utun0'); - assert.equal(addr.toNormalizedString(), '2001:db8:f53a:0:0:0:0:1%utun0'); - assert.equal(addr.toString(), '2001:db8:f53a::1%utun0'); + let addr = new IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1], 'utun0'); + equal(addr.toNormalizedString(), '2001:db8:f53a:0:0:0:0:1%utun0'); + equal(addr.toString(), '2001:db8:f53a::1%utun0'); - assert.equal( - ipaddr.parse('2001:db8:f53a::1%2').toString(), + equal( + parse('2001:db8:f53a::1%2').toString(), '2001:db8:f53a::1%2' ); - assert.equal( - ipaddr.parse('2001:db8:f53a::1%WAT').toString(), + equal( + parse('2001:db8:f53a::1%WAT').toString(), '2001:db8:f53a::1%WAT' ); - assert.equal( - ipaddr.parse('2001:db8:f53a::1%sUp').toString(), + equal( + parse('2001:db8:f53a::1%sUp').toString(), '2001:db8:f53a::1%sUp' ); }) @@ -278,451 +276,451 @@ describe('ipaddr', () => { it('compresses a trailing zero run when a zoneIndex is present', () => { // The zone identifier (RFC 4007) is not part of the address and must // not prevent zero-run compression at the end of it (RFC 5952, 4.2.2). - assert.equal(new ipaddr.IPv6([0xfe80, 0, 0, 0, 0, 0, 0, 0], 'eth0').toString(), 'fe80::%eth0'); - assert.equal(new ipaddr.IPv6([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0], 'eth0').toRFC5952String(), '2001:db8::%eth0'); - assert.equal(ipaddr.parse('fe80::%eth0').toString(), 'fe80::%eth0'); - assert.equal(ipaddr.parse('::%eth0').toString(), '::%eth0'); + equal(new IPv6([0xfe80, 0, 0, 0, 0, 0, 0, 0], 'eth0').toString(), 'fe80::%eth0'); + equal(new IPv6([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0], 'eth0').toRFC5952String(), '2001:db8::%eth0'); + equal(parse('fe80::%eth0').toString(), 'fe80::%eth0'); + equal(parse('::%eth0').toString(), '::%eth0'); }) it('returns IPv6 zoneIndex for IPv4-mapped IPv6 addresses', () => { - let addr = ipaddr.parse('::ffff:192.168.1.1%eth0'); - assert.equal(addr.toNormalizedString(), '0:0:0:0:0:ffff:c0a8:101%eth0'); - assert.equal(addr.toString(), '::ffff:c0a8:101%eth0'); + let addr = parse('::ffff:192.168.1.1%eth0'); + equal(addr.toNormalizedString(), '0:0:0:0:0:ffff:c0a8:101%eth0'); + equal(addr.toString(), '::ffff:c0a8:101%eth0'); - assert.equal( - ipaddr.parse('::ffff:192.168.1.1%2').toString(), + equal( + parse('::ffff:192.168.1.1%2').toString(), '::ffff:c0a8:101%2' ); - assert.equal( - ipaddr.parse('::ffff:192.168.1.1%WAT').toString(), + equal( + parse('::ffff:192.168.1.1%WAT').toString(), '::ffff:c0a8:101%WAT' ); - assert.equal( - ipaddr.parse('::ffff:192.168.1.1%sUp').toString(), + equal( + parse('::ffff:192.168.1.1%sUp').toString(), '::ffff:c0a8:101%sUp' ); }) it('returns correct kind for IPv6', () => { - let addr = new ipaddr.IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); - assert.equal(addr.kind(), 'ipv6'); + let addr = new IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); + equal(addr.kind(), 'ipv6'); }) it('allows to access IPv6 address parts', () => { - let addr = new ipaddr.IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 42, 0, 1]); - assert.equal(addr.parts[5], 42); + let addr = new IPv6([0x2001, 0xdb8, 0xf53a, 0, 0, 42, 0, 1]); + equal(addr.parts[5], 42); }) it('checks IPv6 address format', () => { - assert.equal(ipaddr.IPv6.isIPv6('2001:db8:F53A::1'), true); - assert.equal(ipaddr.IPv6.isIPv6('200001::1'), true); - assert.equal(ipaddr.IPv6.isIPv6('::ffff:192.168.1.1'), true); - assert.equal(ipaddr.IPv6.isIPv6('::ffff:192.168.1.1%z'), true); - assert.equal(ipaddr.IPv6.isIPv6('::10.2.3.4'), true); - assert.equal(ipaddr.IPv6.isIPv6('::12.34.56.78%z'), true); - assert.equal(ipaddr.IPv6.isIPv6('::ffff:300.168.1.1'), false); - assert.equal(ipaddr.IPv6.isIPv6('::ffff:300.168.1.1:0'), false); - assert.equal(ipaddr.IPv6.isIPv6('fe80::wtf'), false); - assert.equal(ipaddr.IPv6.isIPv6('fe80::%'), false); + equal(IPv6.isIPv6('2001:db8:F53A::1'), true); + equal(IPv6.isIPv6('200001::1'), true); + equal(IPv6.isIPv6('::ffff:192.168.1.1'), true); + equal(IPv6.isIPv6('::ffff:192.168.1.1%z'), true); + equal(IPv6.isIPv6('::10.2.3.4'), true); + equal(IPv6.isIPv6('::12.34.56.78%z'), true); + equal(IPv6.isIPv6('::ffff:300.168.1.1'), false); + equal(IPv6.isIPv6('::ffff:300.168.1.1:0'), false); + equal(IPv6.isIPv6('fe80::wtf'), false); + equal(IPv6.isIPv6('fe80::%'), false); }) it('validates IPv6 addresses', () => { - assert.equal(ipaddr.IPv6.isValid('2001:db8:F53A::1'), true); - assert.equal(ipaddr.IPv6.isValid('200001::1'), false); - assert.equal(ipaddr.IPv6.isValid('::ffff:192.168.1.1'), true); - assert.equal(ipaddr.IPv6.isValid('::ffff:192.168.1.1%z'), true); - assert.equal(ipaddr.IPv6.isValid('::1.1.1.1'), true); - assert.equal(ipaddr.IPv6.isValid('::1.2.3.4%z'), true); - assert.equal(ipaddr.IPv6.isValid('::ffff:300.168.1.1'), false); - assert.equal(ipaddr.IPv6.isValid('::ffff:300.168.1.1:0'), false); - assert.equal(ipaddr.IPv6.isValid('::ffff:222.1.41.9000'), false); - assert.equal(ipaddr.IPv6.isValid('2001:db8::F53A::1'), false); - assert.equal(ipaddr.IPv6.isValid('fe80::wtf'), false); - assert.equal(ipaddr.IPv6.isValid('fe80::%'), false); - assert.equal(ipaddr.IPv6.isValid('2002::2:'), false); - assert.equal(ipaddr.IPv6.isValid('::%z'), true); - - assert.equal(ipaddr.IPv6.isValid(undefined), false); + equal(IPv6.isValid('2001:db8:F53A::1'), true); + equal(IPv6.isValid('200001::1'), false); + equal(IPv6.isValid('::ffff:192.168.1.1'), true); + equal(IPv6.isValid('::ffff:192.168.1.1%z'), true); + equal(IPv6.isValid('::1.1.1.1'), true); + equal(IPv6.isValid('::1.2.3.4%z'), true); + equal(IPv6.isValid('::ffff:300.168.1.1'), false); + equal(IPv6.isValid('::ffff:300.168.1.1:0'), false); + equal(IPv6.isValid('::ffff:222.1.41.9000'), false); + equal(IPv6.isValid('2001:db8::F53A::1'), false); + equal(IPv6.isValid('fe80::wtf'), false); + equal(IPv6.isValid('fe80::%'), false); + equal(IPv6.isValid('2002::2:'), false); + equal(IPv6.isValid('::%z'), true); + + equal(IPv6.isValid(undefined), false); }) it('rejects IPv6 parts longer than four hexadecimal digits', () => { - assert.equal(ipaddr.IPv6.isValid('00000::1'), false); - assert.throws(() => ipaddr.IPv6.parse('00000::1')); - assert.equal(ipaddr.IPv6.isValid('00000:0:0:0:0:0:1.2.3.4'), false); - assert.throws(() => ipaddr.IPv6.parse('00000:0:0:0:0:0:1.2.3.4')); + equal(IPv6.isValid('00000::1'), false); + throws(() => IPv6.parse('00000::1')); + equal(IPv6.isValid('00000:0:0:0:0:0:1.2.3.4'), false); + throws(() => IPv6.parse('00000:0:0:0:0:0:1.2.3.4')); }) it('rejects compression of zero IPv6 parts', () => { - assert.equal(ipaddr.IPv6.isValid('1:2:3:4:5:6:7:8::'), false); - assert.throws(() => ipaddr.IPv6.parse('1:2:3:4:5:6:7:8::')); - assert.equal(ipaddr.IPv6.isValid('::1:2:3:4:5:6:7:8'), false); - assert.throws(() => ipaddr.IPv6.parse('::1:2:3:4:5:6:7:8')); + equal(IPv6.isValid('1:2:3:4:5:6:7:8::'), false); + throws(() => IPv6.parse('1:2:3:4:5:6:7:8::')); + equal(IPv6.isValid('::1:2:3:4:5:6:7:8'), false); + throws(() => IPv6.parse('::1:2:3:4:5:6:7:8')); }) it('does not reject compression of exactly one IPv6 part', () => { - assert.equal(ipaddr.IPv6.isValid('1:2:3:4:5:6:7::'), true); - assert.equal(ipaddr.IPv6.isValid('::1:2:3:4:5:6:7'), true); + equal(IPv6.isValid('1:2:3:4:5:6:7::'), true); + equal(IPv6.isValid('::1:2:3:4:5:6:7'), true); }) it('validates IPv6 addresses in CIDR notation', () => { - assert.equal(ipaddr.IPv6.isValidCIDR('::/0'), true); - assert.equal(ipaddr.IPv6.isValidCIDR('2001:db8:F53A::1%z/64'), true); - assert.equal(ipaddr.IPv6.isValidCIDR('2001:db8:F53A::1/-1'), false); - assert.equal(ipaddr.IPv6.isValidCIDR('2001:db8:F53A::1'), false); - assert.equal(ipaddr.IPv6.isValidCIDR('2001:db8:F53A::1/129'), false); - assert.equal(ipaddr.IPv6.isValidCIDR('2001:db8:F53A::1%z/129'), false); - assert.equal(ipaddr.IPv6.isValidCIDR('2001:db8:F53A::1/64%z'), false); - assert.equal(ipaddr.IPv6.isValidCIDR('2001:db8:F53A::1/64%'), false); - assert.equal(ipaddr.IPv6.isValidCIDR('2001:db8:F53A::1/64%z/64'), false); - assert.equal(ipaddr.IPv6.isValidCIDR(undefined), false); + equal(IPv6.isValidCIDR('::/0'), true); + equal(IPv6.isValidCIDR('2001:db8:F53A::1%z/64'), true); + equal(IPv6.isValidCIDR('2001:db8:F53A::1/-1'), false); + equal(IPv6.isValidCIDR('2001:db8:F53A::1'), false); + equal(IPv6.isValidCIDR('2001:db8:F53A::1/129'), false); + equal(IPv6.isValidCIDR('2001:db8:F53A::1%z/129'), false); + equal(IPv6.isValidCIDR('2001:db8:F53A::1/64%z'), false); + equal(IPv6.isValidCIDR('2001:db8:F53A::1/64%'), false); + equal(IPv6.isValidCIDR('2001:db8:F53A::1/64%z/64'), false); + equal(IPv6.isValidCIDR(undefined), false); }) it('parses IPv6 in different formats', () => { - assert.deepEqual(ipaddr.IPv6.parse('2001:db8:F53A:0:0:0:0:1').parts, [0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); - assert.deepEqual(ipaddr.IPv6.parse('fe80::10').parts, [0xfe80, 0, 0, 0, 0, 0, 0, 0x10]); - assert.deepEqual(ipaddr.IPv6.parse('2001:db8:F53A::').parts, [0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 0]); - assert.deepEqual(ipaddr.IPv6.parse('::1').parts, [0, 0, 0, 0, 0, 0, 0, 1]); - assert.deepEqual(ipaddr.IPv6.parse('::8.8.8.8').parts, [0, 0, 0, 0, 0, 0xffff, 2056, 2056]); - assert.deepEqual(ipaddr.IPv6.parse('FFFF::255.255.255.255').parts, [0xffff, 0, 0, 0, 0, 0, 0xffff, 0xffff]); - assert.deepEqual(ipaddr.IPv6.parse('64:ff9a::0.0.0.0').parts, [0x64, 0xff9a, 0, 0, 0, 0, 0, 0]); - assert.deepEqual(ipaddr.IPv6.parse('::').parts, [0, 0, 0, 0, 0, 0, 0, 0]); - assert.deepEqual(ipaddr.IPv6.parse('::%z').parts, [0, 0, 0, 0, 0, 0, 0, 0]); - assert.deepEqual(ipaddr.IPv6.parse('::%z').zoneId, 'z'); + deepEqual(IPv6.parse('2001:db8:F53A:0:0:0:0:1').parts, [0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 1]); + deepEqual(IPv6.parse('fe80::10').parts, [0xfe80, 0, 0, 0, 0, 0, 0, 0x10]); + deepEqual(IPv6.parse('2001:db8:F53A::').parts, [0x2001, 0xdb8, 0xf53a, 0, 0, 0, 0, 0]); + deepEqual(IPv6.parse('::1').parts, [0, 0, 0, 0, 0, 0, 0, 1]); + deepEqual(IPv6.parse('::8.8.8.8').parts, [0, 0, 0, 0, 0, 0xffff, 2056, 2056]); + deepEqual(IPv6.parse('FFFF::255.255.255.255').parts, [0xffff, 0, 0, 0, 0, 0, 0xffff, 0xffff]); + deepEqual(IPv6.parse('64:ff9a::0.0.0.0').parts, [0x64, 0xff9a, 0, 0, 0, 0, 0, 0]); + deepEqual(IPv6.parse('::').parts, [0, 0, 0, 0, 0, 0, 0, 0]); + deepEqual(IPv6.parse('::%z').parts, [0, 0, 0, 0, 0, 0, 0, 0]); + deepEqual(IPv6.parse('::%z').zoneId, 'z'); }) it('barfs at invalid IPv6', () => { - assert.throws(() => { - ipaddr.IPv6.parse('fe80::0::1'); + throws(() => { + IPv6.parse('fe80::0::1'); }); }) it('throws a descriptive error when parsing a malformed IPv6 address', () => { - assert.throws(() => ipaddr.IPv6.parse('foobar'), /string is not formatted like an IPv6 Address/); - assert.throws(() => ipaddr.IPv6.parse('::ffff:999.1.1.1'), /string is not formatted like an IPv6 Address/); - assert.throws(() => ipaddr.IPv6.parse('1::2::3:1.2.3.4'), /string is not formatted like an IPv6 Address/); + throws(() => IPv6.parse('foobar'), /string is not formatted like an IPv6 Address/); + throws(() => IPv6.parse('::ffff:999.1.1.1'), /string is not formatted like an IPv6 Address/); + throws(() => IPv6.parse('1::2::3:1.2.3.4'), /string is not formatted like an IPv6 Address/); }) it('matches IPv6 CIDR correctly', () => { - let addr = ipaddr.IPv6.parse('2001:db8:f53a::1'); - assert.equal(addr.match(ipaddr.IPv6.parse('::'), 0), true); - assert.equal(addr.match(ipaddr.IPv6.parse('2001:db8:f53a::1:1'), 64), true); - assert.equal(addr.match(ipaddr.IPv6.parse('2001:db8:f53b::1:1'), 48), false); - assert.equal(addr.match(ipaddr.IPv6.parse('2001:db8:f531::1:1'), 44), true); - assert.equal(addr.match(ipaddr.IPv6.parse('2001:db8:f500::1'), 40), true); - assert.equal(addr.match(ipaddr.IPv6.parse('2001:db8:f500::1%z'), 40), true); - assert.equal(addr.match(ipaddr.IPv6.parse('2001:db9:f500::1'), 40), false); - assert.equal(addr.match(ipaddr.IPv6.parse('2001:db9:f500::1'), 40), false); - assert.equal(addr.match(ipaddr.IPv6.parse('2001:db9:f500::1%z'), 40), false); - assert.equal(addr.match(addr, 128), true); + let addr = IPv6.parse('2001:db8:f53a::1'); + equal(addr.match(IPv6.parse('::'), 0), true); + equal(addr.match(IPv6.parse('2001:db8:f53a::1:1'), 64), true); + equal(addr.match(IPv6.parse('2001:db8:f53b::1:1'), 48), false); + equal(addr.match(IPv6.parse('2001:db8:f531::1:1'), 44), true); + equal(addr.match(IPv6.parse('2001:db8:f500::1'), 40), true); + equal(addr.match(IPv6.parse('2001:db8:f500::1%z'), 40), true); + equal(addr.match(IPv6.parse('2001:db9:f500::1'), 40), false); + equal(addr.match(IPv6.parse('2001:db9:f500::1'), 40), false); + equal(addr.match(IPv6.parse('2001:db9:f500::1%z'), 40), false); + equal(addr.match(addr, 128), true); }) it('parses IPv6 CIDR correctly', () => { - let addr = ipaddr.IPv6.parse('2001:db8:f53a::1'); - assert.equal(addr.match(ipaddr.IPv6.parseCIDR('::/0')), true); - assert.equal(addr.match(ipaddr.IPv6.parseCIDR('2001:db8:f53a::1:1/64')), true); - assert.equal(addr.match(ipaddr.IPv6.parseCIDR('2001:db8:f53b::1:1/48')), false); - assert.equal(addr.match(ipaddr.IPv6.parseCIDR('2001:db8:f531::1:1/44')), true); - assert.equal(addr.match(ipaddr.IPv6.parseCIDR('2001:db8:f500::1/40')), true); - assert.equal(addr.match(ipaddr.IPv6.parseCIDR('2001:db8:f500::1%z/40')), true); - assert.equal(addr.match(ipaddr.IPv6.parseCIDR('2001:db9:f500::1/40')), false); - assert.equal(addr.match(ipaddr.IPv6.parseCIDR('2001:db9:f500::1%z/40')), false); - assert.equal(addr.match(ipaddr.IPv6.parseCIDR('2001:db8:f53a::1/128')), true); - assert.throws(() => { - ipaddr.IPv6.parseCIDR('2001:db8:f53a::1'); + let addr = IPv6.parse('2001:db8:f53a::1'); + equal(addr.match(IPv6.parseCIDR('::/0')), true); + equal(addr.match(IPv6.parseCIDR('2001:db8:f53a::1:1/64')), true); + equal(addr.match(IPv6.parseCIDR('2001:db8:f53b::1:1/48')), false); + equal(addr.match(IPv6.parseCIDR('2001:db8:f531::1:1/44')), true); + equal(addr.match(IPv6.parseCIDR('2001:db8:f500::1/40')), true); + equal(addr.match(IPv6.parseCIDR('2001:db8:f500::1%z/40')), true); + equal(addr.match(IPv6.parseCIDR('2001:db9:f500::1/40')), false); + equal(addr.match(IPv6.parseCIDR('2001:db9:f500::1%z/40')), false); + equal(addr.match(IPv6.parseCIDR('2001:db8:f53a::1/128')), true); + throws(() => { + IPv6.parseCIDR('2001:db8:f53a::1'); }); - assert.throws(() => { - ipaddr.IPv6.parseCIDR('2001:db8:f53a::1/-1'); + throws(() => { + IPv6.parseCIDR('2001:db8:f53a::1/-1'); }); - assert.throws(() => { - ipaddr.IPv6.parseCIDR('2001:db8:f53a::1/129'); + throws(() => { + IPv6.parseCIDR('2001:db8:f53a::1/129'); }); }) it('converts between IPv4-mapped IPv6 addresses and IPv4 addresses', () => { - let addr = ipaddr.IPv4.parse('77.88.21.11'); + let addr = IPv4.parse('77.88.21.11'); let mapped = addr.toIPv4MappedAddress(); - assert.deepEqual(mapped.parts, [0, 0, 0, 0, 0, 0xffff, 0x4d58, 0x150b]); - assert.deepEqual(mapped.toIPv4Address().octets, addr.octets); + deepEqual(mapped.parts, [0, 0, 0, 0, 0, 0xffff, 0x4d58, 0x150b]); + deepEqual(mapped.toIPv4Address().octets, addr.octets); }) it('refuses to convert non-IPv4-mapped IPv6 address to IPv4 address', () => { - assert.throws(() => { - ipaddr.IPv6.parse('2001:db8::1').toIPv4Address(); + throws(() => { + IPv6.parse('2001:db8::1').toIPv4Address(); }); }) it('detects reserved IPv6 networks', () => { - assert.equal(ipaddr.IPv6.parse('::').range(), 'unspecified'); - assert.equal(ipaddr.IPv6.parse('fe80::1234:5678:abcd:0123').range(), 'linkLocal'); - assert.equal(ipaddr.IPv6.parse('ff00::1234').range(), 'multicast'); - assert.equal(ipaddr.IPv6.parse('::1').range(), 'loopback'); - assert.equal(ipaddr.IPv6.parse('100::42').range(), 'discard'); - assert.equal(ipaddr.IPv6.parse('fc00::').range(), 'uniqueLocal'); - assert.equal(ipaddr.IPv6.parse('::ffff:192.168.1.10').range(), 'ipv4Mapped'); - assert.equal(ipaddr.IPv6.parse('fec0::1234').range(), 'deprecatedSiteLocal'); - assert.equal(ipaddr.IPv6.parse('::ffff:0:192.168.1.10').range(), 'rfc6145'); - assert.equal(ipaddr.IPv6.parse('64:ff9b::1234').range(), 'rfc6052'); - assert.equal(ipaddr.IPv6.parse('64:ff9b:1::1234').range(), 'rfc6052'); - assert.equal(ipaddr.IPv6.parse('2002:1f63:45e8::1').range(), '6to4'); - assert.equal(ipaddr.IPv6.parse('2001::4242').range(), 'teredo'); - assert.equal(ipaddr.IPv6.parse('2001:2::').range(), 'benchmarking'); - assert.equal(ipaddr.IPv6.parse('2001:3::').range(), 'amt'); - assert.equal(ipaddr.IPv6.parse('2001:4:112::').range(), 'as112v6'); - assert.equal(ipaddr.IPv6.parse('2620:4f:8000::').range(), 'as112v6'); - assert.equal(ipaddr.IPv6.parse('2001:10::').range(), 'deprecatedOrchid'); - assert.equal(ipaddr.IPv6.parse('2001:20::').range(), 'orchid2'); - assert.equal(ipaddr.IPv6.parse('2001:30::').range(), 'droneRemoteIdProtocolEntityTags'); - assert.equal(ipaddr.IPv6.parse('2001:db8::3210').range(), 'reserved'); - assert.equal(ipaddr.IPv6.parse('2001:470:8:66::1').range(), 'unicast'); - assert.equal(ipaddr.IPv6.parse('2001:470:8:66::1%z').range(), 'unicast'); - assert.equal(ipaddr.IPv6.parse('5f00::1').range(), 'segmentRouting'); - assert.equal(ipaddr.IPv6.parse('3fff::1').range(), 'reserved'); + equal(IPv6.parse('::').range(), 'unspecified'); + equal(IPv6.parse('fe80::1234:5678:abcd:0123').range(), 'linkLocal'); + equal(IPv6.parse('ff00::1234').range(), 'multicast'); + equal(IPv6.parse('::1').range(), 'loopback'); + equal(IPv6.parse('100::42').range(), 'discard'); + equal(IPv6.parse('fc00::').range(), 'uniqueLocal'); + equal(IPv6.parse('::ffff:192.168.1.10').range(), 'ipv4Mapped'); + equal(IPv6.parse('fec0::1234').range(), 'deprecatedSiteLocal'); + equal(IPv6.parse('::ffff:0:192.168.1.10').range(), 'rfc6145'); + equal(IPv6.parse('64:ff9b::1234').range(), 'rfc6052'); + equal(IPv6.parse('64:ff9b:1::1234').range(), 'rfc6052'); + equal(IPv6.parse('2002:1f63:45e8::1').range(), '6to4'); + equal(IPv6.parse('2001::4242').range(), 'teredo'); + equal(IPv6.parse('2001:2::').range(), 'benchmarking'); + equal(IPv6.parse('2001:3::').range(), 'amt'); + equal(IPv6.parse('2001:4:112::').range(), 'as112v6'); + equal(IPv6.parse('2620:4f:8000::').range(), 'as112v6'); + equal(IPv6.parse('2001:10::').range(), 'deprecatedOrchid'); + equal(IPv6.parse('2001:20::').range(), 'orchid2'); + equal(IPv6.parse('2001:30::').range(), 'droneRemoteIdProtocolEntityTags'); + equal(IPv6.parse('2001:db8::3210').range(), 'reserved'); + equal(IPv6.parse('2001:470:8:66::1').range(), 'unicast'); + equal(IPv6.parse('2001:470:8:66::1%z').range(), 'unicast'); + equal(IPv6.parse('5f00::1').range(), 'segmentRouting'); + equal(IPv6.parse('3fff::1').range(), 'reserved'); }) it('is able to determine IP address type', () => { - assert.equal(ipaddr.parse('8.8.8.8').kind(), 'ipv4'); - assert.equal(ipaddr.parse('2001:db8:3312::1').kind(), 'ipv6'); - assert.equal(ipaddr.parse('2001:db8:3312::1%z').kind(), 'ipv6'); + equal(parse('8.8.8.8').kind(), 'ipv4'); + equal(parse('2001:db8:3312::1').kind(), 'ipv6'); + equal(parse('2001:db8:3312::1%z').kind(), 'ipv6'); }) it('throws an error if tried to parse an invalid address', () => { - assert.throws(() => { - ipaddr.parse('::some.nonsense'); + throws(() => { + parse('::some.nonsense'); }); }) it('correctly processes IPv4-mapped addresses', () => { - assert.equal(ipaddr.process('8.8.8.8').kind(), 'ipv4'); - assert.equal(ipaddr.process('2001:db8:3312::1').kind(), 'ipv6'); - assert.equal(ipaddr.process('::ffff:192.168.1.1').kind(), 'ipv4'); - assert.equal(ipaddr.process('::ffff:192.168.1.1%z').kind(), 'ipv4'); - assert.equal(ipaddr.process('::8.8.8.8').kind(), 'ipv4'); + equal(process('8.8.8.8').kind(), 'ipv4'); + equal(process('2001:db8:3312::1').kind(), 'ipv6'); + equal(process('::ffff:192.168.1.1').kind(), 'ipv4'); + equal(process('::ffff:192.168.1.1%z').kind(), 'ipv4'); + equal(process('::8.8.8.8').kind(), 'ipv4'); }) it('correctly converts IPv6 and IPv4 addresses to byte arrays', () => { - assert.deepEqual( - ipaddr.parse('1.2.3.4').toByteArray(), + deepEqual( + parse('1.2.3.4').toByteArray(), [0x1, 0x2, 0x3, 0x4] ); // Fuck yeah. The first byte of Google's IPv6 address is 42. 42! - assert.deepEqual( - ipaddr.parse('2a00:1450:8007::68').toByteArray(), + deepEqual( + parse('2a00:1450:8007::68').toByteArray(), [42, 0x00, 0x14, 0x50, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68] ); - assert.deepEqual( - ipaddr.parse('2a00:1450:8007::68%z').toByteArray(), + deepEqual( + parse('2a00:1450:8007::68%z').toByteArray(), [42, 0x00, 0x14, 0x50, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68] ); }) it('correctly parses 1 as an IPv4 address', () => { - assert.equal(ipaddr.IPv6.isValid('1'), false); - assert.equal(ipaddr.IPv4.isValid('1'), true); - assert.deepEqual(new ipaddr.IPv4([0, 0, 0, 1]), ipaddr.parse('1')); + equal(IPv6.isValid('1'), false); + equal(IPv4.isValid('1'), true); + deepEqual(new IPv4([0, 0, 0, 1]), parse('1')); }) it('correctly detects IPv4 and IPv6 CIDR addresses', () => { - assert.deepEqual( - [ipaddr.IPv6.parse('fc00::'), 64], - ipaddr.parseCIDR('fc00::/64') + deepEqual( + [IPv6.parse('fc00::'), 64], + parseCIDR('fc00::/64') ); - assert.deepEqual( - [ipaddr.IPv4.parse('1.2.3.4'), 5], - ipaddr.parseCIDR('1.2.3.4/5') + deepEqual( + [IPv4.parse('1.2.3.4'), 5], + parseCIDR('1.2.3.4/5') ); }) it('does not consider a very large or very small number a valid IP address', () => { - assert.equal(ipaddr.isValid('4999999999'), false); - assert.equal(ipaddr.isValid('-1'), false); + equal(isValid('4999999999'), false); + equal(isValid('-1'), false); }) it('does not hang on ::8:8:8:8:8:8:8:8:8', () => { - assert.equal(ipaddr.IPv6.isValid('::8:8:8:8:8:8:8:8:8'), false); - assert.equal(ipaddr.IPv6.isValid('::8:8:8:8:8:8:8:8:8%z'), false); + equal(IPv6.isValid('::8:8:8:8:8:8:8:8:8'), false); + equal(IPv6.isValid('::8:8:8:8:8:8:8:8:8%z'), false); }) it('subnetMatch does not fail on empty range', () => { - ipaddr.subnetMatch(new ipaddr.IPv4([1, 2, 3, 4]), {}, false); - ipaddr.subnetMatch(new ipaddr.IPv4([1, 2, 3, 4]), { subnet: [] }, false); + subnetMatch(new IPv4([1, 2, 3, 4]), {}, false); + subnetMatch(new IPv4([1, 2, 3, 4]), { subnet: [] }, false); }) it('subnetMatch returns default subnet on empty range', () => { - assert.equal(ipaddr.subnetMatch(new ipaddr.IPv4([1, 2, 3, 4]), {}, false), false); - assert.equal(ipaddr.subnetMatch(new ipaddr.IPv4([1, 2, 3, 4]), { subnet: [] }, false), false); + equal(subnetMatch(new IPv4([1, 2, 3, 4]), {}, false), false); + equal(subnetMatch(new IPv4([1, 2, 3, 4]), { subnet: [] }, false), false); }) it('subnetMatch does not fail on IPv4 when looking for IPv6', () => { - let rangelist = { subnet6: ipaddr.parseCIDR('fe80::/64') }; - assert.equal(ipaddr.subnetMatch(new ipaddr.IPv4([1, 2, 3, 4]), rangelist, false), false); + let rangelist = { subnet6: parseCIDR('fe80::/64') }; + equal(subnetMatch(new IPv4([1, 2, 3, 4]), rangelist, false), false); }) it('subnetMatch does not fail on IPv6 when looking for IPv4', () => { - let rangelist = { subnet4: ipaddr.parseCIDR('1.2.3.0/24') }; - assert.equal(ipaddr.subnetMatch(new ipaddr.IPv6([0xfe80, 0, 0, 0, 0, 0, 0, 1]), rangelist, false), false); + let rangelist = { subnet4: parseCIDR('1.2.3.0/24') }; + equal(subnetMatch(new IPv6([0xfe80, 0, 0, 0, 0, 0, 0, 1]), rangelist, false), false); }) it('subnetMatch can use a hybrid IPv4/IPv6 range list', () => { - let rangelist = { dual64: [ipaddr.parseCIDR('1.2.4.0/24'), ipaddr.parseCIDR('2001:1:2:3::/64')] }; - assert.equal(ipaddr.subnetMatch(new ipaddr.IPv4([1, 2, 4, 1]), rangelist, false), 'dual64'); - assert.equal(ipaddr.subnetMatch(new ipaddr.IPv6([0x2001, 1, 2, 3, 0, 0, 0, 1]), rangelist, false), 'dual64'); + let rangelist = { dual64: [parseCIDR('1.2.4.0/24'), parseCIDR('2001:1:2:3::/64')] }; + equal(subnetMatch(new IPv4([1, 2, 4, 1]), rangelist, false), 'dual64'); + equal(subnetMatch(new IPv6([0x2001, 1, 2, 3, 0, 0, 0, 1]), rangelist, false), 'dual64'); }) it('is able to determine IP address type from byte array input', () => { - assert.equal(ipaddr.fromByteArray([0x7f, 0, 0, 1]).kind(), 'ipv4'); - assert.equal(ipaddr.fromByteArray([0x20, 0x01, 0xd, 0xb8, 0xf5, 0x3a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]).kind(), 'ipv6'); - assert.throws(() => { - ipaddr.fromByteArray([1]); + equal(fromByteArray([0x7f, 0, 0, 1]).kind(), 'ipv4'); + equal(fromByteArray([0x20, 0x01, 0xd, 0xb8, 0xf5, 0x3a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]).kind(), 'ipv6'); + throws(() => { + fromByteArray([1]); }); }) it('prefixLengthFromSubnetMask returns proper CIDR notation for standard IPv4 masks', () => { - assert.equal(ipaddr.IPv4.parse('255.255.255.255').prefixLengthFromSubnetMask(), 32); - assert.equal(ipaddr.IPv4.parse('255.255.255.254').prefixLengthFromSubnetMask(), 31); - assert.equal(ipaddr.IPv4.parse('255.255.255.252').prefixLengthFromSubnetMask(), 30); - assert.equal(ipaddr.IPv4.parse('255.255.255.248').prefixLengthFromSubnetMask(), 29); - assert.equal(ipaddr.IPv4.parse('255.255.255.240').prefixLengthFromSubnetMask(), 28); - assert.equal(ipaddr.IPv4.parse('255.255.255.224').prefixLengthFromSubnetMask(), 27); - assert.equal(ipaddr.IPv4.parse('255.255.255.192').prefixLengthFromSubnetMask(), 26); - assert.equal(ipaddr.IPv4.parse('255.255.255.128').prefixLengthFromSubnetMask(), 25); - assert.equal(ipaddr.IPv4.parse('255.255.255.0').prefixLengthFromSubnetMask(), 24); - assert.equal(ipaddr.IPv4.parse('255.255.254.0').prefixLengthFromSubnetMask(), 23); - assert.equal(ipaddr.IPv4.parse('255.255.252.0').prefixLengthFromSubnetMask(), 22); - assert.equal(ipaddr.IPv4.parse('255.255.248.0').prefixLengthFromSubnetMask(), 21); - assert.equal(ipaddr.IPv4.parse('255.255.240.0').prefixLengthFromSubnetMask(), 20); - assert.equal(ipaddr.IPv4.parse('255.255.224.0').prefixLengthFromSubnetMask(), 19); - assert.equal(ipaddr.IPv4.parse('255.255.192.0').prefixLengthFromSubnetMask(), 18); - assert.equal(ipaddr.IPv4.parse('255.255.128.0').prefixLengthFromSubnetMask(), 17); - assert.equal(ipaddr.IPv4.parse('255.255.0.0').prefixLengthFromSubnetMask(), 16); - assert.equal(ipaddr.IPv4.parse('255.254.0.0').prefixLengthFromSubnetMask(), 15); - assert.equal(ipaddr.IPv4.parse('255.252.0.0').prefixLengthFromSubnetMask(), 14); - assert.equal(ipaddr.IPv4.parse('255.248.0.0').prefixLengthFromSubnetMask(), 13); - assert.equal(ipaddr.IPv4.parse('255.240.0.0').prefixLengthFromSubnetMask(), 12); - assert.equal(ipaddr.IPv4.parse('255.224.0.0').prefixLengthFromSubnetMask(), 11); - assert.equal(ipaddr.IPv4.parse('255.192.0.0').prefixLengthFromSubnetMask(), 10); - assert.equal(ipaddr.IPv4.parse('255.128.0.0').prefixLengthFromSubnetMask(), 9); - assert.equal(ipaddr.IPv4.parse('255.0.0.0').prefixLengthFromSubnetMask(), 8); - assert.equal(ipaddr.IPv4.parse('254.0.0.0').prefixLengthFromSubnetMask(), 7); - assert.equal(ipaddr.IPv4.parse('252.0.0.0').prefixLengthFromSubnetMask(), 6); - assert.equal(ipaddr.IPv4.parse('248.0.0.0').prefixLengthFromSubnetMask(), 5); - assert.equal(ipaddr.IPv4.parse('240.0.0.0').prefixLengthFromSubnetMask(), 4); - assert.equal(ipaddr.IPv4.parse('224.0.0.0').prefixLengthFromSubnetMask(), 3); - assert.equal(ipaddr.IPv4.parse('192.0.0.0').prefixLengthFromSubnetMask(), 2); - assert.equal(ipaddr.IPv4.parse('128.0.0.0').prefixLengthFromSubnetMask(), 1); - assert.equal(ipaddr.IPv4.parse('0.0.0.0').prefixLengthFromSubnetMask(), 0); + equal(IPv4.parse('255.255.255.255').prefixLengthFromSubnetMask(), 32); + equal(IPv4.parse('255.255.255.254').prefixLengthFromSubnetMask(), 31); + equal(IPv4.parse('255.255.255.252').prefixLengthFromSubnetMask(), 30); + equal(IPv4.parse('255.255.255.248').prefixLengthFromSubnetMask(), 29); + equal(IPv4.parse('255.255.255.240').prefixLengthFromSubnetMask(), 28); + equal(IPv4.parse('255.255.255.224').prefixLengthFromSubnetMask(), 27); + equal(IPv4.parse('255.255.255.192').prefixLengthFromSubnetMask(), 26); + equal(IPv4.parse('255.255.255.128').prefixLengthFromSubnetMask(), 25); + equal(IPv4.parse('255.255.255.0').prefixLengthFromSubnetMask(), 24); + equal(IPv4.parse('255.255.254.0').prefixLengthFromSubnetMask(), 23); + equal(IPv4.parse('255.255.252.0').prefixLengthFromSubnetMask(), 22); + equal(IPv4.parse('255.255.248.0').prefixLengthFromSubnetMask(), 21); + equal(IPv4.parse('255.255.240.0').prefixLengthFromSubnetMask(), 20); + equal(IPv4.parse('255.255.224.0').prefixLengthFromSubnetMask(), 19); + equal(IPv4.parse('255.255.192.0').prefixLengthFromSubnetMask(), 18); + equal(IPv4.parse('255.255.128.0').prefixLengthFromSubnetMask(), 17); + equal(IPv4.parse('255.255.0.0').prefixLengthFromSubnetMask(), 16); + equal(IPv4.parse('255.254.0.0').prefixLengthFromSubnetMask(), 15); + equal(IPv4.parse('255.252.0.0').prefixLengthFromSubnetMask(), 14); + equal(IPv4.parse('255.248.0.0').prefixLengthFromSubnetMask(), 13); + equal(IPv4.parse('255.240.0.0').prefixLengthFromSubnetMask(), 12); + equal(IPv4.parse('255.224.0.0').prefixLengthFromSubnetMask(), 11); + equal(IPv4.parse('255.192.0.0').prefixLengthFromSubnetMask(), 10); + equal(IPv4.parse('255.128.0.0').prefixLengthFromSubnetMask(), 9); + equal(IPv4.parse('255.0.0.0').prefixLengthFromSubnetMask(), 8); + equal(IPv4.parse('254.0.0.0').prefixLengthFromSubnetMask(), 7); + equal(IPv4.parse('252.0.0.0').prefixLengthFromSubnetMask(), 6); + equal(IPv4.parse('248.0.0.0').prefixLengthFromSubnetMask(), 5); + equal(IPv4.parse('240.0.0.0').prefixLengthFromSubnetMask(), 4); + equal(IPv4.parse('224.0.0.0').prefixLengthFromSubnetMask(), 3); + equal(IPv4.parse('192.0.0.0').prefixLengthFromSubnetMask(), 2); + equal(IPv4.parse('128.0.0.0').prefixLengthFromSubnetMask(), 1); + equal(IPv4.parse('0.0.0.0').prefixLengthFromSubnetMask(), 0); // negative cases - assert.equal(ipaddr.IPv4.parse('192.168.255.0').prefixLengthFromSubnetMask(), null); - assert.equal(ipaddr.IPv4.parse('255.0.255.0').prefixLengthFromSubnetMask(), null); + equal(IPv4.parse('192.168.255.0').prefixLengthFromSubnetMask(), null); + equal(IPv4.parse('255.0.255.0').prefixLengthFromSubnetMask(), null); }) it('prefixLengthFromSubnetMask returns proper CIDR notation for standard IPv6 masks', () => { - assert.equal(ipaddr.IPv6.parse('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff').prefixLengthFromSubnetMask(), 128); - assert.equal(ipaddr.IPv6.parse('ffff:ffff:ffff:ffff::').prefixLengthFromSubnetMask(), 64); - assert.equal(ipaddr.IPv6.parse('ffff:ffff:ffff:ff80::').prefixLengthFromSubnetMask(), 57); - assert.equal(ipaddr.IPv6.parse('ffff:ffff:ffff::').prefixLengthFromSubnetMask(), 48); - assert.equal(ipaddr.IPv6.parse('ffff:ffff:ffff::%z').prefixLengthFromSubnetMask(), 48); - assert.equal(ipaddr.IPv6.parse('::').prefixLengthFromSubnetMask(), 0); - assert.equal(ipaddr.IPv6.parse('::%z').prefixLengthFromSubnetMask(), 0); + equal(IPv6.parse('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff').prefixLengthFromSubnetMask(), 128); + equal(IPv6.parse('ffff:ffff:ffff:ffff::').prefixLengthFromSubnetMask(), 64); + equal(IPv6.parse('ffff:ffff:ffff:ff80::').prefixLengthFromSubnetMask(), 57); + equal(IPv6.parse('ffff:ffff:ffff::').prefixLengthFromSubnetMask(), 48); + equal(IPv6.parse('ffff:ffff:ffff::%z').prefixLengthFromSubnetMask(), 48); + equal(IPv6.parse('::').prefixLengthFromSubnetMask(), 0); + equal(IPv6.parse('::%z').prefixLengthFromSubnetMask(), 0); // negative cases - assert.equal(ipaddr.IPv6.parse('2001:db8::').prefixLengthFromSubnetMask(), null); - assert.equal(ipaddr.IPv6.parse('ffff:0:0:ffff::').prefixLengthFromSubnetMask(), null); - assert.equal(ipaddr.IPv6.parse('ffff:0:0:ffff::%z').prefixLengthFromSubnetMask(), null); + equal(IPv6.parse('2001:db8::').prefixLengthFromSubnetMask(), null); + equal(IPv6.parse('ffff:0:0:ffff::').prefixLengthFromSubnetMask(), null); + equal(IPv6.parse('ffff:0:0:ffff::%z').prefixLengthFromSubnetMask(), null); }) it('subnetMaskFromPrefixLength returns correct IPv4 subnet mask given prefix length', () => { - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(0), '0.0.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(1), '128.0.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(2), '192.0.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(3), '224.0.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(4), '240.0.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(5), '248.0.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(6), '252.0.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(7), '254.0.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(8), '255.0.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(9), '255.128.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(10), '255.192.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(11), '255.224.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(12), '255.240.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(13), '255.248.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(14), '255.252.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(15), '255.254.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(16), '255.255.0.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(17), '255.255.128.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(18), '255.255.192.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(19), '255.255.224.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(20), '255.255.240.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(21), '255.255.248.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(22), '255.255.252.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(23), '255.255.254.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(24), '255.255.255.0'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(25), '255.255.255.128'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(26), '255.255.255.192'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(27), '255.255.255.224'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(28), '255.255.255.240'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(29), '255.255.255.248'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(30), '255.255.255.252'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(31), '255.255.255.254'); - assert.equal(ipaddr.IPv4.subnetMaskFromPrefixLength(32), '255.255.255.255'); + equal(IPv4.subnetMaskFromPrefixLength(0), '0.0.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(1), '128.0.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(2), '192.0.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(3), '224.0.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(4), '240.0.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(5), '248.0.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(6), '252.0.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(7), '254.0.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(8), '255.0.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(9), '255.128.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(10), '255.192.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(11), '255.224.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(12), '255.240.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(13), '255.248.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(14), '255.252.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(15), '255.254.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(16), '255.255.0.0'); + equal(IPv4.subnetMaskFromPrefixLength(17), '255.255.128.0'); + equal(IPv4.subnetMaskFromPrefixLength(18), '255.255.192.0'); + equal(IPv4.subnetMaskFromPrefixLength(19), '255.255.224.0'); + equal(IPv4.subnetMaskFromPrefixLength(20), '255.255.240.0'); + equal(IPv4.subnetMaskFromPrefixLength(21), '255.255.248.0'); + equal(IPv4.subnetMaskFromPrefixLength(22), '255.255.252.0'); + equal(IPv4.subnetMaskFromPrefixLength(23), '255.255.254.0'); + equal(IPv4.subnetMaskFromPrefixLength(24), '255.255.255.0'); + equal(IPv4.subnetMaskFromPrefixLength(25), '255.255.255.128'); + equal(IPv4.subnetMaskFromPrefixLength(26), '255.255.255.192'); + equal(IPv4.subnetMaskFromPrefixLength(27), '255.255.255.224'); + equal(IPv4.subnetMaskFromPrefixLength(28), '255.255.255.240'); + equal(IPv4.subnetMaskFromPrefixLength(29), '255.255.255.248'); + equal(IPv4.subnetMaskFromPrefixLength(30), '255.255.255.252'); + equal(IPv4.subnetMaskFromPrefixLength(31), '255.255.255.254'); + equal(IPv4.subnetMaskFromPrefixLength(32), '255.255.255.255'); }) it('subnetMaskFromPrefixLength returns correct IPv6 subnet mask given prefix length', () => { - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(128), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(112), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:0'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(96), 'ffff:ffff:ffff:ffff:ffff:ffff::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(72), 'ffff:ffff:ffff:ffff:ff00::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(64), 'ffff:ffff:ffff:ffff::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(48), 'ffff:ffff:ffff::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(32), 'ffff:ffff::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(16), 'ffff::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(8), 'ff00::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(4), 'f000::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(2), 'c000::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(1), '8000::'); - assert.equal(ipaddr.IPv6.subnetMaskFromPrefixLength(0), '::'); + equal(IPv6.subnetMaskFromPrefixLength(128), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'); + equal(IPv6.subnetMaskFromPrefixLength(112), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:0'); + equal(IPv6.subnetMaskFromPrefixLength(96), 'ffff:ffff:ffff:ffff:ffff:ffff::'); + equal(IPv6.subnetMaskFromPrefixLength(72), 'ffff:ffff:ffff:ffff:ff00::'); + equal(IPv6.subnetMaskFromPrefixLength(64), 'ffff:ffff:ffff:ffff::'); + equal(IPv6.subnetMaskFromPrefixLength(48), 'ffff:ffff:ffff::'); + equal(IPv6.subnetMaskFromPrefixLength(32), 'ffff:ffff::'); + equal(IPv6.subnetMaskFromPrefixLength(16), 'ffff::'); + equal(IPv6.subnetMaskFromPrefixLength(8), 'ff00::'); + equal(IPv6.subnetMaskFromPrefixLength(4), 'f000::'); + equal(IPv6.subnetMaskFromPrefixLength(2), 'c000::'); + equal(IPv6.subnetMaskFromPrefixLength(1), '8000::'); + equal(IPv6.subnetMaskFromPrefixLength(0), '::'); }) it('subnetMaskFromPrefixLength throws on an out-of-range or non-numeric prefix length', () => { - assert.throws(() => ipaddr.IPv4.subnetMaskFromPrefixLength(-1)); - assert.throws(() => ipaddr.IPv4.subnetMaskFromPrefixLength(33)); - assert.throws(() => ipaddr.IPv4.subnetMaskFromPrefixLength('not a number')); - assert.throws(() => ipaddr.IPv6.subnetMaskFromPrefixLength(-1)); - assert.throws(() => ipaddr.IPv6.subnetMaskFromPrefixLength(129)); - assert.throws(() => ipaddr.IPv6.subnetMaskFromPrefixLength('not a number')); + throws(() => IPv4.subnetMaskFromPrefixLength(-1)); + throws(() => IPv4.subnetMaskFromPrefixLength(33)); + throws(() => IPv4.subnetMaskFromPrefixLength('not a number')); + throws(() => IPv6.subnetMaskFromPrefixLength(-1)); + throws(() => IPv6.subnetMaskFromPrefixLength(129)); + throws(() => IPv6.subnetMaskFromPrefixLength('not a number')); }) it('broadcastAddressFromCIDR returns correct IPv4 broadcast address', () => { - assert.equal(ipaddr.IPv4.broadcastAddressFromCIDR('172.0.0.1/24'), '172.0.0.255'); - assert.equal(ipaddr.IPv4.broadcastAddressFromCIDR('172.0.0.1/26'), '172.0.0.63'); + equal(IPv4.broadcastAddressFromCIDR('172.0.0.1/24'), '172.0.0.255'); + equal(IPv4.broadcastAddressFromCIDR('172.0.0.1/26'), '172.0.0.63'); }) it('networkAddressFromCIDR returns correct IPv4 network address', () => { - assert.equal(ipaddr.IPv4.networkAddressFromCIDR('172.0.0.1/24'), '172.0.0.0'); - assert.equal(ipaddr.IPv4.networkAddressFromCIDR('172.0.0.1/5'), '168.0.0.0'); + equal(IPv4.networkAddressFromCIDR('172.0.0.1/24'), '172.0.0.0'); + equal(IPv4.networkAddressFromCIDR('172.0.0.1/5'), '168.0.0.0'); }) it('networkAddressFromCIDR returns correct IPv6 network address', () => { - assert.equal(ipaddr.IPv6.networkAddressFromCIDR('::/0'), '::'); - assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f53a::1:1/64'), '2001:db8:f53a::'); - assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f53b::1:1/48'), '2001:db8:f53b::'); - assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f531::1:1/44'), '2001:db8:f530::'); - assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f500::1/40'), '2001:db8:f500::'); - assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f500::1%z/40'), '2001:db8:f500::'); - assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db9:f500::1/40'), '2001:db9:f500::'); - assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db9:f500::1%z/40'), '2001:db9:f500::'); - assert.equal(ipaddr.IPv6.networkAddressFromCIDR('2001:db8:f53a::1/128'), '2001:db8:f53a::1'); + equal(IPv6.networkAddressFromCIDR('::/0'), '::'); + equal(IPv6.networkAddressFromCIDR('2001:db8:f53a::1:1/64'), '2001:db8:f53a::'); + equal(IPv6.networkAddressFromCIDR('2001:db8:f53b::1:1/48'), '2001:db8:f53b::'); + equal(IPv6.networkAddressFromCIDR('2001:db8:f531::1:1/44'), '2001:db8:f530::'); + equal(IPv6.networkAddressFromCIDR('2001:db8:f500::1/40'), '2001:db8:f500::'); + equal(IPv6.networkAddressFromCIDR('2001:db8:f500::1%z/40'), '2001:db8:f500::'); + equal(IPv6.networkAddressFromCIDR('2001:db9:f500::1/40'), '2001:db9:f500::'); + equal(IPv6.networkAddressFromCIDR('2001:db9:f500::1%z/40'), '2001:db9:f500::'); + equal(IPv6.networkAddressFromCIDR('2001:db8:f53a::1/128'), '2001:db8:f53a::1'); }) it('broadcastAddressFromCIDR returns correct IPv6 broadcast address', () => { - assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('::/0'), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'); - assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f53a::1:1/64'), '2001:db8:f53a:0:ffff:ffff:ffff:ffff'); - assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f53b::1:1/48'), '2001:db8:f53b:ffff:ffff:ffff:ffff:ffff'); - assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f531::1:1/44'), '2001:db8:f53f:ffff:ffff:ffff:ffff:ffff'); - assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f500::1/40'), '2001:db8:f5ff:ffff:ffff:ffff:ffff:ffff'); - assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f500::1%z/40'), '2001:db8:f5ff:ffff:ffff:ffff:ffff:ffff'); - assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db9:f500::1/40'), '2001:db9:f5ff:ffff:ffff:ffff:ffff:ffff'); - assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db9:f500::1%z/40'), '2001:db9:f5ff:ffff:ffff:ffff:ffff:ffff'); - assert.equal(ipaddr.IPv6.broadcastAddressFromCIDR('2001:db8:f53a::1/128'), '2001:db8:f53a::1'); + equal(IPv6.broadcastAddressFromCIDR('::/0'), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'); + equal(IPv6.broadcastAddressFromCIDR('2001:db8:f53a::1:1/64'), '2001:db8:f53a:0:ffff:ffff:ffff:ffff'); + equal(IPv6.broadcastAddressFromCIDR('2001:db8:f53b::1:1/48'), '2001:db8:f53b:ffff:ffff:ffff:ffff:ffff'); + equal(IPv6.broadcastAddressFromCIDR('2001:db8:f531::1:1/44'), '2001:db8:f53f:ffff:ffff:ffff:ffff:ffff'); + equal(IPv6.broadcastAddressFromCIDR('2001:db8:f500::1/40'), '2001:db8:f5ff:ffff:ffff:ffff:ffff:ffff'); + equal(IPv6.broadcastAddressFromCIDR('2001:db8:f500::1%z/40'), '2001:db8:f5ff:ffff:ffff:ffff:ffff:ffff'); + equal(IPv6.broadcastAddressFromCIDR('2001:db9:f500::1/40'), '2001:db9:f5ff:ffff:ffff:ffff:ffff:ffff'); + equal(IPv6.broadcastAddressFromCIDR('2001:db9:f500::1%z/40'), '2001:db9:f5ff:ffff:ffff:ffff:ffff:ffff'); + equal(IPv6.broadcastAddressFromCIDR('2001:db8:f53a::1/128'), '2001:db8:f53a::1'); }) }) From 6dd93b57193b3fa119d05e1db6ea586dfb241d04 Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Tue, 4 Aug 2026 16:12:43 -0500 Subject: [PATCH 4/9] Convert IPv4 to a class --- lib/ipaddr.js | 389 +++++++++++++++++++++++++------------------------- 1 file changed, 195 insertions(+), 194 deletions(-) diff --git a/lib/ipaddr.js b/lib/ipaddr.js index b570970..1787874 100644 --- a/lib/ipaddr.js +++ b/lib/ipaddr.js @@ -155,11 +155,13 @@ function padPart (part, length) { } // An IPv4 address (RFC791). -export const IPv4 = (function () { +export class IPv4 { + /** @type number[] */ + octets // Constructs a new IPv4 address from an array of four octets // in network order (MSB first) // Verifies the input. - function IPv4 (octets) { + constructor(octets) { if (octets.length !== 4) { throw new Error('ipaddr: ipv4 octet count should be 4'); } @@ -178,7 +180,7 @@ export const IPv4 = (function () { // Special IPv4 address ranges. // See also https://en.wikipedia.org/wiki/Reserved_IP_addresses - IPv4.prototype.SpecialRanges = { + static SpecialRanges = { unspecified: [[new IPv4([0, 0, 0, 0]), 8]], broadcast: [[new IPv4([255, 255, 255, 255]), 32]], // RFC3171 @@ -217,12 +219,12 @@ export const IPv4 = (function () { }; // The 'kind' method exists on both IPv4 and IPv6 classes. - IPv4.prototype.kind = function () { + kind() { return 'ipv4'; }; // Checks if this address matches other one within given CIDR range. - IPv4.prototype.match = function (other, cidrRange) { + match(other, cidrRange) { let ref; if (cidrRange === undefined) { ref = other; @@ -240,7 +242,7 @@ export const IPv4 = (function () { // returns a number of leading ones in IPv4 address, making sure that // the rest is a solid sequence of 0's (valid netmask) // returns either the CIDR length or null if mask is not valid - IPv4.prototype.prefixLengthFromSubnetMask = function () { + prefixLengthFromSubnetMask() { let cidr = 0; // non-zero encountered stop scanning for zeroes let stop = false; @@ -280,249 +282,248 @@ export const IPv4 = (function () { }; // Checks if the address corresponds to one of the special ranges. - IPv4.prototype.range = function () { - return subnetMatch(this, this.SpecialRanges); + range() { + return subnetMatch(this, IPv4.SpecialRanges); }; // Returns an array of byte-sized values in network order (MSB first) - IPv4.prototype.toByteArray = function () { + toByteArray() { return this.octets.slice(0); }; // Converts this IPv4 address to an IPv4-mapped IPv6 address. - IPv4.prototype.toIPv4MappedAddress = function () { + toIPv4MappedAddress() { return IPv6.parse(`::ffff:${this.toString()}`); }; // Symmetrical method strictly for aligning with the IPv6 methods. - IPv4.prototype.toNormalizedString = function () { + toNormalizedString() { return this.toString(); }; // Returns the address in convenient, decimal-dotted format. - IPv4.prototype.toString = function () { + toString() { return this.octets.join('.'); }; - return IPv4; -})(); + // A utility function to return broadcast address given the IPv4 interface and prefix length in CIDR notation + static broadcastAddressFromCIDR(string) { -// A utility function to return broadcast address given the IPv4 interface and prefix length in CIDR notation -IPv4.broadcastAddressFromCIDR = function (string) { + try { + const cidr = this.parseCIDR(string); + const ipInterfaceOctets = cidr[0].toByteArray(); + const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); + const octets = []; + let i = 0; + while (i < 4) { + // Broadcast address is bitwise OR between ip interface and inverted mask + octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255); + i++; + } - try { - const cidr = this.parseCIDR(string); - const ipInterfaceOctets = cidr[0].toByteArray(); - const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); - const octets = []; - let i = 0; - while (i < 4) { - // Broadcast address is bitwise OR between ip interface and inverted mask - octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255); - i++; + return new this(octets); + } catch (e) { + throw new Error('ipaddr: the address does not have IPv4 CIDR format', { cause: e }); } + }; - return new this(octets); - } catch (e) { - throw new Error('ipaddr: the address does not have IPv4 CIDR format', { cause: e }); - } -}; - -// Checks if a given string is formatted like IPv4 address. -IPv4.isIPv4 = function (string) { - return this.parser(string) !== null; -}; - -// Checks if a given string is a valid IPv4 address. -IPv4.isValid = function (string) { - try { - new this(this.parser(string)); - return true; - } catch { - return false; - } -}; - -// Checks if a given string is a valid IPv4 address in CIDR notation. -IPv4.isValidCIDR = function (string) { - try { - this.parseCIDR(string); - return true; - } catch { - return false; - } -}; - -// Checks if a given string is a full four-part IPv4 Address. -IPv4.isValidFourPartDecimal = function (string) { - if (IPv4.isValid(string) && string.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/)) { - return true; - } else { - return false; - } -}; + // Checks if a given string is formatted like IPv4 address. + static isIPv4(string) { + return this.parser(string) !== null; + }; -// Checks if a given string is a full four-part IPv4 Address with CIDR prefix. -IPv4.isValidCIDRFourPartDecimal = function (string) { - const match = string.match(/^(.+)\/(\d+)$/); + // Checks if a given string is a valid IPv4 address. + static isValid(string) { + try { + new this(this.parser(string)); + return true; + } catch { + return false; + } + }; - if (!IPv4.isValidCIDR(string) || !match) { - return false; - } + // Checks if a given string is a valid IPv4 address in CIDR notation. + static isValidCIDR(string) { + try { + this.parseCIDR(string); + return true; + } catch { + return false; + } + }; - return IPv4.isValidFourPartDecimal(match[1]); -}; + // Checks if a given string is a full four-part IPv4 Address. + static isValidFourPartDecimal(string) { + if (IPv4.isValid(string) && string.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/)) { + return true; + } else { + return false; + } + }; -// A utility function to return network address given the IPv4 interface and prefix length in CIDR notation -IPv4.networkAddressFromCIDR = function (string) { - let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; + // Checks if a given string is a full four-part IPv4 Address with CIDR prefix. + static isValidCIDRFourPartDecimal(string) { + const match = string.match(/^(.+)\/(\d+)$/); - try { - cidr = this.parseCIDR(string); - ipInterfaceOctets = cidr[0].toByteArray(); - subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); - octets = []; - i = 0; - while (i < 4) { - // Network address is bitwise AND between ip interface and mask - octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10)); - i++; + if (!IPv4.isValidCIDR(string) || !match) { + return false; } - return new this(octets); - } catch (e) { - throw new Error('ipaddr: the address does not have IPv4 CIDR format', { cause: e }); - } -}; + return IPv4.isValidFourPartDecimal(match[1]); + }; -// Tries to parse and validate a string with IPv4 address. -// Throws an error if it fails. -IPv4.parse = function (string) { - const parts = this.parser(string); + // A utility function to return network address given the IPv4 interface and prefix length in CIDR notation + static networkAddressFromCIDR(string) { + let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; - if (parts === null) { - throw new Error('ipaddr: string is not formatted like an IPv4 Address'); - } + try { + cidr = this.parseCIDR(string); + ipInterfaceOctets = cidr[0].toByteArray(); + subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); + octets = []; + i = 0; + while (i < 4) { + // Network address is bitwise AND between ip interface and mask + octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10)); + i++; + } - return new this(parts); -}; + return new this(octets); + } catch (e) { + throw new Error('ipaddr: the address does not have IPv4 CIDR format', { cause: e }); + } + }; -// Parses the string as an IPv4 Address with CIDR Notation. -IPv4.parseCIDR = function (string) { - let match; + // Tries to parse and validate a string with IPv4 address. + // Throws an error if it fails. + static parse(string) { + const parts = this.parser(string); - if ((match = string.match(/^(.+)\/(\d+)$/))) { - const maskLength = parseInt(match[2]); - if (maskLength >= 0 && maskLength <= 32) { - const parsed = [this.parse(match[1]), maskLength]; - Object.defineProperty(parsed, 'toString', { - value: function () { - return this.join('/'); - } - }); - return parsed; + if (parts === null) { + throw new Error('ipaddr: string is not formatted like an IPv4 Address'); } - } - throw new Error('ipaddr: string is not formatted like an IPv4 CIDR range'); -}; - -// Classful variants (like a.b, where a is an octet, and b is a 24-bit -// value representing last three octets; this corresponds to a class C -// address) are omitted due to classless nature of modern Internet. -IPv4.parser = function (string) { - let match, part, value; + return new this(parts); + }; - // parseInt recognizes all that octal & hexadecimal weirdness for us - if ((match = string.match(ipv4Regexes.fourOctet))) { - return (function () { - const ref = match.slice(1, 6); - const results = []; + // Parses the string as an IPv4 Address with CIDR Notation. + static parseCIDR(string) { + let match; - for (let i = 0; i < ref.length; i++) { - part = ref[i]; - results.push(parseIntAuto(part)); + if ((match = string.match(/^(.+)\/(\d+)$/))) { + const maskLength = parseInt(match[2]); + if (maskLength >= 0 && maskLength <= 32) { + const parsed = [this.parse(match[1]), maskLength]; + Object.defineProperty(parsed, 'toString', { + value: function () { + return this.join('/'); + } + }); + return parsed; } - - return results; - })(); - } else if ((match = string.match(ipv4Regexes.longValue))) { - value = parseIntAuto(match[1]); - if (value > 0xffffffff || value < 0) { - throw new Error('ipaddr: address outside defined range'); } - return ((function () { - const results = []; - let shift; - - for (shift = 0; shift <= 24; shift += 8) { - results.push((value >> shift) & 0xff); - } + throw new Error('ipaddr: string is not formatted like an IPv4 CIDR range'); + }; - return results; - })()).reverse(); - } else if ((match = string.match(ipv4Regexes.twoOctet))) { - return (function () { - const ref = match.slice(1, 4); - const results = []; + // Classful variants (like a.b, where a is an octet, and b is a 24-bit + // value representing last three octets; this corresponds to a class C + // address) are omitted due to classless nature of modern Internet. + static parser(string) { + let match, part, value; + + // parseInt recognizes all that octal & hexadecimal weirdness for us + if ((match = string.match(ipv4Regexes.fourOctet))) { + return (function () { + const ref = match.slice(1, 6); + const results = []; + + for (let i = 0; i < ref.length; i++) { + part = ref[i]; + results.push(parseIntAuto(part)); + } - value = parseIntAuto(ref[1]); - if (value > 0xffffff || value < 0) { + return results; + })(); + } else if ((match = string.match(ipv4Regexes.longValue))) { + value = parseIntAuto(match[1]); + if (value > 0xffffffff || value < 0) { throw new Error('ipaddr: address outside defined range'); } - results.push(parseIntAuto(ref[0])); - results.push((value >> 16) & 0xff); - results.push((value >> 8) & 0xff); - results.push( value & 0xff); + return ((function () { + const results = []; + let shift; - return results; - })(); - } else if ((match = string.match(ipv4Regexes.threeOctet))) { - return (function () { - const ref = match.slice(1, 5); - const results = []; + for (shift = 0; shift <= 24; shift += 8) { + results.push((value >> shift) & 0xff); + } - value = parseIntAuto(ref[2]); - if (value > 0xffff || value < 0) { - throw new Error('ipaddr: address outside defined range'); - } + return results; + })()).reverse(); + } else if ((match = string.match(ipv4Regexes.twoOctet))) { + return (function () { + const ref = match.slice(1, 4); + const results = []; - results.push(parseIntAuto(ref[0])); - results.push(parseIntAuto(ref[1])); - results.push((value >> 8) & 0xff); - results.push( value & 0xff); + value = parseIntAuto(ref[1]); + if (value > 0xffffff || value < 0) { + throw new Error('ipaddr: address outside defined range'); + } - return results; - })(); - } else { - return null; - } -}; + results.push(parseIntAuto(ref[0])); + results.push((value >> 16) & 0xff); + results.push((value >> 8) & 0xff); + results.push( value & 0xff); + + return results; + })(); + } else if ((match = string.match(ipv4Regexes.threeOctet))) { + return (function () { + const ref = match.slice(1, 5); + const results = []; + + value = parseIntAuto(ref[2]); + if (value > 0xffff || value < 0) { + throw new Error('ipaddr: address outside defined range'); + } -// A utility function to return subnet mask in IPv4 format given the prefix length -IPv4.subnetMaskFromPrefixLength = function (prefix) { - prefix = parseInt(prefix); - if (Number.isNaN(prefix) || prefix < 0 || prefix > 32) { - throw new Error('ipaddr: invalid IPv4 prefix length'); - } + results.push(parseIntAuto(ref[0])); + results.push(parseIntAuto(ref[1])); + results.push((value >> 8) & 0xff); + results.push( value & 0xff); - const octets = [0, 0, 0, 0]; - let j = 0; - const filledOctetCount = Math.floor(prefix / 8); + return results; + })(); + } else { + return null; + } + }; - while (j < filledOctetCount) { - octets[j] = 255; - j++; - } + // A utility function to return subnet mask in IPv4 format given the prefix length + static subnetMaskFromPrefixLength(prefix) { + prefix = parseInt(prefix); + if (Number.isNaN(prefix) || prefix < 0 || prefix > 32) { + throw new Error('ipaddr: invalid IPv4 prefix length'); + } - if (filledOctetCount < 4) { - octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8); - } + const octets = [0, 0, 0, 0]; + let j = 0; + const filledOctetCount = Math.floor(prefix / 8); + + while (j < filledOctetCount) { + octets[j] = 255; + j++; + } + + if (filledOctetCount < 4) { + octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8); + } + + return new this(octets); + }; +} - return new this(octets); -}; // An IPv6 address (RFC2460) export const IPv6 = (function () { From 0e87f34028d9176471c73d7cbf73ce401678bee4 Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Tue, 4 Aug 2026 16:21:42 -0500 Subject: [PATCH 5/9] Convert IPv6 to a class --- lib/ipaddr.js | 326 +++++++++++++++++++++++++------------------------- 1 file changed, 162 insertions(+), 164 deletions(-) diff --git a/lib/ipaddr.js b/lib/ipaddr.js index 1787874..da06924 100644 --- a/lib/ipaddr.js +++ b/lib/ipaddr.js @@ -526,11 +526,11 @@ export class IPv4 { // An IPv6 address (RFC2460) -export const IPv6 = (function () { +export class IPv6 { // Constructs an IPv6 address from an array of eight 16 - bit parts // or sixteen 8 - bit parts in network order(MSB first). // Throws an error if the input is invalid. - function IPv6 (parts, zoneId) { + constructor(parts, zoneId) { let i, part; if (parts.length === 16) { @@ -557,7 +557,7 @@ export const IPv6 = (function () { } // Special IPv6 ranges - IPv6.prototype.SpecialRanges = { + static SpecialRanges = { // RFC4291, here and after unspecified: [new IPv6([0, 0, 0, 0, 0, 0, 0, 0]), 128], linkLocal: [new IPv6([0xfe80, 0, 0, 0, 0, 0, 0, 0]), 10], @@ -610,17 +610,17 @@ export const IPv6 = (function () { }; // Checks if this address is an IPv4-mapped IPv6 address. - IPv6.prototype.isIPv4MappedAddress = function () { + isIPv4MappedAddress() { return this.range() === 'ipv4Mapped'; }; // The 'kind' method exists on both IPv4 and IPv6 classes. - IPv6.prototype.kind = function () { + kind() { return 'ipv6'; }; // Checks if this address matches other one within given CIDR range. - IPv6.prototype.match = function (other, cidrRange) { + match(other, cidrRange) { let ref; if (cidrRange === undefined) { @@ -639,7 +639,7 @@ export const IPv6 = (function () { // returns a number of leading ones in IPv6 address, making sure that // the rest is a solid sequence of 0's (valid netmask) // returns either the CIDR length or null if mask is not valid - IPv6.prototype.prefixLengthFromSubnetMask = function () { + prefixLengthFromSubnetMask() { let cidr = 0; // non-zero encountered stop scanning for zeroes let stop = false; @@ -688,12 +688,12 @@ export const IPv6 = (function () { // Checks if the address corresponds to one of the special ranges. - IPv6.prototype.range = function () { - return subnetMatch(this, this.SpecialRanges); + range() { + return subnetMatch(this, IPv6.SpecialRanges); }; // Returns an array of byte-sized values in network order (MSB first) - IPv6.prototype.toByteArray = function () { + toByteArray() { let part; const bytes = []; const ref = this.parts; @@ -708,7 +708,7 @@ export const IPv6 = (function () { // Returns the address in expanded format with all zeroes included, like // 2001:0db8:0008:0066:0000:0000:0000:0001 - IPv6.prototype.toFixedLengthString = function () { + toFixedLengthString() { const addr = ((function () { const results = []; for (let i = 0; i < this.parts.length; i++) { @@ -729,7 +729,7 @@ export const IPv6 = (function () { // Converts this address to IPv4 address if it is an IPv4-mapped IPv6 address. // Throws an error otherwise. - IPv6.prototype.toIPv4Address = function () { + toIPv4Address() { if (!this.isIPv4MappedAddress()) { throw new Error('ipaddr: trying to convert a generic ipv6 address to ipv4'); } @@ -745,7 +745,7 @@ export const IPv6 = (function () { // 2001:db8:8:66:0:0:0:1 // // Deprecated: use toFixedLengthString() instead. - IPv6.prototype.toNormalizedString = function () { + toNormalizedString() { const addr = ((function () { const results = []; @@ -768,7 +768,7 @@ export const IPv6 = (function () { // Returns the address in compact, human-readable format like // 2001:db8:8:66::1 // in line with RFC 5952 (see https://tools.ietf.org/html/rfc5952#section-4) - IPv6.prototype.toRFC5952String = function () { + toRFC5952String() { const regex = /((^|:)(0(:|$)){2,})/g; // The zone identifier (RFC 4007) is not part of the address; match // against the address alone so a trailing zero run right before the @@ -807,191 +807,189 @@ export const IPv6 = (function () { // Returns the address in compact, human-readable format like // 2001:db8:8:66::1 // Calls toRFC5952String under the hood. - IPv6.prototype.toString = function () { + toString() { return this.toRFC5952String(); }; - return IPv6; - -})(); + // A utility function to return broadcast address given the IPv6 interface and prefix length in CIDR notation + static broadcastAddressFromCIDR = function (string) { + try { + const cidr = this.parseCIDR(string); + const ipInterfaceOctets = cidr[0].toByteArray(); + const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); + const octets = []; + let i = 0; + while (i < 16) { + // Broadcast address is bitwise OR between ip interface and inverted mask + octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255); + i++; + } -// A utility function to return broadcast address given the IPv6 interface and prefix length in CIDR notation -IPv6.broadcastAddressFromCIDR = function (string) { - try { - const cidr = this.parseCIDR(string); - const ipInterfaceOctets = cidr[0].toByteArray(); - const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); - const octets = []; - let i = 0; - while (i < 16) { - // Broadcast address is bitwise OR between ip interface and inverted mask - octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255); - i++; + return new this(octets); + } catch (e) { + throw new Error('ipaddr: the address does not have IPv6 CIDR format', { cause: e }); } + }; - return new this(octets); - } catch (e) { - throw new Error('ipaddr: the address does not have IPv6 CIDR format', { cause: e }); - } -}; + // Checks if a given string is formatted like IPv6 address. + static isIPv6 = function (string) { + return this.parser(string) !== null; + }; -// Checks if a given string is formatted like IPv6 address. -IPv6.isIPv6 = function (string) { - return this.parser(string) !== null; -}; + // Checks to see if string is a valid IPv6 Address + static isValid = function (string) { -// Checks to see if string is a valid IPv6 Address -IPv6.isValid = function (string) { + // Since IPv6.isValid is always called first, this shortcut + // provides a substantial performance gain. + if (typeof string === 'string' && string.indexOf(':') === -1) { + return false; + } - // Since IPv6.isValid is always called first, this shortcut - // provides a substantial performance gain. - if (typeof string === 'string' && string.indexOf(':') === -1) { - return false; - } + try { + const addr = this.parser(string); + new this(addr.parts, addr.zoneId); + return true; + } catch { + return false; + } + }; - try { - const addr = this.parser(string); - new this(addr.parts, addr.zoneId); - return true; - } catch { - return false; - } -}; + // Checks if a given string is a valid IPv6 address in CIDR notation. + static isValidCIDR = function (string) { -// Checks if a given string is a valid IPv6 address in CIDR notation. -IPv6.isValidCIDR = function (string) { + // See note in IPv6.isValid + if (typeof string === 'string' && string.indexOf(':') === -1) { + return false; + } - // See note in IPv6.isValid - if (typeof string === 'string' && string.indexOf(':') === -1) { - return false; - } + try { + this.parseCIDR(string); + return true; + } catch { + return false; + } + }; - try { - this.parseCIDR(string); - return true; - } catch { - return false; - } -}; + // A utility function to return network address given the IPv6 interface and prefix length in CIDR notation + static networkAddressFromCIDR = function (string) { + let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; -// A utility function to return network address given the IPv6 interface and prefix length in CIDR notation -IPv6.networkAddressFromCIDR = function (string) { - let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; + try { + cidr = this.parseCIDR(string); + ipInterfaceOctets = cidr[0].toByteArray(); + subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); + octets = []; + i = 0; + while (i < 16) { + // Network address is bitwise AND between ip interface and mask + octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10)); + i++; + } - try { - cidr = this.parseCIDR(string); - ipInterfaceOctets = cidr[0].toByteArray(); - subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); - octets = []; - i = 0; - while (i < 16) { - // Network address is bitwise AND between ip interface and mask - octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10)); - i++; + return new this(octets); + } catch (e) { + throw new Error('ipaddr: the address does not have IPv6 CIDR format', { cause: e }); } + }; - return new this(octets); - } catch (e) { - throw new Error('ipaddr: the address does not have IPv6 CIDR format', { cause: e }); - } -}; - -// Tries to parse and validate a string with IPv6 address. -// Throws an error if it fails. -IPv6.parse = function (string) { - const addr = this.parser(string); + // Tries to parse and validate a string with IPv6 address. + // Throws an error if it fails. + static parse = function (string) { + const addr = this.parser(string); - if (addr === null) { - throw new Error('ipaddr: string is not formatted like an IPv6 Address'); - } + if (addr === null) { + throw new Error('ipaddr: string is not formatted like an IPv6 Address'); + } - return new this(addr.parts, addr.zoneId); -}; + return new this(addr.parts, addr.zoneId); + }; -IPv6.parseCIDR = function (string) { - let maskLength, match, parsed; + static parseCIDR = function (string) { + let maskLength, match, parsed; - if ((match = string.match(/^(.+)\/(\d+)$/))) { - maskLength = parseInt(match[2]); - if (maskLength >= 0 && maskLength <= 128) { - parsed = [this.parse(match[1]), maskLength]; - Object.defineProperty(parsed, 'toString', { - value: function () { - return this.join('/'); - } - }); - return parsed; + if ((match = string.match(/^(.+)\/(\d+)$/))) { + maskLength = parseInt(match[2]); + if (maskLength >= 0 && maskLength <= 128) { + parsed = [this.parse(match[1]), maskLength]; + Object.defineProperty(parsed, 'toString', { + value: function () { + return this.join('/'); + } + }); + return parsed; + } } - } - throw new Error('ipaddr: string is not formatted like an IPv6 CIDR range'); -}; + throw new Error('ipaddr: string is not formatted like an IPv6 CIDR range'); + }; -// Parse an IPv6 address. -IPv6.parser = function (string) { - let addr, i, match, octet, octets, zoneId; + // Parse an IPv6 address. + static parser = function (string) { + let addr, i, match, octet, octets, zoneId; - if ((match = string.match(ipv6Regexes.deprecatedTransitional))) { - return this.parser(`::ffff:${match[1]}`); - } - if (ipv6Regexes.native.test(string)) { - return expandIPv6(string, 8); - } - if ((match = string.match(ipv6Regexes.transitional))) { - zoneId = match[6] || ''; - addr = match[1] - if (!match[1].endsWith('::')) { - addr = addr.slice(0, -1) + if ((match = string.match(ipv6Regexes.deprecatedTransitional))) { + return this.parser(`::ffff:${match[1]}`); } - addr = expandIPv6(addr + zoneId, 6); - if (addr && addr.parts) { - octets = [ - parseInt(match[2]), - parseInt(match[3]), - parseInt(match[4]), - parseInt(match[5]) - ]; - for (i = 0; i < octets.length; i++) { - octet = octets[i]; - if (!((0 <= octet && octet <= 255))) { - return null; - } + if (ipv6Regexes.native.test(string)) { + return expandIPv6(string, 8); + } + if ((match = string.match(ipv6Regexes.transitional))) { + zoneId = match[6] || ''; + addr = match[1] + if (!match[1].endsWith('::')) { + addr = addr.slice(0, -1) } + addr = expandIPv6(addr + zoneId, 6); + if (addr && addr.parts) { + octets = [ + parseInt(match[2]), + parseInt(match[3]), + parseInt(match[4]), + parseInt(match[5]) + ]; + for (i = 0; i < octets.length; i++) { + octet = octets[i]; + if (!((0 <= octet && octet <= 255))) { + return null; + } + } - addr.parts.push(octets[0] << 8 | octets[1]); - addr.parts.push(octets[2] << 8 | octets[3]); - return { - parts: addr.parts, - zoneId: addr.zoneId - }; + addr.parts.push(octets[0] << 8 | octets[1]); + addr.parts.push(octets[2] << 8 | octets[3]); + return { + parts: addr.parts, + zoneId: addr.zoneId + }; + } } - } - return null; -}; + return null; + }; -// A utility function to return subnet mask in IPv6 format given the prefix length -IPv6.subnetMaskFromPrefixLength = function (prefix) { - prefix = parseInt(prefix); - if (Number.isNaN(prefix) || prefix < 0 || prefix > 128) { - throw new Error('ipaddr: invalid IPv6 prefix length'); - } + // A utility function to return subnet mask in IPv6 format given the prefix length + static subnetMaskFromPrefixLength = function (prefix) { + prefix = parseInt(prefix); + if (Number.isNaN(prefix) || prefix < 0 || prefix > 128) { + throw new Error('ipaddr: invalid IPv6 prefix length'); + } - const octets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - let j = 0; - const filledOctetCount = Math.floor(prefix / 8); + const octets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let j = 0; + const filledOctetCount = Math.floor(prefix / 8); - while (j < filledOctetCount) { - octets[j] = 255; - j++; - } + while (j < filledOctetCount) { + octets[j] = 255; + j++; + } - if (filledOctetCount < 16) { - octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8); - } + if (filledOctetCount < 16) { + octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8); + } + + return new this(octets); + }; +} - return new this(octets); -}; // Try to parse an array in network order (MSB first) for IPv4 and IPv6 export const fromByteArray = function (bytes) { From 19bb88a5792f790e22bb0d4bbec29be2c4d83458 Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Tue, 4 Aug 2026 16:24:22 -0500 Subject: [PATCH 6/9] Update ipaddr.d.ts for ESM, copied from https://github.com/whitequark/ipaddr.js/pull/193 --- lib/ipaddr.d.ts | 124 +++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 65 deletions(-) diff --git a/lib/ipaddr.d.ts b/lib/ipaddr.d.ts index ee02c02..0e1026c 100644 --- a/lib/ipaddr.d.ts +++ b/lib/ipaddr.d.ts @@ -1,72 +1,66 @@ -declare module "ipaddr.js" { - type IPvXRangeDefaults = 'unicast' | 'unspecified' | 'multicast' | 'linkLocal' | 'loopback' | 'reserved' | 'benchmarking' | 'amt'; - type IPv4Range = IPvXRangeDefaults | 'broadcast' | 'carrierGradeNat' | 'private' | 'as112'; - type IPv6Range = IPvXRangeDefaults | 'uniqueLocal' | 'ipv4Mapped' | 'rfc6145' | 'rfc6052' | '6to4' | 'teredo' | 'as112v6' | 'orchid2' | 'droneRemoteIdProtocolEntityTags'; +export type IPvXRangeDefaults = 'unicast' | 'unspecified' | 'multicast' | 'linkLocal' | 'loopback' | 'reserved' | 'benchmarking' | 'amt'; +export type IPv4Range = IPvXRangeDefaults | 'broadcast' | 'carrierGradeNat' | 'private' | 'as112'; +export type IPv6Range = IPvXRangeDefaults | 'uniqueLocal' | 'ipv4Mapped' | 'rfc6145' | 'rfc6052' | '6to4' | 'teredo' | 'as112v6' | 'orchid2' | 'droneRemoteIdProtocolEntityTags'; - interface RangeList { - [name: string]: [T, number] | [T, number][]; - } - - // Common methods/properties for IPv4 and IPv6 classes. - class IP { - prefixLengthFromSubnetMask(): number | null; - toByteArray(): number[]; - toNormalizedString(): string; - toString(): string; - } +export interface RangeList { + [name: string]: [T, number] | [T, number][]; +} - namespace Address { - export function fromByteArray(bytes: number[]): IPv4 | IPv6; - export function isValid(addr: string): boolean; - export function isValidCIDR(addr: string): boolean; - export function parse(addr: string): IPv4 | IPv6; - export function parseCIDR(mask: string): [IPv4 | IPv6, number]; - export function process(addr: string): IPv4 | IPv6; - export function subnetMatch(addr: IPv4 | IPv6, rangeList: RangeList, defaultName?: string): string; +// Common methods/properties for IPv4 and IPv6 classes. +export class IP { + prefixLengthFromSubnetMask(): number | null; + toByteArray(): number[]; + toNormalizedString(): string; + toString(): string; +} - export class IPv4 extends IP { - static broadcastAddressFromCIDR(addr: string): IPv4; - static isIPv4(addr: string): boolean; - static isValid(addr: string): boolean; - static isValidCIDR(addr: string): boolean; - static isValidFourPartDecimal(addr: string): boolean; - static isValidCIDRFourPartDecimal(addr: string): boolean; - static networkAddressFromCIDR(addr: string): IPv4; - static parse(addr: string): IPv4; - static parseCIDR(addr: string): [IPv4, number]; - static subnetMaskFromPrefixLength(prefix: number): IPv4; - constructor(octets: number[]); - octets: number[] +export function fromByteArray(bytes: number[]): IPv4 | IPv6; +export function isValid(addr: string): boolean; +export function isValidCIDR(addr: string): boolean; +export function parse(addr: string): IPv4 | IPv6; +export function parseCIDR(mask: string): [IPv4 | IPv6, number]; +export function process(addr: string): IPv4 | IPv6; +export function subnetMatch(addr: IPv4 | IPv6, rangeList: RangeList, defaultName?: string): string; - kind(): 'ipv4'; - match(what: IPv4 | IPv6 | [IPv4 | IPv6, number], bits?: number): boolean; - range(): IPv4Range; - subnetMatch(rangeList: RangeList, defaultName?: string): string; - toIPv4MappedAddress(): IPv6; - } +export class IPv4 extends IP { + static broadcastAddressFromCIDR(addr: string): IPv4; + static isIPv4(addr: string): boolean; + static isValid(addr: string): boolean; + static isValidCIDR(addr: string): boolean; + static isValidFourPartDecimal(addr: string): boolean; + static isValidCIDRFourPartDecimal(addr: string): boolean; + static networkAddressFromCIDR(addr: string): IPv4; + static parse(addr: string): IPv4; + static parseCIDR(addr: string): [IPv4, number]; + static subnetMaskFromPrefixLength(prefix: number): IPv4; + constructor(octets: number[]); + octets: number[] - export class IPv6 extends IP { - static broadcastAddressFromCIDR(addr: string): IPv6; - static isIPv6(addr: string): boolean; - static isValid(addr: string): boolean; - static isValidCIDR(addr: string): boolean; - static networkAddressFromCIDR(addr: string): IPv6; - static parse(addr: string): IPv6; - static parseCIDR(addr: string): [IPv6, number]; - static subnetMaskFromPrefixLength(prefix: number): IPv6; - constructor(parts: number[]); - parts: number[] - zoneId?: string + kind(): 'ipv4'; + match(what: IPv4 | IPv6 | [IPv4 | IPv6, number], bits?: number): boolean; + range(): IPv4Range; + subnetMatch(rangeList: RangeList, defaultName?: string): string; + toIPv4MappedAddress(): IPv6; +} - isIPv4MappedAddress(): boolean; - kind(): 'ipv6'; - match(what: IPv4 | IPv6 | [IPv4 | IPv6, number], bits?: number): boolean; - range(): IPv6Range; - subnetMatch(rangeList: RangeList, defaultName?: string): string; - toIPv4Address(): IPv4; - toRFC5952String(): string; - } - } +export class IPv6 extends IP { + static broadcastAddressFromCIDR(addr: string): IPv6; + static isIPv6(addr: string): boolean; + static isValid(addr: string): boolean; + static isValidCIDR(addr: string): boolean; + static networkAddressFromCIDR(addr: string): IPv6; + static parse(addr: string): IPv6; + static parseCIDR(addr: string): [IPv6, number]; + static subnetMaskFromPrefixLength(prefix: number): IPv6; + constructor(parts: number[]); + parts: number[] + zoneId?: string - export = Address; -} + isIPv4MappedAddress(): boolean; + kind(): 'ipv6'; + match(what: IPv4 | IPv6 | [IPv4 | IPv6, number], bits?: number): boolean; + range(): IPv6Range; + subnetMatch(rangeList: RangeList, defaultName?: string): string; + toIPv4Address(): IPv4; + toRFC5952String(): string; +} \ No newline at end of file From 555ee8596146cb35d07009f5127dc8f8cf31e35e Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Tue, 4 Aug 2026 16:29:03 -0500 Subject: [PATCH 7/9] Fix version in package.json hahah 3.4.0 would not be the next major version after 2.4.0 regardless, but main has 2.5.0, so both are wrong --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf3a4e8..8d942c3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "ipaddr.js", "description": "A library for manipulating IPv4 and IPv6 addresses in JavaScript.", "type": "module", - "version": "3.4.0", + "version": "3.0.0", "author": "whitequark ", "directories": { "lib": "./lib" From fe7bae2352e9513807c580b49d35f7b3c3897c5c Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Tue, 4 Aug 2026 16:30:20 -0500 Subject: [PATCH 8/9] Fix accidental package.json issues, drift from inital branch --- package.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8d942c3..e04badc 100644 --- a/package.json +++ b/package.json @@ -7,15 +7,16 @@ "directories": { "lib": "./lib" }, - "dependencies": {}, "devDependencies": { - "eslint": "^9.19.0", - "uglify-es": "*" + "@eslint/js": "^10.0.1", + "eslint": "^10.3.0", + "globals": "^17.6.0" }, "scripts": { "lint": "npx eslint lib", "lintfix": "npx eslint --fix lib test", - "test": "node --test" + "test": "node --test", + "test:coverage": "node --test --experimental-test-coverage" }, "files": [ "lib/ipaddr.js", @@ -35,7 +36,7 @@ ".": "./lib/ipaddr.js" }, "engines": { - "node": ">= 10" + "node": ">= 12" }, "license": "MIT", "types": "./lib/ipaddr.d.ts" From 1bddb0ac038ce80f289aef670ad7dffbba27fc29 Mon Sep 17 00:00:00 2001 From: Catherine Date: Tue, 18 Aug 2026 19:33:30 +0000 Subject: [PATCH 9/9] Update .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index e22975d..2cc5f6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .idea node_modules package-lock.json -.DS_Store \ No newline at end of file