diff options
author | Peter Powell <petpow@saberuk.com> | 2019-05-13 17:24:25 +0100 |
---|---|---|
committer | Peter Powell <petpow@saberuk.com> | 2019-10-14 11:03:03 +0100 |
commit | d0f802e30c492cb1b7e55f51063bfd38b29931c6 (patch) | |
tree | a11cbb5c48fae151d285e0536d4f51b2b09659da | |
parent | 51b9b4c9b404bd801be194644133be47bd035b58 (diff) |
Implement serialisation of users.
This allows for various things which will be coming in the future.
e.g. Transferring users to another server on upgrade.
-rw-r--r-- | include/extensible.h | 10 | ||||
-rw-r--r-- | include/inspircd.h | 1 | ||||
-rw-r--r-- | include/serialize.h | 117 | ||||
-rw-r--r-- | include/users.h | 14 | ||||
-rw-r--r-- | src/serializable.cpp | 282 | ||||
-rw-r--r-- | src/users.cpp | 9 |
6 files changed, 432 insertions, 1 deletions
diff --git a/include/extensible.h b/include/extensible.h index c24984f26..f3eeabdcf 100644 --- a/include/extensible.h +++ b/include/extensible.h @@ -133,7 +133,9 @@ class CoreExport ExtensionItem : public ServiceProvider, public usecountbase * a flags variable, and each module defining bits within the flag as 'theirs' as it is less prone to conflict and * supports arbitary data storage). */ -class CoreExport Extensible : public classbase +class CoreExport Extensible + : public classbase + , public Serializable { public: typedef insp::flat_map<reference<ExtensionItem>, void*> ExtensibleStore; @@ -165,6 +167,12 @@ class CoreExport Extensible : public classbase * Free all extension items attached to this Extensible */ void FreeAllExtItems(); + + /** @copydoc Serializable::Deserialize. */ + bool Deserialize(Data& data) CXX11_OVERRIDE; + + /** @copydoc Serializable::Deserialize. */ + bool Serialize(Serializable::Data& data) CXX11_OVERRIDE; }; class CoreExport ExtensionManager diff --git a/include/inspircd.h b/include/inspircd.h index 35183c3bf..775c201f9 100644 --- a/include/inspircd.h +++ b/include/inspircd.h @@ -74,6 +74,7 @@ struct fakederef #include "dynref.h" #include "consolecolors.h" #include "cull_list.h" +#include "serialize.h" #include "extensible.h" #include "fileutils.h" #include "ctables.h" diff --git a/include/serialize.h b/include/serialize.h new file mode 100644 index 000000000..b3783a48f --- /dev/null +++ b/include/serialize.h @@ -0,0 +1,117 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2019 Peter Powell <petpow@saberuk.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +/** Base class for serializable elements. */ +class CoreExport Serializable +{ + protected: + Serializable() { } + + public: + /** Encapsulates a chunk of serialised data. */ + class CoreExport Data + { + public: + /** Maps keys to serialised data. */ + typedef TR1NS::unordered_map<std::string, Data> ChildMap; + + /** Maps keys to simple values. */ + typedef TR1NS::unordered_map<std::string, std::string> EntryMap; + + private: + /** A mapping of keys to serialised data. */ + ChildMap children; + + /** A mapping of keys to values. */ + EntryMap entries; + + public: + /** Retrieves the child elements. */ + const ChildMap& GetChildren() const { return children; } + ChildMap& GetChildren() { return children; } + + /** Retrieves the key/value map. */ + const EntryMap& GetEntries() const { return entries; } + EntryMap& GetEntries() { return entries; } + + /** Loads the serialised data with the specified key. + * @param key The key by which this serialised data is identified. + * @param out The location to store the serialised data for this key. + */ + Data& Load(const std::string& key, Data& out); + + /** Loads the value with the specified key. + * @param key The key by which this data is identified. + * @param out The location to store the value for this keu + */ + Data& Load(const std::string& key, std::string& out); + + /** Loads the value with the specified key. The value will be converted to the specified type. + * @param key The key by which this data is identified. + * @param out The location to store the value for this key. + */ + template <typename T> + Data& Load(const std::string& key, T& out) + { + // Attempt to load as a string. + std::string str; + Load(key, str); + + std::stringstream ss(str); + ss >> out; + return *this; + } + + /** Stores the serialised data against the specified key. + * @param key The key by which this serialised data should be stored against. + * @param out The serialised data to store. + */ + Data& Store(const std::string& key, const Data& value); + + /** Stores the value against the specified key. + * @param key The key by which this value should be stored against. + * @param out The value to store. + */ + Data& Store(const std::string& key, const std::string& value); + + /** Stores the value against the specified key. The value will be converted to a string using ConvToStr. + * @param key The key by which this value should be stored against. + * @param out The value to store. + */ + template <typename T> + Data& Store(const std::string& key, const T& value) + { + return Store(key, ConvToStr(value)); + } + }; + + /** Deserializes the specified Data instance into this object. + * @param data The Data object to deserialize from. + * @return True if the deserialisation succeeded; otherwise, false. + */ + virtual bool Deserialize(Data& data) = 0; + + /** Serializes the this object into the specified Data obect. + * @param data The Data object to serialize to. + * @return True if the serialisation succeeded; otherwise, false. + */ + virtual bool Serialize(Data& data) = 0; +}; diff --git a/include/users.h b/include/users.h index e58e8e316..6cc7f423b 100644 --- a/include/users.h +++ b/include/users.h @@ -676,6 +676,12 @@ class CoreExport User : public Extensible */ virtual ~User(); CullResult cull() CXX11_OVERRIDE; + + /** @copydoc Serializable::Deserialize. */ + bool Deserialize(Data& data) CXX11_OVERRIDE; + + /** @copydoc Serializable::Deserialize. */ + bool Serialize(Serializable::Data& data) CXX11_OVERRIDE; }; class CoreExport UserIOHandler : public StreamSocket @@ -729,6 +735,8 @@ class CoreExport LocalUser : public User, public insp::intrusive_list_node<Local public: LocalUser(int fd, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server); + LocalUser(int fd, const std::string& uuid, Serializable::Data& data); + CullResult cull() CXX11_OVERRIDE; UserIOHandler eh; @@ -872,6 +880,12 @@ class CoreExport LocalUser : public User, public insp::intrusive_list_node<Local * @param msg Message to send. */ void Send(ClientProtocol::EventProvider& protoevprov, ClientProtocol::Message& msg); + + /** @copydoc Serializable::Deserialize. */ + bool Deserialize(Data& data) CXX11_OVERRIDE; + + /** @copydoc Serializable::Deserialize. */ + bool Serialize(Serializable::Data& data) CXX11_OVERRIDE; }; class RemoteUser : public User diff --git a/src/serializable.cpp b/src/serializable.cpp new file mode 100644 index 000000000..1d8a794d3 --- /dev/null +++ b/src/serializable.cpp @@ -0,0 +1,282 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2019 Peter Powell <petpow@saberuk.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +Serializable::Data& Serializable::Data::Load(const std::string& key, std::string& out) +{ + EntryMap::iterator iter = this->entries.find(key); + if (iter == this->entries.end()) + { + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Unable to load missing kv %s!", key.c_str()); + } + else + { + out = iter->second; + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Loaded kv %s: %s", key.c_str(), out.c_str()); + } + return *this; +} + +Serializable::Data& Serializable::Data::Load(const std::string& key, Serializable::Data& out) +{ + ChildMap::iterator iter = this->children.find(key); + if (iter == this->children.end()) + { + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Unable to load missing data %s!", key.c_str()); + } + else + { + out = iter->second; + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Loaded data: %s", key.c_str()); + } + return *this; +} + +Serializable::Data& Serializable::Data::Store(const std::string& key, const std::string& value) +{ + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Stored kv %s: %s", key.c_str(), value.c_str()); + this->entries[key] = value; + return *this; +} + +Serializable::Data& Serializable::Data::Store(const std::string& key, const Serializable::Data& value) +{ + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Stored data: %s", key.c_str()); + this->children[key] = value; + return *this; +} + +bool Extensible::Deserialize(Serializable::Data& data) +{ + // If the extensible has been culled then it shouldn't be deserialized. + if (culled) + return false; + + const Serializable::Data::EntryMap& entries = data.GetEntries(); + for (Serializable::Data::EntryMap::const_iterator iter = entries.begin(); iter != entries.end(); ++iter) + { + const std::string& name = iter->first; + ExtensionItem* item = ServerInstance->Extensions.GetItem(name); + if (item) + { + item->FromInternal(this, iter->second); + continue; + } + + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Tried to deserialize the %s extension item but it doesn't exist", + name.c_str()); + } + return true; +} + +bool Extensible::Serialize(Serializable::Data& data) +{ + // If the extensible has been culled then it shouldn't be serialized. + if (culled) + { + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Tried to serialize an extensible which has been culled"); + return false; + } + + for (Extensible::ExtensibleStore::const_iterator iter = extensions.begin(); iter != extensions.end(); ++iter) + { + ExtensionItem* item = iter->first; + const std::string value = item->ToInternal(this, iter->second); + if (!value.empty()) + data.Store(item->name, value); + } + return true; +} + +bool User::Deserialize(Serializable::Data& data) +{ + // If the user is quitting they shouldn't be deserialized. + if (quitting) + { + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Tried to deserialize %s who is in the process of quitting", + uuid.c_str()); + return false; + } + + // Check we're actually deserialising data for this user. + std::string client_uuid; + data.Load("uuid", client_uuid); + if (!client_uuid.empty() && client_uuid != uuid) + { + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Tried to deserialize %s into %s", + client_uuid.c_str(), uuid.c_str()); + return false; + } + + // Deserialize the extensions first. + Serializable::Data extensions; + data.Load("extensions", extensions); + if (!Extensible::Deserialize(extensions)) + return false; + + long client_port; + std::string client_addr; + std::string user_modes; + std::string user_oper; + std::string user_snomasks; + + // Apply the members which can be applied directly. + data.Load("age", age) + .Load("awaymsg", awaymsg) + .Load("awaytime", awaytime) + .Load("client_sa.addr", client_addr) + .Load("client_sa.port", client_port) + .Load("displayhost", displayhost) + .Load("ident", ident) + .Load("modes", user_modes) + .Load("nick", nick) + .Load("oper", user_oper) + .Load("realhost", realhost) + .Load("realname", realname) + .Load("signon", signon) + .Load("snomasks", user_snomasks); + + // Apply the rest of the members. + modes = std::bitset<ModeParser::MODEID_MAX>(user_modes); + snomasks = std::bitset<64>(user_snomasks); + + ServerConfig::OperIndex::const_iterator iter = ServerInstance->Config->OperTypes.find(user_oper); + if (iter != ServerInstance->Config->OperTypes.end()) + oper = iter->second; + else + oper = new OperInfo(user_oper); + + irc::sockets::sockaddrs sa; + if (irc::sockets::aptosa(client_addr, client_port, sa) || irc::sockets::untosa(client_addr, sa)) + client_sa = sa; + + InvalidateCache(); + return true; +} + +bool User::Serialize(Serializable::Data& data) +{ + // If the user is quitting they shouldn't be serialized. + if (quitting) + { + ServerInstance->Logs->Log("SERIALIZE", LOG_DEBUG, "Tried to serialize %s who is in the process of quitting", + uuid.c_str()); + return false; + } + + // If the user is unregistered they shouldn't be serialised. + if (registered != REG_ALL) + return false; + + // Serialize the extensions first. + Serializable::Data extensions; + if (!Extensible::Serialize(extensions)) + return false; + data.Store("extensions", extensions); + + // The following member variables not checked above are not serialised: + // * cached_fullhost (serialising cache variables is unnecessary) + // * cached_fullrealhost (serialising cache variables is unnecessary) + // * cached_hostip (serialising cache variables is unnecessary) + // * cached_makehost (serialising cache variables is unnecessary) + // * cachedip (serialising cache variables is unnecessary) + // * server (specific to the origin server) + // * usertype (can't be networked reliably) + data.Store("age", age) + .Store("awaymsg", awaymsg) + .Store("awaytime", awaytime) + .Store("client_sa.addr", client_sa.addr()) + .Store("client_sa.port", client_sa.port()) + .Store("displayhost", displayhost) + .Store("ident", ident) + .Store("modes", modes.to_string()) + .Store("nick", nick) + .Store("oper", oper ? oper->name : "") + .Store("realhost", realhost) + .Store("realname", realname) + .Store("signon", signon) + .Store("snomasks", snomasks.to_string()) + .Store("uuid", uuid); + + return true; +} + +bool LocalUser::Deserialize(Serializable::Data& data) +{ + + // Deserialize the base class first. + if (!User::Deserialize(data)) + return false; + + bool user_exempt; + bool user_lastping; + long server_port; + std::string server_addr; + + // Apply the members which can be applied directly. + data.Load("bytes_in", bytes_in) + .Load("bytes_out", bytes_out) + .Load("cmds_in", cmds_in) + .Load("cmds_out", cmds_out) + .Load("CommandFloodPenalty", CommandFloodPenalty) + .Load("exempt", user_exempt) + .Load("idle_lastmsg", idle_lastmsg) + .Load("lastping", user_lastping) + .Load("nextping", nextping) + .Load("password", password) + .Load("server_sa.addr", server_addr) + .Load("server_sa.port", server_port); + + // Apply the rest of the members. + irc::sockets::sockaddrs sa; + if (irc::sockets::aptosa(server_addr, server_port, sa) || irc::sockets::untosa(server_addr, sa)) + server_sa = sa; + + // These are bitfields so we need to ensure they only get the appropriate bits. + exempt = user_exempt ? 1 : 0; + lastping = user_lastping ? 1 : 0; + return true; +} + +bool LocalUser::Serialize(Serializable::Data& data) +{ + // Serialize the base class first. + if (!User::Serialize(data)) + return false; + + // The following member variables not checked above are not serialised: + // * already_sent (can't be networked reliably) + // * eh (shouldn't be networked) + // * MyClass (might not be the same on a different server) + // * serializer (might not be the same on a different connection) + data.Store("bytes_in", bytes_in) + .Store("bytes_out", bytes_out) + .Store("cmds_in", cmds_in) + .Store("cmds_out", cmds_out) + .Store("CommandFloodPenalty", CommandFloodPenalty) + .Store("exempt", exempt) + .Store("idle_lastmsg", idle_lastmsg) + .Store("lastping", lastping) + .Store("nextping", nextping) + .Store("password", password) + .Store("server_sa.addr", server_sa.addr()) + .Store("server_sa.port", server_sa.port()); + return true; +} diff --git a/src/users.cpp b/src/users.cpp index b8a2d31a8..01bad8b61 100644 --- a/src/users.cpp +++ b/src/users.cpp @@ -112,6 +112,15 @@ LocalUser::LocalUser(int myfd, irc::sockets::sockaddrs* client, irc::sockets::so ChangeRealHost(GetIPString(), true); } +LocalUser::LocalUser(int myfd, const std::string& uuid, Serializable::Data& data) + : User(uuid, ServerInstance->FakeClient->server, USERTYPE_LOCAL) + , eh(this) + , already_sent(0) +{ + eh.SetFd(myfd); + Deserialize(data); +} + User::~User() { } |