Skip to content

Commit a0bc361

Browse files
authored
Merge pull request #52 from maxmind/greg/network
Add ability to get record associated with lookup
2 parents 1a1caaa + 0f17d19 commit a0bc361

File tree

14 files changed

+272
-107
lines changed

14 files changed

+272
-107
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
<configuration>
103103
<format>xml</format>
104104
<maxmem>256m</maxmem>
105+
<check/>
105106
</configuration>
106107
</plugin>
107108
<plugin>

src/main/java/com/maxmind/db/BufferHolder.java

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ final class BufferHolder {
1616
private final ByteBuffer buffer;
1717

1818
BufferHolder(File database, FileMode mode) throws IOException {
19-
final RandomAccessFile file = new RandomAccessFile(database, "r");
20-
boolean threw = true;
21-
try {
22-
final FileChannel channel = file.getChannel();
19+
try (
20+
final RandomAccessFile file = new RandomAccessFile(database, "r");
21+
final FileChannel channel = file.getChannel();
22+
) {
2323
if (mode == FileMode.MEMORY) {
2424
this.buffer = ByteBuffer.wrap(new byte[(int) channel.size()]);
2525
if (channel.read(this.buffer) != this.buffer.capacity()) {
@@ -30,19 +30,6 @@ final class BufferHolder {
3030
} else {
3131
this.buffer = channel.map(MapMode.READ_ONLY, 0, channel.size());
3232
}
33-
threw = false;
34-
} finally {
35-
try {
36-
// Also closes the underlying channel.
37-
file.close();
38-
} catch (final IOException e) {
39-
// If an exception was underway when we entered the finally
40-
// block, don't stomp over it due to an error closing the file
41-
// and channel.
42-
if (!threw) {
43-
throw e;
44-
}
45-
}
4633
}
4734
}
4835

@@ -66,11 +53,6 @@ final class BufferHolder {
6653
this.buffer = ByteBuffer.wrap(baos.toByteArray());
6754
}
6855

69-
// This is just to ease unit testing
70-
BufferHolder(ByteBuffer buffer) {
71-
this.buffer = buffer;
72-
}
73-
7456
/*
7557
* Returns a duplicate of the underlying ByteBuffer. The returned ByteBuffer
7658
* should not be shared between threads.

src/main/java/com/maxmind/db/CHMCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public CHMCache() {
2424

2525
public CHMCache(int capacity) {
2626
this.capacity = capacity;
27-
this.cache = new ConcurrentHashMap<Integer, JsonNode>(capacity);
27+
this.cache = new ConcurrentHashMap<>(capacity);
2828
}
2929

3030
@Override

src/main/java/com/maxmind/db/Decoder.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.nio.charset.CharacterCodingException;
77
import java.nio.charset.Charset;
88
import java.nio.charset.CharsetDecoder;
9+
import java.nio.charset.StandardCharsets;
910
import java.util.ArrayList;
1011
import java.util.Collections;
1112
import java.util.HashMap;
@@ -23,7 +24,7 @@
2324
*/
2425
final class Decoder {
2526

26-
private static final Charset UTF_8 = Charset.forName("UTF-8");
27+
private static final Charset UTF_8 = StandardCharsets.UTF_8;
2728

2829
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
2930

@@ -41,14 +42,14 @@ final class Decoder {
4142

4243
private final ByteBuffer buffer;
4344

44-
static enum Type {
45+
enum Type {
4546
EXTENDED, POINTER, UTF8_STRING, DOUBLE, BYTES, UINT16, UINT32, MAP, INT32, UINT64, UINT128, ARRAY, CONTAINER, END_MARKER, BOOLEAN, FLOAT;
4647

4748
// Java clones the array when you call values(). Caching it increased
4849
// the speed by about 5000 requests per second on my machine.
4950
final static Type[] values = Type.values();
5051

51-
public static Type get(int i) {
52+
static Type get(int i) {
5253
return Type.values[i];
5354
}
5455

@@ -57,7 +58,7 @@ private static Type get(byte b) {
5758
return Type.get(b & 0xFF);
5859
}
5960

60-
public static Type fromControlByte(int b) {
61+
static Type fromControlByte(int b) {
6162
// The type is encoded in the first 3 bits of the byte.
6263
return Type.get((byte) ((0xFF & b) >>> 5));
6364
}
@@ -87,7 +88,7 @@ JsonNode decode(int offset) throws IOException {
8788
return decode();
8889
}
8990

90-
JsonNode decode() throws IOException {
91+
private JsonNode decode() throws IOException {
9192
int ctrlByte = 0xFF & this.buffer.get();
9293

9394
Type type = Type.fromControlByte(ctrlByte);
@@ -171,7 +172,6 @@ private JsonNode decodeByType(Type type, int size)
171172
case INT32:
172173
return this.decodeInt32(size);
173174
case UINT64:
174-
return this.decodeBigInteger(size);
175175
case UINT128:
176176
return this.decodeBigInteger(size);
177177
default:
@@ -263,7 +263,7 @@ private static BooleanNode decodeBoolean(int size)
263263

264264
private JsonNode decodeArray(int size) throws IOException {
265265

266-
List<JsonNode> array = new ArrayList<JsonNode>(size);
266+
List<JsonNode> array = new ArrayList<>(size);
267267
for (int i = 0; i < size; i++) {
268268
JsonNode r = this.decode();
269269
array.add(r);
@@ -274,7 +274,7 @@ private JsonNode decodeArray(int size) throws IOException {
274274

275275
private JsonNode decodeMap(int size) throws IOException {
276276
int capacity = (int) (size / 0.75F + 1.0F);
277-
Map<String, JsonNode> map = new HashMap<String, JsonNode>(capacity);
277+
Map<String, JsonNode> map = new HashMap<>(capacity);
278278

279279
for (int i = 0; i < size; i++) {
280280
String key = this.decode().asText();
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.maxmind.db;
2+
3+
import java.net.InetAddress;
4+
import java.net.UnknownHostException;
5+
6+
/**
7+
* <code>Network</code> represents an IP network.
8+
*/
9+
public final class Network {
10+
private final InetAddress ipAddress;
11+
private final int prefixLength;
12+
private InetAddress networkAddress = null;
13+
14+
/**
15+
* Construct a <code>Network</code>
16+
*
17+
* @param ipAddress An IP address in the network. This does not have to be
18+
* the first address in the network.
19+
* @param prefixLength The prefix length for the network.
20+
*/
21+
public Network(InetAddress ipAddress, int prefixLength) {
22+
this.ipAddress = ipAddress;
23+
this.prefixLength = prefixLength;
24+
}
25+
26+
/**
27+
* @return The first address in the network.
28+
*/
29+
public InetAddress getNetworkAddress() {
30+
if (networkAddress != null) {
31+
return networkAddress;
32+
}
33+
byte[] ipBytes = ipAddress.getAddress();
34+
byte[] networkBytes = new byte[ipBytes.length];
35+
int curPrefix = prefixLength;
36+
for (int i = 0; i < ipBytes.length && curPrefix > 0; i++) {
37+
byte b = ipBytes[i];
38+
if (curPrefix < 8) {
39+
int shiftN = 8 - curPrefix;
40+
b = (byte) ((b >> shiftN) << shiftN);
41+
}
42+
networkBytes[i] = b;
43+
curPrefix -= 8;
44+
}
45+
46+
try {
47+
networkAddress = InetAddress.getByAddress(networkBytes);
48+
} catch (UnknownHostException e) {
49+
throw new RuntimeException("Illegal network address byte length of " + networkBytes.length);
50+
}
51+
return networkAddress;
52+
}
53+
54+
/**
55+
* @return The prefix length is the number of leading 1 bits in the subnet
56+
* mask. Sometimes also known as netmask length.
57+
*/
58+
public int getPrefixLength() {
59+
return prefixLength;
60+
}
61+
62+
/***
63+
* @return A string representation of the network in CIDR notation, e.g.,
64+
* <code>1.2.3.0/24</code> or <code>2001::/8</code>.
65+
*/
66+
public String toString() {
67+
return getNetworkAddress().getHostAddress() + "/" + prefixLength;
68+
}
69+
}

src/main/java/com/maxmind/db/NodeCache.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
public interface NodeCache {
88

9-
public interface Loader {
9+
interface Loader {
1010
JsonNode load(int key) throws IOException;
1111
}
1212

13-
public JsonNode get(int key, Loader loader) throws IOException;
13+
JsonNode get(int key, Loader loader) throws IOException;
1414

1515
}

src/main/java/com/maxmind/db/Reader.java

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public Reader(File database, FileMode fileMode, NodeCache cache) throws IOExcept
117117
}
118118

119119
private Reader(BufferHolder bufferHolder, String name, NodeCache cache) throws IOException {
120-
this.bufferHolderReference = new AtomicReference<BufferHolder>(
120+
this.bufferHolderReference = new AtomicReference<>(
121121
bufferHolder);
122122

123123
if (cache == null) {
@@ -135,52 +135,55 @@ private Reader(BufferHolder bufferHolder, String name, NodeCache cache) throws I
135135
}
136136

137137
/**
138-
* Looks up the <code>address</code> in the MaxMind DB.
138+
* Looks up <code>ipAddress</code> in the MaxMind DB.
139139
*
140140
* @param ipAddress the IP address to look up.
141-
* @return the record for the IP address.
141+
* @return the record data for the IP address.
142142
* @throws IOException if a file I/O error occurs.
143143
*/
144144
public JsonNode get(InetAddress ipAddress) throws IOException {
145-
ByteBuffer buffer = this.getBufferHolder().get();
146-
int pointer = this.findAddressInTree(buffer, ipAddress);
147-
if (pointer == 0) {
148-
return null;
149-
}
150-
return this.resolveDataPointer(buffer, pointer);
151-
}
152-
153-
private BufferHolder getBufferHolder() throws ClosedDatabaseException {
154-
BufferHolder bufferHolder = this.bufferHolderReference.get();
155-
if (bufferHolder == null) {
156-
throw new ClosedDatabaseException();
157-
}
158-
return bufferHolder;
145+
return getRecord(ipAddress).getData();
159146
}
147+
/**
148+
* Looks up <code>ipAddress</code> in the MaxMind DB.
149+
*
150+
* @param ipAddress the IP address to look up.
151+
* @return the record for the IP address. If there is no data for the
152+
* address, the non-null {@link Record} will still be returned.
153+
* @throws IOException if a file I/O error occurs.
154+
*/
155+
public Record getRecord(InetAddress ipAddress)
156+
throws IOException {
157+
ByteBuffer buffer = this.getBufferHolder().get();
160158

161-
private int findAddressInTree(ByteBuffer buffer, InetAddress address)
162-
throws InvalidDatabaseException {
163-
byte[] rawAddress = address.getAddress();
159+
byte[] rawAddress = ipAddress.getAddress();
164160

165161
int bitLength = rawAddress.length * 8;
166162
int record = this.startNode(bitLength);
163+
int nodeCount = this.metadata.getNodeCount();
167164

168-
for (int i = 0; i < bitLength; i++) {
169-
if (record >= this.metadata.getNodeCount()) {
170-
break;
171-
}
172-
int b = 0xFF & rawAddress[i / 8];
173-
int bit = 1 & (b >> 7 - (i % 8));
165+
int pl = 0;
166+
for (; pl < bitLength && record < nodeCount; pl++) {
167+
int b = 0xFF & rawAddress[pl / 8];
168+
int bit = 1 & (b >> 7 - (pl % 8));
174169
record = this.readNode(buffer, record, bit);
175170
}
176-
if (record == this.metadata.getNodeCount()) {
177-
// record is empty
178-
return 0;
179-
} else if (record > this.metadata.getNodeCount()) {
171+
172+
JsonNode dataRecord = null;
173+
if (record > nodeCount) {
180174
// record is a data pointer
181-
return record;
175+
dataRecord = this.resolveDataPointer(buffer, record);
182176
}
183-
throw new InvalidDatabaseException("Something bad happened");
177+
178+
return new Record(dataRecord, ipAddress, pl);
179+
}
180+
181+
private BufferHolder getBufferHolder() throws ClosedDatabaseException {
182+
BufferHolder bufferHolder = this.bufferHolderReference.get();
183+
if (bufferHolder == null) {
184+
throw new ClosedDatabaseException();
185+
}
186+
return bufferHolder;
184187
}
185188

186189
private int startNode(int bitLength) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.maxmind.db;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
5+
import java.net.InetAddress;
6+
7+
/**
8+
* Record represents the data and metadata associated with a database lookup.
9+
*/
10+
public final class Record {
11+
private final JsonNode data;
12+
private final Network network;
13+
14+
/**
15+
* Create a new record.
16+
*
17+
* @param data the data for the record in the database.
18+
* @param ipAddress the IP address used in the lookup.
19+
* @param prefixLength the network prefix length associated with the record in the database.
20+
*/
21+
public Record( JsonNode data, InetAddress ipAddress, int prefixLength) {
22+
this.data = data;
23+
this.network = new Network(ipAddress, prefixLength);
24+
}
25+
26+
/**
27+
* @return the data for the record in the database. The record will be
28+
* <code>null</code> if there was no data for the address in the database.
29+
*/
30+
public JsonNode getData() {
31+
return data;
32+
}
33+
34+
/**
35+
* @return the network associated with the record in the database. This is
36+
* the largest network where all of the IPs in the network have the same
37+
* data.
38+
*/
39+
public Network getNetwork() {
40+
return network;
41+
}
42+
}

src/main/java/com/maxmind/db/package-info.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
/**
2-
*
3-
*/
41
/**
52
* @author greg
63
*

0 commit comments

Comments
 (0)