/**
* @classdesc
* CIDR library
*
* **PURPOSE**: Provides methods for working with CIDRs.
*
* The key methods it provides are:
*
* * `doSubnetsOverlap()` - Pass it two subnet strings, and it tells you whether they overlap
* * `sortCidrByBinary()` - Use this as your sort function's callback and you can sort subnets by their binary representation
*
* Another public method some may find useful is:
*
* * `getBinaryRepresentation()` - Returns the binary representation of a CIDR string.
*
* A CIDR is a Classless Inter-Domain Routing address. See https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing.
*
* In a bit more detail, a CIDR is an IP address with an optional range suffix appended after a forward slash. The range is
* the number of digits (bits, counted from left to right) in the binary representation of the IP address to count as part of
* that CIDR's prefix. When the range suffix is present, only the prefix (and not the rest of the IP address's) digits are
* considered to be significant. The result is the prefix represents a range of IP addresses, which is called a subnet.
*
* **USAGE**:
*
* To use this library, import and instantiate the `Cidr` class, then call the`doSubnetsOverlap` method with two CIDRs,
* like this:
*
* ```javascript
* import { Cidr } from './cidr.js';
*
* let cidr = new Cidr();
*
* let overlaps = cidr.doSubnetsOverlap('11.1.1.2/21', '11.1.1.1/20');
* ```
*
* Note: In the future I might use a different library in the app for which I wrote this one. A couple far more full-featured
* libraries are below:
*
* * [https://github.com/whitequark/ipaddr.js](https://github.com/whitequark/ipaddr.js)
* * [https://github.com/beaugunderson/ip-address](https://github.com/beaugunderson/ip-address)
*
*/
export class Cidr {
/**
* Constructor
*/
constructor () {}
/**
* Determine if the new subnet overlaps the existing subnet
*
* @param {string} first_cidr The first CIDR to compare
* @param {string} second_cidr The second CIDR to compare
* @returns {boolean}
*/
doSubnetsOverlap (first_cidr, second_cidr) {
// Convert subnets to their IP addresses' binary representations, truncated to the
// shortest of their two CIDR prefix lengths
const prefixes_array = this._getPrefixesOfShortestEqualLength(first_cidr, second_cidr);
// Compare the two prefixes
return (prefixes_array[0] === prefixes_array[1]);
}
/**
* Convert subnets to their IP addresses' binary representations, truncated to the
* shortest of their two CIDR prefix lengths
*
* @param {string} first_cidr The first CIDR from which to get a prefix
* @param {string} second_cidr The second CIDR from which to get a prefix
* @returns {array} An array containing the binary representations of the two input CIDRs, truncated to the shortest of their two prefix lengths
*/
_getPrefixesOfShortestEqualLength (first_cidr, second_cidr) {
const shortest_prefix_length = Math.min(this._getPrefixLength(first_cidr), this._getPrefixLength(second_cidr));
const first_binary_prefix = this._getBinaryPrefix(first_cidr, shortest_prefix_length);
const second_binary_prefix = this._getBinaryPrefix(second_cidr, shortest_prefix_length);
return [first_binary_prefix, second_binary_prefix];
}
/**
* Convert one subnet to the binary representation of its IP address, truncated to its
* CIDR prefix length. If the cidr is incomplete or specifies no prefix length,
* assume it is the left-most portion of the CIDR, and consider its binary length to
* be its prefix_length.
*
* @param {string} cidr The CIDR whose binary prefix should be returned
* @param {number} prefix_length The integer length of the CIDR's binary prefix
* @returns {string} The CIDR's binary prefix
*/
_getBinaryPrefix (cidr, prefix_length) {
const binary_classes = this.getBinaryRepresentation(cidr);
// Handle incomplete cidr
if (binary_classes.length < 32 && cidr.indexOf('/') === -1){
prefix_length = binary_classes.length;
}
// Truncate string to prefix length
const binary_prefix = binary_classes.substring(0, prefix_length);
return binary_prefix;
}
/**
* Get the CIDR's binary representation including both its prefix and suffix
*
* @param {string} cidr The CIDR whose binary representation should be returned
* @returns {string} The CIDR's binary representation
*/
getBinaryRepresentation (cidr) {
// Get classes as an array
var classes = this._getClasses(cidr);
// Convert classes to binary, and join the classes into one string
const binaryClassesString = classes.map(function(decimal){
const unpadded = parseInt(decimal, 10).toString(2);
const pad = '00000000';
const padded = pad.substring(0, pad.length - unpadded.length) + unpadded;
return padded;
}).join('');
return binaryClassesString;
}
/**
* Get the CIDR's prefix length as an integer. Handle incomplete cidrs by counting their binary
* length as their prefix_length.
*
* @param {string} cidr The CIDR whose prefix length should be returned
* @returns {number} The CIDR's prefix length as an integer
*/
_getPrefixLength (cidr) {
return cidr.indexOf('/') !== -1 ? parseInt(cidr.split('/')[1], 10) : this._getBinaryPrefix(cidr).length;
}
/**
* Get the CIDR's classes as an array of classes
*
* @param {string} cidr The CIDR whose classes should be returned
* @returns {array} An array of the classes in the CIDR
*/
_getClasses (cidr) {
return cidr.split('/')[0].split('.');
}
/**
* Sort CIDRs by their binary representation
*
* @param {string} a The first CIDR to sort
* @param {string} b The second CIDR to sort
* @returns {number} 1 means sort a before b; 0 means they sort to the same level; -1 means sort a after b
*/
sortCidrByBinary (a, b) {
const a_bin = this.getBinaryRepresentation(a);
const b_bin = this.getBinaryRepresentation(b);
// Compare as strings (to handle integers bigger than 2^53)
let a_string = String(a_bin, 10);
let b_string = String(b_bin, 10);
let out;
if (a_string > b_string) {
out = 1;
} else if (a_string === b_string) {
out = 0;
} else if (a_string < b_string) {
out = -1;
}
return out;
}
}