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
)
80 , m_data(new uchar
[size
])
84 GeoIPDatabase
*GeoIPDatabase::load(const Path
&filename
, QString
&error
)
86 QFile file
{filename
.data()};
87 if (file
.size() > MAX_FILE_SIZE
)
89 error
= tr("Unsupported database file size.");
93 if (!file
.open(QFile::ReadOnly
))
95 error
= file
.errorString();
99 auto *db
= new GeoIPDatabase(file
.size());
101 if (file
.read(reinterpret_cast<char *>(db
->m_data
), db
->m_size
) != db
->m_size
)
103 error
= file
.errorString();
109 if (!db
->parseMetadata(db
->readMetadata(), error
) || !db
->loadDB(error
))
118 GeoIPDatabase
*GeoIPDatabase::load(const QByteArray
&data
, QString
&error
)
120 if (data
.size() > MAX_FILE_SIZE
)
122 error
= tr("Unsupported database file size.");
126 auto *db
= new GeoIPDatabase(data
.size());
128 memcpy(reinterpret_cast<char *>(db
->m_data
), data
.constData(), db
->m_size
);
130 if (!db
->parseMetadata(db
->readMetadata(), error
) || !db
->loadDB(error
))
139 GeoIPDatabase::~GeoIPDatabase()
144 QString
GeoIPDatabase::type() const
149 quint16
GeoIPDatabase::ipVersion() const
154 QDateTime
GeoIPDatabase::buildEpoch() const
159 QString
GeoIPDatabase::lookup(const QHostAddress
&hostAddr
) const
161 Q_IPV6ADDR addr
= hostAddr
.toIPv6Address();
163 const uchar
*ptr
= m_data
;
165 for (int i
= 0; i
< 16; ++i
)
167 for (int j
= 0; j
< 8; ++j
)
169 const bool right
= static_cast<bool>((addr
[i
] >> (7 - j
)) & 1);
170 // Interpret the left/right record as number
172 ptr
+= m_recordBytes
;
175 auto *idPtr
= reinterpret_cast<uchar
*>(&id
);
176 memcpy(&idPtr
[4 - m_recordBytes
], ptr
, m_recordBytes
);
177 fromBigEndian(idPtr
, 4);
179 if (id
== m_nodeCount
)
183 if (id
> m_nodeCount
)
185 QString country
= m_countries
.value(id
);
186 if (country
.isEmpty())
188 const quint32 offset
= id
- m_nodeCount
- sizeof(DATA_SECTION_SEPARATOR
);
189 quint32 tmp
= offset
+ m_indexSize
+ sizeof(DATA_SECTION_SEPARATOR
);
190 const QVariant val
= readDataField(tmp
);
191 if (val
.userType() == QMetaType::QVariantHash
)
193 country
= val
.toHash()[u
"country"_s
].toHash()[u
"iso_code"_s
].toString();
194 m_countries
[id
] = country
;
200 ptr
= m_data
+ (id
* m_nodeSize
);
207 #define CHECK_METADATA_REQ(key, type) \
208 if (!metadata.contains(key)) \
210 error = errMsgNotFound.arg(key); \
213 if (metadata.value(key).userType() != QMetaType::type) \
215 error = errMsgInvalid.arg(key); \
219 #define CHECK_METADATA_OPT(key, type) \
220 if (metadata.contains(key)) \
222 if (metadata.value(key).userType() != QMetaType::type) \
224 error = errMsgInvalid.arg(key); \
229 bool GeoIPDatabase::parseMetadata(const QVariantHash
&metadata
, QString
&error
)
231 const QString errMsgNotFound
= tr("Metadata error: '%1' entry not found.");
232 const QString errMsgInvalid
= tr("Metadata error: '%1' entry has invalid type.");
234 qDebug() << "Parsing MaxMindDB metadata...";
236 CHECK_METADATA_REQ(u
"binary_format_major_version"_s
, UShort
);
237 CHECK_METADATA_REQ(u
"binary_format_minor_version"_s
, UShort
);
238 const uint versionMajor
= metadata
.value(u
"binary_format_major_version"_s
).toUInt();
239 const uint versionMinor
= metadata
.value(u
"binary_format_minor_version"_s
).toUInt();
240 if (versionMajor
!= 2)
242 error
= tr("Unsupported database version: %1.%2").arg(versionMajor
).arg(versionMinor
);
246 CHECK_METADATA_REQ(u
"ip_version"_s
, UShort
);
247 m_ipVersion
= metadata
.value(u
"ip_version"_s
).value
<quint16
>();
248 if (m_ipVersion
!= 6)
250 error
= tr("Unsupported IP version: %1").arg(m_ipVersion
);
254 CHECK_METADATA_REQ(u
"record_size"_s
, UShort
);
255 m_recordSize
= metadata
.value(u
"record_size"_s
).value
<quint16
>();
256 if (m_recordSize
!= 24)
258 error
= tr("Unsupported record size: %1").arg(m_recordSize
);
261 m_nodeSize
= m_recordSize
/ 4;
262 m_recordBytes
= m_nodeSize
/ 2;
264 CHECK_METADATA_REQ(u
"node_count"_s
, UInt
);
265 m_nodeCount
= metadata
.value(u
"node_count"_s
).value
<quint32
>();
266 m_indexSize
= m_nodeCount
* m_nodeSize
;
268 CHECK_METADATA_REQ(u
"database_type"_s
, QString
);
269 m_dbType
= metadata
.value(u
"database_type"_s
).toString();
271 CHECK_METADATA_REQ(u
"build_epoch"_s
, ULongLong
);
272 m_buildEpoch
= QDateTime::fromSecsSinceEpoch(metadata
.value(u
"build_epoch"_s
).toULongLong());
274 CHECK_METADATA_OPT(u
"languages"_s
, QVariantList
);
275 CHECK_METADATA_OPT(u
"description"_s
, QVariantHash
);
280 bool GeoIPDatabase::loadDB(QString
&error
) const
282 qDebug() << "Parsing IP geolocation database index tree...";
284 const int nodeSize
= m_recordSize
/ 4; // in bytes
285 const int indexSize
= m_nodeCount
* nodeSize
;
286 if ((m_size
< (indexSize
+ sizeof(DATA_SECTION_SEPARATOR
)))
287 || (memcmp(m_data
+ indexSize
, DATA_SECTION_SEPARATOR
, sizeof(DATA_SECTION_SEPARATOR
)) != 0))
289 error
= tr("Database corrupted: no data section found.");
296 QVariantHash
GeoIPDatabase::readMetadata() const
298 const char *ptr
= reinterpret_cast<const char *>(m_data
);
299 quint32 size
= m_size
;
300 if (m_size
> MAX_METADATA_SIZE
)
302 ptr
+= m_size
- MAX_METADATA_SIZE
;
303 size
= MAX_METADATA_SIZE
;
306 const QByteArray data
= QByteArray::fromRawData(ptr
, size
);
307 int index
= data
.lastIndexOf(METADATA_BEGIN_MARK
);
310 if (m_size
> MAX_METADATA_SIZE
)
311 index
+= (m_size
- MAX_METADATA_SIZE
); // from begin of all data
312 auto offset
= static_cast<quint32
>(index
+ strlen(METADATA_BEGIN_MARK
));
313 const QVariant metadata
= readDataField(offset
);
314 if (metadata
.userType() == QMetaType::QVariantHash
)
315 return metadata
.toHash();
321 QVariant
GeoIPDatabase::readDataField(quint32
&offset
) const
323 DataFieldDescriptor descr
;
324 if (!readDataFieldDescriptor(offset
, descr
))
327 quint32 locOffset
= offset
;
328 bool usePointer
= false;
329 if (descr
.fieldType
== DataType::Pointer
)
332 // convert offset from data section to global
333 locOffset
= descr
.offset
+ (m_nodeCount
* m_recordSize
/ 4) + sizeof(DATA_SECTION_SEPARATOR
);
334 if (!readDataFieldDescriptor(locOffset
, descr
))
339 switch (descr
.fieldType
)
341 case DataType::Pointer
:
342 qDebug() << "* Illegal Pointer using";
344 case DataType::String
:
345 fieldValue
= QString::fromUtf8(reinterpret_cast<const char *>(m_data
+ locOffset
), descr
.fieldSize
);
346 locOffset
+= descr
.fieldSize
;
348 case DataType::Double
:
349 if (descr
.fieldSize
== 8)
350 fieldValue
= readPlainValue
<double>(locOffset
, descr
.fieldSize
);
352 qDebug() << "* Invalid field size for type: Double";
354 case DataType::Bytes
:
355 fieldValue
= QByteArray(reinterpret_cast<const char *>(m_data
+ locOffset
), descr
.fieldSize
);
356 locOffset
+= descr
.fieldSize
;
358 case DataType::Integer16
:
359 fieldValue
= readPlainValue
<quint16
>(locOffset
, descr
.fieldSize
);
361 case DataType::Integer32
:
362 fieldValue
= readPlainValue
<quint32
>(locOffset
, descr
.fieldSize
);
365 fieldValue
= readMapValue(locOffset
, descr
.fieldSize
);
367 case DataType::SignedInteger32
:
368 fieldValue
= readPlainValue
<qint32
>(locOffset
, descr
.fieldSize
);
370 case DataType::Integer64
:
371 fieldValue
= readPlainValue
<quint64
>(locOffset
, descr
.fieldSize
);
373 case DataType::Integer128
:
374 qDebug() << "* Unsupported data type: Integer128";
376 case DataType::Array
:
377 fieldValue
= readArrayValue(locOffset
, descr
.fieldSize
);
379 case DataType::DataCacheContainer
:
380 qDebug() << "* Unsupported data type: DataCacheContainer";
382 case DataType::EndMarker
:
383 qDebug() << "* Unsupported data type: EndMarker";
385 case DataType::Boolean
:
386 fieldValue
= QVariant::fromValue(static_cast<bool>(descr
.fieldSize
));
388 case DataType::Float
:
389 if (descr
.fieldSize
== 4)
390 fieldValue
= readPlainValue
<float>(locOffset
, descr
.fieldSize
);
392 qDebug() << "* Invalid field size for type: Float";
395 qDebug() << "* Unsupported data type: Unknown";
403 bool GeoIPDatabase::readDataFieldDescriptor(quint32
&offset
, DataFieldDescriptor
&out
) const
405 const uchar
*dataPtr
= m_data
+ offset
;
406 const int availSize
= m_size
- offset
;
407 if (availSize
< 1) return false;
409 out
.fieldType
= static_cast<DataType
>((dataPtr
[0] & 0xE0) >> 5);
410 if (out
.fieldType
== DataType::Pointer
)
412 const int size
= ((dataPtr
[0] & 0x18) >> 3);
413 if (availSize
< (size
+ 2)) return false;
416 out
.offset
= ((dataPtr
[0] & 0x07) << 8) + dataPtr
[1];
418 out
.offset
= ((dataPtr
[0] & 0x07) << 16) + (dataPtr
[1] << 8) + dataPtr
[2] + 2048;
420 out
.offset
= ((dataPtr
[0] & 0x07) << 24) + (dataPtr
[1] << 16) + (dataPtr
[2] << 8) + dataPtr
[3] + 526336;
422 out
.offset
= (dataPtr
[1] << 24) + (dataPtr
[2] << 16) + (dataPtr
[3] << 8) + dataPtr
[4];
428 out
.fieldSize
= dataPtr
[0] & 0x1F;
429 if (out
.fieldSize
<= 28)
431 if (out
.fieldType
== DataType::Unknown
)
433 out
.fieldType
= static_cast<DataType
>(dataPtr
[1] + 7);
434 if ((out
.fieldType
<= DataType::Map
) || (out
.fieldType
> DataType::Float
) || (availSize
< 3))
443 else if (out
.fieldSize
== 29)
445 if (availSize
< 2) return false;
446 out
.fieldSize
= dataPtr
[1] + 29;
449 else if (out
.fieldSize
== 30)
451 if (availSize
< 3) return false;
452 out
.fieldSize
= (dataPtr
[1] << 8) + dataPtr
[2] + 285;
455 else if (out
.fieldSize
== 31)
457 if (availSize
< 4) return false;
458 out
.fieldSize
= (dataPtr
[1] << 16) + (dataPtr
[2] << 8) + dataPtr
[3] + 65821;
465 void GeoIPDatabase::fromBigEndian([[maybe_unused
]] uchar
*buf
, [[maybe_unused
]] const quint32 len
) const
467 #if (Q_BYTE_ORDER == Q_LITTLE_ENDIAN)
468 std::reverse(buf
, buf
+ len
);
472 QVariant
GeoIPDatabase::readMapValue(quint32
&offset
, const quint32 count
) const
476 for (quint32 i
= 0; i
< count
; ++i
)
478 QVariant field
= readDataField(offset
);
479 if (field
.userType() != QMetaType::QString
)
482 const QString key
= field
.toString();
483 field
= readDataField(offset
);
484 if (field
.userType() == QMetaType::UnknownType
)
493 QVariant
GeoIPDatabase::readArrayValue(quint32
&offset
, const quint32 count
) const
497 for (quint32 i
= 0; i
< count
; ++i
)
499 const QVariant field
= readDataField(offset
);
500 if (field
.userType() == QMetaType::UnknownType
)
509 template <typename T
>
510 QVariant
GeoIPDatabase::readPlainValue(quint32
&offset
, const quint8 len
) const
513 const uchar
*const data
= m_data
+ offset
;
514 const quint32 availSize
= m_size
- offset
;
516 if ((len
> 0) && (len
<= sizeof(T
) && (availSize
>= len
)))
518 // copy input data to last 'len' bytes of 'value'
519 uchar
*dst
= reinterpret_cast<uchar
*>(&value
) + (sizeof(T
) - len
);
520 memcpy(dst
, data
, len
);
521 fromBigEndian(reinterpret_cast<uchar
*>(&value
), sizeof(T
));
525 return QVariant::fromValue(value
);