ip_converter.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { Convert } from "pvtsutils";
  2. export class IpConverter {
  3. static isIPv4(ip) {
  4. return /^(\d{1,3}\.){3}\d{1,3}$/.test(ip);
  5. }
  6. static parseIPv4(ip) {
  7. const parts = ip.split(".");
  8. if (parts.length !== 4) {
  9. throw new Error("Invalid IPv4 address");
  10. }
  11. return parts.map((part) => {
  12. const num = parseInt(part, 10);
  13. if (isNaN(num) || num < 0 || num > 255) {
  14. throw new Error("Invalid IPv4 address part");
  15. }
  16. return num;
  17. });
  18. }
  19. static parseIPv6(ip) {
  20. const expandedIP = this.expandIPv6(ip);
  21. const parts = expandedIP.split(":");
  22. if (parts.length !== 8) {
  23. throw new Error("Invalid IPv6 address");
  24. }
  25. return parts.reduce((bytes, part) => {
  26. const num = parseInt(part, 16);
  27. if (isNaN(num) || num < 0 || num > 0xffff) {
  28. throw new Error("Invalid IPv6 address part");
  29. }
  30. bytes.push((num >> 8) & 0xff);
  31. bytes.push(num & 0xff);
  32. return bytes;
  33. }, []);
  34. }
  35. static expandIPv6(ip) {
  36. if (!ip.includes("::")) {
  37. return ip;
  38. }
  39. const parts = ip.split("::");
  40. if (parts.length > 2) {
  41. throw new Error("Invalid IPv6 address");
  42. }
  43. const left = parts[0] ? parts[0].split(":") : [];
  44. const right = parts[1] ? parts[1].split(":") : [];
  45. const missing = 8 - (left.length + right.length);
  46. if (missing < 0) {
  47. throw new Error("Invalid IPv6 address");
  48. }
  49. return [...left, ...Array(missing).fill("0"), ...right].join(":");
  50. }
  51. static formatIPv6(bytes) {
  52. const parts = [];
  53. for (let i = 0; i < 16; i += 2) {
  54. parts.push(((bytes[i] << 8) | bytes[i + 1]).toString(16));
  55. }
  56. return this.compressIPv6(parts.join(":"));
  57. }
  58. static compressIPv6(ip) {
  59. const parts = ip.split(":");
  60. let longestZeroStart = -1;
  61. let longestZeroLength = 0;
  62. let currentZeroStart = -1;
  63. let currentZeroLength = 0;
  64. for (let i = 0; i < parts.length; i++) {
  65. if (parts[i] === "0") {
  66. if (currentZeroStart === -1) {
  67. currentZeroStart = i;
  68. }
  69. currentZeroLength++;
  70. }
  71. else {
  72. if (currentZeroLength > longestZeroLength) {
  73. longestZeroStart = currentZeroStart;
  74. longestZeroLength = currentZeroLength;
  75. }
  76. currentZeroStart = -1;
  77. currentZeroLength = 0;
  78. }
  79. }
  80. if (currentZeroLength > longestZeroLength) {
  81. longestZeroStart = currentZeroStart;
  82. longestZeroLength = currentZeroLength;
  83. }
  84. if (longestZeroLength > 1) {
  85. const before = parts.slice(0, longestZeroStart).join(":");
  86. const after = parts.slice(longestZeroStart + longestZeroLength).join(":");
  87. return `${before}::${after}`;
  88. }
  89. return ip;
  90. }
  91. static parseCIDR(text) {
  92. const [addr, prefixStr] = text.split("/");
  93. const prefix = parseInt(prefixStr, 10);
  94. if (this.isIPv4(addr)) {
  95. if (prefix < 0 || prefix > 32) {
  96. throw new Error("Invalid IPv4 prefix length");
  97. }
  98. return [this.parseIPv4(addr), prefix];
  99. }
  100. else {
  101. if (prefix < 0 || prefix > 128) {
  102. throw new Error("Invalid IPv6 prefix length");
  103. }
  104. return [this.parseIPv6(addr), prefix];
  105. }
  106. }
  107. static decodeIP(value) {
  108. if (value.length === 64 && parseInt(value, 16) === 0) {
  109. return "::/0";
  110. }
  111. if (value.length !== 16) {
  112. return value;
  113. }
  114. const mask = parseInt(value.slice(8), 16)
  115. .toString(2)
  116. .split("")
  117. .reduce((a, k) => a + +k, 0);
  118. let ip = value.slice(0, 8).replace(/(.{2})/g, (match) => `${parseInt(match, 16)}.`);
  119. ip = ip.slice(0, -1);
  120. return `${ip}/${mask}`;
  121. }
  122. static toString(buf) {
  123. const uint8 = new Uint8Array(buf);
  124. if (uint8.length === 4) {
  125. return Array.from(uint8).join(".");
  126. }
  127. if (uint8.length === 16) {
  128. return this.formatIPv6(uint8);
  129. }
  130. if (uint8.length === 8 || uint8.length === 32) {
  131. const half = uint8.length / 2;
  132. const addrBytes = uint8.slice(0, half);
  133. const maskBytes = uint8.slice(half);
  134. const isAllZeros = uint8.every((byte) => byte === 0);
  135. if (isAllZeros) {
  136. return uint8.length === 8 ? "0.0.0.0/0" : "::/0";
  137. }
  138. const prefixLen = maskBytes.reduce((a, b) => a + (b.toString(2).match(/1/g) || []).length, 0);
  139. if (uint8.length === 8) {
  140. const addrStr = Array.from(addrBytes).join(".");
  141. return `${addrStr}/${prefixLen}`;
  142. }
  143. else {
  144. const addrStr = this.formatIPv6(addrBytes);
  145. return `${addrStr}/${prefixLen}`;
  146. }
  147. }
  148. return this.decodeIP(Convert.ToHex(buf));
  149. }
  150. static fromString(text) {
  151. if (text.includes("/")) {
  152. const [addr, prefix] = this.parseCIDR(text);
  153. const maskBytes = new Uint8Array(addr.length);
  154. let bitsLeft = prefix;
  155. for (let i = 0; i < maskBytes.length; i++) {
  156. if (bitsLeft >= 8) {
  157. maskBytes[i] = 0xff;
  158. bitsLeft -= 8;
  159. }
  160. else if (bitsLeft > 0) {
  161. maskBytes[i] = 0xff << (8 - bitsLeft);
  162. bitsLeft = 0;
  163. }
  164. }
  165. const out = new Uint8Array(addr.length * 2);
  166. out.set(addr, 0);
  167. out.set(maskBytes, addr.length);
  168. return out.buffer;
  169. }
  170. const bytes = this.isIPv4(text) ? this.parseIPv4(text) : this.parseIPv6(text);
  171. return new Uint8Array(bytes).buffer;
  172. }
  173. }