cidr-tools 12.0.2 → 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 (2) hide show
  1. package/dist/index.js +81 -64
  2. package/package.json +19 -16
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,23 +77,33 @@ 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) {
@@ -129,7 +143,8 @@ function parseCidr(str) {
129
143
  let prefixNum = prefixPresent ? parsePrefixNum(str, slashIndex) : -1;
130
144
  const { number, version, ipv4mapped, scopeid } = parseIp(ipPart);
131
145
  if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
132
- if (prefixNum === -1) prefixNum = bits[version];
146
+ const numBits = bits[version];
147
+ if (prefixNum === -1) prefixNum = numBits;
133
148
  const prefix = prefixNumStrings[prefixNum] ?? String(prefixNum);
134
149
  const ip = stringifyIp({
135
150
  number,
@@ -137,16 +152,21 @@ function parseCidr(str) {
137
152
  ipv4mapped,
138
153
  scopeid
139
154
  });
140
- const hostBits = bits[version] - prefixNum;
141
- 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
+ }
142
162
  return {
143
163
  cidr: ip + prefixStrings[prefixNum],
144
164
  ip,
145
165
  version,
146
166
  prefix,
147
167
  prefixPresent,
148
- start: number & ~mask,
149
- end: number | mask
168
+ start,
169
+ end
150
170
  };
151
171
  }
152
172
  function parseCidrLean(str) {
@@ -160,8 +180,9 @@ function parseCidrLean(str) {
160
180
  let prefixNum = slashIndex !== -1 ? parsePrefixNum(str, slashIndex) : -1;
161
181
  const { number, version } = parseIp(ipPart);
162
182
  if (!version) throw new Error(`Network is not a CIDR or IP: "${str}"`);
163
- if (prefixNum === -1) prefixNum = bits[version];
164
- const hostBits = bits[version] - prefixNum;
183
+ const numBits = bits[version];
184
+ if (prefixNum === -1) prefixNum = numBits;
185
+ const hostBits = numBits - prefixNum;
165
186
  if (version === 4) {
166
187
  const num = Number(number);
167
188
  if (hostBits >= 32) return {
@@ -176,10 +197,14 @@ function parseCidrLean(str) {
176
197
  version: 4
177
198
  };
178
199
  }
179
- const mask = hostBits > 0 ? (1n << BigInt(hostBits)) - 1n : 0n;
200
+ if (hostBits <= 0) return {
201
+ start: number,
202
+ end: number,
203
+ version: 6
204
+ };
180
205
  return {
181
- start: number & ~mask,
182
- end: number | mask,
206
+ start: number & hostNotMasks[hostBits],
207
+ end: number | hostMasks[hostBits],
183
208
  version: 6
184
209
  };
185
210
  }
@@ -211,10 +236,7 @@ function subparts4(pStart, pEnd, output) {
211
236
  const size = pEnd - start + 1;
212
237
  const lowBit = (start & -start) >>> 0;
213
238
  const blockSize = lowBit !== 0 && lowBit <= size ? lowBit : biggestPowerOfTwo4(size);
214
- output.push({
215
- start,
216
- end: start + blockSize - 1
217
- });
239
+ output.push(formatIPv4Fast(start) + prefixStrings[Math.clz32(blockSize - 1)]);
218
240
  start += blockSize;
219
241
  }
220
242
  }
@@ -224,29 +246,20 @@ function subparts6(pStart, pEnd, output) {
224
246
  const size = pEnd - start + 1n;
225
247
  const lowBit = start & -start;
226
248
  if ((size & size - 1n) === 0n && (lowBit === 0n || lowBit >= size)) {
227
- output.push({
228
- start,
229
- end: pEnd
230
- });
249
+ output.push(stringifyIp({
250
+ number: start,
251
+ version: 6
252
+ }) + prefixStrings[129 - bigintBitLength(size)]);
231
253
  return;
232
254
  }
233
255
  const blockSize = lowBit !== 0n && lowBit <= size ? lowBit : biggestPowerOfTwo(size);
234
- output.push({
235
- start,
236
- end: start + blockSize - 1n
237
- });
256
+ output.push(stringifyIp({
257
+ number: start,
258
+ version: 6
259
+ }) + prefixStrings[129 - bigintBitLength(blockSize)]);
238
260
  start += blockSize;
239
261
  }
240
262
  }
241
- function formatPart4(part) {
242
- return formatIPv4Fast(part.start) + prefixStrings[Math.clz32(part.end - part.start)];
243
- }
244
- function formatPart6(part) {
245
- return stringifyIp({
246
- number: part.start,
247
- version: 6
248
- }) + prefixStrings[128 - bigintBitLength(part.end - part.start)];
249
- }
250
263
  function mergeIntervalsRaw4(nets) {
251
264
  if (nets.length === 0) return [];
252
265
  nets.sort(cmpV4StartEnd);
@@ -297,16 +310,6 @@ function mergeIntervalsRaw6(nets) {
297
310
  });
298
311
  return merged;
299
312
  }
300
- function mergeIntervals4(nets) {
301
- const merged = [];
302
- for (const part of mergeIntervalsRaw4(nets)) subparts4(part.start, part.end, merged);
303
- return merged;
304
- }
305
- function mergeIntervals6(nets) {
306
- const merged = [];
307
- for (const part of mergeIntervalsRaw6(nets)) subparts6(part.start, part.end, merged);
308
- return merged;
309
- }
310
313
  function subtractSorted4(bases, excls) {
311
314
  if (excls.length === 0) return bases;
312
315
  if (bases.length === 0) return [];
@@ -368,8 +371,8 @@ function mergeCidr(nets) {
368
371
  else v6.push(n);
369
372
  }
370
373
  const merged = [];
371
- for (const part of mergeIntervals4(v4)) merged.push(formatPart4(part));
372
- 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);
373
376
  return merged;
374
377
  }
375
378
  /** Returns an array of merged remaining networks of the subtraction of `excludeNetworks` from `baseNetworks`. */
@@ -390,16 +393,14 @@ function excludeCidr(base, excl) {
390
393
  }
391
394
  const result = [];
392
395
  {
393
- const remaining = subtractSorted4(mergeIntervalsRaw4(v4base), mergeIntervalsRaw4(v4excl));
394
- const aligned = [];
395
- for (const part of remaining) subparts4(part.start, part.end, aligned);
396
- 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);
397
399
  }
398
400
  {
399
- const remaining = subtractSorted6(mergeIntervalsRaw6(v6base), mergeIntervalsRaw6(v6excl));
400
- const aligned = [];
401
- for (const part of remaining) subparts6(part.start, part.end, aligned);
402
- 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);
403
404
  }
404
405
  return result;
405
406
  }
@@ -430,9 +431,25 @@ function* expandCidr(nets) {
430
431
  number: 0n,
431
432
  version: 6
432
433
  };
433
- for (const part of mergeIntervalsRaw6(v6)) for (let num = part.start; num <= part.end; num++) {
434
- ipObj.number = num;
435
- 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
+ }
436
453
  }
437
454
  }
438
455
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cidr-tools",
3
- "version": "12.0.2",
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.5"
32
+ "ip-bigint": "^9.0.6"
30
33
  },
31
34
  "devDependencies": {
32
- "@types/node": "25.9.1",
33
- "@typescript/native-preview": "7.0.0-dev.20260527.2",
34
- "@vitest/coverage-v8": "4.1.7",
35
- "eslint": "10.4.0",
36
- "eslint-config-silverwind": "133.0.5",
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.1",
39
- "tsdown-config-silverwind": "3.0.2",
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.17.3",
43
- "updates-config-silverwind": "3.0.2",
44
- "versions": "15.1.0",
45
- "vitest": "4.1.7",
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
  }