diff --git a/src/base/bittorrent/peerinfo.cpp b/src/base/bittorrent/peerinfo.cpp index efbdf2288bd..64fe1258b01 100644 --- a/src/base/bittorrent/peerinfo.cpp +++ b/src/base/bittorrent/peerinfo.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015-2022 Vladimir Golovnev + * Copyright (C) 2015-2023 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -33,6 +33,7 @@ #include "base/bittorrent/ltqbitarray.h" #include "base/net/geoipmanager.h" #include "base/unicodestrings.h" +#include "base/utils/bytearray.h" #include "peeraddress.h" using namespace BitTorrent; @@ -168,6 +169,9 @@ bool PeerInfo::isPlaintextEncrypted() const PeerAddress PeerInfo::address() const { + if (useI2PSocket()) + return {}; + // fast path for platforms which boost.asio internal struct maps to `sockaddr` return {QHostAddress(m_nativeInfo.ip.data()), m_nativeInfo.ip.port()}; // slow path for the others @@ -175,6 +179,23 @@ PeerAddress PeerInfo::address() const // , m_nativeInfo.ip.port()}; } +QString PeerInfo::I2PAddress() const +{ + if (!useI2PSocket()) + return {}; + +#ifdef QBT_USES_LIBTORRENT2 + if (m_I2PAddress.isEmpty()) + { + const lt::sha256_hash destHash = m_nativeInfo.i2p_destination(); + const QByteArray base32Dest = Utils::ByteArray::toBase32({destHash.data(), destHash.size()}).replace('=', "").toLower(); + m_I2PAddress = QString::fromLatin1(base32Dest) + u".b32.i2p"; + } +#endif + + return m_I2PAddress; +} + QString PeerInfo::client() const { return QString::fromStdString(m_nativeInfo.client); diff --git a/src/base/bittorrent/peerinfo.h b/src/base/bittorrent/peerinfo.h index e6db94f8930..86f3c0c7adb 100644 --- a/src/base/bittorrent/peerinfo.h +++ b/src/base/bittorrent/peerinfo.h @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015-2022 Vladimir Golovnev + * Copyright (C) 2015-2023 Vladimir Golovnev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -76,6 +76,7 @@ namespace BitTorrent bool isPlaintextEncrypted() const; PeerAddress address() const; + QString I2PAddress() const; QString client() const; QString peerIdClient() const; qreal progress() const; @@ -101,5 +102,6 @@ namespace BitTorrent QString m_flagsDescription; mutable QString m_country; + mutable QString m_I2PAddress; }; } diff --git a/src/base/utils/bytearray.cpp b/src/base/utils/bytearray.cpp index 86d70aa977f..d557117425a 100644 --- a/src/base/utils/bytearray.cpp +++ b/src/base/utils/bytearray.cpp @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Vladimir Golovnev * Copyright (C) 2018 Mike Tzou (Chocobo1) * * This program is free software; you can redistribute it and/or @@ -68,3 +69,45 @@ const QByteArray Utils::ByteArray::midView(const QByteArray &in, const int pos, : len; return QByteArray::fromRawData(in.constData() + pos, validLen); } + +QByteArray Utils::ByteArray::toBase32(const QByteArray &in) +{ + const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + const char padchar = '='; + + const qsizetype inSize = in.size(); + + auto tmp = QByteArray((inSize + 4) / 5 * 8, Qt::Uninitialized); + qsizetype inIndex = 0; + char *out = tmp.data(); + while (inIndex < inSize) + { + // encode 5 bytes at a time + qsizetype inPadLen = 5; + int64_t chunk = 0; + while (inPadLen > 0) + { + chunk |= static_cast(static_cast(in.data()[inIndex++])) << (--inPadLen * 8); + if (inIndex == inSize) + break; + } + + const int outCharCounts[] = {8, 7, 5, 4, 2}; + for (int i = 7; i >= 0; --i) + { + if (i >= (8 - outCharCounts[inPadLen])) + { + const int shift = (i * 5); + const int64_t mask = static_cast(0x1f) << shift; + const int charIndex = (chunk & mask) >> shift; + *out++ = alphabet[charIndex]; + } + else + { + *out++ = padchar; + } + } + } + + return tmp; +} diff --git a/src/base/utils/bytearray.h b/src/base/utils/bytearray.h index 8d00f1671f3..792a4d1e7e4 100644 --- a/src/base/utils/bytearray.h +++ b/src/base/utils/bytearray.h @@ -1,5 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Vladimir Golovnev * Copyright (C) 2018 Mike Tzou (Chocobo1) * * This program is free software; you can redistribute it and/or @@ -41,4 +42,6 @@ namespace Utils::ByteArray // Mimic QByteArray::mid(pos, len) but instead of returning a full-copy, // we only return a partial view const QByteArray midView(const QByteArray &in, int pos, int len = -1); + + QByteArray toBase32(const QByteArray &in); } diff --git a/src/gui/properties/peerlistwidget.cpp b/src/gui/properties/peerlistwidget.cpp index 680eedf6e9f..77d9250f19c 100644 --- a/src/gui/properties/peerlistwidget.cpp +++ b/src/gui/properties/peerlistwidget.cpp @@ -121,7 +121,7 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent) // List Model m_listModel = new QStandardItemModel(0, PeerListColumns::COL_COUNT, this); m_listModel->setHeaderData(PeerListColumns::COUNTRY, Qt::Horizontal, tr("Country/Region")); // Country flag column - m_listModel->setHeaderData(PeerListColumns::IP, Qt::Horizontal, tr("IP")); + m_listModel->setHeaderData(PeerListColumns::IP, Qt::Horizontal, tr("IP/Address")); m_listModel->setHeaderData(PeerListColumns::PORT, Qt::Horizontal, tr("Port")); m_listModel->setHeaderData(PeerListColumns::FLAGS, Qt::Horizontal, tr("Flags")); m_listModel->setHeaderData(PeerListColumns::CONNECTION, Qt::Horizontal, tr("Connection")); @@ -434,9 +434,6 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent) const bool hideZeroValues = Preferences::instance()->getHideZeroValues(); for (const BitTorrent::PeerInfo &peer : peers) { - if (peer.address().ip.isNull()) - continue; - const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()}; auto itemIter = m_peerItems.find(peerEndpoint); @@ -448,7 +445,7 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent) const bool useI2PSocket = peer.useI2PSocket(); - const QString peerIPString = useI2PSocket ? tr("N/A") : peerEndpoint.address.ip.toString(); + const QString peerIPString = useI2PSocket ? peer.I2PAddress() : peerEndpoint.address.ip.toString(); setModelData(m_listModel, row, PeerListColumns::IP, peerIPString, peerIPString, {}, peerIPString); const QString peerIPHiddenString = useI2PSocket ? QString() : peerEndpoint.address.ip.toString(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7d8dce8921f..e43799f25ef 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,6 +15,7 @@ set(testFiles testorderedset.cpp testpath.cpp testutilscompare.cpp + testutilsbytearray.cpp testutilsgzip.cpp testutilsstring.cpp testutilsversion.cpp diff --git a/test/testutilsbytearray.cpp b/test/testutilsbytearray.cpp new file mode 100644 index 00000000000..951ded736a6 --- /dev/null +++ b/test/testutilsbytearray.cpp @@ -0,0 +1,54 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2023 Vladimir Golovnev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include + +#include "base/global.h" +#include "base/utils/bytearray.h" + +class TestUtilsByteArray final : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(TestUtilsByteArray) + +public: + TestUtilsByteArray() = default; + +private slots: + void testBase32Encode() const + { + QCOMPARE(Utils::ByteArray::toBase32(""), ""); + QCOMPARE(Utils::ByteArray::toBase32("0123456789"), "GAYTEMZUGU3DOOBZ"); + QCOMPARE(Utils::ByteArray::toBase32("ABCDE"), "IFBEGRCF"); + QCOMPARE(Utils::ByteArray::toBase32("0000000000"), "GAYDAMBQGAYDAMBQ"); + QCOMPARE(Utils::ByteArray::toBase32("1"), "GE======"); + } +}; + +QTEST_APPLESS_MAIN(TestUtilsByteArray) +#include "testutilsbytearray.moc"