cidr-tools 11.3.0 → 11.3.2

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 +179 -123
  2. package/package.json +11 -11
package/dist/index.js CHANGED
@@ -5,16 +5,24 @@ const bits = {
5
5
  4: 32,
6
6
  6: 128
7
7
  };
8
- function parseIPv4Fast(s) {
8
+ const octetStrings = Array.from({ length: 256 }, (_, i) => String(i));
9
+ const octetDotStrings = Array.from({ length: 256 }, (_, i) => `${i}.`);
10
+ const prefixStrings = Array.from({ length: 129 }, (_, i) => `/${i}`);
11
+ const prefixNumStrings = Array.from({ length: 129 }, (_, i) => String(i));
12
+ const cmpV4StartEnd = (a, b) => a.start - b.start || a.end - b.end;
13
+ const cmpV4Start = (a, b) => a.start - b.start;
14
+ 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;
15
+ const cmpV6Start = (a, b) => a.start > b.start ? 1 : a.start < b.start ? -1 : 0;
16
+ function parseIPv4Fast(s, end) {
9
17
  let num = 0;
10
18
  let octet = 0;
11
19
  let dots = 0;
12
20
  let digits = 0;
13
- for (let i = 0; i < s.length; i++) {
21
+ for (let i = 0; i < end; i++) {
14
22
  const c = s.charCodeAt(i);
15
23
  if (c === 46) {
16
24
  if (digits === 0 || octet > 255) return -1;
17
- num = (num << 8 | octet) >>> 0;
25
+ num = num << 8 | octet;
18
26
  octet = 0;
19
27
  dots++;
20
28
  digits = 0;
@@ -27,7 +35,7 @@ function parseIPv4Fast(s) {
27
35
  return (num << 8 | octet) >>> 0;
28
36
  }
29
37
  function formatIPv4Fast(n) {
30
- return `${n >>> 24 & 255}.${n >>> 16 & 255}.${n >>> 8 & 255}.${n & 255}`;
38
+ return octetDotStrings[n >>> 24 & 255] + octetDotStrings[n >>> 16 & 255] + octetDotStrings[n >>> 8 & 255] + octetStrings[n & 255];
31
39
  }
32
40
  function parsePrefixNum(str, slashIndex) {
33
41
  if (slashIndex === -1) return -1;
@@ -40,19 +48,44 @@ function parsePrefixNum(str, slashIndex) {
40
48
  }
41
49
  return prefixNum;
42
50
  }
43
- function doNormalize(cidr, { compress = true, hexify = false } = {}) {
51
+ let rangeV4Start = 0;
52
+ let rangeV4End = 0;
53
+ let rangeV4Prefix = 0;
54
+ let rangeSlashIndex = -1;
55
+ function parseIPv4Range(str) {
56
+ rangeSlashIndex = str.indexOf("/");
57
+ const v4num = parseIPv4Fast(str, rangeSlashIndex !== -1 ? rangeSlashIndex : str.length);
58
+ if (v4num === -1) return false;
59
+ rangeV4Prefix = rangeSlashIndex !== -1 ? parsePrefixNum(str, rangeSlashIndex) : 32;
60
+ const hostBits = 32 - rangeV4Prefix;
61
+ if (hostBits >= 32) {
62
+ rangeV4Start = 0;
63
+ rangeV4End = 4294967295;
64
+ } else {
65
+ const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
66
+ rangeV4Start = (v4num & ~mask) >>> 0;
67
+ rangeV4End = (v4num | mask) >>> 0;
68
+ }
69
+ return true;
70
+ }
71
+ function doNormalize(cidr, opts) {
72
+ if (parseIPv4Range(cidr)) {
73
+ const ip = formatIPv4Fast(rangeV4Start);
74
+ return rangeSlashIndex !== -1 ? ip + prefixStrings[rangeV4Prefix] : ip;
75
+ }
44
76
  const { start, end, prefix, version, prefixPresent } = parseCidr(cidr);
45
77
  if (version === 4) {
46
78
  const ip = formatIPv4Fast(Number(start));
47
- return start !== end || prefixPresent ? `${ip}/${prefix}` : ip;
79
+ return start !== end || prefixPresent ? ip + prefixStrings[Number(prefix)] : ip;
48
80
  }
49
- if (start !== end || prefixPresent) return `${normalizeIp(stringifyIp({
81
+ const { compress = true, hexify = false } = opts || {};
82
+ if (start !== end || prefixPresent) return normalizeIp(stringifyIp({
50
83
  number: start,
51
84
  version
52
85
  }), {
53
86
  compress,
54
87
  hexify
55
- })}/${prefix}`;
88
+ }) + prefixStrings[Number(prefix)];
56
89
  else return normalizeIp(cidr, {
57
90
  compress,
58
91
  hexify
@@ -66,49 +99,39 @@ function normalizeCidr(cidr, opts) {
66
99
  /** 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. */
67
100
  function parseCidr(str) {
68
101
  const slashIndex = str.indexOf("/");
69
- let ipPart;
70
- let prefixNum;
71
- let prefixPresent;
72
- if (slashIndex !== -1) {
73
- ipPart = str.substring(0, slashIndex);
74
- prefixNum = parsePrefixNum(str, slashIndex);
75
- prefixPresent = true;
76
- } else {
77
- ipPart = str;
78
- prefixNum = -1;
79
- prefixPresent = false;
80
- }
81
- if (!ipPart.includes(":")) {
82
- const v4num = parseIPv4Fast(ipPart);
83
- if (v4num !== -1) {
84
- if (prefixNum === -1) prefixNum = 32;
85
- const ip = formatIPv4Fast(v4num);
86
- const prefix = String(prefixNum);
87
- const hostBits = 32 - prefixNum;
88
- let startNum, endNum;
89
- if (hostBits >= 32) {
90
- startNum = 0;
91
- endNum = 4294967295;
92
- } else {
93
- const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
94
- startNum = (v4num & ~mask) >>> 0;
95
- endNum = (v4num | mask) >>> 0;
96
- }
97
- return {
98
- cidr: `${ip}/${prefix}`,
99
- ip,
100
- version: 4,
101
- prefix,
102
- prefixPresent,
103
- start: BigInt(startNum),
104
- end: BigInt(endNum)
105
- };
102
+ const ipEnd = slashIndex !== -1 ? slashIndex : str.length;
103
+ const prefixPresent = slashIndex !== -1;
104
+ const v4num = parseIPv4Fast(str, ipEnd);
105
+ if (v4num !== -1) {
106
+ const prefixNum = prefixPresent ? parsePrefixNum(str, slashIndex) : 32;
107
+ const ip = formatIPv4Fast(v4num);
108
+ const prefix = prefixNumStrings[prefixNum];
109
+ const hostBits = 32 - prefixNum;
110
+ let startNum, endNum;
111
+ if (hostBits >= 32) {
112
+ startNum = 0;
113
+ endNum = 4294967295;
114
+ } else {
115
+ const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
116
+ startNum = (v4num & ~mask) >>> 0;
117
+ endNum = (v4num | mask) >>> 0;
106
118
  }
119
+ return {
120
+ cidr: ip + prefixStrings[prefixNum],
121
+ ip,
122
+ version: 4,
123
+ prefix,
124
+ prefixPresent,
125
+ start: BigInt(startNum),
126
+ end: BigInt(endNum)
127
+ };
107
128
  }
129
+ const ipPart = prefixPresent ? str.substring(0, slashIndex) : str;
130
+ let prefixNum = prefixPresent ? parsePrefixNum(str, slashIndex) : -1;
108
131
  const { number, version, ipv4mapped, scopeid } = parseIp(ipPart);
109
132
  if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
110
133
  if (prefixNum === -1) prefixNum = bits[version];
111
- const prefix = String(prefixNum);
134
+ const prefix = prefixNumStrings[prefixNum];
112
135
  const ip = stringifyIp({
113
136
  number,
114
137
  version,
@@ -118,7 +141,7 @@ function parseCidr(str) {
118
141
  const hostBits = bits[version] - prefixNum;
119
142
  const mask = hostBits > 0 ? (1n << BigInt(hostBits)) - 1n : 0n;
120
143
  return {
121
- cidr: `${ip}/${prefix}`,
144
+ cidr: ip + prefixStrings[prefixNum],
122
145
  ip,
123
146
  version,
124
147
  prefix,
@@ -128,34 +151,14 @@ function parseCidr(str) {
128
151
  };
129
152
  }
130
153
  function parseCidrLean(str) {
131
- const slashIndex = str.indexOf("/");
132
- let ipPart;
133
- let prefixNum;
134
- if (slashIndex !== -1) {
135
- ipPart = str.substring(0, slashIndex);
136
- prefixNum = parsePrefixNum(str, slashIndex);
137
- } else {
138
- ipPart = str;
139
- prefixNum = -1;
140
- }
141
- if (!ipPart.includes(":")) {
142
- const v4num = parseIPv4Fast(ipPart);
143
- if (v4num !== -1) {
144
- if (prefixNum === -1) prefixNum = 32;
145
- const hostBits = 32 - prefixNum;
146
- if (hostBits >= 32) return {
147
- start: 0,
148
- end: 4294967295,
149
- version: 4
150
- };
151
- const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
152
- return {
153
- start: (v4num & ~mask) >>> 0,
154
- end: (v4num | mask) >>> 0,
155
- version: 4
156
- };
157
- }
158
- }
154
+ if (parseIPv4Range(str)) return {
155
+ start: rangeV4Start,
156
+ end: rangeV4End,
157
+ version: 4
158
+ };
159
+ const slashIndex = rangeSlashIndex;
160
+ const ipPart = slashIndex !== -1 ? str.substring(0, slashIndex) : str;
161
+ let prefixNum = slashIndex !== -1 ? parsePrefixNum(str, slashIndex) : -1;
159
162
  const { number, version } = parseIp(ipPart);
160
163
  if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
161
164
  if (prefixNum === -1) prefixNum = bits[version];
@@ -332,21 +335,17 @@ function subparts6(pStart, pEnd, output) {
332
335
  if (end !== pEnd) subparts6(end + 1n, pEnd, output);
333
336
  }
334
337
  function formatPart4(part) {
335
- const ip = formatIPv4Fast(part.start);
336
- const size = part.end - part.start + 1;
337
- return `${ip}/${32 - (size <= 1 ? 0 : size >= 4294967296 ? 32 : 31 - Math.clz32(size))}`;
338
+ return formatIPv4Fast(part.start) + prefixStrings[Math.clz32(part.end - part.start)];
338
339
  }
339
340
  function formatPart6(part) {
340
- const ip = stringifyIp({
341
+ return stringifyIp({
341
342
  number: part.start,
342
343
  version: 6
343
- });
344
- const size = part.end - part.start + 1n;
345
- return `${ip}/${128 - (size <= 1n ? 0 : bigintBitLength(size) - 1)}`;
344
+ }) + prefixStrings[128 - bigintBitLength(part.end - part.start)];
346
345
  }
347
346
  function mergeIntervalsRaw4(nets) {
348
347
  if (nets.length === 0) return [];
349
- nets.sort((a, b) => a.start - b.start || a.end - b.end);
348
+ nets.sort(cmpV4StartEnd);
350
349
  const merged = [];
351
350
  let curStart = nets[0].start;
352
351
  let curEnd = nets[0].end;
@@ -371,7 +370,7 @@ function mergeIntervalsRaw4(nets) {
371
370
  }
372
371
  function mergeIntervalsRaw6(nets) {
373
372
  if (nets.length === 0) return [];
374
- 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);
373
+ nets.sort(cmpV6StartEnd);
375
374
  const merged = [];
376
375
  let curStart = nets[0].start;
377
376
  let curEnd = nets[0].end;
@@ -509,15 +508,36 @@ function* expandCidr(nets) {
509
508
  if (n.version === 4) v4.push(n);
510
509
  else v6.push(n);
511
510
  }
512
- if (v4.length > 0) for (const part of mergeIntervalsRaw4(v4)) for (let n = part.start; n <= part.end; n++) yield formatIPv4Fast(n);
513
- if (v6.length > 0) for (const part of mergeIntervalsRaw6(v6)) for (let num = part.start; num <= part.end; num++) yield stringifyIp({
514
- number: num,
515
- version: 6
516
- });
511
+ if (v4.length > 0) for (const part of mergeIntervalsRaw4(v4)) {
512
+ let prevHigh = -1;
513
+ let prefix = "";
514
+ for (let n = part.start; n <= part.end; n++) {
515
+ const high = n >>> 8;
516
+ if (high !== prevHigh) {
517
+ prefix = octetDotStrings[n >>> 24 & 255] + octetDotStrings[n >>> 16 & 255] + octetDotStrings[n >>> 8 & 255];
518
+ prevHigh = high;
519
+ }
520
+ yield prefix + octetStrings[n & 255];
521
+ }
522
+ }
523
+ if (v6.length > 0) {
524
+ const ipObj = {
525
+ number: 0n,
526
+ version: 6
527
+ };
528
+ for (const part of mergeIntervalsRaw6(v6)) for (let num = part.start; num <= part.end; num++) {
529
+ ipObj.number = num;
530
+ yield stringifyIp(ipObj);
531
+ }
532
+ }
517
533
  }
518
534
  /** Returns a boolean that indicates if `networksA` overlap (intersect) with `networksB`. */
519
535
  function overlapCidr(a, b) {
520
536
  if (!Array.isArray(a) && !Array.isArray(b)) {
537
+ if (parseIPv4Range(a)) {
538
+ const startA = rangeV4Start, endA = rangeV4End;
539
+ if (parseIPv4Range(b)) return startA <= rangeV4End && rangeV4Start <= endA;
540
+ }
521
541
  const pa = parseCidrLean(a);
522
542
  const pb = parseCidrLean(b);
523
543
  if (pa.version !== pb.version) return false;
@@ -537,9 +557,15 @@ function overlapCidr(a, b) {
537
557
  if (n.version === 4) v4b.push(n);
538
558
  else v6b.push(n);
539
559
  }
540
- if (v4a.length > 0 && v4b.length > 0) {
541
- v4a.sort((x, y) => x.start - y.start);
542
- v4b.sort((x, y) => x.start - y.start);
560
+ if (v4a.length > 0 && v4b.length > 0) if (v4b.length === 1) {
561
+ const bs = v4b[0].start, be = v4b[0].end;
562
+ for (const el of v4a) if (el.start <= be && bs <= el.end) return true;
563
+ } else if (v4a.length === 1) {
564
+ const as = v4a[0].start, ae = v4a[0].end;
565
+ for (const el of v4b) if (as <= el.end && el.start <= ae) return true;
566
+ } else {
567
+ v4a.sort(cmpV4Start);
568
+ v4b.sort(cmpV4Start);
543
569
  let i = 0, j = 0;
544
570
  while (i < v4a.length && j < v4b.length) {
545
571
  if (v4a[i].start <= v4b[j].end && v4b[j].start <= v4a[i].end) return true;
@@ -547,9 +573,15 @@ function overlapCidr(a, b) {
547
573
  else j++;
548
574
  }
549
575
  }
550
- if (v6a.length > 0 && v6b.length > 0) {
551
- v6a.sort((x, y) => x.start > y.start ? 1 : x.start < y.start ? -1 : 0);
552
- v6b.sort((x, y) => x.start > y.start ? 1 : x.start < y.start ? -1 : 0);
576
+ if (v6a.length > 0 && v6b.length > 0) if (v6b.length === 1) {
577
+ const bs = v6b[0].start, be = v6b[0].end;
578
+ for (const el of v6a) if (el.start <= be && bs <= el.end) return true;
579
+ } else if (v6a.length === 1) {
580
+ const as = v6a[0].start, ae = v6a[0].end;
581
+ for (const el of v6b) if (as <= el.end && el.start <= ae) return true;
582
+ } else {
583
+ v6a.sort(cmpV6Start);
584
+ v6b.sort(cmpV6Start);
553
585
  let i = 0, j = 0;
554
586
  while (i < v6a.length && j < v6b.length) {
555
587
  if (v6a[i].start <= v6b[j].end && v6b[j].start <= v6a[i].end) return true;
@@ -562,6 +594,10 @@ function overlapCidr(a, b) {
562
594
  /** Returns a boolean that indicates whether `networksA` fully contain all `networksB`. */
563
595
  function containsCidr(a, b) {
564
596
  if (!Array.isArray(a) && !Array.isArray(b)) {
597
+ if (parseIPv4Range(a)) {
598
+ const startA = rangeV4Start, endA = rangeV4End;
599
+ if (parseIPv4Range(b)) return startA <= rangeV4Start && endA >= rangeV4End;
600
+ }
565
601
  const pa = parseCidrLean(a);
566
602
  const pb = parseCidrLean(b);
567
603
  if (pa.version !== pb.version) return false;
@@ -583,40 +619,60 @@ function containsCidr(a, b) {
583
619
  }
584
620
  if (v4b.length > 0) {
585
621
  if (v4a.length === 0) return false;
586
- v4a.sort((x, y) => x.start - y.start);
587
- const maxEnd = new Array(v4a.length);
588
- maxEnd[0] = v4a[0].end;
589
- for (let i = 1; i < v4a.length; i++) maxEnd[i] = Math.max(v4a[i].end, maxEnd[i - 1]);
590
- for (const target of v4b) {
591
- let lo = 0, hi = v4a.length - 1;
592
- let idx = -1;
593
- while (lo <= hi) {
594
- const mid = lo + hi >> 1;
595
- if (v4a[mid].start <= target.start) {
596
- idx = mid;
597
- lo = mid + 1;
598
- } else hi = mid - 1;
622
+ if (v4b.length === 1) {
623
+ const ts = v4b[0].start, te = v4b[0].end;
624
+ let found = false;
625
+ for (const a of v4a) if (a.start <= ts && a.end >= te) {
626
+ found = true;
627
+ break;
628
+ }
629
+ if (!found) return false;
630
+ } else {
631
+ v4a.sort(cmpV4Start);
632
+ const maxEnd = new Array(v4a.length);
633
+ maxEnd[0] = v4a[0].end;
634
+ for (let i = 1; i < v4a.length; i++) maxEnd[i] = Math.max(v4a[i].end, maxEnd[i - 1]);
635
+ for (const target of v4b) {
636
+ let lo = 0, hi = v4a.length - 1;
637
+ let idx = -1;
638
+ while (lo <= hi) {
639
+ const mid = lo + hi >> 1;
640
+ if (v4a[mid].start <= target.start) {
641
+ idx = mid;
642
+ lo = mid + 1;
643
+ } else hi = mid - 1;
644
+ }
645
+ if (idx < 0 || maxEnd[idx] < target.end) return false;
599
646
  }
600
- if (idx < 0 || maxEnd[idx] < target.end) return false;
601
647
  }
602
648
  }
603
649
  if (v6b.length > 0) {
604
650
  if (v6a.length === 0) return false;
605
- v6a.sort((x, y) => x.start > y.start ? 1 : x.start < y.start ? -1 : 0);
606
- const maxEnd = new Array(v6a.length);
607
- maxEnd[0] = v6a[0].end;
608
- for (let i = 1; i < v6a.length; i++) maxEnd[i] = v6a[i].end > maxEnd[i - 1] ? v6a[i].end : maxEnd[i - 1];
609
- for (const target of v6b) {
610
- let lo = 0, hi = v6a.length - 1;
611
- let idx = -1;
612
- while (lo <= hi) {
613
- const mid = lo + hi >> 1;
614
- if (v6a[mid].start <= target.start) {
615
- idx = mid;
616
- lo = mid + 1;
617
- } else hi = mid - 1;
651
+ if (v6b.length === 1) {
652
+ const ts = v6b[0].start, te = v6b[0].end;
653
+ let found = false;
654
+ for (const a of v6a) if (a.start <= ts && a.end >= te) {
655
+ found = true;
656
+ break;
657
+ }
658
+ if (!found) return false;
659
+ } else {
660
+ v6a.sort(cmpV6Start);
661
+ const maxEnd = new Array(v6a.length);
662
+ maxEnd[0] = v6a[0].end;
663
+ for (let i = 1; i < v6a.length; i++) maxEnd[i] = v6a[i].end > maxEnd[i - 1] ? v6a[i].end : maxEnd[i - 1];
664
+ for (const target of v6b) {
665
+ let lo = 0, hi = v6a.length - 1;
666
+ let idx = -1;
667
+ while (lo <= hi) {
668
+ const mid = lo + hi >> 1;
669
+ if (v6a[mid].start <= target.start) {
670
+ idx = mid;
671
+ lo = mid + 1;
672
+ } else hi = mid - 1;
673
+ }
674
+ if (idx < 0 || maxEnd[idx] < target.end) return false;
618
675
  }
619
- if (idx < 0 || maxEnd[idx] < target.end) return false;
620
676
  }
621
677
  }
622
678
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cidr-tools",
3
- "version": "11.3.0",
3
+ "version": "11.3.2",
4
4
  "author": "silverwind <me@silverwind.io>",
5
5
  "description": "Tools to work with IPv4 and IPv6 CIDR",
6
6
  "repository": "silverwind/cidr-tools",
@@ -17,22 +17,22 @@
17
17
  "node": ">=18"
18
18
  },
19
19
  "dependencies": {
20
- "ip-bigint": "^8.2.12"
20
+ "ip-bigint": "^8.3.2"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "25.5.0",
24
- "@typescript/native-preview": "7.0.0-dev.20260317.1",
25
- "eslint": "9.39.4",
26
- "eslint-config-silverwind": "125.0.3",
24
+ "@typescript/native-preview": "7.0.0-dev.20260324.1",
25
+ "eslint": "10.1.0",
26
+ "eslint-config-silverwind": "127.1.4",
27
27
  "jest-extended": "7.0.0",
28
28
  "tsdown": "0.21.4",
29
29
  "tsdown-config-silverwind": "2.0.2",
30
30
  "typescript": "5.9.3",
31
- "typescript-config-silverwind": "16.0.0",
32
- "updates": "17.9.1",
33
- "updates-config-silverwind": "1.0.4",
34
- "versions": "14.2.1",
35
- "vitest": "4.1.0",
36
- "vitest-config-silverwind": "10.6.5"
31
+ "typescript-config-silverwind": "16.1.0",
32
+ "updates": "17.11.2",
33
+ "updates-config-silverwind": "2.0.1",
34
+ "versions": "14.2.4",
35
+ "vitest": "4.1.1",
36
+ "vitest-config-silverwind": "11.0.0"
37
37
  }
38
38
  }