UNPKG

cidr-block

Version:

ipv4 and ipv6 address and cidr range utilities

501 lines (500 loc) 15.3 kB
'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