@simplyappdevs/cidr-calculator
Version:
Classless Inter-Domain Routing (CIDR) Notation Calculator
599 lines (598 loc) • 20.5 kB
JavaScript
;
/**
* Node imports
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CIDRModule = exports.OCTECTSMAX = exports.OCTECTSMIN = void 0;
/**
* SimplyAppDevs imports
*/
/**
* 3rd party imports
*/
const bitset_1 = __importDefault(require("bitset"));
/**
* Return defalt or cloned Octects instance
* @param clone Octect instance to clonef from
* @returns Return default or cloned instance
*/
function defaultOctects(clone) {
if (clone) {
return {
first: clone.first,
second: clone.second,
third: clone.third,
fourth: clone.fourth,
add: (num) => { return defaultOctects(OctectsImpl.add(clone, num)); },
substract: (num) => { return defaultOctects(OctectsImpl.substract(clone, num)); },
toArray: () => { return clone.toArray(); },
toBinary: () => { return clone.toBinary(); },
toString: () => { return clone.toString(); }
};
}
else {
return {
first: 0,
second: 0,
third: 0,
fourth: 0,
add: (num) => { return defaultOctects(OctectsImpl.add(exports.OCTECTSMIN, num)); },
substract: (num) => { return defaultOctects(OctectsImpl.substract(exports.OCTECTSMIN, num)); },
toArray: () => { return [0, 0, 0, 0]; },
toBinary: () => { return '00000000000000000000000000000000'; },
toString: () => { return '0.0.0.0'; }
};
}
}
/**
* Octects implementation
*/
class OctectsImpl {
/**
* Creates new OCTECTS instance
* @param ip IP in either IP string, OCTECTS, or array of number
*/
constructor(ip) {
// private members
this._first = 0;
this._second = 0;
this._third = 0;
this._fourth = 0;
if (ip) {
if (typeof ip === 'string') {
const tmpOct = OctectsImpl.octectsFromString(ip);
this.setOctects(tmpOct.first, tmpOct.second, tmpOct.third, tmpOct.fourth);
}
else if (typeof ip === 'number') {
const tmpOct = OctectsImpl.numberToOctects(ip);
this.setOctects(tmpOct.first, tmpOct.second, tmpOct.third, tmpOct.fourth);
}
else if (Array.isArray(ip)) {
this.setOctects(ip[0], ip[1], ip[2], ip[3]);
}
else {
// OCTECTS
this.setOctects(ip.first, ip.second, ip.third, ip.fourth);
}
}
}
/**
* Sets octects
* @param f First octect
* @param s Second octect
* @param t Third octect
* @param h Forth octect
*/
setOctects(f, s, t, h) {
// validate before we set
if ([f, s, t, h].some((octect) => {
return ((octect < 0) || (octect > 255));
})) {
throw new Error('One or more octect values are invalid');
}
// set
this._first = f;
this._second = s;
this._third = t;
this._fourth = h;
}
/**
* Gets first octect
*/
get first() {
return this._first;
}
/**
* Gets second octect
*/
get second() {
return this._second;
}
/**
* Gets third octect
*/
get third() {
return this._third;
}
/**
* Gets fourth octect
*/
get fourth() {
return this._fourth;
}
/**
* Adds number of IPs to current IP
* @param num Number of IP to add
* @returns New resulting OCTECTS
*/
add(num) {
return OctectsImpl.add(this, num);
}
/**
* Subtract number of IPs to currrent IP
* @param num Number of IP to substract
* @returns New resulting OCTECTS
*/
substract(num) {
return OctectsImpl.substract(this, num);
}
/**
* Returns octects as array of number
* @returns Octects as [first, second, third, forth]
*/
toArray() {
return [this._first, this._second, this._third, this._fourth];
}
/**
* Returns octects as IP string
* @returns Octects as IP string
*/
toString() {
return `${this._first}.${this._second}.${this._third}.${this._fourth}`;
}
/**
* Returns octects as binary string
* @returns Octects as binary string
*/
toBinary() {
// return `${this.formatBinary(this._first)}${this.formatBinary(this._second)}${this.formatBinary(this._third)}${this.formatBinary(this._fourth)}`;
return `${OctectsImpl.formatBinary(OctectsImpl.octectsToNumber(this))}`;
}
/**
* Converts from IP string to OCTECTS
* @param ip IP in string format
* @returns IP as OCTECTS
*/
static octectsFromString(ip) {
// validate
ip = ip.trim();
if (!OctectsImpl.IPREGEX.test(ip)) {
throw new Error('Invalid IP format');
}
// extract
let octString = OctectsImpl.IPREGEX.exec(ip);
// remove first item
octString.splice(0, 1);
let octs = octString.map((oct) => {
return parseInt(oct);
});
return new OctectsImpl([octs[0], octs[1], octs[2], octs[3]]);
}
/**
* Returns number for the octects
* @param octects Octects to convert to decimal
* @returns Decimal value for octects
*/
static octectsToNumber(octects) {
// get array
const octs = octects.toArray();
return octs.reduce((acc, oct, ind) => {
return acc | ((oct << OctectsImpl.SHIFTVALS[ind]) >>> 0);
}, 0) >>> 0;
}
/**
* Returns OCTECTS representation of a number
* @param num Number to convert to OCTECTS
* @returns OCTECTS for the number
*/
static numberToOctects(num) {
// validate
if (num > OctectsImpl.MAXOCTECTSNUM) {
throw new Error(`Number ${num} is greater than maximum octects value ${OctectsImpl.MAXOCTECTSNUM}`);
}
// AND the number by masks and right shift by the octect bits
const octs = OctectsImpl.ANDVALS.map((val, ind) => {
return (num & val) >>> OctectsImpl.SHIFTVALS[ind];
});
// return
return new OctectsImpl([octs[0], octs[1], octs[2], octs[3]]);
}
/**
* Formats octect in binary string
* @param octect Octect to format in binary
* @param separator Optional seperator to use
* @returns Returns octect in binary string (8 bits)
*/
static formatBinary(octect, separator) {
// convert to binary string
const binString = octect.toString(2);
const binLen = binString.length;
// determine the max string length
let padCount = 0;
OctectsImpl.LENVALS.forEach((val) => {
if ((binLen <= val) && (padCount === 0)) {
padCount = val;
}
});
// return
const fullBinString = binString.padStart(padCount, '0');
const fillBinStrLen = fullBinString.length;
separator = separator || '';
const seps = [separator, separator, separator, ''];
let startInd = 0;
let endInd = 0;
return OctectsImpl.LENVALS.reduce((acc, val, ind) => {
endInd += 8;
acc = `${acc}${fullBinString.substring(startInd, endInd)}${seps[ind]}`;
startInd = endInd;
return acc;
}, '');
}
/**
* Adds number of IPs to an IP
* @param octects Octects to add to
* @param num Number of IP to add
* @returns New resulting OCTECTS
*/
static add(octects, num) {
// validation
const curVal = octects.toString();
if (curVal === '255.255.255.255') {
throw new Error(`Cannot add to \'${curVal}\'`);
}
if (num < 0) {
throw new Error(`Invalid number \'${num}\' to add`);
}
if (num === 0) {
return new OctectsImpl(octects);
}
// convert octects to number
let octNum = OctectsImpl.octectsToNumber(octects);
// add it
octNum += num;
// validate
if (octNum > OctectsImpl.MAXOCTECTSNUM) {
throw new Error(`Resulting value \'${octNum}\' is greater than maximum octects value \'${OctectsImpl.MAXOCTECTSNUM}\'`);
}
return new OctectsImpl(OctectsImpl.numberToOctects(octNum));
}
/**
* Subtract number of IPs to an IP
* @param octects Octects to substract from
* @param num Number of IP to substract
* @returns New resulting OCTECTS
*/
static substract(octects, num) {
// validation
const curVal = octects.toString();
if (curVal === '0.0.0.0') {
throw new Error(`Cannot substract from \'${curVal}\'`);
}
if (num < 0) {
throw new Error(`Invalid number \'${num}\' to substract`);
}
if (num === 0) {
return new OctectsImpl(octects);
}
// covert octects to number
let octNum = OctectsImpl.octectsToNumber(octects);
octNum -= num;
return new OctectsImpl(OctectsImpl.numberToOctects(octNum));
}
}
OctectsImpl.MAXOCTECTSNUM = 0xFFFFFFFF >>> 0;
OctectsImpl.IPREGEX = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
OctectsImpl.SHIFTVALS = [24, 16, 8, 0];
OctectsImpl.LENVALS = [8, 16, 24, 32];
OctectsImpl.ANDVALS = [0xFF000000, 0xFF0000, 0xFF00, 0xFF];
// MAX & MIN octects
exports.OCTECTSMIN = defaultOctects();
exports.OCTECTSMAX = defaultOctects(new OctectsImpl('255.255.255.255'));
/**
* Returns default or clone CIDRInformation instance
* @returns Default CIDRInformation instance
*/
function defaultCIDRInformation() {
return {
networkPrefix: '0.0.0.0',
cidrBlock: 0,
subnetMask: '0.0.0.0',
wilcardMask: '0.0.0.0',
broadcastIP: '0.0.0.0',
minIP: '0.0.0.0',
maxIP: '0.0.0.0',
totalIPs: 0
};
}
/**
* Returns default CIDR instance
* @param clone CIDR instance to clone from
* @returns Default CIDR instance
*/
function defaultCIDR(clone) {
return {
inputIP: clone.inputIP,
inputCIDR: clone.inputCIDR,
ipOctects: defaultOctects(clone.ipOctects),
cidrInformation: clone.cidrInformation,
maxSubnetCount: clone.maxSubnetCount,
calculateCIDR: (cidr) => { return clone.calculateCIDR(cidr); },
splitCIDR: (subnetCount) => { return clone.splitCIDR(subnetCount); },
toString: () => { return clone.toString(); }
};
}
/**
* CIDR implementation
*/
class CIDRImpl {
/**
* Creates new CIDR instance
* @param ip IP in either IP string, OCTECTS, or array of number
* @param cidr CIDR block
*/
constructor(ip, cidr) {
// private
this._octects = new OctectsImpl();
this._inputIP = this._octects.toString();
this._inputCIDR = 0;
this._cidrInfo = defaultCIDRInformation();
this._maxSubnetCount = 0;
this._networkBitCount = 0;
this._hostBitCount = 0;
if (ip) {
this._octects = new OctectsImpl(ip); // validation of IP happens in here
this._inputIP = this._octects.toString();
}
if (cidr) {
if (typeof cidr === 'string') {
const cidrNum = parseInt(cidr);
if (isNaN(cidrNum)) {
throw new Error(`\'${cidr}\' is an invalid CIDR block value`);
}
this._inputCIDR = cidrNum;
}
else {
this._inputCIDR = cidr;
}
}
// if we have IP, we need CIDR
if ((this._inputIP !== '0.0.0.0') && (this._inputCIDR <= 0)) {
throw new Error(`Must specify CIDR block value for IP \'${this._inputIP}\'`);
}
// calculate CIDR
if ((this._octects.toString() !== '0.0.0.0') && (this._inputCIDR > 0)) {
this.calculateCIDR(this._inputCIDR);
}
}
/**
* Converts BitSet to Octects
* @param bs Bitset to convert
* @returns Returns Octects representation of the BitSet
*/
bitsetToIP(bs) {
// grab every 8bits
const frh = +bs.slice(0, 7).toString(10);
const thd = +bs.slice(8, 15).toString(10);
const sec = +bs.slice(16, 23).toString(10);
const fst = +bs.slice(24, 31).toString(10);
return new OctectsImpl([fst, sec, thd, frh]);
}
/**
* Gets IP that was used to initialize this instance
*/
get inputIP() {
return this._inputIP;
}
;
/**
* Gets IP that was used to initialize this instance
*/
get inputCIDR() {
return this._inputCIDR;
}
;
/**
* Gets octects
*/
get ipOctects() {
return this._octects;
}
/**
* Gets CIDRInformation (last successful calculation)
*/
get cidrInformation() {
return this._cidrInfo;
}
/**
* Gets the maximum number of subnet that this CIDR can be split into
*/
get maxSubnetCount() {
return this._maxSubnetCount;
}
/**
* Calculate CIDR information based on CIDR block
* @param cidr CIDR block
*/
calculateCIDR(cidr) {
// CIDR is between 1-32
if ((cidr < 1) || (cidr > 32)) {
throw new Error(`CIDR block value must be between 1 and 32`);
}
if (this._octects.first === 0) {
throw new Error(`Cannot calculate CIDR for \'${this.inputIP}\'`);
}
// IP consists of 4x 8bits octects
// each octect range is 0 - 255
// CIDR block value indicates which top N bits to "lock" (unchangble) based on the input IP
// for example:
// IP := 172.90.0.2 and CIDR := 8 - that means 172 is locked while the last 3 octects (24bits) can change
// bitset for CIDR block
const cidrBS = new bitset_1.default('0xffffffff');
// calculate the lower bits that can change
let varBits = 32 - cidr;
this._networkBitCount = cidr;
this._hostBitCount = varBits;
if (varBits > 0) {
varBits -= 1; // 0 based array index
cidrBS.setRange(0, varBits, 0);
}
// bitset for input IP
const inputIPBS = new bitset_1.default(`${this.ipOctects.toBinary()}`);
// netmask - AND inputIP and CIDR block
const lockedCIDR = inputIPBS.and(cidrBS);
// minimum IP is just the locked CIDR block (this is also the network prefix)
const minIP = this.bitsetToIP(lockedCIDR).toString();
// maximum IP is lockedCIDR block but with the lower 32-cidr bits flipped
const maxIPBS = lockedCIDR.clone();
maxIPBS.flip(0, 31 - cidr);
const maxIP = this.bitsetToIP(maxIPBS).toString();
// maximum # of IP 2^N where N is the # of lower bits that can change
const maxIPCount = Math.pow(2, 32 - cidr);
// wildcard address is NOT of netmask
const wilcardMask = cidrBS.clone().not();
// save result
this._inputCIDR = cidr;
this._maxSubnetCount = Math.pow(2, this._hostBitCount) - 2;
this._cidrInfo = {
networkPrefix: minIP,
cidrBlock: this.inputCIDR,
subnetMask: `${this.bitsetToIP(cidrBS).toString()}`,
wilcardMask: `${this.bitsetToIP(wilcardMask).toString()}`,
broadcastIP: maxIP,
minIP: minIP,
maxIP: maxIP,
totalIPs: maxIPCount
};
return this._cidrInfo;
}
/**
* Returns subnet segments for the current CIDR
* @param subnetCount Number of subnets to split from the current CIDR
*/
splitCIDR(subnetCount) {
// validate
if ((this._inputIP === '0.0.0.0') || (this._inputCIDR === 0)) {
throw new Error('Cannot split CIDR because it has not been defined.');
}
if (subnetCount <= 1) {
return [this._cidrInfo];
}
if (this._hostBitCount === 0) {
return [];
}
if (subnetCount > this.maxSubnetCount) {
throw new Error(`CIDR can only be split into maximum of \'${this.maxSubnetCount}\' subnets.`);
}
// subnetting takes the available host bits and lock N high order bits
// to use as network bits (in addtional to the CIDR network bits)
// so we need to find the max bits we need to reserve for the subnetting
// formula: 2^N >= subnetCount - find N that result to subnetCount or the next
// one greater than subnetCount
let realSubnetCount = 0;
let n = 0;
while (realSubnetCount === 0) {
const subCount = Math.pow(2, n);
if (subCount >= subnetCount) {
realSubnetCount = subCount;
}
else {
++n;
}
}
// new CIDR
let newCIDR = this._cidrInfo.cidrBlock + n;
// loop to calculate subnets
let subnets = [];
let startIP = this._cidrInfo.minIP;
let startIPOctects = new OctectsImpl(startIP);
for (let i = 0; i < realSubnetCount; i++) {
// subnet using startIP but new CIDR
subnets[i] = new CIDRImpl(startIP, newCIDR).cidrInformation;
// next start IP is current subnet maxIP + 1
startIPOctects = new OctectsImpl(subnets[i].maxIP);
startIPOctects = startIPOctects.add(1);
startIP = startIPOctects.toString();
}
return subnets;
}
/**
* Return string representation of this instance
* @returns String representation of this CIDR instance
*/
toString() {
return `${this._inputIP}/${this.inputCIDR}`;
}
/**
* Returns CIDR block based on subnet mask
* @param mask Subnet mask
*/
static cidrBlockFromSubnetMask(mask) {
// convert to octects
const maskOctects = new OctectsImpl(mask);
// bitset
const maskBS = new bitset_1.default(maskOctects.toBinary());
if (maskBS.toString(10) === '0') {
throw new Error(`\'${mask}\' is not a valid subnet mask value`);
}
// subnet mask cannot have 0 in between 1s
const bits = maskBS.toString(2);
if (/01/.test(bits)) {
throw new Error(`\'${mask}\' is not a valid subnet mask format`);
}
// get the last 0s
const zeroBits = /(0+)/.exec(bits);
if (!zeroBits) {
// all 1s
return 32;
}
else {
// regex match - first item is the full matched text and subsequent items are the groups
// in this case it has to be a match and the first item is all the 0s
return 32 - zeroBits[0].length;
}
}
}
/**
* CIDR module
*/
exports.CIDRModule = {
parseCIDR: (ip, cidr) => {
return defaultCIDR(new CIDRImpl(ip, cidr));
},
parseOctects: (ip) => {
return defaultOctects(new OctectsImpl(ip));
},
cidrFromSubnetMask: (mask) => {
return CIDRImpl.cidrBlockFromSubnetMask(mask);
},
octectsFromIPString: (ip) => {
return defaultOctects(OctectsImpl.octectsFromString(ip));
},
octectsToNumber: (octs) => {
return OctectsImpl.octectsToNumber(octs);
},
numberToBinary: (num, sep) => {
return OctectsImpl.formatBinary(num, sep);
},
addToIP: (ip, num) => {
const octs = new OctectsImpl(ip);
return defaultOctects(octs.add(num));
},
substractFromIP: (ip, num) => {
const octs = new OctectsImpl(ip);
return defaultOctects(octs.substract(num));
}
};