AddressTools.hx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. package asys.net;
  2. import haxe.io.Bytes;
  3. import asys.net.IpFamily;
  4. /**
  5. Methods for converting to and from `Address` instances.
  6. **/
  7. class AddressTools {
  8. static final v4re = {
  9. final v4seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
  10. final v4str = '${v4seg}\\.${v4seg}\\.${v4seg}\\.${v4seg}';
  11. new EReg('^${v4str}$$', "");
  12. };
  13. static final v6re = {
  14. final v4seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
  15. final v4str = '${v4seg}\\.${v4seg}\\.${v4seg}\\.${v4seg}';
  16. final v6seg = "(?:[0-9a-fA-F]{1,4})";
  17. new EReg("^("
  18. + '(?:${v6seg}:){7}(?:${v6seg}|:)|'
  19. + '(?:${v6seg}:){6}(?:${v4str}|:${v6seg}|:)|'
  20. + '(?:${v6seg}:){5}(?::${v4str}|(:${v6seg}){1,2}|:)|'
  21. + '(?:${v6seg}:){4}(?:(:${v6seg}){0,1}:${v4str}|(:${v6seg}){1,3}|:)|'
  22. + '(?:${v6seg}:){3}(?:(:${v6seg}){0,2}:${v4str}|(:${v6seg}){1,4}|:)|'
  23. + '(?:${v6seg}:){2}(?:(:${v6seg}){0,3}:${v4str}|(:${v6seg}){1,5}|:)|'
  24. + '(?:${v6seg}:){1}(?:(:${v6seg}){0,4}:${v4str}|(:${v6seg}){1,6}|:)|'
  25. + '(?::((?::${v6seg}){0,5}:${v4str}|(?::${v6seg}){1,7}|:))'
  26. + ")$", // "(%[0-9a-zA-Z]{1,})?$", // TODO: interface not supported
  27. "");
  28. };
  29. /**
  30. Returns the IP address representing all hosts for the given IP family.
  31. - For IPv4, the address is `0.0.0.0`.
  32. - For IPv6, the address is `::`.
  33. **/
  34. public static function all(family:IpFamily):Address {
  35. return (switch (family) {
  36. case Ipv4: Ipv4(0);
  37. case Ipv6: Ipv6(Bytes.ofHex("00000000000000000000000000000000"));
  38. });
  39. }
  40. /**
  41. Returns the IP address representing the local hosts for the given IP family.
  42. - For IPv4, the address is `127.0.0.1`.
  43. - For IPv6, the address is `::1`.
  44. **/
  45. public static function localhost(family:IpFamily):Address {
  46. return (switch (family) {
  47. case Ipv4: Ipv4(0x7F000001);
  48. case Ipv6: Ipv6(Bytes.ofHex("00000000000000000000000000000001"));
  49. });
  50. }
  51. /**
  52. Converts an `Address` to a `String`.
  53. - IPv4 addresses are represented with the dotted quad format, e.g.
  54. `192.168.0.1`.
  55. - IPv6 addresses are represented with the standard lowercased hexadecimal
  56. representation, with `::` used to mark a long stretch of zeros.
  57. **/
  58. public static function toString(address:Address):String {
  59. return (switch (address) {
  60. case Ipv4(ip):
  61. '${ip >>> 24}.${(ip >> 16) & 0xFF}.${(ip >> 8) & 0xFF}.${ip & 0xFF}';
  62. case Ipv6(ip):
  63. var groups = [for (i in 0...8) (ip.get(i * 2) << 8) | ip.get(i * 2 + 1)];
  64. var longestRun = -1;
  65. var longestPos = -1;
  66. for (i in 0...8) {
  67. if (groups[i] != 0)
  68. continue;
  69. var run = 1;
  70. // TODO: skip if the longest run cannot be beaten
  71. for (j in i + 1...8) {
  72. if (groups[j] != 0)
  73. break;
  74. run++;
  75. }
  76. if (run > longestRun) {
  77. longestRun = run;
  78. longestPos = i;
  79. }
  80. }
  81. inline function hex(groups:Array<Int>):String {
  82. return groups.map(value -> StringTools.hex(value, 1).toLowerCase()).join(":");
  83. }
  84. if (longestRun > 1) {
  85. hex(groups.slice(0, longestPos)) + "::" + hex(groups.slice(longestPos + longestRun));
  86. } else {
  87. hex(groups);
  88. }
  89. });
  90. }
  91. /**
  92. Returns `true` if `address` represents a valid IPv4 or IPv6 address.
  93. **/
  94. public static function isIp(address:String):Bool {
  95. return isIpv4(address) || isIpv6(address);
  96. }
  97. /**
  98. Returns `true` if `address` represents a valid IPv4 address.
  99. **/
  100. public static function isIpv4(address:String):Bool {
  101. return v4re.match(address);
  102. }
  103. /**
  104. Returns `true` if `address` represents a valid IPv6 address.
  105. **/
  106. public static function isIpv6(address:String):Bool {
  107. return v6re.match(address);
  108. }
  109. /**
  110. Tries to convert the `String` `address` to an `Address` instance. Returns
  111. the parsed `Address` or `null` if `address` does not represent a valid IP
  112. address.
  113. **/
  114. public static function toIp(address:String):Null<Address> {
  115. var ipv4 = toIpv4(address);
  116. return ipv4 != null ? ipv4 : toIpv6(address);
  117. }
  118. /**
  119. Tries to convert the `String` `address` to an IPv4 `Address` instance.
  120. Returns the parsed `Address` or `null` if `address` does not represent a
  121. valid IPv4 address.
  122. **/
  123. public static function toIpv4(address:String):Null<Address> {
  124. if (!isIpv4(address))
  125. return null;
  126. var components = address.split(".").map(Std.parseInt);
  127. return Ipv4((components[0] << 24) | (components[1] << 16) | (components[2] << 8) | components[3]);
  128. }
  129. /**
  130. Tries to convert the `String` `address` to an IPv6 `Address` instance.
  131. Returns the parsed `Address` or `null` if `address` does not represent a
  132. valid IPv6 address.
  133. **/
  134. public static function toIpv6(address:String):Null<Address> {
  135. if (!isIpv6(address))
  136. return null;
  137. var buffer = Bytes.alloc(16);
  138. buffer.fill(0, 16, 0);
  139. function parse(component:String, res:Int):Void {
  140. var value = Std.parseInt('0x0$component');
  141. buffer.set(res, value >> 8);
  142. buffer.set(res + 1, value & 0xFF);
  143. }
  144. var stretch = address.split("::");
  145. var components = stretch[0].split(":");
  146. for (i in 0...components.length)
  147. parse(components[i], i * 2);
  148. if (stretch.length > 1) {
  149. var end = 16;
  150. components = stretch[1].split(":");
  151. if (isIpv4(components[components.length - 1])) {
  152. end -= 4;
  153. var ip = components.pop().split(".").map(Std.parseInt);
  154. for (i in 0...4)
  155. buffer.set(end + i, ip[i]);
  156. }
  157. end -= components.length * 2;
  158. for (i in 0...components.length)
  159. parse(components[i], end + i);
  160. }
  161. return Ipv6(buffer);
  162. }
  163. /**
  164. Returns the IPv6 version of the given `address`. IPv6 addresses are
  165. returned unmodified, IPv4 addresses are mapped to IPv6 using the
  166. `:ffff:0:0/96` IPv4 transition prefix.
  167. ```haxe
  168. "127.0.0.1".toIpv4().mapToIpv6().toString(); // ::ffff:7f00:1
  169. ```
  170. **/
  171. public static function mapToIpv6(address:Address):Address {
  172. return (switch (address) {
  173. case Ipv4(ip):
  174. var buffer = Bytes.alloc(16);
  175. buffer.set(10, 0xFF);
  176. buffer.set(11, 0xFF);
  177. buffer.set(12, ip >>> 24);
  178. buffer.set(13, (ip >> 16) & 0xFF);
  179. buffer.set(14, (ip >> 8) & 0xFF);
  180. buffer.set(15, ip & 0xFF);
  181. Ipv6(buffer);
  182. case _:
  183. address;
  184. });
  185. }
  186. /**
  187. Returns `true` if `a` and `b` are the same IP address.
  188. If `ipv6mapped` is `true`, bot `a` and `b` are mapped to IPv6 (using
  189. `mapToIpv6`) before the comparison.
  190. **/
  191. public static function equals(a:Address, b:Address, ?ipv6mapped:Bool = false):Bool {
  192. if (ipv6mapped) {
  193. return (switch [mapToIpv6(a), mapToIpv6(b)] {
  194. case [Ipv6(a), Ipv6(b)]: a.compare(b) == 0;
  195. case _: false; // cannot happen?
  196. });
  197. }
  198. return (switch [a, b] {
  199. case [Ipv4(a), Ipv4(b)]: a == b;
  200. case [Ipv6(a), Ipv6(b)]: a.compare(b) == 0;
  201. case _: false;
  202. });
  203. }
  204. }