ip-utilities 1.0.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.
- package/LICENSE +21 -0
- package/README.md +238 -0
- package/dist/cidr.d.ts +2 -0
- package/dist/cidr.d.ts.map +1 -0
- package/dist/cidr.js +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +89 -0
- package/dist/internal/cidr.d.ts +112 -0
- package/dist/internal/cidr.d.ts.map +1 -0
- package/dist/internal/cidr.js +297 -0
- package/dist/internal/ipv4-ranges.d.ts +26 -0
- package/dist/internal/ipv4-ranges.d.ts.map +1 -0
- package/dist/internal/ipv4-ranges.js +73 -0
- package/dist/internal/ipv4.d.ts +44 -0
- package/dist/internal/ipv4.d.ts.map +1 -0
- package/dist/internal/ipv4.js +183 -0
- package/dist/internal/ipv6-ranges.d.ts +47 -0
- package/dist/internal/ipv6-ranges.d.ts.map +1 -0
- package/dist/internal/ipv6-ranges.js +159 -0
- package/dist/internal/ipv6.d.ts +67 -0
- package/dist/internal/ipv6.d.ts.map +1 -0
- package/dist/internal/ipv6.js +385 -0
- package/dist/internal/regex.d.ts +37 -0
- package/dist/internal/regex.d.ts.map +1 -0
- package/dist/internal/regex.js +118 -0
- package/dist/internal/sort.d.ts +22 -0
- package/dist/internal/sort.d.ts.map +1 -0
- package/dist/internal/sort.js +207 -0
- package/dist/ipv4.d.ts +2 -0
- package/dist/ipv4.d.ts.map +1 -0
- package/dist/ipv4.js +1 -0
- package/dist/ipv6.d.ts +2 -0
- package/dist/ipv6.d.ts.map +1 -0
- package/dist/ipv6.js +1 -0
- package/dist/ranges.d.ts +3 -0
- package/dist/ranges.d.ts.map +1 -0
- package/dist/ranges.js +2 -0
- package/dist/regex.d.ts +3 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +1 -0
- package/dist/sort.d.ts +2 -0
- package/dist/sort.d.ts.map +1 -0
- package/dist/sort.js +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { parseIPv4, parseIPv4Raw, maskV4LUT } from "./ipv4.js";
|
|
2
|
+
import { parseIPv6, parseIPv6Raw, ipv6RawBuf } from "./ipv6.js";
|
|
3
|
+
import { matchesCidr, prefixMaskV6 } from "./ipv6-ranges.js";
|
|
4
|
+
// --- Constants & lookup tables ---
|
|
5
|
+
// Prebuilt IPv6 prefix masks for all prefix lengths 0-128, four uint32 words each
|
|
6
|
+
const V6_MASK_LUT = new Uint32Array(129 * 4);
|
|
7
|
+
for (let p = 0; p <= 128; p++) {
|
|
8
|
+
const base = p * 4;
|
|
9
|
+
for (let w = 0; w < 4; w++)
|
|
10
|
+
V6_MASK_LUT[base + w] = prefixMaskV6(p, w);
|
|
11
|
+
}
|
|
12
|
+
// --- Mutable state ---
|
|
13
|
+
// Last parsed IPv4 raw value from parseIPOrCIDR
|
|
14
|
+
export let parseIPOrCIDRv4 = 0;
|
|
15
|
+
// --- Exports: prefix parsing ---
|
|
16
|
+
/**
|
|
17
|
+
* Parses a decimal prefix length from a substring.
|
|
18
|
+
* @param s - Source string
|
|
19
|
+
* @param start - Start index of the prefix digits
|
|
20
|
+
* @param end - End index (exclusive)
|
|
21
|
+
* @param max - Maximum allowed prefix value
|
|
22
|
+
* @returns The prefix length, or -1 if invalid
|
|
23
|
+
*/
|
|
24
|
+
export function parsePrefixInline(s, start, end, max) {
|
|
25
|
+
const len = end - start;
|
|
26
|
+
if (len === 0 || len > 3)
|
|
27
|
+
return -1;
|
|
28
|
+
if (len > 1 && s.charCodeAt(start) === 48)
|
|
29
|
+
return -1;
|
|
30
|
+
let v = 0;
|
|
31
|
+
for (let i = start; i < end; i++) {
|
|
32
|
+
const ch = s.charCodeAt(i) - 48;
|
|
33
|
+
if (ch < 0 || ch > 9)
|
|
34
|
+
return -1;
|
|
35
|
+
v = v * 10 + ch;
|
|
36
|
+
}
|
|
37
|
+
return v > max ? -1 : v;
|
|
38
|
+
}
|
|
39
|
+
// --- Exports: CIDR parsing ---
|
|
40
|
+
/**
|
|
41
|
+
* Parses a CIDR string in IPv4 notation (e.g. "192.168.1.0/24").
|
|
42
|
+
* @param s - The CIDR string
|
|
43
|
+
* @returns The parsed CIDRv4, or null if invalid
|
|
44
|
+
*/
|
|
45
|
+
export function parseCIDRv4(s) {
|
|
46
|
+
const slashIdx = s.indexOf('/');
|
|
47
|
+
if (slashIdx === -1)
|
|
48
|
+
return null;
|
|
49
|
+
const addr = parseIPv4(s, slashIdx);
|
|
50
|
+
if (addr === null)
|
|
51
|
+
return null;
|
|
52
|
+
const prefix = parsePrefixInline(s, slashIdx + 1, s.length, 32);
|
|
53
|
+
if (prefix === -1)
|
|
54
|
+
return null;
|
|
55
|
+
return { addr, prefix, kind: 4 };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parses a CIDR string in IPv6 notation (e.g. "::1/128").
|
|
59
|
+
* @param s - The CIDR string
|
|
60
|
+
* @returns The parsed CIDRv6, or null if invalid
|
|
61
|
+
*/
|
|
62
|
+
export function parseCIDRv6(s) {
|
|
63
|
+
const slashIdx = s.indexOf('/');
|
|
64
|
+
if (slashIdx === -1)
|
|
65
|
+
return null;
|
|
66
|
+
const addr = parseIPv6(s, slashIdx);
|
|
67
|
+
if (addr === null)
|
|
68
|
+
return null;
|
|
69
|
+
const prefix = parsePrefixInline(s, slashIdx + 1, s.length, 128);
|
|
70
|
+
if (prefix === -1)
|
|
71
|
+
return null;
|
|
72
|
+
return { addr, prefix, kind: 6 };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parses a CIDR string, auto-detecting IPv4 or IPv6.
|
|
76
|
+
* @param s - The CIDR string
|
|
77
|
+
* @returns The parsed CIDR, or null if invalid
|
|
78
|
+
*/
|
|
79
|
+
export function parseCIDR(s) {
|
|
80
|
+
const c0 = s.charCodeAt(0);
|
|
81
|
+
if (c0 >= 48 && c0 <= 57) {
|
|
82
|
+
let hasColon = false;
|
|
83
|
+
for (let i = 1, len = s.length; i < len; i++) {
|
|
84
|
+
const ch = s.charCodeAt(i);
|
|
85
|
+
if (ch === 58) {
|
|
86
|
+
hasColon = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (ch === 47)
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (!hasColon)
|
|
93
|
+
return parseCIDRv4(s);
|
|
94
|
+
}
|
|
95
|
+
return parseCIDRv6(s);
|
|
96
|
+
}
|
|
97
|
+
// --- Exports: validation ---
|
|
98
|
+
/**
|
|
99
|
+
* Checks whether a string is valid CIDR notation (IPv4 or IPv6).
|
|
100
|
+
* @param s - The string to validate
|
|
101
|
+
* @returns True if the string is valid CIDR
|
|
102
|
+
*/
|
|
103
|
+
export function isCIDR(s) {
|
|
104
|
+
const slashIdx = s.indexOf('/');
|
|
105
|
+
if (slashIdx === -1)
|
|
106
|
+
return false;
|
|
107
|
+
const c0 = s.charCodeAt(0);
|
|
108
|
+
if (c0 >= 48 && c0 <= 57) {
|
|
109
|
+
let hasColon = false;
|
|
110
|
+
for (let i = 1; i < slashIdx; i++) {
|
|
111
|
+
if (s.charCodeAt(i) === 58) {
|
|
112
|
+
hasColon = true;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!hasColon) {
|
|
117
|
+
return parseIPv4Raw(s, 0, slashIdx) !== -1 && parsePrefixInline(s, slashIdx + 1, s.length, 32) !== -1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return parseIPv6Raw(s, slashIdx) && parsePrefixInline(s, slashIdx + 1, s.length, 128) !== -1;
|
|
121
|
+
}
|
|
122
|
+
// --- Exports: containment ---
|
|
123
|
+
/**
|
|
124
|
+
* Checks whether an IPv4 address is within a CIDRv4 range.
|
|
125
|
+
* @param ip - The IPv4 address
|
|
126
|
+
* @param cidr - The CIDRv4 range
|
|
127
|
+
* @returns True if the address is within the range
|
|
128
|
+
*/
|
|
129
|
+
export function ipInCIDRv4(ip, cidr) {
|
|
130
|
+
const m = maskV4LUT[cidr.prefix];
|
|
131
|
+
return ((ip.ip & m) >>> 0) === ((cidr.addr.ip & m) >>> 0);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Checks whether an IPv6 address is within a CIDRv6 range.
|
|
135
|
+
* @param ip - The IPv6 address
|
|
136
|
+
* @param cidr - The CIDRv6 range
|
|
137
|
+
* @returns True if the address is within the range
|
|
138
|
+
*/
|
|
139
|
+
export function ipInCIDRv6(ip, cidr) {
|
|
140
|
+
return matchesCidr(ip.w0, ip.w1, ip.w2, ip.w3, cidr.addr.w0, cidr.addr.w1, cidr.addr.w2, cidr.addr.w3, cidr.prefix);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Checks whether an IP address is within a CIDR range. Both must be the same version.
|
|
144
|
+
* @param ip - The IP address (IPv4 or IPv6)
|
|
145
|
+
* @param cidr - The CIDR range (IPv4 or IPv6)
|
|
146
|
+
* @returns True if the address is within the range, false if mismatched versions
|
|
147
|
+
*/
|
|
148
|
+
export function ipInCIDR(ip, cidr) {
|
|
149
|
+
if (ip.kind === 4 && cidr.kind === 4)
|
|
150
|
+
return ipInCIDRv4(ip, cidr);
|
|
151
|
+
if (ip.kind === 6 && cidr.kind === 6)
|
|
152
|
+
return ipInCIDRv6(ip, cidr);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Parses an IP string and CIDR string, then checks containment.
|
|
157
|
+
* @param ipStr - The IP address string
|
|
158
|
+
* @param cidrStr - The CIDR range string
|
|
159
|
+
* @returns True if the IP is within the CIDR range
|
|
160
|
+
*/
|
|
161
|
+
export function isIPInCIDR(ipStr, cidrStr) {
|
|
162
|
+
const cidr = parseCIDR(cidrStr);
|
|
163
|
+
if (cidr === null)
|
|
164
|
+
return false;
|
|
165
|
+
if (cidr.kind === 4) {
|
|
166
|
+
const ip = parseIPv4(ipStr);
|
|
167
|
+
if (ip === null)
|
|
168
|
+
return false;
|
|
169
|
+
return ipInCIDRv4(ip, cidr);
|
|
170
|
+
}
|
|
171
|
+
const ip = parseIPv6(ipStr);
|
|
172
|
+
if (ip === null)
|
|
173
|
+
return false;
|
|
174
|
+
return ipInCIDRv6(ip, cidr);
|
|
175
|
+
}
|
|
176
|
+
// --- Exports: network & broadcast ---
|
|
177
|
+
/**
|
|
178
|
+
* Computes the network address (first address) of an IPv4 CIDR range.
|
|
179
|
+
* @param cidr - The CIDRv4 range
|
|
180
|
+
* @returns The network address
|
|
181
|
+
*/
|
|
182
|
+
export function networkAddressV4(cidr) {
|
|
183
|
+
const m = maskV4LUT[cidr.prefix];
|
|
184
|
+
return { ip: (cidr.addr.ip & m) >>> 0, kind: 4 };
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Computes the broadcast address (last address) of an IPv4 CIDR range.
|
|
188
|
+
* @param cidr - The CIDRv4 range
|
|
189
|
+
* @returns The broadcast address
|
|
190
|
+
*/
|
|
191
|
+
export function broadcastAddressV4(cidr) {
|
|
192
|
+
const m = maskV4LUT[cidr.prefix];
|
|
193
|
+
return { ip: (cidr.addr.ip | ~m) >>> 0, kind: 4 };
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Computes the network address (first address) of an IPv6 CIDR range.
|
|
197
|
+
* @param cidr - The CIDRv6 range
|
|
198
|
+
* @returns The network address
|
|
199
|
+
*/
|
|
200
|
+
export function networkAddressV6(cidr) {
|
|
201
|
+
const base = cidr.prefix << 2;
|
|
202
|
+
const a = cidr.addr;
|
|
203
|
+
return {
|
|
204
|
+
w0: (a.w0 & V6_MASK_LUT[base]) >>> 0,
|
|
205
|
+
w1: (a.w1 & V6_MASK_LUT[base + 1]) >>> 0,
|
|
206
|
+
w2: (a.w2 & V6_MASK_LUT[base + 2]) >>> 0,
|
|
207
|
+
w3: (a.w3 & V6_MASK_LUT[base + 3]) >>> 0,
|
|
208
|
+
kind: 6,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Computes the broadcast address (last address) of an IPv6 CIDR range.
|
|
213
|
+
* @param cidr - The CIDRv6 range
|
|
214
|
+
* @returns The broadcast address
|
|
215
|
+
*/
|
|
216
|
+
export function broadcastAddressV6(cidr) {
|
|
217
|
+
const base = cidr.prefix << 2;
|
|
218
|
+
const a = cidr.addr;
|
|
219
|
+
return {
|
|
220
|
+
w0: (a.w0 | ~V6_MASK_LUT[base]) >>> 0,
|
|
221
|
+
w1: (a.w1 | ~V6_MASK_LUT[base + 1]) >>> 0,
|
|
222
|
+
w2: (a.w2 | ~V6_MASK_LUT[base + 2]) >>> 0,
|
|
223
|
+
w3: (a.w3 | ~V6_MASK_LUT[base + 3]) >>> 0,
|
|
224
|
+
kind: 6,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// --- Exports: subnet utilities ---
|
|
228
|
+
/**
|
|
229
|
+
* Returns the subnet mask for an IPv4 prefix length.
|
|
230
|
+
* @param prefix - Prefix length (0-32)
|
|
231
|
+
* @returns The uint32 subnet mask
|
|
232
|
+
*/
|
|
233
|
+
export function subnetMaskV4(prefix) {
|
|
234
|
+
return maskV4LUT[prefix];
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Converts a subnet mask to its prefix length.
|
|
238
|
+
* @param mask - The uint32 subnet mask (must be contiguous)
|
|
239
|
+
* @returns The prefix length, or null if the mask is not contiguous
|
|
240
|
+
*/
|
|
241
|
+
export function prefixFromSubnetMask(mask) {
|
|
242
|
+
if (mask === 0)
|
|
243
|
+
return 0;
|
|
244
|
+
const inv = (~mask) >>> 0;
|
|
245
|
+
if ((inv & (inv + 1)) !== 0)
|
|
246
|
+
return null;
|
|
247
|
+
return Math.clz32(inv);
|
|
248
|
+
}
|
|
249
|
+
// --- Exports: IP/CIDR routing ---
|
|
250
|
+
/**
|
|
251
|
+
* Parses an IP or CIDR string, routing v4/v6 in a single forward scan.
|
|
252
|
+
* On success, the v4 result is in {@link parseIPOrCIDRv4} and v6 result in ipv6RawBuf.
|
|
253
|
+
* @param s - The IP or CIDR string
|
|
254
|
+
* @returns 4 for valid IPv4, 6 for valid IPv6, 0 for invalid
|
|
255
|
+
*/
|
|
256
|
+
export function parseIPOrCIDR(s) {
|
|
257
|
+
const len = s.length;
|
|
258
|
+
const c0 = s.charCodeAt(0);
|
|
259
|
+
if (c0 >= 48 && c0 <= 57) {
|
|
260
|
+
let hasColon = false;
|
|
261
|
+
let slashIdx = -1;
|
|
262
|
+
for (let i = 1; i < len; i++) {
|
|
263
|
+
const ch = s.charCodeAt(i);
|
|
264
|
+
if (ch === 58) {
|
|
265
|
+
hasColon = true;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
if (ch === 47) {
|
|
269
|
+
slashIdx = i;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (!hasColon) {
|
|
274
|
+
if (slashIdx === -1) {
|
|
275
|
+
parseIPOrCIDRv4 = parseIPv4Raw(s, 0, len);
|
|
276
|
+
return parseIPOrCIDRv4 !== -1 ? 4 : 0;
|
|
277
|
+
}
|
|
278
|
+
parseIPOrCIDRv4 = parseIPv4Raw(s, 0, slashIdx);
|
|
279
|
+
if (parseIPOrCIDRv4 === -1 || parsePrefixInline(s, slashIdx + 1, len, 32) === -1)
|
|
280
|
+
return 0;
|
|
281
|
+
return 4;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const slashIdx = s.indexOf('/');
|
|
285
|
+
if (slashIdx === -1)
|
|
286
|
+
return parseIPv6Raw(s, len) ? 6 : 0;
|
|
287
|
+
if (!parseIPv6Raw(s, slashIdx) || parsePrefixInline(s, slashIdx + 1, len, 128) === -1)
|
|
288
|
+
return 0;
|
|
289
|
+
return 6;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Constructs an IPv6Addr from the shared ipv6RawBuf after a successful parseIPv6Raw call.
|
|
293
|
+
* @returns The IPv6Addr built from the raw buffer
|
|
294
|
+
*/
|
|
295
|
+
export function v6AddrFromBuf() {
|
|
296
|
+
return { w0: ipv6RawBuf[0], w1: ipv6RawBuf[1], w2: ipv6RawBuf[2], w3: ipv6RawBuf[3], kind: 6 };
|
|
297
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IPv4RangeName } from '../types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Classifies a packed uint32 IPv4 address into its IANA-assigned range.
|
|
4
|
+
* @param ip - Packed uint32 IP address
|
|
5
|
+
* @returns The range name (e.g. "private", "loopback", "unicast")
|
|
6
|
+
*/
|
|
7
|
+
export declare function ipv4Range(ip: number): IPv4RangeName;
|
|
8
|
+
/**
|
|
9
|
+
* Checks whether a packed uint32 IPv4 address is in a private range (RFC 1918).
|
|
10
|
+
* @param ip - Packed uint32 IP address
|
|
11
|
+
* @returns True if the address is in 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16
|
|
12
|
+
*/
|
|
13
|
+
export declare function isPrivateIPv4(ip: number): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Checks whether a packed uint32 IPv4 address is a loopback address (127.0.0.0/8).
|
|
16
|
+
* @param ip - Packed uint32 IP address
|
|
17
|
+
* @returns True if the address is in the loopback range
|
|
18
|
+
*/
|
|
19
|
+
export declare function isLoopbackIPv4(ip: number): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Checks whether a packed uint32 IPv4 address is link-local (169.254.0.0/16).
|
|
22
|
+
* @param ip - Packed uint32 IP address
|
|
23
|
+
* @returns True if the address is in the link-local range
|
|
24
|
+
*/
|
|
25
|
+
export declare function isLinkLocalIPv4(ip: number): boolean;
|
|
26
|
+
//# sourceMappingURL=ipv4-ranges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipv4-ranges.d.ts","sourceRoot":"","sources":["../../src/internal/ipv4-ranges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAwChD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,CAMnD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAMjD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEnD"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const RANGE_MASK = new Uint32Array([
|
|
2
|
+
0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFF00, 0xFFFFFF00,
|
|
3
|
+
0xFFFFFF00, 0xFFFFFF00, 0xFFFFFF00, 0xFFFFFF00,
|
|
4
|
+
0xFFFFFF00, 0xFFFF0000, 0xFFFF0000, 0xFFFE0000,
|
|
5
|
+
0xFFF00000, 0xFFC00000, 0xFF000000, 0xFF000000,
|
|
6
|
+
0xFF000000, 0xF0000000, 0xF0000000,
|
|
7
|
+
]);
|
|
8
|
+
const RANGE_MASKED = new Uint32Array([
|
|
9
|
+
0xFFFFFFFF, 0xC0AF3000, 0xC01FC400, 0xC034C100,
|
|
10
|
+
0xC0000000, 0xC0000200, 0xC0586300, 0xC6336400,
|
|
11
|
+
0xCB007100, 0xC0A80000, 0xA9FE0000, 0xC6120000,
|
|
12
|
+
0xAC100000, 0x64400000, 0x0A000000, 0x7F000000,
|
|
13
|
+
0x00000000, 0xE0000000, 0xF0000000,
|
|
14
|
+
]);
|
|
15
|
+
const RANGE_NAMES = [
|
|
16
|
+
'broadcast', 'as112', 'as112', 'amt',
|
|
17
|
+
'reserved', 'reserved', 'reserved', 'reserved',
|
|
18
|
+
'reserved', 'private', 'linkLocal', 'reserved',
|
|
19
|
+
'private', 'carrierGradeNat', 'private', 'loopback',
|
|
20
|
+
'unspecified', 'multicast', 'reserved',
|
|
21
|
+
];
|
|
22
|
+
const RANGE_COUNT = RANGE_MASK.length;
|
|
23
|
+
// First-octet filter: 0 = may belong to a special range, 0xFF = unicast
|
|
24
|
+
const FIRST_OCTET = new Uint8Array(256);
|
|
25
|
+
FIRST_OCTET.fill(0xFF);
|
|
26
|
+
for (let i = 0; i < RANGE_COUNT; i++) {
|
|
27
|
+
const lo = RANGE_MASKED[i] >>> 24;
|
|
28
|
+
const maskBits = Math.clz32(~RANGE_MASK[i] >>> 0);
|
|
29
|
+
const span = maskBits <= 8 ? 1 << (8 - maskBits) : 1;
|
|
30
|
+
for (let o = lo; o < lo + span && o < 256; o++) {
|
|
31
|
+
FIRST_OCTET[o] = 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Classifies a packed uint32 IPv4 address into its IANA-assigned range.
|
|
36
|
+
* @param ip - Packed uint32 IP address
|
|
37
|
+
* @returns The range name (e.g. "private", "loopback", "unicast")
|
|
38
|
+
*/
|
|
39
|
+
export function ipv4Range(ip) {
|
|
40
|
+
if (FIRST_OCTET[ip >>> 24] === 0xFF)
|
|
41
|
+
return 'unicast';
|
|
42
|
+
for (let i = 0; i < RANGE_COUNT; i++) {
|
|
43
|
+
if (((ip & RANGE_MASK[i]) >>> 0) === RANGE_MASKED[i])
|
|
44
|
+
return RANGE_NAMES[i];
|
|
45
|
+
}
|
|
46
|
+
return 'unicast';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Checks whether a packed uint32 IPv4 address is in a private range (RFC 1918).
|
|
50
|
+
* @param ip - Packed uint32 IP address
|
|
51
|
+
* @returns True if the address is in 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16
|
|
52
|
+
*/
|
|
53
|
+
export function isPrivateIPv4(ip) {
|
|
54
|
+
return ((ip >>> 24) === 10 ||
|
|
55
|
+
(ip >>> 20) === 0xAC1 ||
|
|
56
|
+
(ip >>> 16) === 0xC0A8);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Checks whether a packed uint32 IPv4 address is a loopback address (127.0.0.0/8).
|
|
60
|
+
* @param ip - Packed uint32 IP address
|
|
61
|
+
* @returns True if the address is in the loopback range
|
|
62
|
+
*/
|
|
63
|
+
export function isLoopbackIPv4(ip) {
|
|
64
|
+
return (ip >>> 24) === 127;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Checks whether a packed uint32 IPv4 address is link-local (169.254.0.0/16).
|
|
68
|
+
* @param ip - Packed uint32 IP address
|
|
69
|
+
* @returns True if the address is in the link-local range
|
|
70
|
+
*/
|
|
71
|
+
export function isLinkLocalIPv4(ip) {
|
|
72
|
+
return (ip >>> 16) === 0xA9FE;
|
|
73
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { IPv4Addr } from '../types.ts';
|
|
2
|
+
declare const maskV4LUT: Uint32Array<ArrayBuffer>;
|
|
3
|
+
export { maskV4LUT };
|
|
4
|
+
/**
|
|
5
|
+
* Parses a strict dotted-decimal IPv4 string into a raw uint32.
|
|
6
|
+
* @param s - Source string
|
|
7
|
+
* @param start - Start index within the string
|
|
8
|
+
* @param end - End index (exclusive) within the string
|
|
9
|
+
* @returns The packed uint32 IP address, or -1 if invalid
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseIPv4Raw(s: string, start: number, end: number): number;
|
|
12
|
+
/**
|
|
13
|
+
* Parses a strict dotted-decimal IPv4 string into an IPv4Addr.
|
|
14
|
+
* @param s - The IPv4 string (e.g. "192.168.1.1")
|
|
15
|
+
* @param end - Optional end index for parsing a substring
|
|
16
|
+
* @returns The parsed address, or null if invalid
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseIPv4(s: string, end?: number): IPv4Addr | null;
|
|
19
|
+
/**
|
|
20
|
+
* Checks whether a string is a valid strict dotted-decimal IPv4 address.
|
|
21
|
+
* @param s - The string to validate
|
|
22
|
+
* @returns True if the string is a valid IPv4 address
|
|
23
|
+
*/
|
|
24
|
+
export declare function isIPv4(s: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Parses an IPv4 string with relaxed rules: hex (0x), octal (0), and
|
|
27
|
+
* classful shorthand (e.g. "10.1" = "10.0.0.1") are accepted.
|
|
28
|
+
* @param s - The IPv4 string in any accepted notation
|
|
29
|
+
* @returns The parsed address, or null if invalid
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseIPv4Loose(s: string): IPv4Addr | null;
|
|
32
|
+
/**
|
|
33
|
+
* Extracts the four octets of a packed uint32 IPv4 address.
|
|
34
|
+
* @param ip - Packed uint32 IP address
|
|
35
|
+
* @returns Tuple of four octets [a, b, c, d]
|
|
36
|
+
*/
|
|
37
|
+
export declare function ipv4ToOctets(ip: number): [number, number, number, number];
|
|
38
|
+
/**
|
|
39
|
+
* Converts a packed uint32 IPv4 address to dotted-decimal string.
|
|
40
|
+
* @param ip - Packed uint32 IP address
|
|
41
|
+
* @returns Dotted-decimal string (e.g. "192.168.1.1")
|
|
42
|
+
*/
|
|
43
|
+
export declare function ipv4ToString(ip: number): string;
|
|
44
|
+
//# sourceMappingURL=ipv4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipv4.d.ts","sourceRoot":"","sources":["../../src/internal/ipv4.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAa3C,QAAA,MAAM,SAAS,0BAAsB,CAAA;AAErC,OAAO,EAAE,SAAS,EAAE,CAAA;AA4CpB;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAgC1E;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAGlE;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEzC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAoCzD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAEzE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE/C"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// --- Constants & lookup tables ---
|
|
2
|
+
// Prebuilt "a.b" strings for all 16-bit values, indexed by (hi_octet << 8 | lo_octet)
|
|
3
|
+
const PAIR_CACHE = new Array(65536);
|
|
4
|
+
for (let i = 0; i < 65536; i++)
|
|
5
|
+
PAIR_CACHE[i] = String((i >>> 8) & 0xFF) + '.' + String(i & 0xFF);
|
|
6
|
+
// Same as PAIR_CACHE but with trailing dot for concatenation in ipv4ToString
|
|
7
|
+
const PAIR_DOT_CACHE = new Array(65536);
|
|
8
|
+
for (let i = 0; i < 65536; i++)
|
|
9
|
+
PAIR_DOT_CACHE[i] = PAIR_CACHE[i] + '.';
|
|
10
|
+
// Subnet masks for prefix lengths 0..32
|
|
11
|
+
const maskV4LUT = new Uint32Array(33);
|
|
12
|
+
for (let i = 1; i <= 32; i++)
|
|
13
|
+
maskV4LUT[i] = (0xFFFFFFFF << (32 - i)) >>> 0;
|
|
14
|
+
export { maskV4LUT };
|
|
15
|
+
// Character classification table: maps charCode to digit value (0-9), dot (0xFE), or invalid (0xFF)
|
|
16
|
+
const V4_CHAR = new Uint8Array(128);
|
|
17
|
+
V4_CHAR.fill(0xFF);
|
|
18
|
+
for (let i = 0; i < 10; i++)
|
|
19
|
+
V4_CHAR[48 + i] = i;
|
|
20
|
+
V4_CHAR[46] = 0xFE;
|
|
21
|
+
// --- Non-exported helpers ---
|
|
22
|
+
function parseAutoInt(s, start, end) {
|
|
23
|
+
if (start >= end)
|
|
24
|
+
return -1;
|
|
25
|
+
const c0 = s.charCodeAt(start);
|
|
26
|
+
if (c0 === 48 && end - start > 1) {
|
|
27
|
+
const c1 = s.charCodeAt(start + 1) | 0x20;
|
|
28
|
+
if (c1 === 0x78) {
|
|
29
|
+
let v = 0;
|
|
30
|
+
for (let i = start + 2; i < end; i++) {
|
|
31
|
+
const ch = s.charCodeAt(i) | 0x20;
|
|
32
|
+
if (ch >= 48 && ch <= 57)
|
|
33
|
+
v = v * 16 + (ch - 48);
|
|
34
|
+
else if (ch >= 97 && ch <= 102)
|
|
35
|
+
v = v * 16 + (ch - 87);
|
|
36
|
+
else
|
|
37
|
+
return -1;
|
|
38
|
+
}
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
let v = 0;
|
|
42
|
+
for (let i = start; i < end; i++) {
|
|
43
|
+
const ch = s.charCodeAt(i);
|
|
44
|
+
if (ch < 48 || ch > 55)
|
|
45
|
+
return -1;
|
|
46
|
+
v = (v << 3) + (ch - 48);
|
|
47
|
+
}
|
|
48
|
+
return v;
|
|
49
|
+
}
|
|
50
|
+
let v = 0;
|
|
51
|
+
for (let i = start; i < end; i++) {
|
|
52
|
+
const ch = s.charCodeAt(i);
|
|
53
|
+
if (ch < 48 || ch > 57)
|
|
54
|
+
return -1;
|
|
55
|
+
v = v * 10 + (ch - 48);
|
|
56
|
+
}
|
|
57
|
+
return v;
|
|
58
|
+
}
|
|
59
|
+
// --- Exports ---
|
|
60
|
+
/**
|
|
61
|
+
* Parses a strict dotted-decimal IPv4 string into a raw uint32.
|
|
62
|
+
* @param s - Source string
|
|
63
|
+
* @param start - Start index within the string
|
|
64
|
+
* @param end - End index (exclusive) within the string
|
|
65
|
+
* @returns The packed uint32 IP address, or -1 if invalid
|
|
66
|
+
*/
|
|
67
|
+
export function parseIPv4Raw(s, start, end) {
|
|
68
|
+
const len = end - start;
|
|
69
|
+
if (len < 7 || len > 15)
|
|
70
|
+
return -1;
|
|
71
|
+
let ip = 0;
|
|
72
|
+
let octet = 0;
|
|
73
|
+
let oStart = start;
|
|
74
|
+
let dot = 0;
|
|
75
|
+
for (let i = start; i < end; i++) {
|
|
76
|
+
const ch = s.charCodeAt(i);
|
|
77
|
+
if (ch >= 128)
|
|
78
|
+
return -1;
|
|
79
|
+
const v = V4_CHAR[ch];
|
|
80
|
+
if (v <= 9) {
|
|
81
|
+
octet = (octet * 10 + v) | 0;
|
|
82
|
+
}
|
|
83
|
+
else if (v === 0xFE) {
|
|
84
|
+
const dLen = i - oStart;
|
|
85
|
+
if (dLen === 0 || dLen > 3 || octet > 255)
|
|
86
|
+
return -1;
|
|
87
|
+
if (dLen > 1 && s.charCodeAt(oStart) === 48)
|
|
88
|
+
return -1;
|
|
89
|
+
ip = ip | (octet << (24 - (dot << 3)));
|
|
90
|
+
octet = 0;
|
|
91
|
+
oStart = i + 1;
|
|
92
|
+
if (++dot > 3)
|
|
93
|
+
return -1;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
return -1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const dLen = end - oStart;
|
|
100
|
+
if (dot !== 3 || dLen === 0 || dLen > 3 || octet > 255)
|
|
101
|
+
return -1;
|
|
102
|
+
if (dLen > 1 && s.charCodeAt(oStart) === 48)
|
|
103
|
+
return -1;
|
|
104
|
+
return (ip | octet) >>> 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parses a strict dotted-decimal IPv4 string into an IPv4Addr.
|
|
108
|
+
* @param s - The IPv4 string (e.g. "192.168.1.1")
|
|
109
|
+
* @param end - Optional end index for parsing a substring
|
|
110
|
+
* @returns The parsed address, or null if invalid
|
|
111
|
+
*/
|
|
112
|
+
export function parseIPv4(s, end) {
|
|
113
|
+
const ip = parseIPv4Raw(s, 0, end !== undefined ? end : s.length);
|
|
114
|
+
return ip !== -1 ? { ip, kind: 4 } : null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Checks whether a string is a valid strict dotted-decimal IPv4 address.
|
|
118
|
+
* @param s - The string to validate
|
|
119
|
+
* @returns True if the string is a valid IPv4 address
|
|
120
|
+
*/
|
|
121
|
+
export function isIPv4(s) {
|
|
122
|
+
return parseIPv4Raw(s, 0, s.length) !== -1;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Parses an IPv4 string with relaxed rules: hex (0x), octal (0), and
|
|
126
|
+
* classful shorthand (e.g. "10.1" = "10.0.0.1") are accepted.
|
|
127
|
+
* @param s - The IPv4 string in any accepted notation
|
|
128
|
+
* @returns The parsed address, or null if invalid
|
|
129
|
+
*/
|
|
130
|
+
export function parseIPv4Loose(s) {
|
|
131
|
+
const len = s.length;
|
|
132
|
+
if (len === 0)
|
|
133
|
+
return null;
|
|
134
|
+
const dot1 = s.indexOf('.');
|
|
135
|
+
if (dot1 === -1) {
|
|
136
|
+
const v = parseAutoInt(s, 0, len);
|
|
137
|
+
if (v < 0 || v > 0xFFFFFFFF)
|
|
138
|
+
return null;
|
|
139
|
+
return { ip: v >>> 0, kind: 4 };
|
|
140
|
+
}
|
|
141
|
+
const dot2 = s.indexOf('.', dot1 + 1);
|
|
142
|
+
if (dot2 === -1) {
|
|
143
|
+
const a = parseAutoInt(s, 0, dot1);
|
|
144
|
+
const b = parseAutoInt(s, dot1 + 1, len);
|
|
145
|
+
if (a < 0 || a > 255 || b < 0 || b > 0xFFFFFF)
|
|
146
|
+
return null;
|
|
147
|
+
return { ip: ((a << 24) | (b & 0xFFFFFF)) >>> 0, kind: 4 };
|
|
148
|
+
}
|
|
149
|
+
const dot3 = s.indexOf('.', dot2 + 1);
|
|
150
|
+
if (dot3 === -1) {
|
|
151
|
+
const a = parseAutoInt(s, 0, dot1);
|
|
152
|
+
const b = parseAutoInt(s, dot1 + 1, dot2);
|
|
153
|
+
const c = parseAutoInt(s, dot2 + 1, len);
|
|
154
|
+
if (a < 0 || a > 255 || b < 0 || b > 255 || c < 0 || c > 0xFFFF)
|
|
155
|
+
return null;
|
|
156
|
+
return { ip: ((a << 24) | (b << 16) | (c & 0xFFFF)) >>> 0, kind: 4 };
|
|
157
|
+
}
|
|
158
|
+
if (s.indexOf('.', dot3 + 1) !== -1)
|
|
159
|
+
return null;
|
|
160
|
+
const a = parseAutoInt(s, 0, dot1);
|
|
161
|
+
const b = parseAutoInt(s, dot1 + 1, dot2);
|
|
162
|
+
const c = parseAutoInt(s, dot2 + 1, dot3);
|
|
163
|
+
const d = parseAutoInt(s, dot3 + 1, len);
|
|
164
|
+
if (a < 0 || a > 255 || b < 0 || b > 255 || c < 0 || c > 255 || d < 0 || d > 255)
|
|
165
|
+
return null;
|
|
166
|
+
return { ip: ((a << 24) | (b << 16) | (c << 8) | d) >>> 0, kind: 4 };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Extracts the four octets of a packed uint32 IPv4 address.
|
|
170
|
+
* @param ip - Packed uint32 IP address
|
|
171
|
+
* @returns Tuple of four octets [a, b, c, d]
|
|
172
|
+
*/
|
|
173
|
+
export function ipv4ToOctets(ip) {
|
|
174
|
+
return [ip >>> 24, (ip >>> 16) & 0xFF, (ip >>> 8) & 0xFF, ip & 0xFF];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Converts a packed uint32 IPv4 address to dotted-decimal string.
|
|
178
|
+
* @param ip - Packed uint32 IP address
|
|
179
|
+
* @returns Dotted-decimal string (e.g. "192.168.1.1")
|
|
180
|
+
*/
|
|
181
|
+
export function ipv4ToString(ip) {
|
|
182
|
+
return PAIR_DOT_CACHE[ip >>> 16] + PAIR_CACHE[ip & 0xFFFF];
|
|
183
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { IPv6Addr, IPv6RangeName } from '../types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Tests whether an IPv6 address matches a CIDR prefix using raw uint32 words.
|
|
4
|
+
* @param aw0 - Address word 0 (bits 0-31)
|
|
5
|
+
* @param aw1 - Address word 1 (bits 32-63)
|
|
6
|
+
* @param aw2 - Address word 2 (bits 64-95)
|
|
7
|
+
* @param aw3 - Address word 3 (bits 96-127)
|
|
8
|
+
* @param nw0 - Network word 0
|
|
9
|
+
* @param nw1 - Network word 1
|
|
10
|
+
* @param nw2 - Network word 2
|
|
11
|
+
* @param nw3 - Network word 3
|
|
12
|
+
* @param prefix - Prefix length (0-128)
|
|
13
|
+
* @returns True if the address is within the network prefix
|
|
14
|
+
*/
|
|
15
|
+
export declare function matchesCidr(aw0: number, aw1: number, aw2: number, aw3: number, nw0: number, nw1: number, nw2: number, nw3: number, prefix: number): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Computes the bitmask for a specific 32-bit word of an IPv6 prefix.
|
|
18
|
+
* @param prefix - Prefix length (0-128)
|
|
19
|
+
* @param wordOffset - Which 32-bit word (0-3)
|
|
20
|
+
* @returns The uint32 mask for that word
|
|
21
|
+
*/
|
|
22
|
+
export declare function prefixMaskV6(prefix: number, wordOffset: number): number;
|
|
23
|
+
/**
|
|
24
|
+
* Classifies an IPv6 address into its IANA-assigned range.
|
|
25
|
+
* @param addr - The IPv6 address
|
|
26
|
+
* @returns The range name (e.g. "uniqueLocal", "loopback", "unicast")
|
|
27
|
+
*/
|
|
28
|
+
export declare function ipv6Range(addr: IPv6Addr): IPv6RangeName;
|
|
29
|
+
/**
|
|
30
|
+
* Checks whether an IPv6 address is private (unique-local, multicast, link-local, or loopback).
|
|
31
|
+
* @param addr - The IPv6 address
|
|
32
|
+
* @returns True if the address is non-globally-routable
|
|
33
|
+
*/
|
|
34
|
+
export declare function isPrivateIPv6(addr: IPv6Addr): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Checks whether an IPv6 address is the loopback address (::1).
|
|
37
|
+
* @param addr - The IPv6 address
|
|
38
|
+
* @returns True if the address is ::1
|
|
39
|
+
*/
|
|
40
|
+
export declare function isLoopbackIPv6(addr: IPv6Addr): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Checks whether an IPv6 address is link-local (fe80::/10).
|
|
43
|
+
* @param addr - The IPv6 address
|
|
44
|
+
* @returns True if the address is in the link-local range
|
|
45
|
+
*/
|
|
46
|
+
export declare function isLinkLocalIPv6(addr: IPv6Addr): boolean;
|
|
47
|
+
//# sourceMappingURL=ipv6-ranges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipv6-ranges.d.ts","sourceRoot":"","sources":["../../src/internal/ipv6-ranges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AA2C1D;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAClD,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAClD,MAAM,EAAE,MAAM,GACb,OAAO,CA0BT;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAMvE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,aAAa,CAqBvD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAOrD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEvD"}
|