diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp
index bb24b48f..410ee86f 100644
--- a/daemon/HTTPServer.cpp
+++ b/daemon/HTTPServer.cpp
@@ -1206,6 +1206,7 @@ namespace http {
}
s << "\r\n
\r\n";
}
+#ifndef NO_TORRENTS
auto& torrentsTunnels = i2p::client::context.GetTorrentsTunnels ();
if (!torrentsTunnels.empty ())
{
@@ -1223,10 +1224,12 @@ namespace http {
}
s << "\r\n
\r\n";
}
+#endif
}
static void ShowTorrentsPage (std::stringstream& s, std::map& params)
{
+#ifndef NO_TORRENTS
bool js; i2p::config::GetOption("http.javascript", js);
if (!js)
{
@@ -1284,6 +1287,9 @@ namespace http {
}
else
s << "Not found rpc tunnel " << tunnelname << "
\r\n";
+#else
+ s << "Torrents are not supported
\r\n";
+#endif
}
HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket):
diff --git a/libi2pd/Datagram.cpp b/libi2pd/Datagram.cpp
index 9e3362a2..9fb7776e 100644
--- a/libi2pd/Datagram.cpp
+++ b/libi2pd/Datagram.cpp
@@ -98,10 +98,10 @@ namespace datagram
{
constexpr uint8_t flags[] = { 0x00, 0x02 }; // datagram2, no options
// signature
- std::vector signedData (len + m_From.size () + 2);
- memcpy (signedData.data (), m_From.data (), m_From.size ());
- memcpy (signedData.data () + m_From.size (), flags, 2);
- memcpy (signedData.data () + m_From.size () + 2, payload, len);
+ std::vector signedData (len + 32 + 2);
+ memcpy (signedData.data (), session->GetRemoteIdent (), 32);
+ memcpy (signedData.data () + 32, flags, 2);
+ memcpy (signedData.data () + 32 + 2, payload, len);
m_Owner->Sign (signedData.data (), signedData.size (), m_Signature.data ());
// TODO: offline signatures and options
msg = CreateDataMessage ({{m_From.data (), m_From.size ()}, {flags, 2}, {payload, len},
@@ -267,7 +267,7 @@ namespace datagram
return;
}
std::vector signedData (len + 32 - identityLen - signatureLen);
- memcpy (signedData.data (), identity.GetIdentHash (), 32);
+ memcpy (signedData.data (), m_Owner->GetIdentity ()->GetIdentHash (), 32);
memcpy (signedData.data () + 32, buf + identityLen, signedData.size () - 32);
verified = transientVerifier ? transientVerifier->Verify (signedData.data (), signedData.size (), buf + len - signatureLen) :
identity.Verify (signedData.data (), signedData.size (), buf + len - signatureLen);
@@ -488,10 +488,10 @@ namespace datagram
void DatagramDestination::CleanUp ()
{
- if (m_Sessions.empty ()) return;
auto now = i2p::util::GetMillisecondsSinceEpoch();
- LogPrint(eLogDebug, "DatagramDestination: clean up sessions");
std::unique_lock lock(m_SessionsMutex);
+ if (m_Sessions.empty ()) return;
+ LogPrint(eLogDebug, "DatagramDestination: clean up sessions");
// for each session ...
for (auto it = m_Sessions.begin (); it != m_Sessions.end (); )
{
diff --git a/libi2pd/Datagram.h b/libi2pd/Datagram.h
index 22011153..478ec51d 100644
--- a/libi2pd/Datagram.h
+++ b/libi2pd/Datagram.h
@@ -80,6 +80,7 @@ namespace datagram
bool IsRatchets () const { return m_RoutingSession && m_RoutingSession->IsRatchets (); }
void SetRemoteLeaseSet (std::shared_ptr ls) { m_RemoteLeaseSet = ls; }
std::shared_ptr GetRemoteLeaseSet () const { return m_RemoteLeaseSet; }
+ const i2p::data::IdentHash& GetRemoteIdent () const { return m_RemoteIdent; }
DatagramVersion GetVersion () const { return m_Version; }
void SetVersion (DatagramVersion version) { m_Version = version; }
diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp
index 03321cf5..43a110bc 100644
--- a/libi2pd/HTTP.cpp
+++ b/libi2pd/HTTP.cpp
@@ -133,6 +133,7 @@ namespace http
}
/* hostname[:port][/path] */
+ if (pos_p >= url.length ()) return false; /* nothing left after schema or user part */
if (url.at(pos_p) == '[') // ipv6
{
auto pos_b = url.find(']', pos_p);
@@ -616,6 +617,11 @@ namespace http
return false; /* too large chunk */
char * buf = new char[len];
in.read (buf, len);
+ if (in.gcount () != len) /* chunk is shorter than announced */
+ {
+ delete[] buf;
+ return false;
+ }
out.write (buf, len);
delete[] buf;
std::getline (in, hexLen); // read \r\n after chunk
diff --git a/libi2pd/NTCP2.cpp b/libi2pd/NTCP2.cpp
index efbc29f2..f601d16b 100644
--- a/libi2pd/NTCP2.cpp
+++ b/libi2pd/NTCP2.cpp
@@ -1283,8 +1283,14 @@ namespace transport
LogPrint (eLogDebug, "NTCP2: Block type ", (int)blk, " of size ", size);
if (offset + size > len)
{
- LogPrint (eLogError, "NTCP2: Unexpected block length ", size);
- break;
+ if (blk == eNTCP2BlkPadding)
+ // padding is always last block and we set size up to end of frame
+ size = len - offset;
+ else
+ {
+ LogPrint (eLogError, "NTCP2: Unexpected block length ", size, " of type ", (int)blk);
+ break;
+ }
}
switch (blk)
{
diff --git a/libi2pd/Reseed.cpp b/libi2pd/Reseed.cpp
index ee02a953..5e47b8c5 100644
--- a/libi2pd/Reseed.cpp
+++ b/libi2pd/Reseed.cpp
@@ -330,7 +330,9 @@ namespace data
{
int numFiles = 0;
size_t contentPos = s.tellg ();
- while (!s.eof ())
+ // not !eof: seekg clears eofbit, so a stream broken in the middle of a
+ // header stays not-eof forever and the loop never ends
+ while (s.good ())
{
uint32_t signature;
s.read ((char *)&signature, 4);
@@ -357,13 +359,18 @@ namespace data
uint16_t fileNameLength, extraFieldLength;
s.read ((char *)&fileNameLength, 2);
fileNameLength = le16toh (fileNameLength);
+ s.read ((char *)&extraFieldLength, 2);
+ extraFieldLength = le16toh (extraFieldLength);
+ if (!s.good ())
+ {
+ LogPrint (eLogError, "Reseed: Truncated zip local file header");
+ return numFiles;
+ }
if ( fileNameLength >= 255 ) {
// too big
LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength);
return numFiles;
}
- s.read ((char *)&extraFieldLength, 2);
- extraFieldLength = le16toh (extraFieldLength);
char localFileName[255];
s.read (localFileName, fileNameLength);
localFileName[fileNameLength] = 0;
@@ -371,7 +378,7 @@ namespace data
// take care about data descriptor if presented
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
{
- size_t pos = s.tellg ();
+ auto pos = s.tellg ();
if (!FindZipDataDescriptor (s))
{
LogPrint (eLogError, "Reseed: SU3 archive data descriptor not found");
@@ -402,6 +409,12 @@ namespace data
uint8_t * compressed = new uint8_t[compressedSize];
s.read ((char *)compressed, compressedSize);
+ if (!s.good ())
+ {
+ LogPrint (eLogError, "Reseed: Truncated zip file data");
+ delete[] compressed;
+ return numFiles;
+ }
if (compressionMethod) // we assume Deflate
{
z_stream inflator;
@@ -483,7 +496,7 @@ namespace data
bool Reseeder::FindZipDataDescriptor (std::istream& s)
{
size_t nextInd = 0;
- while (!s.eof ())
+ while (s.good ()) // a broken stream never reaches eof, see ProcessZIPStream
{
uint8_t nextByte;
s.read ((char *)&nextByte, 1);
diff --git a/libi2pd/Streaming.cpp b/libi2pd/Streaming.cpp
index d59ab3a5..6afddfce 100644
--- a/libi2pd/Streaming.cpp
+++ b/libi2pd/Streaming.cpp
@@ -1531,6 +1531,9 @@ namespace stream
void Stream::SendPackets (const std::list& packets)
{
+ // the local destination is a reference and it can be gone already: a handler
+ // queued before the destination was stopped runs after it
+ if (m_Status == eStreamStatusTerminated) return;
if (!m_RemoteLeaseSet)
{
CancelRemoteLeaseChange ();
diff --git a/libi2pd/TunnelEndpoint.cpp b/libi2pd/TunnelEndpoint.cpp
index c8b31343..8bdb90ba 100644
--- a/libi2pd/TunnelEndpoint.cpp
+++ b/libi2pd/TunnelEndpoint.cpp
@@ -322,6 +322,12 @@ namespace tunnel
LogPrint (eLogInfo, "TunnelMessage: Message expired");
return;
}
+ if (msg.data->GetLength () < I2NP_HEADER_SIZE)
+ {
+ // GetPayloadLength subtracts the header size, a shorter message wraps it around
+ LogPrint (eLogError, "TunnelMessage: Message of ", msg.data->GetLength (), " bytes is shorter than I2NP header, dropped");
+ return;
+ }
uint8_t typeID = msg.data->GetTypeID ();
LogPrint (eLogDebug, "TunnelMessage: Handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID);
diff --git a/libi2pd_client/ClientContext.cpp b/libi2pd_client/ClientContext.cpp
index d5089e78..2a60cd29 100644
--- a/libi2pd_client/ClientContext.cpp
+++ b/libi2pd_client/ClientContext.cpp
@@ -117,9 +117,11 @@ namespace client
m_AddressBook.StartResolvers ();
+#ifndef NO_TORRENTS
// start torrents RPC server
for (auto& it: m_TorrentsRPCServers)
it.second->Start ();
+#endif
}
std::shared_ptr ClientContext::DetachHttpProxy ()
@@ -168,6 +170,7 @@ namespace client
}
m_ServerTunnels.clear ();
+#ifndef NO_TORRENTS
// start torrents RPC server
for (auto& it: m_TorrentsRPCServers)
it.second->Stop ();
@@ -178,6 +181,7 @@ namespace client
it.second->Stop ();
}
m_TorrentsTunnels.clear ();
+#endif
if (m_SamBridge)
{
@@ -980,7 +984,7 @@ namespace client
}
else if (type == I2P_TUNNELS_SECTION_TYPE_TORRENTS)
{
-
+#ifndef NO_TORRENTS
// mandatory params
std::string torrentsDir = section.second.get (TORRENTS_TUNNEL_TORRENTS_DIR);
// optional params
@@ -1038,6 +1042,9 @@ namespace client
}
else
LogPrint (eLogError, "Clients: Failed to create torrents tunnel ", name, ". Duplicate keys ", keys);
+#else
+ LogPrint (eLogError, "Clients: Torrents tunnels are not supported. Comppile without NO_TORRENTS");
+#endif
}
else
LogPrint (eLogError, "Clients: Unknown section type = ", type, " of ", name, " in ", tunConf);
@@ -1216,6 +1223,7 @@ namespace client
}
}
+#ifndef NO_TORRENTS
std::shared_ptr ClientContext::CreateTorrentsRPCServer (
std::string_view address, uint16_t port)
{
@@ -1238,5 +1246,6 @@ namespace client
}
return nullptr;
}
+#endif
}
}
diff --git a/libi2pd_client/ClientContext.h b/libi2pd_client/ClientContext.h
index 663a3276..f9e37925 100644
--- a/libi2pd_client/ClientContext.h
+++ b/libi2pd_client/ClientContext.h
@@ -152,7 +152,9 @@ namespace client
void CreateNewSharedLocalDestination ();
void AddLocalDestination (std::shared_ptr localDestination);
+#ifndef NO_TORRENTS
std::shared_ptr CreateTorrentsRPCServer (std::string_view address, uint16_t port);
+#endif
private:
@@ -171,8 +173,10 @@ namespace client
std::map > m_ClientForwards; // local endpoint -> udp tunnel
std::map, std::shared_ptr > m_ServerForwards; // -> udp tunnel
+#ifndef NO_TORRENTS
std::unordered_map > m_TorrentsTunnels; // local destination -> torrents tunnel
std::unordered_map > m_TorrentsRPCServers;
+#endif
SAMBridge * m_SamBridge;
BOBCommandChannel * m_BOBCommandChannel;
@@ -194,8 +198,10 @@ namespace client
const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; };
const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; }
const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; }
+#ifndef NO_TORRENTS
const decltype(m_TorrentsTunnels)& GetTorrentsTunnels () const { return m_TorrentsTunnels; }
const decltype(m_TorrentsRPCServers)& GetTorrentsRPCServers () const { return m_TorrentsRPCServers; }
+#endif
std::shared_ptr GetHttpProxy () const
{
std::lock_guard l(m_ProxyMutex);
diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp
index d7f24531..5d580504 100644
--- a/libi2pd_client/Torrents.cpp
+++ b/libi2pd_client/Torrents.cpp
@@ -6,6 +6,8 @@
* See full license text in LICENSE file at top of project tree
*/
+#ifndef NO_TORRENTS
+
#include
#include
#include
@@ -147,6 +149,13 @@ namespace torrents
return ret;
}
+ static std::string CreateInteger (int64_t v)
+ {
+ std::string ret ("i");
+ ret += std::to_string (v); ret += "e";
+ return ret;
+ }
+
static std::string CreateDictionary (const std::vector >& items)
{
std::stringstream s;
@@ -154,7 +163,7 @@ namespace torrents
for (const auto& [name, value]: items)
if (!name.empty () && !value.empty ())
{
- s << name; s << value;
+ s << CreateByteString (name); s << value;
}
s << 'e';
return s.str ();
@@ -322,11 +331,15 @@ namespace torrents
}
}
- Torrent::Torrent (std::string_view buf):
+ Torrent::Torrent ():
m_Length (0), m_PieceLength (0), m_IsComplete (false), m_IsStopped (false),
m_Uploaded (0), m_Downloaded (0)
{
ResetStats ();
+ }
+
+ Torrent::Torrent (std::string_view buf): Torrent ()
+ {
ParseDictionary (buf, [this](std::string_view key, std::string_view buf)->size_t
{
if (key == "announce")
@@ -341,6 +354,10 @@ namespace torrents
});
}
+ Torrent::Torrent (const InfoHash& infoHash): Torrent ()
+ {
+ m_InfoHash = infoHash;
+ }
size_t Torrent::ParsePieces (std::string_view buf)
{
@@ -356,7 +373,6 @@ namespace torrents
return len;
}
-
size_t Torrent::ParseInfo (std::string_view buf)
{
size_t len = ParseDictionary (buf, [this](std::string_view key, std::string_view buf)->size_t
@@ -398,11 +414,20 @@ namespace torrents
return 0;
});
if (!len) return 0;
+ // save info
+ m_Info.resize (len);
+ memcpy (m_Info.data (), (const uint8_t *)buf.data (), len);
// calculate info hash
- SHA1 ((const uint8_t *)buf.data (), len, m_InfoHash.data ());
+ SHA1 (m_Info.data (), len, m_InfoHash.data ());
return len;
}
+ std::string Torrent::CreateTorrentFileContent () const
+ {
+ if (m_Info.empty ()) return "";
+ return CreateDictionary ( {{ "info", std::string_view ((const char *)m_Info.data (), m_Info.size ()) }} );
+ }
+
bool Torrent::IsSafeName (std::string_view name)
{
if (name.empty () || name == "." || name == "..") return false;
@@ -490,9 +515,8 @@ namespace torrents
void Torrent::ParseTrackerResponse (size_t trackerID, std::string_view buf)
{
- if (trackerID >= m_TrackersInfo.size ())
- m_TrackersInfo.resize (trackerID + 1, TrackerInfo{{}, MIN_TRACKER_REQUESTS_INTERVAL,
- 0, 0, 0, i2p::util::GetSecondsSinceEpoch ()});
+ CheckTrackerStatsSize (trackerID);
+ std::get<6>(m_TrackerStats[trackerID]) = ""; // clear error
ParseDictionary (buf, [this, trackerID](std::string_view key, std::string_view buf)->size_t
{
if (key == "interval")
@@ -501,8 +525,8 @@ namespace torrents
if (l)
{
int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, (int)value*1000); // in milliseconds
- std::get<1>(m_TrackersInfo[trackerID]) = interval;
- std::get<2>(m_TrackersInfo[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval; // reset next request
+ std::get<1>(m_TrackerStats[trackerID]) = interval;
+ std::get<2>(m_TrackerStats[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval; // reset next request
}
return l;
}
@@ -511,29 +535,34 @@ namespace torrents
else if (key == "complete")
{
auto [seeders, l] = ExtractInteger (buf);
- if (l) std::get<3>(m_TrackersInfo[trackerID]) = seeders;
+ if (l) std::get<3>(m_TrackerStats[trackerID]) = seeders;
return l;
}
else if (key == "incomplete")
{
auto [leechers, l] = ExtractInteger (buf);
- if (l) std::get<4>(m_TrackersInfo[trackerID]) = leechers;
+ if (l) std::get<4>(m_TrackerStats[trackerID]) = leechers;
return l;
}
else if (key == "failure reason")
{
auto [reason, l] = ExtractByteString (buf);
LogPrint (eLogError, "Torrents: Tracker error: ", reason);
+ std::get<6>(m_TrackerStats[trackerID]) = reason;
+ // double interval if tracker failure
+ int interval = std::max (MIN_TRACKER_REQUESTS_INTERVAL, std::get<1>(m_TrackerStats[trackerID])*2);
+ std::get<1>(m_TrackerStats[trackerID]) = interval;
+ std::get<2>(m_TrackerStats[trackerID]) = i2p::util::GetMonotonicMilliseconds () + interval;
return l;
}
return 0;
});
- std::get<5>(m_TrackersInfo[trackerID]) = i2p::util::GetSecondsSinceEpoch ();
+ std::get<5>(m_TrackerStats[trackerID]) = i2p::util::GetSecondsSinceEpoch ();
}
size_t Torrent::ParsePeers (size_t trackerID, std::string_view buf)
{
- auto& peers = std::get<0>(m_TrackersInfo[trackerID]);
+ auto& peers = std::get<0>(m_TrackerStats[trackerID]);
peers.clear ();
auto [hashes, len] = ExtractByteString (buf);
while (!hashes.empty ())
@@ -547,10 +576,9 @@ namespace torrents
void Torrent::HandleDatagramTrackerResponse (size_t trackerID, uint32_t interval,
const uint8_t * hashes, size_t hashesLen, int numSeeders, int numLeechers)
{
- if (trackerID >= m_TrackersInfo.size ())
- m_TrackersInfo.resize (trackerID + 1, TrackerInfo{{}, MIN_TRACKER_REQUESTS_INTERVAL,
- 0, 0, 0, i2p::util::GetSecondsSinceEpoch () });
- auto& [peers, trackerRequestInterval, nextRequestTime, seeders, leechers, lastUpdateTime] = m_TrackersInfo[trackerID];
+ CheckTrackerStatsSize (trackerID);
+ auto& [peers, trackerRequestInterval, nextRequestTime, seeders, leechers,
+ lastUpdateTime, error] = m_TrackerStats[trackerID];
trackerRequestInterval = interval*1000; // milliseconds
nextRequestTime = i2p::util::GetMonotonicMilliseconds () + trackerRequestInterval;
seeders = numSeeders;
@@ -567,6 +595,19 @@ namespace torrents
}
}
+ void Torrent::SetTrackerError (size_t trackerID, std::string_view error)
+ {
+ CheckTrackerStatsSize (trackerID);
+ std::get<6>(m_TrackerStats[trackerID]) = error;
+ }
+
+ void Torrent::CheckTrackerStatsSize (size_t trackerID)
+ {
+ if (trackerID >= m_TrackerStats.size ())
+ m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL,
+ 0, 0, 0, i2p::util::GetSecondsSinceEpoch (), ""});
+ }
+
std::pair, bool> Torrent::CreateBitfield () const
{
size_t numPieces = m_Pieces.size ();
@@ -778,10 +819,18 @@ namespace torrents
}
else
{
- if (piece.IsComplete ()) currentCompletedSize += (filesIT->second - currentSize);
+ size_t leftoverSize = filesIT->second - currentSize;
+ if (piece.IsComplete ()) currentCompletedSize += leftoverSize;
completed.push_back (currentCompletedSize);
- currentSize = 0; currentCompletedSize = 0;
+ currentSize = m_PieceLength - leftoverSize;
filesIT++;
+ while (filesIT != m_Files.end () && filesIT->second <= currentSize)
+ {
+ completed.push_back (filesIT->second);
+ currentSize -= filesIT->second;
+ filesIT++;
+ }
+ currentCompletedSize = piece.IsComplete () ? currentSize : 0;
if (filesIT == m_Files.end ()) break;
}
}
@@ -791,10 +840,10 @@ namespace torrents
std::unordered_set Torrent::GetPeers () const
{
- if (m_TrackersInfo.size () == 1)
- return std::get<0>(m_TrackersInfo.front ());
+ if (m_TrackerStats.size () == 1)
+ return std::get<0>(m_TrackerStats.front ());
std::unordered_set ret;
- for (const auto& it: m_TrackersInfo)
+ for (const auto& it: m_TrackerStats)
{
const auto& peers = std::get<0>(it);
ret.insert (peers.begin (), peers.end ());
@@ -804,16 +853,16 @@ namespace torrents
uint64_t Torrent::GetNextTrackerRequestTime (size_t trackerID) const
{
- if (trackerID < m_TrackersInfo.size ()) return std::get<2>(m_TrackersInfo[trackerID]);
+ if (trackerID < m_TrackerStats.size ()) return std::get<2>(m_TrackerStats[trackerID]);
return 0;
}
void Torrent::SetNextTrackerRequestTime (size_t trackerID, uint64_t ts)
{
- if (trackerID >= m_TrackersInfo.size ())
- m_TrackersInfo.resize (trackerID + 1, TrackerInfo{{}, MIN_TRACKER_REQUESTS_INTERVAL,
- 0, 0, 0, i2p::util::GetSecondsSinceEpoch ()});
- std::get<2>(m_TrackersInfo[trackerID]) = ts;
+ if (trackerID >= m_TrackerStats.size ())
+ m_TrackerStats.resize (trackerID + 1, TrackerStats{{}, MIN_TRACKER_REQUESTS_INTERVAL,
+ 0, 0, 0, i2p::util::GetSecondsSinceEpoch (), ""});
+ std::get<2>(m_TrackerStats[trackerID]) = ts;
}
TorrentStatus Torrent::GetStatus () const
@@ -828,7 +877,7 @@ namespace torrents
m_Stream (stream), m_ReceiveBufferOffset (0), m_NextMsgLength (0),
m_IsHandshakeSent (false), m_IsEstablished (false), m_IsChoked (true), m_IsRemoteChoked (true),
m_IsInterested (false), m_IsRemoteInterested (false), m_LastReceiveTime (0), m_LastSendTime (0),
- m_NumRequests (0), m_NumPieces (0), m_LastRequestedPieceIndex (-1),
+ m_NumRequests (0), m_NumPieces (0), m_LastRequestedPieceIndex (-1), m_RemoteMetadataSize (0),
m_Downloaded (0), m_Uploaded (0)
{
ResetStats ();
@@ -1197,6 +1246,8 @@ namespace torrents
void PeerConnection::HandleHaveMsg (const uint8_t * buf, size_t len)
{
if (len < 4) return;
+ if (m_RemoteBitfield.empty ()) // bitfield was not received before because was empty
+ m_RemoteBitfield.resize (m_Torrent->GetNumPieces ());
uint32_t index = bufbe32toh (buf);
if (index < m_RemoteBitfield.size ())
{
@@ -1233,7 +1284,7 @@ namespace torrents
void PeerConnection::HandleBitfieldMsg (const uint8_t * buf, size_t len)
{
- if (!m_Torrent) return;
+ if (!m_Torrent || !m_Torrent->GetLength ()) return; // we are magnet and don't have torrent info yet
m_IsInterested = false;
size_t numPieces = m_Torrent->GetNumPieces ();
m_RemoteBitfield.resize (numPieces);
@@ -1275,6 +1326,13 @@ namespace torrents
size_t numPieces = m_Torrent->GetNumPieces ();
m_RemoteBitfield.resize (numPieces);
m_RemoteBitfield.set ();
+ if (!m_Torrent->IsComplete ())
+ {
+ m_IsInterested = true;
+ SendInterestedMsg ();
+ }
+ else
+ Terminate (); // we don't need this connection
}
void PeerConnection::HandleHaveNoneMsg ()
@@ -1563,7 +1621,23 @@ namespace torrents
ParseDictionary (std::string_view ((const char *)(buf + 1), len -1),
[this](std::string_view key, std::string_view buf)->size_t
{
- if (key == "v")
+ if (key == "m")
+ {
+ return ParseDictionary (buf, [this](std::string_view msg, std::string_view msgID)->size_t
+ {
+ auto [id, l] = ExtractInteger (msgID);
+ if (l)
+ AddExtendedMsgHandler (msg, id);
+ return l;
+ });
+ }
+ else if (key == "metadata_size")
+ {
+ auto [s, l] = ExtractInteger (buf);
+ if (l) m_RemoteMetadataSize = s;
+ return l;
+ }
+ else if (key == "v")
{
auto [v, l] = ExtractByteString (buf);
if (l) m_RemoteName = v;
@@ -1571,20 +1645,131 @@ namespace torrents
}
return 0;
});
+ if (!m_Torrent->GetLength () && m_RemoteMetadataSize) // torrent is magnet and peer supports BEP9
+ // request first piece of info
+ SendExtendedMsg (EXTENSION_MSGID_UT_METADATA, CreateDictionary ({{ "msg_type", CreateInteger (0) }, { "piece", CreateInteger (0) }}));
+ }
+ else
+ {
+ auto it = m_ExtendedMessageHandlers.find (buf[0]);
+ if (it != m_ExtendedMessageHandlers.end ())
+ (this->*(it->second))(buf + 1, len - 1);
+ else
+ LogPrint (eLogInfo, "Torrents: Unexpected extended message type ", (int)buf[0], " received");
}
}
- void PeerConnection::SendExtendedMsg ()
+ void PeerConnection::AddExtendedMsgHandler (std::string_view extensionName, int64_t msgID)
{
- std::string payload = CreateDictionary ({{ CreateByteString ("v"), CreateByteString ("i2pd") }});
- std::vector sendBuffer (payload.length () + 1 + 5);
- htobe32buf (sendBuffer.data (), payload.length () + 1 + 1); // length
+ if (extensionName == EXTENSION_NAME_UT_METADATA)
+ m_ExtendedMessageHandlers.emplace (msgID, &PeerConnection::HandleUtMetadataExtension);
+ }
+
+ void PeerConnection::SendExtendedMsg (uint8_t extendedMsgID, std::string_view payload, std::string_view data)
+ {
+ std::string str;
+ if (!extendedMsgID) // handshake
+ {
+ str = CreateDictionary ({
+ { "m", CreateDictionary ({
+ { EXTENSION_NAME_UT_METADATA, CreateInteger (EXTENSION_MSGID_UT_METADATA) }
+ }) },
+ { "metadata_size", CreateInteger (m_Torrent->GetInfo ().size ()) },
+ { "v", CreateByteString ("i2pd") }
+ });
+ payload = str;
+ }
+ std::vector sendBuffer (payload.length () + data.length () + 1 + 5);
+ htobe32buf (sendBuffer.data (), payload.length () + data.length () + 1 + 1); // length
sendBuffer[4] = eMessageTypeExtended; // msg ID
- sendBuffer[5] = 0; // handshake
+ sendBuffer[5] = extendedMsgID;
memcpy (sendBuffer.data () + 6, payload.data (), payload.size ());
+ if (!data.empty ())
+ memcpy (sendBuffer.data () + 6 + payload.size (), data.data (), data.size ());
WriteToStream (sendBuffer.data (), sendBuffer.size ());
}
+ void PeerConnection::HandleUtMetadataExtension (const uint8_t * buf, size_t len)
+ {
+ if (!m_Torrent) return;
+ int msgType = -1, piece = -1;
+ auto payloadLen = ParseDictionary (std::string_view ((const char *)buf, len),
+ [&msgType, &piece](std::string_view key, std::string_view buf)->size_t
+ {
+ if (key == "msg_type")
+ {
+ auto [value, l] = ExtractInteger (buf);
+ if (l) msgType = value;
+ return l;
+ }
+ else if (key == "piece")
+ {
+ auto [value, l] = ExtractInteger (buf);
+ if (l) piece = value;
+ return l;
+ }
+ // ignore total_szie
+ return 0;
+ });
+ if (msgType >=0 && piece >= 0)
+ {
+ switch (msgType)
+ {
+ case 0: // request
+ {
+ auto& info = m_Torrent->GetInfo ();
+ size_t offset = piece*REQUEST_BLOCK_SIZE;
+ if (offset < info.size ())
+ {
+ size_t totalSize = std::min (info.size () - offset, REQUEST_BLOCK_SIZE);
+ SendExtendedMsg (EXTENSION_MSGID_UT_METADATA,
+ CreateDictionary ({{ "msg_type", CreateInteger (1) },
+ { "piece", CreateInteger (piece) },
+ { "total_size", CreateInteger (m_Torrent->GetInfo ().size ()) } }),
+ std::string_view ((const char *)info.data () + offset, totalSize));
+ }
+ else
+ SendExtendedMsg (EXTENSION_MSGID_UT_METADATA, CreateDictionary ({{ "msg_type", CreateInteger (2) }, { "piece", CreateInteger (piece) }}));
+ break;
+ }
+ case 1: // data
+ {
+ size_t offset = piece*REQUEST_BLOCK_SIZE;
+ if (offset > m_RemoteMetadataSize) break;
+ size_t size = m_RemoteMetadataSize - offset;
+ if (size > REQUEST_BLOCK_SIZE) size = REQUEST_BLOCK_SIZE;
+ if (payloadLen + size > len) break;
+ if (offset == m_RemoteMetadata.size () && size)
+ {
+ m_RemoteMetadata.resize (m_RemoteMetadata.size () + size);
+ memcpy (m_RemoteMetadata.data () + offset, buf + payloadLen, size);
+ if (m_RemoteMetadata.size () < m_RemoteMetadataSize)
+ // request next piece
+ SendExtendedMsg (EXTENSION_MSGID_UT_METADATA, CreateDictionary ({{ "msg_type", CreateInteger (0) }, { "piece", CreateInteger (piece + 1) }}));
+ else
+ {
+ // all info received
+ LogPrint (eLogDebug, "Torrents: ut_metadata ", m_RemoteMetadataSize, " bytes of info received");
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1 (m_RemoteMetadata.data (), m_RemoteMetadata.size (), digest);
+ if (!memcmp (m_Torrent->GetInfoHash ().data (), digest, SHA_DIGEST_LENGTH))
+ GetTorrentsTunnel ()->UpdateTorrentInfo (m_Torrent, std::string_view ((const char *)m_RemoteMetadata.data (), m_RemoteMetadata.size ()));
+ else
+ LogPrint (eLogError, "Torrents: ut_metadata info doesn't match infoHash");
+ Close (); // we need to reconnect to receive bitfield
+ }
+ }
+ break;
+ }
+ case 2: // reject
+ LogPrint (eLogError, "Torrents: ut_metadata piece ", piece, " request rejected");
+ break;
+ default:
+ LogPrint (eLogInfo, "Torrents: ut_metadata msg_type ", msgType, " is not supported");
+ }
+ }
+ }
+
std::optional PeerConnection::GetNextBlockToRequest ()
{
auto block = m_Torrent->GetNextBlockToRequest (shared_from_this (), true); // skip already requested pieces
@@ -1627,3 +1812,5 @@ namespace torrents
}
}
}
+
+#endif // NO_TORRENTS
diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h
index 38095366..99f54ccf 100644
--- a/libi2pd_client/Torrents.h
+++ b/libi2pd_client/Torrents.h
@@ -9,6 +9,8 @@
#ifndef TORRENTS_H__
#define TORRENTS_H__
+#ifndef NO_TORRENTS
+
#include
#include
#include
@@ -53,6 +55,10 @@ namespace torrents
constexpr size_t REQUEST_MSG_LENGTH = REQUEST_MSG_PAYLOAD_LENGTH + 5;
constexpr size_t HAVE_MSG_PAYLOAD_LENGTH = 4;
+ // extensions
+ constexpr std::string_view EXTENSION_NAME_UT_METADATA { "ut_metadata" };
+ constexpr uint8_t EXTENSION_MSGID_UT_METADATA = 1;
+
enum MessageType
{
eMessageTypeChoke = 0,
@@ -146,14 +152,18 @@ namespace torrents
using RequestedBlock = std::tuple; // (index, offset, len)
class Torrent final
{
+ using TrackerStats = std::tuple,
+ int, uint64_t, int, int, uint64_t, std::string>;
+ // (peers, interval, next tracker request time in monotonic milliseconds,
+ // seeders, leechers, last update time in seconds since epoch, error)
public:
- using InfoHash = std::array;
- using TrackerInfo = std::tuple, int, uint64_t, int, int, uint64_t>;
- // (peers, interval, next tracker request time in monotonic milliseconds,
- // seeders, leechers, last update time in seconds since epoch)
+ using InfoHash = std::array;
Torrent (std::string_view buf);
+ Torrent (const InfoHash& infoHash);
+ size_t ParseInfo (std::string_view buf);
+ std::string CreateTorrentFileContent () const;
void ParseTrackerResponse (size_t trackerID, std::string_view buf);
void HandleDatagramTrackerResponse (size_t trackerID, uint32_t interval,
const uint8_t * hashes, size_t hashesLen, int numSeeders, int numLeechers);
@@ -173,7 +183,8 @@ namespace torrents
std::list >& GetFiles () { return m_Files; }
size_t GetLength () const { return m_Length; }
size_t GetPieceLength () const { return m_PieceLength; }
- int GetInterval (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<1>(m_TrackersInfo[trackerID]) : MIN_TRACKER_REQUESTS_INTERVAL; }
+ int GetInterval (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<1>(m_TrackerStats[trackerID]) : MIN_TRACKER_REQUESTS_INTERVAL; }
+ const std::vector GetInfo () const { return m_Info; }
const InfoHash& GetInfoHash () const { return m_InfoHash; }
size_t GetLeft () const;
std::string GetHexStringInfoHash () const; // in url format
@@ -210,27 +221,31 @@ namespace torrents
int GetNumUploadingToPeers () const { return m_NumUploadingToPeers; }
void SetNumUploadingToPeers (int numUploadingToPeers) { m_NumUploadingToPeers = numUploadingToPeers; }
- int GetNumSeeders (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<3>(m_TrackersInfo[trackerID]) : 0; }
- int GetNumLeechers (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<4>(m_TrackersInfo[trackerID]) : 0; }
- int GetNumPeers (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<0>(m_TrackersInfo[trackerID]).size () : 0; }
- uint64_t GetLastTrackerUpdateTime (size_t trackerID) const { return (trackerID < m_TrackersInfo.size ()) ? std::get<5>(m_TrackersInfo[trackerID]) : 0; }
+ int GetNumSeeders (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<3>(m_TrackerStats[trackerID]) : 0; }
+ int GetNumLeechers (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<4>(m_TrackerStats[trackerID]) : 0; }
+ int GetNumPeers (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<0>(m_TrackerStats[trackerID]).size () : 0; }
+ uint64_t GetLastTrackerUpdateTime (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<5>(m_TrackerStats[trackerID]) : 0; }
+ std::string GetTrackerError (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<6>(m_TrackerStats[trackerID]) : ""; }
+ void SetTrackerError (size_t trackerID, std::string_view error);
private:
+ Torrent ();
size_t ParsePieces (std::string_view buf);
- size_t ParseInfo (std::string_view buf);
size_t ParsePeers (size_t trackerID, std::string_view buf);
size_t ParseFiles (std::string_view buf);
static bool IsSafeName (std::string_view name); // a name from a torrent file before it becomes a path
+ void CheckTrackerStatsSize (size_t trackerID);
private:
std::string m_Name, m_Announce;
std::filesystem::path m_FullPath;
size_t m_Length, m_PieceLength;
+ std::vector m_Info; // for BEP9
InfoHash m_InfoHash; // SHA1
std::vector m_Pieces;
- std::vector m_TrackersInfo;
+ std::vector m_TrackerStats;
bool m_IsComplete, m_IsStopped;
std::list > m_Files; // list of (path, length)
size_t m_Uploaded, m_Downloaded;
@@ -242,6 +257,8 @@ namespace torrents
class TorrentsTunnel;
class PeerConnection: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this
{
+ using ExtendedMessageHandler = void (PeerConnection::*)(const uint8_t * buf, size_t len);
+
public:
using PeerID = std::array;
@@ -310,7 +327,9 @@ namespace torrents
void SendChokeMsg ();
void HandleChokeMsg ();
void HandleExtendedMsg (const uint8_t * buf, size_t len);
- void SendExtendedMsg ();
+ void SendExtendedMsg (uint8_t extendedMsgID = 0, std::string_view payload = "", std::string_view data = "");
+ void AddExtendedMsgHandler (std::string_view extensionName, int64_t msgID);
+ void HandleUtMetadataExtension (const uint8_t * buf, size_t len); // BEP9
std::optional GetNextBlockToRequest ();
bool RequestNextBlocks ();
@@ -332,6 +351,10 @@ namespace torrents
std::list m_IncomingRequestsQueue;
int m_LastRequestedPieceIndex;
std::unique_ptr m_HandshakeReceiveTimer;
+ std::unordered_map m_ExtendedMessageHandlers;
+ // BEP9
+ size_t m_RemoteMetadataSize;
+ std::vector m_RemoteMetadata;
// stats
uint64_t m_DownloadRate, m_UploadRate; // B/sec
uint64_t m_LastBlockDownloadTimestamp, m_LastBlockUploadTimestamp; // monotonic milliseconds
@@ -340,4 +363,7 @@ namespace torrents
};
}
}
+
+#endif // NO_TORRENTS
+
#endif
diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp
index f85bfceb..d23ad40d 100644
--- a/libi2pd_client/TorrentsRPC.cpp
+++ b/libi2pd_client/TorrentsRPC.cpp
@@ -6,6 +6,8 @@
* See full license text in LICENSE file at top of project tree
*/
+#ifndef NO_TORRENTS
+
#include
#if !defined(ANDROID) && (BOOST_VERSION >= 108100) // boost::json since 1.75, we allow it since 1.81 due to std::string_view compatibility
#include
@@ -46,7 +48,7 @@ namespace torrents
private:
std::string SuccessResponse (int64_t id, boost::json::object&& arguments);
- std::string ResultResponse (int64_t id, boost::json::object&& result);
+ std::string ResultResponse (int64_t id, boost::json::value&& result);
std::string ErrorResponse (JSONRPCErrorCode errorCode, int64_t id, std::string_view message);
static int64_t GetTag (boost::json::object& jsonRequest);
static std::vector GetTorrentIds (boost::json::object& arguments);
@@ -54,6 +56,7 @@ namespace torrents
boost::json::array GetPeers (std::shared_ptr torrent) const;
boost::json::array GetTrackers (std::shared_ptr torrent) const;
boost::json::array GetTrackerStats (std::shared_ptr torrent) const;
+ boost::json::array GetFiles (std::shared_ptr torrent) const;
static std::string_view RecognizeClientByPeerID (const PeerConnection::PeerID& peerID);
std::string HandleTorrentAdd (boost::json::object&& jsonRequest);
@@ -76,7 +79,7 @@ namespace torrents
return boost::json::serialize(response);
}
- std::string JSONRPCHandler::ResultResponse (int64_t id, boost::json::object&& result)
+ std::string JSONRPCHandler::ResultResponse (int64_t id, boost::json::value&& result)
{
boost::json::object response;
response["jsonrpc"] = "2.0";
@@ -166,32 +169,40 @@ namespace torrents
std::string JSONRPCHandler::HandleTorrentAdd (boost::json::object&& jsonRequest)
{
auto arguments = jsonRequest.at ("arguments").as_object ();
- auto b64torrent = arguments.at ("metainfo").as_string ();
- std::string torrentFileContent;
- torrentFileContent.resize (boost::beast::detail::base64::decoded_size (b64torrent.size ()));
- boost::beast::detail::base64::decode (torrentFileContent.data (), b64torrent.data (), b64torrent.size ());
- auto [torrent, id] = m_Tunnel->AddTorrent (torrentFileContent);
+ std::shared_ptr torrent; int id = 0;
+ if (arguments.contains ("metainfo"))
+ {
+ auto b64torrent = arguments.at ("metainfo").as_string ();
+ std::string torrentFileContent;
+ torrentFileContent.resize (boost::beast::detail::base64::decoded_size (b64torrent.size ()));
+ boost::beast::detail::base64::decode (torrentFileContent.data (), b64torrent.data (), b64torrent.size ());
+ std::tie (torrent, id) = m_Tunnel->AddTorrent (torrentFileContent);
+ }
+ else if (arguments.contains ("filename"))
+ std::tie (torrent, id) = m_Tunnel->AddMagnet (arguments.at ("filename").as_string ());
- boost::json::object response, torrentInfo;
if (torrent)
{
+ boost::json::object response, torrentInfo;
std::string hexHash;
boost::algorithm::hex (torrent->GetInfoHash ().begin(), torrent->GetInfoHash ().end(), std::back_inserter(hexHash));
torrentInfo["id"] = id;
torrentInfo["hashString"] = hexHash;
torrentInfo["name"] = torrent->GetName ();
- }
- if (id)
- {
- response["torrent-added"] = torrentInfo;
- LogPrint (eLogDebug, "TorrentsRPC: torrent added ", torrentInfo["name"].as_string ());
+ if (id)
+ {
+ response["torrent-added"] = torrentInfo;
+ LogPrint (eLogDebug, "TorrentsRPC: torrent added ", torrentInfo["name"].as_string ());
+ }
+ else
+ {
+ response["torrent-duplicate"] = torrentInfo;
+ LogPrint (eLogDebug, "TorrentsRPC: duplicate torrent ", torrentInfo["name"].as_string ());
+ }
+ return SuccessResponse (GetTag (jsonRequest), std::move (response));
}
else
- {
- response["torrent-duplicate"] = torrentInfo;
- LogPrint (eLogDebug, "TorrentsRPC: duplicate torrent ", torrentInfo["name"].as_string ());
- }
- return SuccessResponse (GetTag (jsonRequest), std::move (response));
+ return ResultResponse (GetTag (jsonRequest), boost::json::string ("invalid or corrupt torrent file"));
}
std::string JSONRPCHandler::HandleTorrentRemove (boost::json::object&& jsonRequest)
@@ -244,6 +255,8 @@ namespace torrents
t["trackers"] = GetTrackers (torrent);
else if (field == "trackerStats")
t["trackerStats"] = GetTrackerStats (torrent);
+ else if (field == "files")
+ t["files"] = GetFiles (torrent);
else
{
auto fieldValue = GetFieldValue (field.as_string (), torrent);
@@ -296,35 +309,6 @@ namespace torrents
return boost::json::value(b64pieces);
}
},
- { "files", [](std::shared_ptr torrent)
- {
- boost::json::array files;
- const auto& torrentFiles = torrent->GetFiles ();
- if (torrentFiles.empty ())
- {
- boost::json::object file;
- file["name"] = torrent->GetFullPath ().string ();
- file["length"] = torrent->GetLength ();
- file["bytesCompleted"] = torrent->GetLength () - torrent->GetLeft ();
- files.push_back (file);
- }
- else
- {
- auto filesCompleted = torrent->GetFilesCompleted ();
- size_t ind = 0;
- for (const auto& [filePath, fileSize]: torrentFiles)
- {
- boost::json::object file;
- file["name"] = filePath.string ();
- file["length"] = fileSize;
- file["bytesCompleted"] = (ind < filesCompleted.size ()) ? filesCompleted[ind] : 0;
- files.push_back (file);
- ind++;
- }
- }
- return files;
- }
- },
{ "wanted", [](std::shared_ptr torrent)
{
boost::json::array wanted;
@@ -401,7 +385,8 @@ namespace torrents
peer["clientIsIntersted"] = it->IsInterested ();
peer["peerIsInterested"] = it->IsRemoteInterested ();
peer["flagStr"] = flags;
- peer["progress"] = torrent->GetLength () ? ((float)it->GetDownloaded ())/(float)(torrent->GetLength ()) : 1.0;
+ const auto& remoteBitfield = it->GetRemoteBitfield ();
+ peer["progress"] = remoteBitfield.size () ? ((float)remoteBitfield.count ())/((float)remoteBitfield.size ()) : 0.0;
peers.push_back (peer);
}
}
@@ -411,12 +396,11 @@ namespace torrents
boost::json::array JSONRPCHandler::GetTrackers (std::shared_ptr torrent) const
{
boost::json::array trackers;
- const auto& tunnelTrackers = m_Tunnel->GetTrackers ();
- for (size_t i = 0; i < tunnelTrackers.size (); i++)
+ for (size_t i = 0; i < m_Tunnel->GetNumTrackers (); i++)
{
boost::json::object tracker;
tracker["id"] = i;
- tracker["announce"] = tunnelTrackers[i];
+ tracker["announce"] = m_Tunnel->GetTrackerAnnounce (i);
tracker["tier"] = 0;
trackers.push_back (tracker);
}
@@ -426,14 +410,13 @@ namespace torrents
boost::json::array JSONRPCHandler::GetTrackerStats (std::shared_ptr torrent) const
{
boost::json::array trackers;
- const auto& tunnelTrackers = m_Tunnel->GetTrackers ();
- for (size_t i = 0; i < tunnelTrackers.size (); i++)
+ for (size_t i = 0; i < m_Tunnel->GetNumTrackers (); i++)
{
boost::json::object tracker;
tracker["id"] = i;
- tracker["announce"] = tunnelTrackers[i];
+ tracker["announce"] = m_Tunnel->GetTrackerAnnounce (i);
i2p::http::URL announceURL;
- announceURL.parse (tunnelTrackers[i]);
+ announceURL.parse (m_Tunnel->GetTrackerAnnounce (i));
tracker["host"]= announceURL.host;
tracker["announceState"] = 1;
tracker["scrapeState"] = 0;
@@ -445,8 +428,17 @@ namespace torrents
tracker["seederCount"] = torrent->GetNumSeeders (i);
tracker["leecherCount"] = torrent->GetNumLeechers (i);
tracker["lastAnnouncePeerCount"] = torrent->GetNumPeers (i);
- tracker["lastAnnounceResult"] = "Success"; // TODO:
- tracker["lastAnnounceSucceeded"] = true; // TODO:
+ auto trackerError = torrent->GetTrackerError (i);
+ if (trackerError.empty ())
+ {
+ tracker["lastAnnounceResult"] = "Success";
+ tracker["lastAnnounceSucceeded"] = true;
+ }
+ else
+ {
+ tracker["lastAnnounceResult"] = trackerError;
+ tracker["lastAnnounceSucceeded"] = false;
+ }
tracker["lastAnnounceTimedOut"] = false; // TODO:
tracker["nextAnnounceTime"] = (torrent->GetNextTrackerRequestTime (i) -
i2p::util::GetMonotonicMilliseconds () + i2p::util::GetMillisecondsSinceEpoch ())/1000;
@@ -463,6 +455,36 @@ namespace torrents
return trackers;
}
+ boost::json::array JSONRPCHandler::GetFiles (std::shared_ptr torrent) const
+ {
+ boost::json::array files;
+ const auto& torrentFiles = torrent->GetFiles ();
+ if (torrentFiles.empty ())
+ {
+ boost::json::object file;
+ file["name"] = torrent->GetName ();
+ file["length"] = torrent->GetLength ();
+ file["bytesCompleted"] = torrent->GetLength () - torrent->GetLeft ();
+ files.push_back (file);
+ }
+ else
+ {
+ const auto& torrentsDir = m_Tunnel->GetTorrentsDir ();
+ auto filesCompleted = torrent->GetFilesCompleted ();
+ size_t ind = 0;
+ for (const auto& [filePath, fileSize]: torrentFiles)
+ {
+ boost::json::object file;
+ file["name"] = std::filesystem::relative (filePath, torrentsDir).string ();
+ file["length"] = fileSize;
+ file["bytesCompleted"] = (ind < filesCompleted.size ()) ? filesCompleted[ind] : 0;
+ files.push_back (file);
+ ind++;
+ }
+ }
+ return files;
+ }
+
std::string_view JSONRPCHandler::RecognizeClientByPeerID (const PeerConnection::PeerID& peerID)
{
static constexpr std::array i2psnark { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03 };
@@ -526,28 +548,32 @@ namespace torrents
{
if (!ecode)
{
+#ifdef JSON_SUPPORTED
if (m_Request.method() == boost::beast::http::verb::post)
{
auto path = m_Request.target ();
auto tunnel = m_Server.GetTunnel ( {path.data (), path.size ()}); // boost::beast::string_view to std::string_view
if (tunnel)
{
- if (m_Request[boost::beast::http::field::content_type] == "application/json" ||
+ static constexpr std::string_view appjson { "application/json" };
+#if __cplusplus >= 202002L // C++20
+ if (m_Request[boost::beast::http::field::content_type].starts_with (appjson) || // starts with
+#else
+ if (m_Request[boost::beast::http::field::content_type].substr (0, appjson.size ()) == appjson || // starts with
+#endif
m_Request[boost::beast::http::field::content_type] == "application/x-www-form-urlencoded")
{
-#ifdef JSON_SUPPORTED
+
JSONRPCHandler jsonHandler (tunnel);
auto response = jsonHandler.HandleRequest (m_Request.body ());
if (!response.empty ())
SendResponse (boost::beast::http::status::ok, response);
-#else
- SendResponse (boost::beast::http::status::not_implemented, std::string ("Your boost version ") + BOOST_LIB_VERSION + " is too old");
-#endif
}
else
SendResponse (boost::beast::http::status::no_content);
}
- else {
+ else
+ {
LogPrint(eLogDebug, "TorrentRPC ", path, " not found ", tunnel);
SendResponse (boost::beast::http::status::not_found);
}
@@ -556,6 +582,9 @@ namespace torrents
SendResponse (boost::beast::http::status::no_content, "", true);
else
SendResponse (boost::beast::http::status::method_not_allowed);
+#else
+ SendResponse (boost::beast::http::status::not_implemented, std::string ("Your boost version ") + BOOST_LIB_VERSION + " is too old");
+#endif
}
}
@@ -609,7 +638,10 @@ namespace torrents
std::string rpcPath ("/");
if (!path.empty ())
rpcPath += std::string (path) + "/";
- rpcPath += "rpc/";
+ // insert both pathes rpc and rpc/
+ rpcPath += "rpc";
+ m_Tunnels.emplace (rpcPath, tunnel);
+ rpcPath += "/";
m_Tunnels.emplace (rpcPath, tunnel);
}
@@ -649,3 +681,5 @@ namespace torrents
}
}
}
+
+#endif
diff --git a/libi2pd_client/TorrentsRPC.h b/libi2pd_client/TorrentsRPC.h
index a04fdb04..342ef878 100644
--- a/libi2pd_client/TorrentsRPC.h
+++ b/libi2pd_client/TorrentsRPC.h
@@ -9,6 +9,8 @@
#ifndef TORRENTS_RPC_H__
#define TORRENTS_RPC_H__
+#ifndef NO_TORRENTS
+
#include
#include
#include
@@ -76,3 +78,5 @@ namespace torrents
}
#endif
+
+#endif
diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp
index 724f7c07..1c106e8d 100644
--- a/libi2pd_client/TorrentsTunnel.cpp
+++ b/libi2pd_client/TorrentsTunnel.cpp
@@ -6,8 +6,11 @@
* See full license text in LICENSE file at top of project tree
*/
+#ifndef NO_TORRENTS
+
#include
#include
+#include
#include
#include
#include "Log.h"
@@ -33,7 +36,12 @@ namespace torrents
m_PeerID += localDestination->GetIdentHash ().ToBase64 ();
m_PeerID.resize (20, '0');
if (!trackers.empty ())
- boost::split(m_Trackers, trackers, boost::is_any_of(","), boost::token_compress_on);
+ {
+ std::vector trackersList;
+ boost::split(trackersList, trackers, boost::is_any_of(","), boost::token_compress_on);
+ for (const auto& it: trackersList)
+ m_Trackers.emplace_back (TrackerInfo{ it, 0, 0, 0 });
+ }
}
void TorrentsTunnel::Start ()
@@ -41,7 +49,7 @@ namespace torrents
i2p::client::I2PService::Start ();
m_DiskIOService.Start ();
- auto dgramDest = GetLocalDestination ()->CreateDatagramDestination (false, i2p::datagram::eDatagramV3);
+ auto dgramDest = GetLocalDestination ()->CreateDatagramDestination (true, i2p::datagram::eDatagramV3);
if (dgramDest)
dgramDest->SetRawReceiver (std::bind (&TorrentsTunnel::HandleRecvFromI2PRaw,
std::static_pointer_cast(shared_from_this ()),
@@ -126,6 +134,20 @@ namespace torrents
}
}
+ void TorrentsTunnel::SaveTorrentFile (std::shared_ptr torrent)
+ {
+ if (!torrent) return;
+ auto torrentFilePath = torrent->GetFullPath (); torrentFilePath += ".torrent";
+ std::ofstream f(torrentFilePath, std::ofstream::binary);
+ if (f)
+ {
+ auto content = torrent->CreateTorrentFileContent ();
+ f.write (content.data (), content.size ());
+ }
+ else
+ LogPrint (eLogError, "TorrentsTunnel: Can't open ", torrentFilePath);
+ }
+
void TorrentsTunnel::InitTorrentFiles (std::shared_ptr torrent)
{
if (!torrent) return;
@@ -307,20 +329,80 @@ namespace torrents
if (m_Torrents.find (torrent->GetInfoHash ()) == m_Torrents.end ())
{
torrent->SetFullPath (m_TorrentsDir/std::filesystem::path (torrent->GetName ()));
- {
- auto torrentFilePath = torrent->GetFullPath (); torrentFilePath += ".torrent";
- std::ofstream f(torrentFilePath, std::ofstream::binary);
- if (f)
- f.write (torrentFileContent.data (), torrentFileContent.size ());
- else
- return { torrent, 0 };
- }
- InitTorrentFiles (torrent);
+ std::string content (torrentFileContent);
+ boost::asio::post (GetDiskIOService (), [this, torrent, content]()
+ {
+ auto torrentFilePath = torrent->GetFullPath (); torrentFilePath += ".torrent";
+ {
+ // TODO: replace by SaveTorrentFile
+ std::ofstream f(torrentFilePath, std::ofstream::binary);
+ if (f)
+ f.write (content.data (), content.size ());
+ else
+ LogPrint (eLogError, "TorrentsTunnel: Can't open ", torrentFilePath);
+ }
+ InitTorrentFiles (torrent);
+ });
return { torrent, InsertTorrent (torrent) };
}
return { torrent, 0 };
}
+ std::pair, int> TorrentsTunnel::AddMagnet (std::string_view magnet)
+ {
+ // magnet:?xt=urn:btih:&dn=&tr=
+ static constexpr std::string_view magnetPrefix { "magnet:?" };
+#if __cplusplus >= 202002L // C++20
+ if (magnet.starts_with (magnetPrefix))
+#else
+ if (magnet.substr (0, magnetPrefix.size ()) == magnetPrefix)
+#endif
+ {
+ Torrent::InfoHash infoHash;
+ bool isInfoHashFound = false;
+ magnet = magnet.substr (magnetPrefix.size ());
+ while (!magnet.empty())
+ {
+ auto pos = magnet.find ('&');
+ std::string_view param;
+ if (pos != std::string::npos)
+ {
+ param = magnet.substr (0, pos);
+ magnet = magnet.substr (pos + 1);
+ }
+ else
+ {
+ param = magnet;
+ magnet = "";
+ }
+ static constexpr std::string_view hashPrefix { "xt=urn:btih:" };
+#if __cplusplus >= 202002L // C++20
+ if (param.starts_with (hashPrefix))
+#else
+ if (param.substr (0, hashPrefix.size ()) == hashPrefix)
+#endif
+ {
+ std::string_view hexStr = param.substr (hashPrefix.size (), infoHash.size ()*2);
+ try
+ {
+ boost::algorithm::unhex (hexStr.begin(), hexStr.end(), infoHash.begin());
+ isInfoHashFound = true;
+ }
+ catch (std::exception& ex)
+ {
+ LogPrint (eLogInfo, "TorentsTunnel: Can't unhex magnet hash ", hexStr);
+ }
+ }
+ }
+ if (isInfoHashFound && m_Torrents.find (infoHash) == m_Torrents.end ())
+ {
+ auto torrent = std::make_shared (infoHash);
+ return { torrent, InsertTorrent (torrent) };
+ }
+ }
+ return { nullptr, 0 };
+ }
+
int TorrentsTunnel::InsertTorrent (std::shared_ptr torrent)
{
if (!torrent) return 0;
@@ -439,6 +521,18 @@ namespace torrents
return true;
}
+ void TorrentsTunnel::UpdateTorrentInfo (std::shared_ptr torrent, std::string_view info)
+ {
+ if (!torrent) return;
+ torrent->ParseInfo (info);
+ torrent->SetFullPath (m_TorrentsDir/std::filesystem::path (torrent->GetName ()));
+ boost::asio::post (GetDiskIOService (), [this, torrent]()
+ {
+ SaveTorrentFile (torrent);
+ InitTorrentFiles (torrent);
+ });
+ }
+
void TorrentsTunnel::Accept ()
{
auto localDestination = GetLocalDestination ();
@@ -473,7 +567,7 @@ namespace torrents
if (!torrent) return;
i2p::http::URL reqURL;
if (trackerID < m_Trackers.size())
- reqURL.parse (m_Trackers[trackerID]);
+ reqURL.parse (std::get<0>(m_Trackers[trackerID]));
else
reqURL.parse (torrent->GetAnnounce ());
#if __cplusplus >= 202002L // C++20
@@ -487,7 +581,20 @@ namespace torrents
}
if (reqURL.schema == "udp")
{
- ConnectToDatagramTracker (torrent, trackerID, reqURL.host, reqURL.port, event);
+ if (trackerID >= m_Trackers.size()) return;
+ auto ts = i2p::util::GetMonotonicMilliseconds ();
+ auto& [announce, connectionID, expiration, fromPort] = m_Trackers[trackerID];
+ if (connectionID && ts <= expiration)
+ SendAnnounceToDatagramTracker (trackerID, connectionID, torrent, reqURL.host, reqURL.port, fromPort, event);
+ else
+ {
+ if (!expiration || ts >= expiration) // don't connect if pending connection
+ {
+ connectionID = 0;
+ expiration = ts + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT;
+ ConnectToDatagramTracker (trackerID, reqURL.host, reqURL.port);
+ }
+ }
return;
}
std::map params;
@@ -604,7 +711,7 @@ namespace torrents
auto it = m_DatragramTrackerTransactions.begin ();
while (it != m_DatragramTrackerTransactions.end ())
{
- if (ts > (std::get<5>(it->second) + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT)*1000LL)
+ if (ts > std::get<3>(it->second) + DATAGRAM_TRACKER_TRANSACTION_TIMEOUT)
it = m_DatragramTrackerTransactions.erase (it);
else
it++;
@@ -816,12 +923,21 @@ namespace torrents
{
uint32_t transactionID = bufbe32toh (buf);
LogPrint (eLogDebug, "TorrentsTunnel: Datagram tracker action error response ", transactionID);
- m_DatragramTrackerTransactions.erase (transactionID);
- LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker error response: ", std::string_view ((const char *)(buf + 4), len - 4));
+ auto it = m_DatragramTrackerTransactions.find (transactionID);
+ if (it == m_DatragramTrackerTransactions.end ())
+ {
+ LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker transaction ", transactionID, " not found");
+ return;
+ }
+ std::string_view error ((const char *)(buf + 4), len - 4);
+ LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker error response: ", error);
+ auto [trackerID, fromPort, torrent, ts] = it->second;
+ if (!torrent.expired ()) // response to announce
+ torrent.lock ()->SetTrackerError (trackerID, error);
+ m_DatragramTrackerTransactions.erase (it);
}
- void TorrentsTunnel::ConnectToDatagramTracker (std::shared_ptr torrent,
- size_t trackerID, std::string_view dest, uint16_t port, TrackerAnnounceEvent event)
+ void TorrentsTunnel::ConnectToDatagramTracker (size_t trackerID, std::string_view dest, uint16_t port)
{
LogPrint (eLogDebug, "TorrentsTunnel: Connecting to datagram tracker ", dest, ":", port);
auto address = i2p::client::context.GetAddressBook ().GetAddress (dest);
@@ -840,10 +956,11 @@ namespace torrents
auto session = dgramDest->GetSession (address->identHash);
if (session)
{
- m_DatragramTrackerTransactions.emplace (transactionID, std::make_tuple (torrent,
- trackerID, address->identHash, port, fromPort, i2p::util::GetMonotonicSeconds (), event));
+ m_DatragramTrackerTransactions.emplace (transactionID, std::make_tuple (
+ trackerID, fromPort, std::shared_ptr(nullptr), i2p::util::GetMonotonicMilliseconds ()));
session->SetVersion (i2p::datagram::eDatagramV2); // send datagram2
dgramDest->SendDatagram (session, connectRequest, 16, fromPort, port);
+ dgramDest->FlushSendQueue (session);
}
else
LogPrint (eLogInfo, "TorrentsTunnel: Can't obtain datagram session to ", dest);
@@ -871,48 +988,69 @@ namespace torrents
return;
}
uint64_t connectionID = bufbe64toh (buf + 4);
- auto [torrent, trackerID, ident, port, fromPort, ts, event] = it->second;
- if (!torrent.expired ())
- SendAnnounceToDatagramTracker (transactionID, connectionID, torrent.lock (), ident, port, fromPort, event);
- else
- m_DatragramTrackerTransactions.erase (it);
+ int lifetime = DATAGRAM_TRACKER_CONNECTION_EXPIRATION;
+ if (len >= 14)
+ {
+ lifetime = std::max (bufbe16toh (buf + 12)*1000, DATAGRAM_TRACKER_CONNECTION_EXPIRATION);
+ LogPrint (eLogDebug, "TorrentsTunnel: Datagram tracker connection lifetime set to ", lifetime/1000, " seconds");
+ }
+ auto [trackerID, fromPort, torrent, ts] = it->second;
+ if (trackerID < m_Trackers.size ())
+ {
+ auto& [announce, connID, connExpiration, connFromPort] = m_Trackers[trackerID];
+ connID = connectionID;
+ connExpiration = i2p::util::GetMonotonicMilliseconds () + lifetime;
+ connFromPort = fromPort;
+ }
+ m_DatragramTrackerTransactions.erase (it);
}
- void TorrentsTunnel::SendAnnounceToDatagramTracker (uint32_t transactionID,
- uint64_t connectionID, std::shared_ptr torrent, const i2p::data::IdentHash& ident,
- uint16_t port, uint16_t fromPort, TrackerAnnounceEvent event)
+ void TorrentsTunnel::SendAnnounceToDatagramTracker (size_t trackerID, uint64_t connectionID,
+ std::shared_ptr torrent, std::string_view dest, uint16_t port,
+ uint16_t fromPort, TrackerAnnounceEvent event)
{
- auto localDestination = GetLocalDestination ();
- auto dgramDest = localDestination->GetDatagramDestination ();
- if (dgramDest)
+ LogPrint (eLogDebug, "TorrentsTunnel: Sending announce to datagram tracker ", dest, ":", port);
+ auto address = i2p::client::context.GetAddressBook ().GetAddress (dest);
+ if (address && address->IsIdentHash ())
{
- uint8_t announce[98];
- htobe64buf (announce, connectionID); // connection_id
- htobe32buf (announce + 8, eDatagramTrackerActionAnnounce); // action
- htobe32buf (announce + 12, transactionID); // transaction_id
- memcpy (announce + 16, torrent->GetInfoHash ().data (), 20); // info_hash
- memcpy (announce + 36, m_PeerID.data (), 20); // peer_id
- auto left = torrent->GetLeft ();
- htobe64buf (announce + 56, torrent->GetLength () - left); // downloaded
- htobe64buf (announce + 64, left); // left
- htobe64buf (announce + 72, torrent->GetUploaded ()); // uploaded
- htobe32buf (announce + 80, event); // event
- htobe32buf (announce + 84, 0); // IP address 0:not used
- htobe32buf (announce + 88, 0); // key, ignored
- int numWant = 0;
- if (!torrent->IsComplete () && (event == eTrackerAnnounceEventNone || event == eTrackerAnnounceEventStarted))
- numWant = TRACKER_MAX_NUM_WANT;
- htobe32buf (announce + 92, numWant); // num_want
- htobe16buf (announce + 96, fromPort); // from port
- auto session = dgramDest->GetSession (ident);
- if (session)
+ auto localDestination = GetLocalDestination ();
+ auto dgramDest = localDestination->GetDatagramDestination ();
+ if (dgramDest)
{
- session->SetVersion (i2p::datagram::eDatagramV3); // send datagram3
- dgramDest->SendDatagram (session, announce, 98, fromPort, port);
+ uint8_t announce[98];
+ htobe64buf (announce, connectionID); // connection_id
+ htobe32buf (announce + 8, eDatagramTrackerActionAnnounce); // action
+ uint32_t transactionID = localDestination->GetRng()();
+ htobe32buf (announce + 12, transactionID); // transaction_id
+ memcpy (announce + 16, torrent->GetInfoHash ().data (), 20); // info_hash
+ memcpy (announce + 36, m_PeerID.data (), 20); // peer_id
+ auto left = torrent->GetLeft ();
+ htobe64buf (announce + 56, torrent->GetLength () - left); // downloaded
+ htobe64buf (announce + 64, left); // left
+ htobe64buf (announce + 72, torrent->GetUploaded ()); // uploaded
+ htobe32buf (announce + 80, event); // event
+ htobe32buf (announce + 84, 0); // IP address 0:not used
+ htobe32buf (announce + 88, 0); // key, ignored
+ int numWant = 0;
+ if (!torrent->IsComplete () && (event == eTrackerAnnounceEventNone || event == eTrackerAnnounceEventStarted))
+ numWant = TRACKER_MAX_NUM_WANT;
+ htobe32buf (announce + 92, numWant); // num_want
+ htobe16buf (announce + 96, fromPort); // from port
+ auto session = dgramDest->GetSession (address->identHash);
+ if (session)
+ {
+ m_DatragramTrackerTransactions.emplace (transactionID, std::make_tuple (
+ trackerID, fromPort, torrent, i2p::util::GetMonotonicMilliseconds ()));
+ session->SetVersion (i2p::datagram::eDatagramV3); // send datagram3
+ dgramDest->SendDatagram (session, announce, 98, fromPort, port);
+ dgramDest->FlushSendQueue (session);
+ }
+ else
+ LogPrint (eLogInfo, "TorrentsTunnel: Can't obtain datagram session");
}
- else
- LogPrint (eLogInfo, "TorrentsTunnel: Can't obtain datagram session");
}
+ else
+ LogPrint (eLogInfo, "TorrentsTunnel: Tracker not found: ", dest);
}
void TorrentsTunnel::HandleAnnounceResponse (const uint8_t * buf, size_t len)
@@ -930,7 +1068,7 @@ namespace torrents
LogPrint (eLogInfo, "TorrentsTunnel: Datagram tracker transaction ", transactionID, " not found");
return;
}
- auto [torrent, trackerID, ident, port, fromPort, ts, event] = it->second;
+ auto [trackerID, fromPort, torrent, ts] = it->second;
if (!torrent.expired ())
{
uint32_t interval = bufbe32toh (buf + 4);
@@ -942,3 +1080,5 @@ namespace torrents
}
}
}
+
+#endif // NO_TORRENTS
diff --git a/libi2pd_client/TorrentsTunnel.h b/libi2pd_client/TorrentsTunnel.h
index 80f7b915..1c4a942b 100644
--- a/libi2pd_client/TorrentsTunnel.h
+++ b/libi2pd_client/TorrentsTunnel.h
@@ -6,6 +6,8 @@
* See full license text in LICENSE file at top of project tree
*/
+#ifndef NO_TORRENTS
+
#ifndef TORRENTS_TUNNEL_H__
#define TORRENTS_TUNNEL_H__
@@ -32,7 +34,8 @@ namespace i2p
namespace torrents
{
constexpr int TRACKER_RESPONSE_TIMEOUT = 8; // in seconds
- constexpr int DATAGRAM_TRACKER_TRANSACTION_TIMEOUT = 60; // in seconds
+ constexpr int DATAGRAM_TRACKER_TRANSACTION_TIMEOUT = 10000; // in milliseconds
+ constexpr int DATAGRAM_TRACKER_CONNECTION_EXPIRATION = 60000; // in milliseconds
constexpr int TRACKER_REQUESTS_CHECK_TIMEOUT = 1900; // in milliseconds
constexpr int RECONNECT_CHECK_INTERVAL = 70; // in seconds
constexpr int TRACKER_REQUESTS_INTERVAL_VARIANCE = 3000; // in milliseconds
@@ -76,6 +79,9 @@ namespace torrents
void Stop () { StopWorkAndFinishTasks (); }
};
+ using TrackerInfo = std::tuple;
+ // (announce, connection_id, connection expiration time in monotonic milliseconds, connction from_port)
+
public:
TorrentsTunnel (std::string_view name, std::shared_ptr localDestination,
@@ -86,11 +92,15 @@ namespace torrents
auto& GetDiskIOService () { return m_DiskIOService.GetService (); };
const std::string& GetPeerID () const { return m_PeerID; }
- const std::vector& GetTrackers () const { return m_Trackers; }
+ const std::filesystem::path& GetTorrentsDir () const { return m_TorrentsDir; }
+ std::string GetTrackerAnnounce (size_t id) const { return (id < m_Trackers.size ()) ? std::get<0>(m_Trackers[id]) : ""; }
+ size_t GetNumTrackers () const { return m_Trackers.size (); }
std::shared_ptr FindTorrent (const Torrent::InfoHash& infoHash) const;
std::shared_ptr FindTorrentByID (int id) const;
std::vector GetTorrentIDs () const;
- std::pair, int> AddTorrent (std::string_view torrentFileContent); // (tunnel, id)
+ std::pair, int> AddTorrent (std::string_view torrentFileContent); // retrun (torrent, id)
+ std::pair, int> AddMagnet (std::string_view magnet); // return (torrent, id)
+ void UpdateTorrentInfo (std::shared_ptr torrent, std::string_view info); // magnet
bool RemoveTorrent (int id, bool deleteFiles);
bool StopTorrent (int id);
bool StartTorrent (int id);
@@ -103,6 +113,7 @@ namespace torrents
void Accept ();
void ReadTorrentFile (const std::filesystem::path& torrentFilePath);
+ void SaveTorrentFile (std::shared_ptr torrent);
void InitTorrentFiles (std::shared_ptr torrent);
int InsertTorrent (std::shared_ptr torrent); // returns id > 0 if success and 0 if failed
void RemoveTorrent (std::shared_ptr torrent, bool deleteFiles);
@@ -134,32 +145,32 @@ namespace torrents
void UpdateStats ();
void HandleRecvFromI2PRaw (uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
- void ConnectToDatagramTracker (std::shared_ptr torrent, size_t trackerID,
- std::string_view dest, uint16_t port, TrackerAnnounceEvent event);
+ void ConnectToDatagramTracker (size_t trackerID, std::string_view dest, uint16_t port);
void HandleConnectResponse (const uint8_t * buf, size_t len);
void HandleErrorResponse (const uint8_t * buf, size_t len);
void HandleAnnounceResponse (const uint8_t * buf, size_t len);
- void SendAnnounceToDatagramTracker (uint32_t transactionID, uint64_t connectionID,
- std::shared_ptr torrent, const i2p::data::IdentHash& ident,
- uint16_t port, uint16_t fromPort, TrackerAnnounceEvent event);
+ void SendAnnounceToDatagramTracker (size_t trackerID, uint64_t connectionID,
+ std::shared_ptr torrent, std::string_view dest, uint16_t port,
+ uint16_t fromPort, TrackerAnnounceEvent event);
private:
std::string m_Name, m_PeerID; // 20 characters
std::filesystem::path m_TorrentsDir;
- std::vector m_Trackers;
+ std::vector m_Trackers;
std::map > m_Torrents;
std::map > m_TorrentsByID;
mutable std::mutex m_TorrentsMutex;
boost::asio::steady_timer m_TrackerRequestsCheckTimer, m_KeepAliveCheckTimer,
m_ReconnectCheckTimer, m_TorrentsStatusUpdateTimer;
DiskIOService m_DiskIOService;
- std::unordered_map, size_t, i2p::data::IdentHash,
- uint16_t, uint16_t, uint64_t, TrackerAnnounceEvent> > m_DatragramTrackerTransactions;
- // transactionID->(torrent, trackerID, ident, port, fromPort, ts in monotonic seconds, event)
+ std::unordered_map, uint64_t > > m_DatragramTrackerTransactions;
+ // transactionID->(trackerID, from_port, torrent, timestamp monotonic milliseconds)
};
}
}
#endif
+
+#endif // NO_TORRENTS
diff --git a/libi2pd_client/UDPTunnel.cpp b/libi2pd_client/UDPTunnel.cpp
index 6c509c08..a2076df3 100644
--- a/libi2pd_client/UDPTunnel.cpp
+++ b/libi2pd_client/UDPTunnel.cpp
@@ -470,15 +470,20 @@ namespace client
m_IsUniqueLocal (true), m_Name (name), m_LocalAddress (localAddress),
m_RemoteEndpoint (forwardTo), m_LocalDest (localDestination), m_inPort(inPort), m_Gzip (gzip)
{
+ // without a reference the destination counts as unused and a config reload
+ // stops it, leaving the tunnel with a destination that receives nothing
+ if (m_LocalDest) m_LocalDest->Acquire ();
}
I2PUDPServerTunnel::~I2PUDPServerTunnel ()
{
Stop ();
+ if (m_LocalDest) m_LocalDest->Release ();
}
void I2PUDPServerTunnel::Start ()
{
+ m_IsStarted = true;
m_LocalDest->Start ();
auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip);
@@ -549,6 +554,11 @@ namespace client
void I2PUDPServerTunnel::Stop ()
{
+ // a config reload builds a second tunnel object and drops it when the same
+ // one is already there. Its destructor must not reset the receiver of the
+ // live tunnel: they share the datagram destination
+ if (!m_IsStarted) return;
+ m_IsStarted = false;
if (m_StatsTimer) m_StatsTimer->cancel ();
if (m_CleanupTimer) m_CleanupTimer->cancel ();
auto dgram = m_LocalDest->GetDatagramDestination ();
@@ -594,15 +604,18 @@ namespace client
m_ResolveThread (nullptr), m_LocalSocket (nullptr), RemotePort (remotePort),
m_LastPort (0), m_cancel_resolve (false), m_Gzip (gzip), m_DatagramVersion (datagramVersion)
{
+ if (m_LocalDest) m_LocalDest->Acquire ();
}
I2PUDPClientTunnel::~I2PUDPClientTunnel ()
{
Stop ();
+ if (m_LocalDest) m_LocalDest->Release ();
}
void I2PUDPClientTunnel::Start ()
{
+ m_IsStarted = true;
UDPConnection::Start ();
// Reset flag in case of tunnel reload
if (m_cancel_resolve) m_cancel_resolve = false;
@@ -637,6 +650,8 @@ namespace client
void I2PUDPClientTunnel::Stop ()
{
+ if (!m_IsStarted) return; // see the comment in I2PUDPServerTunnel::Stop
+ m_IsStarted = false;
if (m_KeepAliveTimer) m_KeepAliveTimer->cancel ();
if (m_StatsTimer) m_StatsTimer->cancel ();
diff --git a/libi2pd_client/UDPTunnel.h b/libi2pd_client/UDPTunnel.h
index 3b77615f..8c0e522d 100644
--- a/libi2pd_client/UDPTunnel.h
+++ b/libi2pd_client/UDPTunnel.h
@@ -194,6 +194,7 @@ namespace client
UDPSessionPtr m_LastSession;
uint16_t m_inPort;
bool m_Gzip;
+ bool m_IsStarted = false; // a tunnel that never started shares nothing to tear down
uint32_t m_NumRawNoSession = 0;
size_t m_MaxWindow = I2P_UDP_MIN_MAX_NUM_UNACKED_DATAGRAMS;
std::unique_ptr m_StatsTimer;
@@ -264,6 +265,7 @@ namespace client
uint16_t RemotePort, m_LastPort;
bool m_cancel_resolve;
bool m_Gzip;
+ bool m_IsStarted = false; // see the comment in I2PUDPServerTunnel
i2p::datagram::DatagramVersion m_DatagramVersion;
std::shared_ptr m_LastSession;
uint32_t m_KeepAliveInterval = 0;
diff --git a/tests/test-http-merge_chunked.cpp b/tests/test-http-merge_chunked.cpp
index 31b6a298..bdba5c3b 100644
--- a/tests/test-http-merge_chunked.cpp
+++ b/tests/test-http-merge_chunked.cpp
@@ -21,5 +21,11 @@ int main() {
assert(MergeChunkedResponse(in, out) == true);
assert(out.str() == "HTTP response with \r\nchunks.");
+ /* a chunk that promises more bytes than the response carries */
+ std::stringstream truncated("20\r\nshort\r\n");
+ std::stringstream out2;
+
+ assert(MergeChunkedResponse(truncated, out2) == false);
+
return 0;
}
diff --git a/tests/test-http-url.cpp b/tests/test-http-url.cpp
index a5021c43..061c83ce 100644
--- a/tests/test-http-url.cpp
+++ b/tests/test-http-url.cpp
@@ -125,6 +125,12 @@ int main() {
assert(url->frag == "frag");
delete url;
+ /* nothing after the schema or the user part: must not throw */
+ url = new URL;
+ assert(url->parse("http://") == false);
+ assert(url->parse("http://user@") == false);
+ delete url;
+
return 0;
}