summaryrefslogtreecommitdiff
path: root/include/clientprotocol.h
diff options
context:
space:
mode:
authorPeter Powell <petpow@saberuk.com>2018-08-13 20:17:46 +0100
committerPeter Powell <petpow@saberuk.com>2018-08-13 21:51:11 +0100
commit58a0a7e01422e62de1565a8eb0a1febdc463d04d (patch)
tree8861789deefe9df3524690de8ccd11e5366f1f2e /include/clientprotocol.h
parente2a820cce21342478653a34cf8ce2b593128d035 (diff)
Implement IRCv3 message tag support.
Co-authored-by: Attila Molnar <attilamolnar@hush.com>
Diffstat (limited to 'include/clientprotocol.h')
-rw-r--r--include/clientprotocol.h726
1 files changed, 726 insertions, 0 deletions
diff --git a/include/clientprotocol.h b/include/clientprotocol.h
new file mode 100644
index 000000000..a3efcf984
--- /dev/null
+++ b/include/clientprotocol.h
@@ -0,0 +1,726 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.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
+
+#include "event.h"
+
+namespace ClientProtocol
+{
+ class EventHook;
+ class MessageSource;
+ struct RFCEvents;
+ struct ParseOutput;
+ class TagSelection;
+}
+
+/** Contains a message parsed from wire format.
+ * Used by Serializer::Parse().
+ */
+struct ClientProtocol::ParseOutput
+{
+ /** Command name, must not be empty.
+ */
+ std::string cmd;
+
+ /** Parameter list, may be empty.
+ */
+ ClientProtocol::ParamList params;
+
+ /** Message tags, may be empty.
+ */
+ ClientProtocol::TagMap tags;
+};
+
+/** A selection of zero or more tags in a TagMap.
+ */
+class ClientProtocol::TagSelection
+{
+ std::bitset<64> selection;
+
+ public:
+ /** Check if a tag is selected.
+ * @param tags TagMap the tag is in. The TagMap must contain the same tags as it had when the tag
+ * was selected with Select(), otherwise the result is not meaningful.
+ * @param it Iterator to the tag to check.
+ * @return True if the tag is selected, false otherwise.
+ */
+ bool IsSelected(const TagMap& tags, TagMap::const_iterator it) const
+ {
+ const size_t index = std::distance(tags.begin(), it);
+ return ((index < selection.size()) && (selection[index]));
+ }
+
+ /** Select a tag.
+ * @param tags TagMap the tag is in. This parameter must be the same every time the method is called.
+ * The TagMap must not be altered otherwise the results of IsSelected() is not meaningful.
+ * @param it Iterator to the tag to mark as selected.
+ */
+ void Select(const TagMap& tags, TagMap::const_iterator it)
+ {
+ const size_t index = std::distance(tags.begin(), it);
+ if (index < selection.size())
+ selection[index] = true;
+ }
+
+ /** Check if a TagSelection is equivalent to this object.
+ * @param other Other TagSelection object to compare this with.
+ * @return True if the objects are equivalent, false if they aren't.
+ */
+ bool operator==(const TagSelection& other) const
+ {
+ return (this->selection == other.selection);
+ }
+};
+
+class ClientProtocol::MessageSource
+{
+ User* sourceuser;
+ const std::string* sourcestr;
+
+ public:
+ /** Constructor, sets the source to be the full host of a user or sets it to be nothing.
+ * The actual source string when serializing will be obtained from User::GetFullHost() if the user is non-NULL.
+ * @param Sourceuser User to set as source of the message. If NULL, the message won't have a source when serialized.
+ * Optional, defaults to NULL.
+ */
+ MessageSource(User* Sourceuser = NULL)
+ {
+ SetSourceUser(Sourceuser);
+ }
+
+ /** Constructor, sets the source to the supplied string and optionally sets the source user.
+ * @param Sourcestr String to use as message source for serialization purposes. Must remain valid
+ * as long as this object is alive.
+ * @param Sourceuser User to set as source. Optional, defaults to NULL. It will not be used for serialization but
+ * if provided it may be used internally, for example to create message tags.
+ * Useful when the source string is synthesized but it is still related to a User.
+ */
+ MessageSource(const std::string& Sourcestr, User* Sourceuser = NULL)
+ {
+ SetSource(Sourcestr, Sourceuser);
+ }
+
+ /** Get the source of this message as a string.
+ * @return Pointer to the message source string or NULL if there is no source.
+ */
+ const std::string* GetSource() const
+ {
+ // Return string if there's one explicitly set
+ if (sourcestr)
+ return sourcestr;
+ if (sourceuser)
+ return &sourceuser->GetFullHost();
+ return NULL;
+ }
+
+ /** Get the source User.
+ * This shouldn't be used for serialization, use GetSource() for that.
+ * @return User pointer if the message has a source user, NULL otherwise.
+ */
+ User* GetSourceUser() const { return sourceuser; }
+
+ /** Set the source of this message to a User.
+ * See the one parameter constructor for a more detailed description.
+ * @param Sourceuser User to set as source.
+ */
+ void SetSourceUser(User* Sourceuser)
+ {
+ sourceuser = Sourceuser;
+ sourcestr = NULL;
+ }
+
+ /** Set the source string and optionally source user.
+ * See the two parameter constructor for a more detailed description.
+ * @param Sourcestr String source, to be used for serialization purposes. Must remain valid as long
+ * as this object is alive.
+ * @param Sourceuser Source user to set, optional.
+ */
+ void SetSource(const std::string& Sourcestr, User* Sourceuser = NULL)
+ {
+ sourcestr = &Sourcestr;
+ sourceuser = Sourceuser;
+ }
+
+ /** Copy the source from a MessageSource object.
+ * @param other MessageSource object to copy from.
+ */
+ void SetSource(const MessageSource& other)
+ {
+ sourcestr = other.sourcestr;
+ sourceuser = other.sourceuser;
+ }
+};
+
+/** Outgoing client protocol message.
+ * Represents a high level client protocol message which is turned into raw or wire format
+ * by a Serializer. Messages can be serialized into different format by different serializers.
+ *
+ * Messages are created on demand and are disposed of after they have been sent.
+ *
+ * All messages have a command name, a list of parameters and a map of tags, the last two can be empty.
+ * They also always have a source, see class MessageSource.
+ */
+class ClientProtocol::Message : public ClientProtocol::MessageSource
+{
+ public:
+ /** Contains information required to identify a specific version of a serialized message.
+ */
+ struct SerializedInfo
+ {
+ const Serializer* serializer;
+ TagSelection tagwl;
+
+ /** Constructor.
+ * @param Ser Serializer used to serialize the message.
+ * @param Tagwl Tag whitelist used to serialize the message.
+ */
+ SerializedInfo(const Serializer* Ser, const TagSelection& Tagwl)
+ : serializer(Ser)
+ , tagwl(Tagwl)
+ {
+ }
+
+ /** Check if a SerializedInfo object is equivalent to this object.
+ * @param other Other SerializedInfo object.
+ * @return True if other is equivalent to this object, false otherwise.
+ */
+ bool operator==(const SerializedInfo& other) const
+ {
+ return ((serializer == other.serializer) && (tagwl == other.tagwl));
+ }
+ };
+
+ class Param
+ {
+ const std::string* ptr;
+ insp::aligned_storage<std::string> str;
+ bool owned;
+
+ void InitFrom(const Param& other)
+ {
+ owned = other.owned;
+ if (owned)
+ new(str) std::string(*other.str);
+ else
+ ptr = other.ptr;
+ }
+
+ public:
+ operator const std::string&() const { return (owned ? *str : *ptr); }
+
+ Param()
+ : ptr(NULL)
+ , owned(false)
+ {
+ }
+
+ Param(const std::string& s)
+ : ptr(&s)
+ , owned(false)
+ {
+ }
+
+ Param(int, const char* s)
+ : owned(true)
+ {
+ new(str) std::string(s);
+ }
+
+ Param(int, const std::string& s)
+ : owned(true)
+ {
+ new(str) std::string(s);
+ }
+
+ Param(const Param& other)
+ {
+ InitFrom(other);
+ }
+
+ ~Param()
+ {
+ using std::string;
+ if (owned)
+ str->~string();
+ }
+
+ Param& operator=(const Param& other)
+ {
+ if (&other == this)
+ return *this;
+
+ using std::string;
+ if (owned)
+ str->~string();
+ InitFrom(other);
+ return *this;
+ }
+
+ bool IsOwned() const { return owned; }
+ };
+
+ typedef std::vector<Param> ParamList;
+
+ private:
+ typedef std::vector<std::pair<SerializedInfo, SerializedMessage> > SerializedList;
+
+ ParamList params;
+ TagMap tags;
+ std::string command;
+ bool msginit_done;
+ mutable SerializedList serlist;
+ bool sideeffect;
+
+ protected:
+ /** Set command string.
+ * @param cmd Command string to set.
+ */
+ void SetCommand(const char* cmd)
+ {
+ command.clear();
+ if (cmd)
+ command = cmd;
+ }
+
+ public:
+ /** Constructor.
+ * @param cmd Command name, e.g. "JOIN", "NICK". May be NULL. If NULL, the command must be set
+ * with SetCommand() before the message is serialized.
+ * @param Sourceuser See the one parameter constructor of MessageSource for description.
+ */
+ Message(const char* cmd, User* Sourceuser = NULL)
+ : ClientProtocol::MessageSource(Sourceuser)
+ , command(cmd ? cmd : std::string())
+ , msginit_done(false)
+ , sideeffect(false)
+ {
+ params.reserve(8);
+ serlist.reserve(8);
+ }
+
+ /** Constructor.
+ * @param cmd Command name, e.g. "JOIN", "NICK". May be NULL. If NULL, the command must be set
+ * with SetCommand() before the message is serialized.
+ * @param Sourcestr See the two parameter constructor of MessageSource for description.
+ * Must remain valid as long as this object is alive.
+ * @param Sourceuser See the two parameter constructor of MessageSource for description.
+ */
+ Message(const char* cmd, const std::string& Sourcestr, User* Sourceuser = NULL)
+ : ClientProtocol::MessageSource(Sourcestr, Sourceuser)
+ , command(cmd ? cmd : std::string())
+ , msginit_done(false)
+ , sideeffect(false)
+ {
+ params.reserve(8);
+ serlist.reserve(8);
+ }
+
+ /** Get the parameters of this message.
+ * @return List of parameters.
+ */
+ const ParamList& GetParams() const { return params; }
+
+ /** Get a map of tags attached to this message.
+ * The map contains the tag providers that attached the tag to the message.
+ * @return Map of tags.
+ */
+ const TagMap& GetTags() const { return tags; }
+
+ /** Get the command string.
+ * @return Command string, e.g. "NICK", "001".
+ */
+ const char* GetCommand() const { return command.c_str(); }
+
+ /** Add a parameter to the parameter list.
+ * @param str String to add, will be copied.
+ */
+ void PushParam(const char* str) { params.push_back(Param(0, str)); }
+
+ /** Add a parameter to the parameter list.
+ * @param str String to add, will be copied.
+ */
+ void PushParam(const std::string& str) { params.push_back(Param(0, str)); }
+
+ /** Add a parameter to the parameter list.
+ * @param str String to add.
+ * The string will NOT be copied, it must remain alive until ClearParams() is called or until the object is destroyed.
+ */
+ void PushParamRef(const std::string& str) { params.push_back(str); }
+
+ /** Add a placeholder parameter to the parameter list.
+ * Placeholder parameters must be filled in later with actual parameters using ReplaceParam() or ReplaceParamRef().
+ */
+ void PushParamPlaceholder() { params.push_back(Param()); }
+
+ /** Replace a parameter or a placeholder that is already in the parameter list.
+ * @param index Index of the parameter to replace. Must be less than GetParams().size().
+ * @param str String to replace the parameter or placeholder with, will be copied.
+ */
+ void ReplaceParam(unsigned int index, const char* str) { params[index] = Param(0, str); }
+
+ /** Replace a parameter or a placeholder that is already in the parameter list.
+ * @param index Index of the parameter to replace. Must be less than GetParams().size().
+ * @param str String to replace the parameter or placeholder with, will be copied.
+ */
+ void ReplaceParam(unsigned int index, const std::string& str) { params[index] = Param(0, str); }
+
+ /** Replace a parameter or a placeholder that is already in the parameter list.
+ * @param index Index of the parameter to replace. Must be less than GetParams().size().
+ * @param str String to replace the parameter or placeholder with.
+ * The string will NOT be copied, it must remain alive until ClearParams() is called or until the object is destroyed.
+ */
+ void ReplaceParamRef(unsigned int index, const std::string& str) { params[index] = Param(str); }
+
+ /** Add a tag.
+ * @param tagname Raw name of the tag to use in the protocol.
+ * @param tagprov Provider of the tag.
+ * @param val Tag value. If empty no value will be sent with the tag.
+ * @param tagdata Tag provider specific data, will be passed to MessageTagProvider::ShouldSendTag(). Optional, defaults to NULL.
+ */
+ void AddTag(const std::string& tagname, MessageTagProvider* tagprov, const std::string& val, void* tagdata = NULL)
+ {
+ tags.insert(std::make_pair(tagname, MessageTagData(tagprov, val, tagdata)));
+ }
+
+ /** Add all tags in a TagMap to the tags in this message. Existing tags will not be overwritten.
+ * @param newtags New tags to add.
+ */
+ void AddTags(const ClientProtocol::TagMap& newtags)
+ {
+ tags.insert(newtags.begin(), newtags.end());
+ }
+
+ /** Get the message in a serialized form.
+ * @param serializeinfo Information about which exact serialized form of the message is the caller asking for
+ * (which serializer to use and which tags to include).
+ * @return Serialized message according to serializeinfo. The returned reference remains valid until the
+ * next call to this method.
+ */
+ const SerializedMessage& GetSerialized(const SerializedInfo& serializeinfo) const;
+
+ /** Clear the parameter list and tags.
+ */
+ void ClearParams()
+ {
+ msginit_done = false;
+ params.clear();
+ tags.clear();
+ InvalidateCache();
+ }
+
+ /** Remove all serialized messages.
+ * If a parameter is changed after the message has been sent at least once, this method must be called before
+ * serializing the message again to ensure the cache won't contain stale data.
+ */
+ void InvalidateCache()
+ {
+ serlist.clear();
+ }
+
+ void CopyAll()
+ {
+ size_t j = 0;
+ for (ParamList::iterator i = params.begin(); i != params.end(); ++i, j++)
+ {
+ Param& curr = *i;
+ if (!curr.IsOwned())
+ ReplaceParam(j, curr);
+ }
+ }
+
+ void SetSideEffect(bool Sideeffect) { sideeffect = Sideeffect; }
+ bool IsSideEffect() const { return sideeffect; }
+
+ friend class Serializer;
+};
+
+/** Client protocol event class.
+ * All messages sent to a user must be part of an event. A single event may result in more than one protocol message
+ * being sent, for example a join event may result in a JOIN and a MODE protocol message sent to members of the channel
+ * if the joining user has some prefix modes set.
+ *
+ * Event hooks attached to a specific event can alter the messages sent for that event.
+ */
+class ClientProtocol::Event
+{
+ EventProvider* event;
+ Message* initialmsg;
+ const MessageList* initialmsglist;
+ bool eventinit_done;
+
+ public:
+ /** Constructor.
+ * @param protoevent Protocol event provider the event is an instance of.
+ */
+ Event(EventProvider& protoeventprov)
+ : event(&protoeventprov)
+ , initialmsg(NULL)
+ , initialmsglist(NULL)
+ , eventinit_done(false)
+ {
+ }
+
+ /** Constructor.
+ * @param protoevent Protocol event provider the event is an instance of.
+ * @param msg Message to include in this event by default.
+ */
+ Event(EventProvider& protoeventprov, ClientProtocol::Message& msg)
+ : event(&protoeventprov)
+ , initialmsg(&msg)
+ , initialmsglist(NULL)
+ , eventinit_done(false)
+ {
+ }
+
+ /** Set a single message as the initial message in the event.
+ * Modules may alter this later.
+ */
+ void SetMessage(Message* msg)
+ {
+ initialmsg = msg;
+ initialmsglist = NULL;
+ }
+
+ /** Set a list of messages as the initial messages in the event.
+ * Modules may alter this later.
+ */
+ void SetMessageList(const MessageList& msglist)
+ {
+ initialmsg = NULL;
+ initialmsglist = &msglist;
+ }
+
+ /** Get a list of messages to send to a user.
+ * The exact messages sent to a user are determined by the initial message(s) set and hooks.
+ * @param user User to get the messages for.
+ * @param messagelist List to fill in with messages to send to the user for the event
+ */
+ void GetMessagesForUser(LocalUser* user, MessageList& messagelist);
+};
+
+/** Base class for message tag providers.
+ * All message tags belong to a message tag provider. Message tag providers can populate messages
+ * with tags before the message is sent and they have the job of determining whether a user should
+ * get a message tag or be allowed to send one.
+ */
+class ClientProtocol::MessageTagProvider : public Events::ModuleEventListener
+{
+ public:
+ /** Constructor.
+ * @param mod Module owning the provider.
+ */
+ MessageTagProvider(Module* mod)
+ : Events::ModuleEventListener(mod, "event/messagetag")
+ {
+ }
+
+ /** Called when a message is ready to be sent to give the tag provider a chance to add tags to the message.
+ * To add tags call Message::AddTag(). If the provided tag or tags have been added already elsewhere or if the
+ * provider doesn't want its tag(s) to be on the message, the implementation doesn't have to do anything special.
+ * The default implementation does nothing.
+ * @param msg Message to be populated with tags.
+ */
+ virtual void OnClientProtocolPopulateTags(ClientProtocol::Message& msg)
+ {
+ }
+
+ /** Called for each tag that the server receives from a client in a message.
+ * @param user User that sent the tag.
+ * @param tagname Name of the tag.
+ * @param value Value of the tag, empty string if the tag has no value. May be modified.
+ * @return MOD_RES_ALLOW to accept the tag with the value in 'value', MOD_RES_DENY to reject the tag and act as if it wasn't sent,
+ * MOD_RES_PASSTHRU to make no decision. If no hooks accept a tag, the tag is rejected.
+ * The default implementation returns MOD_RES_PASSTHRU.
+ */
+ virtual ModResult OnClientProtocolProcessTag(LocalUser* user, const std::string& tagname, std::string& tagvalue)
+ {
+ return MOD_RES_PASSTHRU;
+ }
+
+ /** Called whenever a user is about to receive a message that has a tag attached which is provided by this provider
+ * to determine whether or not the user should get the tag.
+ * @param user User in question.
+ * @param tagdata Tag in question.
+ * @return True if the tag should be sent to the user, false otherwise.
+ */
+ virtual bool ShouldSendTag(LocalUser* user, const MessageTagData& tagdata) = 0;
+};
+
+/** Base class for client protocol event hooks.
+ * A protocol event hook is attached to a single event type. It has the ability to alter or block messages
+ * sent to users which belong to the event the hook is attached to.
+ */
+class ClientProtocol::EventHook : public Events::ModuleEventListener
+{
+ public:
+ static std::string GetEventName(const std::string& name)
+ {
+ return "event/protoevent_" + name;
+ }
+
+ /** Constructor.
+ * @param mod Owner of the hook.
+ * @param name Name of the event to hook.
+ * @param priority Priority of the hook. Determines the order in which hooks for the same event get called.
+ * Optional.
+ */
+ EventHook(Module* mod, const std::string& name, unsigned int priority = Events::ModuleEventListener::DefaultPriority)
+ : Events::ModuleEventListener(mod, GetEventName(name), priority)
+ {
+ }
+
+ /** Called exactly once before an event is sent to any user.
+ * The default implementation doesn't do anything.
+ * @param ev Event being sent to one or more users.
+ */
+ virtual void OnEventInit(const ClientProtocol::Event& ev)
+ {
+ }
+
+ /** Called for each user that may receive the event.
+ * The hook may alter the messages sent to the user and decide whether further hooks should be executed.
+ * @param user User the message list is being prepared to be sent to.
+ * @param ev Event associated with the messages.
+ * @param messagelist List of messages to send to the user. The hook can alter this in any way it sees fit, for example it can replace messages,
+ * add new messages, etc. The list must not be empty when the method returns.
+ * @return MOD_RES_PASSTHRU to run hooks after the called hook or if no hooks are after the called hook, send the messages in messagelist to the user.
+ * MOD_RES_DENY to not send any messages to the user and to not run other hooks,
+ * MOD_RES_ALLOW to send the messages in messagelist to the user and to not run other hooks.
+ */
+ virtual ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) = 0;
+};
+
+/** Event provider for client protocol events.
+ * Protocol event hooks can be attached to the instances of these providers. The core has event
+ * providers for most common IRC events defined in RFC1459.
+ */
+class ClientProtocol::EventProvider : public Events::ModuleEventProvider
+{
+ public:
+ /** Constructor.
+ * @param Mod Module that owns the event provider.
+ * @param eventname Name of the event this provider is for, e.g. "JOIN", "PART", "NUMERIC".
+ * Should match command name if applicable.
+ */
+ EventProvider(Module* Mod, const std::string& eventname)
+ : Events::ModuleEventProvider(Mod, ClientProtocol::EventHook::GetEventName(eventname))
+ {
+ }
+};
+
+/** Commonly used client protocol events.
+ * Available via InspIRCd::GetRFCEvents().
+ */
+struct ClientProtocol::RFCEvents
+{
+ EventProvider numeric;
+ EventProvider join;
+ EventProvider part;
+ EventProvider kick;
+ EventProvider quit;
+ EventProvider nick;
+ EventProvider mode;
+ EventProvider topic;
+ EventProvider privmsg;
+ EventProvider invite;
+ EventProvider ping;
+ EventProvider pong;
+ EventProvider error;
+
+ RFCEvents()
+ : numeric(NULL, "NUMERIC")
+ , join(NULL, "JOIN")
+ , part(NULL, "PART")
+ , kick(NULL, "KICK")
+ , quit(NULL, "QUIT")
+ , nick(NULL, "NICK")
+ , mode(NULL, "MODE")
+ , topic(NULL, "TOPIC")
+ , privmsg(NULL, "PRIVMSG")
+ , invite(NULL, "INVITE")
+ , ping(NULL, "PING")
+ , pong(NULL, "PONG")
+ , error(NULL, "ERROR")
+ {
+ }
+};
+
+/** Base class for client protocol serializers.
+ * A serializer has to implement serialization and parsing of protocol messages to/from wire format.
+ */
+class CoreExport ClientProtocol::Serializer : public DataProvider
+{
+ Events::ModuleEventProvider evprov;
+
+ /** Make a white list containing which tags a user should get.
+ * @param user User in question.
+ * @param tagmap Tag map that contains all possible tags.
+ * @return Whitelist of tags to send to the user.
+ */
+ TagSelection MakeTagWhitelist(LocalUser* user, const TagMap& tagmap) const;
+
+ public:
+ /** Constructor.
+ * @param mod Module owning the serializer.
+ * @param Name Name of the serializer, e.g. "rfc".
+ */
+ Serializer(Module* mod, const char* Name);
+
+ /** Handle a tag in a message being parsed. Call this method for each parsed tag.
+ * @param user User sending the tag.
+ * @param tagname Name of the tag.
+ * @param tagvalue Tag value, may be empty.
+ * @param tags TagMap to place the tag into, if it gets accepted.
+ * @return True if no error occured, false if the tag name is invalid or if this tag already exists.
+ */
+ bool HandleTag(LocalUser* user, const std::string& tagname, std::string& tagvalue, TagMap& tags) const;
+
+ /** Serialize a message for a user.
+ * @param user User to serialize the message for.
+ * @param msg Message to serialize.
+ * @return Raw serialized message, only containing the appropriate tags for the user.
+ * The reference is guaranteed to be valid as long as the Message object is alive and until the same
+ * Message is serialized for another user.
+ */
+ const SerializedMessage& SerializeForUser(LocalUser* user, Message& msg);
+
+ /** Serialize a high level protocol message into wire format.
+ * @param msg High level message to serialize. Contains all necessary information about the message, including all possible tags.
+ * @param tagwl Message tags to include in the serialized message. Tags attached to the message but not included in the whitelist must not
+ * appear in the output. This is because each user may get a different set of tags for the same message.
+ * @return Protocol message in wire format. Must contain message delimiter as well, if any (e.g. CRLF for RFC1459).
+ */
+ virtual std::string Serialize(const Message& msg, const TagSelection& tagwl) const = 0;
+
+ /** Parse a protocol message from wire format.
+ * @param user Source of the message.
+ * @param line Raw protocol message.
+ * @param parseoutput Output of the parser.
+ * @return True if the message was parsed successfully into parseoutput and should be processed, false to drop the message.
+ */
+ virtual bool Parse(LocalUser* user, const std::string& line, ParseOutput& parseoutput) = 0;
+};
+
+inline ClientProtocol::MessageTagData::MessageTagData(MessageTagProvider* prov, const std::string& val, void* data)
+ : tagprov(prov)
+ , value(val)
+ , provdata(data)
+{
+}