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 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
- It is expected that the passed CIDRs and IPs are validated as the module's own input validation is rudimentary. You are encouraged to use modules like [is-cidr](https://github.com/silverwind/is-cidr) and [is-ip](https://github.com/sindresorhus/is-ip) to validate before passing to this module.
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
- - [is-ip](https://github.com/sindresorhus/is-ip) - Check if a string is an IP address
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 { normalizeIp, parseIp, stringifyIp } from "ip-bigint";
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 === 46) {
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 === -1) return -1;
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 new Error(`Network is not a CIDR or IP: "${str}"`);
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
- rangeSlashIndex = str.indexOf("/");
61
- const v4num = parseIPv4Fast(str, rangeSlashIndex !== -1 ? rangeSlashIndex : str.length);
62
- if (v4num === -1) return false;
63
- rangeV4Prefix = rangeSlashIndex !== -1 ? parsePrefixNum(str, rangeSlashIndex) : 32;
64
- const hostBits = 32 - rangeV4Prefix;
65
- if (hostBits >= 32) {
66
- rangeV4Start = 0;
67
- rangeV4End = 4294967295;
68
- } else {
69
- const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
70
- rangeV4Start = (v4num & ~mask) >>> 0;
71
- rangeV4End = (v4num | mask) >>> 0;
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
- let startNum = Number(number);
88
- if (hostBits >= 32) startNum = 0;
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 { compress = true, hexify = false } = opts || {};
125
+ const compress = opts?.compress ?? true;
126
+ const hexify = opts?.hexify ?? false;
94
127
  const hostBits = 128 - prefixNum;
95
- if (hostBits <= 0 && !prefixPresent) return normalizeIp(cidr, {
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
- const ip = stringifyIp({
138
+ return stringifyIp({
101
139
  number: start,
102
140
  version,
103
141
  ipv4mapped: ipv4mapped && start >> 32n === 65535n,
104
142
  scopeid
105
- }, { hexify });
106
- return (compress ? ip : normalizeIp(ip, {
143
+ }, {
107
144
  compress,
108
145
  hexify
109
- })) + prefixStrings[prefixNum];
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, ipEnd);
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 prefix = prefixNumStrings[prefixNum] ?? String(prefixNum);
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(startNum),
142
- end: BigInt(endNum)
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 parseCidrLean(str) {
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
- if (hostBits >= 32) return {
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 blockSize = lowBit !== 0n && lowBit <= size ? lowBit : biggestPowerOfTwo(size);
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 - bigintBitLength(blockSize)]);
265
- start += blockSize;
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 === 0) return [];
290
+ if (nets.length < 2) return nets;
270
291
  nets.sort(cmpV4StartEnd);
271
- const merged = [];
272
- let curStart = nets[0].start;
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 <= curEnd + 1) {
277
- if (end > curEnd) curEnd = end;
296
+ if (start <= cur.end + 1) {
297
+ if (end > cur.end) cur.end = end;
278
298
  } else {
279
- merged.push({
280
- start: curStart,
281
- end: curEnd
282
- });
283
- curStart = start;
284
- curEnd = end;
299
+ cur = nets[++last];
300
+ cur.start = start;
301
+ cur.end = end;
285
302
  }
286
303
  }
287
- merged.push({
288
- start: curStart,
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 === 0) return [];
308
+ if (nets.length < 2) return nets;
295
309
  nets.sort(cmpV6StartEnd);
296
- const merged = [];
297
- let curStart = nets[0].start;
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 <= curEnd + 1n) {
302
- if (end > curEnd) curEnd = end;
314
+ if (start <= cur.end + 1n) {
315
+ if (end > cur.end) cur.end = end;
303
316
  } else {
304
- merged.push({
305
- start: curStart,
306
- end: curEnd
307
- });
308
- curStart = start;
309
- curEnd = end;
317
+ cur = nets[++last];
318
+ cur.start = start;
319
+ cur.end = end;
310
320
  }
311
321
  }
312
- merged.push({
313
- start: curStart,
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
- const arr = typeof nets === "string" ? [nets] : nets;
371
- const v4 = [];
372
- const v6 = [];
373
- for (const s of arr) {
374
- const n = parseCidrLean(s);
375
- if (n.version === 4) v4.push(n);
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
- const baseArr = typeof base === "string" ? [base] : base;
386
- const exclArr = typeof excl === "string" ? [excl] : excl;
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 s of baseArr) {
390
- const n = parseCidrLean(s);
391
- if (n.version === 4) v4base.push(n);
392
- else v6base.push(n);
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 s of exclArr) {
395
- const n = parseCidrLean(s);
396
- if (n.version === 4) v4excl.push(n);
397
- else v6excl.push(n);
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
- const baseParts = mergeIntervalsRaw4(v4base);
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* expandCidr(nets) {
414
- const arr = typeof nets === "string" ? [nets] : nets;
415
- const v4 = [];
416
- const v6 = [];
417
- for (const s of arr) {
418
- const n = parseCidrLean(s);
419
- if (n.version === 4) v4.push(n);
420
- else v6.push(n);
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 = parseCidrLean(b);
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 = parseCidrLean(a);
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 s of aArr) {
481
- const n = parseCidrLean(s);
482
- if (n.version === 4) v4a.push(n);
483
- else v6a.push(n);
484
- }
485
- for (const s of bArr) {
486
- const n = parseCidrLean(s);
487
- if (n.version === 4) v4b.push(n);
488
- else v6b.push(n);
489
- }
490
- if (v4a.length > 0 && v4b.length > 0) if (v4b.length === 1) {
491
- const bs = v4b[0].start, be = v4b[0].end;
492
- for (const el of v4a) if (el.start <= be && bs <= el.end) return true;
493
- } else if (v4a.length === 1) {
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 bs = v6b[0].start, be = v6b[0].end;
508
- for (const el of v6a) if (el.start <= be && bs <= el.end) return true;
509
- } else if (v6a.length === 1) {
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 = parseCidrLean(b);
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 = parseCidrLean(a);
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 s of aArr) {
544
- const n = parseCidrLean(s);
545
- if (n.version === 4) v4a.push(n);
546
- else v6a.push(n);
547
- }
548
- for (const s of bArr) {
549
- const n = parseCidrLean(s);
550
- if (n.version === 4) v4b.push(n);
551
- else v6b.push(n);
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": "12.1.2",
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
- "ip-bigint": "^9.0.6"
32
+ "cidr-regex": "^7.1.0",
33
+ "ip-bigint": "^10.0.1"
33
34
  },
34
35
  "devDependencies": {
35
- "@types/node": "26.0.1",
36
- "@typescript/native-preview": "7.0.0-dev.20260627.2",
37
- "@vitest/coverage-v8": "4.1.9",
38
- "eslint": "10.6.0",
39
- "eslint-config-silverwind": "138.0.0",
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.3",
42
- "tsdown-config-silverwind": "3.0.4",
42
+ "tsdown": "0.22.14",
43
+ "tsdown-config-silverwind": "4.0.3",
43
44
  "typescript": "6.0.3",
44
- "typescript-config-silverwind": "20.0.0",
45
- "updates": "17.18.0",
46
- "updates-config-silverwind": "4.0.0",
47
- "versions": "15.1.1",
48
- "vitest": "4.1.9",
49
- "vitest-config-silverwind": "11.3.6"
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
  }