lib/ipv4.js
"use strict";
const IPV4_LONG_MAX = 4294967295;
const IPV4_REGEX = /^([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}$/;
class IPv4 {
static get IP_LONG_MAX () {
return IPV4_LONG_MAX;
}
constructor (ip, mask) {
[ip, mask] = this._initializeIpProperties(ip, mask);
this._initializeMaskProperties(mask);
this._initializeNetworkProperties();
}
toString(format = void 0) {
if(format !== void 0 && format >= 2 && format <= 32) {
return `${this.ipLong.toString(format)}/${this.maskLong.toString(format)}`;
}
return `${this.ip}/${this.mask}`;
}
belongsToSubnet(ip) {
let [_ip, ipLong, _mask] = IPv4._normalizeIp(ip);
return ipLong >= this.netAddrLong
&& ipLong <= this.broadcastLong;
}
* eachForSubnet(startIp = void 0) {
const last = this.broadcastLong;
let current = this.netAddrLong;
if(startIp !== void 0) {
if(IPv4.isDottedStringNotation(startIp)) {
startIp = IPv4.toLong(startIp);
}
if (startIp < this.netAddrLong || startIp > last) {
throw new Error (`Starting IP address ${IPv4.fromLong(startIp)} is out of the subnet scope. Subnet starts with ${IPv4.fromLong(this.netAddrLong)} and ends with ${IPv4.fromLong(last)}`);
}
current = startIp;
}
while(current <= last) {
yield new IPv4(current, this.maskLong);
current += 1;
}
}
_initializeIpProperties(ip, mask) {
this.ip = void 0;
this.ipLong = void 0;
let [_ip, ipLong, _mask] = IPv4._normalizeIp(ip);
if (mask === void 0) {
mask = _mask;
}
this.ip = _ip;
this.ipLong = ipLong;
return [_ip, mask];
}
_initializeMaskProperties(mask) {
this.mask = void 0;
this.maskLong = void 0;
this.maskCidr = void 0;
if(IPv4.isDottedStringNotation(mask)) {
this.maskLong = IPv4.toLong(mask);
this.mask = mask;
} else if (typeof mask === "string" || mask <= IPv4.IP_LONG_MAX) {
if (mask <= 32) {
this.maskCidr = parseInt(mask);
this.maskLong = (IPv4.IP_LONG_MAX << (32 - mask)) >>> 0;
} else {
this.maskLong = mask;
}
this.mask = IPv4.fromLong(this.maskLong);
} else {
throw new Error(`Invalid mask format - ${mask}`);
}
if(this.maskCidr === void 0) {
this.maskCidr = IPv4.cidrFromMask(this.maskLong);
}
this.hostMaskLong = ~this.maskLong;
this.hostMask = IPv4.fromLong(this.hostMaskLong);
}
_initializeNetworkProperties() {
this.netSize = 2 ** (32 - this.maskCidr);
this.netAddrLong = (this.ipLong & this.maskLong) >>> 0;
this.netAddr = IPv4.fromLong(this.netAddrLong);
this.broadcastLong = this.netAddrLong + this.netSize - 1;
this.broadcast = IPv4.fromLong(this.broadcastLong);
}
static fromLong (ipLong) {
if (ipLong < 0 || ipLong > IPv4.IP_LONG_MAX) {
throw new Error(`IPv4 long representation out of range, min value is 0, max value is ${IPv4.IP_LONG_MAX}`);
}
const a = ipLong >> 24 & 255;
const b = ipLong >> 16 & 255;
const c = ipLong >> 8 & 255;
const d = ipLong & 255;
return `${a}.${b}.${c}.${d}`;
}
static toLong (ipStr) {
if(IPv4.isDottedStringNotation(ipStr) === false) {
throw new Error("Invalid IPv4 string representation");
}
const ipBytes = ipStr.split(".");
const ipLong = (
ipBytes[0] << 24
| ipBytes[1] << 16
| ipBytes[2] << 8
| ipBytes[3]
) >>> 0;
return ipLong;
}
static cidrFromMask(mask) {
if(IPv4.isDottedStringNotation(mask)) {
mask = IPv4.toLong(mask);
}
if (mask <= 32) {
return parseInt(mask);
}
mask = mask.toString(2).replace(/0*$/, "");
return mask.length;
}
static isDottedStringNotation(ipStr) {
if(typeof ipStr !== "string") return false;
return ipStr.match(IPV4_REGEX) !== null;
}
static _normalizeIp(ip) {
let mask = void 0;
let ipLong = void 0;
if(typeof ip === "string") {
if (ip.includes("/")) {
[ip, mask] = ip.split("/");
}
ipLong = IPv4.toLong(ip);
} else if (typeof ip === "number") {
ipLong = ip;
ip = IPv4.fromLong(ip);
} else {
throw new Error(`Invalid IP address format - ${ip}`);
}
return [ip, ipLong, mask];
}
}
module.exports = {
IPv4
};