From 380eae375f3ccd3f18b3d4406b212510b725275a Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Wed, 20 Jan 2016 15:55:16 -0800 Subject: [PATCH 1/4] Reduce code duplication and throw exceptions for error states --- .../geoip/InvalidDatabaseException.java | 26 + .../java/com/maxmind/geoip/LookupService.java | 585 +++++++----------- .../com/maxmind/geoip/CityLookupTest.java | 9 + .../resources/GeoIP/GeoIPCity-Corrupt.dat | Bin 0 -> 4065 bytes 4 files changed, 252 insertions(+), 368 deletions(-) create mode 100644 src/main/java/com/maxmind/geoip/InvalidDatabaseException.java create mode 100644 src/test/resources/GeoIP/GeoIPCity-Corrupt.dat diff --git a/src/main/java/com/maxmind/geoip/InvalidDatabaseException.java b/src/main/java/com/maxmind/geoip/InvalidDatabaseException.java new file mode 100644 index 0000000..504a68e --- /dev/null +++ b/src/main/java/com/maxmind/geoip/InvalidDatabaseException.java @@ -0,0 +1,26 @@ +package com.maxmind.geoip; + +import java.io.IOException; + +/** + * Signals that there was an issue reading from the database file due to + * unexpected data formatting. This generally suggests that the database is + * corrupt or otherwise not in a format supported by the reader. + */ +public class InvalidDatabaseException extends RuntimeException { + + /** + * @param message A message describing the reason why the exception was thrown. + */ + public InvalidDatabaseException(String message) { + super(message); + } + + /** + * @param message A message describing the reason why the exception was thrown. + * @param cause The cause of the exception. + */ + public InvalidDatabaseException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/maxmind/geoip/LookupService.java b/src/main/java/com/maxmind/geoip/LookupService.java index 9049425..259a5e5 100644 --- a/src/main/java/com/maxmind/geoip/LookupService.java +++ b/src/main/java/com/maxmind/geoip/LookupService.java @@ -25,6 +25,10 @@ import java.io.RandomAccessFile; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; /** * Provides a lookup service for information based on an IP address. The @@ -71,53 +75,55 @@ public class LookupService { /** * Database file. */ - private RandomAccessFile file = null; - private File databaseFile = null; + private RandomAccessFile file; + private final File databaseFile; /** * Information about the database. */ - private DatabaseInfo databaseInfo = null; + private DatabaseInfo databaseInfo; + + private static final Charset charset = Charset.forName("ISO-8859-1"); + private final CharsetDecoder charsetDecoder = charset.newDecoder(); /** * The database type. Default is the country edition. */ - byte databaseType = DatabaseInfo.COUNTRY_EDITION; - - int databaseSegments[]; - int recordLength; - - String licenseKey; - int dboptions; - byte dbbuffer[]; - byte index_cache[]; - long mtime; - int last_netmask; - private final static int US_OFFSET = 1; - private final static int CANADA_OFFSET = 677; - private final static int WORLD_OFFSET = 1353; - private final static int FIPS_RANGE = 360; - private final static int COUNTRY_BEGIN = 16776960; - private final static int STATE_BEGIN_REV0 = 16700000; - private final static int STATE_BEGIN_REV1 = 16000000; - private final static int STRUCTURE_INFO_MAX_SIZE = 20; - private final static int DATABASE_INFO_MAX_SIZE = 100; - public final static int GEOIP_STANDARD = 0; - public final static int GEOIP_MEMORY_CACHE = 1; - public final static int GEOIP_CHECK_CACHE = 2; - public final static int GEOIP_INDEX_CACHE = 4; - public final static int GEOIP_UNKNOWN_SPEED = 0; - public final static int GEOIP_DIALUP_SPEED = 1; - public final static int GEOIP_CABLEDSL_SPEED = 2; - public final static int GEOIP_CORPORATE_SPEED = 3; - - private final static int SEGMENT_RECORD_LENGTH = 3; - private final static int STANDARD_RECORD_LENGTH = 3; - private final static int ORG_RECORD_LENGTH = 4; - private final static int MAX_RECORD_LENGTH = 4; - - private final static int MAX_ORG_RECORD_LENGTH = 300; - private final static int FULL_RECORD_LENGTH = 60; + private byte databaseType = DatabaseInfo.COUNTRY_EDITION; + + private int[] databaseSegments; + private int recordLength; + + private int dboptions; + private byte[] dbbuffer; + private byte[] index_cache; + private long mtime; + private int last_netmask; + private static final int US_OFFSET = 1; + private static final int CANADA_OFFSET = 677; + private static final int WORLD_OFFSET = 1353; + private static final int FIPS_RANGE = 360; + private static final int COUNTRY_BEGIN = 16776960; + private static final int STATE_BEGIN_REV0 = 16700000; + private static final int STATE_BEGIN_REV1 = 16000000; + private static final int STRUCTURE_INFO_MAX_SIZE = 20; + private static final int DATABASE_INFO_MAX_SIZE = 100; + public static final int GEOIP_STANDARD = 0; + public static final int GEOIP_MEMORY_CACHE = 1; + public static final int GEOIP_CHECK_CACHE = 2; + public static final int GEOIP_INDEX_CACHE = 4; + public static final int GEOIP_UNKNOWN_SPEED = 0; + public static final int GEOIP_DIALUP_SPEED = 1; + public static final int GEOIP_CABLEDSL_SPEED = 2; + public static final int GEOIP_CORPORATE_SPEED = 3; + + private static final int SEGMENT_RECORD_LENGTH = 3; + private static final int STANDARD_RECORD_LENGTH = 3; + private static final int ORG_RECORD_LENGTH = 4; + private static final int MAX_RECORD_LENGTH = 4; + + private static final int MAX_ORG_RECORD_LENGTH = 300; + private static final int FULL_RECORD_LENGTH = 60; private final Country UNKNOWN_COUNTRY = new Country("--", "N/A"); @@ -213,8 +219,9 @@ public class LookupService { /* init the hashmap once at startup time */ static { - if (countryCode.length != countryName.length) + if (countryCode.length != countryName.length) { throw new AssertionError("countryCode.length!=countryName.length"); + } } /** @@ -222,7 +229,7 @@ public class LookupService { * * @param databaseFile * String representation of the database file. - * @throws java.io.IOException + * @throws IOException * if an error occured creating the lookup service from the * database file. */ @@ -235,13 +242,13 @@ public LookupService(String databaseFile) throws IOException { * * @param databaseFile * the database file. - * @throws java.io.IOException + * @throws IOException * if an error occured creating the lookup service from the * database file. */ public LookupService(File databaseFile) throws IOException { this.databaseFile = databaseFile; - this.file = new RandomAccessFile(databaseFile, "r"); + file = new RandomAccessFile(databaseFile, "r"); init(); } @@ -254,7 +261,7 @@ public LookupService(File databaseFile) throws IOException { * database flags to use when opening the database GEOIP_STANDARD * read database from disk GEOIP_MEMORY_CACHE cache the database * in RAM and read it from RAM - * @throws java.io.IOException + * @throws IOException * if an error occured creating the lookup service from the * database file. */ @@ -271,13 +278,13 @@ public LookupService(String databaseFile, int options) throws IOException { * database flags to use when opening the database GEOIP_STANDARD * read database from disk GEOIP_MEMORY_CACHE cache the database * in RAM and read it from RAM - * @throws java.io.IOException + * @throws IOException * if an error occured creating the lookup service from the * database file. */ public LookupService(File databaseFile, int options) throws IOException { this.databaseFile = databaseFile; - this.file = new RandomAccessFile(databaseFile, "r"); + file = new RandomAccessFile(databaseFile, "r"); dboptions = options; init(); } @@ -285,11 +292,10 @@ public LookupService(File databaseFile, int options) throws IOException { /** * Reads meta-data from the database file. * - * @throws java.io.IOException + * @throws IOException * if an error occurs reading from the database file. */ - synchronized private void init() throws IOException { - int i, j; + private synchronized void init() throws IOException { byte[] delim = new byte[3]; byte[] buf = new byte[SEGMENT_RECORD_LENGTH]; @@ -300,7 +306,7 @@ synchronized private void init() throws IOException { mtime = databaseFile.lastModified(); } file.seek(file.length() - 3); - for (i = 0; i < STRUCTURE_INFO_MAX_SIZE; i++) { + for (int i = 0; i < STRUCTURE_INFO_MAX_SIZE; i++) { file.readFully(delim); if (delim[0] == -1 && delim[1] == -1 && delim[2] == -1) { databaseType = file.readByte(); @@ -347,7 +353,7 @@ synchronized private void init() throws IOException { recordLength = ORG_RECORD_LENGTH; } file.readFully(buf); - for (j = 0; j < SEGMENT_RECORD_LENGTH; j++) { + for (int j = 0; j < SEGMENT_RECORD_LENGTH; j++) { databaseSegments[0] += (unsignedByteToInt(buf[j]) << (j * 8)); } } @@ -369,16 +375,14 @@ synchronized private void init() throws IOException { dbbuffer = new byte[l]; file.seek(0); file.readFully(dbbuffer, 0, l); - databaseInfo = this.getDatabaseInfo(); + databaseInfo = getDatabaseInfo(); file.close(); } if ((dboptions & GEOIP_INDEX_CACHE) != 0) { int l = databaseSegments[0] * recordLength * 2; index_cache = new byte[l]; - if (index_cache != null) { - file.seek(0); - file.readFully(index_cache, 0, l); - } + file.seek(0); + file.readFully(index_cache, 0, l); } else { index_cache = null; } @@ -387,7 +391,7 @@ synchronized private void init() throws IOException { /** * Closes the lookup service. */ -synchronized public void close() { + public synchronized void close() { try { if (file != null) { file.close(); @@ -429,7 +433,7 @@ public Country getCountry(String ipAddress) { } catch (UnknownHostException e) { return UNKNOWN_COUNTRY; } - return getCountry(bytesToLong(addr.getAddress())); + return getCountry(addr); } /** @@ -499,16 +503,15 @@ public synchronized int getID(long ipAddress) { if (file == null && (dboptions & GEOIP_MEMORY_CACHE) == 0) { throw new IllegalStateException("Database has been closed."); } - int ret = seekCountry(ipAddress) - databaseSegments[0]; - return ret; + return seekCountry(ipAddress) - databaseSegments[0]; } public int last_netmask() { - return this.last_netmask; + return last_netmask; } public void netmask(int nm) { - this.last_netmask = nm; + last_netmask = nm; } /** @@ -550,13 +553,13 @@ public synchronized DatabaseInfo getDatabaseInfo() { byte[] dbInfo = new byte[i]; file.readFully(dbInfo); // Create the database info object using the string. - this.databaseInfo = new DatabaseInfo(new String(dbInfo)); + databaseInfo = new DatabaseInfo(new String(dbInfo, charset)); return databaseInfo; } file.seek(file.getFilePointer() - 4); } - } catch (Exception e) { - e.printStackTrace(); + } catch (IOException e) { + throw new InvalidDatabaseException("Error reading database info", e); } return new DatabaseInfo(""); } @@ -575,7 +578,7 @@ synchronized void _check_mtime() { } } } catch (IOException e) { - System.out.println("file not found"); + throw new InvalidDatabaseException("Database not found", e); } } @@ -619,12 +622,16 @@ public synchronized Region getRegion(String str) { return getRegion(bytesToLong(addr.getAddress())); } + public synchronized Region getRegion(InetAddress addr) { + return getRegion(bytesToLong(addr.getAddress())); + } + public synchronized Region getRegion(long ipnum) { Region record = new Region(); - int seek_region = 0; + int seek_region; if (databaseType == DatabaseInfo.REGION_EDITION_REV0) { seek_region = seekCountry(ipnum) - STATE_BEGIN_REV0; - char ch[] = new char[2]; + char[] ch = new char[2]; if (seek_region >= 1000) { record.countryCode = "US"; record.countryName = "United States"; @@ -638,7 +645,7 @@ public synchronized Region getRegion(long ipnum) { } } else if (databaseType == DatabaseInfo.REGION_EDITION_REV1) { seek_region = seekCountry(ipnum) - STATE_BEGIN_REV1; - char ch[] = new char[2]; + char[] ch = new char[2]; if (seek_region < US_OFFSET) { record.countryCode = ""; record.countryName = ""; @@ -667,197 +674,116 @@ public synchronized Region getRegion(long ipnum) { } public synchronized Location getLocationV6(InetAddress addr) { - int record_pointer; - byte record_buf[] = new byte[FULL_RECORD_LENGTH]; - int record_buf_offset = 0; - Location record = new Location(); - int str_length = 0; - int j, seek_country; - double latitude = 0, longitude = 0; + int seek_country; try { seek_country = seekCountryV6(addr); - if (seek_country == databaseSegments[0]) { - return null; - } - record_pointer = seek_country + (2 * recordLength - 1) - * databaseSegments[0]; - - if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { - // read from memory - System.arraycopy(dbbuffer, record_pointer, record_buf, 0, Math - .min(dbbuffer.length - record_pointer, - FULL_RECORD_LENGTH)); - } else { - // read from disk - file.seek(record_pointer); - file.readFully(record_buf); - } - - // get country - record.countryCode = countryCode[unsignedByteToInt(record_buf[0])]; - record.countryName = countryName[unsignedByteToInt(record_buf[0])]; - record_buf_offset++; - - // get region - while (record_buf[record_buf_offset + str_length] != '\0') - str_length++; - if (str_length > 0) { - record.region = new String(record_buf, record_buf_offset, - str_length); - } - record_buf_offset += str_length + 1; - str_length = 0; - - // get city - while (record_buf[record_buf_offset + str_length] != '\0') - str_length++; - if (str_length > 0) { - record.city = new String(record_buf, record_buf_offset, - str_length, "ISO-8859-1"); - } - record_buf_offset += str_length + 1; - str_length = 0; - - // get postal code - while (record_buf[record_buf_offset + str_length] != '\0') - str_length++; - if (str_length > 0) { - record.postalCode = new String(record_buf, record_buf_offset, - str_length); - } - record_buf_offset += str_length + 1; - - // get latitude - for (j = 0; j < 3; j++) - latitude += (unsignedByteToInt(record_buf[record_buf_offset + j]) << (j * 8)); - record.latitude = (float) latitude / 10000 - 180; - record_buf_offset += 3; - - // get longitude - for (j = 0; j < 3; j++) - longitude += (unsignedByteToInt(record_buf[record_buf_offset - + j]) << (j * 8)); - record.longitude = (float) longitude / 10000 - 180; - - record.dma_code = record.metro_code = 0; - record.area_code = 0; - if (databaseType == DatabaseInfo.CITY_EDITION_REV1) { - // get DMA code - int metroarea_combo = 0; - if ("US".equals(record.countryCode)) { - record_buf_offset += 3; - for (j = 0; j < 3; j++) - metroarea_combo += (unsignedByteToInt(record_buf[record_buf_offset - + j]) << (j * 8)); - record.metro_code = record.dma_code = metroarea_combo / 1000; - record.area_code = metroarea_combo % 1000; - } - } + return readCityRecord(seek_country); } catch (IOException e) { - System.err.println("IO Exception while seting up segments"); + throw new InvalidDatabaseException("Error while seting up segments", e); } - return record; } public synchronized Location getLocation(long ipnum) { - int record_pointer; - byte record_buf[] = new byte[FULL_RECORD_LENGTH]; - int record_buf_offset = 0; - Location record = new Location(); - int str_length = 0; - int j, seek_country; - double latitude = 0, longitude = 0; + int seek_country; try { seek_country = seekCountry(ipnum); - if (seek_country == databaseSegments[0]) { - return null; - } - record_pointer = seek_country + (2 * recordLength - 1) - * databaseSegments[0]; - - if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { - // read from memory - System.arraycopy(dbbuffer, record_pointer, record_buf, 0, Math - .min(dbbuffer.length - record_pointer, - FULL_RECORD_LENGTH)); - } else { - // read from disk - file.seek(record_pointer); - // We do not know the exact EOF - try { - file.readFully(record_buf); - } catch (IOException e) { - } - } + return readCityRecord(seek_country); + } catch (IOException e) { + throw new InvalidDatabaseException("Error while seting up segments", e); + } + } - // get country - record.countryCode = countryCode[unsignedByteToInt(record_buf[0])]; - record.countryName = countryName[unsignedByteToInt(record_buf[0])]; - record_buf_offset++; - - // get region - while (record_buf[record_buf_offset + str_length] != '\0') - str_length++; - if (str_length > 0) { - record.region = new String(record_buf, record_buf_offset, - str_length); - } - record_buf_offset += str_length + 1; - str_length = 0; - - // get city - while (record_buf[record_buf_offset + str_length] != '\0') - str_length++; - if (str_length > 0) { - record.city = new String(record_buf, record_buf_offset, - str_length, "ISO-8859-1"); - } - record_buf_offset += str_length + 1; - str_length = 0; - - // get postal code - while (record_buf[record_buf_offset + str_length] != '\0') - str_length++; - if (str_length > 0) { - record.postalCode = new String(record_buf, record_buf_offset, - str_length); - } - record_buf_offset += str_length + 1; - - // get latitude - for (j = 0; j < 3; j++) - latitude += (unsignedByteToInt(record_buf[record_buf_offset + j]) << (j * 8)); - record.latitude = (float) latitude / 10000 - 180; - record_buf_offset += 3; - - // get longitude - for (j = 0; j < 3; j++) - longitude += (unsignedByteToInt(record_buf[record_buf_offset - + j]) << (j * 8)); - record.longitude = (float) longitude / 10000 - 180; - - record.dma_code = record.metro_code = 0; - record.area_code = 0; - if (databaseType == DatabaseInfo.CITY_EDITION_REV1) { - // get DMA code - int metroarea_combo = 0; - if ("US".equals(record.countryCode)) { - record_buf_offset += 3; - for (j = 0; j < 3; j++) - metroarea_combo += (unsignedByteToInt(record_buf[record_buf_offset - + j]) << (j * 8)); - record.metro_code = record.dma_code = metroarea_combo / 1000; - record.area_code = metroarea_combo % 1000; - } + private Location readCityRecord(int seekCountry) throws IOException { + if (seekCountry == databaseSegments[0]) { + return null; + } + ByteBuffer buffer = readRecordBuf(seekCountry, FULL_RECORD_LENGTH); + + Location record = new Location(); + int country = unsignedByteToInt(buffer.get()); + + // get country + record.countryCode = countryCode[country]; + record.countryName = countryName[country]; + + record.region = readString(buffer); + record.city = readString(buffer); + record.postalCode = readString(buffer); + record.latitude = readAngle(buffer); + record.longitude = readAngle(buffer); + + if (databaseType == DatabaseInfo.CITY_EDITION_REV1) { + // get DMA code + if ("US".equals(record.countryCode)) { + int metroarea_combo = readMetroAreaCombo(buffer); + record.metro_code = record.dma_code = metroarea_combo / 1000; + record.area_code = metroarea_combo % 1000; } - } catch (IOException e) { - System.err.println("IO Exception while seting up segments"); } return record; } + private ByteBuffer readRecordBuf(int seek, int maxLength) throws IOException { + byte[] recordBuf = new byte[maxLength]; + + int recordPointer = seek + (2 * recordLength - 1) + * databaseSegments[0]; + + if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { + // read from memory + System.arraycopy(dbbuffer, recordPointer, recordBuf, 0, Math + .min(dbbuffer.length - recordPointer, recordBuf.length)); + } else { + // read from disk + file.seek(recordPointer); + file.read(recordBuf); + } + return ByteBuffer.wrap(recordBuf); + } + + + private String readString(ByteBuffer buffer) throws CharacterCodingException { + int start = buffer.position(); + int oldLimit = buffer.limit(); + + while (buffer.hasRemaining() && buffer.get() != 0) {} + + int end = buffer.position() - 1; + String str = null; + if (end > start) { + buffer.position(start); + buffer.limit(end); + str = charsetDecoder.decode(buffer).toString(); + buffer.limit(oldLimit); + } + buffer.position(end + 1); + return str; + } + + private static float readAngle(ByteBuffer buffer) { + if (buffer.remaining() < 3) { + throw new InvalidDatabaseException("Unexpected end of data record when reading angle"); + } + double num = 0; + for (int j = 0; j < 3; j++) { + num += unsignedByteToInt(buffer.get()) << (j * 8); + } + return (float) num / 10000 - 180; + } + + private static int readMetroAreaCombo(ByteBuffer buffer) { + if (buffer.remaining() < 3) { + throw new InvalidDatabaseException("Unexpected end of data record when reading metro area"); + } + int metroareaCombo = 0; + for (int j = 0; j < 3; j++) { + metroareaCombo += unsignedByteToInt(buffer.get()) << (j * 8); + } + return metroareaCombo; + } + public String getOrg(InetAddress addr) { return getOrg(bytesToLong(addr.getAddress())); } @@ -874,40 +800,12 @@ public String getOrg(String str) { // GeoIP Organization and ISP Edition methods public synchronized String getOrg(long ipnum) { - int seek_org; - int record_pointer; - byte[] buf = new byte[MAX_ORG_RECORD_LENGTH]; - String org_buf; - try { - seek_org = seekCountry(ipnum); - if (seek_org == databaseSegments[0]) { - return null; - } + int seekOrg = seekCountry(ipnum); + return readOrgRecord(seekOrg); - record_pointer = seek_org + (2 * recordLength - 1) - * databaseSegments[0]; - if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { - // read from memory - System.arraycopy(dbbuffer, record_pointer, buf, 0, Math - .min(dbbuffer.length - record_pointer, - MAX_ORG_RECORD_LENGTH)); - } else { - // read from disk - file.seek(record_pointer); - try { - // read as much as possible - file.readFully(buf); - } catch (IOException e) { - } - } - int strLength; - for (strLength = 0; strLength < buf.length && buf[strLength] != 0; strLength++) { } - org_buf = new String(buf, 0, strLength, "ISO-8859-1"); - return org_buf; } catch (IOException e) { - System.out.println("IO Exception"); - return null; + throw new InvalidDatabaseException("Error while reading org", e); } } @@ -923,39 +821,20 @@ public String getOrgV6(String str) { // GeoIP Organization and ISP Edition methods public synchronized String getOrgV6(InetAddress addr) { - int seek_org; - int record_pointer; - - byte[] buf = new byte[MAX_ORG_RECORD_LENGTH]; - String org_buf; - - try { - seek_org = seekCountryV6(addr); - if (seek_org == databaseSegments[0]) { - return null; - } - - record_pointer = seek_org + (2 * recordLength - 1) - * databaseSegments[0]; - if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { - // read from memory - System.arraycopy(dbbuffer, record_pointer, buf, 0, Math - .min(dbbuffer.length - record_pointer, - MAX_ORG_RECORD_LENGTH)); - } else { - // read from disk - file.seek(record_pointer); - file.readFully(buf); - } - - int strLength; - for (strLength = 0; strLength < buf.length && buf[strLength] != 0; strLength++) { } - org_buf = new String(buf, 0, strLength, "ISO-8859-1"); - return org_buf; + try { + int seekOrg = seekCountryV6(addr); + return readOrgRecord(seekOrg); } catch (IOException e) { - System.out.println("IO Exception"); + throw new InvalidDatabaseException("Error while reading org", e); + } + } + + private String readOrgRecord(int seekOrg) throws IOException { + if (seekOrg == databaseSegments[0]) { return null; } + ByteBuffer buf = readRecordBuf(seekOrg, MAX_ORG_RECORD_LENGTH); + return readString(buf); } /** @@ -982,35 +861,7 @@ private synchronized int seekCountryV6(InetAddress addr) { int offset = 0; _check_mtime(); for (int depth = 127; depth >= 0; depth--) { - if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { - // read from memory - for (int i = 0; i < 2 * MAX_RECORD_LENGTH; i++) { - buf[i] = dbbuffer[(2 * recordLength * offset) + i]; - } - } else if ((dboptions & GEOIP_INDEX_CACHE) != 0) { - // read from index cache - for (int i = 0; i < 2 * MAX_RECORD_LENGTH; i++) { - buf[i] = index_cache[(2 * recordLength * offset) + i]; - } - } else { - // read from disk - try { - file.seek(2 * recordLength * offset); - file.readFully(buf); - } catch (IOException e) { - System.out.println("IO Exception"); - } - } - for (int i = 0; i < 2; i++) { - x[i] = 0; - for (int j = 0; j < recordLength; j++) { - int y = buf[i * recordLength + j]; - if (y < 0) { - y += 256; - } - x[i] += (y << (j * 8)); - } - } + readIntoSeekBuf(buf, x, offset); int bnum = 127 - depth; int idx = bnum >> 3; @@ -1030,10 +881,8 @@ private synchronized int seekCountryV6(InetAddress addr) { } } - // shouldn't reach here - System.err.println("Error seeking country while seeking " + throw new InvalidDatabaseException("Error seeking country while searching for " + addr.getHostAddress()); - return 0; } /** @@ -1049,35 +898,7 @@ private synchronized int seekCountry(long ipAddress) { int offset = 0; _check_mtime(); for (int depth = 31; depth >= 0; depth--) { - if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { - // read from memory - for (int i = 0; i < 2 * recordLength; i++) { - buf[i] = dbbuffer[(2 * recordLength * offset) + i]; - } - } else if ((dboptions & GEOIP_INDEX_CACHE) != 0) { - // read from index cache - for (int i = 0; i < 2 * recordLength; i++) { - buf[i] = index_cache[(2 * recordLength * offset) + i]; - } - } else { - // read from disk - try { - file.seek(2 * recordLength * offset); - file.readFully(buf); - } catch (IOException e) { - System.out.println("IO Exception"); - } - } - for (int i = 0; i < 2; i++) { - x[i] = 0; - for (int j = 0; j < recordLength; j++) { - int y = buf[i * recordLength + j]; - if (y < 0) { - y += 256; - } - x[i] += (y << (j * 8)); - } - } + readIntoSeekBuf(buf, x, offset); if ((ipAddress & (1 << depth)) > 0) { if (x[1] >= databaseSegments[0]) { @@ -1093,10 +914,38 @@ private synchronized int seekCountry(long ipAddress) { offset = x[0]; } } + throw new InvalidDatabaseException("Error seeking country while searching for " + + ipAddress); + } - // shouldn't reach here - System.err.println("Error seeking country while seeking " + ipAddress); - return 0; + private void readIntoSeekBuf(byte[] buf, int[] x, int offset) { + if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { + // read from memory + for (int i = 0; i < 2 * recordLength; i++) { + buf[i] = dbbuffer[(2 * recordLength * offset) + i]; + } + } else if ((dboptions & GEOIP_INDEX_CACHE) != 0) { + // read from index cache + System.arraycopy(index_cache, (2 * recordLength * offset), buf, 0, 2 * recordLength); + } else { + // read from disk + try { + file.seek(2 * recordLength * offset); + file.read(buf); + } catch (IOException e) { + throw new InvalidDatabaseException("Error seeking in database", e); + } + } + for (int i = 0; i < 2; i++) { + x[i] = 0; + for (int j = 0; j < recordLength; j++) { + int y = buf[i * recordLength + j]; + if (y < 0) { + y += 256; + } + x[i] += (y << (j * 8)); + } + } } /** diff --git a/src/test/java/com/maxmind/geoip/CityLookupTest.java b/src/test/java/com/maxmind/geoip/CityLookupTest.java index 47b40e7..15fa53e 100644 --- a/src/test/java/com/maxmind/geoip/CityLookupTest.java +++ b/src/test/java/com/maxmind/geoip/CityLookupTest.java @@ -41,4 +41,13 @@ public void testCityLookup() throws IOException { } + @Test(expected=InvalidDatabaseException.class) + public void testCityLookupInInvalidDatabase() throws IOException { + + LookupService cl = new LookupService( + "src/test/resources/GeoIP/GeoIPCity-Corrupt.dat", + LookupService.GEOIP_MEMORY_CACHE); + Location l2 = cl.getLocation("66.92.181.240"); + + } } diff --git a/src/test/resources/GeoIP/GeoIPCity-Corrupt.dat b/src/test/resources/GeoIP/GeoIPCity-Corrupt.dat new file mode 100644 index 0000000000000000000000000000000000000000..2508759f6633abb4335d8215ec80c7a26ee9450b GIT binary patch literal 4065 zcmY+H33L->7lz+EEzm+~*|!2s8=!2JbV+I5N-4B}MWC&K8yecs20~Jj6s&@R3gU{p zAfkYv0^$au;sPiji>xYwC>RhCWK$8@r2m)KbIdoeE$9Q9v~C zq2MDyJ)nn$wq$+M>^7jp0I`&YWFxJ{0gct;fdus?l%`}ept;P;)GVmzy7?1+k0XHxP=)}Ww$j)RJ(#)ReTbSp|61$5O_TUSK@Mr&T{ttv-Pg0D_u? zfEx9Qlu6`WWGy-0!aWo-#$;fM`n|w?>QgDx$oqi@C=Y6NI%Nj=5HOSSF!=~zwvVmI zX8J!4EC3d=^aMEzc#>kOPf?x*p3&r4;5qf@DKC(-$vNbUz+8&y@DgPn@G|8Ua=unK zSa?-^HLys1G4PuD>y$UhCBRb3GOfNzSx&x1z74#i)psfHkt=|el=ro|3iyEjA^DN! zyUoR+e?oq$`Defy^|in{`sbQ_L0J!M&}1XjnaHsB<`4s*qJGN}W0 z0lR^(xqF=ahWwV?Lw-l@CHIlvlcvvp;0OAT&G6<0q5v{YjU1)fxJjw0xoOyAK(hT zGtUl*3?QUMMv*3rrqmPE*Q9|Uh90X)LqQ{Y9NAd&cuInxi6%`c%>>OgX(8yt8Cnb4 z2)xYO3fc)q>Y7A>Ly#_L&!hv{kxU|OWHM=HP7&DYoiynz=t55=yK3G|&|SR;rKccG zldA;1=p~#mL-T%=OfpO0q-2x5Ni#zZC0B4YS#XQ4yj5_U z`t6iaf;%X8k}h&ISx#1vZgLD+Nt(`80+0Gw$~ba*6vk`r6Zi$y+?^m;ObKW;NC}ZO zf{Bz#TD^->E4W*edjyl!rwHz)-zS(Vm?pSi@TA}=!2^N^1&;_G70iml;wVfPtfI^i zY>vW1G0fd_h?+*g)B+`InSU!jwaT!<3&jKO*=={V3&E!7;&c`fq|$ zloRHzzea|3-pVcT%uei{}Ei#yiOwT z{idzi#Db+BMTsWsk@YPs(CZmvA(ml7vJn|aHYVdq(=)+B6ZNJRn$eqU(t^^`LMuvZ z&D&7glIx<8sCj!z2ePAuB#KR|$&?fec1=22=uGcIrfS~RLN|JMa=*FM<`D}I&Bstr zC=?uB6R4~*e^eJHd-^6^Ue_R8kdOXeTdBLU##iA-t|KGMiS~}>F(wK1BNR+yKwtQd6@ALT4J14`Lj=Xb8d#mDBZ;Cao zr7++g@Arj}o8`>RkxEChV}H!>lDM-X(q~el*}}%=7bH8D#u*707E3?3H{hzZ^>w?- z&74Q4CRODn>>3plnRBqmTkQ$Cg2;6`GM!kTmb~&n%$Z?v;eo{{@Nz| z!^v}+#2?Iv4HxC3pTDNk>k9t+8L(`gZR^bVLlWc7aG=ob4F%lh^=ooUn*kjL56DHa z-&_atjy|K*J};QKFg*i9f?o5gzGiNxo{1d><>#Q-Rq3lWZ|WyfqQZ$eFmLgB_MLm$ z59*tRVqPX^ZjQr&cIo!jrxO30{!dWi_mx+9%t_YwvR^#SHHC-DtK7k~P*u%%chKiC z|1AW2*=I*;*%%(_>T}mjG=t`yPq}i1M+|kBPpEPG%KbHg5YR2vzVDlJP_{N>9_V_A+R9B~> zeI2?CE5LAh$Uy?AV?%WllomP8i^?37Y!=2#YQ0c-+3~Sl)D19i$)2#?@*8aXI8C)1e)S)Nj|Eyg)D&Y$H#sXOGVbk(|m hw4=7NjgClG<{wvUzKe`ww!$qAGizO4on@ZH{{SqaZ~XuO literal 0 HcmV?d00001 From 57b1ca97ad0183f945d2770849e7014360af163d Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Jan 2016 06:55:22 -0800 Subject: [PATCH 2/4] Reduce unnecessary byte[] allocations --- .../java/com/maxmind/geoip/LookupService.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/maxmind/geoip/LookupService.java b/src/main/java/com/maxmind/geoip/LookupService.java index 259a5e5..eadfd41 100644 --- a/src/main/java/com/maxmind/geoip/LookupService.java +++ b/src/main/java/com/maxmind/geoip/LookupService.java @@ -726,21 +726,22 @@ private Location readCityRecord(int seekCountry) throws IOException { } private ByteBuffer readRecordBuf(int seek, int maxLength) throws IOException { - byte[] recordBuf = new byte[maxLength]; int recordPointer = seek + (2 * recordLength - 1) * databaseSegments[0]; + ByteBuffer buffer; if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { - // read from memory - System.arraycopy(dbbuffer, recordPointer, recordBuf, 0, Math - .min(dbbuffer.length - recordPointer, recordBuf.length)); + buffer = ByteBuffer.wrap(dbbuffer, recordPointer, Math + .min(dbbuffer.length - recordPointer, maxLength)); } else { + byte[] recordBuf = new byte[maxLength]; // read from disk file.seek(recordPointer); file.read(recordBuf); + buffer = ByteBuffer.wrap(recordBuf); } - return ByteBuffer.wrap(recordBuf); + return buffer; } @@ -861,7 +862,7 @@ private synchronized int seekCountryV6(InetAddress addr) { int offset = 0; _check_mtime(); for (int depth = 127; depth >= 0; depth--) { - readIntoSeekBuf(buf, x, offset); + readNode(buf, x, offset); int bnum = 127 - depth; int idx = bnum >> 3; @@ -898,7 +899,7 @@ private synchronized int seekCountry(long ipAddress) { int offset = 0; _check_mtime(); for (int depth = 31; depth >= 0; depth--) { - readIntoSeekBuf(buf, x, offset); + readNode(buf, x, offset); if ((ipAddress & (1 << depth)) > 0) { if (x[1] >= databaseSegments[0]) { @@ -918,12 +919,10 @@ private synchronized int seekCountry(long ipAddress) { + ipAddress); } - private void readIntoSeekBuf(byte[] buf, int[] x, int offset) { + private void readNode(byte[] buf, int[] x, int offset) { if ((dboptions & GEOIP_MEMORY_CACHE) == 1) { // read from memory - for (int i = 0; i < 2 * recordLength; i++) { - buf[i] = dbbuffer[(2 * recordLength * offset) + i]; - } + System.arraycopy(dbbuffer, (2 * recordLength * offset), buf, 0, 2 * recordLength); } else if ((dboptions & GEOIP_INDEX_CACHE) != 0) { // read from index cache System.arraycopy(index_cache, (2 * recordLength * offset), buf, 0, 2 * recordLength); From 51458905e8f203a1b91a578d3ee1cd7d477748dd Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Jan 2016 07:11:12 -0800 Subject: [PATCH 3/4] Update changes for 1.3.0 --- Changes.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Changes.md b/Changes.md index b34dc00..5b421f7 100644 --- a/Changes.md +++ b/Changes.md @@ -1,6 +1,19 @@ Changes ======= +1.3.0 (2016-01-XX) +------------------ + +* `LookupService` will now throw an `InvalidDatabaseException` if there is a + problem with the database. This is a unchecked, runtime exception in order + to not introduce an API change. Previously `LookupService` would swallow + exceptions, either returning `null` or an invalid value or throwing an + exception such as `ArrayIndexOutOfBoundsException` cause by the invalid + state. +* When using `GEOIP_MEMORY_CACHE`, the number of allocations has been reduced, + providing a moderate performance increase. +* Minor code clean-up and de-duplication. + 1.2.15 (2015-07-17) ------------------- @@ -87,7 +100,7 @@ Changes * Update FIPS codes 20100530 ( Boris Zentner ) * Update FIPS codes 20100510 ( Boris Zentner ) * Add IPv6 support via getCountryV6 ( Boris Zentner ) -* Add exmaple for IPv6 lookups: CountryLookupTestV6 ( Boris Zentner ) +* Add example for IPv6 lookups: CountryLookupTestV6 ( Boris Zentner ) * Fix DatabaseInfo string ( Boris Zentner ) * Add netmask and last_netmask methods ( Boris Zentner ) * Remove static keyword from objects that are reinitialized in the init() @@ -109,7 +122,7 @@ Changes * Fix rare out of range error, when the last entry of the Org/ISP/Domain database is copied ( MEMORY_CACHE only ). ( Boris Zentner ) * Update timezones for Australia -* Remore "\n" from RegionLookupTest.java. println add already a newline +* Remove "\n" from RegionLookupTest.java. println add already a newline ( Boris Zentner ) * Change regionName.java and generate_regionName.pl to support FIPS codes with letters ( Boris Zentner ). @@ -195,7 +208,7 @@ Changes 1.1.1 (2002-10-31) ------------------ -* Added support for GeoIP Full Edition, remaned CountryLookup class +* Added support for GeoIP Full Edition, renamed CountryLookup class to Lookup 1.1.0 (2002-10-28) From 600db7ea75e9bb4a8b05ff46e319dca8217e325c Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Jan 2016 07:15:20 -0800 Subject: [PATCH 4/4] Make exception class final --- src/main/java/com/maxmind/geoip/InvalidDatabaseException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/maxmind/geoip/InvalidDatabaseException.java b/src/main/java/com/maxmind/geoip/InvalidDatabaseException.java index 504a68e..c5dde17 100644 --- a/src/main/java/com/maxmind/geoip/InvalidDatabaseException.java +++ b/src/main/java/com/maxmind/geoip/InvalidDatabaseException.java @@ -7,7 +7,7 @@ * unexpected data formatting. This generally suggests that the database is * corrupt or otherwise not in a format supported by the reader. */ -public class InvalidDatabaseException extends RuntimeException { +public final class InvalidDatabaseException extends RuntimeException { /** * @param message A message describing the reason why the exception was thrown.