Skip to content

Commit 47492ef

Browse files
authored
Merge pull request #131 from maxmind/fatih/add-iterator
Add iteration mechanism
2 parents 4e1376e + e59f46d commit 47492ef

File tree

7 files changed

+702
-14
lines changed

7 files changed

+702
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
CHANGELOG
22
=========
33

4+
3.1.0
5+
------------------
6+
* Reader supports iterating over the whole database or within a network.
7+
48
3.0.0 (2022-12-12)
59
------------------
610

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,28 @@ public class Lookup {
120120
}
121121
```
122122

123+
You can also use the reader object to iterate over the database.
124+
The `reader.networks()` and `reader.networksWithin()` methods can
125+
be used for this purpose.
126+
127+
```java
128+
Reader reader = new Reader(file);
129+
Networks networks = reader.networks(Map.class);
130+
131+
while(networks.hasNext()) {
132+
DatabaseRecord<Map<String, String>> iteration = networks.next();
133+
134+
// Get the data.
135+
Map<String, String> data = iteration.getData();
136+
137+
// The IP Address
138+
InetAddress ipAddress = InetAddress.getByName(data.get("ip"));
139+
140+
// ...
141+
}
142+
```
143+
144+
123145
## Caching ##
124146

125147
The database API supports pluggable caching (by default, no caching is
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.maxmind.db;
2+
3+
import java.net.InetAddress;
4+
5+
public class InvalidNetworkException extends Exception {
6+
public InvalidNetworkException(InetAddress ip) {
7+
super("you attempted to use an IPv6 network in an IPv4-only database: " + ip.toString());
8+
}
9+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package com.maxmind.db;
2+
3+
import java.io.IOException;
4+
import java.net.Inet4Address;
5+
import java.net.InetAddress;
6+
import java.nio.ByteBuffer;
7+
import java.util.Arrays;
8+
import java.util.Iterator;
9+
import java.util.Stack;
10+
11+
/**
12+
* Instances of this class provide an iterator over the networks in a database.
13+
* The iterator will return a {@link DatabaseRecord} for each network.
14+
*
15+
* @param <T> The type of data returned by the iterator.
16+
*/
17+
public final class Networks<T> implements Iterator<DatabaseRecord<T>> {
18+
private final Reader reader;
19+
private final Stack<NetworkNode> nodes;
20+
private NetworkNode lastNode;
21+
private final boolean includeAliasedNetworks;
22+
private final ByteBuffer buffer; /* Stores the buffer for Next() calls */
23+
private final Class<T> typeParameterClass;
24+
25+
/**
26+
* Constructs a Networks instance.
27+
* @param reader The reader object.
28+
* @param includeAliasedNetworks The boolean to include aliased networks.
29+
* @param typeParameterClass The type of data returned by the iterator.
30+
* @throws ClosedDatabaseException Exception for a closed database.
31+
*/
32+
Networks(Reader reader, boolean includeAliasedNetworks, Class<T> typeParameterClass)
33+
throws ClosedDatabaseException {
34+
this(reader, includeAliasedNetworks, new NetworkNode[]{}, typeParameterClass);
35+
}
36+
37+
/**
38+
* Constructs a Networks instance.
39+
* @param reader The reader object.
40+
* @param includeAliasedNetworks The boolean to include aliased networks.
41+
* @param nodes The initial nodes array to start Networks iterator with.
42+
* @param typeParameterClass The type of data returned by the iterator.
43+
* @throws ClosedDatabaseException Exception for a closed database.
44+
*/
45+
Networks(
46+
Reader reader,
47+
boolean includeAliasedNetworks,
48+
NetworkNode[] nodes,
49+
Class<T> typeParameterClass)
50+
throws ClosedDatabaseException {
51+
this.reader = reader;
52+
this.includeAliasedNetworks = includeAliasedNetworks;
53+
this.buffer = reader.getBufferHolder().get();
54+
this.nodes = new Stack<>();
55+
this.typeParameterClass = typeParameterClass;
56+
for (NetworkNode node : nodes) {
57+
this.nodes.push(node);
58+
}
59+
}
60+
61+
/**
62+
* Constructs a Networks instance with includeAliasedNetworks set to false by default.
63+
* @param reader The reader object.
64+
* @param typeParameterClass The type of data returned by the iterator.
65+
*/
66+
Networks(Reader reader, Class<T> typeParameterClass) throws ClosedDatabaseException {
67+
this(reader, false, typeParameterClass);
68+
}
69+
70+
/**
71+
* Returns the next DataRecord.
72+
* @return The next DataRecord.
73+
* @throws NetworksIterationException An exception when iterating over the networks.
74+
*/
75+
@Override
76+
public DatabaseRecord<T> next() {
77+
try {
78+
T data = this.reader.resolveDataPointer(
79+
this.buffer, this.lastNode.pointer, this.typeParameterClass);
80+
81+
byte[] ip = this.lastNode.ip;
82+
int prefixLength = this.lastNode.prefix;
83+
84+
// We do this because uses of includeAliasedNetworks will get IPv4 networks
85+
// from the ::FFFF:0:0/96. We want to return the IPv4 form of the address
86+
// in that case.
87+
if (!this.includeAliasedNetworks && isInIpv4Subtree(ip)) {
88+
ip = Arrays.copyOfRange(ip, 12, ip.length);
89+
prefixLength -= 96;
90+
}
91+
92+
// If the ip is in ipv6 form, drop the prefix manually
93+
// as InetAddress converts it to ipv4.
94+
InetAddress ipAddr = InetAddress.getByAddress(ip);
95+
if (ipAddr instanceof Inet4Address && ip.length > 4 && prefixLength > 96) {
96+
prefixLength -= 96;
97+
}
98+
99+
return new DatabaseRecord<T>(data, ipAddr, prefixLength);
100+
} catch (IOException e) {
101+
throw new NetworksIterationException(e);
102+
}
103+
}
104+
105+
private boolean isInIpv4Subtree(byte[] ip) {
106+
if (ip.length != 16) {
107+
return false;
108+
}
109+
for (int i = 0; i < 12; i++) {
110+
if (ip[i] != 0) {
111+
return false;
112+
}
113+
}
114+
return true;
115+
}
116+
117+
/**
118+
* hasNext prepares the next network for reading with the Network method. It
119+
* returns true if there is another network to be processed and false if there
120+
* are no more networks.
121+
* @return boolean True if there is another network to be processed.
122+
* @throws NetworksIterationException Exception while iterating over the networks.
123+
*/
124+
@Override
125+
public boolean hasNext() {
126+
while (!this.nodes.isEmpty()) {
127+
NetworkNode node = this.nodes.pop();
128+
129+
// Next until we don't have data.
130+
while (node.pointer != this.reader.getMetadata().getNodeCount()) {
131+
// This skips IPv4 aliases without hardcoding the networks that the writer
132+
// currently aliases.
133+
if (!this.includeAliasedNetworks && this.reader.getIpv4Start() != 0
134+
&& node.pointer == this.reader.getIpv4Start()
135+
&& !isInIpv4Subtree(node.ip)) {
136+
break;
137+
}
138+
139+
if (node.pointer > this.reader.getMetadata().getNodeCount()) {
140+
this.lastNode = node;
141+
return true;
142+
}
143+
144+
byte[] ipRight = Arrays.copyOf(node.ip, node.ip.length);
145+
if (ipRight.length <= (node.prefix >> 3)) {
146+
throw new NetworksIterationException("Invalid search tree");
147+
}
148+
149+
ipRight[node.prefix >> 3] |= 1 << (7 - (node.prefix % 8));
150+
151+
try {
152+
int rightPointer = this.reader.readNode(this.buffer, node.pointer, 1);
153+
node.prefix++;
154+
155+
this.nodes.push(new NetworkNode(ipRight, node.prefix, rightPointer));
156+
node.pointer = this.reader.readNode(this.buffer, node.pointer, 0);
157+
} catch (InvalidDatabaseException e) {
158+
throw new NetworksIterationException(e);
159+
}
160+
}
161+
}
162+
return false;
163+
}
164+
165+
static class NetworkNode {
166+
public byte[] ip;
167+
public int prefix;
168+
public int pointer;
169+
170+
/**
171+
* Constructs a network node for internal use.
172+
* @param ip The ip address of the node.
173+
* @param prefix The prefix of the node.
174+
* @param pointer The node number
175+
*/
176+
NetworkNode(byte[] ip, int prefix, int pointer) {
177+
this.ip = ip;
178+
this.prefix = prefix;
179+
this.pointer = pointer;
180+
}
181+
}
182+
183+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.maxmind.db;
2+
3+
/**
4+
* <p>
5+
* This class represents an error encountered while iterating over the networks.
6+
* The most likely causes are corrupt data in the database, or a bug in the reader code.
7+
* </p>
8+
* <p>
9+
* This exception extends RuntimeException because it is thrown by the iterator
10+
* methods in {@link Networks}.
11+
* </p>
12+
*
13+
* @see Networks
14+
*/
15+
public class NetworksIterationException extends RuntimeException {
16+
NetworksIterationException(String message) {
17+
super(message);
18+
}
19+
20+
NetworksIterationException(Throwable cause) {
21+
super(cause);
22+
}
23+
}

0 commit comments

Comments
 (0)