Overview

Namespaces

  • GeoIp2
    • Database
    • Exception
    • Model
    • Record
    • WebService
  • MaxMind
    • Db
      • Reader
  • PHP

Classes

  • GeoIp2\Database\Reader
  • GeoIp2\Model\AnonymousIp
  • GeoIp2\Model\Asn
  • GeoIp2\Model\City
  • GeoIp2\Model\ConnectionType
  • GeoIp2\Model\Country
  • GeoIp2\Model\Domain
  • GeoIp2\Model\Enterprise
  • GeoIp2\Model\Insights
  • GeoIp2\Model\Isp
  • GeoIp2\Record\AbstractPlaceRecord
  • GeoIp2\Record\AbstractRecord
  • GeoIp2\Record\City
  • GeoIp2\Record\Continent
  • GeoIp2\Record\Country
  • GeoIp2\Record\Location
  • GeoIp2\Record\MaxMind
  • GeoIp2\Record\Postal
  • GeoIp2\Record\RepresentedCountry
  • GeoIp2\Record\Subdivision
  • GeoIp2\Record\Traits
  • GeoIp2\WebService\Client
  • MaxMind\Db\Reader
  • MaxMind\Db\Reader\Decoder
  • MaxMind\Db\Reader\Metadata
  • MaxMind\Db\Reader\Util

Interfaces

  • GeoIp2\ProviderInterface
  • JsonSerializable
  • Throwable

Exceptions

  • BadFunctionCallException
  • BadMethodCallException
  • Exception
  • GeoIp2\Exception\AddressNotFoundException
  • GeoIp2\Exception\AuthenticationException
  • GeoIp2\Exception\GeoIp2Exception
  • GeoIp2\Exception\HttpException
  • GeoIp2\Exception\InvalidRequestException
  • GeoIp2\Exception\OutOfQueriesException
  • InvalidArgumentException
  • LogicException
  • MaxMind\Db\Reader\InvalidDatabaseException
  • Overview
  • Namespace
  • Class
  1: <?php
  2: 
  3: namespace MaxMind\Db\Reader;
  4: 
  5: class Decoder
  6: {
  7:     private $fileStream;
  8:     private $pointerBase;
  9:     // This is only used for unit testing
 10:     private $pointerTestHack;
 11:     private $switchByteOrder;
 12: 
 13:     private $types = [
 14:         0 => 'extended',
 15:         1 => 'pointer',
 16:         2 => 'utf8_string',
 17:         3 => 'double',
 18:         4 => 'bytes',
 19:         5 => 'uint16',
 20:         6 => 'uint32',
 21:         7 => 'map',
 22:         8 => 'int32',
 23:         9 => 'uint64',
 24:         10 => 'uint128',
 25:         11 => 'array',
 26:         12 => 'container',
 27:         13 => 'end_marker',
 28:         14 => 'boolean',
 29:         15 => 'float',
 30:     ];
 31: 
 32:     public function __construct(
 33:         $fileStream,
 34:         $pointerBase = 0,
 35:         $pointerTestHack = false
 36:     ) {
 37:         $this->fileStream = $fileStream;
 38:         $this->pointerBase = $pointerBase;
 39:         $this->pointerTestHack = $pointerTestHack;
 40: 
 41:         $this->switchByteOrder = $this->isPlatformLittleEndian();
 42:     }
 43: 
 44:     public function decode($offset)
 45:     {
 46:         list(, $ctrlByte) = unpack(
 47:             'C',
 48:             Util::read($this->fileStream, $offset, 1)
 49:         );
 50:         $offset++;
 51: 
 52:         $type = $this->types[$ctrlByte >> 5];
 53: 
 54:         // Pointers are a special case, we don't read the next $size bytes, we
 55:         // use the size to determine the length of the pointer and then follow
 56:         // it.
 57:         if ($type === 'pointer') {
 58:             list($pointer, $offset) = $this->decodePointer($ctrlByte, $offset);
 59: 
 60:             // for unit testing
 61:             if ($this->pointerTestHack) {
 62:                 return [$pointer];
 63:             }
 64: 
 65:             list($result) = $this->decode($pointer);
 66: 
 67:             return [$result, $offset];
 68:         }
 69: 
 70:         if ($type === 'extended') {
 71:             list(, $nextByte) = unpack(
 72:                 'C',
 73:                 Util::read($this->fileStream, $offset, 1)
 74:             );
 75: 
 76:             $typeNum = $nextByte + 7;
 77: 
 78:             if ($typeNum < 8) {
 79:                 throw new InvalidDatabaseException(
 80:                     'Something went horribly wrong in the decoder. An extended type '
 81:                     . 'resolved to a type number < 8 ('
 82:                     . $this->types[$typeNum]
 83:                     . ')'
 84:                 );
 85:             }
 86: 
 87:             $type = $this->types[$typeNum];
 88:             $offset++;
 89:         }
 90: 
 91:         list($size, $offset) = $this->sizeFromCtrlByte($ctrlByte, $offset);
 92: 
 93:         return $this->decodeByType($type, $offset, $size);
 94:     }
 95: 
 96:     private function decodeByType($type, $offset, $size)
 97:     {
 98:         switch ($type) {
 99:             case 'map':
100:                 return $this->decodeMap($size, $offset);
101:             case 'array':
102:                 return $this->decodeArray($size, $offset);
103:             case 'boolean':
104:                 return [$this->decodeBoolean($size), $offset];
105:         }
106: 
107:         $newOffset = $offset + $size;
108:         $bytes = Util::read($this->fileStream, $offset, $size);
109:         switch ($type) {
110:             case 'utf8_string':
111:                 return [$this->decodeString($bytes), $newOffset];
112:             case 'double':
113:                 $this->verifySize(8, $size);
114: 
115:                 return [$this->decodeDouble($bytes), $newOffset];
116:             case 'float':
117:                 $this->verifySize(4, $size);
118: 
119:                 return [$this->decodeFloat($bytes), $newOffset];
120:             case 'bytes':
121:                 return [$bytes, $newOffset];
122:             case 'uint16':
123:             case 'uint32':
124:                 return [$this->decodeUint($bytes), $newOffset];
125:             case 'int32':
126:                 return [$this->decodeInt32($bytes), $newOffset];
127:             case 'uint64':
128:             case 'uint128':
129:                 return [$this->decodeBigUint($bytes, $size), $newOffset];
130:             default:
131:                 throw new InvalidDatabaseException(
132:                     'Unknown or unexpected type: ' . $type
133:                 );
134:         }
135:     }
136: 
137:     private function verifySize($expected, $actual)
138:     {
139:         if ($expected !== $actual) {
140:             throw new InvalidDatabaseException(
141:                 "The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
142:             );
143:         }
144:     }
145: 
146:     private function decodeArray($size, $offset)
147:     {
148:         $array = [];
149: 
150:         for ($i = 0; $i < $size; $i++) {
151:             list($value, $offset) = $this->decode($offset);
152:             array_push($array, $value);
153:         }
154: 
155:         return [$array, $offset];
156:     }
157: 
158:     private function decodeBoolean($size)
159:     {
160:         return $size === 0 ? false : true;
161:     }
162: 
163:     private function decodeDouble($bits)
164:     {
165:         // XXX - Assumes IEEE 754 double on platform
166:         list(, $double) = unpack('d', $this->maybeSwitchByteOrder($bits));
167: 
168:         return $double;
169:     }
170: 
171:     private function decodeFloat($bits)
172:     {
173:         // XXX - Assumes IEEE 754 floats on platform
174:         list(, $float) = unpack('f', $this->maybeSwitchByteOrder($bits));
175: 
176:         return $float;
177:     }
178: 
179:     private function decodeInt32($bytes)
180:     {
181:         $bytes = $this->zeroPadLeft($bytes, 4);
182:         list(, $int) = unpack('l', $this->maybeSwitchByteOrder($bytes));
183: 
184:         return $int;
185:     }
186: 
187:     private function decodeMap($size, $offset)
188:     {
189:         $map = [];
190: 
191:         for ($i = 0; $i < $size; $i++) {
192:             list($key, $offset) = $this->decode($offset);
193:             list($value, $offset) = $this->decode($offset);
194:             $map[$key] = $value;
195:         }
196: 
197:         return [$map, $offset];
198:     }
199: 
200:     private $pointerValueOffset = [
201:         1 => 0,
202:         2 => 2048,
203:         3 => 526336,
204:         4 => 0,
205:     ];
206: 
207:     private function decodePointer($ctrlByte, $offset)
208:     {
209:         $pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
210: 
211:         $buffer = Util::read($this->fileStream, $offset, $pointerSize);
212:         $offset = $offset + $pointerSize;
213: 
214:         $packed = $pointerSize === 4
215:             ? $buffer
216:             : (pack('C', $ctrlByte & 0x7)) . $buffer;
217: 
218:         $unpacked = $this->decodeUint($packed);
219:         $pointer = $unpacked + $this->pointerBase
220:             + $this->pointerValueOffset[$pointerSize];
221: 
222:         return [$pointer, $offset];
223:     }
224: 
225:     private function decodeUint($bytes)
226:     {
227:         list(, $int) = unpack('N', $this->zeroPadLeft($bytes, 4));
228: 
229:         return $int;
230:     }
231: 
232:     private function decodeBigUint($bytes, $byteLength)
233:     {
234:         $maxUintBytes = log(PHP_INT_MAX, 2) / 8;
235: 
236:         if ($byteLength === 0) {
237:             return 0;
238:         }
239: 
240:         $numberOfLongs = ceil($byteLength / 4);
241:         $paddedLength = $numberOfLongs * 4;
242:         $paddedBytes = $this->zeroPadLeft($bytes, $paddedLength);
243:         $unpacked = array_merge(unpack("N$numberOfLongs", $paddedBytes));
244: 
245:         $integer = 0;
246: 
247:         // 2^32
248:         $twoTo32 = '4294967296';
249: 
250:         foreach ($unpacked as $part) {
251:             // We only use gmp or bcmath if the final value is too big
252:             if ($byteLength <= $maxUintBytes) {
253:                 $integer = ($integer << 32) + $part;
254:             } elseif (extension_loaded('gmp')) {
255:                 $integer = gmp_strval(gmp_add(gmp_mul($integer, $twoTo32), $part));
256:             } elseif (extension_loaded('bcmath')) {
257:                 $integer = bcadd(bcmul($integer, $twoTo32), $part);
258:             } else {
259:                 throw new \RuntimeException(
260:                     'The gmp or bcmath extension must be installed to read this database.'
261:                 );
262:             }
263:         }
264: 
265:         return $integer;
266:     }
267: 
268:     private function decodeString($bytes)
269:     {
270:         // XXX - NOOP. As far as I know, the end user has to explicitly set the
271:         // encoding in PHP. Strings are just bytes.
272:         return $bytes;
273:     }
274: 
275:     private function sizeFromCtrlByte($ctrlByte, $offset)
276:     {
277:         $size = $ctrlByte & 0x1f;
278:         $bytesToRead = $size < 29 ? 0 : $size - 28;
279:         $bytes = Util::read($this->fileStream, $offset, $bytesToRead);
280:         $decoded = $this->decodeUint($bytes);
281: 
282:         if ($size === 29) {
283:             $size = 29 + $decoded;
284:         } elseif ($size === 30) {
285:             $size = 285 + $decoded;
286:         } elseif ($size > 30) {
287:             $size = ($decoded & (0x0FFFFFFF >> (32 - (8 * $bytesToRead))))
288:                 + 65821;
289:         }
290: 
291:         return [$size, $offset + $bytesToRead];
292:     }
293: 
294:     private function zeroPadLeft($content, $desiredLength)
295:     {
296:         return str_pad($content, $desiredLength, "\x00", STR_PAD_LEFT);
297:     }
298: 
299:     private function maybeSwitchByteOrder($bytes)
300:     {
301:         return $this->switchByteOrder ? strrev($bytes) : $bytes;
302:     }
303: 
304:     private function isPlatformLittleEndian()
305:     {
306:         $testint = 0x00FF;
307:         $packed = pack('S', $testint);
308: 
309:         return $testint === current(unpack('v', $packed));
310:     }
311: }
312: 
GeoIP2 PHP API v2.9.0 API documentation generated by ApiGen