| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- <?php
- namespace app\extra\ip2region\xdb;
- class Searcher
- {
- // ip version
- private $version;
- // xdb file handle
- private $handle = null;
- private $ioCount = 0;
- // vector index in binary string.
- // string decode will be faster than the map based Array.
- private $vectorIndex = null;
- // xdb content buffer
- private $contentBuff = null;
- // ---
- // static function to create searcher
- /**
- * @throws Exception
- */
- public static function newWithFileOnly($version, $dbFile) {
- return new self($version, $dbFile, null, null);
- }
- /**
- * @throws Exception
- */
- public static function newWithVectorIndex($version, $dbFile, $vIndex) {
- return new self($version, $dbFile, $vIndex, null);
- }
- /**
- * @throws Exception
- */
- public static function newWithBuffer($version, $cBuff) {
- return new self($version, null, null, $cBuff);
- }
- // --- End of static creator
- /**
- * initialize the xdb searcher
- * @throws Exception
- */
- function __construct($version, $dbFile, $vectorIndex=null, $cBuff=null) {
- $this->version = $version;
- // check the content buffer first
- if ($cBuff != null) {
- $this->vectorIndex = null;
- $this->contentBuff = $cBuff;
- } else {
- // open the xdb binary file
- $this->handle = fopen($dbFile, "r");
- if ($this->handle === false) {
- throw new \Exception("failed to open xdb file '%s'", $dbFile);
- }
- $this->vectorIndex = $vectorIndex;
- }
- }
- public function close() {
- if ($this->handle != null) {
- fclose($this->handle);
- }
- }
- public function getIPVersion() {
- return $this->version;
- }
- public function getIOCount() {
- return $this->ioCount;
- }
- /**
- * find the region info for the specified ip address.
- * @Note: the ip address couldO ONLY be a human-readable IP address string,
- * DO not use the packed binary string returned by #parseIP
- *
- * @throws Exception
- */
- public function search($ip) {
- $ipBytes = SearchIp::parseIP($ip);
- if ($ipBytes == null) {
- throw new \Exception("invalid ip address `{$ip}`");
- }
- return $this->searchByBytes($ipBytes);
- }
- /**
- * find the region info for the specified binary ip bytes returned by #parseIP.
- *
- * @throws Exception
- */
- public function searchByBytes($ipBytes) {
- // ip version check
- if (strlen($ipBytes) != $this->version->bytes) {
- throw new \Exception("invalid ip address ({$this->version->name} expected)");
- }
- // reset the global counter
- $this->ioCount = 0;
- // locate the segment index block based on the vector index
- $il0 = ord($ipBytes[0]) & 0xFF;
- $il1 = ord($ipBytes[1]) & 0xFF;
- $idx = $il0 * VectorIndexCols * VectorIndexSize + $il1 * VectorIndexSize;
- if ($this->vectorIndex != null) {
- $sPtr = SearchIp::le_getUint32($this->vectorIndex, $idx);
- $ePtr = SearchIp::le_getUint32($this->vectorIndex, $idx + 4);
- } else if ($this->contentBuff != null) {
- $sPtr = SearchIp::le_getUint32($this->contentBuff, HeaderInfoLength + $idx);
- $ePtr = SearchIp::le_getUint32($this->contentBuff, HeaderInfoLength + $idx + 4);
- } else {
- // read the vector index block
- $buff = $this->read(HeaderInfoLength + $idx, 8);
- $sPtr = SearchIp::le_getUint32($buff, 0);
- $ePtr = SearchIp::le_getUint32($buff, 4);
- }
- // printf("sPtr: %d, ePtr: %d\n", $sPtr, $ePtr);
- // @Note: ptr validate, zero ptr means source data missing
- // so we could just stop here and return an empty string.
- if ($sPtr == 0 || $ePtr == 0) {
- return "";
- }
- [$bytes, $dBytes] = [strlen($ipBytes), strlen($ipBytes) << 1];
- // binary search the segment index to get the region info
- $idxSize = $this->version->segmentIndexSize;
- [$dataLen, $dataPtr, $l, $h] = [0, 0, 0, ($ePtr - $sPtr) / $idxSize];
- while ($l <= $h) {
- $m = ($l + $h) >> 1;
- $p = $sPtr + $m * $idxSize;
- // read the segment index
- $buff = $this->read($p, $idxSize);
- // compare the segment index
- if ($this->version->ipSubCompare($ipBytes, $buff, 0) < 0) {
- $h = $m - 1;
- } else if ($this->version->ipSubCompare($ipBytes, $buff, $bytes) > 0) {
- $l = $m + 1;
- } else {
- $dataLen = SearchIp::le_getUint16($buff, $dBytes);
- $dataPtr = SearchIp::le_getUint32($buff, $dBytes + 2);
- break;
- }
- }
- // empty match interception.
- // printf("dataLen: %d, dataPtr: %d\n", $dataLen, $dataPtr);
- if ($dataLen == 0) {
- return "";
- }
- // load and return the region data
- return $this->read($dataPtr, $dataLen);
- }
- // read specified bytes from the specified index
- private function read($offset, $len) {
- // check the in-memory buffer first
- if ($this->contentBuff != null) {
- return substr($this->contentBuff, $offset, $len);
- }
- // read from the file
- $r = fseek($this->handle, $offset);
- if ($r == -1) {
- throw new \Exception("failed to fseek to {$offset}");
- }
- $this->ioCount++;
- $buff = fread($this->handle, $len);
- if ($buff === false) {
- throw new \Exception("failed to fread from {$len}");
- }
- if (strlen($buff) != $len) {
- throw new \Exception("incomplete read: read bytes should be {$len}");
- }
- return $buff;
- }
- }
|