├── Makefile ├── README.md ├── bencode ├── bencode.h ├── decoder.cpp └── encoder.cpp ├── ctorrent ├── peer.cpp ├── peer.h ├── torrent.cpp ├── torrent.h ├── torrentfilemanager.cpp ├── torrentfilemanager.h ├── torrentmeta.cpp ├── torrentmeta.h ├── tracker.cpp └── tracker.h ├── main.cpp ├── net ├── connection.cpp ├── connection.h ├── decl.h ├── inputmessage.cpp ├── inputmessage.h ├── outputmessage.cpp ├── outputmessage.h ├── server.cpp └── server.h └── util ├── auxiliar.cpp ├── auxiliar.h ├── bitset.h ├── databuffer.h └── serializer.h /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ("$(origin D)", "command line") 2 | BTYPE := -O0 -g 3 | else 4 | BTYPE ?= -O3 5 | endif 6 | 7 | BIN_DIR = bin 8 | BIN = tc 9 | 10 | DEP_DIR = dep 11 | DEPFLAGS = -MT $@ -MMD -MP -MF $(DEP_DIR)/$*.d 12 | 13 | CXX ?= $(CROSS_BUILD)g++ 14 | CXXFLAGS = -std=c++11 $(DEPFLAGS) $(BTYPE) -Wall -Wextra -Wno-deprecated-declarations \ 15 | -Wno-sign-compare -Wno-unused-variable -Wno-unused-parameter -I"." -I"D:\boost_1_60_0" 16 | 17 | LIBS = -L"D:\boost_1_60_0\stage\lib" -lboost_system -lboost_filesystem -lboost_program_options 18 | ifeq ($(OS),Windows_NT) 19 | LIBS += -lws2_32 -lshlwapi -lMswsock 20 | else 21 | LIBS += -lpthread -lcurses 22 | endif 23 | 24 | OBJ_DIR = obj 25 | SRC = bencode/decoder.cpp bencode/encoder.cpp \ 26 | ctorrent/tracker.cpp ctorrent/peer.cpp ctorrent/torrentmeta.cpp \ 27 | ctorrent/torrentfilemanager.cpp ctorrent/torrent.cpp \ 28 | net/server.cpp net/connection.cpp net/inputmessage.cpp net/outputmessage.cpp \ 29 | util/auxiliar.cpp \ 30 | main.cpp 31 | OBJ = $(SRC:%.cpp=$(OBJ_DIR)/%.o) 32 | DEP = $(SRC:%.cpp=$(DEP_DIR)/%.d) 33 | 34 | .PHONY: all clean 35 | .PRECIOUS: $(DEP_DIR)/%.d 36 | 37 | all: $(BIN) 38 | clean: 39 | @$(RM) $(OBJ_DIR)/*.o 40 | @$(RM) $(OBJ_DIR)/*/*.o 41 | @$(RM) $(DEP_DIR)/*.d 42 | @$(RM) $(DEP_DIR)/*/*.d 43 | @$(RM) $(BIN) 44 | @echo " Cleaned" 45 | 46 | $(BIN): $(DEP_DIR) $(OBJ_DIR) $(OBJ) $(DEP) 47 | @echo " LD $@" 48 | @$(CXX) -o $@ $(OBJ) $(LIBS) 49 | 50 | $(OBJ_DIR)/%.o: %.cpp $(DEP_DIR)/%.d 51 | @echo " CXX $<" 52 | @$(CXX) -c $(CXXFLAGS) -o $@ $< 53 | 54 | -include $(DEP) 55 | $(DEP_DIR)/%.d: ; 56 | 57 | $(DEP_DIR): 58 | @mkdir -p $(DEP_DIR) 59 | @mkdir -p $(DEP_DIR)/bencode 60 | @mkdir -p $(DEP_DIR)/ctorrent 61 | @mkdir -p $(DEP_DIR)/net 62 | @mkdir -p $(DEP_DIR)/util 63 | 64 | $(OBJ_DIR): 65 | @mkdir -p $(OBJ_DIR) 66 | @mkdir -p $(OBJ_DIR)/bencode 67 | @mkdir -p $(OBJ_DIR)/ctorrent 68 | @mkdir -p $(OBJ_DIR)/net 69 | @mkdir -p $(OBJ_DIR)/util 70 | 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTorrent 2 | 3 | CTorrent is a torrent client written in C++11 with some help from Boost. 4 | ![img](https://i.imgur.com/WcjjLG5.png) 5 | 6 | ## Requirements 7 | 8 | - A C++ compiler with C++11 and OpenMP support (GCC or clang are preferred) 9 | - Boost C++ Libraries (for ASIO, SHA1, programoptions and any) 10 | 11 | ## Developer information 12 | 13 | - net/ Contains classes which are responsible for establishing connections, etc. 14 | - bencode/ Contains the decoder and encoder which can decode or encode torrent files. 15 | - ctorrent/ Contains the core classes responsibile for downloading torrents etc. 16 | - util/ Contains various utility functions which are used in bencode/ and ctorrent/ 17 | - main.cpp Makes use of all the above (Also is the main for the console application) 18 | 19 | ## License 20 | 21 | MIT (The Expat License). 22 | -------------------------------------------------------------------------------- /bencode/bencode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __BENCODE_H 23 | #define __BENCODE_H 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #ifdef _DEBUG 31 | #include 32 | #endif 33 | 34 | #include 35 | #include 36 | 37 | typedef std::map Dictionary; 38 | typedef std::vector VectorType; 39 | 40 | class Bencode { 41 | public: 42 | Bencode() { m_pos = 0; } 43 | ~Bencode() = default; 44 | 45 | Dictionary decode(const std::string& fileName); 46 | Dictionary decode(const char *, size_t); 47 | inline void encode(const Dictionary &dict) { m_buffer.setSize(m_pos); return writeDictionary(dict); } 48 | 49 | template 50 | static inline T cast(const boost::any &value) 51 | { 52 | try { 53 | return boost::any_cast(value); 54 | } catch (...) { 55 | #ifdef _DEBUG 56 | std::cerr << "Bencode::cast<" << typeid(T).name() << ">: unable to cast from: " << value.type().name() << std::endl; 57 | #endif 58 | return T(); 59 | } 60 | } 61 | 62 | size_t pos() const { return m_pos; } 63 | const char *buffer(size_t pos, size_t &bufferSize) const 64 | { 65 | bufferSize = m_buffer.size() - pos; 66 | return &m_buffer[pos]; 67 | } 68 | 69 | protected: 70 | inline int64_t readInt(); 71 | inline uint64_t readUint(); 72 | VectorType readVector(); 73 | std::string readString(); 74 | Dictionary readDictionary(); 75 | 76 | void writeString(const std::string &); 77 | void writeInt(int64_t); 78 | void writeUint(uint64_t); 79 | void writeVector(const VectorType &); 80 | void writeDictionary(const Dictionary &); 81 | void writeType(const boost::any *); 82 | 83 | template 84 | bool readIntUntil(const char byte, T &value); 85 | 86 | inline bool unpackByte(char &b) { 87 | if (m_pos + 1 > m_buffer.cap()) 88 | return false; 89 | 90 | b = m_buffer[m_pos++]; 91 | return true; 92 | } 93 | 94 | void internalWriteString(const std::string &); 95 | 96 | private: 97 | DataBuffer m_buffer; 98 | size_t m_pos; 99 | }; 100 | 101 | #endif 102 | 103 | -------------------------------------------------------------------------------- /bencode/decoder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "bencode.h" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | Dictionary Bencode::decode(const char *data, size_t size) 29 | { 30 | if (data[0] != 'd') 31 | return Dictionary(); 32 | 33 | m_buffer.setData(const_cast(&data[1]), size - 1); 34 | return readDictionary(); 35 | } 36 | 37 | Dictionary Bencode::decode(const std::string &fileName) 38 | { 39 | std::ifstream f(fileName, std::ios_base::binary | std::ios_base::in); 40 | if (!f.is_open()) 41 | return Dictionary(); 42 | 43 | f.seekg(0, f.end); 44 | size_t size = f.tellg(); 45 | f.seekg(0, f.beg); 46 | 47 | char buffer[size]; 48 | f.read(buffer, size); 49 | f.close(); 50 | return decode(buffer, size); 51 | } 52 | 53 | template 54 | bool Bencode::readIntUntil(const char byte, T &value) 55 | { 56 | // Find first occurance of byte in the data buffer 57 | size_t pos = 0; 58 | for (pos = m_pos; pos < m_buffer.cap() && m_buffer[pos] != byte; ++pos); 59 | // Sanity check 60 | if (m_buffer[pos] != byte) 61 | return false; 62 | 63 | size_t size = pos - m_pos; 64 | if (size > m_buffer.rem()) 65 | return false; 66 | 67 | std::istringstream is(std::string(&m_buffer[m_pos], size)); 68 | is >> value; 69 | 70 | m_pos = pos + 1; 71 | return true; 72 | } 73 | 74 | int64_t Bencode::readInt() 75 | { 76 | int64_t i; 77 | if (readIntUntil('e', i)) 78 | return i; 79 | 80 | return std::numeric_limits::max(); 81 | } 82 | 83 | uint64_t Bencode::readUint() 84 | { 85 | uint64_t u; 86 | if (readIntUntil('e', u)) 87 | return u; 88 | 89 | return std::numeric_limits::max(); 90 | } 91 | 92 | VectorType Bencode::readVector() 93 | { 94 | VectorType ret; 95 | 96 | for (;;) { 97 | char byte; 98 | if (!unpackByte(byte)) 99 | return ret; 100 | 101 | switch (byte) { 102 | case 'i': ret.push_back(readUint()); break; 103 | case 'l': ret.push_back(readVector()); break; 104 | case 'd': ret.push_back(readDictionary()); break; 105 | case 'e': return ret; 106 | default: 107 | --m_pos; 108 | ret.push_back(readString()); 109 | break; 110 | } 111 | } 112 | 113 | return ret; 114 | } 115 | 116 | std::string Bencode::readString() 117 | { 118 | uint64_t len; 119 | if (!readIntUntil(':', len)) 120 | return std::string(); 121 | 122 | if (len + m_pos > m_buffer.cap()) 123 | return std::string(); 124 | 125 | char buffer[len]; 126 | memcpy(buffer, &m_buffer[m_pos], len); 127 | 128 | m_pos += len; 129 | return std::string(buffer, len); 130 | } 131 | 132 | Dictionary Bencode::readDictionary() 133 | { 134 | Dictionary ret; 135 | 136 | for (;;) { 137 | std::string key = readString(); 138 | if (key.empty()) 139 | return Dictionary(); 140 | 141 | char byte; 142 | if (!unpackByte(byte)) 143 | return Dictionary(); 144 | 145 | switch (byte) { 146 | case 'i': ret[key] = readUint(); break; 147 | case 'l': ret[key] = readVector(); break; 148 | case 'd': ret[key] = readDictionary(); break; 149 | default: 150 | --m_pos; 151 | ret[key] = readString(); 152 | break; 153 | } 154 | 155 | if (!unpackByte(byte)) 156 | return Dictionary(); 157 | else if (byte == 'e') 158 | break; 159 | else 160 | --m_pos; 161 | } 162 | 163 | return ret; 164 | } 165 | 166 | -------------------------------------------------------------------------------- /bencode/encoder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include 23 | 24 | #include "bencode.h" 25 | 26 | void Bencode::internalWriteString(const std::string& str) 27 | { 28 | for (size_t i = 0; i < str.length(); ++i) 29 | m_buffer.add(str[i]); 30 | m_pos += str.length(); 31 | } 32 | 33 | void Bencode::writeString(const std::string& str) 34 | { 35 | internalWriteString(std::to_string(str.length())); 36 | m_buffer.add(':'); 37 | ++m_pos; 38 | internalWriteString(str); 39 | } 40 | 41 | void Bencode::writeInt(int64_t i) 42 | { 43 | m_buffer.add('i'); 44 | ++m_pos; 45 | internalWriteString(std::to_string(i)); 46 | m_buffer.add('e'); 47 | ++m_pos; 48 | } 49 | 50 | void Bencode::writeUint(uint64_t i) 51 | { 52 | m_buffer.add('i'); 53 | ++m_pos; 54 | internalWriteString(std::to_string(i)); 55 | m_buffer.add('e'); 56 | ++m_pos; 57 | } 58 | 59 | void Bencode::writeVector(const VectorType& vector) 60 | { 61 | m_buffer.add('l'); 62 | ++m_pos; 63 | for (const boost::any& value : vector) 64 | writeType(&value); 65 | m_buffer.add('e'); 66 | ++m_pos; 67 | } 68 | 69 | void Bencode::writeDictionary(const Dictionary& map) 70 | { 71 | m_buffer.add('d'); 72 | ++m_pos; 73 | 74 | for (const auto& pair : map) { 75 | writeString(pair.first); 76 | writeType(&pair.second); 77 | } 78 | 79 | m_buffer.add('e'); 80 | ++m_pos; 81 | } 82 | 83 | void Bencode::writeType(const boost::any *value) 84 | { 85 | const std::type_info& type = value->type(); 86 | 87 | if (type == typeid(int64_t)) 88 | writeInt(*boost::unsafe_any_cast(value)); 89 | else if (type == typeid(uint64_t)) 90 | writeUint(*boost::unsafe_any_cast(value)); 91 | else if (type == typeid(int) || type == typeid(int32_t)) 92 | writeInt((int64_t)*boost::unsafe_any_cast(value)); 93 | else if (type == typeid(uint32_t)) 94 | writeUint((uint64_t)*boost::unsafe_any_cast(value)); 95 | else if (type == typeid(std::string)) 96 | writeString(*boost::unsafe_any_cast(value)); 97 | else if (type == typeid(const char *)) 98 | writeString(*boost::unsafe_any_cast(value)); 99 | else if (type == typeid(Dictionary)) 100 | writeDictionary(*boost::unsafe_any_cast(value)); 101 | else if (type == typeid(VectorType)) 102 | writeVector(*boost::unsafe_any_cast(value)); 103 | } 104 | -------------------------------------------------------------------------------- /ctorrent/peer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "peer.h" 23 | #include "torrent.h" 24 | 25 | #define KEEPALIVE_INTERVAL 30 * 1000 26 | 27 | Peer::Peer(Torrent *torrent) 28 | : m_bitset(torrent->fileManager()->totalPieces()), 29 | m_torrent(torrent), 30 | m_conn(new Connection()) 31 | { 32 | m_state = PS_AmChoked | PS_PeerChoked; 33 | } 34 | 35 | Peer::Peer(const ConnectionPtr &c, Torrent *t) 36 | : m_bitset(t->fileManager()->totalPieces()), 37 | m_torrent(t), 38 | m_conn(c) 39 | { 40 | m_state = PS_AmChoked | PS_PeerChoked; 41 | } 42 | 43 | Peer::~Peer() 44 | { 45 | for (Piece *p : m_queue) 46 | delete p; 47 | m_queue.clear(); 48 | } 49 | 50 | void Peer::disconnect() 51 | { 52 | m_conn->close(false); 53 | m_conn->setErrorCallback(nullptr); // deref 54 | } 55 | 56 | void Peer::connect(const std::string &ip, const std::string &port) 57 | { 58 | m_conn->setErrorCallback(std::bind(&Peer::handleError, shared_from_this(), std::placeholders::_1)); 59 | m_conn->connect(ip, port, [this] () { 60 | const uint8_t *m_handshake = m_torrent->handshake(); 61 | m_conn->write(m_handshake, 68); 62 | m_conn->read(68, [this, m_handshake] (const uint8_t *handshake, size_t size) { 63 | if (size != 68 || 64 | (handshake[0] != 0x13 && memcmp(&handshake[1], "BitTorrent protocol", 19) != 0) || 65 | memcmp(&handshake[28], &m_handshake[28], 20) != 0) 66 | return handleError("info hash/protocol type mismatch"); 67 | 68 | std::string peerId((const char *)&handshake[48], 20); 69 | if (!m_peerId.empty() && peerId != m_peerId) 70 | return handleError("peer id mismatch: unverified"); 71 | 72 | m_peerId = peerId; 73 | m_torrent->addPeer(shared_from_this()); 74 | m_torrent->sendBitfield(shared_from_this()); 75 | m_conn->read(4, std::bind(&Peer::handle, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); 76 | }); 77 | }); 78 | } 79 | 80 | void Peer::verify() 81 | { 82 | const uint8_t *m_handshake = m_torrent->handshake(); 83 | m_conn->setErrorCallback(std::bind(&Peer::handleError, shared_from_this(), std::placeholders::_1)); 84 | m_conn->read(68, [this, m_handshake] (const uint8_t *handshake, size_t size) { 85 | if (size != 68 || 86 | (handshake[0] != 0x13 && memcmp(&handshake[1], "BitTorrent protocol", 19) != 0) || 87 | memcmp(&handshake[28], &m_handshake[28], 20) != 0) 88 | return handleError("info hash/protocol type mismatch"); 89 | 90 | std::string peerId((const char *)&handshake[48], 20); 91 | if (!m_peerId.empty() && peerId != m_peerId) 92 | return handleError("unverified"); 93 | 94 | m_peerId = peerId; 95 | m_conn->write(m_handshake, 68); 96 | m_torrent->handleNewPeer(shared_from_this()); 97 | m_conn->read(4, std::bind(&Peer::handle, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); 98 | }); 99 | } 100 | 101 | void Peer::handle(const uint8_t *data, size_t size) 102 | { 103 | if (size != 4) 104 | return handleError("Peer::handle(): Expected 4-byte length"); 105 | 106 | uint32_t length = readBE32(data); 107 | switch (length) { 108 | case 0: // Keep alive 109 | return m_conn->read(4, std::bind(&Peer::handle, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); 110 | default: 111 | m_conn->read(length, [this] (const uint8_t *data, size_t size) { 112 | InputMessage in(const_cast(&data[1]), size - 1, ByteOrder::BigEndian); 113 | handleMessage((MessageType)data[0], in); 114 | }); 115 | } 116 | } 117 | 118 | void Peer::handleMessage(MessageType messageType, InputMessage in) 119 | { 120 | size_t payloadSize = in.getSize(); 121 | 122 | switch (messageType) { 123 | case MT_Choke: 124 | if (payloadSize != 0) 125 | return handleError("invalid choke-message size"); 126 | 127 | m_state |= PS_PeerChoked; 128 | break; 129 | case MT_UnChoke: 130 | if (payloadSize != 0) 131 | return handleError("invalid unchoke-message size"); 132 | 133 | m_state &= ~PS_PeerChoked; 134 | for (const Piece *piece : m_queue) 135 | requestPiece(piece->index); 136 | break; 137 | case MT_Interested: 138 | { 139 | if (payloadSize != 0) 140 | return handleError("invalid interested-message size"); 141 | 142 | m_state |= PS_PeerInterested; 143 | if (isLocalChoked()) 144 | sendUnchoke(); 145 | break; 146 | } 147 | case MT_NotInterested: 148 | if (payloadSize != 0) 149 | return handleError("invalid not-interested-message size"); 150 | 151 | m_state &= ~PS_PeerInterested; 152 | break; 153 | case MT_Have: 154 | { 155 | if (payloadSize != 4) 156 | return handleError("invalid have-message size"); 157 | 158 | uint32_t i = in.getU32(); 159 | if (i < m_bitset.size()) 160 | m_bitset.set(i); 161 | break; 162 | } 163 | case MT_Bitfield: 164 | { 165 | if (payloadSize < 1) 166 | return handleError("invalid bitfield-message size"); 167 | 168 | uint8_t *buf = in.getBuffer(); 169 | for (size_t i = 0; i < payloadSize; ++i) { 170 | for (size_t x = 0; x < 8; ++x) { 171 | if (buf[i] & (1 << (7 - x))) { 172 | size_t idx = i * 8 + x; 173 | if (idx < m_torrent->fileManager()->totalPieces()) 174 | m_bitset.set(idx); 175 | } 176 | } 177 | } 178 | 179 | if (!m_torrent->isFinished()) 180 | m_torrent->requestPiece(shared_from_this()); 181 | break; 182 | } 183 | case MT_Request: 184 | { 185 | if (payloadSize != 12) 186 | return handleError("invalid request-message size"); 187 | 188 | if (!isRemoteInterested()) 189 | return handleError("peer requested piece block without showing interest"); 190 | 191 | if (isLocalChoked()) 192 | return handleError("peer requested piece while choked"); 193 | 194 | uint32_t index, begin, length; 195 | in >> index; 196 | in >> begin; 197 | in >> length; 198 | 199 | if (length > maxRequestSize) 200 | return handleError("peer requested block of length " + bytesToHumanReadable(length, true) + " which is beyond our max request size"); 201 | 202 | m_torrent->handlePeerDebug(shared_from_this(), "requested piece block of length " + bytesToHumanReadable(length, true)); 203 | if (!m_torrent->handleRequestBlock(shared_from_this(), index, begin, length)) { 204 | sendChoke(); 205 | break; 206 | } 207 | 208 | m_requestedBlocks.push_back(PieceBlockInfo(index, begin, length)); 209 | break; 210 | } 211 | case MT_PieceBlock: 212 | { 213 | if (payloadSize < 9) 214 | return handleError("invalid pieceblock-message size"); 215 | 216 | uint32_t index, begin; 217 | in >> index; 218 | in >> begin; 219 | 220 | payloadSize -= 8; // deduct index and begin 221 | if (payloadSize == 0 || payloadSize > maxRequestSize) 222 | return handleError("received too big piece block of size " + bytesToHumanReadable(payloadSize, true)); 223 | 224 | auto it = std::find_if(m_queue.begin(), m_queue.end(), 225 | [index](const Piece *piece) { return piece->index == index; }); 226 | if (it == m_queue.end()) 227 | return handleError("received piece " + std::to_string(index) + " which we did not ask for"); 228 | 229 | Piece *piece = *it; 230 | uint32_t blockIndex = begin / maxRequestSize; 231 | if (blockIndex >= piece->numBlocks) 232 | return handleError("received too big block index"); 233 | 234 | if (m_torrent->fileManager()->pieceDone(index)) { 235 | cancelPiece(piece); 236 | m_queue.erase(it); 237 | delete piece; 238 | } else { 239 | uint8_t *payload = in.getBuffer(payloadSize); 240 | PieceBlock *block = &piece->blocks[blockIndex]; 241 | block->size = payloadSize; 242 | block->data = payload; 243 | ++piece->currentBlocks; 244 | 245 | if (piece->currentBlocks == piece->numBlocks) { 246 | DataBuffer pieceData; 247 | pieceData.reserve(piece->numBlocks * maxRequestSize); // just a prediction could be bit less 248 | for (size_t x = 0; x < piece->numBlocks; ++x) 249 | for (size_t y = 0; y < piece->blocks[x].size; ++y) 250 | pieceData.add_unchecked(piece->blocks[x].data[y]); 251 | 252 | m_queue.erase(it); 253 | delete piece; 254 | 255 | if (!m_torrent->handlePieceCompleted(shared_from_this(), index, std::move(pieceData))) 256 | sendChoke(); 257 | else if (!m_torrent->isFinished()) 258 | m_torrent->requestPiece(shared_from_this()); 259 | } 260 | } 261 | 262 | break; 263 | } 264 | case MT_Cancel: 265 | { 266 | if (payloadSize != 12) 267 | return handleError("invalid cancel-message size"); 268 | 269 | uint32_t index, begin, length; 270 | in >> index; 271 | in >> begin; 272 | in >> length; 273 | 274 | auto it = std::find_if(m_requestedBlocks.begin(), m_requestedBlocks.end(), 275 | [=] (const PieceBlockInfo &i) { return i.index == index 276 | && i.begin == begin 277 | && i.length == length; } ); 278 | if (it != m_requestedBlocks.end()) 279 | m_requestedBlocks.erase(it); 280 | break; 281 | } 282 | case MT_Port: 283 | if (payloadSize != 2) 284 | return handleError("invalid port-message size"); 285 | 286 | uint16_t port; 287 | in >> port; 288 | break; 289 | } 290 | 291 | m_conn->read(4, std::bind(&Peer::handle, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); 292 | } 293 | 294 | void Peer::handleError(const std::string &errmsg) 295 | { 296 | m_torrent->removePeer(shared_from_this(), errmsg); 297 | disconnect(); 298 | } 299 | 300 | void Peer::handlePieceBlockData(size_t index, size_t begin, const uint8_t *block, size_t size) 301 | { 302 | // Check if piece block cancel was issued 303 | auto it = std::find_if(m_requestedBlocks.begin(), m_requestedBlocks.end(), 304 | [=] (const PieceBlockInfo &i) { return i.index == index && i.begin == begin; } ); 305 | if (it == m_requestedBlocks.end()) 306 | return; 307 | 308 | m_requestedBlocks.erase(it); 309 | if (!isLocalChoked() && isRemoteInterested()) 310 | sendPieceBlock(index, begin, block, size); 311 | } 312 | 313 | void Peer::sendKeepAlive() 314 | { 315 | const uint8_t keepalive[] = { 0, 0, 0, 0 }; 316 | m_conn->write(keepalive, sizeof(keepalive)); 317 | } 318 | 319 | void Peer::sendChoke() 320 | { 321 | const uint8_t choke[5] = { 0, 0, 0, 1, MT_Choke }; 322 | m_conn->write(choke, sizeof(choke)); 323 | m_state |= PS_AmChoked; 324 | } 325 | 326 | void Peer::sendUnchoke() 327 | { 328 | const uint8_t unchoke[5] = { 0, 0, 0, 1, MT_UnChoke }; 329 | m_conn->write(unchoke, sizeof(unchoke)); 330 | m_state &= ~PS_AmChoked; 331 | } 332 | 333 | void Peer::sendBitfield(const uint8_t *bits, size_t size) 334 | { 335 | OutputMessage out(ByteOrder::BigEndian, 5 + size); 336 | out << (uint32_t)(1UL + size); 337 | out << (uint8_t)MT_Bitfield; 338 | out.addBytes(bits, size); 339 | 340 | m_conn->write(out); 341 | } 342 | 343 | void Peer::sendHave(uint32_t index) 344 | { 345 | if (isLocalChoked()) 346 | return; 347 | 348 | OutputMessage out(ByteOrder::BigEndian, 9); 349 | out << (uint32_t)5UL; // length 350 | out << (uint8_t)MT_Have; 351 | out << index; 352 | 353 | m_conn->write(out); 354 | } 355 | 356 | void Peer::sendPieceBlock(uint32_t index, uint32_t begin, const uint8_t *block, size_t length) 357 | { 358 | OutputMessage out(ByteOrder::BigEndian, 13 + length); 359 | out << (uint32_t)(9UL + length); // length 360 | out << (uint8_t)MT_PieceBlock; 361 | out << index; 362 | out << begin; 363 | out.addBytes(block, length); 364 | 365 | m_conn->write(out); 366 | } 367 | 368 | void Peer::sendPieceRequest(uint32_t index) 369 | { 370 | sendInterested(); 371 | 372 | uint32_t pieceLength = m_torrent->fileManager()->pieceSize(index); 373 | size_t numBlocks = (int)(ceil(double(pieceLength) / maxRequestSize)); 374 | 375 | Piece *piece = new Piece(); 376 | piece->index = index; 377 | piece->currentBlocks = 0; 378 | piece->numBlocks = numBlocks; 379 | piece->blocks = new PieceBlock[numBlocks]; 380 | 381 | m_queue.push_back(piece); 382 | if (!isRemoteChoked()) 383 | requestPiece(index); 384 | } 385 | 386 | void Peer::sendRequest(uint32_t index, uint32_t begin, uint32_t length) 387 | { 388 | OutputMessage out(ByteOrder::BigEndian, 17); 389 | out << (uint32_t)13UL; // length 390 | out << (uint8_t)MT_Request; 391 | out << index; 392 | out << begin; 393 | out << length; 394 | 395 | m_conn->write(out); 396 | } 397 | 398 | void Peer::sendInterested() 399 | { 400 | // 4-byte length, 1 byte packet type 401 | const uint8_t interested[5] = { 0, 0, 0, 1, MT_Interested }; 402 | m_conn->write(interested, sizeof(interested)); 403 | m_state |= PS_AmInterested; 404 | } 405 | 406 | void Peer::cancelPiece(Piece *p) 407 | { 408 | size_t begin = 0; 409 | size_t length = m_torrent->fileManager()->pieceSize(p->index); 410 | for (; length > maxRequestSize; length -= maxRequestSize, begin += maxRequestSize) 411 | sendCancel(p->index, begin, maxRequestSize); 412 | sendCancel(p->index, begin, length); 413 | } 414 | 415 | void Peer::sendCancel(uint32_t index, uint32_t begin, uint32_t length) 416 | { 417 | OutputMessage out(ByteOrder::BigEndian, 17); 418 | out << (uint32_t)13UL; // length 419 | out << (uint8_t)MT_Cancel; 420 | out << index; 421 | out << begin; 422 | out << length; 423 | 424 | m_conn->write(out); 425 | } 426 | 427 | void Peer::requestPiece(size_t pieceIndex) 428 | { 429 | if (isRemoteChoked()) 430 | return handleError("Attempt to request piece from a peer that is remotely choked"); 431 | 432 | size_t begin = 0; 433 | size_t length = m_torrent->fileManager()->pieceSize(pieceIndex); 434 | for (; length > maxRequestSize; length -= maxRequestSize, begin += maxRequestSize) 435 | sendRequest(pieceIndex, begin, maxRequestSize); 436 | sendRequest(pieceIndex, begin, length); 437 | } 438 | 439 | -------------------------------------------------------------------------------- /ctorrent/peer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __PEER_H 23 | #define __PEER_H 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | class Torrent; 34 | class Peer : public std::enable_shared_from_this 35 | { 36 | struct Piece; 37 | enum State : uint8_t { 38 | PS_AmChoked = 1 << 0, // We choked this peer (aka we're not giving him anymore pieces) 39 | PS_AmInterested = 1 << 1, // We're interested in this peer's pieces 40 | PS_PeerChoked = 1 << 2, // Peer choked us 41 | PS_PeerInterested = 1 << 3, // Peer interested in our stuff 42 | }; 43 | 44 | enum MessageType : uint8_t { 45 | MT_Choke = 0, 46 | MT_UnChoke = 1, 47 | MT_Interested = 2, 48 | MT_NotInterested = 3, 49 | MT_Have = 4, 50 | MT_Bitfield = 5, 51 | MT_Request = 6, 52 | MT_PieceBlock = 7, 53 | MT_Cancel = 8, 54 | MT_Port = 9 55 | }; 56 | 57 | public: 58 | Peer(Torrent *t); 59 | Peer(const ConnectionPtr &c, Torrent *t); 60 | ~Peer(); 61 | 62 | inline void setId(const std::string &id) { m_peerId = id; } 63 | inline std::string getIP() const { return m_conn->getIPString(); } 64 | inline uint32_t ip() const { return m_conn->getIP(); } 65 | void disconnect(); 66 | void connect(const std::string &ip, const std::string &port); 67 | 68 | protected: 69 | void verify(); 70 | void handle(const uint8_t *data, size_t size); 71 | void handleMessage(MessageType mType, InputMessage in); 72 | void handleError(const std::string &errmsg); 73 | void handlePieceBlockData(size_t index, size_t begin, const uint8_t *block, size_t size); 74 | 75 | void sendKeepAlive(); 76 | void sendChoke(); 77 | void sendUnchoke(); 78 | void sendBitfield(const uint8_t *bits, size_t size); 79 | void sendHave(uint32_t index); 80 | void sendPieceBlock(uint32_t index, uint32_t begin, const uint8_t *block, size_t size); 81 | void sendPieceRequest(uint32_t index); 82 | void sendRequest(uint32_t index, uint32_t begin, uint32_t size); 83 | void sendInterested(); 84 | void sendCancel(uint32_t index, uint32_t begin, uint32_t size); 85 | 86 | void requestPiece(size_t pieceIndex); 87 | void cancelPiece(Piece *p); 88 | 89 | inline bool hasPiece(size_t i) const { return m_bitset.test(i); } 90 | inline bool isRemoteChoked() const { return test_bit(m_state, PS_PeerChoked); } 91 | inline bool isLocalChoked() const { return test_bit(m_state, PS_AmChoked); } 92 | 93 | inline bool isRemoteInterested() const { return test_bit(m_state, PS_PeerInterested); } 94 | inline bool isLocalInterested() const { return test_bit(m_state, PS_AmInterested); } 95 | 96 | private: 97 | struct PieceBlock { 98 | size_t size; 99 | uint8_t *data; 100 | 101 | PieceBlock() { data = nullptr; size = 0; } 102 | ~PieceBlock() { delete []data; } 103 | }; 104 | 105 | struct Piece { 106 | size_t index; 107 | size_t currentBlocks; 108 | size_t numBlocks; 109 | 110 | ~Piece() { delete []blocks; } 111 | PieceBlock *blocks; 112 | }; 113 | 114 | struct PieceBlockInfo { 115 | size_t index; 116 | size_t begin; 117 | size_t length; 118 | 119 | PieceBlockInfo(size_t i, int64_t beg, int64_t len) 120 | : index(i), 121 | begin(beg), 122 | length(len) 123 | { 124 | } 125 | 126 | bool operator==(const PieceBlockInfo &other) const 127 | { 128 | return other.index == index 129 | && other.begin == begin 130 | && other.length == length; 131 | } 132 | }; 133 | 134 | bitset m_bitset; 135 | std::vector m_queue; 136 | std::vector m_requestedBlocks; 137 | std::string m_peerId; 138 | uint8_t m_state; 139 | 140 | Torrent *m_torrent; 141 | ConnectionPtr m_conn; 142 | 143 | friend class Torrent; 144 | }; 145 | typedef std::shared_ptr PeerPtr; 146 | 147 | #endif 148 | 149 | -------------------------------------------------------------------------------- /ctorrent/torrent.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "torrent.h" 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | extern std::ofstream logfile; 35 | 36 | Torrent::Torrent() 37 | : m_listener(nullptr), 38 | m_fileManager(this), 39 | m_uploadedBytes(0), 40 | m_downloadedBytes(0), 41 | m_wastedBytes(0), 42 | m_hashMisses(0) 43 | { 44 | 45 | } 46 | 47 | Torrent::~Torrent() 48 | { 49 | for (Tracker *tracker : m_activeTrackers) 50 | delete tracker; 51 | m_activeTrackers.clear(); 52 | m_peers.clear(); 53 | 54 | if (m_listener) 55 | m_listener->stop(); 56 | delete m_listener; 57 | } 58 | 59 | bool Torrent::open(const std::string &fileName, const std::string &downloadDir) 60 | { 61 | if (!m_meta.parse(fileName)) 62 | return false; 63 | 64 | m_handshake[0] = 0x13; // 19 length of string "BitTorrent protocol" 65 | memcpy(&m_handshake[1], "BitTorrent protocol", 19); 66 | memset(&m_handshake[20], 0x00, 8); // reserved bytes (last |= 0x01 for DHT or last |= 0x04 for FPE) 67 | memcpy(&m_handshake[48], "-CT11000", 8); // Azureus-style peer id (-CT0000-XXXXXXXXXXXX) 68 | memcpy(&m_peerId[0], "-CT11000", 8); 69 | 70 | // write info hash 71 | const uint32_t *checkSum = m_meta.checkSum(); 72 | for (size_t i = 0; i < 5; ++i) 73 | writeBE32(&m_handshake[28 + i * 4], checkSum[i]); 74 | 75 | static std::random_device rd; 76 | static std::knuth_b generator(rd()); 77 | static std::uniform_int_distribution random(0x00, 0xFF); 78 | for (size_t i = 0; i < 12; ++i) 79 | m_handshake[56 + i] = m_peerId[8 + i] = random(generator); 80 | 81 | std::string dir = downloadDir; 82 | if (!ends_with(dir, PATH_SEP)) 83 | dir += PATH_SEP; 84 | 85 | return m_fileManager.registerFiles(dir, m_meta.files()); 86 | } 87 | 88 | double Torrent::eta() 89 | { 90 | clock_t elapsed_s = elapsed() / CLOCKS_PER_SEC; 91 | size_t downloaded = computeDownloaded(); 92 | return (double)(m_meta.totalSize() - downloaded) * elapsed_s / (double)downloaded; 93 | } 94 | 95 | double Torrent::downloadSpeed() 96 | { 97 | clock_t elapsed_s = elapsed() / CLOCKS_PER_SEC; 98 | size_t downloaded = computeDownloaded(); 99 | if (elapsed_s == 0) 100 | return 0.0f; 101 | 102 | double speed = (double)(downloaded / elapsed_s); 103 | return (speed / 1024) * 0.0125; 104 | } 105 | 106 | clock_t Torrent::elapsed() 107 | { 108 | return clock() - m_startTime; 109 | } 110 | 111 | TrackerQuery Torrent::makeTrackerQuery(TrackerEvent event) 112 | { 113 | size_t downloaded = computeDownloaded(); 114 | TrackerQuery q = { 115 | .event = event, 116 | .downloaded = downloaded, 117 | .uploaded = m_uploadedBytes, 118 | .remaining = m_meta.totalSize() - downloaded 119 | }; 120 | 121 | return q; 122 | } 123 | 124 | Torrent::DownloadState Torrent::prepare(uint16_t port, bool seeder) 125 | { 126 | if (seeder) { 127 | if (!m_listener && !(m_listener = new(std::nothrow) Server(port))) 128 | return DownloadState::NetworkError; 129 | } else if (isFinished()) 130 | return DownloadState::AlreadyDownloaded; 131 | 132 | if (!queryTrackers(makeTrackerQuery(TrackerEvent::Started), seeder ? port : 0)) 133 | return DownloadState::TrackerQueryFailure; 134 | 135 | m_startTime = clock(); 136 | return DownloadState::None; 137 | } 138 | 139 | bool Torrent::finish() 140 | { 141 | TrackerEvent event = isFinished() ? TrackerEvent::Completed : TrackerEvent::Stopped; 142 | TrackerQuery q = makeTrackerQuery(event); 143 | for (auto it = m_activeTrackers.begin(); it != m_activeTrackers.end();) { 144 | Tracker *tracker = *it; 145 | tracker->query(q); 146 | it = m_activeTrackers.erase(it); 147 | delete tracker; 148 | } 149 | 150 | return !!(event == TrackerEvent::Completed); 151 | } 152 | 153 | bool Torrent::checkTrackers() 154 | { 155 | for (Tracker *tracker : m_activeTrackers) 156 | if (tracker->timeUp()) 157 | std::async(std::launch::async, std::bind(&Tracker::query, tracker, makeTrackerQuery(TrackerEvent::None))); 158 | return true; 159 | } 160 | 161 | bool Torrent::nextConnection() 162 | { 163 | if (m_listener) { 164 | m_listener->accept([this] (const ConnectionPtr &c) { 165 | auto peer = std::make_shared(c, this); 166 | peer->verify(); 167 | }); 168 | 169 | return true; 170 | } 171 | 172 | return false; 173 | } 174 | 175 | bool Torrent::queryTrackers(const TrackerQuery &query, uint16_t port) 176 | { 177 | bool success = queryTracker(m_meta.tracker(), query, port); 178 | if (success) 179 | return success; 180 | 181 | for (const boost::any &s : m_meta.trackers()) { 182 | if (s.type() == typeid(VectorType)) { 183 | const VectorType &vType = Bencode::cast(s); 184 | for (const boost::any &announce : vType) 185 | if (queryTracker(Bencode::cast(announce), query, port)) 186 | return true; 187 | } else if (s.type() == typeid(std::string) && 188 | queryTracker(Bencode::cast(&s), query, port)) 189 | return true; 190 | } 191 | 192 | return false; 193 | } 194 | 195 | bool Torrent::queryTracker(const std::string &furl, const TrackerQuery &q, uint16_t tport) 196 | { 197 | UrlData url = parseUrl(furl); 198 | std::string host = URL_HOSTNAME(url); 199 | if (host.empty()) 200 | return false; 201 | 202 | std::string port = URL_SERVNAME(url); 203 | std::string protocol = URL_PROTOCOL(url); 204 | 205 | Tracker *tracker = new Tracker(this, host, port, protocol, tport); 206 | if (!tracker->query(q)) 207 | return false; 208 | 209 | m_activeTrackers.push_back(tracker); 210 | return true; 211 | } 212 | 213 | void Torrent::rawConnectPeers(const uint8_t *peers, size_t size) 214 | { 215 | m_peers.reserve(m_peers.size() + size / 6); 216 | 217 | // 6 bytes each (first 4 is ip address, last 2 port) all in big endian notation 218 | for (size_t i = 0; i < size; i += 6) { 219 | const uint8_t *iport = peers + i; 220 | uint32_t ip = readLE32(iport); 221 | if (ip == 0 || m_blacklisted.find(ip) != m_blacklisted.end()) 222 | continue; 223 | 224 | auto it = m_peers.find(ip); 225 | if (it != m_peers.end()) 226 | continue; 227 | 228 | // Asynchronously connect to that peer, and do not add it to our 229 | // active peers list unless a connection was established successfully. 230 | auto peer = std::make_shared(this); 231 | peer->connect(ip2str(ip), std::to_string(readBE16(iport + 4))); 232 | m_blacklisted.insert(ip); // remove upon connection 233 | } 234 | } 235 | 236 | void Torrent::rawConnectPeer(Dictionary &peerInfo) 237 | { 238 | std::string peerId = boost::any_cast(peerInfo["peer id"]); 239 | std::string ip = boost::any_cast(peerInfo["ip"]); 240 | int64_t port = boost::any_cast(peerInfo["port"]); 241 | 242 | uint32_t strip = str2ip(ip); 243 | if (m_blacklisted.find(strip) != m_blacklisted.end() || m_peers.find(strip) != m_peers.end()) 244 | return; 245 | 246 | // Asynchronously connect to that peer, and do not add it to our 247 | // active peers list unless a connection was established successfully. 248 | auto peer = std::make_shared(this); 249 | peer->setId(peerId); 250 | peer->connect(ip, std::to_string(port)); 251 | m_blacklisted.insert(strip); // remove upon connection 252 | } 253 | 254 | void Torrent::connectToPeers(const boost::any &_peers) 255 | { 256 | if (isFinished()) 257 | return; 258 | 259 | if (_peers.type() == typeid(std::string)) { 260 | std::string peers = *boost::unsafe_any_cast(&_peers); 261 | return rawConnectPeers((const uint8_t *)peers.c_str(), peers.length()); 262 | } 263 | 264 | if (_peers.type() == typeid(Dictionary)) { // no compat 265 | Dictionary peers = *boost::unsafe_any_cast(&_peers); 266 | m_peers.reserve(m_peers.size() + peers.size()); 267 | 268 | try { 269 | for (const auto &pair : peers) { 270 | Dictionary peerInfo = boost::any_cast(pair.second); 271 | rawConnectPeer(peerInfo); 272 | } 273 | } catch (const std::exception &e) { 274 | logfile << "connectToPeers(): non-compat Dictionary-type, exception: " << e.what() << std::endl; 275 | } 276 | } else if (_peers.type() == typeid(VectorType)) { 277 | VectorType peers = *boost::unsafe_any_cast(&_peers); 278 | m_peers.reserve(m_peers.size() + peers.size()); 279 | 280 | try { 281 | for (const boost::any &any : peers) { 282 | Dictionary peerInfo = boost::any_cast(any); 283 | rawConnectPeer(peerInfo); 284 | } 285 | } catch (const std::exception &e) { 286 | logfile << "connectToPeers(): non-compat Vector-type, exception: " << e.what() << std::endl; 287 | } 288 | } 289 | } 290 | 291 | void Torrent::addPeer(const PeerPtr &peer) 292 | { 293 | auto it = m_blacklisted.find(peer->ip()); 294 | if (it != m_blacklisted.end()) 295 | m_blacklisted.erase(it); 296 | 297 | logfile << peer->getIP() << ": now connected" << std::endl; 298 | m_peers.insert(std::make_pair(peer->ip(), peer)); 299 | } 300 | 301 | void Torrent::removePeer(const PeerPtr &peer, const std::string &errmsg) 302 | { 303 | auto it = m_peers.find(peer->ip()); 304 | if (it != m_peers.end()) 305 | m_peers.erase(it); 306 | 307 | logfile << peer->getIP() << ": closing link: " << errmsg << std::endl; 308 | } 309 | 310 | void Torrent::disconnectPeers() 311 | { 312 | for (auto it : m_peers) 313 | it.second->disconnect(); 314 | m_peers.clear(); 315 | } 316 | 317 | void Torrent::sendBitfield(const PeerPtr &peer) 318 | { 319 | const bitset *b = m_fileManager.completedBits(); 320 | if (b->count() == 0) 321 | return; 322 | 323 | uint8_t bits[b->size()]; 324 | for (size_t i = 0; i < b->size(); ++i) 325 | if (b->test(i)) 326 | bits[i] = b->bitsAt(i) & (1 << (7 - (i & 7))); 327 | peer->sendBitfield(&bits[0], b->size()); 328 | } 329 | 330 | void Torrent::requestPiece(const PeerPtr &peer) 331 | { 332 | size_t index = m_fileManager.getPieceforRequest(std::bind(&Peer::hasPiece, peer, std::placeholders::_1)); 333 | if (index != std::numeric_limits::max()) 334 | peer->sendPieceRequest(index); 335 | } 336 | 337 | bool Torrent::handlePieceCompleted(const PeerPtr &peer, uint32_t index, DataBuffer &&data) 338 | { 339 | logfile << peer->getIP() << ": finished downloading piece: " << index << std::endl; 340 | if (m_fileManager.writePieceBlock(index, peer->ip(), std::move(data))) 341 | return true; 342 | 343 | m_wastedBytes += data.size(); 344 | ++m_hashMisses; 345 | return false; 346 | } 347 | 348 | bool Torrent::handleRequestBlock(const PeerPtr &peer, uint32_t index, uint32_t begin, uint32_t length) 349 | { 350 | logfile << peer->getIP() << ": Requested piece block: " << index << std::endl; 351 | return m_fileManager.requestPieceBlock(index, peer->ip(), begin, length); 352 | } 353 | 354 | void Torrent::onPieceWriteComplete(uint32_t from, size_t index) 355 | { 356 | logfile << ip2str(from) << ": Finished writing piece: " << index << std::endl; 357 | logfile << "Pieces so far: " << m_fileManager.completedPieces() << "/" << m_fileManager.totalPieces() << std::endl; 358 | 359 | m_downloadedBytes += m_fileManager.pieceSize(index); 360 | for (const auto &it : m_peers) 361 | if (it.second->ip() != from && !it.second->hasPiece(index)) 362 | it.second->sendHave(index); 363 | } 364 | 365 | void Torrent::onPieceReadComplete(uint32_t from, size_t index, int64_t begin, uint8_t *block, size_t size) 366 | { 367 | auto it = m_peers.find(from); 368 | if (it != m_peers.end()) { 369 | it->second->sendPieceBlock(index, begin, block, size); 370 | m_uploadedBytes += size; 371 | } 372 | 373 | delete []block; 374 | } 375 | 376 | void Torrent::handleTrackerError(Tracker *tracker, const std::string &error) 377 | { 378 | logfile << tracker->host() << ": (T): " << error << std::endl; 379 | } 380 | 381 | void Torrent::handlePeerDebug(const PeerPtr &peer, const std::string &msg) 382 | { 383 | logfile << peer->getIP() << ": " << msg << std::endl; 384 | } 385 | 386 | void Torrent::handleNewPeer(const PeerPtr &peer) 387 | { 388 | m_peers.insert(std::make_pair(peer->ip(), peer)); 389 | sendBitfield(peer); 390 | } 391 | -------------------------------------------------------------------------------- /ctorrent/torrent.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __TORRENT_H 23 | #define __TORRENT_H 24 | 25 | #include "peer.h" 26 | #include "tracker.h" 27 | #include "torrentmeta.h" 28 | #include "torrentfilemanager.h" 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | #include 42 | 43 | static size_t maxRequestSize = 16384; // 16KiB initial (per piece) 44 | class Torrent 45 | { 46 | public: 47 | enum class DownloadState { 48 | None = 0, 49 | Completed = 1, 50 | TrackerQueryFailure = 2, 51 | AlreadyDownloaded = 3, 52 | NetworkError = 4 53 | }; 54 | 55 | Torrent(); 56 | ~Torrent(); 57 | 58 | DownloadState prepare(uint16_t port, bool seeder); 59 | bool checkTrackers(); 60 | bool open(const std::string& fileName, const std::string &downloadDir); 61 | bool prepareforSeed(uint16_t port); 62 | bool nextConnection(); 63 | bool finish(); 64 | bool isFinished() const { return m_fileManager.totalPieces() == m_fileManager.completedPieces(); } 65 | bool hasTrackers() const { return !m_activeTrackers.empty(); } 66 | 67 | size_t activePeers() const { return m_peers.size(); } 68 | size_t downloadedBytes() const { return m_downloadedBytes; } 69 | size_t uploadedBytes() const { return m_uploadedBytes; } 70 | size_t wastedBytes() const { return m_wastedBytes; } 71 | size_t hashMisses() const { return m_hashMisses; } 72 | size_t computeDownloaded() { return m_fileManager.computeDownloaded(); } 73 | 74 | double eta(); 75 | double downloadSpeed(); 76 | clock_t elapsed(); 77 | 78 | // Get associated meta info for this torrent 79 | TorrentMeta *meta() { return &m_meta; } 80 | 81 | // Get associated file manager for this torrent 82 | TorrentFileManager *fileManager() { return &m_fileManager; } 83 | 84 | protected: 85 | bool queryTrackers(const TrackerQuery &r, uint16_t port); 86 | bool queryTracker(const std::string &url, const TrackerQuery &r, uint16_t port); 87 | void rawConnectPeers(const uint8_t *peers, size_t size); 88 | void rawConnectPeer(Dictionary &peerInfo); 89 | void connectToPeers(const boost::any &peers); 90 | void sendBitfield(const PeerPtr &peer); 91 | void requestPiece(const PeerPtr &peer); 92 | 93 | TrackerQuery makeTrackerQuery(TrackerEvent event); 94 | void addBlacklist(uint32_t ip); 95 | void addPeer(const PeerPtr &peer); 96 | void removePeer(const PeerPtr &peer, const std::string &errmsg); 97 | void disconnectPeers(); 98 | 99 | const uint8_t *peerId() const { return m_peerId; } 100 | const uint8_t *handshake() const { return m_handshake; } 101 | 102 | // Peer -> Torrent 103 | void handleTrackerError(Tracker *tracker, const std::string &error); 104 | void handlePeerDebug(const PeerPtr &peer, const std::string &msg); 105 | void handleNewPeer(const PeerPtr &peer); 106 | bool handlePieceCompleted(const PeerPtr &peer, uint32_t index, DataBuffer &&data); 107 | bool handleRequestBlock(const PeerPtr &peer, uint32_t index, uint32_t begin, uint32_t length); 108 | 109 | public: 110 | // TorrentFileManager -> Torrent 111 | void onPieceWriteComplete(uint32_t from, size_t index); 112 | void onPieceReadComplete(uint32_t from, size_t index, int64_t begin, uint8_t *block, size_t size); 113 | 114 | private: 115 | Server *m_listener; 116 | TorrentMeta m_meta; 117 | TorrentFileManager m_fileManager; 118 | 119 | std::vector m_activeTrackers; 120 | std::unordered_map m_peers; 121 | std::unordered_set m_blacklisted; 122 | 123 | size_t m_uploadedBytes; 124 | size_t m_downloadedBytes; 125 | size_t m_wastedBytes; 126 | size_t m_hashMisses; 127 | 128 | clock_t m_startTime; 129 | uint8_t m_handshake[68]; 130 | uint8_t m_peerId[20]; 131 | 132 | friend class Peer; 133 | friend class Tracker; 134 | }; 135 | 136 | #endif 137 | 138 | -------------------------------------------------------------------------------- /ctorrent/torrentfilemanager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "torrentfilemanager.h" 23 | #include "torrent.h" 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | struct TorrentFile { 39 | FILE *fp; 40 | TorrentFileInfo info; 41 | }; 42 | 43 | struct ReadRequest { 44 | size_t index; 45 | uint32_t id; 46 | uint32_t from; 47 | size_t begin; 48 | size_t size; 49 | }; 50 | struct LeastReadRequest { 51 | bool operator() (const ReadRequest &lhs, const ReadRequest &rhs) const { 52 | return lhs.id > rhs.id; 53 | } 54 | }; 55 | 56 | struct WriteRequest { 57 | size_t index; 58 | uint32_t from; 59 | DataBuffer data; 60 | }; 61 | 62 | struct Piece { 63 | Piece(uint32_t *sum) { 64 | memcpy(&hash[0], &sum[0], sizeof(hash)); 65 | pri = 0; 66 | } 67 | 68 | int32_t pri; // TODO Support more piece download strategies 69 | uint32_t hash[5]; 70 | }; 71 | 72 | class TorrentFileManagerImpl { 73 | public: 74 | TorrentFileManagerImpl(Torrent *t) { 75 | m_torrent = t; 76 | m_stopped = false; 77 | m_thread = std::thread(std::bind(&TorrentFileManagerImpl::thread, this)); 78 | } 79 | 80 | ~TorrentFileManagerImpl() { 81 | lock(); 82 | m_stopped = true; 83 | unlock_and_notify(); 84 | m_thread.join(); 85 | } 86 | 87 | public: 88 | void lock() { m_mutex.lock(); } 89 | void unlock() { m_mutex.unlock(); } 90 | void unlock_and_notify() { m_mutex.unlock(); m_condition.notify_all(); } 91 | 92 | void push_read(const ReadRequest &r) { m_readRequests.push(r); } 93 | void push_write(WriteRequest &&w) { m_writeRequests.push(std::move(w)); m_pendingBits.set(w.index); } 94 | void push_file(const TorrentFile &f) { m_files.push_back(f); } 95 | void scan_file(const TorrentFile &f); 96 | void scan_pieces(); 97 | 98 | const bitset *completed_bits() const { return &m_completedBits; } 99 | size_t pending() const { return m_pendingBits.count(); } 100 | size_t completed_pieces() const { return m_completedBits.count(); } 101 | size_t total_pieces() const { return m_pieces.size(); } 102 | size_t get_next_piece(const std::function &fun); 103 | size_t compute_downloaded(); 104 | 105 | bool piece_done(size_t index) const { return index < m_pieces.size() && m_completedBits.test(index); } 106 | bool piece_pending(size_t index) const { return index < m_pieces.size() && m_pendingBits.test(index); } 107 | bool intact(size_t index) const { return index < m_pieces.size(); } 108 | bool is_read_eligible(size_t index, int64_t end) const { return m_completedBits.test(index) && end < piece_length(index); } 109 | bool is_write_eligible(size_t index, const uint8_t *data, size_t size) const { 110 | if (m_pendingBits.test(index)) 111 | return false; 112 | 113 | boost::uuids::detail::sha1 sha1; 114 | sha1.process_bytes(data, size); 115 | 116 | uint32_t digest[5]; 117 | sha1.get_digest(digest); 118 | 119 | return memcmp(digest, m_pieces[index].hash, 5) == 0; 120 | } 121 | 122 | int64_t piece_length(size_t index) const { 123 | const TorrentMeta *meta = m_torrent->meta(); 124 | if (index == m_pieces.size() - 1) 125 | return last_piece_length(); 126 | 127 | return meta->pieceLength(); 128 | } 129 | 130 | int64_t last_piece_length() const { 131 | const TorrentMeta *meta = m_torrent->meta(); 132 | if (int64_t r = meta->totalSize() % meta->pieceLength()) 133 | return r; 134 | 135 | return meta->pieceLength(); 136 | } 137 | 138 | void init_pieces() { 139 | auto sha1sums = m_torrent->meta()->sha1sums(); 140 | m_pieces.reserve(sha1sums.size()); 141 | m_completedBits.construct(sha1sums.size()); 142 | m_pendingBits.construct(sha1sums.size()); 143 | 144 | for (sha1sum s : sha1sums) 145 | m_pieces.push_back(Piece(s)); 146 | } 147 | 148 | protected: 149 | void thread(); 150 | 151 | bool process_read(const ReadRequest &r); 152 | bool process_write(const WriteRequest &w); 153 | 154 | private: 155 | std::priority_queue, LeastReadRequest> m_readRequests; 156 | std::queue m_writeRequests; 157 | 158 | bitset m_completedBits; 159 | bitset m_pendingBits; 160 | 161 | std::vector m_files; 162 | std::vector m_pieces; 163 | 164 | std::thread m_thread; 165 | std::mutex m_mutex; 166 | std::condition_variable m_condition; 167 | std::atomic_bool m_stopped; 168 | 169 | Torrent *m_torrent; 170 | friend class TorrentFileManager; 171 | }; 172 | 173 | void TorrentFileManagerImpl::scan_file(const TorrentFile &f) 174 | { 175 | FILE *fp = f.fp; 176 | fseek(fp, 0L, SEEK_END); 177 | size_t fileLength = ftello(fp); 178 | fseek(fp, 0L, SEEK_SET); 179 | 180 | if (f.info.length > fileLength) { 181 | // truncate(f.info.path.c_str(), f.info.length); 182 | return; 183 | } 184 | 185 | size_t m_pieceLength = m_torrent->meta()->pieceLength(); 186 | size_t pieceIndex = 0; 187 | if (f.info.begin > 0) 188 | pieceIndex = f.info.begin / m_pieceLength; 189 | 190 | size_t pieceBegin = pieceIndex * m_pieceLength; 191 | size_t pieceLength = piece_length(pieceIndex); 192 | size_t fileEnd = f.info.begin + f.info.length; 193 | if (pieceBegin + pieceLength > fileEnd) 194 | return; 195 | 196 | uint8_t buf[m_pieceLength]; 197 | if (pieceBegin < f.info.begin) { 198 | size_t bufPos = f.info.begin - pieceBegin; 199 | if (fread(&buf[bufPos], 1, m_pieceLength - bufPos, fp) != m_pieceLength - bufPos) 200 | return; 201 | 202 | size_t index = f.info.index; 203 | while (bufPos != 0) { 204 | TorrentFile *cf = &m_files[--index]; 205 | FILE *fpp = cf->fp; 206 | 207 | if (bufPos > cf->info.length) { 208 | if (fread(&buf[bufPos - cf->info.length], 1, bufPos, fpp) != cf->info.length) 209 | return; 210 | 211 | bufPos -= cf->info.length; 212 | } else { 213 | fseek(fpp, cf->info.length - bufPos, SEEK_SET); 214 | if (fread(&buf[0], 1, bufPos, fpp) != bufPos) 215 | return; 216 | 217 | break; 218 | } 219 | } 220 | 221 | if (is_write_eligible(pieceIndex, &buf[0], m_pieceLength)) 222 | m_completedBits.set(pieceIndex); 223 | 224 | pieceBegin += pieceLength; 225 | ++pieceIndex; 226 | } 227 | 228 | fseek(fp, pieceBegin - f.info.begin, SEEK_SET); 229 | size_t real = pieceIndex; 230 | bool escape = false; 231 | #pragma omp parallel for 232 | for (size_t i = pieceIndex; i < m_pieces.size() - 1; ++i) { 233 | if (escape) 234 | continue; 235 | 236 | if (pieceBegin + m_pieceLength >= fileEnd) { 237 | #pragma omp critical 238 | escape = true; 239 | real = i; 240 | continue; 241 | } 242 | 243 | fread(&buf[0], 1, m_pieceLength, fp); 244 | if (is_write_eligible(i, &buf[0], m_pieceLength)) 245 | m_completedBits.set(i); 246 | 247 | pieceBegin += m_pieceLength; 248 | } 249 | 250 | pieceIndex = real; 251 | if (pieceIndex == total_pieces() - 1) { 252 | pieceLength = last_piece_length(); 253 | fread(&buf[0], 1, pieceLength, fp); 254 | if (is_write_eligible(pieceIndex, &buf[0], pieceLength)) 255 | m_completedBits.set(pieceIndex); 256 | } 257 | } 258 | 259 | void TorrentFileManagerImpl::scan_pieces() 260 | { 261 | for (const TorrentFile &f : m_files) 262 | scan_file(f); 263 | } 264 | 265 | void TorrentFileManagerImpl::thread() 266 | { 267 | std::unique_lock lock(m_mutex, std::defer_lock); 268 | 269 | while (!m_stopped) { 270 | lock.lock(); 271 | if (m_writeRequests.empty() && m_readRequests.empty()) 272 | m_condition.wait(lock); 273 | 274 | // It doesn't really matter which one we process first 275 | // as torrent should be aware of our write process and should not 276 | // mark the piece as fully have before we fully wrote it to disk. 277 | while (!m_writeRequests.empty() && !m_stopped) { 278 | const WriteRequest &w = m_writeRequests.front(); 279 | if (!process_write(w)) 280 | break; 281 | 282 | m_writeRequests.pop(); 283 | } 284 | 285 | while (!m_readRequests.empty() && !m_stopped) { 286 | ReadRequest r = m_readRequests.top(); 287 | if (!process_read(r)) 288 | break; 289 | 290 | m_readRequests.pop(); 291 | } 292 | 293 | if (m_stopped) 294 | break; 295 | 296 | lock.unlock(); 297 | } 298 | } 299 | 300 | bool TorrentFileManagerImpl::process_read(const ReadRequest &r) 301 | { 302 | const TorrentMeta *meta = m_torrent->meta(); 303 | int64_t blockBegin = r.begin + r.index * meta->pieceLength(); 304 | int64_t blockEnd = r.begin + r.size; 305 | uint8_t *block = new uint8_t[r.size]; 306 | 307 | size_t writePos = 0; 308 | for (const TorrentFile &f : m_files) { 309 | const TorrentFileInfo &i = f.info; 310 | size_t filePos = blockBegin + writePos; 311 | if (filePos < i.begin) 312 | return false; 313 | 314 | size_t fileEnd = i.begin + i.length; 315 | if (filePos > fileEnd) 316 | continue; 317 | fseek(f.fp, filePos - i.begin, SEEK_SET); 318 | 319 | size_t readSize = std::max(fileEnd - filePos, r.size - writePos); 320 | size_t maxRead = writePos + readSize; 321 | while (writePos < maxRead) { 322 | int read = fread(&block[writePos], 1, readSize - writePos, f.fp); 323 | if (read <= 0) 324 | return false; 325 | 326 | writePos += read; 327 | } 328 | } 329 | 330 | std::async(std::launch::async, 331 | std::bind(&Torrent::onPieceReadComplete, m_torrent, r.from, r.index, r.begin, block, r.size)); 332 | return true; 333 | } 334 | 335 | bool TorrentFileManagerImpl::process_write(const WriteRequest &w) 336 | { 337 | const TorrentMeta *meta = m_torrent->meta(); 338 | size_t beginPos = w.index * meta->pieceLength(); 339 | 340 | const uint8_t *buf = &w.data[0]; 341 | size_t length = w.data.size(); 342 | size_t off = 0; 343 | 344 | for (const TorrentFile &f : m_files) { 345 | const TorrentFileInfo &i = f.info; 346 | if (beginPos < i.begin) 347 | break; 348 | 349 | size_t fileEnd = i.begin + i.length; 350 | if (beginPos >= fileEnd) 351 | continue; 352 | 353 | size_t amount = fileEnd - beginPos; 354 | if (amount > length) 355 | amount = length; 356 | 357 | fseek(f.fp, beginPos - i.begin, SEEK_SET); 358 | size_t wrote = fwrite(buf + off, 1, amount, f.fp); 359 | off += wrote; 360 | beginPos += wrote; 361 | length -= wrote; 362 | } 363 | 364 | m_pendingBits.clear(w.index); 365 | m_completedBits.set(w.index); 366 | std::async(std::launch::async, 367 | std::bind(&Torrent::onPieceWriteComplete, m_torrent, w.from, w.index)); 368 | return true; 369 | } 370 | 371 | size_t TorrentFileManagerImpl::get_next_piece(const std::function &fun) 372 | { 373 | size_t index = 0; 374 | int32_t priority = std::numeric_limits::max(); 375 | 376 | std::lock_guard guard(m_mutex); 377 | for (size_t i = 0; i < m_pieces.size(); ++i) { 378 | Piece *p = &m_pieces[i]; 379 | if (m_completedBits.test(i) || m_pendingBits.test(i) || !fun(i)) 380 | continue; 381 | 382 | if (!p->pri) { 383 | p->pri = 1; 384 | return i; 385 | } 386 | 387 | if (priority > p->pri) { 388 | priority = p->pri; 389 | index = i; 390 | } 391 | } 392 | 393 | if (priority != std::numeric_limits::max()) { 394 | ++m_pieces[index].pri; 395 | return index; 396 | } 397 | 398 | return std::numeric_limits::max(); 399 | } 400 | 401 | size_t TorrentFileManagerImpl::compute_downloaded() 402 | { 403 | size_t i = 0; 404 | size_t downloaded = 0; 405 | size_t pieceLength = m_torrent->meta()->pieceLength(); 406 | 407 | std::lock_guard guard(m_mutex); 408 | for (; i < m_pieces.size() - 1; ++i) 409 | if (m_completedBits.test(i)) 410 | downloaded += pieceLength; 411 | 412 | if (m_completedBits.test(i + 1)) 413 | downloaded += last_piece_length(); 414 | 415 | return downloaded; 416 | } 417 | 418 | TorrentFileManager::TorrentFileManager(Torrent *torrent) 419 | { 420 | i = new TorrentFileManagerImpl(torrent); 421 | } 422 | 423 | TorrentFileManager::~TorrentFileManager() 424 | { 425 | delete i; 426 | } 427 | 428 | const bitset *TorrentFileManager::completedBits() const 429 | { 430 | return i->completed_bits(); 431 | } 432 | 433 | size_t TorrentFileManager::pieceSize(size_t index) const 434 | { 435 | return i->piece_length(index); 436 | } 437 | 438 | size_t TorrentFileManager::completedPieces() const 439 | { 440 | return i->completed_pieces(); 441 | } 442 | 443 | size_t TorrentFileManager::totalPieces() const 444 | { 445 | return i->total_pieces(); 446 | } 447 | 448 | size_t TorrentFileManager::getPieceforRequest(const std::function &fun) 449 | { 450 | return i->get_next_piece(fun); 451 | } 452 | 453 | size_t TorrentFileManager::computeDownloaded() 454 | { 455 | return i->compute_downloaded(); 456 | } 457 | 458 | size_t TorrentFileManager::pending() const 459 | { 460 | return i->pending(); 461 | } 462 | 463 | bool TorrentFileManager::pieceDone(size_t index) const 464 | { 465 | return i->piece_done(index); 466 | } 467 | 468 | bool TorrentFileManager::piecePending(size_t index) const 469 | { 470 | return i->piece_pending(index); 471 | } 472 | 473 | bool TorrentFileManager::registerFiles(const std::string &baseDir, const TorrentFiles &files) 474 | { 475 | // We have to initialize pieces here 476 | i->init_pieces(); 477 | 478 | // Make sure the base directory exists 479 | MKDIR(baseDir); 480 | 481 | for (const TorrentFileInfo &inf : files) { 482 | std::string filePath = inf.path.substr(0, inf.path.find_last_of('/')); 483 | std::string fullPath = baseDir + inf.path; 484 | 485 | if (inf.path != filePath) { 486 | std::string path = baseDir + filePath; 487 | if (!validatePath(baseDir, path)) 488 | return false; 489 | 490 | if (!nodeExists(path)) 491 | MKDIR(path); 492 | } 493 | 494 | if (!nodeExists(fullPath)) { 495 | // touch 496 | FILE *fp = fopen(fullPath.c_str(), "wb"); 497 | if (!fp) 498 | return false; 499 | 500 | fclose(fp); 501 | } 502 | 503 | // Open for read and write 504 | FILE *fp = fopen(fullPath.c_str(), "rb+"); 505 | if (!fp) 506 | return false; 507 | 508 | TorrentFile f = { 509 | .fp = fp, 510 | .info = inf 511 | }; 512 | i->push_file(f); 513 | } 514 | 515 | i->scan_pieces(); 516 | return true; 517 | } 518 | 519 | bool TorrentFileManager::requestPieceBlock(size_t index, uint32_t from, size_t begin, size_t size) 520 | { 521 | static uint32_t rid = 0; 522 | if (!i->intact(index) || !i->is_read_eligible(index, begin + size)) 523 | return false; 524 | 525 | ReadRequest r = { 526 | .index = index, 527 | .id = rid++, 528 | .from = from, 529 | .begin = begin, 530 | .size = size 531 | }; 532 | 533 | i->lock(); 534 | i->push_read(r); 535 | i->unlock_and_notify(); 536 | return true; 537 | } 538 | 539 | bool TorrentFileManager::writePieceBlock(size_t index, uint32_t from, DataBuffer &&data) 540 | { 541 | if (!i->intact(index) || !i->is_write_eligible(index, &data[0], data.size())) 542 | return false; 543 | 544 | WriteRequest w; 545 | w.index = index; 546 | w.from = from; 547 | w.data = std::move(data); 548 | 549 | i->lock(); 550 | i->push_write(std::move(w)); 551 | i->unlock_and_notify(); 552 | return true; 553 | } 554 | 555 | -------------------------------------------------------------------------------- /ctorrent/torrentfilemanager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __TORRENTFILEMANAGER_H 23 | #define __TORRENTFILEMANAGER_H 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | struct TorrentFileInfo { 34 | std::string path; 35 | size_t index; 36 | size_t begin; 37 | size_t length; 38 | }; 39 | typedef std::vector TorrentFiles; 40 | 41 | class Torrent; 42 | class TorrentFileManagerImpl; 43 | class TorrentFileManager { 44 | public: 45 | TorrentFileManager(Torrent *torrent); 46 | ~TorrentFileManager(); 47 | 48 | const bitset *completedBits() const; 49 | size_t pending() const; 50 | size_t pieceSize(size_t index) const; 51 | size_t completedPieces() const; 52 | size_t totalPieces() const; 53 | size_t getPieceforRequest(const std::function &fun); 54 | size_t computeDownloaded(); 55 | 56 | bool pieceDone(size_t index) const; 57 | bool piecePending(size_t index) const; 58 | bool registerFiles(const std::string &baseDir, const TorrentFiles &files); 59 | bool requestPieceBlock(size_t index, uint32_t from, size_t begin, size_t size); 60 | bool writePieceBlock(size_t index, uint32_t from, DataBuffer &&data); 61 | 62 | private: 63 | TorrentFileManagerImpl *i; 64 | }; 65 | 66 | #endif 67 | 68 | -------------------------------------------------------------------------------- /ctorrent/torrentmeta.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "torrentmeta.h" 23 | 24 | #include 25 | #include 26 | 27 | TorrentMeta::TorrentMeta() 28 | : m_pieceLength(0), 29 | m_totalSize(0) 30 | { 31 | } 32 | 33 | TorrentMeta::~TorrentMeta() 34 | { 35 | } 36 | 37 | bool TorrentMeta::parse(const std::string &fileName) 38 | { 39 | Bencode bencode; 40 | Dictionary dict = bencode.decode(fileName); 41 | 42 | if (dict.empty()) 43 | return false; 44 | 45 | return internalParse(dict, bencode); 46 | } 47 | 48 | bool TorrentMeta::parse(const char *data, size_t size) 49 | { 50 | Bencode bencode; 51 | Dictionary dict = bencode.decode(data, size); 52 | 53 | if (dict.empty()) 54 | return false; 55 | 56 | return internalParse(dict, bencode); 57 | } 58 | 59 | bool TorrentMeta::internalParse(Dictionary &dict, Bencode &bencode) 60 | { 61 | if (!dict.count("announce")) 62 | return false; 63 | 64 | m_mainTracker = Bencode::cast(dict["announce"]); 65 | if (dict.count("comment")) 66 | m_comment = Bencode::cast(dict["comment"]); 67 | if (dict.count("created by")) 68 | m_createdBy = Bencode::cast(dict["created by"]); 69 | if (dict.count("announce-list")) 70 | m_trackers = Bencode::cast(dict["announce-list"]); 71 | 72 | Dictionary info = Bencode::cast(dict["info"]); 73 | if (info.empty()) 74 | return false; 75 | 76 | size_t pos = bencode.pos(); 77 | bencode.encode(info); 78 | if (!info.count("pieces") || !info.count("piece length")) 79 | return false; 80 | 81 | size_t bufferSize; 82 | const char *buffer = bencode.buffer(pos, bufferSize); 83 | 84 | boost::uuids::detail::sha1 sha1; 85 | sha1.process_bytes(buffer, bufferSize); 86 | sha1.get_digest(m_checkSum); 87 | 88 | m_name = Bencode::cast(info["name"]); 89 | m_pieceLength = Bencode::cast(info["piece length"]); 90 | 91 | std::string pieces = Bencode::cast(info["pieces"]); 92 | m_sha1sums.reserve(pieces.size() / 20); 93 | 94 | for (size_t i = 0; i < pieces.size(); i += 20) { 95 | sha1sum s; 96 | const uint8_t *sha1sum = (const uint8_t *)pieces.c_str() + i; 97 | for (size_t k = 0; k < 5; ++k) { 98 | const uint8_t *tmp = sha1sum + k * 4; 99 | uint32_t *out = &s.i[k]; 100 | 101 | *out = tmp[0] << 24; 102 | *out |= tmp[1] << 16; 103 | *out |= tmp[2] << 8; 104 | *out |= tmp[3] << 0; 105 | } 106 | 107 | m_sha1sums.push_back(s); 108 | } 109 | 110 | if (info.count("files")) { 111 | m_dirName = Bencode::cast(info["name"]); 112 | 113 | size_t index = 0; 114 | size_t begin = 0; 115 | 116 | const boost::any &any = info["files"]; 117 | if (any.type() == typeid(Dictionary)) { 118 | for (const auto &pair : Bencode::cast(any)) { 119 | Dictionary v = Bencode::cast(pair.second); 120 | VectorType pathList = Bencode::cast(v["path"]); 121 | 122 | if (!parseFile(pathList, index, begin, Bencode::cast(v["length"]))) 123 | return false; 124 | } 125 | } else if (any.type() == typeid(VectorType)) { 126 | for (const auto &f : Bencode::cast(any)) { 127 | Dictionary v = Bencode::cast(f); 128 | VectorType pathList = Bencode::cast(v["path"]); 129 | 130 | if (!parseFile(pathList, index, begin, Bencode::cast(v["length"]))) 131 | return false; 132 | } 133 | } else { 134 | // ... nope 135 | return false; 136 | } 137 | } else { 138 | size_t length = Bencode::cast(info["length"]); 139 | if (length == 0) 140 | return false; 141 | 142 | TorrentFileInfo f = { 143 | .path = m_name, 144 | .index = 0, 145 | .begin = 0, 146 | .length = length 147 | }; 148 | 149 | m_totalSize = length; 150 | m_files.push_back(f); 151 | } 152 | 153 | return true; 154 | } 155 | 156 | bool TorrentMeta::parseFile(const VectorType &pathList, size_t &index, size_t &begin, size_t length) 157 | { 158 | std::string path = ""; 159 | for (const boost::any &any : pathList) { 160 | const std::string &s = Bencode::cast(any); 161 | if (!path.empty()) 162 | path += PATH_SEP; 163 | path += s; 164 | } 165 | 166 | TorrentFileInfo file = { 167 | .path = path, 168 | .index = index, 169 | .begin = begin, 170 | .length = length 171 | }; 172 | 173 | m_files.push_back(file); 174 | m_totalSize += length; 175 | begin += length; 176 | ++index; 177 | return true; 178 | } 179 | 180 | -------------------------------------------------------------------------------- /ctorrent/torrentmeta.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __TORRENTMETA_H 23 | #define __TORRENTMETA_H 24 | 25 | #include "torrentfilemanager.h" 26 | 27 | #include 28 | 29 | struct sha1sum { 30 | uint32_t i[5]; 31 | operator uint32_t *() { return &i[0]; } 32 | }; 33 | 34 | class TorrentMeta { 35 | public: 36 | TorrentMeta(); 37 | ~TorrentMeta(); 38 | 39 | bool parse(const std::string &fileName); 40 | bool parse(const char *data, size_t size); 41 | 42 | inline TorrentFiles files() const { return m_files; } 43 | inline std::string baseDir() const { return m_dirName; } 44 | inline std::vector sha1sums() const { return m_sha1sums; } 45 | 46 | inline std::string name() const { return m_name; } 47 | inline std::string comment() const { return m_comment; } 48 | inline std::string createdBy() const { return m_createdBy; } 49 | inline std::string tracker() const { return m_mainTracker; } 50 | inline VectorType trackers() const { return m_trackers; } 51 | 52 | inline const uint32_t *checkSum() const { return &m_checkSum[0]; } 53 | inline size_t pieceLength() const { return m_pieceLength; } 54 | inline size_t totalSize() const { return m_totalSize; } 55 | 56 | protected: 57 | bool internalParse(Dictionary &d, Bencode &b); 58 | bool parseFile(const VectorType &pathList, size_t &index, size_t &begin, size_t length); 59 | 60 | private: 61 | std::string m_dirName; 62 | std::string m_name; 63 | std::string m_comment; 64 | std::string m_createdBy; 65 | std::string m_mainTracker; 66 | 67 | uint32_t m_checkSum[5]; 68 | size_t m_pieceLength; 69 | size_t m_totalSize; 70 | 71 | TorrentFiles m_files; 72 | VectorType m_trackers; 73 | std::vector m_sha1sums; 74 | }; 75 | 76 | #endif 77 | 78 | -------------------------------------------------------------------------------- /ctorrent/tracker.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "tracker.h" 23 | #include "torrent.h" 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | bool Tracker::query(const TrackerQuery &req) 30 | { 31 | bool ret = m_type == TrackerHTTP ? httpRequest(req) : udpRequest(req); 32 | if (!ret) 33 | m_timeToNextRequest = std::chrono::system_clock::now() + std::chrono::milliseconds(100); 34 | return ret; 35 | } 36 | 37 | bool Tracker::httpRequest(const TrackerQuery &r) 38 | { 39 | asio::ip::tcp::resolver resolver(g_service); 40 | asio::ip::tcp::resolver::query query(m_host, m_port); 41 | boost::system::error_code error; 42 | 43 | asio::ip::tcp::resolver::iterator endpoint = resolver.resolve(query, error); 44 | asio::ip::tcp::resolver::iterator end; 45 | if (error) { 46 | m_torrent->handleTrackerError(this, "Unable to resolve host: " + error.message()); 47 | return false; 48 | } 49 | 50 | asio::ip::tcp::socket socket(g_service); 51 | do { 52 | socket.close(); 53 | socket.connect(*endpoint++, error); 54 | } while (error && endpoint != end); 55 | if (error) { 56 | m_torrent->handleTrackerError(this, "Failed to connect: " + error.message()); 57 | return false; 58 | } 59 | 60 | std::string req; 61 | switch (r.event) { 62 | case TrackerEvent::None: req = ""; break; // prevent a useless warning 63 | case TrackerEvent::Completed: req = "event=completed&"; break; 64 | case TrackerEvent::Stopped: req = "event=stopped&"; break; 65 | case TrackerEvent::Started: req = "event=started&"; break; 66 | } 67 | 68 | // Used to extract some things. 69 | const uint8_t *handshake = m_torrent->handshake(); 70 | char infoHash[20]; 71 | memcpy(infoHash, &handshake[28], 20); 72 | 73 | asio::streambuf params; 74 | std::ostream buf(¶ms); 75 | buf << "GET /announce?" << req << "info_hash=" << urlencode(std::string(infoHash, 20)) << "&port=" << m_tport << "&compact=1&key=1337T0RRENT" 76 | << "&peer_id=" << urlencode(std::string((const char *)m_torrent->peerId(), 20)) << "&downloaded=" << r.downloaded << "&uploaded=" << r.uploaded 77 | << "&left=" << r.remaining << " HTTP/1.0\r\n" 78 | << "Host: " << m_host << "\r\n" 79 | << "\r\n"; 80 | asio::write(socket, params); 81 | 82 | asio::streambuf response; 83 | try { 84 | asio::read_until(socket, response, "\r\n"); 85 | } catch (const std::exception &e) { 86 | m_torrent->handleTrackerError(this, "Unable to read data: " + std::string(e.what())); 87 | return false; 88 | } 89 | 90 | std::istream rbuf(&response); 91 | std::string httpVersion; 92 | rbuf >> httpVersion; 93 | 94 | if (httpVersion.substr(0, 5) != "HTTP/") { 95 | m_torrent->handleTrackerError(this, "Tracker send an invalid HTTP version response"); 96 | return false; 97 | } 98 | 99 | int status; 100 | rbuf >> status; 101 | if (status != 200) { 102 | std::ostringstream os; 103 | os << "Tracker failed to process our request: " << status; 104 | m_torrent->handleTrackerError(this, os.str()); 105 | return false; 106 | } 107 | 108 | try { 109 | asio::read_until(socket, response, "\r\n\r\n"); 110 | } catch (const std::exception &e) { 111 | m_torrent->handleTrackerError(this, "Unable to read data: " + std::string(e.what())); 112 | return false; 113 | } 114 | socket.close(); 115 | 116 | // Seek to start of body 117 | std::string header; 118 | while (std::getline(rbuf, header) && header != "\r"); 119 | if (!rbuf) { 120 | m_torrent->handleTrackerError(this, "Unable to get to tracker response body."); 121 | return false; 122 | } 123 | 124 | std::ostringstream os; 125 | os << &response; 126 | 127 | Bencode bencode; 128 | Dictionary dict = bencode.decode(os.str().c_str(), os.str().length()); 129 | if (dict.empty()) { 130 | m_torrent->handleTrackerError(this, "Unable to decode tracker response body"); 131 | return false; 132 | } 133 | 134 | if (dict.count("failure reason") != 0) { 135 | m_torrent->handleTrackerError(this, Bencode::cast(dict["failure reason"])); 136 | return false; 137 | } 138 | 139 | m_timeToNextRequest = std::chrono::system_clock::now() 140 | + std::chrono::seconds(Bencode::cast(dict["interval"])); 141 | m_torrent->connectToPeers(dict["peers"]); 142 | return true; 143 | } 144 | 145 | bool Tracker::udpRequest(const TrackerQuery &r) 146 | { 147 | asio::ip::udp::resolver resolver(g_service); 148 | asio::ip::udp::resolver::query query(m_host, m_port); 149 | boost::system::error_code error; 150 | 151 | asio::ip::udp::resolver::iterator ep_iter = resolver.resolve(query, error); 152 | if (error) { 153 | m_torrent->handleTrackerError(this, "Unable to resolve host: " + error.message()); 154 | return false; 155 | } 156 | 157 | asio::ip::udp::endpoint r_endpoint; 158 | asio::ip::udp::endpoint endpoint = *ep_iter; 159 | asio::ip::udp::socket socket(g_service); 160 | socket.open(endpoint.protocol()); 161 | 162 | static std::random_device rd; 163 | static std::mt19937 generator(rd()); 164 | static std::uniform_int_distribution random(0x00, 0xFFFFFFFF); 165 | uint32_t tx = random(generator); 166 | 167 | uint8_t buf[16]; 168 | writeBE64(&buf[0], 0x41727101980); // connection id 169 | writeBE32(&buf[8], 0x00); // action 170 | writeBE32(&buf[12], tx); 171 | 172 | if (socket.send_to(asio::buffer(&buf[0], 16), endpoint) != 16) { 173 | m_torrent->handleTrackerError(this, "Failed to initiate UDP transaction"); 174 | socket.close(); 175 | return false; 176 | } 177 | 178 | boost::asio::socket_base::non_blocking_io command(true); 179 | socket.io_control(command); 180 | 181 | int len = 0; 182 | for (int tries = 0; tries < 10 && len != 16; ++tries) { 183 | len = socket.receive_from(asio::buffer(buf, 16), r_endpoint, 0, error); 184 | std::this_thread::sleep_for(std::chrono::milliseconds(tries * 30)); 185 | } 186 | 187 | if (readBE32(&buf[0]) != 0) { 188 | m_torrent->handleTrackerError(this, "action mismatch"); 189 | socket.close(); 190 | return false; 191 | } 192 | 193 | if (readBE32(&buf[4]) != tx) { 194 | m_torrent->handleTrackerError(this, "transaction id mismatch"); 195 | socket.close(); 196 | return false; 197 | } 198 | 199 | uint64_t cid = readBE64(&buf[8]); 200 | uint8_t re[98]; 201 | writeBE64(&re[0], cid); 202 | writeBE32(&re[8], 1); 203 | writeBE32(&re[12], tx); 204 | 205 | const uint8_t *handshake = m_torrent->handshake(); 206 | memcpy(&re[16], &handshake[28], 20); 207 | memcpy(&re[36], m_torrent->peerId(), 20); 208 | 209 | writeBE64(&re[56], r.downloaded); 210 | writeBE64(&re[64], r.remaining); 211 | writeBE64(&re[72], r.uploaded); 212 | writeBE32(&re[80], (uint32_t)r.event); 213 | writeBE32(&re[84], 0); // ip 214 | writeBE32(&re[88], 0); // key 215 | writeBE32(&re[92], -1); // num want 216 | writeBE16(&re[96], m_tport); 217 | 218 | if (socket.send_to(asio::buffer(&re[0], sizeof(re)), endpoint) != sizeof(re)) { 219 | m_torrent->handleTrackerError(this, "failed to send announce data"); 220 | socket.close(); 221 | return false; 222 | } 223 | 224 | uint8_t response[1500]; 225 | len = 0; 226 | for (int tries = 0; tries < 15; ++tries) { 227 | int read = socket.receive_from(asio::buffer(response + len, sizeof(response) - len), r_endpoint, 0, error); 228 | if (read > 0) 229 | len += read; 230 | 231 | std::this_thread::sleep_for(std::chrono::milliseconds(tries * 30)); 232 | } 233 | 234 | socket.close(); 235 | if (len < 20) { 236 | m_torrent->handleTrackerError(this, "expected at least 20 bytes response"); 237 | return false; 238 | } 239 | 240 | if (readBE32(&response[0]) != 1) { 241 | m_torrent->handleTrackerError(this, "2: action mismatch"); 242 | return false; 243 | } 244 | 245 | if (readBE32(&response[4]) != tx) { 246 | m_torrent->handleTrackerError(this, "2: transaction mismatch"); 247 | return false; 248 | } 249 | 250 | m_timeToNextRequest = std::chrono::system_clock::now() + std::chrono::seconds(readBE32(&response[8])); 251 | m_torrent->rawConnectPeers(&response[20], len - 20); 252 | return true; 253 | } 254 | 255 | -------------------------------------------------------------------------------- /ctorrent/tracker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __TRACKER_H 23 | #define __TRACKER_H 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | typedef std::chrono::time_point TimePoint; 30 | 31 | enum class TrackerEvent { 32 | None = 0, 33 | Completed = 1, 34 | Started = 2, 35 | Stopped = 3, 36 | }; 37 | 38 | struct TrackerQuery { 39 | TrackerEvent event; 40 | size_t downloaded; 41 | size_t uploaded; 42 | size_t remaining; 43 | }; 44 | 45 | class Torrent; 46 | class Tracker 47 | { 48 | enum TrackerType { 49 | TrackerHTTP, 50 | TrackerUDP, 51 | }; 52 | 53 | public: 54 | Tracker(Torrent *torrent, const std::string &host, const std::string &port, const std::string &proto, uint16_t tport) 55 | : m_torrent(torrent), 56 | m_tport(tport), 57 | m_host(host), 58 | m_port(port) 59 | { 60 | m_type = proto == "http" ? TrackerHTTP : TrackerUDP; 61 | } 62 | 63 | std::string host() const { return m_host; } 64 | std::string port() const { return m_port; } 65 | 66 | // Start querying this tracker for peers etc. 67 | bool query(const TrackerQuery &request); 68 | bool timeUp(void) { return std::chrono::system_clock::now() >= m_timeToNextRequest; } 69 | void setNextRequestTime(const TimePoint &p) { m_timeToNextRequest = p; } 70 | 71 | protected: 72 | bool httpRequest(const TrackerQuery &r); 73 | bool udpRequest(const TrackerQuery &r); 74 | 75 | private: 76 | Torrent *m_torrent; 77 | TimePoint m_timeToNextRequest; 78 | 79 | TrackerType m_type; 80 | uint16_t m_tport; 81 | std::string m_host; 82 | std::string m_port; 83 | 84 | friend class Torrent; 85 | }; 86 | 87 | #endif 88 | 89 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifndef _WIN32 34 | #include 35 | #endif 36 | 37 | /* Externed */ 38 | std::ofstream logfile; 39 | 40 | #ifdef _WIN32 41 | enum { 42 | COL_BLACK = 0, 43 | COL_GREEN = 10, 44 | COL_YELLOW = 14, 45 | }; 46 | 47 | static void set_color(int col) 48 | { 49 | CONSOLE_SCREEN_BUFFER_INFO i; 50 | GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &i); 51 | 52 | int back = (i.wAttributes / 16) % 16; 53 | if (col % 16 == back % 16) 54 | ++col; 55 | col %= 16; 56 | back %= 16; 57 | 58 | uint16_t attrib = ((unsigned)back << 4) | (unsigned)col; 59 | SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), attrib); 60 | } 61 | 62 | #define printc(c, fmt, args...) do { \ 63 | set_color((c)); \ 64 | printf(fmt, ##args); \ 65 | set_color(COL_BLACK); \ 66 | } while (0) 67 | #else 68 | enum { 69 | COL_GREEN = 1, 70 | COL_YELLOW = 2, 71 | }; 72 | 73 | #define printc(c, fmt, args...) do { \ 74 | attron(COLOR_PAIR((c))); \ 75 | printw(fmt, ##args); \ 76 | } while (0) 77 | #endif 78 | 79 | static void print_stats(Torrent *t) 80 | { 81 | TorrentMeta *meta = t->meta(); 82 | TorrentFileManager *fm = t->fileManager(); 83 | 84 | printc(COL_GREEN, "\r%s: ", meta->name().c_str()); 85 | printc(COL_YELLOW, "%.2f Mbps (%zd/%zd MB) [ %zd uploaded - %zd hash miss - %zd wasted - %.2f seconds left ] ", 86 | t->downloadSpeed(), t->computeDownloaded() / 1024 / 1024, meta->totalSize() / 1024 / 1024, 87 | t->uploadedBytes(), t->hashMisses(), t->wastedBytes(), t->eta()); 88 | printc(COL_YELLOW, "[ %zd/%zd/%zd pieces %zd peers active ]\n", 89 | fm->completedPieces(), fm->pending(), fm->totalPieces(), t->activePeers()); 90 | } 91 | 92 | static void print_all_stats(Torrent *torrents, size_t total) 93 | { 94 | #ifdef _WIN32 95 | COORD coord; 96 | CONSOLE_SCREEN_BUFFER_INFO info; 97 | GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); 98 | coord.X = info.dwCursorPosition.X; 99 | coord.Y = info.dwCursorPosition.Y; 100 | #else 101 | move(0, 0); 102 | #endif 103 | for (size_t i = 0; i < total; ++i) 104 | print_stats(&torrents[i]); 105 | #ifdef _WIN32 106 | SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord); 107 | #else 108 | printw("\n"); 109 | refresh(); 110 | #endif 111 | } 112 | 113 | int main(int argc, char *argv[]) 114 | { 115 | bool noseed = true; 116 | bool nodownload = false; 117 | int startport = 6881; 118 | size_t max_peers = 30; 119 | std::string dldir = "Torrents"; 120 | std::string lfname = "out.txt"; 121 | std::vector files; 122 | 123 | namespace po = boost::program_options; 124 | po::options_description opts; 125 | opts.add_options() 126 | ("version,v", "print version string") 127 | ("help,h", "print this help message") 128 | ("port,p", po::value(&startport), "specify start port for seeder torrents") 129 | ("peers,m", po::value(&max_peers), "maximum amount of peers to feel sufficient with, 0 implies as many as possible") 130 | ("nodownload,n", po::bool_switch(&nodownload), "do not download anything, just print info about torrents") 131 | ("piecesize,s", po::value(&maxRequestSize), "specify piece block size") 132 | ("dldir,d", po::value(&dldir), "specify downloads directory") 133 | ("noseed,e", po::bool_switch(&noseed), "do not seed after download has finished.") 134 | ("log,l", po::value(&lfname), "specify log file name") 135 | ("torrents,t", po::value>(&files)->required()->multitoken(), "specify torrent file(s)"); 136 | 137 | if (argc == 1) { 138 | std::clog << opts << std::endl; 139 | std::clog << "Example: " << argv[0] << " --nodownload --torrents a.torrent b.torrent c.torrent" << std::endl; 140 | return 1; 141 | } 142 | 143 | po::variables_map vm; 144 | try { 145 | po::store(po::parse_command_line(argc, argv, opts), vm); 146 | po::notify(vm); 147 | } catch (const std::exception &e) { 148 | std::cerr << argv[0] << ": error parsing command line arguments: " << e.what() << std::endl; 149 | std::clog << opts << std::endl; 150 | std::clog << "Example: " << argv[0] << " --nodownload --torrents a.torrent b.torrent c.torrent" << std::endl; 151 | return 1; 152 | } 153 | 154 | if (vm.count("help")) { 155 | std::clog << opts << std::endl; 156 | std::clog << "Example: " << argv[0] << " --nodownload --torrents a.torrent b.torrent c.torrent" << std::endl; 157 | return 0; 158 | } 159 | 160 | if (vm.count("version")) { 161 | std::clog << "CTorrent version 1.0" << std::endl; 162 | return 0; 163 | } 164 | 165 | if (vm.count("piecesize")) 166 | maxRequestSize = 1 << (32 - __builtin_clz(maxRequestSize - 1)); 167 | 168 | logfile.open(lfname, std::ios_base::trunc | std::ios_base::out); 169 | if (!logfile.is_open()) { 170 | std::cerr << "Cannot open log file " << lfname << ": " << errno << std::endl; 171 | return 1; 172 | } 173 | 174 | size_t total = files.size(); 175 | size_t total_bits = 0; 176 | size_t completed = 0; 177 | size_t errors = 0; 178 | size_t eseed = 0; 179 | size_t started = 0; 180 | 181 | std::vector torrents(total); 182 | for (size_t i = 0; i < total; ++i) { 183 | std::string file = files[i]; 184 | Torrent *t = &torrents[i]; 185 | total_bits |= 1 << i; 186 | 187 | std::clog << "Scanning: " << file << "... "; 188 | if (!t->open(file, dldir)) { 189 | std::cerr << "corrupted torrent file" << std::endl; 190 | errors |= 1 << i; 191 | continue; 192 | } 193 | std::clog << "Done" << std::endl; 194 | 195 | const TorrentMeta *meta = t->meta(); 196 | if (nodownload) { 197 | const TorrentFileManager *fm = t->fileManager(); 198 | 199 | std::clog << meta->name() << ": Total size: " << bytesToHumanReadable(meta->totalSize(), true) << std::endl; 200 | std::clog << meta->name() << ": Completed pieces: " << fm->completedPieces() << "/" << fm->totalPieces() << std::endl; 201 | std::clog << meta->name() << ": Piece Length: " << meta->pieceLength() << std::endl; 202 | completed |= 1 << i; 203 | continue; 204 | } 205 | 206 | std::clog << "Preparing " << file << "... "; 207 | Torrent::DownloadState state = t->prepare(startport++, !noseed); 208 | switch (state) { 209 | case Torrent::DownloadState::None: 210 | ++started; 211 | std::clog << "Done" << std::endl; 212 | break; 213 | case Torrent::DownloadState::Completed: 214 | completed |= 1 << i; 215 | std::clog << "Done (already downloaded)" << std::endl; 216 | break; 217 | default: 218 | errors |= 1 << i; 219 | std::cerr << "Failed: " << (int)state << std::endl; 220 | break; 221 | } 222 | } 223 | 224 | #ifndef _WIN32 225 | usleep(10000); 226 | initscr(); 227 | assume_default_colors(COLOR_WHITE, COLOR_BLACK); 228 | init_pair(1, COLOR_GREEN, COLOR_BLACK); 229 | init_pair(2, COLOR_YELLOW, COLOR_BLACK); 230 | attrset(A_BOLD); // boldy 231 | curs_set(0); // don't show cursor 232 | #endif 233 | 234 | if (!nodownload && started > 0) { 235 | std::clog << "Downloading torrents..." << std::endl; 236 | while (total_bits ^ (completed | errors)) { 237 | for (size_t i = 0; i < total; ++i) { 238 | if (errors & (1 << i)) 239 | continue; 240 | 241 | Torrent *t = &torrents[i]; 242 | if (t->isFinished()) { 243 | if (!noseed) 244 | t->finish(); 245 | completed |= 1 << i; 246 | } else { 247 | if (max_peers == 0 || t->activePeers() < max_peers) 248 | t->checkTrackers(); 249 | if (!noseed) 250 | t->nextConnection(); 251 | } 252 | } 253 | 254 | Connection::poll(); 255 | print_all_stats(&torrents[0], total); 256 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); 257 | } 258 | } 259 | 260 | std::clog << "\nDone downloading\n" << std::endl; 261 | if (!noseed && completed > 0) { 262 | std::clog << "Now seeding" << std::endl; 263 | for (size_t i = 0; i < total; ++i) { 264 | Torrent *t = &torrents[i]; 265 | if (!t->hasTrackers()) 266 | eseed |= 1 << i; 267 | } 268 | 269 | while (eseed ^ total_bits) { 270 | for (size_t i = 0; i < total; ++i) { 271 | Torrent *t = &torrents[i]; 272 | if (!t->nextConnection() || (t->activePeers() < max_peers && !t->checkTrackers())) 273 | eseed |= 1 << i; 274 | } 275 | 276 | Connection::poll(); 277 | print_all_stats(&torrents[0], total); 278 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); 279 | } 280 | } 281 | 282 | #ifndef _WIN32 283 | endwin(); 284 | #endif 285 | 286 | for (size_t i = 0; i < total; ++i) { 287 | Torrent *t = &torrents[i]; 288 | const TorrentMeta *meta = t->meta(); 289 | if (completed & (1 << i)) 290 | std::clog << "Completed: "; 291 | else if (errors & (1 << i)) 292 | std::clog << "Something went wrong downloading: "; 293 | else if (eseed & (1 << i)) 294 | std::clog << "Failed to seed: "; 295 | std::clog << meta->name() << std::endl; 296 | } 297 | 298 | std::clog << "Finished" << std::endl; 299 | logfile.close(); 300 | return 0; 301 | } 302 | 303 | -------------------------------------------------------------------------------- /net/connection.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "connection.h" 23 | 24 | asio::io_service g_service; 25 | static std::mutex g_connectionLock; 26 | static std::list> g_outputStreams; 27 | 28 | Connection::Connection() : 29 | m_delayedWriteTimer(g_service), 30 | m_connectTimer(g_service), 31 | m_resolver(g_service), 32 | m_socket(g_service) 33 | { 34 | 35 | } 36 | 37 | Connection::~Connection() 38 | { 39 | boost::system::error_code ec; 40 | m_socket.cancel(ec); 41 | 42 | m_delayedWriteTimer.cancel(); 43 | if (m_socket.is_open()) 44 | m_socket.close(); 45 | } 46 | 47 | void Connection::poll() 48 | { 49 | g_service.reset(); 50 | g_service.poll(); 51 | } 52 | 53 | void Connection::connect(const std::string &host, const std::string &port, const ConnectCallback &cb) 54 | { 55 | asio::ip::tcp::resolver::query query(host, port); 56 | 57 | m_connectTimer.cancel(); 58 | m_cb = cb; 59 | m_resolver.async_resolve(query, std::bind(&Connection::handleResolve, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); 60 | } 61 | 62 | void Connection::close(bool warn) 63 | { 64 | m_delayedWriteTimer.cancel(); 65 | m_connectTimer.cancel(); 66 | 67 | if (!isConnected()) { 68 | if (m_eh && warn) 69 | m_eh("Connection::close(): Called on an already closed connection!"); 70 | return; 71 | } 72 | 73 | boost::system::error_code ec; 74 | m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ec); 75 | m_socket.close(); 76 | 77 | if (ec && warn && m_eh) 78 | m_eh(ec.message()); 79 | } 80 | 81 | void Connection::write(const uint8_t *bytes, size_t size) 82 | { 83 | if (!isConnected()) 84 | return; 85 | 86 | if (!m_outputStream) { 87 | g_connectionLock.lock(); 88 | if (!g_outputStreams.empty()) { 89 | m_outputStream = g_outputStreams.front(); 90 | g_outputStreams.pop_front(); 91 | } else 92 | m_outputStream = std::shared_ptr(new asio::streambuf); 93 | g_connectionLock.unlock(); 94 | 95 | m_delayedWriteTimer.cancel(); 96 | m_delayedWriteTimer.expires_from_now(boost::posix_time::milliseconds(10)); 97 | m_delayedWriteTimer.async_wait(std::bind(&Connection::internalWrite, shared_from_this(), std::placeholders::_1)); 98 | } 99 | 100 | std::ostream os(m_outputStream.get()); 101 | os.write((const char *)bytes, size); 102 | os.flush(); 103 | } 104 | 105 | void Connection::read_partial(size_t bytes, const ReadCallback &rc) 106 | { 107 | if (!isConnected()) 108 | return; 109 | 110 | m_rc = rc; 111 | m_socket.async_read_some(asio::buffer(m_inputStream.prepare(bytes)), 112 | std::bind(&Connection::handleRead, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); 113 | } 114 | 115 | void Connection::read(size_t bytes, const ReadCallback &rc) 116 | { 117 | if (!isConnected()) 118 | return; 119 | 120 | m_rc = rc; 121 | asio::async_read(m_socket, asio::buffer(m_inputStream.prepare(bytes)), 122 | std::bind(&Connection::handleRead, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); 123 | } 124 | 125 | void Connection::internalWrite(const boost::system::error_code &e) 126 | { 127 | m_delayedWriteTimer.cancel(); 128 | 129 | if (e == asio::error::operation_aborted || !m_socket.is_open()) 130 | return; 131 | 132 | std::shared_ptr outputStream = m_outputStream; 133 | m_outputStream = nullptr; 134 | 135 | asio::async_write(m_socket, *outputStream, 136 | std::bind(&Connection::handleWrite, shared_from_this(), std::placeholders::_1, std::placeholders::_2, outputStream)); 137 | } 138 | 139 | void Connection::handleWrite(const boost::system::error_code &e, size_t bytes, std::shared_ptr outputStream) 140 | { 141 | if (e == asio::error::operation_aborted) 142 | return; 143 | 144 | outputStream->consume(outputStream->size()); 145 | g_outputStreams.push_back(outputStream); 146 | if (e) 147 | handleError(e); 148 | } 149 | 150 | void Connection::handleRead(const boost::system::error_code &e, size_t readSize) 151 | { 152 | if (e) 153 | return handleError(e); 154 | 155 | if (m_rc) { 156 | const uint8_t *data = asio::buffer_cast(m_inputStream.data()); 157 | m_rc(data, readSize); 158 | } 159 | 160 | m_inputStream.consume(readSize); 161 | } 162 | 163 | void Connection::handleResolve(const boost::system::error_code &e, 164 | asio::ip::basic_resolver::iterator endpoint) 165 | { 166 | m_connectTimer.cancel(); 167 | if (e) 168 | return handleError(e); 169 | 170 | m_socket.async_connect(*endpoint, std::bind(&Connection::handleConnect, shared_from_this(), std::placeholders::_1)); 171 | m_connectTimer.expires_from_now(boost::posix_time::seconds(30)); 172 | m_connectTimer.async_wait(std::bind(&Connection::handleTimeout, shared_from_this(), std::placeholders::_1)); 173 | } 174 | 175 | void Connection::handleConnect(const boost::system::error_code &e) 176 | { 177 | m_connectTimer.cancel(); 178 | if (e) 179 | return handleError(e); 180 | else if (m_cb) 181 | m_cb(); 182 | } 183 | 184 | void Connection::handleError(const boost::system::error_code &error) 185 | { 186 | if (error != asio::error::operation_aborted && m_eh) 187 | m_eh(error.message()); 188 | } 189 | 190 | void Connection::handleTimeout(const boost::system::error_code &error) 191 | { 192 | if (error != asio::error::operation_aborted) 193 | return handleError(asio::error::timed_out); 194 | } 195 | 196 | std::string Connection::getIPString() const 197 | { 198 | if (!isConnected()) 199 | return "0000.000.00.0"; 200 | 201 | boost::system::error_code error; 202 | const asio::ip::tcp::endpoint ip = m_socket.remote_endpoint(error); 203 | if (!error) 204 | return ip.address().to_string(); 205 | 206 | return "0.00.000.0000"; 207 | } 208 | 209 | uint32_t Connection::getIP() const 210 | { 211 | if (!isConnected()) 212 | return 0; 213 | 214 | boost::system::error_code error; 215 | const asio::ip::tcp::endpoint ip = m_socket.remote_endpoint(error); 216 | if (!error) 217 | return asio::detail::socket_ops::host_to_network_long(ip.address().to_v4().to_ulong()); 218 | 219 | return 0; 220 | } 221 | 222 | -------------------------------------------------------------------------------- /net/connection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __CONNECTION_H 23 | #define __CONNECTION_H 24 | 25 | #if 0 26 | #ifdef _WIN32 27 | #ifdef __STRICT_ANSI__ 28 | #undef __STRICT_ANSI__ 29 | #endif 30 | #define _WIN32_WINNT 0x0501 31 | #define WINVER 0x0501 32 | #endif 33 | #endif 34 | #include 35 | #include 36 | 37 | #include 38 | 39 | #include 40 | #include 41 | 42 | #include "outputmessage.h" 43 | 44 | namespace asio = boost::asio; 45 | 46 | class Connection : public std::enable_shared_from_this 47 | { 48 | typedef std::function ReadCallback; 49 | typedef std::function ConnectCallback; 50 | typedef std::function ErrorCallback; 51 | 52 | public: 53 | Connection(); 54 | ~Connection(); 55 | 56 | // poll io service (general purpose) 57 | static void poll(); 58 | 59 | void setErrorCallback(const ErrorCallback &ec) { m_eh = ec; } 60 | void connect(const std::string &host, const std::string &port, const ConnectCallback &cb); 61 | void close(bool warn = true); /// Pass false in ErrorCallback otherwise possible infinite recursion 62 | bool isConnected() const { return m_socket.is_open(); } 63 | 64 | inline void write(const OutputMessage &o) { write(o.data(0), o.size()); } 65 | inline void write(const std::string &str) { return write((const uint8_t *)str.c_str(), str.length()); } 66 | void write(const uint8_t *data, size_t bytes); 67 | void read_partial(size_t bytes, const ReadCallback &rc); 68 | void read(size_t bytes, const ReadCallback &rc); 69 | 70 | std::string getIPString() const; 71 | uint32_t getIP() const; 72 | 73 | protected: 74 | void internalWrite(const boost::system::error_code &); 75 | void handleRead(const boost::system::error_code &, size_t); 76 | void handleWrite(const boost::system::error_code &, size_t, std::shared_ptr); 77 | void handleConnect(const boost::system::error_code &); 78 | void handleResolve(const boost::system::error_code &, asio::ip::basic_resolver::iterator); 79 | void handleError(const boost::system::error_code &); 80 | void handleTimeout(const boost::system::error_code &); 81 | 82 | private: 83 | asio::deadline_timer m_delayedWriteTimer; 84 | asio::deadline_timer m_connectTimer; 85 | asio::ip::tcp::resolver m_resolver; 86 | asio::ip::tcp::socket m_socket; 87 | 88 | ReadCallback m_rc; 89 | ConnectCallback m_cb; 90 | ErrorCallback m_eh; 91 | 92 | std::shared_ptr m_outputStream; 93 | asio::streambuf m_inputStream; 94 | 95 | friend class Server; 96 | }; 97 | typedef std::shared_ptr ConnectionPtr; 98 | 99 | extern asio::io_service g_service; 100 | 101 | #endif 102 | 103 | -------------------------------------------------------------------------------- /net/decl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __NET_DECL_H 23 | #define __NET_DECL_H 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #define DATA_SIZE 10240 31 | 32 | enum class ByteOrder { 33 | LittleEndian, 34 | BigEndian 35 | }; 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /net/inputmessage.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, 2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "inputmessage.h" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | InputMessage::InputMessage(uint8_t *data, size_t size, ByteOrder order) : 29 | m_data(data), 30 | m_size(size), 31 | m_pos(0), 32 | m_order(order) 33 | { 34 | } 35 | 36 | InputMessage::InputMessage(ByteOrder order) : 37 | m_data(nullptr), 38 | m_size(0), 39 | m_pos(0), 40 | m_order(order) 41 | { 42 | 43 | } 44 | 45 | InputMessage::~InputMessage() 46 | { 47 | 48 | } 49 | 50 | uint8_t *InputMessage::getBuffer(size_t size) 51 | { 52 | if (m_pos + size > m_size) 53 | return nullptr; 54 | 55 | uint8_t *buffer = new uint8_t[size]; 56 | memcpy(buffer, &m_data[m_pos], size); 57 | return buffer; 58 | } 59 | 60 | uint8_t *InputMessage::getBuffer(void) 61 | { 62 | return &m_data[m_pos]; 63 | } 64 | 65 | uint8_t InputMessage::getByte() 66 | { 67 | return m_data[m_pos++]; 68 | } 69 | 70 | uint16_t InputMessage::getU16() 71 | { 72 | uint16_t tmp = (m_order == ByteOrder::BigEndian ? readBE16(&m_data[m_pos]) : readLE16(&m_data[m_pos])); 73 | m_pos += 2; 74 | return tmp; 75 | } 76 | 77 | uint32_t InputMessage::getU32() 78 | { 79 | uint32_t tmp = (m_order == ByteOrder::BigEndian ? readBE32(&m_data[m_pos]) : readLE32(&m_data[m_pos])); 80 | m_pos += 4; 81 | return tmp; 82 | } 83 | 84 | uint64_t InputMessage::getU64() 85 | { 86 | uint64_t tmp = (m_order == ByteOrder::BigEndian ? readBE64(&m_data[m_pos]) : readLE64(&m_data[m_pos])); 87 | m_pos += 8; 88 | return tmp; 89 | } 90 | 91 | std::string InputMessage::getString() 92 | { 93 | uint16_t len = getU16(); 94 | if (!len) 95 | return std::string(); 96 | 97 | if (m_pos + len > m_size) 98 | return std::string(); 99 | 100 | std::string ret((char *)&m_data[m_pos], len); 101 | m_pos += len; 102 | return ret; 103 | } 104 | -------------------------------------------------------------------------------- /net/inputmessage.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, 2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __INPUTMESSAGE_H 23 | #define __INPUTMESSAGE_H 24 | 25 | #include "decl.h" 26 | 27 | class InputMessage 28 | { 29 | public: 30 | InputMessage(uint8_t *data, size_t size, ByteOrder order); 31 | InputMessage(ByteOrder order); 32 | ~InputMessage(); 33 | 34 | void setData(uint8_t *d) { m_data = d; } 35 | size_t getSize() { return m_size; } 36 | void setSize(size_t size) { m_size = size; } 37 | void setByteOrder(ByteOrder order) { m_order = order; } 38 | 39 | uint8_t *getBuffer(size_t size); 40 | uint8_t *getBuffer(void); 41 | uint8_t getByte(); 42 | uint16_t getU16(); 43 | uint32_t getU32(); 44 | uint64_t getU64(); 45 | std::string getString(); 46 | 47 | inline uint8_t &operator[] (size_t i) { return m_data[i]; } 48 | inline InputMessage &operator=(uint8_t *data) { m_data = data; return *this; } 49 | inline InputMessage &operator>>(uint8_t &b) { b = getByte(); return *this; } 50 | inline InputMessage &operator>>(uint16_t &u) { u = getU16(); return *this; } 51 | inline InputMessage &operator>>(uint32_t &u) { u = getU32(); return *this; } 52 | inline InputMessage &operator>>(uint64_t &u) { u = getU64(); return *this; } 53 | inline InputMessage &operator>>(std::string &s) { s = getString(); return *this; } 54 | 55 | private: 56 | uint8_t *m_data; 57 | size_t m_size; 58 | uint32_t m_pos; 59 | ByteOrder m_order; 60 | }; 61 | 62 | #endif 63 | 64 | -------------------------------------------------------------------------------- /net/outputmessage.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, 2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "outputmessage.h" 23 | 24 | #include 25 | 26 | #include 27 | 28 | OutputMessage::OutputMessage(ByteOrder order, size_t fixedSize) 29 | : m_buffer(fixedSize != 0 ? fixedSize : 64), 30 | m_pos(0), 31 | m_order(order) 32 | { 33 | } 34 | 35 | OutputMessage::~OutputMessage() 36 | { 37 | 38 | } 39 | 40 | void OutputMessage::addBytes(const uint8_t *bytes, size_t size) 41 | { 42 | m_buffer.grow(m_buffer.size() + size); 43 | memcpy(&m_buffer[m_pos], &bytes[0], size); 44 | m_pos += size; 45 | } 46 | 47 | void OutputMessage::addByte(uint8_t byte) 48 | { 49 | m_buffer.grow(m_buffer.size() + 1); 50 | m_buffer[m_pos++] = byte; 51 | } 52 | 53 | void OutputMessage::addU16(uint16_t val) 54 | { 55 | m_buffer.grow(m_buffer.size() + 1); 56 | if (m_order == ByteOrder::BigEndian) 57 | writeBE16(&m_buffer[m_pos], val); 58 | else 59 | writeLE16(&m_buffer[m_pos], val); 60 | m_pos += 2; 61 | } 62 | 63 | void OutputMessage::addU32(uint32_t val) 64 | { 65 | m_buffer.grow(m_buffer.size() + 4); 66 | if (m_order == ByteOrder::BigEndian) 67 | writeBE32(&m_buffer[m_pos], val); 68 | else 69 | writeLE32(&m_buffer[m_pos], val); 70 | m_pos += 4; 71 | } 72 | 73 | void OutputMessage::addU64(uint64_t val) 74 | { 75 | m_buffer.grow(m_buffer.size() + 8); 76 | if (m_order == ByteOrder::BigEndian) 77 | writeBE64(&m_buffer[m_pos], val); 78 | else 79 | writeLE64(&m_buffer[m_pos], val); 80 | m_pos += 8; 81 | } 82 | 83 | void OutputMessage::addString(const std::string &str) 84 | { 85 | uint16_t len = str.length(); 86 | 87 | addU16(len); 88 | m_buffer.grow(m_buffer.size() + len); 89 | memcpy((char *)&m_buffer[m_pos], str.c_str(), len); 90 | m_pos += len; 91 | } 92 | -------------------------------------------------------------------------------- /net/outputmessage.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __OUTPUTMESSAGE_H 23 | #define __OUTPUTMESSAGE_H 24 | 25 | #include "decl.h" 26 | 27 | class OutputMessage 28 | { 29 | public: 30 | explicit OutputMessage(DataBuffer &&buffer) 31 | : m_buffer(std::move(buffer)), 32 | m_pos(buffer.size()), 33 | m_order(ByteOrder::BigEndian) 34 | { 35 | } 36 | OutputMessage(ByteOrder order, size_t fixedSize = 0); 37 | ~OutputMessage(); 38 | 39 | void clear() { m_buffer.clear(); m_pos = 0; } 40 | void addByte(uint8_t byte); 41 | void addBytes(const uint8_t *bytes, size_t size); 42 | void addU16(uint16_t val); 43 | void addU32(uint32_t val); 44 | void addU64(uint64_t val); 45 | void addString(const std::string &str); 46 | 47 | const uint8_t *data() const { return &m_buffer[m_pos]; } 48 | const uint8_t *data(size_t p) const { return &m_buffer[p]; } 49 | size_t size() const { return m_pos; } 50 | 51 | inline const uint8_t &operator[] (size_t index) const { return m_buffer[index]; } 52 | inline OutputMessage &operator<<(const uint8_t &b) { addByte(b); return *this; } 53 | inline OutputMessage &operator<<(const uint16_t &u) { addU16(u); return *this; } 54 | inline OutputMessage &operator<<(const uint32_t &u) { addU32(u); return *this; } 55 | inline OutputMessage &operator<<(const uint64_t &u) { addU64(u); return *this; } 56 | inline OutputMessage &operator<<(const std::string &s) { addString(s); return *this; } 57 | 58 | private: 59 | DataBuffer m_buffer; 60 | size_t m_pos; 61 | ByteOrder m_order; 62 | }; 63 | 64 | #endif 65 | 66 | -------------------------------------------------------------------------------- /net/server.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "server.h" 23 | 24 | Server::Server(uint16_t port) 25 | : m_acceptor(g_service, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)) 26 | { 27 | m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); 28 | } 29 | 30 | Server::~Server() 31 | { 32 | boost::system::error_code ec; 33 | m_acceptor.cancel(ec); 34 | m_acceptor.close(); 35 | } 36 | 37 | void Server::stop() 38 | { 39 | m_acceptor.cancel(); 40 | m_acceptor.close(); 41 | m_stopped = true; 42 | } 43 | 44 | void Server::accept(const Acceptor &ac) 45 | { 46 | ConnectionPtr conn(new Connection()); 47 | m_acceptor.async_accept(conn->m_socket, 48 | [=] (const boost::system::error_code &error) { 49 | if (!error) 50 | return ac(conn); 51 | } 52 | ); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /net/server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __SERVER_H 23 | #define __SERVER_H 24 | 25 | #include "connection.h" 26 | 27 | class Server { 28 | typedef std::function Acceptor; 29 | public: 30 | Server(uint16_t port); 31 | ~Server(); 32 | 33 | bool stopped() const { return m_stopped; } 34 | void stop(); 35 | void accept(const Acceptor &ac); 36 | 37 | private: 38 | bool m_stopped; 39 | asio::ip::tcp::acceptor m_acceptor; 40 | }; 41 | 42 | #endif 43 | 44 | -------------------------------------------------------------------------------- /util/auxiliar.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #include "auxiliar.h" 23 | 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | UrlData parseUrl(const std::string &str) 38 | { 39 | /// TODO: Regex could be quicker? 40 | std::string protocol, host, port; 41 | 42 | const std::string protocol_end("://"); 43 | auto protocolIter = std::search(str.begin(), str.end(), protocol_end.begin(), protocol_end.end()); 44 | if (protocolIter == str.end()) { 45 | std::cerr << str << ": unable to find start of protocol" << std::endl; 46 | return std::make_tuple("", "", ""); 47 | } 48 | 49 | protocol.reserve(std::distance(str.begin(), protocolIter)); // reserve for "http"/"https" etc. 50 | std::transform( 51 | str.begin(), protocolIter, 52 | std::back_inserter(protocol), std::ptr_fun(tolower) 53 | ); 54 | std::advance(protocolIter, protocol_end.length()); // eat "://" 55 | 56 | auto hostIter = std::find(protocolIter, str.end(), ':'); // could be protocol://host:port/ 57 | if (hostIter == str.end()) 58 | hostIter = std::find(protocolIter, str.end(), '/'); // Nope, protocol://host/?query 59 | 60 | host.reserve(std::distance(protocolIter, hostIter)); 61 | std::transform( 62 | protocolIter, hostIter, 63 | std::back_inserter(host), std::ptr_fun(tolower) 64 | ); 65 | 66 | auto portIter = std::find(hostIter, str.end(), ':'); 67 | if (portIter == str.end()) // Port optional 68 | return std::make_tuple(protocol, host, protocol); // No port, it's according to the protocol then 69 | 70 | auto queryBegin = std::find(portIter, str.end(), '/'); // Can we find a "/" of where the query would begin? 71 | if (queryBegin != str.end()) 72 | port.assign(portIter + 1, queryBegin); 73 | else 74 | port.assign(portIter + 1, str.end()); 75 | 76 | return std::make_tuple(protocol, host, port); 77 | } 78 | 79 | /** 80 | * bytesToHumanReadable() from: 81 | * http://stackoverflow.com/a/3758880/1551592 (Java version) 82 | */ 83 | std::string bytesToHumanReadable(uint32_t bytes, bool si) 84 | { 85 | uint32_t u = si ? 1000 : 1024; 86 | if (bytes < u) 87 | return std::to_string(bytes) + " B"; 88 | 89 | size_t exp = static_cast(std::log(bytes) / std::log(u)); 90 | const char *e = si ? "kMGTPE" : "KMGTPE"; 91 | std::ostringstream os; 92 | os << static_cast(bytes / std::pow(u, exp)) << " "; 93 | os << e[exp - 1] << (si ? "" : "i") << "B"; 94 | 95 | return os.str(); 96 | } 97 | 98 | std::string ip2str(uint32_t ip) 99 | { 100 | char buffer[17]; 101 | sprintf(buffer, "%u.%u.%u.%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, ip >> 24); 102 | return buffer; 103 | } 104 | 105 | uint32_t str2ip(const std::string &ip) 106 | { 107 | const uint8_t *c = (const uint8_t *)ip.c_str(); 108 | // return c[0] | c[1] << 8 | c[2] << 16 | c[3] << 24; 109 | return c[0] << 24 | c[1] << 16 | c[2] << 8 | c[3]; 110 | } 111 | 112 | std::string getcwd() 113 | { 114 | const size_t chunkSize = 255; 115 | const int maxChunks = 10240; // 2550 KiBs of current path are more than enough 116 | 117 | char stackBuffer[chunkSize]; // Stack buffer for the "normal" case 118 | if (getcwd(stackBuffer, sizeof(stackBuffer))) 119 | return stackBuffer; 120 | 121 | if (errno != ERANGE) 122 | throw std::runtime_error("Cannot determine the current path."); 123 | 124 | for (int chunks = 2; chunks < maxChunks; chunks++) { 125 | std::unique_ptr cwd(new char[chunkSize * chunks]); 126 | if (getcwd(cwd.get(), chunkSize * chunks)) 127 | return cwd.get(); 128 | 129 | if (errno != ERANGE) 130 | throw std::runtime_error("Cannot determine the current path."); 131 | } 132 | 133 | throw std::runtime_error("Cannot determine the current path; path too long"); 134 | } 135 | 136 | /** Following function shamelessly copied from: 137 | * http://stackoverflow.com/a/7214192/502230 138 | * And converted to take just the request instead of the 139 | * entire URL, e.g.: 140 | * std::string str = "?what=0xbaadf00d&something=otherthing"; 141 | * std::string enc = urlencode(str); 142 | */ 143 | std::string urlencode(const std::string &value) 144 | { 145 | std::ostringstream escaped; 146 | escaped.fill('0'); 147 | escaped << std::hex << std::uppercase; 148 | 149 | for (std::string::const_iterator i = value.begin(); i != value.end(); ++i) { 150 | std::string::value_type c = *i; 151 | // Keep alphanumeric and other accepted characters intact 152 | if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') 153 | escaped << c; 154 | else // Any other characters are percent-encoded 155 | escaped << '%' << std::setw(2) << static_cast((unsigned char)c); 156 | } 157 | 158 | return escaped.str(); 159 | } 160 | 161 | bool validatePath(const std::string &base, const std::string &path) 162 | { 163 | boost::filesystem::path p1(path); 164 | boost::filesystem::path p2(base); 165 | 166 | return p1.parent_path() == p2.parent_path(); 167 | } 168 | 169 | bool nodeExists(const std::string &node) 170 | { 171 | #ifdef _WIN32 172 | return PathFileExists(node.c_str()); 173 | #else 174 | struct stat st; 175 | return stat(node.c_str(), &st) == 0; 176 | #endif 177 | } 178 | 179 | -------------------------------------------------------------------------------- /util/auxiliar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __AUXILIAR_H 23 | #define __AUXILIAR_H 24 | 25 | #ifdef __STRICT_ANSI__ 26 | #undef __STRICT_ANSI__ 27 | #endif 28 | #include 29 | #include 30 | #include 31 | 32 | #ifdef _WIN32 33 | #define PATH_SEP "\\" 34 | #define MKDIR(name) mkdir((name).c_str()) 35 | #ifndef WIN32_LEAN_AND_MEAN 36 | #define WIN32_LEAN_AND_MEAN 37 | #endif 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #else 45 | #define PATH_SEP "/" 46 | #define MKDIR(name) mkdir((name).c_str(), 0700) 47 | #include 48 | #include 49 | #include 50 | #include 51 | #endif 52 | #define CHDIR(name) chdir((name).c_str()) 53 | 54 | #include 55 | #include 56 | #include 57 | 58 | typedef std::tuple UrlData; 59 | #define URL_PROTOCOL(u) std::get<0>((u)) 60 | #define URL_HOSTNAME(u) std::get<1>((u)) 61 | #define URL_SERVNAME(u) std::get<2>((u)) 62 | 63 | extern UrlData parseUrl(const std::string &str); 64 | extern std::string bytesToHumanReadable(uint32_t bytes, bool si); 65 | extern std::string ip2str(uint32_t ip); 66 | extern uint32_t str2ip(const std::string &ip); 67 | extern std::string getcwd(); 68 | extern std::string urlencode(const std::string& url); 69 | 70 | extern bool validatePath(const std::string &base, const std::string &path); 71 | extern bool nodeExists(const std::string &node); 72 | 73 | static inline bool test_bit(uint32_t bits, uint32_t bit) 74 | { 75 | return (bits & bit) == bit; 76 | } 77 | 78 | static inline bool starts_with(const std::string &s, const std::string &start) 79 | { 80 | if (s.length() < start.length()) 81 | return false; 82 | 83 | return s.substr(0, start.length()) == start; 84 | } 85 | 86 | static inline bool ends_with(const std::string &s, const std::string &end) 87 | { 88 | if (s.length() < end.length()) 89 | return false; 90 | 91 | return s.substr(s.length() - end.length()) == end; 92 | } 93 | 94 | #ifdef __MINGW32__ 95 | #include 96 | 97 | namespace std { 98 | template 99 | string to_string(const T &value) 100 | { 101 | return boost::lexical_cast(value); 102 | } 103 | } 104 | #endif 105 | #endif 106 | 107 | -------------------------------------------------------------------------------- /util/bitset.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Ahmed Samy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | #ifndef __BITSET_H 23 | #define __BITSET_H 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #ifdef _MSC_VER 30 | #include 31 | #endif 32 | 33 | class bitset { 34 | public: 35 | bitset(size_t size) 36 | { 37 | construct(size); 38 | } 39 | bitset() 40 | { 41 | m_size = 0; 42 | m_bits = nullptr; 43 | } 44 | bitset(bitset const &) = delete; 45 | ~bitset() { delete m_bits; m_bits = nullptr; } 46 | 47 | void construct(size_t size) 48 | { 49 | m_size = size; 50 | m_bits = new uint8_t[m_size]; 51 | memset(m_bits, 0x00, m_size); 52 | } 53 | void resize(size_t size) 54 | { 55 | uint8_t *bits = new uint8_t[size]; 56 | if (!bits) 57 | return; 58 | 59 | memcpy(bits, m_bits, size); 60 | delete []m_bits; 61 | 62 | m_bits = bits; 63 | m_size = size; 64 | } 65 | 66 | // simply modulus by 8 but since this is a bitset lets keep this all 67 | // bit relevant 68 | bool test(size_t i) const { return !!(bitsAt(i) & (1 << (i & 7))); } 69 | void set(size_t i) { bitsAt(i) |= (1 << (i & 7)); } 70 | void set(size_t i, bool v) { bitsAt(i) ^= ((int)-v ^ bitsAt(i)) & (1 << (i & 7)); } 71 | void clear(size_t i) { bitsAt(i) &= ~(1 << (i & 7)); } 72 | void toggle(size_t i) { bitsAt(i) ^= (1 << (i & 7)); } 73 | 74 | bool operator[] (size_t i) { return test(i); } 75 | bool operator[] (size_t i) const { return test(i); } 76 | 77 | const uint8_t *bits() const { return m_bits; } 78 | uint8_t *bits() { return m_bits; } 79 | 80 | size_t popcnt(uint64_t v) const 81 | { 82 | // Hamming Weight 83 | v = v - ((v >> 1) & 0x5555555555555555); 84 | v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333); 85 | return (((v + (v >> 4)) & 0x0F0F0F0F0F0F0F0F) * 0x0101010101010101) >> 56; 86 | } 87 | 88 | size_t size() const { return m_size; } 89 | size_t count() const 90 | { 91 | size_t set = 0; 92 | const uint8_t *src = m_bits; 93 | const uint8_t *dst = m_bits + m_size; 94 | while (src + 7 <= dst) { 95 | set += popcnt(*(uint64_t *)src); 96 | src += 8; 97 | } 98 | 99 | if (src + 3 <= dst) { 100 | set += popcnt(*(uint32_t *)src); 101 | src += 4; 102 | } 103 | 104 | if (src + 1 <= dst) { 105 | set += popcnt(*(uint16_t *)src); 106 | src += 2; 107 | } 108 | 109 | if (src < dst) 110 | set += popcnt(*(uint8_t *)src); 111 | 112 | return set; 113 | } 114 | 115 | // Simply division by 8 but since this is a bitset let's keep it all 116 | // bit relevant 117 | uint8_t bitsAt(int i) const { return m_bits[i >> 3]; } 118 | uint8_t &bitsAt(int i) { return m_bits[i >> 3]; } 119 | 120 | void raw_set(const uint8_t *bits, size_t size) 121 | { 122 | assert(size <= m_size); 123 | memcpy(&m_bits[0], bits, size); 124 | } 125 | 126 | private: 127 | uint8_t *m_bits; 128 | size_t m_size; 129 | }; 130 | 131 | #endif 132 | 133 | -------------------------------------------------------------------------------- /util/databuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef __DATABUFFER_H 2 | #define __DATABUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | template 12 | class DataBuffer 13 | { 14 | public: 15 | DataBuffer(size_t res = 64) 16 | : m_size(0), 17 | m_capacity(res), 18 | m_buffer(new T[m_capacity]) 19 | { 20 | } 21 | DataBuffer(DataBuffer const &buf) = delete; 22 | DataBuffer(DataBuffer &&buf) 23 | { 24 | m_capacity = buf.m_capacity; 25 | m_size = buf.m_size; 26 | m_buffer = buf.m_buffer; 27 | 28 | buf.m_buffer = nullptr; 29 | } 30 | ~DataBuffer() { delete[] m_buffer; m_buffer = nullptr; } 31 | 32 | inline size_t size() const { return m_size; } 33 | inline size_t cap() const { return m_capacity; } 34 | inline size_t rem() const { return cap() - size(); } 35 | inline T *data() const { return m_buffer; } 36 | 37 | inline const T &operator[](size_t i) const 38 | { 39 | assert(i < m_capacity); 40 | return m_buffer[i]; 41 | } 42 | inline T &operator[](size_t i) 43 | { 44 | assert(i < m_capacity); 45 | return m_buffer[i]; 46 | } 47 | 48 | void swap(DataBuffer &rhs) 49 | { 50 | std::swap(m_capacity, rhs.m_capacity); 51 | std::swap(m_size, rhs.m_size); 52 | std::swap(m_buffer, rhs.m_buffer); 53 | } 54 | 55 | inline DataBuffer &operator=(DataBuffer const &rhs) = delete; 56 | inline DataBuffer &operator=(DataBuffer &&rhs) 57 | { 58 | rhs.swap(*this); 59 | return *this; 60 | } 61 | 62 | inline void setData(T *data, size_t size) 63 | { 64 | delete []m_buffer; 65 | m_buffer = new T[size]; 66 | m_capacity = size; 67 | m_size = 0; 68 | for (size_t i = 0; i < size; ++i) 69 | m_buffer[i] = data[i]; 70 | } 71 | inline void setSize(size_t s) { m_size = s; } 72 | 73 | inline void clear(void) 74 | { 75 | memset(&m_buffer[0], 0x00, m_capacity); 76 | m_size = 0; 77 | } 78 | 79 | inline void reserve(size_t n) 80 | { 81 | if (n > m_capacity) { 82 | T *buffer = new T[n]; 83 | for (size_t i=0; i m_capacity) { 97 | size_t newcapacity = m_capacity; 98 | do 99 | newcapacity *= 2; 100 | while (newcapacity < n); 101 | reserve(newcapacity); 102 | } 103 | m_size = n; 104 | } 105 | 106 | inline void add(const T &v) 107 | { 108 | grow(m_size + 1); 109 | m_buffer[m_size-1] = v; 110 | } 111 | 112 | inline void add_unchecked(const T &v) 113 | { 114 | m_buffer[m_size++] = v; 115 | } 116 | 117 | inline DataBuffer &operator<<(const T &t) 118 | { 119 | add(t); 120 | return *this; 121 | } 122 | 123 | private: 124 | size_t m_size; 125 | size_t m_capacity; 126 | T *m_buffer; 127 | }; 128 | 129 | #endif 130 | 131 | -------------------------------------------------------------------------------- /util/serializer.h: -------------------------------------------------------------------------------- 1 | #ifndef __SERIALIZER_H 2 | #define __SERIALIZER_H 3 | 4 | #include 5 | #include 6 | 7 | ///////////////// Little Endian 8 | inline uint16_t readLE16(const uint8_t *addr) 9 | { 10 | return (uint16_t)addr[1] << 8 | addr[0]; 11 | } 12 | 13 | inline uint32_t readLE32(const uint8_t *addr) 14 | { 15 | return (uint32_t)readLE16(addr + 2) << 16 | readLE16(addr); 16 | } 17 | 18 | inline uint64_t readLE64(const uint8_t *addr) 19 | { 20 | return (uint64_t)readLE32(addr + 4) << 32 | readLE32(addr); 21 | } 22 | 23 | inline void writeLE16(uint8_t *addr, uint16_t value) 24 | { 25 | addr[1] = value >> 8; 26 | addr[0] = (uint8_t)value; 27 | } 28 | 29 | inline void writeLE32(uint8_t *addr, uint32_t value) 30 | { 31 | writeLE16(addr + 2, value >> 16); 32 | writeLE16(addr, (uint16_t)value); 33 | } 34 | 35 | inline void writeLE64(uint8_t *addr, uint64_t value) 36 | { 37 | writeLE32(addr + 4, value >> 32); 38 | writeLE32(addr, (uint32_t)value); 39 | } 40 | 41 | ///////////////// Big Endian 42 | inline uint16_t readBE16(const uint8_t *addr) 43 | { 44 | return (uint16_t)addr[1] | (uint16_t)(addr[0]) << 8; 45 | } 46 | 47 | inline uint32_t readBE32(const uint8_t *addr) 48 | { 49 | return (uint32_t)readBE16(addr + 2) | readBE16(addr) << 16; 50 | } 51 | 52 | inline uint64_t readBE64(const uint8_t *addr) 53 | { 54 | return (uint64_t)readBE32(addr + 4) | (uint64_t)readBE32(addr) << 32; 55 | } 56 | 57 | inline void writeBE16(uint8_t *addr, uint16_t value) 58 | { 59 | addr[0] = value >> 8; 60 | addr[1] = (uint8_t)value; 61 | } 62 | 63 | inline void writeBE32(uint8_t *addr, uint32_t value) 64 | { 65 | addr[0] = (uint8_t)(value >> 24); 66 | addr[1] = (uint8_t)(value >> 16); 67 | addr[2] = (uint8_t)(value >> 8); 68 | addr[3] = (uint8_t)(value); 69 | } 70 | 71 | inline void writeBE64(uint8_t *addr, uint64_t value) 72 | { 73 | addr[0] = (uint8_t)(value >> 56); 74 | addr[1] = (uint8_t)(value >> 48); 75 | addr[2] = (uint8_t)(value >> 40); 76 | addr[3] = (uint8_t)(value >> 32); 77 | addr[4] = (uint8_t)(value >> 24); 78 | addr[5] = (uint8_t)(value >> 16); 79 | addr[6] = (uint8_t)(value >> 8); 80 | addr[7] = (uint8_t)(value); 81 | } 82 | #endif 83 | 84 | --------------------------------------------------------------------------------