#include "instance.hpp" #include "config.hpp" #include "uid.hpp" #include #include #include #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; } static std::tuple colonSplit(std::string_view s) { // Find the colon auto colonPos = s.find(" :"); if (colonPos == s.npos) { return {s, ""}; } // Split there return {s.substr(0, colonPos+1), s.substr(colonPos+2, s.size()-1)}; } 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] = {split[0].data()+1, split[0].size()-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::string(split[0]); } name = std::move(split[1]); if (split.size() == 3) { // Get args and text std::tie(args, text) = colonSplit(split[2]); } } void Command::parse(std::string_view str) { auto split = strSplit(str, ' ', 1); name = std::move(split[0]); // Get text from args std::tie(args, text) = colonSplit(split[1]); } void ModeSet::parse(std::string_view in, NetworkInfo &netInfo) { enum { ADDING, REMOVING } op; for (const auto character : in) { if (character == '+') { op = ADDING; } else if (character == '-') { op = REMOVING; } else { if (op == ADDING) { if (str.find(character) == str.npos) { str.push_back(character); } } else if (op == REMOVING) { auto res = str.find(character); if (res != str.npos) { str.erase(res); } } } } } void User::parse_euid(const Event& event, NetworkInfo& netInfo) { 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::string(split[1])); umode.parse(split[3], netInfo); ident = std::move(split[4]); host = std::move(split[5]); realhost = std::move(split[6]); uid = UUID(std::move(split[7])); // Get realname realname = std::move(event.text); } void User::removeChannel(const s_Channel& channel) { std::remove(channels.begin(), channels.end(), channel); } void Channel::parse_sjoin(const Event& event, Cache& cache, NetworkInfo& netInfo) { this->server = std::get(event.sender.id); auto split = strSplit(event.args, ' ', 2); // Check size if (split.size() != 3) { throw ParseError("In euid parser: This euid event does not have enough arguments"); } // Move values name = std::move(split[1]); mode.parse(split[2], netInfo); // Get members for (auto& raw_uuid : strSplit(event.text, ' ')) { s_User member; // Erase leading sign if (raw_uuid.size() > UUID_len) { raw_uuid = {raw_uuid.data()+1, raw_uuid.size()-1}; } // Find user in cache auto res = cache.find_user_by_uid(UUID(raw_uuid)); if (res == cache.users.end()) { throw DesyncError(); } member = *res; // Append member to list members.push_back(std::move(member)); } } void Channel::removeMember(const s_User &member) { std::remove(members.begin(), members.end(), member); } std::vector::iterator Cache::find_user_by_nick(std::string_view nick) { for (auto it = users.begin(); ; it++) { if (it == users.end() || it->get()->nick == nick) { return it; } } } std::vector::iterator Cache::find_user_by_uid(const UUID& uid) { for (auto it = users.begin(); ; it++) { if (it == users.end() || it->get()->uid == uid) { return it; } } } std::vector::iterator Cache::find_channel_by_name(std::string_view name) { for (auto it = channels.begin(); ; it++) { if (it == channels.end() || it->get()->name == name) { return it; } } } 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 = {line.data(), line.size()-1}; } // Check if server sent an event or command, then parse and process it if (line[0] == ':') { Event event; event.parse(std::move(line)); async::detach(process(std::move(event))); } else { Command command; command.parse(std::move(line)); 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; // Wait for burst to complete std::cout << "I'm done bursting too. Waiting for network informations..." << std::endl; co_await socket->send("VERSION\n"_sv); co_await netInfo.wait_ready(); std::cout << "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"); } // Fetched info else if (event.name == "005") { // Check if that 005 was for me if (event.args.find(config.server.uid.str()) != 0) { co_return; } // Split the list auto split = strSplit(event.args, ' '); // Iterate and find the information we need for (const auto& field : split) { // Split into key and value auto split = strSplit(field, '=', 1); // Check size if (split.size() != 2) { continue; } // Check if we've got the right key std::cout << split[0] << ' ' << split[1] << std::endl; if (split[0] == "NETWORK") { netInfo.name = std::move(split[1]); netInfo.fields_received++; } else if (split[0] == "CHANMODES") { auto modeLists = strSplit(split[1], ',', 3); netInfo.channelModes = { .listModes = std::string(modeLists[0]), .paramOnSetAndUnsetModes = std::string(modeLists[1]), .paramOnSetOnlyModes = std::string(modeLists[2]), .paramLessModes = std::string(modeLists[3]) }; netInfo.fields_received++; } } // Check if everything needed has been fetched if (netInfo.fields_received == 2 && !netInfo.ready) { netInfo.mark_ready(); } } else { co_await netInfo.wait_ready(); std::clog << event.dump() << std::flush; // User updates if (event.name == "EUID") { // User connected to the network // Create user and parse event auto user = std::make_shared(); user->parse_euid(event, netInfo); // Append user to cache cache.users.push_back(std::move(user)); } else if (event.name == "QUIT") { // User disconnected from the network // Find user in cache auto res = cache.find_user_by_uid(std::get(event.sender.id)); if (res == cache.users.end()) { throw DesyncError(); } // Delete user from all channels for (auto& channel : res->get()->channels) { channel->removeMember({*res}); } // Delete user from cache cache.users.erase(res); } else if (event.name == "NICK") { // User changed their nick // Find user in cache auto res = cache.find_user_by_uid(std::get(event.sender.id)); if (res == cache.users.end()) { throw DesyncError(); } // Set nick res->get()->nick = strSplit(event.args, ' ', 1)[0]; } else if (event.name == "MODE") { // User changed their mode // Find user in cache auto res = cache.find_user_by_uid(std::get(event.sender.id)); if (res == cache.users.end()) { throw DesyncError(); } // Update mode res->get()->umode.parse(event.text, netInfo); } // Channel updates else if (event.name == "SJOIN") { // Channel was created // Create channel and parse event auto channel = std::make_shared(); channel->parse_sjoin(event, cache, netInfo); // Append channel to cache cache.channels.push_back(channel); } else if (event.name == "TOPIC" || event.name == "TB") { // Channels topic changed // Find channel in cache auto res = cache.find_channel_by_name(strSplit(event.args, ' ', 1)[0]); if (res == cache.channels.end()) { throw DesyncError(); } // Set topic res->get()->topic = event.text; } } //std::clog << event.dump() << std::flush; } async::result Instance::send_ping() { co_await socket->send("PING :{}\n"_format(connected_server.uid.str())); }