cidr-tools 11.0.11 → 11.0.13

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 +135 -77
  2. package/package.json +10 -9
package/dist/index.js CHANGED
@@ -5,9 +5,29 @@ const bits = {
5
5
  4: 32,
6
6
  6: 128
7
7
  };
8
- function uniq(arr) {
9
- const set = new Set(arr);
10
- return set.size === arr.length ? arr : Array.from(set);
8
+ function parseIPv4Fast(s) {
9
+ let num = 0;
10
+ let octet = 0;
11
+ let dots = 0;
12
+ let digits = 0;
13
+ for (let i = 0; i < s.length; i++) {
14
+ const c = s.charCodeAt(i);
15
+ if (c === 46) {
16
+ if (digits === 0 || octet > 255) return -1;
17
+ num = (num << 8 | octet) >>> 0;
18
+ octet = 0;
19
+ dots++;
20
+ digits = 0;
21
+ } else if (c >= 48 && c <= 57) {
22
+ octet = octet * 10 + (c - 48);
23
+ digits++;
24
+ } else return -1;
25
+ }
26
+ if (dots !== 3 || digits === 0 || octet > 255) return -1;
27
+ return (num << 8 | octet) >>> 0;
28
+ }
29
+ function formatIPv4Fast(n) {
30
+ return `${n >>> 24 & 255}.${n >>> 16 & 255}.${n >>> 8 & 255}.${n & 255}`;
11
31
  }
12
32
  function doNormalize(cidr, { compress = true, hexify = false } = {}) {
13
33
  const { start, end, prefix, version, prefixPresent } = parseCidr(cidr);
@@ -30,37 +50,40 @@ function normalizeCidr(cidr, opts) {
30
50
  }
31
51
  /** 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. */
32
52
  function parseCidr(str) {
33
- const parsed = Object.create(null);
34
53
  const slashIndex = str.indexOf("/");
35
54
  let ipPart;
36
55
  let prefix;
56
+ let prefixPresent;
37
57
  if (slashIndex !== -1) {
38
58
  ipPart = str.substring(0, slashIndex);
39
59
  prefix = str.substring(slashIndex + 1);
40
60
  if (!/^[0-9]+$/.test(prefix)) throw new Error(`Network is not a CIDR or IP: "${str}"`);
41
- parsed.prefixPresent = true;
61
+ prefixPresent = true;
42
62
  } else {
43
63
  ipPart = str;
44
- parsed.prefixPresent = false;
64
+ prefixPresent = false;
45
65
  prefix = "";
46
66
  }
47
67
  const { number, version, ipv4mapped, scopeid } = parseIp(ipPart);
48
68
  if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
49
- if (!parsed.prefixPresent) prefix = String(bits[version]);
50
- parsed.version = version;
51
- parsed.ip = stringifyIp({
69
+ if (!prefixPresent) prefix = String(bits[version]);
70
+ const ip = stringifyIp({
52
71
  number,
53
72
  version,
54
73
  ipv4mapped,
55
74
  scopeid
56
75
  });
57
- parsed.cidr = `${parsed.ip}/${prefix}`;
58
- parsed.prefix = prefix;
59
76
  const hostBits = bits[version] - Number(prefix);
60
77
  const mask = hostBits > 0 ? (1n << BigInt(hostBits)) - 1n : 0n;
61
- parsed.start = number & ~mask;
62
- parsed.end = number | mask;
63
- return parsed;
78
+ return {
79
+ cidr: `${ip}/${prefix}`,
80
+ ip,
81
+ version,
82
+ prefix,
83
+ prefixPresent,
84
+ start: number & ~mask,
85
+ end: number | mask
86
+ };
64
87
  }
65
88
  function parseCidrLean(str) {
66
89
  const slashIndex = str.indexOf("/");
@@ -68,13 +91,35 @@ function parseCidrLean(str) {
68
91
  let prefixNum;
69
92
  if (slashIndex !== -1) {
70
93
  ipPart = str.substring(0, slashIndex);
71
- const prefixStr = str.substring(slashIndex + 1);
72
- if (!/^[0-9]+$/.test(prefixStr)) throw new Error(`Network is not a CIDR or IP: "${str}"`);
73
- prefixNum = Number(prefixStr);
94
+ if (slashIndex + 1 >= str.length) throw new Error(`Network is not a CIDR or IP: "${str}"`);
95
+ prefixNum = 0;
96
+ for (let i = slashIndex + 1; i < str.length; i++) {
97
+ const c = str.charCodeAt(i);
98
+ if (c < 48 || c > 57) throw new Error(`Network is not a CIDR or IP: "${str}"`);
99
+ prefixNum = prefixNum * 10 + (c - 48);
100
+ }
74
101
  } else {
75
102
  ipPart = str;
76
103
  prefixNum = -1;
77
104
  }
105
+ if (!ipPart.includes(":")) {
106
+ const v4num = parseIPv4Fast(ipPart);
107
+ if (v4num !== -1) {
108
+ if (prefixNum === -1) prefixNum = 32;
109
+ const hostBits = 32 - prefixNum;
110
+ if (hostBits >= 32) return {
111
+ start: 0n,
112
+ end: 4294967295n,
113
+ version: 4
114
+ };
115
+ const mask = hostBits > 0 ? (1 << hostBits >>> 0) - 1 : 0;
116
+ return {
117
+ start: BigInt((v4num & ~mask) >>> 0),
118
+ end: BigInt((v4num | mask) >>> 0),
119
+ version: 4
120
+ };
121
+ }
122
+ }
78
123
  const { number, version } = parseIp(ipPart);
79
124
  if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
80
125
  if (prefixNum === -1) prefixNum = bits[version];
@@ -90,69 +135,72 @@ function biggestPowerOfTwo(num) {
90
135
  if (num === 0n) return 0n;
91
136
  return 1n << BigInt(num.toString(2).length - 1);
92
137
  }
93
- function subparts(part, output) {
94
- if (!output) output = [];
95
- if (part.end < part.start) return output;
96
- if (part.end === part.start) {
97
- output.push(part);
98
- return output;
138
+ function subparts(pStart, pEnd, output) {
139
+ if (pEnd < pStart) return;
140
+ if (pEnd === pStart) {
141
+ output.push({
142
+ start: pStart,
143
+ end: pEnd
144
+ });
145
+ return;
99
146
  }
100
- if (part.end - part.start === 1n) {
101
- if (part.end % 2n === 0n) output.push({
102
- start: part.start,
103
- end: part.start
147
+ if (pEnd - pStart === 1n) {
148
+ if (pEnd % 2n === 0n) output.push({
149
+ start: pStart,
150
+ end: pStart
104
151
  }, {
105
- start: part.end,
106
- end: part.end
152
+ start: pEnd,
153
+ end: pEnd
107
154
  });
108
155
  else output.push({
109
- start: part.start,
110
- end: part.end
156
+ start: pStart,
157
+ end: pEnd
111
158
  });
112
- return output;
159
+ return;
113
160
  }
114
- const size = diff(part.end, part.start);
161
+ const size = diff(pEnd, pStart);
115
162
  let biggest = biggestPowerOfTwo(size);
116
163
  let start;
117
164
  let end;
118
- if (size === biggest && part.start % biggest === 0n) {
119
- output.push(part);
120
- return output;
121
- } else if (part.start % biggest === 0n) {
122
- start = part.start;
165
+ if (size === biggest && pStart % biggest === 0n) {
166
+ output.push({
167
+ start: pStart,
168
+ end: pEnd
169
+ });
170
+ return;
171
+ } else if (pStart % biggest === 0n) {
172
+ start = pStart;
123
173
  end = start + biggest - 1n;
124
174
  } else {
125
- start = part.end / biggest * biggest;
126
- if (start + biggest - 1n > part.end) {
127
- start = (part.end / biggest - 1n) * biggest;
128
- while (start < part.start) {
175
+ start = pEnd / biggest * biggest;
176
+ if (start + biggest - 1n > pEnd) {
177
+ start = (pEnd / biggest - 1n) * biggest;
178
+ while (start < pStart) {
129
179
  biggest /= 2n;
130
- start = (part.end / biggest - 1n) * biggest;
180
+ start = (pEnd / biggest - 1n) * biggest;
131
181
  }
132
182
  end = start + biggest - 1n;
133
183
  } else {
134
- start = part.end / biggest * biggest;
184
+ start = pEnd / biggest * biggest;
135
185
  end = start + biggest - 1n;
136
186
  }
137
187
  }
138
- if (start !== part.start) subparts({
139
- start: part.start,
140
- end: start - 1n
141
- }, output);
188
+ if (start !== pStart) subparts(pStart, start - 1n, output);
142
189
  output.push({
143
190
  start,
144
191
  end
145
192
  });
146
- if (end !== part.end) subparts({
147
- start: end + 1n,
148
- end: part.end
149
- }, output);
150
- return output;
193
+ if (end !== pEnd) subparts(end + 1n, pEnd, output);
151
194
  }
152
195
  function diff(a, b) {
153
196
  return a + 1n - b;
154
197
  }
155
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
+ }
156
204
  const ip = stringifyIp({
157
205
  number: part.start,
158
206
  version
@@ -163,12 +211,12 @@ function formatPart(part, version) {
163
211
  }
164
212
  function mergeIntervalsRaw(nets) {
165
213
  if (nets.length === 0) return [];
166
- const sorted = nets.slice().sort((a, b) => a.start > b.start ? 1 : a.start < b.start ? -1 : a.end > b.end ? 1 : a.end < b.end ? -1 : 0);
214
+ 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);
167
215
  const merged = [];
168
- let curStart = sorted[0].start;
169
- let curEnd = sorted[0].end;
170
- for (let i = 1; i < sorted.length; i++) {
171
- const { start, end } = sorted[i];
216
+ let curStart = nets[0].start;
217
+ let curEnd = nets[0].end;
218
+ for (let i = 1; i < nets.length; i++) {
219
+ const { start, end } = nets[i];
172
220
  if (start <= curEnd + 1n) {
173
221
  if (end > curEnd) curEnd = end;
174
222
  } else {
@@ -188,7 +236,7 @@ function mergeIntervalsRaw(nets) {
188
236
  }
189
237
  function mergeIntervals(nets) {
190
238
  const merged = [];
191
- for (const part of mergeIntervalsRaw(nets)) subparts(part, merged);
239
+ for (const part of mergeIntervalsRaw(nets)) subparts(part.start, part.end, merged);
192
240
  return merged;
193
241
  }
194
242
  function subtractSorted(bases, excls) {
@@ -218,7 +266,7 @@ function subtractSorted(bases, excls) {
218
266
  }
219
267
  /** Returns an array of merged networks */
220
268
  function mergeCidr(nets) {
221
- const arr = uniq(Array.isArray(nets) ? nets : [nets]).map(parseCidrLean);
269
+ const arr = (Array.isArray(nets) ? nets : [nets]).map(parseCidrLean);
222
270
  const byVersion = {
223
271
  4: [],
224
272
  6: []
@@ -230,8 +278,8 @@ function mergeCidr(nets) {
230
278
  }
231
279
  /** Returns an array of merged remaining networks of the subtraction of `excludeNetworks` from `baseNetworks`. */
232
280
  function excludeCidr(base, excl) {
233
- const baseArr = uniq(Array.isArray(base) ? base : [base]).map(parseCidrLean);
234
- const exclArr = uniq(Array.isArray(excl) ? excl : [excl]).map(parseCidrLean);
281
+ const baseArr = (Array.isArray(base) ? base : [base]).map(parseCidrLean);
282
+ const exclArr = (Array.isArray(excl) ? excl : [excl]).map(parseCidrLean);
235
283
  const baseByVersion = {
236
284
  4: [],
237
285
  6: []
@@ -245,27 +293,37 @@ function excludeCidr(base, excl) {
245
293
  const result = [];
246
294
  for (const v of [4, 6]) {
247
295
  const remaining = subtractSorted(mergeIntervalsRaw(baseByVersion[v]), mergeIntervalsRaw(exclByVersion[v]));
248
- for (const part of remaining) {
249
- const aligned = subparts(part);
250
- for (const p of aligned) result.push(formatPart(p, v));
251
- }
296
+ 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));
252
299
  }
253
300
  return result;
254
301
  }
255
302
  function* expandCidr(nets) {
256
- const arr = uniq(Array.isArray(nets) ? nets : [nets]);
257
- for (const net of mergeCidr(arr)) {
258
- const { start, end, version } = parseCidrLean(net);
259
- for (let number = start; number <= end; number++) yield normalizeIp(stringifyIp({
260
- number,
261
- version
262
- }));
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
+ });
263
321
  }
264
322
  }
265
323
  /** Returns a boolean that indicates if `networksA` overlap (intersect) with `networksB`. */
266
324
  function overlapCidr(a, b) {
267
- const aNets = uniq(Array.isArray(a) ? a : [a]).map(parseCidrLean);
268
- const bNets = uniq(Array.isArray(b) ? b : [b]).map(parseCidrLean);
325
+ const aNets = (Array.isArray(a) ? a : [a]).map(parseCidrLean);
326
+ const bNets = (Array.isArray(b) ? b : [b]).map(parseCidrLean);
269
327
  const aByVersion = {
270
328
  4: [],
271
329
  6: []
@@ -292,8 +350,8 @@ function overlapCidr(a, b) {
292
350
  }
293
351
  /** Returns a boolean that indicates whether `networksA` fully contain all `networksB`. */
294
352
  function containsCidr(a, b) {
295
- const aNets = uniq(Array.isArray(a) ? a : [a]).map(parseCidrLean);
296
- const bNets = uniq(Array.isArray(b) ? b : [b]).map(parseCidrLean);
353
+ const aNets = (Array.isArray(a) ? a : [a]).map(parseCidrLean);
354
+ const bNets = (Array.isArray(b) ? b : [b]).map(parseCidrLean);
297
355
  const aByVersion = {
298
356
  4: [],
299
357
  6: []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cidr-tools",
3
- "version": "11.0.11",
3
+ "version": "11.0.13",
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,21 +17,22 @@
17
17
  "node": ">=18"
18
18
  },
19
19
  "dependencies": {
20
- "ip-bigint": "^8.2.7"
20
+ "ip-bigint": "^8.2.9"
21
21
  },
22
22
  "devDependencies": {
23
- "@types/node": "25.3.0",
24
- "@typescript/native-preview": "7.0.0-dev.20260224.1",
23
+ "@types/node": "25.3.3",
24
+ "@typescript/native-preview": "7.0.0-dev.20260305.1",
25
25
  "eslint": "9.39.3",
26
- "eslint-config-silverwind": "121.1.3",
26
+ "eslint-config-silverwind": "121.2.0",
27
+ "fast-cidr-tools": "0.3.4",
27
28
  "jest-extended": "7.0.0",
28
- "tsdown": "0.20.3",
29
- "tsdown-config-silverwind": "1.7.5",
29
+ "tsdown": "0.21.0",
30
+ "tsdown-config-silverwind": "2.0.0",
30
31
  "typescript": "5.9.3",
31
32
  "typescript-config-silverwind": "15.0.0",
32
- "updates": "17.6.2",
33
+ "updates": "17.8.1",
33
34
  "updates-config-silverwind": "1.0.3",
34
- "versions": "14.2.0",
35
+ "versions": "14.2.1",
35
36
  "vitest": "4.0.18",
36
37
  "vitest-config-silverwind": "10.6.3"
37
38
  }