Push failed torrent code as backup before I remove it

This commit is contained in:
2026-01-07 11:03:33 -06:00
parent 8b6801e608
commit 1d5ba40ef0
36 changed files with 2192 additions and 7 deletions

View File

@@ -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
View 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
View 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;
}

View 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
View 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
View 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;
}

View File

@@ -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;
}

View 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");
};
}

View 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();
}

View 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);
};
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -57,5 +57,7 @@ namespace Tesses::Framework::Filesystem
void Chmod(VFSPath path, uint32_t mode);
void Lock(VFSPath path);
void Unlock(VFSPath path);
};
}

View File

@@ -41,5 +41,7 @@ namespace Tesses::Framework::Filesystem
void Chmod(VFSPath path, uint32_t mode);
void Lock(VFSPath path);
void Unlock(VFSPath path);
};
}

View File

@@ -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();
};
}

View File

@@ -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();

View File

@@ -3,4 +3,8 @@
#undef DeleteFile
#undef MoveFile
#undef MoveDirectory
/*
Just in case
*/
#undef Lock
#undef Unlock

View File

@@ -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);

View 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();
};
}

View 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);
};
}

View File

@@ -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>;

View File

@@ -34,6 +34,7 @@ namespace Tesses::Framework::Streams
bool success;
bool endOfStream;
public:
bool DataAvailable(int timeout=0);
bool EndOfStream();
bool CanRead();
bool CanWrite();

View File

@@ -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"

View 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);
}
}

View 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;
}
}

View 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;
}
}
}

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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));

View File

@@ -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();
}
}

View File

@@ -519,5 +519,13 @@ namespace Tesses::Framework::Filesystem
}
void VFS::Close() {
}
void VFS::Lock(VFSPath path)
{
}
void VFS::Unlock(VFSPath path)
{
}
}

View File

@@ -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
View 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);
}
}

View 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));
}
}

View File

@@ -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;

View File

@@ -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;
}
}