1
0
mirror of https://github.com/asamy/ctorrent synced 2025-10-05 23:52:41 +02:00

Initial commit

This commit is contained in:
A. Samy
2015-11-04 17:14:06 +00:00
commit aaebb0de0b
24 changed files with 3098 additions and 0 deletions

48
Makefile Normal file
View File

@@ -0,0 +1,48 @@
BIN_DIR = bin
BIN = $(BIN_DIR)/ctorrent
CXX = g++
BTYPE = -g3 -ggdb3 -O0 -D_DEBUG
CXXFLAGS = -std=c++11 $(BTYPE) -fopenmp -Wall -Wextra -Wno-sign-compare -Wno-unused-variable -Wno-unused-parameter -I"."
LIBS = -fopenmp -lcrypto
ifeq ($(OS),Windows_NT)
LIBS += -lboost_system-mt -lws2_32 -lshlwapi
else
LIBS += -lboost_system -lpthread
endif
OBJ_DIR = obj
SRC = bencode/decoder.cpp bencode/encoder.cpp \
ctorrent/peer.cpp ctorrent/torrent.cpp \
net/connection.cpp net/inputmessage.cpp net/outputmessage.cpp \
util/auxiliar.cpp \
main.cpp
OBJ = $(SRC:%.cpp=$(OBJ_DIR)/%.o)
.PHONY: all clean
all: $(BIN)
clean:
$(RM) $(OBJ_DIR)/*.o
$(RM) $(OBJ_DIR)/*/*.o
$(RM) $(BIN)
$(BIN): $(OBJ_DIR) $(BIN_DIR) $(OBJ)
@echo "LD $@"
@$(CXX) -o $@ $(OBJ) $(LIBS)
$(OBJ_DIR)/%.o: %.cpp
@echo "CXX $<"
@$(CXX) -c $(CXXFLAGS) -o $@ $<
$(BIN_DIR):
@mkdir -p $(BIN_DIR)
$(OBJ_DIR):
@mkdir -p $(OBJ_DIR)
@mkdir -p $(OBJ_DIR)/bencode
@mkdir -p $(OBJ_DIR)/ctorrent
@mkdir -p $(OBJ_DIR)/net
@mkdir -p $(OBJ_DIR)/util

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
# CTorrent
CTorrent is a torrent client written in C++11 with some help from Boost (for any and ASIO) + OpenSSL for SHA1.
## Requirements
- A C++ compiler with C++11 support (GCC or clang are preferred)
- Boost C++ Libraries (for ASIO and any)
- OpenSSL for SHA1
- getopt
## Developer information
- net/ Contains classes which are responsible for establishing connections, etc.
- bencode/ Contains the decoder and encoder which can decode or encode torrent files.
- ctorrent/ Contains the core classes responsibile for downloading torrents etc.
- util/ Contains various utility functions which are used in bencode/ and ctorrent/
- main.cpp Makes use of all the above (Also is the main for the console application)
## License
MIT (The Expat License).

95
bencode/bencode.h Normal file
View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef __BENCODE_H
#define __BENCODE_H
#include <boost/any.hpp>
#include <string>
#include <map>
#include <vector>
#include <util/serializer.h>
#include <util/databuffer.h>
typedef std::map<std::string, boost::any> Dictionary;
typedef std::vector<boost::any> VectorType;
class Bencode {
public:
Bencode() { m_pos = 0; }
~Bencode() = default;
Dictionary decode(const std::string& fileName);
Dictionary decode(const char *, size_t);
inline void encode(const Dictionary& dict) { return writeDictionary(dict); }
template <typename T>
inline T cast(const boost::any &value)
{
return boost::any_cast<T>(value);
}
template <typename T>
inline T unsafe_cast(const boost::any &value)
{
return *boost::unsafe_any_cast<T>(&value);
}
size_t pos() const { return m_pos; }
const char *buffer(size_t pos, size_t &bufferSize) const {
bufferSize = m_buffer.size() - pos;
return &m_buffer[pos];
}
protected:
inline int64_t readInt();
inline uint64_t readUint();
VectorType readVector();
std::string readString();
Dictionary readDictionary();
void writeString(const std::string &);
void writeInt(int64_t);
void writeUint(uint64_t);
void writeVector(const VectorType &);
void writeDictionary(const Dictionary &);
void writeType(const boost::any *);
template <typename T>
bool readIntUntil(const char byte, T &value);
inline bool unpackByte(char &b) {
if (m_pos + 1 > m_buffer.size())
return false;
b = m_buffer[m_pos++];
return true;
}
void internalWriteString(const std::string&);
private:
DataBuffer<char> m_buffer;
size_t m_pos;
};
#endif

168
bencode/decoder.cpp Normal file
View File

@@ -0,0 +1,168 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "bencode.h"
#include <sstream>
#include <fstream>
#include <iostream>
Dictionary Bencode::decode(const char *data, size_t size)
{
if (data[0] != 'd')
return Dictionary();
++m_pos;
m_buffer = const_cast<char *>(data);
m_buffer.setSize(size);
return readDictionary();
}
Dictionary Bencode::decode(const std::string& fileName)
{
std::ifstream f(fileName, std::ios_base::binary | std::ios_base::in);
if (!f.is_open())
return Dictionary();
f.seekg(0, f.end);
size_t size = f.tellg();
f.seekg(0, f.beg);
char *buffer = new char[size];
f.read(buffer, size);
f.close();
return decode(buffer, size);
}
template <typename T>
bool Bencode::readIntUntil(const char byte, T &value)
{
// Find first occurance of byte in the data buffer
size_t size = 0, pos = 0;
for (pos = m_pos; pos < m_buffer.size() && m_buffer[pos] != byte; ++pos, ++size);
// Sanity check
if (m_buffer[pos] != byte)
return false;
char buffer[size];
memcpy(buffer, &m_buffer[m_pos], size);
buffer[size] = '\0';
std::istringstream ss(buffer);
ss >> value;
m_pos = pos + 1;
return true;
}
int64_t Bencode::readInt()
{
int64_t i;
if (readIntUntil('e', i))
return i;
return ~0;
}
uint64_t Bencode::readUint()
{
uint64_t u;
if (readIntUntil('e', u))
return u;
return std::numeric_limits<uint64_t>::max();
}
std::vector<boost::any> Bencode::readVector()
{
std::vector<boost::any> ret;
for (;;) {
char byte;
if (!unpackByte(byte))
return ret;
switch (byte) {
case 'i': ret.push_back(readInt()); break;
case 'l': ret.push_back(readVector()); break;
case 'd': ret.push_back(readDictionary()); break;
case 'e': return ret;
default:
--m_pos;
ret.push_back(readString());
break;
}
}
return ret;
}
std::string Bencode::readString()
{
uint64_t len;
if (!readIntUntil(':', len))
return std::string();
if (len + m_pos > m_buffer.size())
return std::string();
char buffer[len];
memcpy(buffer, (char *)&m_buffer[m_pos], len);
m_pos += len;
return std::string((const char *)buffer, len);
}
Dictionary Bencode::readDictionary()
{
Dictionary ret;
for (;;) {
std::string key = readString();
if (key.empty())
return Dictionary();
char byte;
if (!unpackByte(byte))
return Dictionary();
switch (byte) {
case 'i': ret[key] = readInt(); break;
case 'l': ret[key] = readVector(); break;
case 'd': ret[key] = readDictionary(); break;
default:
{
--m_pos;
ret[key] = readString();
break;
}
}
if (!unpackByte(byte))
return Dictionary();
else if (byte == 'e')
break;
else
--m_pos;
}
return ret;
}

103
bencode/encoder.cpp Normal file
View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <util/auxiliar.h>
#include "bencode.h"
void Bencode::internalWriteString(const std::string& str)
{
for (size_t i = 0; i < str.length(); ++i)
m_buffer.add(str[i]);
m_pos += str.length();
}
void Bencode::writeString(const std::string& str)
{
internalWriteString(std::to_string(str.length()));
m_buffer.add(':');
++m_pos;
internalWriteString(str);
}
void Bencode::writeInt(int64_t i)
{
m_buffer.add('i');
++m_pos;
internalWriteString(std::to_string(i));
m_buffer.add('e');
++m_pos;
}
void Bencode::writeUint(uint64_t i)
{
m_buffer.add('i');
++m_pos;
internalWriteString(std::to_string(i));
m_buffer.add('e');
++m_pos;
}
void Bencode::writeVector(const VectorType& vector)
{
m_buffer.add('l');
++m_pos;
for (const boost::any& value : vector)
writeType(&value);
m_buffer.add('e');
++m_pos;
}
void Bencode::writeDictionary(const Dictionary& map)
{
m_buffer.add('d');
++m_pos;
for (const auto& pair : map) {
writeString(pair.first);
writeType(&pair.second);
}
m_buffer.add('e');
++m_pos;
}
void Bencode::writeType(const boost::any *value)
{
const std::type_info& type = value->type();
if (type == typeid(int64_t))
writeInt(*boost::unsafe_any_cast<int64_t>(value));
else if (type == typeid(uint64_t))
writeUint(*boost::unsafe_any_cast<uint64_t>(value));
else if (type == typeid(int) || type == typeid(int32_t))
writeInt((int64_t)*boost::unsafe_any_cast<int>(value));
else if (type == typeid(uint32_t))
writeUint((uint64_t)*boost::unsafe_any_cast<uint32_t>(value));
else if (type == typeid(std::string))
writeString(*boost::unsafe_any_cast<std::string>(value));
else if (type == typeid(const char *))
writeString(*boost::unsafe_any_cast<const char *>(value));
else if (type == typeid(Dictionary))
writeDictionary(*boost::unsafe_any_cast<Dictionary>(value));
else if (type == typeid(VectorType))
writeVector(*boost::unsafe_any_cast<VectorType>(value));
}

355
ctorrent/peer.cpp Normal file
View File

@@ -0,0 +1,355 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "peer.h"
#include "torrent.h"
#include <iostream>
Peer::Peer(Torrent *torrent)
: m_torrent(torrent)
{
m_conn = new Connection();
m_state = PS_AmChoked | PS_PeerChoked;
}
Peer::~Peer()
{
delete m_conn;
for (Piece *p : m_queue)
delete p;
m_queue.clear();
}
void Peer::disconnect()
{
m_torrent->removePeer(shared_from_this(), "disconnect called");
m_conn->close();
}
void Peer::connect(const std::string &ip, const std::string &port)
{
m_conn->setErrorCallback(std::bind(&Peer::handleError, shared_from_this(), std::placeholders::_1));
m_conn->connect(ip, port,
[this] ()
{
const uint8_t *m_handshake = m_torrent->handshake();
m_conn->write(m_handshake, 68);
m_conn->read(68,
[this, m_handshake] (const uint8_t *handshake, size_t size)
{
if (size != 68
|| (handshake[0] != 0x13 && memcmp(&handshake[1], "BitTorrent protocol", 19) != 0)
|| memcmp(&handshake[28], &m_handshake[28], 20) != 0)
return handleError("info hash/protocol type mismatch");
std::string peerId((const char *)&handshake[48], 20);
if (!m_peerId.empty() && peerId != m_peerId)
return handleError("unverified");
m_peerId = peerId;
m_torrent->addPeer(shared_from_this());
m_conn->read(4, std::bind(&Peer::handle, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
);
}
);
}
void Peer::handle(const uint8_t *data, size_t size)
{
if (size != 4)
return handleError("Peer::handle(): Expected 4-byte length");
uint32_t length = readBE32(data);
switch (length) {
case 0: // Keep alive
return m_conn->read(4, std::bind(&Peer::handle, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
default:
m_conn->read(length,
[this] (const uint8_t *data, size_t size)
{
InputMessage in(const_cast<uint8_t *>(&data[1]), size - 1, ByteOrder::BigEndian);
handleMessage((MessageType)data[0], in);
}
);
}
}
void Peer::handleMessage(MessageType messageType, InputMessage in)
{
size_t payloadSize = in.getSize();
switch (messageType) {
case MT_Choke:
if (payloadSize != 0)
return handleError("invalid choke-message size");
std::clog << getIP() << " choke" << std::endl;
m_state |= PS_PeerChoked;
break;
case MT_UnChoke:
if (payloadSize != 0)
return handleError("invalid unchoke-message size");
m_state &= ~PS_PeerChoked;
std::clog << getIP() << " unchoke" << std::endl;
for (Piece *piece : m_queue)
requestPiece(piece->index);
break;
case MT_Interested:
{
if (payloadSize != 0)
return handleError("invalid interested-message size");
std::clog << getIP() << " is interested." << std::endl;
m_state |= PS_PeerInterested;
if (isLocalChoked()) {
// 4-byte length, 1-byte packet type
static const uint8_t unchoke[5] = { 0, 0, 0, 1, MT_UnChoke };
m_conn->write(unchoke, sizeof(unchoke));
m_state &= ~PS_AmChoked;
}
break;
}
case MT_NotInterested:
if (payloadSize != 0)
return handleError("invalid not-interested-message size");
std::clog << getIP() << " not interested." << std::endl;
m_state &= ~PS_PeerInterested;
break;
case MT_Have:
{
if (payloadSize != 4)
return handleError("invalid have-message size");
uint32_t p = in.getU32();
if (!hasPiece(p))
pushPiece(p);
break;
}
case MT_Bitfield:
{
if (payloadSize < 1)
return handleError("invalid bitfield-message size");
std::clog << getIP() << " bit field" << std::endl;
uint8_t *buf = in.getBuffer();
#if 0 // FIXME: This is broken for bytes that start with 4 zero bits.
for (size_t i = 0, index = 0; i < payloadSize; ++i) {
uint8_t b = buf[i];
if (b == 0) {
index += 8;
continue;
}
uint8_t leading = CHAR_BIT - (sizeof(unsigned int) * CHAR_BIT - __builtin_clz(b));
uint8_t trailing = __builtin_ctz(b);
// skip leading zero bits first, we skip trailing zero bits later
index += leading;
// push this piece, we know it's there
pushPiece(index++);
for (b >>= trailing + (leading | 1); b != 0; b >>= 1, ++index)
if (b & 1)
pushPiece(index);
// skip trailing
index += trailing;
}
#else
for (size_t i = 0, index = 0; i < payloadSize && index < m_torrent->totalPieces(); ++i)
for (size_t x = CHAR_BIT * sizeof(uint8_t); x > 0; --x, ++index)
if (buf[i] & (1 << x))
pushPiece(index);
m_torrent->requestPiece(shared_from_this());
#endif
break;
}
case MT_Request:
{
if (payloadSize != 12)
return handleError("invalid request-message size");
if (!isRemoteInterested())
return handleError("peer requested piece block without showing interest");
if (isLocalChoked())
return handleError("peer requested piece while choked");
uint32_t index, begin, length;
in >> index;
in >> begin;
in >> length;
if (length > maxRequestSize)
return handleError("peer requested piece of size " + bytesToHumanReadable(length, true) + " which is beyond our max request size");
std::clog << getIP() << " requested piece block of length " << bytesToHumanReadable(length, true) << std::endl;
m_torrent->handleRequestBlock(shared_from_this(), index, begin, length);
break;
}
case MT_PieceBlock:
{
if (payloadSize < 9)
return handleError("invalid pieceblock-message size");
uint32_t index, begin;
in >> index;
in >> begin;
payloadSize -= 8; // deduct index and begin
if (payloadSize <= 0 || payloadSize > maxRequestSize)
return handleError("received too big piece block of size " + bytesToHumanReadable(payloadSize, true));
auto it = std::find_if(m_queue.begin(), m_queue.end(),
[index](const Piece *piece) { return piece->index == index; });
if (it == m_queue.end())
return handleError("received piece " + std::to_string(index) + " which we did not ask for it");
Piece *piece = *it;
uint32_t blockIndex = begin / maxRequestSize;
if (blockIndex >= piece->numBlocks)
return handleError("received too big block index");
piece->blocks[blockIndex].size = payloadSize;
piece->blocks[blockIndex].data = in.getBuffer(payloadSize);
if (++piece->currentBlocks == piece->numBlocks) {
DataBuffer<uint8_t> pieceData;
pieceData.reserve(piece->numBlocks * maxRequestSize); // just a prediction could be bit less
for (size_t x = 0; x < piece->numBlocks; ++x)
for (size_t y = 0; y < piece->blocks[x].size; ++y)
pieceData.add(piece->blocks[x].data[y]);
m_torrent->handlePieceCompleted(shared_from_this(), index, pieceData);
m_queue.erase(it);
delete piece;
// We have to do this here, if we do it inside of handlePieceCompleted
// m_queue will fail us due to sendPieceRequest changing position
if (m_torrent->completedPieces() != m_torrent->totalPieces())
m_torrent->requestPiece(shared_from_this());
}
break;
}
case MT_Cancel:
{
if (payloadSize != 12)
return handleError("invalid cancel-message size");
uint32_t index, begin, length;
in >> index;
in >> begin;
in >> length;
std::clog << getIP() << " cancel" << std::endl;
break;
}
case MT_Port:
if (payloadSize != 2)
return handleError("invalid port-message size");
uint16_t port;
in >> port;
break;
}
m_conn->read(4, std::bind(&Peer::handle, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
void Peer::handleError(const std::string &errmsg)
{
m_torrent->removePeer(shared_from_this(), errmsg);
m_conn->close(false); // close but don't call me again
}
void Peer::sendHave(uint32_t index)
{
if (isLocalChoked())
return;
OutputMessage out(ByteOrder::BigEndian, 9);
out << (uint32_t)5; // length
out << (uint8_t)MT_Have;
out << index;
m_conn->write(out);
}
void Peer::sendPieceRequest(uint32_t index)
{
sendInterested();
uint32_t pieceLength = m_torrent->pieceSize(index);
size_t numBlocks = (int)(ceil(double(pieceLength) / maxRequestSize));
Piece *piece = new Piece();
piece->index = index;
piece->currentBlocks = 0;
piece->numBlocks = numBlocks;
piece->blocks = new PieceBlock[numBlocks];
m_queue.push_back(piece);
if (!isRemoteChoked())
requestPiece(index);
}
void Peer::sendRequest(uint32_t index, uint32_t begin, uint32_t length)
{
OutputMessage out(ByteOrder::BigEndian, 17);
out << (uint32_t)13; // length
out << (uint8_t)MT_Request;
out << index;
out << begin;
out << length;
m_conn->write(out);
}
void Peer::sendInterested()
{
// 4-byte length, 1 byte packet type
const uint8_t interested[5] = { 0, 0, 0, 1, MT_Interested };
m_conn->write(interested, sizeof(interested));
m_state |= PS_AmInterested;
}
void Peer::requestPiece(size_t pieceIndex)
{
if (isRemoteChoked())
return handleError("Attempt to request piece from a peer that is remotely choked");
size_t begin = 0;
size_t length = m_torrent->pieceSize(pieceIndex);
for (; length > maxRequestSize; length -= maxRequestSize, begin += maxRequestSize)
sendRequest(pieceIndex, begin, maxRequestSize);
sendRequest(pieceIndex, begin, length);
}

113
ctorrent/peer.h Normal file
View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef __PEER_H
#define __PEER_H
#include <net/connection.h>
#include <net/inputmessage.h>
#include <memory>
#include <vector>
class Torrent;
class Peer : public std::enable_shared_from_this<Peer>
{
enum State : uint8_t {
PS_AmChoked = 1 << 0, // We choked this peer (aka we're not giving him anymore pieces)
PS_AmInterested = 1 << 1, // We're interested in this peer's pieces
PS_PeerChoked = 1 << 2, // Peer choked us
PS_PeerInterested = 1 << 3, // Peer interested in our stuff
};
enum MessageType : uint8_t {
MT_Choke = 0,
MT_UnChoke = 1,
MT_Interested = 2,
MT_NotInterested = 3,
MT_Have = 4,
MT_Bitfield = 5,
MT_Request = 6,
MT_PieceBlock = 7,
MT_Cancel = 8,
MT_Port = 9
};
public:
Peer(Torrent *t);
~Peer();
inline void setId(const std::string &id) { m_peerId = id; }
inline std::string getIP() const { return m_conn->getIPString(); }
void disconnect();
void connect(const std::string &ip, const std::string &port);
protected:
void handle(const uint8_t *data, size_t size);
void handleMessage(MessageType mType, InputMessage in);
void handleError(const std::string &);
void sendHave(uint32_t index);
void sendPieceRequest(uint32_t index);
void sendRequest(uint32_t index, uint32_t begin, uint32_t size);
void sendInterested();
void requestPiece(size_t pieceIndex);
inline std::vector<size_t> getPieces() const { return m_pieces; }
inline void pushPiece(size_t i) { m_pieces.push_back(i); }
inline bool hasPiece(size_t index) { return std::find(m_pieces.begin(), m_pieces.end(), index) != m_pieces.end(); }
inline bool isRemoteChoked() const { return test_bit(m_state, PS_PeerChoked); }
inline bool isLocalChoked() const { return test_bit(m_state, PS_AmChoked); }
inline bool isRemoteInterested() const { return test_bit(m_state, PS_PeerInterested); }
inline bool isLocalInterested() const { return test_bit(m_state, PS_AmInterested); }
private:
struct PieceBlock {
size_t size;
uint8_t *data;
~PieceBlock() { delete []data; }
};
struct Piece {
size_t index;
size_t currentBlocks;
size_t numBlocks;
~Piece() { delete []blocks; }
PieceBlock *blocks;
};
std::vector<size_t> m_pieces;
std::vector<Piece *> m_queue;
std::string m_peerId;
uint8_t m_state;
Torrent *m_torrent;
Connection *m_conn;
friend class Torrent;
};
typedef std::shared_ptr<Peer> PeerPtr;
#endif

662
ctorrent/torrent.cpp Normal file
View File

@@ -0,0 +1,662 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "torrent.h"
#include <net/connection.h>
#include <util/auxiliar.h>
#include <algorithm>
#include <iostream>
#include <thread>
#include <openssl/sha.h>
Torrent::Torrent()
: m_completedPieces(0),
m_uploadedBytes(0),
m_downloadedBytes(0),
m_hashMisses(0),
m_pieceMisses(0)
{
}
Torrent::~Torrent()
{
for (auto f : m_files)
fclose(f.fp);
disconnectPeers();
}
bool Torrent::open(const std::string &fileName, const std::string &downloadDir)
{
Bencode bencode;
Dictionary dict = bencode.decode(fileName);
if (dict.empty()) {
std::cerr << fileName << ": unable to decode data" << std::endl;
return false;
}
m_mainTracker = bencode.unsafe_cast<std::string>(dict["announce"]);
m_comment = bencode.unsafe_cast<std::string>(dict["comment"]);
if (dict.count("announce-list") > 0)
m_trackers = bencode.unsafe_cast<VectorType>(dict["announce-list"]);
Dictionary info = bencode.unsafe_cast<Dictionary>(dict["info"]);
size_t pos = bencode.pos();
bencode.encode(info);
size_t bufferSize;
const char *buffer = bencode.buffer(pos, bufferSize);
uint8_t checkSum[SHA_DIGEST_LENGTH];
SHA1((uint8_t *)buffer, bufferSize, checkSum);
m_handshake[0] = 0x13; // 19 length of string "BitTorrent protocol"
memcpy(&m_handshake[1], "BitTorrent protocol", 19);
memset(&m_handshake[20], 0x00, 8); // reserved bytes (last |= 0x01 for DHT or last |= 0x04 for FPE)
memcpy(&m_handshake[28], checkSum, 20); // info hash
memcpy(&m_handshake[48], "-CT11000", 8); // Azureus-style peer id (-CT0000-XXXXXXXXXXXX)
memcpy(&m_peerId[0], "-CT11000", 8);
static std::random_device rd;
static std::ranlux24 generator(rd());
static std::uniform_int_distribution<uint8_t> random(0x00, 0xFF);
for (size_t i = 8; i < 20; ++i)
m_handshake[56 + i] = m_peerId[i] = random(generator);
m_name = bencode.unsafe_cast<std::string>(info["name"]);
m_pieceLength = bencode.unsafe_cast<int64_t>(info["piece length"]);
std::string pieces = bencode.unsafe_cast<std::string>(info["pieces"]);
for (size_t i = 0; i < pieces.size(); i += 20) {
Piece piece;
piece.done = false;
piece.priority = 0;
memcpy(&piece.hash[0], pieces.c_str() + i, 20);
m_pieces.push_back(piece);
}
MKDIR(downloadDir);
chdir(downloadDir.c_str());
std::string base = getcwd();
if (info.count("files") > 0) {
std::string dirName = bencode.unsafe_cast<std::string>(info["name"]);
MKDIR(dirName);
CHDIR(dirName);
base = getcwd(); /* += "/" + dirName */
int64_t begin = 0;
const Dictionary files = bencode.unsafe_cast<Dictionary>(info["files"]);
size_t index = 0;
for (const auto& pair : files) {
Dictionary v = bencode.unsafe_cast<Dictionary>(pair.second);
VectorType pathList = bencode.unsafe_cast<VectorType>(v["path"]);
std::string path;
for (auto it = pathList.begin();; ++it) {
const std::string &s = bencode.unsafe_cast<std::string>(*it);
if (it == pathList.end() - 1) {
path += s + PATH_SEP;
break;
}
path += s + PATH_SEP;
if (!nodeExists(path))
MKDIR(path);
}
int64_t length = bencode.unsafe_cast<int64_t>(v["length"]);
File file = {
.path = path,
.fp = nullptr,
.begin = begin,
.length = length
};
if (!nodeExists(path)) {
/* Open for writing and create */
file.fp = fopen(path.c_str(), "wb");
if (!file.fp) {
std::cerr << m_name << ": unable to create " << path << std::endl;
return false;
}
} else {
/* Open for both writing and reading */
file.fp = fopen(path.c_str(), "rb+");
if (!file.fp) {
std::cerr << m_name << ": unable to open " << path << std::endl;
return false;
}
findCompletedPieces(&file, index);
std::clog << m_name << ": " << path << ": Completed pieces: " << m_completedPieces << "/" << m_pieces.size() << std::endl;
}
m_files.push_back(file);
m_totalSize += length;
begin += length;
++index;
}
chdir("..");
} else {
// Just one file
if (!validatePath(base, base + "/" + m_name)) {
std::cerr << m_name << ": error validating path: " << base << "/" << m_name << std::endl;
return false;
}
int64_t length = bencode.unsafe_cast<int64_t>(info["length"]);
File file = {
.path = m_name.c_str(),
.fp = nullptr,
.begin = 0,
.length = length
};
if (!nodeExists(m_name.c_str())) {
/* Open for writing and create */
file.fp = fopen(m_name.c_str(), "wb");
if (!file.fp) {
std::cerr << m_name << ": unable to create " << m_name << std::endl;
return false;
}
} else {
/* Open for both reading and writing */
file.fp = fopen(m_name.c_str(), "rb+");
if (!file.fp) {
std::cerr << m_name << ": unable to open " << m_name << std::endl;
return false;
}
findCompletedPieces(&file, 0);
std::clog << m_name << ": Completed pieces: " << m_completedPieces << "/" << m_pieces.size() << std::endl;
}
m_totalSize = length;
m_files.push_back(file);
}
chdir("..");
return true;
}
bool Torrent::checkPieceHash(const uint8_t *data, size_t size, uint32_t index)
{
if (index >= m_pieces.size())
return false;
uint8_t checkSum[SHA_DIGEST_LENGTH];
SHA1(data, size, checkSum);
return memcmp(checkSum, m_pieces[index].hash, SHA_DIGEST_LENGTH) == 0;
}
void Torrent::findCompletedPieces(const File *f, size_t index)
{
#if 0
FILE *fp = f->fp;
fseek(fp, 0L, SEEK_END);
off_t fileLength = ftello(fp);
fseek(fp, 0L, SEEK_SET);
if (fileLength > f->length) {
std::clog << m_name << ": Truncating " << f->path << " to " << bytesToHumanReadable(f->length, true) << std::endl;
truncate(f->path.c_str(), f->length);
return; // file truncated
}
size_t pieceIndex = 0; // start piece index
if (f->begin > 0) // not first file?
pieceIndex = f->begin / m_pieceLength;
size_t pieceBegin = pieceIndex * m_pieceLength;
size_t pieceLength = pieceSize(pieceIndex);
int64_t fileEnd = f->begin + f->length;
if (pieceBegin + pieceLength > fileEnd) {
std::cerr << m_name << ": insane piece size" << std::endl;
return;
}
#endif
}
Torrent::DownloadError Torrent::download(int port)
{
int64_t piecesNeeded = (int64_t)m_pieces.size();
if (m_completedPieces == piecesNeeded)
return DownloadError::AlreadyDownloaded;
// Following (const char *) casts are to shut GCC 4.8.1 on Windows (MinGW32) up.
Dictionary dict;
dict["event"] = (const char *)"started";
if (!queryTracker(dict, port))
return DownloadError::TrackerQueryFailure;
connectToPeers(dict["peers"]);
dict.clear();
while (m_completedPieces != piecesNeeded) {
if (m_peers.size() < 10 && std::chrono::system_clock::now() >= m_timeToNextRequest) {
std::clog << "Requesting more peers" << std::endl;
std::ostringstream os;
os << m_peers.size() * 4;
dict.clear();
dict["numwant"] = os.str().c_str();
if (!queryTracker(dict, port))
return DownloadError::TrackerQueryFailure;
//disconnectPeers();
connectToPeers(dict["peers"]);
}
sleep(1);
}
disconnectPeers();
for (const auto &f : m_files)
fclose(f.fp);
m_files.clear();
dict["event"] = (const char *)"stopped";
if (m_completedPieces == piecesNeeded)
dict["completed"] = (const char *)"1";
queryTracker(dict, port);
return (m_completedPieces == piecesNeeded) ? DownloadError::Completed : DownloadError::NetworkError;
}
bool Torrent::queryTracker(Dictionary &request, int portnr)
{
std::string req = "";
for (const auto& pair : request)
req += pair.first + "=" + boost::any_cast<const char *>(pair.second) + "&";
request.clear();
UrlData url = parseUrl(m_mainTracker);
std::string host = URL_HOSTNAME(url);
if (host.empty()) {
std::cerr << m_name << ": queryTracker(): failed to parse announce url: " << m_mainTracker << " bailing out..." << std::endl;
return false;
}
std::string port = URL_SERVNAME(url);
std::string protocol;
if (!port.empty())
protocol = port;
else
protocol = URL_PROTOCOL(url);
/// TODO: Some trackers require a UDP connection.
asio::ip::tcp::socket socket(g_service);
if (!connectTo(host, protocol, socket)) {
if (m_trackers.empty()) {
std::cerr << m_name << ": queryTracker(): This Torrent did not provide multiple trackers, bailing out..." << std::endl;
return false;
}
bool success = false;
for (const boost::any &s : m_trackers) {
/* From random torrent from thepiratebay.se:
d8:announce40:http://tracker.thepiratebay.org/announce13:announce-listll40:http://tracker.thepiratebay.org/announceel35:udp://tracker.openbittorrent.com:80el25:udp://tracker-ccc.de:6969el29:udp://tracker.publicbt.com:80ee
It basically ended the list after each tracker, so I am not quite sure if this right, the announce-list is supposed to be like so:
[ "alt_tracker1", "alt_tracker2", ... ]
Not:
[ [ "alt_tracker1" ], [ "alt_tracker2" ], ... ]
I may not be right, I think this is how qBitTorrent generates it, since later in that torrent:
created by20:qBittorrent v3.1.9.213
*/
if (s.type() == typeid(VectorType)) {
const VectorType &vType = *boost::unsafe_any_cast<VectorType>(&s);
for (const boost::any &announce : vType) {
url = parseUrl(*boost::unsafe_any_cast<std::string>(&announce));
host = URL_HOSTNAME(url);
if (host.empty()) /* Semantic error? */
continue;
port = URL_SERVNAME(url);
if (!port.empty())
protocol = port;
else
protocol = URL_PROTOCOL(url);
std::cerr << m_name << ": queryTracker(): Trying: " << host << ":" << protocol << std::endl;
if ((success = connectTo(host, protocol, socket)))
break;
}
} else if (s.type() == typeid(std::string)) {
url = parseUrl(*boost::unsafe_any_cast<std::string>(&s));
host = URL_HOSTNAME(url);
port = URL_SERVNAME(url);
if (!port.empty())
protocol = port;
else
protocol = URL_PROTOCOL(url);
std::cerr << m_name << ": queryTracker(): Trying: " << host << ":" << protocol << std::endl;
if ((success = connectTo(host, protocol, socket)))
break;
}
}
if (!success) {
std::cerr << m_name << ": queryTracker(): Tried to connect to " << m_trackers.size() << " tracker(s) but none of them seem to respond" << std::endl;
return false;
}
}
char infoHash[20];
memcpy(infoHash, &m_handshake[28], 20);
int64_t downloaded = downloadedBytes();
asio::streambuf params;
std::ostream buf(&params);
buf << "GET /announce?" << req << "info_hash=" << urlencode(std::string(infoHash, 20)) << "&port=" << portnr << "&compact=1&key=1337T0RRENT"
<< "&peer_id=" << urlencode(std::string((const char *)peerId(), 20)) << "&downloaded=" << downloaded << "&uploaded=" << m_uploadedBytes
<< "&left=" << m_totalSize - downloaded << " HTTP/1.0\r\n"
<< "Host: " << host << "\r\n"
<< "\r\n";
asio::write(socket, params);
asio::streambuf response;
try {
asio::read_until(socket, response, "\r\n");
} catch (const std::exception &e) {
std::cerr << m_name << ": queryTracker(): Unable to read from " << host << ":" << protocol << " (" << e.what() << ")" << std::endl;
return false;
}
std::istream rbuf(&response);
std::string httpVersion;
rbuf >> httpVersion;
if (httpVersion.substr(0, 5) != "HTTP/") {
std::cerr << m_name << ": queryTracker(): Internal error: Tracker send an invalid HTTP version response" << std::endl;
return false;
}
int status;
rbuf >> status;
if (status != 200) {
std::cerr << m_name << ": queryTracker(): Tracker failed to process our request: " << status << std::endl;
return false;
}
try {
asio::read_until(socket, response, "\r\n\r\n");
} catch (const std::exception &e) {
std::cerr << m_name << ": queryTracker(): Unable to read from " << host << ":" << protocol << " (" << e.what() << ")" << std::endl;
return false;
}
// Seek to start of body
std::string header;
while (std::getline(rbuf, header) && header != "\r");
if (!rbuf) {
std::cerr << m_name << ": queryTracker(): Unable to get to tracker response body." << std::endl;
return false;
}
std::ostringstream os;
os << &response;
std::string buffer = os.str();
Bencode bencode;
request = bencode.decode(buffer.c_str(), buffer.length());
if (request.empty()) {
std::cerr << m_name << ": queryTracker(): Unable to decode tracker response body" << std::endl;
return false;
}
if (request.count("failure reason") != 0) {
std::cerr << m_name << ": queryTracker(): Tracker responded: " << bencode.unsafe_cast<std::string>(request["failure reason"]) << std::endl;
return false;
}
m_timeToNextRequest = std::chrono::system_clock::now()
+ std::chrono::milliseconds(bencode.unsafe_cast<int64_t>(request["interval"]));
return true;
}
void Torrent::connectToPeers(const boost::any &_peers)
{
if (_peers.type() == typeid(std::string)) {
std::string peers = *boost::unsafe_any_cast<std::string>(&_peers);
m_peers.reserve(peers.length() / 6);
// 6 bytes each (first 4 is ip address, last 2 port) all in big endian notation
for (size_t i = 0; i < peers.length(); i += 6) {
const uint8_t *iport = (const uint8_t *)peers.c_str() + i;
// Asynchronously connect to that peer, and do not add it to our
// active peers list unless a connection was established successfully.
auto peer = std::make_shared<Peer>(this);
peer->connect(ip2str(isLittleEndian() ? readLE32(iport) : readBE32(iport)), std::to_string(readBE16(iport + 4)));
}
} else if (_peers.type() == typeid(Dictionary)) {
/* If we got here it means the tracker does not support compact mode,
* this is bad for us; bandwidth waste and slower than the method above. */
Dictionary peers = *boost::unsafe_any_cast<Dictionary>(&_peers);
assert(!peers.empty());
m_peers.reserve(peers.size());
for (const auto &pair : peers) {
Dictionary peerInfo = boost::any_cast<Dictionary>(pair.second);
std::string peerId = boost::any_cast<std::string>(peerInfo["peer id"]);
std::string ip = boost::any_cast<std::string>(peerInfo["ip"]);
int64_t port = boost::any_cast<int64_t>(peerInfo["port"]);
// Asynchronously connect to that peer, and do not add it to our
// active peers list unless a connection was established successfully.
auto peer = std::make_shared<Peer>(this);
peer->setId(peerId);
peer->connect(ip, std::to_string(port));
}
}
}
void Torrent::addTracker(const std::string &tracker)
{
m_trackers.push_back(tracker);
}
void Torrent::addPeer(const PeerPtr &peer)
{
m_peers.push_back(peer);
std::clog << m_name << ": Peers: " << m_peers.size() << std::endl;
}
void Torrent::removePeer(const PeerPtr &peer, const std::string &errmsg)
{
auto it = std::find(m_peers.begin(), m_peers.end(), peer);
if (it == m_peers.end())
std::cerr << m_name << ": removePeer(): failed to find peer " << peer << std::endl;
else
m_peers.erase(it);
std::clog << m_name << ": " << peer->getIP() << ": " << errmsg << std::endl;
std::clog << m_name << ": Peers: " << m_peers.size() << std::endl;
}
void Torrent::disconnectPeers()
{
for (const PeerPtr &peer : m_peers)
peer->disconnect();
m_peers.clear();
}
void Torrent::requestPiece(const PeerPtr &peer)
{
size_t index = 0;
int32_t priority = std::numeric_limits<int32_t>::max();
std::vector<size_t> peerPieces = peer->getPieces();
for (size_t i = 0; i < peerPieces.size(); ++i) {
size_t piece = peerPieces[i];
if (piece >= m_pieces.size())
continue;
Piece *p = &m_pieces[piece];
if (p->done)
continue;
if (!p->priority) {
p->priority = 1;
return peer->sendPieceRequest(piece);
}
if (priority > p->priority) {
priority = p->priority;
index = piece;
}
}
if (priority != std::numeric_limits<int32_t>::max()) {
++m_pieces[index].priority;
peer->sendPieceRequest(index);
}
}
void Torrent::handlePieceCompleted(const PeerPtr &peer, uint32_t index, const DataBuffer<uint8_t> &pieceData)
{
m_downloadedBytes += pieceData.size();
if (m_pieces[index].done) {
++m_pieceMisses;
std::cerr << m_name << ": " << peer->getIP() << " piece miss for piece #" << index << " total misses: " << m_pieceMisses << std::endl;
peer->sendHave(index);
return;
}
if (!checkPieceHash(&pieceData[0], pieceData.size(), index)) {
std::cerr << m_name << ": " << peer->getIP() << " checksum mismatch for piece " << index << "." << std::endl;
++m_hashMisses;
return;
}
m_pieces[index].done = true;
++m_completedPieces;
int64_t beginPos = index * m_pieceLength;
const uint8_t *data = &pieceData[0];
size_t off = 0;
size_t size = pieceData.size();
for (const File &file : m_files) {
if (beginPos < file.begin)
break;
int64_t fileEnd = file.begin + file.length;
if (beginPos >= fileEnd)
break;
int64_t amount = fileEnd - beginPos;
if (amount > size)
amount = size;
fseek(file.fp, beginPos - file.begin, SEEK_SET);
size_t wrote = fwrite(data + off, 1, amount, file.fp);
off += wrote;
size -= wrote;
beginPos += wrote;
}
for (const PeerPtr &peer : m_peers)
peer->sendHave(index);
std::clog << m_name << ": " << peer->getIP() << " Completed " << m_completedPieces << "/" << m_pieces.size() << " pieces "
<< "(Downloaded: " << bytesToHumanReadable(m_downloadedBytes, true) << ", "
<< "Hash miss: " << m_hashMisses << ", piece miss: " << m_pieceMisses << ")"
<< std::endl;
}
void Torrent::handleRequestBlock(const PeerPtr &peer, uint32_t index, uint32_t begin, uint32_t length)
{
// Peer requested block from us
if (index >= m_pieces.size() || !m_pieces[index].done)
return; // We haven't finished downloading that piece yet, so we can't give it out.
size_t blockEnd = begin + length;
if (blockEnd > pieceSize(index))
return peer->disconnect(); // This peer is on drugs
uint8_t block[length];
m_uploadedBytes += length;
/// TODO
}
int64_t Torrent::pieceSize(size_t pieceIndex) const
{
// Last piece can be different in size
if (pieceIndex == m_pieces.size() - 1) {
int64_t r = m_totalSize % m_pieceLength;
if (r != 0)
return r;
}
return m_pieceLength;
}
inline int64_t Torrent::downloadedBytes() const
{
int64_t downloaded = 0;
for (size_t i = 0; i < m_pieces.size(); ++i)
if (m_pieces[i].done)
downloaded += pieceSize(i);
return downloaded;
}
bool Torrent::connectTo(
const std::string &host, const std::string &service,
asio::ip::tcp::socket &socket
)
{
asio::ip::tcp::resolver resolver(g_service);
asio::ip::tcp::resolver::query query(host, service);
boost::system::error_code error;
asio::ip::tcp::resolver::iterator endpoint = resolver.resolve(query, error);
asio::ip::tcp::resolver::iterator end;
if (error) {
std::cerr << m_name << ": queryTracker(): DNS Lookup failure: "
<< host << "(service name: " << service << "): "
<< "(error message: " << error.message() << ")"
<< std::endl;
return false;
}
do {
socket.close();
socket.connect(*endpoint++, error);
} while (error && endpoint != end);
if (error) {
std::cerr << m_name << ": queryTracker(): Unable to connect to tracker at "
<< host << " (service name: " << query.service_name() << "): "
<< "(error message: " << error.message() << ")"
<< std::endl;
return false;
}
return true;
}

131
ctorrent/torrent.h Normal file
View File

@@ -0,0 +1,131 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef __TORRENT_H
#define __TORRENT_H
#include "peer.h"
#include <boost/any.hpp>
#include <vector>
#include <map>
#include <string>
#include <atomic>
#include <chrono>
#include <fstream>
#include <iosfwd>
#include <bencode/bencode.h>
static int64_t maxRequestSize = 16384; // 16KiB (per piece)
class Torrent
{
private:
struct File {
std::string path;
FILE *fp;
int64_t begin; // offset of which the piece(s) begin
int64_t length;
};
public:
enum class DownloadError {
Completed = 0,
TrackerQueryFailure = 1,
AlreadyDownloaded = 2,
NetworkError = 3
};
Torrent();
~Torrent();
bool open(const std::string& fileName, const std::string &downloadDir);
DownloadError download(int port);
inline size_t activePeers() const { return m_peers.size(); }
inline int64_t totalSize() const { return m_totalSize; }
int64_t downloadedBytes() const;
inline uint32_t uploadedBytes() const { return m_uploadedBytes; }
inline std::string name() const { return m_name; }
protected:
bool checkPieceHash(const uint8_t *data, size_t size, uint32_t index);
bool queryTracker(Dictionary &request, int port);
void findCompletedPieces(const struct File *f, size_t index);
void connectToPeers(const boost::any &peers);
void requestPiece(const PeerPtr &peer, size_t pieceIndex);
void requestPiece(const PeerPtr &peer);
int64_t pieceSize(size_t pieceIndex) const;
inline bool pieceDone(size_t pieceIndex) const { return m_pieces[pieceIndex].done; }
bool connectTo(
const std::string &host, const std::string &service,
asio::ip::tcp::socket &socket
);
void addTracker(const std::string &tracker);
void addPeer(const PeerPtr &peer);
void removePeer(const PeerPtr &peer, const std::string &errmsg);
inline void disconnectPeers();
inline const uint8_t *peerId() const { return m_peerId; }
inline const uint8_t *handshake() const { return m_handshake; }
inline size_t totalPieces() const { return m_pieces.size(); }
inline size_t completedPieces() const { return m_completedPieces; }
void handlePieceCompleted(const PeerPtr &peer, uint32_t index, const DataBuffer<uint8_t> &data);
void handleRequestBlock(const PeerPtr &peer, uint32_t index, uint32_t begin, uint32_t length);
private:
struct Piece {
bool done;
int32_t priority;
uint8_t hash[20];
};
std::vector<PeerPtr> m_peers;
std::vector<Piece> m_pieces;
std::vector<File> m_files;
uint32_t m_completedPieces;
uint32_t m_uploadedBytes;
uint32_t m_downloadedBytes;
int64_t m_pieceLength;
int64_t m_totalSize;
size_t m_hashMisses;
size_t m_pieceMisses;
VectorType m_trackers;
std::string m_name;
std::string m_mainTracker;
std::string m_comment;
std::chrono::time_point<std::chrono::system_clock> m_timeToNextRequest;
std::atomic_bool m_paused;
uint8_t m_handshake[68];
uint8_t m_peerId[20];
friend class Peer;
};
#endif

175
main.cpp Normal file
View File

@@ -0,0 +1,175 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <ctorrent/torrent.h>
#include <net/connection.h>
#include <util/auxiliar.h>
#include <thread>
#include <functional>
#include <iostream>
#include <stdlib.h>
#include <getopt.h>
static void print_help(const char *p)
{
std::clog << "Usage: " << p << " <options...> <torrent...>" << std::endl;
std::clog << "Mandatory arguments to long options are mandatory for short options too." << std::endl;
std::clog << "\t\t--version (-v), --help (-h) do not take any argument" << std::endl;
std::clog << "\t\t--nodownload (-n) Just check pieces completed, torrent size, etc." << std::endl;
std::clog << "\t\t--piecesize (-s) Specify piece size in KB, this will be rounded to the nearest power of two. Default is 16 KB" << std::endl;
std::clog << "\t\t--port (-p) Not yet fully implemented." << std::endl;
std::clog << "\t\t--dldir (-d) Specify downloads directory." << std::endl;
std::clog << "Example: " << p << " --nodownload a.torrent b.torrent c.torrent" << std::endl;
}
int main(int argc, char *argv[])
{
static const struct option opts[] = {
{ "version", no_argument, 0, 'v' },
{ "help", no_argument, 0, 'h' },
{ "port", required_argument, 0, 'p' },
{ "nodownload", no_argument, 0, 'n' },
{ "piecesize", required_argument, 0, 's' },
{ "dldir", required_argument, 0, 'd' },
{ 0, 0, 0, 0 }
};
if (argc == 1) {
print_help(argv[0]);
return 1;
}
char c;
int optidx = 0;
bool nodownload = false;
int startport = 6881;
std::string dldir = "Torrents";
while ((c = getopt_long(argc, argv, "nvp:hs:d:", opts, &optidx)) != -1) {
switch (c) {
case 'n':
nodownload = true;
break;
case 's':
maxRequestSize = 1 << (32 - __builtin_clz(atoi(optarg) - 1));
break;
case 'p':
startport = atoi(optarg);
break;
case 'd':
dldir = optarg;
break;
case '?':
return 1;
case 'v':
std::clog << "CTorrent v1.0" << std::endl;
/* fallthru */
case 'h':
print_help(argv[0]);
return 0;
default:
if (optopt == 'c')
std::cerr << "Option -" << optopt << " requires an argument." << std::endl;
else if (isprint(optopt))
std::cerr << "Unknown option -" << optopt << "." << std::endl;
else
std::cerr << "Unknown option character '\\x" << std::hex << optopt << "'." << std::endl;
return 1;
}
}
if (optind >= argc) {
std::clog << argv[0] << ": Please specify torrent file(s) after arguments." << std::endl;
return 1;
}
std::thread thread;
if (!nodownload) {
std::clog << "Using " << dldir << " as downloads directory and "
<< bytesToHumanReadable(maxRequestSize, true) << " piece block size" << std::endl;
thread = std::thread([] () { while (true) Connection::poll(); });
thread.detach();
}
int total = argc - optind;
int completed = 0;
int errors = 0;
Torrent torrents[total];
std::thread threads[total];
for (int i = 0; optind < argc; ++i) {
const char *file = argv[optind++];
Torrent *t = &torrents[i];
if (!t->open(file, dldir)) {
std::clog << file << ": corrupted torrent file" << std::endl;
++errors;
continue;
}
std::clog << t->name() << ": Total size: " << bytesToHumanReadable(t->totalSize(), true) << std::endl;
if (nodownload)
continue;
threads[i] = std::thread([t, &startport, &completed, &errors]() {
auto error = t->download(startport++);
switch (error) {
case Torrent::DownloadError::Completed:
std::clog << t->name() << " finished download" << std::endl;
++completed;
break;
case Torrent::DownloadError::AlreadyDownloaded:
std::clog << t->name() << " was already downloaded" << std::endl;
++completed;
break;
case Torrent::DownloadError::NetworkError:
std::clog << "Network error was encountered, check your internet connection" << std::endl;
++errors;
break;
case Torrent::DownloadError::TrackerQueryFailure:
std::clog << "The tracker has failed to respond in time or some internal error has occured" << std::endl;
++errors;
break;
}
std::clog << "Downloaded: " << bytesToHumanReadable(t->downloadedBytes(), true) << std::endl;
std::clog << "Uploaded: " << bytesToHumanReadable(t->uploadedBytes(), true) << std::endl;
});
threads[i].detach();
}
if (!nodownload) {
int last = 0;
while (completed != total) {
if (last == completed) {
sleep(5);
continue;
}
std::clog << "Completed " << completed << "/" << total - errors << " (" << errors << " errnoeous torrents)" << std::endl;
last = completed;
}
}
return 0;
}

203
net/connection.cpp Normal file
View File

@@ -0,0 +1,203 @@
/*
* Copyright (c) 2013 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "connection.h"
asio::io_service g_service;
static std::mutex g_connectionLock;
static std::list<std::shared_ptr<asio::streambuf>> g_outputStreams;
Connection::Connection() :
m_delayedWriteTimer(g_service),
m_resolver(g_service),
m_socket(g_service)
{
}
Connection::~Connection()
{
close(false);
}
void Connection::poll()
{
g_service.reset();
g_service.poll();
}
void Connection::connect(const std::string &host, const std::string &port, const ConnectCallback &cb)
{
asio::ip::tcp::resolver::query query(host, port);
m_cb = cb;
m_resolver.async_resolve(
query,
std::bind(&Connection::handleResolve, this, std::placeholders::_1, std::placeholders::_2)
);
}
void Connection::close(bool warn)
{
if (!isConnected()) {
if (m_eh && warn)
m_eh("Connection::close(): Called on an already closed connection!");
return;
}
boost::system::error_code ec;
m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
if (ec && warn && m_eh)
m_eh(ec.message());
m_socket.close();
}
void Connection::write(const uint8_t *bytes, size_t size)
{
if (!isConnected())
return;
if (!m_outputStream) {
g_connectionLock.lock();
if (!g_outputStreams.empty()) {
m_outputStream = g_outputStreams.front();
g_outputStreams.pop_front();
} else
m_outputStream = std::shared_ptr<asio::streambuf>(new asio::streambuf);
g_connectionLock.unlock();
m_delayedWriteTimer.cancel();
m_delayedWriteTimer.expires_from_now(boost::posix_time::milliseconds(10));
m_delayedWriteTimer.async_wait(std::bind(&Connection::internalWrite, this, std::placeholders::_1));
}
std::ostream os(m_outputStream.get());
os.write((const char *)bytes, size);
os.flush();
}
void Connection::read_partial(size_t bytes, const ReadCallback &rc)
{
if (!isConnected())
return;
m_rc = rc;
m_socket.async_read_some(
asio::buffer(m_inputStream.prepare(bytes)),
std::bind(&Connection::handleRead, this, std::placeholders::_1, std::placeholders::_2)
);
}
void Connection::read(size_t bytes, const ReadCallback &rc)
{
if (!isConnected())
return;
m_rc = rc;
asio::async_read(
m_socket, asio::buffer(m_inputStream.prepare(bytes)),
std::bind(&Connection::handleRead, this, std::placeholders::_1, std::placeholders::_2)
);
}
void Connection::internalWrite(const boost::system::error_code& e)
{
if (e == asio::error::operation_aborted)
return;
std::shared_ptr<asio::streambuf> outputStream = m_outputStream;
m_outputStream = nullptr;
asio::async_write(
m_socket, *outputStream,
std::bind(&Connection::handleWrite, this, std::placeholders::_1, std::placeholders::_2, outputStream)
);
}
void Connection::handleWrite(const boost::system::error_code &e, size_t bytes, std::shared_ptr<asio::streambuf> outputStream)
{
m_delayedWriteTimer.cancel();
if (e == asio::error::operation_aborted)
return;
outputStream->consume(outputStream->size());
g_outputStreams.push_back(outputStream);
if (e)
handleError(e);
}
void Connection::handleRead(const boost::system::error_code &e, size_t readSize)
{
if (e)
return handleError(e);
if (m_rc) {
const uint8_t *data = asio::buffer_cast<const uint8_t *>(m_inputStream.data());
m_rc(data, readSize);
}
m_inputStream.consume(readSize);
}
void Connection::handleResolve(
const boost::system::error_code &e,
asio::ip::basic_resolver<asio::ip::tcp>::iterator endpoint
)
{
if (e)
return handleError(e);
m_socket.async_connect(*endpoint, std::bind(&Connection::handleConnect, this, std::placeholders::_1));
}
void Connection::handleConnect(const boost::system::error_code &e)
{
if (e)
return handleError(e);
else if (m_cb)
m_cb();
}
void Connection::handleError(const boost::system::error_code& error)
{
if (error == asio::error::operation_aborted)
return;
if (m_eh)
m_eh(error.message());
if (isConnected()) // User is free to close the connection before us
close();
}
uint32_t Connection::getIP() const
{
if (!isConnected())
return 0;
boost::system::error_code error;
const asio::ip::tcp::endpoint ip = m_socket.remote_endpoint(error);
if (!error)
return asio::detail::socket_ops::host_to_network_long(ip.address().to_v4().to_ulong());
return 0;
}

93
net/connection.h Normal file
View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2013 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef __CONNECTION_H
#define __CONNECTION_H
#ifdef _WIN32
#ifdef __STRICT_ANSI__
#undef __STRICT_ANSI__
#endif
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#endif
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <util/auxiliar.h>
#include <mutex>
#include <list>
#include "outputmessage.h"
namespace asio = boost::asio;
class Connection
{
typedef std::function<void(const uint8_t *, size_t)> ReadCallback;
typedef std::function<void()> ConnectCallback;
typedef std::function<void(const std::string &)> ErrorCallback;
public:
Connection();
~Connection();
static void poll();
void connect(const std::string &host, const std::string &port, const ConnectCallback &cb);
void close(bool warn = true); /// Pass false in ErrorCallback otherwise possible infinite recursion
bool isConnected() const { return m_socket.is_open(); }
inline void write(const OutputMessage &o) { write(o.data(), o.size()); }
inline void write(const std::string &str) { return write((const uint8_t *)str.c_str(), str.length()); }
void write(const uint8_t *data, size_t bytes);
void read_partial(size_t bytes, const ReadCallback &rc);
void read(size_t bytes, const ReadCallback &rc);
std::string getIPString() const { return ip2str(getIP()); }
uint32_t getIP() const;
void setErrorCallback(const ErrorCallback &ec) { m_eh = ec; }
protected:
void internalWrite(const boost::system::error_code &);
void handleRead(const boost::system::error_code &, size_t);
void handleWrite(const boost::system::error_code &, size_t, std::shared_ptr<asio::streambuf>);
void handleConnect(const boost::system::error_code &);
void handleResolve(const boost::system::error_code &, asio::ip::basic_resolver<asio::ip::tcp>::iterator);
void handleError(const boost::system::error_code &);
private:
asio::deadline_timer m_delayedWriteTimer;
asio::ip::tcp::resolver m_resolver;
asio::ip::tcp::socket m_socket;
ReadCallback m_rc;
ConnectCallback m_cb;
ErrorCallback m_eh;
std::shared_ptr<asio::streambuf> m_outputStream;
asio::streambuf m_inputStream;
};
extern asio::io_service g_service;
#endif

37
net/decl.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2013-2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef __NET_DECL_H
#define __NET_DECL_H
#include <util/serializer.h>
#include <util/databuffer.h>
#include <string>
#define DATA_SIZE 10240
enum class ByteOrder {
LittleEndian,
BigEndian
};
#endif

103
net/inputmessage.cpp Normal file
View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) 2013, 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "inputmessage.h"
#include <util/serializer.h>
#include <memory>
#include <string.h>
InputMessage::InputMessage(uint8_t *data, size_t size, ByteOrder order) :
m_data(data),
m_size(size),
m_pos(0),
m_order(order)
{
}
InputMessage::InputMessage(ByteOrder order) :
m_data(nullptr),
m_size(0),
m_pos(0),
m_order(order)
{
}
InputMessage::~InputMessage()
{
}
uint8_t *InputMessage::getBuffer(size_t size)
{
if (m_pos + size > m_size)
return nullptr;
uint8_t *buffer = new uint8_t[size];
memcpy(buffer, &m_data[m_pos], size);
return buffer;
}
uint8_t *InputMessage::getBuffer(void)
{
return &m_data[m_pos];
}
uint8_t InputMessage::getByte()
{
return m_data[m_pos++];
}
uint16_t InputMessage::getU16()
{
uint16_t tmp = (m_order == ByteOrder::BigEndian ? readBE16(&m_data[m_pos]) : readLE16(&m_data[m_pos]));
m_pos += 2;
return tmp;
}
uint32_t InputMessage::getU32()
{
uint32_t tmp = (m_order == ByteOrder::BigEndian ? readBE32(&m_data[m_pos]) : readLE32(&m_data[m_pos]));
m_pos += 4;
return tmp;
}
uint64_t InputMessage::getU64()
{
uint64_t tmp = (m_order == ByteOrder::BigEndian ? readBE64(&m_data[m_pos]) : readLE64(&m_data[m_pos]));
m_pos += 8;
return tmp;
}
std::string InputMessage::getString()
{
uint16_t len = getU16();
if (!len)
return std::string();
if (m_pos + len > m_size)
return std::string();
std::string ret((char *)&m_data[m_pos], len);
m_pos += len;
return ret;
}

86
net/inputmessage.h Normal file
View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2013, 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef __INPUTMESSAGE_H
#define __INPUTMESSAGE_H
#include "decl.h"
class InputMessage
{
public:
InputMessage(uint8_t *data, size_t size, ByteOrder order);
InputMessage(ByteOrder order);
~InputMessage();
void setData(uint8_t *d) { m_data = d; }
size_t getSize() { return m_size; }
void setSize(size_t size) { m_size = size; }
void setByteOrder(ByteOrder order) { m_order = order; }
uint8_t *getBuffer(size_t size);
uint8_t *getBuffer(void);
uint8_t getByte();
uint16_t getU16();
uint32_t getU32();
uint64_t getU64();
std::string getString();
inline InputMessage &operator=(uint8_t *data)
{
m_data = data;
return *this;
}
inline InputMessage &operator>>(uint8_t &b)
{
b = getByte();
return *this;
}
inline InputMessage &operator>>(uint16_t &u)
{
u = getU16();
return *this;
}
inline InputMessage &operator>>(uint32_t &u)
{
u = getU32();
return *this;
}
inline InputMessage &operator>>(uint64_t &u)
{
u = getU64();
return *this;
}
inline InputMessage &operator>>(std::string &s)
{
s = getString();
return *this;
}
private:
uint8_t *m_data;
size_t m_size;
uint32_t m_pos;
ByteOrder m_order;
};
#endif

80
net/outputmessage.cpp Normal file
View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2013, 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "outputmessage.h"
#include <string.h>
#include <util/serializer.h>
OutputMessage::OutputMessage(ByteOrder order, size_t fixedSize)
: m_pos(0),
m_order(order)
{
if (fixedSize != 0)
m_buffer.reserve(fixedSize);
}
OutputMessage::~OutputMessage()
{
}
void OutputMessage::addByte(uint8_t byte)
{
m_buffer[m_pos++] = byte;
}
void OutputMessage::addU16(uint16_t val)
{
if (m_order == ByteOrder::BigEndian)
writeBE16(&m_buffer[m_pos], val);
else
writeLE16(&m_buffer[m_pos], val);
m_pos += 2;
}
void OutputMessage::addU32(uint32_t val)
{
if (m_order == ByteOrder::BigEndian)
writeBE32(&m_buffer[m_pos], val);
else
writeLE32(&m_buffer[m_pos], val);
m_pos += 4;
}
void OutputMessage::addU64(uint64_t val)
{
if (m_order == ByteOrder::BigEndian)
writeBE64(&m_buffer[m_pos], val);
else
writeLE64(&m_buffer[m_pos], val);
m_pos += 8;
}
void OutputMessage::addString(const std::string &str)
{
uint16_t len = str.length();
addU16(len);
memcpy((char *)&m_buffer[m_pos], str.c_str(), len);
m_pos += len;
}

74
net/outputmessage.h Normal file
View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2013-2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef __OUTPUTMESSAGE_H
#define __OUTPUTMESSAGE_H
#include "decl.h"
class OutputMessage
{
public:
OutputMessage(ByteOrder order, size_t fixedSize = 0);
~OutputMessage();
void addByte(uint8_t byte);
void addU16(uint16_t val);
void addU32(uint32_t val);
void addU64(uint64_t val);
void addString(const std::string& str);
const uint8_t *data() const { return &m_buffer[0]; }
size_t size() const { return m_pos; }
inline OutputMessage &operator<<(const uint8_t &b)
{
addByte(b);
return *this;
}
inline OutputMessage &operator<<(const uint16_t &u)
{
addU16(u);
return *this;
}
inline OutputMessage &operator<<(const uint32_t &u)
{
addU32(u);
return *this;
}
inline OutputMessage &operator<<(const uint64_t &u)
{
addU64(u);
return *this;
}
inline OutputMessage &operator<<(const std::string &s)
{
addString(s);
return *this;
}
private:
DataBuffer<uint8_t> m_buffer;
size_t m_pos;
ByteOrder m_order;
};
#endif

39
tests/bencode.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "bencode/bencode.h"
#include <iostream>
#include <stdio.h>
int main()
{
Dictionary dict;
dict["announce"] = (const char *)"http://bttracker.debian.org:6969/announce";
dict["comment"] = (const char *)"\"Debian CD from cdimage.debian.org\"";
dict["creation date"] = 1391870037;
std::vector<boost::any> seeds;
seeds.push_back((const char *)"http://cdimage.debian.org/cdimage/release/7.4.0/iso-cd/debian-7.4.0-amd64-netinst.iso");
seeds.push_back((const char *)"http://cdimage.debian.org/cdimage/archive/7.4.0/iso-cd/debian-7.4.0-amd64-netinst.iso");
dict["httpseeds"] = seeds;
Dictionary info;
info["length"] = 232783872;
info["name"] = (const char *)"debian-7.4.0-amd64-netinst.iso";
info["piece length"] = 262144;
info["pieces"] = (const char *)"";
dict["info"] = info;
Bencode bencode;
bencode.encode(dict);
size_t size = 0;
const uint8_t *buffer = (const uint8_t *)bencode.buffer(0, size);
uint8_t expected[] = "d8:announce41:http://bttracker.debian.org:6969/announce7:comment35:\"Debian CD from cdimage.debian.org\"13:creation datei1391870037e9:httpseedsl85:http://cdimage.debian.org/cdimage/release/7.4.0/iso-cd/debian-7.4.0-amd64-netinst.iso85:http://cdimage.debian.org/cdimage/archive/7.4.0/iso-cd/debian-7.4.0-amd64-netinst.isoe4:infod6:lengthi232783872e4:name30:debian-7.4.0-amd64-netinst.iso12:piece lengthi262144e6:pieces0:ee\0";
if (memcmp(buffer, expected, size) == 0)
printf("yep\n");
else
printf("nope\n");
printf("%*s\n", size, (const char *)buffer);
printf("%s\n", expected);
return 0;
}

51
tests/bitfield.cpp Normal file
View File

@@ -0,0 +1,51 @@
#include <stdio.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <iostream>
#include <vector>
int main()
{
uint8_t payload[] = {
80, /* 0101 0000 => 2 pieces (indices 1 and 3) */
120, /* 0111 1000 => 4 pieces (indices 9, 10, 11, and 12) */
160, /* 1010 0000 => 2 pieces (indices 16 and 18) */
255, /* 1111 1111 => 8 pieces (indices 24, 25, 26, 27, 28, 29, 30, and 31 */
7, /* 0000 0111 => 3 pieces (indices 37, 38 and 39 */
15, /* 0000 1111 => 4 pieces (indices 45, 46, 47 and 48 */
};
size_t payloadSize = sizeof(payload);
std::vector<size_t> pieces;
for (size_t i = 0, index = 0; i < payloadSize; ++i) {
uint8_t b = payload[i];
if (b == 0)
continue;
uint32_t leading = CHAR_BIT - (sizeof(uint32_t) * CHAR_BIT - __builtin_clz(b));
uint32_t trailing = __builtin_ctz(b);
// skip leading zero bits first, we skip trailing zero bits later
index += leading;
// push this piece, we know it's there
pieces.push_back(index++);
b >>= trailing + leading;
for (; b != 0; b >>= 1, ++index)
if (b & 1) {
printf("%d: %d %d\n", i, b, index);
pieces.push_back(index);
}
// skip trailing
index += trailing;
}
printf("total pieces found: %zd\n", pieces.size());
for (size_t p : pieces)
printf("%zd\n", p);
}

34
tests/endian.cpp Normal file
View File

@@ -0,0 +1,34 @@
#include "util/serializer.h"
#include <stdio.h>
#include <assert.h>
#include <sstream>
int main() {
uint8_t data[4] = { 0, 0, 0, 14 };
printf("big: %ld little: %ld\n", readBE32(data), readLE32(data));
printf("big: %hd little: %hd\n\n", readBE16(data), readLE16(data));
assert(readBE32(data) == 14 && readBE16(data) == 0);
uint8_t data0[4] = { 14, 0, 0, 0 };
printf("little: %ld big: %ld\n", readLE32(data0), readBE32(data0));
printf("little: %hd big: %hd\n\n", readLE16(data0), readBE16(data0));
assert(readLE16(data0) == 14);
uint32_t i = 0;
i |= (uint32_t)data0[3] << 24;
i |= (uint32_t)data0[2] << 16;
i |= (uint32_t)data0[1] << 8;
i |= (uint32_t)data0[0];
printf("little: %d\n", i);
i = 0;
i |= (uint32_t)data[3];
i |= (uint32_t)data[2] << 8;
i |= (uint32_t)data[1] << 16;
i |= (uint32_t)data[0] << 24;
printf("big: %d\n\n", i);
return 0;
}

179
util/auxiliar.cpp Normal file
View File

@@ -0,0 +1,179 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "auxiliar.h"
#include <string.h>
#include <memory>
#include <iomanip>
#include <iterator>
#include <algorithm>
#include <sstream>
#include <cmath>
#include <iostream>
#include <stdexcept>
UrlData parseUrl(const std::string &str)
{
/// TODO: Regex could be quicker?
std::string protocol, host, port;
const std::string protocol_end("://");
auto protocolIter = std::search(str.begin(), str.end(), protocol_end.begin(), protocol_end.end());
if (protocolIter == str.end()) {
std::cerr << str << ": unable to find start of protocol" << std::endl;
return std::make_tuple("", "", "");
}
protocol.reserve(std::distance(str.begin(), protocolIter)); // reserve for "http"/"https" etc.
std::transform(
str.begin(), protocolIter,
std::back_inserter(protocol), std::ptr_fun<int, int>(tolower)
);
std::advance(protocolIter, protocol_end.length()); // eat "://"
auto hostIter = std::find(protocolIter, str.end(), ':'); // could be protocol://host:port/
if (hostIter == str.end())
hostIter = std::find(protocolIter, str.end(), '/'); // Nope, protocol://host/?query
host.reserve(std::distance(protocolIter, hostIter));
std::transform(
protocolIter, hostIter,
std::back_inserter(host), std::ptr_fun<int, int>(tolower)
);
auto portIter = std::find(hostIter, str.end(), ':');
if (portIter == str.end()) // Port optional
return std::make_tuple(protocol, host, protocol); // No port, it's according to the protocol then
auto queryBegin = std::find(portIter, str.end(), '/'); // Can we find a "/" of where the query would begin?
if (queryBegin != str.end())
port.assign(portIter + 1, queryBegin);
else
port.assign(portIter + 1, str.end());
return std::make_tuple(protocol, host, port);
}
/**
* bytesToHumanReadable() from:
* http://stackoverflow.com/a/3758880/1551592 (Java version)
*/
std::string bytesToHumanReadable(uint32_t bytes, bool si)
{
uint32_t u = si ? 1000 : 1024;
if (bytes < u)
return bytes + " B";
size_t exp = static_cast<size_t>(std::log(bytes) / std::log(u));
const char *e = si ? "kMGTPE" : "KMGTPE";
std::ostringstream os;
os << static_cast<double>(bytes / std::pow(u, exp)) << " ";
os << e[exp - 1] << (si ? "" : "i") << "B";
return os.str();
}
std::string ip2str(uint32_t ip)
{
char buffer[17];
sprintf(buffer, "%u.%u.%u.%u", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, ip >> 24);
return buffer;
}
std::string getcwd()
{
const size_t chunkSize = 255;
const int maxChunks = 10240; // 2550 KiBs of current path are more than enough
char stackBuffer[chunkSize]; // Stack buffer for the "normal" case
if (getcwd(stackBuffer, sizeof(stackBuffer)))
return stackBuffer;
if (errno != ERANGE)
throw std::runtime_error("Cannot determine the current path.");
for (int chunks = 2; chunks < maxChunks; chunks++) {
std::unique_ptr<char> cwd(new char[chunkSize * chunks]);
if (getcwd(cwd.get(), chunkSize * chunks))
return cwd.get();
if (errno != ERANGE)
throw std::runtime_error("Cannot determine the current path.");
}
throw std::runtime_error("Cannot determine the current path; path too long");
}
/** Following function shamelessly copied from:
* http://stackoverflow.com/a/7214192/502230
* And converted to take just the request instead of the
* entire URL, e.g.:
* std::string str = "?what=0xbaadf00d&something=otherthing";
* std::string enc = urlencode(str);
*/
std::string urlencode(const std::string &value)
{
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex << std::uppercase;
for (std::string::const_iterator i = value.begin(); i != value.end(); ++i) {
std::string::value_type c = *i;
// Keep alphanumeric and other accepted characters intact
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
escaped << c;
else // Any other characters are percent-encoded
escaped << '%' << std::setw(2) << static_cast<int>((unsigned char)c);
}
return escaped.str();
}
bool validatePath(const std::string& base, const std::string& path)
{
#if 0
char absolutePath[PATH_MAX + 1];
#ifdef _WIN32
if (!_fullpath(absolutePath, path.c_str(), PATH_MAX))
#else
if (!realpath(path.c_str(), absolutePath))
#endif
return false;
if (strlen(absolutePath) < base.length())
throw std::runtime_error("path is too short");
else if (base != std::string(absolutePath).substr(0, base.length()))
throw std::runtime_error("path mismatch");
#endif
return true;
}
bool nodeExists(const std::string& node)
{
#ifdef _WIN32
return PathFileExists(node.c_str());
#else
struct stat st;
return stat(node.c_str(), &st) == 0;
#endif
}

91
util/auxiliar.h Normal file
View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2014 Ahmed Samy <f.fallen45@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef __AUXILIAR_H
#define __AUXILIAR_H
#ifdef __STRICT_ANSI__
#undef __STRICT_ANSI__
#endif
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#ifdef _WIN32
#define PATH_SEP "\\"
#define MKDIR(name) mkdir((name).c_str())
#include <io.h>
#include <windows.h>
#include <Shlwapi.h>
#else
#define PATH_SEP "/"
#define MKDIR(name) mkdir((name).c_str(), 0700)
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#define CHDIR(name) chdir((name).c_str())
#include <string>
#include <fstream>
#include <tuple>
union Mix {
int sdat;
char cdat[4];
};
static constexpr Mix mix { 0x1 };
constexpr bool isLittleEndian()
{
return mix.cdat[0] == 1;
}
typedef std::tuple<std::string, std::string, std::string> UrlData;
#define URL_PROTOCOL(u) std::get<0>((u))
#define URL_HOSTNAME(u) std::get<1>((u))
#define URL_SERVNAME(u) std::get<2>((u))
UrlData parseUrl(const std::string &str);
std::string bytesToHumanReadable(uint32_t bytes, bool si);
std::string ip2str(uint32_t ip);
std::string getcwd();
std::string urlencode(const std::string& url);
bool validatePath(const std::string& base, const std::string& path);
bool nodeExists(const std::string& node);
static inline bool test_bit(uint32_t bits, uint32_t bit)
{
return (bits & bit) == bit;
}
#ifdef __MINGW32__
#include <boost/lexical_cast.hpp>
namespace std {
template <typename T>
string to_string(T value)
{
return boost::lexical_cast<string>(value);
}
}
#endif
#endif

79
util/databuffer.h Normal file
View File

@@ -0,0 +1,79 @@
#ifndef __DATABUFFER_H
#define __DATABUFFER_H
#include <stdint.h>
#include <stddef.h>
template<class T>
class DataBuffer
{
public:
DataBuffer(size_t res = 64) :
m_size(0),
m_capacity(res),
m_buffer(new T[m_capacity])
{
m_allocated = true;
}
~DataBuffer()
{
if (m_buffer && m_allocated)
delete[] m_buffer;
}
inline size_t size() const { return m_size; }
inline T *data() const { return m_buffer; }
inline const T &operator[](size_t i) const { return m_buffer[i]; }
inline T &operator[](size_t i) { return m_buffer[i]; }
DataBuffer<T> &operator=(T *data) { if (m_allocated) delete []m_buffer; m_buffer = data; m_allocated = false; return *this; }
inline void setSize(size_t size) { m_size = size; }
inline void reserve(size_t n)
{
if (n > m_capacity) {
T *buffer = new T[n];
for (size_t i=0; i<m_size; ++i)
buffer[i] = m_buffer[i];
delete[] m_buffer;
m_buffer = buffer;
m_capacity = n;
}
}
inline void grow(size_t n)
{
if (n <= m_size)
return;
if (n > m_capacity) {
size_t newcapacity = m_capacity;
do
newcapacity *= 2;
while (newcapacity < n);
reserve(newcapacity);
}
m_size = n;
}
inline void add(const T& v)
{
grow(m_size + 1);
m_buffer[m_size-1] = v;
}
inline DataBuffer &operator<<(const T &t)
{
add(t);
return *this;
}
private:
bool m_allocated;
size_t m_size;
size_t m_capacity;
T *m_buffer;
};
#endif

77
util/serializer.h Normal file
View File

@@ -0,0 +1,77 @@
#ifndef __SERIALIZER_H
#define __SERIALIZER_H
#include <stdint.h>
#include <assert.h>
///////////////// Little Endian
inline uint16_t readLE16(const uint8_t *addr)
{
return (uint16_t)addr[1] << 8 | addr[0];
}
inline uint32_t readLE32(const uint8_t *addr)
{
return (uint32_t)readLE16(addr + 2) << 16 | readLE16(addr);
}
inline uint64_t readLE64(const uint8_t *addr)
{
return (uint64_t)readLE32(addr + 4) << 32 | readLE32(addr);
}
inline void writeLE16(uint8_t *addr, uint16_t value)
{
addr[1] = value >> 8;
addr[0] = (uint8_t)value;
}
inline void writeLE32(uint8_t *addr, uint32_t value)
{
writeLE16(addr + 2, value >> 16);
writeLE16(addr, (uint16_t)value);
}
inline void writeLE64(uint8_t *addr, uint64_t value)
{
writeLE32(addr + 4, value >> 32);
writeLE32(addr, (uint32_t)value);
}
///////////////// Big Endian
inline uint16_t readBE16(const uint8_t *addr)
{
return (uint16_t)addr[1] | (uint16_t)(addr[0]) << 8;
}
inline uint32_t readBE32(const uint8_t *addr)
{
return (uint32_t)readBE16(addr + 2) | readBE16(addr) << 16;
}
inline uint64_t readBE64(const uint8_t *addr)
{
return (uint64_t)readBE32(addr + 4) | (uint64_t)readBE32(addr) << 32;
}
inline void writeBE16(uint8_t *addr, uint16_t value)
{
addr[0] = (uint8_t)value >> 8;
addr[1] = (uint8_t)value;
}
inline void writeBE32(uint8_t *addr, uint32_t value)
{
addr[0] = (uint8_t)(value >> 24);
addr[1] = (uint8_t)(value >> 16);
addr[2] = (uint8_t)(value >> 8);
addr[3] = (uint8_t)(value);
}
inline void writeBE64(uint8_t *addr, uint64_t value)
{
writeBE32(addr, value >> 32);
writeBE32(addr + 4, (uint32_t)value);
}
#endif