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; } }