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( ...@@ -117,6 +117,7 @@ file(
src/network/command_handler.* src/network/command_handler.*
src/network/server.* src/network/server.*
src/network/transfer_sender.* src/network/transfer_sender.*
src/network/command.*
) )
add_library(server STATIC ${source_server}) add_library(server STATIC ${source_server})
...@@ -124,6 +125,7 @@ target_link_libraries( ...@@ -124,6 +125,7 @@ target_link_libraries(
server server
logging logging
config config
database
) )
file( file(
...@@ -132,6 +134,7 @@ file( ...@@ -132,6 +134,7 @@ file(
src/network/client.* src/network/client.*
src/network/command_handler.* src/network/command_handler.*
src/network/transfer_receiver.* src/network/transfer_receiver.*
src/network/command.*
) )
add_library(client STATIC ${source_client}) add_library(client STATIC ${source_client})
...@@ -141,6 +144,20 @@ target_link_libraries( ...@@ -141,6 +144,20 @@ target_link_libraries(
config config
) )
file(
GLOB
source_game
src/game/*.[ch]pp
)
add_library(game STATIC ${source_game})
target_link_libraries(
game
client
logging
config
)
file( file(
GLOB GLOB
......
...@@ -208,7 +208,7 @@ bool Database::update(const DbObject* object, const std::string& table_name) ...@@ -208,7 +208,7 @@ bool Database::update(const DbObject* object, const std::string& table_name)
if (this->do_update(query) == false) if (this->do_update(query) == false)
return false; return false;
return true; return true;
}; }
bool Database::remove(const DbObject* object, const std::string& table_name) 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) ...@@ -229,4 +229,4 @@ bool Database::remove(const DbObject* object, const std::string& table_name)
if (this->do_remove(query) == false) if (this->do_remove(query) == false)
return false; return false;
return true; return true;
}; }
...@@ -43,16 +43,24 @@ void Game::on_connection_success(const std::string& login, const std::string& pa ...@@ -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) void Game::authenticate(const std::string& login, const std::string& password)
{ {
// TODO send a password hash, and do not use that stupid separator. // 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) 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()); int res = atoi(result.data());
log_debug("on_authenticate :" << res << "." << ((res > 4) ? "Unknown error" : auth_messages[res])); log_debug("on_authenticate :" << res << "." << ((res > 4) ? "Unknown error" : auth_messages[res]));
if (res == 0) if (res == 0)
...@@ -68,15 +76,3 @@ void Game::run() ...@@ -68,15 +76,3 @@ void Game::run()
usleep(10000); // replace with game logic 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 // The main class for the game itself
// Network
#include <network/client.hpp>
#ifndef __GAME_HPP__ #ifndef __GAME_HPP__
# define __GAME_HPP__ # define __GAME_HPP__
#include <network/client.hpp>
#include <network/command.hpp>
class Game class Game
{ {
public: public:
...@@ -16,7 +15,7 @@ public: ...@@ -16,7 +15,7 @@ public:
void on_connection_failed(const std::string&, const short&); void on_connection_failed(const std::string&, const short&);
void on_connection_success(const std::string&, const std::string&); void on_connection_success(const std::string&, const std::string&);
void authenticate(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. * Sends a request for a file transfer.
* @param filename The file that we want to receive. * @param filename The file that we want to receive.
......
...@@ -30,15 +30,3 @@ Logger::~Logger() ...@@ -30,15 +30,3 @@ Logger::~Logger()
delete this->_stream; delete this->_stream;
delete this->null_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 @@ ...@@ -58,8 +58,9 @@
/** /**
* Juste a structure representing a stream doing nothing with its input. * Juste a structure representing a stream doing nothing with its input.
*/ */
struct nullstream: public std::ostream class nullstream: public std::ostream
{ {
public:
nullstream(): nullstream():
std::ostream(0) std::ostream(0)
{ } { }
......
...@@ -53,12 +53,12 @@ void Client::connect_handler(boost::function< void(void) > on_success, ...@@ -53,12 +53,12 @@ void Client::connect_handler(boost::function< void(void) > on_success,
void Client::install_callbacks() 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; std::vector<std::string> args;
boost::split(args, arg, boost::is_any_of("|")); boost::split(args, arg, boost::is_any_of("|"));
if (args.size() != 3) if (args.size() != 3)
...@@ -79,7 +79,7 @@ void Client::on_transfer_ended(const TransferReceiver* receiver) ...@@ -79,7 +79,7 @@ void Client::on_transfer_ended(const TransferReceiver* receiver)
if (*it == receiver) if (*it == receiver)
this->receivers.erase(it); this->receivers.erase(it);
delete receiver; delete receiver;
exit(0);
} }
void Client::poll(void) void Client::poll(void)
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
# define __CLIENT_HPP__ # define __CLIENT_HPP__
#include <network/command_handler.hpp> #include <network/command_handler.hpp>
#include <network/command.hpp>
#include <network/transfer_receiver.hpp> #include <network/transfer_receiver.hpp>
using boost::asio::ip::tcp; using boost::asio::ip::tcp;
...@@ -63,7 +64,7 @@ private: ...@@ -63,7 +64,7 @@ private:
* Called when the server initiate a file transfer with us. * Called when the server initiate a file transfer with us.
* Install the temporary callback to receive each file chunk. * 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; std::vector<TransferReceiver*> receivers;
boost::asio::io_service io_service; 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, ...@@ -18,9 +18,19 @@ void CommandHandler::install_callback_once(const std::string& command,
this->callbacks_once[command] = callback; 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) t_read_callback CommandHandler::get_callback(const std::string& command)
{ {
log_debug("get_callback");
std::map<const std::string, t_read_callback >::iterator it; std::map<const std::string, t_read_callback >::iterator it;
it = this->callbacks.find(command); it = this->callbacks.find(command);
...@@ -59,50 +69,58 @@ void CommandHandler::read_handler(const boost::system::error_code& error, const ...@@ -59,50 +69,58 @@ void CommandHandler::read_handler(const boost::system::error_code& error, const
while (c[pos] && c[pos] != '.') while (c[pos] && c[pos] != '.')
pos++; pos++;
std::string command; std::string command_name;
std::size_t size; std::size_t size;
if (pos == bytes_transferred) if (pos == bytes_transferred)
{ // no . was found { // no . was found
command = std::string(c, pos-1); command_name = std::string(c, pos-1);
size = 0; size = 0;
} }
else 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 : 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; delete[] c;
// Find out if a callback was registered for that command. // 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 // 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(); 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, boost::asio::async_read(*this->socket,
this->data, this->data,
boost::asio::transfer_at_least(length_to_read), boost::asio::transfer_at_least(length_to_read),
boost::bind(&CommandHandler::binary_read_handler, this, boost::bind(&CommandHandler::binary_read_handler, this,
boost::asio::placeholders::error, boost::asio::placeholders::error,
command,
size, callback)); size, callback));
} }
void CommandHandler::binary_read_handler(const boost::system::error_code& error, void CommandHandler::binary_read_handler(const boost::system::error_code& error,
Command* command,
std::size_t bytes_transferred, std::size_t bytes_transferred,
// std::size_t size,
t_read_callback callback) t_read_callback callback)
{ {
if (error)
{
log_error("binary_read_handler failed: "<< error);
exit(1);
}
log_debug("binary_read_handler " << bytes_transferred); log_debug("binary_read_handler " << bytes_transferred);
char *c = new char[bytes_transferred+1]; command->body = new char[bytes_transferred];
this->data.sgetn(c, bytes_transferred); this->data.sgetn(command->body, bytes_transferred);
command->set_body_size(bytes_transferred);
c[bytes_transferred] = 0;
if (callback) if (callback)
callback(c, bytes_transferred); callback(command);
else else
log_debug("no callback"); log_debug("no callback");
delete[] c; delete command;
this->install_read_handler(); this->install_read_handler();
} }
...@@ -119,42 +137,38 @@ void CommandHandler::install_read_handler(void) ...@@ -119,42 +137,38 @@ void CommandHandler::install_read_handler(void)
// Send a command and add a callback to be called when the answer to // Send a command and add a callback to be called when the answer to
// this command will be received // this command will be received
void CommandHandler::request_answer(const char* command, const char* data, void CommandHandler::request_answer(Command* command, t_read_callback on_answer)
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. // We may want to send a command that do not require an answer.
if (on_answer) if (on_answer)
this->install_callback_once(command, on_answer); this->install_callback_once(command->get_name(), on_answer);
this->send(msg.data()); 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) command->pack();
length = strlen(msg); 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, async_write(*this->socket,
boost::asio::buffer(msg, length), buffs,
boost::bind(&CommandHandler::send_handler, this, boost::bind(&CommandHandler::send_handler, this,
boost::asio::placeholders::error, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred, boost::asio::placeholders::bytes_transferred,
on_sent)); on_sent, command));
log_debug("Sending [" << length << "] bytes");
} }
void CommandHandler::send_handler(const boost::system::error_code& error, void CommandHandler::send_handler(const boost::system::error_code& error,
std::size_t bytes_transferred, 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 // TODO check for error
if (error) if (error)
exit(1); exit(1);
log_debug(bytes_transferred << " bytes sent");
if (on_sent) if (on_sent)
on_sent(); on_sent();
} }
...@@ -14,16 +14,16 @@ ...@@ -14,16 +14,16 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include <logging/logging.hpp>
#ifndef __COMMAND_HANDLER_HPP__ #ifndef __COMMAND_HANDLER_HPP__
# define __COMMAND_HANDLER_HPP__ # define __COMMAND_HANDLER_HPP__
#include <logging/logging.hpp>
#include <network/transfer_sender.hpp> #include <network/transfer_sender.hpp>
#include <network/command.hpp>
using boost::asio::ip::tcp; 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 class CommandHandler
{ {
...@@ -43,12 +43,12 @@ public: ...@@ -43,12 +43,12 @@ public:
* Read the arguments after a command (can read 0 bytes too) and pass that * Read the arguments after a command (can read 0 bytes too) and pass that
* to the callback that was associated with this command. * 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 * Sends a command, and use install_callback_once to wait for the answer
* and call that callback to handle it. * and call that callback to handle it.
*/ */
void request_answer(const char