From f4709c97abcffed7ede80e38bfcb93517ac4094d Mon Sep 17 00:00:00 2001 From: Hizenberg Date: Mon, 8 Jul 2024 15:54:04 +0530 Subject: [PATCH] first commit --- .gitignore | 6 + CMakeLists.txt | 79 ++++ cmake/AddGitSubmodule.cmake | 39 ++ include/BProtocol.hpp | 57 +++ include/Network.hpp | 86 ++++ include/Torrent.hpp | 124 +++++ include/Tracker.hpp | 54 +++ include/debug.hpp | 71 +++ include/torrent_structure.hpp | 139 ++++++ src/BProtocol.cpp | 588 ++++++++++++++++++++++++ src/CMakeLists.txt | 37 ++ src/Network.cpp | 210 +++++++++ src/Torrent.cpp | 830 ++++++++++++++++++++++++++++++++++ src/Tracker.cpp | 182 ++++++++ src/torrent_structure.cpp | 81 ++++ test/CMakeLists.txt | 21 + test/test_main.cpp | 87 ++++ 17 files changed, 2691 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/AddGitSubmodule.cmake create mode 100644 include/BProtocol.hpp create mode 100644 include/Network.hpp create mode 100644 include/Torrent.hpp create mode 100644 include/Tracker.hpp create mode 100644 include/debug.hpp create mode 100644 include/torrent_structure.hpp create mode 100644 src/BProtocol.cpp create mode 100644 src/CMakeLists.txt create mode 100644 src/Network.cpp create mode 100644 src/Torrent.cpp create mode 100644 src/Tracker.cpp create mode 100644 src/torrent_structure.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/test_main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8ad1de --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*vsidx +*.opendb +.vs +out +CMakePresets.json +utils/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a804f01 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.4) + +#Setting Project +project(BIT-TORRENT-APP VERSION 1.0.0 LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +#Build Library and Executable path +#Scheme 1 +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/$") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/$") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/$") + +#Setting Directory +set(HEADER_DIR ${CMAKE_SOURCE_DIR}/include) +set(SOURCE_DIR ${CMAKE_SOURCE_DIR}/src) +set(EXTERNAL_DIR ${CMAKE_SOURCE_DIR}/utils) + + +#Setting Library and executable name +set(TORRENT_LIB torrent) +set(TEST_EXE test_main) + + +#Setting Fetch content function directory +include(FetchContent) +set(FETCHCONTENT_BASE_DIR "${CMAKE_SOURCE_DIR}/utils") + +set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") +include(AddGitSubmodule) + + +#OPENSSL LIBRARY +set(OPENSSL_USE_STATIC_LIBS TRUE) +find_package(OpenSSL) + +if( OPENSSL_FOUND ) + message(STATUS "OpenSSL include found: ${OPENSSL_INCLUDE_DIR}") + message(STATUS "OpenSSL crypto lib found: ${OPENSSL_CRYPTO_LIBRARY}") + message(STATUS "OpenSSL ssl lib found: ${OPENSSL_SSL_LIBRARY}") + message(STATUS "OpenSSL all lib found: ${OPENSSL_LIBRARIES}") +else() + message("System OpenSSL not found") +endif() + + +#eventpp +FetchContent_Declare( + eventpp + GIT_REPOSITORY https://github.com/wqking/eventpp.git + GIT_SHALLOW TRUE) +FetchContent_MakeAvailable(eventpp) + +#set(EVENTPP_HEADER_DIR +#${CMAKE_SOURCE_DIR}/utils/eventpp-src/include) + +#CURL LIBRARY + +# Find the CURL package +find_package(CURL REQUIRED) + +#get_target_property(CURL_INCLUDE_DIRS CURL::libcurl INTERFACE_INCLUDE_DIRECTORIES) +#get_target_property(CURL_LIBRARIES CURL::libcurl INTERFACE_LINK_LIBRARIES) +if( CURL_FOUND ) + message(STATUS "CURL include found: ${OPENSSL_INCLUDE_DIR}") + message(STATUS "CURL lib found: ${CURL_LIBRARIES}") +else() + message("System CURL not found") +endif() + + +#Adding subdirectory CMakeLists.txt +add_subdirectory(src) +add_subdirectory(test) + + +#add_subdirectory(utils/eventpp-src) diff --git a/cmake/AddGitSubmodule.cmake b/cmake/AddGitSubmodule.cmake new file mode 100644 index 0000000..8583cb5 --- /dev/null +++ b/cmake/AddGitSubmodule.cmake @@ -0,0 +1,39 @@ +function(add_git_submodule relative_dir lib link) + find_package(Git REQUIRED) + + set(FULL_DIR ${CMAKE_SOURCE_DIR}/${relative_dir}) + + #For intializing git if not already initialized. + if(NOT EXISTS "${CMAKE_SOURCE_DIR}/.git") + # If .git directory does not exist, execute git init + execute_process( + COMMAND git init + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE result + ) + + if(result) + message(FATAL_ERROR "Failed to initialize git repository.") + endif() + endif() + + #For adding the external deps if not added yet + if (NOT EXISTS ${FULL_DIR}/${lib}) + execute_process( + COMMAND ${GIT_EXECUTABLE} + submodule add -- ${link} + WORKING_DIRECTORY ${FULL_DIR} + ) + elseif (NOT EXISTS ${FULL_DIR}/CMakeLists.txt) + execute_process(COMMAND ${GIT_EXECUTABLE} + submodule update --init -- ${relative_dir} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) + endif() + + if (EXISTS ${FULL_DIR}/${lib}/CMakeLists.txt) + message("Submodule is CMake Project: ${FULL_DIR}/${lib}/CMakeLists.txt") + add_subdirectory(${FULL_DIR}/${lib}) + else() + message("Submodule is NO CMake Project: ${FULL_DIR}") + endif() +endfunction(add_git_submodule) \ No newline at end of file diff --git a/include/BProtocol.hpp b/include/BProtocol.hpp new file mode 100644 index 0000000..9c558ed --- /dev/null +++ b/include/BProtocol.hpp @@ -0,0 +1,57 @@ +#ifndef __B_PROTOCOL +#define __B_PROTOCOL + +#include "torrent_structure.hpp" +#include + + +/*************************B_Protocol literals*********************/ + + + +#define DictionaryStart 'd' +#define DictionaryEnd 'e' +#define ListStart 'l' +#define ListEnd 'e' +#define NumberStart 'i' +#define NumberEnd 'e' +#define ByteArrayDivider ':' + + +/*************************B_Protocol literals*********************/ + + +/*************************Decode Functions Implementation*************************/ + +/*Implemented*/ +long long _read_file(const std::string& torrentFile, char*& raw_data); + +/*Implemented*/ +bNode* b_decode(char*& raw_data, long long &size); + +/*Implemented*/ +long long int integer_decode(char*& raw_data, long long int &size); + +/*Implemented*/ +std::string string_decode(char*& raw_data, long long int& size); + + + +/*************************Decode Functions Implementation*************************/ + + +/*************************Encode Functions Implementation*************************/ + + +/* Always decrease the return size of e_data by 1, after processing*/ +int b_encode(bNode* tFile, char*& e_data, long long& size); + + + +/*************************Encode Functions Implementation*************************/ + + + +//void TorrentInfoDump(bNode* res, TorrentInfo* &torrentInfo); + +#endif \ No newline at end of file diff --git a/include/Network.hpp b/include/Network.hpp new file mode 100644 index 0000000..ee244d5 --- /dev/null +++ b/include/Network.hpp @@ -0,0 +1,86 @@ +#ifndef __NETWORK__ + + +#define __NETWORK__ + +//Networking Feature; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//178.17.4.234:51413 +//78.159.131.21:42363 +//136.54.172.182:50413 + +//91.103.253.141:1 + +//84.17.63.56:44320 + +//122.162.144.112:32927 + +//122.162.144.112:32927 helloworld.txt.to +//182.69.177.39::9001 + +#define DEST_PORT 9001 +#define SERVER_IP_ADDRESS "182.69.183.180" + + +#define BT_CHOKE 0 +#define BT_UNCHOKE 1 +#define BT_INTERSTED 2 +#define BT_NOT_INTERESTED 3 +#define BT_HAVE 4 +#define BT_BITFIELD 5 +#define BT_REQUEST 6 +#define BT_PIECE 7 +#define BT_CANCEL 8 + + +typedef struct { + char* bitfield; + size_t size; +}bt_bitfield_t; + +typedef struct { + int index; + int begin; + int length; +}bt_request_t; + +typedef struct { + int index; + int begin; + char piece[0]; +}bt_piece_t; + +typedef struct bt_msg { + int length; + unsigned char bt_type; + + union { + bt_bitfield_t bitfield; + int have; + bt_piece_t piece; + bt_request_t request; + bt_request_t cancel; + char data[0]; + }payload; + + +}bt_msg_t; + +void start_client_handshake(int& sockfd,char handshake[],Torrent *torrent); +char* buildHandShake(Torrent *torrent); + + + + +#endif \ No newline at end of file diff --git a/include/Torrent.hpp b/include/Torrent.hpp new file mode 100644 index 0000000..bc3998d --- /dev/null +++ b/include/Torrent.hpp @@ -0,0 +1,124 @@ +#ifndef __TORRENT_H +#define __TORRENT_H + +//To use POSIX TIMER library +#define __USE_POSIX199309 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define DIR_SEPARATOR '\\' +#else +#define DIR_SEPARATOR '/' +#endif + +class FileItem { +public: + + std::string Path; + long long int Size; + long long int Offset; + + std::string FormattedSize; + + FileItem(); + void set_FormattedSize(); + +}; + +class Tracker; +class Torrent { +public: + + eventpp::EventDispatcher &)> PeerListUpdated; + + long long int torrentFileSize; + + std::string Name; + int IsPrivate; + std::vector Files; + std::string FileDirectory(); + std::string DownloadDirectory; + + std::vector Trackers; + std::string Comment; + std::string CreatedBy; + time_t CreationDate; + std::string Encoding; + + int BlockSize; + int PieceSize; + long long int TotalSize(); + + std::string FormattedPieceSize(); + std::string FormattedTotalSize(); + + int PieceCount(); + + std::vector> PieceHashes; + std::vector IsPieceVerified; + std::vector> IsBlockAcquired; + + std::string VerifiedPiecesString(); + int VerifiedPieceCount(); + double VerifiedRatio(); + bool IsCompleted(); + bool IsStarted(); + + long long int Uploaded; + long long int Downloaded(); + long long int Left(); + + + char Infohash[20]; + std::string HexStringInfoHash(); //Not Implemented... + std::string UrlSafeStringInfoHash(); + + //Locking... + + Torrent(); + Torrent(std::string name, std::string location, + std::vector files, + std::vector trackers, + int pieceSize, char* pieceHashes, + int blockSize, + int isPrivate); + + void UpdateTrackers(TrackerEvent ev, + std::string id, int port); + void ResetTrackerLastRequest(); + + bNode* TorrentToBEncodingObject(Torrent torrent); + bDictionary* TorrentInfoToBEncodingObject(Torrent torrent); + Torrent* BEncodingObjectToTorrent(bNode* bencoding, + std::string name, std::string downloadPath); + //Encoding and creationdate left to implement... + + void Verify(int piece); + int GetPieceSize(int piece); + int GetBlockCount(int piece); + std::vector GetHash(int piece); + std::string ReadPiece(int piece); + std::string Read(long long int start, int length); + + Torrent* LoadFromFile(std::string filePath, std::string downloadPath); +}; + + +#endif \ No newline at end of file diff --git a/include/Tracker.hpp b/include/Tracker.hpp new file mode 100644 index 0000000..a44e86c --- /dev/null +++ b/include/Tracker.hpp @@ -0,0 +1,54 @@ +#ifndef __TRACKER_H +#define __TRACKER_H + +//To use POSIX TIMER library +#define __USE_POSIX199309 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef enum { + Started, + Paused, + Stopped +} TrackerEvent; + + +class Torrent; +class Tracker { +public: + + eventpp::EventDispatcher&)> PeerListUpdated; + + std::string Address; + struct timespec LastPeerRequested; + long long int PeerRequestInterval; //In seconds + //The Response from tracker... + std::string httpWebRequest; + + Tracker(std::string address); + + void Request(char* url); + + void Update(Torrent torrent, TrackerEvent ev, std::string id, + int port); + + + //To Implment response from tracker.... + + void Handle_response(); + void ResetLastRequest(); + + std::string returnTracker(); +}; +#endif \ No newline at end of file diff --git a/include/debug.hpp b/include/debug.hpp new file mode 100644 index 0000000..aba606e --- /dev/null +++ b/include/debug.hpp @@ -0,0 +1,71 @@ +#ifndef __DEBUG__ +#define __DEBUG__ +#include +#include +#include + +inline void error_msg(std::string msg,const char* function_name) { + std::cout << "Error:\n"; + std::cout << msg << '\n'; + std::cout << "Error in Function : " << + std::string(function_name) << '\n'; +} + + +inline void _dump_parsed_torrent_file_data(bNode* tFile) { + + b_type ty = tFile->type; + + switch (ty) { + case B_DICTIONARY: { + + int d_size = tFile->value.dict->count; + + bDictionaryNode* curr = tFile->value.dict->head; + + while (curr != NULL) { + + std::cout << curr->key << ' '; + + _dump_parsed_torrent_file_data(curr->value); + std::cout << '\n'; + + curr = curr->next; + } + break; + } + case B_LIST: { + + int l_size = tFile->value.list->count; + + bListNode* curr = tFile->value.list->head; + + while (curr != NULL) { + + _dump_parsed_torrent_file_data(curr->value); + std::cout << '\n'; + + curr = curr->next; + } + break; + } + case B_STRING: { + + std::cout << tFile->value.str << '\n'; + break; + } + case B_INTEGER: { + + std::cout << tFile->value.number << '\n'; + break; + } + case B_UNKNOWN: { + + error_msg("Data-type doesn't belong to .torrent file \ +syntax.\n\ +.torrent file is broken.\n", __FUNCTION__); + } + + } +} +#endif \ No newline at end of file diff --git a/include/torrent_structure.hpp b/include/torrent_structure.hpp new file mode 100644 index 0000000..a54874e --- /dev/null +++ b/include/torrent_structure.hpp @@ -0,0 +1,139 @@ +#ifndef __TORRENT_STRUCT_H +#define __TORRENT_STRUCT_H + + +#include +#include +#include +#include +#include + +/*************************** (.torrent) content structure for parsing*************************/ + +typedef enum { + + B_STRING, + B_INTEGER, + B_DICTIONARY, + B_LIST, + B_UNKNOWN + +}b_type; + +class bDictionary; +class bList; +class bListNode; +class bDictionaryNode; + +typedef bDictionary TorrentFile; + +class bNode { + +public: + + b_type type; + long long int size; + + class val { + public: + + std::string str; + long long number; + bDictionary* dict; + bList* list; //Pointer to array of pointer of bNode... + uint64_t start; + uint64_t end; + + val(); + }; + + val value; + + bNode(); +}; + +class bList { +public: + bListNode* head; + bListNode* tail; + int count; + + bList(); + + std::vector to_Stl_list(); +}; + +class bListNode { +public: + + bNode* value; + bListNode* next; + + bListNode(); +}; + + +class bDictionary { +public: + bDictionaryNode* head; + bDictionaryNode* tail; + int count; + + bDictionary(); + + std::map to_Stl_map(); +}; + + + +class bDictionaryNode { + +public: + std::string key; + bNode* value; + bDictionaryNode* next; + + bDictionaryNode(); +}; + +/*************************** (.torrent) content structure for parsing*************************/ + + + + +// +//class TorrentInfo { +// +//public: +// std::string announce; +// std::string comment; +// std::string created_by; +// long long int creation_date; +// std::string encoding; +// +// +// uint64_t start; +// uint64_t end; +// +// long long int length; +// std::string name; +// long long int blockSize; +// long long int pieceSize; +// +// +// char** pieceHashes; +// +// std::string DownloadDirectory; +// std::string FileDirectory; +// +// //std::listFiles; +// +// +// +// +//}; + + + + +#endif \ No newline at end of file diff --git a/src/BProtocol.cpp b/src/BProtocol.cpp new file mode 100644 index 0000000..3c3955f --- /dev/null +++ b/src/BProtocol.cpp @@ -0,0 +1,588 @@ +#include +#include +#include +#include + +#include + + +/*************************Decode Functions Implementation*************************/ + +long long _read_file(const std::string& torrentFile, char*& raw_data) { + + long long size; + + std::ifstream t_file(torrentFile, std::ios::in | std::ios::binary | std::ios::ate); + + if (t_file.is_open()) { + + size = (std::streampos)t_file.tellg(); + + //std::cout << "Size of file : " << size << " bytes\n"; + + raw_data = new char[size+1LL]; + t_file.seekg(0, std::ios::beg); + + t_file.read(raw_data, size); + + t_file.close(); + + std::cout << "Raw Data reading from torrent file complete!!!!\n"; + + return size; + } + else { + + std::cout << "Unable to open torrent file to read raw data\n"; + } + + return -1; +} + +long long int integer_decode(char*& raw_data, long long int &size) +{ + long long int res = 0; + + while ((*raw_data) != 'e') + { + res = res * 10 + ((*raw_data) - 48); + raw_data++; + size--; + } + + //now I am at e , since it is useless lets increment this too; + raw_data++; + size--; + + return res; //retrun the converted data; + + +} + +std::string string_decode(char*& raw_data, long long int& size) { + //to parse the string we first need to extract the length + + int length = 0; + while ((*(raw_data)) != ':') + { + length = length * 10 + ((*raw_data) - 48); + //std::cout << ((*raw_data)); + (raw_data)++; + size--; + } + + //std::cout << "Length:" << length << std::endl; + + + //now I have length; + (raw_data)++; //skipping the ":" + size--; + std::string value = ""; + + while (length--) + { + value = value + (*raw_data); + (raw_data)++; + size--; + } + + //(raw_data)++; + //size--; + + return value; +} + +bNode* b_decode(char* &raw_data, long long &size) { + + if (size == 0) { + + return NULL; + } + + bNode* curNode = NULL; + bNode* head; + //bNode* value = NULL; + + switch (*raw_data) { + + case 'd': { + + + curNode = new bNode; + curNode->type = B_DICTIONARY; + curNode->value.dict = new bDictionary; + + + //for skipping 'd' character... + + (raw_data)++; + size--; + //now to decode the value , but value can be anything + + while ((*raw_data) != 'e') { + /* Parsing key */ + std::string key = string_decode((raw_data), size); + + bDictionaryNode* res = new bDictionaryNode; + + res->key = key; + + res->value = b_decode(raw_data, size); + + if (curNode->value.dict->count == 0) { + curNode->value.dict->head = res; + curNode->value.dict->tail = res; + curNode->value.dict->count++; + continue; + } + + curNode->value.dict->tail->next = res; + curNode->value.dict->tail = curNode->value.dict->tail->next; + curNode->value.dict->count++; + + } + + //for skipping 'e' + + + (raw_data)++; + size--; + + return curNode; + + break; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + std::string res = string_decode((raw_data), size); + curNode = new bNode; + curNode->type = B_STRING; + curNode->value.str = res; + + + + return curNode; + break; + } + case 'l': { + + //for skipping 'l' character... + (raw_data)++; + size--; + + curNode = new bNode; + curNode->type = B_LIST; + curNode->value.list = new bList; + + while ((*raw_data) != 'e') { + bListNode* res = new bListNode; + + res->value = b_decode(raw_data, size); + + if (curNode->value.list->count == 0) { + curNode->value.list->head = res; + curNode->value.list->tail = res; + curNode->value.list->count++; + continue; + } + + curNode->value.list->tail->next = res; + curNode->value.list->tail = curNode->value.list->tail->next; + curNode->value.list->count++; + + } + + //Escaping the 'e' character... + raw_data++; + size--; + + return curNode; + break; + } + case 'i': { + //now I have encountered an i; + raw_data++; //now I have skipped i; + size--; + long long int integer = integer_decode(raw_data, size); + + curNode = new bNode; + curNode->type = B_INTEGER; + curNode->value.number = integer; + + + return curNode; + + break; + } + default: { + error_msg("data-type found is unknow while parsing.\n", + __FUNCTION__); + break; + } + + + } + + return curNode; +} + +/*************************Encode Functions Implementation*************************/ + + +void encode_integer(char*& e_data, long long& size, long long num) { + + + std::string res = ""; + + res += 'i'; + res += std::to_string(num); + res += 'e'; + + long long sz = (long long)res.size(); + + for (long long i = 0; i < sz; i++) { + e_data[size++] = res[i]; + } + + return; +} + +void encode_string(char*& e_data, long long& size, const std::string& str) { + + long long len = (long long)str.size(); + + std::string res = ""; + + + res += std::to_string(len); + res += ':'; + res += str; + + long long sz = (long long)res.size(); + + for (long long i = 0; i < sz; i++) { + e_data[size++] = res[i]; + } + + return; +} + +int b_encode(bNode* tFile, char*& e_data, long long& size) { + + b_type ty = tFile->type; + switch (ty) { + + case B_DICTIONARY: { + + e_data[size] = 'd'; + size++; + + bDictionaryNode* curr = tFile->value.dict->head; + std::string key; + + while (curr != NULL) { + + key = curr->key; + encode_string(e_data,size, key); + + b_encode(curr->value, e_data, size); + curr = curr->next; + + } + + e_data[size] = 'e'; + size++; + + return 1; + break; + } + case B_LIST: { + + e_data[size] = 'l'; + size++; + + bListNode* curr = tFile->value.list->head; + + while (curr != NULL) { + + b_encode(curr->value, e_data, size); + curr = curr->next; + + } + + e_data[size] = 'e'; + size++; + + return 1; + break; + } + case B_STRING: { + + encode_string(e_data, size, tFile->value.str); + + return 1; + break; + } + case B_INTEGER: { + + encode_integer(e_data, size, tFile->value.number); + + return 1; + + break; + } + default: { + + return -1; + break; + } + } + + + return -1; +} + + +/*************************Encode Functions Implementation*************************/ + + +//void TorrentInfoDump(bNode* res, TorrentInfo*& torrentInfo) +//{ +// b_type ty = res->type; +// switch (ty) +// { +// case B_DICTIONARY: +// { +// bDictionaryNode* curr = res->value.dict->head; +// std::string key; +// +// while (curr != NULL) +// { +// //announce would always be a string!!! +// key = curr->key; +// +// if (key == "info") +// { +// torrentInfo->start = curr->value->value.start; +// torrentInfo->end = curr->value->value.end; +// } +// +// if (key == "announce") { +// torrentInfo->announce = curr->value->value.str; +// } +// if (key == "comment") +// { +// torrentInfo->comment = curr->value->value.str; +// } +// +// if (key == "created by") +// { +// torrentInfo->created_by = curr->value->value.str; +// } +// +// if (key == "creation date") +// { +// torrentInfo->creation_date = curr->value->value.number; +// +// } +// +// if (key == "encoding") +// { +// torrentInfo->encoding = curr->value->value.str; +// } +// +// if (key == "piece length") +// { +// torrentInfo->pieceSize = curr->value->value.number; +// } +// +// if (key == "length") +// { +// torrentInfo->length = curr->value->value.number; +// } +// +// if (key == "pieces") +// { +// long long int num_pieces = (torrentInfo->length / torrentInfo->pieceSize) + 1; +// +// torrentInfo->pieceHashes = new char* [num_pieces]; +// +// std::cout << "NumPieces:" << num_pieces << "\n"; +// +// for (long long int i = 0; i < num_pieces; i++) +// { +// torrentInfo->pieceHashes[i] = new char[20]; +// char* src = ((char*)(&curr->value->value.str) + (20 * i)); +// for (long long int j = 0; j < 20; j++) { +// torrentInfo->pieceHashes[i][j] = curr->value->value.str[j + 20 * i]; +// } +// +// } +// +// } +// +// +// // if(key=="") +// +// +// +// +// TorrentInfoDump(curr->value, torrentInfo); +// curr = curr->next; +// } +// return; +// +// } +// case B_STRING: +// { +// +// return; +// } +// +// case B_LIST: { +// bListNode* curr = res->value.list->head; +// while (curr != NULL) +// { +// //torrentInfo->Files=curr->value->value. +// TorrentInfoDump(curr->value, torrentInfo); +// curr = curr->next; +// } +// return; +// } +// +// +// +// default: { +// return; +// } +// +// +// } +//} +// + +// +//void TorrentInfoDump(bNode* res, TorrentInfo*& torrentInfo) +//{ +// b_type ty = res->type; +// switch (ty) +// { +// case B_DICTIONARY: +// { +// bDictionaryNode* curr = res->value.dict->head; +// std::string key; +// +// while (curr != NULL) +// { +// //announce would always be a string!!! +// key = curr->key; +// +// if (key == "info") +// { +// torrentInfo->start = curr->value->value.start; +// torrentInfo->end = curr->value->value.end; +// } +// +// if (key == "announce") { +// torrentInfo->announce = curr->value->value.str; +// } +// if (key == "comment") +// { +// torrentInfo->comment = curr->value->value.str; +// } +// +// if (key == "created by") +// { +// torrentInfo->created_by = curr->value->value.str; +// } +// +// if (key == "creation date") +// { +// torrentInfo->creation_date = curr->value->value.number; +// +// } +// +// if (key == "encoding") +// { +// torrentInfo->encoding = curr->value->value.str; +// } +// +// if (key == "piece length") +// { +// torrentInfo->pieceSize = curr->value->value.number; +// } +// +// if (key == "length") +// { +// torrentInfo->length = curr->value->value.number; +// } +// +// if (key == "pieces") +// { +// long long int num_pieces = (torrentInfo->length / torrentInfo->pieceSize) + 1; +// +// torrentInfo->pieceHashes = new char* [num_pieces]; +// +// std::cout << "NumPieces:" << num_pieces << "\n"; +// +// for (long long int i = 0; i < num_pieces; i++) +// { +// torrentInfo->pieceHashes[i] = new char[20]; +// char* src = ((char*)(&curr->value->value.str) + (20 * i)); +// for (long long int j = 0; j < 20; j++) { +// torrentInfo->pieceHashes[i][j] = curr->value->value.str[j + 20 * i]; +// } +// +// } +// +// } +// +// +// // if(key=="") +// +// +// +// +// TorrentInfoDump(curr->value, torrentInfo); +// curr = curr->next; +// } +// return; +// +// } +// case B_STRING: +// { +// +// return; +// } +// +// case B_LIST: { +// bListNode* curr = res->value.list->head; +// while (curr != NULL) +// { +// //torrentInfo->Files=curr->value->value. +// TorrentInfoDump(curr->value, torrentInfo); +// curr = curr->next; +// } +// return; +// } +// +// +// +// default: { +// return; +// } +// +// +// } +//} +// +// \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..a8ffc8f --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,37 @@ +set(TORRENT_STRUCTURE_SRC + "torrent_structure.cpp") + +set(BPROTOCOL_SRC + "BProtocol.cpp") + +set(TORRENT_SRC + "Torrent.cpp") + +set(TRACKER_SRC + "Tracker.cpp") + +set(NETWORK_SRC + "Network.cpp") + + + +add_library(${TORRENT_LIB} STATIC + ${TORRENT_STRUCTURE_SRC} + ${BPROTOCOL_SRC} + ${TORRENT_SRC} + ${TRACKER_SRC} + ${NETWORK_SRC}) + +target_link_libraries(${TORRENT_LIB} PUBLIC + ${OPENSSL_LIBRARIES} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${CURL_LIBRARIES} + eventpp::eventpp) + + +target_include_directories(${TORRENT_LIB} PUBLIC + ${HEADER_DIR} + ${OPENSSL_INCLUDE_DIR} + ${CURL_INCLUDE_DIRS} + ${EVENTPP_HEADER_DIR}) \ No newline at end of file diff --git a/src/Network.cpp b/src/Network.cpp new file mode 100644 index 0000000..0346a74 --- /dev/null +++ b/src/Network.cpp @@ -0,0 +1,210 @@ +#include "Network.hpp" +#include +#include + + +/*HANDHSAKE BUILD*/ +char* buildHandShake(Torrent* torrent) +{ + + //handshake: [wikitheory.org] + + const char* pstr = "BitTorrent protocol"; + unsigned char pstrlen = strlen(pstr); + const char reserved[8] = { 0 }; + const char* g_local_peer_id = "-MY0001-123456654321"; + + size_t bufflen = 1 + pstrlen + sizeof(reserved) + 20 + strlen(g_local_peer_id); + + off_t off = 0; + char* buff = (char*)malloc(bufflen); + + buff[0] = pstrlen; + off++; + + memcpy(buff + off, pstr, pstrlen); + off += pstrlen; + assert(off == 20); + + memcpy(buff + off, reserved, sizeof(reserved)); + off += sizeof(reserved); + assert(off == 28); + + memcpy(buff + off, torrent->Infohash, 20); + + off += 20; + memcpy(buff + off, g_local_peer_id, strlen(g_local_peer_id)); + + + + + return buff; + + +} + +int sendData(int sockfd,char handshake[],ssize_t len) +{ + ssize_t tot_sent = 0; + // ssize_t len = 68; + + while (tot_sent < len) { + ssize_t sent = send(sockfd, handshake, len - tot_sent, 0); + if (sent < 0) + + { + std::cout << "No data sent\n"; + return -1; + } + + std::cout << "Sent:" << sent << '\n'; + + tot_sent += sent; + handshake += sent; + } + return 1; +} + + + +int receiveData(int sockfd, ssize_t len, char* buff, int flag) +{ + unsigned tot_recv = 0; + ssize_t nb=0; + // char buff[len]; + if (len == 0) { + std::cout << "No data available" << '\n'; + return -1; + } + + do { + assert(len - tot_recv > 0); + nb = recv(sockfd, buff + tot_recv, len - tot_recv, 0); + if (nb <= 0) { + std::cout << "No data received" << '\n'; + return -1; + } + std::cout << "Received in chunks :" << nb << "\n"; + + tot_recv += nb; + + } while (nb > 0 && tot_recv < len); + + if (tot_recv == len) { + std::cout << "Received All" << '\n'; + return 1; + // return; + } + + return -1; +} + + +void start_client_handshake(int& sockfd,char handshake[],Torrent* torrent) +{ + struct sockaddr_in server_addr; + + char buffer[68]; + // Create a socket + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("Socket creation error"); + return; + } + + // Initialize server address structure + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(DEST_PORT); + + // Convert IP address from text to binary form + if (inet_pton(AF_INET, SERVER_IP_ADDRESS, &server_addr.sin_addr) <= 0) { + perror("Invalid address/ Address not supported"); + return; + } + + // Connect to the server + if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + perror("Connection failed"); + return; + } + + // Send the handshake message + + + assert(sendData(sockfd, handshake, 68) == 1); + + printf("Handshake message sent successfully\n"); + + + char* buf = (char*)malloc(68); + int data = receiveData(sockfd, 68,buf,0); + assert(data==1); + + std::cout << "Received Handshake"<<'\n'; + + //Receive BitField + + memset(buffer, 0, sizeof(buffer)); + bt_msg_t Recvmsg; + + int n_pieces = torrent->PieceCount(); + int sizeRmsg = sizeof(Recvmsg.length) + sizeof(Recvmsg.bt_type) + (sizeof(char) * n_pieces); + + std::cout << "sizeRmsg:" << sizeRmsg << '\n'; + + // Interested Message + bt_msg_t Sendmsg; + Sendmsg.length = sizeof(Sendmsg.bt_type); + Sendmsg.bt_type = BT_INTERSTED; + assert(sendData(sockfd, (char*)&Sendmsg, sizeof(Sendmsg)) == 1); + + + + //Piece-Request + Sendmsg; + Sendmsg.bt_type = BT_REQUEST; + Sendmsg.payload.request.begin = 0; + Sendmsg.payload.request.index = 0; + Sendmsg.payload.request.length = 4; + Sendmsg.length = sizeof(Sendmsg.bt_type)+sizeof(Sendmsg.payload); + + + assert(sendData(sockfd, (char*)&Sendmsg, sizeof(Sendmsg)) == 1); + //PieceFetch + do { + receiveData(sockfd, 8, buffer, 1); + memcpy(&Recvmsg, buffer, sizeof(Sendmsg)); + memset(buffer, 0, sizeof(buffer)); + } while (Recvmsg.bt_type!=BT_PIECE); + + std::cout << "\nTYPE:" << (int)Recvmsg.bt_type; + std::cout <<"\nsize:" << Recvmsg.payload.bitfield.size; + + // Close the socket + close(sockfd); +} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Torrent.cpp b/src/Torrent.cpp new file mode 100644 index 0000000..a45551b --- /dev/null +++ b/src/Torrent.cpp @@ -0,0 +1,830 @@ +#include +#include +#include +#include +#include +#include +#include + + +/**********************************************************/ + +static std::string encodeURl(std::string Info) +{ + //Info Hash is 20byte long , so no stress of extra character at end + std::string encoded = ""; + + encoded += "%"; + for (int i = 0; i < Info.size();) + { + if (i + 1 < Info.size()) + { + encoded += Info[i]; + encoded += Info[i + 1]; + } + encoded += "%"; + i += 2; + } + encoded.pop_back(); + return encoded; +} + +/**********************************************************/ + +static std::vector sha1(const char* input, + long long int size); + +FileItem::FileItem() { + + this->Path = ""; + this->Size = -1; + this->Offset = -1; + this->FormattedSize = ""; +} + + + +void FileItem::set_FormattedSize() { + this->FormattedSize = std::to_string(Size); +} + +std::string Torrent::FileDirectory() { + return this->Files.size() > 1 ? this->Name + + DIR_SEPARATOR : ""; +} + +long long int Torrent::TotalSize() { + long long int totalSize = 0; + + + for (int i = 0; i < (int)Files.size(); i++) { + totalSize += Files[i].Size; + } + + return totalSize; +} + +std::string Torrent::FormattedPieceSize() { + return std::to_string(PieceSize); +} + +std::string Torrent::FormattedTotalSize() { + return std::to_string(this->TotalSize()); +} + +int Torrent::PieceCount() { + return (int)this->PieceHashes.size(); +} + +std::string Torrent::VerifiedPiecesString() { + std::string verificationString = ""; + + for (int i = 0; i < IsPieceVerified.size(); i++) { + verificationString += IsPieceVerified[i] ? "1" : "0"; + } + + return verificationString; +} + +int Torrent::VerifiedPieceCount() { + int count = 0; + + for (int i = 0; i < (int)IsPieceVerified.size(); i++) { + count += IsPieceVerified[i] ? 1 : 0; + } + return count; +} + +double Torrent::VerifiedRatio() { + return this->VerifiedPieceCount() / (double)this->PieceCount(); +} + +bool Torrent::IsCompleted() { + return this->VerifiedPieceCount() == this->PieceCount(); +} + +bool Torrent::IsStarted() { + return this->VerifiedPieceCount() > 0; +} + + +long long int Torrent::Downloaded() { + return this->PieceSize * this->VerifiedPieceCount(); +} + +long long int Torrent::Left() { + return this->TotalSize() - this->Downloaded(); +} + + +//To encode string into Url safe string. +static std::string url_encode(const std::string& decoded) +{ + const auto encoded_value = curl_easy_escape(nullptr, decoded.c_str(), static_cast(decoded.length())); + std::string result(encoded_value); + curl_free(encoded_value); + return result; +} + +//To decode string into Url safe string. +static std::string url_decode(const std::string& encoded) +{ + int output_length; + const auto decoded_value = curl_easy_unescape(nullptr, encoded.c_str(), static_cast(encoded.length()), &output_length); + std::string result(decoded_value, output_length); + curl_free(decoded_value); + return result; +} + +std::string Torrent::HexStringInfoHash() { + std::ostringstream oss; + for (unsigned char byte : this->Infohash) { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); + } + return oss.str(); +} + +std::string Torrent::UrlSafeStringInfoHash() { + std::string infoHash(this->HexStringInfoHash()); + infoHash = url_encode(infoHash); + return encodeURl(infoHash); +} + +Torrent::Torrent() { + + +} + +Torrent::Torrent(std::string name, std::string location, + std::vector files, + std::vector trackers, + int pieceSize, char* pieceHashes = NULL, + int blockSize = 16384, + int isPrivate = -1) { + + this->Name = name; + this->DownloadDirectory = location; + this->Files = files; + + + //locking.... + + this->Trackers.resize(trackers.size()); + + if ((int)trackers.size() > 0) { + Tracker* tracker = NULL; + int i = 0; + for (std::string url : trackers) { + tracker = new Tracker(url); + tracker->PeerListUpdated.appendListener("peerlist", + [this](std::vector& peerList) { + this->PeerListUpdated.dispatch("peerlist", + peerList); + }); + this->Trackers[i] = tracker; + i++; + + tracker = NULL; + } + } + + + this->PieceSize = pieceSize; + this->BlockSize = blockSize; + this->IsPrivate = isPrivate; + + int count = (int)(std::ceil(this->TotalSize() / + (double)this->PieceSize)); + + PieceHashes.resize(count); + IsPieceVerified.resize(count); + IsBlockAcquired.resize(count); + + for (int i = 0; i < this->PieceCount(); i++) { + IsBlockAcquired[i].resize(this->GetBlockCount(i)); + } + + if (pieceHashes == NULL) { + + //this is a new torrent so we have to create the hashes form + //the files... + for (int i = 0; i < this->PieceCount(); i++) { + this->PieceHashes[i] = this->GetHash(i); + } + + } + else { + + for (int i = 0; i < this->PieceCount(); i++) { + this->PieceHashes[i].resize(20); + + for (int j = 20 * i; j < 20 + 20 * i; j++) { + this->PieceHashes[i][j-20*i] = *(pieceHashes + j); + } + } + } + + bDictionary* dict = this->TorrentInfoToBEncodingObject(*this); + bNode* info = new bNode(); + info->type = B_DICTIONARY; + info->value.dict = dict; + + //_dump_parsed_torrent_file_data(info); + + char* buf = new char[1048576]; //1MB .torrent file only + long long int size = 0; + + b_encode(info, buf, size); + char* buffer = new char[size + 1]; + + for (int i = 0; i <=size; i++) { + buffer[i] = buf[i]; + } + + delete[] buf; + + std::vector hash = sha1(buffer,size); + + for (int i = 0; i < 20; i++) { + this->Infohash[i] = hash[i]; + } + + //for (int i = 0; i < this->PieceCount(); i++) { + // this->Verify(i); + //} +} + +static bool isHashEqual(const std::vector& hash1, + const std::vector& hash2) { + + assert(hash1.size() == hash2.size()); + + for (int i = 0; i < hash1.size(); i++) { + if (hash1[i] != hash2[i]) + return false; + } + + return true; +} + +static bool areAllBlockPieceAcquired(std::vector& + IsBlockAcquired) { + + for (int i = 0; i < IsBlockAcquired.size(); i++) { + if (!IsBlockAcquired[i]) { + return false; + } + } + + return true; +} + +void Torrent::Verify(int piece) { + + std::vector hash = GetHash(piece); + + bool isVerfied = (hash.size() > 0 && + isHashEqual(hash, this->PieceHashes[piece])); + + if (isVerfied) { + this->IsPieceVerified[piece] = true; + + for (int i = 0; i < this->IsBlockAcquired[piece].size(); i++) { + this->IsBlockAcquired[piece][i] = true; + } + + return; + } + + IsPieceVerified[piece] = false; + + //reload the entire piece.. + if (areAllBlockPieceAcquired(this->IsBlockAcquired[piece])) { + + for (int j = 0; j < this->IsBlockAcquired[piece].size(); j++) { + this->IsBlockAcquired[piece][j] = false; + } + } +} + +int Torrent::GetPieceSize(int piece) { + + if (piece == this->PieceCount() - 1) { + + int remainder = this->TotalSize() % this->PieceSize; + + if( remainder != 0 ) + return remainder; + } + + return this->PieceSize; +} + +int Torrent::GetBlockCount(int piece) { + + return (int)(std::ceil(this->GetPieceSize(piece) / + (double)this->BlockSize)); +} + +static std::vector sha1(const char* input, + long long int size) { + // Buffer to hold the hash + std::vector hash(SHA_DIGEST_LENGTH); + + // Perform the SHA1 hash + SHA1((const unsigned char*)input, size, hash.data()); + + return hash; +} + +std::vector Torrent::GetHash(int piece) { + + std::string data = this->ReadPiece(piece); + + if (data == "") { + error_msg("Piece Reading went wrong!", __FUNCTION__); + return {}; + } + + return sha1((const char*)data.c_str(), + (long long int)data.size()); +} + +std::string Torrent::ReadPiece(int piece) { + return Read(1LL * piece * this->PieceSize, this->GetPieceSize(piece)); +} + +std::string Torrent::Read(long long int start, int length) { + + long long int end = start + length; + + std::string resbuffer; + char* buffer = new char[length]; + + for (int i = 0; i < (int)Files.size(); i++) + { + if ((start < Files[i].Offset && end < Files[i].Offset) || + (start > Files[i].Offset + Files[i].Size && end > Files[i].Offset + Files[i].Size)) + continue; + + std::string filePath = DownloadDirectory + + DIR_SEPARATOR + + FileDirectory() + Files[i].Path; + + if (!std::filesystem::exists(filePath)) { + std::cout << "filePath: " << filePath << "\n"; + error_msg("File don't exist.\ +Something went wrong!", __FUNCTION__); + return ""; + } + + + long long int fstart = std::max(0LL, + start - Files[i].Offset); + long long int fend = std::min(end - + Files[i].Offset, Files[i].Size); + int flength = fend - fstart; + int bStart = std::max(0LL, + Files[i].Offset - start); + + std::ifstream file(filePath, std::ios::in); + + if (file.is_open()) { + file.seekg(fstart, std::ios::beg); + file.read(buffer, flength); + file.close(); + } + else { + error_msg("File didn't open", __FUNCTION__); + return ""; + } + + } + + resbuffer.assign(buffer); + delete[] buffer; + + return resbuffer; +} + + +bNode* Torrent::TorrentToBEncodingObject(Torrent torrent) { + + bNode* dict = new bNode(); + dict->type = B_DICTIONARY; + dict->value.dict = new bDictionary(); + + if ((int)torrent.Trackers.size() == 0) { + error_msg("Torrent class doesn't contain\ +any tracker right now!!", __FUNCTION__); + return NULL; + } + + bDictionaryNode* dictNode = NULL; + dictNode = new bDictionaryNode(); + dictNode->key = "announce"; + dictNode->value = new bNode(); + dictNode->value->type = B_STRING; + dictNode->value->value.str = Trackers[0]->Address; + + dict->value.dict->head = + dict->value.dict->tail = dictNode; + dict->value.dict->count++; + dictNode = NULL; + + if ((int)torrent.Trackers.size() > 1) { + for (int i = 1; i < (int)torrent.Trackers.size(); i++) { + dictNode = new bDictionaryNode(); + dictNode->key = "announce"; + dictNode->value = new bNode(); + dictNode->value->type = B_STRING; + dictNode->value->value.str = Trackers[i]->Address; + + dict->value.dict->tail->next = dictNode; + dict->value.dict->tail = + dict->value.dict->tail->next; + dict->value.dict->count++; + dictNode = NULL; + } + } + + dictNode = new bDictionaryNode(); + dictNode->key = "comment"; + dictNode->value = new bNode(); + dictNode->value->type = B_STRING; + dictNode->value->value.str = torrent.Comment; + + dict->value.dict->tail->next = dictNode; + dict->value.dict->tail = + dict->value.dict->tail->next; + dict->value.dict->count++; + dictNode = NULL; + + dictNode = new bDictionaryNode(); + dictNode->key = "created by"; + dictNode->value = new bNode(); + dictNode->value->type = B_STRING; + dictNode->value->value.str = torrent.CreatedBy; + + dict->value.dict->tail->next = dictNode; + dict->value.dict->tail = + dict->value.dict->tail->next; + dict->value.dict->count++; + dictNode = NULL; + + dictNode = new bDictionaryNode(); + dictNode->key = "creation date"; + dictNode->value = new bNode(); + dictNode->value->type = B_STRING; + dictNode->value->value.str = torrent.CreationDate; + + dict->value.dict->tail->next = dictNode; + dict->value.dict->tail = + dict->value.dict->tail->next; + dict->value.dict->count++; + dictNode = NULL; + + + dictNode = new bDictionaryNode(); + dictNode->key = "encoding"; + dictNode->value = new bNode(); + dictNode->value->type = B_STRING; + dictNode->value->value.str = torrent.Encoding; + + dict->value.dict->tail->next = dictNode; + dict->value.dict->tail = + dict->value.dict->tail->next; + dict->value.dict->count++; + dictNode = NULL; + + dictNode = new bDictionaryNode(); + dictNode->key = "info"; + dictNode->value = new bNode(); + dictNode->value->type = B_DICTIONARY; + dictNode->value->value.dict = + this->TorrentInfoToBEncodingObject(torrent); + + dict->value.dict->tail->next = dictNode; + dict->value.dict->tail = + dict->value.dict->tail->next; + dict->value.dict->count++; + dictNode = NULL; + + return dict; +} + +bDictionary* Torrent::TorrentInfoToBEncodingObject(Torrent torrent) { + + bDictionary* dict = new bDictionary(); + + bDictionaryNode* node = new bDictionaryNode(); + + if (torrent.Files.size() == 0) { + error_msg("No file mentioned!", __FUNCTION__); + return NULL; + } + else if (torrent.Files.size() == 1) { + + //node = new bDictionaryNode(); + node->key = "length"; + node->value = new bNode(); + node->value->type = B_INTEGER; + node->value->value.number = torrent.Files[0].Size; + + dict->head = dict->tail = node; + dict->count++; + node = NULL; + + + + + node = new bDictionaryNode(); + node->key = "name"; + node->value = new bNode(); + node->value->type = B_STRING; + node->value->value.str = torrent.Files[0].Path; + + dict->tail->next = node; + dict->tail = dict->tail->next; + dict->count++; + node = NULL; + + } + else { + + node = new bDictionaryNode(); + node->key = "files"; + node->value = new bNode(); + node->value->type = B_LIST; + bList* list = new bList(); + + bListNode* l_node = NULL; + bDictionaryNode* n_node = NULL; + + for (int i = 0; i < torrent.Files.size(); i++) { + l_node = new bListNode(); + l_node->value = new bNode(); + l_node->value->type = B_DICTIONARY; + l_node->value->value.dict = new bDictionary(); + + n_node = new bDictionaryNode(); + n_node->key = "length"; + n_node->value = new bNode(); + n_node->value->type = B_INTEGER; + n_node->value->value.number = torrent.Files[i].Size; + + + l_node->value->value.dict->tail = n_node; + l_node->value->value.dict->tail = + l_node->value->value.dict->tail->next; + l_node->value->value.dict->count++; + n_node = NULL; + + n_node = new bDictionaryNode(); + n_node->key = "path"; + n_node->value = new bNode(); + n_node->value->type = B_STRING; + n_node->value->value.str = torrent.Files[i].Path; + + + l_node->value->value.dict->head = + l_node->value->value.dict->tail = n_node; + l_node->value->value.dict->count++; + n_node = NULL; + + if (list->count == 0) { + list->head = list->tail = l_node; + list->count++; + continue; + } + + list->tail->next = l_node; + list->tail = list->tail->next; + list->count++; + l_node = NULL; + } + + node->value->value.list = list; + dict->tail->next = node; + dict->tail = dict->tail->next; + dict->count++; + node = NULL; + + node = new bDictionaryNode(); + node->key = "name"; + node->value = new bNode(); + node->value->type = B_STRING; + node->value->value.str = torrent.FileDirectory(); + + dict->tail->next = node; + dict->tail = dict->tail->next; + dict->count++; + node = NULL; + } + + node = new bDictionaryNode(); + node->key = "piece length"; + node->value = new bNode(); + node->value->type = B_INTEGER; + node->value->value.number = torrent.PieceSize; + + dict->tail->next = node; + dict->tail = dict->tail->next; + dict->count++; + node = NULL; + + std::string pieces(20 * torrent.PieceCount(), ' '); + for (int i = 0; i < torrent.PieceCount(); i++) { + for (int j = 20 * i; j < 20 + 20 * i; j++) { + pieces[j] = torrent.PieceHashes[i][j - 20LL * i]; + } + } + + node = new bDictionaryNode(); + node->key = "pieces"; + node->value = new bNode(); + node->value->type = B_STRING; + node->value->value.str = pieces; + + dict->tail->next = node; + dict->tail = dict->tail->next; + dict->count++; + node = NULL; + + if (torrent.IsPrivate != -1) { + node = new bDictionaryNode(); + node->key = "private"; + node->value = new bNode(); + node->value->type = B_INTEGER; + node->value->value.number = torrent.IsPrivate; + + dict->tail->next = node; + dict->tail = dict->tail->next; + dict->count++; + node = NULL; + } + + return dict; +} + +Torrent* Torrent::BEncodingObjectToTorrent(bNode* bencoding, + std::string name, std::string downloadPath) { + + if (bencoding->type != B_DICTIONARY) { + error_msg("Decoding .torrent file is not\ + in correct format", __FUNCTION__); + return NULL; + } + + std::map dict = bencoding-> + value.dict->to_Stl_map(); + + std::vector trackers; + if (dict.find("announce") != dict.end()) { + + if (dict["announce"]->type == B_STRING) { + trackers.push_back(dict["announce"]->value.str); + } + else if (dict["announce"]->type == B_LIST) { + std::vector + t_list= dict["announce"]->value.list->to_Stl_list(); + + for (int i = 0; i < t_list.size(); i++) { + if (t_list[i]->type == B_STRING) { + trackers.push_back(t_list[i]->value.str); + } + else { + error_msg("announce list element \ +is wrong", __FUNCTION__); + } + } + } + } + + if (dict.find("info") == dict.end()) { + error_msg("info section missing decoded bNode \ +object", __FUNCTION__); + exit(EXIT_FAILURE); + } + + std::map + info = dict["info"]->value.dict->to_Stl_map(); + + std::vector files; + + if (info.find("name") != info.end() && + info.find("length") != info.end()) { + FileItem item; + item.Path = info["name"]->value.str; + + item.Size = info["length"]->value.number; + + files.push_back(item); + } + else if( info.find("files") != info.end()){ + + long long int running = 0; + std::vector f_list = info["files"]->value.list + ->to_Stl_list(); + + + + for (bNode* item : f_list) { + std::map + dict = item->value.dict->to_Stl_map(); + + if (dict.find("path") == dict.end() && + dict.find("length") == dict.end()) { + + error_msg("Incorrent .torrent file specification", + __FUNCTION__); + exit(EXIT_FAILURE); + } + + std::string path = ""; + + std::vector l_path = dict["path"]->value + .list->to_Stl_list(); + + for (bNode* l_item : l_path) { + + path += (DIR_SEPARATOR + + l_item->value.str); + } + + long long int size = (long long int)dict["length"]->value + .number; + + + FileItem fileItem; + fileItem.Path = path; + fileItem.Size = size; + fileItem.Offset = running; + + files.push_back(fileItem); + + running += size; + } + } + else { + error_msg("No file specified in .torrent file", + __FUNCTION__); + exit(EXIT_FAILURE); + } + + if (info.find("piece length") == info.end()) { + error_msg("piece length no mentioned in torrent file", + __FUNCTION__); + exit(EXIT_FAILURE); + } + + int pieceSize = info["piece length"]->value.number; + + if (info.find("pieces") == info.end()) { + error_msg("pieces not mentioned in torrent file", + __FUNCTION__); + exit(EXIT_FAILURE); + } + + std::string pieceHashes = info["pieces"]->value.str; + + int IsPrivate = -1; + if (info.find("private") != info.end()) { + IsPrivate = info["private"]->value.number; + } + + + + Torrent* torrent = new Torrent(name, downloadPath, + files, trackers, pieceSize, (char*)pieceHashes.c_str(), + 16384, IsPrivate); + + if (dict.find("comment") != dict.end()) { + torrent->Comment = dict["comment"]->value.str; + } + + if (dict.find("created by") != dict.end()) { + torrent->CreatedBy = dict["created by"]->value.str; + } + + return torrent; + +} + +void Torrent::UpdateTrackers(TrackerEvent ev, std::string id, + int port) { + + for (Tracker* t : this->Trackers) { + t->Update(*this, ev, id, port); + } +} + +void Torrent::ResetTrackerLastRequest() { + for (Tracker* t : this->Trackers) { + t->ResetLastRequest(); + } +} + +Torrent* LoadFromFile(std::string filePath, std::string downloadPath) { + +} \ No newline at end of file diff --git a/src/Tracker.cpp b/src/Tracker.cpp new file mode 100644 index 0000000..db0eba3 --- /dev/null +++ b/src/Tracker.cpp @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include + + +Tracker::Tracker(std::string address) { + this->Address = address; + this->LastPeerRequested = { 0,0 }; + this->PeerRequestInterval = 1800; + this->httpWebRequest = ""; +} + +void Tracker::Update(Torrent torrent, TrackerEvent ev, + std::string id, int port) { + struct timespec curr_time; + + clock_gettime(CLOCK_REALTIME, &curr_time); + + long long int time_check = ((double)curr_time.tv_sec + + (double)curr_time.tv_nsec / 1.0e9); + + long long int time_limit = ((double)LastPeerRequested.tv_sec + + (double)LastPeerRequested.tv_nsec / 1.0e9) + + this->PeerRequestInterval; + + if (ev == Started && time_check < time_limit) + return; + + clock_gettime(CLOCK_REALTIME, &(this->LastPeerRequested)); + + std::string eventEnum; + + switch (ev) { + case Started: { + eventEnum.assign("started"); + break; + } + case Paused: { + eventEnum.assign("paused"); + break; + } + case Stopped: { + eventEnum.assign("stopped"); + break; + } + default: { + error_msg("TrackerEvent ev have undefined value\n", + __FUNCTION__); + } + } + + + char url[400]; + sprintf(url, "%s?info_hash=%s&peer_id=%s\ +&port=%d&uploaded=%lld&\ +downloaded=%lld&left=%lld&event=%s&compact=1", +(char*)(this->Address).c_str(), (char*)(torrent.UrlSafeStringInfoHash()).c_str(), +(char*)id.c_str(), port, +torrent.Uploaded, torrent.Downloaded(), torrent.Left(), +(char*)eventEnum.c_str()); + + std::string Url(url); + + this->Request((char*)Url.c_str()); + this->Handle_response(); +} + +// Callback function to write the response data +static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { + size_t totalSize = size * nmemb; + std::string* str = (std::string*)userp; + str->append((char*)contents, totalSize); + return totalSize; +} + +void Tracker::Request(char* url) { + + CURL* curl; + CURLcode res; + //std::string readBuffer; + + curl_global_init(CURL_GLOBAL_DEFAULT); + curl = curl_easy_init(); + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 30 seconds timeout + //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &(this->httpWebRequest)); + // Increase timeout + + // Perform the request + res = curl_easy_perform(curl); + + // Check for errors + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + } + /*else { + std::cout << this->httpWebRequest << std::endl; + }*/ + + // Cleanup + curl_easy_cleanup(curl); + } + curl_global_cleanup(); +} + +void Tracker::Handle_response() { + + long long int sz_response = (long long int)this->httpWebRequest.size(); + char* response = new char[sz_response]; + + if (this->httpWebRequest.size() == 0) { + error_msg("Tracker didn't reply any response!!", + __FUNCTION__); + return; + } + + strncpy(response, this->httpWebRequest.c_str(), + sz_response); + + + bNode* info = b_decode(response, + sz_response); + + if (sz_response > 0) { + error_msg("b_decode() didn't parse the\ +complete responce\n", __FUNCTION__); + return; + } + + bDictionary* dict = info->value.dict; + + std::map + dictionary = dict->to_Stl_map(); + + this->PeerRequestInterval = dictionary["interval"]->value.number; + const char* peerInfo = dictionary["peers"]->value.str.c_str(); + + std::vector peers; + + int offset; + std::string address; + + uint8_t ip[4]; + uint16_t ip_port; + + for (int i = 0; i < strlen(peerInfo) / 6; i++) { + + offset = i * 6; + memcpy(ip, peerInfo + offset, 4); + memcpy(&ip_port, peerInfo + 4, 2); + ip_port = ntohs(ip_port); //convert port number.. + char ip_str[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, ip, ip_str, INET_ADDRSTRLEN) == NULL) { + error_msg("inet_ntop failed", __FUNCTION__); + return; + } + + address.assign(ip_str); + std::stringstream ss; + ss << ip_port; + + peers.push_back(address + "::" + ss.str()); + + address.clear(); + } + + this->PeerListUpdated.dispatch("peerlist", peers); +} + +void Tracker::ResetLastRequest() { + LastPeerRequested = { 0,0 }; +} + +std::string Tracker::returnTracker() { + std::string trackerAddress = "[Tracker : " + this->Address + " ]"; + return trackerAddress; +} \ No newline at end of file diff --git a/src/torrent_structure.cpp b/src/torrent_structure.cpp new file mode 100644 index 0000000..3893c24 --- /dev/null +++ b/src/torrent_structure.cpp @@ -0,0 +1,81 @@ +#include +#include + +/*************************** (.torrent) content structure for parsing*************************/ + +bNode::bNode() { + this->size = 0; + this->type = B_UNKNOWN; +} + +bNode::val::val() { + this->str = ""; + this->number = 0; + this->dict = NULL; + this->list = NULL; +} + +bList::bList() { + this->head = NULL; + this->tail = NULL; + this->count = 0; +} + +std::vector +bList::to_Stl_list() { + + bListNode* curr = this->head; + assert(curr != NULL); + + std::vector resList; + + for (; curr != NULL; curr = curr->next) { + resList.push_back(curr->value); + } + + return resList; +} + +bListNode::bListNode() { + this->value = NULL; + this->next = NULL; +} + +bDictionary::bDictionary() { + head = tail = NULL; + count = 0; +} + +std::map +bDictionary::to_Stl_map() { + + bDictionaryNode* curr = this->head; + assert(curr != NULL); + + std::map resMap; + + for (; curr != NULL; curr = curr->next) { + resMap[curr->key] = curr->value; + } + + return resMap; +} + +bDictionaryNode::bDictionaryNode() { + this->key = ""; + this->value = NULL; + this->next = NULL; +} + + +/*************************** Torrent info structure for processing****************************/ +//FileItem::FileItem(std::string path, long long size, +// long long offset) { +// +// this->Path = path; +// this->size = size; +// this->offset = offset; +// this->FormattedSize = std::to_string(size); +//} +// + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..f120e7e --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,21 @@ +set(TEST_MAIN "test_main.cpp") + +add_executable(${TEST_EXE} + ${TEST_MAIN}) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +target_link_libraries(${TEST_EXE} PUBLIC + ${TORRENT_LIB} + ${OPENSSL_LIBRARIES} + ${OPENSSL_CRYPTO_LIBRARY} + ${OPENSSL_SSL_LIBRARY} + ${CURL_LIBRARIES} + Threads::Threads) + +target_include_directories(${TEST_EXE} PUBLIC + ${HEADER_DIR} + ${OPENSSL_INCLUDE_DIR} + ${CURL_INCLUDE_DIRS} + ${EVENTPP_HEADER_DIR}) \ No newline at end of file diff --git a/test/test_main.cpp b/test/test_main.cpp new file mode 100644 index 0000000..4e64949 --- /dev/null +++ b/test/test_main.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + + +int main(int argc, char** argv) { + + //Step 1 : Specify the .torrent file. + std::string torrentFile; + if (argc > 1) { + torrentFile.assign(argv[1]); + + //std::cout << "torrent file : " << torrentFile << '\n'; + } + else { + error_msg("Torrent File not specified.\n\ + Please specify Torrent file (.torrent)\n", + __FUNCTION__); + } + + //Step2 : Read the .torrent file in raw byte. + char* rawData; + long long int tFilesize = _read_file(torrentFile, rawData); + + char* buffer=NULL; //buffer for storing the decoded data + buffer = new char[tFilesize]; + + long long sz = tFilesize; + + bNode* reso = b_decode(rawData, sz); + + reso->size = tFilesize; + + std::cout << "Decode successfull!!!!\n"; + + + Torrent* torrent = new Torrent(); + torrent = torrent->BEncodingObjectToTorrent(reso, torrentFile, + "/"); + + + torrent->PeerListUpdated.appendListener("peerlist", + [](std::vector& peers) { + std::cout << "Peer List Updated:\n"; + for (std::string peer : peers) { + std::cout << peer << '\n'; + } + }); + + torrent->Trackers[0]->Update(*torrent, Started, + "76433642664923430920", 9001); + + /* std::cout << "Info Hash : " << + torrent->HexStringInfoHash() << '\n';*/ + + + char* handshake = buildHandShake(torrent); + + std::cout << "\nMY HANDSHAKE\n"; + for (int i = 0; i < 68; i++) + { + std::cout << handshake[i]; + } + std::cout << "\n-------------------------\n"; + + + + int sockfd; + start_client_handshake(sockfd,handshake,torrent); + + delete torrent; + + return 0; + +}