cidr-tools 12.0.1 → 12.0.3

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +101 -155
  3. package/package.json +19 -16
package/dist/index.d.ts CHANGED
@@ -23,6 +23,7 @@ declare function parseCidr(str: Network): ParsedCidr;
23
23
  declare function mergeCidr(nets: Networks): Array<Network>;
24
24
  /** Returns an array of merged remaining networks of the subtraction of `excludeNetworks` from `baseNetworks`. */
25
25
  declare function excludeCidr(base: Networks, excl: Networks): Array<Network>;
26
+ /** Returns a generator for individual IPs contained in the networks. */
26
27
  declare function expandCidr(nets: Networks): Generator<Network>;
27
28
  /** Returns a boolean that indicates if `networksA` overlap (intersect) with `networksB`. */
28
29
  declare function overlapCidr(a: Networks, b: Networks): boolean;
package/dist/index.js CHANGED
@@ -9,6 +9,10 @@ const octetStrings = Array.from({ length: 256 }, (_, i) => String(i));
9
9
  const octetDotStrings = Array.from({ length: 256 }, (_, i) => `${i}.`);
10
10
  const prefixStrings = Array.from({ length: 129 }, (_, i) => `/${i}`);
11
11
  const prefixNumStrings = Array.from({ length: 129 }, (_, i) => String(i));
12
+ const hexStrings = Array.from({ length: 256 }, (_, i) => i.toString(16));
13
+ const hexPadStrings = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
14
+ const hostMasks = Array.from({ length: 129 }, (_, i) => (1n << BigInt(i)) - 1n);
15
+ const hostNotMasks = hostMasks.map((mask) => ~mask);
12
16
  const cmpV4StartEnd = (a, b) => a.start - b.start || a.end - b.end;
13
17
  const cmpV4Start = (a, b) => a.start - b.start;
14
18
  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;
@@ -73,28 +77,37 @@ function doNormalize(cidr, opts) {
73
77
  const ip = formatIPv4Fast(rangeV4Start);
74
78
  return rangeSlashIndex !== -1 ? ip + prefixStrings[rangeV4Prefix] : ip;
75
79
  }
76
- const { start, end, prefix, version, prefixPresent } = parseCidr(cidr);
80
+ const slashIndex = rangeSlashIndex;
81
+ const prefixPresent = slashIndex !== -1;
82
+ let prefixNum = prefixPresent ? parsePrefixNum(cidr, slashIndex) : -1;
83
+ const { number, version } = parseIp(prefixPresent ? cidr.substring(0, slashIndex) : cidr);
84
+ if (prefixNum === -1) prefixNum = bits[version];
77
85
  if (version === 4) {
78
- const ip = formatIPv4Fast(Number(start));
79
- return start !== end || prefixPresent ? ip + prefixStrings[Number(prefix)] : ip;
86
+ 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);
91
+ return hostBits > 0 || prefixPresent ? ip + prefixStrings[prefixNum] : ip;
80
92
  }
81
93
  const { compress = true, hexify = false } = opts || {};
82
- if (start !== end || prefixPresent) return normalizeIp(stringifyIp({
83
- number: start,
84
- version
85
- }), {
94
+ const hostBits = 128 - prefixNum;
95
+ if (hostBits <= 0 && !prefixPresent) return normalizeIp(cidr, {
86
96
  compress,
87
97
  hexify
88
- }) + prefixStrings[Number(prefix)];
89
- else return normalizeIp(cidr, {
98
+ });
99
+ const ip = stringifyIp({
100
+ number: hostBits > 0 ? number & hostNotMasks[hostBits] : number,
101
+ version
102
+ });
103
+ return (compress ? ip : normalizeIp(ip, {
90
104
  compress,
91
105
  hexify
92
- });
106
+ })) + prefixStrings[prefixNum];
93
107
  }
94
108
  /** 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. */
95
109
  function normalizeCidr(cidr, opts) {
96
- if (Array.isArray(cidr)) return cidr.map((entry) => normalizeCidr(entry, opts));
97
- else return doNormalize(cidr, opts);
110
+ return Array.isArray(cidr) ? cidr.map((entry) => doNormalize(entry, opts)) : doNormalize(cidr, opts);
98
111
  }
99
112
  /** 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. */
100
113
  function parseCidr(str) {
@@ -130,7 +143,8 @@ function parseCidr(str) {
130
143
  let prefixNum = prefixPresent ? parsePrefixNum(str, slashIndex) : -1;
131
144
  const { number, version, ipv4mapped, scopeid } = parseIp(ipPart);
132
145
  if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
133
- if (prefixNum === -1) prefixNum = bits[version];
146
+ const numBits = bits[version];
147
+ if (prefixNum === -1) prefixNum = numBits;
134
148
  const prefix = prefixNumStrings[prefixNum] ?? String(prefixNum);
135
149
  const ip = stringifyIp({
136
150
  number,
@@ -138,16 +152,21 @@ function parseCidr(str) {
138
152
  ipv4mapped,
139
153
  scopeid
140
154
  });
141
- const hostBits = bits[version] - prefixNum;
142
- const mask = hostBits > 0 ? (1n << BigInt(hostBits)) - 1n : 0n;
155
+ const hostBits = numBits - prefixNum;
156
+ let start = number;
157
+ let end = number;
158
+ if (hostBits > 0) {
159
+ start = number & hostNotMasks[hostBits];
160
+ end = number | hostMasks[hostBits];
161
+ }
143
162
  return {
144
163
  cidr: ip + prefixStrings[prefixNum],
145
164
  ip,
146
165
  version,
147
166
  prefix,
148
167
  prefixPresent,
149
- start: number & ~mask,
150
- end: number | mask
168
+ start,
169
+ end
151
170
  };
152
171
  }
153
172
  function parseCidrLean(str) {
@@ -161,8 +180,9 @@ function parseCidrLean(str) {
161
180
  let prefixNum = slashIndex !== -1 ? parsePrefixNum(str, slashIndex) : -1;
162
181
  const { number, version } = parseIp(ipPart);
163
182
  if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
164
- if (prefixNum === -1) prefixNum = bits[version];
165
- const hostBits = bits[version] - prefixNum;
183
+ const numBits = bits[version];
184
+ if (prefixNum === -1) prefixNum = numBits;
185
+ const hostBits = numBits - prefixNum;
166
186
  if (version === 4) {
167
187
  const num = Number(number);
168
188
  if (hostBits >= 32) return {
@@ -177,10 +197,14 @@ function parseCidrLean(str) {
177
197
  version: 4
178
198
  };
179
199
  }
180
- const mask = hostBits > 0 ? (1n << BigInt(hostBits)) - 1n : 0n;
200
+ if (hostBits <= 0) return {
201
+ start: number,
202
+ end: number,
203
+ version: 6
204
+ };
181
205
  return {
182
- start: number & ~mask,
183
- end: number | mask,
206
+ start: number & hostNotMasks[hostBits],
207
+ end: number | hostMasks[hostBits],
184
208
  version: 6
185
209
  };
186
210
  }
@@ -207,117 +231,34 @@ function biggestPowerOfTwo4(num) {
207
231
  return 1 << 31 - Math.clz32(num) >>> 0;
208
232
  }
209
233
  function subparts4(pStart, pEnd, output) {
210
- if (pEnd < pStart) return;
211
- if (pEnd === pStart) {
212
- output.push({
213
- start: pStart,
214
- end: pEnd
215
- });
216
- return;
217
- }
218
- if (pEnd - pStart === 1) {
219
- if (pEnd % 2 === 0) output.push({
220
- start: pStart,
221
- end: pStart
222
- }, {
223
- start: pEnd,
224
- end: pEnd
225
- });
226
- else output.push({
227
- start: pStart,
228
- end: pEnd
229
- });
230
- return;
231
- }
232
- const size = pEnd - pStart + 1;
233
- if ((size & size - 1) === 0 && pStart % size === 0) {
234
- output.push({
235
- start: pStart,
236
- end: pEnd
237
- });
238
- return;
239
- }
240
- let biggest = biggestPowerOfTwo4(size);
241
- let start;
242
- if (pStart % biggest === 0) start = pStart;
243
- else {
244
- start = Math.floor(pEnd / biggest) * biggest;
245
- if (start + biggest - 1 > pEnd) {
246
- start -= biggest;
247
- while (start < pStart) {
248
- biggest /= 2;
249
- start = (Math.floor(pEnd / biggest) - 1) * biggest;
250
- }
251
- }
234
+ let start = pStart;
235
+ while (start <= pEnd) {
236
+ const size = pEnd - start + 1;
237
+ const lowBit = (start & -start) >>> 0;
238
+ const blockSize = lowBit !== 0 && lowBit <= size ? lowBit : biggestPowerOfTwo4(size);
239
+ output.push(formatIPv4Fast(start) + prefixStrings[Math.clz32(blockSize - 1)]);
240
+ start += blockSize;
252
241
  }
253
- const end = start + biggest - 1;
254
- if (start !== pStart) subparts4(pStart, start - 1, output);
255
- output.push({
256
- start,
257
- end
258
- });
259
- if (end !== pEnd) subparts4(end + 1, pEnd, output);
260
242
  }
261
243
  function subparts6(pStart, pEnd, output) {
262
- if (pEnd < pStart) return;
263
- if (pEnd === pStart) {
264
- output.push({
265
- start: pStart,
266
- end: pEnd
267
- });
268
- return;
269
- }
270
- if (pEnd - pStart === 1n) {
271
- if (pEnd % 2n === 0n) output.push({
272
- start: pStart,
273
- end: pStart
274
- }, {
275
- start: pEnd,
276
- end: pEnd
277
- });
278
- else output.push({
279
- start: pStart,
280
- end: pEnd
281
- });
282
- return;
283
- }
284
- const size = pEnd - pStart + 1n;
285
- if ((size & size - 1n) === 0n && (pStart & size - 1n) === 0n) {
286
- output.push({
287
- start: pStart,
288
- end: pEnd
289
- });
290
- return;
291
- }
292
- let biggest = biggestPowerOfTwo(size);
293
- let start;
294
- if ((pStart & biggest - 1n) === 0n) start = pStart;
295
- else {
296
- start = pEnd & -biggest;
297
- if (start + biggest - 1n > pEnd) {
298
- start -= biggest;
299
- while (start < pStart) {
300
- biggest >>= 1n;
301
- start = (pEnd & -biggest) - biggest;
302
- }
244
+ let start = pStart;
245
+ while (start <= pEnd) {
246
+ const size = pEnd - start + 1n;
247
+ const lowBit = start & -start;
248
+ if ((size & size - 1n) === 0n && (lowBit === 0n || lowBit >= size)) {
249
+ output.push(stringifyIp({
250
+ number: start,
251
+ version: 6
252
+ }) + prefixStrings[129 - bigintBitLength(size)]);
253
+ return;
303
254
  }
255
+ const blockSize = lowBit !== 0n && lowBit <= size ? lowBit : biggestPowerOfTwo(size);
256
+ output.push(stringifyIp({
257
+ number: start,
258
+ version: 6
259
+ }) + prefixStrings[129 - bigintBitLength(blockSize)]);
260
+ start += blockSize;
304
261
  }
305
- const end = start + biggest - 1n;
306
- if (start !== pStart) subparts6(pStart, start - 1n, output);
307
- output.push({
308
- start,
309
- end
310
- });
311
- if (end !== pEnd) subparts6(end + 1n, pEnd, output);
312
- }
313
- function formatPart4(part) {
314
- return formatIPv4Fast(part.start) + prefixStrings[Math.clz32(part.end - part.start)];
315
- }
316
- function formatPart6(part) {
317
- return stringifyIp({
318
- number: part.start,
319
- version: 6
320
- }) + prefixStrings[128 - bigintBitLength(part.end - part.start)];
321
262
  }
322
263
  function mergeIntervalsRaw4(nets) {
323
264
  if (nets.length === 0) return [];
@@ -369,16 +310,6 @@ function mergeIntervalsRaw6(nets) {
369
310
  });
370
311
  return merged;
371
312
  }
372
- function mergeIntervals4(nets) {
373
- const merged = [];
374
- for (const part of mergeIntervalsRaw4(nets)) subparts4(part.start, part.end, merged);
375
- return merged;
376
- }
377
- function mergeIntervals6(nets) {
378
- const merged = [];
379
- for (const part of mergeIntervalsRaw6(nets)) subparts6(part.start, part.end, merged);
380
- return merged;
381
- }
382
313
  function subtractSorted4(bases, excls) {
383
314
  if (excls.length === 0) return bases;
384
315
  if (bases.length === 0) return [];
@@ -440,8 +371,8 @@ function mergeCidr(nets) {
440
371
  else v6.push(n);
441
372
  }
442
373
  const merged = [];
443
- for (const part of mergeIntervals4(v4)) merged.push(formatPart4(part));
444
- for (const part of mergeIntervals6(v6)) merged.push(formatPart6(part));
374
+ for (const part of mergeIntervalsRaw4(v4)) subparts4(part.start, part.end, merged);
375
+ for (const part of mergeIntervalsRaw6(v6)) subparts6(part.start, part.end, merged);
445
376
  return merged;
446
377
  }
447
378
  /** Returns an array of merged remaining networks of the subtraction of `excludeNetworks` from `baseNetworks`. */
@@ -462,19 +393,18 @@ function excludeCidr(base, excl) {
462
393
  }
463
394
  const result = [];
464
395
  {
465
- const remaining = subtractSorted4(mergeIntervalsRaw4(v4base), mergeIntervalsRaw4(v4excl));
466
- const aligned = [];
467
- for (const part of remaining) subparts4(part.start, part.end, aligned);
468
- for (const p of aligned) result.push(formatPart4(p));
396
+ const baseParts = mergeIntervalsRaw4(v4base);
397
+ const exclParts = mergeIntervalsRaw4(v4excl);
398
+ for (const part of subtractSorted4(baseParts, exclParts)) subparts4(part.start, part.end, result);
469
399
  }
470
400
  {
471
- const remaining = subtractSorted6(mergeIntervalsRaw6(v6base), mergeIntervalsRaw6(v6excl));
472
- const aligned = [];
473
- for (const part of remaining) subparts6(part.start, part.end, aligned);
474
- for (const p of aligned) result.push(formatPart6(p));
401
+ const baseParts = mergeIntervalsRaw6(v6base);
402
+ const exclParts = mergeIntervalsRaw6(v6excl);
403
+ for (const part of subtractSorted6(baseParts, exclParts)) subparts6(part.start, part.end, result);
475
404
  }
476
405
  return result;
477
406
  }
407
+ /** Returns a generator for individual IPs contained in the networks. */
478
408
  function* expandCidr(nets) {
479
409
  const arr = Array.isArray(nets) ? nets : [nets];
480
410
  const v4 = [];
@@ -487,13 +417,13 @@ function* expandCidr(nets) {
487
417
  if (v4.length > 0) for (const part of mergeIntervalsRaw4(v4)) {
488
418
  let prevHigh = -1;
489
419
  let prefix = "";
490
- for (let n = part.start; n <= part.end; n++) {
491
- const high = n >>> 8;
420
+ for (let num = part.start; num <= part.end; num++) {
421
+ const high = num >>> 8;
492
422
  if (high !== prevHigh) {
493
- prefix = octetDotStrings[n >>> 24 & 255] + octetDotStrings[n >>> 16 & 255] + octetDotStrings[n >>> 8 & 255];
423
+ prefix = octetDotStrings[num >>> 24 & 255] + octetDotStrings[num >>> 16 & 255] + octetDotStrings[num >>> 8 & 255];
494
424
  prevHigh = high;
495
425
  }
496
- yield prefix + octetStrings[n & 255];
426
+ yield prefix + octetStrings[num & 255];
497
427
  }
498
428
  }
499
429
  if (v6.length > 0) {
@@ -501,9 +431,25 @@ function* expandCidr(nets) {
501
431
  number: 0n,
502
432
  version: 6
503
433
  };
504
- for (const part of mergeIntervalsRaw6(v6)) for (let num = part.start; num <= part.end; num++) {
505
- ipObj.number = num;
506
- yield stringifyIp(ipObj);
434
+ for (const part of mergeIntervalsRaw6(v6)) {
435
+ let num = part.start;
436
+ while (num <= part.end) {
437
+ const blockStart = num & -65536n;
438
+ const blockEnd = blockStart | 65535n;
439
+ let group = Number(num & 65535n);
440
+ const groupEnd = part.end < blockEnd ? Number(part.end & 65535n) : 65535;
441
+ if (group === 0) {
442
+ ipObj.number = blockStart;
443
+ yield stringifyIp(ipObj);
444
+ group = 1;
445
+ }
446
+ if (group <= groupEnd) {
447
+ ipObj.number = blockStart | 1n;
448
+ const prefix = stringifyIp(ipObj).slice(0, -1);
449
+ for (; group <= groupEnd; group++) yield group < 256 ? prefix + hexStrings[group] : prefix + hexStrings[group >>> 8] + hexPadStrings[group & 255];
450
+ }
451
+ num = blockEnd + 1n;
452
+ }
507
453
  }
508
454
  }
509
455
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cidr-tools",
3
- "version": "12.0.1",
3
+ "version": "12.0.3",
4
4
  "author": "silverwind <me@silverwind.io>",
5
5
  "description": "Tools to work with IPv4 and IPv6 CIDR",
6
6
  "keywords": [
@@ -11,7 +11,10 @@
11
11
  "subnet",
12
12
  "network"
13
13
  ],
14
- "repository": "silverwind/cidr-tools",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "silverwind/cidr-tools"
17
+ },
15
18
  "license": "BSD-2-Clause",
16
19
  "type": "module",
17
20
  "sideEffects": false,
@@ -26,23 +29,23 @@
26
29
  "bun": "*"
27
30
  },
28
31
  "dependencies": {
29
- "ip-bigint": "^9.0.3"
32
+ "ip-bigint": "^9.0.6"
30
33
  },
31
34
  "devDependencies": {
32
- "@types/node": "25.8.0",
33
- "@typescript/native-preview": "7.0.0-dev.20260516.1",
34
- "@vitest/coverage-v8": "4.1.6",
35
- "eslint": "10.4.0",
36
- "eslint-config-silverwind": "133.0.3",
35
+ "@types/node": "25.9.2",
36
+ "@typescript/native-preview": "7.0.0-dev.20260609.1",
37
+ "@vitest/coverage-v8": "4.1.8",
38
+ "eslint": "10.4.1",
39
+ "eslint-config-silverwind": "135.0.1",
37
40
  "jest-extended": "7.0.0",
38
- "tsdown": "0.22.0",
39
- "tsdown-config-silverwind": "3.0.1",
41
+ "tsdown": "0.22.2",
42
+ "tsdown-config-silverwind": "3.0.3",
40
43
  "typescript": "6.0.3",
41
- "typescript-config-silverwind": "18.0.0",
42
- "updates": "17.16.12",
43
- "updates-config-silverwind": "3.0.2",
44
- "versions": "15.0.4",
45
- "vitest": "4.1.6",
46
- "vitest-config-silverwind": "11.3.5"
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.8",
49
+ "vitest-config-silverwind": "11.3.6"
47
50
  }
48
51
  }