Searcher.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. <?php
  2. namespace app\extra\ip2region\xdb;
  3. class Searcher
  4. {
  5. // ip version
  6. private $version;
  7. // xdb file handle
  8. private $handle = null;
  9. private $ioCount = 0;
  10. // vector index in binary string.
  11. // string decode will be faster than the map based Array.
  12. private $vectorIndex = null;
  13. // xdb content buffer
  14. private $contentBuff = null;
  15. // ---
  16. // static function to create searcher
  17. /**
  18. * @throws Exception
  19. */
  20. public static function newWithFileOnly($version, $dbFile) {
  21. return new self($version, $dbFile, null, null);
  22. }
  23. /**
  24. * @throws Exception
  25. */
  26. public static function newWithVectorIndex($version, $dbFile, $vIndex) {
  27. return new self($version, $dbFile, $vIndex, null);
  28. }
  29. /**
  30. * @throws Exception
  31. */
  32. public static function newWithBuffer($version, $cBuff) {
  33. return new self($version, null, null, $cBuff);
  34. }
  35. // --- End of static creator
  36. /**
  37. * initialize the xdb searcher
  38. * @throws Exception
  39. */
  40. function __construct($version, $dbFile, $vectorIndex=null, $cBuff=null) {
  41. $this->version = $version;
  42. // check the content buffer first
  43. if ($cBuff != null) {
  44. $this->vectorIndex = null;
  45. $this->contentBuff = $cBuff;
  46. } else {
  47. // open the xdb binary file
  48. $this->handle = fopen($dbFile, "r");
  49. if ($this->handle === false) {
  50. throw new \Exception("failed to open xdb file '%s'", $dbFile);
  51. }
  52. $this->vectorIndex = $vectorIndex;
  53. }
  54. }
  55. public function close() {
  56. if ($this->handle != null) {
  57. fclose($this->handle);
  58. }
  59. }
  60. public function getIPVersion() {
  61. return $this->version;
  62. }
  63. public function getIOCount() {
  64. return $this->ioCount;
  65. }
  66. /**
  67. * find the region info for the specified ip address.
  68. * @Note: the ip address couldO ONLY be a human-readable IP address string,
  69. * DO not use the packed binary string returned by #parseIP
  70. *
  71. * @throws Exception
  72. */
  73. public function search($ip) {
  74. $ipBytes = SearchIp::parseIP($ip);
  75. if ($ipBytes == null) {
  76. throw new \Exception("invalid ip address `{$ip}`");
  77. }
  78. return $this->searchByBytes($ipBytes);
  79. }
  80. /**
  81. * find the region info for the specified binary ip bytes returned by #parseIP.
  82. *
  83. * @throws Exception
  84. */
  85. public function searchByBytes($ipBytes) {
  86. // ip version check
  87. if (strlen($ipBytes) != $this->version->bytes) {
  88. throw new \Exception("invalid ip address ({$this->version->name} expected)");
  89. }
  90. // reset the global counter
  91. $this->ioCount = 0;
  92. // locate the segment index block based on the vector index
  93. $il0 = ord($ipBytes[0]) & 0xFF;
  94. $il1 = ord($ipBytes[1]) & 0xFF;
  95. $idx = $il0 * VectorIndexCols * VectorIndexSize + $il1 * VectorIndexSize;
  96. if ($this->vectorIndex != null) {
  97. $sPtr = SearchIp::le_getUint32($this->vectorIndex, $idx);
  98. $ePtr = SearchIp::le_getUint32($this->vectorIndex, $idx + 4);
  99. } else if ($this->contentBuff != null) {
  100. $sPtr = SearchIp::le_getUint32($this->contentBuff, HeaderInfoLength + $idx);
  101. $ePtr = SearchIp::le_getUint32($this->contentBuff, HeaderInfoLength + $idx + 4);
  102. } else {
  103. // read the vector index block
  104. $buff = $this->read(HeaderInfoLength + $idx, 8);
  105. $sPtr = SearchIp::le_getUint32($buff, 0);
  106. $ePtr = SearchIp::le_getUint32($buff, 4);
  107. }
  108. // printf("sPtr: %d, ePtr: %d\n", $sPtr, $ePtr);
  109. // @Note: ptr validate, zero ptr means source data missing
  110. // so we could just stop here and return an empty string.
  111. if ($sPtr == 0 || $ePtr == 0) {
  112. return "";
  113. }
  114. [$bytes, $dBytes] = [strlen($ipBytes), strlen($ipBytes) << 1];
  115. // binary search the segment index to get the region info
  116. $idxSize = $this->version->segmentIndexSize;
  117. [$dataLen, $dataPtr, $l, $h] = [0, 0, 0, ($ePtr - $sPtr) / $idxSize];
  118. while ($l <= $h) {
  119. $m = ($l + $h) >> 1;
  120. $p = $sPtr + $m * $idxSize;
  121. // read the segment index
  122. $buff = $this->read($p, $idxSize);
  123. // compare the segment index
  124. if ($this->version->ipSubCompare($ipBytes, $buff, 0) < 0) {
  125. $h = $m - 1;
  126. } else if ($this->version->ipSubCompare($ipBytes, $buff, $bytes) > 0) {
  127. $l = $m + 1;
  128. } else {
  129. $dataLen = SearchIp::le_getUint16($buff, $dBytes);
  130. $dataPtr = SearchIp::le_getUint32($buff, $dBytes + 2);
  131. break;
  132. }
  133. }
  134. // empty match interception.
  135. // printf("dataLen: %d, dataPtr: %d\n", $dataLen, $dataPtr);
  136. if ($dataLen == 0) {
  137. return "";
  138. }
  139. // load and return the region data
  140. return $this->read($dataPtr, $dataLen);
  141. }
  142. // read specified bytes from the specified index
  143. private function read($offset, $len) {
  144. // check the in-memory buffer first
  145. if ($this->contentBuff != null) {
  146. return substr($this->contentBuff, $offset, $len);
  147. }
  148. // read from the file
  149. $r = fseek($this->handle, $offset);
  150. if ($r == -1) {
  151. throw new \Exception("failed to fseek to {$offset}");
  152. }
  153. $this->ioCount++;
  154. $buff = fread($this->handle, $len);
  155. if ($buff === false) {
  156. throw new \Exception("failed to fread from {$len}");
  157. }
  158. if (strlen($buff) != $len) {
  159. throw new \Exception("incomplete read: read bytes should be {$len}");
  160. }
  161. return $buff;
  162. }
  163. }