mirror of
https://github.com/asamy/ctorrent
synced 2025-10-05 23:52:41 +02:00
Initial commit
This commit is contained in:
48
Makefile
Normal file
48
Makefile
Normal 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
22
README.md
Normal 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
95
bencode/bencode.h
Normal 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
168
bencode/decoder.cpp
Normal 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
103
bencode/encoder.cpp
Normal 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
355
ctorrent/peer.cpp
Normal 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
113
ctorrent/peer.h
Normal 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
662
ctorrent/torrent.cpp
Normal 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(¶ms);
|
||||
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
131
ctorrent/torrent.h
Normal 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
175
main.cpp
Normal 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
203
net/connection.cpp
Normal 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
93
net/connection.h
Normal 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
37
net/decl.h
Normal 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
103
net/inputmessage.cpp
Normal 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
86
net/inputmessage.h
Normal 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
80
net/outputmessage.cpp
Normal 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
74
net/outputmessage.h
Normal 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
39
tests/bencode.cpp
Normal 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
51
tests/bitfield.cpp
Normal 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
34
tests/endian.cpp
Normal 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
179
util/auxiliar.cpp
Normal 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
91
util/auxiliar.h
Normal 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
79
util/databuffer.h
Normal 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
77
util/serializer.h
Normal 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
|
||||
|
Reference in New Issue
Block a user