cidr-block
Version:
IPv4 and IPv6 address and cidr range utilities
557 lines (554 loc) • 19 kB
JavaScript
import { ipv6 } from './ipv6.js';
import { Ipv6Address } from './ipv6-address.js';
import { InvalidIpv6CidrError, InvalidIpv6CidrRangeError } from './ipv6-errors.js';
/**
* Represents an IPv6 CIDR block with utility methods for subnet operations.
*
* While you can instantiate this class directly, it is recommended to use the
* {@link ipv6.cidr} shorthand method from the `ipv6` namespace instead.
*
* @example Creating CIDR blocks (prefer the shorthand)
* ```ts
* import { ipv6 } from 'cidr-block';
*
* // Recommended: use the ipv6 namespace shorthand
* const cidr1 = ipv6.cidr("2001:db8::/32");
* const cidr2 = ipv6.cidr({ address: "2001:db8::", range: 32 });
* const cidr3 = ipv6.cidr(["2001:db8::", 32]);
* ```
*
* @example Getting CIDR properties
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.baseAddress().toString(); // "2001:db8::"
* cidr.range(); // 32
* cidr.netmask().toString(); // "ffff:ffff::"
* cidr.network().toString(); // "2001:db8::"
* cidr.addressCount(); // 79228162514264337593543950336n
* ```
*
* @example Working with usable addresses
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/126");
* cidr.getFirstUsableAddress()?.toString(); // "2001:db8::1"
* cidr.getLastUsableAddress()?.toString(); // "2001:db8::2"
* ```
*
* @example Checking containment and overlap
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.includes(ipv6.address("2001:db8::1")); // true
* cidr.includes(ipv6.address("2001:db9::1")); // false
* cidr.overlaps("2001:db8::/48"); // true
* cidr.overlaps("2001:db9::/32"); // false
* ```
*
* @example Splitting into subranges
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
*
* // Split into equal /48 subnets
* cidr.subnet(34).map(s => s.toString());
* // ["2001:db8::/34", "2001:db8:4000::/34", "2001:db8:8000::/34", "2001:db8:c000::/34"]
* ```
*
* @example Navigating sequential CIDRs
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.nextCIDR()?.toString(); // "2001:db9::/32"
* cidr.previousCIDR()?.toString(); // "2001:db7::/32"
* ```
*/
class Ipv6Cidr {
#address;
#range;
constructor(address) {
if (!ipv6.isValidCIDR(address)) {
throw new InvalidIpv6CidrError(address);
}
if (typeof address === 'string') {
const [ip, rangeStr] = address.split('/');
this.#address = new Ipv6Address(ip);
this.#range = Number.parseInt(rangeStr, 10);
}
else if (Array.isArray(address)) {
this.#address = new Ipv6Address(address[0]);
this.#range = address[1];
}
else if (typeof address === 'object' && address !== null) {
this.#address = new Ipv6Address(address.address);
this.#range = address.range;
}
else {
throw new InvalidIpv6CidrError(address);
}
}
/**
* Gets the base IPv6 address of the CIDR.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.baseAddress().toString(); // "2001:db8::"
* ```
*
* @returns The base IPv6 address.
*/
baseAddress() {
return this.#address;
}
/**
* Gets the CIDR range.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.range(); // 32
* ```
*
* @returns The CIDR range as a number.
*/
range() {
return this.#range;
}
/**
* Calculates the netmask for the CIDR range.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* ipv6.cidr("2001:db8::/32").netmask().toString(); // "ffff:ffff::"
* ipv6.cidr("2001:db8::/64").netmask().toString(); // "ffff:ffff:ffff:ffff::"
* ipv6.cidr("2001:db8::/128").netmask().toString(); // "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
* ```
*
* @returns The netmask as an Ipv6Address.
*/
netmask() {
if (this.#range === 0) {
return new Ipv6Address(0n);
}
// Create a mask with `range` 1-bits followed by (128 - range) 0-bits
const mask = (1n << 128n) - (1n << BigInt(128 - this.#range));
return new Ipv6Address(mask);
}
/**
* Calculates the hostmask (inverse of the netmask) for the CIDR range.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* ipv6.cidr("2001:db8::/32").hostmask().toString(); // "::ffff:ffff:ffff:ffff:ffff:ffff"
* ipv6.cidr("2001:db8::/64").hostmask().toString(); // "::ffff:ffff:ffff:ffff"
* ipv6.cidr("2001:db8::/48").hostmask().toString(); // "::ffff:ffff:ffff:ffff:ffff"
* ```
*
* @returns The hostmask as an Ipv6Address.
*/
hostmask() {
if (this.#range === 0) {
// For /0, hostmask is all 1s
return new Ipv6Address((1n << 128n) - 1n);
}
if (this.#range === 128) {
// For /128, hostmask is all 0s
return new Ipv6Address(0n);
}
// Hostmask is the bitwise NOT of the netmask
const mask = (1n << 128n) - (1n << BigInt(128 - this.#range));
const hostmask = ((1n << 128n) - 1n) ^ mask;
return new Ipv6Address(hostmask);
}
/**
* Calculates the network address by applying the netmask to the base address.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* ipv6.cidr("2001:db8::1234/32").network().toString(); // "2001:db8::"
* ipv6.cidr("2001:db8:1:2:3:4:5:6/64").network().toString(); // "2001:db8:1:2::"
* ipv6.cidr("2001:db8:abcd::/48").network().toString(); // "2001:db8:abcd::"
* ```
*
* @returns The network address as an Ipv6Address.
*/
network() {
if (this.#range === 0) {
return new Ipv6Address(0n);
}
const mask = (1n << 128n) - (1n << BigInt(128 - this.#range));
const networkNumber = this.#address.toBigInt() & mask;
return new Ipv6Address(networkNumber);
}
/**
* Calculates the network CIDR by applying the netmask to the base address and returning a CIDR with the network address.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* ipv6.cidr("2001:db8::1234/32").networkCIDR().toString(); // "2001:db8::/32"
* ipv6.cidr("2001:db8:1:2:3:4:5:6/64").networkCIDR().toString(); // "2001:db8:1:2::/64"
* ipv6.cidr("2001:db8:abcd::/48").networkCIDR().toString(); // "2001:db8:abcd::/48"
* ```
*
* @returns The network CIDR as an Ipv6Cidr.
*/
networkCIDR() {
if (this.#range === 0) {
return new Ipv6Cidr([0n, 0]);
}
const mask = (1n << 128n) - (1n << BigInt(128 - this.#range));
const networkNumber = this.#address.toBigInt() & mask;
return new Ipv6Cidr([networkNumber, this.#range]);
}
/**
* Returns the string representation of the IPv6 CIDR.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr({ address: "2001:db8::", range: 32 });
* cidr.toString(); // "2001:db8::/32"
* ```
*
* @returns The IPv6 CIDR as a string (example: "2001:db8::/32").
*/
toString() {
return `${this.#address.toString()}/${this.#range}`;
}
/**
* Gets the address and range parts of the IPv6 CIDR.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const [address, range] = ipv6.cidr("2001:db8::/32").rangeParts();
* address.toString(); // "2001:db8::"
* range; // 32
* ```
*
* @returns A tuple containing the IPv6 address and the CIDR range.
*/
rangeParts() {
return [this.#address, this.#range];
}
/**
* Calculates the total number of addresses in the CIDR range.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* ipv6.cidr("2001:db8::/64").addressCount(); // 18446744073709551616n
* ipv6.cidr("2001:db8::/128").addressCount(); // 1n
* ipv6.cidr("2001:db8::/126").addressCount(); // 4n
* ```
*
* @returns The total number of addresses in the CIDR range as a BigInt.
*/
addressCount() {
return 1n << BigInt(128 - this.#range);
}
/**
* Generates IPv6 addresses within the CIDR range.
* Note: For large CIDR ranges, this may generate an extremely large number of addresses.
* Use with caution and consider using a limit parameter.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/126");
* for (const addr of cidr.addresses()) {
* console.log(addr.toString());
* }
* // Output: "2001:db8::", "2001:db8::1", "2001:db8::2", "2001:db8::3"
* ```
*
* @param limit - Optional maximum number of addresses to generate (defaults to all addresses).
* @returns A generator that yields each IPv6 address in the CIDR range.
*/
*addresses(limit) {
const baseBigInt = this.#address.toBigInt();
const count = this.addressCount();
const maxIterations = limit !== undefined && limit < count ? limit : count;
for (let i = 0n; i < maxIterations; i++) {
yield new Ipv6Address(baseBigInt + i);
}
}
/**
* Checks if this CIDR is equal to another CIDR.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.equals("2001:db8::/32"); // true
* cidr.equals({ address: "2001:db8::", range: 32 }); // true
* cidr.equals("2001:db8::/48"); // false
* ```
*
* @param other - The other IPv6 CIDR to compare with.
* @returns True if both CIDRs have the same base address and range; otherwise, false.
*/
equals(other) {
const otherCidr = other instanceof Ipv6Cidr ? other : new Ipv6Cidr(other);
return this.#address.equals(otherCidr.#address) && this.#range === otherCidr.#range;
}
/**
* Checks if there is a next sequential CIDR.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* ipv6.cidr("2001:db8::/32").hasNextCIDR(); // true
* ipv6.cidr("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/120").hasNextCIDR(); // false
* ```
*
* @returns true if there is a next CIDR.
*/
hasNextCIDR() {
const nextBaseBigInt = this.#address.toBigInt() + this.addressCount();
return nextBaseBigInt <= ipv6.MAX_SIZE;
}
/**
* Gets the next sequential CIDR.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.nextCIDR()?.toString(); // "2001:db9::/32"
* ```
*
* @returns The next CIDR, or undefined if there is no next CIDR.
*/
nextCIDR() {
const nextBaseBigInt = this.#address.toBigInt() + this.addressCount();
if (nextBaseBigInt > ipv6.MAX_SIZE) {
return undefined;
}
return new Ipv6Cidr([nextBaseBigInt, this.#range]);
}
/**
* Checks if there is a previous sequential CIDR.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* ipv6.cidr("2001:db8::/32").hasPreviousCIDR(); // true
* ipv6.cidr("::/32").hasPreviousCIDR(); // false
* ```
*
* @returns true if there is a previous CIDR.
*/
hasPreviousCIDR() {
return this.#address.toBigInt() >= this.addressCount();
}
/**
* Gets the previous sequential CIDR.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.previousCIDR()?.toString(); // "2001:db7::/32"
*
* const firstCidr = ipv6.cidr("::/32");
* firstCidr.previousCIDR(); // undefined
* ```
*
* @returns The previous CIDR, or undefined if there is no previous CIDR.
*/
previousCIDR() {
const prevBaseBigInt = this.#address.toBigInt() - this.addressCount();
if (prevBaseBigInt < ipv6.MIN_SIZE) {
return undefined;
}
return new Ipv6Cidr([prevBaseBigInt, this.#range]);
}
/**
* Gets the first usable address in the CIDR range.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/64");
* cidr.getFirstUsableAddress()?.toString(); // "2001:db8::1"
*
* const hostCidr = ipv6.cidr("2001:db8::1/128");
* hostCidr.getFirstUsableAddress(); // undefined (no usable addresses in /128)
* ```
*
* @returns The first usable IPv6 address, or undefined if the range is /128.
*/
getFirstUsableAddress() {
if (this.#range === 128) {
return undefined;
}
const firstUsableBigInt = this.#address.toBigInt() + 1n;
return new Ipv6Address(firstUsableBigInt);
}
/**
* Gets the last usable address in the CIDR range.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/126");
* cidr.getLastUsableAddress()?.toString(); // "2001:db8::2"
*
* const hostCidr = ipv6.cidr("2001:db8::1/128");
* hostCidr.getLastUsableAddress(); // undefined (no usable addresses in /128)
* ```
*
* @returns The last usable IPv6 address, or undefined if the range is /128.
*/
getLastUsableAddress() {
if (this.#range === 128) {
return undefined;
}
const lastUsableBigInt = this.#address.toBigInt() + this.addressCount() - 2n;
return new Ipv6Address(lastUsableBigInt);
}
/**
* Checks if the given IPv6 address is within the CIDR range.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.includes(ipv6.address("2001:db8::1")); // true
* cidr.includes(ipv6.address("2001:db9::1")); // false
* ```
*
* @param ip - The IPv6 address to check.
* @returns True if the address is within the CIDR range; otherwise, false.
*/
includes(ip) {
const ipBigInt = ip.toBigInt();
const baseBigInt = this.#address.toBigInt();
const count = this.addressCount();
return ipBigInt >= baseBigInt && ipBigInt < baseBigInt + count;
}
/**
* Checks if this CIDR overlaps with another CIDR.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* cidr.overlaps("2001:db8::/48"); // true (subset)
* cidr.overlaps("2001:db8:8000::/33"); // true (partial overlap)
* cidr.overlaps("2001:db9::/32"); // false (no overlap)
* ```
*
* @param other - The other IPv6 CIDR to check for overlap.
* @returns True if the CIDRs overlap; otherwise, false.
*/
overlaps(other) {
const otherCidr = other instanceof Ipv6Cidr ? other : new Ipv6Cidr(other);
const thisBase = this.#address.toBigInt();
const thisCount = this.addressCount();
const otherBase = otherCidr.#address.toBigInt();
const otherCount = otherCidr.addressCount();
return thisBase < otherBase + otherCount && otherBase < thisBase + thisCount;
}
/**
* Splits the CIDR into smaller subranges of the specified new range.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* const subnets = cidr.subnet(34);
* subnets.map(s => s.toString());
* // ["2001:db8::/34", "2001:db8:4000::/34", "2001:db8:8000::/34", "2001:db8:c000::/34"]
* ```
*
* @param newRange - The new CIDR range for the subnets.
* @returns An array of Ipv6Cidr instances representing the subnets.
* @throws InvalidIpv6CidrRangeError if the new range is less than the current range or greater than the maximum range.
*/
subnet(newRange) {
if (newRange < this.#range || newRange > ipv6.MAX_RANGE) {
throw new InvalidIpv6CidrRangeError(`New range ${newRange} must be between current range ${this.#range} and ${ipv6.MAX_RANGE}`);
}
const subranges = [];
const totalSubnets = 1n << BigInt(newRange - this.#range);
const baseBigInt = this.#address.toBigInt();
const subnetSize = 1n << BigInt(128 - newRange);
for (let i = 0n; i < totalSubnets; i++) {
const subnetBaseBigInt = baseBigInt + i * subnetSize;
subranges.push(new Ipv6Cidr([subnetBaseBigInt, newRange]));
}
return subranges;
}
/**
* Splits the CIDR into sequential subranges with the specified CIDR ranges.
* Each range in the input array creates a subrange starting where the previous one ended.
*
* @example
* ```ts
* import { ipv6 } from 'cidr-block';
*
* const cidr = ipv6.cidr("2001:db8::/32");
* const subnets = cidr.subnetBy([48, 36, 48]);
* subnets.map(s => s.toString());
* // ["2001:db8::/48", "2001:db8:1::/36", "2001:db8:1100::/48"]
* ```
*
* @param ranges - An array of CIDR range values (e.g., [48, 36, 48]).
* @returns An array of Ipv6Cidr instances representing the subnets.
* @throws InvalidIpv6CidrRangeError if any range is less than the current range or greater than 128.
*/
subnetBy(ranges) {
const subranges = [];
let currentBase = this.#address.toBigInt();
const endAddress = currentBase + this.addressCount();
for (const range of ranges) {
if (range < this.#range || range > ipv6.MAX_RANGE) {
throw new InvalidIpv6CidrRangeError(`Range ${range} must be between current range ${this.#range} and ${ipv6.MAX_RANGE}`);
}
const subnetSize = 1n << BigInt(128 - range);
if (currentBase + subnetSize > endAddress) {
throw new InvalidIpv6CidrRangeError(`Subrange /${range} at ${new Ipv6Address(currentBase).toString()} exceeds the bounds of the parent CIDR`);
}
subranges.push(new Ipv6Cidr([currentBase, range]));
currentBase += subnetSize;
}
return subranges;
}
}
export { Ipv6Cidr };
//# sourceMappingURL=ipv6-cidr.js.map