#pragma once #include #include #include #include "AsyncUtils.hh" #include "PSOEncryption.hh" #include "PSOProtocol.hh" #include "Version.hh" class Channel { public: Version version; Language language; std::shared_ptr crypt_in; std::shared_ptr crypt_out; std::string name; phosg::TerminalFormat terminal_send_color; phosg::TerminalFormat terminal_recv_color; bool censor_received_credentials; bool censor_sent_credentials; struct Message { uint16_t command; uint32_t flag; std::string data; template const T& check_size_t(size_t min_size, size_t max_size) const { return ::check_size_t(this->data.data(), this->data.size(), min_size, max_size); } template T& check_size_t(size_t min_size, size_t max_size) { return ::check_size_t(this->data.data(), this->data.size(), min_size, max_size); } template const T& check_size_t(size_t max_size) const { return ::check_size_t(this->data.data(), this->data.size(), sizeof(T), max_size); } template T& check_size_t(size_t max_size) { return ::check_size_t(this->data.data(), this->data.size(), sizeof(T), max_size); } template const T& check_size_t() const { return ::check_size_t(this->data.data(), this->data.size(), sizeof(T), sizeof(T)); } template T& check_size_t() { return ::check_size_t(this->data.data(), this->data.size(), sizeof(T), sizeof(T)); } }; virtual ~Channel() = default; virtual std::string default_name() const = 0; // Returns whether the channel is connected or not. virtual bool connected() const = 0; // Disconnects the channel. Any pending data will still be sent before the underlying transport (e.g. socket) is // closed, but further send calls will do nothing. virtual void disconnect() = 0; // Sends a message with an automatically-constructed header. void send(uint16_t cmd, uint32_t flag = 0, bool silent = false); void send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent = false); void send(uint16_t cmd, uint32_t flag, const std::vector> blocks, bool silent = false); void send(uint16_t cmd, uint32_t flag, const std::string& data, bool silent = false); template requires(!std::is_pointer_v) void send(uint16_t cmd, uint32_t flag, const CmdT& data, bool silent = false) { this->send(cmd, flag, &data, sizeof(data), silent); } // Sends a message with a pre-existing header (as the first few bytes in the data) void send(const void* data, size_t size, bool silent = false); void send(const std::string& data, bool silent = false); // Receives a message. Throws std::out_of_range if no messages are available. asio::awaitable recv(); protected: Channel( Version version, Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color, bool censor_received_credentials, bool censor_sent_credentials); Channel(const Channel& other) = delete; Channel(Channel&& other) = delete; Channel& operator=(const Channel& other) = delete; Channel& operator=(Channel&& other) = delete; // Sends raw data on the underlying transport. If the channel is already disconnected, silently drops the data. virtual void send_raw(std::string&& data) = 0; // Receives raw data on the underlying transport. Raises when the channel is disconnected. virtual asio::awaitable recv_raw(void* data, size_t size) = 0; }; // Standard channel type, used for most PSO clients. Represents an open TCP socket. class SocketChannel : public Channel, public std::enable_shared_from_this { public: std::unique_ptr sock; asio::ip::tcp::endpoint local_addr; asio::ip::tcp::endpoint remote_addr; // SocketChannel has a static constructor because it has an internal task, which is necessary to support flushing // before disconnection (for example) and also to make send_raw not a coroutine, which keeps the rest of the code // cleaner. static std::shared_ptr create(std::shared_ptr io_context, std::unique_ptr&& sock, Version version, Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color, bool censor_received_credentials, bool censor_sent_credentials); virtual std::string default_name() const; virtual bool connected() const; virtual void disconnect(); virtual void send_raw(std::string&& data); virtual asio::awaitable recv_raw(void* data, size_t size); private: SocketChannel( std::shared_ptr io_context, std::unique_ptr&& sock, Version version, Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color, bool censor_received_credentials, bool censor_sent_credentials); std::deque outbound_data; bool should_disconnect = false; AsyncEvent send_buffer_nonempty_signal; asio::awaitable send_task(); }; // In-process peer channel, used for replay testing. class PeerChannel : public Channel { public: std::weak_ptr peer; PeerChannel( std::shared_ptr io_context, Version version, Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color, bool censor_received_credentials, bool censor_sent_credentials); static void link_peers(std::shared_ptr peer1, std::shared_ptr peer2); virtual std::string default_name() const; virtual bool connected() const; virtual void disconnect(); virtual void send_raw(std::string&& data); virtual asio::awaitable recv_raw(void* data, size_t size); private: AsyncEvent send_buffer_nonempty_signal; std::deque inbound_data; };