xbot/connection.cpp

236 lines
5.6 KiB
C++

#include "connection.hpp"
#include "linebuffer.hpp"
#include <boost/log/trivial.hpp>
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<boost::asio::const_buffer> 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<void>
{
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);
}