2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
29 #include "geoipdatabase.h"
34 #include <QHostAddress>
37 #include "base/global.h"
38 #include "base/path.h"
42 const qint32 MAX_FILE_SIZE
= 67108864; // 64MB
43 const quint32 MAX_METADATA_SIZE
= 131072; // 128KB
44 const char METADATA_BEGIN_MARK
[] = "\xab\xcd\xefMaxMind.com";
45 const char DATA_SECTION_SEPARATOR
[16] = {0};
61 DataCacheContainer
= 12,
68 struct DataFieldDescriptor
70 DataType fieldType
{DataType::Unknown
};
73 quint32 fieldSize
= 0;
74 quint32 offset
; // Pointer
78 GeoIPDatabase::GeoIPDatabase(const quint32 size
)
86 , m_data(new uchar
[size
])
90 GeoIPDatabase
*GeoIPDatabase::load(const Path
&filename
, QString
&error
)
92 GeoIPDatabase
*db
= nullptr;
93 QFile file
{filename
.data()};
94 if (file
.size() > MAX_FILE_SIZE
)
96 error
= tr("Unsupported database file size.");
100 if (!file
.open(QFile::ReadOnly
))
102 error
= file
.errorString();
106 db
= new GeoIPDatabase(file
.size());
108 if (file
.read(reinterpret_cast<char *>(db
->m_data
), db
->m_size
) != db
->m_size
)
110 error
= file
.errorString();
116 if (!db
->parseMetadata(db
->readMetadata(), error
) || !db
->loadDB(error
))
125 GeoIPDatabase
*GeoIPDatabase::load(const QByteArray
&data
, QString
&error
)
127 GeoIPDatabase
*db
= nullptr;
128 if (data
.size() > MAX_FILE_SIZE
)
130 error
= tr("Unsupported database file size.");
134 db
= new GeoIPDatabase(data
.size());
136 memcpy(reinterpret_cast<char *>(db
->m_data
), data
.constData(), db
->m_size
);
138 if (!db
->parseMetadata(db
->readMetadata(), error
) || !db
->loadDB(error
))
147 GeoIPDatabase::~GeoIPDatabase()
152 QString
GeoIPDatabase::type() const
157 quint16
GeoIPDatabase::ipVersion() const
162 QDateTime
GeoIPDatabase::buildEpoch() const
167 QString
GeoIPDatabase::lookup(const QHostAddress
&hostAddr
) const
169 Q_IPV6ADDR addr
= hostAddr
.toIPv6Address();
171 const uchar
*ptr
= m_data
;
173 for (int i
= 0; i
< 16; ++i
)
175 for (int j
= 0; j
< 8; ++j
)
177 const bool right
= static_cast<bool>((addr
[i
] >> (7 - j
)) & 1);
178 // Interpret the left/right record as number
180 ptr
+= m_recordBytes
;
183 auto *idPtr
= reinterpret_cast<uchar
*>(&id
);
184 memcpy(&idPtr
[4 - m_recordBytes
], ptr
, m_recordBytes
);
185 fromBigEndian(idPtr
, 4);
187 if (id
== m_nodeCount
)
191 if (id
> m_nodeCount
)
193 QString country
= m_countries
.value(id
);
194 if (country
.isEmpty())
196 const quint32 offset
= id
- m_nodeCount
- sizeof(DATA_SECTION_SEPARATOR
);
197 quint32 tmp
= offset
+ m_indexSize
+ sizeof(DATA_SECTION_SEPARATOR
);
198 const QVariant val
= readDataField(tmp
);
199 if (val
.userType() == QMetaType::QVariantHash
)
201 country
= val
.toHash()[u
"country"_qs
].toHash()[u
"iso_code"_qs
].toString();
202 m_countries
[id
] = country
;
208 ptr
= m_data
+ (id
* m_nodeSize
);
215 #define CHECK_METADATA_REQ(key, type) \
216 if (!metadata.contains(key)) \
218 error = errMsgNotFound.arg(key); \
221 if (metadata.value(key).userType() != QMetaType::type) \
223 error = errMsgInvalid.arg(key); \
227 #define CHECK_METADATA_OPT(key, type) \
228 if (metadata.contains(key)) \
230 if (metadata.value(key).userType() != QMetaType::type) \
232 error = errMsgInvalid.arg(key); \
237 bool GeoIPDatabase::parseMetadata(const QVariantHash
&metadata
, QString
&error
)
239 const QString errMsgNotFound
= tr("Metadata error: '%1' entry not found.");
240 const QString errMsgInvalid
= tr("Metadata error: '%1' entry has invalid type.");
242 qDebug() << "Parsing MaxMindDB metadata...";
244 CHECK_METADATA_REQ(u
"binary_format_major_version"_qs
, UShort
);
245 CHECK_METADATA_REQ(u
"binary_format_minor_version"_qs
, UShort
);
246 const uint versionMajor
= metadata
.value(u
"binary_format_major_version"_qs
).toUInt();
247 const uint versionMinor
= metadata
.value(u
"binary_format_minor_version"_qs
).toUInt();
248 if (versionMajor
!= 2)
250 error
= tr("Unsupported database version: %1.%2").arg(versionMajor
).arg(versionMinor
);
254 CHECK_METADATA_REQ(u
"ip_version"_qs
, UShort
);
255 m_ipVersion
= metadata
.value(u
"ip_version"_qs
).value
<quint16
>();
256 if (m_ipVersion
!= 6)
258 error
= tr("Unsupported IP version: %1").arg(m_ipVersion
);
262 CHECK_METADATA_REQ(u
"record_size"_qs
, UShort
);
263 m_recordSize
= metadata
.value(u
"record_size"_qs
).value
<quint16
>();
264 if (m_recordSize
!= 24)
266 error
= tr("Unsupported record size: %1").arg(m_recordSize
);
269 m_nodeSize
= m_recordSize
/ 4;
270 m_recordBytes
= m_nodeSize
/ 2;
272 CHECK_METADATA_REQ(u
"node_count"_qs
, UInt
);
273 m_nodeCount
= metadata
.value(u
"node_count"_qs
).value
<quint32
>();
274 m_indexSize
= m_nodeCount
* m_nodeSize
;
276 CHECK_METADATA_REQ(u
"database_type"_qs
, QString
);
277 m_dbType
= metadata
.value(u
"database_type"_qs
).toString();
279 CHECK_METADATA_REQ(u
"build_epoch"_qs
, ULongLong
);
280 m_buildEpoch
= QDateTime::fromSecsSinceEpoch(metadata
.value(u
"build_epoch"_qs
).toULongLong());
282 CHECK_METADATA_OPT(u
"languages"_qs
, QVariantList
);
283 CHECK_METADATA_OPT(u
"description"_qs
, QVariantHash
);
288 bool GeoIPDatabase::loadDB(QString
&error
) const
290 qDebug() << "Parsing IP geolocation database index tree...";
292 const int nodeSize
= m_recordSize
/ 4; // in bytes
293 const int indexSize
= m_nodeCount
* nodeSize
;
294 if ((m_size
< (indexSize
+ sizeof(DATA_SECTION_SEPARATOR
)))
295 || (memcmp(m_data
+ indexSize
, DATA_SECTION_SEPARATOR
, sizeof(DATA_SECTION_SEPARATOR
)) != 0))
297 error
= tr("Database corrupted: no data section found.");
304 QVariantHash
GeoIPDatabase::readMetadata() const
306 const char *ptr
= reinterpret_cast<const char *>(m_data
);
307 quint32 size
= m_size
;
308 if (m_size
> MAX_METADATA_SIZE
)
310 ptr
+= m_size
- MAX_METADATA_SIZE
;
311 size
= MAX_METADATA_SIZE
;
314 const QByteArray data
= QByteArray::fromRawData(ptr
, size
);
315 int index
= data
.lastIndexOf(METADATA_BEGIN_MARK
);
318 if (m_size
> MAX_METADATA_SIZE
)
319 index
+= (m_size
- MAX_METADATA_SIZE
); // from begin of all data
320 auto offset
= static_cast<quint32
>(index
+ strlen(METADATA_BEGIN_MARK
));
321 const QVariant metadata
= readDataField(offset
);
322 if (metadata
.userType() == QMetaType::QVariantHash
)
323 return metadata
.toHash();
329 QVariant
GeoIPDatabase::readDataField(quint32
&offset
) const
331 DataFieldDescriptor descr
;
332 if (!readDataFieldDescriptor(offset
, descr
))
335 quint32 locOffset
= offset
;
336 bool usePointer
= false;
337 if (descr
.fieldType
== DataType::Pointer
)
340 // convert offset from data section to global
341 locOffset
= descr
.offset
+ (m_nodeCount
* m_recordSize
/ 4) + sizeof(DATA_SECTION_SEPARATOR
);
342 if (!readDataFieldDescriptor(locOffset
, descr
))
347 switch (descr
.fieldType
)
349 case DataType::Pointer
:
350 qDebug() << "* Illegal Pointer using";
352 case DataType::String
:
353 fieldValue
= QString::fromUtf8(reinterpret_cast<const char *>(m_data
+ locOffset
), descr
.fieldSize
);
354 locOffset
+= descr
.fieldSize
;
356 case DataType::Double
:
357 if (descr
.fieldSize
== 8)
358 fieldValue
= readPlainValue
<double>(locOffset
, descr
.fieldSize
);
360 qDebug() << "* Invalid field size for type: Double";
362 case DataType::Bytes
:
363 fieldValue
= QByteArray(reinterpret_cast<const char *>(m_data
+ locOffset
), descr
.fieldSize
);
364 locOffset
+= descr
.fieldSize
;
366 case DataType::Integer16
:
367 fieldValue
= readPlainValue
<quint16
>(locOffset
, descr
.fieldSize
);
369 case DataType::Integer32
:
370 fieldValue
= readPlainValue
<quint32
>(locOffset
, descr
.fieldSize
);
373 fieldValue
= readMapValue(locOffset
, descr
.fieldSize
);
375 case DataType::SignedInteger32
:
376 fieldValue
= readPlainValue
<qint32
>(locOffset
, descr
.fieldSize
);
378 case DataType::Integer64
:
379 fieldValue
= readPlainValue
<quint64
>(locOffset
, descr
.fieldSize
);
381 case DataType::Integer128
:
382 qDebug() << "* Unsupported data type: Integer128";
384 case DataType::Array
:
385 fieldValue
= readArrayValue(locOffset
, descr
.fieldSize
);
387 case DataType::DataCacheContainer
:
388 qDebug() << "* Unsupported data type: DataCacheContainer";
390 case DataType::EndMarker
:
391 qDebug() << "* Unsupported data type: EndMarker";
393 case DataType::Boolean
:
394 fieldValue
= QVariant::fromValue(static_cast<bool>(descr
.fieldSize
));
396 case DataType::Float
:
397 if (descr
.fieldSize
== 4)
398 fieldValue
= readPlainValue
<float>(locOffset
, descr
.fieldSize
);
400 qDebug() << "* Invalid field size for type: Float";
403 qDebug() << "* Unsupported data type: Unknown";
411 bool GeoIPDatabase::readDataFieldDescriptor(quint32
&offset
, DataFieldDescriptor
&out
) const
413 const uchar
*dataPtr
= m_data
+ offset
;
414 const int availSize
= m_size
- offset
;
415 if (availSize
< 1) return false;
417 out
.fieldType
= static_cast<DataType
>((dataPtr
[0] & 0xE0) >> 5);
418 if (out
.fieldType
== DataType::Pointer
)
420 const int size
= ((dataPtr
[0] & 0x18) >> 3);
421 if (availSize
< (size
+ 2)) return false;
424 out
.offset
= ((dataPtr
[0] & 0x07) << 8) + dataPtr
[1];
426 out
.offset
= ((dataPtr
[0] & 0x07) << 16) + (dataPtr
[1] << 8) + dataPtr
[2] + 2048;
428 out
.offset
= ((dataPtr
[0] & 0x07) << 24) + (dataPtr
[1] << 16) + (dataPtr
[2] << 8) + dataPtr
[3] + 526336;
430 out
.offset
= (dataPtr
[1] << 24) + (dataPtr
[2] << 16) + (dataPtr
[3] << 8) + dataPtr
[4];
436 out
.fieldSize
= dataPtr
[0] & 0x1F;
437 if (out
.fieldSize
<= 28)
439 if (out
.fieldType
== DataType::Unknown
)
441 out
.fieldType
= static_cast<DataType
>(dataPtr
[1] + 7);
442 if ((out
.fieldType
<= DataType::Map
) || (out
.fieldType
> DataType::Float
) || (availSize
< 3))
451 else if (out
.fieldSize
== 29)
453 if (availSize
< 2) return false;
454 out
.fieldSize
= dataPtr
[1] + 29;
457 else if (out
.fieldSize
== 30)
459 if (availSize
< 3) return false;
460 out
.fieldSize
= (dataPtr
[1] << 8) + dataPtr
[2] + 285;
463 else if (out
.fieldSize
== 31)
465 if (availSize
< 4) return false;
466 out
.fieldSize
= (dataPtr
[1] << 16) + (dataPtr
[2] << 8) + dataPtr
[3] + 65821;
473 void GeoIPDatabase::fromBigEndian(uchar
*buf
, const quint32 len
) const
475 #if (Q_BYTE_ORDER == Q_LITTLE_ENDIAN)
476 std::reverse(buf
, buf
+ len
);
483 QVariant
GeoIPDatabase::readMapValue(quint32
&offset
, const quint32 count
) const
487 for (quint32 i
= 0; i
< count
; ++i
)
489 QVariant field
= readDataField(offset
);
490 if (field
.userType() != QMetaType::QString
)
493 const QString key
= field
.toString();
494 field
= readDataField(offset
);
495 if (field
.userType() == QVariant::Invalid
)
504 QVariant
GeoIPDatabase::readArrayValue(quint32
&offset
, const quint32 count
) const
508 for (quint32 i
= 0; i
< count
; ++i
)
510 const QVariant field
= readDataField(offset
);
511 if (field
.userType() == QVariant::Invalid
)