diff --git a/email_client/CMakeLists.txt b/email_client/CMakeLists.txt new file mode 100644 index 0000000..0c41d65 --- /dev/null +++ b/email_client/CMakeLists.txt @@ -0,0 +1,72 @@ +project(email-client) + +cmake_minimum_required(VERSION 3.9) + +MACRO(ADD_MSVC_PRECOMPILED_HEADER PrecompiledHeader PrecompiledSource SourcesVar) + IF(MSVC) + GET_FILENAME_COMPONENT(PrecompiledBasename ${PrecompiledHeader} NAME_WE) + SET(PrecompiledBinary "${CMAKE_CURRENT_BINARY_DIR}/${PrecompiledBasename}.pch") + SET(Sources ${${SourcesVar}}) + + SET_SOURCE_FILES_PROPERTIES(${PrecompiledSource} + PROPERTIES COMPILE_FLAGS "/Yc\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\"" + OBJECT_OUTPUTS "${PrecompiledBinary}") + SET_SOURCE_FILES_PROPERTIES(${Sources} + PROPERTIES COMPILE_FLAGS "/Yu\"${PrecompiledHeader}\" /FI\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\"" + OBJECT_DEPENDS "${PrecompiledBinary}") + # Add precompiled header to SourcesVar + LIST(APPEND ${SourcesVar} ${PrecompiledSource}) + ENDIF(MSVC) +ENDMACRO(ADD_MSVC_PRECOMPILED_HEADER) + +ADD_MSVC_PRECOMPILED_HEADER(${PROJECT_NAME} "pch.h" "pch.cpp") + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}\ + -Werror\ + -Wall\ + -Wextra\ + -Winit-self\ + -Wold-style-cast\ + -Woverloaded-virtual\ + -Winit-self\ + -pedantic-errors\ + -Wuninitialized\ + -Wuseless-cast\ + -Woverloaded-virtual\ + -Wnon-virtual-dtor\ + -Wconversion\ + -Wswitch-default\ + -Wtrigraphs\ + -Wlogical-op\ + -Wfloat-equal\ + -Wstrict-overflow=4\ + -fsanitize=leak\ + -fsanitize=undefined\ + -fomit-frame-pointer\ +") + +add_executable( + ${PROJECT_NAME} + src/main.cpp + + src/client.cpp + src/email.cpp + + src/network/response.cpp + src/network/request.cpp + src/network/client_socket.cpp + + src/util/commands.cpp + src/util/user_view.cpp + + src/serialisation/serialization.cpp + +) + +target_link_libraries(${PROJECT_NAME}) + +target_include_directories( + ${PROJECT_NAME} + PUBLIC ${PROJECT_SOURCE_DIR}/include +) diff --git a/email_client/Readme.md b/email_client/Readme.md new file mode 100644 index 0000000..16f92c6 --- /dev/null +++ b/email_client/Readme.md @@ -0,0 +1,83 @@ +# Протокол для обмена сообщениями в системе "Email server" + +## Общие замечания + +- тип запроса и статус ответа -- 1 байт (`uint8`) +- все числа в 4 байта -- `uint32` в _little endian_ +- все строки в кодировке UTF_8 + +### Заголовок всех запросов от клиента серверу + +- 4 байта -- размер сообщения (без учёта этих 4-х байт) +- 4 байта -- размер адреса +- строка -- адрес отправителя +- 1 байт -- тип запроса + +### Заголовок всех запросов от сервера клиентам + +- 4 байта -- размер сообщения + +### Статус ответа + +В каждом ответе сервер присылает 1 байт (`uint8`) -- статус ответа на запрос +Статус 0 -- запрос успешен +Статус не ноль -- что-то пошло не так (см. ниже тело отчёта) + +- _только при статусе отличном от 0_ +- 4 байта -- размер сообщения об ошибке +- сообщение об ошибке + +### 1. A посылает сообщение для B + +Запрос: +- заголовок (тип сообщения 0) +- 4 байта -- размер адреса отправителя +- адрес получателя +- 4 байта -- размер адреса получателя +- адрес получателя +- 4 байта -- размер темы письма +- тема письма +- 4 байта -- размер тела письма +- тело письма + +Ответ: +- заголовок +- 1 байт -- статус запроса +- _сообщение при статусе != 0_ + +### 2. A проверяет свои входящие сообщения + +Запрос: +- заголовок (тип сообщения 1) + +Ответ: +- заголовок +- 1 байт -- статус запроса +- _тело ниже только при статусе 0_ + - 4 байта -- количество сообщений (**n**) + - _дальше **n** записей_ + - 4 байта -- id письма + - 4 байта -- размер адреса отправителя + - адрес получателя + - 4 байта -- размер темы письма + - тема письма +- _сообщение при статусе != 0_ + +### 3. A хочет получить сообщение с сервера + +Запрос: +- Заголовок (тип сообщения 2) +- 4 байта -- id письма + +Ответ: +- 1 байт -- статус запроса +- _тело ниже только при статусе 0_ + - 4 байта -- размер адреса отправителя + - адрес получателя + - 4 байта -- размер адреса получателя + - адрес получателя + - 4 байта -- размер темы письма + - тема письма + - 4 байта -- размер тела письма + - тело письма +- _сообщение при статусе != 0_ diff --git a/email_client/include/client.h b/email_client/include/client.h new file mode 100644 index 0000000..d5e2aeb --- /dev/null +++ b/email_client/include/client.h @@ -0,0 +1,50 @@ +#pragma once + +#include "pch.h" +#include "email.h" +#include "network/response.h" +#include "network/request.h" +#include "network/client_socket.h" +#include "util/commands.h" +#include "util/user_view.h" + +namespace email { + + using port_t = uint16_t; + using net_address_t = std::string; + + class Client { + public: + static const port_t DEFAULT_SERVER_PORT; + static const std::string DEFAULT_SERVER_ADDRESS; + + Client(const std::string &host_email, const std::string &server_address, uint16_t port); + + static Client from_input(); + + void run(); + + void shut_down(); + + private: + + const std::string host_email_; + const std::string server_address_; + const port_t server_port_; + bool is_alive_; + network::ClientSocket socket_; + + void process_command(util::ClientCommands::CommandID cmd); + + void send_email(const Email &email); + + void check_email(); + + void get_email(uint32_t id); + + void print_help(); + + void process_error_response(const std::shared_ptr& response); + }; + +} // namespace email \ No newline at end of file diff --git a/email_client/include/email.h b/email_client/include/email.h new file mode 100644 index 0000000..8516dbc --- /dev/null +++ b/email_client/include/email.h @@ -0,0 +1,54 @@ +#pragma once + +#include "pch.h" + +namespace email { + + class Email { + public: + + Email() = default; + + Email(const Email &other) = default; + + Email(const std::string &recipient, const std::string &author, + const std::string &theme, const std::string &body); + + Email(Email&& email) noexcept = default; + + const std::string &get_author() const; + + const std::string &get_recipient() const; + + const std::string &get_theme() const; + + const std::string &get_body() const; + + uint32_t size() const; + + private: + std::string author_; + std::string recipient_; + std::string theme_; + std::string body_; + }; + + class EmailInfo { + public: + EmailInfo() = default; + + EmailInfo(uint32_t id, const std::string &author, const std::string &theme); + + uint32_t get_id() const; + + const std::string &get_author() const; + + const std::string &get_theme() const; + + private: + uint32_t id_; + std::string author_; + std::string theme_; + }; + +} // namespace email \ No newline at end of file diff --git a/email_client/include/network/client_socket.h b/email_client/include/network/client_socket.h new file mode 100644 index 0000000..4a831ff --- /dev/null +++ b/email_client/include/network/client_socket.h @@ -0,0 +1,47 @@ +#pragma once + +#include "pch.h" +#include "serialization/serialization.h" +#include "response.h" +#include "request.h" + +namespace network { + + struct socket_exception : std::exception { + explicit socket_exception(std::string msg = ""); + + const char *what() const noexcept override; + + private: + std::string msg_; + }; + + class ClientSocket { + public: + + ClientSocket(const std::string &server_address, uint16_t port); + + ~ClientSocket(); + + void open_connection(); + + void close_connection(); + + std::shared_ptr send_request(std::unique_ptr &request); + + private: + int descriptor_; + std::string server_address_; + uint16_t port_; + + void write_message(const serialization::SerializedMessage &message); + + std::unique_ptr receive_message(); + + uint32_t read_message_size(); + + void read_to_buffer(uint8_t *buffer, size_t size); + + }; + +} // namespace network \ No newline at end of file diff --git a/email_client/include/network/message.h b/email_client/include/network/message.h new file mode 100644 index 0000000..b4df6ca --- /dev/null +++ b/email_client/include/network/message.h @@ -0,0 +1,15 @@ +#pragma once + +#include "pch.h" + +namespace network { + + class Message { + public: + virtual uint32_t size() const = 0; + + virtual ~Message() = default; + + }; + +} // namespace network \ No newline at end of file diff --git a/email_client/include/network/request.h b/email_client/include/network/request.h new file mode 100644 index 0000000..c5194d9 --- /dev/null +++ b/email_client/include/network/request.h @@ -0,0 +1,67 @@ +#pragma once + +#include "pch.h" +#include "email.h" +#include "network/message.h" + +namespace request { + + enum RequestType { + SEND_EMAIL = 1, + CHECK_EMAIL = 2, + GET_EMAIL = 3 + }; + + class Request : public network::Message { + protected: + Request(RequestType type, const std::string &author); + + public: + uint32_t size() const override; + + virtual RequestType get_type() const; + + const std::string &get_author() const; + + private: + RequestType type_; + const std::string author_; + }; + + class SendRequest : public Request { + public: + explicit SendRequest(email::Email email); + + const email::Email &get_email() const; + + RequestType get_type() const override; + + uint32_t size() const override; + + private: + email::Email email_; + }; + + class CheckRequest : public Request { + public: + explicit CheckRequest(const std::string &author); + + uint32_t size() const override; + + }; + + class GetRequest : public Request { + public: + GetRequest(const std::string &author, uint32_t id); + + uint32_t get_id() const; + + protected: + uint32_t size() const override; + + private: + uint32_t id_; + uint32_t size_; + }; + +} // namespace request \ No newline at end of file diff --git a/email_client/include/network/response.h b/email_client/include/network/response.h new file mode 100644 index 0000000..e8a188d --- /dev/null +++ b/email_client/include/network/response.h @@ -0,0 +1,58 @@ +#pragma once + +#include "pch.h" +#include "email.h" + +namespace response { + + enum ResponseStatus { + OK, ERROR + }; + + class Response { + public: + bool is_error(); + + protected: + explicit Response(ResponseStatus status); + + private: + ResponseStatus status_; + }; + + class BadResponse : public Response { + public: + explicit BadResponse(const std::string &error_message); + + const std::string &get_error_message() const; + + private: + std::string error_message_; + }; + + class SendResponse : public Response { + public: + explicit SendResponse(); + }; + + class CheckResponse : public Response { + public: + explicit CheckResponse(const std::vector &infos); + + const std::vector &get_infos() const; + + private: + std::vector infos_; + }; + + class GetResponse : public Response { + public: + explicit GetResponse(email::Email email); + + const email::Email &get_email() const; + + private: + email::Email email_; + }; + +} // namespace response \ No newline at end of file diff --git a/email_client/include/pch.h b/email_client/include/pch.h new file mode 100644 index 0000000..420f8bf --- /dev/null +++ b/email_client/include/pch.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define INT_SIZE 4 +#define POWER_OF_TWO 8 +#define MESSAGE_TYPE_SIZE 1 +#define UCHAR_MAX 0xffu diff --git a/email_client/include/serialization/serialization.h b/email_client/include/serialization/serialization.h new file mode 100644 index 0000000..f0d392b --- /dev/null +++ b/email_client/include/serialization/serialization.h @@ -0,0 +1,70 @@ +#pragma once + +#include "pch.h" +#include "email.h" +#include "network/response.h" +#include "network/request.h" + +namespace serialization { + + class SerializedMessage { + public: + SerializedMessage(size_t size_, const std::shared_ptr &message_); + + size_t size() const; + + const uint8_t *get_message() const; + + private: + size_t size_; + std::shared_ptr message_; + }; + + class Serializer { + public: + Serializer() = default; + + explicit Serializer(const request::Request *request); + + SerializedMessage serialize(); + + private: + + const request::Request *request_; + std::shared_ptr buffer_; + uint8_t *buf_; + size_t index_; + + void write(uint32_t number); + + void write(const std::string &string); + + void write(request::RequestType type); + + void write(const email::Email &email); + }; + + class Deserializer { + public: + explicit Deserializer(const uint8_t *buffer, uint32_t offset = 0); + + uint32_t parse_uint32(); + + std::string parse_string(); + + response::ResponseStatus parse_response_status(); + + email::Email parse_email(); + + email::EmailInfo parse_email_info(); + + std::vector parse_email_infos(); + + std::shared_ptr parse_response(request::RequestType type); + + private: + const uint8_t *buffer_; + size_t index_; + }; + +} // namespace serialization \ No newline at end of file diff --git a/email_client/include/util/commands.h b/email_client/include/util/commands.h new file mode 100644 index 0000000..ee2807f --- /dev/null +++ b/email_client/include/util/commands.h @@ -0,0 +1,50 @@ +#pragma once + +#include "pch.h" + +namespace util { + + struct ClientCommands { + + static const size_t COMMANDS_NUM = 6; + + enum CommandID { + SEND, + CHECK, + GET, + HELP, + EXIT, + NONE + }; + + static const std::string commands_names[COMMANDS_NUM]; + static const std::string commands_args[COMMANDS_NUM]; + static const std::string commands_actions[COMMANDS_NUM]; + + static CommandID str_to_id(const std::string &str) { + if (str == "send") return SEND; + if (str == "check") return CHECK; + if (str == "get") return GET; + if (str == "help") return HELP; + if (str == "exit") return EXIT; + return NONE; + } + + static std::string name(CommandID id) { + return commands_names[id]; + } + + static std::string args(CommandID id) { + return commands_args[id]; + } + + static std::string description(CommandID id) { + return commands_actions[id]; + } + + static std::string info_string(CommandID id) { + return name(id) + " " + args(id) + " - " + description(id); + } + }; + +} // namespace util \ No newline at end of file diff --git a/email_client/include/util/user_view.h b/email_client/include/util/user_view.h new file mode 100644 index 0000000..7bd96fc --- /dev/null +++ b/email_client/include/util/user_view.h @@ -0,0 +1,19 @@ +#pragma once + +#include "pch.h" +#include "email.h" + +namespace util { + + struct UserView { + static std::string get_user_input(const std::string &str = ""); + + static void println(const std::string &str = ""); + + static void println(const email::Email &e); + + static void println(const email::EmailInfo &i); + }; + + +} // namespace util \ No newline at end of file diff --git a/email_client/src/client.cpp b/email_client/src/client.cpp new file mode 100644 index 0000000..ea6d220 --- /dev/null +++ b/email_client/src/client.cpp @@ -0,0 +1,144 @@ +#include + +namespace email { + + using namespace util; + + const port_t Client::DEFAULT_SERVER_PORT = 1111; + const std::string Client::DEFAULT_SERVER_ADDRESS = "127.0.0.1"; + + Client::Client(const std::string &host_email, + const std::string &server_address = DEFAULT_SERVER_ADDRESS, + port_t port = DEFAULT_SERVER_PORT + ) + : host_email_(host_email), + server_address_(server_address), + server_port_(port), + is_alive_(false), + socket_(network::ClientSocket(server_address_, port)) {} + + Client Client::from_input() { + std::string email = util::UserView::get_user_input("Enter your email: "); + + std::string server = email::Client::DEFAULT_SERVER_ADDRESS; + std::string server_str = util::UserView::get_user_input("Enter server address or leave default 127.0.0.1: "); + server = server_str.empty() ? server : server_str; + + uint16_t port = email::Client::DEFAULT_SERVER_PORT; + std::string port_str = util::UserView::get_user_input( + "Enter server port or leave default " + std::to_string(port) + ": "); + + port = port_str.empty() ? port : static_cast(std::stoi(port_str)); + return {email, server, port}; + } + + void Client::run() { + is_alive_ = true; + + while (is_alive_) { + ClientCommands::CommandID cmd = ClientCommands::str_to_id(UserView::get_user_input()); + try { + process_command(cmd); + } catch (const network::socket_exception& exception) { + UserView::println("Some errors were acquired (while connecting to the server):"); + UserView::println(std::string(exception.what())); + } + } + } + + void Client::process_command(util::ClientCommands::CommandID cmd) { + switch (cmd) { + case ClientCommands::SEND: { + std::string recipient = UserView::get_user_input("Enter the recipient email: "); + if (recipient == host_email_) { + UserView::println("Invalid recipient!"); + break; + } + std::string theme = UserView::get_user_input("Enter the email theme: "); + std::string body = UserView::get_user_input("Enter the email body: "); + Email email(recipient, host_email_, theme, body); + send_email(email); + break; + } + case ClientCommands::CHECK: { + check_email(); + break; + } + case ClientCommands::GET: { + std::string input = UserView::get_user_input("Enter the email id: "); + auto email_id = static_cast(std::atoi(input.data())); + get_email(email_id); + break; + } + case ClientCommands::EXIT: { + shut_down(); + break; + } + default: { + print_help(); + break; + } + } + } + + void Client::shut_down() { + socket_.close_connection(); + is_alive_ = false; + } + + void Client::print_help() { + UserView::println("Available commands for email client: "); + for (size_t cmd = 0; cmd < ClientCommands::COMMANDS_NUM - 1; ++cmd) { + auto id = static_cast(cmd); + UserView::println(ClientCommands::info_string(id)); + } + } + + void Client::send_email(const Email &email) { + std::unique_ptr request(new request::SendRequest(email)); + std::shared_ptr response = socket_.send_request(request); + if (response->is_error()) { + process_error_response(response); + return; + } + util::UserView::println("Message sent successfully!"); + } + + void Client::check_email() { + std::unique_ptr request(new request::CheckRequest(host_email_)); + std::shared_ptr response = socket_.send_request(request); + + if (response->is_error()) { + process_error_response(response); + return; + } + + auto check_response = reinterpret_cast(response.get ()); + auto infos = check_response->get_infos(); + util::UserView::println(infos.empty() ? "No new messages." : "New messages:"); + for (auto &info : infos) { + util::UserView::println(); + util::UserView::println(info); + } + } + + void Client::get_email(uint32_t id) { + std::unique_ptr request(new request::GetRequest(host_email_, id)); + std::shared_ptr response = socket_.send_request(request); + + if (response->is_error()) { + process_error_response(response); + return; + } + + auto get_response = reinterpret_cast(response.get()); + UserView::println(get_response->get_email()); + } + + void Client::process_error_response(const std::shared_ptr &response) { + auto error_response = reinterpret_cast(response.get()); + UserView::println("Some problems were acquired (on server):"); + UserView::println(error_response->get_error_message()); + } + +} // namespace email diff --git a/email_client/src/email.cpp b/email_client/src/email.cpp new file mode 100644 index 0000000..e3192ce --- /dev/null +++ b/email_client/src/email.cpp @@ -0,0 +1,55 @@ +#include + +namespace email { + + Email::Email(const std::string &recipient, const std::string &author, const std::string &theme, + const std::string &body) + : author_(author), + recipient_(recipient), + theme_(theme), + body_(body) {} + + const std::string &Email::get_author() const { + return author_; + } + + const std::string &Email::get_recipient() const { + return recipient_; + } + + const std::string &Email::get_theme() const { + return theme_; + } + + const std::string &Email::get_body() const { + return body_; + } + + uint32_t Email::size() const { + size_t result = 4 * INT_SIZE; + result += recipient_.length(); + result += author_.length(); + result += theme_.length(); + result += body_.length(); + return static_cast(result); + } + + EmailInfo::EmailInfo(uint32_t id, const std::string &author, const std::string &theme) + : id_(id), + author_(author), + theme_(theme) {} + + uint32_t EmailInfo::get_id() const { + return id_; + } + + const std::string &EmailInfo::get_author() const { + return author_; + } + + const std::string &EmailInfo::get_theme() const { + return theme_; + } + +} // namespace email + diff --git a/email_client/src/main.cpp b/email_client/src/main.cpp new file mode 100644 index 0000000..36ee9ea --- /dev/null +++ b/email_client/src/main.cpp @@ -0,0 +1,23 @@ +#include "client.h" + +namespace { + std::function shutdown_handler; + + void signal_handler(int signal) { + shutdown_handler(signal); + } +} + +int main() { + auto client = email::Client::from_input(); + + shutdown_handler = [&](int) { + client.shut_down(); + exit(0); + }; + std::signal(SIGINT, signal_handler); + std::signal(SIGTERM, signal_handler); + + client.run(); + return 0; +} \ No newline at end of file diff --git a/email_client/src/network/client_socket.cpp b/email_client/src/network/client_socket.cpp new file mode 100644 index 0000000..3378d2a --- /dev/null +++ b/email_client/src/network/client_socket.cpp @@ -0,0 +1,94 @@ +#include + +namespace network { + + socket_exception::socket_exception(std::string msg) : msg_(std::move(msg)) {} + + const char *socket_exception::what() const noexcept { + return msg_.data(); + } + + ClientSocket::ClientSocket(const std::string &server_address, uint16_t port) + : descriptor_(-1), server_address_(server_address), port_(port) {} + + void ClientSocket::open_connection() { + struct sockaddr_in serv_addr; + + if ((descriptor_ = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + descriptor_ = -1; + throw socket_exception("Socket creation error!"); + } + + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(port_); + + // Convert IPv4 and IPv6 addresses from text to binary form + if (inet_pton(AF_INET, server_address_.c_str(), &serv_addr.sin_addr) <= 0) { + descriptor_ = -1; + throw socket_exception("Invalid address / Address not supported!"); + } + if (connect(descriptor_, reinterpret_cast(&serv_addr), sizeof(serv_addr)) < 0) { + descriptor_ = -1; + throw socket_exception("Connection failed!"); + } + } + + void ClientSocket::close_connection() { + if (descriptor_ != -1) { + ::close(descriptor_); + descriptor_ = -1; + } + } + + std::shared_ptr ClientSocket::send_request(std::unique_ptr &request) { + open_connection(); + + serialization::Serializer serializer(request.get()); + write_message(serializer.serialize()); + + auto buffer = receive_message(); + serialization::Deserializer deserializer(buffer.get()); + auto response = deserializer.parse_response(request->get_type()); + close_connection(); + return response; + } + + void ClientSocket::write_message(const serialization::SerializedMessage &message) { + send(descriptor_, message.get_message(), message.size(), 0); + } + + std::unique_ptr ClientSocket::receive_message() { + uint32_t size = read_message_size(); + std::unique_ptr buffer(new uint8_t[size]); + read_to_buffer(buffer.get(), size); + return buffer; + } + + uint32_t ClientSocket::read_message_size() { + uint8_t size_buf[INT_SIZE]; + read_to_buffer(size_buf, INT_SIZE); + serialization::Deserializer deserializer(size_buf); + return deserializer.parse_uint32(); + } + + void ClientSocket::read_to_buffer(uint8_t *buffer, size_t size) { + size_t offset = 0; + while (size > 0) { + ssize_t readed = read(descriptor_, buffer + offset, size); + if (readed < 0) { + throw socket_exception("Socket is unavailable for reading!"); + } + offset += static_cast(readed); + size -= static_cast(readed); + } + } + + ClientSocket::~ClientSocket() { + if (descriptor_ == -1) { + close_connection(); + } + } + +} // namespace network + diff --git a/email_client/src/network/request.cpp b/email_client/src/network/request.cpp new file mode 100644 index 0000000..765696a --- /dev/null +++ b/email_client/src/network/request.cpp @@ -0,0 +1,56 @@ +#include + +namespace request { + + Request::Request(RequestType type, const std::string &author) + : type_(type), + author_(author) {} + + RequestType Request::get_type() const { + return type_; + } + + const std::string &Request::get_author() const { + return author_; + } + + uint32_t Request::size() const { + return static_cast(MESSAGE_TYPE_SIZE + INT_SIZE + author_.length()); + } + + SendRequest::SendRequest(email::Email email) + : Request(SEND_EMAIL, email.get_author()), + email_(std::move(email)) {} + + const email::Email &SendRequest::get_email() const { + return email_; + } + + RequestType SendRequest::get_type() const { + return Request::get_type(); + } + + uint32_t SendRequest::size() const { + return Request::size() + email_.size(); + } + + CheckRequest::CheckRequest(const std::string &author) + : Request(CHECK_EMAIL, author) {} + + uint32_t CheckRequest::size() const { + return Request::size(); + } + + GetRequest::GetRequest(const std::string &author, uint32_t id) + : Request(GET_EMAIL, author), + id_(id), + size_(0) {} + + uint32_t GetRequest::get_id() const { + return id_; + } + + uint32_t GetRequest::size() const { + return Request::size() + INT_SIZE; + } +} // namespace request \ No newline at end of file diff --git a/email_client/src/network/response.cpp b/email_client/src/network/response.cpp new file mode 100644 index 0000000..d3c0a12 --- /dev/null +++ b/email_client/src/network/response.cpp @@ -0,0 +1,38 @@ +#include + +namespace response { + + Response::Response(ResponseStatus status) + : status_(status) {} + + bool Response::is_error() { + return status_ == ERROR; + } + + BadResponse::BadResponse(const std::string &error_message) + : Response(ERROR), + error_message_(error_message) {} + + const std::string &BadResponse::get_error_message() const { + return error_message_; + } + + SendResponse::SendResponse() : Response(OK) {} + + CheckResponse::CheckResponse(const std::vector &infos) + : Response(OK), + infos_(infos) {} + + const std::vector &CheckResponse::get_infos() const { + return infos_; + } + + GetResponse::GetResponse(email::Email email) + : Response(OK), + email_(std::move(email)) {} + + const email::Email &GetResponse::get_email() const { + return email_; + } + +} // namespace response \ No newline at end of file diff --git a/email_client/src/pch.cpp b/email_client/src/pch.cpp new file mode 100644 index 0000000..1730571 --- /dev/null +++ b/email_client/src/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" \ No newline at end of file diff --git a/email_client/src/serialisation/serialization.cpp b/email_client/src/serialisation/serialization.cpp new file mode 100644 index 0000000..d91b750 --- /dev/null +++ b/email_client/src/serialisation/serialization.cpp @@ -0,0 +1,145 @@ +#include + +namespace serialization { + + SerializedMessage::SerializedMessage(size_t size_, const std::shared_ptr &message_) + : size_(size_), + message_(message_) {} + + size_t SerializedMessage::size() const { + return size_; + } + + const uint8_t *SerializedMessage::get_message() const { + return message_.get(); + } + + Serializer::Serializer(const request::Request *request) + : request_(request), + buffer_(new uint8_t[request->size() + INT_SIZE]), + buf_(buffer_.get()), + index_(0) {} + + void Serializer::write(uint32_t number) { + for (int i = 0; i < INT_SIZE; ++i) { + buf_[index_++] = static_cast(number & UCHAR_MAX); + number >>= POWER_OF_TWO; + } + } + + void Serializer::write(const std::string &string) { + auto length = static_cast(string.length()); + write(length); + const auto *data = reinterpret_cast(string.c_str()); + for (size_t i = 0; i < length; ++i) { + buf_[index_++] = data[i]; + } + } + + void Serializer::write(request::RequestType type) { + buf_[index_++] = type; + } + + void Serializer::write(const email::Email &email) { + write(email.get_author()); + write(email.get_recipient()); + write(email.get_theme()); + write(email.get_body()); + } + + SerializedMessage Serializer::serialize() { + auto type = request_->get_type(); + write(request_->size()); + write(type); + write(request_->get_author()); + switch (type) { + case request::SEND_EMAIL: { + auto &email = reinterpret_cast(request_)->get_email(); + write(email); + break; + } + case request::GET_EMAIL: { + write(reinterpret_cast(request_)->get_id()); + break; + } + default: + break; + } + return SerializedMessage(request_->size() + INT_SIZE, buffer_); + } + + Deserializer::Deserializer(const uint8_t *buffer, uint32_t offset) + : buffer_(buffer), + index_(offset) {} + + uint32_t Deserializer::parse_uint32() { + uint32_t result = buffer_[index_ + INT_SIZE - 1]; + for (size_t i = index_ + INT_SIZE - 1; i > index_; --i) { + result <<= POWER_OF_TWO; + result += buffer_[i - 1]; + } + index_ += INT_SIZE; + return result; + } + + std::string Deserializer::parse_string() { + size_t size = parse_uint32(); + const char *p = reinterpret_cast(buffer_ + index_); + std::string string(p, size); + index_ += size; + return string; + } + + response::ResponseStatus Deserializer::parse_response_status() { + return static_cast(buffer_[index_++]); + } + + email::Email Deserializer::parse_email() { + auto author = parse_string(); + auto recipient = parse_string(); + auto theme = parse_string(); + auto body = parse_string(); + return email::Email(recipient, author, theme, body); + } + + email::EmailInfo Deserializer::parse_email_info() { + uint32_t id = parse_uint32(); + auto author = parse_string(); + auto theme = parse_string(); + return email::EmailInfo(id, author, theme); + } + + std::vector Deserializer::parse_email_infos() { + uint32_t size = parse_uint32(); + std::vector result; + result.reserve(size); + for (size_t i = 0; i < size; ++i) { + result.push_back(parse_email_info()); + } + return result; + } + + std::shared_ptr Deserializer::parse_response(request::RequestType type) { + response::ResponseStatus status = parse_response_status(); + std::shared_ptr responseBody; + if (status == response::OK) { + switch (type) { + case request::SEND_EMAIL: + return std::make_shared(); + case request::CHECK_EMAIL: { + std::vector infos = parse_email_infos(); + return std::make_shared(infos); + } + case request::GET_EMAIL: { + email::Email email = parse_email(); + return std::make_shared(email); + } + default: + break; + } + } + std::string message = parse_string(); + return std::make_shared(message); + } + +} // namespace serialization \ No newline at end of file diff --git a/email_client/src/util/commands.cpp b/email_client/src/util/commands.cpp new file mode 100644 index 0000000..d1ed81f --- /dev/null +++ b/email_client/src/util/commands.cpp @@ -0,0 +1,21 @@ +#include "util/commands.h" + +namespace util { + + const std::string ClientCommands::commands_names[] = { + "send", "check", "get", "help", "exit" + }; + + const std::string ClientCommands::commands_args[] = { + " ", "", "", "help", "exit" + }; + + const std::string ClientCommands::commands_actions[] = { + "send mail", + "check new messages", + "get message with appropriate id", + "print this message", + "shut down the client" + }; + +} // namespace util \ No newline at end of file diff --git a/email_client/src/util/user_view.cpp b/email_client/src/util/user_view.cpp new file mode 100644 index 0000000..27233b4 --- /dev/null +++ b/email_client/src/util/user_view.cpp @@ -0,0 +1,32 @@ +#include + +namespace util { + + std::string UserView::get_user_input(const std::string &str) { + std::string input; + std::cout << (str.empty() ? "> " : str); + if (getline(std::cin, input)) { + return input; + } + return "exit"; + } + + void UserView::println(const std::string &str) { + std::cout << str << std::endl; + } + + void UserView::println(const email::Email &e) { + std::cout << "author:\t" << e.get_author() << std::endl; + std::cout << "recipient:\t" << e.get_recipient() << std::endl; + std::cout << "theme:\t" << e.get_theme() << std::endl; + std::cout << "---------------------" << std::endl; + std::cout << e.get_body() << std::endl; + } + + void UserView::println(const email::EmailInfo &i) { + std::cout << "id:\t" << i.get_id() << std::endl; + std::cout << "author:\t" << i.get_author() << std::endl; + std::cout << "theme:\t" << i.get_theme() << std::endl; + } + +} // namespace util \ No newline at end of file