From 1d5ba40ef0cb2aedd2e3ec501172010b36573b2b Mon Sep 17 00:00:00 2001 From: Mike Nolan Date: Wed, 7 Jan 2026 11:03:33 -0600 Subject: [PATCH] Push failed torrent code as backup before I remove it --- CMakeLists.txt | 31 + apps/printtorrent.cpp | 26 + apps/tclearwebseed.cpp | 22 + apps/tdownloadsingletorrent.cpp | 51 ++ apps/trng.cpp | 36 + apps/ttorrentcreate.cpp | 66 ++ examples/tests.cpp | 22 + .../BitTorrent/TorrentFile.hpp | 70 ++ .../BitTorrent/TorrentManager.hpp | 95 +++ .../BitTorrent/TorrentStream.hpp | 46 ++ include/TessesFramework/Common.hpp | 4 + .../TessesFramework/Filesystem/LocalFS.hpp | 3 + .../Filesystem/MountableFilesystem.hpp | 2 + .../Filesystem/SubdirFilesystem.hpp | 2 + include/TessesFramework/Filesystem/TempFS.hpp | 3 + include/TessesFramework/Filesystem/VFS.hpp | 3 + include/TessesFramework/Filesystem/VFSFix.hpp | 6 +- include/TessesFramework/Http/HttpUtils.hpp | 4 + include/TessesFramework/Random.hpp | 15 + .../TessesFramework/Serialization/Bencode.hpp | 59 ++ .../TessesFramework/Serialization/Json.hpp | 4 +- .../TessesFramework/Streams/NetworkStream.hpp | 1 + include/TessesFramework/TessesFramework.hpp | 5 +- src/BitTorrent/TorrentFile.cpp | 379 ++++++++++ src/BitTorrent/TorrentManager.cpp | 657 ++++++++++++++++++ src/BitTorrent/TorrentStream.cpp | 122 ++++ src/Filesystem/LocalFS.cpp | 22 +- src/Filesystem/MountableFilesystem.cpp | 28 + src/Filesystem/SubdirFilesystem.cpp | 9 + src/Filesystem/TempFS.cpp | 11 + src/Filesystem/VFS.cpp | 8 + src/Http/HttpUtils.cpp | 38 + src/Random.cpp | 31 + src/Serialization/Bencode.cpp | 253 +++++++ src/Streams/NetworkStream.cpp | 27 + src/TF_Init.cpp | 38 +- 36 files changed, 2192 insertions(+), 7 deletions(-) create mode 100644 apps/printtorrent.cpp create mode 100644 apps/tclearwebseed.cpp create mode 100644 apps/tdownloadsingletorrent.cpp create mode 100644 apps/trng.cpp create mode 100644 apps/ttorrentcreate.cpp create mode 100644 include/TessesFramework/BitTorrent/TorrentFile.hpp create mode 100644 include/TessesFramework/BitTorrent/TorrentManager.hpp create mode 100644 include/TessesFramework/BitTorrent/TorrentStream.hpp create mode 100644 include/TessesFramework/Random.hpp create mode 100644 include/TessesFramework/Serialization/Bencode.hpp create mode 100644 src/BitTorrent/TorrentFile.cpp create mode 100644 src/BitTorrent/TorrentManager.cpp create mode 100644 src/BitTorrent/TorrentStream.cpp create mode 100644 src/Random.cpp create mode 100644 src/Serialization/Bencode.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 67f00ac..c6b7a15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/apps/printtorrent.cpp b/apps/printtorrent.cpp new file mode 100644 index 0000000..73054a1 --- /dev/null +++ b/apps/printtorrent.cpp @@ -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(bencode)) + { + + TorrentFile file(std::get(bencode)); + file.Print(std::make_shared()); + } + return 0; +} \ No newline at end of file diff --git a/apps/tclearwebseed.cpp b/apps/tclearwebseed.cpp new file mode 100644 index 0000000..be4ac5e --- /dev/null +++ b/apps/tclearwebseed.cpp @@ -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(data)){ + std::get(data).SetValue("url-list",BeUndefined()); + } + strm = LocalFS->OpenFile((std::string)argv[1],"wb"); + Bencode::Save(strm,data); + return 0; +} \ No newline at end of file diff --git a/apps/tdownloadsingletorrent.cpp b/apps/tdownloadsingletorrent.cpp new file mode 100644 index 0000000..1481c98 --- /dev/null +++ b/apps/tdownloadsingletorrent.cpp @@ -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(bencode)) + { + + TorrentFile file(std::get(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; +} \ No newline at end of file diff --git a/apps/trng.cpp b/apps/trng.cpp new file mode 100644 index 0000000..c3c3c55 --- /dev/null +++ b/apps/trng.cpp @@ -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; + } + } +} \ No newline at end of file diff --git a/apps/ttorrentcreate.cpp b/apps/ttorrentcreate.cpp new file mode 100644 index 0000000..e7aade4 --- /dev/null +++ b/apps/ttorrentcreate.cpp @@ -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 webseeds; + std::vector 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; +} \ No newline at end of file diff --git a/examples/tests.cpp b/examples/tests.cpp index d0abf20..ba8d48f 100644 --- a/examples/tests.cpp +++ b/examples/tests.cpp @@ -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; } \ No newline at end of file diff --git a/include/TessesFramework/BitTorrent/TorrentFile.hpp b/include/TessesFramework/BitTorrent/TorrentFile.hpp new file mode 100644 index 0000000..eae5b16 --- /dev/null +++ b/include/TessesFramework/BitTorrent/TorrentFile.hpp @@ -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> 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 GetStreamFromFilesystem(std::shared_ptr 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 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 announce_list; + std::vector 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 writer); + + static void CreateTorrent(std::shared_ptr torrent_file_stream,const std::vector& trackers,const std::vector& webseeds, std::shared_ptr 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"); + + }; +} \ No newline at end of file diff --git a/include/TessesFramework/BitTorrent/TorrentManager.hpp b/include/TessesFramework/BitTorrent/TorrentManager.hpp new file mode 100644 index 0000000..96bce06 --- /dev/null +++ b/include/TessesFramework/BitTorrent/TorrentManager.hpp @@ -0,0 +1,95 @@ +#pragma once +#include "TorrentFile.hpp" +#include +#include "../Threading/Thread.hpp" +#include "../Random.hpp" +namespace Tesses::Framework::BitTorrent +{ + + class ActiveTorrent; + + class TorrentBitField { + std::vector 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& 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 stream; + std::vector cancel_requests; + std::vector messages; + TorrentBitField has; + + std::vector> blocksRequested; + std::string ip; + uint16_t port; + }; + + class ActiveTorrent { + public: + Random rng; + ActiveTorrent(TorrentFile file, std::shared_ptr vfs,Tesses::Framework::Filesystem::VFSPath directory, std::string peer_id); + std::shared_ptr vfs; + std::map 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> blocksAquired; + TorrentBitField has; + TorrentFile file; + int64_t torrentSize; //to make it more efficient + Tesses::Framework::Filesystem::VFSPath directory; + std::vector> connections; + std::shared_ptr torrent_disk; + + size_t pieceSize(size_t piece); + std::array buffer; + void process(); + bool processMessages(std::shared_ptr peer); + }; + class TorrentManager + { + std::shared_ptr vfs; + Tesses::Framework::Filesystem::VFSPath defaultDirectory; + std::vector> downloading; + std::vector> seeding; + std::queue> torrentQueue; + int torrentCount; + std::atomic running=false; + public: + TorrentManager(std::shared_ptr 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(); + +} \ No newline at end of file diff --git a/include/TessesFramework/BitTorrent/TorrentStream.hpp b/include/TessesFramework/BitTorrent/TorrentStream.hpp new file mode 100644 index 0000000..921c2d6 --- /dev/null +++ b/include/TessesFramework/BitTorrent/TorrentStream.hpp @@ -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 vfs; + Tesses::Framework::Filesystem::VFSPath path; + uint64_t length; + TorrentFileStream(std::shared_ptr 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 mtxs; + public: + std::shared_ptr vfs; + Tesses::Framework::Filesystem::VFSPath path; + std::vector entries; + TorrentDirectoryStream(std::shared_ptr vfs, Tesses::Framework::Filesystem::VFSPath path,std::vector entries); + bool ReadBlockAt(uint64_t offset, uint8_t* data, size_t len); + void WriteBlockAt(uint64_t offset, const uint8_t* data, size_t len); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Common.hpp b/include/TessesFramework/Common.hpp index 019e63b..5c96d27 100644 --- a/include/TessesFramework/Common.hpp +++ b/include/TessesFramework/Common.hpp @@ -9,6 +9,7 @@ #include #include #include "Threading/Mutex.hpp" +#include namespace Tesses::Framework { @@ -88,8 +89,10 @@ namespace Tesses::Framework }; extern EventList OnItteraton; + std::optional 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 cb); + std::string TF_FileSize(uint64_t bytes, bool usesBin=true); #if defined(TESSESFRAMEWORK_LOGTOFILE) void TF_Log(std::string dataToLog); diff --git a/include/TessesFramework/Filesystem/LocalFS.hpp b/include/TessesFramework/Filesystem/LocalFS.hpp index fb1827c..b629711 100644 --- a/include/TessesFramework/Filesystem/LocalFS.hpp +++ b/include/TessesFramework/Filesystem/LocalFS.hpp @@ -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 LocalFS; } \ No newline at end of file diff --git a/include/TessesFramework/Filesystem/MountableFilesystem.hpp b/include/TessesFramework/Filesystem/MountableFilesystem.hpp index 22be923..03dafe9 100644 --- a/include/TessesFramework/Filesystem/MountableFilesystem.hpp +++ b/include/TessesFramework/Filesystem/MountableFilesystem.hpp @@ -57,5 +57,7 @@ namespace Tesses::Framework::Filesystem void Chmod(VFSPath path, uint32_t mode); + void Lock(VFSPath path); + void Unlock(VFSPath path); }; } \ No newline at end of file diff --git a/include/TessesFramework/Filesystem/SubdirFilesystem.hpp b/include/TessesFramework/Filesystem/SubdirFilesystem.hpp index 321ef88..3a14e40 100644 --- a/include/TessesFramework/Filesystem/SubdirFilesystem.hpp +++ b/include/TessesFramework/Filesystem/SubdirFilesystem.hpp @@ -41,5 +41,7 @@ namespace Tesses::Framework::Filesystem void Chmod(VFSPath path, uint32_t mode); + void Lock(VFSPath path); + void Unlock(VFSPath path); }; } \ No newline at end of file diff --git a/include/TessesFramework/Filesystem/TempFS.hpp b/include/TessesFramework/Filesystem/TempFS.hpp index 7246749..f4527e3 100644 --- a/include/TessesFramework/Filesystem/TempFS.hpp +++ b/include/TessesFramework/Filesystem/TempFS.hpp @@ -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(); }; } \ No newline at end of file diff --git a/include/TessesFramework/Filesystem/VFS.hpp b/include/TessesFramework/Filesystem/VFS.hpp index 71f9549..6793e1e 100644 --- a/include/TessesFramework/Filesystem/VFS.hpp +++ b/include/TessesFramework/Filesystem/VFS.hpp @@ -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(); diff --git a/include/TessesFramework/Filesystem/VFSFix.hpp b/include/TessesFramework/Filesystem/VFSFix.hpp index 17088a6..3c6f742 100644 --- a/include/TessesFramework/Filesystem/VFSFix.hpp +++ b/include/TessesFramework/Filesystem/VFSFix.hpp @@ -3,4 +3,8 @@ #undef DeleteFile #undef MoveFile #undef MoveDirectory - \ No newline at end of file +/* + Just in case +*/ +#undef Lock +#undef Unlock \ No newline at end of file diff --git a/include/TessesFramework/Http/HttpUtils.hpp b/include/TessesFramework/Http/HttpUtils.hpp index b2f6f5a..9380c4d 100644 --- a/include/TessesFramework/Http/HttpUtils.hpp +++ b/include/TessesFramework/Http/HttpUtils.hpp @@ -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& data); + static void BytesToHex(std::string& text,const std::vector& data); + static std::vector HexToBytes(const std::string& text); + static void HexToBytes(std::vector& 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); diff --git a/include/TessesFramework/Random.hpp b/include/TessesFramework/Random.hpp new file mode 100644 index 0000000..17a5e50 --- /dev/null +++ b/include/TessesFramework/Random.hpp @@ -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(); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Serialization/Bencode.hpp b/include/TessesFramework/Serialization/Bencode.hpp new file mode 100644 index 0000000..fd45d74 --- /dev/null +++ b/include/TessesFramework/Serialization/Bencode.hpp @@ -0,0 +1,59 @@ +#pragma once +#include +#include +#include +#include +#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; + + class BeArray { + public: + std::vector tokens; + }; + + class BeDictionary { + public: + std::vector> 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& data); + std::vector 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 strm,const BeToken& value); + static BeToken Load(std::shared_ptr 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 writer, BeToken tkn); + }; +} \ No newline at end of file diff --git a/include/TessesFramework/Serialization/Json.hpp b/include/TessesFramework/Serialization/Json.hpp index ec9ee84..cd74163 100644 --- a/include/TessesFramework/Serialization/Json.hpp +++ b/include/TessesFramework/Serialization/Json.hpp @@ -9,9 +9,7 @@ namespace Tesses::Framework::Serialization::Json { class JArray; class JObject; - class JUndefined { - public: - }; + using JUndefined = std::monostate; using JToken = std::variant; diff --git a/include/TessesFramework/Streams/NetworkStream.hpp b/include/TessesFramework/Streams/NetworkStream.hpp index 2819a5f..3d8e930 100644 --- a/include/TessesFramework/Streams/NetworkStream.hpp +++ b/include/TessesFramework/Streams/NetworkStream.hpp @@ -34,6 +34,7 @@ namespace Tesses::Framework::Streams bool success; bool endOfStream; public: + bool DataAvailable(int timeout=0); bool EndOfStream(); bool CanRead(); bool CanWrite(); diff --git a/include/TessesFramework/TessesFramework.hpp b/include/TessesFramework/TessesFramework.hpp index 0a6a0de..b4d4236 100644 --- a/include/TessesFramework/TessesFramework.hpp +++ b/include/TessesFramework/TessesFramework.hpp @@ -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" \ No newline at end of file +#include "Args.hpp" +#include "BitTorrent/TorrentManager.hpp" +#include "Random.hpp" \ No newline at end of file diff --git a/src/BitTorrent/TorrentFile.cpp b/src/BitTorrent/TorrentFile.cpp new file mode 100644 index 0000000..cf87cbe --- /dev/null +++ b/src/BitTorrent/TorrentFile.cpp @@ -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(this->fileListOrLength)) + return std::get(fileListOrLength); + else if(std::holds_alternative>(this->fileListOrLength)) { + auto& files= std::get>(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(this->fileListOrLength)) + { + this->info.tokens.emplace_back("length",std::get(this->fileListOrLength)); + } + else if(std::holds_alternative>(this->fileListOrLength)) + { + Serialization::Bencode::BeArray a; + for(auto& item : std::get>(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(o)) + this->name = std::get(o); + else + this->name = {}; + + o=dict.GetValue("piece length"); + if(std::holds_alternative(o)) + this->piece_length = std::get(o); + else + this->piece_length = DEFAULT_PIECE_LENGTH; + + o=dict.GetValue("private"); + if(std::holds_alternative(o)) + this->isPrivate = std::get(o); + else + this->isPrivate = 0; + + o=dict.GetValue("pieces"); + if(std::holds_alternative(o)) + this->pieces = std::get(o); + else + this->pieces = {}; + + + + o=dict.GetValue("files"); + if(std::holds_alternative(o)) + { + std::vector ents; + + for(auto& item : std::get(o).tokens) + { + if(std::holds_alternative(item)) + { + TorrentFileEntry fe; + fe.path.relative=true; + + auto& d2=std::get(item); + auto o2=d2.GetValue("length"); + if(std::holds_alternative(o2)) + { + fe.length = std::get(o2); + } + o2=d2.GetValue("path"); + if(std::holds_alternative(o2)) + { + auto& arr=std::get(o2); + for(auto& itm : arr.tokens) + { + if(std::holds_alternative(itm)) + { + fe.path.path.push_back(std::get(itm)); + } + } + } + ents.push_back(fe); + } + } + this->fileListOrLength = ents; + } + else { + o = dict.GetValue("length"); + if(std::holds_alternative(o)) + { + this->fileListOrLength = std::get(o); + } + else { + this->fileListOrLength = (int64_t)0; + } + } + + } + Serialization::Bencode::BeString TorrentFileInfo::GetInfoHash() + { + auto strm = std::make_shared(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(o)) + { + this->info.Load(std::get(o)); + } + o=tkn.GetValue("announce"); + if(std::holds_alternative(o)) + { + this->announce = std::get(o); + } + o=tkn.GetValue("announce-list"); + if(std::holds_alternative(o)) + { + auto& ls = std::get(o); + for(auto& item : ls.tokens) + { + if(std::holds_alternative(item)) + { + auto ls2 = std::get(item); + auto item2=ls2.tokens.at(0); + if(std::holds_alternative(item2)) + { + this->announce_list.push_back(std::get(item2)); + } + } + } + } + o=tkn.GetValue("creation date"); + if(std::holds_alternative(o)) + { + this->creation_date = std::get(o); + } + o=tkn.GetValue("comment"); + if(std::holds_alternative(o)) + { + this->comment = std::get(o); + } + o=tkn.GetValue("created by"); + if(std::holds_alternative(o)) + { + this->created_by = std::get(o); + } + o=tkn.GetValue("url-list"); + if(std::holds_alternative(o)) + { + auto& li =std::get(o); + for(auto& itm : li.tokens) + { + if(std::holds_alternative(itm)) + this->url_list.push_back(std::get(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 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(this->info.fileListOrLength)) + { + writer->WriteLine("\tIs Single File: true"); + writer->WriteLine("\tFile length: " + TF_FileSize((uint64_t)std::get(this->info.fileListOrLength))); + } + else if(std::holds_alternative>(this->info.fileListOrLength)) + { + writer->WriteLine("\tIs Single File: false"); + writer->WriteLine("\tFiles:"); + auto& files = std::get>(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 TorrentFileInfo::GetStreamFromFilesystem(std::shared_ptr vfs, const Tesses::Framework::Filesystem::VFSPath& path) + { + if(std::holds_alternative>(this->fileListOrLength)) + { + return std::make_shared(vfs,path / this->name, std::get>(this->fileListOrLength)); + } + else if(std::holds_alternative(this->fileListOrLength)) { + return std::make_shared(vfs,path / this->name, (uint64_t)std::get(this->fileListOrLength)); + } + return nullptr; + + } + + static void ParsePieces(TorrentFileInfo* fi,std::shared_ptr rwa, int64_t flength) + { + int64_t pieces = fi->pieces.data.size()/20; + std::vector 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 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 ents; + std::function 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 torrent_file_stream,const std::vector& trackers,const std::vector& webseeds, std::shared_ptr 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); + } +} \ No newline at end of file diff --git a/src/BitTorrent/TorrentManager.cpp b/src/BitTorrent/TorrentManager.cpp new file mode 100644 index 0000000..ec4bbe9 --- /dev/null +++ b/src/BitTorrent/TorrentManager.cpp @@ -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& 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(); + 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(ip,port,false,false,false); + + std::vector 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(bencode)) + { + auto& dict = std::get(bencode); + auto peers = dict.GetValue("peers"); + if(std::holds_alternative(peers)) + { + //this is compact + auto compactPeers=std::get(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(peers); + for(auto& item : normalPeers.tokens) + { + if(std::holds_alternative(item)) + { + auto& dict2=std::get(item); + auto ip = dict2.GetValue("ip"); + auto port = dict2.GetValue("port"); + if(std::holds_alternative(ip) && std::holds_alternative(port)) + { + addPeer((std::string)std::get(ip),(uint16_t)(uint64_t)std::get(port)); + } + + } + } + } + } + } + } + + void ActiveTorrent::process() + { + if(mustAnnounce()) + { + std::vector 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 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 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 msg((size_t)5); + BitConverter::FromUint32BE(msg[0],1); + msg[4] = 3; + + peer->stream->WriteBlock(msg.data(),msg.size()); + } + else { + std::vector 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 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 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 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 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 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; + } +} \ No newline at end of file diff --git a/src/BitTorrent/TorrentStream.cpp b/src/BitTorrent/TorrentStream.cpp new file mode 100644 index 0000000..6fcb7e0 --- /dev/null +++ b/src/BitTorrent/TorrentStream.cpp @@ -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 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 vfs, Tesses::Framework::Filesystem::VFSPath path,std::vector 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; + + + } + } +} \ No newline at end of file diff --git a/src/Filesystem/LocalFS.cpp b/src/Filesystem/LocalFS.cpp index 2dd1063..1a69103 100644 --- a/src/Filesystem/LocalFS.cpp +++ b/src/Filesystem/LocalFS.cpp @@ -230,7 +230,27 @@ namespace Tesses::Framework::Filesystem #endif } - std::shared_ptr 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 LocalFS = std::make_shared(); } // C:/Users/Jim/Joel diff --git a/src/Filesystem/MountableFilesystem.cpp b/src/Filesystem/MountableFilesystem.cpp index 4de3600..618311a 100644 --- a/src/Filesystem/MountableFilesystem.cpp +++ b/src/Filesystem/MountableFilesystem.cpp @@ -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 = 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 = root; + + GetFS(path, destRoot, destPath, vfs); + + if(vfs != nullptr) + vfs->Unlock(destPath); + } void MountableFilesystem::GetDate(VFSPath path, Date::DateTime& lastWrite, Date::DateTime& lastAccess) { diff --git a/src/Filesystem/SubdirFilesystem.cpp b/src/Filesystem/SubdirFilesystem.cpp index 50bc203..589628b 100644 --- a/src/Filesystem/SubdirFilesystem.cpp +++ b/src/Filesystem/SubdirFilesystem.cpp @@ -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)); diff --git a/src/Filesystem/TempFS.cpp b/src/Filesystem/TempFS.cpp index 6c99c24..388d190 100644 --- a/src/Filesystem/TempFS.cpp +++ b/src/Filesystem/TempFS.cpp @@ -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(); } + } \ No newline at end of file diff --git a/src/Filesystem/VFS.cpp b/src/Filesystem/VFS.cpp index 4af15a2..593854e 100644 --- a/src/Filesystem/VFS.cpp +++ b/src/Filesystem/VFS.cpp @@ -519,5 +519,13 @@ namespace Tesses::Framework::Filesystem } void VFS::Close() { + } + void VFS::Lock(VFSPath path) + { + + } + void VFS::Unlock(VFSPath path) + { + } } \ No newline at end of file diff --git a/src/Http/HttpUtils.cpp b/src/Http/HttpUtils.cpp index 200c38d..305ff12 100644 --- a/src/Http/HttpUtils.cpp +++ b/src/Http/HttpUtils.cpp @@ -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& data) + { + std::string text; + BytesToHex(text,data); + return text; + } + void HttpUtils::BytesToHex(std::string& text,const std::vector& 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 HttpUtils::HexToBytes(const std::string& text) + { + std::vector data; + HexToBytes(data,text); + return data; + } + void HttpUtils::HexToBytes(std::vector& 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; + } + } } diff --git a/src/Random.cpp b/src/Random.cpp new file mode 100644 index 0000000..b117e77 --- /dev/null +++ b/src/Random.cpp @@ -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); + } +} \ No newline at end of file diff --git a/src/Serialization/Bencode.cpp b/src/Serialization/Bencode.cpp new file mode 100644 index 0000000..c8129bb --- /dev/null +++ b/src/Serialization/Bencode.cpp @@ -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(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& 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 strm,const BeToken& value) + { + if(std::holds_alternative(value)) + { + auto& array = std::get(value); + strm->WriteByte((uint8_t)'l'); + for(auto& item : array.tokens) + { + Save(strm,item); + } + strm->WriteByte((uint8_t)'e'); + } + else if(std::holds_alternative(value)) + { + auto& dict = std::get(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(value)) + { + auto& str = std::get(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(value)) + { + int64_t val = std::get(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 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(key)) break; + if(!std::holds_alternative(key)) throw std::runtime_error("Key must be a string"); + auto value = Load(strm); + if(std::holds_alternative(key)) throw std::runtime_error("Incomplete dictionary entry"); + dict.tokens.emplace_back(std::get(key),value); + } + return dict; + } + break; + case 'l': + { + BeArray array; + while(true) + { + auto tkn = Load(strm); + if(std::holds_alternative(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(tkn)) + { + auto& dict = std::get(tkn); + Json::JObject o; + for(auto& itm : dict.tokens) + { + o.SetValue(itm.first,ToJson(itm.second)); + } + return o; + } + if(std::holds_alternative(tkn)) + { + auto& array = std::get(tkn); + Json::JArray a; + for(auto& itm : array.tokens) + { + a.Add(ToJson(itm)); + } + return a; + } + if(std::holds_alternative(tkn)) + { + return (std::string)std::get(tkn); + } + if(std::holds_alternative(tkn)) + { + return std::get(tkn); + } + return Json::JUndefined(); + } + void Bencode::Print(std::shared_ptr writer, BeToken tkn) + { + writer->WriteLine(Json::Json::Encode(ToJson(tkn),true)); + } +} \ No newline at end of file diff --git a/src/Streams/NetworkStream.cpp b/src/Streams/NetworkStream.cpp index c41cc5f..91560c2 100644 --- a/src/Streams/NetworkStream.cpp +++ b/src/Streams/NetworkStream.cpp @@ -15,6 +15,7 @@ using HttpUtils = Tesses::Framework::Http::HttpUtils; #include #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 #endif +#include } #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; diff --git a/src/TF_Init.cpp b/src/TF_Init.cpp index 6ef499a..45f2f31 100644 --- a/src/TF_Init.cpp +++ b/src/TF_Init.cpp @@ -182,7 +182,6 @@ namespace Tesses::Framework } void TF_Init() { - Tesses::Framework::Filesystem::LocalFS = std::make_shared(); #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 _argv0=std::nullopt; + + void TF_AllowPortable(std::string argv0) + { + _argv0 = argv0; + } + + std::optional TF_GetCommandName() + { + return _argv0; + } }