mirror of
https://onedev.site.tesses.net/tesses-framework
synced 2026-02-08 07:45:46 +00:00
Push failed torrent code as backup before I remove it
This commit is contained in:
@@ -5,6 +5,7 @@ project(TessesFramework VERSION 1.0.0)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
list(APPEND TESSESFRAMEWORK_SOURCE
|
||||
src/Random.cpp
|
||||
src/Date/Date.cpp
|
||||
src/Http/FileServer.cpp
|
||||
src/Http/MountableServer.cpp
|
||||
@@ -22,6 +23,7 @@ src/Mail/Smtp.cpp
|
||||
src/Serialization/Json.cpp
|
||||
src/Serialization/SQLite.cpp
|
||||
src/Serialization/BitConverter.cpp
|
||||
src/Serialization/Bencode.cpp
|
||||
src/Platform/Environment.cpp
|
||||
src/Platform/Process.cpp
|
||||
src/Streams/FileStream.cpp
|
||||
@@ -58,6 +60,9 @@ src/Crypto/MbedTLS/Crypto.cpp
|
||||
src/Args.cpp
|
||||
src/TF_Init.cpp
|
||||
src/HiddenField.cpp
|
||||
src/BitTorrent/TorrentFile.cpp
|
||||
src/BitTorrent/TorrentStream.cpp
|
||||
src/BitTorrent/TorrentManager.cpp
|
||||
)
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
|
||||
@@ -384,6 +389,32 @@ install(TARGETS ttime DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
add_executable(tshell apps/tshell.cpp)
|
||||
target_link_libraries(tshell PUBLIC tessesframework)
|
||||
install(TARGETS tshell DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
|
||||
add_executable(tprinttorrent apps/printtorrent.cpp)
|
||||
|
||||
target_link_libraries(tprinttorrent PUBLIC tessesframework)
|
||||
install(TARGETS tprinttorrent DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
|
||||
add_executable(ttorrentcreate apps/ttorrentcreate.cpp)
|
||||
|
||||
target_link_libraries(ttorrentcreate PUBLIC tessesframework)
|
||||
install(TARGETS ttorrentcreate DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
|
||||
add_executable(tclearwebseed apps/tclearwebseed.cpp)
|
||||
|
||||
target_link_libraries(tclearwebseed PUBLIC tessesframework)
|
||||
install(TARGETS tclearwebseed DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
|
||||
add_executable(tdownloadsingletorrent apps/tdownloadsingletorrent.cpp)
|
||||
|
||||
target_link_libraries(tdownloadsingletorrent PUBLIC tessesframework)
|
||||
install(TARGETS tdownloadsingletorrent DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
|
||||
|
||||
add_executable(trng apps/trng.cpp)
|
||||
|
||||
target_link_libraries(trng PUBLIC tessesframework)
|
||||
install(TARGETS trng DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
endif()
|
||||
|
||||
include(InstallRequiredSystemLibraries)
|
||||
|
||||
26
apps/printtorrent.cpp
Normal file
26
apps/printtorrent.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "TessesFramework/TessesFramework.hpp"
|
||||
|
||||
using namespace Tesses::Framework;
|
||||
using namespace Tesses::Framework::Streams;
|
||||
using namespace Tesses::Framework::Filesystem;
|
||||
using namespace Tesses::Framework::Serialization::Bencode;
|
||||
using namespace Tesses::Framework::TextStreams;
|
||||
using namespace Tesses::Framework::BitTorrent;
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
TF_Init();
|
||||
if(argc < 2)
|
||||
{
|
||||
printf("USAGE: %s /path/to/torrent/file\n",argv[0]);
|
||||
return 1;
|
||||
}
|
||||
auto strm = LocalFS->OpenFile((std::string)argv[1],"rb");
|
||||
auto bencode = Bencode::Load(strm);
|
||||
if(std::holds_alternative<BeDictionary>(bencode))
|
||||
{
|
||||
|
||||
TorrentFile file(std::get<BeDictionary>(bencode));
|
||||
file.Print(std::make_shared<ConsoleWriter>());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
22
apps/tclearwebseed.cpp
Normal file
22
apps/tclearwebseed.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "TessesFramework/TessesFramework.hpp"
|
||||
using namespace Tesses::Framework;
|
||||
using namespace Tesses::Framework::BitTorrent;
|
||||
using namespace Tesses::Framework::Serialization::Bencode;
|
||||
using namespace Tesses::Framework::Filesystem;
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
TF_Init();
|
||||
if(argc < 2)
|
||||
{
|
||||
printf("USAGE: %s torrent_file\n",argv[0]);
|
||||
return 1;
|
||||
}
|
||||
auto strm = LocalFS->OpenFile((std::string)argv[1],"rb");
|
||||
auto data = Bencode::Load(strm);
|
||||
if(std::holds_alternative<BeDictionary>(data)){
|
||||
std::get<BeDictionary>(data).SetValue("url-list",BeUndefined());
|
||||
}
|
||||
strm = LocalFS->OpenFile((std::string)argv[1],"wb");
|
||||
Bencode::Save(strm,data);
|
||||
return 0;
|
||||
}
|
||||
51
apps/tdownloadsingletorrent.cpp
Normal file
51
apps/tdownloadsingletorrent.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "TessesFramework/TessesFramework.hpp"
|
||||
using namespace Tesses::Framework;
|
||||
using namespace Tesses::Framework::Streams;
|
||||
using namespace Tesses::Framework::Filesystem;
|
||||
using namespace Tesses::Framework::Serialization::Bencode;
|
||||
using namespace Tesses::Framework::TextStreams;
|
||||
using namespace Tesses::Framework::BitTorrent;
|
||||
int percent(ActiveTorrent& torrent)
|
||||
{
|
||||
if(torrent.torrentSize == 0) return 100;
|
||||
auto left = torrent.getLeft();
|
||||
return 100-(int)(((double)left / (double)torrent.torrentSize)*100.0);
|
||||
|
||||
|
||||
}
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
TF_Init();
|
||||
if(argc < 2)
|
||||
{
|
||||
printf("USAGE: %s /path/to/torrent/file\n",argv[0]);
|
||||
return 1;
|
||||
}
|
||||
auto strm = LocalFS->OpenFile((std::string)argv[1],"rb");
|
||||
auto bencode = Bencode::Load(strm);
|
||||
if(std::holds_alternative<BeDictionary>(bencode))
|
||||
{
|
||||
|
||||
TorrentFile file(std::get<BeDictionary>(bencode));
|
||||
|
||||
VFSPath path;
|
||||
path.relative=true;
|
||||
ActiveTorrent active(file,Filesystem::LocalFS,path,GeneratePeerId());
|
||||
int lastPercent = 0;
|
||||
std::cout << "0%" << std::endl;
|
||||
while(TF_IsRunning())
|
||||
{
|
||||
active.process();
|
||||
|
||||
int cur=percent(active);
|
||||
if(cur > lastPercent)
|
||||
{
|
||||
lastPercent = cur;
|
||||
std::cout << "\r" << std::to_string(cur) << "%";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
TF_Quit();
|
||||
return 0;
|
||||
}
|
||||
36
apps/trng.cpp
Normal file
36
apps/trng.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "TessesFramework/TessesFramework.hpp"
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if(argc < 2)
|
||||
{
|
||||
|
||||
std::cout << "USAGE: " << argv[0] << " times" << std::endl;
|
||||
std::cout << "USAGE: " << argv[0] << " times max" << std::endl;
|
||||
std::cout << "USAGE: " << argv[0] << " times min max" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
Tesses::Framework::Random random;
|
||||
int times=std::atoi(argv[1]);
|
||||
if(argc > 2)
|
||||
{
|
||||
int32_t min = 0;
|
||||
int32_t max = std::stoi(argv[2]);
|
||||
if(argc > 3)
|
||||
{
|
||||
min = max;
|
||||
max = std::stoi(argv[3]);
|
||||
|
||||
}
|
||||
for(int i = 0; i < times; i++)
|
||||
{
|
||||
std::cout << random.Next(min,max) << std::endl;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for(int i = 0; i < times; i++)
|
||||
{
|
||||
std::cout << random.Next() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
apps/ttorrentcreate.cpp
Normal file
66
apps/ttorrentcreate.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "TessesFramework/TessesFramework.hpp"
|
||||
|
||||
using namespace Tesses::Framework;
|
||||
using namespace Tesses::Framework::Streams;
|
||||
using namespace Tesses::Framework::Filesystem;
|
||||
using namespace Tesses::Framework::Serialization::Bencode;
|
||||
using namespace Tesses::Framework::TextStreams;
|
||||
using namespace Tesses::Framework::BitTorrent;
|
||||
void usage(Args& args)
|
||||
{
|
||||
std::cout << "USAGE: " << args.filename << " [options] torrent_contents torrent_file.torrent" << std::endl << std::endl;
|
||||
std::cout << "OPTIONS:" << std::endl;
|
||||
std::cout << "tracker: torrent tracker url (require at least one)" << std::endl;
|
||||
std::cout << "webseed: url for web seeding" << std::endl;
|
||||
std::cout << "comment: set the comment" << std::endl;
|
||||
std::cout << "created_by: set the created by field, defaults to TessesFrameworkTorrent" << std::endl;
|
||||
std::cout << "piece_length: set the piece length, defaults to " + std::to_string(DEFAULT_PIECE_LENGTH) << std::endl << std::endl;
|
||||
std::cout << "FLAGS:" << std::endl;
|
||||
std::cout << "help: bring this help message up" << std::endl;
|
||||
std::cout << "private: enable private tracker flag" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
TF_Init();
|
||||
std::vector<BeString> webseeds;
|
||||
std::vector<BeString> trackers;
|
||||
bool isPrivate=false;
|
||||
int64_t pieceLength = DEFAULT_PIECE_LENGTH;
|
||||
std::string comment="";
|
||||
std::string created_by = "TessesFrameworkTorrent";
|
||||
|
||||
Args args(argc, argv);
|
||||
if(args.positional.size() < 2)
|
||||
usage(args);
|
||||
|
||||
for(auto& opt : args.options)
|
||||
{
|
||||
if(opt.first == "tracker")
|
||||
trackers.push_back(opt.second);
|
||||
else if(opt.first == "webseed")
|
||||
webseeds.push_back(opt.second);
|
||||
else if(opt.first == "comment")
|
||||
comment = opt.second;
|
||||
else if(opt.first == "created_by")
|
||||
created_by = opt.second;
|
||||
else if(opt.first == "piece_length")
|
||||
pieceLength = std::stoll(opt.second);
|
||||
}
|
||||
|
||||
for(auto& flag : args.flags)
|
||||
{
|
||||
if(flag == "help")
|
||||
usage(args);
|
||||
if(flag == "private")
|
||||
isPrivate = true;
|
||||
}
|
||||
|
||||
if(trackers.empty())
|
||||
usage(args);
|
||||
{
|
||||
auto torrent_file_stream = LocalFS->OpenFile(args.positional[1],"wb");
|
||||
TorrentFile::CreateTorrent(torrent_file_stream,trackers,webseeds,LocalFS, args.positional[0], isPrivate, pieceLength, comment, created_by);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Tesses::Framework::TF_Init();
|
||||
std::cout << "Basic auth test" << std::endl;
|
||||
std::string theEncoded="dXNlcjpwYXNz";
|
||||
Tesses::Framework::Http::ServerContext ctx(nullptr);
|
||||
ctx.requestHeaders.SetValue("Authorization","Basic " + theEncoded);
|
||||
@@ -10,4 +11,25 @@ int main(int argc, char** argv)
|
||||
std::cout << Tesses::Framework::Http::BasicAuthServer::GetCreds(ctx,user,pass) << std::endl;
|
||||
std::cout << user << std::endl;
|
||||
std::cout << pass << std::endl;
|
||||
|
||||
std::cout << "Bitfield test" << std::endl;
|
||||
Tesses::Framework::BitTorrent::TorrentBitField bf(16);
|
||||
bf.set(7,false);
|
||||
bf.set(6,false);
|
||||
bf.set(5,true);
|
||||
bf.set(4,false);
|
||||
bf.set(3,true);
|
||||
bf.set(2,false);
|
||||
bf.set(1,true);
|
||||
bf.set(0,false);
|
||||
bf.set(15,false);
|
||||
bf.set(14,true);
|
||||
bf.set(13,false);
|
||||
bf.set(12,true);
|
||||
bf.set(11,false);
|
||||
bf.set(10,false);
|
||||
bf.set(9,false);
|
||||
bf.set(8,true);
|
||||
|
||||
std::cout << std::string(bf.data().begin(),bf.data().end()) << std::endl;
|
||||
}
|
||||
70
include/TessesFramework/BitTorrent/TorrentFile.hpp
Normal file
70
include/TessesFramework/BitTorrent/TorrentFile.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include "../Serialization/Bencode.hpp"
|
||||
#include "TorrentStream.hpp"
|
||||
namespace Tesses::Framework::BitTorrent
|
||||
{
|
||||
constexpr int DEFAULT_PIECE_LENGTH = 524288;
|
||||
|
||||
class TorrentFileInfo {
|
||||
|
||||
Serialization::Bencode::BeDictionary info;
|
||||
public:
|
||||
TorrentFileInfo();
|
||||
TorrentFileInfo(const Serialization::Bencode::BeDictionary& tkn);
|
||||
|
||||
|
||||
int64_t piece_length=DEFAULT_PIECE_LENGTH;
|
||||
Serialization::Bencode::BeString pieces;
|
||||
int64_t isPrivate=0;
|
||||
Serialization::Bencode::BeString name;
|
||||
std::variant<int64_t, std::vector<TorrentFileEntry>> fileListOrLength;
|
||||
int64_t GetTorrentFileSize();
|
||||
/// @brief Generate the info
|
||||
/// @return the dictionary containing the info
|
||||
Serialization::Bencode::BeDictionary& GenerateInfoDictionary();
|
||||
/// @brief Get the info hash for info (if you are filling out the fields, you need to call GenerateInfoDictionary first)
|
||||
/// @return the 20 byte info hash
|
||||
Serialization::Bencode::BeString GetInfoHash();
|
||||
/// @brief Get the info (does not regenerate info from the fields)
|
||||
/// @return the dictionary containing the info
|
||||
Serialization::Bencode::BeDictionary& GetInfoDictionary();
|
||||
|
||||
/// @brief Used to load a dictionary in, will overwrite all the fields
|
||||
/// @param dict the dictionary to load
|
||||
void Load(const Serialization::Bencode::BeDictionary& dict);
|
||||
/// @brief Gets a read write based on this info object
|
||||
/// @param vfs the vfs object
|
||||
/// @param path file or directory inside vfs
|
||||
/// @return The read write at object
|
||||
std::shared_ptr<ReadWriteAt> GetStreamFromFilesystem(std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, const Tesses::Framework::Filesystem::VFSPath& path);
|
||||
|
||||
/// @brief Create info from file/directory
|
||||
/// @param vfs the vfs object
|
||||
/// @param path file or directory inside vfs
|
||||
/// @param isPrivate whether you use private trackers
|
||||
/// @param pieceLength length of pieces
|
||||
void CreateFromFilesystem(std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, const Tesses::Framework::Filesystem::VFSPath& path, bool isPrivate=false, int64_t pieceLength=DEFAULT_PIECE_LENGTH);
|
||||
};
|
||||
class TorrentFile {
|
||||
public:
|
||||
TorrentFile();
|
||||
TorrentFile(const Serialization::Bencode::BeDictionary& tkn);
|
||||
|
||||
|
||||
Serialization::Bencode::BeString announce;
|
||||
std::vector<Serialization::Bencode::BeString> announce_list;
|
||||
std::vector<Serialization::Bencode::BeString> url_list;
|
||||
Serialization::Bencode::BeString comment;
|
||||
Serialization::Bencode::BeString created_by;
|
||||
int64_t creation_date=0;
|
||||
TorrentFileInfo info;
|
||||
|
||||
|
||||
Serialization::Bencode::BeDictionary ToDictionary();
|
||||
|
||||
void Print(std::shared_ptr<TextStreams::TextWriter> writer);
|
||||
|
||||
static void CreateTorrent(std::shared_ptr<Tesses::Framework::Streams::Stream> torrent_file_stream,const std::vector<Serialization::Bencode::BeString>& trackers,const std::vector<Serialization::Bencode::BeString>& webseeds, std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, const Tesses::Framework::Filesystem::VFSPath& path, bool isPrivate=false, int64_t pieceLength=DEFAULT_PIECE_LENGTH, Serialization::Bencode::BeString comment="", Serialization::Bencode::BeString created_by = "TessesFrameworkTorrent");
|
||||
|
||||
};
|
||||
}
|
||||
95
include/TessesFramework/BitTorrent/TorrentManager.hpp
Normal file
95
include/TessesFramework/BitTorrent/TorrentManager.hpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include "TorrentFile.hpp"
|
||||
#include <queue>
|
||||
#include "../Threading/Thread.hpp"
|
||||
#include "../Random.hpp"
|
||||
namespace Tesses::Framework::BitTorrent
|
||||
{
|
||||
|
||||
class ActiveTorrent;
|
||||
|
||||
class TorrentBitField {
|
||||
std::vector<uint8_t> bits;
|
||||
size_t no_bits=0;
|
||||
public:
|
||||
TorrentBitField();
|
||||
TorrentBitField(size_t bits);
|
||||
bool get(size_t index);
|
||||
void set(size_t index, bool val);
|
||||
void zero();
|
||||
size_t size();
|
||||
void resize(size_t len);
|
||||
std::vector<uint8_t>& data();
|
||||
bool allone();
|
||||
};
|
||||
struct CancelRequest {
|
||||
uint32_t piece;
|
||||
uint32_t begin;
|
||||
uint32_t length;
|
||||
};
|
||||
|
||||
class TorrentPeer {
|
||||
public:
|
||||
bool isChokingMe;
|
||||
bool isChoked;
|
||||
bool intrested;
|
||||
std::shared_ptr<Tesses::Framework::Streams::NetworkStream> stream;
|
||||
std::vector<CancelRequest> cancel_requests;
|
||||
std::vector<uint8_t> messages;
|
||||
TorrentBitField has;
|
||||
|
||||
std::vector<std::optional<TorrentBitField>> blocksRequested;
|
||||
std::string ip;
|
||||
uint16_t port;
|
||||
};
|
||||
|
||||
class ActiveTorrent {
|
||||
public:
|
||||
Random rng;
|
||||
ActiveTorrent(TorrentFile file, std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs,Tesses::Framework::Filesystem::VFSPath directory, std::string peer_id);
|
||||
std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs;
|
||||
std::map<std::string,uint64_t> udp_connection_ids;
|
||||
int64_t downloaded;
|
||||
int64_t uploaded;
|
||||
int64_t getLeft();
|
||||
void addPeer(std::string ip, uint16_t port);
|
||||
Tesses::Framework::Serialization::Bencode::BeString info_hash;
|
||||
std::string peer_id;
|
||||
time_t lastTime;
|
||||
bool mustAnnounce();
|
||||
void udpAnounce(Tesses::Framework::Http::Uri uri);
|
||||
void httpAnounce(std::string url);
|
||||
Tesses::Framework::Threading::Mutex mtx;
|
||||
std::vector<std::optional<TorrentBitField>> blocksAquired;
|
||||
TorrentBitField has;
|
||||
TorrentFile file;
|
||||
int64_t torrentSize; //to make it more efficient
|
||||
Tesses::Framework::Filesystem::VFSPath directory;
|
||||
std::vector<std::shared_ptr<TorrentPeer>> connections;
|
||||
std::shared_ptr<ReadWriteAt> torrent_disk;
|
||||
|
||||
size_t pieceSize(size_t piece);
|
||||
std::array<uint8_t,1024> buffer;
|
||||
void process();
|
||||
bool processMessages(std::shared_ptr<TorrentPeer> peer);
|
||||
};
|
||||
class TorrentManager
|
||||
{
|
||||
std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs;
|
||||
Tesses::Framework::Filesystem::VFSPath defaultDirectory;
|
||||
std::vector<std::shared_ptr<ActiveTorrent>> downloading;
|
||||
std::vector<std::shared_ptr<ActiveTorrent>> seeding;
|
||||
std::queue<std::pair<TorrentFile,Tesses::Framework::Filesystem::VFSPath>> torrentQueue;
|
||||
int torrentCount;
|
||||
std::atomic<bool> running=false;
|
||||
public:
|
||||
TorrentManager(std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, Tesses::Framework::Filesystem::VFSPath defaultDirectory, int torrentCount);
|
||||
void AddTorrent(TorrentFile file);
|
||||
void AddTorrent(TorrentFile file, Tesses::Framework::Filesystem::VFSPath directory);
|
||||
void Start();
|
||||
void Stop();
|
||||
~TorrentManager();
|
||||
};
|
||||
std::string GeneratePeerId();
|
||||
|
||||
}
|
||||
46
include/TessesFramework/BitTorrent/TorrentStream.hpp
Normal file
46
include/TessesFramework/BitTorrent/TorrentStream.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "TessesFramework/Http/HttpClient.hpp"
|
||||
#include "TessesFramework/Streams/NetworkStream.hpp"
|
||||
namespace Tesses::Framework::BitTorrent
|
||||
{
|
||||
class TorrentFileEntry {
|
||||
public:
|
||||
TorrentFileEntry();
|
||||
TorrentFileEntry(Tesses::Framework::Filesystem::VFSPath path, int64_t length);
|
||||
Tesses::Framework::Filesystem::VFSPath path;
|
||||
int64_t length=0;
|
||||
};
|
||||
class ReadWriteAt {
|
||||
public:
|
||||
virtual bool ReadBlockAt(uint64_t offset, uint8_t* data, size_t len)=0;
|
||||
virtual void WriteBlockAt(uint64_t offset, const uint8_t* data, size_t len)=0;
|
||||
virtual ~ReadWriteAt();
|
||||
|
||||
};
|
||||
|
||||
class TorrentFileStream : public ReadWriteAt
|
||||
{
|
||||
Tesses::Framework::Threading::Mutex mtx;
|
||||
public:
|
||||
std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs;
|
||||
Tesses::Framework::Filesystem::VFSPath path;
|
||||
uint64_t length;
|
||||
TorrentFileStream(std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, Tesses::Framework::Filesystem::VFSPath path, uint64_t length);
|
||||
bool ReadBlockAt(uint64_t offset, uint8_t* data, size_t len);
|
||||
void WriteBlockAt(uint64_t offset, const uint8_t* data, size_t len);
|
||||
|
||||
};
|
||||
class TorrentDirectoryStream : public ReadWriteAt
|
||||
{
|
||||
|
||||
std::vector<Tesses::Framework::Threading::Mutex> mtxs;
|
||||
public:
|
||||
std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs;
|
||||
Tesses::Framework::Filesystem::VFSPath path;
|
||||
std::vector<TorrentFileEntry> entries;
|
||||
TorrentDirectoryStream(std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, Tesses::Framework::Filesystem::VFSPath path,std::vector<TorrentFileEntry> entries);
|
||||
bool ReadBlockAt(uint64_t offset, uint8_t* data, size_t len);
|
||||
void WriteBlockAt(uint64_t offset, const uint8_t* data, size_t len);
|
||||
};
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include "Threading/Mutex.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace Tesses::Framework
|
||||
{
|
||||
@@ -88,8 +89,10 @@ namespace Tesses::Framework
|
||||
};
|
||||
|
||||
extern EventList<uint64_t> OnItteraton;
|
||||
std::optional<std::string> TF_GetCommandName();
|
||||
void TF_Init();
|
||||
void TF_InitWithConsole();
|
||||
void TF_AllowPortable(std::string argv0);
|
||||
void TF_ConnectToSelf(uint16_t port);
|
||||
void TF_InitEventLoop();
|
||||
void TF_RunEventLoop();
|
||||
@@ -101,6 +104,7 @@ namespace Tesses::Framework
|
||||
void TF_SetConsoleEventsEnabled(bool flag);
|
||||
void TF_InitConsole();
|
||||
void TF_Invoke(std::function<void()> cb);
|
||||
std::string TF_FileSize(uint64_t bytes, bool usesBin=true);
|
||||
|
||||
#if defined(TESSESFRAMEWORK_LOGTOFILE)
|
||||
void TF_Log(std::string dataToLog);
|
||||
|
||||
@@ -39,6 +39,9 @@ namespace Tesses::Framework::Filesystem
|
||||
|
||||
void Chmod(VFSPath path, uint32_t mode);
|
||||
|
||||
void Lock(VFSPath path);
|
||||
void Unlock(VFSPath path);
|
||||
|
||||
};
|
||||
extern std::shared_ptr<LocalFilesystem> LocalFS;
|
||||
}
|
||||
@@ -57,5 +57,7 @@ namespace Tesses::Framework::Filesystem
|
||||
|
||||
void Chmod(VFSPath path, uint32_t mode);
|
||||
|
||||
void Lock(VFSPath path);
|
||||
void Unlock(VFSPath path);
|
||||
};
|
||||
}
|
||||
@@ -41,5 +41,7 @@ namespace Tesses::Framework::Filesystem
|
||||
|
||||
void Chmod(VFSPath path, uint32_t mode);
|
||||
|
||||
void Lock(VFSPath path);
|
||||
void Unlock(VFSPath path);
|
||||
};
|
||||
}
|
||||
@@ -46,6 +46,9 @@ namespace Tesses::Framework::Filesystem
|
||||
|
||||
void Chmod(VFSPath path, uint32_t mode);
|
||||
void Close();
|
||||
|
||||
void Lock(VFSPath path);
|
||||
void Unlock(VFSPath path);
|
||||
~TempFS();
|
||||
};
|
||||
}
|
||||
@@ -153,6 +153,9 @@ namespace Tesses::Framework::Filesystem
|
||||
|
||||
virtual void Chmod(VFSPath path, uint32_t mode);
|
||||
|
||||
virtual void Lock(VFSPath path);
|
||||
virtual void Unlock(VFSPath path);
|
||||
|
||||
virtual ~VFS();
|
||||
|
||||
virtual void Close();
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
#undef DeleteFile
|
||||
#undef MoveFile
|
||||
#undef MoveDirectory
|
||||
|
||||
/*
|
||||
Just in case
|
||||
*/
|
||||
#undef Lock
|
||||
#undef Unlock
|
||||
@@ -145,6 +145,10 @@ struct CaseInsensitiveLess {
|
||||
public:
|
||||
static char NibbleToHex(uint8_t nibble);
|
||||
static uint8_t HexToNibble(char c);
|
||||
static std::string BytesToHex(const std::vector<uint8_t>& data);
|
||||
static void BytesToHex(std::string& text,const std::vector<uint8_t>& data);
|
||||
static std::vector<uint8_t> HexToBytes(const std::string& text);
|
||||
static void HexToBytes(std::vector<uint8_t>& data,const std::string& text);
|
||||
static std::string MimeType(std::filesystem::path p);
|
||||
static bool Invalid(char c);
|
||||
static std::string Sanitise(std::string text);
|
||||
|
||||
15
include/TessesFramework/Random.hpp
Normal file
15
include/TessesFramework/Random.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "Common.hpp"
|
||||
|
||||
namespace Tesses::Framework {
|
||||
class Random {
|
||||
uint64_t num;
|
||||
public:
|
||||
Random();
|
||||
Random(uint64_t seed);
|
||||
uint64_t Next();
|
||||
uint32_t Next(uint32_t max);
|
||||
int32_t Next(int32_t min,int32_t max);
|
||||
uint8_t NextByte();
|
||||
};
|
||||
}
|
||||
59
include/TessesFramework/Serialization/Bencode.hpp
Normal file
59
include/TessesFramework/Serialization/Bencode.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <string>
|
||||
#include "../Streams/Stream.hpp"
|
||||
#include "../TextStreams/TextWriter.hpp"
|
||||
#include "Json.hpp"
|
||||
namespace Tesses::Framework::Serialization::Bencode {
|
||||
class BeArray;
|
||||
class BeDictionary;
|
||||
class BeString;
|
||||
using BeUndefined = std::monostate;
|
||||
|
||||
using BeToken = std::variant<BeUndefined,BeArray,BeDictionary,BeString,int64_t>;
|
||||
|
||||
class BeArray {
|
||||
public:
|
||||
std::vector<BeToken> tokens;
|
||||
};
|
||||
|
||||
class BeDictionary {
|
||||
public:
|
||||
std::vector<std::pair<BeString,BeToken>> tokens;
|
||||
BeToken GetValue(BeString key) const;
|
||||
void SetValue(BeString key, BeToken value);
|
||||
};
|
||||
class BeString {
|
||||
public:
|
||||
BeString();
|
||||
BeString(const std::string& text);
|
||||
BeString(const char* text);
|
||||
BeString(const std::vector<uint8_t>& data);
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
operator std::string() const;
|
||||
};
|
||||
bool operator==(const BeString& lStr, const BeString& rStr);
|
||||
bool operator==(const BeString& lStr, const std::string& rStr);
|
||||
bool operator==(const std::string& lStr, const BeString& rStr);
|
||||
bool operator==(const BeString& lStr, const char* rStr);
|
||||
bool operator==(const char* lStr, const BeString& rStr);
|
||||
|
||||
bool operator!=(const BeString& lStr, const BeString& rStr);
|
||||
bool operator!=(const BeString& lStr, const std::string& rStr);
|
||||
bool operator!=(const std::string& lStr, const BeString& rStr);
|
||||
bool operator!=(const BeString& lStr, const char* rStr);
|
||||
bool operator!=(const char* lStr, const BeString& rStr);
|
||||
|
||||
class Bencode {
|
||||
public:
|
||||
static void Save(std::shared_ptr<Tesses::Framework::Streams::Stream> strm,const BeToken& value);
|
||||
static BeToken Load(std::shared_ptr<Tesses::Framework::Streams::Stream> strm);
|
||||
//This cannot be converted back to torrent, this may (probably will) be out of order
|
||||
static Json::JToken ToJson(const BeToken& tkn);
|
||||
//This may (probably will) be out of order
|
||||
static void Print(std::shared_ptr<Tesses::Framework::TextStreams::TextWriter> writer, BeToken tkn);
|
||||
};
|
||||
}
|
||||
@@ -9,9 +9,7 @@ namespace Tesses::Framework::Serialization::Json
|
||||
{
|
||||
class JArray;
|
||||
class JObject;
|
||||
class JUndefined {
|
||||
public:
|
||||
};
|
||||
using JUndefined = std::monostate;
|
||||
|
||||
using JToken = std::variant<JUndefined,std::nullptr_t,bool,int64_t,double,std::string, JArray, JObject>;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Tesses::Framework::Streams
|
||||
bool success;
|
||||
bool endOfStream;
|
||||
public:
|
||||
bool DataAvailable(int timeout=0);
|
||||
bool EndOfStream();
|
||||
bool CanRead();
|
||||
bool CanWrite();
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
#include "HiddenField.hpp"
|
||||
#include "Serialization/Json.hpp"
|
||||
#include "Serialization/SQLite.hpp"
|
||||
#include "Serialization/Bencode.hpp"
|
||||
#include "Platform/Environment.hpp"
|
||||
#include "Platform/Process.hpp"
|
||||
#include "Serialization/BitConverter.hpp"
|
||||
#include "Args.hpp"
|
||||
#include "Args.hpp"
|
||||
#include "BitTorrent/TorrentManager.hpp"
|
||||
#include "Random.hpp"
|
||||
379
src/BitTorrent/TorrentFile.cpp
Normal file
379
src/BitTorrent/TorrentFile.cpp
Normal file
@@ -0,0 +1,379 @@
|
||||
#include "TessesFramework/BitTorrent/TorrentFile.hpp"
|
||||
#include "TessesFramework/Streams/MemoryStream.hpp"
|
||||
#include "TessesFramework/Crypto/Crypto.hpp"
|
||||
#include "TessesFramework/Http/HttpUtils.hpp"
|
||||
namespace Tesses::Framework::BitTorrent
|
||||
{
|
||||
|
||||
TorrentFileInfo::TorrentFileInfo()
|
||||
{
|
||||
this->fileListOrLength = 0;
|
||||
}
|
||||
TorrentFileInfo::TorrentFileInfo(const Serialization::Bencode::BeDictionary& tkn)
|
||||
{
|
||||
Load(tkn);
|
||||
}
|
||||
int64_t TorrentFileInfo::GetTorrentFileSize()
|
||||
{
|
||||
if(std::holds_alternative<int64_t>(this->fileListOrLength))
|
||||
return std::get<int64_t>(fileListOrLength);
|
||||
else if(std::holds_alternative<std::vector<TorrentFileEntry>>(this->fileListOrLength)) {
|
||||
auto& files= std::get<std::vector<TorrentFileEntry>>(this->fileListOrLength);
|
||||
int64_t no=0;
|
||||
for(auto itm : files)
|
||||
{
|
||||
no += itm.length;
|
||||
}
|
||||
return no;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Serialization::Bencode::BeDictionary& TorrentFileInfo::GenerateInfoDictionary()
|
||||
{
|
||||
this->info = {};
|
||||
this->info.tokens.emplace_back("name",this->name);
|
||||
this->info.tokens.emplace_back("piece length",this->piece_length);
|
||||
this->info.tokens.emplace_back("pieces",this->pieces);
|
||||
if(this->isPrivate)
|
||||
this->info.tokens.emplace_back("private",1);
|
||||
|
||||
if(std::holds_alternative<int64_t>(this->fileListOrLength))
|
||||
{
|
||||
this->info.tokens.emplace_back("length",std::get<int64_t>(this->fileListOrLength));
|
||||
}
|
||||
else if(std::holds_alternative<std::vector<TorrentFileEntry>>(this->fileListOrLength))
|
||||
{
|
||||
Serialization::Bencode::BeArray a;
|
||||
for(auto& item : std::get<std::vector<TorrentFileEntry>>(this->fileListOrLength))
|
||||
{
|
||||
Serialization::Bencode::BeDictionary dict;
|
||||
dict.tokens.emplace_back("length",item.length);
|
||||
Serialization::Bencode::BeArray path;
|
||||
for(auto& p : item.path.path)
|
||||
path.tokens.push_back(p);
|
||||
dict.tokens.emplace_back("path",path);
|
||||
|
||||
a.tokens.push_back(dict);
|
||||
}
|
||||
this->info.tokens.emplace_back("files", a);
|
||||
}
|
||||
return this->info;
|
||||
}
|
||||
Serialization::Bencode::BeDictionary& TorrentFileInfo::GetInfoDictionary()
|
||||
{
|
||||
return this->info;
|
||||
}
|
||||
|
||||
void TorrentFileInfo::Load(const Serialization::Bencode::BeDictionary& dict)
|
||||
{
|
||||
this->info = dict;
|
||||
auto o=dict.GetValue("name");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeString>(o))
|
||||
this->name = std::get<Serialization::Bencode::BeString>(o);
|
||||
else
|
||||
this->name = {};
|
||||
|
||||
o=dict.GetValue("piece length");
|
||||
if(std::holds_alternative<int64_t>(o))
|
||||
this->piece_length = std::get<int64_t>(o);
|
||||
else
|
||||
this->piece_length = DEFAULT_PIECE_LENGTH;
|
||||
|
||||
o=dict.GetValue("private");
|
||||
if(std::holds_alternative<int64_t>(o))
|
||||
this->isPrivate = std::get<int64_t>(o);
|
||||
else
|
||||
this->isPrivate = 0;
|
||||
|
||||
o=dict.GetValue("pieces");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeString>(o))
|
||||
this->pieces = std::get<Serialization::Bencode::BeString>(o);
|
||||
else
|
||||
this->pieces = {};
|
||||
|
||||
|
||||
|
||||
o=dict.GetValue("files");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeArray>(o))
|
||||
{
|
||||
std::vector<TorrentFileEntry> ents;
|
||||
|
||||
for(auto& item : std::get<Serialization::Bencode::BeArray>(o).tokens)
|
||||
{
|
||||
if(std::holds_alternative<Serialization::Bencode::BeDictionary>(item))
|
||||
{
|
||||
TorrentFileEntry fe;
|
||||
fe.path.relative=true;
|
||||
|
||||
auto& d2=std::get<Serialization::Bencode::BeDictionary>(item);
|
||||
auto o2=d2.GetValue("length");
|
||||
if(std::holds_alternative<int64_t>(o2))
|
||||
{
|
||||
fe.length = std::get<int64_t>(o2);
|
||||
}
|
||||
o2=d2.GetValue("path");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeArray>(o2))
|
||||
{
|
||||
auto& arr=std::get<Serialization::Bencode::BeArray>(o2);
|
||||
for(auto& itm : arr.tokens)
|
||||
{
|
||||
if(std::holds_alternative<Serialization::Bencode::BeString>(itm))
|
||||
{
|
||||
fe.path.path.push_back(std::get<Serialization::Bencode::BeString>(itm));
|
||||
}
|
||||
}
|
||||
}
|
||||
ents.push_back(fe);
|
||||
}
|
||||
}
|
||||
this->fileListOrLength = ents;
|
||||
}
|
||||
else {
|
||||
o = dict.GetValue("length");
|
||||
if(std::holds_alternative<int64_t>(o))
|
||||
{
|
||||
this->fileListOrLength = std::get<int64_t>(o);
|
||||
}
|
||||
else {
|
||||
this->fileListOrLength = (int64_t)0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Serialization::Bencode::BeString TorrentFileInfo::GetInfoHash()
|
||||
{
|
||||
auto strm = std::make_shared<Streams::MemoryStream>(true);
|
||||
Serialization::Bencode::Bencode::Save(strm,this->info);
|
||||
strm->Seek(0L,Streams::SeekOrigin::Begin);
|
||||
return Crypto::Sha1::ComputeHash(strm);
|
||||
}
|
||||
|
||||
|
||||
TorrentFile::TorrentFile()
|
||||
{
|
||||
this->created_by = "TessesFrameworkTorrent";
|
||||
this->creation_date = (int64_t)time(NULL);
|
||||
}
|
||||
TorrentFile::TorrentFile(const Serialization::Bencode::BeDictionary& tkn)
|
||||
{
|
||||
auto o=tkn.GetValue("info");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeDictionary>(o))
|
||||
{
|
||||
this->info.Load(std::get<Serialization::Bencode::BeDictionary>(o));
|
||||
}
|
||||
o=tkn.GetValue("announce");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeString>(o))
|
||||
{
|
||||
this->announce = std::get<Serialization::Bencode::BeString>(o);
|
||||
}
|
||||
o=tkn.GetValue("announce-list");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeArray>(o))
|
||||
{
|
||||
auto& ls = std::get<Serialization::Bencode::BeArray>(o);
|
||||
for(auto& item : ls.tokens)
|
||||
{
|
||||
if(std::holds_alternative<Serialization::Bencode::BeArray>(item))
|
||||
{
|
||||
auto ls2 = std::get<Serialization::Bencode::BeArray>(item);
|
||||
auto item2=ls2.tokens.at(0);
|
||||
if(std::holds_alternative<Serialization::Bencode::BeString>(item2))
|
||||
{
|
||||
this->announce_list.push_back(std::get<Serialization::Bencode::BeString>(item2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
o=tkn.GetValue("creation date");
|
||||
if(std::holds_alternative<int64_t>(o))
|
||||
{
|
||||
this->creation_date = std::get<int64_t>(o);
|
||||
}
|
||||
o=tkn.GetValue("comment");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeString>(o))
|
||||
{
|
||||
this->comment = std::get<Serialization::Bencode::BeString>(o);
|
||||
}
|
||||
o=tkn.GetValue("created by");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeString>(o))
|
||||
{
|
||||
this->created_by = std::get<Serialization::Bencode::BeString>(o);
|
||||
}
|
||||
o=tkn.GetValue("url-list");
|
||||
if(std::holds_alternative<Serialization::Bencode::BeArray>(o))
|
||||
{
|
||||
auto& li =std::get<Serialization::Bencode::BeArray>(o);
|
||||
for(auto& itm : li.tokens)
|
||||
{
|
||||
if(std::holds_alternative<Serialization::Bencode::BeString>(itm))
|
||||
this->url_list.push_back(std::get<Serialization::Bencode::BeString>(itm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Serialization::Bencode::BeDictionary TorrentFile::ToDictionary()
|
||||
{
|
||||
Serialization::Bencode::BeDictionary dict;
|
||||
dict.SetValue("info",this->info.GetInfoDictionary());
|
||||
dict.SetValue("announce",this->announce);
|
||||
if(!this->announce_list.empty())
|
||||
{
|
||||
Serialization::Bencode::BeArray a;
|
||||
for(auto& item : this->announce_list)
|
||||
{
|
||||
Serialization::Bencode::BeArray a2;
|
||||
a2.tokens.push_back(item);
|
||||
a.tokens.push_back(a2);
|
||||
}
|
||||
dict.SetValue("announce-list",a);
|
||||
}
|
||||
if(!this->url_list.empty())
|
||||
{
|
||||
Serialization::Bencode::BeArray ls;
|
||||
for(auto& itm : this->url_list)
|
||||
{
|
||||
ls.tokens.push_back(itm);
|
||||
}
|
||||
dict.SetValue("url-list",ls);
|
||||
}
|
||||
|
||||
dict.SetValue("created by", this->created_by);
|
||||
dict.SetValue("creation date", this->creation_date);
|
||||
dict.SetValue("comment",this->comment);
|
||||
return dict;
|
||||
}
|
||||
|
||||
void TorrentFile::Print(std::shared_ptr<TextStreams::TextWriter> writer)
|
||||
{
|
||||
writer->WriteLine("Announce: " + (std::string)this->announce);
|
||||
writer->WriteLine("Announce List:");
|
||||
for(auto& item : this->announce_list)
|
||||
{
|
||||
writer->WriteLine("\t" + (std::string)item);
|
||||
}
|
||||
writer->WriteLine("Comment: " + (std::string)this->comment);
|
||||
writer->WriteLine("Created By: " + (std::string)this->created_by);
|
||||
Date::DateTime dt(this->creation_date);
|
||||
dt.SetToLocal();
|
||||
writer->WriteLine("Creation Date: " + dt.ToString());
|
||||
writer->WriteLine("Info Hash: " + Http::HttpUtils::BytesToHex(this->info.GetInfoHash().data));
|
||||
writer->WriteLine("Info:");
|
||||
writer->WriteLine("\tName: " + (std::string)this->info.name);
|
||||
writer->WriteLine(this->info.isPrivate ? "\tPrivate: true" : "\tPrivate: false");
|
||||
writer->WriteLine("\tPiece Length: " + TF_FileSize(this->info.piece_length));
|
||||
if(std::holds_alternative<int64_t>(this->info.fileListOrLength))
|
||||
{
|
||||
writer->WriteLine("\tIs Single File: true");
|
||||
writer->WriteLine("\tFile length: " + TF_FileSize((uint64_t)std::get<int64_t>(this->info.fileListOrLength)));
|
||||
}
|
||||
else if(std::holds_alternative<std::vector<TorrentFileEntry>>(this->info.fileListOrLength))
|
||||
{
|
||||
writer->WriteLine("\tIs Single File: false");
|
||||
writer->WriteLine("\tFiles:");
|
||||
auto& files = std::get<std::vector<TorrentFileEntry>>(this->info.fileListOrLength);
|
||||
for(auto& file : files)
|
||||
{
|
||||
writer->WriteLine("\t\tPath: " + file.path.ToString());
|
||||
writer->WriteLine("\t\tLength: " + TF_FileSize(file.length));
|
||||
writer->WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ReadWriteAt> TorrentFileInfo::GetStreamFromFilesystem(std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, const Tesses::Framework::Filesystem::VFSPath& path)
|
||||
{
|
||||
if(std::holds_alternative<std::vector<TorrentFileEntry>>(this->fileListOrLength))
|
||||
{
|
||||
return std::make_shared<TorrentDirectoryStream>(vfs,path / this->name, std::get<std::vector<TorrentFileEntry>>(this->fileListOrLength));
|
||||
}
|
||||
else if(std::holds_alternative<int64_t>(this->fileListOrLength)) {
|
||||
return std::make_shared<TorrentFileStream>(vfs,path / this->name, (uint64_t)std::get<int64_t>(this->fileListOrLength));
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
}
|
||||
|
||||
static void ParsePieces(TorrentFileInfo* fi,std::shared_ptr<ReadWriteAt> rwa, int64_t flength)
|
||||
{
|
||||
int64_t pieces = fi->pieces.data.size()/20;
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize((size_t)fi->piece_length);
|
||||
size_t lastPieceSize = (size_t)(flength % (int64_t)buffer.size());
|
||||
for(int64_t i = 0; i < pieces; i++)
|
||||
{
|
||||
size_t len = buffer.size();
|
||||
if(i == pieces-1 && lastPieceSize != 0) len = std::min(len,lastPieceSize);
|
||||
rwa->ReadBlockAt(i*buffer.size(), buffer.data(), len);
|
||||
auto hsh=Crypto::Sha1::ComputeHash(buffer.data(),len);
|
||||
std::copy(hsh.begin(),hsh.end(),fi->pieces.data.begin()+(i*20));
|
||||
}
|
||||
}
|
||||
void TorrentFileInfo::CreateFromFilesystem(std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, const Tesses::Framework::Filesystem::VFSPath& path, bool isPrivate, int64_t pieceLength)
|
||||
{
|
||||
this->isPrivate=isPrivate;
|
||||
this->piece_length = pieceLength;
|
||||
this->pieces.data.clear();
|
||||
this->name = path.GetFileName();
|
||||
int64_t len=0;
|
||||
if(vfs->FileExists(path))
|
||||
{
|
||||
auto strm = vfs->OpenFile(path,"rb");
|
||||
len = strm->GetLength();
|
||||
this->fileListOrLength= len;
|
||||
int64_t pieces = len / piece_length;
|
||||
if((len % piece_length) != 0) pieces++;
|
||||
|
||||
this->pieces.data.resize(pieces*20);
|
||||
}
|
||||
else if(vfs->DirectoryExists(path))
|
||||
{
|
||||
std::vector<TorrentFileEntry> ents;
|
||||
std::function<void(Tesses::Framework::Filesystem::VFSPath,Tesses::Framework::Filesystem::VFSPath)> crawl;
|
||||
crawl= [&](Tesses::Framework::Filesystem::VFSPath inFS, Tesses::Framework::Filesystem::VFSPath inTorrent)->void{
|
||||
for(auto ent : vfs->EnumeratePaths(inFS))
|
||||
{
|
||||
if(vfs->FileExists(ent))
|
||||
{
|
||||
auto strm = vfs->OpenFile(ent,"rb");
|
||||
auto flen = strm->GetLength();
|
||||
ents.emplace_back(inTorrent / ent.GetFileName(),flen);
|
||||
|
||||
len += flen;
|
||||
}
|
||||
else if(vfs->DirectoryExists(ent))
|
||||
{
|
||||
crawl(ent,inTorrent / ent.GetFileName());
|
||||
}
|
||||
}
|
||||
};
|
||||
Tesses::Framework::Filesystem::VFSPath p2;
|
||||
p2.relative=true;
|
||||
crawl(path, p2);
|
||||
|
||||
this->fileListOrLength = ents;
|
||||
int64_t pieces = len / piece_length;
|
||||
if((len % piece_length) != 0) pieces++;
|
||||
|
||||
this->pieces.data.resize(pieces*20);
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("File or directory does not exist");
|
||||
}
|
||||
ParsePieces(this,this->GetStreamFromFilesystem(vfs,path.GetParent()),len);
|
||||
|
||||
|
||||
this->GenerateInfoDictionary();
|
||||
}
|
||||
|
||||
void TorrentFile::CreateTorrent(std::shared_ptr<Tesses::Framework::Streams::Stream> torrent_file_stream,const std::vector<Serialization::Bencode::BeString>& trackers,const std::vector<Serialization::Bencode::BeString>& webseeds, std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, const Tesses::Framework::Filesystem::VFSPath& path, bool isPrivate, int64_t pieceLength, Serialization::Bencode::BeString comment, Serialization::Bencode::BeString created_by)
|
||||
{
|
||||
TorrentFile file;
|
||||
file.announce = trackers.at(0);
|
||||
file.announce_list = trackers;
|
||||
file.url_list = webseeds;
|
||||
file.comment = comment;
|
||||
file.created_by = created_by;
|
||||
file.info.CreateFromFilesystem(vfs,path,isPrivate,pieceLength);
|
||||
auto dict=file.ToDictionary();
|
||||
Serialization::Bencode::Bencode::Save(torrent_file_stream,dict);
|
||||
}
|
||||
}
|
||||
657
src/BitTorrent/TorrentManager.cpp
Normal file
657
src/BitTorrent/TorrentManager.cpp
Normal file
@@ -0,0 +1,657 @@
|
||||
#include "TessesFramework/BitTorrent/TorrentManager.hpp"
|
||||
#include "TessesFramework/Serialization/BitConverter.hpp"
|
||||
#include "TessesFramework/Crypto/Crypto.hpp"
|
||||
#include "TessesFramework/Random.hpp"
|
||||
|
||||
namespace Tesses::Framework::BitTorrent
|
||||
{
|
||||
|
||||
TorrentBitField::TorrentBitField()
|
||||
{
|
||||
|
||||
}
|
||||
TorrentBitField::TorrentBitField(size_t len)
|
||||
{
|
||||
resize(len);
|
||||
}
|
||||
size_t TorrentBitField::size()
|
||||
{
|
||||
return this->no_bits;
|
||||
}
|
||||
void TorrentBitField::resize(size_t len)
|
||||
{
|
||||
this->no_bits = len;
|
||||
size_t no = len / 8;
|
||||
if((len % 8) != 0)
|
||||
no++;
|
||||
this->bits.resize(no);
|
||||
}
|
||||
bool TorrentBitField::get(size_t bit)
|
||||
{
|
||||
size_t byte = bit / 8;
|
||||
size_t byteBit = 7-bit % 8;
|
||||
|
||||
return (this->bits.at(byte) >> byteBit) & 1;
|
||||
}
|
||||
bool TorrentBitField::allone()
|
||||
{
|
||||
|
||||
for(size_t i = 0; i < this->no_bits; i++)
|
||||
{
|
||||
size_t byte = i / 8;
|
||||
size_t byteBit = i % 8;
|
||||
if(((this->bits.at(byte) >> byteBit) & 1) == 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void TorrentBitField::set(size_t bit, bool val)
|
||||
{
|
||||
size_t byte = bit / 8;
|
||||
size_t byteBit = 7-bit % 8;
|
||||
|
||||
|
||||
if(val)
|
||||
{
|
||||
this->bits.at(byte) |= 1 << byteBit;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->bits.at(byte) &= ~(1 << byteBit);
|
||||
}
|
||||
}
|
||||
void TorrentBitField::zero()
|
||||
{
|
||||
std::fill(this->bits.begin(),this->bits.end(),0);
|
||||
}
|
||||
std::vector<uint8_t>& TorrentBitField::data()
|
||||
{
|
||||
return this->bits;
|
||||
}
|
||||
|
||||
|
||||
void TorrentManager::Start()
|
||||
{
|
||||
|
||||
}
|
||||
bool ActiveTorrent::mustAnnounce()
|
||||
{
|
||||
time_t curTime = time(NULL);
|
||||
if(curTime < lastTime || (lastTime + 1800) <= curTime)
|
||||
{
|
||||
std::cout << "Must anounce" << std::endl;
|
||||
this->lastTime = curTime;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::string bt_handshake_str = "BitTorrent protocol";
|
||||
void ActiveTorrent::addPeer(std::string ip, uint16_t port)
|
||||
{
|
||||
std::cout << "Got peer: " << ip << ":" << port << std::endl;
|
||||
if(port == 0) return;
|
||||
for(auto conn : this->connections)
|
||||
{
|
||||
if(conn->ip == ip && conn->port == port) return;
|
||||
}
|
||||
auto peer = std::make_shared<TorrentPeer>();
|
||||
peer->ip = ip;
|
||||
peer->port = port;
|
||||
peer->isChoked=true;
|
||||
peer->isChokingMe=true;
|
||||
peer->intrested=false;
|
||||
peer->has.resize(this->has.size());
|
||||
peer->blocksRequested.resize(this->has.size());
|
||||
peer->stream = std::make_shared<Tesses::Framework::Streams::NetworkStream>(ip,port,false,false,false);
|
||||
|
||||
std::vector<uint8_t> handshake;
|
||||
handshake.resize(49+bt_handshake_str.size());
|
||||
handshake[0] = (uint8_t)bt_handshake_str.size();
|
||||
std::copy(bt_handshake_str.begin(),bt_handshake_str.end(),handshake.begin()+1);
|
||||
auto off = bt_handshake_str.size()+1;
|
||||
for(size_t i = 0; i < 8; i++)
|
||||
handshake[off+i] = 0;
|
||||
std::copy(this->info_hash.data.begin(),this->info_hash.data.end(),handshake.begin()+off+8);
|
||||
std::copy(this->peer_id.begin(),this->peer_id.end(),handshake.begin()+off+28);
|
||||
peer->stream->WriteBlock(handshake.data(),handshake.size());
|
||||
handshake.resize(5+this->has.data().size());
|
||||
Serialization::BitConverter::FromUint32BE(handshake[0],this->has.data().size());
|
||||
handshake[4] = 5;
|
||||
|
||||
std::copy(this->has.data().begin(),this->has.data().end(),handshake.begin()+5);
|
||||
peer->stream->WriteBlock(handshake.data(),handshake.size());
|
||||
|
||||
Serialization::BitConverter::FromUint32BE(handshake[0],1);
|
||||
handshake[4] = 2;
|
||||
|
||||
peer->stream->WriteBlock(handshake.data(),5);
|
||||
std::cout << "Handshake send complete for: " << ip << ":" << port << std::endl;
|
||||
|
||||
|
||||
this->connections.push_back(peer);
|
||||
}
|
||||
void ActiveTorrent::udpAnounce(Tesses::Framework::Http::Uri uri)
|
||||
{
|
||||
std::string connUrl = uri.HostPort();
|
||||
std::cout << "udp anouncing via: " << connUrl << std::endl;
|
||||
Streams::NetworkStream strm(uri.host,uri.port,true,false,false);
|
||||
|
||||
auto trans_id = rng.Next(0xFFFFFFFF);
|
||||
if(this->udp_connection_ids.count(connUrl) == 0)
|
||||
{
|
||||
|
||||
|
||||
Serialization::BitConverter::FromUint64BE(this->buffer[0],0x41727101980);
|
||||
Serialization::BitConverter::FromUint32BE(this->buffer[8],0);
|
||||
Serialization::BitConverter::FromUint32BE(this->buffer[12],trans_id);
|
||||
|
||||
std::cout << "writing ze data" << std::endl;
|
||||
strm.Write(this->buffer.data(),16);
|
||||
std::cout << "reading ze data" << std::endl;
|
||||
if(!strm.DataAvailable(5000)) {
|
||||
std::cout << "timeout" << std::endl;
|
||||
return;
|
||||
}
|
||||
if(strm.Read(this->buffer.data(),this->buffer.size()) != 16) return;
|
||||
std::cout << "read ze data" << std::endl;
|
||||
if(Serialization::BitConverter::ToUint32BE(this->buffer[0]) == 0 && Serialization::BitConverter::ToUint32BE(this->buffer[4]) == trans_id)
|
||||
{
|
||||
this->udp_connection_ids[connUrl] = Serialization::BitConverter::ToUint64BE(this->buffer[8]);
|
||||
|
||||
trans_id = rng.Next(0xFFFFFFFF);
|
||||
} else return;
|
||||
}
|
||||
Serialization::BitConverter::FromUint64BE(this->buffer[0],this->udp_connection_ids[connUrl]);
|
||||
Serialization::BitConverter::FromUint32BE(this->buffer[8],1);
|
||||
Serialization::BitConverter::FromUint32BE(this->buffer[12],trans_id);
|
||||
std::copy(this->info_hash.data.begin(),this->info_hash.data.end(),this->buffer.begin()+16);
|
||||
|
||||
std::copy(this->peer_id.begin(),this->peer_id.end(),this->buffer.begin()+36);
|
||||
Serialization::BitConverter::FromUint64BE(this->buffer[56], this->downloaded);
|
||||
Serialization::BitConverter::FromUint64BE(this->buffer[64], this->getLeft());
|
||||
Serialization::BitConverter::FromUint64BE(this->buffer[72], this->uploaded);
|
||||
Serialization::BitConverter::FromUint32BE(this->buffer[80],0);
|
||||
Serialization::BitConverter::FromUint32BE(this->buffer[84],0);
|
||||
Serialization::BitConverter::FromUint32BE(this->buffer[88],0);
|
||||
Serialization::BitConverter::FromUint32BE(this->buffer[92],1); //peers
|
||||
Serialization::BitConverter::FromUint16BE(this->buffer[96],0);
|
||||
strm.Write(this->buffer.data(),98);
|
||||
if(!strm.DataAvailable(5000)) {
|
||||
std::cout << "timeout" << std::endl;
|
||||
return;
|
||||
}
|
||||
size_t read = strm.Read(this->buffer.data(),this->buffer.size());
|
||||
if(read < 20) return;
|
||||
if(Serialization::BitConverter::ToUint32BE(this->buffer[0]) == 1 && Serialization::BitConverter::ToUint32BE(this->buffer[4]) == trans_id)
|
||||
{
|
||||
std::cout << "Found " << ((read - 20) / 6) << " peers" << std::endl;
|
||||
for(size_t peerIdx = 20; peerIdx + 6 <= read; peerIdx+=6)
|
||||
{
|
||||
std::string ip = std::to_string((uint32_t)this->buffer[peerIdx]);
|
||||
ip += "." + std::to_string((uint32_t)this->buffer[peerIdx+1]);
|
||||
ip += "." + std::to_string((uint32_t)this->buffer[peerIdx+2]);
|
||||
ip += "." + std::to_string((uint32_t)this->buffer[peerIdx+3]);
|
||||
|
||||
uint16_t port = Tesses::Framework::Serialization::BitConverter::ToUint16BE(this->buffer[peerIdx+4]);
|
||||
|
||||
addPeer(ip,port);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
int64_t ActiveTorrent::getLeft()
|
||||
{
|
||||
int64_t left = 0;
|
||||
size_t normalPieceSize = pieceSize(0);
|
||||
size_t lastPieceSize = pieceSize(this->has.size()-1);
|
||||
for(size_t i = 0;i < this->has.size(); i++)
|
||||
{
|
||||
if(!this->has.get(i))
|
||||
if(i == this->has.size() - 1)
|
||||
left += lastPieceSize;
|
||||
else
|
||||
left += normalPieceSize;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
void ActiveTorrent::httpAnounce(std::string url)
|
||||
{
|
||||
std::string newUrl = url + "?info_hash=" + Http::HttpUtils::UrlEncode((std::string)this->info_hash) + "&peer_id=" + Http::HttpUtils::UrlEncode(this->peer_id) + "&uploaded=" + std::to_string(this->uploaded) + "&downloaded=" + std::to_string(this->downloaded) + "&left=" + std::to_string(getLeft()) + "&numwant=10&compact=1&no_peer_id=1&port=0";
|
||||
Http::HttpRequest req;
|
||||
req.url= newUrl;
|
||||
req.method = "GET";
|
||||
Http::HttpResponse resp(req);
|
||||
if(resp.statusCode == 200)
|
||||
{
|
||||
auto bencode=Tesses::Framework::Serialization::Bencode::Bencode::Load(resp.ReadAsStream());
|
||||
if(std::holds_alternative<Tesses::Framework::Serialization::Bencode::BeDictionary>(bencode))
|
||||
{
|
||||
auto& dict = std::get<Tesses::Framework::Serialization::Bencode::BeDictionary>(bencode);
|
||||
auto peers = dict.GetValue("peers");
|
||||
if(std::holds_alternative<Tesses::Framework::Serialization::Bencode::BeString>(peers))
|
||||
{
|
||||
//this is compact
|
||||
auto compactPeers=std::get<Tesses::Framework::Serialization::Bencode::BeString>(peers);
|
||||
for(size_t peerIdx = 0; peerIdx + 6 <= compactPeers.data.size(); peerIdx+=6)
|
||||
{
|
||||
std::string ip = std::to_string((uint32_t)compactPeers.data[peerIdx]);
|
||||
ip += "." + std::to_string((uint32_t)compactPeers.data[peerIdx+1]);
|
||||
ip += "." + std::to_string((uint32_t)compactPeers.data[peerIdx+2]);
|
||||
ip += "." + std::to_string((uint32_t)compactPeers.data[peerIdx+3]);
|
||||
|
||||
uint16_t port = Tesses::Framework::Serialization::BitConverter::ToUint16BE(compactPeers.data[peerIdx+4]);
|
||||
|
||||
addPeer(ip,port);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto normalPeers = std::get<Tesses::Framework::Serialization::Bencode::BeArray>(peers);
|
||||
for(auto& item : normalPeers.tokens)
|
||||
{
|
||||
if(std::holds_alternative<Tesses::Framework::Serialization::Bencode::BeDictionary>(item))
|
||||
{
|
||||
auto& dict2=std::get<Tesses::Framework::Serialization::Bencode::BeDictionary>(item);
|
||||
auto ip = dict2.GetValue("ip");
|
||||
auto port = dict2.GetValue("port");
|
||||
if(std::holds_alternative<Tesses::Framework::Serialization::Bencode::BeString>(ip) && std::holds_alternative<int64_t>(port))
|
||||
{
|
||||
addPeer((std::string)std::get<Tesses::Framework::Serialization::Bencode::BeString>(ip),(uint16_t)(uint64_t)std::get<int64_t>(port));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveTorrent::process()
|
||||
{
|
||||
if(mustAnnounce())
|
||||
{
|
||||
std::vector<Tesses::Framework::Serialization::Bencode::BeString> announces;
|
||||
if(this->file.announce_list.empty())
|
||||
announces.push_back(this->file.announce);
|
||||
announces.insert(announces.end(),this->file.announce_list.begin(),this->file.announce_list.end());
|
||||
|
||||
for(auto& a : announces)
|
||||
{
|
||||
Tesses::Framework::Http::Uri uri;
|
||||
if(Tesses::Framework::Http::Uri::TryParse(a,uri))
|
||||
{
|
||||
if(uri.scheme == "udp:")
|
||||
{
|
||||
udpAnounce(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpAnounce(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "end of anounces" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
for(auto item : this->connections)
|
||||
{
|
||||
if(!item->isChokingMe)
|
||||
{
|
||||
for(size_t piece = 0; piece < this->has.size(); piece++)
|
||||
{
|
||||
if(!this->has.get(piece) && item->has.get(piece))
|
||||
{
|
||||
bool hasFound=false;
|
||||
//find this piece in bitfields
|
||||
//find a block we don't have and that we never requested
|
||||
//we need to ensure that both the peer and I have the bitfields for the piece
|
||||
|
||||
size_t _pieceSize = pieceSize(piece);
|
||||
auto blockCount = _pieceSize / 16384;
|
||||
if((_pieceSize % 16384) != 0) blockCount++;
|
||||
if(!this->blocksAquired[piece] )
|
||||
{
|
||||
this->blocksAquired[piece] =TorrentBitField(blockCount);
|
||||
}
|
||||
if(!item->blocksRequested[piece] )
|
||||
{
|
||||
item->blocksRequested[piece] =TorrentBitField(blockCount);
|
||||
}
|
||||
|
||||
for(size_t block = 0; block < blockCount; block++)
|
||||
{
|
||||
if(!this->blocksAquired[piece]->get(block) && !item->blocksRequested[piece]->get(block))
|
||||
{
|
||||
item->blocksRequested[piece]->set(block,true);
|
||||
hasFound=true;
|
||||
|
||||
std::vector<uint8_t> msg((size_t)17);
|
||||
Serialization::BitConverter::FromUint32BE(msg[0],13);
|
||||
msg[4] = 6;
|
||||
Serialization::BitConverter::FromUint32BE(msg[5], (uint32_t)piece);
|
||||
|
||||
Serialization::BitConverter::FromUint32BE(msg[9], (uint32_t)(16384*block));
|
||||
size_t readBlockSize = 16384;
|
||||
if((_pieceSize % 16384) != 0 && block >= blockCount - 1) readBlockSize = _pieceSize % 16384;
|
||||
|
||||
Serialization::BitConverter::FromUint32BE(msg[13], (uint32_t)(readBlockSize));
|
||||
|
||||
item->stream->WriteBlock(msg.data(),msg.size());
|
||||
|
||||
std::cout << "Request block: " << block << " of piece: " << piece << " from peer: " << item->ip << ":" << item->port << std::endl;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(hasFound) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else std::cout << "Is choking" << std::endl;
|
||||
if(item->stream->DataAvailable(1))
|
||||
{
|
||||
std::cout << "Has message" << std::endl;
|
||||
size_t read = item->stream->Read(buffer.data(),buffer.size());
|
||||
item->messages.insert(item->messages.cend(),buffer.begin(),buffer.begin()+read);
|
||||
if(!processMessages(item))
|
||||
{
|
||||
std::cout << "Peer " << item->ip << ":" << item->port << " misbehaved" << std::endl;
|
||||
item->stream=nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(auto idx = this->connections.begin(); idx != this->connections.end(); idx++)
|
||||
{
|
||||
auto item = *idx;
|
||||
bool destroy = true;
|
||||
if(item)
|
||||
{
|
||||
destroy = !item->stream;
|
||||
}
|
||||
if(destroy)
|
||||
{
|
||||
this->connections.erase(idx);
|
||||
idx--;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
size_t ActiveTorrent::pieceSize(size_t piece)
|
||||
{
|
||||
if(piece == this->has.size()-1)
|
||||
{
|
||||
int64_t remainder = this->torrentSize % this->file.info.piece_length;
|
||||
if(remainder != 0)
|
||||
return (size_t)remainder;
|
||||
}
|
||||
return this->file.info.piece_length;
|
||||
}
|
||||
bool ActiveTorrent::processMessages(std::shared_ptr<TorrentPeer> peer)
|
||||
{
|
||||
if(!peer->stream) return false;
|
||||
using namespace Tesses::Framework::Serialization;
|
||||
while(peer->messages.size() >= 4)
|
||||
{
|
||||
auto len = BitConverter::ToUint32BE(peer->messages[0]);
|
||||
if(len + 4 > peer->messages.size()) break;
|
||||
|
||||
if(len > 0)
|
||||
{
|
||||
uint8_t id = peer->messages[4];
|
||||
switch(id)
|
||||
{
|
||||
case 0:
|
||||
peer->isChokingMe=true;
|
||||
std::cout << peer->ip << ":" << peer->port << " choked me :(" << std::endl;
|
||||
break;
|
||||
case 1:
|
||||
peer->isChokingMe=false;
|
||||
|
||||
std::cout << peer->ip << ":" << peer->port << " unchoked me :)" << std::endl;
|
||||
if(this->has.allone())
|
||||
{
|
||||
//send not intrested
|
||||
std::vector<uint8_t> msg((size_t)5);
|
||||
BitConverter::FromUint32BE(msg[0],1);
|
||||
msg[4] = 3;
|
||||
|
||||
peer->stream->WriteBlock(msg.data(),msg.size());
|
||||
}
|
||||
else {
|
||||
std::vector<uint8_t> msg((size_t)5);
|
||||
BitConverter::FromUint32BE(msg[0],1);
|
||||
msg[4] = 2;
|
||||
|
||||
peer->stream->WriteBlock(msg.data(),msg.size());
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
|
||||
std::cout << peer->ip << ":" << peer->port << " is intrested" << std::endl;
|
||||
peer->intrested=true;
|
||||
break;
|
||||
case 3:
|
||||
|
||||
std::cout << peer->ip << ":" << peer->port << " is not intrested" << std::endl;
|
||||
peer->intrested=false;
|
||||
break;
|
||||
case 4:
|
||||
{
|
||||
|
||||
auto pieceIndex = BitConverter::ToUint32BE(peer->messages[5]);
|
||||
if(pieceIndex < peer->has.size())
|
||||
peer->has.set((size_t)pieceIndex,true);
|
||||
else return false;
|
||||
|
||||
std::cout << peer->ip << ":" << peer->port << " has piece " << pieceIndex << std::endl;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
{
|
||||
if(len-1 == peer->has.data().size())
|
||||
{
|
||||
std::copy(peer->messages.begin()+5,peer->messages.begin()+4+len,peer->has.data().begin());
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
{
|
||||
if(peer->isChoked) break;
|
||||
if(len == 13)
|
||||
{
|
||||
auto pieceIndex = BitConverter::ToUint32BE(peer->messages[5]);
|
||||
auto offset = BitConverter::ToUint32BE(peer->messages[9]);
|
||||
auto length = BitConverter::ToUint32BE(peer->messages[13]);
|
||||
|
||||
|
||||
|
||||
std::cout << peer->ip << ":" << peer->port << " wants piece: " << pieceIndex << " offset: " << offset << " length: " << length << std::endl;
|
||||
|
||||
|
||||
|
||||
if(pieceIndex < this->has.size())
|
||||
{
|
||||
size_t _pieceSize = pieceSize((size_t)pieceIndex);
|
||||
if((size_t)offset >= _pieceSize) return false;
|
||||
if(length > 16384) return false;
|
||||
if(length+offset > _pieceSize) return false;
|
||||
|
||||
if(this->has.get((size_t)pieceIndex))
|
||||
{
|
||||
for(auto index = peer->cancel_requests.begin(); index != peer->cancel_requests.end(); index++)
|
||||
{
|
||||
if(index->piece == pieceIndex && index->begin == offset && index->length == length)
|
||||
{
|
||||
peer->cancel_requests.erase(index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> message;
|
||||
message.resize(13+length);
|
||||
BitConverter::FromUint32BE(message[0],9+length);
|
||||
message[4] = 7;
|
||||
BitConverter::FromUint32BE(message[5], pieceIndex);
|
||||
BitConverter::FromUint32BE(message[9], offset);
|
||||
|
||||
this->torrent_disk->ReadBlockAt(pieceIndex * this->file.info.piece_length + offset, message.data()+13 ,message.size()-13);
|
||||
peer->stream->WriteBlock(message.data(),message.size());
|
||||
}
|
||||
}
|
||||
else return false;
|
||||
} else return false;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
{
|
||||
if(len > 9)
|
||||
{
|
||||
auto pieceIndex = BitConverter::ToUint32BE(peer->messages[5]);
|
||||
auto offset = BitConverter::ToUint32BE(peer->messages[9]);
|
||||
auto length = len - 9;
|
||||
|
||||
std::cout << peer->ip << ":" << peer->port << " gave me a piece! piece: " << pieceIndex << " offset: " << offset << " length: " << length << std::endl;
|
||||
|
||||
if(pieceIndex < this->has.size())
|
||||
{
|
||||
size_t _pieceSize = pieceSize((size_t)pieceIndex);
|
||||
if((size_t)offset >= _pieceSize) return false;
|
||||
if(length > 16384) return false;
|
||||
if(length+offset > _pieceSize) return false;
|
||||
if(!this->has.get((size_t)pieceIndex))
|
||||
{
|
||||
this->torrent_disk->WriteBlockAt(pieceIndex * this->file.info.piece_length + offset, peer->messages.data()+9 ,length);
|
||||
if(!this->blocksAquired[pieceIndex] )
|
||||
{
|
||||
auto blockCount = _pieceSize / 16384;
|
||||
if((_pieceSize % 16384) != 0) blockCount++;
|
||||
|
||||
this->blocksAquired[pieceIndex] = Tesses::Framework::BitTorrent::TorrentBitField(blockCount);
|
||||
}
|
||||
|
||||
this->blocksAquired[pieceIndex]->set(offset/16384, true);
|
||||
if(this->blocksAquired[pieceIndex]->allone())
|
||||
{
|
||||
std::vector<uint8_t> data(_pieceSize);
|
||||
if(this->torrent_disk->ReadBlockAt(pieceIndex*this->file.info.piece_length,data.data(),data.size()))
|
||||
{
|
||||
auto hash = Tesses::Framework::Crypto::Sha1::ComputeHash(data.data(),data.size());
|
||||
if(std::equal(hash.begin(),hash.end(),this->file.info.pieces.data.begin()+(pieceIndex*20)))
|
||||
{
|
||||
this->has.set(pieceIndex,true);
|
||||
auto pieceFile = directory / this->file.info.name + ".tftpart";
|
||||
{
|
||||
auto pfs = vfs->OpenFile(pieceFile,"wb");
|
||||
pfs->WriteBlock(has.data().data(),has.data().size());
|
||||
}
|
||||
this->blocksAquired[pieceIndex] = std::nullopt;
|
||||
peer->blocksRequested[pieceIndex] = std::nullopt;
|
||||
{
|
||||
std::vector<uint8_t> msg((size_t)9);
|
||||
BitConverter::FromUint32BE(msg[0],5);
|
||||
msg[4] = 4;
|
||||
BitConverter::FromUint32BE(msg[5],(uint32_t)pieceIndex);
|
||||
for(auto& peer2 : this->connections)
|
||||
{
|
||||
if(peer2->stream)
|
||||
{
|
||||
peer2->stream->WriteBlock(msg.data(),msg.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
if(this->has.allone())
|
||||
{
|
||||
TF_LOG("The torrent: " + (std::string)this->file.info.name + " is completed");
|
||||
std::vector<uint8_t> msg((size_t)5);
|
||||
BitConverter::FromUint32BE(msg[0],1);
|
||||
msg[4] = 3;
|
||||
for(auto& peer2 : this->connections)
|
||||
{
|
||||
if(peer2->stream)
|
||||
{
|
||||
//send not intrested
|
||||
peer2->stream->WriteBlock(msg.data(),msg.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
//CORRUPT
|
||||
this->blocksAquired[pieceIndex]->zero();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//CORRUPT
|
||||
this->blocksAquired[pieceIndex]->zero();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
{
|
||||
if(len == 13)
|
||||
{
|
||||
auto pieceIndex = BitConverter::ToUint32BE(peer->messages[5]);
|
||||
auto offset = BitConverter::ToUint32BE(peer->messages[9]);
|
||||
auto length = BitConverter::ToUint32BE(peer->messages[13]);
|
||||
CancelRequest r;
|
||||
r.piece = pieceIndex;
|
||||
r.begin = offset;
|
||||
r.length = length;
|
||||
|
||||
peer->cancel_requests.push_back(r);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
peer->messages.erase(peer->messages.begin(),peer->messages.begin()+4+len);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ActiveTorrent::ActiveTorrent(TorrentFile file, std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs,Tesses::Framework::Filesystem::VFSPath directory, std::string peer_id)
|
||||
{
|
||||
this->file = file;
|
||||
this->vfs = vfs;
|
||||
this->directory = directory;
|
||||
this->peer_id = peer_id;
|
||||
this->has.resize(this->file.info.pieces.data.size()/20);
|
||||
this->torrentSize = this->file.info.GetTorrentFileSize();
|
||||
this->info_hash = this->file.info.GetInfoHash();
|
||||
this->lastTime = 0;
|
||||
this->blocksAquired.resize(this->has.size());
|
||||
this->uploaded = 0;
|
||||
this->downloaded = 0;
|
||||
|
||||
auto pieceFile = directory / this->file.info.name + ".tftpart";
|
||||
if(vfs->FileExists(pieceFile))
|
||||
{
|
||||
auto strm = vfs->OpenFile(pieceFile,"rb");
|
||||
strm->ReadBlock(has.data().data(),has.data().size());
|
||||
}
|
||||
this->torrent_disk = this->file.info.GetStreamFromFilesystem(vfs,directory);
|
||||
}
|
||||
std::string lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
std::string GeneratePeerId()
|
||||
{
|
||||
std::string peerId((size_t)20,' ');
|
||||
peerId.insert(0,"TFWT");
|
||||
Random r;
|
||||
for(size_t i = 4; i < peerId.size(); i++)
|
||||
peerId[i] = lookup[(int)r.Next((uint32_t)lookup.size())];
|
||||
return peerId;
|
||||
}
|
||||
}
|
||||
122
src/BitTorrent/TorrentStream.cpp
Normal file
122
src/BitTorrent/TorrentStream.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "TessesFramework/BitTorrent/TorrentStream.hpp"
|
||||
|
||||
namespace Tesses::Framework::BitTorrent
|
||||
{
|
||||
TorrentFileEntry::TorrentFileEntry()
|
||||
{
|
||||
|
||||
}
|
||||
TorrentFileEntry::TorrentFileEntry(Tesses::Framework::Filesystem::VFSPath path, int64_t length): path(path), length(length)
|
||||
{}
|
||||
|
||||
ReadWriteAt::~ReadWriteAt(){}
|
||||
|
||||
TorrentFileStream::TorrentFileStream(std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, Tesses::Framework::Filesystem::VFSPath path,uint64_t length) : vfs(vfs), path(path), length(length)
|
||||
{
|
||||
}
|
||||
bool TorrentFileStream::ReadBlockAt(uint64_t offset, uint8_t* data, size_t len)
|
||||
{
|
||||
if(!vfs->FileExists(path)) return false;
|
||||
auto strm = vfs->OpenFile(path,"rb");
|
||||
strm->Seek((int64_t)offset,Streams::SeekOrigin::Begin);
|
||||
strm->ReadBlock(data,len);
|
||||
return true;
|
||||
}
|
||||
void TorrentFileStream::WriteBlockAt(uint64_t offset, const uint8_t* data, size_t len)
|
||||
{
|
||||
len = (size_t)std::min((int64_t)len, (int64_t)this->length-(int64_t)offset);
|
||||
if(len == 0) return;
|
||||
|
||||
mtx.Lock();
|
||||
auto strm = vfs->OpenFile(path,"wb");
|
||||
strm->Seek((int64_t)offset,Streams::SeekOrigin::Begin);
|
||||
strm->WriteBlock(data,len);
|
||||
mtx.Unlock();
|
||||
}
|
||||
|
||||
TorrentDirectoryStream::TorrentDirectoryStream(std::shared_ptr<Tesses::Framework::Filesystem::VFS> vfs, Tesses::Framework::Filesystem::VFSPath path,std::vector<TorrentFileEntry> entries) : vfs(vfs), path(path), entries(entries)
|
||||
{
|
||||
if(!vfs->DirectoryExists(path))
|
||||
this->vfs->CreateDirectory(path);
|
||||
this->mtxs.resize(entries.size());
|
||||
}
|
||||
|
||||
// From https://www.seanjoflynn.com/research/bittorrent.html , which is licensed under MIT based on code repo
|
||||
bool TorrentDirectoryStream::ReadBlockAt(uint64_t offset, uint8_t* data, size_t len)
|
||||
{
|
||||
uint64_t currentOffset = 0;
|
||||
uint64_t end = offset + len;
|
||||
for(size_t i = 0; i < this->entries.size(); i++)
|
||||
{
|
||||
if(offset < currentOffset && end < currentOffset) {
|
||||
currentOffset += this->entries[i].length;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(offset > currentOffset + this->entries[i].length && end > currentOffset + this->entries[i].length ){
|
||||
currentOffset += this->entries[i].length;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto path = this->path / this->entries[i].path;
|
||||
if(!vfs->FileExists(path)) return false;
|
||||
|
||||
int64_t fstart = std::max((int64_t)0,(int64_t)offset - (int64_t)currentOffset);
|
||||
int64_t fend = std::min((int64_t)end - (int64_t)currentOffset, (int64_t)this->entries[i].length);
|
||||
int flength = (int)(fend - fstart);
|
||||
int bstart = std::max((int)0,(int)((int64_t)currentOffset - (int64_t)offset));
|
||||
auto strm = vfs->OpenFile(path,"rb");
|
||||
strm->Seek(fstart, Streams::SeekOrigin::Begin);
|
||||
strm->ReadBlock(data+bstart,flength);
|
||||
|
||||
|
||||
|
||||
currentOffset += this->entries[i].length;
|
||||
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// From https://www.seanjoflynn.com/research/bittorrent.html , which is licensed under MIT based on code repo
|
||||
void TorrentDirectoryStream::WriteBlockAt(uint64_t offset, const uint8_t* data, size_t len)
|
||||
{
|
||||
uint64_t currentOffset = 0;
|
||||
uint64_t end = offset + len;
|
||||
for(size_t i = 0; i < this->entries.size(); i++)
|
||||
{
|
||||
if(offset < currentOffset && end < currentOffset) {
|
||||
currentOffset += this->entries[i].length;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(offset > currentOffset + this->entries[i].length && end > currentOffset + this->entries[i].length ){
|
||||
currentOffset += this->entries[i].length;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto path = this->path / this->entries[i].path;
|
||||
auto parent = path.GetParent();
|
||||
if(!vfs->DirectoryExists(parent))
|
||||
vfs->CreateDirectory(parent);
|
||||
|
||||
int64_t fstart = std::max((int64_t)0,(int64_t)offset - (int64_t)currentOffset);
|
||||
int64_t fend = std::min((int64_t)end - (int64_t)currentOffset, (int64_t)this->entries[i].length);
|
||||
int flength = (int)(fend - fstart);
|
||||
int bstart = std::max((int)0,(int)((int64_t)currentOffset - (int64_t)offset));
|
||||
|
||||
this->mtxs[i].Lock();
|
||||
{
|
||||
auto strm = vfs->OpenFile(path,"wb");
|
||||
strm->Seek(fstart, Streams::SeekOrigin::Begin);
|
||||
strm->WriteBlock(data+bstart,flength);
|
||||
}
|
||||
this->mtxs[i].Unlock();
|
||||
|
||||
|
||||
currentOffset += this->entries[i].length;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +230,27 @@ namespace Tesses::Framework::Filesystem
|
||||
#endif
|
||||
}
|
||||
|
||||
std::shared_ptr<LocalFilesystem> LocalFS;
|
||||
|
||||
void LocalFilesystem::Lock(VFSPath path)
|
||||
{
|
||||
auto p2 = VFSPathToSystem(path);
|
||||
const char* fopenPath = p2.c_str();
|
||||
while(true)
|
||||
{
|
||||
FILE* fp = fopen(fopenPath,"wx");
|
||||
if(fp) {
|
||||
fclose(fp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void LocalFilesystem::Unlock(VFSPath path)
|
||||
{
|
||||
std::error_code error;
|
||||
std::filesystem::remove(VFSPathToSystem(path),error);
|
||||
}
|
||||
|
||||
std::shared_ptr<LocalFilesystem> LocalFS = std::make_shared<LocalFilesystem>();
|
||||
}
|
||||
|
||||
// C:/Users/Jim/Joel
|
||||
|
||||
@@ -311,6 +311,34 @@ namespace Tesses::Framework::Filesystem
|
||||
if(vfs != nullptr)
|
||||
vfs->DeleteFile(destPath);
|
||||
|
||||
}
|
||||
void MountableFilesystem::Lock(VFSPath path)
|
||||
{
|
||||
path = path.CollapseRelativeParents();
|
||||
|
||||
VFSPath destRoot;
|
||||
VFSPath destPath = path;
|
||||
std::shared_ptr<VFS> vfs = root;
|
||||
|
||||
GetFS(path, destRoot, destPath, vfs);
|
||||
|
||||
if(vfs != nullptr)
|
||||
vfs->Lock(destPath);
|
||||
|
||||
}
|
||||
void MountableFilesystem::Unlock(VFSPath path)
|
||||
{
|
||||
path = path.CollapseRelativeParents();
|
||||
|
||||
VFSPath destRoot;
|
||||
VFSPath destPath = path;
|
||||
std::shared_ptr<VFS> vfs = root;
|
||||
|
||||
GetFS(path, destRoot, destPath, vfs);
|
||||
|
||||
if(vfs != nullptr)
|
||||
vfs->Unlock(destPath);
|
||||
|
||||
}
|
||||
void MountableFilesystem::GetDate(VFSPath path, Date::DateTime& lastWrite, Date::DateTime& lastAccess)
|
||||
{
|
||||
|
||||
@@ -87,6 +87,15 @@ namespace Tesses::Framework::Filesystem
|
||||
{
|
||||
this->parent->DeleteFile(ToParent(path));
|
||||
}
|
||||
void SubdirFilesystem::Lock(VFSPath path)
|
||||
{
|
||||
this->parent->Lock(ToParent(path));
|
||||
}
|
||||
|
||||
void SubdirFilesystem::Unlock(VFSPath path)
|
||||
{
|
||||
this->parent->Unlock(ToParent(path));
|
||||
}
|
||||
void SubdirFilesystem::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile)
|
||||
{
|
||||
this->parent->CreateSymlink(ToParent(existingFile),ToParent(symlinkFile));
|
||||
|
||||
@@ -109,6 +109,16 @@ namespace Tesses::Framework::Filesystem {
|
||||
if(this->vfs == nullptr) return;
|
||||
this->vfs->DeleteFile(path);
|
||||
}
|
||||
void TempFS::Lock(VFSPath path)
|
||||
{
|
||||
if(this->vfs == nullptr) return;
|
||||
this->vfs->Lock(path);
|
||||
}
|
||||
void TempFS::Unlock(VFSPath path)
|
||||
{
|
||||
if(this->vfs == nullptr) return;
|
||||
this->vfs->Unlock(path);
|
||||
}
|
||||
void TempFS::CreateSymlink(VFSPath existingFile, VFSPath symlinkFile)
|
||||
{
|
||||
if(this->vfs == nullptr) return;
|
||||
@@ -202,4 +212,5 @@ namespace Tesses::Framework::Filesystem {
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -519,5 +519,13 @@ namespace Tesses::Framework::Filesystem
|
||||
}
|
||||
void VFS::Close() {
|
||||
|
||||
}
|
||||
void VFS::Lock(VFSPath path)
|
||||
{
|
||||
|
||||
}
|
||||
void VFS::Unlock(VFSPath path)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -965,5 +965,43 @@ namespace Tesses::Framework::Http {
|
||||
if(!TryGetFirst(key,val)) return false;
|
||||
return val == "true" || val == "on";
|
||||
}
|
||||
std::string HttpUtils::BytesToHex(const std::vector<uint8_t>& data)
|
||||
{
|
||||
std::string text;
|
||||
BytesToHex(text,data);
|
||||
return text;
|
||||
}
|
||||
void HttpUtils::BytesToHex(std::string& text,const std::vector<uint8_t>& data)
|
||||
{
|
||||
if(data.empty()) {
|
||||
text.clear();
|
||||
return;
|
||||
}
|
||||
text.resize(data.size()*2);
|
||||
for(size_t i = 0; i < data.size(); i++)
|
||||
{
|
||||
text[i*2] = NibbleToHex(data[i] >> 4);
|
||||
text[i*2+1] += NibbleToHex(data[i]);
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> HttpUtils::HexToBytes(const std::string& text)
|
||||
{
|
||||
std::vector<uint8_t> data;
|
||||
HexToBytes(data,text);
|
||||
return data;
|
||||
}
|
||||
void HttpUtils::HexToBytes(std::vector<uint8_t>& data,const std::string& text)
|
||||
{
|
||||
if(text.empty()) { data.clear(); return;}
|
||||
if(text.size() % 2 != 0) throw std::runtime_error("Text length is not even");
|
||||
|
||||
|
||||
data.resize(text.size()/2);
|
||||
for(size_t i = 0; i < text.size(); i+=2)
|
||||
{
|
||||
uint8_t b = HexToNibble(text[i]) << 4;
|
||||
b |= HexToNibble(text[i+1]);
|
||||
data[i/2] = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31
src/Random.cpp
Normal file
31
src/Random.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "TessesFramework/Random.hpp"
|
||||
|
||||
namespace Tesses::Framework {
|
||||
Random::Random() : Random((uint64_t)time(NULL))
|
||||
{
|
||||
|
||||
}
|
||||
Random::Random(uint64_t seed) : num(seed)
|
||||
{
|
||||
|
||||
}
|
||||
uint32_t Random::Next(uint32_t max)
|
||||
{
|
||||
return (uint32_t)Next(0,(int32_t)max);
|
||||
}
|
||||
int32_t Random::Next(int32_t min, int32_t max)
|
||||
{
|
||||
uint32_t number = (uint32_t)(Next() >> 31);
|
||||
int32_t range = max-min;
|
||||
|
||||
return (uint32_t)((((double)number / (double)0xFFFFFFFF) * (double)range)+min);
|
||||
}
|
||||
uint64_t Random::Next()
|
||||
{
|
||||
return num = 6364136223846793005 * num + 1;
|
||||
}
|
||||
uint8_t Random::NextByte()
|
||||
{
|
||||
return (uint8_t)Next(0,256);
|
||||
}
|
||||
}
|
||||
253
src/Serialization/Bencode.cpp
Normal file
253
src/Serialization/Bencode.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
#include "TessesFramework/Serialization/Bencode.hpp"
|
||||
|
||||
namespace Tesses::Framework::Serialization::Bencode {
|
||||
BeToken BeDictionary::GetValue(BeString key) const
|
||||
{
|
||||
for(auto item : this->tokens)
|
||||
if(item.first == key) return item.second;
|
||||
return BeUndefined();
|
||||
}
|
||||
void BeDictionary::SetValue(BeString key, BeToken value)
|
||||
{
|
||||
if(std::holds_alternative<BeUndefined>(value))
|
||||
{
|
||||
for(auto idx = this->tokens.begin(); idx != this->tokens.end(); idx++)
|
||||
{
|
||||
if(idx->first == key) {
|
||||
this->tokens.erase(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto& item : this->tokens)
|
||||
{
|
||||
if(item.first == key)
|
||||
{
|
||||
item.second = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->tokens.emplace_back(key,value);
|
||||
}
|
||||
}
|
||||
|
||||
BeString::BeString()
|
||||
{
|
||||
|
||||
}
|
||||
BeString::BeString(const std::string& text)
|
||||
{
|
||||
this->data.insert(this->data.end(),text.cbegin(),text.cend());
|
||||
}
|
||||
BeString::BeString(const char* text)
|
||||
{
|
||||
size_t len = strlen(text);
|
||||
this->data.insert(this->data.end(),text,text+len);
|
||||
}
|
||||
BeString::BeString(const std::vector<uint8_t>& data) : data(data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
BeString::operator std::string() const
|
||||
{
|
||||
return std::string(data.cbegin(),data.cend());
|
||||
}
|
||||
|
||||
|
||||
bool operator==(const BeString& lStr, const BeString& rStr)
|
||||
{
|
||||
return lStr.data == rStr.data;
|
||||
}
|
||||
bool operator==(const BeString& lStr, const std::string& rStr)
|
||||
{
|
||||
if(lStr.data.size() != rStr.size()) return false;
|
||||
return std::equal(lStr.data.cbegin(),lStr.data.cend(),rStr.cbegin());
|
||||
}
|
||||
bool operator==(const std::string& lStr, const BeString& rStr)
|
||||
{
|
||||
if(lStr.size() != rStr.data.size()) return false;
|
||||
return std::equal(lStr.cbegin(),lStr.cend(),rStr.data.cbegin());
|
||||
}
|
||||
bool operator==(const BeString& lStr, const char* rStr)
|
||||
{
|
||||
size_t len = strlen(rStr);
|
||||
if(lStr.data.size() != len) return false;
|
||||
return std::equal(lStr.data.cbegin(),lStr.data.cend(),rStr);
|
||||
}
|
||||
bool operator==(const char* lStr, const BeString& rStr)
|
||||
{
|
||||
size_t len = strlen(lStr);
|
||||
if(rStr.data.size() != len) return false;
|
||||
|
||||
return std::equal(lStr,lStr+len,rStr.data.cbegin());
|
||||
}
|
||||
|
||||
bool operator!=(const BeString& lStr, const BeString& rStr)
|
||||
{
|
||||
return !(lStr == rStr);
|
||||
}
|
||||
bool operator!=(const BeString& lStr, const std::string& rStr)
|
||||
{
|
||||
return !(lStr == rStr);
|
||||
}
|
||||
bool operator!=(const std::string& lStr, const BeString& rStr)
|
||||
{
|
||||
return !(lStr == rStr);
|
||||
}
|
||||
bool operator!=(const BeString& lStr, const char* rStr)
|
||||
{
|
||||
return !(lStr == rStr);
|
||||
}
|
||||
bool operator!=(const char* lStr, const BeString& rStr)
|
||||
{
|
||||
return !(lStr == rStr);
|
||||
}
|
||||
|
||||
void Bencode::Save(std::shared_ptr<Tesses::Framework::Streams::Stream> strm,const BeToken& value)
|
||||
{
|
||||
if(std::holds_alternative<BeArray>(value))
|
||||
{
|
||||
auto& array = std::get<BeArray>(value);
|
||||
strm->WriteByte((uint8_t)'l');
|
||||
for(auto& item : array.tokens)
|
||||
{
|
||||
Save(strm,item);
|
||||
}
|
||||
strm->WriteByte((uint8_t)'e');
|
||||
}
|
||||
else if(std::holds_alternative<BeDictionary>(value))
|
||||
{
|
||||
auto& dict = std::get<BeDictionary>(value);
|
||||
strm->WriteByte((uint8_t)'d');
|
||||
for(auto& item : dict.tokens)
|
||||
{
|
||||
Save(strm,item.first);
|
||||
Save(strm,item.second);
|
||||
}
|
||||
strm->WriteByte((uint8_t)'e');
|
||||
}
|
||||
else if(std::holds_alternative<BeString>(value))
|
||||
{
|
||||
auto& str = std::get<BeString>(value);
|
||||
std::string prefix = std::to_string(str.data.size()) + ":";
|
||||
strm->WriteBlock((const uint8_t*)prefix.data(),prefix.size());
|
||||
strm->WriteBlock(str.data.data(),str.data.size());
|
||||
}
|
||||
else if(std::holds_alternative<int64_t>(value))
|
||||
{
|
||||
int64_t val = std::get<int64_t>(value);
|
||||
std::string str = "i" + std::to_string(val) + "e";
|
||||
strm->WriteBlock((const uint8_t*)str.data(),str.size());
|
||||
}
|
||||
}
|
||||
BeToken Bencode::Load(std::shared_ptr<Tesses::Framework::Streams::Stream> strm)
|
||||
{
|
||||
auto chr = strm->ReadByte();
|
||||
switch(chr)
|
||||
{
|
||||
case 'i':
|
||||
{
|
||||
std::string no;
|
||||
while(true) {
|
||||
chr = strm->ReadByte();
|
||||
if(chr == -1) throw std::out_of_range("End of file");
|
||||
if(chr == 'e') break;
|
||||
no.push_back((char)chr);
|
||||
}
|
||||
return std::stoll(no);
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
{
|
||||
BeDictionary dict;
|
||||
while(true)
|
||||
{
|
||||
auto key = Load(strm);
|
||||
if(std::holds_alternative<BeUndefined>(key)) break;
|
||||
if(!std::holds_alternative<BeString>(key)) throw std::runtime_error("Key must be a string");
|
||||
auto value = Load(strm);
|
||||
if(std::holds_alternative<BeUndefined>(key)) throw std::runtime_error("Incomplete dictionary entry");
|
||||
dict.tokens.emplace_back(std::get<BeString>(key),value);
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
{
|
||||
BeArray array;
|
||||
while(true)
|
||||
{
|
||||
auto tkn = Load(strm);
|
||||
if(std::holds_alternative<BeUndefined>(tkn)) break;
|
||||
array.tokens.push_back(tkn);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
return BeUndefined();
|
||||
case -1:
|
||||
throw std::out_of_range("End of file");
|
||||
default:
|
||||
{
|
||||
std::string no({(char)chr});
|
||||
while(true) {
|
||||
chr = strm->ReadByte();
|
||||
if(chr == -1) throw std::out_of_range("End of file");
|
||||
if(chr == ':') break;
|
||||
no.push_back((char)chr);
|
||||
}
|
||||
auto len = std::stoll(no);
|
||||
if(len < 0) throw std::out_of_range("Less than zero byte string");
|
||||
BeString str;
|
||||
str.data.resize((size_t)len);
|
||||
|
||||
size_t result = strm->ReadBlock(str.data.data(),str.data.size());
|
||||
if(result != str.data.size() || result != (size_t)len) throw std::out_of_range("Didn't read entire string");
|
||||
return str;
|
||||
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
Json::JToken Bencode::ToJson(const BeToken& tkn)
|
||||
{
|
||||
if(std::holds_alternative<BeDictionary>(tkn))
|
||||
{
|
||||
auto& dict = std::get<BeDictionary>(tkn);
|
||||
Json::JObject o;
|
||||
for(auto& itm : dict.tokens)
|
||||
{
|
||||
o.SetValue(itm.first,ToJson(itm.second));
|
||||
}
|
||||
return o;
|
||||
}
|
||||
if(std::holds_alternative<BeArray>(tkn))
|
||||
{
|
||||
auto& array = std::get<BeArray>(tkn);
|
||||
Json::JArray a;
|
||||
for(auto& itm : array.tokens)
|
||||
{
|
||||
a.Add(ToJson(itm));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
if(std::holds_alternative<BeString>(tkn))
|
||||
{
|
||||
return (std::string)std::get<BeString>(tkn);
|
||||
}
|
||||
if(std::holds_alternative<int64_t>(tkn))
|
||||
{
|
||||
return std::get<int64_t>(tkn);
|
||||
}
|
||||
return Json::JUndefined();
|
||||
}
|
||||
void Bencode::Print(std::shared_ptr<Tesses::Framework::TextStreams::TextWriter> writer, BeToken tkn)
|
||||
{
|
||||
writer->WriteLine(Json::Json::Encode(ToJson(tkn),true));
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using HttpUtils = Tesses::Framework::Http::HttpUtils;
|
||||
#include <network.h>
|
||||
#define NETWORK_GETSOCKNAME net_getsockname
|
||||
#define NETWORK_RECV net_recv
|
||||
#define NETWORK_POLL net_poll
|
||||
#define sockaddr_storage sockaddr_in
|
||||
#error "Not supported yet"
|
||||
#else
|
||||
@@ -46,6 +47,7 @@ extern "C" {
|
||||
#if defined(AF_UNIX) && !defined(GEKKO) && !defined(__SWITCH__) && !defined(__PS2__)
|
||||
#include <sys/un.h>
|
||||
#endif
|
||||
#include <sys/poll.h>
|
||||
}
|
||||
#endif
|
||||
#if defined(GEKKO)
|
||||
@@ -68,10 +70,13 @@ extern "C" uint32_t if_config( char *local_ip, char *netmask, char *gateway,bool
|
||||
#define NETWORK_GETADDRINFO getaddrinfo
|
||||
#define NETWORK_FREEADDRINFO freeaddrinfo
|
||||
#define NETWORK_GETSOCKNAME getsockname
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define NETWORK_CLOSE closesocket
|
||||
#define NETWORK_POLL WSAPoll
|
||||
#else
|
||||
#define NETWORK_CLOSE close
|
||||
#define NETWORK_POLL poll
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -288,6 +293,28 @@ namespace Tesses::Framework::Streams {
|
||||
uint32_t addr;
|
||||
uint8_t addr_parts[4];
|
||||
} my_addr_t;
|
||||
|
||||
bool NetworkStream::DataAvailable(int timeout)
|
||||
{
|
||||
pollfd fd;
|
||||
fd.events = POLLIN;
|
||||
fd.fd = this->sock;
|
||||
int res= NETWORK_POLL(&fd,1,timeout);
|
||||
if (res == -1) {
|
||||
if(errno == EINTR) return false;
|
||||
throw std::runtime_error("poll() failed");
|
||||
}
|
||||
else if(res == 0) return false;
|
||||
else if(fd.revents & POLLIN)
|
||||
return true;
|
||||
else if(fd.revents & (POLLERR | POLLNVAL))
|
||||
{
|
||||
this->endOfStream=true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NetworkStream::NetworkStream(std::string unixPath,bool isServer)
|
||||
{
|
||||
this->endOfStream=false;
|
||||
|
||||
@@ -182,7 +182,6 @@ namespace Tesses::Framework
|
||||
}
|
||||
void TF_Init()
|
||||
{
|
||||
Tesses::Framework::Filesystem::LocalFS = std::make_shared<Tesses::Framework::Filesystem::LocalFilesystem>();
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_SQLITE)
|
||||
sqlite3_initialize();
|
||||
#if defined(GEKKO) || defined(__SWITCH__) || defined(__PS2__)
|
||||
@@ -303,4 +302,41 @@ if (iResult != 0) {
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
static std::string TF_FileSizeBin(uint64_t bytes)
|
||||
{
|
||||
if(bytes == 1) return "1 Byte";
|
||||
if(bytes < 1024ULL) return std::to_string(bytes) + " Bytes";
|
||||
if(bytes < 1024ULL*1024ULL) return std::to_string(bytes / 1024ULL) + " kiB";
|
||||
if(bytes < 1024ULL*1024ULL*1024ULL) return std::to_string(bytes / (1024ULL*1024ULL)) + " MiB";
|
||||
if(bytes < 1024ULL*1024ULL*1024ULL*1024ULL) return std::to_string(bytes / (1024ULL*1024ULL*1024ULL)) + " GiB";
|
||||
if(bytes < 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL) return std::to_string(bytes / (1024ULL*1024ULL*1024ULL*1024ULL)) + " TiB";
|
||||
return std::to_string(bytes / (1024ULL*1024ULL*1024ULL*1024ULL*1024ULL)) + " PiB";
|
||||
}
|
||||
static std::string TF_FileSizeDec(uint64_t bytes)
|
||||
{
|
||||
if(bytes == 1) return "1 Byte";
|
||||
if(bytes < 1000ULL) return std::to_string(bytes) + " Bytes";
|
||||
if(bytes < 1000ULL*1000ULL) return std::to_string(bytes / 1000ULL) + " kB";
|
||||
if(bytes < 1000ULL*1000ULL*1000ULL) return std::to_string(bytes / (1000ULL*1000ULL)) + " MB";
|
||||
if(bytes < 1000ULL*1000ULL*1000ULL*1000ULL) return std::to_string(bytes / (1000ULL*1000ULL*1000ULL)) + " GB";
|
||||
if(bytes < 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL) return std::to_string(bytes / (1000ULL*1000ULL*1000ULL*1000ULL)) + " TB";
|
||||
return std::to_string(bytes / (1000ULL*1000ULL*1000ULL*1000ULL*1000ULL)) + " PB";
|
||||
}
|
||||
std::string TF_FileSize(uint64_t bytes, bool usesBin)
|
||||
{
|
||||
return usesBin ? TF_FileSizeBin(bytes) : TF_FileSizeDec(bytes);
|
||||
}
|
||||
|
||||
std::optional<std::string> _argv0=std::nullopt;
|
||||
|
||||
void TF_AllowPortable(std::string argv0)
|
||||
{
|
||||
_argv0 = argv0;
|
||||
}
|
||||
|
||||
std::optional<std::string> TF_GetCommandName()
|
||||
{
|
||||
return _argv0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user