1
+ <?php
2
+
3
+ namespace ipip \db ;
4
+
5
+ class Reader
6
+ {
7
+ const IPV4 = 1 ;
8
+ const IPV6 = 2 ;
9
+
10
+ private $ file = NULL ;
11
+ private $ fileSize = 0 ;
12
+ private $ nodeCount = 0 ;
13
+ private $ nodeOffset = 0 ;
14
+
15
+ private $ meta = [];
16
+
17
+ private $ database = '' ;
18
+
19
+ /**
20
+ * Reader constructor.
21
+ * @param $database
22
+ * @throws \Exception
23
+ */
24
+ public function __construct ($ database )
25
+ {
26
+ $ this ->database = $ database ;
27
+
28
+ $ this ->init ();
29
+ }
30
+
31
+ private function init ()
32
+ {
33
+ if (is_readable ($ this ->database ) === FALSE )
34
+ {
35
+ throw new \InvalidArgumentException ("The IP Database file \"{$ this ->database }\" does not exist or is not readable. " );
36
+ }
37
+ $ this ->file = @fopen ($ this ->database , 'rb ' );
38
+ if ($ this ->file === FALSE )
39
+ {
40
+ throw new \InvalidArgumentException ("IP Database File opening \"{$ this ->database }\". " );
41
+ }
42
+ $ this ->fileSize = @filesize ($ this ->database );
43
+ if ($ this ->fileSize === FALSE )
44
+ {
45
+ throw new \UnexpectedValueException ("Error determining the size of \"{$ this ->database }\". " );
46
+ }
47
+
48
+ $ metaLength = unpack ('N ' , fread ($ this ->file , 4 ))[1 ];
49
+ $ text = fread ($ this ->file , $ metaLength );
50
+
51
+ $ this ->meta = json_decode ($ text , 1 );
52
+ if (!isset ($ this ->meta ['fields ' ]) || !isset ($ this ->meta ['languages ' ]))
53
+ {
54
+ throw new \Exception ('IP Database metadata error. ' );
55
+ }
56
+
57
+ $ fileSize = 4 + $ metaLength + $ this ->meta ['total_size ' ];
58
+ if ($ fileSize != $ this ->fileSize )
59
+ {
60
+ throw new \Exception ('IP Database size error. ' );
61
+ }
62
+
63
+ $ this ->nodeCount = $ this ->meta ['node_count ' ];
64
+ $ this ->nodeOffset = 4 + $ metaLength ;
65
+ }
66
+
67
+ /**
68
+ * @param $ip
69
+ * @param string $language
70
+ * @return array|NULL
71
+ */
72
+ public function find ($ ip , $ language = 'CN ' )
73
+ {
74
+ if (is_resource ($ this ->file ) === FALSE )
75
+ {
76
+ throw new \BadMethodCallException ('closed IPIP DB. ' );
77
+ }
78
+
79
+ if (!isset ($ this ->meta ['languages ' ][$ language ]))
80
+ {
81
+ throw new \InvalidArgumentException ("language : {$ language } not support " );
82
+ }
83
+
84
+ if (filter_var ($ ip , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 ) === FALSE )
85
+ {
86
+ throw new \InvalidArgumentException ("The value \"$ ip \" is not a valid IP address. " );
87
+ }
88
+
89
+ if (strpos ($ ip , '. ' ) !== FALSE )
90
+ {
91
+ if (!$ this ->supportV4 ())
92
+ {
93
+
94
+ }
95
+ }
96
+ elseif (strpos ($ ip , ': ' ) !== FALSE )
97
+ {
98
+ if (!$ this ->supportV6 ())
99
+ {
100
+
101
+ }
102
+ }
103
+
104
+ try
105
+ {
106
+ $ node = $ this ->findNode ($ ip );
107
+ if ($ node > 0 )
108
+ {
109
+ $ data = $ this ->resolve ($ node );
110
+
111
+ $ values = explode ("\t" , $ data );
112
+
113
+ return array_slice ($ values , $ this ->meta ['languages ' ][$ language ], count ($ this ->meta ['fields ' ]));
114
+ }
115
+ }
116
+ catch (\Exception $ e )
117
+ {
118
+ return NULL ;
119
+ }
120
+
121
+ return NULL ;
122
+ }
123
+
124
+ public function findMap ($ ip , $ language = 'CN ' )
125
+ {
126
+ $ array = $ this ->find ($ ip , $ language );
127
+ if (NULL == $ array )
128
+ {
129
+ return NULL ;
130
+ }
131
+
132
+ return array_combine ($ this ->meta ['fields ' ], $ array );
133
+ }
134
+
135
+ public function findInfo ($ ip , $ language = 'CN ' )
136
+ {
137
+ $ map = $ this ->findMap ($ ip , $ language );
138
+ if (NULL == $ map )
139
+ {
140
+ return NULL ;
141
+ }
142
+
143
+ return new Info ($ map );
144
+ }
145
+
146
+ /**
147
+ * @param $ip
148
+ * @return int
149
+ * @throws \Exception
150
+ */
151
+ private function findNode ($ ip )
152
+ {
153
+ static $ v4offset = 0 ;
154
+ static $ v6offsetCache = [];
155
+
156
+ $ binary = inet_pton ($ ip );
157
+ $ bitCount = strlen ($ binary ) * 8 ; // 32 | 128
158
+ $ key = substr ($ binary , 0 , 2 );
159
+ $ node = 0 ;
160
+ $ index = 0 ;
161
+ if ($ bitCount === 32 )
162
+ {
163
+ if ($ v4offset === 0 )
164
+ {
165
+ for ($ i = 0 ; $ i < 96 && $ node < $ this ->nodeCount ; $ i ++)
166
+ {
167
+ if ($ i >= 80 )
168
+ {
169
+ $ idx = 1 ;
170
+ }
171
+ else
172
+ {
173
+ $ idx = 0 ;
174
+ }
175
+ $ node = $ this ->readNode ($ node , $ idx );
176
+ if ($ node > $ this ->nodeCount )
177
+ {
178
+ return 0 ;
179
+ }
180
+ }
181
+ $ v4offset = $ node ;
182
+ }
183
+ else
184
+ {
185
+ $ node = $ v4offset ;
186
+ }
187
+ }
188
+ else
189
+ {
190
+ if (isset ($ v6offsetCache [$ key ]))
191
+ {
192
+ $ index = 16 ;
193
+ $ node = $ v6offsetCache [$ key ];
194
+ }
195
+ }
196
+
197
+ for ($ i = $ index ; $ i < $ bitCount ; $ i ++)
198
+ {
199
+ if ($ node >= $ this ->nodeCount )
200
+ {
201
+ break ;
202
+ }
203
+
204
+ $ node = $ this ->readNode ($ node , 1 & ((0xFF & ord ($ binary [$ i >> 3 ])) >> 7 - ($ i % 8 )));
205
+
206
+ if ($ i == 15 )
207
+ {
208
+ $ v6offsetCache [$ key ] = $ node ;
209
+ }
210
+ }
211
+
212
+ if ($ node === $ this ->nodeCount )
213
+ {
214
+ return 0 ;
215
+ }
216
+ elseif ($ node > $ this ->nodeCount )
217
+ {
218
+ return $ node ;
219
+ }
220
+
221
+ throw new \Exception ("find node failed " );
222
+ }
223
+
224
+ /**
225
+ * @param $node
226
+ * @param $index
227
+ * @return mixed
228
+ * @throws \Exception
229
+ */
230
+ private function readNode ($ node , $ index )
231
+ {
232
+ return unpack ('N ' , $ this ->read ($ this ->file , $ node * 8 + $ index * 4 , 4 ))[1 ];
233
+ }
234
+
235
+ /**
236
+ * @param $node
237
+ * @return mixed
238
+ * @throws \Exception
239
+ */
240
+ private function resolve ($ node )
241
+ {
242
+ $ resolved = $ node - $ this ->nodeCount + $ this ->nodeCount * 8 ;
243
+ if ($ resolved >= $ this ->fileSize )
244
+ {
245
+ return NULL ;
246
+ }
247
+
248
+ $ bytes = $ this ->read ($ this ->file , $ resolved , 2 );
249
+ $ size = unpack ('N ' , str_pad ($ bytes , 4 , "\x00" , STR_PAD_LEFT ))[1 ];
250
+
251
+ $ resolved += 2 ;
252
+
253
+ return $ this ->read ($ this ->file , $ resolved , $ size );
254
+ }
255
+
256
+ public function close ()
257
+ {
258
+ if (is_resource ($ this ->file ) === TRUE )
259
+ {
260
+ fclose ($ this ->file );
261
+ }
262
+ }
263
+
264
+ /**
265
+ * @param $stream
266
+ * @param $offset
267
+ * @param $length
268
+ * @return bool|string
269
+ * @throws \Exception
270
+ */
271
+ private function read ($ stream , $ offset , $ length )
272
+ {
273
+ if ($ length > 0 )
274
+ {
275
+ if (fseek ($ stream , $ offset + $ this ->nodeOffset ) === 0 )
276
+ {
277
+ $ value = fread ($ stream , $ length );
278
+ if (strlen ($ value ) === $ length )
279
+ {
280
+ return $ value ;
281
+ }
282
+ }
283
+
284
+ throw new \Exception ("The DB file read bad data " );
285
+ }
286
+
287
+ return '' ;
288
+ }
289
+
290
+ public function supportV6 ()
291
+ {
292
+ return ($ this ->meta ['ip_version ' ] & self ::IPV6 ) === self ::IPV6 ;
293
+ }
294
+
295
+ public function supportV4 ()
296
+ {
297
+ return ($ this ->meta ['ip_version ' ] & self ::IPV4 ) === self ::IPV4 ;
298
+ }
299
+
300
+ public function getMeta ()
301
+ {
302
+ return $ this ->meta ;
303
+ }
304
+
305
+ /**
306
+ * @return int UTC Timestamp
307
+ */
308
+ public function getBuildTime ()
309
+ {
310
+ return $ this ->meta ['build ' ];
311
+ }
312
+ }
0 commit comments