split up driver and library
This commit is contained in:
36
myirc/include/bot.hpp
Normal file
36
myirc/include/bot.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "client.hpp"
|
||||
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct Bot : std::enable_shared_from_this<Bot>
|
||||
{
|
||||
struct Command
|
||||
{
|
||||
std::string_view source;
|
||||
std::string_view target;
|
||||
std::string_view oper;
|
||||
std::string_view account;
|
||||
std::string_view command;
|
||||
std::string_view arguments;
|
||||
};
|
||||
|
||||
std::shared_ptr<Client> self_;
|
||||
char command_prefix_;
|
||||
|
||||
boost::signals2::signal<void(const Command &)> sig_command;
|
||||
|
||||
Bot(std::shared_ptr<Client> self)
|
||||
: self_{std::move(self)}
|
||||
, command_prefix_{'!'}
|
||||
{}
|
||||
|
||||
auto on_chat(const Chat &) -> void;
|
||||
auto process_command(std::string_view message, const Chat &msg) -> void;
|
||||
static auto start(std::shared_ptr<Client>) -> std::shared_ptr<Bot>;
|
||||
|
||||
auto shutdown() -> void;
|
||||
};
|
16
myirc/include/c_callback.hpp
Normal file
16
myirc/include/c_callback.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
template <typename> struct CCallback_;
|
||||
template <typename F, typename R, typename... Ts>
|
||||
struct CCallback_<R (F::*) (Ts...) const>
|
||||
{
|
||||
static R invoke(Ts... args, void* u)
|
||||
{
|
||||
return (*reinterpret_cast<F*>(u))(args...);
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Wrapper for passing closures through C-style callbacks.
|
||||
/// @tparam F Type of the closure
|
||||
template <typename F>
|
||||
using CCallback = CCallback_<decltype(&F::operator())>;
|
31
myirc/include/challenge.hpp
Normal file
31
myirc/include/challenge.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "connection.hpp"
|
||||
#include "ref.hpp"
|
||||
|
||||
#include <boost/signals2/connection.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
/// @brief Implements the CHALLENGE command protocol to identify as an operator.
|
||||
class Challenge : std::enable_shared_from_this<Challenge>
|
||||
{
|
||||
EVP_PKEY_Ref key_;
|
||||
Connection &connection_;
|
||||
boost::signals2::scoped_connection slot_;
|
||||
std::string buffer_;
|
||||
|
||||
auto on_ircmsg(IrcCommand cmd, const IrcMsg &msg) -> void;
|
||||
auto finish_challenge() -> void;
|
||||
|
||||
public:
|
||||
Challenge(EVP_PKEY_Ref, Connection &);
|
||||
|
||||
/// @brief Starts the CHALLENGE protocol.
|
||||
/// @param connection Registered connection.
|
||||
/// @param user Operator username
|
||||
/// @param key Operator private RSA key
|
||||
/// @return Handle to the challenge object.
|
||||
static auto start(Connection &, std::string_view user, EVP_PKEY_Ref key) -> std::shared_ptr<Challenge>;
|
||||
};
|
103
myirc/include/client.hpp
Normal file
103
myirc/include/client.hpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include "connection.hpp"
|
||||
#include "sasl_mechanism.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <span>
|
||||
|
||||
struct Connection;
|
||||
struct IrcMsg;
|
||||
|
||||
enum class Casemap
|
||||
{
|
||||
Rfc1459,
|
||||
Rfc1459_Strict,
|
||||
Ascii,
|
||||
};
|
||||
|
||||
struct Chat {
|
||||
std::span<const irctag> tags;
|
||||
bool is_notice;
|
||||
char status_msg;
|
||||
std::string_view source;
|
||||
std::string_view target;
|
||||
std::string_view message;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Thread to track this connection's identity, and IRC state.
|
||||
*
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
Connection &connection_;
|
||||
|
||||
std::string nickname_;
|
||||
std::string mode_;
|
||||
std::unordered_set<std::string> channels_;
|
||||
|
||||
// RPL_ISUPPORT state
|
||||
std::unordered_map<std::string, std::string> isupport_;
|
||||
std::unique_ptr<SaslMechanism> sasl_mechanism_;
|
||||
|
||||
Casemap casemap_;
|
||||
std::string channel_prefix_;
|
||||
std::string status_msg_;
|
||||
|
||||
std::unordered_map<std::string, std::string> caps_available_;
|
||||
std::unordered_set<std::string> caps_;
|
||||
|
||||
auto on_welcome(const IrcMsg &irc) -> void;
|
||||
auto on_isupport(const IrcMsg &irc) -> void;
|
||||
auto on_nick(const IrcMsg &irc) -> void;
|
||||
auto on_umodeis(const IrcMsg &irc) -> void;
|
||||
auto on_join(const IrcMsg &irc) -> void;
|
||||
auto on_kick(const IrcMsg &irc) -> void;
|
||||
auto on_part(const IrcMsg &irc) -> void;
|
||||
auto on_mode(const IrcMsg &irc) -> void;
|
||||
auto on_cap(const IrcMsg &irc) -> void;
|
||||
auto on_authenticate(std::string_view) -> void;
|
||||
auto on_registered() -> void;
|
||||
auto on_chat(bool, const IrcMsg &irc) -> void;
|
||||
|
||||
public:
|
||||
boost::signals2::signal<void()> sig_registered;
|
||||
boost::signals2::signal<void(const std::unordered_map<std::string, std::string> &)> sig_cap_ls;
|
||||
boost::signals2::signal<void(const Chat &)> sig_chat;
|
||||
|
||||
Client(Connection &connection)
|
||||
: connection_{connection}
|
||||
, casemap_{Casemap::Rfc1459}
|
||||
, channel_prefix_{"#&"}
|
||||
, status_msg_{"+@"}
|
||||
{
|
||||
}
|
||||
|
||||
auto get_connection() -> Connection & { return connection_; }
|
||||
|
||||
static auto start(Connection &) -> std::shared_ptr<Client>;
|
||||
|
||||
auto start_sasl(std::unique_ptr<SaslMechanism> mechanism) -> void;
|
||||
|
||||
auto get_connection() const -> std::shared_ptr<Connection>
|
||||
{
|
||||
return connection_.shared_from_this();
|
||||
}
|
||||
|
||||
auto get_my_nick() const -> const std::string &;
|
||||
auto get_my_mode() const -> const std::string &;
|
||||
auto get_my_channels() const -> const std::unordered_set<std::string> &;
|
||||
|
||||
auto list_caps() -> void;
|
||||
|
||||
auto is_my_nick(std::string_view nick) const -> bool;
|
||||
auto is_my_mask(std::string_view mask) const -> bool;
|
||||
auto is_channel(std::string_view name) const -> bool;
|
||||
|
||||
auto casemap(std::string_view) const -> std::string;
|
||||
auto casemap_compare(std::string_view, std::string_view) const -> int;
|
||||
|
||||
auto shutdown() -> void;
|
||||
};
|
131
myirc/include/connection.hpp
Normal file
131
myirc/include/connection.hpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include "irc_command.hpp"
|
||||
#include "ircmsg.hpp"
|
||||
#include "ref.hpp"
|
||||
#include "snote.hpp"
|
||||
#include "stream.hpp"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Connection : public std::enable_shared_from_this<Connection>
|
||||
{
|
||||
public:
|
||||
struct Settings
|
||||
{
|
||||
bool tls;
|
||||
std::string host;
|
||||
std::uint16_t port;
|
||||
|
||||
X509_Ref client_cert;
|
||||
EVP_PKEY_Ref client_key;
|
||||
std::string verify;
|
||||
std::string sni;
|
||||
|
||||
std::string socks_host;
|
||||
std::uint16_t socks_port;
|
||||
std::string socks_user;
|
||||
std::string socks_pass;
|
||||
};
|
||||
|
||||
private:
|
||||
Stream stream_;
|
||||
boost::asio::steady_timer watchdog_timer_;
|
||||
std::list<std::string> write_strings_;
|
||||
bool write_posted_;
|
||||
|
||||
// Set true when watchdog triggers.
|
||||
// Set false when message received.
|
||||
bool stalled_;
|
||||
|
||||
// AUTHENTICATE support
|
||||
std::string authenticate_buffer_;
|
||||
|
||||
auto write_buffers() -> void;
|
||||
auto dispatch_line(char *line) -> void;
|
||||
|
||||
static constexpr std::chrono::seconds watchdog_duration = std::chrono::seconds{30};
|
||||
auto watchdog() -> void;
|
||||
auto watchdog_activity() -> void;
|
||||
|
||||
auto connect(Settings settings) -> boost::asio::awaitable<void>;
|
||||
auto on_authenticate(std::string_view) -> void;
|
||||
|
||||
/// Build and send well-formed IRC message from individual parameters
|
||||
auto write_irc(std::string) -> void;
|
||||
auto write_irc(std::string, std::string_view) -> void;
|
||||
template <typename... Args>
|
||||
auto write_irc(std::string front, std::string_view next, Args... rest) -> void;
|
||||
|
||||
public:
|
||||
boost::signals2::signal<void()> sig_connect;
|
||||
boost::signals2::signal<void()> sig_disconnect;
|
||||
boost::signals2::signal<void(IrcCommand, const IrcMsg &)> sig_ircmsg;
|
||||
boost::signals2::signal<void(SnoteMatch &)> sig_snote;
|
||||
boost::signals2::signal<void(std::string_view)> sig_authenticate;
|
||||
|
||||
Connection(boost::asio::io_context &io);
|
||||
|
||||
/// Write bytes into the socket.
|
||||
auto write_line(std::string message) -> void;
|
||||
|
||||
auto get_executor() -> boost::asio::any_io_executor
|
||||
{
|
||||
return stream_.get_executor();
|
||||
}
|
||||
|
||||
auto start(Settings) -> void;
|
||||
auto close() -> void;
|
||||
|
||||
auto send_ping(std::string_view) -> void;
|
||||
auto send_pong(std::string_view) -> void;
|
||||
auto send_pass(std::string_view) -> void;
|
||||
auto send_user(std::string_view, std::string_view) -> void;
|
||||
auto send_nick(std::string_view) -> void;
|
||||
auto send_join(std::string_view) -> void;
|
||||
auto send_names(std::string_view channel) -> void;
|
||||
auto send_kick(std::string_view, std::string_view, std::string_view) -> void;
|
||||
auto send_kill(std::string_view, std::string_view) -> void;
|
||||
auto send_quit(std::string_view) -> void;
|
||||
auto send_cap_ls() -> void;
|
||||
auto send_cap_end() -> void;
|
||||
auto send_map() -> void;
|
||||
auto send_testline(std::string_view) -> void;
|
||||
auto send_testmask(std::string_view) -> void;
|
||||
auto send_testmask_gecos(std::string_view, std::string_view) -> void;
|
||||
auto send_masktrace(std::string_view) -> void;
|
||||
auto send_masktrace_gecos(std::string_view, std::string_view) -> void;
|
||||
auto send_get_topic(std::string_view) -> void;
|
||||
auto send_set_topic(std::string_view, std::string_view) -> void;
|
||||
auto send_cap_req(std::string_view) -> void;
|
||||
auto send_privmsg(std::string_view, std::string_view) -> void;
|
||||
auto send_wallops(std::string_view) -> void;
|
||||
auto send_notice(std::string_view, std::string_view) -> void;
|
||||
auto send_authenticate(std::string_view message) -> void;
|
||||
auto send_authenticate_encoded(std::string_view message) -> void;
|
||||
auto send_authenticate_abort() -> void;
|
||||
auto send_whois(std::string_view) -> void;
|
||||
auto send_whois_remote(std::string_view, std::string_view) -> void;
|
||||
auto send_challenge(std::string_view) -> void;
|
||||
auto send_oper(std::string_view, std::string_view) -> void;
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
auto Connection::write_irc(std::string front, std::string_view next, Args... rest) -> void
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
if (next.empty() || next.front() == ':' || next.find_first_of("\r\n \0"sv) != next.npos)
|
||||
{
|
||||
throw std::runtime_error{"bad irc argument"};
|
||||
}
|
||||
|
||||
front += " ";
|
||||
front += next;
|
||||
write_irc(std::move(front), rest...);
|
||||
}
|
282
myirc/include/irc_command.hpp
Normal file
282
myirc/include/irc_command.hpp
Normal file
@@ -0,0 +1,282 @@
|
||||
enum class IrcCommand
|
||||
{
|
||||
UNKNOWN,
|
||||
RPL_WELCOME,
|
||||
RPL_YOURHOST,
|
||||
RPL_CREATED,
|
||||
RPL_MYINFO,
|
||||
RPL_ISUPPORT,
|
||||
RPL_SNOMASK,
|
||||
RPL_REDIR,
|
||||
RPL_MAP,
|
||||
RPL_MAPMORE,
|
||||
RPL_MAPEND,
|
||||
RPL_SAVENICK,
|
||||
RPL_TRACELINK,
|
||||
RPL_TRACECONNECTING,
|
||||
RPL_TRACEHANDSHAKE,
|
||||
RPL_TRACEUNKNOWN,
|
||||
RPL_TRACEOPERATOR,
|
||||
RPL_TRACEUSER,
|
||||
RPL_TRACESERVER,
|
||||
RPL_TRACENEWTYPE,
|
||||
RPL_TRACECLASS,
|
||||
RPL_STATSLINKINFO,
|
||||
RPL_STATSCOMMANDS,
|
||||
RPL_STATSCLINE,
|
||||
RPL_STATSNLINE,
|
||||
RPL_STATSILINE,
|
||||
RPL_STATSKLINE,
|
||||
RPL_STATSQLINE,
|
||||
RPL_STATSYLINE,
|
||||
RPL_ENDOFSTATS,
|
||||
RPL_STATSPLINE,
|
||||
RPL_UMODEIS,
|
||||
RPL_STATSFLINE,
|
||||
RPL_STATSDLINE,
|
||||
RPL_SERVLIST,
|
||||
RPL_SERVLISTEND,
|
||||
RPL_STATSLLINE,
|
||||
RPL_STATSUPTIME,
|
||||
RPL_STATSOLINE,
|
||||
RPL_STATSHLINE,
|
||||
RPL_STATSSLINE,
|
||||
RPL_STATSXLINE,
|
||||
RPL_STATSULINE,
|
||||
RPL_STATSDEBUG,
|
||||
RPL_STATSCONN,
|
||||
RPL_LUSERCLIENT,
|
||||
RPL_LUSEROP,
|
||||
RPL_LUSERUNKNOWN,
|
||||
RPL_LUSERCHANNELS,
|
||||
RPL_LUSERME,
|
||||
RPL_ADMINME,
|
||||
RPL_ADMINLOC1,
|
||||
RPL_ADMINLOC2,
|
||||
RPL_ADMINEMAIL,
|
||||
RPL_TRACELOG,
|
||||
RPL_ENDOFTRACE,
|
||||
RPL_LOAD2HI,
|
||||
RPL_LOCALUSERS,
|
||||
RPL_GLOBALUSERS,
|
||||
RPL_PRIVS,
|
||||
RPL_WHOISCERTFP,
|
||||
RPL_ACCEPTLIST,
|
||||
RPL_ENDOFACCEPT,
|
||||
RPL_NONE,
|
||||
RPL_AWAY,
|
||||
RPL_USERHOST,
|
||||
RPL_ISON,
|
||||
RPL_TEXT,
|
||||
RPL_UNAWAY,
|
||||
RPL_NOWAWAY,
|
||||
RPL_WHOISHELPOP,
|
||||
RPL_WHOISUSER,
|
||||
RPL_WHOISSERVER,
|
||||
RPL_WHOISOPERATOR,
|
||||
RPL_WHOWASUSER,
|
||||
RPL_ENDOFWHOWAS,
|
||||
RPL_WHOISCHANOP,
|
||||
RPL_WHOISIDLE,
|
||||
RPL_ENDOFWHOIS,
|
||||
RPL_WHOISCHANNELS,
|
||||
RPL_WHOISSPECIAL,
|
||||
RPL_LISTSTART,
|
||||
RPL_LIST,
|
||||
RPL_LISTEND,
|
||||
RPL_CHANNELMODEIS,
|
||||
RPL_CHANNELMLOCK,
|
||||
RPL_CHANNELURL,
|
||||
RPL_CREATIONTIME,
|
||||
RPL_WHOISLOGGEDIN,
|
||||
RPL_NOTOPIC,
|
||||
RPL_TOPIC,
|
||||
RPL_TOPICWHOTIME,
|
||||
RPL_WHOISTEXT,
|
||||
RPL_WHOISACTUALLY,
|
||||
RPL_INVITING,
|
||||
RPL_SUMMONING,
|
||||
RPL_INVITELIST,
|
||||
RPL_ENDOFINVITELIST,
|
||||
RPL_EXCEPTLIST,
|
||||
RPL_ENDOFEXCEPTLIST,
|
||||
RPL_VERSION,
|
||||
RPL_WHOREPLY,
|
||||
RPL_WHOSPCRPL,
|
||||
RPL_ENDOFWHO,
|
||||
RPL_NAMREPLY,
|
||||
RPL_WHOWASREAL,
|
||||
RPL_ENDOFNAMES,
|
||||
RPL_KILLDONE,
|
||||
RPL_CLOSING,
|
||||
RPL_CLOSEEND,
|
||||
RPL_LINKS,
|
||||
RPL_ENDOFLINKS,
|
||||
RPL_BANLIST,
|
||||
RPL_ENDOFBANLIST,
|
||||
RPL_INFO,
|
||||
RPL_MOTD,
|
||||
RPL_INFOSTART,
|
||||
RPL_ENDOFINFO,
|
||||
RPL_MOTDSTART,
|
||||
RPL_ENDOFMOTD,
|
||||
RPL_WHOISHOST,
|
||||
RPL_YOUREOPER,
|
||||
RPL_REHASHING,
|
||||
RPL_MYPORTIS,
|
||||
RPL_NOTOPERANYMORE,
|
||||
RPL_RSACHALLENGE,
|
||||
RPL_TIME,
|
||||
RPL_USERSSTART,
|
||||
RPL_USERS,
|
||||
RPL_ENDOFUSERS,
|
||||
RPL_NOUSERS,
|
||||
RPL_HOSTHIDDEN,
|
||||
ERR_NOSUCHNICK,
|
||||
ERR_NOSUCHSERVER,
|
||||
ERR_NOSUCHCHANNEL,
|
||||
ERR_CANNOTSENDTOCHAN,
|
||||
ERR_TOOMANYCHANNELS,
|
||||
ERR_WASNOSUCHNICK,
|
||||
ERR_TOOMANYTARGETS,
|
||||
ERR_NOORIGIN,
|
||||
ERR_INVALIDCAPCMD,
|
||||
ERR_NORECIPIENT,
|
||||
ERR_NOTEXTTOSEND,
|
||||
ERR_NOTOPLEVEL,
|
||||
ERR_WILDTOPLEVEL,
|
||||
ERR_MSGNEEDREGGEDNICK,
|
||||
ERR_TOOMANYMATCHES,
|
||||
ERR_UNKNOWNCOMMAND,
|
||||
ERR_NOMOTD,
|
||||
ERR_NOADMININFO,
|
||||
ERR_FILEERROR,
|
||||
ERR_NONICKNAMEGIVEN,
|
||||
ERR_ERRONEUSNICKNAME,
|
||||
ERR_NICKNAMEINUSE,
|
||||
ERR_BANNICKCHANGE,
|
||||
ERR_NICKCOLLISION,
|
||||
ERR_UNAVAILRESOURCE,
|
||||
ERR_NICKTOOFAST,
|
||||
ERR_SERVICESDOWN,
|
||||
ERR_USERNOTINCHANNEL,
|
||||
ERR_NOTONCHANNEL,
|
||||
ERR_USERONCHANNEL,
|
||||
ERR_NOLOGIN,
|
||||
ERR_SUMMONDISABLED,
|
||||
ERR_USERSDISABLED,
|
||||
ERR_NOTREGISTERED,
|
||||
ERR_ACCEPTFULL,
|
||||
ERR_ACCEPTEXIST,
|
||||
ERR_ACCEPTNOT,
|
||||
ERR_NEEDMOREPARAMS,
|
||||
ERR_ALREADYREGISTRED,
|
||||
ERR_NOPERMFORHOST,
|
||||
ERR_PASSWDMISMATCH,
|
||||
ERR_YOUREBANNEDCREEP,
|
||||
ERR_YOUWILLBEBANNED,
|
||||
ERR_KEYSET,
|
||||
ERR_LINKCHANNEL,
|
||||
ERR_CHANNELISFULL,
|
||||
ERR_UNKNOWNMODE,
|
||||
ERR_INVITEONLYCHAN,
|
||||
ERR_BANNEDFROMCHAN,
|
||||
ERR_BADCHANNELKEY,
|
||||
ERR_BADCHANMASK,
|
||||
ERR_NEEDREGGEDNICK,
|
||||
ERR_BANLISTFULL,
|
||||
ERR_BADCHANNAME,
|
||||
ERR_THROTTLE,
|
||||
ERR_NOPRIVILEGES,
|
||||
ERR_CHANOPRIVSNEEDED,
|
||||
ERR_CANTKILLSERVER,
|
||||
ERR_ISCHANSERVICE,
|
||||
ERR_BANNEDNICK,
|
||||
ERR_NONONREG,
|
||||
ERR_VOICENEEDED,
|
||||
ERR_NOOPERHOST,
|
||||
ERR_CANNOTSENDTOUSER,
|
||||
ERR_OWNMODE,
|
||||
ERR_UMODEUNKNOWNFLAG,
|
||||
ERR_USERSDONTMATCH,
|
||||
ERR_GHOSTEDCLIENT,
|
||||
ERR_USERNOTONSERV,
|
||||
ERR_WRONGPONG,
|
||||
ERR_DISABLED,
|
||||
ERR_HELPNOTFOUND,
|
||||
RPL_STARTTLS,
|
||||
RPL_WHOISSECURE,
|
||||
ERR_STARTTLS,
|
||||
RPL_MODLIST,
|
||||
RPL_ENDOFMODLIST,
|
||||
RPL_HELPSTART,
|
||||
RPL_HELPTXT,
|
||||
RPL_ENDOFHELP,
|
||||
ERR_TARGCHANGE,
|
||||
RPL_ETRACEFULL,
|
||||
RPL_ETRACE,
|
||||
RPL_KNOCK,
|
||||
RPL_KNOCKDLVR,
|
||||
ERR_TOOMANYKNOCK,
|
||||
ERR_CHANOPEN,
|
||||
ERR_KNOCKONCHAN,
|
||||
ERR_KNOCKDISABLED,
|
||||
ERR_TARGUMODEG,
|
||||
RPL_TARGNOTIFY,
|
||||
RPL_UMODEGMSG,
|
||||
RPL_OMOTDSTART,
|
||||
RPL_OMOTD,
|
||||
RPL_ENDOFOMOTD,
|
||||
ERR_NOPRIVS,
|
||||
RPL_TESTMASK,
|
||||
RPL_TESTLINE,
|
||||
RPL_NOTESTLINE,
|
||||
RPL_TESTMASKGECO,
|
||||
RPL_QUIETLIST,
|
||||
RPL_ENDOFQUIETLIS,
|
||||
RPL_MONONLINE,
|
||||
RPL_MONOFFLINE,
|
||||
RPL_MONLIST,
|
||||
RPL_ENDOFMONLIS,
|
||||
ERR_MONLISTFULL,
|
||||
RPL_RSACHALLENGE2,
|
||||
RPL_ENDOFRSACHALLENGE2,
|
||||
ERR_MLOCKRESTRICTE,
|
||||
ERR_INVALIDBAN,
|
||||
ERR_TOPICLOCK,
|
||||
RPL_SCANMATCHED,
|
||||
RPL_SCANUMODES,
|
||||
RPL_LOGGEDIN,
|
||||
RPL_LOGGEDOUT,
|
||||
ERR_NICKLOCKED,
|
||||
RPL_SASLSUCCESS,
|
||||
ERR_SASLFAIL,
|
||||
ERR_SASLTOOLONG,
|
||||
ERR_SASLABORTED,
|
||||
ERR_SASLALREADY,
|
||||
RPL_SASLMECHS,
|
||||
ACCOUNT,
|
||||
AUTHENTICATE,
|
||||
AWAY,
|
||||
BATCH,
|
||||
BOUNCER,
|
||||
CAP,
|
||||
CHGHOST,
|
||||
ERROR,
|
||||
INVITE,
|
||||
JOIN,
|
||||
KICK,
|
||||
KILL,
|
||||
MODE,
|
||||
NICK,
|
||||
NOTICE,
|
||||
PART,
|
||||
PING,
|
||||
PONG,
|
||||
PRIVMSG,
|
||||
QUIT,
|
||||
SETNAME,
|
||||
TAGMSG,
|
||||
TOPIC,
|
||||
WALLOPS,
|
||||
};
|
277
myirc/include/irc_coroutine.hpp
Normal file
277
myirc/include/irc_coroutine.hpp
Normal file
@@ -0,0 +1,277 @@
|
||||
#pragma once
|
||||
|
||||
#include "connection.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <coroutine>
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
struct irc_promise;
|
||||
|
||||
/// A coroutine that can co_await on various IRC events
|
||||
struct irc_coroutine : std::coroutine_handle<irc_promise>
|
||||
{
|
||||
using promise_type = irc_promise;
|
||||
|
||||
/// Start the coroutine and associate it with a specific connection.
|
||||
auto start(Connection &connection) -> void;
|
||||
|
||||
/// Returns true when this coroutine is still waiting on events
|
||||
auto is_running() -> bool;
|
||||
|
||||
/// Returns the exception that terminated this coroutine, if there is one.
|
||||
auto exception() -> std::exception_ptr;
|
||||
};
|
||||
|
||||
struct irc_promise
|
||||
{
|
||||
// Pointer to the connection while running. Cleared on termination.
|
||||
std::shared_ptr<Connection> connection_;
|
||||
|
||||
// Pointer to exception that terminated this coroutine if there is one.
|
||||
std::exception_ptr exception_;
|
||||
|
||||
auto get_return_object() -> irc_coroutine
|
||||
{
|
||||
return {irc_coroutine::from_promise(*this)};
|
||||
}
|
||||
|
||||
// Suspend waiting for start() to initialize connection_
|
||||
auto initial_suspend() noexcept -> std::suspend_always { return {}; }
|
||||
|
||||
// Suspend so that is_running() and exception() work
|
||||
auto final_suspend() noexcept -> std::suspend_always { return {}; }
|
||||
|
||||
// Normal termination
|
||||
auto return_void() -> void
|
||||
{
|
||||
connection_.reset();
|
||||
}
|
||||
|
||||
// Abnormal termination - remember the exception
|
||||
auto unhandled_exception() -> void
|
||||
{
|
||||
connection_.reset();
|
||||
exception_ = std::current_exception();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class Wait;
|
||||
|
||||
/// Argument to a Wait that expects one or more IRC messages
|
||||
class wait_ircmsg
|
||||
{
|
||||
// Vector of commands this wait is expecting. Leave empty to accept all messages.
|
||||
std::vector<IrcCommand> want_cmds_;
|
||||
|
||||
// Slot for the ircmsg event
|
||||
boost::signals2::scoped_connection ircmsg_slot_;
|
||||
|
||||
public:
|
||||
using result_type = std::pair<IrcCommand, const IrcMsg &>;
|
||||
|
||||
wait_ircmsg(std::initializer_list<IrcCommand> want_cmds)
|
||||
: want_cmds_{want_cmds}
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t I, typename... Ts>
|
||||
auto start(Wait<Ts...> &command) -> void;
|
||||
auto stop() -> void { ircmsg_slot_.disconnect(); }
|
||||
};
|
||||
|
||||
class wait_snote
|
||||
{
|
||||
// Vector of tags this wait is expecting. Leave empty to accept all messages.
|
||||
std::vector<SnoteTag> want_tags_;
|
||||
|
||||
// Slot for the snote event
|
||||
boost::signals2::scoped_connection snote_slot_;
|
||||
|
||||
public:
|
||||
using result_type = SnoteMatch;
|
||||
|
||||
wait_snote(std::initializer_list<SnoteTag> want_tags)
|
||||
: want_tags_{want_tags}
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t I, typename... Ts>
|
||||
auto start(Wait<Ts...> &command) -> void;
|
||||
auto stop() -> void { snote_slot_.disconnect(); }
|
||||
};
|
||||
|
||||
class wait_timeout
|
||||
{
|
||||
std::optional<boost::asio::steady_timer> timer_;
|
||||
std::chrono::milliseconds timeout_;
|
||||
|
||||
public:
|
||||
struct result_type
|
||||
{
|
||||
};
|
||||
wait_timeout(std::chrono::milliseconds timeout)
|
||||
: timeout_{timeout}
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t I, typename... Ts>
|
||||
auto start(Wait<Ts...> &command) -> void;
|
||||
auto stop() -> void { timer_->cancel(); }
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class Wait
|
||||
{
|
||||
// State associated with each wait mode
|
||||
std::tuple<Ts...> modes_;
|
||||
|
||||
// Result from any one of the wait modes
|
||||
std::optional<std::variant<typename Ts::result_type...>> result_;
|
||||
|
||||
// Handle of the continuation to be resumed when one of the wait
|
||||
// modes is ready.
|
||||
std::coroutine_handle<irc_promise> handle_;
|
||||
|
||||
// Slot for tearing down the irc_coroutine in case the connection
|
||||
// fails before any wait modes complete.
|
||||
boost::signals2::scoped_connection disconnect_slot_;
|
||||
|
||||
template <size_t I>
|
||||
auto start_mode() -> void
|
||||
{
|
||||
std::get<I>(modes_).template start<I, Ts...>(*this);
|
||||
}
|
||||
|
||||
template <std::size_t... Indices>
|
||||
auto start_modes(std::index_sequence<Indices...>) -> void
|
||||
{
|
||||
(start_mode<Indices>(), ...);
|
||||
}
|
||||
|
||||
template <std::size_t... Indices>
|
||||
auto stop_modes(std::index_sequence<Indices...>) -> void
|
||||
{
|
||||
(std::get<Indices>(modes_).stop(), ...);
|
||||
}
|
||||
|
||||
public:
|
||||
Wait(Ts &&...modes)
|
||||
: modes_{std::forward<Ts>(modes)...}
|
||||
{
|
||||
}
|
||||
|
||||
// Get the connection that this coroutine was started with.
|
||||
auto get_connection() const -> Connection &
|
||||
{
|
||||
return *handle_.promise().connection_;
|
||||
}
|
||||
|
||||
// Store a successful result and resume the coroutine
|
||||
template <size_t I, typename... Args>
|
||||
auto complete(Args &&...args) -> void
|
||||
{
|
||||
result_.emplace(std::in_place_index<I>, std::forward<Args>(args)...);
|
||||
handle_.resume();
|
||||
}
|
||||
|
||||
// The coroutine always needs to wait for a message. It will never
|
||||
// be ready immediately.
|
||||
auto await_ready() noexcept -> bool { return false; }
|
||||
|
||||
/// Install event handles in the connection that will resume this coroutine.
|
||||
auto await_suspend(std::coroutine_handle<irc_promise> handle) -> void;
|
||||
|
||||
auto await_resume() -> std::variant<typename Ts::result_type...>;
|
||||
};
|
||||
|
||||
template <size_t I, typename... Ts>
|
||||
auto wait_ircmsg::start(Wait<Ts...> &command) -> void
|
||||
{
|
||||
ircmsg_slot_ = command.get_connection().sig_ircmsg.connect([this, &command](auto cmd, auto &msg) {
|
||||
const auto wanted = want_cmds_.empty() || std::find(want_cmds_.begin(), want_cmds_.end(), cmd) != want_cmds_.end();
|
||||
if (wanted)
|
||||
{
|
||||
command.template complete<I>(cmd, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <size_t I, typename... Ts>
|
||||
auto wait_snote::start(Wait<Ts...> &command) -> void
|
||||
{
|
||||
snote_slot_ = command.get_connection().sig_snote.connect([this, &command](auto &match) {
|
||||
const auto tag = match.get_tag();
|
||||
const auto wanted = want_tags_.empty() || std::find(want_tags_.begin(), want_tags_.end(), tag) != want_tags_.end();
|
||||
if (wanted)
|
||||
{
|
||||
command.template complete<I>(match);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <size_t I, typename... Ts>
|
||||
auto wait_timeout::start(Wait<Ts...> &command) -> void
|
||||
{
|
||||
timer_.emplace(command.get_connection().get_executor());
|
||||
timer_->expires_after(timeout_);
|
||||
timer_->async_wait([this, &command](const auto &error) {
|
||||
if (not error)
|
||||
{
|
||||
timer_.reset();
|
||||
command.template complete<I>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto Wait<Ts...>::await_suspend(std::coroutine_handle<irc_promise> handle) -> void
|
||||
{
|
||||
handle_ = handle;
|
||||
|
||||
const auto tuple_size = std::tuple_size_v<decltype(modes_)>;
|
||||
start_modes(std::make_index_sequence<tuple_size>{});
|
||||
|
||||
disconnect_slot_ = get_connection().sig_disconnect.connect([this]() {
|
||||
handle_.resume();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto Wait<Ts...>::await_resume() -> std::variant<typename Ts::result_type...>
|
||||
{
|
||||
const auto tuple_size = std::tuple_size_v<decltype(modes_)>;
|
||||
stop_modes(std::make_index_sequence<tuple_size>{});
|
||||
|
||||
disconnect_slot_.disconnect();
|
||||
|
||||
if (result_)
|
||||
{
|
||||
return std::move(*result_);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error{"connection terminated"};
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the coroutine and associate it with a specific connection.
|
||||
inline auto irc_coroutine::start(Connection &connection) -> void
|
||||
{
|
||||
promise().connection_ = connection.shared_from_this();
|
||||
resume();
|
||||
}
|
||||
|
||||
/// Returns true when this coroutine is still waiting on events
|
||||
inline auto irc_coroutine::is_running() -> bool
|
||||
{
|
||||
return promise().connection_ != nullptr;
|
||||
}
|
||||
|
||||
inline auto irc_coroutine::exception() -> std::exception_ptr
|
||||
{
|
||||
return promise().exception_;
|
||||
}
|
74
myirc/include/ircmsg.hpp
Normal file
74
myirc/include/ircmsg.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
struct irctag
|
||||
{
|
||||
std::string_view key;
|
||||
std::string_view val;
|
||||
|
||||
irctag(std::string_view key, std::string_view val)
|
||||
: key{key}
|
||||
, val{val}
|
||||
{
|
||||
}
|
||||
|
||||
friend auto operator==(const irctag &, const irctag &) -> bool = default;
|
||||
};
|
||||
|
||||
struct IrcMsg
|
||||
{
|
||||
std::vector<irctag> tags;
|
||||
std::vector<std::string_view> args;
|
||||
std::string_view source;
|
||||
std::string_view command;
|
||||
|
||||
IrcMsg() = default;
|
||||
|
||||
IrcMsg(
|
||||
std::vector<irctag> &&tags,
|
||||
std::string_view source,
|
||||
std::string_view command,
|
||||
std::vector<std::string_view> &&args
|
||||
)
|
||||
: tags(std::move(tags))
|
||||
, args(std::move(args))
|
||||
, source{source}
|
||||
, command{command}
|
||||
{
|
||||
}
|
||||
|
||||
bool hassource() const;
|
||||
|
||||
friend bool operator==(const IrcMsg &, const IrcMsg &) = default;
|
||||
};
|
||||
|
||||
enum class irc_error_code
|
||||
{
|
||||
MISSING_TAG,
|
||||
MISSING_COMMAND,
|
||||
};
|
||||
|
||||
auto operator<<(std::ostream &out, irc_error_code) -> std::ostream &;
|
||||
|
||||
struct irc_parse_error : public std::exception
|
||||
{
|
||||
irc_error_code code;
|
||||
irc_parse_error(irc_error_code code)
|
||||
: code(code)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the given IRC message into a structured format.
|
||||
* The original message is mangled to store string fragments
|
||||
* that are pointed to by the structured message type.
|
||||
*
|
||||
* Returns zero for success, non-zero for parse error.
|
||||
*/
|
||||
auto parse_irc_message(char *msg) -> IrcMsg;
|
||||
|
||||
auto parse_irc_tags(char *msg) -> std::vector<irctag>;
|
101
myirc/include/linebuffer.hpp
Normal file
101
myirc/include/linebuffer.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
/**
|
||||
* @file linebuffer.hpp
|
||||
* @author Eric Mertens <emertens@gmail.com>
|
||||
* @brief A line buffering class
|
||||
* @version 0.1
|
||||
* @date 2023-08-22
|
||||
*
|
||||
* @copyright Copyright (c) 2023
|
||||
*
|
||||
*/
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief Fixed-size buffer with line-oriented dispatch
|
||||
*
|
||||
*/
|
||||
class LineBuffer
|
||||
{
|
||||
std::vector<char> buffer;
|
||||
|
||||
// [buffer.begin(), end_) contains buffered data
|
||||
// [end_, buffer.end()) is available buffer space
|
||||
std::vector<char>::iterator end_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new Line Buffer object
|
||||
*
|
||||
* @param n Buffer size
|
||||
*/
|
||||
LineBuffer(std::size_t n)
|
||||
: buffer(n)
|
||||
, end_{buffer.begin()}
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the available buffer space
|
||||
*
|
||||
* @return boost::asio::mutable_buffer
|
||||
*/
|
||||
auto get_buffer() -> boost::asio::mutable_buffer
|
||||
{
|
||||
return boost::asio::buffer(&*end_, std::distance(end_, buffer.end()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Commit new buffer bytes and dispatch line callback
|
||||
*
|
||||
* The first n bytes of the buffer will be considered to be
|
||||
* populated. The line callback function will be called once
|
||||
* per completed line. Those lines are removed from the buffer
|
||||
* and the is ready for additional calls to get_buffer and
|
||||
* add_bytes.
|
||||
*
|
||||
* @param n Bytes written to the last call of get_buffer
|
||||
* @param line_cb Callback function to run on each completed line
|
||||
*/
|
||||
auto add_bytes(std::size_t n, std::invocable<char *> auto line_cb) -> void
|
||||
{
|
||||
const auto start = end_;
|
||||
std::advance(end_, n);
|
||||
|
||||
// new data is now located in [start, end_)
|
||||
|
||||
// cursor marks the beginning of the current line
|
||||
auto cursor = buffer.begin();
|
||||
|
||||
for (auto nl = std::find(start, end_, '\n');
|
||||
nl != end_;
|
||||
nl = std::find(cursor, end_, '\n'))
|
||||
{
|
||||
// Null-terminate the line. Support both \n and \r\n
|
||||
if (cursor < nl && *std::prev(nl) == '\r')
|
||||
{
|
||||
*std::prev(nl) = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
*nl = '\0';
|
||||
}
|
||||
|
||||
line_cb(&*cursor);
|
||||
|
||||
cursor = std::next(nl);
|
||||
}
|
||||
|
||||
// If any lines were processed, move all processed lines to
|
||||
// the front of the buffer
|
||||
if (cursor != buffer.begin())
|
||||
{
|
||||
end_ = std::move(cursor, end_, buffer.begin());
|
||||
}
|
||||
}
|
||||
};
|
9
myirc/include/openssl_utils.hpp
Normal file
9
myirc/include/openssl_utils.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "ref.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
auto log_openssl_errors(const std::string_view prefix) -> void;
|
||||
auto key_from_file(const std::string &filename, const std::string_view password) -> EVP_PKEY_Ref;
|
||||
auto cert_from_file(const std::string &filename) -> X509_Ref;
|
46
myirc/include/ref.hpp
Normal file
46
myirc/include/ref.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
template <typename> struct RefTraits {};
|
||||
|
||||
template <> struct RefTraits<EVP_PKEY> {
|
||||
static constexpr void (*Free)(EVP_PKEY*) = EVP_PKEY_free;
|
||||
static constexpr int (*UpRef)(EVP_PKEY*) = EVP_PKEY_up_ref;
|
||||
};
|
||||
|
||||
template <> struct RefTraits<X509> {
|
||||
static constexpr void (*Free)(X509*) = X509_free;
|
||||
static constexpr int (*UpRef)(X509*) = X509_up_ref;
|
||||
};
|
||||
|
||||
template <> struct RefTraits<EVP_PKEY_CTX> {
|
||||
static constexpr void (*Free)(EVP_PKEY_CTX*) = EVP_PKEY_CTX_free;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct FnDeleter {
|
||||
auto operator()(T *ptr) const -> void { RefTraits<T>::Free(ptr); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Ref : std::unique_ptr<T, FnDeleter<T>> {
|
||||
using std::unique_ptr<T, FnDeleter<T>>::unique_ptr;
|
||||
Ref(const Ref &ref) {
|
||||
*this = ref;
|
||||
}
|
||||
Ref &operator=(const Ref &ref) {
|
||||
if (ref) {
|
||||
RefTraits<T>::UpRef(ref.get());
|
||||
this->reset(ref.get());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
using EVP_PKEY_CTX_Ref = Ref<EVP_PKEY_CTX>;
|
||||
using X509_Ref = Ref<X509>;
|
||||
using EVP_PKEY_Ref = Ref<EVP_PKEY>;
|
52
myirc/include/registration.hpp
Normal file
52
myirc/include/registration.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "connection.hpp"
|
||||
#include "client.hpp"
|
||||
#include "ref.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
class Registration : public std::enable_shared_from_this<Registration>
|
||||
{
|
||||
public:
|
||||
struct Settings {
|
||||
std::string nickname;
|
||||
std::string username;
|
||||
std::string realname;
|
||||
std::string password;
|
||||
std::string sasl_mechanism;
|
||||
std::string sasl_authcid;
|
||||
std::string sasl_authzid;
|
||||
std::string sasl_password;
|
||||
EVP_PKEY_Ref sasl_key;
|
||||
};
|
||||
|
||||
private:
|
||||
Settings settings_;
|
||||
std::shared_ptr<Client> client_;
|
||||
|
||||
boost::signals2::scoped_connection slot_;
|
||||
boost::signals2::scoped_connection caps_slot_;
|
||||
|
||||
auto on_connect() -> void;
|
||||
auto on_cap_list(const std::unordered_map<std::string, std::string> &) -> void;
|
||||
|
||||
auto on_ircmsg(IrcCommand, const IrcMsg &msg) -> void;
|
||||
|
||||
auto send_req() -> void;
|
||||
auto randomize_nick() -> void;
|
||||
|
||||
public:
|
||||
Registration(
|
||||
Settings,
|
||||
std::shared_ptr<Client>
|
||||
);
|
||||
|
||||
static auto start(
|
||||
Settings,
|
||||
std::shared_ptr<Client>
|
||||
) -> std::shared_ptr<Registration>;
|
||||
};
|
76
myirc/include/sasl_mechanism.hpp
Normal file
76
myirc/include/sasl_mechanism.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
class SaslMechanism
|
||||
{
|
||||
public:
|
||||
struct NoReply {};
|
||||
struct Failure {};
|
||||
|
||||
using StepResult = std::variant<std::string, NoReply, Failure>;
|
||||
|
||||
virtual ~SaslMechanism() {}
|
||||
|
||||
virtual auto mechanism_name() const -> std::string = 0;
|
||||
virtual auto step(std::string_view msg) -> StepResult = 0;
|
||||
virtual auto is_complete() const -> bool = 0;
|
||||
};
|
||||
|
||||
class SaslPlain final : public SaslMechanism
|
||||
{
|
||||
std::string authcid_;
|
||||
std::string authzid_;
|
||||
std::string password_;
|
||||
bool complete_;
|
||||
|
||||
public:
|
||||
SaslPlain(std::string authcid, std::string authzid, std::string password)
|
||||
: authcid_{std::move(authcid)}
|
||||
, authzid_{std::move(authzid)}
|
||||
, password_{std::move(password)}
|
||||
, complete_{false}
|
||||
{}
|
||||
|
||||
auto mechanism_name() const -> std::string override
|
||||
{
|
||||
return "PLAIN";
|
||||
}
|
||||
|
||||
auto step(std::string_view msg) -> StepResult override;
|
||||
|
||||
auto is_complete() const -> bool override
|
||||
{
|
||||
return complete_;
|
||||
}
|
||||
};
|
||||
|
||||
class SaslExternal final : public SaslMechanism
|
||||
{
|
||||
std::string authzid_;
|
||||
bool complete_;
|
||||
|
||||
public:
|
||||
SaslExternal(std::string authzid)
|
||||
: authzid_{std::move(authzid)}
|
||||
, complete_{false}
|
||||
{}
|
||||
|
||||
auto mechanism_name() const -> std::string override
|
||||
{
|
||||
return "EXTERNAL";
|
||||
}
|
||||
|
||||
auto step(std::string_view msg) -> StepResult override;
|
||||
|
||||
auto is_complete() const -> bool override
|
||||
{
|
||||
return complete_;
|
||||
}
|
||||
};
|
92
myirc/include/snote.hpp
Normal file
92
myirc/include/snote.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include "ircmsg.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
struct hs_database;
|
||||
struct hs_scratch;
|
||||
|
||||
enum class SnoteTag
|
||||
{
|
||||
ClientConnecting,
|
||||
ClientExiting,
|
||||
CreateChannel,
|
||||
DisconnectingKlined,
|
||||
DroppedChannel,
|
||||
FailedChallenge,
|
||||
FailedChallengeFingerprintMismatch,
|
||||
FailedChallengeHostMismatch,
|
||||
FailedChallengeMissingSecure,
|
||||
FailedChallengeNoBlock,
|
||||
FailedChallengeTls,
|
||||
Freeze,
|
||||
IsNowOper,
|
||||
JoinedJuped,
|
||||
Killed,
|
||||
KilledRemote,
|
||||
KilledRemoteOper,
|
||||
LoginAttempts,
|
||||
NewPropagatedKline,
|
||||
NewTemporaryKline,
|
||||
NickChange,
|
||||
NickCollision,
|
||||
NickCollisionServices,
|
||||
OperspyWhois,
|
||||
PossibleFlooder,
|
||||
PropagatedBanExpired,
|
||||
RejectingKlined,
|
||||
SaveMessage,
|
||||
SetVhostOnMarkedAccount,
|
||||
SighupReloadingConf,
|
||||
Spambot,
|
||||
TemporaryDline,
|
||||
TemporaryKlineExpired,
|
||||
TooManyGlobalConnections,
|
||||
TooManyUserConnections,
|
||||
};
|
||||
|
||||
class SnoteMatch
|
||||
{
|
||||
SnoteTag tag_;
|
||||
std::variant<std::pair<const std::regex &, std::string_view>, std::match_results<std::string_view::const_iterator>> components_;
|
||||
|
||||
public:
|
||||
SnoteMatch(SnoteTag tag, const std::regex ®ex, std::string_view full)
|
||||
: tag_{tag}
|
||||
, components_{std::make_pair(std::ref(regex), full)}
|
||||
{
|
||||
}
|
||||
|
||||
auto get_tag() -> SnoteTag { return tag_; }
|
||||
auto get_results() -> const std::match_results<std::string_view::const_iterator> &;
|
||||
};
|
||||
|
||||
struct SnoteCore
|
||||
{
|
||||
struct DbDeleter
|
||||
{
|
||||
auto operator()(hs_database *db) const -> void;
|
||||
};
|
||||
|
||||
struct ScratchDeleter
|
||||
{
|
||||
auto operator()(hs_scratch *scratch) const -> void;
|
||||
};
|
||||
|
||||
/// @brief Database of server notice patterns
|
||||
std::unique_ptr<hs_database, DbDeleter> db_;
|
||||
|
||||
/// @brief HyperScan scratch space
|
||||
std::unique_ptr<hs_scratch, ScratchDeleter> scratch_;
|
||||
|
||||
SnoteCore();
|
||||
auto match(const IrcMsg &msg) -> std::optional<SnoteMatch>;
|
||||
};
|
||||
|
||||
extern SnoteCore snoteCore;
|
114
myirc/include/stream.hpp
Normal file
114
myirc/include/stream.hpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <variant>
|
||||
|
||||
/// @brief Abstraction over plain-text and TLS streams.
|
||||
class Stream : private
|
||||
std::variant<
|
||||
boost::asio::ip::tcp::socket,
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>
|
||||
{
|
||||
public:
|
||||
using tcp_socket = boost::asio::ip::tcp::socket;
|
||||
using tls_stream = boost::asio::ssl::stream<tcp_socket>;
|
||||
|
||||
/// @brief The type of the executor associated with the stream.
|
||||
using executor_type = boost::asio::any_io_executor;
|
||||
|
||||
/// @brief Type of the lowest layer of this stream
|
||||
using lowest_layer_type = tcp_socket::lowest_layer_type;
|
||||
|
||||
private:
|
||||
using base_type = std::variant<tcp_socket, tls_stream>;
|
||||
auto base() -> base_type& { return *this; }
|
||||
auto base() const -> base_type const& { return *this; }
|
||||
|
||||
public:
|
||||
|
||||
/// @brief Initialize stream with a plain TCP socket
|
||||
/// @param ioc IO context of stream
|
||||
template <typename T>
|
||||
Stream(T&& executor) : base_type{std::in_place_type<tcp_socket>, std::forward<T>(executor)} {}
|
||||
|
||||
/// @brief Reset stream to a plain TCP socket
|
||||
/// @return Reference to internal socket object
|
||||
auto reset() -> tcp_socket&
|
||||
{
|
||||
return base().emplace<tcp_socket>(get_executor());
|
||||
}
|
||||
|
||||
/// @brief Upgrade a plain TCP socket into a TLS stream.
|
||||
/// @param ctx TLS context used for handshake
|
||||
/// @return Reference to internal stream object
|
||||
auto upgrade(boost::asio::ssl::context& ctx) -> tls_stream&
|
||||
{
|
||||
auto socket = std::move(std::get<tcp_socket>(base()));
|
||||
return base().emplace<tls_stream>(std::move(socket), ctx);
|
||||
}
|
||||
|
||||
/// @brief Get underlying basic socket
|
||||
/// @return Reference to underlying socket
|
||||
auto lowest_layer() -> lowest_layer_type&
|
||||
{
|
||||
return std::visit([](auto&& x) -> decltype(auto) { return x.lowest_layer(); }, base());
|
||||
}
|
||||
|
||||
/// @brief Get underlying basic socket
|
||||
/// @return Reference to underlying socket
|
||||
auto lowest_layer() const -> lowest_layer_type const&
|
||||
{
|
||||
return std::visit([](auto&& x) -> decltype(auto) { return x.lowest_layer(); }, base());
|
||||
}
|
||||
|
||||
/// @brief Get the executor associated with this stream.
|
||||
/// @return The executor associated with the stream.
|
||||
auto get_executor() -> executor_type const&
|
||||
{
|
||||
return lowest_layer().get_executor();
|
||||
}
|
||||
|
||||
/// @brief Initiates an asynchronous read operation.
|
||||
/// @tparam MutableBufferSequence Type of the buffer sequence.
|
||||
/// @tparam Token Type of the completion token.
|
||||
/// @param buffers The buffer sequence into which data will be read.
|
||||
/// @param token The completion token for the read operation.
|
||||
/// @return The result determined by the completion token.
|
||||
template <
|
||||
typename MutableBufferSequence,
|
||||
boost::asio::completion_token_for<void(boost::system::error_code, std::size_t)> Token>
|
||||
auto async_read_some(MutableBufferSequence&& buffers, Token&& token) -> decltype(auto)
|
||||
{
|
||||
return std::visit([&buffers, &token](auto&& x) -> decltype(auto) {
|
||||
return x.async_read_some(std::forward<MutableBufferSequence>(buffers), std::forward<Token>(token));
|
||||
}, base());
|
||||
}
|
||||
|
||||
/// @brief Initiates an asynchronous write operation.
|
||||
/// @tparam ConstBufferSequence Type of the buffer sequence.
|
||||
/// @tparam Token Type of the completion token.
|
||||
/// @param buffers The buffer sequence from which data will be written.
|
||||
/// @param token The completion token for the write operation.
|
||||
/// @return The result determined by the completion token.
|
||||
template <
|
||||
typename ConstBufferSequence,
|
||||
boost::asio::completion_token_for<void(boost::system::error_code, std::size_t)> Token>
|
||||
auto async_write_some(ConstBufferSequence&& buffers, Token&& token) -> decltype(auto)
|
||||
{
|
||||
return std::visit([&buffers, &token](auto&& x) -> decltype(auto) {
|
||||
return x.async_write_some(std::forward<ConstBufferSequence>(buffers), std::forward<Token>(token));
|
||||
}, base());
|
||||
}
|
||||
|
||||
/// @brief Tear down the network stream
|
||||
auto close() -> void
|
||||
{
|
||||
boost::system::error_code err;
|
||||
auto& socket = lowest_layer();
|
||||
socket.shutdown(socket.shutdown_both, err);
|
||||
socket.lowest_layer().close(err);
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user