From 3d01909801ee10053e982391706545701e89dc7f Mon Sep 17 00:00:00 2001 From: Nils Date: Fri, 18 Jun 2021 21:23:19 +0200 Subject: [PATCH] Huge jump forwards --- instance.cpp | 259 +++++++++++++++++++++++++++++++++++++++++++-------- instance.hpp | 92 +++++++++++++++--- uid.hpp | 8 ++ 3 files changed, 309 insertions(+), 50 deletions(-) diff --git a/instance.cpp b/instance.cpp index ecbc634..54ac904 100644 --- a/instance.cpp +++ b/instance.cpp @@ -4,7 +4,10 @@ #include #include +#include #include +#include +#include #include using fmt::operator""_format; @@ -15,8 +18,8 @@ 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; +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)); @@ -27,71 +30,156 @@ static std::vector strsplit(std::string_view s, char delimiter, std 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); + 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 ':' + 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::move(split[0]); + sender = std::string(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]); - } + std::tie(args, text) = colonSplit(split[2]); } } void Command::parse(std::string_view str) { - auto split = strsplit(str, ' ', 1); + 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]); + 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(Event event) { +void User::parse_euid(const Event& event, NetworkInfo& netInfo) { this->server = std::get(event.sender.id); - auto split = strsplit(event.args, ' ', 8); + 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]); + 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]); - id = UUID(std::move(split[7])); + 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 @@ -120,24 +208,22 @@ async::result Instance::run() { static_cast(data->nread) }; // Split by newlines - for (auto& line : strsplit(dataStr, '\n')) { + for (auto& line : strSplit(dataStr, '\n')) { if (line.size() < 2) { continue; // Empty line } // Remove \r if (line.back() == '\r') { - line.pop_back(); + 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)); - 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))); } } @@ -155,14 +241,18 @@ async::result Instance::burst() { // End burst co_await send_ping(); client_bursting = false; - std::cout << "I'm done bursting too. Ready." << std::endl; + // 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]; + auto given_password = strSplit(command.args, ' ', 1)[0]; if (given_password != config.auth.accept_password) { throw ConnectionError("Server supplied wrong password during authentication"); } @@ -174,7 +264,7 @@ async::result Instance::process(const Command command) { 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.name = strSplit(command.args, ' ', 1)[0]; connected_server.description = command.text; } else if (command.name == "PING") { // End of burst @@ -193,13 +283,106 @@ async::result Instance::process(const Event event) { // 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; + // 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() { diff --git a/instance.hpp b/instance.hpp index a8f32ec..96502e5 100644 --- a/instance.hpp +++ b/instance.hpp @@ -4,10 +4,13 @@ #include #include +#include +#include #include #include #include -#include +#include +#include #include #include @@ -19,6 +22,21 @@ struct ParseError : public std::runtime_error { struct ConnectionError : public std::runtime_error { using std::runtime_error::runtime_error; }; +struct DesyncError : public std::exception { + const char *what() const throw() { + return "Server has desynced!!!"; + } + DesyncError() { + std::cout << "DESCONACCREKO" << std::endl; + } +}; + +struct User; +struct Channel; +struct Cache; +struct NetworkInfo; +using s_User = std::shared_ptr; +using s_Channel = std::shared_ptr; struct Event { AnyUID sender; @@ -39,31 +57,80 @@ struct Command { void parse(std::string_view str); }; +struct ModeSet { + std::string str; + + void parse(std::string_view str, NetworkInfo& netInfo); + ModeSet() {} +}; + struct User { SUID server; - // ... == "EUID" std::string nick; size_t hops; - time_t ts; - std::string umode; + ModeSet umode; std::string ident; std::string host; std::string realhost; - UUID id; + UUID uid; std::string realname; - - User() { - ts = time(nullptr); - } + std::vector channels; Event dump() const { return Event{ .sender = SUID(server), .name = "EUID", - .args = fmt::format("{} 1 {} {} {} {} 0 {} * * :{}", nick, ts, umode, ident, host, id.str(), realname) + .args = fmt::format("{} 1 {} {} {} {} 0 {} * * :{}", nick, time(nullptr), umode.str, ident, host, uid.str(), realname) }; } - void parse(Event event); + void parse_euid(const Event &event, NetworkInfo& netInfo); + void removeChannel(const s_Channel &channel); +}; + +struct Channel { + SUID server; + std::string name; + ModeSet mode; + std::string topic; + std::vector members; + + void parse_sjoin(const Event &event, Cache& cache, NetworkInfo& netInfo); + void removeMember(const s_User& member); +}; + +struct Cache { + std::vector users; + std::vector channels; + + std::vector::iterator find_user_by_nick(std::string_view nick); + std::vector::iterator find_user_by_uid(const UUID& uid); + std::vector::iterator find_channel_by_name(std::string_view name); +}; + +struct NetworkInfo { + async::oneshot_event ready_event; + size_t fields_received = 0; + bool ready = false; + std::string name; + + struct { + // list modes first, then modes that take a param when setting but not when unsetting (just +k), then modes that can only be set once and take args, then modes that can only be set once but take no args. + std::string listModes, // Lists like mode b + paramOnSetAndUnsetModes, // Properties like k + paramOnSetOnlyModes, // Properties like f + paramLessModes; // Properties like z + } channelModes; + + async::result wait_ready() { + if (!ready) { + co_await ready_event.wait(); + } + } + + void mark_ready() { + ready = true; + ready_event.raise(); + } }; class Instance { @@ -72,10 +139,11 @@ class Instance { uvpp::Addr addr; uvpp::tcp *socket = nullptr; Config::Server connected_server; + NetworkInfo netInfo; + Cache cache; bool server_bursting = true, client_bursting = true; bool authed = false; - std::unordered_map users; public: Instance(uvpp::loop_service &s, const Config& config) : s(s), config(config) { diff --git a/uid.hpp b/uid.hpp index 6ddb287..39778e2 100644 --- a/uid.hpp +++ b/uid.hpp @@ -23,6 +23,10 @@ public: *this = UID(std::string_view{initializer, len}); } + constexpr bool operator ==(const UID& other) { + return str() == other.str(); + } + constexpr bool has_value() const { return array[0] != '\0'; } @@ -62,6 +66,10 @@ struct AnyUID { } } + bool operator ==(const AnyUID& other) { + return str() == other.str(); + } + auto operator =(const SUID& val) { type = SERVER; id = val;