4
4
import java .io .File ;
5
5
import java .io .IOException ;
6
6
import java .io .InputStream ;
7
+ import java .net .Inet6Address ;
7
8
import java .net .InetAddress ;
9
+ import java .net .UnknownHostException ;
8
10
import java .nio .ByteBuffer ;
9
11
import java .util .concurrent .ConcurrentHashMap ;
10
12
import java .util .concurrent .atomic .AtomicReference ;
14
16
* addresses can be looked up using the <code>get</code> method.
15
17
*/
16
18
public final class Reader implements Closeable {
19
+ private static final int IPV4_LEN = 4 ;
20
+ private static final int IPV6_LEN = 6 ;
17
21
private static final int DATA_SECTION_SEPARATOR_SIZE = 16 ;
18
22
private static final byte [] METADATA_START_MARKER = {(byte ) 0xAB ,
19
23
(byte ) 0xCD , (byte ) 0xEF , 'M' , 'a' , 'x' , 'M' , 'i' , 'n' , 'd' , '.' ,
@@ -149,6 +153,10 @@ public <T> T get(InetAddress ipAddress, Class<T> cls) throws IOException {
149
153
return getRecord (ipAddress , cls ).getData ();
150
154
}
151
155
156
+ protected int getIpv4Start () {
157
+ return this .ipV4Start ;
158
+ }
159
+
152
160
/**
153
161
* Looks up <code>ipAddress</code> in the MaxMind DB.
154
162
*
@@ -190,7 +198,40 @@ record = this.readNode(buffer, record, bit);
190
198
return new DatabaseRecord <>(dataRecord , ipAddress , pl );
191
199
}
192
200
193
- private BufferHolder getBufferHolder () throws ClosedDatabaseException {
201
+ /**
202
+ * Creates a Networks iterator.
203
+ * Please note that a MaxMind DB may map IPv4 networks into several locations
204
+ * in an IPv6 database. This iterator will iterate over all of these locations
205
+ * separately. To only iterate over the IPv4 networks once, use the
206
+ * SkipAliasedNetworks option.
207
+ *
208
+ * @param <T> the generics class for the type of the data in DatabaseRecord.
209
+ * @param skipAliasedNetworks Enable skipping aliased networks.
210
+ * @return Networks The Networks iterator.
211
+ * @throws BadVersionException Exception for using an IPv6 network in ipv4-only database.
212
+ * @throws ClosedDatabaseException Exception for a closed databased.
213
+ * @throws InvalidDatabaseException Exception for an invalid database.
214
+ */
215
+ public <T > Networks <T > networks (boolean skipAliasedNetworks ) throws
216
+ BadVersionException , ClosedDatabaseException , InvalidDatabaseException {
217
+ try {
218
+ InetAddress ipv4 = InetAddress .getByAddress (new byte [4 ]);
219
+ InetAddress ipv6 = InetAddress .getByAddress (new byte [16 ]);
220
+ Network ipAllV4 = new Network (ipv4 , 0 ); // Mask 32.
221
+ Network ipAllV6 = new Network (ipv6 , 0 ); // Mask 128.
222
+
223
+ if (this .getMetadata ().getIpVersion () == 6 ) {
224
+ return this .networksWithIn (ipAllV6 , skipAliasedNetworks );
225
+ }
226
+ return this .networksWithIn (ipAllV4 , skipAliasedNetworks );
227
+ } catch (UnknownHostException e ) {
228
+ /* This is returned by getByAddress. This should never happen
229
+ as the ipv4 and ipv6 are constants set by us. */
230
+ return null ;
231
+ }
232
+ }
233
+
234
+ protected BufferHolder getBufferHolder () throws ClosedDatabaseException {
194
235
BufferHolder bufferHolder = this .bufferHolderReference .get ();
195
236
if (bufferHolder == null ) {
196
237
throw new ClosedDatabaseException ();
@@ -222,20 +263,98 @@ private int findIpV4StartNode(ByteBuffer buffer)
222
263
return node ;
223
264
}
224
265
225
- private int readNode (ByteBuffer buffer , int nodeNumber , int index )
226
- throws InvalidDatabaseException {
266
+ /**
267
+ * Returns an iterator within the specified network.
268
+ * Please note that a MaxMind DB may map IPv4 networks into several locations
269
+ * in an IPv6 database. This iterator will iterate over all of these locations
270
+ * separately. To only iterate over the IPv4 networks once, use the
271
+ * SkipAliasedNetworks option.
272
+ * @param <T> Represents the data type(e.g., Map, HastMap, etc.).
273
+ * @param network Specifies the network to be iterated.
274
+ * @param skipAliasedNetworks Boolean for skipping aliased networks.
275
+ * @return Networks
276
+ * @throws BadVersionException Exception for using an IPv6 network in ipv4-only database.
277
+ * @throws ClosedDatabaseException Exception for a closed databased.
278
+ * @throws InvalidDatabaseException Exception for an invalid database.
279
+ */
280
+ public <T > Networks <T > networksWithIn (Network network , boolean skipAliasedNetworks )
281
+ throws BadVersionException , ClosedDatabaseException , InvalidDatabaseException {
282
+ InetAddress networkAddress = network .getNetworkAddress ();
283
+ if (this .metadata .getIpVersion () == 4 && networkAddress instanceof Inet6Address ) {
284
+ throw new BadVersionException (networkAddress );
285
+ }
286
+
287
+ byte [] ipBytes = networkAddress .getAddress ();
288
+ int prefixLength = network .getPrefixLength ();
289
+
290
+ if (this .metadata .getIpVersion () == 6 && ipBytes .length == IPV4_LEN ) {
291
+ if (skipAliasedNetworks ) {
292
+ ipBytes = new byte []{0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
293
+ ipBytes [0 ], ipBytes [1 ], ipBytes [2 ], ipBytes [3 ] };
294
+ } else {
295
+ // Convert it to the IP address (in 16-byte from) of the IPv4 address.
296
+ ipBytes = new byte []{0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
297
+ -1 , -1 , // -1 is for 0xff.
298
+ ipBytes [0 ], ipBytes [1 ], ipBytes [2 ], ipBytes [3 ]};
299
+ }
300
+ prefixLength += 96 ;
301
+ }
302
+
303
+ int [] traverseResult = this .traverseTree (ipBytes , 0 , prefixLength );
304
+ int node = traverseResult [0 ];
305
+ int prefix = traverseResult [1 ];
306
+
307
+ Networks <T > networks = new Networks <T >(this , skipAliasedNetworks ,
308
+ new Networks .NetworkNode []{ new Networks .NetworkNode (ipBytes , prefix , node ) });
309
+
310
+ return networks ;
311
+ }
312
+
313
+ /**
314
+ * Returns the node number and the prefix for the network.
315
+ * @param ip The ip address to travese.
316
+ * @param node The number of the node.
317
+ * @param bitCount The prefix.
318
+ * @return int[]
319
+ */
320
+ public int [] traverseTree (byte [] ip , int node , int bitCount )
321
+ throws ClosedDatabaseException , InvalidDatabaseException {
322
+ int nodeCount = this .metadata .getNodeCount ();
323
+ int i = 0 ;
324
+
325
+ ByteBuffer buffer = this .getBufferHolder ().get ();
326
+
327
+ for (; i < bitCount && node < nodeCount ; i ++) {
328
+ int bit = 1 & (ip [i >> 3 ] >> (7 - (i % 8 )));
329
+
330
+ // bit:0 -> left record.
331
+ // bit:1 -> right record.
332
+ node = this .readNode (buffer , node , bit );
333
+ }
334
+
335
+ return new int []{node , i };
336
+ }
337
+
338
+ protected int readNode (ByteBuffer buffer , int nodeNumber , int index )
339
+ throws InvalidDatabaseException {
340
+ // index is the index of the record within the node, which
341
+ // can either be 0 or 1.
227
342
int baseOffset = nodeNumber * this .metadata .getNodeByteSize ();
228
343
229
344
switch (this .metadata .getRecordSize ()) {
230
345
case 24 :
346
+ // For a 24 bit record, each record is 3 bytes.
231
347
buffer .position (baseOffset + index * 3 );
232
348
return Decoder .decodeInteger (buffer , 0 , 3 );
233
349
case 28 :
234
350
int middle = buffer .get (baseOffset + 3 );
235
351
236
352
if (index == 0 ) {
353
+ // We get the most significant from the first half
354
+ // of the byte. It belongs to the first record.
237
355
middle = (0xF0 & middle ) >>> 4 ;
238
356
} else {
357
+ // We get the most significant byte of the second record.
239
358
middle = 0x0F & middle ;
240
359
}
241
360
buffer .position (baseOffset + index * 4 );
@@ -249,7 +368,7 @@ private int readNode(ByteBuffer buffer, int nodeNumber, int index)
249
368
}
250
369
}
251
370
252
- private <T > T resolveDataPointer (
371
+ protected <T > T resolveDataPointer (
253
372
ByteBuffer buffer ,
254
373
int pointer ,
255
374
Class <T > cls
0 commit comments