Refactored (#31)

* Build system changed to CMake
* Converted tabs to spaces
* Removed all usages of boost::log
* Replaced boost::thread with std::thread
* Update copyright year
* Added a tiny bit of unit tests
* Added a simple message queue
* Added basic independent logging
* Added CONTRIBUTING
This commit is contained in:
Naim A 2017-09-27 04:33:21 +03:00 committed by Naim A
parent 7193f945a9
commit f34fcdbd04
No known key found for this signature in database
GPG key ID: FD7948915D9EF8B9
29 changed files with 3243 additions and 3091 deletions

44
.github/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,44 @@
# Contributing
Thank you for looking into [UDPT](https://github.com/naim94a/udpt)!
This document lists various ways to contribute to UDPT. If you can, please do.
## Bug Reports
Bug reports are always welcome, they help us maintain a quality product.
Before submitting a bug report, please first check that the issue does not already exist and that the effected version
is the most recent one.
* If a similar (or same) issue already exists, please upvote, comment, elaborate on the existing issue.
* If you found a new issue, please attach the used configuration and how to cause the bug to appear.
* Core dumps are always welcome
Bugs should be reported at [UDPT's issue tracker](https://github.com/naim94a/udpt) on GitHub.
## Suggesting new features
New features are welcome, it doesn't mean they will enter immediately.
Suggestions should be filed as issues along with bug reports, just add the "enhancement" label to the created issue.
## Donations
Please show us your appreciation by donating BitCoin at bitcoin:1KMeZvcgnmWdHitu51yEFWBNcSTXL1eBk3.
![bitcoin:1KMeZvcgnmWdHitu51yEFWBNcSTXL1eBk3](bitcoin-qr.png)
## Pull requests
Before submitting a pull request, please check that the changes don't effect other platforms or have any unwanted
side-effects.
Pull Requests should address open issue(s), where the project owner and contributors can discuss how the issue should be
addressed.
Pull requests that add new features should be first mentioned in issues in order to verify that the project owner will
allow such changes to the project.
Code in pull requests should meet the following requirements:
* Class names should be CamelCase, and the first letter should be capital.
* Functions should be camelCase, and the first letter should be lower-case.
* Class members should be prefixed with `m_`, for example: `bool m_isRunning`
* All variables, classes, functions and class members should have an indicative name.
* Adding external libraries is allowed, but frowned upon

BIN
.github/bitcoin-qr.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -1,15 +1,24 @@
project(udpt) project(udpt)
cmake_minimum_required(VERSION 3.6) cmake_minimum_required(VERSION 3.2)
enable_testing()
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_definitions(-DBOOST_LOG_DYN_LINK)
file(GLOB src_files "src/*.c" file(GLOB src_files "src/*.c"
"src/*.cpp" "src/*.cpp"
"src/db/*.cpp" "src/db/*.cpp"
"src/http/*.cpp") "src/http/*.cpp")
LIST(APPEND LIBS "pthread" "sqlite3" "boost_program_options" "boost_system")
add_executable(udpt ${src_files}) add_executable(udpt ${src_files})
target_link_libraries(udpt pthread sqlite3 boost_log boost_program_options boost_thread boost_system) target_link_libraries(udpt ${LIBS})
add_executable(udpt_tests tests/main.cpp ${src_files})
target_compile_definitions(udpt_tests PRIVATE TEST=1)
target_link_libraries(udpt_tests gtest ${LIBS})
add_test(NAME udpt_tests COMMAND udpt_tests)

64
src/MessageQueue.hpp Normal file
View file

@ -0,0 +1,64 @@
/*
* Copyright © 2012-2017 Naim A.
*
* This file is part of UDPT.
*
* UDPT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* UDPT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with UDPT. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <queue>
#include <mutex>
namespace UDPT
{
namespace Utils {
template<class T>
class MessageQueue {
public:
MessageQueue() {}
virtual ~MessageQueue() {}
bool IsEmpty() const {
return m_queue.empty();
}
T Pop() {
m_queueMutex.lock();
T val = m_queue.front();
m_queue.pop();
m_queueMutex.unlock();
return val;
}
void Push(T obj) {
m_queueMutex.lock();
m_queue.push(obj);
m_queueMutex.unlock();
}
size_t Count() const {
return m_queue.size();
}
private:
std::queue<T> m_queue;
std::mutex m_queueMutex;
};
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -21,100 +21,100 @@
namespace UDPT namespace UDPT
{ {
namespace Data namespace Data
{ {
DatabaseDriver::DatabaseDriver(const boost::program_options::variables_map& conf, bool isDynamic) : m_conf(conf) DatabaseDriver::DatabaseDriver(const boost::program_options::variables_map& conf, bool isDynamic) : m_conf(conf)
{ {
this->is_dynamic = isDynamic; this->is_dynamic = isDynamic;
} }
bool DatabaseDriver::addTorrent(uint8_t hash [20]) bool DatabaseDriver::addTorrent(uint8_t hash [20])
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
bool DatabaseDriver::removeTorrent(uint8_t hash[20]) bool DatabaseDriver::removeTorrent(uint8_t hash[20])
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
bool DatabaseDriver::isDynamic() bool DatabaseDriver::isDynamic()
{ {
return this->is_dynamic; return this->is_dynamic;
} }
bool DatabaseDriver::genConnectionId(uint64_t *cid, uint32_t ip, uint16_t port) bool DatabaseDriver::genConnectionId(uint64_t *cid, uint32_t ip, uint16_t port)
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
bool DatabaseDriver::verifyConnectionId(uint64_t cid, uint32_t ip, uint16_t port) bool DatabaseDriver::verifyConnectionId(uint64_t cid, uint32_t ip, uint16_t port)
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
bool DatabaseDriver::updatePeer(uint8_t peer_id [20], uint8_t info_hash [20], bool DatabaseDriver::updatePeer(uint8_t peer_id [20], uint8_t info_hash [20],
uint32_t ip, uint16_t port, uint32_t ip, uint16_t port,
int64_t downloaded, int64_t left, int64_t uploaded, int64_t downloaded, int64_t left, int64_t uploaded,
enum TrackerEvents event) enum TrackerEvents event)
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
bool DatabaseDriver::removePeer (uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port) bool DatabaseDriver::removePeer (uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port)
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
bool DatabaseDriver::getTorrentInfo (TorrentEntry *e) bool DatabaseDriver::getTorrentInfo (TorrentEntry *e)
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
bool DatabaseDriver::getPeers (uint8_t info_hash [20], int *max_count, PeerEntry *pe) bool DatabaseDriver::getPeers (uint8_t info_hash [20], int *max_count, PeerEntry *pe)
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
void DatabaseDriver::cleanup() void DatabaseDriver::cleanup()
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
bool DatabaseDriver::isTorrentAllowed(uint8_t info_hash[20]) bool DatabaseDriver::isTorrentAllowed(uint8_t info_hash[20])
{ {
throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED);
} }
DatabaseDriver::~DatabaseDriver() DatabaseDriver::~DatabaseDriver()
{ {
} }
/*-- Exceptions --*/ /*-- Exceptions --*/
static const char *EMessages[] = { static const char *EMessages[] = {
"Unknown Error", "Unknown Error",
"Not Implemented", "Not Implemented",
"Failed to connect to database" "Failed to connect to database"
}; };
DatabaseException::DatabaseException() DatabaseException::DatabaseException()
{ {
this->errorNum = E_UNKNOWN; this->errorNum = E_UNKNOWN;
} }
DatabaseException::DatabaseException(enum EType e) DatabaseException::DatabaseException(enum EType e)
{ {
this->errorNum = e; this->errorNum = e;
} }
enum DatabaseException::EType DatabaseException::getErrorType() enum DatabaseException::EType DatabaseException::getErrorType()
{ {
return this->errorNum; return this->errorNum;
} }
const char* DatabaseException::getErrorMessage() const char* DatabaseException::getErrorMessage()
{ {
return EMessages[this->errorNum]; return EMessages[this->errorNum];
} }
}; };
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -21,154 +21,152 @@
#define DATABASE_HPP_ #define DATABASE_HPP_
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
namespace UDPT namespace UDPT
{ {
namespace Data namespace Data
{ {
class DatabaseException class DatabaseException
{ {
public: public:
enum EType { enum EType {
E_UNKNOWN = 0, // Unknown error E_UNKNOWN = 0, // Unknown error
E_NOT_IMPLEMENTED = 1, // not implemented E_NOT_IMPLEMENTED = 1, // not implemented
E_CONNECTION_FAILURE = 2 E_CONNECTION_FAILURE = 2
}; };
DatabaseException(); DatabaseException();
DatabaseException(EType); DatabaseException(EType);
EType getErrorType(); EType getErrorType();
const char* getErrorMessage(); const char* getErrorMessage();
private: private:
EType errorNum; EType errorNum;
}; };
class DatabaseDriver class DatabaseDriver
{ {
public: public:
typedef struct { typedef struct {
uint8_t *info_hash; uint8_t *info_hash;
int32_t seeders; int32_t seeders;
int32_t leechers; int32_t leechers;
int32_t completed; int32_t completed;
} TorrentEntry; } TorrentEntry;
typedef struct { typedef struct {
uint32_t ip; uint32_t ip;
uint16_t port; uint16_t port;
} PeerEntry; } PeerEntry;
enum TrackerEvents { enum TrackerEvents {
EVENT_UNSPEC = 0, EVENT_UNSPEC = 0,
EVENT_COMPLETE = 1, EVENT_COMPLETE = 1,
EVENT_START = 2, EVENT_START = 2,
EVENT_STOP = 3 EVENT_STOP = 3
}; };
/** /**
* Opens the DB's connection * Opens the DB's connection
* @param dClass Settings class ('database' class). * @param dClass Settings class ('database' class).
*/ */
DatabaseDriver(const boost::program_options::variables_map& conf, bool isDynamic = false); DatabaseDriver(const boost::program_options::variables_map& conf, bool isDynamic = false);
/** /**
* Adds a torrent to the Database. automatically done if in dynamic mode. * Adds a torrent to the Database. automatically done if in dynamic mode.
* @param hash The info_hash of the torrent. * @param hash The info_hash of the torrent.
* @return true on success. false on failure. * @return true on success. false on failure.
*/ */
virtual bool addTorrent(uint8_t hash[20]); virtual bool addTorrent(uint8_t hash[20]);
/** /**
* Removes a torrent from the database. should be used only for non-dynamic trackers or by cleanup. * Removes a torrent from the database. should be used only for non-dynamic trackers or by cleanup.
* @param hash The info_hash to drop. * @param hash The info_hash to drop.
* @return true if torrent's database was dropped or no longer exists. otherwise false (shouldn't happen - critical) * @return true if torrent's database was dropped or no longer exists. otherwise false (shouldn't happen - critical)
*/ */
virtual bool removeTorrent(uint8_t hash[20]); virtual bool removeTorrent(uint8_t hash[20]);
/** /**
* Checks if the Database is acting as a dynamic tracker DB. * Checks if the Database is acting as a dynamic tracker DB.
* @return true if dynamic. otherwise false. * @return true if dynamic. otherwise false.
*/ */
bool isDynamic(); bool isDynamic();
/** /**
* Checks if the torrent can be used in the tracker. * Checks if the torrent can be used in the tracker.
* @param info_hash The torrent's info_hash. * @param info_hash The torrent's info_hash.
* @return true if allowed. otherwise false. * @return true if allowed. otherwise false.
*/ */
virtual bool isTorrentAllowed(uint8_t info_hash [20]); virtual bool isTorrentAllowed(uint8_t info_hash [20]);
/** /**
* Generate a Connection ID for the peer. * Generate a Connection ID for the peer.
* @param connectionId (Output) the generated connection ID. * @param connectionId (Output) the generated connection ID.
* @param ip The peer's IP (requesting peer. not remote) * @param ip The peer's IP (requesting peer. not remote)
* @param port The peer's IP (remote port if tracker accepts) * @param port The peer's IP (remote port if tracker accepts)
* @return * @return
*/ */
virtual bool genConnectionId(uint64_t *connectionId, uint32_t ip, uint16_t port); virtual bool genConnectionId(uint64_t *connectionId, uint32_t ip, uint16_t port);
virtual bool verifyConnectionId(uint64_t connectionId, uint32_t ip, uint16_t port); virtual bool verifyConnectionId(uint64_t connectionId, uint32_t ip, uint16_t port);
/** /**
* Updates/Adds a peer to/in the database. * Updates/Adds a peer to/in the database.
* @param peer_id the peer's peer_id * @param peer_id the peer's peer_id
* @param info_hash the torrent info_hash * @param info_hash the torrent info_hash
* @param ip IP of peer (remote ip if tracker accepts) * @param ip IP of peer (remote ip if tracker accepts)
* @param port TCP port of peer (remote port if tracker accepts) * @param port TCP port of peer (remote port if tracker accepts)
* @param downloaded total Bytes downloaded * @param downloaded total Bytes downloaded
* @param left total bytes left * @param left total bytes left
* @param uploaded total bytes uploaded * @param uploaded total bytes uploaded
* @return true on success, false on failure. * @return true on success, false on failure.
*/ */
virtual bool updatePeer(uint8_t peer_id [20], uint8_t info_hash [20], virtual bool updatePeer(uint8_t peer_id [20], uint8_t info_hash [20],
uint32_t ip, uint16_t port, uint32_t ip, uint16_t port,
int64_t downloaded, int64_t left, int64_t uploaded, int64_t downloaded, int64_t left, int64_t uploaded,
enum TrackerEvents event); enum TrackerEvents event);
/** /**
* Remove a peer from a torrent (if stop action occurred, or if peer is inactive in cleanup) * Remove a peer from a torrent (if stop action occurred, or if peer is inactive in cleanup)
* @param peer_id The peer's peer_id * @param peer_id The peer's peer_id
* @param info_hash Torrent's info_hash * @param info_hash Torrent's info_hash
* @param ip The IP of the peer (remote IP if tracker accepts) * @param ip The IP of the peer (remote IP if tracker accepts)
* @param port The TCP port (remote port if tracker accepts) * @param port The TCP port (remote port if tracker accepts)
* @return true on success. false on failure (shouldn't happen - critical) * @return true on success. false on failure (shouldn't happen - critical)
*/ */
virtual bool removePeer(uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port); virtual bool removePeer(uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port);
/** /**
* Gets stats on a torrent * Gets stats on a torrent
* @param e TorrentEntry, only this info_hash has to be set * @param e TorrentEntry, only this info_hash has to be set
* @return true on success, false on failure. * @return true on success, false on failure.
*/ */
virtual bool getTorrentInfo(TorrentEntry *e); virtual bool getTorrentInfo(TorrentEntry *e);
/** /**
* Gets a list of peers from the database. * Gets a list of peers from the database.
* @param info_hash The torrent's info_hash * @param info_hash The torrent's info_hash
* @param max_count The maximum amount of peers to load from the database. The amount of loaded peers is returned through this variable. * @param max_count The maximum amount of peers to load from the database. The amount of loaded peers is returned through this variable.
* @param pe The list of peers. Must be pre-allocated to the size of max_count. * @param pe The list of peers. Must be pre-allocated to the size of max_count.
* @return true on success, otherwise false (shouldn't happen). * @return true on success, otherwise false (shouldn't happen).
*/ */
virtual bool getPeers(uint8_t info_hash [20], int *max_count, PeerEntry *pe); virtual bool getPeers(uint8_t info_hash [20], int *max_count, PeerEntry *pe);
/** /**
* Cleanup the database. * Cleanup the database.
* Other actions may be locked when using this depending on the driver. * Other actions may be locked when using this depending on the driver.
*/ */
virtual void cleanup(); virtual void cleanup();
/** /**
* Closes the connections, and releases all other resources. * Closes the connections, and releases all other resources.
*/ */
virtual ~DatabaseDriver(); virtual ~DatabaseDriver();
protected: protected:
const boost::program_options::variables_map& m_conf; const boost::program_options::variables_map& m_conf;
private: private:
bool is_dynamic; bool is_dynamic;
}; };
}; };
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -26,406 +26,410 @@
#include <cassert> #include <cassert>
#include <cstring> // memcpy #include <cstring> // memcpy
#include "../multiplatform.h" #include "../multiplatform.h"
#include "../logging.hpp"
using namespace std; using namespace std;
namespace UDPT namespace UDPT
{ {
namespace Data namespace Data
{ {
static const char hexadecimal[] = "0123456789abcdef"; static const char hexadecimal[] = "0123456789abcdef";
static char* _to_hex_str (const uint8_t *hash, char *data) static char* _to_hex_str (const uint8_t *hash, char *data)
{ {
int i; int i;
for (i = 0;i < 20;i++) for (i = 0;i < 20;i++)
{ {
data[i * 2] = hexadecimal[hash[i] / 16]; data[i * 2] = hexadecimal[hash[i] / 16];
data[i * 2 + 1] = hexadecimal[hash[i] % 16]; data[i * 2 + 1] = hexadecimal[hash[i] % 16];
} }
data[40] = '\0'; data[40] = '\0';
return data; return data;
} }
static uint8_t* _hash_to_bin (const char *hash, uint8_t *data) static uint8_t* _hash_to_bin (const char *hash, uint8_t *data)
{ {
for (int i = 0;i < 20;i++) for (int i = 0;i < 20;i++)
{ {
data [i] = 0; data [i] = 0;
char a = hash[i * 2]; char a = hash[i * 2];
char b = hash[i * 2 + 1]; char b = hash[i * 2 + 1];
assert ( (a >= 'a' && a <= 'f') || (a >= '0' && a <= '9') ); assert ( (a >= 'a' && a <= 'f') || (a >= '0' && a <= '9') );
assert ( (b >= 'a' && b <= 'f') || (b >= '0' && b <= '9') ); assert ( (b >= 'a' && b <= 'f') || (b >= '0' && b <= '9') );
data[i] = ( (a >= '0' && a <= 'f') ? (a - '0') : (a - 'f' + 10) ); data[i] = ( (a >= '0' && a <= 'f') ? (a - '0') : (a - 'f' + 10) );
data[i] <<= 4; data[i] <<= 4;
data[i] = ( (b >= '0' && b <= 'f') ? (b - '0') : (b - 'f' + 10) ); data[i] = ( (b >= '0' && b <= 'f') ? (b - '0') : (b - 'f' + 10) );
} }
return data; return data;
} }
SQLite3Driver::SQLite3Driver(const boost::program_options::variables_map& conf, bool isDyn) : DatabaseDriver(conf, isDyn), m_logger(boost::log::keywords::channel="SQLite3") SQLite3Driver::SQLite3Driver(const boost::program_options::variables_map& conf, bool isDyn) : DatabaseDriver(conf, isDyn)
{ {
int r; int r;
bool doSetup; bool doSetup;
fstream fCheck; fstream fCheck;
string filename = m_conf["db.param"].as<std::string>(); string filename = m_conf["db.param"].as<std::string>();
fCheck.open(filename.c_str(), ios::binary | ios::in); fCheck.open(filename.c_str(), ios::binary | ios::in);
if (fCheck.is_open()) if (fCheck.is_open())
{ {
doSetup = false; doSetup = false;
fCheck.close(); fCheck.close();
} }
else else
doSetup = true; doSetup = true;
r = sqlite3_open(filename.c_str(), &this->db); r = sqlite3_open(filename.c_str(), &this->db);
if (r != SQLITE_OK) if (r != SQLITE_OK)
{ {
sqlite3_close(this->db); LOG_FATAL("db-sqlite", "Failed to connect DB. sqlite returned " << r);
throw DatabaseException (DatabaseException::E_CONNECTION_FAILURE); sqlite3_close(this->db);
} throw DatabaseException (DatabaseException::E_CONNECTION_FAILURE);
}
if (doSetup)
this->doSetup(); if (doSetup)
} this->doSetup();
}
void SQLite3Driver::doSetup()
{ void SQLite3Driver::doSetup()
char *eMsg = NULL; {
// for quicker stats. char *eMsg = NULL;
sqlite3_exec(this->db, "CREATE TABLE stats (" LOG_INFO("db-sqlite", "Setting up database...");
"info_hash blob(20) UNIQUE," // for quicker stats.
"completed INTEGER DEFAULT 0," sqlite3_exec(this->db, "CREATE TABLE stats ("
"leechers INTEGER DEFAULT 0," "info_hash blob(20) UNIQUE,"
"seeders INTEGER DEFAULT 0," "completed INTEGER DEFAULT 0,"
"last_mod INTEGER DEFAULT 0" "leechers INTEGER DEFAULT 0,"
")", NULL, NULL, &eMsg); "seeders INTEGER DEFAULT 0,"
"last_mod INTEGER DEFAULT 0"
sqlite3_exec(this->db, "CREATE TABLE torrents (" ")", NULL, NULL, &eMsg);
"info_hash blob(20) UNIQUE,"
"created INTEGER" sqlite3_exec(this->db, "CREATE TABLE torrents ("
")", NULL, NULL, &eMsg); "info_hash blob(20) UNIQUE,"
} "created INTEGER"
")", NULL, NULL, &eMsg);
bool SQLite3Driver::getTorrentInfo(TorrentEntry *e) }
{
bool gotInfo = false; bool SQLite3Driver::getTorrentInfo(TorrentEntry *e)
{
const char sql[] = "SELECT seeders,leechers,completed FROM 'stats' WHERE info_hash=?"; bool gotInfo = false;
sqlite3_stmt *stmt;
const char sql[] = "SELECT seeders,leechers,completed FROM 'stats' WHERE info_hash=?";
e->seeders = 0; sqlite3_stmt *stmt;
e->leechers = 0;
e->completed = 0; e->seeders = 0;
e->leechers = 0;
e->completed = 0;
sqlite3_prepare (this->db, sql, -1, &stmt, NULL);
sqlite3_bind_blob (stmt, 1, (void*)e->info_hash, 20, NULL);
sqlite3_prepare (this->db, sql, -1, &stmt, NULL);
if (sqlite3_step(stmt) == SQLITE_ROW) sqlite3_bind_blob (stmt, 1, (void*)e->info_hash, 20, NULL);
{
e->seeders = sqlite3_column_int (stmt, 0); if (sqlite3_step(stmt) == SQLITE_ROW)
e->leechers = sqlite3_column_int (stmt, 1); {
e->completed = sqlite3_column_int (stmt, 2); e->seeders = sqlite3_column_int (stmt, 0);
e->leechers = sqlite3_column_int (stmt, 1);
gotInfo = true; e->completed = sqlite3_column_int (stmt, 2);
}
gotInfo = true;
sqlite3_finalize (stmt); }
return gotInfo; sqlite3_finalize (stmt);
}
return gotInfo;
bool SQLite3Driver::getPeers (uint8_t info_hash [20], int *max_count, PeerEntry *pe) }
{
string sql; bool SQLite3Driver::getPeers (uint8_t info_hash [20], int *max_count, PeerEntry *pe)
char hash [50]; {
sqlite3_stmt *stmt; string sql;
int r, i; char hash [50];
sqlite3_stmt *stmt;
to_hex_str(info_hash, hash); int r, i;
sql = "SELECT ip,port FROM 't"; to_hex_str(info_hash, hash);
sql += hash;
sql += "' LIMIT ?"; sql = "SELECT ip,port FROM 't";
sql += hash;
sqlite3_prepare(this->db, sql.c_str(), sql.length(), &stmt, NULL); sql += "' LIMIT ?";
sqlite3_bind_int(stmt, 1, *max_count);
sqlite3_prepare(this->db, sql.c_str(), sql.length(), &stmt, NULL);
i = 0; sqlite3_bind_int(stmt, 1, *max_count);
while (*max_count > i)
{ i = 0;
r = sqlite3_step(stmt); while (*max_count > i)
if (r == SQLITE_ROW) {
{ r = sqlite3_step(stmt);
const char *ip = (const char*)sqlite3_column_blob (stmt, 0); if (r == SQLITE_ROW)
const char *port = (const char*)sqlite3_column_blob (stmt, 1); {
const char *ip = (const char*)sqlite3_column_blob (stmt, 0);
memcpy(&pe[i].ip, ip, 4); const char *port = (const char*)sqlite3_column_blob (stmt, 1);
memcpy(&pe[i].port, port, 2);
memcpy(&pe[i].ip, ip, 4);
i++; memcpy(&pe[i].port, port, 2);
}
else i++;
{ }
break; else
} {
} break;
}
BOOST_LOG_SEV(m_logger, boost::log::trivial::debug) << "Retrieved " << i << " peers"; }
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
*max_count = i; *max_count = i;
return true; return true;
} }
bool SQLite3Driver::updatePeer(uint8_t peer_id[20], uint8_t info_hash[20], uint32_t ip, uint16_t port, int64_t downloaded, int64_t left, int64_t uploaded, enum TrackerEvents event) bool SQLite3Driver::updatePeer(uint8_t peer_id[20], uint8_t info_hash[20], uint32_t ip, uint16_t port, int64_t downloaded, int64_t left, int64_t uploaded, enum TrackerEvents event)
{ {
char xHash [50]; // we just need 40 + \0 = 41. char xHash [50]; // we just need 40 + \0 = 41.
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
string sql; string sql;
int r; int r;
char *hash = xHash; char *hash = xHash;
to_hex_str(info_hash, hash); to_hex_str(info_hash, hash);
addTorrent (info_hash); addTorrent (info_hash);
sql = "REPLACE INTO 't"; sql = "REPLACE INTO 't";
sql += hash; sql += hash;
sql += "' (peer_id,ip,port,uploaded,downloaded,left,last_seen) VALUES (?,?,?,?,?,?,?)"; sql += "' (peer_id,ip,port,uploaded,downloaded,left,last_seen) VALUES (?,?,?,?,?,?,?)";
sqlite3_prepare(this->db, sql.c_str(), sql.length(), &stmt, NULL); sqlite3_prepare(this->db, sql.c_str(), sql.length(), &stmt, NULL);
sqlite3_bind_blob(stmt, 1, (void*)peer_id, 20, NULL); sqlite3_bind_blob(stmt, 1, (void*)peer_id, 20, NULL);
sqlite3_bind_blob(stmt, 2, (void*)&ip, 4, NULL); sqlite3_bind_blob(stmt, 2, (void*)&ip, 4, NULL);
sqlite3_bind_blob(stmt, 3, (void*)&port, 2, NULL); sqlite3_bind_blob(stmt, 3, (void*)&port, 2, NULL);
sqlite3_bind_blob(stmt, 4, (void*)&uploaded, 8, NULL); sqlite3_bind_blob(stmt, 4, (void*)&uploaded, 8, NULL);
sqlite3_bind_blob(stmt, 5, (void*)&downloaded, 8, NULL); sqlite3_bind_blob(stmt, 5, (void*)&downloaded, 8, NULL);
sqlite3_bind_blob(stmt, 6, (void*)&left, 8, NULL); sqlite3_bind_blob(stmt, 6, (void*)&left, 8, NULL);
sqlite3_bind_int(stmt, 7, time(NULL)); sqlite3_bind_int(stmt, 7, time(NULL));
r = sqlite3_step(stmt); r = sqlite3_step(stmt);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
sql = "REPLACE INTO stats (info_hash,last_mod) VALUES (?,?)"; sql = "REPLACE INTO stats (info_hash,last_mod) VALUES (?,?)";
sqlite3_prepare (this->db, sql.c_str(), sql.length(), &stmt, NULL); sqlite3_prepare (this->db, sql.c_str(), sql.length(), &stmt, NULL);
sqlite3_bind_blob (stmt, 1, hash, 20, NULL); sqlite3_bind_blob (stmt, 1, hash, 20, NULL);
sqlite3_bind_int (stmt, 2, time(NULL)); sqlite3_bind_int (stmt, 2, time(NULL));
sqlite3_step (stmt); sqlite3_step (stmt);
sqlite3_finalize (stmt); sqlite3_finalize (stmt);
return r; return r;
} }
bool SQLite3Driver::addTorrent (uint8_t info_hash[20]) bool SQLite3Driver::addTorrent (uint8_t info_hash[20])
{ {
char xHash [41]; char xHash [41];
char *err_msg; char *err_msg;
int r; int r;
_to_hex_str(info_hash, xHash); _to_hex_str(info_hash, xHash);
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
sqlite3_prepare(this->db, "INSERT INTO torrents (info_hash,created) VALUES (?,?)", -1, &stmt, NULL); sqlite3_prepare(this->db, "INSERT INTO torrents (info_hash,created) VALUES (?,?)", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, info_hash, 20, NULL); sqlite3_bind_blob(stmt, 1, info_hash, 20, NULL);
sqlite3_bind_int(stmt, 2, time(NULL)); sqlite3_bind_int(stmt, 2, time(NULL));
sqlite3_step(stmt); sqlite3_step(stmt);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
string sql = "CREATE TABLE IF NOT EXISTS 't"; string sql = "CREATE TABLE IF NOT EXISTS 't";
sql += xHash; sql += xHash;
sql += "' ("; sql += "' (";
sql += "peer_id blob(20)," sql += "peer_id blob(20),"
"ip blob(4)," "ip blob(4),"
"port blob(2)," "port blob(2),"
"uploaded blob(8)," // uint64 "uploaded blob(8)," // uint64
"downloaded blob(8)," "downloaded blob(8),"
"left blob(8)," "left blob(8),"
"last_seen INT DEFAULT 0"; "last_seen INT DEFAULT 0";
sql += ", CONSTRAINT c1 UNIQUE (ip,port) ON CONFLICT REPLACE)"; sql += ", CONSTRAINT c1 UNIQUE (ip,port) ON CONFLICT REPLACE)";
// create table. // create table.
r = sqlite3_exec(this->db, sql.c_str(), NULL, NULL, &err_msg); r = sqlite3_exec(this->db, sql.c_str(), NULL, NULL, &err_msg);
if (SQLITE_OK == r) if (SQLITE_OK == r)
{ {
BOOST_LOG_SEV(m_logger, boost::log::trivial::info) << "Added torrent"; return true;
return true; }
} else
else {
{ return false;
BOOST_LOG_SEV(m_logger, boost::log::trivial::error) << "Failed to add torrent: SQLite3 error code = " << r; }
return false; }
}
} bool SQLite3Driver::isTorrentAllowed(uint8_t *info_hash)
{
bool SQLite3Driver::isTorrentAllowed(uint8_t *info_hash) if (this->isDynamic())
{ return true;
if (this->isDynamic()) sqlite3_stmt *stmt;
return true; sqlite3_prepare(this->db, "SELECT COUNT(*) FROM torrents WHERE info_hash=?", -1, &stmt, NULL);
sqlite3_stmt *stmt; sqlite3_bind_blob(stmt, 1, info_hash, 20, NULL);
sqlite3_prepare(this->db, "SELECT COUNT(*) FROM torrents WHERE info_hash=?", -1, &stmt, NULL); sqlite3_step(stmt);
sqlite3_bind_blob(stmt, 1, info_hash, 20, NULL);
sqlite3_step(stmt); int n = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
int n = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt); return (n == 1);
}
return (n == 1);
} void SQLite3Driver::cleanup()
{
void SQLite3Driver::cleanup() LOG_INFO("db-sqlite", "Cleaning up...");
{ int exp = time (NULL) - 7200; // 2 hours, expired.
int exp = time (NULL) - 7200; // 2 hours, expired. int r = 0;
// drop all peers with no activity for 2 hours. // drop all peers with no activity for 2 hours.
sqlite3_stmt *getTables; sqlite3_stmt *getTables;
// torrent table names: t<hex-of-sha-1> // torrent table names: t<hex-of-sha-1>
sqlite3_prepare(this->db, "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 't________________________________________'", -1, &getTables, NULL); r = sqlite3_prepare(this->db, "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 't________________________________________'", -1, &getTables, NULL);
if (r != SQLITE_OK) {
uint8_t buff [20]; LOG_ERR("db-sqlite", "Failed fetch tables from DB for cleanup.");
sqlite3_stmt *updateStats; return;
assert (sqlite3_prepare(this->db, "REPLACE INTO stats (info_hash,seeders,leechers,last_mod) VALUES (?,?,?,?)", -1, &updateStats, NULL) == SQLITE_OK); }
uint8_t buff [20];
while (sqlite3_step(getTables) == SQLITE_ROW) sqlite3_stmt *updateStats;
{ r = sqlite3_prepare(this->db, "REPLACE INTO stats (info_hash,seeders,leechers,last_mod) VALUES (?,?,?,?)", -1, &updateStats, NULL);
char* tblN = (char*)sqlite3_column_text(getTables, 0); if (r != SQLITE_OK) {
stringstream sStr; LOG_ERR("db-sqlite", "Failed to prepare update stats query.");
sStr << "DELETE FROM " << tblN << " WHERE last_seen<" << exp; return;
}
assert (sqlite3_exec(this->db, sStr.str().c_str(), NULL, NULL, NULL) == SQLITE_OK);
while (sqlite3_step(getTables) == SQLITE_ROW)
sStr.str (string()); {
sStr << "SELECT left,COUNT(*) FROM " << tblN << " GROUP BY left==0"; char* tblN = (char*)sqlite3_column_text(getTables, 0);
stringstream sStr;
sqlite3_stmt *collectStats; sStr << "DELETE FROM " << tblN << " WHERE last_seen<" << exp;
sqlite3_prepare(this->db, sStr.str().c_str(), sStr.str().length(), &collectStats, NULL); r = sqlite3_exec(this->db, sStr.str().c_str(), NULL, NULL, NULL);
if (r != SQLITE_OK) {
if (sqlite3_errcode(this->db) != SQLITE_OK) LOG_ERR("db-sqlite", "Failed to execute cleanup for table '" << tblN << "'.");
{ continue;
// TODO: Log this error }
}
sStr.str (string());
int seeders = 0, leechers = 0; sStr << "SELECT left,COUNT(*) FROM " << tblN << " GROUP BY left==0";
while (sqlite3_step(collectStats) == SQLITE_ROW) // expecting two results.
{ sqlite3_stmt *collectStats;
if (sqlite3_column_int(collectStats, 0) == 0)
seeders = sqlite3_column_int (collectStats, 1); r = sqlite3_prepare(this->db, sStr.str().c_str(), sStr.str().length(), &collectStats, NULL);
else
leechers = sqlite3_column_int (collectStats, 1); if (r != SQLITE_OK)
} {
sqlite3_finalize(collectStats); LOG_ERR("db-sqlite", "Failed while trying to prepare stats query for '" << tblN << "', sqlite returned " << r);
continue;
sqlite3_bind_blob(updateStats, 1, _hash_to_bin((const char*)(tblN + 1), buff), 20, NULL); }
sqlite3_bind_int(updateStats, 2, seeders);
sqlite3_bind_int(updateStats, 3, leechers); int seeders = 0, leechers = 0;
sqlite3_bind_int(updateStats, 4, time (NULL)); while (sqlite3_step(collectStats) == SQLITE_ROW) // expecting two results.
{
sqlite3_step(updateStats); if (sqlite3_column_int(collectStats, 0) == 0)
sqlite3_reset (updateStats); seeders = sqlite3_column_int (collectStats, 1);
} else
sqlite3_finalize(updateStats); leechers = sqlite3_column_int (collectStats, 1);
sqlite3_finalize(getTables); }
} sqlite3_finalize(collectStats);
bool SQLite3Driver::removeTorrent(uint8_t info_hash[20]) sqlite3_bind_blob(updateStats, 1, _hash_to_bin((const char*)(tblN + 1), buff), 20, NULL);
{ sqlite3_bind_int(updateStats, 2, seeders);
// if non-dynamic, remove from table sqlite3_bind_int(updateStats, 3, leechers);
sqlite3_stmt *stmt; sqlite3_bind_int(updateStats, 4, time (NULL));
sqlite3_prepare(this->db, "DELETE FROM torrents WHERE info_hash=?", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, info_hash, 20, NULL); sqlite3_step(updateStats);
sqlite3_step(stmt); sqlite3_reset (updateStats);
sqlite3_finalize(stmt); }
sqlite3_finalize(updateStats);
// remove from stats sqlite3_finalize(getTables);
sqlite3_stmt *rmS; }
if (sqlite3_prepare(this->db, "DELETE FROM stats WHERE info_hash=?", -1, &rmS, NULL) != SQLITE_OK)
{ bool SQLite3Driver::removeTorrent(uint8_t info_hash[20]) {
sqlite3_finalize(rmS); // if non-dynamic, remove from table
return false; sqlite3_stmt *stmt;
} sqlite3_prepare(this->db, "DELETE FROM torrents WHERE info_hash=?", -1, &stmt, NULL);
sqlite3_bind_blob(rmS, 1, (const void*)info_hash, 20, NULL); sqlite3_bind_blob(stmt, 1, info_hash, 20, NULL);
sqlite3_step(rmS); sqlite3_step(stmt);
sqlite3_finalize(rmS); sqlite3_finalize(stmt);
// remove table // remove from stats
string str = "DROP TABLE IF EXISTS 't"; sqlite3_stmt *rmS;
char buff [41]; if (sqlite3_prepare(this->db, "DELETE FROM stats WHERE info_hash=?", -1, &rmS, NULL) != SQLITE_OK)
str += _to_hex_str(info_hash, buff); {
str += "'"; sqlite3_finalize(rmS);
return false;
sqlite3_exec(this->db, str.c_str(), NULL, NULL, NULL); }
sqlite3_bind_blob(rmS, 1, (const void*)info_hash, 20, NULL);
BOOST_LOG_SEV(m_logger, boost::log::trivial::info) << "Torrent removed."; sqlite3_step(rmS);
sqlite3_finalize(rmS);
return true;
} // remove table
string str = "DROP TABLE IF EXISTS 't";
bool SQLite3Driver::removePeer(uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port) char buff [41];
{ str += _to_hex_str(info_hash, buff);
string sql; str += "'";
char xHash [50];
sqlite3_stmt *stmt; sqlite3_exec(this->db, str.c_str(), NULL, NULL, NULL);
_to_hex_str (info_hash, xHash); return true;
}
sql += "DELETE FROM 't";
sql += xHash; bool SQLite3Driver::removePeer(uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port) {
sql += "' WHERE ip=? AND port=? AND peer_id=?"; string sql;
char xHash [50];
sqlite3_prepare (this->db, sql.c_str(), sql.length(), &stmt, NULL); sqlite3_stmt *stmt;
sqlite3_bind_blob(stmt, 0, (const void*)&ip, 4, NULL); _to_hex_str (info_hash, xHash);
sqlite3_bind_blob(stmt, 1, (const void*)&port, 2, NULL);
sqlite3_bind_blob(stmt, 2, (const void*)peer_id, 20, NULL); sql += "DELETE FROM 't";
sql += xHash;
sqlite3_step(stmt); sql += "' WHERE ip=? AND port=? AND peer_id=?";
sqlite3_finalize(stmt); sqlite3_prepare (this->db, sql.c_str(), sql.length(), &stmt, NULL);
return true; sqlite3_bind_blob(stmt, 0, (const void*)&ip, 4, NULL);
} sqlite3_bind_blob(stmt, 1, (const void*)&port, 2, NULL);
sqlite3_bind_blob(stmt, 2, (const void*)peer_id, 20, NULL);
static uint64_t _genCiD (uint32_t ip, uint16_t port)
{ sqlite3_step(stmt);
uint64_t x;
x = (time(NULL) / 3600) * port; // x will probably overload. sqlite3_finalize(stmt);
x = (ip ^ port);
x <<= 16; return true;
x |= (~port); }
return x;
} static uint64_t _genCiD (uint32_t ip, uint16_t port) {
uint64_t x;
bool SQLite3Driver::genConnectionId (uint64_t *connectionId, uint32_t ip, uint16_t port) x = (time(NULL) / 3600) * port; // x will probably overload.
{ x = (ip ^ port);
*connectionId = _genCiD(ip, port); x <<= 16;
return true; x |= (~port);
} return x;
}
bool SQLite3Driver::verifyConnectionId(uint64_t cId, uint32_t ip, uint16_t port)
{ bool SQLite3Driver::genConnectionId (uint64_t *connectionId, uint32_t ip, uint16_t port) {
if (cId == _genCiD(ip, port)) *connectionId = _genCiD(ip, port);
return true; return true;
else }
return false;
} bool SQLite3Driver::verifyConnectionId(uint64_t cId, uint32_t ip, uint16_t port) {
if (cId == _genCiD(ip, port))
SQLite3Driver::~SQLite3Driver() return true;
{ else
BOOST_LOG_SEV(m_logger, boost::log::trivial::info) << "Closing SQLite"; return false;
sqlite3_close(this->db); }
}
}; SQLite3Driver::~SQLite3Driver() {
sqlite3_close(this->db);
}
};
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -26,31 +26,30 @@
namespace UDPT namespace UDPT
{ {
namespace Data namespace Data
{ {
class SQLite3Driver : public DatabaseDriver class SQLite3Driver : public DatabaseDriver
{ {
public: public:
SQLite3Driver(const boost::program_options::variables_map& conf, bool isDyn = false); SQLite3Driver(const boost::program_options::variables_map& conf, bool isDyn = false);
bool addTorrent(uint8_t info_hash[20]); bool addTorrent(uint8_t info_hash[20]);
bool removeTorrent(uint8_t info_hash[20]); bool removeTorrent(uint8_t info_hash[20]);
bool genConnectionId(uint64_t *connId, uint32_t ip, uint16_t port); bool genConnectionId(uint64_t *connId, uint32_t ip, uint16_t port);
bool verifyConnectionId(uint64_t connId, uint32_t ip, uint16_t port); bool verifyConnectionId(uint64_t connId, uint32_t ip, uint16_t port);
bool updatePeer(uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port, int64_t downloaded, int64_t left, int64_t uploaded, enum TrackerEvents event); bool updatePeer(uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port, int64_t downloaded, int64_t left, int64_t uploaded, enum TrackerEvents event);
bool removePeer(uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port); bool removePeer(uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port);
bool getTorrentInfo(TorrentEntry *e); bool getTorrentInfo(TorrentEntry *e);
bool isTorrentAllowed(uint8_t info_hash[20]); bool isTorrentAllowed(uint8_t info_hash[20]);
bool getPeers(uint8_t info_hash [20], int *max_count, PeerEntry *pe); bool getPeers(uint8_t info_hash [20], int *max_count, PeerEntry *pe);
void cleanup(); void cleanup();
virtual ~SQLite3Driver(); virtual ~SQLite3Driver();
private: private:
sqlite3 *db; sqlite3 *db;
boost::log::sources::severity_channel_logger_mt<> m_logger;
void doSetup(); void doSetup();
}; };
}; };
}; };
#endif /* DATABASE_H_ */ #endif /* DATABASE_H_ */

View file

@ -4,73 +4,73 @@
namespace UDPT namespace UDPT
{ {
class UDPTException class UDPTException
{ {
public: public:
UDPTException(const char* errorMsg, int errorCode = 0) : m_error(errorMsg), m_errorCode(errorCode) UDPTException(const char* errorMsg, int errorCode = 0) : m_error(errorMsg), m_errorCode(errorCode)
{ {
} }
UDPTException(int errorCode = 0) : m_errorCode(errorCode), m_error("") UDPTException(int errorCode = 0) : m_errorCode(errorCode), m_error("")
{ {
} }
virtual const char* what() const virtual const char* what() const
{ {
return m_error; return m_error;
} }
virtual int getErrorCode() const virtual int getErrorCode() const
{ {
return m_errorCode; return m_errorCode;
} }
virtual ~UDPTException() virtual ~UDPTException()
{ {
} }
protected: protected:
const char* m_error; const char* m_error;
const int m_errorCode; const int m_errorCode;
}; };
class OSError : public UDPTException class OSError : public UDPTException
{ {
public: public:
OSError(int errorCode OSError(int errorCode
#ifdef WIN32 #ifdef WIN32
= ::GetLastError() = ::GetLastError()
#endif #endif
) : UDPTException(errorCode) ) : UDPTException(errorCode)
{ {
} }
virtual ~OSError() {} virtual ~OSError() {}
const char* what() const const char* what() const
{ {
if (m_errorMessage.length() > 0) if (m_errorMessage.length() > 0)
{ {
return m_errorMessage.c_str(); return m_errorMessage.c_str();
} }
#ifdef WIN32 #ifdef WIN32
char *buffer = nullptr; char *buffer = nullptr;
DWORD msgLen = ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, m_errorCode, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), reinterpret_cast<LPSTR>(&buffer), 1, NULL); DWORD msgLen = ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, m_errorCode, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), reinterpret_cast<LPSTR>(&buffer), 1, NULL);
std::shared_ptr<void> formatStr = std::shared_ptr<void>( std::shared_ptr<void> formatStr = std::shared_ptr<void>(
buffer, buffer,
::LocalFree); ::LocalFree);
m_errorMessage = std::string(reinterpret_cast<char*>(formatStr.get())); m_errorMessage = std::string(reinterpret_cast<char*>(formatStr.get()));
return m_errorMessage.c_str(); return m_errorMessage.c_str();
#else #else
return "OSError"; return "OSError";
#endif #endif
} }
private: private:
// allow to generate a message only when needed. // allow to generate a message only when needed.
mutable std::string m_errorMessage; mutable std::string m_errorMessage;
}; };
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2013-2016 Naim A. * Copyright © 2013-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -29,507 +29,507 @@ using namespace std;
namespace UDPT namespace UDPT
{ {
namespace Server namespace Server
{ {
/* HTTPServer */ /* HTTPServer */
HTTPServer::HTTPServer (uint16_t port, int threads) HTTPServer::HTTPServer (uint16_t port, int threads)
{ {
SOCKADDR_IN sa; SOCKADDR_IN sa;
memset((void*)&sa, 0, sizeof(sa)); memset((void*)&sa, 0, sizeof(sa));
sa.sin_addr.s_addr = 0L; sa.sin_addr.s_addr = 0L;
sa.sin_family = AF_INET; sa.sin_family = AF_INET;
sa.sin_port = htons (port); sa.sin_port = htons (port);
this->init(sa, threads); this->init(sa, threads);
} }
HTTPServer::HTTPServer(const boost::program_options::variables_map& conf) HTTPServer::HTTPServer(const boost::program_options::variables_map& conf)
{ {
list<SOCKADDR_IN> localEndpoints; list<SOCKADDR_IN> localEndpoints;
uint16_t port; uint16_t port;
int threads; int threads;
port = conf["apiserver.port"].as<unsigned short>(); port = conf["apiserver.port"].as<unsigned short>();
threads = conf["apiserver.threads"].as<unsigned short>(); threads = conf["apiserver.threads"].as<unsigned short>();
if (threads <= 0) if (threads <= 0)
threads = 1; threads = 1;
if (localEndpoints.empty()) if (localEndpoints.empty())
{ {
SOCKADDR_IN sa; SOCKADDR_IN sa;
memset((void*)&sa, 0, sizeof(sa)); memset((void*)&sa, 0, sizeof(sa));
sa.sin_family = AF_INET; sa.sin_family = AF_INET;
sa.sin_port = htons (port); sa.sin_port = htons (port);
sa.sin_addr.s_addr = 0L; sa.sin_addr.s_addr = 0L;
localEndpoints.push_front(sa); localEndpoints.push_front(sa);
} }
this->init(localEndpoints.front(), threads); this->init(localEndpoints.front(), threads);
} }
void HTTPServer::init (SOCKADDR_IN &localEndpoint, int threads) void HTTPServer::init (SOCKADDR_IN &localEndpoint, int threads)
{ {
int r; int r;
this->thread_count = threads; this->thread_count = threads;
this->threads = new HANDLE[threads]; this->threads = new HANDLE[threads];
this->isRunning = false; this->isRunning = false;
this->rootNode.callback = NULL; this->rootNode.callback = NULL;
this->srv = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); this->srv = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (this->srv == INVALID_SOCKET) if (this->srv == INVALID_SOCKET)
{ {
throw ServerException (1, "Failed to create Socket"); throw ServerException (1, "Failed to create Socket");
} }
r = ::bind(this->srv, (SOCKADDR*)&localEndpoint, sizeof(localEndpoint)); r = ::bind(this->srv, (SOCKADDR*)&localEndpoint, sizeof(localEndpoint));
if (r == SOCKET_ERROR) if (r == SOCKET_ERROR)
{ {
throw ServerException(2, "Failed to bind socket"); throw ServerException(2, "Failed to bind socket");
} }
this->isRunning = true; this->isRunning = true;
for (int i = 0;i < threads;i++) for (int i = 0;i < threads;i++)
{ {
#ifdef WIN32 #ifdef WIN32
this->threads[i] = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)_thread_start, this, 0, NULL); this->threads[i] = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)_thread_start, this, 0, NULL);
#else #else
pthread_create (&this->threads[i], NULL, &HTTPServer::_thread_start, this); pthread_create (&this->threads[i], NULL, &HTTPServer::_thread_start, this);
#endif #endif
} }
} }
#ifdef WIN32 #ifdef WIN32
DWORD HTTPServer::_thread_start (LPVOID arg) DWORD HTTPServer::_thread_start (LPVOID arg)
#else #else
void* HTTPServer::_thread_start (void *arg) void* HTTPServer::_thread_start (void *arg)
#endif #endif
{ {
HTTPServer *s = (HTTPServer*)arg; HTTPServer *s = (HTTPServer*)arg;
doSrv: doSrv:
try { try {
HTTPServer::handleConnections (s); HTTPServer::handleConnections (s);
} catch (const ServerException &se) } catch (const ServerException &se)
{ {
cerr << "SRV ERR #" << se.getErrorCode() << ": " << se.getErrorMsg () << endl; cerr << "SRV ERR #" << se.getErrorCode() << ": " << se.getErrorMsg () << endl;
goto doSrv; goto doSrv;
} }
return 0; return 0;
} }
void HTTPServer::handleConnections (HTTPServer *server) void HTTPServer::handleConnections (HTTPServer *server)
{ {
int r; int r;
#ifdef WIN32 #ifdef WIN32
int addrSz; int addrSz;
#else #else
socklen_t addrSz; socklen_t addrSz;
#endif #endif
SOCKADDR_IN addr; SOCKADDR_IN addr;
SOCKET cli; SOCKET cli;
while (server->isRunning) while (server->isRunning)
{ {
r = ::listen(server->srv, 50); r = ::listen(server->srv, 50);
if (r == SOCKET_ERROR) if (r == SOCKET_ERROR)
{ {
#ifdef WIN32 #ifdef WIN32
::Sleep(500); ::Sleep(500);
#else #else
::sleep(1); ::sleep(1);
#endif #endif
continue; continue;
} }
addrSz = sizeof addr; addrSz = sizeof addr;
cli = accept (server->srv, (SOCKADDR*)&addr, &addrSz); cli = accept (server->srv, (SOCKADDR*)&addr, &addrSz);
if (cli == INVALID_SOCKET) if (cli == INVALID_SOCKET)
continue; continue;
Response resp (cli); // doesn't throw exceptions. Response resp (cli); // doesn't throw exceptions.
try { try {
Request req (cli, &addr); // may throw exceptions. Request req (cli, &addr); // may throw exceptions.
reqCallback *cb = getRequestHandler (&server->rootNode, req.getPath()); reqCallback *cb = getRequestHandler (&server->rootNode, req.getPath());
if (cb == NULL) if (cb == NULL)
{ {
// error 404 // error 404
resp.setStatus (404, "Not Found"); resp.setStatus (404, "Not Found");
resp.addHeader ("Content-Type", "text/html; charset=US-ASCII"); resp.addHeader ("Content-Type", "text/html; charset=US-ASCII");
stringstream stream; stringstream stream;
stream << "<html>"; stream << "<html>";
stream << "<head><title>Not Found</title></head>"; stream << "<head><title>Not Found</title></head>";
stream << "<body><h1>Not Found</h1><div>The server couldn't find the request resource.</div><br /><hr /><div style=\"font-size:small;text-align:center;\"><a href=\"http://github.com/naim94a/udpt\">UDPT</a></div></body>"; stream << "<body><h1>Not Found</h1><div>The server couldn't find the request resource.</div><br /><hr /><div style=\"font-size:small;text-align:center;\"><a href=\"http://github.com/naim94a/udpt\">UDPT</a></div></body>";
stream << "</html>"; stream << "</html>";
string str = stream.str(); string str = stream.str();
resp.write (str.c_str(), str.length()); resp.write (str.c_str(), str.length());
} }
else else
{ {
try { try {
cb (server, &req, &resp); cb (server, &req, &resp);
} catch (...) } catch (...)
{ {
resp.setStatus(500, "Internal Server Error"); resp.setStatus(500, "Internal Server Error");
resp.addHeader ("Content-Type", "text/html; charset=US-ASCII"); resp.addHeader ("Content-Type", "text/html; charset=US-ASCII");
stringstream stream; stringstream stream;
stream << "<html>"; stream << "<html>";
stream << "<head><title>Internal Server Error</title></head>"; stream << "<head><title>Internal Server Error</title></head>";
stream << "<body><h1>Internal Server Error</h1><div>An Error Occurred while trying to process your request.</div><br /><hr /><div style=\"font-size:small;text-align:center;\"><a href=\"http://github.com/naim94a/udpt\">UDPT</a></div></body>"; stream << "<body><h1>Internal Server Error</h1><div>An Error Occurred while trying to process your request.</div><br /><hr /><div style=\"font-size:small;text-align:center;\"><a href=\"http://github.com/naim94a/udpt\">UDPT</a></div></body>";
stream << "</html>"; stream << "</html>";
string str = stream.str(); string str = stream.str();
resp.write (str.c_str(), str.length()); resp.write (str.c_str(), str.length());
} }
} }
resp.finalize(); resp.finalize();
} catch (ServerException &e) } catch (ServerException &e)
{ {
// Error 400 Bad Request! // Error 400 Bad Request!
} }
closesocket (cli); closesocket (cli);
} }
} }
void HTTPServer::addApp (list<string> *path, reqCallback *cb) void HTTPServer::addApp (list<string> *path, reqCallback *cb)
{ {
list<string>::iterator it = path->begin(); list<string>::iterator it = path->begin();
appNode *node = &this->rootNode; appNode *node = &this->rootNode;
while (it != path->end()) while (it != path->end())
{ {
map<string, appNode>::iterator se; map<string, appNode>::iterator se;
se = node->nodes.find (*it); se = node->nodes.find (*it);
if (se == node->nodes.end()) if (se == node->nodes.end())
{ {
node->nodes[*it].callback = NULL; node->nodes[*it].callback = NULL;
} }
node = &node->nodes[*it]; node = &node->nodes[*it];
it++; it++;
} }
node->callback = cb; node->callback = cb;
} }
HTTPServer::reqCallback* HTTPServer::getRequestHandler (appNode *node, list<string> *path) HTTPServer::reqCallback* HTTPServer::getRequestHandler (appNode *node, list<string> *path)
{ {
appNode *cn = node; appNode *cn = node;
list<string>::iterator it = path->begin(), list<string>::iterator it = path->begin(),
end = path->end(); end = path->end();
map<string, appNode>::iterator n; map<string, appNode>::iterator n;
while (true) while (true)
{ {
if (it == end) if (it == end)
{ {
return cn->callback; return cn->callback;
} }
n = cn->nodes.find (*it); n = cn->nodes.find (*it);
if (n == cn->nodes.end()) if (n == cn->nodes.end())
return NULL; // node not found! return NULL; // node not found!
cn = &n->second; cn = &n->second;
it++; it++;
} }
return NULL; return NULL;
} }
void HTTPServer::setData(string k, void *d) void HTTPServer::setData(string k, void *d)
{ {
this->customData[k] = d; this->customData[k] = d;
} }
void* HTTPServer::getData(string k) void* HTTPServer::getData(string k)
{ {
map<string, void*>::iterator it = this->customData.find(k); map<string, void*>::iterator it = this->customData.find(k);
if (it == this->customData.end()) if (it == this->customData.end())
return NULL; return NULL;
return it->second; return it->second;
} }
HTTPServer::~HTTPServer () HTTPServer::~HTTPServer ()
{ {
if (this->srv != INVALID_SOCKET) if (this->srv != INVALID_SOCKET)
closesocket (this->srv); closesocket (this->srv);
if (this->isRunning) if (this->isRunning)
{ {
for (int i = 0;i < this->thread_count;i++) for (int i = 0;i < this->thread_count;i++)
{ {
#ifdef WIN32 #ifdef WIN32
TerminateThread (this->threads[i], 0x00); TerminateThread (this->threads[i], 0x00);
#else #else
pthread_detach (this->threads[i]); pthread_detach (this->threads[i]);
pthread_cancel (this->threads[i]); pthread_cancel (this->threads[i]);
#endif #endif
} }
} }
delete[] this->threads; delete[] this->threads;
} }
/* HTTPServer::Request */ /* HTTPServer::Request */
HTTPServer::Request::Request (SOCKET cli, const SOCKADDR_IN *addr) HTTPServer::Request::Request (SOCKET cli, const SOCKADDR_IN *addr)
{ {
this->conn = cli; this->conn = cli;
this->addr = addr; this->addr = addr;
this->parseRequest (); this->parseRequest ();
} }
inline static char* nextReqLine (int &cPos, char *buff, int len) inline static char* nextReqLine (int &cPos, char *buff, int len)
{ {
for (int i = cPos;i < len - 1;i++) for (int i = cPos;i < len - 1;i++)
{ {
if (buff[i] == '\r' && buff[i + 1] == '\n') if (buff[i] == '\r' && buff[i + 1] == '\n')
{ {
buff[i] = '\0'; buff[i] = '\0';
int r = cPos; int r = cPos;
cPos = i + 2; cPos = i + 2;
return (buff + r); return (buff + r);
} }
} }
return (buff + len); // end return (buff + len); // end
} }
inline void parseURL (string request, list<string> *path, map<string, string> *params) inline void parseURL (string request, list<string> *path, map<string, string> *params)
{ {
string::size_type p; string::size_type p;
string query, url; string query, url;
p = request.find ('?'); p = request.find ('?');
if (p == string::npos) if (p == string::npos)
{ {
p = request.length(); p = request.length();
} }
else else
{ {
query = request.substr (p + 1); query = request.substr (p + 1);
} }
url = request.substr (0, p); url = request.substr (0, p);
path->clear (); path->clear ();
string::size_type s, e; string::size_type s, e;
s = 0; s = 0;
while (true) while (true)
{ {
e = url.find ('/', s); e = url.find ('/', s);
if (e == string::npos) if (e == string::npos)
e = url.length(); e = url.length();
string x = url.substr (s, e - s); string x = url.substr (s, e - s);
if (!(x.length() == 0 || x == ".")) if (!(x.length() == 0 || x == "."))
{ {
if (x == "..") if (x == "..")
{ {
if (path->empty()) if (path->empty())
throw ServerException (1, "Hack attempt"); throw ServerException (1, "Hack attempt");
else else
path->pop_back (); path->pop_back ();
} }
path->push_back (x); path->push_back (x);
} }
if (e == url.length()) if (e == url.length())
break; break;
s = e + 1; s = e + 1;
} }
string::size_type vS, vE, kS, kE; string::size_type vS, vE, kS, kE;
vS = vE = kS = kE = 0; vS = vE = kS = kE = 0;
while (kS < query.length()) while (kS < query.length())
{ {
kE = query.find ('=', kS); kE = query.find ('=', kS);
if (kE == string::npos) break; if (kE == string::npos) break;
vS = kE + 1; vS = kE + 1;
vE = query.find ('&', vS); vE = query.find ('&', vS);
if (vE == string::npos) vE = query.length(); if (vE == string::npos) vE = query.length();
params->insert (pair<string, string>( query.substr (kS, kE - kS), query.substr (vS, vE - vS) )); params->insert (pair<string, string>( query.substr (kS, kE - kS), query.substr (vS, vE - vS) ));
kS = vE + 1; kS = vE + 1;
} }
} }
inline void setCookies (string &data, map<string, string> *cookies) inline void setCookies (string &data, map<string, string> *cookies)
{ {
string::size_type kS, kE, vS, vE; string::size_type kS, kE, vS, vE;
kS = 0; kS = 0;
while (kS < data.length ()) while (kS < data.length ())
{ {
kE = data.find ('=', kS); kE = data.find ('=', kS);
if (kE == string::npos) if (kE == string::npos)
break; break;
vS = kE + 1; vS = kE + 1;
vE = data.find ("; ", vS); vE = data.find ("; ", vS);
if (vE == string::npos) if (vE == string::npos)
vE = data.length(); vE = data.length();
(*cookies) [data.substr (kS, kE-kS)] = data.substr (vS, vE-vS); (*cookies) [data.substr (kS, kE-kS)] = data.substr (vS, vE-vS);
kS = vE + 2; kS = vE + 2;
} }
} }
void HTTPServer::Request::parseRequest () void HTTPServer::Request::parseRequest ()
{ {
char buffer [REQUEST_BUFFER_SIZE]; char buffer [REQUEST_BUFFER_SIZE];
int r; int r;
r = recv (this->conn, buffer, REQUEST_BUFFER_SIZE, 0); r = recv (this->conn, buffer, REQUEST_BUFFER_SIZE, 0);
if (r == REQUEST_BUFFER_SIZE) if (r == REQUEST_BUFFER_SIZE)
throw ServerException (1, "Request Size too big."); throw ServerException (1, "Request Size too big.");
if (r <= 0) if (r <= 0)
throw ServerException (2, "Socket Error"); throw ServerException (2, "Socket Error");
char *cLine; char *cLine;
int n = 0; int n = 0;
int pos = 0; int pos = 0;
string::size_type p; string::size_type p;
while ( (cLine = nextReqLine (pos, buffer, r)) < (buffer + r)) while ( (cLine = nextReqLine (pos, buffer, r)) < (buffer + r))
{ {
string line = string (cLine); string line = string (cLine);
if (line.length() == 0) break; // CRLF CRLF = end of headers. if (line.length() == 0) break; // CRLF CRLF = end of headers.
n++; n++;
if (n == 1) if (n == 1)
{ {
string::size_type uS, uE; string::size_type uS, uE;
p = line.find (' '); p = line.find (' ');
if (p == string::npos) if (p == string::npos)
throw ServerException (5, "Malformed request method"); throw ServerException (5, "Malformed request method");
uS = p + 1; uS = p + 1;
this->requestMethod.str = line.substr (0, p); this->requestMethod.str = line.substr (0, p);
if (this->requestMethod.str == "GET") if (this->requestMethod.str == "GET")
this->requestMethod.rm = RM_GET; this->requestMethod.rm = RM_GET;
else if (this->requestMethod.str == "POST") else if (this->requestMethod.str == "POST")
this->requestMethod.rm = RM_POST; this->requestMethod.rm = RM_POST;
else else
this->requestMethod.rm = RM_UNKNOWN; this->requestMethod.rm = RM_UNKNOWN;
uE = uS; uE = uS;
while (p < line.length()) while (p < line.length())
{ {
if (p == string::npos) if (p == string::npos)
break; break;
p = line.find (' ', p + 1); p = line.find (' ', p + 1);
if (p == string::npos) if (p == string::npos)
break; break;
uE = p; uE = p;
} }
if (uE + 1 >= line.length()) if (uE + 1 >= line.length())
throw ServerException (6, "Malformed request"); throw ServerException (6, "Malformed request");
string httpVersion = line.substr (uE + 1); string httpVersion = line.substr (uE + 1);
parseURL (line.substr (uS, uE - uS), &this->path, &this->params); parseURL (line.substr (uS, uE - uS), &this->path, &this->params);
} }
else else
{ {
p = line.find (": "); p = line.find (": ");
if (p == string::npos) if (p == string::npos)
throw ServerException (4, "Malformed headers"); throw ServerException (4, "Malformed headers");
string key = line.substr (0, p); string key = line.substr (0, p);
string value = line.substr (p + 2); string value = line.substr (p + 2);
if (key != "Cookie") if (key != "Cookie")
this->headers.insert(pair<string, string>( key, value)); this->headers.insert(pair<string, string>( key, value));
else else
setCookies (value, &this->cookies); setCookies (value, &this->cookies);
} }
} }
if (n == 0) if (n == 0)
throw ServerException (3, "No Request header."); throw ServerException (3, "No Request header.");
} }
list<string>* HTTPServer::Request::getPath () list<string>* HTTPServer::Request::getPath ()
{ {
return &this->path; return &this->path;
} }
string HTTPServer::Request::getParam (const string key) string HTTPServer::Request::getParam (const string key)
{ {
map<string, string>::iterator it = this->params.find (key); map<string, string>::iterator it = this->params.find (key);
if (it == this->params.end()) if (it == this->params.end())
return ""; return "";
else else
return it->second; return it->second;
} }
multimap<string, string>::iterator HTTPServer::Request::getHeader (const string name) multimap<string, string>::iterator HTTPServer::Request::getHeader (const string name)
{ {
multimap<string, string>::iterator it = this->headers.find (name); multimap<string, string>::iterator it = this->headers.find (name);
return it; return it;
} }
HTTPServer::Request::RequestMethod HTTPServer::Request::getRequestMethod () HTTPServer::Request::RequestMethod HTTPServer::Request::getRequestMethod ()
{ {
return this->requestMethod.rm; return this->requestMethod.rm;
} }
string HTTPServer::Request::getRequestMethodStr () string HTTPServer::Request::getRequestMethodStr ()
{ {
return this->requestMethod.str; return this->requestMethod.str;
} }
string HTTPServer::Request::getCookie (const string name) string HTTPServer::Request::getCookie (const string name)
{ {
map<string, string>::iterator it = this->cookies.find (name); map<string, string>::iterator it = this->cookies.find (name);
if (it == this->cookies.end()) if (it == this->cookies.end())
return ""; return "";
else else
return it->second; return it->second;
} }
const SOCKADDR_IN* HTTPServer::Request::getAddress () const SOCKADDR_IN* HTTPServer::Request::getAddress ()
{ {
return this->addr; return this->addr;
} }
/* HTTPServer::Response */ /* HTTPServer::Response */
HTTPServer::Response::Response (SOCKET cli) HTTPServer::Response::Response (SOCKET cli)
{ {
this->conn = cli; this->conn = cli;
setStatus (200, "OK"); setStatus (200, "OK");
} }
void HTTPServer::Response::setStatus (int c, const string m) void HTTPServer::Response::setStatus (int c, const string m)
{ {
this->status_code = c; this->status_code = c;
this->status_msg = m; this->status_msg = m;
} }
void HTTPServer::Response::addHeader (string key, string value) void HTTPServer::Response::addHeader (string key, string value)
{ {
this->headers.insert (pair<string, string>(key, value)); this->headers.insert (pair<string, string>(key, value));
} }
void HTTPServer::Response::write (const char *data, int len) void HTTPServer::Response::write (const char *data, int len)
{ {
if (len < 0) if (len < 0)
len = strlen (data); len = strlen (data);
msg.write(data, len); msg.write(data, len);
} }
void HTTPServer::Response::finalize () void HTTPServer::Response::finalize ()
{ {
stringstream x; stringstream x;
x << "HTTP/1.1 " << this->status_code << " " << this->status_msg << "\r\n"; x << "HTTP/1.1 " << this->status_code << " " << this->status_msg << "\r\n";
multimap<string, string>::iterator it, end; multimap<string, string>::iterator it, end;
end = this->headers.end(); end = this->headers.end();
for (it = this->headers.begin(); it != end;it++) for (it = this->headers.begin(); it != end;it++)
{ {
x << it->first << ": " << it->second << "\r\n"; x << it->first << ": " << it->second << "\r\n";
} }
x << "Connection: Close\r\n"; x << "Connection: Close\r\n";
x << "Content-Length: " << this->msg.tellp() << "\r\n"; x << "Content-Length: " << this->msg.tellp() << "\r\n";
x << "Server: udpt\r\n"; x << "Server: udpt\r\n";
x << "\r\n"; x << "\r\n";
x << this->msg.str(); x << this->msg.str();
// write to socket // write to socket
send (this->conn, x.str().c_str(), x.str().length(), 0); send (this->conn, x.str().c_str(), x.str().length(), 0);
} }
}; };
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2013-2016 Naim A. * Copyright © 2013-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -32,139 +32,139 @@ using namespace std;
namespace UDPT namespace UDPT
{ {
namespace Server namespace Server
{ {
class ServerException class ServerException
{ {
public: public:
inline ServerException (int ec) inline ServerException (int ec)
{ {
this->ec = ec; this->ec = ec;
this->em = NULL; this->em = NULL;
} }
inline ServerException (int ec, const char *em) inline ServerException (int ec, const char *em)
{ {
this->ec = ec; this->ec = ec;
this->em = em; this->em = em;
} }
inline const char *getErrorMsg () const inline const char *getErrorMsg () const
{ {
return this->em; return this->em;
} }
inline int getErrorCode () const inline int getErrorCode () const
{ {
return this->ec; return this->ec;
} }
private: private:
int ec; int ec;
const char *em; const char *em;
}; };
class HTTPServer class HTTPServer
{ {
public: public:
class Request class Request
{ {
public: public:
enum RequestMethod enum RequestMethod
{ {
RM_UNKNOWN = 0, RM_UNKNOWN = 0,
RM_GET = 1, RM_GET = 1,
RM_POST = 2 RM_POST = 2
}; };
Request (SOCKET, const SOCKADDR_IN *); Request (SOCKET, const SOCKADDR_IN *);
list<string>* getPath (); list<string>* getPath ();
string getParam (const string key); string getParam (const string key);
multimap<string, string>::iterator getHeader (const string name); multimap<string, string>::iterator getHeader (const string name);
RequestMethod getRequestMethod (); RequestMethod getRequestMethod ();
string getRequestMethodStr (); string getRequestMethodStr ();
string getCookie (const string name); string getCookie (const string name);
const SOCKADDR_IN* getAddress (); const SOCKADDR_IN* getAddress ();
private: private:
const SOCKADDR_IN *addr; const SOCKADDR_IN *addr;
SOCKET conn; SOCKET conn;
struct { struct {
int major; int major;
int minor; int minor;
} httpVer; } httpVer;
struct { struct {
string str; string str;
RequestMethod rm; RequestMethod rm;
} requestMethod; } requestMethod;
list<string> path; list<string> path;
map<string, string> params; map<string, string> params;
map<string, string> cookies; map<string, string> cookies;
multimap<string, string> headers; multimap<string, string> headers;
void parseRequest (); void parseRequest ();
}; };
class Response class Response
{ {
public: public:
Response (SOCKET conn); Response (SOCKET conn);
void setStatus (int, const string); void setStatus (int, const string);
void addHeader (string key, string value); void addHeader (string key, string value);
int writeRaw (const char *data, int len); int writeRaw (const char *data, int len);
void write (const char *data, int len = -1); void write (const char *data, int len = -1);
private: private:
friend class HTTPServer; friend class HTTPServer;
SOCKET conn; SOCKET conn;
int status_code; int status_code;
string status_msg; string status_msg;
multimap<string, string> headers; multimap<string, string> headers;
stringstream msg; stringstream msg;
void finalize (); void finalize ();
}; };
typedef void (reqCallback)(HTTPServer*,Request*,Response*); typedef void (reqCallback)(HTTPServer*,Request*,Response*);
HTTPServer (uint16_t port, int threads); HTTPServer (uint16_t port, int threads);
HTTPServer(const boost::program_options::variables_map& conf); HTTPServer(const boost::program_options::variables_map& conf);
void addApp (list<string> *path, reqCallback *); void addApp (list<string> *path, reqCallback *);
void setData (string, void *); void setData (string, void *);
void* getData (string); void* getData (string);
virtual ~HTTPServer (); virtual ~HTTPServer ();
private: private:
typedef struct appNode typedef struct appNode
{ {
reqCallback *callback; reqCallback *callback;
map<string, appNode> nodes; map<string, appNode> nodes;
} appNode; } appNode;
SOCKET srv; SOCKET srv;
int thread_count; int thread_count;
HANDLE *threads; HANDLE *threads;
bool isRunning; bool isRunning;
appNode rootNode; appNode rootNode;
map<string, void*> customData; map<string, void*> customData;
void init (SOCKADDR_IN &localEndpoint, int threads); void init (SOCKADDR_IN &localEndpoint, int threads);
static void handleConnections (HTTPServer *); static void handleConnections (HTTPServer *);
#ifdef WIN32 #ifdef WIN32
static DWORD _thread_start (LPVOID); static DWORD _thread_start (LPVOID);
#else #else
static void* _thread_start (void*); static void* _thread_start (void*);
#endif #endif
static reqCallback* getRequestHandler (appNode *, list<string> *); static reqCallback* getRequestHandler (appNode *, list<string> *);
}; };
}; };
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2013-2016 Naim A. * Copyright © 2013-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -25,190 +25,190 @@ using namespace std;
namespace UDPT namespace UDPT
{ {
namespace Server namespace Server
{ {
static uint32_t _getNextIPv4 (string::size_type &i, string &line) static uint32_t _getNextIPv4 (string::size_type &i, string &line)
{ {
string::size_type len = line.length(); string::size_type len = line.length();
char c; char c;
while (i < len) while (i < len)
{ {
c = line.at(i); c = line.at(i);
if (c >= '0' && c <= '9') if (c >= '0' && c <= '9')
break; break;
i++; i++;
} }
uint32_t ip = 0; uint32_t ip = 0;
for (int n = 0;n < 4;n++) for (int n = 0;n < 4;n++)
{ {
int cn = 0; int cn = 0;
while (i < len) while (i < len)
{ {
c = line.at (i++); c = line.at (i++);
if (c == '.' || ((c == ' ' || c == ',' || c == ';') && n == 3)) if (c == '.' || ((c == ' ' || c == ',' || c == ';') && n == 3))
break; break;
else if (!(c >= '0' && c <= '9')) else if (!(c >= '0' && c <= '9'))
return 0; return 0;
cn *= 10; cn *= 10;
cn += (c - '0'); cn += (c - '0');
} }
ip *= 256; ip *= 256;
ip += cn; ip += cn;
} }
return ip; return ip;
} }
static bool _hex2bin (uint8_t *data, const string str) static bool _hex2bin (uint8_t *data, const string str)
{ {
int len = str.length(); int len = str.length();
if (len % 2 != 0) if (len % 2 != 0)
return false; return false;
char a, b; char a, b;
uint8_t c; uint8_t c;
for (int i = 0;i < len;i+=2) for (int i = 0;i < len;i+=2)
{ {
a = str.at (i); a = str.at (i);
b = str.at (i + 1); b = str.at (i + 1);
c = 0; c = 0;
if (a >= 'a' && a <= 'f') if (a >= 'a' && a <= 'f')
a = (a - 'a') + 10; a = (a - 'a') + 10;
else if (a >= '0' && a <= '9') else if (a >= '0' && a <= '9')
a = (a - '0'); a = (a - '0');
else else
return false; return false;
if (b >= 'a' && b <= 'f') if (b >= 'a' && b <= 'f')
b = (b - 'a') + 10; b = (b - 'a') + 10;
else if (b >= '0' && b <= '9') else if (b >= '0' && b <= '9')
b = (b - '0'); b = (b - '0');
else else
return false; return false;
c = (a * 16) + b; c = (a * 16) + b;
data [i / 2] = c; data [i / 2] = c;
} }
return true; return true;
} }
WebApp::WebApp(std::shared_ptr<HTTPServer> srv, DatabaseDriver *db, const boost::program_options::variables_map& conf) : m_conf(conf), m_server(srv) WebApp::WebApp(std::shared_ptr<HTTPServer> srv, DatabaseDriver *db, const boost::program_options::variables_map& conf) : m_conf(conf), m_server(srv)
{ {
this->db = db; this->db = db;
// TODO: Implement authentication by keys // TODO: Implement authentication by keys
m_server->setData("webapp", this); m_server->setData("webapp", this);
} }
WebApp::~WebApp() WebApp::~WebApp()
{ {
} }
void WebApp::deploy() void WebApp::deploy()
{ {
list<string> path; list<string> path;
m_server->addApp(&path, &WebApp::handleRoot); m_server->addApp(&path, &WebApp::handleRoot);
path.push_back("api"); path.push_back("api");
m_server->addApp(&path, &WebApp::handleAPI); // "/api" m_server->addApp(&path, &WebApp::handleAPI); // "/api"
path.pop_back(); path.pop_back();
path.push_back("announce"); path.push_back("announce");
m_server->addApp(&path, &WebApp::handleAnnounce); m_server->addApp(&path, &WebApp::handleAnnounce);
} }
void WebApp::handleRoot(HTTPServer *srv, HTTPServer::Request *req, HTTPServer::Response *resp) void WebApp::handleRoot(HTTPServer *srv, HTTPServer::Request *req, HTTPServer::Response *resp)
{ {
// It would be very appreciated to keep this in the code. // It would be very appreciated to keep this in the code.
resp->write("<html>" resp->write("<html>"
"<head><title>UDPT Torrent Tracker</title></head>" "<head><title>UDPT Torrent Tracker</title></head>"
"<body>" "<body>"
"<div style=\"vertical-align:top;\">This tracker is running on UDPT Software.</div>" "<div style=\"vertical-align:top;\">This tracker is running on UDPT Software.</div>"
"<br /><hr /><div style=\"text-align:center;font-size:small;\"><a href=\"http://github.com/naim94a/udpt\">UDPT</a></div>" "<br /><hr /><div style=\"text-align:center;font-size:small;\"><a href=\"http://github.com/naim94a/udpt\">UDPT</a></div>"
"</body>" "</body>"
"</html>"); "</html>");
} }
void WebApp::doRemoveTorrent (HTTPServer::Request *req, HTTPServer::Response *resp) void WebApp::doRemoveTorrent (HTTPServer::Request *req, HTTPServer::Response *resp)
{ {
string strHash = req->getParam("hash"); string strHash = req->getParam("hash");
if (strHash.length() != 40) if (strHash.length() != 40)
{ {
resp->write("{\"error\":\"Hash length must be 40 characters.\"}"); resp->write("{\"error\":\"Hash length must be 40 characters.\"}");
return; return;
} }
uint8_t hash [20]; uint8_t hash [20];
if (!_hex2bin(hash, strHash)) if (!_hex2bin(hash, strHash))
{ {
resp->write("{\"error\":\"invalid info_hash.\"}"); resp->write("{\"error\":\"invalid info_hash.\"}");
return; return;
} }
if (this->db->removeTorrent(hash)) if (this->db->removeTorrent(hash))
resp->write("{\"success\":true}"); resp->write("{\"success\":true}");
else else
resp->write("{\"error\":\"failed to remove torrent from DB\"}"); resp->write("{\"error\":\"failed to remove torrent from DB\"}");
} }
void WebApp::doAddTorrent (HTTPServer::Request *req, HTTPServer::Response *resp) void WebApp::doAddTorrent (HTTPServer::Request *req, HTTPServer::Response *resp)
{ {
std::string strHash = req->getParam("hash"); std::string strHash = req->getParam("hash");
if (strHash.length() != 40) if (strHash.length() != 40)
{ {
resp->write("{\"error\":\"Hash length must be 40 characters.\"}"); resp->write("{\"error\":\"Hash length must be 40 characters.\"}");
return; return;
} }
uint8_t hash [20]; uint8_t hash [20];
if (!_hex2bin(hash, strHash)) if (!_hex2bin(hash, strHash))
{ {
resp->write("{\"error\":\"invalid info_hash.\"}"); resp->write("{\"error\":\"invalid info_hash.\"}");
return; return;
} }
if (this->db->addTorrent(hash)) if (this->db->addTorrent(hash))
resp->write("{\"success\":true}"); resp->write("{\"success\":true}");
else else
resp->write("{\"error\":\"failed to add torrent to DB\"}"); resp->write("{\"error\":\"failed to add torrent to DB\"}");
} }
void WebApp::handleAnnounce (HTTPServer *srv, HTTPServer::Request *req, HTTPServer::Response *resp) void WebApp::handleAnnounce (HTTPServer *srv, HTTPServer::Request *req, HTTPServer::Response *resp)
{ {
resp->write("d14:failure reason42:this is a UDP tracker, not a HTTP tracker.e"); resp->write("d14:failure reason42:this is a UDP tracker, not a HTTP tracker.e");
} }
void WebApp::handleAPI(HTTPServer *srv, HTTPServer::Request *req, HTTPServer::Response *resp) void WebApp::handleAPI(HTTPServer *srv, HTTPServer::Request *req, HTTPServer::Response *resp)
{ {
if (req->getAddress()->sin_family != AF_INET) if (req->getAddress()->sin_family != AF_INET)
{ {
throw ServerException (0, "IPv4 supported Only."); throw ServerException (0, "IPv4 supported Only.");
} }
WebApp *app = (WebApp*)srv->getData("webapp"); WebApp *app = (WebApp*)srv->getData("webapp");
if (app == NULL) if (app == NULL)
throw ServerException(0, "WebApp object wasn't found"); throw ServerException(0, "WebApp object wasn't found");
if (req->getAddress()->sin_addr.s_addr != 0x0100007f) if (req->getAddress()->sin_addr.s_addr != 0x0100007f)
{ {
resp->setStatus(403, "Forbidden"); resp->setStatus(403, "Forbidden");
resp->write("Access Denied. Only 127.0.0.1 can access this method."); resp->write("Access Denied. Only 127.0.0.1 can access this method.");
return; return;
} }
std::string action = req->getParam("action"); std::string action = req->getParam("action");
if (action == "add") if (action == "add")
app->doAddTorrent(req, resp); app->doAddTorrent(req, resp);
else if (action == "remove") else if (action == "remove")
app->doRemoveTorrent(req, resp); app->doRemoveTorrent(req, resp);
else else
{ {
resp->write("{\"error\":\"unknown action\"}"); resp->write("{\"error\":\"unknown action\"}");
} }
} }
}; };
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2013-2016 Naim A. * Copyright © 2013-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -33,27 +33,27 @@ using namespace UDPT::Data;
namespace UDPT namespace UDPT
{ {
namespace Server namespace Server
{ {
class WebApp class WebApp
{ {
public: public:
WebApp(std::shared_ptr<HTTPServer> , DatabaseDriver *, const boost::program_options::variables_map& conf); WebApp(std::shared_ptr<HTTPServer> , DatabaseDriver *, const boost::program_options::variables_map& conf);
virtual ~WebApp(); virtual ~WebApp();
void deploy (); void deploy ();
private: private:
std::shared_ptr<HTTPServer> m_server; std::shared_ptr<HTTPServer> m_server;
UDPT::Data::DatabaseDriver *db; UDPT::Data::DatabaseDriver *db;
const boost::program_options::variables_map& m_conf; const boost::program_options::variables_map& m_conf;
static void handleRoot (HTTPServer*,HTTPServer::Request*, HTTPServer::Response*); static void handleRoot (HTTPServer*,HTTPServer::Request*, HTTPServer::Response*);
static void handleAnnounce (HTTPServer*,HTTPServer::Request*, HTTPServer::Response*); static void handleAnnounce (HTTPServer*,HTTPServer::Request*, HTTPServer::Response*);
static void handleAPI (HTTPServer*,HTTPServer::Request*, HTTPServer::Response*); static void handleAPI (HTTPServer*,HTTPServer::Request*, HTTPServer::Response*);
void doAddTorrent (HTTPServer::Request*, HTTPServer::Response*); void doAddTorrent (HTTPServer::Request*, HTTPServer::Response*);
void doRemoveTorrent (HTTPServer::Request*, HTTPServer::Response*); void doRemoveTorrent (HTTPServer::Request*, HTTPServer::Response*);
}; };
}; };
}; };

140
src/logging.cpp Normal file
View file

@ -0,0 +1,140 @@
/*
* Copyright © 2012-2017 Naim A.
*
* This file is part of UDPT.
*
* UDPT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* UDPT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with UDPT. If not, see <http://www.gnu.org/licenses/>.
*/
#include "logging.hpp"
#include <sstream>
#include <chrono>
#include <cstring>
#include <iostream>
namespace UDPT {
namespace Logging {
Logger::Logger() : m_cleaningUp(false), m_workerThread(Logger::worker, this), m_minLogLevel(Severity::FATAL) {
}
Logger::~Logger() {
try {
// prevent new log messages from entering queue.
m_cleaningUp = true;
// tell thread to exit
m_runningCondition.notify_one();
// wait for worker to terminate
m_workerThread.join();
// flush any remaining logs
flush();
// flush iostreams
for (std::vector<std::pair<std::ostream*, Severity>>::const_iterator it = m_outputStreams.begin(); it != m_outputStreams.end(); ++it) {
it->first->flush();
}
} catch (...) {
// can't do much here... this is a logging class...
}
}
Logger& Logger::getLogger() {
static Logger instance;
return instance;
}
void Logger::log(Severity severity, const std::string& channel, const std::string& message) {
if (severity < m_minLogLevel) {
return;
}
m_queue.Push(LogEntry{
.when=std::chrono::system_clock::now(),
.severity=severity,
.channel=channel,
.message=message
});
}
void Logger::addStream(std::ostream *stream, Severity severity) {
m_minLogLevel = m_minLogLevel > severity ? severity : m_minLogLevel;
m_outputStreams.push_back(std::pair<std::ostream*, Severity>(stream, severity));
}
void Logger::flush() {
while (!m_queue.IsEmpty()) {
LogEntry entry = m_queue.Pop();
std::stringstream sstream;
//TODO: Write the log time in a more elegant manner.
time_t timestamp = std::chrono::system_clock::to_time_t(entry.when);
char* time_buffer = ctime(&timestamp);
time_buffer[strlen(time_buffer) - 1] = '\0';
sstream << time_buffer << "\t";
switch (entry.severity) {
case Severity::DEBUG:
sstream << "DEBUG";
break;
case Severity::INFO:
sstream << "INFO ";
break;
case Severity::WARNING:
sstream << "WARN ";
break;
case Severity::ERROR:
sstream << "ERROR";
break;
case Severity::FATAL:
sstream << "FATAL";
break;
}
sstream << " [" << entry.channel << "]\t" << entry.message;
const std::string& result_log = sstream.str();
for (std::vector<std::pair<std::ostream*, Severity>>::const_iterator it = m_outputStreams.begin(); it != m_outputStreams.end(); ++it) {
if (entry.severity < it->second) {
// message severity isn't high enough for this logger.
continue;
}
std::ostream& current_stream = *(it->first);
// catch an exception in case we get a broken pipe or something...
try {
current_stream << result_log << std::endl;
} catch (...) {
// do nothing.
}
}
}
}
void Logger::worker(Logger *me) {
std::unique_lock<std::mutex> lk (me->m_runningMutex);
while (true) {
me->flush();
if (std::cv_status::no_timeout == me->m_runningCondition.wait_for(lk, std::chrono::seconds(5))) {
break;
}
}
}
}
}

83
src/logging.hpp Normal file
View file

@ -0,0 +1,83 @@
/*
* Copyright © 2012-2017 Naim A.
*
* This file is part of UDPT.
*
* UDPT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* UDPT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with UDPT. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include <ostream>
#include <thread>
#include <atomic>
#include <condition_variable>
#include "MessageQueue.hpp"
#define LOG(severity, channel, message) \
{\
std::stringstream __sstream; \
__sstream << message; \
UDPT::Logging::Logger::getLogger().log(severity, channel, __sstream.str()); \
}
#define LOG_INFO(channel, message) LOG(UDPT::Logging::Severity::INFO, channel, message)
#define LOG_DEBUG(channel, message) LOG(UDPT::Logging::Severity::DEBUG, channel, message)
#define LOG_WARN(channel, message) LOG(UDPT::Logging::Severity::WARNING, channel, message)
#define LOG_ERR(channel, message) LOG(UDPT::Logging::Severity::ERROR, channel, message)
#define LOG_FATAL(channel, message) LOG(UDPT::Logging::Severity::FATAL, channel, message)
namespace UDPT {
namespace Logging {
enum Severity {
UNSET = 0,
DEBUG = 10,
INFO = 20,
WARNING = 30,
ERROR = 40,
FATAL = 50
};
struct LogEntry {
const std::chrono::time_point<std::chrono::system_clock> when;
Severity severity;
const std::string channel;
const std::string message;
};
class Logger {
public:
static Logger& getLogger();
void log(Severity severity, const std::string& channel, const std::string& message);
void addStream(std::ostream *, Severity minSeverity=INFO);
private:
Logger();
virtual ~Logger();
static void worker(Logger*);
void flush();
std::vector<std::pair<std::ostream*, UDPT::Logging::Severity>> m_outputStreams;
UDPT::Utils::MessageQueue<struct LogEntry> m_queue;
std::thread m_workerThread;
std::atomic_bool m_cleaningUp;
std::mutex m_runningMutex;
std::condition_variable m_runningCondition;
Severity m_minLogLevel;
};
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -25,8 +25,6 @@
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include "multiplatform.h" #include "multiplatform.h"
#include "udpTracker.hpp" #include "udpTracker.hpp"
@ -34,35 +32,39 @@
#include "http/webapp.hpp" #include "http/webapp.hpp"
#include "tracker.hpp" #include "tracker.hpp"
#include "service.hpp" #include "service.hpp"
#include "logging.hpp"
static void _signal_handler(int sig) extern "C" void _signal_handler(int sig)
{ {
switch (sig) switch (sig) {
{ case SIGTERM:
case SIGTERM: case SIGQUIT:
UDPT::Tracker::getInstance().stop(); case SIGINT: {
break; LOG_INFO("core", "Received signal " << sig << ", requesting to stop tracker");
} UDPT::Tracker::getInstance().stop();
break;
}
}
} }
#ifdef linux #ifdef linux
static void daemonize(const boost::program_options::variables_map& conf) static void daemonize(const boost::program_options::variables_map& conf)
{ {
if (1 == ::getppid()) return; // already a daemon if (1 == ::getppid()) return; // already a daemon
int r = ::fork(); int r = ::fork();
if (0 > r) ::exit(-1); // failed to daemonize. if (0 > r) ::exit(-1); // failed to daemonize.
if (0 < r) ::exit(0); // parent exists. if (0 < r) ::exit(0); // parent exists.
::umask(0); ::umask(0);
::setsid(); ::setsid();
// close all fds. // close all fds.
for (int i = ::getdtablesize(); i >=0; --i) for (int i = ::getdtablesize(); i >=0; --i)
{ {
::close(i); ::close(i);
} }
::chdir(conf["daemon.chdir"].as<std::string>().c_str()); ::chdir(conf["daemon.chdir"].as<std::string>().c_str());
} }
#endif #endif
@ -70,159 +72,163 @@ static void daemonize(const boost::program_options::variables_map& conf)
#ifdef WIN32 #ifdef WIN32
void _close_wsa() void _close_wsa()
{ {
::WSACleanup(); ::WSACleanup();
} }
#endif #endif
#ifdef TEST
int real_main(int argc, char *argv[])
#else
int main(int argc, char *argv[]) int main(int argc, char *argv[])
#endif
{ {
#ifdef WIN32 #ifdef WIN32
WSADATA wsadata; WSADATA wsadata;
::WSAStartup(MAKEWORD(2, 2), &wsadata); ::WSAStartup(MAKEWORD(2, 2), &wsadata);
::atexit(_close_wsa); ::atexit(_close_wsa);
#endif #endif
boost::program_options::options_description commandLine("Command line options"); boost::program_options::options_description commandLine("Command line options");
commandLine.add_options() commandLine.add_options()
("help,h", "produce help message") ("help,h", "produce help message")
("all-help", "displays all help") ("all-help", "displays all help")
("test,t", "test configuration file") ("test,t", "test configuration file")
("config,c", boost::program_options::value<std::string>()->default_value("/etc/udpt.conf"), "configuration file to use") ("config,c", boost::program_options::value<std::string>()->default_value("/etc/udpt.conf"), "configuration file to use")
#ifdef linux #ifdef linux
("interactive,i", "doesn't start as daemon") ("interactive,i", "doesn't start as daemon")
#endif #endif
#ifdef WIN32 #ifdef WIN32
("service,s", boost::program_options::value<std::string>(), "start/stop/install/uninstall service") ("service,s", boost::program_options::value<std::string>(), "start/stop/install/uninstall service")
#endif #endif
; ;
const boost::program_options::options_description& configOptions = Tracker::getConfigOptions(); const boost::program_options::options_description& configOptions = Tracker::getConfigOptions();
boost::program_options::variables_map var_map; boost::program_options::variables_map var_map;
boost::program_options::store(boost::program_options::parse_command_line(argc, argv, commandLine), var_map); boost::program_options::store(boost::program_options::parse_command_line(argc, argv, commandLine), var_map);
boost::program_options::notify(var_map); boost::program_options::notify(var_map);
if (var_map.count("help")) if (var_map.count("help"))
{ {
std::cout << "UDP Tracker (UDPT) " << VERSION << " (" << PLATFORM << ")" << std::endl std::cout << "UDP Tracker (UDPT) " << VERSION << " (" << PLATFORM << ")" << std::endl
<< "Copyright 2012-2016 Naim A. <naim94a@gmail.com>" << std::endl << "Copyright 2012-2016 Naim A. <naim94a@gmail.com>" << std::endl
<< "Build Date: " << __DATE__ << std::endl << std::endl; << "Build Date: " << __DATE__ << std::endl << std::endl;
std::cout << commandLine << std::endl; std::cout << commandLine << std::endl;
return 0; return 0;
} }
if (var_map.count("all-help")) if (var_map.count("all-help"))
{ {
std::cout << commandLine << std::endl; std::cout << commandLine << std::endl;
std::cout << configOptions << std::endl; std::cout << configOptions << std::endl;
return 0; return 0;
} }
std::string config_filename(var_map["config"].as<std::string>()); std::string config_filename(var_map["config"].as<std::string>());
bool isTest = (0 != var_map.count("test")); bool isTest = (0 != var_map.count("test"));
if (var_map.count("config")) if (var_map.count("config"))
{ {
try try
{ {
boost::program_options::basic_parsed_options<wchar_t> parsed_options = boost::program_options::parse_config_file<wchar_t>(config_filename.c_str(), configOptions); boost::program_options::basic_parsed_options<wchar_t> parsed_options = boost::program_options::parse_config_file<wchar_t>(config_filename.c_str(), configOptions);
boost::program_options::store( boost::program_options::store(
parsed_options, parsed_options,
var_map); var_map);
} }
catch (const boost::program_options::error& ex) catch (const boost::program_options::error& ex)
{ {
std::cerr << "ERROR: " << ex.what() << std::endl; std::cerr << "ERROR: " << ex.what() << std::endl;
return -1; return -1;
} }
if (isTest) if (isTest)
{ {
std::cout << "Config OK" << std::endl; std::cout << "Config OK" << std::endl;
return 0; return 0;
} }
} }
boost::log::sources::severity_channel_logger_mt<> logger(boost::log::keywords::channel = "main");
Tracker::setupLogging(var_map, logger);
#ifdef linux #ifdef linux
if (!var_map.count("interactive")) if (!var_map.count("interactive"))
{ {
daemonize(var_map); daemonize(var_map);
} }
::signal(SIGTERM, _signal_handler); ::signal(SIGTERM, _signal_handler);
::signal(SIGINT, _signal_handler);
#endif #endif
#ifdef WIN32 #ifdef WIN32
UDPT::Service svc(var_map); UDPT::Service svc(var_map);
if (var_map.count("service")) if (var_map.count("service"))
{ {
const std::string& action = var_map["service"].as<std::string>(); const std::string& action = var_map["service"].as<std::string>();
try try
{ {
if ("install" == action) if ("install" == action)
{ {
std::cerr << "Installing service..." << std::endl; std::cerr << "Installing service..." << std::endl;
svc.install(var_map["config"].as<std::string>()); svc.install(var_map["config"].as<std::string>());
std::cerr << "Installed." << std::endl; std::cerr << "Installed." << std::endl;
} }
else if ("uninstall" == action) else if ("uninstall" == action)
{ {
std::cerr << "Removing service..." << std::endl; std::cerr << "Removing service..." << std::endl;
svc.uninstall(); svc.uninstall();
std::cerr << "Removed." << std::endl; std::cerr << "Removed." << std::endl;
} }
else if ("start" == action) else if ("start" == action)
{ {
svc.start(); svc.start();
} }
else if ("stop" == action) else if ("stop" == action)
{ {
svc.stop(); svc.stop();
} }
else else
{ {
std::cerr << "No such service command." << std::endl; std::cerr << "No such service command." << std::endl;
return -1; return -1;
} }
} }
catch (const UDPT::OSError& ex) catch (const UDPT::OSError& ex)
{ {
std::cerr << "An operating system error occurred: " << ex.what() << std::endl; std::cerr << "An operating system error occurred: " << ex.what() << std::endl;
return -1; return -1;
} }
return 0; return 0;
} }
try try
{ {
svc.setup(); svc.setup();
return 0; return 0;
} }
catch (const OSError& err) catch (const OSError& err)
{ {
if (ERROR_FAILED_SERVICE_CONTROLLER_CONNECT != err.getErrorCode()) if (ERROR_FAILED_SERVICE_CONTROLLER_CONNECT != err.getErrorCode())
{ {
BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Failed to start as a Windows service: (" << err.getErrorCode() << "): " << err.what(); BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Failed to start as a Windows service: (" << err.getErrorCode() << "): " << err.what();
return -1; return -1;
} }
} }
#endif #endif
try try
{ {
Tracker& tracker = UDPT::Tracker::getInstance(); Tracker& tracker = UDPT::Tracker::getInstance();
tracker.start(var_map); tracker.start(var_map);
tracker.wait(); tracker.wait();
} }
catch (const UDPT::UDPTException& ex) catch (const UDPT::UDPTException& ex)
{ {
BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "UDPT exception: (" << ex.getErrorCode() << "): " << ex.what(); std::cerr << "UDPT exception: (" << ex.getErrorCode() << "): " << ex.what();
return -1; return -1;
} }
return 0; LOG_INFO("core", "UDPT terminated.");
return 0;
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -23,234 +23,234 @@
namespace UDPT namespace UDPT
{ {
SERVICE_STATUS_HANDLE Service::s_hServiceStatus = nullptr; SERVICE_STATUS_HANDLE Service::s_hServiceStatus = nullptr;
SERVICE_STATUS Service::s_serviceStatus = { 0 }; SERVICE_STATUS Service::s_serviceStatus = { 0 };
Service::Service(const boost::program_options::variables_map& conf) : m_conf(conf) Service::Service(const boost::program_options::variables_map& conf) : m_conf(conf)
{ {
} }
Service::~Service() Service::~Service()
{ {
} }
void Service::install(const std::string& config_path) void Service::install(const std::string& config_path)
{ {
std::string& binaryPath = getFilename(); std::string& binaryPath = getFilename();
binaryPath = "\"" + binaryPath + "\" -c \"" + config_path + "\""; binaryPath = "\"" + binaryPath + "\" -c \"" + config_path + "\"";
std::shared_ptr<void> svcMgr = getServiceManager(SC_MANAGER_CREATE_SERVICE); std::shared_ptr<void> svcMgr = getServiceManager(SC_MANAGER_CREATE_SERVICE);
{ {
SC_HANDLE installedService = ::CreateService(reinterpret_cast<SC_HANDLE>(svcMgr.get()), SC_HANDLE installedService = ::CreateService(reinterpret_cast<SC_HANDLE>(svcMgr.get()),
m_conf["service.name"].as<std::string>().c_str(), m_conf["service.name"].as<std::string>().c_str(),
"UDPT Tracker", "UDPT Tracker",
SC_MANAGER_CREATE_SERVICE, SC_MANAGER_CREATE_SERVICE,
SERVICE_WIN32_OWN_PROCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL, SERVICE_ERROR_NORMAL,
binaryPath.c_str(), binaryPath.c_str(),
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL, NULL,
NULL NULL
); );
if (nullptr == installedService) if (nullptr == installedService)
{ {
throw OSError(); throw OSError();
} }
::CloseServiceHandle(installedService); ::CloseServiceHandle(installedService);
} }
} }
void Service::uninstall() void Service::uninstall()
{ {
std::shared_ptr<void> service = getService(DELETE); std::shared_ptr<void> service = getService(DELETE);
BOOL bRes = ::DeleteService(reinterpret_cast<SC_HANDLE>(service.get())); BOOL bRes = ::DeleteService(reinterpret_cast<SC_HANDLE>(service.get()));
if (FALSE == bRes) if (FALSE == bRes)
{ {
throw OSError(); throw OSError();
} }
} }
void Service::start() void Service::start()
{ {
std::shared_ptr<void> hSvc = getService(SERVICE_START); std::shared_ptr<void> hSvc = getService(SERVICE_START);
BOOL bRes = ::StartService(reinterpret_cast<SC_HANDLE>(hSvc.get()), 0, NULL); BOOL bRes = ::StartService(reinterpret_cast<SC_HANDLE>(hSvc.get()), 0, NULL);
if (FALSE == bRes) if (FALSE == bRes)
{ {
throw OSError(); throw OSError();
} }
} }
void Service::stop() void Service::stop()
{ {
SERVICE_STATUS status = { 0 }; SERVICE_STATUS status = { 0 };
std::shared_ptr<void> hSvc = getService(SERVICE_STOP); std::shared_ptr<void> hSvc = getService(SERVICE_STOP);
BOOL bRes = ::ControlService(reinterpret_cast<SC_HANDLE>(hSvc.get()), SERVICE_CONTROL_STOP, &status); BOOL bRes = ::ControlService(reinterpret_cast<SC_HANDLE>(hSvc.get()), SERVICE_CONTROL_STOP, &status);
if (FALSE == bRes) if (FALSE == bRes)
{ {
throw OSError(); throw OSError();
} }
} }
void Service::setup() void Service::setup()
{ {
SERVICE_TABLE_ENTRY service[] = { SERVICE_TABLE_ENTRY service[] = {
{ const_cast<char*>(m_conf["service.name"].as<std::string>().c_str()), reinterpret_cast<LPSERVICE_MAIN_FUNCTION>(&Service::serviceMain) }, { const_cast<char*>(m_conf["service.name"].as<std::string>().c_str()), reinterpret_cast<LPSERVICE_MAIN_FUNCTION>(&Service::serviceMain) },
{0, 0} {0, 0}
}; };
if (FALSE == ::StartServiceCtrlDispatcher(service)) if (FALSE == ::StartServiceCtrlDispatcher(service))
{ {
throw OSError(); throw OSError();
} }
} }
DWORD Service::handler(DWORD controlCode, DWORD dwEventType, LPVOID eventData, LPVOID context) DWORD Service::handler(DWORD controlCode, DWORD dwEventType, LPVOID eventData, LPVOID context)
{ {
switch (controlCode) switch (controlCode)
{ {
case SERVICE_CONTROL_INTERROGATE: case SERVICE_CONTROL_INTERROGATE:
return NO_ERROR; return NO_ERROR;
case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_STOP:
{ {
reportServiceStatus(SERVICE_STOP_PENDING, 0, 3000); reportServiceStatus(SERVICE_STOP_PENDING, 0, 3000);
Tracker::getInstance().stop(); Tracker::getInstance().stop();
return NO_ERROR; return NO_ERROR;
} }
default: default:
return ERROR_CALL_NOT_IMPLEMENTED; return ERROR_CALL_NOT_IMPLEMENTED;
} }
} }
void Service::reportServiceStatus(DWORD currentState, DWORD dwExitCode, DWORD dwWaitHint) void Service::reportServiceStatus(DWORD currentState, DWORD dwExitCode, DWORD dwWaitHint)
{ {
static DWORD checkpoint = 1; static DWORD checkpoint = 1;
if (currentState == SERVICE_STOPPED || currentState == SERVICE_RUNNING) if (currentState == SERVICE_STOPPED || currentState == SERVICE_RUNNING)
{ {
checkpoint = 0; checkpoint = 0;
} }
else else
{ {
++checkpoint; ++checkpoint;
} }
switch (currentState) switch (currentState)
{ {
case SERVICE_RUNNING: case SERVICE_RUNNING:
s_serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; s_serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
break; break;
default: default:
s_serviceStatus.dwControlsAccepted = 0; s_serviceStatus.dwControlsAccepted = 0;
} }
s_serviceStatus.dwCheckPoint = checkpoint; s_serviceStatus.dwCheckPoint = checkpoint;
s_serviceStatus.dwCurrentState = currentState; s_serviceStatus.dwCurrentState = currentState;
s_serviceStatus.dwWin32ExitCode = dwExitCode; s_serviceStatus.dwWin32ExitCode = dwExitCode;
s_serviceStatus.dwWaitHint = dwWaitHint; s_serviceStatus.dwWaitHint = dwWaitHint;
::SetServiceStatus(s_hServiceStatus, &s_serviceStatus); ::SetServiceStatus(s_hServiceStatus, &s_serviceStatus);
} }
VOID Service::serviceMain(DWORD argc, LPCSTR argv[]) VOID Service::serviceMain(DWORD argc, LPCSTR argv[])
{ {
boost::log::sources::severity_channel_logger_mt<> logger(boost::log::keywords::channel = "service"); boost::log::sources::severity_channel_logger_mt<> logger(boost::log::keywords::channel = "service");
wchar_t *commandLine = ::GetCommandLineW(); wchar_t *commandLine = ::GetCommandLineW();
int argCount = 0; int argCount = 0;
std::shared_ptr<LPWSTR> args(::CommandLineToArgvW(commandLine, &argCount), ::LocalFree); std::shared_ptr<LPWSTR> args(::CommandLineToArgvW(commandLine, &argCount), ::LocalFree);
if (nullptr == args) if (nullptr == args)
{ {
BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Failed parse command-line."; BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Failed parse command-line.";
::exit(-1); ::exit(-1);
} }
if (3 != argCount) if (3 != argCount)
{ {
BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Bad command-line length (must have exactly 2 arguments)."; BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Bad command-line length (must have exactly 2 arguments).";
::exit(-1); ::exit(-1);
} }
if (std::wstring(args.get()[1]) != L"-c") if (std::wstring(args.get()[1]) != L"-c")
{ {
BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Argument 1 must be \"-c\"."; BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Argument 1 must be \"-c\".";
::exit(-1); ::exit(-1);
} }
std::wstring wFilename(args.get()[2]); std::wstring wFilename(args.get()[2]);
std::string cFilename(wFilename.begin(), wFilename.end()); std::string cFilename(wFilename.begin(), wFilename.end());
boost::program_options::options_description& configOptions = UDPT::Tracker::getConfigOptions(); boost::program_options::options_description& configOptions = UDPT::Tracker::getConfigOptions();
boost::program_options::variables_map config; boost::program_options::variables_map config;
boost::program_options::basic_parsed_options<wchar_t> parsed_options = boost::program_options::parse_config_file<wchar_t>(cFilename.c_str(), configOptions); boost::program_options::basic_parsed_options<wchar_t> parsed_options = boost::program_options::parse_config_file<wchar_t>(cFilename.c_str(), configOptions);
boost::program_options::store(parsed_options, config); boost::program_options::store(parsed_options, config);
s_hServiceStatus = ::RegisterServiceCtrlHandlerEx(config["service.name"].as<std::string>().c_str(), Service::handler, NULL); s_hServiceStatus = ::RegisterServiceCtrlHandlerEx(config["service.name"].as<std::string>().c_str(), Service::handler, NULL);
if (nullptr == s_hServiceStatus) if (nullptr == s_hServiceStatus)
{ {
BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Failed to register service control handler."; BOOST_LOG_SEV(logger, boost::log::trivial::fatal) << "Failed to register service control handler.";
::exit(-1); ::exit(-1);
} }
s_serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; s_serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
s_serviceStatus.dwServiceSpecificExitCode = 0; s_serviceStatus.dwServiceSpecificExitCode = 0;
reportServiceStatus(SERVICE_START_PENDING, 0, 0); reportServiceStatus(SERVICE_START_PENDING, 0, 0);
{ {
UDPT::Tracker& tracker = UDPT::Tracker::getInstance(); UDPT::Tracker& tracker = UDPT::Tracker::getInstance();
tracker.start(config); tracker.start(config);
reportServiceStatus(SERVICE_RUNNING, 0, 0); reportServiceStatus(SERVICE_RUNNING, 0, 0);
tracker.wait(); tracker.wait();
reportServiceStatus(SERVICE_STOPPED, 0, 0); reportServiceStatus(SERVICE_STOPPED, 0, 0);
} }
} }
std::shared_ptr<void> Service::getService(DWORD access) std::shared_ptr<void> Service::getService(DWORD access)
{ {
std::shared_ptr<void> serviceManager = getServiceManager(access); std::shared_ptr<void> serviceManager = getServiceManager(access);
{ {
SC_HANDLE service = ::OpenService(reinterpret_cast<SC_HANDLE>(serviceManager.get()), m_conf["service.name"].as<std::string>().c_str(), access); SC_HANDLE service = ::OpenService(reinterpret_cast<SC_HANDLE>(serviceManager.get()), m_conf["service.name"].as<std::string>().c_str(), access);
if (nullptr == service) if (nullptr == service)
{ {
throw OSError(); throw OSError();
} }
return std::shared_ptr<void>(service, ::CloseServiceHandle); return std::shared_ptr<void>(service, ::CloseServiceHandle);
} }
} }
std::shared_ptr<void> Service::getServiceManager(DWORD access) std::shared_ptr<void> Service::getServiceManager(DWORD access)
{ {
SC_HANDLE svcMgr = ::OpenSCManager(NULL, NULL, access); SC_HANDLE svcMgr = ::OpenSCManager(NULL, NULL, access);
if (nullptr == svcMgr) if (nullptr == svcMgr)
{ {
throw OSError(); throw OSError();
} }
return std::shared_ptr<void>(svcMgr, ::CloseServiceHandle); return std::shared_ptr<void>(svcMgr, ::CloseServiceHandle);
} }
std::string Service::getFilename() std::string Service::getFilename()
{ {
char filename[MAX_PATH]; char filename[MAX_PATH];
DWORD dwRet = ::GetModuleFileName(NULL, filename, sizeof(filename) / sizeof(char)); DWORD dwRet = ::GetModuleFileName(NULL, filename, sizeof(filename) / sizeof(char));
if (0 == dwRet) if (0 == dwRet)
{ {
throw OSError(); throw OSError();
} }
return std::string(filename); return std::string(filename);
} }
} }
#endif #endif

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -28,42 +28,42 @@
#ifdef WIN32 #ifdef WIN32
namespace UDPT namespace UDPT
{ {
class Service class Service
{ {
public: public:
Service(const boost::program_options::variables_map& conf); Service(const boost::program_options::variables_map& conf);
virtual ~Service(); virtual ~Service();
void install(const std::string& config_path); void install(const std::string& config_path);
void uninstall(); void uninstall();
void start(); void start();
void stop(); void stop();
void setup(); void setup();
private: private:
const boost::program_options::variables_map& m_conf; const boost::program_options::variables_map& m_conf;
static SERVICE_STATUS_HANDLE s_hServiceStatus; static SERVICE_STATUS_HANDLE s_hServiceStatus;
static SERVICE_STATUS s_serviceStatus; static SERVICE_STATUS s_serviceStatus;
std::shared_ptr<void> getService(DWORD access); std::shared_ptr<void> getService(DWORD access);
static DWORD WINAPI handler(DWORD controlCode, DWORD dwEventType, LPVOID eventData, LPVOID context); static DWORD WINAPI handler(DWORD controlCode, DWORD dwEventType, LPVOID eventData, LPVOID context);
static void reportServiceStatus(DWORD currentState, DWORD dwExitCode, DWORD dwWaitHint); static void reportServiceStatus(DWORD currentState, DWORD dwExitCode, DWORD dwWaitHint);
static VOID WINAPI serviceMain(DWORD argc, LPCSTR argv[]); static VOID WINAPI serviceMain(DWORD argc, LPCSTR argv[]);
static std::shared_ptr<void> getServiceManager(DWORD access); static std::shared_ptr<void> getServiceManager(DWORD access);
static std::string getFilename(); static std::string getFilename();
}; };
} }
#endif #endif

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -22,32 +22,32 @@
void m_byteswap (void *dest, void *src, int sz) void m_byteswap (void *dest, void *src, int sz)
{ {
int i; int i;
for (i = 0;i < sz;i++) for (i = 0;i < sz;i++)
{ {
((char*)dest)[i] = ((char*)src)[(sz - 1) - i]; ((char*)dest)[i] = ((char*)src)[(sz - 1) - i];
} }
} }
uint16_t m_hton16(uint16_t n) uint16_t m_hton16(uint16_t n)
{ {
uint16_t r; uint16_t r;
m_byteswap (&r, &n, 2); m_byteswap (&r, &n, 2);
return r; return r;
} }
uint64_t m_hton64 (uint64_t n) uint64_t m_hton64 (uint64_t n)
{ {
uint64_t r; uint64_t r;
m_byteswap (&r, &n, 8); m_byteswap (&r, &n, 8);
return r; return r;
} }
uint32_t m_hton32 (uint32_t n) uint32_t m_hton32 (uint32_t n)
{ {
uint64_t r; uint64_t r;
m_byteswap (&r, &n, 4); m_byteswap (&r, &n, 4);
return r; return r;
} }
@ -55,11 +55,11 @@ static const char hexadecimal[] = "0123456789abcdef";
void to_hex_str (const uint8_t *hash, char *data) void to_hex_str (const uint8_t *hash, char *data)
{ {
int i; int i;
for (i = 0;i < 20;i++) for (i = 0;i < 20;i++)
{ {
data[i * 2] = hexadecimal[hash[i] / 16]; data[i * 2] = hexadecimal[hash[i] / 16];
data[i * 2 + 1] = hexadecimal[hash[i] % 16]; data[i * 2 + 1] = hexadecimal[hash[i] % 16];
} }
data[40] = '\0'; data[40] = '\0';
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -16,52 +16,60 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with UDPT. If not, see <http://www.gnu.org/licenses/>. * along with UDPT. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <iostream>
#include <fstream>
#include "tracker.hpp" #include "tracker.hpp"
#include "logging.hpp"
namespace UDPT namespace UDPT
{ {
Tracker::Tracker() : m_logger(boost::log::keywords::channel = "TRACKER") Tracker::Tracker() : m_logStream(nullptr)
{ {
BOOST_LOG_SEV(m_logger, boost::log::trivial::debug) << "Initialized Tracker";
} }
Tracker::~Tracker() Tracker::~Tracker()
{ {
BOOST_LOG_SEV(m_logger, boost::log::trivial::debug) << "Destroying Tracker..."; try {
if (nullptr != m_logStream) {
m_logStream->flush();
delete m_logStream;
m_logStream = nullptr;
}
} catch (...) {
}
} }
void Tracker::stop() void Tracker::stop()
{ {
BOOST_LOG_SEV(m_logger, boost::log::trivial::info) << "Requesting Tracker to terminate."; LOG_INFO("tracker", "Requesting components to terminate...");
m_udpTracker->stop(); m_udpTracker->stop();
wait();
// cause other components to destruct.
m_apiSrv = nullptr; m_apiSrv = nullptr;
m_webApp = nullptr; m_webApp = nullptr;
m_udpTracker = nullptr;
} }
void Tracker::wait() void Tracker::wait()
{ {
m_udpTracker->wait(); m_udpTracker->wait();
BOOST_LOG_SEV(m_logger, boost::log::trivial::info) << "Tracker terminated.";
} }
void Tracker::start(const boost::program_options::variables_map& conf) void Tracker::start(const boost::program_options::variables_map& conf)
{ {
BOOST_LOG_SEV(m_logger, boost::log::trivial::debug) << "Starting Tracker..."; setupLogging(conf);
m_udpTracker = std::shared_ptr<UDPTracker>(new UDPTracker(conf)); LOG_INFO("core", "Initializing...");
m_udpTracker = std::shared_ptr<UDPTracker>(new UDPTracker(conf));
if (conf["apiserver.enable"].as<bool>()) if (conf["apiserver.enable"].as<bool>())
{ {
BOOST_LOG_SEV(m_logger, boost::log::trivial::info) << "Initializing and deploying WebAPI...";
m_apiSrv = std::shared_ptr<UDPT::Server::HTTPServer>(new UDPT::Server::HTTPServer(conf)); m_apiSrv = std::shared_ptr<UDPT::Server::HTTPServer>(new UDPT::Server::HTTPServer(conf));
m_webApp = std::shared_ptr<UDPT::Server::WebApp>(new UDPT::Server::WebApp(m_apiSrv, m_udpTracker->m_conn.get(), conf)); m_webApp = std::shared_ptr<UDPT::Server::WebApp>(new UDPT::Server::WebApp(m_apiSrv, m_udpTracker->m_conn.get(), conf));
m_webApp->deploy(); m_webApp->deploy();
} }
m_udpTracker->start(); m_udpTracker->start();
BOOST_LOG_SEV(m_logger, boost::log::trivial::info) << "Tracker started.";
} }
Tracker& Tracker::getInstance() Tracker& Tracker::getInstance()
@ -71,75 +79,73 @@ namespace UDPT
return s_tracker; return s_tracker;
} }
boost::program_options::options_description Tracker::getConfigOptions() boost::program_options::options_description Tracker::getConfigOptions()
{ {
boost::program_options::options_description configOptions("Configuration options"); boost::program_options::options_description configOptions("Configuration options");
configOptions.add_options() configOptions.add_options()
("db.driver", boost::program_options::value<std::string>()->default_value("sqlite3"), "database driver to use") ("db.driver", boost::program_options::value<std::string>()->default_value("sqlite3"), "database driver to use")
("db.param", boost::program_options::value<std::string>()->default_value("/var/lib/udpt.db"), "database connection parameters") ("db.param", boost::program_options::value<std::string>()->default_value("/var/lib/udpt.db"), "database connection parameters")
("tracker.is_dynamic", boost::program_options::value<bool>()->default_value(true), "Sets if the tracker is dynamic") ("tracker.is_dynamic", boost::program_options::value<bool>()->default_value(true), "Sets if the tracker is dynamic")
("tracker.port", boost::program_options::value<unsigned short>()->default_value(6969), "UDP port to listen on") ("tracker.port", boost::program_options::value<unsigned short>()->default_value(6969), "UDP port to listen on")
("tracker.threads", boost::program_options::value<unsigned>()->default_value(5), "threads to run (UDP only)") ("tracker.threads", boost::program_options::value<unsigned>()->default_value(5), "threads to run (UDP only)")
("tracker.allow_remotes", boost::program_options::value<bool>()->default_value(true), "allows clients to report remote IPs") ("tracker.allow_remotes", boost::program_options::value<bool>()->default_value(true), "allows clients to report remote IPs")
("tracker.allow_iana_ips", boost::program_options::value<bool>()->default_value(false), "allows IANA reserved IPs to connect (useful for debugging)") ("tracker.allow_iana_ips", boost::program_options::value<bool>()->default_value(false), "allows IANA reserved IPs to connect (useful for debugging)")
("tracker.announce_interval", boost::program_options::value<unsigned>()->default_value(1800), "announce interval") ("tracker.announce_interval", boost::program_options::value<unsigned>()->default_value(1800), "announce interval")
("tracker.cleanup_interval", boost::program_options::value<unsigned>()->default_value(120), "sets database cleanup interval") ("tracker.cleanup_interval", boost::program_options::value<unsigned>()->default_value(120), "sets database cleanup interval")
("apiserver.enable", boost::program_options::value<bool>()->default_value(0), "Enable API server?") ("apiserver.enable", boost::program_options::value<bool>()->default_value(0), "Enable API server?")
("apiserver.threads", boost::program_options::value<unsigned short>()->default_value(1), "threads for API server") ("apiserver.threads", boost::program_options::value<unsigned short>()->default_value(1), "threads for API server")
("apiserver.port", boost::program_options::value<unsigned short>()->default_value(6969), "TCP port to listen on") ("apiserver.port", boost::program_options::value<unsigned short>()->default_value(6969), "TCP port to listen on")
("logging.filename", boost::program_options::value<std::string>()->default_value("/var/log/udpt.log"), "file to write logs to") ("logging.filename", boost::program_options::value<std::string>()->default_value("/var/log/udpt.log"), "file to write logs to")
("logging.level", boost::program_options::value<std::string>()->default_value("warning"), "log level (fatal/error/warning/info/debug/trace)") ("logging.level", boost::program_options::value<std::string>()->default_value("warning"), "log level (fatal/error/warning/info/debug)")
#ifdef linux #ifdef linux
("daemon.chdir", boost::program_options::value<std::string>()->default_value("/"), "home directory for daemon") ("daemon.chdir", boost::program_options::value<std::string>()->default_value("/"), "home directory for daemon")
#endif #endif
#ifdef WIN32 #ifdef WIN32
("service.name", boost::program_options::value<std::string>()->default_value("udpt"), "service name to use") ("service.name", boost::program_options::value<std::string>()->default_value("udpt"), "service name to use")
#endif #endif
; ;
return configOptions; return configOptions;
} }
void Tracker::setupLogging(const boost::program_options::variables_map& config, boost::log::sources::severity_channel_logger_mt<>& logger) void Tracker::setupLogging(const boost::program_options::variables_map& va_map) {
{ Logging::Logger& logger = Logging::Logger::getLogger();
boost::log::add_common_attributes();
boost::shared_ptr<boost::log::sinks::text_file_backend> logBackend = boost::make_shared<boost::log::sinks::text_file_backend>(
boost::log::keywords::file_name = config["logging.filename"].as<std::string>(),
boost::log::keywords::auto_flush = true,
boost::log::keywords::open_mode = std::ios::out | std::ios::app
);
typedef boost::log::sinks::asynchronous_sink<boost::log::sinks::text_file_backend> udptSink_t;
boost::shared_ptr<udptSink_t> async_sink(new udptSink_t(logBackend));
async_sink->set_formatter(
boost::log::expressions::stream
<< boost::log::expressions::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y-%m-%d %H:%M:%S") << " "
<< boost::log::expressions::attr<int>("Severity")
<< " [" << boost::log::expressions::attr<std::string>("Channel") << "] \t"
<< boost::log::expressions::smessage
);
auto loggingCore = boost::log::core::get();
loggingCore->add_sink(async_sink);
std::string severity = config["logging.level"].as<std::string>(); logger.addStream(&std::cerr, Logging::Severity::FATAL);
std::transform(severity.begin(), severity.end(), severity.begin(), ::tolower);
int severityVal = boost::log::trivial::warning;
if ("fatal" == severity) severityVal = boost::log::trivial::fatal;
else if ("error" == severity) severityVal = boost::log::trivial::error;
else if ("warning" == severity) severityVal = boost::log::trivial::warning;
else if ("info" == severity) severityVal = boost::log::trivial::info;
else if ("debug" == severity) severityVal = boost::log::trivial::debug;
else if ("trace" == severity) severityVal = boost::log::trivial::trace;
else
{
BOOST_LOG_SEV(logger, boost::log::trivial::warning) << "Unknown debug level \"" << severity << "\" defaulting to warning";
}
loggingCore->set_filter( Logging::Severity severity;
boost::log::trivial::severity >= severityVal std::string severity_text = va_map["logging.level"].as<std::string>();
); std::transform(severity_text.begin(), severity_text.end(), severity_text.begin(), ::tolower);
}
if (severity_text == "fatal") {
severity = Logging::Severity::FATAL;
} else if (severity_text == "error") {
severity = Logging::Severity::ERROR;
} else if (severity_text == "warning") {
severity = Logging::Severity::WARNING;
} else if (severity_text == "info") {
severity = Logging::Severity::INFO;
} else if (severity_text == "debug") {
severity = Logging::Severity::DEBUG;
} else {
severity = Logging::Severity::UNSET;
}
const std::string& logFileName = va_map["logging.filename"].as<std::string>();
Logging::Severity real_severity = (severity == Logging::Severity::UNSET ? Logging::Severity::INFO : severity);
if (logFileName.length() == 0 || logFileName == "--") {
logger.addStream(&std::cerr, real_severity);
} else {
m_logStream = new ofstream(logFileName, std::ios::app | std::ios::out);
logger.addStream(m_logStream, real_severity);
}
if (severity != real_severity) {
LOG_WARN("core", "'" << severity_text << "' is invalid, log level set to " << real_severity);
}
}
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -21,16 +21,7 @@
#include <memory> #include <memory>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp> #include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/sinks/async_frontend.hpp>
#include <boost/log/keywords/format.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include "multiplatform.h" #include "multiplatform.h"
#include "udpTracker.hpp" #include "udpTracker.hpp"
@ -51,18 +42,18 @@ namespace UDPT
void wait(); void wait();
static Tracker& getInstance(); static Tracker& getInstance();
static boost::program_options::options_description getConfigOptions(); static boost::program_options::options_description getConfigOptions();
static void setupLogging(const boost::program_options::variables_map& config, boost::log::sources::severity_channel_logger_mt<>& logger);
private: private:
std::shared_ptr<UDPT::UDPTracker> m_udpTracker; std::shared_ptr<UDPT::UDPTracker> m_udpTracker;
std::shared_ptr<UDPT::Server::HTTPServer> m_apiSrv; std::shared_ptr<UDPT::Server::HTTPServer> m_apiSrv;
std::shared_ptr<UDPT::Server::WebApp> m_webApp; std::shared_ptr<UDPT::Server::WebApp> m_webApp;
boost::log::sources::severity_channel_logger_mt<> m_logger;
Tracker(); Tracker();
void setupLogging(const boost::program_options::variables_map& va_map);
std::ostream *m_logStream;
}; };
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -18,6 +18,7 @@
*/ */
#include "udpTracker.hpp" #include "udpTracker.hpp"
#include "logging.hpp"
using namespace UDPT::Data; using namespace UDPT::Data;
@ -26,452 +27,424 @@ using namespace UDPT::Data;
namespace UDPT namespace UDPT
{ {
UDPTracker::UDPTracker(const boost::program_options::variables_map& conf) : m_conf(conf), m_logger(boost::log::keywords::channel = "UDPTracker") UDPTracker::UDPTracker(const boost::program_options::variables_map& conf) : m_conf(conf) {
{ this->m_allowRemotes = conf["tracker.allow_remotes"].as<bool>();
this->m_allowRemotes = conf["tracker.allow_remotes"].as<bool>(); this->m_allowIANA_IPs = conf["tracker.allow_iana_ips"].as<bool>();
this->m_allowIANA_IPs = conf["tracker.allow_iana_ips"].as<bool>(); this->m_isDynamic = conf["tracker.is_dynamic"].as<bool>();
this->m_isDynamic = conf["tracker.is_dynamic"].as<bool>();
this->m_announceInterval = conf["tracker.announce_interval"].as<unsigned>(); this->m_announceInterval = conf["tracker.announce_interval"].as<unsigned>();
this->m_cleanupInterval = conf["tracker.cleanup_interval"].as<unsigned>(); this->m_cleanupInterval = conf["tracker.cleanup_interval"].as<unsigned>();
this->m_port = conf["tracker.port"].as<unsigned short>(); this->m_port = conf["tracker.port"].as<unsigned short>();
this->m_threadCount = conf["tracker.threads"].as<unsigned>() + 1; this->m_threadCount = conf["tracker.threads"].as<unsigned>() + 1;
std::list<SOCKADDR_IN> addrs; this->m_localEndpoint.sin_family = AF_INET;
this->m_localEndpoint.sin_port = m_hton16(m_port);
this->m_localEndpoint.sin_addr.s_addr = 0L;
if (addrs.empty()) this->m_conn = std::shared_ptr<DatabaseDriver>(new Data::SQLite3Driver(m_conf, this->m_isDynamic));
{ }
SOCKADDR_IN sa;
sa.sin_port = m_hton16(m_port);
sa.sin_addr.s_addr = 0L;
addrs.push_back(sa);
}
this->m_localEndpoint = addrs.front(); UDPTracker::~UDPTracker() {
}
this->m_conn = std::shared_ptr<DatabaseDriver>(new Data::SQLite3Driver(m_conf, this->m_isDynamic)); void UDPTracker::start() {
} SOCKET sock;
int r, // saves results
i, // loop index
yup; // just to set TRUE
std::string dbname;// saves the Database name.
UDPTracker::~UDPTracker() sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
{ if (sock == INVALID_SOCKET)
stop(); {
} LOG_FATAL("udp-tracker", "Failed to create socket. error=" << errno);
throw UDPT::UDPTException("Failed to create socket");
}
void UDPTracker::start() yup = 1;
{ ::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yup, 1);
SOCKET sock;
int r, // saves results
i, // loop index
yup; // just to set TRUE
std::string dbname;// saves the Database name.
sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); {
if (sock == INVALID_SOCKET) // don't block recvfrom for too long.
{
throw UDPT::UDPTException("Failed to create socket");
}
yup = 1;
::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yup, 1);
{
// don't block recvfrom for too long.
#if defined(linux) #if defined(linux)
timeval timeout = { 0 }; timeval timeout = { 0 };
timeout.tv_sec = 5; timeout.tv_sec = 5;
#elif defined(WIN32) #elif defined(WIN32)
DWORD timeout = 5000; DWORD timeout = 5000;
#else #else
#error Unsupported OS. #error Unsupported OS.
#endif #endif
::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout)); ::setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout));
} }
this->m_localEndpoint.sin_family = AF_INET; this->m_localEndpoint.sin_family = AF_INET;
r = ::bind(sock, reinterpret_cast<SOCKADDR*>(&this->m_localEndpoint), sizeof(SOCKADDR_IN)); r = ::bind(sock, reinterpret_cast<SOCKADDR*>(&this->m_localEndpoint), sizeof(SOCKADDR_IN));
if (r == SOCKET_ERROR) if (r == SOCKET_ERROR)
{ {
#ifdef WIN32 LOG_FATAL("udp-tracker", "Failed to bind socket. error=" << errno);
::closesocket(sock);
#elif defined (linux)
::close(sock);
#endif
throw UDPT::UDPTException("Failed to bind socket.");
}
{ #ifdef WIN32
char buff[INET_ADDRSTRLEN]; ::closesocket(sock);
BOOST_LOG_SEV(m_logger, boost::log::trivial::info) << "UDP tracker bound on " << ::inet_ntop(AF_INET, reinterpret_cast<LPVOID>(&m_localEndpoint.sin_addr), buff, sizeof(buff)) << ":" << htons(m_localEndpoint.sin_port); #elif defined (linux)
} ::close(sock);
#endif
throw UDPT::UDPTException("Failed to bind socket.");
}
this->m_sock = sock; this->m_sock = sock;
// create maintainer thread. LOG_INFO("udp-tracker", "Tracker bound to " << inet_ntoa(this->m_localEndpoint.sin_addr));
m_threads.push_back(boost::thread(UDPTracker::_maintainance_start, this)); // create maintainer thread.
m_threads.push_back(std::thread(UDPTracker::_maintainance_start, this));
LOG_INFO("udp-tracker", "Started maintenance thread @" << m_threads.back().get_id());
for (i = 1;i < this->m_threadCount; i++) m_shouldRun = true;
{ for (i = 1;i < this->m_threadCount; i++)
m_threads.push_back(boost::thread(UDPTracker::_thread_start, this)); {
} m_threads.push_back(std::thread(UDPTracker::_thread_start, this));
} LOG_INFO("udp-tracker", "Started worker thread @" << m_threads.back().get_id());
}
}
void UDPTracker::stop() {
// tell workers to stop running...
m_shouldRun = false;
// stop maintenance thread's sleep...
m_maintenanceCondition.notify_one();
}
/**
* @brief blocks until all threads die.
* @note This method should be called only once, preferably by the main thread.
* **/
void UDPTracker::wait() {
LOG_INFO("udp-tracker", "Waiting for threads to terminate...");
for (std::vector<std::thread>::iterator it = m_threads.begin(); it != m_threads.end(); ++it)
{
it->join();
}
LOG_INFO("udp-tracker", "UDP Tracker terminated");
void UDPTracker::stop()
{
#ifdef linux #ifdef linux
::close(m_sock); ::close(m_sock);
#elif defined (WIN32) #elif defined (WIN32)
::closesocket(m_sock); ::closesocket(m_sock);
#endif #endif
}
BOOST_LOG_SEV(m_logger, boost::log::trivial::warning) << "Interrupting workers..."; int UDPTracker::sendError(UDPTracker* usi, SOCKADDR_IN* remote, uint32_t transactionID, const std::string &msg) {
for (std::vector<boost::thread>::iterator it = m_threads.begin(); it != m_threads.end(); ++it) struct udp_error_response error;
{ int msg_sz, // message size to send.
it->interrupt(); i; // copy loop
} char buff [1024]; // more than reasonable message size...
wait(); error.action = m_hton32 (3);
} error.transaction_id = transactionID;
error.message = (char*)msg.c_str();
void UDPTracker::wait() msg_sz = 4 + 4 + 1 + msg.length();
{
BOOST_LOG_SEV(m_logger, boost::log::trivial::warning) << "Waiting for threads to terminate...";
for (std::vector<boost::thread>::iterator it = m_threads.begin(); it != m_threads.end(); ++it) // test against overflow message. resolves issue 4.
{ if (msg_sz > 1024)
it->join(); return -1;
}
}
int UDPTracker::sendError(UDPTracker* usi, SOCKADDR_IN* remote, uint32_t transactionID, const std::string &msg) ::memcpy(buff, &error, 8);
{ for (i = 8;i <= msg_sz;i++)
struct udp_error_response error; {
int msg_sz, // message size to send. buff[i] = msg[i - 8];
i; // copy loop }
char buff [1024]; // more than reasonable message size...
error.action = m_hton32 (3); ::sendto(usi->m_sock, buff, msg_sz, 0, reinterpret_cast<SOCKADDR*>(remote), sizeof(*remote));
error.transaction_id = transactionID;
error.message = (char*)msg.c_str();
msg_sz = 4 + 4 + 1 + msg.length(); LOG_DEBUG("udp-tracker", "Error sent to " << inet_ntoa(remote->sin_addr) << ", '" << msg << "' (len=" << msg_sz << ")");
// test against overflow message. resolves issue 4. return 0;
if (msg_sz > 1024) }
return -1;
::memcpy(buff, &error, 8); int UDPTracker::handleConnection(UDPTracker *usi, SOCKADDR_IN *remote, char *data) {
for (i = 8;i <= msg_sz;i++) ConnectionRequest *req = reinterpret_cast<ConnectionRequest*>(data);
{ ConnectionResponse resp;
buff[i] = msg[i - 8];
}
::sendto(usi->m_sock, buff, msg_sz, 0, reinterpret_cast<SOCKADDR*>(remote), sizeof(*remote)); resp.action = m_hton32(0);
resp.transaction_id = req->transaction_id;
return 0; if (!usi->m_conn->genConnectionId(&resp.connection_id,
} m_hton32(remote->sin_addr.s_addr),
m_hton16(remote->sin_port)))
{
return 1;
}
int UDPTracker::handleConnection(UDPTracker *usi, SOCKADDR_IN *remote, char *data) ::sendto(usi->m_sock, (char*)&resp, sizeof(ConnectionResponse), 0, (SOCKADDR*)remote, sizeof(SOCKADDR_IN));
{
ConnectionRequest *req = reinterpret_cast<ConnectionRequest*>(data);
ConnectionResponse resp;
resp.action = m_hton32(0); return 0;
resp.transaction_id = req->transaction_id; }
if (!usi->m_conn->genConnectionId(&resp.connection_id, int UDPTracker::handleAnnounce(UDPTracker *usi, SOCKADDR_IN *remote, char *data) {
m_hton32(remote->sin_addr.s_addr), AnnounceRequest *req;
m_hton16(remote->sin_port))) AnnounceResponse *resp;
{ int q, // peer counts
return 1; bSize, // message size
} i; // loop index
DatabaseDriver::TorrentEntry tE;
::sendto(usi->m_sock, (char*)&resp, sizeof(ConnectionResponse), 0, (SOCKADDR*)remote, sizeof(SOCKADDR_IN)); uint8_t buff[1028]; // Reasonable buffer size. (header+168 peers)
{ req = (AnnounceRequest*)data;
char buffer[INET_ADDRSTRLEN];
BOOST_LOG_SEV(usi->m_logger, boost::log::trivial::debug) << "Connection Request from " << ::inet_ntop(AF_INET, &remote->sin_addr, buffer, sizeof(buffer)) << "; cId=" << resp.connection_id << "; tId=" << resp.transaction_id;
}
if (!usi->m_conn->verifyConnectionId(req->connection_id,
m_hton32(remote->sin_addr.s_addr),
m_hton16(remote->sin_port)))
{
return 1;
}
return 0; // change byte order:
} req->port = m_hton16(req->port);
req->ip_address = m_hton32(req->ip_address);
req->downloaded = m_hton64(req->downloaded);
req->event = m_hton32(req->event); // doesn't really matter for this tracker
req->uploaded = m_hton64(req->uploaded);
req->num_want = m_hton32(req->num_want);
req->left = m_hton64(req->left);
int UDPTracker::handleAnnounce(UDPTracker *usi, SOCKADDR_IN *remote, char *data) if (!usi->m_allowRemotes && req->ip_address != 0)
{ {
AnnounceRequest *req; UDPTracker::sendError(usi, remote, req->transaction_id, "Tracker doesn't allow remote IP's; Request ignored.");
AnnounceResponse *resp; return 0;
int q, // peer counts }
bSize, // message size
i; // loop index
DatabaseDriver::TorrentEntry tE;
uint8_t buff[1028]; // Reasonable buffer size. (header+168 peers) if (!usi->m_conn->isTorrentAllowed(req->info_hash))
{
UDPTracker::sendError(usi, remote, req->transaction_id, "info_hash not registered.");
return 0;
}
req = (AnnounceRequest*)data; // load peers
q = 30;
if (req->num_want >= 1)
q = std::min<int>(q, req->num_want);
if (!usi->m_conn->verifyConnectionId(req->connection_id, DatabaseDriver::TrackerEvents event;
m_hton32(remote->sin_addr.s_addr),
m_hton16(remote->sin_port)))
{
return 1;
}
// change byte order: {
req->port = m_hton16(req->port); std::shared_ptr<DatabaseDriver::PeerEntry> peersSptr = std::shared_ptr<DatabaseDriver::PeerEntry>(new DatabaseDriver::PeerEntry[q]);
req->ip_address = m_hton32(req->ip_address); DatabaseDriver::PeerEntry *peers = peersSptr.get();
req->downloaded = m_hton64(req->downloaded);
req->event = m_hton32(req->event); // doesn't really matter for this tracker
req->uploaded = m_hton64(req->uploaded);
req->num_want = m_hton32(req->num_want);
req->left = m_hton64(req->left);
if (!usi->m_allowRemotes && req->ip_address != 0) switch (req->event)
{ {
UDPTracker::sendError(usi, remote, req->transaction_id, "Tracker doesn't allow remote IP's; Request ignored."); case 1:
return 0; event = DatabaseDriver::EVENT_COMPLETE;
} break;
case 2:
event = DatabaseDriver::EVENT_START;
break;
case 3:
event = DatabaseDriver::EVENT_STOP;
break;
default:
event = DatabaseDriver::EVENT_UNSPEC;
break;
}
if (!usi->m_conn->isTorrentAllowed(req->info_hash)) if (event == DatabaseDriver::EVENT_STOP)
{ q = 0; // no need for peers when stopping.
UDPTracker::sendError(usi, remote, req->transaction_id, "info_hash not registered.");
return 0;
}
// load peers if (q > 0)
q = 30; usi->m_conn->getPeers(req->info_hash, &q, peers);
if (req->num_want >= 1)
q = std::min<int>(q, req->num_want);
DatabaseDriver::TrackerEvents event; bSize = 20; // header is 20 bytes
bSize += (6 * q); // + 6 bytes per peer.
{ tE.info_hash = req->info_hash;
std::shared_ptr<DatabaseDriver::PeerEntry> peersSptr = std::shared_ptr<DatabaseDriver::PeerEntry>(new DatabaseDriver::PeerEntry[q]); usi->m_conn->getTorrentInfo(&tE);
DatabaseDriver::PeerEntry *peers = peersSptr.get();
switch (req->event) resp = (AnnounceResponse*)buff;
{ resp->action = m_hton32(1);
case 1: resp->interval = m_hton32(usi->m_announceInterval);
event = DatabaseDriver::EVENT_COMPLETE; resp->leechers = m_hton32(tE.leechers);
break; resp->seeders = m_hton32(tE.seeders);
case 2: resp->transaction_id = req->transaction_id;
event = DatabaseDriver::EVENT_START;
break;
case 3:
event = DatabaseDriver::EVENT_STOP;
break;
default:
event = DatabaseDriver::EVENT_UNSPEC;
break;
}
if (event == DatabaseDriver::EVENT_STOP) for (i = 0; i < q; i++)
q = 0; // no need for peers when stopping. {
int x = i * 6;
// network byte order!!!
if (q > 0) // IP
usi->m_conn->getPeers(req->info_hash, &q, peers); buff[20 + x] = ((peers[i].ip & (0xff << 24)) >> 24);
buff[21 + x] = ((peers[i].ip & (0xff << 16)) >> 16);
buff[22 + x] = ((peers[i].ip & (0xff << 8)) >> 8);
buff[23 + x] = (peers[i].ip & 0xff);
bSize = 20; // header is 20 bytes // port
bSize += (6 * q); // + 6 bytes per peer. buff[24 + x] = ((peers[i].port & (0xff << 8)) >> 8);
buff[25 + x] = (peers[i].port & 0xff);
tE.info_hash = req->info_hash; }
usi->m_conn->getTorrentInfo(&tE); }
::sendto(usi->m_sock, (char*)buff, bSize, 0, (SOCKADDR*)remote, sizeof(SOCKADDR_IN));
LOG_DEBUG("udp-tracker", "Announce request from " << inet_ntoa(remote->sin_addr) << " (event=" << event << "), Sent " << q << " peers");
resp = (AnnounceResponse*)buff; // update DB.
resp->action = m_hton32(1); uint32_t ip;
resp->interval = m_hton32(usi->m_announceInterval); if (req->ip_address == 0) // default
resp->leechers = m_hton32(tE.leechers); ip = m_hton32 (remote->sin_addr.s_addr);
resp->seeders = m_hton32(tE.seeders); else
resp->transaction_id = req->transaction_id; ip = req->ip_address;
usi->m_conn->updatePeer(req->peer_id, req->info_hash, ip, req->port,
req->downloaded, req->left, req->uploaded, event);
for (i = 0; i < q; i++) return 0;
{ }
int x = i * 6;
// network byte order!!!
// IP int UDPTracker::handleScrape(UDPTracker *usi, SOCKADDR_IN *remote, char *data, int len) {
buff[20 + x] = ((peers[i].ip & (0xff << 24)) >> 24); ScrapeRequest *sR = reinterpret_cast<ScrapeRequest*>(data);
buff[21 + x] = ((peers[i].ip & (0xff << 16)) >> 16); int v, // validation helper
buff[22 + x] = ((peers[i].ip & (0xff << 8)) >> 8); c, // torrent counter
buff[23 + x] = (peers[i].ip & 0xff); i, // loop counter
j; // loop counter
uint8_t hash [20];
ScrapeResponse *resp;
uint8_t buffer [1024]; // up to 74 torrents can be scraped at once (17*74+8) < 1024
// port // validate request length:
buff[24 + x] = ((peers[i].port & (0xff << 8)) >> 8); v = len - 16;
buff[25 + x] = (peers[i].port & 0xff); if (v < 0 || v % 20 != 0)
{
UDPTracker::sendError(usi, remote, sR->transaction_id, "Bad scrape request.");
return 0;
}
} if (!usi->m_conn->verifyConnectionId(sR->connection_id,
} m_hton32(remote->sin_addr.s_addr),
::sendto(usi->m_sock, (char*)buff, bSize, 0, (SOCKADDR*)remote, sizeof(SOCKADDR_IN)); m_hton16(remote->sin_port)))
{
LOG_DEBUG("udp-tracker", "Bad connection id from " << inet_ntoa(remote->sin_addr));
return 1;
}
// update DB. // get torrent count.
uint32_t ip; c = v / 20;
if (req->ip_address == 0) // default
ip = m_hton32 (remote->sin_addr.s_addr);
else
ip = req->ip_address;
usi->m_conn->updatePeer(req->peer_id, req->info_hash, ip, req->port,
req->downloaded, req->left, req->uploaded, event);
return 0; resp = reinterpret_cast<ScrapeResponse*>(buffer);
} resp->action = m_hton32(2);
resp->transaction_id = sR->transaction_id;
int UDPTracker::handleScrape(UDPTracker *usi, SOCKADDR_IN *remote, char *data, int len) for (i = 0;i < c;i++)
{ {
ScrapeRequest *sR = reinterpret_cast<ScrapeRequest*>(data); int32_t *seeders,
int v, // validation helper *completed,
c, // torrent counter *leechers;
i, // loop counter
j; // loop counter
uint8_t hash [20];
ScrapeResponse *resp;
uint8_t buffer [1024]; // up to 74 torrents can be scraped at once (17*74+8) < 1024
// validate request length: for (j = 0; j < 20;j++)
v = len - 16; hash[j] = data[j + (i*20)+16];
if (v < 0 || v % 20 != 0)
{
UDPTracker::sendError(usi, remote, sR->transaction_id, "Bad scrape request.");
return 0;
}
if (!usi->m_conn->verifyConnectionId(sR->connection_id, seeders = (int32_t*)&buffer[i*12+8];
m_hton32(remote->sin_addr.s_addr), completed = (int32_t*)&buffer[i*12+12];
m_hton16(remote->sin_port))) leechers = (int32_t*)&buffer[i*12+16];
{
return 1;
}
// get torrent count. DatabaseDriver::TorrentEntry tE;
c = v / 20; tE.info_hash = hash;
if (!usi->m_conn->getTorrentInfo(&tE))
{
sendError(usi, remote, sR->transaction_id, "Scrape Failed: couldn't retrieve torrent data");
return 0;
}
resp = reinterpret_cast<ScrapeResponse*>(buffer); *seeders = m_hton32(tE.seeders);
resp->action = m_hton32(2); *completed = m_hton32(tE.completed);
resp->transaction_id = sR->transaction_id; *leechers = m_hton32(tE.leechers);
}
for (i = 0;i < c;i++) ::sendto(usi->m_sock, reinterpret_cast<const char*>(buffer), sizeof(buffer), 0, reinterpret_cast<SOCKADDR*>(remote), sizeof(SOCKADDR_IN));
{ LOG_DEBUG("udp-tracker", "Scrape request from " << inet_ntoa(remote->sin_addr) << ", Sent " << c << " torrents");
int32_t *seeders,
*completed,
*leechers;
for (j = 0; j < 20;j++) return 0;
hash[j] = data[j + (i*20)+16]; }
seeders = (int32_t*)&buffer[i*12+8]; int UDPTracker::isIANAIP(uint32_t ip) {
completed = (int32_t*)&buffer[i*12+12]; uint8_t x = (ip % 256);
leechers = (int32_t*)&buffer[i*12+16]; if (x == 0 || x == 10 || x == 127 || x >= 224)
return 1;
return 0;
}
DatabaseDriver::TorrentEntry tE; int UDPTracker::resolveRequest(UDPTracker *usi, SOCKADDR_IN *remote, char *data, int r) {
tE.info_hash = hash; ConnectionRequest* cR = reinterpret_cast<ConnectionRequest*>(data);
if (!usi->m_conn->getTorrentInfo(&tE)) uint32_t action;
{
sendError(usi, remote, sR->transaction_id, "Scrape Failed: couldn't retrieve torrent data");
return 0;
}
*seeders = m_hton32(tE.seeders); action = m_hton32(cR->action);
*completed = m_hton32(tE.completed);
*leechers = m_hton32(tE.leechers);
}
::sendto(usi->m_sock, reinterpret_cast<const char*>(buffer), sizeof(buffer), 0, reinterpret_cast<SOCKADDR*>(remote), sizeof(SOCKADDR_IN)); if (!usi->m_allowIANA_IPs)
{
if (isIANAIP(remote->sin_addr.s_addr))
{
LOG_DEBUG("udp-tracker", "Request from IANA reserved IP rejected (" << inet_ntoa(remote->sin_addr) << ")");
return 0; // Access Denied: IANA reserved IP.
}
}
return 0; if (action == 0 && r >= 16)
} return UDPTracker::handleConnection(usi, remote, data);
else if (action == 1 && r >= 98)
return UDPTracker::handleAnnounce(usi, remote, data);
else if (action == 2)
return UDPTracker::handleScrape(usi, remote, data, r);
else
{
UDPTracker::sendError(usi, remote, cR->transaction_id, "Tracker couldn't understand Client's request.");
return -1;
}
}
int UDPTracker::isIANAIP(uint32_t ip) void UDPTracker::_thread_start(UDPTracker *usi) {
{ SOCKADDR_IN remoteAddr;
uint8_t x = (ip % 256); char tmpBuff[UDP_BUFFER_SIZE];
if (x == 0 || x == 10 || x == 127 || x >= 224)
return 1;
return 0;
}
int UDPTracker::resolveRequest(UDPTracker *usi, SOCKADDR_IN *remote, char *data, int r)
{
ConnectionRequest* cR = reinterpret_cast<ConnectionRequest*>(data);
uint32_t action;
action = m_hton32(cR->action);
if (!usi->m_allowIANA_IPs)
{
if (isIANAIP(remote->sin_addr.s_addr))
{
char buffer[INET_ADDRSTRLEN];
BOOST_LOG_SEV(usi->m_logger, boost::log::trivial::warning) << "Client ignored (IANA IP): " << ::inet_ntop(AF_INET, &remote->sin_addr, buffer, sizeof(buffer));
return 0; // Access Denied: IANA reserved IP.
}
}
{
char buffer[INET_ADDRSTRLEN];
BOOST_LOG_SEV(usi->m_logger, boost::log::trivial::debug) << "Client request from " << ::inet_ntop(AF_INET, &remote->sin_addr, buffer, sizeof(buffer));
}
if (action == 0 && r >= 16)
return UDPTracker::handleConnection(usi, remote, data);
else if (action == 1 && r >= 98)
return UDPTracker::handleAnnounce(usi, remote, data);
else if (action == 2)
return UDPTracker::handleScrape(usi, remote, data, r);
else
{
UDPTracker::sendError(usi, remote, cR->transaction_id, "Tracker couldn't understand Client's request.");
return -1;
}
return 0;
}
void UDPTracker::_thread_start(UDPTracker *usi)
{
BOOST_LOG_SEV(usi->m_logger, boost::log::trivial::info) << "Worker thread started with PID=" << boost::this_thread::get_id() << ".";
SOCKADDR_IN remoteAddr;
char tmpBuff[UDP_BUFFER_SIZE];
#ifdef linux #ifdef linux
socklen_t addrSz; socklen_t addrSz;
#else #else
int addrSz; int addrSz;
#endif #endif
addrSz = sizeof(SOCKADDR_IN); addrSz = sizeof(SOCKADDR_IN);
while (usi->m_shouldRun)
{
// peek into the first 12 bytes of data; determine if connection request or announce request.
int r = ::recvfrom(usi->m_sock, (char*)tmpBuff, UDP_BUFFER_SIZE, 0, (SOCKADDR*)&remoteAddr, &addrSz);
if (r <= 0)
{
std::this_thread::yield();
continue;
}
while (true) UDPTracker::resolveRequest(usi, &remoteAddr, tmpBuff, r);
{ }
// peek into the first 12 bytes of data; determine if connection request or announce request.
int r = ::recvfrom(usi->m_sock, (char*)tmpBuff, UDP_BUFFER_SIZE, 0, (SOCKADDR*)&remoteAddr, &addrSz);
if (r <= 0)
{
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
continue;
}
{ LOG_INFO("udp-tracker", "worker " << std::this_thread::get_id() << " exited.");
boost::this_thread::disable_interruption di; }
UDPTracker::resolveRequest(usi, &remoteAddr, tmpBuff, r); void UDPTracker::_maintainance_start(UDPTracker* usi) {
} std::unique_lock<std::mutex> lk (usi->m_maintenanceMutex);
}
}
void UDPTracker::_maintainance_start(UDPTracker* usi) while (true)
{ {
BOOST_LOG_SEV(usi->m_logger, boost::log::trivial::info) << "Maintenance thread started with PID=" << boost::this_thread::get_id() << "."; if (std::cv_status::no_timeout == usi->m_maintenanceCondition.wait_for(lk, std::chrono::seconds(usi->m_cleanupInterval))) {
while (true) break;
{ }
{
boost::this_thread::disable_interruption di;
BOOST_LOG_SEV(usi->m_logger, boost::log::trivial::info) << "Running cleanup...";
usi->m_conn->cleanup();
}
boost::this_thread::sleep_for(boost::chrono::seconds(usi->m_cleanupInterval)); LOG_INFO("udp-tracker", "Maintenance running...");
} usi->m_conn->cleanup();
} }
lk.unlock();
LOG_INFO("udp-tracker", "Maintenance thread " << std::this_thread::get_id() << " existed.");
}
}; };

View file

@ -1,5 +1,5 @@
/* /*
* Copyright © 2012-2016 Naim A. * Copyright © 2012-2017 Naim A.
* *
* This file is part of UDPT. * This file is part of UDPT.
* *
@ -29,10 +29,10 @@
#include <list> #include <list>
#include <ctime> #include <ctime>
#include <boost/thread.hpp> #include <thread>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
#include <boost/log/trivial.hpp> #include <mutex>
#include <boost/log/sources/severity_channel_logger.hpp> #include <condition_variable>
#include "tools.h" #include "tools.h"
#include "exceptions.h" #include "exceptions.h"
@ -47,139 +47,142 @@
namespace UDPT namespace UDPT
{ {
class UDPTracker class UDPTracker
{ {
public: public:
typedef struct udp_connection_request typedef struct udp_connection_request
{ {
uint64_t connection_id; uint64_t connection_id;
uint32_t action; uint32_t action;
uint32_t transaction_id; uint32_t transaction_id;
} ConnectionRequest; } ConnectionRequest;
typedef struct udp_connection_response typedef struct udp_connection_response
{ {
uint32_t action; uint32_t action;
uint32_t transaction_id; uint32_t transaction_id;
uint64_t connection_id; uint64_t connection_id;
} ConnectionResponse; } ConnectionResponse;
typedef struct udp_announce_request typedef struct udp_announce_request
{ {
uint64_t connection_id; uint64_t connection_id;
uint32_t action; uint32_t action;
uint32_t transaction_id; uint32_t transaction_id;
uint8_t info_hash [20]; uint8_t info_hash [20];
uint8_t peer_id [20]; uint8_t peer_id [20];
uint64_t downloaded; uint64_t downloaded;
uint64_t left; uint64_t left;
uint64_t uploaded; uint64_t uploaded;
uint32_t event; uint32_t event;
uint32_t ip_address; uint32_t ip_address;
uint32_t key; uint32_t key;
int32_t num_want; int32_t num_want;
uint16_t port; uint16_t port;
} AnnounceRequest; } AnnounceRequest;
typedef struct udp_announce_response typedef struct udp_announce_response
{ {
uint32_t action; uint32_t action;
uint32_t transaction_id; uint32_t transaction_id;
uint32_t interval; uint32_t interval;
uint32_t leechers; uint32_t leechers;
uint32_t seeders; uint32_t seeders;
uint8_t *peer_list_data; uint8_t *peer_list_data;
} AnnounceResponse; } AnnounceResponse;
typedef struct udp_scrape_request typedef struct udp_scrape_request
{ {
uint64_t connection_id; uint64_t connection_id;
uint32_t action; uint32_t action;
uint32_t transaction_id; uint32_t transaction_id;
uint8_t *torrent_list_data; uint8_t *torrent_list_data;
} ScrapeRequest; } ScrapeRequest;
typedef struct udp_scrape_response typedef struct udp_scrape_response
{ {
uint32_t action; uint32_t action;
uint32_t transaction_id; uint32_t transaction_id;
uint8_t *data; uint8_t *data;
} ScrapeResponse; } ScrapeResponse;
typedef struct udp_error_response typedef struct udp_error_response
{ {
uint32_t action; uint32_t action;
uint32_t transaction_id; uint32_t transaction_id;
char *message; char *message;
} ErrorResponse; } ErrorResponse;
enum StartStatus enum StartStatus
{ {
START_OK = 0, START_OK = 0,
START_ESOCKET_FAILED = 1, START_ESOCKET_FAILED = 1,
START_EBIND_FAILED = 2 START_EBIND_FAILED = 2
}; };
/** /**
* Initializes the UDP Tracker. * Initializes the UDP Tracker.
* @param settings Settings to start server with * @param settings Settings to start server with
*/ */
UDPTracker(const boost::program_options::variables_map& conf); UDPTracker(const boost::program_options::variables_map& conf);
/** /**
* Starts the Initialized instance. * Starts the Initialized instance.
*/ */
void start(); void start();
/** /**
* Terminates tracker. * Terminates tracker.
*/ */
void stop(); void stop();
/** /**
* Joins worker threads * Joins worker threads
*/ */
void wait(); void wait();
/** /**
* Destroys resources that were created by constructor * Destroys resources that were created by constructor
* @param usi Instance to destroy. * @param usi Instance to destroy.
*/ */
virtual ~UDPTracker(); virtual ~UDPTracker();
std::shared_ptr<UDPT::Data::DatabaseDriver> m_conn; std::shared_ptr<UDPT::Data::DatabaseDriver> m_conn;
private: private:
SOCKET m_sock; SOCKET m_sock;
SOCKADDR_IN m_localEndpoint; SOCKADDR_IN m_localEndpoint;
uint16_t m_port; uint16_t m_port;
uint8_t m_threadCount; uint8_t m_threadCount;
bool m_isDynamic; bool m_isDynamic;
bool m_allowRemotes; bool m_allowRemotes;
bool m_allowIANA_IPs; bool m_allowIANA_IPs;
std::vector<boost::thread> m_threads; std::vector<std::thread> m_threads;
uint32_t m_announceInterval; uint32_t m_announceInterval;
uint32_t m_cleanupInterval; uint32_t m_cleanupInterval;
boost::log::sources::severity_channel_logger_mt<> m_logger; std::atomic_bool m_shouldRun;
const boost::program_options::variables_map& m_conf; std::mutex m_maintenanceMutex;
std::condition_variable m_maintenanceCondition;
static void _thread_start(UDPTracker *usi); const boost::program_options::variables_map& m_conf;
static void _maintainance_start(UDPTracker* usi);
static int resolveRequest(UDPTracker *usi, SOCKADDR_IN *remote, char *data, int r); static void _thread_start(UDPTracker *usi);
static void _maintainance_start(UDPTracker* usi);
static int handleConnection(UDPTracker *usi, SOCKADDR_IN *remote, char *data); static int resolveRequest(UDPTracker *usi, SOCKADDR_IN *remote, char *data, int r);
static int handleAnnounce(UDPTracker *usi, SOCKADDR_IN *remote, char *data);
static int handleScrape(UDPTracker *usi, SOCKADDR_IN *remote, char *data, int len);
static int sendError(UDPTracker *, SOCKADDR_IN *remote, uint32_t transId, const std::string &); static int handleConnection(UDPTracker *usi, SOCKADDR_IN *remote, char *data);
static int handleAnnounce(UDPTracker *usi, SOCKADDR_IN *remote, char *data);
static int handleScrape(UDPTracker *usi, SOCKADDR_IN *remote, char *data, int len);
static int isIANAIP(uint32_t ip); static int sendError(UDPTracker *, SOCKADDR_IN *remote, uint32_t transId, const std::string &);
};
static int isIANAIP(uint32_t ip);
};
}; };
#endif /* UDPTRACKER_H_ */ #endif /* UDPTRACKER_H_ */

55
tests/main.cpp Normal file
View file

@ -0,0 +1,55 @@
#include <gtest/gtest.h>
#include "../src/tools.h"
#include "../src/db/driver_sqlite.hpp"
TEST(Utility, SanityCheck) {
const uint32_t MAGIC = 0xDEADBEEF;
const unsigned char MAGIC_BYTES[4] = {0xEF, 0xBE, 0xAD, 0xDE};
ASSERT_TRUE(memcmp(&MAGIC, MAGIC_BYTES, 4) == 0);
}
TEST(Utility, CheckMTON) {
EXPECT_EQ(m_hton16(0xDEAD), 0xADDE);
EXPECT_EQ(m_hton32(0xDEADBEEF), 0xEFBEADDE);
EXPECT_EQ(m_hton64(0xDEADBEEFA1B2C3E4), 0xE4C3B2A1EFBEADDE);
}
TEST(Utility, HashToHexStr) {
const char EXPECTED_OUTPUT[] = "c670606edd22fd0e3b432c977559a687cc5d9bd2";
const unsigned char DATA[20] = {198, 112, 96, 110, 221, 34, 253, 14, 59, 67, 44, 151, 117, 89, 166, 135, 204, 93, 155, 210};
char OUTPUT_BUFFER[41] = {0};
to_hex_str(DATA, OUTPUT_BUFFER);
ASSERT_EQ(std::string(EXPECTED_OUTPUT), OUTPUT_BUFFER);
}
class SQLiteDriverTest:
public ::testing::Test {
protected:
SQLiteDriverTest(): va_map(), driver(nullptr) {
va_map.insert(std::pair<std::string, boost::program_options::variable_value>("db.param", boost::program_options::variable_value(std::string(":memory:"), true)));
}
virtual void SetUp() {
if (nullptr == driver) {
driver = new UDPT::Data::SQLite3Driver(va_map, false);
}
}
virtual void TearDown() {
if (nullptr != driver) {
delete driver;
driver = nullptr;
}
}
UDPT::Data::SQLite3Driver *driver;
boost::program_options::variables_map va_map;
};
int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -1,22 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UDPT", "UDPT\UDPT.vcxproj", "{9F399AF8-861E-4E50-A94C-1388089E3FA0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Release|Win32 = Release|Win32
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9F399AF8-861E-4E50-A94C-1388089E3FA0}.Debug|Win32.ActiveCfg = Debug|Win32
{9F399AF8-861E-4E50-A94C-1388089E3FA0}.Debug|Win32.Build.0 = Debug|Win32
{9F399AF8-861E-4E50-A94C-1388089E3FA0}.Release|Win32.ActiveCfg = Release|Win32
{9F399AF8-861E-4E50-A94C-1388089E3FA0}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View file

@ -1,111 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{9F399AF8-861E-4E50-A94C-1388089E3FA0}</ProjectGuid>
<RootNamespace>UDPT</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140_xp</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);D:\libs\sqlite3;D:\libs\boost\boost_1_59_0</IncludePath>
<LibraryPath>D:\libs\boost\boost_1_59_0\stage\lib;D:\libs\sqlite3\Release;$(LibraryPath)</LibraryPath>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<IncludePath>$(VC_IncludePath);$(WindowsSDK_IncludePath);D:\libs\sqlite3;D:\libs\boost\boost_1_59_0</IncludePath>
<LibraryPath>D:\libs\boost\boost_1_59_0\stage\lib;D:\libs\sqlite3\Release;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<ExceptionHandling>Async</ExceptionHandling>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ws2_32.lib;sqlite3.lib;advapi32.lib;shell32.lib</AdditionalDependencies>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<ExceptionHandling>Async</ExceptionHandling>
</ClCompile>
<Link>
<EnableCOMDATFolding>
</EnableCOMDATFolding>
<OptimizeReferences>
</OptimizeReferences>
<AdditionalDependencies>ws2_32.lib;sqlite3.lib;advapi32.lib;shell32.lib</AdditionalDependencies>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\src\db\database.cpp" />
<ClCompile Include="..\..\src\db\driver_sqlite.cpp" />
<ClCompile Include="..\..\src\http\httpserver.cpp" />
<ClCompile Include="..\..\src\http\webapp.cpp" />
<ClCompile Include="..\..\src\main.cpp" />
<ClCompile Include="..\..\src\service.cpp" />
<ClCompile Include="..\..\src\tools.c" />
<ClCompile Include="..\..\src\tracker.cpp" />
<ClCompile Include="..\..\src\udpTracker.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\db\database.hpp" />
<ClInclude Include="..\..\src\db\driver_sqlite.hpp" />
<ClInclude Include="..\..\src\exceptions.h" />
<ClInclude Include="..\..\src\http\httpserver.hpp" />
<ClInclude Include="..\..\src\http\webapp.hpp" />
<ClInclude Include="..\..\src\multiplatform.h" />
<ClInclude Include="..\..\src\service.hpp" />
<ClInclude Include="..\..\src\tools.h" />
<ClInclude Include="..\..\src\tracker.hpp" />
<ClInclude Include="..\..\src\udpTracker.hpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -1,90 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Header Files\db">
<UniqueIdentifier>{f5a85f67-2777-4fa5-828e-9c79af9f4b13}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\db">
<UniqueIdentifier>{643f664e-8751-4440-8b90-fe95b15e0aac}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\http">
<UniqueIdentifier>{bcb1fa9f-c9ec-4398-97f9-544baf69f2a9}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\http">
<UniqueIdentifier>{f65a2e69-6a71-4cd0-ad64-36a73c6c94c4}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\src\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\tools.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\udpTracker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\db\database.cpp">
<Filter>Source Files\db</Filter>
</ClCompile>
<ClCompile Include="..\..\src\db\driver_sqlite.cpp">
<Filter>Source Files\db</Filter>
</ClCompile>
<ClCompile Include="..\..\src\http\httpserver.cpp">
<Filter>Source Files\http</Filter>
</ClCompile>
<ClCompile Include="..\..\src\http\webapp.cpp">
<Filter>Source Files\http</Filter>
</ClCompile>
<ClCompile Include="..\..\src\tracker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\src\service.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\multiplatform.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\src\tools.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\src\udpTracker.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\src\db\database.hpp">
<Filter>Header Files\db</Filter>
</ClInclude>
<ClInclude Include="..\..\src\db\driver_sqlite.hpp">
<Filter>Header Files\db</Filter>
</ClInclude>
<ClInclude Include="..\..\src\http\httpserver.hpp">
<Filter>Header Files\http</Filter>
</ClInclude>
<ClInclude Include="..\..\src\http\webapp.hpp">
<Filter>Header Files\http</Filter>
</ClInclude>
<ClInclude Include="..\..\src\exceptions.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\src\tracker.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\src\service.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>