Don't save the GeoIP nodes beforehand.

It saves a lot of memory and isn't much faster.
This commit is contained in:
sledgehammer999 2015-07-23 19:34:42 +03:00
parent 787b824d90
commit ef5b3b90c3
2 changed files with 385 additions and 455 deletions

View file

@ -27,12 +27,8 @@
*/ */
#include <QDebug> #include <QDebug>
#include <QCoreApplication>
#include <QVariant> #include <QVariant>
#include <QHash> #include <QHash>
#include <QList>
#include <QScopedArrayPointer>
#include <QScopedPointer>
#include <QHostAddress> #include <QHostAddress>
#include <QDateTime> #include <QDateTime>
#include <QFile> #include <QFile>
@ -40,28 +36,15 @@
#include "core/types.h" #include "core/types.h"
#include "geoipdatabase.h" #include "geoipdatabase.h"
struct Node
{
quint32 left;
quint32 right;
};
struct GeoIPData
{
// Metadata
quint16 ipVersion;
quint16 recordSize;
quint32 nodeCount;
QDateTime buildEpoch;
// Search data
QList<Node> index;
QHash<quint32, QString> countries;
};
namespace namespace
{ {
const quint32 __ENDIAN_TEST__ = 0x00000001; const quint32 __ENDIAN_TEST__ = 0x00000001;
const bool __IS_LITTLE_ENDIAN__ = (reinterpret_cast<const uchar *>(&__ENDIAN_TEST__)[0] == 0x01); const bool __IS_LITTLE_ENDIAN__ = (reinterpret_cast<const uchar *>(&__ENDIAN_TEST__)[0] == 0x01);
const int MAX_FILE_SIZE = 10485760; // 10MB
const char DB_TYPE[] = "GeoLite2-Country";
const quint32 MAX_METADATA_SIZE = 131072; // 128KB
const char METADATA_BEGIN_MARK[] = "\xab\xcd\xefMaxMind.com";
const char DATA_SECTION_SEPARATOR[16] = { 0 };
enum class DataType enum class DataType
{ {
@ -83,89 +66,60 @@ namespace
Float = 15 Float = 15
}; };
struct DataFieldDescriptor
{
DataType fieldType;
union
{
quint32 fieldSize;
quint32 offset; // Pointer
};
};
const int MAX_FILE_SIZE = 10485760; // 10MB
const char DB_TYPE[] = "GeoLite2-Country";
const quint32 MAX_METADATA_SIZE = 131072; // 128KB
const char METADATA_BEGIN_MARK[] = "\xab\xcd\xefMaxMind.com";
const char DATA_SECTION_SEPARATOR[16] = { 0 };
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
Q_IPV6ADDR createMappedAddress(quint32 ip4); Q_IPV6ADDR createMappedAddress(quint32 ip4);
#endif #endif
class Loader
{
Q_DECLARE_TR_FUNCTIONS(GeoIPDatabase)
public:
GeoIPData *load(const QString &filename);
GeoIPData *load(const QByteArray &data);
QString error() const;
private:
bool parseMetadata(const QVariantHash &metadata);
bool loadDB();
QVariantHash readMetadata();
QVariant readDataField(quint32 &offset);
bool readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor &out);
void fromBigEndian(uchar *buf, quint32 len);
QVariant readMapValue(quint32 &offset, quint32 count);
QVariant readArrayValue(quint32 &offset, quint32 count);
template<typename T>
QVariant readPlainValue(quint32 &offset, quint8 len)
{
T value = 0;
const uchar *const data = m_data + offset;
const quint32 availSize = m_size - offset;
if ((len > 0) && (len <= sizeof(T) && (availSize >= len))) {
// copy input data to last 'len' bytes of 'value'
uchar *dst = reinterpret_cast<uchar *>(&value) + (sizeof(T) - len);
memcpy(dst, data, len);
fromBigEndian(reinterpret_cast<uchar *>(&value), sizeof(T));
offset += len;
}
return QVariant::fromValue(value);
}
private:
const uchar *m_data;
quint32 m_size;
QString m_error;
GeoIPData *m_geoIPData;
};
} }
// GeoIPDatabase struct DataFieldDescriptor
{
DataType fieldType;
union
{
quint32 fieldSize;
quint32 offset; // Pointer
};
};
GeoIPDatabase::GeoIPDatabase(GeoIPData *geoIPData) GeoIPDatabase::GeoIPDatabase(quint32 size)
: m_geoIPData(geoIPData) : m_ipVersion(0)
, m_recordSize(0)
, m_nodeCount(0)
, m_nodeSize(0)
, m_indexSize(0)
, m_recordBytes(0)
, m_size(size)
, m_data(new uchar[size])
{ {
} }
GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error) GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
{ {
GeoIPDatabase *db = 0; GeoIPDatabase *db = 0;
QFile file(filename);
if (file.size() > MAX_FILE_SIZE) {
error = tr("Unsupported database file size.");
return 0;
}
Loader loader; if (!file.open(QFile::ReadOnly)) {
GeoIPData *geoIPData = loader.load(filename); error = file.errorString();
if (!geoIPData) return 0;
error = loader.error(); }
else
db = new GeoIPDatabase(geoIPData); db = new GeoIPDatabase(file.size());
if (file.read((char *)db->m_data, db->m_size) != db->m_size) {
error = file.errorString();
delete db;
return 0;
}
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
delete db;
return 0;
}
return db; return db;
} }
@ -173,20 +127,26 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error) GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
{ {
GeoIPDatabase *db = 0; GeoIPDatabase *db = 0;
if (data.size() > MAX_FILE_SIZE) {
error = tr("Unsupported database file size.");
return 0;
}
Loader loader; db = new GeoIPDatabase(data.size());
GeoIPData *geoIPData = loader.load(data);
if (!geoIPData) memcpy((char *)db->m_data, data.constData(), db->m_size);
error = loader.error();
else if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
db = new GeoIPDatabase(geoIPData); delete db;
return 0;
}
return db; return db;
} }
GeoIPDatabase::~GeoIPDatabase() GeoIPDatabase::~GeoIPDatabase()
{ {
delete m_geoIPData; delete [] m_data;
} }
QString GeoIPDatabase::type() const QString GeoIPDatabase::type() const
@ -196,12 +156,12 @@ QString GeoIPDatabase::type() const
quint16 GeoIPDatabase::ipVersion() const quint16 GeoIPDatabase::ipVersion() const
{ {
return m_geoIPData->ipVersion; return m_ipVersion;
} }
QDateTime GeoIPDatabase::buildEpoch() const QDateTime GeoIPDatabase::buildEpoch() const
{ {
return m_geoIPData->buildEpoch; return m_buildEpoch;
} }
QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
@ -213,402 +173,330 @@ QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
#else #else
Q_IPV6ADDR addr = hostAddr.toIPv6Address(); Q_IPV6ADDR addr = hostAddr.toIPv6Address();
#endif #endif
const quint32 nodeCount = static_cast<quint32>(m_geoIPData->index.size());
Node node = m_geoIPData->index[0]; const uchar *ptr = m_data;
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 8; ++j) { for (int j = 0; j < 8; ++j) {
bool right = static_cast<bool>((addr[i] >> (7 - j)) & 1); bool right = static_cast<bool>((addr[i] >> (7 - j)) & 1);
quint32 id = (right ? node.right : node.left); // Interpret the left/right record as number
if (id == nodeCount) if (right)
ptr += m_recordBytes;
quint32 id = 0;
uchar *idPtr = reinterpret_cast<uchar *>(&id);
memcpy(&idPtr[4 - m_recordBytes], ptr, m_recordBytes);
fromBigEndian(idPtr, 4);
if (id == m_nodeCount) {
return QString(); return QString();
else if (id > nodeCount) }
return m_geoIPData->countries[id]; else if (id > m_nodeCount) {
else QString country = m_countries.value(id);
node = m_geoIPData->index[id]; if (country.isEmpty()) {
const quint32 offset = id - m_nodeCount - sizeof(DATA_SECTION_SEPARATOR);
quint32 tmp = offset + m_indexSize + sizeof(DATA_SECTION_SEPARATOR);
QVariant val = readDataField(tmp);
if (val.userType() == QMetaType::QVariantHash) {
country = val.toHash()["country"].toHash()["iso_code"].toString();
m_countries[id] = country;
}
}
return country;
}
else {
ptr = m_data + (id * m_nodeSize);
}
} }
} }
return QString(); return QString();
} }
namespace
{
// Loader
GeoIPData *Loader::load(const QString &filename)
{
QFile file(filename);
if (file.size() > MAX_FILE_SIZE) {
m_error = tr("Unsupported database file size.");
return 0;
}
if (!file.open(QFile::ReadOnly)) {
m_error = file.errorString();
return 0;
}
m_size = file.size();
QScopedArrayPointer<uchar> data(new uchar[m_size]);
m_data = data.data();
if (file.read((char *)m_data, m_size) != m_size) {
m_error = file.errorString();
return 0;
}
QScopedPointer<GeoIPData> geoIPData(new GeoIPData);
m_geoIPData = geoIPData.data();
if (!parseMetadata(readMetadata()) || !loadDB())
return 0;
return geoIPData.take();
}
GeoIPData *Loader::load(const QByteArray &data)
{
if (data.size() > MAX_FILE_SIZE) {
m_error = tr("Unsupported database file size.");
return 0;
}
m_size = data.size();
m_data = reinterpret_cast<const uchar *>(data.constData());
QScopedPointer<GeoIPData> geoIPData(new GeoIPData);
m_geoIPData = geoIPData.data();
if (!parseMetadata(readMetadata()) || !loadDB())
return 0;
return geoIPData.take();
}
QString Loader::error() const
{
return m_error;
}
#define CHECK_METADATA_REQ(key, type) \ #define CHECK_METADATA_REQ(key, type) \
if (!metadata.contains(#key)) { \ if (!metadata.contains(#key)) { \
m_error = errMsgNotFound.arg(#key); \ error = errMsgNotFound.arg(#key); \
return false; \ return false; \
} \ } \
else if (metadata.value(#key).userType() != QMetaType::type) { \ else if (metadata.value(#key).userType() != QMetaType::type) { \
m_error = errMsgInvalid.arg(#key); \ error = errMsgInvalid.arg(#key); \
return false; \ return false; \
} }
#define CHECK_METADATA_OPT(key, type) \ #define CHECK_METADATA_OPT(key, type) \
if (metadata.contains(#key)) { \ if (metadata.contains(#key)) { \
if (metadata.value(#key).userType() != QMetaType::type) { \ if (metadata.value(#key).userType() != QMetaType::type) { \
m_error = errMsgInvalid.arg(#key); \ error = errMsgInvalid.arg(#key); \
return false; \ return false; \
} \ } \
}
bool GeoIPDatabase::parseMetadata(const QVariantHash &metadata, QString &error)
{
const QString errMsgNotFound = tr("Metadata error: '%1' entry not found.");
const QString errMsgInvalid = tr("Metadata error: '%1' entry has invalid type.");
qDebug() << "Parsing MaxMindDB metadata...";
CHECK_METADATA_REQ(binary_format_major_version, UShort);
CHECK_METADATA_REQ(binary_format_minor_version, UShort);
uint versionMajor = metadata.value("binary_format_major_version").toUInt();
uint versionMinor = metadata.value("binary_format_minor_version").toUInt();
if (versionMajor != 2) {
error = tr("Unsupported database version: %1.%2").arg(versionMajor).arg(versionMinor);
return false;
} }
bool Loader::parseMetadata(const QVariantHash &metadata) CHECK_METADATA_REQ(ip_version, UShort);
{ m_ipVersion = metadata.value("ip_version").value<quint16>();
const QString errMsgNotFound = tr("Metadata error: '%1' entry not found."); if (m_ipVersion != 6) {
const QString errMsgInvalid = tr("Metadata error: '%1' entry has invalid type."); error = tr("Unsupported IP version: %1").arg(m_ipVersion);
return false;
qDebug() << "Parsing MaxMindDB metadata...";
CHECK_METADATA_REQ(binary_format_major_version, UShort);
CHECK_METADATA_REQ(binary_format_minor_version, UShort);
uint versionMajor = metadata.value("binary_format_major_version").toUInt();
uint versionMinor = metadata.value("binary_format_minor_version").toUInt();
if (versionMajor != 2) {
m_error = tr("Unsupported database version: %1.%2").arg(versionMajor).arg(versionMinor);
return false;
}
CHECK_METADATA_REQ(ip_version, UShort);
m_geoIPData->ipVersion = metadata.value("ip_version").value<quint16>();
if (m_geoIPData->ipVersion != 6) {
m_error = tr("Unsupported IP version: %1").arg(m_geoIPData->ipVersion);
return false;
}
CHECK_METADATA_REQ(record_size, UShort);
m_geoIPData->recordSize = metadata.value("record_size").value<quint16>();
if (m_geoIPData->recordSize != 24) {
m_error = tr("Unsupported record size: %1").arg(m_geoIPData->recordSize);
return false;
}
CHECK_METADATA_REQ(node_count, UInt);
m_geoIPData->nodeCount = metadata.value("node_count").value<quint32>();
CHECK_METADATA_REQ(database_type, QString);
QString dbType = metadata.value("database_type").toString();
if (dbType != DB_TYPE) {
m_error = tr("Invalid database type: %1").arg(dbType);
return false;
}
CHECK_METADATA_REQ(build_epoch, ULongLong);
m_geoIPData->buildEpoch = QDateTime::fromTime_t(metadata.value("build_epoch").toULongLong());
CHECK_METADATA_OPT(languages, QVariantList);
CHECK_METADATA_OPT(description, QVariantHash);
return true;
} }
bool Loader::loadDB() CHECK_METADATA_REQ(record_size, UShort);
{ m_recordSize = metadata.value("record_size").value<quint16>();
qDebug() << "Parsing MaxMindDB index tree..."; if (m_recordSize != 24) {
error = tr("Unsupported record size: %1").arg(m_recordSize);
return false;
}
m_nodeSize = m_recordSize / 4;
m_recordBytes = m_nodeSize / 2;
const int nodeSize = m_geoIPData->recordSize / 4; // in bytes CHECK_METADATA_REQ(node_count, UInt);
const int indexSize = m_geoIPData->nodeCount * nodeSize; m_nodeCount = metadata.value("node_count").value<quint32>();
if ((m_size < (indexSize + sizeof(DATA_SECTION_SEPARATOR))) m_indexSize = m_nodeCount * m_nodeSize;
|| (memcmp(m_data + indexSize, DATA_SECTION_SEPARATOR, sizeof(DATA_SECTION_SEPARATOR)) != 0)) {
m_error = tr("Database corrupted: no data section found.");
return false;
}
m_geoIPData->index.reserve(m_geoIPData->nodeCount); CHECK_METADATA_REQ(database_type, QString);
QString dbType = metadata.value("database_type").toString();
const int recordBytes = nodeSize / 2; if (dbType != DB_TYPE) {
const uchar *ptr = m_data; error = tr("Invalid database type: %1").arg(dbType);
bool left = true; return false;
Node node;
for (quint32 i = 0; i < (2 * m_geoIPData->nodeCount); ++i) {
quint32 id = 0;
uchar *idPtr = reinterpret_cast<uchar *>(&id);
memcpy(&idPtr[4 - recordBytes], ptr, recordBytes);
fromBigEndian(idPtr, 4);
if ((id > m_geoIPData->nodeCount) && !m_geoIPData->countries.contains(id)) {
const quint32 offset = id - m_geoIPData->nodeCount - sizeof(DATA_SECTION_SEPARATOR);
quint32 tmp = offset + indexSize + sizeof(DATA_SECTION_SEPARATOR);
QVariant val = readDataField(tmp);
if (val.userType() == QMetaType::QVariantHash) {
m_geoIPData->countries[id] = val.toHash()["country"].toHash()["iso_code"].toString();
}
else if (val.userType() == QVariant::Invalid) {
m_error = tr("Database corrupted: invalid data type at DATA@%1").arg(offset, 8, 16, QLatin1Char('0'));
return false;
}
else {
m_error = tr("Invalid database: unsupported data type at DATA@%1").arg(offset, 8, 16, QLatin1Char('0'));
return false;
}
}
if (left) {
node.left = id;
}
else {
node.right = id;
m_geoIPData->index << node;
}
left = !left;
ptr += recordBytes;
}
return true;
} }
QVariantHash Loader::readMetadata() CHECK_METADATA_REQ(build_epoch, ULongLong);
{ m_buildEpoch = QDateTime::fromTime_t(metadata.value("build_epoch").toULongLong());
const char *ptr = reinterpret_cast<const char *>(m_data);
quint32 size = m_size;
if (m_size > MAX_METADATA_SIZE) {
ptr += m_size - MAX_METADATA_SIZE;
size = MAX_METADATA_SIZE;
}
const QByteArray data = QByteArray::fromRawData(ptr, size); CHECK_METADATA_OPT(languages, QVariantList);
int index = data.lastIndexOf(METADATA_BEGIN_MARK); CHECK_METADATA_OPT(description, QVariantHash);
if (index >= 0) {
if (m_size > MAX_METADATA_SIZE)
index += (m_size - MAX_METADATA_SIZE); // from begin of all data
quint32 offset = static_cast<quint32>(index + strlen(METADATA_BEGIN_MARK));
QVariant metadata = readDataField(offset);
m_size = index; // truncate m_size to not contain metadata section
if (metadata.userType() == QMetaType::QVariantHash)
return metadata.toHash();
}
return QVariantHash(); return true;
}
bool GeoIPDatabase::loadDB(QString &error) const
{
qDebug() << "Parsing MaxMindDB index tree...";
const int nodeSize = m_recordSize / 4; // in bytes
const int indexSize = m_nodeCount * nodeSize;
if ((m_size < (indexSize + sizeof(DATA_SECTION_SEPARATOR)))
|| (memcmp(m_data + indexSize, DATA_SECTION_SEPARATOR, sizeof(DATA_SECTION_SEPARATOR)) != 0)) {
error = tr("Database corrupted: no data section found.");
return false;
} }
QVariant Loader::readDataField(quint32 &offset) return true;
{ }
DataFieldDescriptor descr;
if (!readDataFieldDescriptor(offset, descr)) QVariantHash GeoIPDatabase::readMetadata() const
{
const char *ptr = reinterpret_cast<const char *>(m_data);
quint32 size = m_size;
if (m_size > MAX_METADATA_SIZE) {
ptr += m_size - MAX_METADATA_SIZE;
size = MAX_METADATA_SIZE;
}
const QByteArray data = QByteArray::fromRawData(ptr, size);
int index = data.lastIndexOf(METADATA_BEGIN_MARK);
if (index >= 0) {
if (m_size > MAX_METADATA_SIZE)
index += (m_size - MAX_METADATA_SIZE); // from begin of all data
quint32 offset = static_cast<quint32>(index + strlen(METADATA_BEGIN_MARK));
QVariant metadata = readDataField(offset);
if (metadata.userType() == QMetaType::QVariantHash)
return metadata.toHash();
}
return QVariantHash();
}
QVariant GeoIPDatabase::readDataField(quint32 &offset) const
{
DataFieldDescriptor descr;
if (!readDataFieldDescriptor(offset, descr))
return QVariant();
quint32 locOffset = offset;
bool usePointer = false;
if (descr.fieldType == DataType::Pointer) {
usePointer = true;
// convert offset from data section to global
locOffset = descr.offset + (m_nodeCount * m_recordSize / 4) + sizeof(DATA_SECTION_SEPARATOR);
if (!readDataFieldDescriptor(locOffset, descr))
return QVariant(); return QVariant();
quint32 locOffset = offset;
bool usePointer = false;
if (descr.fieldType == DataType::Pointer) {
usePointer = true;
// convert offset from data section to global
locOffset = descr.offset + (m_geoIPData->nodeCount * m_geoIPData->recordSize / 4) + sizeof(DATA_SECTION_SEPARATOR);
if (!readDataFieldDescriptor(locOffset, descr))
return QVariant();
}
QVariant fieldValue;
switch (descr.fieldType) {
case DataType::Pointer:
qDebug() << "* Illegal Pointer using";
break;
case DataType::String:
fieldValue = QString::fromUtf8(reinterpret_cast<const char *>(m_data + locOffset), descr.fieldSize);
locOffset += descr.fieldSize;
break;
case DataType::Double:
if (descr.fieldSize == 8)
fieldValue = readPlainValue<double>(locOffset, descr.fieldSize);
else
qDebug() << "* Invalid field size for type: Double";
break;
case DataType::Bytes:
fieldValue = QByteArray(reinterpret_cast<const char *>(m_data + locOffset), descr.fieldSize);
locOffset += descr.fieldSize;
break;
case DataType::Integer16:
fieldValue = readPlainValue<quint16>(locOffset, descr.fieldSize);
break;
case DataType::Integer32:
fieldValue = readPlainValue<quint32>(locOffset, descr.fieldSize);
break;
case DataType::Map:
fieldValue = readMapValue(locOffset, descr.fieldSize);
break;
case DataType::SignedInteger32:
fieldValue = readPlainValue<qint32>(locOffset, descr.fieldSize);
break;
case DataType::Integer64:
fieldValue = readPlainValue<quint64>(locOffset, descr.fieldSize);
break;
case DataType::Integer128:
qDebug() << "* Unsupported data type: Integer128";
break;
case DataType::Array:
fieldValue = readArrayValue(locOffset, descr.fieldSize);
break;
case DataType::DataCacheContainer:
qDebug() << "* Unsupported data type: DataCacheContainer";
break;
case DataType::EndMarker:
qDebug() << "* Unsupported data type: EndMarker";
break;
case DataType::Boolean:
fieldValue = QVariant::fromValue(static_cast<bool>(descr.fieldSize));
break;
case DataType::Float:
if (descr.fieldSize == 4)
fieldValue = readPlainValue<float>(locOffset, descr.fieldSize);
else
qDebug() << "* Invalid field size for type: Float";
break;
}
if (!usePointer)
offset = locOffset;
return fieldValue;
} }
bool Loader::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor &out) QVariant fieldValue;
{ switch (descr.fieldType) {
const uchar *dataPtr = m_data + offset; case DataType::Pointer:
int availSize = m_size - offset; qDebug() << "* Illegal Pointer using";
if (availSize < 1) return false; break;
case DataType::String:
fieldValue = QString::fromUtf8(reinterpret_cast<const char *>(m_data + locOffset), descr.fieldSize);
locOffset += descr.fieldSize;
break;
case DataType::Double:
if (descr.fieldSize == 8)
fieldValue = readPlainValue<double>(locOffset, descr.fieldSize);
else
qDebug() << "* Invalid field size for type: Double";
break;
case DataType::Bytes:
fieldValue = QByteArray(reinterpret_cast<const char *>(m_data + locOffset), descr.fieldSize);
locOffset += descr.fieldSize;
break;
case DataType::Integer16:
fieldValue = readPlainValue<quint16>(locOffset, descr.fieldSize);
break;
case DataType::Integer32:
fieldValue = readPlainValue<quint32>(locOffset, descr.fieldSize);
break;
case DataType::Map:
fieldValue = readMapValue(locOffset, descr.fieldSize);
break;
case DataType::SignedInteger32:
fieldValue = readPlainValue<qint32>(locOffset, descr.fieldSize);
break;
case DataType::Integer64:
fieldValue = readPlainValue<quint64>(locOffset, descr.fieldSize);
break;
case DataType::Integer128:
qDebug() << "* Unsupported data type: Integer128";
break;
case DataType::Array:
fieldValue = readArrayValue(locOffset, descr.fieldSize);
break;
case DataType::DataCacheContainer:
qDebug() << "* Unsupported data type: DataCacheContainer";
break;
case DataType::EndMarker:
qDebug() << "* Unsupported data type: EndMarker";
break;
case DataType::Boolean:
fieldValue = QVariant::fromValue(static_cast<bool>(descr.fieldSize));
break;
case DataType::Float:
if (descr.fieldSize == 4)
fieldValue = readPlainValue<float>(locOffset, descr.fieldSize);
else
qDebug() << "* Invalid field size for type: Float";
break;
}
out.fieldType = static_cast<DataType>((dataPtr[0] & 0xE0) >> 5); if (!usePointer)
if (out.fieldType == DataType::Pointer) { offset = locOffset;
int size = ((dataPtr[0] & 0x18) >> 3); return fieldValue;
if (availSize < (size + 2)) return false; }
if (size == 0) bool GeoIPDatabase::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor &out) const
out.offset = ((dataPtr[0] & 0x07) << 8) + dataPtr[1]; {
else if (size == 1) const uchar *dataPtr = m_data + offset;
out.offset = ((dataPtr[0] & 0x07) << 16) + (dataPtr[1] << 8) + dataPtr[2] + 2048; int availSize = m_size - offset;
else if (size == 2) if (availSize < 1) return false;
out.offset = ((dataPtr[0] & 0x07) << 24) + (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 526336;
else if (size == 3)
out.offset = (dataPtr[1] << 24) + (dataPtr[2] << 16) + (dataPtr[3] << 8) + dataPtr[4];
offset += size + 2; out.fieldType = static_cast<DataType>((dataPtr[0] & 0xE0) >> 5);
return true; if (out.fieldType == DataType::Pointer) {
} int size = ((dataPtr[0] & 0x18) >> 3);
if (availSize < (size + 2)) return false;
out.fieldSize = dataPtr[0] & 0x1F; if (size == 0)
if (out.fieldSize <= 28) { out.offset = ((dataPtr[0] & 0x07) << 8) + dataPtr[1];
if (out.fieldType == DataType::Unknown) { else if (size == 1)
out.fieldType = static_cast<DataType>(dataPtr[1] + 7); out.offset = ((dataPtr[0] & 0x07) << 16) + (dataPtr[1] << 8) + dataPtr[2] + 2048;
if ((out.fieldType <= DataType::Map) || (out.fieldType > DataType::Float) || (availSize < 3)) else if (size == 2)
return false; out.offset = ((dataPtr[0] & 0x07) << 24) + (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 526336;
offset += 2; else if (size == 3)
} out.offset = (dataPtr[1] << 24) + (dataPtr[2] << 16) + (dataPtr[3] << 8) + dataPtr[4];
else {
offset += 1; offset += size + 2;
} return true;
} }
else if (out.fieldSize == 29) {
if (availSize < 2) return false; out.fieldSize = dataPtr[0] & 0x1F;
out.fieldSize = dataPtr[1] + 29; if (out.fieldSize <= 28) {
if (out.fieldType == DataType::Unknown) {
out.fieldType = static_cast<DataType>(dataPtr[1] + 7);
if ((out.fieldType <= DataType::Map) || (out.fieldType > DataType::Float) || (availSize < 3))
return false;
offset += 2; offset += 2;
} }
else if (out.fieldSize == 30) { else {
if (availSize < 3) return false; offset += 1;
out.fieldSize = (dataPtr[1] << 8) + dataPtr[2] + 285;
offset += 3;
} }
else if (out.fieldSize == 31) { }
if (availSize < 4) return false; else if (out.fieldSize == 29) {
out.fieldSize = (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 65821; if (availSize < 2) return false;
offset += 4; out.fieldSize = dataPtr[1] + 29;
} offset += 2;
}
return true; else if (out.fieldSize == 30) {
if (availSize < 3) return false;
out.fieldSize = (dataPtr[1] << 8) + dataPtr[2] + 285;
offset += 3;
}
else if (out.fieldSize == 31) {
if (availSize < 4) return false;
out.fieldSize = (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 65821;
offset += 4;
} }
void Loader::fromBigEndian(uchar *buf, quint32 len) return true;
{ }
if (__IS_LITTLE_ENDIAN__)
std::reverse(buf, buf + len); void GeoIPDatabase::fromBigEndian(uchar *buf, quint32 len) const
{
if (__IS_LITTLE_ENDIAN__)
std::reverse(buf, buf + len);
}
QVariant GeoIPDatabase::readMapValue(quint32 &offset, quint32 count) const
{
QVariantHash map;
for (quint32 i = 0; i < count; ++i) {
QVariant field = readDataField(offset);
if (field.userType() != QMetaType::QString)
return QVariant();
QString key = field.toString();
field = readDataField(offset);
if (field.userType() == QVariant::Invalid)
return QVariant();
map[key] = field;
} }
QVariant Loader::readMapValue(quint32 &offset, quint32 count) return map;
{ }
QVariantHash map;
for (quint32 i = 0; i < count; ++i) { QVariant GeoIPDatabase::readArrayValue(quint32 &offset, quint32 count) const
QVariant field = readDataField(offset); {
if (field.userType() != QMetaType::QString) QVariantList array;
return QVariant();
QString key = field.toString(); for (quint32 i = 0; i < count; ++i) {
field = readDataField(offset); QVariant field = readDataField(offset);
if (field.userType() == QVariant::Invalid) if (field.userType() == QVariant::Invalid)
return QVariant(); return QVariant();
map[key] = field; array.append(field);
}
return map;
} }
QVariant Loader::readArrayValue(quint32 &offset, quint32 count) return array;
{ }
QVariantList array;
for (quint32 i = 0; i < count; ++i) {
QVariant field = readDataField(offset);
if (field.userType() == QVariant::Invalid)
return QVariant();
array.append(field);
}
return array;
}
namespace
{
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
Q_IPV6ADDR createMappedAddress(quint32 ip4) Q_IPV6ADDR createMappedAddress(quint32 ip4)
{ {

View file

@ -30,16 +30,19 @@
#define GEOIPDATABASE_H #define GEOIPDATABASE_H
#include <QtGlobal> #include <QtGlobal>
#include <QCoreApplication>
class QHostAddress; class QHostAddress;
class QString; class QString;
class QByteArray; class QByteArray;
class QDateTime; class QDateTime;
struct GeoIPData; struct DataFieldDescriptor;
class GeoIPDatabase class GeoIPDatabase
{ {
Q_DECLARE_TR_FUNCTIONS(GeoIPDatabase)
public: public:
static GeoIPDatabase *load(const QString &filename, QString &error); static GeoIPDatabase *load(const QString &filename, QString &error);
static GeoIPDatabase *load(const QByteArray &data, QString &error); static GeoIPDatabase *load(const QByteArray &data, QString &error);
@ -52,9 +55,48 @@ public:
QString lookup(const QHostAddress &hostAddr) const; QString lookup(const QHostAddress &hostAddr) const;
private: private:
GeoIPDatabase(GeoIPData *geoIPData); GeoIPDatabase(quint32 size);
GeoIPData *m_geoIPData; bool parseMetadata(const QVariantHash &metadata, QString &error);
bool loadDB(QString &error) const;
QVariantHash readMetadata() const;
QVariant readDataField(quint32 &offset) const;
bool readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor &out) const;
void fromBigEndian(uchar *buf, quint32 len) const;
QVariant readMapValue(quint32 &offset, quint32 count) const;
QVariant readArrayValue(quint32 &offset, quint32 count) const;
template<typename T>
QVariant readPlainValue(quint32 &offset, quint8 len) const
{
T value = 0;
const uchar *const data = m_data + offset;
const quint32 availSize = m_size - offset;
if ((len > 0) && (len <= sizeof(T) && (availSize >= len))) {
// copy input data to last 'len' bytes of 'value'
uchar *dst = reinterpret_cast<uchar *>(&value) + (sizeof(T) - len);
memcpy(dst, data, len);
fromBigEndian(reinterpret_cast<uchar *>(&value), sizeof(T));
offset += len;
}
return QVariant::fromValue(value);
}
// Metadata
quint16 m_ipVersion;
quint16 m_recordSize;
quint32 m_nodeCount;
int m_nodeSize;
int m_indexSize;
int m_recordBytes;
QDateTime m_buildEpoch;
// Search data
mutable QHash<quint32, QString> m_countries;
quint32 m_size;
const uchar *m_data;
}; };
#endif // GEOIPDATABASE_H #endif // GEOIPDATABASE_H