mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2026-10-15 06:39:57 +00:00
Merge branch 'PurpleI2P:openssl' into openssl
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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 (); )
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user