UNPKG

3.6 kBJavaScriptView Raw
1export const max4 = 2n ** 32n - 1n;
2export const max6 = 2n ** 128n - 1n;
3const isIP = ip => ip.includes(":") ? 6 : ip.includes(".") ? 4 : 0;
4
5export function parseIp(ip) {
6 const version = isIP(ip);
7 if (!version) throw new Error(`Invalid IP address: ${ip}`);
8
9 const result = Object.create(null);
10 let number = 0n;
11 let exp = 0n;
12
13 if (version === 4) {
14 for (const n of ip.split(".").map(BigInt).reverse()) {
15 number += n * (2n ** exp);
16 exp += 8n;
17 }
18
19 result.number = number;
20 result.version = version;
21 return result;
22 } else {
23 if (ip.includes(".")) {
24 result.ipv4mapped = true;
25 ip = ip.split(":").map(part => {
26 if (part.includes(".")) {
27 const digits = part.split(".").map(str => Number(str).toString(16).padStart(2, "0"));
28 return `${digits[0]}${digits[1]}:${digits[2]}${digits[3]}`;
29 } else {
30 return part;
31 }
32 }).join(":");
33 }
34
35 if (ip.includes("%")) {
36 let scopeid;
37 [, ip, scopeid] = /(.+)%(.+)/.exec(ip);
38 result.scopeid = scopeid;
39 }
40
41 const parts = ip.split(":");
42 const index = parts.indexOf("");
43
44 if (index !== -1) {
45 while (parts.length < 8) {
46 parts.splice(index, 0, "");
47 }
48 }
49
50 for (const n of parts.map(part => BigInt(parseInt(part || 0, 16))).reverse()) {
51 number += n * (2n ** exp);
52 exp += 16n;
53 }
54
55 result.number = number;
56 result.version = version;
57 return result;
58 }
59}
60
61export function stringifyIp({number, version, ipv4mapped, scopeid} = {}) {
62 if (typeof number !== "bigint") throw new Error(`Expected a BigInt`);
63 if (![4, 6].includes(version)) throw new Error(`Invalid version: ${version}`);
64 if (number < 0n || number > (version === 4 ? max4 : max6)) throw new Error(`Invalid number: ${number}`);
65
66 let step = version === 4 ? 24n : 112n;
67 const stepReduction = version === 4 ? 8n : 16n;
68 let remain = number;
69 const parts = [];
70
71 while (step > 0n) {
72 const divisor = 2n ** step;
73 parts.push(remain / divisor);
74 remain = number % divisor;
75 step -= stepReduction;
76 }
77 parts.push(remain);
78
79 if (version === 4) {
80 return parts.join(".");
81 } else {
82 let ip = "";
83 if (ipv4mapped) {
84 for (const [index, num] of parts.entries()) {
85 if (index < 6) {
86 ip += `${num.toString(16)}:`;
87 } else {
88 ip += `${String(num >> 8n)}.${String(num & 255n)}${index === 6 ? "." : ""}`;
89 }
90 }
91 ip = compressIPv6(ip.split(":"));
92 } else {
93 ip = compressIPv6(parts.map(n => n.toString(16)));
94 }
95
96 if (scopeid) {
97 ip = `${ip}%${scopeid}`;
98 }
99
100 return ip;
101 }
102}
103
104export function normalizeIp(ip) {
105 return stringifyIp(parseIp(ip));
106}
107
108// take the longest or first sequence of "0" segments and replace it with "::"
109function compressIPv6(parts) {
110 let longestSequence;
111 let currentSequence;
112 for (const [index, part] of parts.entries()) {
113 if (part === "0") {
114 if (!currentSequence) {
115 currentSequence = new Set([index]);
116 } else {
117 currentSequence.add(index);
118 }
119 } else {
120 if (currentSequence) {
121 if (!longestSequence) {
122 longestSequence = currentSequence;
123 } else if (currentSequence.size > longestSequence.size) {
124 longestSequence = currentSequence;
125 }
126 currentSequence = null;
127 }
128 }
129 }
130 if (!longestSequence && currentSequence) longestSequence = currentSequence;
131
132 for (const index of longestSequence || []) {
133 parts[index] = ":";
134 }
135
136 return parts.filter(Boolean).join(":").replace(/:{2,}/, "::");
137}