Commit cb077850 authored by louiz’'s avatar louiz’

Implement the protocol chunks as a Command class.

This makes the data easier to track and free after
they were sent. And it makes sure the buffer stays valid
for all the async_write() process.
It’s also easier to send data without having to bother with
creating a string, parsing everything, etc.
parent cdc54788
......@@ -117,6 +117,7 @@ file(
src/network/command_handler.*
src/network/server.*
src/network/transfer_sender.*
src/network/command.*
)
add_library(server STATIC ${source_server})
......@@ -124,6 +125,7 @@ target_link_libraries(
server
logging
config
database
)
file(
......@@ -132,6 +134,7 @@ file(
src/network/client.*
src/network/command_handler.*
src/network/transfer_receiver.*
src/network/command.*
)
add_library(client STATIC ${source_client})
......@@ -141,6 +144,20 @@ target_link_libraries(
config
)
file(
GLOB
source_game
src/game/*.[ch]pp
)
add_library(game STATIC ${source_game})
target_link_libraries(
game
client
logging
config
)
file(
GLOB
......
......@@ -208,7 +208,7 @@ bool Database::update(const DbObject* object, const std::string& table_name)
if (this->do_update(query) == false)
return false;
return true;
};
}
bool Database::remove(const DbObject* object, const std::string& table_name)
{
......@@ -229,4 +229,4 @@ bool Database::remove(const DbObject* object, const std::string& table_name)
if (this->do_remove(query) == false)
return false;
return true;
};
}
......@@ -43,16 +43,24 @@ void Game::on_connection_success(const std::string& login, const std::string& pa
void Game::authenticate(const std::string& login, const std::string& password)
{
// TODO send a password hash, and do not use that stupid separator.
this->client.request_answer("AUTH", std::string(login + '*' + password).data(), boost::bind(&Game::on_authenticate, this, _1));
Command* command = new Command;
command->set_name("AUTH");
std::string body = login + '*' + password;
command->set_body(body.data(), body.size());
this->client.request_answer(command, boost::bind(&Game::on_authenticate, this, _1));
}
void Game::request_file(const std::string& filename)
{
this->client.request_answer("TRANSFER", filename.data());
Command* command = new Command;
command->set_name("TRANSFER");
command->set_body(filename.data(), filename.size());
this->client.send(command);
}
void Game::on_authenticate(const std::string& result)
void Game::on_authenticate(Command* received_command)
{
std::string result(received_command->body, received_command->body_size);
int res = atoi(result.data());
log_debug("on_authenticate :" << res << "." << ((res > 4) ? "Unknown error" : auth_messages[res]));
if (res == 0)
......@@ -68,15 +76,3 @@ void Game::run()
usleep(10000); // replace with game logic
}
}
// Test main
// Simulate a login flow
int main(int argc, char** argv)
{
Config::read_conf("../../batajelo.conf");
Game game;
game.on_login_form_validated("testing", "new_pass", "127.0.0.1", 7878);
game.run();
return 0;
}
// The main class for the game itself
// Network
#include <network/client.hpp>
#ifndef __GAME_HPP__
# define __GAME_HPP__
#include <network/client.hpp>
#include <network/command.hpp>
class Game
{
public:
......@@ -16,7 +15,7 @@ public:
void on_connection_failed(const std::string&, const short&);
void on_connection_success(const std::string&, const std::string&);
void authenticate(const std::string&, const std::string&);
void on_authenticate(const std::string&);
void on_authenticate(Command*);
/**
* Sends a request for a file transfer.
* @param filename The file that we want to receive.
......
......@@ -30,15 +30,3 @@ Logger::~Logger()
delete this->_stream;
delete this->null_stream;
}
// Usage example
// int main(int argc, char *argv[])
// {
// Config::read_conf("../config/batajelo.conf");
// int i;
// i = Config::get("log_file", "") == "" ? 0 : 1;
// log_debug("Some debug information: " << i);
// log_info("some information");
// log_error("some error");
// return 0;
// }
......@@ -58,8 +58,9 @@
/**
* Juste a structure representing a stream doing nothing with its input.
*/
struct nullstream: public std::ostream
class nullstream: public std::ostream
{
public:
nullstream():
std::ostream(0)
{ }
......
......@@ -53,12 +53,12 @@ void Client::connect_handler(boost::function< void(void) > on_success,
void Client::install_callbacks()
{
this->install_callback("TRANSFER", boost::bind(&Client::transfer_init_callback, this, _1, _2));
this->install_callback("TRANSFER", boost::bind(&Client::transfer_init_callback, this, _1));
}
void Client::transfer_init_callback(const char* carg, int length)
void Client::transfer_init_callback(Command* received_command)
{
std::string arg(carg, length);
std::string arg(received_command->body, received_command->body_size);
std::vector<std::string> args;
boost::split(args, arg, boost::is_any_of("|"));
if (args.size() != 3)
......@@ -79,7 +79,7 @@ void Client::on_transfer_ended(const TransferReceiver* receiver)
if (*it == receiver)
this->receivers.erase(it);
delete receiver;
exit(0);
}
void Client::poll(void)
......
......@@ -20,6 +20,7 @@
# define __CLIENT_HPP__
#include <network/command_handler.hpp>
#include <network/command.hpp>
#include <network/transfer_receiver.hpp>
using boost::asio::ip::tcp;
......@@ -63,7 +64,7 @@ private:
* Called when the server initiate a file transfer with us.
* Install the temporary callback to receive each file chunk.
*/
void transfer_init_callback(const char*, int);
void transfer_init_callback(Command*);
std::vector<TransferReceiver*> receivers;
boost::asio::io_service io_service;
......
#include <network/command.hpp>
Command::Command():
body(0),
body_size(0)
{
}
Command::~Command()
{
delete[] this->body;
}
void Command::set_body(const char* body, int size)
{
if (size == -1)
size = ::strlen(body);
this->body = new char[size];
::memcpy(this->body, body, size);
this->set_body_size(size);
}
void Command::set_name(const std::string name)
{
this->name = name;
}
inline void Command::set_body_size(int size)
{
this->body_size = size;
}
void Command::pack()
{
std::ostringstream slength;
slength << this->body_size;
this->header = std::string(this->name) + "." + slength.str() + ":";
}
/** @addtogroup Network
* @{
*/
/**
* Represents one single network command.
* A command has a header and a body (also called binary part, which can have
* size of 0).
* The header is COMMAND_NAME.BODY_SIZE:
* The body can be anything, but it is BODY_SIZE bytes long.
* The \n char is not used anywhere in the protocol, and there's no delimiter
* between a body and the header of the next message.
*
* To send a command, a Command object must be created anywhere and filled with
* the correct data and then passed to the CommandHandler::send() method.
* The object will be deleted by the send_handler, after it has been
* succesfully sent.
*
* A Command object is passed by a CommandHandler to the callback associated
* with a command name. This callback is responsible for deleting the command
* object. (MAYBE)
* @class Command
*/
#include <string>
#include <iostream>
#include <cstring>
#include <fstream>
#include <sstream>
#ifndef __COMMAND_HPP__
# define __COMMAND_HPP__
#include <logging/logging.hpp>
class Command
{
public:
Command();
~Command();
/**
* Sets the body of the message. A char* will be new[]ed using the size, and
* the data will be copied in it. To avoid that copy, see the body attribute
* and the set_data() method. You should use one of the two methods on an
* object, not both.
*/
void set_body(const char*, int size = -1);
/**
* If you manually set the content of the body member, use this method to
* set the proper body size. Do not use it after set_body() though.
*/
void set_body_size(int size);
/**
* This must be called before the object is passed to CommandHandler::send(),
* it will set the header correctly, using the body size etc.
*/
void pack();
void set_name(const std::string);
std::string get_name() { return this->name; }
/**
* Use this member to manually set the body. For example you can pass it to
* a ostream::get() method after having manually new[]ed it. This avoid
* copying data twice.
*/
char* body;
std::string header;
std::string name;
size_t body_size;
private:
Command(const Command&);
Command& operator=(const Command&);
};
#endif // __COMMAND_HPP__
......@@ -18,9 +18,19 @@ void CommandHandler::install_callback_once(const std::string& command,
this->callbacks_once[command] = callback;
}
void CommandHandler::remove_callback(const std::string& command)
{
std::map<const std::string, t_read_callback >::iterator it;
it = this->callbacks.find(command);
if (it != this->callbacks.end())
this->callbacks.erase(it);
else
log_warning("Could not remove callback: " << command);
}
t_read_callback CommandHandler::get_callback(const std::string& command)
{
log_debug("get_callback");
std::map<const std::string, t_read_callback >::iterator it;
it = this->callbacks.find(command);
......@@ -59,50 +69,58 @@ void CommandHandler::read_handler(const boost::system::error_code& error, const
while (c[pos] && c[pos] != '.')
pos++;
std::string command;
std::string command_name;
std::size_t size;
if (pos == bytes_transferred)
{ // no . was found
command = std::string(c, pos-1);
command_name = std::string(c, pos-1);
size = 0;
}
else
{
command = std::string(c, pos);
command_name = std::string(c, pos);
size = atoi(std::string(c+pos+1, bytes_transferred-pos-2).data()); // remove the ending :
}
log_debug(command << " . " << size);
log_debug(command_name << " . " << size);
Command* command = new Command;
command->set_name(command_name);
delete[] c;
// Find out if a callback was registered for that command.
t_read_callback callback = this->get_callback(command);
t_read_callback callback = this->get_callback(command_name);
// We check what we need to read on the socket to have the rest of the binary datas
const std::size_t length_to_read = this->data.size() >= size ? 0 : size - this->data.size();
log_debug("length to read on the socket: " << length_to_read);
boost::asio::async_read(*this->socket,
this->data,
boost::asio::transfer_at_least(length_to_read),
boost::bind(&CommandHandler::binary_read_handler, this,
boost::asio::placeholders::error,
command,
size, callback));
}
void CommandHandler::binary_read_handler(const boost::system::error_code& error,
Command* command,
std::size_t bytes_transferred,
// std::size_t size,
t_read_callback callback)
{
if (error)
{
log_error("binary_read_handler failed: "<< error);
exit(1);
}
log_debug("binary_read_handler " << bytes_transferred);
char *c = new char[bytes_transferred+1];
this->data.sgetn(c, bytes_transferred);
command->body = new char[bytes_transferred];
this->data.sgetn(command->body, bytes_transferred);
command->set_body_size(bytes_transferred);
c[bytes_transferred] = 0;
if (callback)
callback(c, bytes_transferred);
callback(command);
else
log_debug("no callback");
delete[] c;
delete command;
this->install_read_handler();
}
......@@ -119,42 +137,38 @@ void CommandHandler::install_read_handler(void)
// Send a command and add a callback to be called when the answer to
// this command will be received
void CommandHandler::request_answer(const char* command, const char* data,
t_read_callback on_answer)
void CommandHandler::request_answer(Command* command, t_read_callback on_answer)
{
std::string msg(command);
std::ostringstream ssize;
ssize << strlen(data);
msg += std::string(".") + std::string(ssize.str()) + std::string(":") + std::string(data);
// We may want to send a command that do not require an answer.
if (on_answer)
this->install_callback_once(command, on_answer);
this->send(msg.data());
this->install_callback_once(command->get_name(), on_answer);
this->send(command);
}
void CommandHandler::send(const char* msg, boost::function< void(void) > on_sent, int length)
void CommandHandler::send(Command* command, boost::function< void(void) > on_sent)
{
if (length == 0)
length = strlen(msg);
command->pack();
std::vector<boost::asio::const_buffer> buffs;
buffs.push_back(boost::asio::buffer(command->header.data(), command->header.length()));
buffs.push_back(boost::asio::buffer(command->body, command->body_size));
async_write(*this->socket,
boost::asio::buffer(msg, length),
boost::bind(&CommandHandler::send_handler, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
on_sent));
log_debug("Sending [" << length << "] bytes");
buffs,
boost::bind(&CommandHandler::send_handler, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
on_sent, command));
}
void CommandHandler::send_handler(const boost::system::error_code& error,
std::size_t bytes_transferred,
boost::function< void(void) > on_sent)
boost::function< void(void) > on_sent, Command* command)
{
assert(bytes_transferred == command->header.length() + command->body_size);
delete command;
// TODO check for error
if (error)
exit(1);
log_debug(bytes_transferred << " bytes sent");
if (on_sent)
on_sent();
}
......@@ -14,16 +14,16 @@
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <logging/logging.hpp>
#ifndef __COMMAND_HANDLER_HPP__
# define __COMMAND_HANDLER_HPP__
#include <logging/logging.hpp>
#include <network/transfer_sender.hpp>
#include <network/command.hpp>
using boost::asio::ip::tcp;
typedef boost::function<void(const char*, int)> t_read_callback;
typedef boost::function<void(Command*)> t_read_callback;
class CommandHandler
{
......@@ -43,12 +43,12 @@ public:
* Read the arguments after a command (can read 0 bytes too) and pass that
* to the callback that was associated with this command.
*/
void binary_read_handler(const boost::system::error_code&, std::size_t, t_read_callback);
void binary_read_handler(const boost::system::error_code&, Command*, std::size_t, t_read_callback);
/**
* Sends a command, and use install_callback_once to wait for the answer
* and call that callback to handle it.
*/
void request_answer(const char*, const char*, t_read_callback on_answer = 0);
void request_answer(Command*, t_read_callback on_answer = 0);
/**
* Install a new callback associated with a command. This callback will
* be called upon receiving that command.
......@@ -62,6 +62,15 @@ public:
*/
void install_callback_once(const std::string&, t_read_callback);
/**
* Remove a callback that has been installed.
*/
void remove_callback(const std::string&);
/**
* Send the given command on the socket.
*/
void send(Command* command, boost::function< void(void) > on_sent = 0);
protected:
/**
* Returns the callback associated with the passed command name.
......@@ -69,14 +78,10 @@ protected:
* return value cause a failure.
*/
t_read_callback get_callback(const std::string&);
/**
* Send the given data on the socket.
*/
void send(const char*, boost::function< void(void) > on_send = 0, int length = 0);
/**
* @todo Check if the data was correctly sent on the socket
*/
void send_handler(const boost::system::error_code&, std::size_t, boost::function< void(void) > on_sent);
void send_handler(const boost::system::error_code&, std::size_t, boost::function< void(void) >, Command*);
/**
* A buffer keeping the data that is read on the socket.
*/
......
......@@ -34,19 +34,21 @@ User* RemoteClient::get_user()
void RemoteClient::install_callbacks()
{
this->install_callback("AUTH", boost::bind(&RemoteClient::auth_callback, this, _1, _2));
this->install_callback("TRANSFER", boost::bind(&RemoteClient::transfer_callback, this, _1, _2));
this->install_callback("AUTH", boost::bind(&RemoteClient::auth_callback, this, _1));
this->install_callback("TRANSFER", boost::bind(&RemoteClient::transfer_callback, this, _1));
}
void RemoteClient::auth_callback(const char* carg, int)
void RemoteClient::auth_callback(Command* received_command)
{
std::string arg(carg);
std::string res("AUTH.");
Command* command = new Command();
std::string arg(received_command->body, received_command->body_size);
std::string name("AUTH");
std::string body;
bool success = false;
log_debug("auth_callback: " << arg);
size_t pos = arg.find('*');
if (pos == std::string::npos)
res += "1:1";
body = "1";
else
{
std::string login = arg.substr(0, pos);
......@@ -57,36 +59,38 @@ void RemoteClient::auth_callback(const char* carg, int)
if (user == 0)
{
log_info("Authentication: User " << login << " does not exist in database.");
res += "1:2";
body = "2";
}
else if (user->get("password") != password)
{
log_info("Authentication: Invalid password for user " << login);
res += "1:3";
delete user;
}
else if (this->server->find_client_by_login(login) != 0)
{
log_info("Authentication: user already logged in from an other location: " << login);
res += "1:4";
body = "3";
delete user;
}
// else if (this->server->find_client_by_login(login) != 0)
// {
// log_info("Authentication: user already logged in from an other location: " << login);
// body = "4";
// delete user;
// }
else
{
log_info("Authentication: succes for user " << login);
res += "1:0";
body = "0";
this->user = static_cast<User*>(user);
success = true;
}
}
this->send(res.data());
command->set_name(name);
command->set_body(body.c_str());
this->send(command);
if (success)
this->on_auth_success();
}
void RemoteClient::transfer_callback(const char* filename, int length)
void RemoteClient::transfer_callback(Command* received_command)
{
this->send_file(std::string(filename, length));
this->send_file(std::string(received_command->body, received_command->body_size));
}
void RemoteClient::on_auth_success()
......@@ -103,7 +107,9 @@ void RemoteClient::start()
void RemoteClient::send_file(const std::string& filename)
{
log_debug("Avant creation du transfert");
TransferSender* sender = new TransferSender(this, filename);
log_debug("Apres creation du transfert");
if (sender->start() == true)
{
this->senders.push_back(sender);
......
......@@ -19,6 +19,7 @@
#include <database/user.hpp>
#include <network/command_handler.hpp>
#include <network/command.hpp>
#include <network/transfer_sender.hpp>
class Server;
......@@ -73,8 +74,8 @@ private:
*/
void install_callbacks();
void install_read_handler(void);
void auth_callback(const char*, int);
void transfer_callback(const char*, int);
void auth_callback(Command*);
void transfer_callback(Command*);
const unsigned long int number;