Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package com.maxmind.db;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.databind.JsonNode;
/**
* Instances of this class provide a reader for the MaxMind DB format. IP
* addresses can be looked up using the get method.
*/
public final class Reader implements Closeable {
private static final int DATA_SECTION_SEPARATOR_SIZE = 16;
private static final byte[] METADATA_START_MARKER = { (byte) 0xAB,
(byte) 0xCD, (byte) 0xEF, 'M', 'a', 'x', 'M', 'i', 'n', 'd', '.',
'c', 'o', 'm' };
private final int ipV4Start;
private final Metadata metadata;
private final AtomicReference bufferHolderReference;
/**
* The file mode to use when opening a MaxMind DB.
*/
public enum FileMode {
/**
* The default file mode. This maps the database to virtual memory. This
* often provides similar performance to loading the database into real
* memory without the overhead.
*/
MEMORY_MAPPED,
/**
* Loads the database into memory when the reader is constructed.
*/
MEMORY
}
/**
* Constructs a Reader for the MaxMind DB format. The file passed to it must
* be a valid MaxMind DB file such as a GeoIP2 database file.
*
* @param database
* the MaxMind DB file to use.
* @throws IOException
* if there is an error opening or reading from the file.
*/
public Reader(File database) throws IOException {
this(database, FileMode.MEMORY_MAPPED);
}
/**
* Constructs a Reader as if in mode {@link FileMode#MEMORY}, without using
* a File instance.
*
* @param source
* the InputStream that contains the MaxMind DB file.
* @throws IOException
* if there is an error reading from the Stream.
*/
public Reader(InputStream source) throws IOException {
this(new BufferHolder(source), "");
}
/**
* Constructs a Reader for the MaxMind DB format. The file passed to it must
* be a valid MaxMind DB file such as a GeoIP2 database file.
*
* @param database
* the MaxMind DB file to use.
* @param fileMode
* the mode to open the file with.
* @throws IOException
* if there is an error opening or reading from the file.
*/
public Reader(File database, FileMode fileMode) throws IOException {
this(new BufferHolder(database, fileMode), database.getName());
}
private Reader(BufferHolder bufferHolder, String name) throws IOException {
this.bufferHolderReference = new AtomicReference(
bufferHolder);
ByteBuffer buffer = bufferHolder.get();
int start = this.findMetadataStart(buffer, name);
Decoder metadataDecoder = new Decoder(buffer, start);
this.metadata = new Metadata(metadataDecoder.decode(start).getNode());
this.ipV4Start = this.findIpV4StartNode(buffer);
}
/**
* Looks up the address in the MaxMind DB.
*
* @param ipAddress
* the IP address to look up.
* @return the record for the IP address.
* @throws IOException
* if a file I/O error occurs.
*/
public JsonNode get(InetAddress ipAddress) throws IOException {
ByteBuffer buffer = this.getBufferHolder().get();
int pointer = this.findAddressInTree(buffer, ipAddress);
if (pointer == 0) {
return null;
}
return this.resolveDataPointer(buffer, pointer);
}
private BufferHolder getBufferHolder() throws ClosedDatabaseException {
BufferHolder bufferHolder = this.bufferHolderReference.get();
if (bufferHolder == null) {
throw new ClosedDatabaseException();
}
return bufferHolder;
}
private int findAddressInTree(ByteBuffer buffer, InetAddress address)
throws InvalidDatabaseException {
byte[] rawAddress = address.getAddress();
int bitLength = rawAddress.length * 8;
int record = this.startNode(buffer, bitLength);
for (int i = 0; i < bitLength; i++) {
if (record >= this.metadata.nodeCount) {
break;
}
int b = 0xFF & rawAddress[i / 8];
int bit = 1 & (b >> 7 - (i % 8));
record = this.readNode(buffer, record, bit);
}
if (record == this.metadata.nodeCount) {
// record is empty
return 0;
} else if (record > this.metadata.nodeCount) {
// record is a data pointer
return record;
}
throw new InvalidDatabaseException("Something bad happened");
}
private int startNode(ByteBuffer buffer, int bitLength)
throws InvalidDatabaseException {
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
// is the case, we can skip over the first 96 nodes.
if (this.metadata.ipVersion == 6 && bitLength == 32) {
return this.ipV4Start;
}
// The first node of the tree is always node 0, at the beginning of the
// value
return 0;
}
private int findIpV4StartNode(ByteBuffer buffer)
throws InvalidDatabaseException {
if (this.metadata.ipVersion == 4) {
return 0;
}
int node = 0;
for (int i = 0; i < 96 && node < this.metadata.nodeCount; i++) {
node = this.readNode(buffer, node, 0);
}
return node;
}
private int readNode(ByteBuffer buffer, int nodeNumber, int index)
throws InvalidDatabaseException {
int baseOffset = nodeNumber * this.metadata.nodeByteSize;
switch (this.metadata.recordSize) {
case 24:
buffer.position(baseOffset + index * 3);
return Decoder.decodeInteger(buffer, 0, 3);
case 28:
int middle = buffer.get(baseOffset + 3);
if (index == 0) {
middle = (0xF0 & middle) >>> 4;
} else {
middle = 0x0F & middle;
}
buffer.position(baseOffset + index * 4);
return Decoder.decodeInteger(buffer, middle, 3);
case 32:
buffer.position(baseOffset + index * 4);
return Decoder.decodeInteger(buffer, 0, 4);
default:
throw new InvalidDatabaseException("Unknown record size: "
+ this.metadata.recordSize);
}
}
private JsonNode resolveDataPointer(ByteBuffer buffer, int pointer)
throws IOException {
int resolved = (pointer - this.metadata.nodeCount)
+ this.metadata.searchTreeSize;
if (resolved >= buffer.capacity()) {
throw new InvalidDatabaseException(
"The MaxMind DB file's search tree is corrupt: "
+ "contains pointer larger than the database.");
}
// We only want the data from the decoder, not the offset where it was
// found.
Decoder decoder = new Decoder(buffer, this.metadata.searchTreeSize
+ DATA_SECTION_SEPARATOR_SIZE);
return decoder.decode(resolved).getNode();
}
/*
* Apparently searching a file for a sequence is not a solved problem in
* Java. This searches from the end of the file for metadata start.
*
* This is an extremely naive but reasonably readable implementation. There
* are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
* an issue, but I suspect it won't be.
*/
private int findMetadataStart(ByteBuffer buffer, String databaseName)
throws InvalidDatabaseException {
int fileSize = buffer.capacity();
FILE: for (int i = 0; i < fileSize - METADATA_START_MARKER.length + 1; i++) {
for (int j = 0; j < METADATA_START_MARKER.length; j++) {
byte b = buffer.get(fileSize - i - j - 1);
if (b != METADATA_START_MARKER[METADATA_START_MARKER.length - j
- 1]) {
continue FILE;
}
}
return fileSize - i;
}
throw new InvalidDatabaseException(
"Could not find a MaxMind DB metadata marker in this file ("
+ databaseName + "). Is this a valid MaxMind DB file?");
}
Metadata getMetadata() {
return this.metadata;
}
/**
* Closes the MaxMind DB and returns resources to the system.
*
* @throws IOException
* if an I/O error occurs.
*/
@Override
public void close() throws IOException {
this.bufferHolderReference.set(null);
}
}