cidr-block
Version:
ipv4 and ipv6 address and cidr range utilities
501 lines (500 loc) • 15.3 kB
JavaScript
'use strict';Object.defineProperty(exports,'__esModule',{value:true});class InvalidIpAddressError extends Error {
constructor(badIp) {
super(`${badIp} is not a valid IPv4 address.`);
}
}
class InvalidCidrBlockError extends Error {
constructor(badCidr) {
super(`${badCidr} is not a valid IPv4 cidr block.`);
}
}var errors=/*#__PURE__*/Object.freeze({__proto__:null,InvalidIpAddressError:InvalidIpAddressError,InvalidCidrBlockError:InvalidCidrBlockError});/**
* The numerical maximum size an IPv4 address can be
*/
const MAX$1 = 2 ** 32 - 1;const MAX_OCTET_SIZE = 255;
/**
* Representation of an IPv4 address. Provides various utility methods like equality
* checking.
*
* @remarks
* Avoid direct instantiation; use {@link ipv4.address} instead.
*/
class Ipv4Address {
constructor(address) {
this._address = typeof address === 'number' ? address : stringToNum$1(address);
}
/**
* The address as a number
*/
get address() {
return this._address;
}
/**
* Returns the string representation of the address
*
* @example
* ```typescript
* import { ipv4 as ip } from 'cidr-block'
*
* ip.address(255) // ==> '0.0.0.255'
* ip.address(0b11111111_00000000_11111111_00000000) // ==> '255.0.255.0'
* ````
*
* @public
* @returns the IPv4 address as a string
*/
toString() {
return numToString$1(this._address);
}
/**
* Compares if two IPv4 addresses are the same.
*
* @example
* ```typescript
* import { ipv4 as ip } from 'cidr-block'
*
* function isLoopback(address: Ipv4Representable) {
* return ip.address(address).equals('127.0.0.1')
* }
* ```
*
* @public
* @param otherIpAddress the other IPv4 address to compare
* @returns if the other IP address is the same
*/
equals(otherIpAddress) {
if (otherIpAddress instanceof Ipv4Address) {
return this._address === otherIpAddress._address;
}
else {
return this._address === address$1(otherIpAddress)._address;
}
}
/**
* Calculates the next logical IPv4 address.
*
* @example
* ```typescript
* import { ipv4 as ip } from 'cidr-block'
*
* const myIp = ip.address('52.89.32.255')
* myIp.nextIp() // ==> '52.89.33.0'
* ```
*
* @public
* @returns the next consecutive IPv4 address
*/
nextIp() {
// TODO: Handle last ip address
return address$1(this._address + 1);
}
/**
* @example
* ```typescript
* import { ipv4 as ip } from 'cidr-block'
*
* const myIp = ip.address('52.89.32.19')
* myIp.previousIp() // ==> '52.89.32.18'
* ```
*
* @public
* @returns the preceding IPv4 address
*/
previousIp() {
return address$1(this._address - 1);
}
}
/**
* Convenience function for creating an IPv4 address instance.
*
* @remarks
* In general, you should use this function instead of instantiating an Ipv4Address
* object directly.
*
* @example
*
* ```typescript
* import { ipv4 as ip } from 'cidr-block'
*
* const localhost = ip.address('127.0.0.1')
* ```
*
* @see {@link Ipv4Address}
*
* @public
* @param ip string representation of the IPv4 address
* @returns an instance of Ipv4Address
*/
function address$1(ip) {
// TODO: Implement memoization
return new Ipv4Address(ip);
}
/**
* Converts the string representation of an IPv4 address to a number.
*
* @example
* ```typescript
* import * as cidr from 'cidr-block'
*
* cidr.ipv4.stringToNum('255.255.255.255') === 4_294_967_295 // ==> true
* cidr.ipv4.stringToNum('0.0.0.255') === 255 // ==> true
* ```
*
* @see This method is the inverse of {@link ipv4.numToString}
* @throws {@link InvalidIpAddressError}
*
* @public
* @param address IPv4 address represented as a string
* @returns numerical number representation of the address
*/
function stringToNum$1(address) {
try {
if (address.length < 7) {
throw new Error();
}
let octets = address.split('.').map(Number);
if (octets.some(octet => octet < 0 || octet > MAX_OCTET_SIZE)) {
throw new Error();
}
let [firstOctet, secondOctet, thirdOctet, fourthOctet] = octets;
firstOctet = (firstOctet << 24) >>> 0;
secondOctet = (secondOctet << 16) >>> 0;
thirdOctet = (thirdOctet << 8) >>> 0;
return firstOctet + secondOctet + thirdOctet + fourthOctet;
}
catch {
throw new InvalidIpAddressError(address);
}
}
/**
* Converts the numerical representation of an IPv4 address to its string representation.
*
* @example
* ```typescript
* import * as cidr from 'cidr-block'
*
* cidr.ipv4.numToString(0) === '0.0.0.0' // ==> true
* cidr.ipv4.numToString(65_280) === '0.0.255.0' // ==> true
* cidr.ipv4.numToString(4_294_967_295) === '255.255.255.255' // ==> true
* ```
*
* @see This method is the inverse of {@link ipv4.stringToNum}
* @throws {@link InvalidIpAddressError}
*
* @public
* @param ip IPv4 address as a number
* @returns string representation of the address
*/
function numToString$1(ip) {
try {
if (ip < 0 || ip > MAX$1) {
throw new Error();
}
const firstOctet = (ip >>> 24) & MAX_OCTET_SIZE;
const secondOctet = (ip >>> 16) & MAX_OCTET_SIZE;
const thirdOctet = (ip >>> 8) & MAX_OCTET_SIZE;
const fourthOctet = ip & MAX_OCTET_SIZE;
return `${firstOctet}.${secondOctet}.${thirdOctet}.${fourthOctet}`;
}
catch {
throw new InvalidIpAddressError(ip.toString());
}
}class Ipv4Cidr {
// TODO: Allow wider-range of values that can be used to create a cidr
constructor(cidrRange) {
try {
const [address, subnetMask] = cidrRange.split('/');
this._ipAddress = address$1(address);
this._maskSize = Number(subnetMask);
}
catch {
throw new InvalidCidrBlockError(cidrRange);
}
}
/**
* The size of the cidr netmask (the number after the slash in cidr notation)
*/
get maskSize() {
return this._maskSize;
}
/**
* Number of IP addresses within the cidr range
*/
get allocatableIpCount() {
return 2 ** this.addressLength;
}
/**
* The actual IPv4 netmask address
*/
get netmask() {
return address$1((2 ** this.maskSize - 1) << this.addressLength);
}
/**
* The first IPv4 address that is usable within the given cidr range
*/
get firstUsableIp() {
return address$1(this._ipAddress.address);
}
/**
* The last IPv4 address that is usable within the given cidr range
*/
get lastUsableIp() {
// FIXME: Handle edge case of when cidr range goes outside valid ip range
return address$1(this._ipAddress.address + 2 ** this.addressLength - 1);
}
get addressLength() {
return Math.abs(32 - this._maskSize);
}
/**
* @returns string representation of the cidr range
*/
toString() {
return `${this._ipAddress.toString()}/${this._maskSize}`;
}
/**
* @returns the next consecutive cidr block
*/
nextBlock(ofSize) {
const nextIp = this._ipAddress.address + 2 ** this.addressLength;
return cidr(`${numToString$1(nextIp)}/${ofSize ?? this._maskSize}`);
}
/**
* @returns the previous cidr block
*/
previousBlock() {
const nextIp = this._ipAddress.address - 2 ** this.addressLength;
return cidr(`${numToString$1(nextIp)}/${this._maskSize}`);
}
/**
* @returns if the given IPv4 address is within the cidr range
*/
includes(address) {
const ip = address instanceof Ipv4Address ? address : address$1(address);
return (
// FIXME: How to handle edge case of next block erroring out?
ip.address >= this._ipAddress.address && ip.address <= this.nextBlock()._ipAddress.address);
}
}
/**
* Convenience function for creating an IPv4 cidr range instance.
*
* @remarks
*
* In general, you should use this function instead of instantiating an Ipv4Cidr
* object directly. While there is nothing wrong with direct instantiation, convenience
* methods like these are meant to help reduce the footprint of your code and increase
* readability.
*
* @example
*
* ```typescript
* import { ipv4 as ip } from 'cidr-block'
*
* const vpcCidrRange = ip.cidr('10.0.0.0/16')
* ```
*
* @see {@link Ipv4Cidr}
*
* @param cidrRange string representation of the cidr range
* @returns an instance of Ipv4Cidr
*/
function cidr(cidrRange) {
return new Ipv4Cidr(cidrRange);
}const RFC_1918_CIDRS = [cidr('10.0.0.0/8'), cidr('172.16.0.0/12'), cidr('192.168.0.0/16')];
/**
* Predicate function that will return true if the given
* address is in the private RFC 1918 ipv4 address space.
*
* See more {@link https://datatracker.ietf.org/doc/html/rfc1918}
*/
function isPrivateRFC1918(address) {
for (const rfcCidr of RFC_1918_CIDRS) {
if (rfcCidr.includes(address)) {
return true;
}
}
return false;
}var rfc1918=/*#__PURE__*/Object.freeze({__proto__:null,isPrivateRFC1918:isPrivateRFC1918});var index$1=/*#__PURE__*/Object.freeze({__proto__:null,rfc1918:rfc1918,Ipv4Address:Ipv4Address,address:address$1,stringToNum:stringToNum$1,numToString:numToString$1,Ipv4Cidr:Ipv4Cidr,cidr:cidr,MAX:MAX$1});const MAX_HEXTET_SIZE = 65535n;
/**
* Representation of an IPv6 address. Provides various utilities methods like equality
* checking.
*
* @remarks
* Avoid direct instantiation; use {@link ipv6.address} instead.
*/
class Ipv6Address {
constructor(address) {
this._address = typeof address === 'bigint' ? address : stringToNum(address);
}
/**
* The address as a bigint
*
* @remarks
* Because the representation of an IPv6 address is too large to fit into a typical
* JavaScript integer, a
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt}
* is used instead.
*/
get address() {
return this._address;
}
// TODO: Add example code
/**
* Returns the string representation of the address
*/
toString() {
return numToString(this._address);
}
/**
* Compares if two IPv6 addresses are the same.
*
* @example
* ```typescript
* import { ipv6 as ip } from 'cidr-block'
*
* function isLoopback(address: Ipv6Representable) {
* return ip.address(address).equals('::1')
* }
*
* @public
* @param otherIpAddress the other Ipv6 address to compare
* @returns if the other IP address is the same
* ```
*/
equals(otherIpAddress) {
if (otherIpAddress instanceof Ipv6Address) {
return this._address === otherIpAddress._address;
}
else {
return this._address === address(otherIpAddress)._address;
}
}
/**
* Calculates the next logical IPv6 address.
*
* @example
* ```typescript
* import { ipv6 as ip } from 'cidr-block'
*
* const myIp = ip.address('2001:0db8::ac10')
* myIp.nextIp() // ==> '2001:0db8::ac11'
* ```
*/
nextIp() {
// TODO: Handle last ip address
return address(this._address + 1n);
}
/**
* Calculates the previous logical IPv6 address.
*
* @example
* ```typescript
* import { ipv6 as ip } from 'cidr-block'
*
* const myIp = ip.address('2001:0db8::ac10')
* myIp.previousIp() // ==> '2001:0db8::ac09'
* ```
*/
previousIp() {
return address(this._address - 1n);
}
}
/**
* Convenience function for creating an IPv6 address instance.
*
* @remarks
* In general, you should use this function instead of instantiating an Ipv6Address
* object directly.
*
* @example
*
* ```typescript
* import { ipv6 as ip } from 'cidr-block'
*
* const localhost = ip.address('::1')
* ```
*
* @see {@link Ipv6Address}
*
* @param ip string representation of the IPv6 address
* @returns an instance of Ipv6Address
*/
function address(ip) {
// TODO: Implement memoization
return new Ipv6Address(ip);
}
// TODO: Add code example
/**
* Converts the string representation of an IPv6 address to a bigint.
*
* @see {@link Ipv6Address}
*
* @public
* @param ip string representation of the IPv6 address
* @returns an instance of Ipv6Address
*/
function stringToNum(address) {
if (address === '::') {
return 0n;
}
let ipv6 = 0n;
const rawHextets = [];
const [leftAddress, rightAddress] = address.split('::');
for (const hextet of leftAddress.split(':')) {
rawHextets.push(hextet || '0');
}
if (rightAddress !== undefined) {
const rightHextets = rightAddress.split(':');
const emptyFillCount = 8 - (rawHextets.length + rightHextets.length);
for (let i = 0; i < emptyFillCount; i++) {
rawHextets.push('0');
}
for (const hextet of rightHextets) {
rawHextets.push(hextet || '0');
}
}
const decimals = rawHextets.map(hextet => parseInt(hextet, 16));
let shiftSize = 0n;
let binHex = 0n;
for (const pos in decimals) {
const num = decimals[pos];
if (num === 0) {
continue;
}
shiftSize = BigInt(Math.abs(parseInt(pos) - 7) * 16);
binHex = BigInt(num) << shiftSize;
ipv6 |= binHex;
}
return ipv6;
}
/**
* Converts the numerical representation of an IPv6 address to its string representation.
*
* @see This method is the inverse of {@link ipv6.stringToNum}
* @throws {@link InvalidIpAddressError}
*
* @public
* @param ip IPv6 address as a number
* @returns string representation of the address
*/
function numToString(num) {
if (num === 0n) {
return '::';
}
const hextets = [];
for (let n = 0; n < 8; n++) {
const bitOffset = BigInt(Math.abs(n - 7) * 16);
hextets.push(((num >> bitOffset) & MAX_HEXTET_SIZE).toString(16));
}
const dropStartIdx = hextets.indexOf('0');
if (dropStartIdx >= 0) {
let dropCount = 1;
for (let i = dropStartIdx + 1; hextets[i] === '0'; i++) {
dropCount++;
}
hextets.splice(dropStartIdx, dropCount, dropStartIdx === 0 || dropStartIdx + dropCount === 8 ? ':' : '');
}
return hextets.join(':');
}/**
* The numerical maximum size an IPv6 address can be
*/
const MAX = 2n ** 128n;var index=/*#__PURE__*/Object.freeze({__proto__:null,Ipv6Address:Ipv6Address,address:address,stringToNum:stringToNum,numToString:numToString,MAX:MAX});exports.errors=errors;exports.ipv4=index$1;exports.ipv6=index;//# sourceMappingURL=index.js.map