Merge branch 'PurpleI2P:openssl' into openssl

This commit is contained in:
Luminok
2026-09-05 01:09:26 +00:00
committed by GitHub
20 changed files with 681 additions and 194 deletions
+6
View File
@@ -1206,6 +1206,7 @@ namespace http {
}
s << "</div>\r\n<br>\r\n";
}
#ifndef NO_TORRENTS
auto& torrentsTunnels = i2p::client::context.GetTorrentsTunnels ();
if (!torrentsTunnels.empty ())
{
@@ -1223,10 +1224,12 @@ namespace http {
}
s << "</div>\r\n<br>\r\n";
}
#endif
}
static void ShowTorrentsPage (std::stringstream& s, std::map<std::string, std::string>& params)
{
#ifndef NO_TORRENTS
bool js; i2p::config::GetOption("http.javascript", js);
if (!js)
{
@@ -1284,6 +1287,9 @@ namespace http {
}
else
s << "<h1 style='color:red'>Not found rpc tunnel " << tunnelname << "</h1>\r\n";
#else
s << "<h1 style='color:red'>Torrents are not supported</h1>\r\n";
#endif
}
HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
+7 -7
View File
@@ -98,10 +98,10 @@ namespace datagram
{
constexpr uint8_t flags[] = { 0x00, 0x02 }; // datagram2, no options
// signature
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<std::mutex> 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 (); )
{
+1
View File
@@ -80,6 +80,7 @@ namespace datagram
bool IsRatchets () const { return m_RoutingSession && m_RoutingSession->IsRatchets (); }
void SetRemoteLeaseSet (std::shared_ptr<const i2p::data::LeaseSet> ls) { m_RemoteLeaseSet = ls; }
std::shared_ptr<const i2p::data::LeaseSet> 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; }
+6
View File
@@ -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
+8 -2
View File
@@ -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)
{
+18 -5
View File
@@ -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);
+3
View File
@@ -1531,6 +1531,9 @@ namespace stream
void Stream::SendPackets (const std::list<Packet *>& 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 ();
+6
View File
@@ -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);
+10 -1
View File
@@ -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<I2PService> 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<std::string> (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<i2p::torrents::TorrentsRPCServer> ClientContext::CreateTorrentsRPCServer (
std::string_view address, uint16_t port)
{
@@ -1238,5 +1246,6 @@ namespace client
}
return nullptr;
}
#endif
}
}
+6
View File
@@ -152,7 +152,9 @@ namespace client
void CreateNewSharedLocalDestination ();
void AddLocalDestination (std::shared_ptr<ClientDestination> localDestination);
#ifndef NO_TORRENTS
std::shared_ptr<i2p::torrents::TorrentsRPCServer> CreateTorrentsRPCServer (std::string_view address, uint16_t port);
#endif
private:
@@ -171,8 +173,10 @@ namespace client
std::map<boost::asio::ip::udp::endpoint, std::shared_ptr<I2PUDPClientTunnel> > m_ClientForwards; // local endpoint -> udp tunnel
std::map<std::pair<i2p::data::IdentHash, int>, std::shared_ptr<I2PUDPServerTunnel> > m_ServerForwards; // <destination,port> -> udp tunnel
#ifndef NO_TORRENTS
std::unordered_map<i2p::data::IdentHash, std::shared_ptr<i2p::torrents::TorrentsTunnel> > m_TorrentsTunnels; // local destination -> torrents tunnel
std::unordered_map<uint16_t, std::shared_ptr<i2p::torrents::TorrentsRPCServer> > 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<const I2PService> GetHttpProxy () const
{
std::lock_guard<std::mutex> l(m_ProxyMutex);
+222 -35
View File
@@ -6,6 +6,8 @@
* See full license text in LICENSE file at top of project tree
*/
#ifndef NO_TORRENTS
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
@@ -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<std::pair<std::string_view, std::string_view> >& 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<std::vector<uint8_t>, 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<i2p::data::IdentHash> 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<i2p::data::IdentHash> 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<uint8_t> 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<uint8_t> 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<RequestedBlock> PeerConnection::GetNextBlockToRequest ()
{
auto block = m_Torrent->GetNextBlockToRequest (shared_from_this (), true); // skip already requested pieces
@@ -1627,3 +1812,5 @@ namespace torrents
}
}
}
#endif // NO_TORRENTS
+38 -12
View File
@@ -9,6 +9,8 @@
#ifndef TORRENTS_H__
#define TORRENTS_H__
#ifndef NO_TORRENTS
#include <inttypes.h>
#include <openssl/sha.h>
#include <boost/asio.hpp>
@@ -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<uint32_t, uint32_t, uint32_t>; // (index, offset, len)
class Torrent final
{
using TrackerStats = std::tuple<std::unordered_set<i2p::data::IdentHash>,
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<uint8_t, 20>;
using TrackerInfo = std::tuple<std::unordered_set<i2p::data::IdentHash>, 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<uint8_t, SHA_DIGEST_LENGTH>;
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<std::pair<std::filesystem::path, size_t> >& 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<uint8_t> 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<uint8_t> m_Info; // for BEP9
InfoHash m_InfoHash; // SHA1
std::vector<Piece> m_Pieces;
std::vector<TrackerInfo> m_TrackersInfo;
std::vector<TrackerStats> m_TrackerStats;
bool m_IsComplete, m_IsStopped;
std::list<std::pair<std::filesystem::path, size_t> > 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<PeerConnection>
{
using ExtendedMessageHandler = void (PeerConnection::*)(const uint8_t * buf, size_t len);
public:
using PeerID = std::array<uint8_t, 20>;
@@ -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<RequestedBlock> GetNextBlockToRequest ();
bool RequestNextBlocks ();
@@ -332,6 +351,10 @@ namespace torrents
std::list<RequestedBlock> m_IncomingRequestsQueue;
int m_LastRequestedPieceIndex;
std::unique_ptr<boost::asio::steady_timer> m_HandshakeReceiveTimer;
std::unordered_map<uint8_t, PeerConnection::ExtendedMessageHandler> m_ExtendedMessageHandlers;
// BEP9
size_t m_RemoteMetadataSize;
std::vector<uint8_t> 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
+98 -64
View File
@@ -6,6 +6,8 @@
* See full license text in LICENSE file at top of project tree
*/
#ifndef NO_TORRENTS
#include <boost/version.hpp>
#if !defined(ANDROID) && (BOOST_VERSION >= 108100) // boost::json since 1.75, we allow it since 1.81 due to std::string_view compatibility
#include <boost/json.hpp>
@@ -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<int> GetTorrentIds (boost::json::object& arguments);
@@ -54,6 +56,7 @@ namespace torrents
boost::json::array GetPeers (std::shared_ptr<Torrent> torrent) const;
boost::json::array GetTrackers (std::shared_ptr<Torrent> torrent) const;
boost::json::array GetTrackerStats (std::shared_ptr<Torrent> torrent) const;
boost::json::array GetFiles (std::shared_ptr<Torrent> 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> 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> 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> 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> 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> 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> 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<uint8_t,12> 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
+4
View File
@@ -9,6 +9,8 @@
#ifndef TORRENTS_RPC_H__
#define TORRENTS_RPC_H__
#ifndef NO_TORRENTS
#include <inttypes.h>
#include <memory>
#include <string>
@@ -76,3 +78,5 @@ namespace torrents
}
#endif
#endif
+196 -56
View File
@@ -6,8 +6,11 @@
* See full license text in LICENSE file at top of project tree
*/
#ifndef NO_TORRENTS
#include <fstream>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/hex.hpp>
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#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<std::string> 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<TorrentsTunnel>(shared_from_this ()),
@@ -126,6 +134,20 @@ namespace torrents
}
}
void TorrentsTunnel::SaveTorrentFile (std::shared_ptr<Torrent> 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> 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<std::shared_ptr<Torrent>, int> TorrentsTunnel::AddMagnet (std::string_view magnet)
{
// magnet:?xt=urn:btih:<hash>&dn=<name>&tr=<tracker>
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<Torrent> (infoHash);
return { torrent, InsertTorrent (torrent) };
}
}
return { nullptr, 0 };
}
int TorrentsTunnel::InsertTorrent (std::shared_ptr<Torrent> torrent)
{
if (!torrent) return 0;
@@ -439,6 +521,18 @@ namespace torrents
return true;
}
void TorrentsTunnel::UpdateTorrentInfo (std::shared_ptr<Torrent> 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<std::string, std::string> 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> 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<Torrent>(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> 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> 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
+23 -12
View File
@@ -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<std::string, uint64_t, uint64_t, uint16_t>;
// (announce, connection_id, connection expiration time in monotonic milliseconds, connction from_port)
public:
TorrentsTunnel (std::string_view name, std::shared_ptr<i2p::client::ClientDestination> localDestination,
@@ -86,11 +92,15 @@ namespace torrents
auto& GetDiskIOService () { return m_DiskIOService.GetService (); };
const std::string& GetPeerID () const { return m_PeerID; }
const std::vector<std::string>& 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<Torrent> FindTorrent (const Torrent::InfoHash& infoHash) const;
std::shared_ptr<Torrent> FindTorrentByID (int id) const;
std::vector<int> GetTorrentIDs () const;
std::pair<std::shared_ptr<Torrent>, int> AddTorrent (std::string_view torrentFileContent); // (tunnel, id)
std::pair<std::shared_ptr<Torrent>, int> AddTorrent (std::string_view torrentFileContent); // retrun (torrent, id)
std::pair<std::shared_ptr<Torrent>, int> AddMagnet (std::string_view magnet); // return (torrent, id)
void UpdateTorrentInfo (std::shared_ptr<Torrent> 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> torrent);
void InitTorrentFiles (std::shared_ptr<Torrent> torrent);
int InsertTorrent (std::shared_ptr<Torrent> torrent); // returns id > 0 if success and 0 if failed
void RemoveTorrent (std::shared_ptr<Torrent> 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> 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> 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> 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<std::string> m_Trackers;
std::vector<TrackerInfo> m_Trackers;
std::map<Torrent::InfoHash, std::shared_ptr<Torrent> > m_Torrents;
std::map<int, std::weak_ptr<Torrent> > 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<uint32_t, std::tuple<std::weak_ptr<Torrent>, 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<uint32_t, std::tuple<size_t, uint16_t, std::weak_ptr<Torrent>, uint64_t > > m_DatragramTrackerTransactions;
// transactionID->(trackerID, from_port, torrent, timestamp monotonic milliseconds)
};
}
}
#endif
#endif // NO_TORRENTS
+15
View File
@@ -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 ();
+2
View File
@@ -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<boost::asio::steady_timer> 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<UDPConvo> m_LastSession;
uint32_t m_KeepAliveInterval = 0;
+6
View File
@@ -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;
}
+6
View File
@@ -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;
}