#include "connection.hpp" #include "linebuffer.hpp" #include namespace { #include "irc_commands.inc" } // namespace Connection::Connection(boost::asio::io_context & io) : stream_{io} , write_timer_{io, std::chrono::steady_clock::time_point::max()} { } auto Connection::writer_immediate() -> void { std::vector buffers; buffers.reserve(write_strings_.size()); for (auto const& elt : write_strings_) { buffers.push_back(boost::asio::buffer(elt)); } boost::asio::async_write( stream_, buffers, [weak = weak_from_this() ,strings = std::move(write_strings_) ](boost::system::error_code const& error, std::size_t) { if (not error) { if (auto self = weak.lock()) { self->writer(); } } }); write_strings_.clear(); } auto Connection::writer() -> void { if (write_strings_.empty()) { write_timer_.async_wait([weak = weak_from_this()](auto){ if (auto self = weak.lock()) { if (not self->write_strings_.empty()) { self->writer_immediate(); } } }); } else { writer_immediate(); } } auto Connection::connect( boost::asio::io_context & io, std::string host, std::string port ) -> boost::asio::awaitable { using namespace std::placeholders; // keep connection alive while coroutine is active auto const self = shared_from_this(); { auto resolver = boost::asio::ip::tcp::resolver{io}; auto const endpoints = co_await resolver.async_resolve(host, port, boost::asio::use_awaitable); co_await boost::asio::async_connect(stream_, endpoints, boost::asio::use_awaitable); sig_connect(); } // Start the queue writer after connection self->writer(); for(LineBuffer buffer{32'768};;) { boost::system::error_code error; auto const n = co_await stream_.async_read_some(buffer.get_buffer(), boost::asio::redirect_error(boost::asio::use_awaitable, error)); if (error) { break; } buffer.add_bytes(n, [this](char * line) { BOOST_LOG_TRIVIAL(debug) << "RECV: " << line; dispatch_line(line); }); } sig_disconnect(); } /// Parse IRC message line and dispatch it to the ircmsg slot. auto Connection::dispatch_line(char *line) -> void { auto const msg = parse_irc_message(line); auto const recognized = IrcCommandHash::in_word_set(msg.command.data(), msg.command.size()); auto const command = recognized && recognized->min_args <= msg.args.size() && recognized->max_args >= msg.args.size() ? recognized->command : IrcCommand::UNKNOWN; switch (command) { // Respond to pings immediate and discard case IrcCommand::PING: send_pong(msg.args[0]); break; // Unknown message generate warnings but do not dispatch // Messages can be unknown due to bad command or bad argument count case IrcCommand::UNKNOWN: BOOST_LOG_TRIVIAL(warning) << "Unrecognized command: " << msg.command << " " << msg.args.size(); break; // Server notice generate snote events but not IRC command events case IrcCommand::NOTICE: if (auto match = snoteCore.match(msg)) { sig_snote(match->first, match->second); break; } /* FALLTHROUGH */ // Normal IRC commands default: sig_ircmsg(command, msg); break; } } auto Connection::write_line(std::string message) -> void { BOOST_LOG_TRIVIAL(debug) << "SEND: " << message; message += "\r\n"; auto const need_cancel = write_strings_.empty(); write_strings_.push_back(std::move(message)); if (need_cancel) { write_timer_.cancel_one(); } } auto Connection::close() -> void { stream_.close(); } static auto is_invalid_last(char x) -> bool { return x == '\0' || x == '\r' || x == '\n'; } auto Connection::write_irc(std::string message) -> void { write_line(std::move(message)); } auto Connection::write_irc(std::string front, std::string_view last) -> void { if (last.end() != std::find_if(last.begin(), last.end(), is_invalid_last)) { throw std::runtime_error{"bad irc argument"}; } front += " :"; front += last; write_irc(std::move(front)); } auto Connection::send_ping(std::string_view txt) -> void { write_irc("PING", txt); } auto Connection::send_pong(std::string_view txt) -> void { write_irc("PONG", txt); } auto Connection::send_pass(std::string_view password) -> void { write_irc("PASS", password); } auto Connection::send_user(std::string_view user, std::string_view real) -> void { write_irc("USER", user, "*", "*", real); } auto Connection::send_nick(std::string_view nick) -> void { write_irc("NICK", nick); } auto Connection::send_cap_ls() -> void { write_irc("CAP", "LS", "302"); } auto Connection::send_cap_end() -> void { write_irc("CAP", "END"); } auto Connection::send_cap_req(std::string_view caps) -> void { write_irc("CAP", "REQ", caps); } auto Connection::send_privmsg(std::string_view target, std::string_view message) -> void { write_irc("PRIVMSG", target, message); } auto Connection::send_notice(std::string_view target, std::string_view message) -> void { write_irc("NOTICE", target, message); } auto Connection::send_authenticate(std::string_view message) -> void { write_irc("AUTHENTICATE", message); }