PHP IPv6 supporting
This commit is contained in:
parent
716326f4bc
commit
95c129c7d1
|
|
@ -1,349 +0,0 @@
|
||||||
<?php
|
|
||||||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a Apache2.0-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
//
|
|
||||||
// @Author Lion <chenxin619315@gmail.com>
|
|
||||||
// @Date 2022/06/21
|
|
||||||
|
|
||||||
class XdbSearcher
|
|
||||||
{
|
|
||||||
const HeaderInfoLength = 256;
|
|
||||||
const VectorIndexRows = 256;
|
|
||||||
const VectorIndexCols = 256;
|
|
||||||
const VectorIndexSize = 8;
|
|
||||||
const SegmentIndexSize = 14;
|
|
||||||
|
|
||||||
// xdb file handle
|
|
||||||
private $handle = null;
|
|
||||||
|
|
||||||
// header info
|
|
||||||
private $header = 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($dbFile) {
|
|
||||||
return new XdbSearcher($dbFile, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static function newWithVectorIndex($dbFile, $vIndex) {
|
|
||||||
return new XdbSearcher($dbFile, $vIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static function newWithBuffer($cBuff) {
|
|
||||||
return new XdbSearcher(null, null, $cBuff);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- End of static creator
|
|
||||||
|
|
||||||
/**
|
|
||||||
* initialize the xdb searcher
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
function __construct($dbFile, $vectorIndex=null, $cBuff=null) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
if ($this->handle != null) {
|
|
||||||
fclose($this->handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIOCount() {
|
|
||||||
return $this->ioCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* find the region info for the specified ip address
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
function search($ip) {
|
|
||||||
// check and convert the sting ip to a 4-bytes long
|
|
||||||
if (is_string($ip)) {
|
|
||||||
$t = self::ip2long($ip);
|
|
||||||
if ($t === null) {
|
|
||||||
throw new Exception("invalid ip address `$ip`");
|
|
||||||
}
|
|
||||||
$ip = $t;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset the global counter
|
|
||||||
$this->ioCount = 0;
|
|
||||||
|
|
||||||
// locate the segment index block based on the vector index
|
|
||||||
$il0 = ($ip >> 24) & 0xFF;
|
|
||||||
$il1 = ($ip >> 16) & 0xFF;
|
|
||||||
$idx = $il0 * self::VectorIndexCols * self::VectorIndexSize + $il1 * self::VectorIndexSize;
|
|
||||||
if ($this->vectorIndex != null) {
|
|
||||||
$sPtr = self::getLong($this->vectorIndex, $idx);
|
|
||||||
$ePtr = self::getLong($this->vectorIndex, $idx + 4);
|
|
||||||
} else if ($this->contentBuff != null) {
|
|
||||||
$sPtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx);
|
|
||||||
$ePtr = self::getLong($this->contentBuff, self::HeaderInfoLength + $idx + 4);
|
|
||||||
} else {
|
|
||||||
// read the vector index block
|
|
||||||
$buff = $this->read(self::HeaderInfoLength + $idx, 8);
|
|
||||||
if ($buff === null) {
|
|
||||||
throw new Exception("failed to read vector index at ${idx}");
|
|
||||||
}
|
|
||||||
|
|
||||||
$sPtr = self::getLong($buff, 0);
|
|
||||||
$ePtr = self::getLong($buff, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// printf("sPtr: %d, ePtr: %d\n", $sPtr, $ePtr);
|
|
||||||
|
|
||||||
// binary search the segment index to get the region info
|
|
||||||
$dataLen = 0;
|
|
||||||
$dataPtr = null;
|
|
||||||
$l = 0;
|
|
||||||
$h = ($ePtr - $sPtr) / self::SegmentIndexSize;
|
|
||||||
while ($l <= $h) {
|
|
||||||
$m = ($l + $h) >> 1;
|
|
||||||
$p = $sPtr + $m * self::SegmentIndexSize;
|
|
||||||
|
|
||||||
// read the segment index
|
|
||||||
$buff = $this->read($p, self::SegmentIndexSize);
|
|
||||||
if ($buff == null) {
|
|
||||||
throw new Exception("failed to read segment index at ${p}");
|
|
||||||
}
|
|
||||||
|
|
||||||
$sip = self::getLong($buff, 0);
|
|
||||||
if ($ip < $sip) {
|
|
||||||
$h = $m - 1;
|
|
||||||
} else {
|
|
||||||
$eip = self::getLong($buff, 4);
|
|
||||||
if ($ip > $eip) {
|
|
||||||
$l = $m + 1;
|
|
||||||
} else {
|
|
||||||
$dataLen = self::getShort($buff, 8);
|
|
||||||
$dataPtr = self::getLong($buff, 10);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// match nothing interception.
|
|
||||||
// @TODO: could this even be a case ?
|
|
||||||
// printf("dataLen: %d, dataPtr: %d\n", $dataLen, $dataPtr);
|
|
||||||
if ($dataPtr == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// load and return the region data
|
|
||||||
$buff = $this->read($dataPtr, $dataLen);
|
|
||||||
if ($buff == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->ioCount++;
|
|
||||||
$buff = fread($this->handle, $len);
|
|
||||||
if ($buff === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen($buff) != $len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- static util functions ----
|
|
||||||
|
|
||||||
// convert a string ip to long
|
|
||||||
public static function ip2long($ip)
|
|
||||||
{
|
|
||||||
$ip = ip2long($ip);
|
|
||||||
if ($ip === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert signed int to unsigned int if on 32 bit operating system
|
|
||||||
if ($ip < 0 && PHP_INT_SIZE == 4) {
|
|
||||||
$ip = sprintf("%u", $ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read a 4bytes long from a byte buffer
|
|
||||||
public static function getLong($b, $idx)
|
|
||||||
{
|
|
||||||
$val = (ord($b[$idx])) | (ord($b[$idx+1]) << 8)
|
|
||||||
| (ord($b[$idx+2]) << 16) | (ord($b[$idx+3]) << 24);
|
|
||||||
|
|
||||||
// convert signed int to unsigned int if on 32 bit operating system
|
|
||||||
if ($val < 0 && PHP_INT_SIZE == 4) {
|
|
||||||
$val = sprintf("%u", $val);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read a 2bytes short from a byte buffer
|
|
||||||
public static function getShort($b, $idx)
|
|
||||||
{
|
|
||||||
return ((ord($b[$idx])) | (ord($b[$idx+1]) << 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
// load header info from a specified file handle
|
|
||||||
public static function loadHeader($handle) {
|
|
||||||
if (fseek($handle, 0) == -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$buff = fread($handle, self::HeaderInfoLength);
|
|
||||||
if ($buff === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read bytes length checking
|
|
||||||
if (strlen($buff) != self::HeaderInfoLength) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the decoded header info
|
|
||||||
return array(
|
|
||||||
'version' => self::getShort($buff, 0),
|
|
||||||
'indexPolicy' => self::getShort($buff, 2),
|
|
||||||
'createdAt' => self::getLong($buff, 4),
|
|
||||||
'startIndexPtr' => self::getLong($buff, 8),
|
|
||||||
'endIndexPtr' => self::getLong($buff, 12)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load header info from the specified xdb file path
|
|
||||||
public static function loadHeaderFromFile($dbFile) {
|
|
||||||
$handle = fopen($dbFile, 'r');
|
|
||||||
if ($handle === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$header = self::loadHeader($handle);
|
|
||||||
fclose($handle);
|
|
||||||
return $header;
|
|
||||||
}
|
|
||||||
|
|
||||||
// load vector index from a file handle
|
|
||||||
public static function loadVectorIndex($handle) {
|
|
||||||
if (fseek($handle, self::HeaderInfoLength) == -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rLen = self::VectorIndexRows * self::VectorIndexCols * self::SegmentIndexSize;
|
|
||||||
$buff = fread($handle, $rLen);
|
|
||||||
if ($buff === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen($buff) != $rLen) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// load vector index from a specified xdb file path
|
|
||||||
public static function loadVectorIndexFromFile($dbFile) {
|
|
||||||
$handle = fopen($dbFile, 'r');
|
|
||||||
if ($handle === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$vIndex = self::loadVectorIndex($handle);
|
|
||||||
fclose($handle);
|
|
||||||
return $vIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the xdb content from a file handle
|
|
||||||
public static function loadContent($handle) {
|
|
||||||
if (fseek($handle, 0, SEEK_END) == -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$size = ftell($handle);
|
|
||||||
if ($size === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// seek to the head for reading
|
|
||||||
if (fseek($handle, 0) == -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$buff = fread($handle, $size);
|
|
||||||
if ($buff === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read length checking
|
|
||||||
if (strlen($buff) != $size) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $buff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the xdb content from a file path
|
|
||||||
public static function loadContentFromFile($dbFile) {
|
|
||||||
$str = file_get_contents($dbFile, false);
|
|
||||||
if ($str === false) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return $str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function now() {
|
|
||||||
return (microtime(true) * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,11 @@
|
||||||
// @Author Lion <chenxin619315@gmail.com>
|
// @Author Lion <chenxin619315@gmail.com>
|
||||||
// @Date 2022/06/22
|
// @Date 2022/06/22
|
||||||
|
|
||||||
require dirname(__FILE__) . '/XdbSearcher.class.php';
|
require dirname(__FILE__) . '/xdb/Searcher.class.php';
|
||||||
|
|
||||||
|
use \ip2region\xdb\Util;
|
||||||
|
use \ip2region\xdb\{IPv4, IPv6};
|
||||||
|
use \ip2region\xdb\Searcher;
|
||||||
|
|
||||||
function printHelp($argv) {
|
function printHelp($argv) {
|
||||||
printf("php %s [command options]\n", $argv[0]);
|
printf("php %s [command options]\n", $argv[0]);
|
||||||
|
|
@ -60,39 +64,41 @@ if (strlen($dbFile) < 1 || strlen($srcFile) < 1) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// printf("debug: dbFile: %s, cachePolicy: %s\n", $dbFile, $cachePolicy);
|
// printf("debug: dbFile: %s, cachePolicy: %s\n", $dbFile, $cachePolicy);
|
||||||
|
$version = IPv4::default();
|
||||||
|
|
||||||
// create the xdb searcher by the cache-policy
|
// create the xdb searcher by the cache-policy
|
||||||
switch ( $cachePolicy ) {
|
switch ( $cachePolicy ) {
|
||||||
case 'file':
|
case 'file':
|
||||||
try {
|
try {
|
||||||
$searcher = XdbSearcher::newWithFileOnly($dbFile);
|
$searcher = Searcher::newWithFileOnly($version, $dbFile);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
printf("failed to create searcher with '%s': %s\n", $dbFile, $e);
|
printf("failed to create searcher with '%s': %s\n", $dbFile, $e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'vectorIndex':
|
case 'vectorIndex':
|
||||||
$vIndex = XdbSearcher::loadVectorIndexFromFile($dbFile);
|
$vIndex = Util::loadVectorIndexFromFile($dbFile);
|
||||||
if ($vIndex == null) {
|
if ($vIndex == null) {
|
||||||
printf("failed to load vector index from '%s'\n", $dbFile);
|
printf("failed to load vector index from '%s'\n", $dbFile);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$searcher = XdbSearcher::newWithVectorIndex($dbFile, $vIndex);
|
$searcher = Searcher::newWithVectorIndex($version, $dbFile, $vIndex);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
printf("failed to create vector index cached searcher with '%s': %s\n", $dbFile, $e);
|
printf("failed to create vector index cached searcher with '%s': %s\n", $dbFile, $e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'content':
|
case 'content':
|
||||||
$cBuff = XdbSearcher::loadContentFromFile($dbFile);
|
$cBuff = Util::loadContentFromFile($dbFile);
|
||||||
if ($cBuff == null) {
|
if ($cBuff == null) {
|
||||||
printf("failed to load xdb content from '%s'\n", $dbFile);
|
printf("failed to load xdb content from '%s'\n", $dbFile);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$searcher = XdbSearcher::newWithBuffer($cBuff);
|
$searcher = Searcher::newWithBuffer($version, $cBuff);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
printf("failed to create content cached searcher: %s", $e);
|
printf("failed to create content cached searcher: %s", $e);
|
||||||
return;
|
return;
|
||||||
|
|
@ -113,7 +119,7 @@ if ($handle === false) {
|
||||||
|
|
||||||
$count = 0;
|
$count = 0;
|
||||||
$costs = 0;
|
$costs = 0;
|
||||||
$sTime = XdbSearcher::now();
|
$sTime = Util::now();
|
||||||
while (!feof($handle)) {
|
while (!feof($handle)) {
|
||||||
$line = trim(fgets($handle, 1024));
|
$line = trim(fgets($handle, 1024));
|
||||||
if (strlen($line) < 1) {
|
if (strlen($line) < 1) {
|
||||||
|
|
@ -126,42 +132,44 @@ while (!feof($handle)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sip = XdbSearcher::ip2long($ps[0]);
|
$sip = Util::parseIP($ps[0]);
|
||||||
if ($sip === null) {
|
if ($sip === null) {
|
||||||
printf("invalid start ip `%s`\n", $ps[0]);
|
printf("invalid start ip `%s`\n", $ps[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$eip = XdbSearcher::ip2long($ps[1]);
|
$eip = Util::parseIP($ps[1]);
|
||||||
if ($eip === null) {
|
if ($eip === null) {
|
||||||
printf("invalid end ip `%s`\n", $ps[1]);
|
printf("invalid end ip `%s`\n", $ps[1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($sip > $eip) {
|
if (Util::ipCompare($sip, $eip) > 0) {
|
||||||
printf("start ip(%s) should not be greater than end ip(%s)\n", $ps[0], $ps[1]);
|
printf(
|
||||||
|
"start ip(%s) should not be greater than end ip(%s)\n",
|
||||||
|
Util::ipToString($ps[0]), Util::ipToString($ps[1])
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mip = ($sip + $eip) >> 1;
|
foreach ([$sip, $eip] as $ip) {
|
||||||
foreach ([$sip, ($sip + $mip) >> 1, $mip, ($mip + $eip) >> 1, $eip] as $ip) {
|
|
||||||
try {
|
try {
|
||||||
$cTime = XdbSearcher::now();
|
$cTime = Util::now();
|
||||||
$region = $searcher->search($ip);
|
$region = $searcher->searchByBytes($ip);
|
||||||
$costs += XdbSearcher::now() - $cTime;
|
$costs += Util::now() - $cTime;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
printf("failed to search ip `%s`\n", long2ip($ip));
|
printf("failed to search ip `%s`: %s\n", Util::ipToString($ip), $e->getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($region == null) {
|
if ($region == null) {
|
||||||
printf("failed to search ip `%s`\n", long2ip($ip));
|
printf("failed to search ip `%s`: empty region info\n", Util::ipToString($ip));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the region info
|
// check the region info
|
||||||
if ($region != $ps[2]) {
|
if ($region != $ps[2]) {
|
||||||
printf("failed search(%s) with (%s != %s)\n", long2ip($ip), $region, $ps[2]);
|
printf("failed search(%s) with (%s != %s)\n", Util::ipToString($ip), $region, $ps[2]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,4 +181,4 @@ while (!feof($handle)) {
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
$searcher->close();
|
$searcher->close();
|
||||||
printf("Bench finished, {cachePolicy: %s, total: %d, took: %ds, cost: %.3f ms/op}\n",
|
printf("Bench finished, {cachePolicy: %s, total: %d, took: %ds, cost: %.3f ms/op}\n",
|
||||||
$cachePolicy, $count, (XdbSearcher::now() - $sTime)/1000, $count == 0 ? 0 : $costs/$count);
|
$cachePolicy, $count, (Util::now() - $sTime)/1000, $count == 0 ? 0 : $costs/$count);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,11 @@
|
||||||
// @Author Lion <chenxin619315@gmail.com>
|
// @Author Lion <chenxin619315@gmail.com>
|
||||||
// @Date 2022/06/21
|
// @Date 2022/06/21
|
||||||
|
|
||||||
require dirname(__FILE__) . '/XdbSearcher.class.php';
|
require dirname(__FILE__) . '/xdb/Searcher.class.php';
|
||||||
|
|
||||||
|
use \ip2region\xdb\Util;
|
||||||
|
use \ip2region\xdb\{IPv4, IPv6};
|
||||||
|
use \ip2region\xdb\Searcher;
|
||||||
|
|
||||||
function printHelp($argv) {
|
function printHelp($argv) {
|
||||||
printf("php %s [command options]\n", $argv[0]);
|
printf("php %s [command options]\n", $argv[0]);
|
||||||
|
|
@ -56,39 +60,41 @@ if (strlen($dbFile) < 1) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// printf("debug: dbFile: %s, cachePolicy: %s\n", $dbFile, $cachePolicy);
|
// printf("debug: dbFile: %s, cachePolicy: %s\n", $dbFile, $cachePolicy);
|
||||||
|
$version = IPv4::default();
|
||||||
|
|
||||||
// create the xdb searcher by the cache-policy
|
// create the xdb searcher by the cache-policy
|
||||||
switch ( $cachePolicy ) {
|
switch ( $cachePolicy ) {
|
||||||
case 'file':
|
case 'file':
|
||||||
try {
|
try {
|
||||||
$searcher = XdbSearcher::newWithFileOnly($dbFile);
|
$searcher = Searcher::newWithFileOnly($version, $dbFile);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
printf("failed to create searcher with '%s': %s\n", $dbFile, $e);
|
printf("failed to create searcher with '%s': %s\n", $dbFile, $e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'vectorIndex':
|
case 'vectorIndex':
|
||||||
$vIndex = XdbSearcher::loadVectorIndexFromFile($dbFile);
|
$vIndex = Util::loadVectorIndexFromFile($dbFile);
|
||||||
if ($vIndex == null) {
|
if ($vIndex == null) {
|
||||||
printf("failed to load vector index from '%s'\n", $dbFile);
|
printf("failed to load vector index from '%s'\n", $dbFile);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$searcher = XdbSearcher::newWithVectorIndex($dbFile, $vIndex);
|
$searcher = Searcher::newWithVectorIndex($version, $dbFile, $vIndex);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
printf("failed to create vector index cached searcher with '%s': %s\n", $dbFile, $e);
|
printf("failed to create vector index cached searcher with '%s': %s\n", $dbFile, $e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'content':
|
case 'content':
|
||||||
$cBuff = XdbSearcher::loadContentFromFile($dbFile);
|
$cBuff = Util::loadContentFromFile($dbFile);
|
||||||
if ($cBuff == null) {
|
if ($cBuff == null) {
|
||||||
printf("failed to load xdb content from '%s'\n", $dbFile);
|
printf("failed to load xdb content from '%s'\n", $dbFile);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$searcher = XdbSearcher::newWithBuffer($cBuff);
|
$searcher = Searcher::newWithBuffer($version, $cBuff);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
printf("failed to create content cached searcher: %s", $e);
|
printf("failed to create content cached searcher: %s", $e);
|
||||||
return;
|
return;
|
||||||
|
|
@ -111,21 +117,20 @@ while ( true ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (XdbSearcher::ip2long($line) === null) {
|
$cost = -1;
|
||||||
echo "Error: invalid ip address\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sTime = XdbSearcher::now();
|
|
||||||
try {
|
try {
|
||||||
|
$sTime = Util::now();
|
||||||
$region = $searcher->search($line);
|
$region = $searcher->search($line);
|
||||||
|
$cost = Util::now() - $sTime;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
printf("search call failed: %s\n", $e);
|
printf("search call failed: %s\n", $e->getMessage());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("{region: %s, ioCount: %d, took: %.5f ms}\n",
|
printf(
|
||||||
$region, $searcher->getIOCount(), XdbSearcher::now() - $sTime);
|
"{region: %s, ioCount: %d, took: %.5f ms}\n",
|
||||||
|
$region, $searcher->getIOCount(), $cost
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// close the searcher at last
|
// close the searcher at last
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
<?php
|
|
||||||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a Apache2.0-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
//
|
|
||||||
// @Author Lion <chenxin619315@gmail.com>
|
|
||||||
// @Date 2022/06/22
|
|
||||||
|
|
||||||
require dirname(__FILE__) . '/XdbSearcher.class.php';
|
|
||||||
|
|
||||||
function testLoadHeader() {
|
|
||||||
$header = XdbSearcher::loadHeaderFromFile('../../data/ip2region.xdb');
|
|
||||||
if ($header == null) {
|
|
||||||
printf("failed to load header from file\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("header loaded: ");
|
|
||||||
print_r($header);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testLoadVectorIndex() {
|
|
||||||
$vIndex = XdbSearcher::loadVectorIndexFromFile('../../data/ip2region.xdb');
|
|
||||||
if ($vIndex == null) {
|
|
||||||
printf("failed to load vector index from file\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("vector index loaded: length=%d\n", strlen($vIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
function testLoadContent() {
|
|
||||||
$cBuff = XdbSearcher::loadContentFromFile('../../data/ip2region.xdb');
|
|
||||||
if ($cBuff == null) {
|
|
||||||
printf("failed to load content from file\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("content loaded, length=%d\n", strlen($cBuff));
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("testing loadHeader ... \n");
|
|
||||||
$now = XdbSearcher::now();
|
|
||||||
testLoadHeader();
|
|
||||||
printf("done, cost: %0.5f ms\n\n", XdbSearcher::now() - $now);
|
|
||||||
|
|
||||||
|
|
||||||
printf("testing loadVectorIndex ... \n");
|
|
||||||
$now = XdbSearcher::now();
|
|
||||||
testLoadVectorIndex();
|
|
||||||
printf("done, cost: %0.5f ms\n\n", XdbSearcher::now() - $now);
|
|
||||||
|
|
||||||
|
|
||||||
printf("testing loadContent ... \n");
|
|
||||||
$now = XdbSearcher::now();
|
|
||||||
testLoadContent();
|
|
||||||
printf("done, cost: %0.5f ms\n\n", XdbSearcher::now() - $now);
|
|
||||||
|
|
@ -0,0 +1,486 @@
|
||||||
|
<?php
|
||||||
|
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a Apache2.0-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
//
|
||||||
|
// @Author Lion <chenxin619315@gmail.com>
|
||||||
|
// @Date 2022/06/21
|
||||||
|
|
||||||
|
namespace ip2region\xdb;
|
||||||
|
use \Exception;
|
||||||
|
|
||||||
|
// global constants
|
||||||
|
const Structure_20 = 2;
|
||||||
|
const Structure_30 = 3;
|
||||||
|
const IPv4VersionNo = 4;
|
||||||
|
const IPv6VersionNo = 6;
|
||||||
|
const HeaderInfoLength = 256;
|
||||||
|
const VectorIndexRows = 256;
|
||||||
|
const VectorIndexCols = 256;
|
||||||
|
const VectorIndexSize = 8;
|
||||||
|
|
||||||
|
|
||||||
|
// Util class
|
||||||
|
class Util {
|
||||||
|
// parse the specified IP address and return its bytes.
|
||||||
|
// returns: NULL for failed or the packed bytes
|
||||||
|
public static function parseIP($ipString) {
|
||||||
|
$flag = FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6;
|
||||||
|
if (!filter_var($ipString, FILTER_VALIDATE_IP, $flag)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inet_pton($ipString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP bytes to string
|
||||||
|
public static function ipToString($ipBytes) {
|
||||||
|
$l = strlen($ipBytes);
|
||||||
|
return ($l == 4 || $l == 16) ? inet_ntop($ipBytes) : '<invalid-ip-bytes>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare two ip bytes (packed string return by parsedIP)
|
||||||
|
// returns: -1 if ip1 < ip2, 0 if ip1 == ip2 or 1 if ip1 > ip2
|
||||||
|
public static function ipSubCompare($ip1, $buff, $offset) {
|
||||||
|
$r = substr_compare($ip1, $buff, $offset, strlen($ip1));
|
||||||
|
if ($r < 0) {
|
||||||
|
return -1;
|
||||||
|
} else if ($r > 0) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns: -1 if ip1 < ip2, 0 if ip1 == ip2 or 1 if ip1 > ip2
|
||||||
|
public static function ipCompare($ip1, $ip2) {
|
||||||
|
$r = strcmp($ip1, $ip2);
|
||||||
|
if ($r < 0) {
|
||||||
|
return -1;
|
||||||
|
} else if ($r > 0) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode a 4bytes long with Little endian byte order from a byte buffer
|
||||||
|
public static function le_getUint32($b, $idx) {
|
||||||
|
$val = (ord($b[$idx])) | (ord($b[$idx+1]) << 8)
|
||||||
|
| (ord($b[$idx+2]) << 16) | (ord($b[$idx+3]) << 24);
|
||||||
|
|
||||||
|
// convert signed int to unsigned int if on 32 bit operating system
|
||||||
|
if ($val < 0 && PHP_INT_SIZE == 4) {
|
||||||
|
$val = sprintf("%u", $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read a 2bytes int with litten endian byte order from a byte buffer
|
||||||
|
public static function le_getUint16($b, $idx) {
|
||||||
|
return ((ord($b[$idx])) | (ord($b[$idx+1]) << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
// load header info from a specified file handle
|
||||||
|
public static function loadHeader($handle) {
|
||||||
|
if (fseek($handle, 0) == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buff = fread($handle, HeaderInfoLength);
|
||||||
|
if ($buff === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read bytes length checking
|
||||||
|
if (strlen($buff) != HeaderInfoLength) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the decoded header info
|
||||||
|
return array(
|
||||||
|
'version' => self::le_getUint16($buff, 0),
|
||||||
|
'indexPolicy' => self::le_getUint16($buff, 2),
|
||||||
|
'createdAt' => self::le_getUint32($buff, 4),
|
||||||
|
'startIndexPtr' => self::le_getUint32($buff, 8),
|
||||||
|
'endIndexPtr' => self::le_getUint32($buff, 12),
|
||||||
|
'ipVersion' => self::le_getUint16($buff, 16),
|
||||||
|
'runtimePtrBytes' => self::le_getUint16($buff, 18)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load header info from the specified xdb file path
|
||||||
|
public static function loadHeaderFromFile($dbFile) {
|
||||||
|
$handle = fopen($dbFile, 'r');
|
||||||
|
if ($handle === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = self::loadHeader($handle);
|
||||||
|
fclose($handle);
|
||||||
|
return $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load vector index from a file handle
|
||||||
|
public static function loadVectorIndex($handle) {
|
||||||
|
if (fseek($handle, HeaderInfoLength) == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rLen = VectorIndexRows * VectorIndexCols * VectorIndexSize;
|
||||||
|
$buff = fread($handle, $rLen);
|
||||||
|
if ($buff === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($buff) != $rLen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load vector index from a specified xdb file path
|
||||||
|
public static function loadVectorIndexFromFile($dbFile) {
|
||||||
|
$handle = fopen($dbFile, 'r');
|
||||||
|
if ($handle === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$vIndex = self::loadVectorIndex($handle);
|
||||||
|
fclose($handle);
|
||||||
|
return $vIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the xdb content from a file handle
|
||||||
|
public static function loadContent($handle) {
|
||||||
|
if (fseek($handle, 0, SEEK_END) == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = ftell($handle);
|
||||||
|
if ($size === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// seek to the head for reading
|
||||||
|
if (fseek($handle, 0) == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buff = fread($handle, $size);
|
||||||
|
if ($buff === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read length checking
|
||||||
|
if (strlen($buff) != $size) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the xdb content from a file path
|
||||||
|
public static function loadContentFromFile($dbFile) {
|
||||||
|
$str = file_get_contents($dbFile, false);
|
||||||
|
if ($str === false) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function now() {
|
||||||
|
return (microtime(true) * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv4 version class
|
||||||
|
class IPv4 {
|
||||||
|
public $id;
|
||||||
|
public $name;
|
||||||
|
public $bytes;
|
||||||
|
public $segmentIndexSize;
|
||||||
|
|
||||||
|
public static function default() {
|
||||||
|
// 14 = 4 + 4 + 2 + 4
|
||||||
|
return new self(IPv4VersionNo, 'IPv4', 4, 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct($id, $name, $bytes, $segmentIndexSize) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->bytes = $bytes;
|
||||||
|
$this->segmentIndexSize = $segmentIndexSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare the two ip bytes with the current version
|
||||||
|
public function ipSubCompare($ip1, $buff, $offset) {
|
||||||
|
// ip1: Little endian byte order encoded long from searcher.
|
||||||
|
// ip2: Little endian byte order read from xdb index.
|
||||||
|
// @Note: to compatible with the old Litten endian index encode implementation.
|
||||||
|
$ip2 = (
|
||||||
|
(ord($buff[$offset ]) << 24) |
|
||||||
|
(ord($buff[$offset+1]) << 16) |
|
||||||
|
(ord($buff[$offset+2]) << 8) | ord($buff[$offset+3])
|
||||||
|
);
|
||||||
|
|
||||||
|
$r = $ip1 - $ip2;
|
||||||
|
if ($r > 0) {
|
||||||
|
return 1;
|
||||||
|
} else if ($r < 0) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString() {
|
||||||
|
return sprintf(
|
||||||
|
"{id:%d, name:%s, bytes:%d, segmentIndexSize:%d}",
|
||||||
|
$this->id, $this->name, $this->bytes, $this->segmentIndexSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IPv6 {
|
||||||
|
public $id;
|
||||||
|
public $name;
|
||||||
|
public $bytes;
|
||||||
|
public $segmentIndexSize;
|
||||||
|
|
||||||
|
public static function default() {
|
||||||
|
// 38 = 16 + 16 + 2 + 4
|
||||||
|
return new self(IPv6VersionNo, 'IPv6', 16, 38);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct($id, $name, $bytes, $segmentIndexSize) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->bytes = $bytes;
|
||||||
|
$this->segmentIndexSize = $segmentIndexSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ipSubCompare($ip, $buff, $offset) {
|
||||||
|
return Util::ipSubCompare($ip, $buff, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString() {
|
||||||
|
return sprintf(
|
||||||
|
"{id:%d, name:%s, bytes:%d, segmentIndexSize:%d}",
|
||||||
|
$this->id, $this->name, $this->bytes, $this->segmentIndexSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Xdb searcher implementation
|
||||||
|
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 = Util::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 = Util::le_getUint32($this->vectorIndex, $idx);
|
||||||
|
$ePtr = Util::le_getUint32($this->vectorIndex, $idx + 4);
|
||||||
|
} else if ($this->contentBuff != null) {
|
||||||
|
$sPtr = Util::le_getUint32($this->contentBuff, HeaderInfoLength + $idx);
|
||||||
|
$ePtr = Util::le_getUint32($this->contentBuff, HeaderInfoLength + $idx + 4);
|
||||||
|
} else {
|
||||||
|
// read the vector index block
|
||||||
|
$buff = $this->read(HeaderInfoLength + $idx, 8);
|
||||||
|
if ($buff === null) {
|
||||||
|
throw new Exception("failed to read vector index at ${idx}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sPtr = Util::le_getUint32($buff, 0);
|
||||||
|
$ePtr = Util::le_getUint32($buff, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// printf("sPtr: %d, ePtr: %d\n", $sPtr, $ePtr);
|
||||||
|
[$bytes, $dBytes] = [strlen($ipBytes), strlen($ipBytes) << 1];
|
||||||
|
if ($bytes == 4) {
|
||||||
|
// encode the IPv4 bytes to an long with Litten endian byte order
|
||||||
|
// to avoid the repeated calcs in the binary search loop.
|
||||||
|
$ipBytes = (
|
||||||
|
(ord($ipBytes[3]) << 24) |
|
||||||
|
(ord($ipBytes[2]) << 24) |
|
||||||
|
(ord($ipBytes[1]) << 24) | (ord($ipBytes[0]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// binary search the segment index to get the region info
|
||||||
|
$idxSize = $this->version->segmentIndexSize;
|
||||||
|
[$dataLen, $dataPtr] = [0, null];
|
||||||
|
[$l, $h] = [0, ($ePtr - $sPtr) / $idxSize];
|
||||||
|
while ($l <= $h) {
|
||||||
|
$m = ($l + $h) >> 1;
|
||||||
|
$p = $sPtr + $m * $idxSize;
|
||||||
|
|
||||||
|
// read the segment index
|
||||||
|
$buff = $this->read($p, $idxSize);
|
||||||
|
if ($buff == null) {
|
||||||
|
throw new Exception("failed to read segment index with ptr={$p}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = Util::le_getUint16($buff, $dBytes);
|
||||||
|
$dataPtr = Util::le_getUint32($buff, $dBytes + 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match nothing interception.
|
||||||
|
// @TODO: could this even be a case ?
|
||||||
|
// printf("dataLen: %d, dataPtr: %d\n", $dataLen, $dataPtr);
|
||||||
|
if ($dataPtr == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load and return the region data
|
||||||
|
$buff = $this->read($dataPtr, $dataLen);
|
||||||
|
if ($buff == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ioCount++;
|
||||||
|
$buff = fread($this->handle, $len);
|
||||||
|
if ($buff === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($buff) != $len) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buff;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
<?php
|
||||||
|
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a Apache2.0-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
//
|
||||||
|
// @Author Lion <chenxin619315@gmail.com>
|
||||||
|
// @Date 2022/06/22
|
||||||
|
|
||||||
|
require dirname(__FILE__) . '/Searcher.class.php';
|
||||||
|
use \ip2region\xdb\Util;
|
||||||
|
use \ip2region\xdb\Searcher;
|
||||||
|
use \ip2region\xdb\IPv4;
|
||||||
|
use \ip2region\xdb\IPv6;
|
||||||
|
|
||||||
|
// check and get the function to run
|
||||||
|
if($argc < 2) {
|
||||||
|
printf("please specified the function name\n");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
$func_name = trim($argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function testLoadHeader() {
|
||||||
|
$header = Util::loadHeaderFromFile('../../data/ip2region_v4.xdb');
|
||||||
|
if ($header == null) {
|
||||||
|
printf("failed to load header from file\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("header loaded: ");
|
||||||
|
print_r($header);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testLoadVectorIndex() {
|
||||||
|
$vIndex = Util::loadVectorIndexFromFile('../../data/ip2region_v4.xdb');
|
||||||
|
if ($vIndex == null) {
|
||||||
|
printf("failed to load vector index from file\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("vector index loaded: length=%d\n", strlen($vIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testLoadContent() {
|
||||||
|
$cBuff = Util::loadContentFromFile('../../data/ip2region_v4.xdb');
|
||||||
|
if ($cBuff == null) {
|
||||||
|
printf("failed to load content from file\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("content loaded, length=%d\n", strlen($cBuff));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testParseIP() {
|
||||||
|
$ips = [
|
||||||
|
// IPv4
|
||||||
|
"1.0.0.1",
|
||||||
|
"192.168.1.100",
|
||||||
|
"121.35.184.170",
|
||||||
|
|
||||||
|
"xx.xx.1.100",
|
||||||
|
|
||||||
|
// IPv6
|
||||||
|
"3000::",
|
||||||
|
"240e:87c:71a:639a:3dff:ffff:ffff:ffff",
|
||||||
|
"240e:87c:71a:c877:900::"
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($ips as $ip) {
|
||||||
|
$bytes = Util::parseIP($ip);
|
||||||
|
if ($bytes == NULL) {
|
||||||
|
printf("invalid ip address: `%s`\n", $ip);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for ($i = 0; $i < strlen($bytes); $i++) {
|
||||||
|
// printf("%d: []: %s, ord: %d\n", $i, $bytes[$i], ord($bytes[$i]));
|
||||||
|
// }
|
||||||
|
|
||||||
|
printf("bytes: %s (%s), address: %s\n", bin2hex($bytes), gettype($bytes), Util::ipToString($bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testIPCompare() {
|
||||||
|
$ipPairs = [
|
||||||
|
["1.0.0.0", "1.0.0.1"],
|
||||||
|
["192.168.1.101", "192.168.1.90"],
|
||||||
|
["219.133.111.87", "114.114.114.114"],
|
||||||
|
["2000::", "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"],
|
||||||
|
["2001:4:112::", "2001:4:112:ffff:ffff:ffff:ffff:ffff"],
|
||||||
|
["ffff::", "2001:4:ffff:ffff:ffff:ffff:ffff:ffff"]
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($ipPairs as $ips) {
|
||||||
|
$ip1 = Util::parseIP($ips[0]);
|
||||||
|
$ip2 = Util::parseIP($ips[1]);
|
||||||
|
printf("ipCompare(%s, %s): %d\n", Util::ipToString($ip1), Util::ipToString($ip2), Util::ipSubCompare($ip1, $ip2, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testAttributes() {
|
||||||
|
printf("IPv4VersioNo: %d\n", \ip2region\xdb\IPv4VersionNo);
|
||||||
|
printf("IPv6VersioNo: %d\n", \ip2region\xdb\IPv6VersionNo);
|
||||||
|
printf("IPv4 Object: %s\n", IPv4::default()->toString());
|
||||||
|
printf("IPv6 Object: %s\n", IPv6::default()->toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!function_exists($func_name)) {
|
||||||
|
printf("function {$func_name} not found\n");
|
||||||
|
} else {
|
||||||
|
printf("calling {$func_name} ... \n");
|
||||||
|
$now = Util::now();
|
||||||
|
$func_name();
|
||||||
|
$cost = Util::now() - $now;
|
||||||
|
printf("done, cost: %0.5f ms\n", $cost);
|
||||||
|
}
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# ip2region php5 c 扩展查询客户端实现
|
|
||||||
|
|
||||||
# 使用方式
|
|
||||||
|
|
||||||
# 查询测试
|
|
||||||
|
|
||||||
# bench 测试
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
# ip2region php7 c 扩展查询客户端实现
|
|
||||||
|
|
||||||
# 使用方式
|
|
||||||
|
|
||||||
# 查询测试
|
|
||||||
|
|
||||||
# bench 测试
|
|
||||||
Loading…
Reference in New Issue