| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- <?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);
- }
- }
|