diff --git a/src/db/database.cpp b/src/db/database.cpp new file mode 100644 index 0000000..a521a9f --- /dev/null +++ b/src/db/database.cpp @@ -0,0 +1,121 @@ +/* + * Copyright © 2012,2013 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 . + */ + +#include "database.hpp" + +namespace UDPT +{ + namespace Data + { + DatabaseDriver::DatabaseDriver(Settings::SettingClass *sc, bool isDynamic) + { + this->dClass = sc; + this->is_dynamic = isDynamic; + } + + bool DatabaseDriver::addTorrent(uint8_t hash [20]) + { + throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); + } + + bool DatabaseDriver::removeTorrent(uint8_t hash[20]) + { + throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); + } + + bool DatabaseDriver::isDynamic() + { + return this->is_dynamic; + } + + bool DatabaseDriver::genConnectionId(uint64_t *cid, uint32_t ip, uint16_t port) + { + throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); + } + + bool DatabaseDriver::verifyConnectionId(uint64_t cid, uint32_t ip, uint16_t port) + { + throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); + } + + bool DatabaseDriver::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) + { + 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) + { + throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); + } + + bool DatabaseDriver::getTorrentInfo (TorrentEntry *e) + { + throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); + } + + bool DatabaseDriver::getPeers (uint8_t info_hash [20], int *max_count, PeerEntry *pe) + { + throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); + } + + void DatabaseDriver::cleanup() + { + throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); + } + + bool DatabaseDriver::isTorrentAllowed(uint8_t info_hash[20]) + { + throw DatabaseException (DatabaseException::E_NOT_IMPLEMENTED); + } + + DatabaseDriver::~DatabaseDriver() + { + } + + /*-- Exceptions --*/ + static const char *EMessages[] = { + "Unknown Error", + "Not Implemented", + "Failed to connect to database" + }; + + DatabaseException::DatabaseException() + { + this->errorNum = E_UNKNOWN; + } + + DatabaseException::DatabaseException(enum EType e) + { + this->errorNum = e; + } + + enum DatabaseException::EType DatabaseException::getErrorType() + { + return this->errorNum; + } + + const char* DatabaseException::getErrorMessage() + { + return EMessages[this->errorNum]; + } + }; +}; diff --git a/src/db/database.h b/src/db/database.h deleted file mode 100644 index f731eea..0000000 --- a/src/db/database.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright © 2012,2013 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 . - */ - -#ifndef DATABASE_H_ -#define DATABASE_H_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct dbConnection dbConnection; - -/** - * Opens a database connection. - * @param pdb Pointer to database instance. - * @param cStr Connection string for the active driver. - * @return 0 on success; otherwise non-zero. - */ -int db_open (dbConnection **pdb, const char *cStr); - -/** - * Closes the database connection. - * @param db Database instance. - * @return 0 on success; otherwise non-zero. - */ -int db_close (dbConnection *db); - -typedef struct { - uint8_t *peer_id; - uint64_t downloaded; - uint64_t uploaded; - uint64_t left; - - uint32_t ip; // currently only support IPv4. - uint16_t port; -} db_peerEntry; - -/** - * Adds/Updates the list of peers. - * @param db The database's instance. - * @param hash The info_hash of the torrent. - * @param pE Peer's information. - * @return 0 on success; otherwise non-zero. - */ -int db_add_peer (dbConnection *db, uint8_t hash[20], db_peerEntry *pE); - -/** - * Loads peers for the requested torrent. - * @param db Database instance. - * @param hash The info_hash of the requested torrent. - * @param lst A allocated array to store results in. - * @param sZ in: The maximum amount of entries to load. out: Amount of loaded entries. - * @return 0 on success; otherwise non-zero. - */ -int db_load_peers (dbConnection *db, uint8_t hash[20], db_peerEntry *lst, int *sZ); - -/** - * Gets stats for the requested torrent. - * @param db The Database connection - * @param hash info_hash of the torrent. - * @param seeders Returns the Seeders for the requested torrent. - * @param leechers Returns the Leechers for the requested torrent. - * @param completed Returns the count of completed downloaded reported. - * @return 0 on success, otherwise non-zero. - */ -int db_get_stats (dbConnection *db, uint8_t hash[20], int32_t *seeders, int32_t *leechers, int32_t *completed); - -/** - * Maintenance routine, Calculates stats & releases space from old entries. - * @param db The database connection. - * @return 0 on success; otherwise non-zero. - */ -int db_cleanup (dbConnection *db); - -/** - * Deletes a peer from the database. - * @param db Database connection - * @param hash info_hash of the torrent. - * @param pE The peer's information. - * @return 0 on success; otherwise non-zero. - */ -int db_remove_peer (dbConnection *db, uint8_t hash [20], db_peerEntry *pE); - -#ifdef __cplusplus -} -#endif -#endif /* DATABASE_H_ */ diff --git a/src/db/database.hpp b/src/db/database.hpp new file mode 100644 index 0000000..ac4f9e5 --- /dev/null +++ b/src/db/database.hpp @@ -0,0 +1,173 @@ +/* + * Copyright © 2012,2013 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 . + */ + +#ifndef DATABASE_HPP_ +#define DATABASE_HPP_ + +#include "../settings.hpp" + +namespace UDPT +{ + namespace Data + { + class DatabaseException + { + public: + enum EType { + E_UNKNOWN = 0, // Unknown error + E_NOT_IMPLEMENTED = 1, // not implemented + E_CONNECTION_FAILURE = 2 + }; + + DatabaseException (); + DatabaseException (EType); + EType getErrorType (); + const char* getErrorMessage (); + private: + EType errorNum; + }; + + class DatabaseDriver + { + public: + typedef struct { + uint8_t *info_hash; + int32_t seeders; + int32_t leechers; + int32_t completed; + } TorrentEntry; + typedef struct { + uint32_t ip; + uint16_t port; + } PeerEntry; + + enum TrackerEvents { + EVENT_UNSPEC = 0, + EVENT_COMPLETE = 1, + EVENT_START = 2, + EVENT_STOP = 3 + }; + + /** + * Opens the DB's connection + * @param dClass Settings class ('database' class). + */ + DatabaseDriver (Settings::SettingClass *dClass, bool isDynamic = false); + + /** + * Adds a torrent to the Database. automatically done if in dynamic mode. + * @param hash The info_hash of the torrent. + * @return true on success. false on failure. + */ + virtual bool addTorrent (uint8_t hash[20]); + + /** + * Removes a torrent from the database. should be used only for non-dynamic trackers or by cleanup. + * @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) + */ + virtual bool removeTorrent (uint8_t hash[20]); + + /** + * Checks if the Database is acting as a dynamic tracker DB. + * @return true if dynamic. otherwise false. + */ + bool isDynamic (); + + /** + * Checks if the torrent can be used in the tracker. + * @param info_hash The torrent's info_hash. + * @return true if allowed. otherwise false. + */ + virtual bool isTorrentAllowed (uint8_t info_hash [20]); + + /** + * Generate a Connection ID for the peer. + * @param connectionId (Output) the generated connection ID. + * @param ip The peer's IP (requesting peer. not remote) + * @param port The peer's IP (remote port if tracker accepts) + * @return + */ + virtual bool genConnectionId (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. + * @param peer_id the peer's peer_id + * @param info_hash the torrent info_hash + * @param ip IP of peer (remote ip if tracker accepts) + * @param port TCP port of peer (remote port if tracker accepts) + * @param downloaded total Bytes downloaded + * @param left total bytes left + * @param uploaded total bytes uploaded + * @return true on success, false on failure. + */ + virtual 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); + + /** + * 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 info_hash Torrent's info_hash + * @param ip The IP of the peer (remote IP if tracker accepts) + * @param port The TCP port (remote port if tracker accepts) + * @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); + + /** + * Gets stats on a torrent + * @param e TorrentEntry, only this info_hash has to be set + * @return true on success, false on failure. + */ + virtual bool getTorrentInfo (TorrentEntry *e); + + /** + * Gets a list of peers from the database. + * @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 pe The list of peers. Must be pre-allocated to the size of max_count. + * @return true on success, otherwise false (shouldn't happen). + */ + virtual bool getPeers (uint8_t info_hash [20], int *max_count, PeerEntry *pe); + + /** + * Cleanup the database. + * Other actions may be locked when using this depending on the driver. + */ + virtual void cleanup (); + + /** + * Closes the connections, and releases all other resources. + */ + virtual ~DatabaseDriver (); + + protected: + Settings::SettingClass *dClass; + private: + bool is_dynamic; + }; + }; +}; + + +#endif /* DATABASE_HPP_ */ diff --git a/src/db/driver_sqlite.c b/src/db/driver_sqlite.c deleted file mode 100644 index 56aef7a..0000000 --- a/src/db/driver_sqlite.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright © 2012,2013 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 . - */ - -#include "database.h" -#include "../multiplatform.h" -#include "../tools.h" -#include -#include -#include -#include -#include - -struct dbConnection -{ - sqlite3 *db; - HANDLE janitor; -}; - -static const char hexadecimal[] = "0123456789abcdef"; - -void _to_hex_str (const uint8_t *hash, char *data) -{ - int i; - for (i = 0;i < 20;i++) - { - data[i * 2] = hexadecimal[hash[i] / 16]; - data[i * 2 + 1] = hexadecimal[hash[i] % 16]; - } - data[40] = '\0'; -} - -static int _db_make_torrent_table (sqlite3 *db, char *hash) -{ - char sql [2000]; - char *err_msg; - int r; - - sql[0] = '\0'; - - strcat(sql, "CREATE TABLE IF NOT EXISTS 't"); - strcat(sql, hash); - strcat (sql, "' ("); - - strcat (sql, "peer_id blob(20)," - "ip blob(4)," - "port blob(2)," - "uploaded blob(8)," // uint64 - "downloaded blob(8)," - "left blob(8)," - "last_seen INT DEFAULT 0"); - - strcat(sql, ", CONSTRAINT c1 UNIQUE (ip,port) ON CONFLICT REPLACE)"); - - // create table. - r = sqlite3_exec(db, sql, NULL, NULL, &err_msg); - printf("E:%s\n", err_msg); - - return r; -} - -static void _db_setup (sqlite3 *db) -{ - sqlite3_exec(db, "CREATE TABLE stats (" - "info_hash blob(20) UNIQUE," - "completed INTEGER DEFAULT 0," - "leechers INTEGER DEFAULT 0," - "seeders INTEGER DEFAULT 0," - "last_mod INTEGER DEFAULT 0" - ")", NULL, NULL, NULL); -} - -int db_open (dbConnection **db, const char *cStr) -{ - FILE *f; - int doSetup, // check if to build DB, or it already exists? - r; - - f = fopen (cStr, "rb"); - doSetup = 0; - if (f == NULL) - doSetup = 1; - else - fclose (f); - - *db = malloc (sizeof(struct dbConnection)); - r = sqlite3_open (cStr, &((*db)->db)); - if (doSetup) - _db_setup((*db)->db); - - return r; -} - -int db_close (dbConnection *db) -{ - int r = sqlite3_close(db->db); - free (db); - return r; -} - -int db_add_peer (dbConnection *db, uint8_t info_hash[20], db_peerEntry *pE) -{ - char xHash [50]; // we just need 40 + \0 = 41. - sqlite3_stmt *stmt; - char sql [1000]; - int r; - - char *hash = xHash; - to_hex_str(info_hash, hash); - - _db_make_torrent_table(db->db, hash); - - - sql[0] = '\0'; - strcat(sql, "REPLACE INTO 't"); - strcat(sql, hash); - strcat(sql, "' (peer_id,ip,port,uploaded,downloaded,left,last_seen) VALUES (?,?,?,?,?,?,?)"); - -// printf("IP->%x::%u\n", pE->ip, pE->port); - - sqlite3_prepare(db->db, sql, -1, &stmt, NULL); - - sqlite3_bind_blob(stmt, 1, (void*)pE->peer_id, 20, NULL); - sqlite3_bind_blob(stmt, 2, (void*)&pE->ip, 4, NULL); - sqlite3_bind_blob(stmt, 3, (void*)&pE->port, 2, NULL); - sqlite3_bind_blob(stmt, 4, (void*)&pE->uploaded, 8, NULL); - sqlite3_bind_blob(stmt, 5, (void*)&pE->downloaded, 8, NULL); - sqlite3_bind_blob(stmt, 6, (void*)&pE->left, 8, NULL); - sqlite3_bind_int(stmt, 7, time(NULL)); - - r = sqlite3_step(stmt); - sqlite3_finalize(stmt); - - strcpy(sql, "REPLACE INTO stats (info_hash,last_mod) VALUES (?,?)"); - sqlite3_prepare (db->db, sql, -1, &stmt, NULL); - sqlite3_bind_blob (stmt, 1, hash, 20, NULL); - sqlite3_bind_int (stmt, 2, time(NULL)); - sqlite3_step (stmt); - sqlite3_finalize (stmt); - - return r; -} - -int db_load_peers (dbConnection *db, uint8_t info_hash[20], db_peerEntry *lst, int *sZ) -{ - char sql [1000]; - char hash [50]; - sqlite3_stmt *stmt; - int r, - i; - - sql[0] = '\0'; - - to_hex_str(info_hash, hash); - - strcat(sql, "SELECT ip,port FROM 't"); - strcat(sql, hash); - strcat(sql, "' LIMIT ?"); - - sqlite3_prepare(db->db, sql, -1, &stmt, NULL); - sqlite3_bind_int(stmt, 1, *sZ); - - i = 0; - while (*sZ > i) - { - r = sqlite3_step(stmt); - if (r == SQLITE_ROW) - { - const char *ip = (const char*)sqlite3_column_blob (stmt, 0); - const char *port = (const char*)sqlite3_column_blob (stmt, 1); - - memcpy(&lst[i].ip, ip, 4); - memcpy(&lst[i].port, port, 2); - - i++; - } - else - break; - } - - printf("%d Clients Dumped.\n", i); - - sqlite3_finalize(stmt); - - *sZ = i; - - return 0; -} - -int db_get_stats (dbConnection *db, uint8_t hash[20], int32_t *seeders, int32_t *leechers, int32_t *completed) -{ - const char sql[] = "SELECT seeders,leechers,completed FROM 'stats' WHERE info_hash=?"; - sqlite3_stmt *stmt; - - *seeders = 0; - *leechers = 0; - *completed = 0; - - - sqlite3_prepare (db->db, sql, -1, &stmt, NULL); - sqlite3_bind_blob (stmt, 1, (void*)hash, 20, NULL); - - if (sqlite3_step(stmt) == SQLITE_ROW) - { - *seeders = sqlite3_column_int (stmt, 0); - *leechers = sqlite3_column_int (stmt, 1); - *completed = sqlite3_column_int (stmt, 2); - } - - sqlite3_finalize (stmt); - - return 0; -} - -int db_cleanup (dbConnection *db) -{ - const char sql[] = "SELECT info_hash FROM stats WHERE last_moddb, sql, -1, &stmt, NULL); - sqlite3_bind_int (stmt, 1, timeframe - 7200); - - while (sqlite3_step(stmt) == SQLITE_ROW) - { - to_hex_str(sqlite3_column_blob(stmt, 0), hash); - - // drop table: - strcpy(temp, "DROP TABLE IF EXISTS 't"); - strcat(temp, hash); - strcat(temp, "'"); - sqlite3_exec(db->db, temp, NULL, NULL, NULL); - } - sqlite3_finalize (stmt); - - // update 'dead' torrents - sqlite3_prepare(db->db, "UPDATE stats SET seeders=0,leechers=0 WHERE last_moddb, "SELECT info_hash FROM stats WHERE last_mod>=?", -1, &stmt, NULL); - sqlite3_bind_int (stmt, 1, timeframe - 7200); - - sqlite3_prepare (db->db, "UPDATE stats SET seeders=?,leechers=?,last_mod=? WHERE info_hash=?", -1, &uStat, NULL); - - while (sqlite3_step(stmt) == SQLITE_ROW) - { - uint8_t *binHash = (uint8_t*)sqlite3_column_blob(stmt, 0); - to_hex_str (binHash, hash); - - // total users... - strcpy (temp, "SELECT COUNT(*) FROM 't"); - strcat (temp, hash); - strcat (temp, "'"); - - sqlite3_prepare (db->db, temp, -1, &sTmp, NULL); - if (sqlite3_step(sTmp) == SQLITE_ROW) - { - leechers = sqlite3_column_int (sTmp, 0); - } - sqlite3_finalize (sTmp); - - // seeders... - strcpy (temp, "SELECT COUNT(*) FROM 't"); - strcat (temp, hash); - strcat (temp, "' WHERE left=0"); - - sqlite3_prepare (db->db, temp, -1, &sTmp, NULL); - if (sqlite3_step(sTmp) == SQLITE_ROW) - { - seeders = sqlite3_column_int (sTmp, 0); - } - sqlite3_finalize (sTmp); - - leechers -= seeders; - - sqlite3_bind_int (uStat, 1, seeders); - sqlite3_bind_int (uStat, 2, leechers); - sqlite3_bind_int (uStat, 3, timeframe); - sqlite3_bind_blob (uStat, 4, binHash, 20, NULL); - sqlite3_step (uStat); - sqlite3_reset (uStat); - - printf("%s: %d seeds/%d leechers;\n", hash, seeders, leechers); - } - sqlite3_finalize (stmt); - - sqlite3_finalize (stmt); - - return 0; -} - -int db_remove_peer (dbConnection *db, uint8_t hash[20], db_peerEntry *pE) -{ - char sql [1000]; - char xHash [50]; - sqlite3_stmt *stmt; - - _to_hex_str (hash, xHash); - - strcpy (sql, "DELETE FROM 't"); - strcat (sql, xHash); - strcat (sql, "' WHERE ip=? AND port=? AND peer_id=?"); - - sqlite3_prepare (db->db, sql, -1, &stmt, NULL); - - sqlite3_bind_blob(stmt, 0, (const void*)&pE->ip, 4, NULL); - sqlite3_bind_blob(stmt, 1, (const void*)&pE->port, 2, NULL); - sqlite3_bind_blob(stmt, 2, (const void*)pE->peer_id, 20, NULL); - - sqlite3_step(stmt); - - sqlite3_finalize(stmt); - - return 0; -} diff --git a/src/db/driver_sqlite.cpp b/src/db/driver_sqlite.cpp new file mode 100644 index 0000000..8d6f83e --- /dev/null +++ b/src/db/driver_sqlite.cpp @@ -0,0 +1,430 @@ +/* + * Copyright © 2012,2013 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 . + */ + +#include "driver_sqlite.hpp" +#include "../multiplatform.h" +#include "../tools.h" +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace UDPT +{ + namespace Data + { + static const char hexadecimal[] = "0123456789abcdef"; + + static char* _to_hex_str (const uint8_t *hash, char *data) + { + int i; + for (i = 0;i < 20;i++) + { + data[i * 2] = hexadecimal[hash[i] / 16]; + data[i * 2 + 1] = hexadecimal[hash[i] % 16]; + } + data[40] = '\0'; + return data; + } + + static uint8_t* _hash_to_bin (const char *hash, uint8_t *data) + { + for (int i = 0;i < 20;i++) + { + data [i] = 0; + char a = hash[i * 2]; + char b = hash[i * 2 + 1]; + + assert ( (a >= 'a' && a <= 'f') || (a >= '0' && a <= '9') ); + assert ( (b >= 'a' && b <= 'f') || (b >= '0' && b <= '9') ); + + data[i] = ( (a >= '0' && a <= 'f') ? (a - '0') : (a - 'f' + 10) ); + data[i] <<= 4; + data[i] = ( (b >= '0' && b <= 'f') ? (b - '0') : (b - 'f' + 10) ); + } + + return data; + } + + SQLite3Driver::SQLite3Driver (Settings::SettingClass *sc, bool isDyn) : DatabaseDriver(sc, isDyn) + { + int r; + bool doSetup; + + fstream fCheck; + string filename = sc->get("file"); + + fCheck.open(filename.c_str(), ios::binary | ios::in); + if (fCheck.is_open()) + { + doSetup = false; + fCheck.close(); + } + else + doSetup = true; + + r = sqlite3_open(filename.c_str(), &this->db); + if (r != SQLITE_OK) + { + sqlite3_close(this->db); + throw DatabaseException (DatabaseException::E_CONNECTION_FAILURE); + } + + if (doSetup) + this->doSetup(); + } + + void SQLite3Driver::doSetup() + { +// cout << "Creating DB..." << endl; + char *eMsg = NULL; + // for quicker stats. + sqlite3_exec(this->db, "CREATE TABLE stats (" + "info_hash blob(20) UNIQUE," + "completed INTEGER DEFAULT 0," + "leechers INTEGER DEFAULT 0," + "seeders INTEGER DEFAULT 0," + "last_mod INTEGER DEFAULT 0" + ")", NULL, NULL, &eMsg); +// cout << "stats: " << (eMsg == NULL ? "OK" : eMsg) << endl; + // for non-Dynamic trackers + sqlite3_exec(this->db, "CREATE TABLE torrents (" + "info_hash blob(20) UNIQUE," + "created INTEGER" + ")", NULL, NULL, &eMsg); +// cout << "torrents: " << (eMsg == NULL ? "OK" : eMsg) << endl; + } + + bool SQLite3Driver::getTorrentInfo(TorrentEntry *e) + { + bool gotInfo = false; + + const char sql[] = "SELECT seeders,leechers,completed FROM 'stats' WHERE info_hash=?"; + sqlite3_stmt *stmt; + + 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); + + if (sqlite3_step(stmt) == SQLITE_ROW) + { + e->seeders = sqlite3_column_int (stmt, 0); + e->leechers = sqlite3_column_int (stmt, 1); + e->completed = sqlite3_column_int (stmt, 2); + + gotInfo = true; + } + + sqlite3_finalize (stmt); + + return gotInfo; + } + + bool SQLite3Driver::getPeers (uint8_t info_hash [20], int *max_count, PeerEntry *pe) + { + char sql [1000]; + char hash [50]; + sqlite3_stmt *stmt; + int r, + i; + + sql[0] = '\0'; + + to_hex_str(info_hash, hash); + + strcat(sql, "SELECT ip,port FROM 't"); + strcat(sql, hash); + strcat(sql, "' LIMIT ?"); + + sqlite3_prepare(this->db, sql, -1, &stmt, NULL); + sqlite3_bind_int(stmt, 1, *max_count); + + i = 0; + while (*max_count > i) + { + r = sqlite3_step(stmt); + if (r == SQLITE_ROW) + { + const char *ip = (const char*)sqlite3_column_blob (stmt, 0); + const char *port = (const char*)sqlite3_column_blob (stmt, 1); + + memcpy(&pe[i].ip, ip, 4); + memcpy(&pe[i].port, port, 2); + + i++; + } + else + { + break; + } + } + + printf("%d Clients Dumped.\n", i); + + sqlite3_finalize(stmt); + + *max_count = i; + + 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) + { + char xHash [50]; // we just need 40 + \0 = 41. + sqlite3_stmt *stmt; + char sql [1000]; + int r; + + char *hash = xHash; + to_hex_str(info_hash, hash); + + addTorrent (info_hash); + + + sql[0] = '\0'; + strcat(sql, "REPLACE INTO 't"); + strcat(sql, hash); + strcat(sql, "' (peer_id,ip,port,uploaded,downloaded,left,last_seen) VALUES (?,?,?,?,?,?,?)"); + + // printf("IP->%x::%u\n", pE->ip, pE->port); + + sqlite3_prepare(this->db, sql, -1, &stmt, NULL); + + sqlite3_bind_blob(stmt, 1, (void*)peer_id, 20, NULL); + sqlite3_bind_blob(stmt, 2, (void*)&ip, 4, NULL); + sqlite3_bind_blob(stmt, 3, (void*)&port, 2, NULL); + sqlite3_bind_blob(stmt, 4, (void*)&uploaded, 8, NULL); + sqlite3_bind_blob(stmt, 5, (void*)&downloaded, 8, NULL); + sqlite3_bind_blob(stmt, 6, (void*)&left, 8, NULL); + sqlite3_bind_int(stmt, 7, time(NULL)); + + r = sqlite3_step(stmt); + sqlite3_finalize(stmt); + + strcpy(sql, "REPLACE INTO stats (info_hash,last_mod) VALUES (?,?)"); + sqlite3_prepare (this->db, sql, -1, &stmt, NULL); + sqlite3_bind_blob (stmt, 1, hash, 20, NULL); + sqlite3_bind_int (stmt, 2, time(NULL)); + sqlite3_step (stmt); + sqlite3_finalize (stmt); + + return r; + } + + bool SQLite3Driver::addTorrent (uint8_t info_hash[20]) + { + char xHash [41]; + char *err_msg; + int r; + + _to_hex_str(info_hash, xHash); + + // if non-dynamic, called only when adding to DB. + if (!this->isDynamic()) + { + sqlite3_stmt *stmt; + 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_int(stmt, 1, time(NULL)); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + + string sql = "CREATE TABLE IF NOT EXISTS 't"; + sql += xHash; + sql += "' ("; + sql += "peer_id blob(20)," + "ip blob(4)," + "port blob(2)," + "uploaded blob(8)," // uint64 + "downloaded blob(8)," + "left blob(8)," + "last_seen INT DEFAULT 0"; + + sql += ", CONSTRAINT c1 UNIQUE (ip,port) ON CONFLICT REPLACE)"; + + // create table. + r = sqlite3_exec(this->db, sql.c_str(), NULL, NULL, &err_msg); + printf("E:%s\n", err_msg); + + return (r == SQLITE_OK); + } + + bool SQLite3Driver::isTorrentAllowed(uint8_t info_hash[20]) + { + if (this->isDynamic()) + return true; + sqlite3_stmt *stmt; + sqlite3_prepare(this->db, "SELECT COUNT(*) FROM torrents WHERE info_hash=?", -1, &stmt, NULL); + sqlite3_bind_blob(stmt, 1, info_hash, 20, NULL); + sqlite3_step(stmt); + + int n = sqlite3_column_int(stmt, 1); + sqlite3_finalize(stmt); + return (n == 1); + } + + void SQLite3Driver::cleanup() + { + int exp = time (NULL) - 7200; // 2 hours, expired. + + // drop all peers with no activity for 2 hours. + sqlite3_stmt *getTables; + // torrent table names: t + sqlite3_prepare(this->db, "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 't________________________________________'", -1, &getTables, NULL); + + uint8_t buff [20]; + sqlite3_stmt *updateStats; + assert (sqlite3_prepare(this->db, "REPLACE INTO stats (info_hash,seeders,leechers,last_mod) VALUES (?,?,?,?)", -1, &updateStats, NULL) == SQLITE_OK); + + + while (sqlite3_step(getTables) == SQLITE_ROW) + { + char* tblN = (char*)sqlite3_column_text(getTables, 0); + stringstream sStr; + sStr << "DELETE FROM " << tblN << " WHERE last_seen<" << exp; + + assert (sqlite3_exec(this->db, sStr.str().c_str(), NULL, NULL, NULL) == SQLITE_OK); + + sStr.str (string()); + sStr << "SELECT left,COUNT(*) FROM " << tblN << " GROUP BY left==0"; + + sqlite3_stmt *collectStats; + + sqlite3_prepare(this->db, sStr.str().c_str(), sStr.str().length(), &collectStats, NULL); + cout << "[" << sqlite3_errmsg(this->db) << "]" << endl; + int seeders = 0, leechers = 0; + while (sqlite3_step(collectStats) == SQLITE_ROW) // expecting two results. + { + if (sqlite3_column_int(collectStats, 0) == 0) + seeders = sqlite3_column_int (collectStats, 1); + else + leechers = sqlite3_column_int (collectStats, 1); + } + sqlite3_finalize(collectStats); + + 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); + sqlite3_bind_int(updateStats, 4, time (NULL)); + + sqlite3_step(updateStats); + sqlite3_reset (updateStats); + } + sqlite3_finalize(updateStats); + sqlite3_finalize(getTables); + } + + bool SQLite3Driver::removeTorrent(uint8_t info_hash[20]) + { + // if non-dynamic, remove from table + if (!this->isDynamic()) + { + sqlite3_stmt *stmt; + sqlite3_prepare(this->db, "DELETE FROM torrents WHERE info_hash=?", -1, &stmt, NULL); + sqlite3_bind_blob(stmt, 1, info_hash, 20, NULL); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + + // remove from stats + sqlite3_stmt *rmS; + if (sqlite3_prepare(this->db, "DELETE FROM stats WHERE info_hash=?", -1, &rmS, NULL) != SQLITE_OK) + { + sqlite3_finalize(rmS); + return false; + } + sqlite3_bind_blob(rmS, 1, (const void*)info_hash, 20, NULL); + sqlite3_step(rmS); + sqlite3_finalize(rmS); + + // remove table + string str = "DROP TABLE IF EXISTS 't"; + char buff [41]; + str += _to_hex_str(info_hash, buff); + str += "'"; + + sqlite3_exec(this->db, str.c_str(), NULL, NULL, NULL); + + return true; + } + + bool SQLite3Driver::removePeer(uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port) + { + char sql [1000]; + char xHash [50]; + sqlite3_stmt *stmt; + + _to_hex_str (info_hash, xHash); + + strcpy (sql, "DELETE FROM 't"); + strcat (sql, xHash); + strcat (sql, "' WHERE ip=? AND port=? AND peer_id=?"); + + sqlite3_prepare (this->db, sql, -1, &stmt, NULL); + + 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); + + sqlite3_step(stmt); + + sqlite3_finalize(stmt); + + return true; + } + + static uint64_t _genCiD (uint32_t ip, uint16_t port) + { + uint64_t x; + x = (time(NULL) / 3600) * port; // x will probably overload. + x = (ip ^ port); + x <<= 16; + x |= (~port); + return x; + } + + bool SQLite3Driver::genConnectionId (uint64_t *connectionId, uint32_t ip, uint16_t port) + { + *connectionId = _genCiD(ip, port); + return true; + } + + bool SQLite3Driver::verifyConnectionId(uint64_t cId, uint32_t ip, uint16_t port) + { + if (cId == _genCiD(ip, port)) + return true; + else + return false; + } + + SQLite3Driver::~SQLite3Driver() + { + sqlite3_close(this->db); + } + }; +}; diff --git a/src/db/driver_sqlite.hpp b/src/db/driver_sqlite.hpp new file mode 100644 index 0000000..34b28d9 --- /dev/null +++ b/src/db/driver_sqlite.hpp @@ -0,0 +1,55 @@ +/* + * Copyright © 2012,2013 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 . + */ + +#ifndef DATABASE_H_ +#define DATABASE_H_ + +#include +#include "database.hpp" +#include + +namespace UDPT +{ + namespace Data + { + class SQLite3Driver : public DatabaseDriver + { + public: + SQLite3Driver (Settings::SettingClass *sc, bool isDyn = false); + bool addTorrent (uint8_t info_hash[20]); + bool removeTorrent (uint8_t info_hash[20]); + bool genConnectionId (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 removePeer (uint8_t peer_id [20], uint8_t info_hash [20], uint32_t ip, uint16_t port); + bool getTorrentInfo (TorrentEntry *e); + bool isTorrentAllowed (uint8_t info_hash[20]); + bool getPeers (uint8_t info_hash [20], int *max_count, PeerEntry *pe); + void cleanup (); + + ~SQLite3Driver (); + private: + sqlite3 *db; + + void doSetup (); + }; + }; +}; + +#endif /* DATABASE_H_ */ diff --git a/src/udpTracker.cpp b/src/udpTracker.cpp index 55efe2b..391a80f 100644 --- a/src/udpTracker.cpp +++ b/src/udpTracker.cpp @@ -25,6 +25,7 @@ #include using namespace std; +using namespace UDPT::Data; #define UDP_BUFFER_SIZE 2048 @@ -91,8 +92,6 @@ namespace UDPT this->port = (s_port == "" ? 6969 : atoi (s_port.c_str())); this->thread_count = (s_threads == "" ? 5 : atoi (s_threads.c_str())) + 1; - cout << "port=" << this->port << endl; - this->threads = new HANDLE[this->thread_count]; this->isRunning = false; @@ -131,7 +130,7 @@ namespace UDPT cout << "Thread (" << ( i + 1) << "/" << ((int)this->thread_count) << ") terminated." << endl; } if (this->conn != NULL) - db_close(this->conn); + delete this->conn; delete[] this->threads; } @@ -148,11 +147,7 @@ namespace UDPT if (sock == INVALID_SOCKET) return START_ESOCKET_FAILED; - #ifdef WIN32 - recvAddr.sin_addr.S_un.S_addr = 0L; - #elif defined (linux) recvAddr.sin_addr.s_addr = 0L; - #endif recvAddr.sin_family = AF_INET; recvAddr.sin_port = m_hton16 (this->port); @@ -173,11 +168,7 @@ namespace UDPT this->sock = sock; - dbname = this->o_settings->get ("database", "file"); - if (dbname == "") - dbname = "tracker.db"; - - db_open(&this->conn, dbname.c_str()); + this->conn = new Data::SQLite3Driver (this->o_settings->getClass("database"), true); this->isRunning = true; cout << "Starting maintenance thread (1/" << ((int)this->thread_count) << ")" << endl; @@ -186,7 +177,7 @@ namespace UDPT #ifdef WIN32 this->threads[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_maintainance_start, (LPVOID)this, 0, NULL); #elif defined (linux) - pthread_create (&usi->threads[0], NULL, _maintainance_start, usi); + pthread_create (&usi->threads[0], NULL, _maintainance_start, (void*)this); #endif for (i = 1;i < this->thread_count; i++) @@ -195,26 +186,13 @@ namespace UDPT #ifdef WIN32 this->threads[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_thread_start, (LPVOID)this, 0, NULL); #elif defined (linux) - pthread_create (&(this->threads[i]), NULL, _thread_start, this); + pthread_create (&(this->threads[i]), NULL, _thread_start, (void*)this); #endif } return START_OK; } -static uint64_t _get_connID (SOCKADDR_IN *remote) -{ - int base; - uint64_t x; - - base = time(NULL); - base /= 3600; // changes every hour. - - x = base; - x += remote->sin_addr.s_addr; - return x; -} - int UDPTracker::sendError (UDPTracker *usi, SOCKADDR_IN *remote, uint32_t transactionID, const string &msg) { struct udp_error_response error; @@ -248,7 +226,13 @@ static uint64_t _get_connID (SOCKADDR_IN *remote) resp.action = m_hton32(0); resp.transaction_id = req->transaction_id; - resp.connection_id = _get_connID(remote); + + if (!usi->conn->genConnectionId(&resp.connection_id, + m_hton32(remote->sin_addr.s_addr), + m_hton16(remote->sin_port))) + { + return 1; + } sendto(usi->sock, (char*)&resp, sizeof(ConnectionResponse), 0, (SOCKADDR*)remote, sizeof(SOCKADDR_IN)); @@ -262,16 +246,16 @@ static uint64_t _get_connID (SOCKADDR_IN *remote) int q, // peer counts bSize, // message size i; // loop index - db_peerEntry *peers; - int32_t seeders, - leechers, - completed; - db_peerEntry pE; // info for DB + DatabaseDriver::PeerEntry *peers; + DatabaseDriver::TorrentEntry tE; + uint8_t buff [1028]; // Reasonable buffer size. (header+168 peers) req = (AnnounceRequest*)data; - if (req->connection_id != _get_connID(remote)) + if (!usi->conn->verifyConnectionId(req->connection_id, + m_hton32(remote->sin_addr.s_addr), + m_hton16(remote->sin_port))) { return 1; } @@ -291,25 +275,54 @@ static uint64_t _get_connID (SOCKADDR_IN *remote) return 0; } + if (!usi->conn->isTorrentAllowed(req->info_hash)) + { + UDPTracker::sendError(usi, remote, req->transaction_id, "info_hash not registered."); + return 0; + } + // load peers q = 30; if (req->num_want >= 1) q = min (q, req->num_want); - peers = (db_peerEntry*)malloc (sizeof(db_peerEntry) * q); + peers = new DatabaseDriver::PeerEntry [q]; - db_load_peers(usi->conn, req->info_hash, peers, &q); + + DatabaseDriver::TrackerEvents event; + switch (req->event) + { + case 1: + 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 (event == DatabaseDriver::EVENT_STOP) + q = 0; // no need for peers when stopping. + + if (q > 0) + usi->conn->getPeers(req->info_hash, &q, peers); bSize = 20; // header is 20 bytes bSize += (6 * q); // + 6 bytes per peer. - db_get_stats (usi->conn, req->info_hash, &seeders, &leechers, &completed); + tE.info_hash = req->info_hash; + usi->conn->getTorrentInfo(&tE); resp = (AnnounceResponse*)buff; resp->action = m_hton32(1); resp->interval = m_hton32 ( usi->announce_interval ); - resp->leechers = m_hton32(leechers); - resp->seeders = m_hton32 (seeders); + resp->leechers = m_hton32(tE.leechers); + resp->seeders = m_hton32 (tE.seeders); resp->transaction_id = req->transaction_id; for (i = 0;i < q;i++) @@ -328,24 +341,17 @@ static uint64_t _get_connID (SOCKADDR_IN *remote) buff[25 + x] = (peers[i].port & 0xff); } - free (peers); + delete[] peers; sendto(usi->sock, (char*)buff, bSize, 0, (SOCKADDR*)remote, sizeof(SOCKADDR_IN)); - // Add peer to list: - pE.downloaded = req->downloaded; - pE.uploaded = req->uploaded; - pE.left = req->left; - pE.peer_id = req->peer_id; + // update DB. + uint32_t ip; if (req->ip_address == 0) // default - { - pE.ip = m_hton32 (remote->sin_addr.s_addr); - } + ip = m_hton32 (remote->sin_addr.s_addr); else - { - pE.ip = req->ip_address; - } - pE.port = req->port; - db_add_peer(usi->conn, req->info_hash, &pE); + ip = req->ip_address; + usi->conn->updatePeer(req->peer_id, req->info_hash, ip, req->port, + req->downloaded, req->left, req->uploaded, event); return 0; } @@ -373,6 +379,13 @@ static uint64_t _get_connID (SOCKADDR_IN *remote) return 0; } + if (!usi->conn->verifyConnectionId(sR->connection_id, + m_hton32(remote->sin_addr.s_addr), + m_hton16(remote->sin_port))) + { + return 1; + } + // get torrent count. c = v / 20; @@ -382,7 +395,6 @@ static uint64_t _get_connID (SOCKADDR_IN *remote) for (i = 0;i < c;i++) { - int32_t s, c, l; int32_t *seeders, *completed, *leechers; @@ -398,11 +410,17 @@ static uint64_t _get_connID (SOCKADDR_IN *remote) completed = (int32_t*)&buffer[i*12+12]; leechers = (int32_t*)&buffer[i*12+16]; - db_get_stats (usi->conn, hash, &s, &l, &c); + DatabaseDriver::TorrentEntry tE; + tE.info_hash = hash; + if (!usi->conn->getTorrentInfo(&tE)) + { + sendError(usi, remote, sR->transaction_id, "Scrape Failed: couldn't retrieve torrent data"); + return 0; + } - *seeders = m_hton32 (s); - *completed = m_hton32 (c); - *leechers = m_hton32 (l); + *seeders = m_hton32 (tE.seeders); + *completed = m_hton32 (tE.completed); + *leechers = m_hton32 (tE.leechers); } cout.flush(); @@ -436,7 +454,7 @@ static int _isIANA_IP (uint32_t ip) } } - cout << ":: " << (void*)remote->sin_addr.s_addr << ": " << remote->sin_port << " ACTION=" << action << endl; + cout << ":: " << (void*)m_hton32(remote->sin_addr.s_addr) << ": " << m_hton16(remote->sin_port) << " ACTION=" << action << endl; if (action == 0 && r >= 16) return UDPTracker::handleConnection (usi, remote, data); @@ -499,10 +517,10 @@ static int _isIANA_IP (uint32_t ip) while (usi->isRunning) { - db_cleanup (usi->conn); + usi->conn->cleanup(); #ifdef WIN32 - Sleep (usi->cleanup_interval * 1000); // wait 2 minutes between every cleanup. + Sleep (usi->cleanup_interval * 1000); #elif defined (linux) sleep (usi->cleanup_interval); #else diff --git a/src/udpTracker.hpp b/src/udpTracker.hpp index 864886f..f0bd18e 100644 --- a/src/udpTracker.hpp +++ b/src/udpTracker.hpp @@ -23,7 +23,7 @@ #include #include "multiplatform.h" -#include "db/database.h" +#include "db/driver_sqlite.hpp" #include "settings.hpp" #include @@ -142,7 +142,7 @@ namespace UDPT uint8_t settings; Settings *o_settings; - dbConnection *conn; + Data::DatabaseDriver *conn; #ifdef WIN32 static DWORD _thread_start (LPVOID arg);