|
@@ -0,0 +1,285 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace app\extra\ip2region\xdb;
|
|
|
|
|
+
|
|
|
|
|
+use \Exception;
|
|
|
|
|
+
|
|
|
|
|
+// global constants
|
|
|
|
|
+const Structure_20 = 2;
|
|
|
|
|
+const Structure_30 = 3;
|
|
|
|
|
+const HeaderInfoLength = 256;
|
|
|
|
|
+const VectorIndexRows = 256;
|
|
|
|
|
+const VectorIndexCols = 256;
|
|
|
|
|
+const VectorIndexSize = 8;
|
|
|
|
|
+
|
|
|
|
|
+class SearchIp
|
|
|
|
|
+{
|
|
|
|
|
+ // 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));
|
|
|
|
|
+ // @Note: substr_compare is not working, use the substr + strcmp instead
|
|
|
|
|
+ $r = strcmp($ip1, substr($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;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // version parse
|
|
|
|
|
+ public static function versionFromName($ver_name) {
|
|
|
|
|
+ $name = strtoupper($ver_name);
|
|
|
|
|
+ if ($name == "V4" || $name == "IPv4") {
|
|
|
|
|
+ return IPv4::default();
|
|
|
|
|
+ } else if ($name == "V6" || $name == "IPv6") {
|
|
|
|
|
+ return IPv6::default();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new Exception("invalid verstion name `{$ver_name}`");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // version parse from header
|
|
|
|
|
+ public static function versionFromHeader($header) {
|
|
|
|
|
+ // Old structure 2.0 with IPv4 supports ONLY
|
|
|
|
|
+ if ($header['version'] == Structure_20) {
|
|
|
|
|
+ return IPv4::default();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // structure 3.0 after IPv6 supporting
|
|
|
|
|
+ if ($header['version'] != Structure_30) {
|
|
|
|
|
+ throw new Exception("invalid xdb structure version `{$header['version']}`");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($header['ipVersion'] == IPv4VersionNo) {
|
|
|
|
|
+ return IPv4::default();
|
|
|
|
|
+ } else if ($header['ipVersion'] == IPv6VersionNo) {
|
|
|
|
|
+ return IPv6::default();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new Exception("invalid ip version number `{$header['ipVersion']}`");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // binary string chars implode with space
|
|
|
|
|
+ public static function bytesToString($buff, $offset, $length) {
|
|
|
|
|
+ $sb = [];
|
|
|
|
|
+ for ($i = 0; $i < $length; $i++) {
|
|
|
|
|
+ $sb[] = ord($buff[$offset+$i]) & 0xFF;
|
|
|
|
|
+ }
|
|
|
|
|
+ return '['.implode(' ', $sb).']';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 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));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Verify if the current Searcher could be used to search the specified xdb file.
|
|
|
|
|
+ // Why do we need this check ?
|
|
|
|
|
+ // The future features of the xdb impl may cause the current searcher not able to work properly.
|
|
|
|
|
+ //
|
|
|
|
|
+ // @Note: You Just need to check this ONCE when the service starts
|
|
|
|
|
+ // Or use another process (eg, A command) to check once Just to confirm the suitability.
|
|
|
|
|
+ // returns: null for everything is ok or the error string.
|
|
|
|
|
+ public static function verify($handle) {
|
|
|
|
|
+ // load the header
|
|
|
|
|
+ $header = self::loadHeader($handle);
|
|
|
|
|
+ if ($header == null) {
|
|
|
|
|
+ return 'failed to load the header';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // get the runtime ptr bytes
|
|
|
|
|
+ $runtimePtrBytes = 0;
|
|
|
|
|
+ if ($header['version'] == Structure_20) {
|
|
|
|
|
+ $runtimePtrBytes = 4;
|
|
|
|
|
+ } else if ($header['version'] == Structure_30) {
|
|
|
|
|
+ $runtimePtrBytes = $header['runtimePtrBytes'];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return "invalid structure version `{$header['version']}`";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1, confirm the xdb file size
|
|
|
|
|
+ // to ensure that the maximum file pointer does not overflow
|
|
|
|
|
+ $stat = fstat($handle);
|
|
|
|
|
+ if ($stat == false) {
|
|
|
|
|
+ return 'failed to stat the xdb file';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $maxFilePtr = (1 << ($runtimePtrBytes * 8)) - 1;
|
|
|
|
|
+ // print_r([$stat['size'], $maxFilePtr]);
|
|
|
|
|
+ if ($stat['size'] > $maxFilePtr) {
|
|
|
|
|
+ return "xdb file exceeds the maximum supported bytes: {$maxFilePtr}";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static function verifyFromFile($dbFile) {
|
|
|
|
|
+ $handle = fopen($dbFile, 'r');
|
|
|
|
|
+ if ($handle === false) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $r = self::verify($handle);
|
|
|
|
|
+ fclose($handle);
|
|
|
|
|
+ return $r;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 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);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|