@eryldor/cidr
Version:
A javascript library to manipulate CIDR blocks
187 lines (151 loc) • 6.03 kB
text/typescript
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
interface ICIDRBlockArgs { addr: number; prefix: number; }
export class CIDRBlock {
/**
* Construct a [[CIDRBlock]] object from four IP component numbers (eg: a.b.c.d) and a prefix. The network
* address is sanitized by zeroing all the bits after the prefix.
* @throws If any IP component number is invalid or if the prefix is invalid.
*/
public static fromNumbers(a: number, b: number, c: number, d: number, prefix: number): CIDRBlock {
const { addr, prefix: netPrefix } = CIDRBlock.fromNumbersInner(a, b, c, d, prefix);
return new CIDRBlock(addr, netPrefix);
}
/**
* Parse a string into a CIDR block.
* @throws If the stirng is not a valid CIDR block
*/
public static fromString(cidr: string): CIDRBlock {
const { addr, prefix } = CIDRBlock.fromStringInner(cidr);
return new CIDRBlock(addr, prefix);
}
protected static fromNumbersInner(a: number, b: number, c: number, d: number, prefix: number): ICIDRBlockArgs {
if (![a, b, c, d].every((n) => CIDRBlock.checkIPBoundaries(n))) {
throw new Error(`Invalid network address for ${a}.${b}.${c}.${d}/${prefix}`);
}
if (!CIDRBlock.checkPrefix(prefix)) {
throw new Error(`Invalid prefix address for ${a}.${b}.${c}.${d}/${prefix}`);
}
let addr = ((a << 24) | (b << 16) | (c << 8) | d) >>> 0;
// Sanitize the network address
const sanitizer = (0xFFFFFFFF << (32 - prefix) >>> 0);
addr = (addr & sanitizer) >>> 0;
return {
addr,
prefix,
};
}
protected static fromStringInner(cidr: string): ICIDRBlockArgs {
const splittedCidr = cidr.split("/");
if (splittedCidr.length !== 2) {
throw new Error(`"${cidr} is not a valid CIDR`);
}
const prefix = Number.parseInt(splittedCidr[1], 10);
if (prefix === null || !CIDRBlock.checkPrefix(prefix)) {
throw new Error(`"${prefix}" is not a valid prefix in CIDR "${cidr}"`);
}
const splittedNetworkAddr = splittedCidr[0].split(".")
.map((i) => Number.parseInt(i, 10))
.filter((n: number) => !Number.isNaN(n));
if (splittedNetworkAddr.length !== 4) {
throw new Error(`"${splittedCidr[0]}" is not a valid network address in CIDR "${cidr}"`);
}
return CIDRBlock.fromNumbersInner(
splittedNetworkAddr[0],
splittedNetworkAddr[1],
splittedNetworkAddr[2],
splittedNetworkAddr[3],
prefix);
}
private static checkIPBoundaries(n: number): boolean {
return Number.isInteger(n) && n >= 0 && n <= 255;
}
private static checkPrefix(prefix: number): boolean {
return Number.isInteger(prefix) && prefix >= 0 && prefix <= 32;
}
private static networkAddressToString(netAddr: number): string {
const a = netAddr >> 24;
const b = netAddr >> 16 & 0xFF;
const c = netAddr >> 8 & 0xFF;
const d = netAddr & 0xFF;
const networkAddr = new Uint8Array([a, b, c, d]);
return `${networkAddr[0]}.${networkAddr[1]}.${networkAddr[2]}.${networkAddr[3]}`;
}
public readonly networkPrefix: number;
private innerNetworkAddr: number;
protected constructor(networkAddr: number, prefix: number) {
this.innerNetworkAddr = networkAddr;
this.networkPrefix = prefix;
}
public get networkAddress(): string {
return CIDRBlock.networkAddressToString(this.innerNetworkAddr);
}
/**
* Return the broadcast address of this CIDR block.
*/
public get broadcastAddress(): string {
const broadcastAddress = this.innerNetworkAddr | (0xFFFFFFFF >>> this.networkPrefix);
return CIDRBlock.networkAddressToString(broadcastAddress);
}
/**
* Return a generator yielding all the IP address this block can contain. It doesn't yield the network address and
* the broadcast address.
*/
public * ipAddress(): IterableIterator<string> {
for (let i = 1; i < this.maxAddressIndex; i++) {
yield this.getAddress(i);
}
}
/**
* Return the CIDR notation of this block
*/
public toString(): string {
return `${this.networkAddress}/${this.networkPrefix}`;
}
/**
* Split the CIDR block into at least `requiredSubnetAmount`.
* @returns The new CIDR blocks generated from the split
* @throws If the block can't be divided by at least `requiredSubnetAmount` or if `requiredSubnetAmount` is negative
*/
public split(requiredSubnetAmount: number): CIDRBlock[] {
if (requiredSubnetAmount <= 0) {
throw new Error("The required subnet amount must be positive");
}
const prefixDiff = Math.ceil(Math.log2(requiredSubnetAmount));
const newPrefix = this.networkPrefix + prefixDiff;
if (!CIDRBlock.checkPrefix(newPrefix)) {
throw new Error(`Can't divide "${this.toString()}" into ${requiredSubnetAmount}`);
}
const resultLength = Math.pow(2, prefixDiff);
const result = [];
for (let i = 0; i < resultLength; i++) {
const networkAddr = this.innerNetworkAddr + (i << (32 - newPrefix));
result.push(new CIDRBlock(networkAddr, newPrefix));
}
return result;
}
/**
* Return the maximum address index usable with {@link CIDRBlock.getAddress}
*/
public get maxAddressIndex(): number {
return 0xFFFFFFFF >>> this.networkPrefix;
}
/**
* Return the i-th address inside this CIDR Block.
*
* Note that `getAddress(0)` is equivalent [[CIDRBlock.networkAddress]],
* `this.getAddress(this.maxAddressIndex)` is equivaent to [[CIDRBlock.broadcastAddress]]
*
* @throws if `i` is not an integer between 0 and [[CIDRBlock.maxAddressIndex]]
*/
public getAddress(i: number) {
if (!Number.isInteger(i) || i < 0 || i > this.maxAddressIndex) {
throw new Error(`${i} is not a valid address index for "${this.toString()}"`);
}
const addr = this.innerNetworkAddr + i;
return CIDRBlock.networkAddressToString(addr);
}
}