cidr-tools 11.0.13 → 11.2.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.
Files changed (2) hide show
  1. package/dist/index.js +305 -118
  2. package/package.json +1 -2
package/dist/index.js CHANGED
@@ -108,14 +108,14 @@ function parseCidrLean(str) {
108
108
  if (prefixNum === -1) prefixNum = 32;
109
109
  const hostBits = 32 - prefixNum;
110
110
  if (hostBits >= 32) return {
111
- start: 0n,
112
- end: 4294967295n,
111
+ start: 0,
112
+ end: 4294967295,
113
113
  version: 4
114
114
  };
115
115
  const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
116
116
  return {
117
- start: BigInt((v4num & ~mask) >>> 0),
118
- end: BigInt((v4num | mask) >>> 0),
117
+ start: (v4num & ~mask) >>> 0,
118
+ end: (v4num | mask) >>> 0,
119
119
  version: 4
120
120
  };
121
121
  }
@@ -124,18 +124,114 @@ function parseCidrLean(str) {
124
124
  if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
125
125
  if (prefixNum === -1) prefixNum = bits[version];
126
126
  const hostBits = bits[version] - prefixNum;
127
+ if (version === 4) {
128
+ const num = Number(number);
129
+ if (hostBits >= 32) return {
130
+ start: 0,
131
+ end: 4294967295,
132
+ version: 4
133
+ };
134
+ const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
135
+ return {
136
+ start: (num & ~mask) >>> 0,
137
+ end: (num | mask) >>> 0,
138
+ version: 4
139
+ };
140
+ }
127
141
  const mask = hostBits > 0 ? (1n << BigInt(hostBits)) - 1n : 0n;
128
142
  return {
129
143
  start: number & ~mask,
130
144
  end: number | mask,
131
- version
145
+ version: 6
132
146
  };
133
147
  }
148
+ function bigintBitLength(n) {
149
+ if (n === 0n) return 0;
150
+ let len = 0;
151
+ if (n >= 18446744073709551616n) {
152
+ n >>= 64n;
153
+ len = 64;
154
+ }
155
+ while (n >= 4294967296n) {
156
+ n >>= 32n;
157
+ len += 32;
158
+ }
159
+ return len + 32 - Math.clz32(Number(n));
160
+ }
134
161
  function biggestPowerOfTwo(num) {
135
162
  if (num === 0n) return 0n;
136
- return 1n << BigInt(num.toString(2).length - 1);
163
+ return 1n << BigInt(bigintBitLength(num) - 1);
164
+ }
165
+ function biggestPowerOfTwo4(num) {
166
+ if (num === 0) return 0;
167
+ if (num >= 4294967296) return 4294967296;
168
+ return 1 << 31 - Math.clz32(num) >>> 0;
137
169
  }
138
- function subparts(pStart, pEnd, output) {
170
+ function subparts4(pStart, pEnd, output) {
171
+ if (pEnd < pStart) return;
172
+ if (pEnd === pStart) {
173
+ output.push({
174
+ start: pStart,
175
+ end: pEnd
176
+ });
177
+ return;
178
+ }
179
+ if (pEnd - pStart === 1) {
180
+ if (pEnd % 2 === 0) output.push({
181
+ start: pStart,
182
+ end: pStart
183
+ }, {
184
+ start: pEnd,
185
+ end: pEnd
186
+ });
187
+ else output.push({
188
+ start: pStart,
189
+ end: pEnd
190
+ });
191
+ return;
192
+ }
193
+ const size = pEnd - pStart + 1;
194
+ if ((size & size - 1) === 0 && pStart % size === 0) {
195
+ output.push({
196
+ start: pStart,
197
+ end: pEnd
198
+ });
199
+ return;
200
+ }
201
+ let biggest = biggestPowerOfTwo4(size);
202
+ let start;
203
+ let end;
204
+ if (size === biggest && pStart % biggest === 0) {
205
+ output.push({
206
+ start: pStart,
207
+ end: pEnd
208
+ });
209
+ return;
210
+ } else if (pStart % biggest === 0) {
211
+ start = pStart;
212
+ end = start + biggest - 1;
213
+ } else {
214
+ start = Math.floor(pEnd / biggest) * biggest;
215
+ if (start + biggest - 1 > pEnd) {
216
+ start = (Math.floor(pEnd / biggest) - 1) * biggest;
217
+ while (start < pStart) {
218
+ biggest /= 2;
219
+ start = (Math.floor(pEnd / biggest) - 1) * biggest;
220
+ }
221
+ end = start + biggest - 1;
222
+ } else {
223
+ start = Math.floor(pEnd / biggest) * biggest;
224
+ end = start + biggest - 1;
225
+ }
226
+ }
227
+ if (start !== pStart) subparts4(pStart, start - 1, output);
228
+ output.push({
229
+ start,
230
+ end
231
+ });
232
+ if (end !== pEnd) subparts4(end + 1, pEnd, output);
233
+ }
234
+ function subparts6(pStart, pEnd, output) {
139
235
  if (pEnd < pStart) return;
140
236
  if (pEnd === pStart) {
141
237
  output.push({
@@ -158,58 +254,86 @@ function subparts(pStart, pEnd, output) {
158
254
  });
159
255
  return;
160
256
  }
161
- const size = diff(pEnd, pStart);
257
+ const size = pEnd - pStart + 1n;
258
+ if ((size & size - 1n) === 0n && (pStart & size - 1n) === 0n) {
259
+ output.push({
260
+ start: pStart,
261
+ end: pEnd
262
+ });
263
+ return;
264
+ }
162
265
  let biggest = biggestPowerOfTwo(size);
163
266
  let start;
164
267
  let end;
165
- if (size === biggest && pStart % biggest === 0n) {
268
+ if (size === biggest && (pStart & biggest - 1n) === 0n) {
166
269
  output.push({
167
270
  start: pStart,
168
271
  end: pEnd
169
272
  });
170
273
  return;
171
- } else if (pStart % biggest === 0n) {
274
+ } else if ((pStart & biggest - 1n) === 0n) {
172
275
  start = pStart;
173
276
  end = start + biggest - 1n;
174
277
  } else {
175
- start = pEnd / biggest * biggest;
278
+ start = pEnd & -biggest;
176
279
  if (start + biggest - 1n > pEnd) {
177
- start = (pEnd / biggest - 1n) * biggest;
280
+ start = (pEnd & -biggest) - biggest;
178
281
  while (start < pStart) {
179
- biggest /= 2n;
180
- start = (pEnd / biggest - 1n) * biggest;
282
+ biggest >>= 1n;
283
+ start = (pEnd & -biggest) - biggest;
181
284
  }
182
285
  end = start + biggest - 1n;
183
286
  } else {
184
- start = pEnd / biggest * biggest;
287
+ start = pEnd & -biggest;
185
288
  end = start + biggest - 1n;
186
289
  }
187
290
  }
188
- if (start !== pStart) subparts(pStart, start - 1n, output);
291
+ if (start !== pStart) subparts6(pStart, start - 1n, output);
189
292
  output.push({
190
293
  start,
191
294
  end
192
295
  });
193
- if (end !== pEnd) subparts(end + 1n, pEnd, output);
296
+ if (end !== pEnd) subparts6(end + 1n, pEnd, output);
194
297
  }
195
- function diff(a, b) {
196
- return a + 1n - b;
298
+ function formatPart4(part) {
299
+ const ip = formatIPv4Fast(part.start);
300
+ const size = part.end - part.start + 1;
301
+ return `${ip}/${32 - (size <= 1 ? 0 : size >= 4294967296 ? 32 : 31 - Math.clz32(size))}`;
197
302
  }
198
- function formatPart(part, version) {
199
- if (version === 4) {
200
- const ip = formatIPv4Fast(Number(part.start));
201
- const sizeNum = Number(part.end - part.start) + 1;
202
- return `${ip}/${32 - (sizeNum <= 1 ? 0 : sizeNum >= 4294967296 ? 32 : 31 - Math.clz32(sizeNum))}`;
203
- }
303
+ function formatPart6(part) {
204
304
  const ip = stringifyIp({
205
305
  number: part.start,
206
- version
306
+ version: 6
307
+ });
308
+ const size = part.end - part.start + 1n;
309
+ return `${ip}/${128 - (size <= 1n ? 0 : bigintBitLength(size) - 1)}`;
310
+ }
311
+ function mergeIntervalsRaw4(nets) {
312
+ if (nets.length === 0) return [];
313
+ nets.sort((a, b) => a.start - b.start || a.end - b.end);
314
+ const merged = [];
315
+ let curStart = nets[0].start;
316
+ let curEnd = nets[0].end;
317
+ for (let i = 1; i < nets.length; i++) {
318
+ const { start, end } = nets[i];
319
+ if (start <= curEnd + 1) {
320
+ if (end > curEnd) curEnd = end;
321
+ } else {
322
+ merged.push({
323
+ start: curStart,
324
+ end: curEnd
325
+ });
326
+ curStart = start;
327
+ curEnd = end;
328
+ }
329
+ }
330
+ merged.push({
331
+ start: curStart,
332
+ end: curEnd
207
333
  });
208
- const size = diff(part.end, part.start);
209
- const hostBits = size <= 1n ? 0 : size.toString(2).length - 1;
210
- return `${ip}/${bits[version] - hostBits}`;
334
+ return merged;
211
335
  }
212
- function mergeIntervalsRaw(nets) {
336
+ function mergeIntervalsRaw6(nets) {
213
337
  if (nets.length === 0) return [];
214
338
  nets.sort((a, b) => a.start > b.start ? 1 : a.start < b.start ? -1 : a.end > b.end ? 1 : a.end < b.end ? -1 : 0);
215
339
  const merged = [];
@@ -234,12 +358,42 @@ function mergeIntervalsRaw(nets) {
234
358
  });
235
359
  return merged;
236
360
  }
237
- function mergeIntervals(nets) {
361
+ function mergeIntervals4(nets) {
238
362
  const merged = [];
239
- for (const part of mergeIntervalsRaw(nets)) subparts(part.start, part.end, merged);
363
+ for (const part of mergeIntervalsRaw4(nets)) subparts4(part.start, part.end, merged);
240
364
  return merged;
241
365
  }
242
- function subtractSorted(bases, excls) {
366
+ function mergeIntervals6(nets) {
367
+ const merged = [];
368
+ for (const part of mergeIntervalsRaw6(nets)) subparts6(part.start, part.end, merged);
369
+ return merged;
370
+ }
371
+ function subtractSorted4(bases, excls) {
372
+ if (excls.length === 0) return bases;
373
+ if (bases.length === 0) return [];
374
+ const result = [];
375
+ let j = 0;
376
+ for (const base of bases) {
377
+ let start = base.start;
378
+ const end = base.end;
379
+ while (j < excls.length && excls[j].end < start) j++;
380
+ let k = j;
381
+ while (k < excls.length && excls[k].start <= end && start <= end) {
382
+ if (excls[k].start > start) result.push({
383
+ start,
384
+ end: excls[k].start - 1
385
+ });
386
+ start = excls[k].end + 1;
387
+ k++;
388
+ }
389
+ if (start <= end) result.push({
390
+ start,
391
+ end
392
+ });
393
+ }
394
+ return result;
395
+ }
396
+ function subtractSorted6(bases, excls) {
243
397
  if (excls.length === 0) return bases;
244
398
  if (bases.length === 0) return [];
245
399
  const result = [];
@@ -266,83 +420,98 @@ function subtractSorted(bases, excls) {
266
420
  }
267
421
  /** Returns an array of merged networks */
268
422
  function mergeCidr(nets) {
269
- const arr = (Array.isArray(nets) ? nets : [nets]).map(parseCidrLean);
270
- const byVersion = {
271
- 4: [],
272
- 6: []
273
- };
274
- for (const n of arr) byVersion[n.version].push(n);
423
+ const arr = Array.isArray(nets) ? nets : [nets];
424
+ const v4 = [];
425
+ const v6 = [];
426
+ for (const s of arr) {
427
+ const n = parseCidrLean(s);
428
+ if (n.version === 4) v4.push(n);
429
+ else v6.push(n);
430
+ }
275
431
  const merged = [];
276
- for (const v of [4, 6]) for (const part of mergeIntervals(byVersion[v])) merged.push(formatPart(part, v));
432
+ for (const part of mergeIntervals4(v4)) merged.push(formatPart4(part));
433
+ for (const part of mergeIntervals6(v6)) merged.push(formatPart6(part));
277
434
  return merged;
278
435
  }
279
436
  /** Returns an array of merged remaining networks of the subtraction of `excludeNetworks` from `baseNetworks`. */
280
437
  function excludeCidr(base, excl) {
281
- const baseArr = (Array.isArray(base) ? base : [base]).map(parseCidrLean);
282
- const exclArr = (Array.isArray(excl) ? excl : [excl]).map(parseCidrLean);
283
- const baseByVersion = {
284
- 4: [],
285
- 6: []
286
- };
287
- const exclByVersion = {
288
- 4: [],
289
- 6: []
290
- };
291
- for (const n of baseArr) baseByVersion[n.version].push(n);
292
- for (const n of exclArr) exclByVersion[n.version].push(n);
438
+ const baseArr = Array.isArray(base) ? base : [base];
439
+ const exclArr = Array.isArray(excl) ? excl : [excl];
440
+ const v4base = [], v6base = [];
441
+ const v4excl = [], v6excl = [];
442
+ for (const s of baseArr) {
443
+ const n = parseCidrLean(s);
444
+ if (n.version === 4) v4base.push(n);
445
+ else v6base.push(n);
446
+ }
447
+ for (const s of exclArr) {
448
+ const n = parseCidrLean(s);
449
+ if (n.version === 4) v4excl.push(n);
450
+ else v6excl.push(n);
451
+ }
293
452
  const result = [];
294
- for (const v of [4, 6]) {
295
- const remaining = subtractSorted(mergeIntervalsRaw(baseByVersion[v]), mergeIntervalsRaw(exclByVersion[v]));
453
+ {
454
+ const remaining = subtractSorted4(mergeIntervalsRaw4(v4base), mergeIntervalsRaw4(v4excl));
455
+ const aligned = [];
456
+ for (const part of remaining) subparts4(part.start, part.end, aligned);
457
+ for (const p of aligned) result.push(formatPart4(p));
458
+ }
459
+ {
460
+ const remaining = subtractSorted6(mergeIntervalsRaw6(v6base), mergeIntervalsRaw6(v6excl));
296
461
  const aligned = [];
297
- for (const part of remaining) subparts(part.start, part.end, aligned);
298
- for (const p of aligned) result.push(formatPart(p, v));
462
+ for (const part of remaining) subparts6(part.start, part.end, aligned);
463
+ for (const p of aligned) result.push(formatPart6(p));
299
464
  }
300
465
  return result;
301
466
  }
302
467
  function* expandCidr(nets) {
303
- const parsed = (Array.isArray(nets) ? nets : [nets]).map(parseCidrLean);
304
- const byVersion = {
305
- 4: [],
306
- 6: []
307
- };
308
- for (const n of parsed) byVersion[n.version].push(n);
309
- for (const v of [4, 6]) {
310
- if (byVersion[v].length === 0) continue;
311
- const intervals = mergeIntervalsRaw(byVersion[v]);
312
- if (v === 4) for (const part of intervals) {
313
- const startNum = Number(part.start);
314
- const endNum = Number(part.end);
315
- for (let n = startNum; n <= endNum; n++) yield formatIPv4Fast(n);
316
- }
317
- else for (const part of intervals) for (let num = part.start; num <= part.end; num++) yield stringifyIp({
318
- number: num,
319
- version: 6
320
- });
468
+ const arr = Array.isArray(nets) ? nets : [nets];
469
+ const v4 = [];
470
+ const v6 = [];
471
+ for (const s of arr) {
472
+ const n = parseCidrLean(s);
473
+ if (n.version === 4) v4.push(n);
474
+ else v6.push(n);
321
475
  }
476
+ if (v4.length > 0) for (const part of mergeIntervalsRaw4(v4)) for (let n = part.start; n <= part.end; n++) yield formatIPv4Fast(n);
477
+ if (v6.length > 0) for (const part of mergeIntervalsRaw6(v6)) for (let num = part.start; num <= part.end; num++) yield stringifyIp({
478
+ number: num,
479
+ version: 6
480
+ });
322
481
  }
323
482
  /** Returns a boolean that indicates if `networksA` overlap (intersect) with `networksB`. */
324
483
  function overlapCidr(a, b) {
325
- const aNets = (Array.isArray(a) ? a : [a]).map(parseCidrLean);
326
- const bNets = (Array.isArray(b) ? b : [b]).map(parseCidrLean);
327
- const aByVersion = {
328
- 4: [],
329
- 6: []
330
- };
331
- const bByVersion = {
332
- 4: [],
333
- 6: []
334
- };
335
- for (const n of aNets) aByVersion[n.version].push(n);
336
- for (const n of bNets) bByVersion[n.version].push(n);
337
- for (const v of [4, 6]) {
338
- const aVer = aByVersion[v].sort((x, y) => x.start > y.start ? 1 : x.start < y.start ? -1 : 0);
339
- const bVer = bByVersion[v].sort((x, y) => x.start > y.start ? 1 : x.start < y.start ? -1 : 0);
484
+ const aArr = Array.isArray(a) ? a : [a];
485
+ const bArr = Array.isArray(b) ? b : [b];
486
+ const v4a = [], v6a = [];
487
+ const v4b = [], v6b = [];
488
+ for (const s of aArr) {
489
+ const n = parseCidrLean(s);
490
+ if (n.version === 4) v4a.push(n);
491
+ else v6a.push(n);
492
+ }
493
+ for (const s of bArr) {
494
+ const n = parseCidrLean(s);
495
+ if (n.version === 4) v4b.push(n);
496
+ else v6b.push(n);
497
+ }
498
+ if (v4a.length > 0 && v4b.length > 0) {
499
+ v4a.sort((x, y) => x.start - y.start);
500
+ v4b.sort((x, y) => x.start - y.start);
340
501
  let i = 0, j = 0;
341
- while (i < aVer.length && j < bVer.length) {
342
- const aNet = aVer[i];
343
- const bNet = bVer[j];
344
- if (aNet.start <= bNet.end && bNet.start <= aNet.end) return true;
345
- if (aNet.end < bNet.end) i++;
502
+ while (i < v4a.length && j < v4b.length) {
503
+ if (v4a[i].start <= v4b[j].end && v4b[j].start <= v4a[i].end) return true;
504
+ if (v4a[i].end < v4b[j].end) i++;
505
+ else j++;
506
+ }
507
+ }
508
+ if (v6a.length > 0 && v6b.length > 0) {
509
+ v6a.sort((x, y) => x.start > y.start ? 1 : x.start < y.start ? -1 : 0);
510
+ v6b.sort((x, y) => x.start > y.start ? 1 : x.start < y.start ? -1 : 0);
511
+ let i = 0, j = 0;
512
+ while (i < v6a.length && j < v6b.length) {
513
+ if (v6a[i].start <= v6b[j].end && v6b[j].start <= v6a[i].end) return true;
514
+ if (v6a[i].end < v6b[j].end) i++;
346
515
  else j++;
347
516
  }
348
517
  }
@@ -350,33 +519,51 @@ function overlapCidr(a, b) {
350
519
  }
351
520
  /** Returns a boolean that indicates whether `networksA` fully contain all `networksB`. */
352
521
  function containsCidr(a, b) {
353
- const aNets = (Array.isArray(a) ? a : [a]).map(parseCidrLean);
354
- const bNets = (Array.isArray(b) ? b : [b]).map(parseCidrLean);
355
- const aByVersion = {
356
- 4: [],
357
- 6: []
358
- };
359
- const bByVersion = {
360
- 4: [],
361
- 6: []
362
- };
363
- for (const n of aNets) aByVersion[n.version].push(n);
364
- for (const n of bNets) bByVersion[n.version].push(n);
365
- for (const v of [4, 6]) {
366
- const containers = aByVersion[v].sort((x, y) => x.start > y.start ? 1 : x.start < y.start ? -1 : 0);
367
- const targets = bByVersion[v];
368
- if (targets.length === 0) continue;
369
- if (containers.length === 0) return false;
370
- const maxEnd = new Array(containers.length);
371
- maxEnd[0] = containers[0].end;
372
- for (let i = 1; i < containers.length; i++) if (containers[i].end > maxEnd[i - 1]) maxEnd[i] = containers[i].end;
373
- else maxEnd[i] = maxEnd[i - 1];
374
- for (const target of targets) {
375
- let lo = 0, hi = containers.length - 1;
522
+ const aArr = Array.isArray(a) ? a : [a];
523
+ const bArr = Array.isArray(b) ? b : [b];
524
+ const v4a = [], v6a = [];
525
+ const v4b = [], v6b = [];
526
+ for (const s of aArr) {
527
+ const n = parseCidrLean(s);
528
+ if (n.version === 4) v4a.push(n);
529
+ else v6a.push(n);
530
+ }
531
+ for (const s of bArr) {
532
+ const n = parseCidrLean(s);
533
+ if (n.version === 4) v4b.push(n);
534
+ else v6b.push(n);
535
+ }
536
+ if (v4b.length > 0) {
537
+ if (v4a.length === 0) return false;
538
+ v4a.sort((x, y) => x.start - y.start);
539
+ const maxEnd = new Array(v4a.length);
540
+ maxEnd[0] = v4a[0].end;
541
+ for (let i = 1; i < v4a.length; i++) maxEnd[i] = Math.max(v4a[i].end, maxEnd[i - 1]);
542
+ for (const target of v4b) {
543
+ let lo = 0, hi = v4a.length - 1;
544
+ let idx = -1;
545
+ while (lo <= hi) {
546
+ const mid = lo + hi >> 1;
547
+ if (v4a[mid].start <= target.start) {
548
+ idx = mid;
549
+ lo = mid + 1;
550
+ } else hi = mid - 1;
551
+ }
552
+ if (idx < 0 || maxEnd[idx] < target.end) return false;
553
+ }
554
+ }
555
+ if (v6b.length > 0) {
556
+ if (v6a.length === 0) return false;
557
+ v6a.sort((x, y) => x.start > y.start ? 1 : x.start < y.start ? -1 : 0);
558
+ const maxEnd = new Array(v6a.length);
559
+ maxEnd[0] = v6a[0].end;
560
+ for (let i = 1; i < v6a.length; i++) maxEnd[i] = v6a[i].end > maxEnd[i - 1] ? v6a[i].end : maxEnd[i - 1];
561
+ for (const target of v6b) {
562
+ let lo = 0, hi = v6a.length - 1;
376
563
  let idx = -1;
377
564
  while (lo <= hi) {
378
565
  const mid = lo + hi >> 1;
379
- if (containers[mid].start <= target.start) {
566
+ if (v6a[mid].start <= target.start) {
380
567
  idx = mid;
381
568
  lo = mid + 1;
382
569
  } else hi = mid - 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cidr-tools",
3
- "version": "11.0.13",
3
+ "version": "11.2.0",
4
4
  "author": "silverwind <me@silverwind.io>",
5
5
  "description": "Tools to work with IPv4 and IPv6 CIDR",
6
6
  "repository": "silverwind/cidr-tools",
@@ -24,7 +24,6 @@
24
24
  "@typescript/native-preview": "7.0.0-dev.20260305.1",
25
25
  "eslint": "9.39.3",
26
26
  "eslint-config-silverwind": "121.2.0",
27
- "fast-cidr-tools": "0.3.4",
28
27
  "jest-extended": "7.0.0",
29
28
  "tsdown": "0.21.0",
30
29
  "tsdown-config-silverwind": "2.0.0",