#include "instance.hpp" #include "config.hpp" #include "uid.hpp" #include #include #include #include using fmt::operator""_format; inline std::string_view operator ""_sv(const char *str, unsigned long len) { return {str, static_cast(len)}; } static std::vector strsplit(std::string_view s, char delimiter, std::vector::size_type times = 0) { std::vector to_return; decltype(s.size()) start = 0, finish = 0; while ((finish = s.find_first_of(delimiter, start)) != std::string_view::npos) { to_return.emplace_back(s.substr(start, finish - start)); start = finish + 1; if (to_return.size() == times) { break; } } to_return.emplace_back(s.substr(start)); return to_return; } void Event::parse(std::string_view str) { auto split = strsplit(str, ' ', 2); // Check split size if (split.size() < 2) { throw ParseError("In event parser: Events are expected to have both a sender and name"); } // Move values split[0].erase(0, 1); // Erase leading ':' auto id_len = split[0].size(); if (id_len == SUID_len) { sender = SUID(std::move(split[0])); } else if (id_len == UUID_len) { sender = UUID(std::move(split[0])); } else { sender = std::move(split[0]); } name = std::move(split[1]); if (split.size() == 3) { // Get args and text split = strsplit(std::move(split[2]), ':', 1); if (split.size() == 2) { text = std::move(split[1]); } if (!split.empty()) { args = std::move(split[0]); } } } void Command::parse(std::string_view str) { auto split = strsplit(str, ' ', 1); name = std::move(split[0]); // Get text from args split = strsplit(std::move(split[1]), ':', 1); if (split.size() == 2) { text = std::move(split[1]); } if (!split.empty()) { args = std::move(split[0]); } } void User::parse(Event event) { this->server = std::get(event.sender.id); auto split = strsplit(event.args, ' ', 8); // Check size if (split.size() != 9) { throw ParseError("In euid parser: This euid event does not have enough arguments"); } // Move values nick = std::move(split[0]); hops = std::stoull(std::move(split[1])); ts = std::stoull(std::move(split[2])); umode = std::move(split[3]); ident = std::move(split[4]); host = std::move(split[5]); realhost = std::move(split[6]); id = UUID(std::move(split[7])); // Get realname realname = std::move(event.text); } async::result Instance::run() { // Create connection socket = new uvpp::tcp{s}; co_await socket->connect(addr); socket->recv_start(); // Login co_await login(); // Mainloop while (true) { // Read auto data = co_await socket->recv(); // Check for general error if (data->error()) { continue; } // Check for broken connection if (data->broken()) { break; } // Make string auto dataStr = std::string_view{ data->data.get(), static_cast(data->nread) }; // Split by newlines for (auto& line : strsplit(dataStr, '\n')) { if (line.size() < 2) { continue; // Empty line } // Remove \r if (line.back() == '\r') { line.pop_back(); } // Check if server sent an event or command, then parse and process it if (line[0] == ':') { Event event; event.parse(std::move(line)); std::clog << event.dump() << std::flush; async::detach(process(std::move(event))); } else { Command command; command.parse(std::move(line)); std::clog << command.dump() << std::flush; async::detach(process(std::move(command))); } } } } async::result Instance::login() { co_await socket->send("PASS {} TS 6 :{}\n"_format(config.auth.send_password, config.server.uid.str())); co_await socket->send("CAPAB :QS EX IE KLN UNKLN ENCAP TB SERVICES EUID EOPMOD MLOCK\n"_sv); co_await socket->send("SERVER {} 1 :{}{}\n"_format(config.server.name, config.server.hidden ? "(H) " : "", config.server.description)); co_await socket->send("SVINFO 6 3 0 :{}\n"_format(time(nullptr))); } async::result Instance::burst() { // End burst co_await send_ping(); client_bursting = false; std::cout << "I'm done bursting too. Ready." << std::endl; } async::result Instance::process(const Command command) { if (command.name == "PASS") { // Check password { auto given_password = strsplit(command.args, ' ', 1)[0]; if (given_password != config.auth.accept_password) { throw ConnectionError("Server supplied wrong password during authentication"); } authed = true; } // Get server ID connected_server.uid = SUID(command.text); } if (!authed) { throw ConnectionError("Server tried to execute a command before authenticating"); } else if (command.name == "SERVER") { // Get name and description of connected server connected_server.name = strsplit(command.args, ' ', 1)[0]; connected_server.description = command.text; } else if (command.name == "PING") { // End of burst if (server_bursting) { server_bursting = false; std::cout << "Server burst is over. It's my turn." << std::endl; co_await burst(); } // Reply co_await socket->send(":{} PONG {} {} :{}\n"_format(config.server.uid.str(), config.server.name, command.args, command.text)); } } async::result Instance::process(const Event event) { if (event.name == "NOTICE") { // Don't do anything special } else if (!authed) { throw ConnectionError("Server tried to send an event before authenticating"); } else if (event.name == "EUID") { User user; user.parse(event); std::cout << "User registered: " << user.dump().dump() << std::flush; users[user.id.str()] = std::move(user); } co_return; } async::result Instance::send_ping() { co_await socket->send("PING :{}\n"_format(connected_server.uid.str())); }