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.
- package/dist/index.js +135 -77
- 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
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
61
|
+
prefixPresent = true;
|
|
42
62
|
} else {
|
|
43
63
|
ipPart = str;
|
|
44
|
-
|
|
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 (!
|
|
50
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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(
|
|
94
|
-
if (
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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 (
|
|
101
|
-
if (
|
|
102
|
-
start:
|
|
103
|
-
end:
|
|
147
|
+
if (pEnd - pStart === 1n) {
|
|
148
|
+
if (pEnd % 2n === 0n) output.push({
|
|
149
|
+
start: pStart,
|
|
150
|
+
end: pStart
|
|
104
151
|
}, {
|
|
105
|
-
start:
|
|
106
|
-
end:
|
|
152
|
+
start: pEnd,
|
|
153
|
+
end: pEnd
|
|
107
154
|
});
|
|
108
155
|
else output.push({
|
|
109
|
-
start:
|
|
110
|
-
end:
|
|
156
|
+
start: pStart,
|
|
157
|
+
end: pEnd
|
|
111
158
|
});
|
|
112
|
-
return
|
|
159
|
+
return;
|
|
113
160
|
}
|
|
114
|
-
const size = diff(
|
|
161
|
+
const size = diff(pEnd, pStart);
|
|
115
162
|
let biggest = biggestPowerOfTwo(size);
|
|
116
163
|
let start;
|
|
117
164
|
let end;
|
|
118
|
-
if (size === biggest &&
|
|
119
|
-
output.push(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 =
|
|
126
|
-
if (start + biggest - 1n >
|
|
127
|
-
start = (
|
|
128
|
-
while (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 = (
|
|
180
|
+
start = (pEnd / biggest - 1n) * biggest;
|
|
131
181
|
}
|
|
132
182
|
end = start + biggest - 1n;
|
|
133
183
|
} else {
|
|
134
|
-
start =
|
|
184
|
+
start = pEnd / biggest * biggest;
|
|
135
185
|
end = start + biggest - 1n;
|
|
136
186
|
}
|
|
137
187
|
}
|
|
138
|
-
if (start !==
|
|
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 !==
|
|
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
|
-
|
|
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 =
|
|
169
|
-
let curEnd =
|
|
170
|
-
for (let i = 1; i <
|
|
171
|
-
const { start, end } =
|
|
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 =
|
|
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 =
|
|
234
|
-
const exclArr =
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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 =
|
|
268
|
-
const bNets =
|
|
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 =
|
|
296
|
-
const bNets =
|
|
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.
|
|
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.
|
|
20
|
+
"ip-bigint": "^8.2.9"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@types/node": "25.3.
|
|
24
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
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.
|
|
26
|
+
"eslint-config-silverwind": "121.2.0",
|
|
27
|
+
"fast-cidr-tools": "0.3.4",
|
|
27
28
|
"jest-extended": "7.0.0",
|
|
28
|
-
"tsdown": "0.
|
|
29
|
-
"tsdown-config-silverwind": "
|
|
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.
|
|
33
|
+
"updates": "17.8.1",
|
|
33
34
|
"updates-config-silverwind": "1.0.3",
|
|
34
|
-
"versions": "14.2.
|
|
35
|
+
"versions": "14.2.1",
|
|
35
36
|
"vitest": "4.0.18",
|
|
36
37
|
"vitest-config-silverwind": "10.6.3"
|
|
37
38
|
}
|