cidr-tools 12.1.2 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/dist/index.d.ts +12 -6
- package/dist/index.js +182 -186
- package/package.json +16 -15
package/README.md
CHANGED
|
@@ -33,24 +33,27 @@ parseCidr("::/64");
|
|
|
33
33
|
|
|
34
34
|
All functions take CIDR addresses or single IP addresses. On single addresses, a prefix of `/32` or `/128` is assumed. Function that return networks will return a merged and sorted set of networks with IPv4 sorted before IPv6.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Networks are validated with [cidr-regex](https://github.com/silverwind/cidr-regex) and anything that is not a CIDR or IP address throws, including out-of-family prefixes like `1.2.3.4/33` and ambiguous zero-padded octets like `010.0.0.1`.
|
|
37
|
+
|
|
38
|
+
Every function takes a trailing `[opts]` *Object*:
|
|
39
|
+
- `validate`: Whether to reject networks that are not a CIDR or IP address. `false` skips the check for input already known to be valid, and then malformed input is parsed on a best-effort basis. Default: `true`.
|
|
37
40
|
|
|
38
41
|
This module requires [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility) support in your environment.
|
|
39
42
|
|
|
40
|
-
### mergeCidr(networks)
|
|
43
|
+
### mergeCidr(networks, [opts])
|
|
41
44
|
|
|
42
45
|
- `networks` *String* or *Array*: One or more CIDR or IP addresses.
|
|
43
46
|
|
|
44
47
|
Returns an array of merged networks.
|
|
45
48
|
|
|
46
|
-
### excludeCidr(baseNetworks, excludeNetworks)
|
|
49
|
+
### excludeCidr(baseNetworks, excludeNetworks, [opts])
|
|
47
50
|
|
|
48
51
|
- `baseNetworks` *String* or *Array*: One or more CIDR or IP addresses.
|
|
49
52
|
- `excludeNetworks` *String* or *Array*: One or more CIDR or IP addresses to exclude from `baseNetworks`.
|
|
50
53
|
|
|
51
54
|
Returns an array of merged remaining networks of the subtraction of `excludeNetworks` from `baseNetworks`.
|
|
52
55
|
|
|
53
|
-
### expandCidr(networks)
|
|
56
|
+
### expandCidr(networks, [opts])
|
|
54
57
|
|
|
55
58
|
- `networks` *String* or *Array*: One or more CIDR or IP addresses.
|
|
56
59
|
|
|
@@ -67,14 +70,14 @@ if (end - start >= 1000000n) {
|
|
|
67
70
|
}
|
|
68
71
|
```
|
|
69
72
|
|
|
70
|
-
### overlapCidr(networksA, networksB)
|
|
73
|
+
### overlapCidr(networksA, networksB, [opts])
|
|
71
74
|
|
|
72
75
|
- `networksA` *String* or *Array*: One or more CIDR or IP address.
|
|
73
76
|
- `networksB` *String* or *Array*: One or more CIDR or IP address.
|
|
74
77
|
|
|
75
78
|
Returns a boolean that indicates if `networksA` overlap (intersect) with `networksB`.
|
|
76
79
|
|
|
77
|
-
### containsCidr(networksA, networksB)
|
|
80
|
+
### containsCidr(networksA, networksB, [opts])
|
|
78
81
|
|
|
79
82
|
- `networksA` *String* or *Array*: One or more CIDR or IP address.
|
|
80
83
|
- `networksB` *String* or *Array*: One or more CIDR or IP address.
|
|
@@ -87,11 +90,11 @@ Returns a boolean that indicates whether `networksA` fully contain all `networks
|
|
|
87
90
|
|
|
88
91
|
Returns a string or array (depending on input) with a normalized representation. Will not include a prefix on single IPs. Will set network address to the start of the network.
|
|
89
92
|
|
|
90
|
-
`opts`: *Object
|
|
93
|
+
`opts`: *Object*, additionally to `validate`:
|
|
91
94
|
- `compress`: Whether to compress the IP. For IPv6, this means the "best representation" all-lowercase shortest possible form. Default: `true`.
|
|
92
95
|
- `hexify`: Whether to convert IPv4-Mapped IPv6 addresses to hex. Default: `false`.
|
|
93
96
|
|
|
94
|
-
### parseCidr(network)
|
|
97
|
+
### parseCidr(network, [opts])
|
|
95
98
|
|
|
96
99
|
- `network` *String*: A CIDR or IP address.
|
|
97
100
|
|
|
@@ -109,9 +112,7 @@ Returns a `parsed` Object which is used internally by this module. It can be use
|
|
|
109
112
|
## Related
|
|
110
113
|
|
|
111
114
|
- [ip-bigint](https://github.com/silverwind/ip-bigint) - Convert IPv4 and IPv6 addresses to native BigInt and vice-versa
|
|
112
|
-
- [ip-regex](https://github.com/sindresorhus/ip-regex) - Regular expression for matching IP addresses
|
|
113
115
|
- [is-cidr](https://github.com/silverwind/is-cidr) - Check if a string is an IP address in CIDR notation
|
|
114
|
-
- [
|
|
115
|
-
- [cidr-regex](https://github.com/silverwind/cidr-regex) - Check if a string is an IP address in CIDR notation
|
|
116
|
+
- [cidr-regex](https://github.com/silverwind/cidr-regex) - Regular expression for matching IP addresses in CIDR notation and bare IP addresses
|
|
116
117
|
|
|
117
118
|
© [silverwind](https://github.com/silverwind), distributed under BSD licence.
|
package/dist/index.d.ts
CHANGED
|
@@ -11,24 +11,30 @@ type ParsedCidr = {
|
|
|
11
11
|
start: bigint;
|
|
12
12
|
end: bigint;
|
|
13
13
|
};
|
|
14
|
+
type CidrOpts = {
|
|
15
|
+
/** Whether to reject networks that are not a CIDR or IP address. Default: `true` */
|
|
16
|
+
validate?: boolean;
|
|
17
|
+
};
|
|
14
18
|
type NormalizeOpts = {
|
|
19
|
+
/** Whether to reject networks that are not a CIDR or IP address. Default: `true` */
|
|
20
|
+
validate?: boolean;
|
|
15
21
|
compress?: boolean;
|
|
16
22
|
hexify?: boolean;
|
|
17
23
|
};
|
|
18
24
|
/** Returns a string or array (depending on input) with a normalized representation. Will not include a prefix on single IPs. Will set network address to the start of the network. */
|
|
19
25
|
declare function normalizeCidr<T extends Networks>(cidr: T, opts?: NormalizeOpts): T;
|
|
20
26
|
/** Returns a `parsed` Object which is used internally by this module. It can be used to test whether the passed network is IPv4 or IPv6 or to work with the BigInts directly. */
|
|
21
|
-
declare function parseCidr(str: Network): ParsedCidr;
|
|
27
|
+
declare function parseCidr(str: Network, opts?: CidrOpts): ParsedCidr;
|
|
22
28
|
/** Returns an array of merged networks */
|
|
23
|
-
declare function mergeCidr(nets: Networks): Array<Network>;
|
|
29
|
+
declare function mergeCidr(nets: Networks, opts?: CidrOpts): Array<Network>;
|
|
24
30
|
/** Returns an array of merged remaining networks of the subtraction of `excludeNetworks` from `baseNetworks`. */
|
|
25
|
-
declare function excludeCidr(base: Networks, excl: Networks): Array<Network>;
|
|
31
|
+
declare function excludeCidr(base: Networks, excl: Networks, opts?: CidrOpts): Array<Network>;
|
|
26
32
|
/** Returns a generator for individual IPs contained in the networks. */
|
|
27
|
-
declare function expandCidr(nets: Networks): Generator<Network>;
|
|
33
|
+
declare function expandCidr(nets: Networks, opts?: CidrOpts): Generator<Network>;
|
|
28
34
|
/** Returns a boolean that indicates if `networksA` overlap (intersect) with `networksB`. */
|
|
29
|
-
declare function overlapCidr(a: Networks, b: Networks): boolean;
|
|
35
|
+
declare function overlapCidr(a: Networks, b: Networks, opts?: CidrOpts): boolean;
|
|
30
36
|
/** Returns a boolean that indicates whether `networksA` fully contain all `networksB`. */
|
|
31
|
-
declare function containsCidr(a: Networks, b: Networks): boolean;
|
|
37
|
+
declare function containsCidr(a: Networks, b: Networks, opts?: CidrOpts): boolean;
|
|
32
38
|
declare const _default: {
|
|
33
39
|
mergeCidr: typeof mergeCidr;
|
|
34
40
|
excludeCidr: typeof excludeCidr;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import cidrRegex from "cidr-regex";
|
|
2
|
+
import { parseIp, stringifyIp } from "ip-bigint";
|
|
2
3
|
|
|
3
4
|
//#region index.ts
|
|
4
5
|
const bits = {
|
|
@@ -13,10 +14,30 @@ const hexStrings = Array.from({ length: 256 }, (_, i) => i.toString(16));
|
|
|
13
14
|
const hexPadStrings = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
14
15
|
const hostMasks = Array.from({ length: 129 }, (_, i) => (1n << BigInt(i)) - 1n);
|
|
15
16
|
const hostNotMasks = hostMasks.map((mask) => ~mask);
|
|
17
|
+
const networkRe = cidrRegex({
|
|
18
|
+
exact: true,
|
|
19
|
+
prefix: "optional"
|
|
20
|
+
});
|
|
21
|
+
const unchecked = { validate: false };
|
|
22
|
+
function invalidNetwork(net) {
|
|
23
|
+
return /* @__PURE__ */ new Error(`Network is not a CIDR or IP: "${net}"`);
|
|
24
|
+
}
|
|
25
|
+
function checkNetwork(net) {
|
|
26
|
+
if (!networkRe.test(net)) throw invalidNetwork(net);
|
|
27
|
+
}
|
|
28
|
+
function checkNetworks(nets, opts) {
|
|
29
|
+
if (opts?.validate === false) return;
|
|
30
|
+
if (typeof nets === "string") checkNetwork(nets);
|
|
31
|
+
else for (const net of nets) checkNetwork(net);
|
|
32
|
+
}
|
|
16
33
|
const cmpV4StartEnd = (a, b) => a.start - b.start || a.end - b.end;
|
|
17
34
|
const cmpV4Start = (a, b) => a.start - b.start;
|
|
18
35
|
const cmpV6StartEnd = (a, b) => a.start > b.start ? 1 : a.start < b.start ? -1 : a.end > b.end ? 1 : a.end < b.end ? -1 : 0;
|
|
19
36
|
const cmpV6Start = (a, b) => a.start > b.start ? 1 : a.start < b.start ? -1 : 0;
|
|
37
|
+
const hostMasks4 = Int32Array.from({ length: 33 }, (_, i) => i === 32 ? -1 : (1 << i) - 1);
|
|
38
|
+
function formatIPv4Fast(n) {
|
|
39
|
+
return octetDotStrings[n >>> 24 & 255] + octetDotStrings[n >>> 16 & 255] + octetDotStrings[n >>> 8 & 255] + octetStrings[n & 255];
|
|
40
|
+
}
|
|
20
41
|
function parseIPv4Fast(s, end) {
|
|
21
42
|
let num = 0;
|
|
22
43
|
let octet = 0;
|
|
@@ -24,30 +45,26 @@ function parseIPv4Fast(s, end) {
|
|
|
24
45
|
let digits = 0;
|
|
25
46
|
for (let i = 0; i < end; i++) {
|
|
26
47
|
const c = s.charCodeAt(i);
|
|
27
|
-
if (c
|
|
48
|
+
if (c >= 48 && c <= 57) {
|
|
49
|
+
octet = octet * 10 + (c - 48);
|
|
50
|
+
digits++;
|
|
51
|
+
} else if (c === 46) {
|
|
28
52
|
if (digits === 0 || octet > 255) return -1;
|
|
29
53
|
num = num << 8 | octet;
|
|
30
54
|
octet = 0;
|
|
31
55
|
dots++;
|
|
32
56
|
digits = 0;
|
|
33
|
-
} else if (c >= 48 && c <= 57) {
|
|
34
|
-
octet = octet * 10 + (c - 48);
|
|
35
|
-
digits++;
|
|
36
57
|
} else return -1;
|
|
37
58
|
}
|
|
38
59
|
if (dots !== 3 || digits === 0 || octet > 255) return -1;
|
|
39
60
|
return (num << 8 | octet) >>> 0;
|
|
40
61
|
}
|
|
41
|
-
function formatIPv4Fast(n) {
|
|
42
|
-
return octetDotStrings[n >>> 24 & 255] + octetDotStrings[n >>> 16 & 255] + octetDotStrings[n >>> 8 & 255] + octetStrings[n & 255];
|
|
43
|
-
}
|
|
44
62
|
function parsePrefixNum(str, slashIndex) {
|
|
45
|
-
if (slashIndex
|
|
46
|
-
if (slashIndex + 1 >= str.length) throw new Error(`Network is not a CIDR or IP: "${str}"`);
|
|
63
|
+
if (slashIndex + 1 >= str.length) throw invalidNetwork(str);
|
|
47
64
|
let prefixNum = 0;
|
|
48
65
|
for (let i = slashIndex + 1; i < str.length; i++) {
|
|
49
66
|
const c = str.charCodeAt(i);
|
|
50
|
-
if (c < 48 || c > 57) throw
|
|
67
|
+
if (c < 48 || c > 57) throw invalidNetwork(str);
|
|
51
68
|
prefixNum = prefixNum * 10 + (c - 48);
|
|
52
69
|
}
|
|
53
70
|
return prefixNum;
|
|
@@ -57,19 +74,36 @@ let rangeV4End = 0;
|
|
|
57
74
|
let rangeV4Prefix = 0;
|
|
58
75
|
let rangeSlashIndex = -1;
|
|
59
76
|
function parseIPv4Range(str) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
const len = str.length;
|
|
78
|
+
let num = 0;
|
|
79
|
+
let octet = 0;
|
|
80
|
+
let dots = 0;
|
|
81
|
+
let digits = 0;
|
|
82
|
+
let i = 0;
|
|
83
|
+
for (; i < len; i++) {
|
|
84
|
+
const c = str.charCodeAt(i);
|
|
85
|
+
if (c >= 48 && c <= 57) {
|
|
86
|
+
octet = octet * 10 + (c - 48);
|
|
87
|
+
digits++;
|
|
88
|
+
} else if (c === 46) {
|
|
89
|
+
if (digits === 0 || octet > 255) break;
|
|
90
|
+
num = num << 8 | octet;
|
|
91
|
+
octet = 0;
|
|
92
|
+
dots++;
|
|
93
|
+
digits = 0;
|
|
94
|
+
} else break;
|
|
72
95
|
}
|
|
96
|
+
if (i < len && str.charCodeAt(i) === 47) rangeSlashIndex = i;
|
|
97
|
+
else {
|
|
98
|
+
rangeSlashIndex = str.indexOf("/", i);
|
|
99
|
+
if (i < len) return false;
|
|
100
|
+
}
|
|
101
|
+
if (dots !== 3 || digits === 0 || octet > 255) return false;
|
|
102
|
+
const v4num = (num << 8 | octet) >>> 0;
|
|
103
|
+
rangeV4Prefix = rangeSlashIndex !== -1 ? parsePrefixNum(str, rangeSlashIndex) : 32;
|
|
104
|
+
const mask = hostMasks4[Math.max(32 - rangeV4Prefix, 0)];
|
|
105
|
+
rangeV4Start = (v4num & ~mask) >>> 0;
|
|
106
|
+
rangeV4End = (v4num | mask) >>> 0;
|
|
73
107
|
return true;
|
|
74
108
|
}
|
|
75
109
|
function doNormalize(cidr, opts) {
|
|
@@ -80,72 +114,65 @@ function doNormalize(cidr, opts) {
|
|
|
80
114
|
const slashIndex = rangeSlashIndex;
|
|
81
115
|
const prefixPresent = slashIndex !== -1;
|
|
82
116
|
let prefixNum = prefixPresent ? parsePrefixNum(cidr, slashIndex) : -1;
|
|
83
|
-
const { number, version, ipv4mapped, scopeid } = parseIp(prefixPresent ? cidr.substring(0, slashIndex) : cidr);
|
|
117
|
+
const { number, version, ipv4mapped, scopeid } = parseIp(prefixPresent ? cidr.substring(0, slashIndex) : cidr, unchecked);
|
|
84
118
|
if (prefixNum === -1) prefixNum = bits[version];
|
|
85
119
|
if (version === 4) {
|
|
86
120
|
const hostBits = 32 - prefixNum;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
else if (hostBits > 0) startNum = (startNum & ~((1 << hostBits >>> 0) - 1)) >>> 0;
|
|
90
|
-
const ip = formatIPv4Fast(startNum);
|
|
121
|
+
const mask = hostMasks4[Math.max(hostBits, 0)];
|
|
122
|
+
const ip = formatIPv4Fast((Number(number) & ~mask) >>> 0);
|
|
91
123
|
return hostBits > 0 || prefixPresent ? ip + prefixStrings[prefixNum] : ip;
|
|
92
124
|
}
|
|
93
|
-
const
|
|
125
|
+
const compress = opts?.compress ?? true;
|
|
126
|
+
const hexify = opts?.hexify ?? false;
|
|
94
127
|
const hostBits = 128 - prefixNum;
|
|
95
|
-
if (hostBits <= 0 && !prefixPresent) return
|
|
128
|
+
if (hostBits <= 0 && !prefixPresent) return stringifyIp({
|
|
129
|
+
number,
|
|
130
|
+
version,
|
|
131
|
+
ipv4mapped,
|
|
132
|
+
scopeid
|
|
133
|
+
}, {
|
|
96
134
|
compress,
|
|
97
135
|
hexify
|
|
98
136
|
});
|
|
99
137
|
const start = hostBits > 0 ? number & hostNotMasks[hostBits] : number;
|
|
100
|
-
|
|
138
|
+
return stringifyIp({
|
|
101
139
|
number: start,
|
|
102
140
|
version,
|
|
103
141
|
ipv4mapped: ipv4mapped && start >> 32n === 65535n,
|
|
104
142
|
scopeid
|
|
105
|
-
}, {
|
|
106
|
-
return (compress ? ip : normalizeIp(ip, {
|
|
143
|
+
}, {
|
|
107
144
|
compress,
|
|
108
145
|
hexify
|
|
109
|
-
})
|
|
146
|
+
}) + prefixStrings[prefixNum];
|
|
110
147
|
}
|
|
111
148
|
/** Returns a string or array (depending on input) with a normalized representation. Will not include a prefix on single IPs. Will set network address to the start of the network. */
|
|
112
149
|
function normalizeCidr(cidr, opts) {
|
|
150
|
+
checkNetworks(cidr, opts);
|
|
113
151
|
return typeof cidr === "string" ? doNormalize(cidr, opts) : cidr.map((entry) => doNormalize(entry, opts));
|
|
114
152
|
}
|
|
115
153
|
/** Returns a `parsed` Object which is used internally by this module. It can be used to test whether the passed network is IPv4 or IPv6 or to work with the BigInts directly. */
|
|
116
|
-
function parseCidr(str) {
|
|
154
|
+
function parseCidr(str, opts) {
|
|
155
|
+
if (opts?.validate !== false) checkNetwork(str);
|
|
117
156
|
const slashIndex = str.indexOf("/");
|
|
118
|
-
const ipEnd = slashIndex !== -1 ? slashIndex : str.length;
|
|
119
157
|
const prefixPresent = slashIndex !== -1;
|
|
120
|
-
const v4num = parseIPv4Fast(str,
|
|
158
|
+
const v4num = parseIPv4Fast(str, prefixPresent ? slashIndex : str.length);
|
|
121
159
|
if (v4num !== -1) {
|
|
122
160
|
const prefixNum = prefixPresent ? parsePrefixNum(str, slashIndex) : 32;
|
|
123
161
|
const ip = formatIPv4Fast(v4num);
|
|
124
|
-
const
|
|
125
|
-
const hostBits = 32 - prefixNum;
|
|
126
|
-
let startNum, endNum;
|
|
127
|
-
if (hostBits >= 32) {
|
|
128
|
-
startNum = 0;
|
|
129
|
-
endNum = 4294967295;
|
|
130
|
-
} else {
|
|
131
|
-
const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
|
|
132
|
-
startNum = (v4num & ~mask) >>> 0;
|
|
133
|
-
endNum = (v4num | mask) >>> 0;
|
|
134
|
-
}
|
|
162
|
+
const mask = hostMasks4[Math.max(32 - prefixNum, 0)];
|
|
135
163
|
return {
|
|
136
164
|
cidr: ip + prefixStrings[prefixNum],
|
|
137
165
|
ip,
|
|
138
166
|
version: 4,
|
|
139
|
-
prefix,
|
|
167
|
+
prefix: prefixNumStrings[prefixNum] ?? String(prefixNum),
|
|
140
168
|
prefixPresent,
|
|
141
|
-
start: BigInt(
|
|
142
|
-
end: BigInt(
|
|
169
|
+
start: BigInt((v4num & ~mask) >>> 0),
|
|
170
|
+
end: BigInt((v4num | mask) >>> 0)
|
|
143
171
|
};
|
|
144
172
|
}
|
|
145
173
|
const ipPart = prefixPresent ? str.substring(0, slashIndex) : str;
|
|
146
174
|
let prefixNum = prefixPresent ? parsePrefixNum(str, slashIndex) : -1;
|
|
147
|
-
const { number, version, ipv4mapped, scopeid } = parseIp(ipPart);
|
|
148
|
-
if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
|
|
175
|
+
const { number, version, ipv4mapped, scopeid } = parseIp(ipPart, unchecked);
|
|
149
176
|
const numBits = bits[version];
|
|
150
177
|
if (prefixNum === -1) prefixNum = numBits;
|
|
151
178
|
const prefix = prefixNumStrings[prefixNum] ?? String(prefixNum);
|
|
@@ -172,28 +199,17 @@ function parseCidr(str) {
|
|
|
172
199
|
end
|
|
173
200
|
};
|
|
174
201
|
}
|
|
175
|
-
function
|
|
176
|
-
if (parseIPv4Range(str)) return {
|
|
177
|
-
start: rangeV4Start,
|
|
178
|
-
end: rangeV4End,
|
|
179
|
-
version: 4
|
|
180
|
-
};
|
|
202
|
+
function parseCidrLeanSlow(str) {
|
|
181
203
|
const slashIndex = rangeSlashIndex;
|
|
182
204
|
const ipPart = slashIndex !== -1 ? str.substring(0, slashIndex) : str;
|
|
183
205
|
let prefixNum = slashIndex !== -1 ? parsePrefixNum(str, slashIndex) : -1;
|
|
184
|
-
const { number, version } = parseIp(ipPart);
|
|
185
|
-
if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
|
|
206
|
+
const { number, version } = parseIp(ipPart, unchecked);
|
|
186
207
|
const numBits = bits[version];
|
|
187
208
|
if (prefixNum === -1) prefixNum = numBits;
|
|
188
209
|
const hostBits = numBits - prefixNum;
|
|
189
210
|
if (version === 4) {
|
|
190
211
|
const num = Number(number);
|
|
191
|
-
|
|
192
|
-
start: 0,
|
|
193
|
-
end: 4294967295,
|
|
194
|
-
version: 4
|
|
195
|
-
};
|
|
196
|
-
const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
|
|
212
|
+
const mask = hostMasks4[Math.max(hostBits, 0)];
|
|
197
213
|
return {
|
|
198
214
|
start: (num & ~mask) >>> 0,
|
|
199
215
|
end: (num | mask) >>> 0,
|
|
@@ -211,6 +227,14 @@ function parseCidrLean(str) {
|
|
|
211
227
|
version: 6
|
|
212
228
|
};
|
|
213
229
|
}
|
|
230
|
+
function parseCidrLean(str) {
|
|
231
|
+
if (parseIPv4Range(str)) return {
|
|
232
|
+
start: rangeV4Start,
|
|
233
|
+
end: rangeV4End,
|
|
234
|
+
version: 4
|
|
235
|
+
};
|
|
236
|
+
return parseCidrLeanSlow(str);
|
|
237
|
+
}
|
|
214
238
|
function bigintBitLength(n) {
|
|
215
239
|
if (n === 0n) return 0;
|
|
216
240
|
let len = 0;
|
|
@@ -224,10 +248,6 @@ function bigintBitLength(n) {
|
|
|
224
248
|
}
|
|
225
249
|
return len + 32 - Math.clz32(Number(n));
|
|
226
250
|
}
|
|
227
|
-
function biggestPowerOfTwo(num) {
|
|
228
|
-
if (num === 0n) return 0n;
|
|
229
|
-
return 1n << BigInt(bigintBitLength(num) - 1);
|
|
230
|
-
}
|
|
231
251
|
function biggestPowerOfTwo4(num) {
|
|
232
252
|
if (num === 0) return 0;
|
|
233
253
|
if (num >= 4294967296) return 4294967296;
|
|
@@ -257,63 +277,50 @@ function subparts6(pStart, pEnd, output) {
|
|
|
257
277
|
while (start <= pEnd) {
|
|
258
278
|
const size = pEnd - start + 1n;
|
|
259
279
|
const lowBit = start & -start;
|
|
260
|
-
const
|
|
280
|
+
const alignedFully = lowBit !== 0n && lowBit <= size;
|
|
281
|
+
const blockBits = bigintBitLength(alignedFully ? lowBit : size);
|
|
261
282
|
output.push(stringifyIp({
|
|
262
283
|
number: start,
|
|
263
284
|
version: 6
|
|
264
|
-
}) + prefixStrings[129 -
|
|
265
|
-
start +=
|
|
285
|
+
}) + prefixStrings[129 - blockBits]);
|
|
286
|
+
start += alignedFully ? lowBit : 1n << BigInt(blockBits - 1);
|
|
266
287
|
}
|
|
267
288
|
}
|
|
268
289
|
function mergeIntervalsRaw4(nets) {
|
|
269
|
-
if (nets.length
|
|
290
|
+
if (nets.length < 2) return nets;
|
|
270
291
|
nets.sort(cmpV4StartEnd);
|
|
271
|
-
|
|
272
|
-
let
|
|
273
|
-
let curEnd = nets[0].end;
|
|
292
|
+
let last = 0;
|
|
293
|
+
let cur = nets[0];
|
|
274
294
|
for (let i = 1; i < nets.length; i++) {
|
|
275
295
|
const { start, end } = nets[i];
|
|
276
|
-
if (start <=
|
|
277
|
-
if (end >
|
|
296
|
+
if (start <= cur.end + 1) {
|
|
297
|
+
if (end > cur.end) cur.end = end;
|
|
278
298
|
} else {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
});
|
|
283
|
-
curStart = start;
|
|
284
|
-
curEnd = end;
|
|
299
|
+
cur = nets[++last];
|
|
300
|
+
cur.start = start;
|
|
301
|
+
cur.end = end;
|
|
285
302
|
}
|
|
286
303
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
end: curEnd
|
|
290
|
-
});
|
|
291
|
-
return merged;
|
|
304
|
+
nets.length = last + 1;
|
|
305
|
+
return nets;
|
|
292
306
|
}
|
|
293
307
|
function mergeIntervalsRaw6(nets) {
|
|
294
|
-
if (nets.length
|
|
308
|
+
if (nets.length < 2) return nets;
|
|
295
309
|
nets.sort(cmpV6StartEnd);
|
|
296
|
-
|
|
297
|
-
let
|
|
298
|
-
let curEnd = nets[0].end;
|
|
310
|
+
let last = 0;
|
|
311
|
+
let cur = nets[0];
|
|
299
312
|
for (let i = 1; i < nets.length; i++) {
|
|
300
313
|
const { start, end } = nets[i];
|
|
301
|
-
if (start <=
|
|
302
|
-
if (end >
|
|
314
|
+
if (start <= cur.end + 1n) {
|
|
315
|
+
if (end > cur.end) cur.end = end;
|
|
303
316
|
} else {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
});
|
|
308
|
-
curStart = start;
|
|
309
|
-
curEnd = end;
|
|
317
|
+
cur = nets[++last];
|
|
318
|
+
cur.start = start;
|
|
319
|
+
cur.end = end;
|
|
310
320
|
}
|
|
311
321
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
end: curEnd
|
|
315
|
-
});
|
|
316
|
-
return merged;
|
|
322
|
+
nets.length = last + 1;
|
|
323
|
+
return nets;
|
|
317
324
|
}
|
|
318
325
|
function subtractSorted4(bases, excls) {
|
|
319
326
|
if (excls.length === 0) return bases;
|
|
@@ -366,14 +373,13 @@ function subtractSorted6(bases, excls) {
|
|
|
366
373
|
return result;
|
|
367
374
|
}
|
|
368
375
|
/** Returns an array of merged networks */
|
|
369
|
-
function mergeCidr(nets) {
|
|
370
|
-
|
|
371
|
-
const v4 = [];
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
else v6.push(n);
|
|
376
|
+
function mergeCidr(nets, opts) {
|
|
377
|
+
checkNetworks(nets, opts);
|
|
378
|
+
const v4 = [], v6 = [];
|
|
379
|
+
for (const str of typeof nets === "string" ? [nets] : nets) {
|
|
380
|
+
const net = parseCidrLean(str);
|
|
381
|
+
if (net.version === 4) v4.push(net);
|
|
382
|
+
else v6.push(net);
|
|
377
383
|
}
|
|
378
384
|
const merged = [];
|
|
379
385
|
for (const part of mergeIntervalsRaw4(v4)) subparts4(part.start, part.end, merged);
|
|
@@ -381,43 +387,37 @@ function mergeCidr(nets) {
|
|
|
381
387
|
return merged;
|
|
382
388
|
}
|
|
383
389
|
/** Returns an array of merged remaining networks of the subtraction of `excludeNetworks` from `baseNetworks`. */
|
|
384
|
-
function excludeCidr(base, excl) {
|
|
385
|
-
|
|
386
|
-
|
|
390
|
+
function excludeCidr(base, excl, opts) {
|
|
391
|
+
checkNetworks(base, opts);
|
|
392
|
+
checkNetworks(excl, opts);
|
|
387
393
|
const v4base = [], v6base = [];
|
|
388
394
|
const v4excl = [], v6excl = [];
|
|
389
|
-
for (const
|
|
390
|
-
const
|
|
391
|
-
if (
|
|
392
|
-
else v6base.push(
|
|
395
|
+
for (const str of typeof base === "string" ? [base] : base) {
|
|
396
|
+
const net = parseCidrLean(str);
|
|
397
|
+
if (net.version === 4) v4base.push(net);
|
|
398
|
+
else v6base.push(net);
|
|
393
399
|
}
|
|
394
|
-
for (const
|
|
395
|
-
const
|
|
396
|
-
if (
|
|
397
|
-
else v6excl.push(
|
|
400
|
+
for (const str of typeof excl === "string" ? [excl] : excl) {
|
|
401
|
+
const net = parseCidrLean(str);
|
|
402
|
+
if (net.version === 4) v4excl.push(net);
|
|
403
|
+
else v6excl.push(net);
|
|
398
404
|
}
|
|
399
405
|
const result = [];
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const exclParts = mergeIntervalsRaw4(v4excl);
|
|
403
|
-
for (const part of subtractSorted4(baseParts, exclParts)) subparts4(part.start, part.end, result);
|
|
404
|
-
}
|
|
405
|
-
{
|
|
406
|
-
const baseParts = mergeIntervalsRaw6(v6base);
|
|
407
|
-
const exclParts = mergeIntervalsRaw6(v6excl);
|
|
408
|
-
for (const part of subtractSorted6(baseParts, exclParts)) subparts6(part.start, part.end, result);
|
|
409
|
-
}
|
|
406
|
+
if (v4base.length > 0) for (const part of subtractSorted4(mergeIntervalsRaw4(v4base), mergeIntervalsRaw4(v4excl))) subparts4(part.start, part.end, result);
|
|
407
|
+
if (v6base.length > 0) for (const part of subtractSorted6(mergeIntervalsRaw6(v6base), mergeIntervalsRaw6(v6excl))) subparts6(part.start, part.end, result);
|
|
410
408
|
return result;
|
|
411
409
|
}
|
|
412
410
|
/** Returns a generator for individual IPs contained in the networks. */
|
|
413
|
-
function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
411
|
+
function expandCidr(nets, opts) {
|
|
412
|
+
checkNetworks(nets, opts);
|
|
413
|
+
return expandChecked(nets);
|
|
414
|
+
}
|
|
415
|
+
function* expandChecked(nets) {
|
|
416
|
+
const v4 = [], v6 = [];
|
|
417
|
+
for (const str of typeof nets === "string" ? [nets] : nets) {
|
|
418
|
+
const net = parseCidrLean(str);
|
|
419
|
+
if (net.version === 4) v4.push(net);
|
|
420
|
+
else v6.push(net);
|
|
421
421
|
}
|
|
422
422
|
if (v4.length > 0) for (const part of mergeIntervalsRaw4(v4)) {
|
|
423
423
|
let prevHigh = -1;
|
|
@@ -459,40 +459,38 @@ function* expandCidr(nets) {
|
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
/** Returns a boolean that indicates if `networksA` overlap (intersect) with `networksB`. */
|
|
462
|
-
function overlapCidr(a, b) {
|
|
462
|
+
function overlapCidr(a, b, opts) {
|
|
463
|
+
checkNetworks(a, opts);
|
|
464
|
+
checkNetworks(b, opts);
|
|
463
465
|
if (typeof a === "string" && typeof b === "string") {
|
|
464
466
|
if (parseIPv4Range(a)) {
|
|
465
467
|
const startA = rangeV4Start, endA = rangeV4End;
|
|
466
468
|
if (parseIPv4Range(b)) return startA <= rangeV4End && rangeV4Start <= endA;
|
|
467
|
-
const pb =
|
|
469
|
+
const pb = parseCidrLeanSlow(b);
|
|
468
470
|
if (pb.version !== 4) return false;
|
|
469
471
|
return startA <= pb.end && pb.start <= endA;
|
|
470
472
|
}
|
|
471
|
-
const pa =
|
|
473
|
+
const pa = parseCidrLeanSlow(a);
|
|
472
474
|
const pb = parseCidrLean(b);
|
|
473
475
|
if (pa.version !== pb.version) return false;
|
|
474
476
|
return pa.start <= pb.end && pb.start <= pa.end;
|
|
475
477
|
}
|
|
476
|
-
const aArr = typeof a === "string" ? [a] : a;
|
|
477
|
-
const bArr = typeof b === "string" ? [b] : b;
|
|
478
478
|
const v4a = [], v6a = [];
|
|
479
479
|
const v4b = [], v6b = [];
|
|
480
|
-
for (const
|
|
481
|
-
const
|
|
482
|
-
if (
|
|
483
|
-
else v6a.push(
|
|
484
|
-
}
|
|
485
|
-
for (const
|
|
486
|
-
const
|
|
487
|
-
if (
|
|
488
|
-
else v6b.push(
|
|
489
|
-
}
|
|
490
|
-
if (v4a.length > 0 && v4b.length > 0) if (v4b.length === 1) {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const as = v4a[0].start, ae = v4a[0].end;
|
|
495
|
-
for (const el of v4b) if (as <= el.end && el.start <= ae) return true;
|
|
480
|
+
for (const str of typeof a === "string" ? [a] : a) {
|
|
481
|
+
const net = parseCidrLean(str);
|
|
482
|
+
if (net.version === 4) v4a.push(net);
|
|
483
|
+
else v6a.push(net);
|
|
484
|
+
}
|
|
485
|
+
for (const str of typeof b === "string" ? [b] : b) {
|
|
486
|
+
const net = parseCidrLean(str);
|
|
487
|
+
if (net.version === 4) v4b.push(net);
|
|
488
|
+
else v6b.push(net);
|
|
489
|
+
}
|
|
490
|
+
if (v4a.length > 0 && v4b.length > 0) if (v4a.length === 1 || v4b.length === 1) {
|
|
491
|
+
const bIsSingle = v4b.length === 1;
|
|
492
|
+
const one = bIsSingle ? v4b[0] : v4a[0];
|
|
493
|
+
for (const el of bIsSingle ? v4a : v4b) if (one.start <= el.end && el.start <= one.end) return true;
|
|
496
494
|
} else {
|
|
497
495
|
v4a.sort(cmpV4Start);
|
|
498
496
|
v4b.sort(cmpV4Start);
|
|
@@ -503,12 +501,10 @@ function overlapCidr(a, b) {
|
|
|
503
501
|
else j++;
|
|
504
502
|
}
|
|
505
503
|
}
|
|
506
|
-
if (v6a.length > 0 && v6b.length > 0) if (v6b.length === 1) {
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const as = v6a[0].start, ae = v6a[0].end;
|
|
511
|
-
for (const el of v6b) if (as <= el.end && el.start <= ae) return true;
|
|
504
|
+
if (v6a.length > 0 && v6b.length > 0) if (v6a.length === 1 || v6b.length === 1) {
|
|
505
|
+
const bIsSingle = v6b.length === 1;
|
|
506
|
+
const one = bIsSingle ? v6b[0] : v6a[0];
|
|
507
|
+
for (const el of bIsSingle ? v6a : v6b) if (one.start <= el.end && el.start <= one.end) return true;
|
|
512
508
|
} else {
|
|
513
509
|
v6a.sort(cmpV6Start);
|
|
514
510
|
v6b.sort(cmpV6Start);
|
|
@@ -522,33 +518,33 @@ function overlapCidr(a, b) {
|
|
|
522
518
|
return false;
|
|
523
519
|
}
|
|
524
520
|
/** Returns a boolean that indicates whether `networksA` fully contain all `networksB`. */
|
|
525
|
-
function containsCidr(a, b) {
|
|
521
|
+
function containsCidr(a, b, opts) {
|
|
522
|
+
checkNetworks(a, opts);
|
|
523
|
+
checkNetworks(b, opts);
|
|
526
524
|
if (typeof a === "string" && typeof b === "string") {
|
|
527
525
|
if (parseIPv4Range(a)) {
|
|
528
526
|
const startA = rangeV4Start, endA = rangeV4End;
|
|
529
527
|
if (parseIPv4Range(b)) return startA <= rangeV4Start && endA >= rangeV4End;
|
|
530
|
-
const pb =
|
|
528
|
+
const pb = parseCidrLeanSlow(b);
|
|
531
529
|
if (pb.version !== 4) return false;
|
|
532
530
|
return startA <= pb.start && endA >= pb.end;
|
|
533
531
|
}
|
|
534
|
-
const pa =
|
|
532
|
+
const pa = parseCidrLeanSlow(a);
|
|
535
533
|
const pb = parseCidrLean(b);
|
|
536
534
|
if (pa.version !== pb.version) return false;
|
|
537
535
|
return pa.start <= pb.start && pa.end >= pb.end;
|
|
538
536
|
}
|
|
539
|
-
const aArr = typeof a === "string" ? [a] : a;
|
|
540
|
-
const bArr = typeof b === "string" ? [b] : b;
|
|
541
537
|
const v4a = [], v6a = [];
|
|
542
538
|
const v4b = [], v6b = [];
|
|
543
|
-
for (const
|
|
544
|
-
const
|
|
545
|
-
if (
|
|
546
|
-
else v6a.push(
|
|
547
|
-
}
|
|
548
|
-
for (const
|
|
549
|
-
const
|
|
550
|
-
if (
|
|
551
|
-
else v6b.push(
|
|
539
|
+
for (const str of typeof a === "string" ? [a] : a) {
|
|
540
|
+
const net = parseCidrLean(str);
|
|
541
|
+
if (net.version === 4) v4a.push(net);
|
|
542
|
+
else v6a.push(net);
|
|
543
|
+
}
|
|
544
|
+
for (const str of typeof b === "string" ? [b] : b) {
|
|
545
|
+
const net = parseCidrLean(str);
|
|
546
|
+
if (net.version === 4) v4b.push(net);
|
|
547
|
+
else v6b.push(net);
|
|
552
548
|
}
|
|
553
549
|
if (v4b.length > 0) {
|
|
554
550
|
if (v4a.length === 0) return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cidr-tools",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"author": "silverwind <me@silverwind.io>",
|
|
5
5
|
"description": "Tools to work with IPv4 and IPv6 CIDR",
|
|
6
6
|
"keywords": [
|
|
@@ -29,23 +29,24 @@
|
|
|
29
29
|
"bun": "*"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"
|
|
32
|
+
"cidr-regex": "^7.1.0",
|
|
33
|
+
"ip-bigint": "^10.0.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@types/node": "26.
|
|
36
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
37
|
-
"@vitest/coverage-v8": "4.1.
|
|
38
|
-
"eslint": "10.
|
|
39
|
-
"eslint-config-silverwind": "
|
|
36
|
+
"@types/node": "26.1.2",
|
|
37
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
38
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
39
|
+
"eslint": "10.8.0",
|
|
40
|
+
"eslint-config-silverwind": "143.0.0",
|
|
40
41
|
"jest-extended": "7.0.0",
|
|
41
|
-
"tsdown": "0.22.
|
|
42
|
-
"tsdown-config-silverwind": "
|
|
42
|
+
"tsdown": "0.22.14",
|
|
43
|
+
"tsdown-config-silverwind": "4.0.3",
|
|
43
44
|
"typescript": "6.0.3",
|
|
44
|
-
"typescript-config-silverwind": "20.0.
|
|
45
|
-
"updates": "17.
|
|
46
|
-
"updates-config-silverwind": "4.0.
|
|
47
|
-
"versions": "15.1.
|
|
48
|
-
"vitest": "4.1.
|
|
49
|
-
"vitest-config-silverwind": "11.
|
|
45
|
+
"typescript-config-silverwind": "20.0.3",
|
|
46
|
+
"updates": "17.19.2",
|
|
47
|
+
"updates-config-silverwind": "4.0.4",
|
|
48
|
+
"versions": "15.1.4",
|
|
49
|
+
"vitest": "4.1.10",
|
|
50
|
+
"vitest-config-silverwind": "11.4.0"
|
|
50
51
|
}
|
|
51
52
|
}
|