SearchIp.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <?php
  2. namespace app\extra\ip2region\xdb;
  3. use \Exception;
  4. // global constants
  5. const Structure_20 = 2;
  6. const Structure_30 = 3;
  7. const HeaderInfoLength = 256;
  8. const VectorIndexRows = 256;
  9. const VectorIndexCols = 256;
  10. const VectorIndexSize = 8;
  11. class SearchIp
  12. {
  13. // parse the specified IP address and return its bytes.
  14. // returns: NULL for failed or the packed bytes
  15. public static function parseIP($ipString) {
  16. $flag = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6;
  17. if (!filter_var($ipString, FILTER_VALIDATE_IP, $flag)) {
  18. return null;
  19. }
  20. return inet_pton($ipString);
  21. }
  22. // IP bytes to string
  23. public static function ipToString($ipBytes) {
  24. $l = strlen($ipBytes);
  25. return ($l == 4 || $l == 16) ? inet_ntop($ipBytes) : '<invalid-ip-bytes>';
  26. }
  27. // compare two ip bytes (packed string return by parsedIP)
  28. // returns: -1 if ip1 < ip2, 0 if ip1 == ip2 or 1 if ip1 > ip2
  29. public static function ipSubCompare($ip1, $buff, $offset) {
  30. // $r = substr_compare($ip1, $buff, $offset, strlen($ip1));
  31. // @Note: substr_compare is not working, use the substr + strcmp instead
  32. $r = strcmp($ip1, substr($buff, $offset, strlen($ip1)));
  33. if ($r < 0) {
  34. return -1;
  35. } else if ($r > 0) {
  36. return 1;
  37. } else {
  38. return 0;
  39. }
  40. }
  41. // returns: -1 if ip1 < ip2, 0 if ip1 == ip2 or 1 if ip1 > ip2
  42. public static function ipCompare($ip1, $ip2) {
  43. $r = strcmp($ip1, $ip2);
  44. if ($r < 0) {
  45. return -1;
  46. } else if ($r > 0) {
  47. return 1;
  48. } else {
  49. return 0;
  50. }
  51. }
  52. // version parse
  53. public static function versionFromName($ver_name) {
  54. $name = strtoupper($ver_name);
  55. if ($name == "V4" || $name == "IPv4") {
  56. return IPv4::default();
  57. } else if ($name == "V6" || $name == "IPv6") {
  58. return IPv6::default();
  59. } else {
  60. throw new Exception("invalid verstion name `{$ver_name}`");
  61. }
  62. }
  63. // version parse from header
  64. public static function versionFromHeader($header) {
  65. // Old structure 2.0 with IPv4 supports ONLY
  66. if ($header['version'] == Structure_20) {
  67. return IPv4::default();
  68. }
  69. // structure 3.0 after IPv6 supporting
  70. if ($header['version'] != Structure_30) {
  71. throw new Exception("invalid xdb structure version `{$header['version']}`");
  72. }
  73. if ($header['ipVersion'] == IPv4VersionNo) {
  74. return IPv4::default();
  75. } else if ($header['ipVersion'] == IPv6VersionNo) {
  76. return IPv6::default();
  77. } else {
  78. throw new Exception("invalid ip version number `{$header['ipVersion']}`");
  79. }
  80. }
  81. // binary string chars implode with space
  82. public static function bytesToString($buff, $offset, $length) {
  83. $sb = [];
  84. for ($i = 0; $i < $length; $i++) {
  85. $sb[] = ord($buff[$offset+$i]) & 0xFF;
  86. }
  87. return '['.implode(' ', $sb).']';
  88. }
  89. // decode a 4bytes long with Little endian byte order from a byte buffer
  90. public static function le_getUint32($b, $idx) {
  91. $val = (ord($b[$idx])) | (ord($b[$idx+1]) << 8)
  92. | (ord($b[$idx+2]) << 16) | (ord($b[$idx+3]) << 24);
  93. // convert signed int to unsigned int if on 32 bit operating system
  94. if ($val < 0 && PHP_INT_SIZE == 4) {
  95. $val = sprintf("%u", $val);
  96. }
  97. return $val;
  98. }
  99. // read a 2bytes int with litten endian byte order from a byte buffer
  100. public static function le_getUint16($b, $idx) {
  101. return ((ord($b[$idx])) | (ord($b[$idx+1]) << 8));
  102. }
  103. // Verify if the current Searcher could be used to search the specified xdb file.
  104. // Why do we need this check ?
  105. // The future features of the xdb impl may cause the current searcher not able to work properly.
  106. //
  107. // @Note: You Just need to check this ONCE when the service starts
  108. // Or use another process (eg, A command) to check once Just to confirm the suitability.
  109. // returns: null for everything is ok or the error string.
  110. public static function verify($handle) {
  111. // load the header
  112. $header = self::loadHeader($handle);
  113. if ($header == null) {
  114. return 'failed to load the header';
  115. }
  116. // get the runtime ptr bytes
  117. $runtimePtrBytes = 0;
  118. if ($header['version'] == Structure_20) {
  119. $runtimePtrBytes = 4;
  120. } else if ($header['version'] == Structure_30) {
  121. $runtimePtrBytes = $header['runtimePtrBytes'];
  122. } else {
  123. return "invalid structure version `{$header['version']}`";
  124. }
  125. // 1, confirm the xdb file size
  126. // to ensure that the maximum file pointer does not overflow
  127. $stat = fstat($handle);
  128. if ($stat == false) {
  129. return 'failed to stat the xdb file';
  130. }
  131. $maxFilePtr = (1 << ($runtimePtrBytes * 8)) - 1;
  132. // print_r([$stat['size'], $maxFilePtr]);
  133. if ($stat['size'] > $maxFilePtr) {
  134. return "xdb file exceeds the maximum supported bytes: {$maxFilePtr}";
  135. }
  136. return null;
  137. }
  138. public static function verifyFromFile($dbFile) {
  139. $handle = fopen($dbFile, 'r');
  140. if ($handle === false) {
  141. return null;
  142. }
  143. $r = self::verify($handle);
  144. fclose($handle);
  145. return $r;
  146. }
  147. // load header info from a specified file handle
  148. public static function loadHeader($handle) {
  149. if (fseek($handle, 0) == -1) {
  150. return null;
  151. }
  152. $buff = fread($handle, HeaderInfoLength);
  153. if ($buff === false) {
  154. return null;
  155. }
  156. // read bytes length checking
  157. if (strlen($buff) != HeaderInfoLength) {
  158. return null;
  159. }
  160. // return the decoded header info
  161. return array(
  162. 'version' => self::le_getUint16($buff, 0),
  163. 'indexPolicy' => self::le_getUint16($buff, 2),
  164. 'createdAt' => self::le_getUint32($buff, 4),
  165. 'startIndexPtr' => self::le_getUint32($buff, 8),
  166. 'endIndexPtr' => self::le_getUint32($buff, 12),
  167. 'ipVersion' => self::le_getUint16($buff, 16),
  168. 'runtimePtrBytes' => self::le_getUint16($buff, 18)
  169. );
  170. }
  171. // load header info from the specified xdb file path
  172. public static function loadHeaderFromFile($dbFile) {
  173. $handle = fopen($dbFile, 'r');
  174. if ($handle === false) {
  175. return null;
  176. }
  177. $header = self::loadHeader($handle);
  178. fclose($handle);
  179. return $header;
  180. }
  181. // load vector index from a file handle
  182. public static function loadVectorIndex($handle) {
  183. if (fseek($handle, HeaderInfoLength) == -1) {
  184. return null;
  185. }
  186. $rLen = VectorIndexRows * VectorIndexCols * VectorIndexSize;
  187. $buff = fread($handle, $rLen);
  188. if ($buff === false) {
  189. return null;
  190. }
  191. if (strlen($buff) != $rLen) {
  192. return null;
  193. }
  194. return $buff;
  195. }
  196. // load vector index from a specified xdb file path
  197. public static function loadVectorIndexFromFile($dbFile) {
  198. $handle = fopen($dbFile, 'r');
  199. if ($handle === false) {
  200. return null;
  201. }
  202. $vIndex = self::loadVectorIndex($handle);
  203. fclose($handle);
  204. return $vIndex;
  205. }
  206. // load the xdb content from a file handle
  207. public static function loadContent($handle) {
  208. if (fseek($handle, 0, SEEK_END) == -1) {
  209. return null;
  210. }
  211. $size = ftell($handle);
  212. if ($size === false) {
  213. return null;
  214. }
  215. // seek to the head for reading
  216. if (fseek($handle, 0) == -1) {
  217. return null;
  218. }
  219. $buff = fread($handle, $size);
  220. if ($buff === false) {
  221. return null;
  222. }
  223. // read length checking
  224. if (strlen($buff) != $size) {
  225. return null;
  226. }
  227. return $buff;
  228. }
  229. // load the xdb content from a file path
  230. public static function loadContentFromFile($dbFile) {
  231. $str = file_get_contents($dbFile, false);
  232. if ($str === false) {
  233. return null;
  234. } else {
  235. return $str;
  236. }
  237. }
  238. public static function now() {
  239. return (microtime(true) * 1000);
  240. }
  241. }