From e142d11c3fa6ae2bdfc95ee9cea54bb0000917c9 Mon Sep 17 00:00:00 2001
From: Tarun Gupta <tarun1995gupta@gmail.com>
Date: Thu, 15 Jun 2017 16:34:26 +0530
Subject: Adds MIX Join Element, its Parser and Serializer.

Add MIXDestroySerializer to FullPayloadSerializerCollection.

License:
This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.

Test-Information:
Added tests for MIX Join Parser and Serializer based on examples in XEP 0369, which passes.
Tested on Ubuntu 16.04 LTS.

Change-Id: Ibf7446dd65714dccdd39c485549fd9fb9faba40c

diff --git a/Swiften/Elements/MIXJoin.h b/Swiften/Elements/MIXJoin.h
new file mode 100644
index 0000000..2d82adc
--- /dev/null
+++ b/Swiften/Elements/MIXJoin.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Elements/Payload.h>
+#include <Swiften/Elements/MIXSubscribe.h>
+#include <Swiften/Elements/Form.h>
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+    class SWIFTEN_API MIXJoin : public Payload {
+
+        public:
+            using ref = std::shared_ptr<MIXJoin>;
+
+        public:
+
+            MIXJoin() {}
+
+            const boost::optional<JID>& getChannel() const {
+                return channel_;
+            }
+
+            void setChannel(const JID& channel) {
+                channel_ = channel;
+            }
+
+            const boost::optional<JID>& getJID() const {
+                return jid_;
+            }
+
+            void setJID(const JID& jid) {
+                jid_ = jid;
+            }
+
+            const std::vector<MIXSubscribe::ref>& getSubscriptions() const {
+                return subscribeItems_;
+            }
+
+            void setSubscriptions(const std::vector<MIXSubscribe::ref>& value) {
+                subscribeItems_ = value ;
+            }
+
+            void addSubscription(MIXSubscribe::ref value) {
+                subscribeItems_.push_back(value);
+            }
+
+            void setForm(std::shared_ptr<Form> form) {
+                form_ = form;
+            }
+
+            const std::shared_ptr<Form>& getForm() const {
+                return form_;
+            }
+
+        private:
+            boost::optional<JID> jid_;
+            boost::optional<JID> channel_;
+            std::vector<MIXSubscribe::ref> subscribeItems_;
+            std::shared_ptr<Form> form_;
+            // FIXME: MIXInvitation to be implemented. boost::optional<MIXInvitation> invitation_;
+    };
+}
diff --git a/Swiften/Elements/MIXSubscribe.h b/Swiften/Elements/MIXSubscribe.h
new file mode 100644
index 0000000..eaf1aa9
--- /dev/null
+++ b/Swiften/Elements/MIXSubscribe.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Elements/Payload.h>
+
+namespace Swift {
+    class SWIFTEN_API MIXSubscribe : public Payload {
+
+        public:
+            using ref = std::shared_ptr<MIXSubscribe>;
+
+        public:
+
+            MIXSubscribe() {}
+
+            const std::string& getNode() const {
+                return node_;
+            }
+
+            void setNode(const std::string& node) {
+                node_ = node;
+            }
+
+        private:
+            std::string node_;
+    };
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index dbfcb6b..76827bb 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -53,6 +53,7 @@
 #include <Swiften/Parser/PayloadParsers/MAMResultParser.h>
 #include <Swiften/Parser/PayloadParsers/MIXParticipantParserFactory.h>
 #include <Swiften/Parser/PayloadParsers/MIXDestroyParser.h>
+#include <Swiften/Parser/PayloadParsers/MIXJoinParserFactory.h>
 #include <Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h>
 #include <Swiften/Parser/PayloadParsers/MUCDestroyPayloadParser.h>
 #include <Swiften/Parser/PayloadParsers/MUCInvitationPayloadParser.h>
@@ -171,6 +172,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
     factories_.push_back(std::make_shared<GenericPayloadParserFactory2<CarbonsReceivedParser> >("received", "urn:xmpp:carbons:2", this));
     factories_.push_back(std::make_shared<GenericPayloadParserFactory2<CarbonsSentParser> >("sent", "urn:xmpp:carbons:2", this));
     factories_.push_back(std::make_shared<GenericPayloadParserFactory<CarbonsPrivateParser> >("private", "urn:xmpp:carbons:2"));
+    factories_.push_back(std::make_shared<MIXJoinParserFactory>());
 
     for (auto& factory : factories_) {
         addFactory(factory.get());
diff --git a/Swiften/Parser/PayloadParsers/MIXJoinParser.cpp b/Swiften/Parser/PayloadParsers/MIXJoinParser.cpp
new file mode 100644
index 0000000..2509667
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MIXJoinParser.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/MIXJoinParser.h>
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Parser/PayloadParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/MIXSubscribeParser.h>
+#include <Swiften/Parser/PayloadParsers/FormParser.h>
+
+using namespace Swift;
+
+MIXJoinParser::MIXJoinParser() : level_(0) {
+}
+
+MIXJoinParser::~MIXJoinParser() {
+}
+
+void MIXJoinParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) {
+    if (level_ == 0) {
+        if (boost::optional<std::string> attributeValue = attributes.getAttributeValue("channel")) {
+            if (boost::optional<JID> jid = JID::parse(*attributeValue)) {
+                getPayloadInternal()->setChannel(*jid);
+            }
+        }
+        if (boost::optional<std::string> attributeValue = attributes.getAttributeValue("jid")) {
+            if (boost::optional<JID> jid = JID::parse(*attributeValue)) {
+                getPayloadInternal()->setJID(*jid);
+            }
+        }
+    }
+
+    if (level_ == 1) {
+        if (element == "subscribe" && ns == "urn:xmpp:mix:1") {
+            currentPayloadParser_ = std::make_shared<MIXSubscribeParser>();
+        }
+        if (element == "x" && ns == "jabber:x:data") {
+            currentPayloadParser_ = std::make_shared<FormParser>();
+        }
+    }
+
+    if (level_ >= 1 && currentPayloadParser_) {
+        currentPayloadParser_->handleStartElement(element, ns, attributes);
+    }
+    ++level_;
+}
+
+void MIXJoinParser::handleEndElement(const std::string& element, const std::string& ns) {
+    --level_;
+    if (currentPayloadParser_) {
+        if (level_ >= 1) {
+            currentPayloadParser_->handleEndElement(element, ns);
+        }
+
+        if (level_ == 1) {
+            if (element == "subscribe" && ns == "urn:xmpp:mix:1") {
+                getPayloadInternal()->addSubscription(std::dynamic_pointer_cast<MIXSubscribe>(currentPayloadParser_->getPayload()));
+            }
+            if (element == "x" && ns == "jabber:x:data") {
+                getPayloadInternal()->setForm(std::dynamic_pointer_cast<Form>(currentPayloadParser_->getPayload()));
+            }
+            currentPayloadParser_.reset();
+        }
+    }
+}
+
+void MIXJoinParser::handleCharacterData(const std::string& data) {
+    if (level_ > 1 && currentPayloadParser_) {
+        currentPayloadParser_->handleCharacterData(data);
+    }
+}
diff --git a/Swiften/Parser/PayloadParsers/MIXJoinParser.h b/Swiften/Parser/PayloadParsers/MIXJoinParser.h
new file mode 100644
index 0000000..c0ded7b
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MIXJoinParser.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Base/Override.h>
+#include <Swiften/Elements/MIXJoin.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+
+namespace Swift {
+    class PayloadParser;
+
+    class SWIFTEN_API MIXJoinParser : public GenericPayloadParser<MIXJoin> {
+        public:
+            MIXJoinParser();
+            virtual ~MIXJoinParser();
+
+            virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) SWIFTEN_OVERRIDE;
+            virtual void handleEndElement(const std::string& element, const std::string&) SWIFTEN_OVERRIDE;
+            virtual void handleCharacterData(const std::string& data) SWIFTEN_OVERRIDE;
+
+        private:
+            int level_;
+            std::shared_ptr<PayloadParser> currentPayloadParser_;
+    };
+}
diff --git a/Swiften/Parser/PayloadParsers/MIXJoinParserFactory.h b/Swiften/Parser/PayloadParsers/MIXJoinParserFactory.h
new file mode 100644
index 0000000..6103c70
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MIXJoinParserFactory.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Parser/GenericPayloadParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/MIXJoinParser.h>
+
+namespace Swift {
+    class SWIFTEN_API MIXJoinParserFactory : public PayloadParserFactory {
+        public:
+            MIXJoinParserFactory() {
+            }
+
+            virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const {
+                return element == "join" && ns == "urn:xmpp:mix:1";
+            }
+
+            virtual PayloadParser* createPayloadParser() {
+                return new MIXJoinParser();
+            }
+    };
+}
diff --git a/Swiften/Parser/PayloadParsers/MIXSubscribeParser.cpp b/Swiften/Parser/PayloadParsers/MIXSubscribeParser.cpp
new file mode 100644
index 0000000..1500716
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MIXSubscribeParser.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/MIXSubscribeParser.h>
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Parser/PayloadParserFactory.h>
+#include <Swiften/Parser/PayloadParserFactoryCollection.h>
+
+using namespace Swift;
+
+MIXSubscribeParser::MIXSubscribeParser() : level_(0) {
+}
+
+MIXSubscribeParser::~MIXSubscribeParser() {
+}
+
+void MIXSubscribeParser::handleStartElement(const std::string&, const std::string&, const AttributeMap& attributes) {
+    if (level_ == 0) {
+        if (boost::optional<std::string> attributeValue = attributes.getAttributeValue("node")) {
+            getPayloadInternal()->setNode(*attributeValue);
+        }
+    }
+    ++level_;
+}
+
+void MIXSubscribeParser::handleEndElement(const std::string& , const std::string& ) {
+    --level_;
+}
+
+void MIXSubscribeParser::handleCharacterData(const std::string& ) {
+}
diff --git a/Swiften/Parser/PayloadParsers/MIXSubscribeParser.h b/Swiften/Parser/PayloadParsers/MIXSubscribeParser.h
new file mode 100644
index 0000000..eecc0d2
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MIXSubscribeParser.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Base/Override.h>
+#include <Swiften/Elements/MIXSubscribe.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+
+namespace Swift {
+    class PayloadParserFactoryCollection;
+    class PayloadParser;
+
+    class SWIFTEN_API MIXSubscribeParser : public GenericPayloadParser<MIXSubscribe> {
+        public:
+            MIXSubscribeParser();
+            virtual ~MIXSubscribeParser();
+
+            virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) SWIFTEN_OVERRIDE;
+            virtual void handleEndElement(const std::string& element, const std::string&) SWIFTEN_OVERRIDE;
+            virtual void handleCharacterData(const std::string& data) SWIFTEN_OVERRIDE;
+
+        private:
+            int level_;
+    };
+}
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/MIXJoinParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/MIXJoinParserTest.cpp
new file mode 100644
index 0000000..7a422f4
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/UnitTest/MIXJoinParserTest.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <gtest/gtest.h>
+
+#include <Swiften/Elements/MIXJoin.h>
+#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h>
+
+using namespace Swift;
+
+TEST(MIXJoinParserTest, XEP0369_Example22) {
+    PayloadsParserTester parser;
+    ASSERT_TRUE(parser.parse(
+                "<join xmlns=\"urn:xmpp:mix:1\" channel=\"coven@mix.shakespeare.example\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:participants\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:config\"/>"
+                "</join>"
+                ));
+
+    MIXJoin::ref payload = parser.getPayload<MIXJoin>();
+    ASSERT_TRUE(payload);
+
+    ASSERT_TRUE(payload->getChannel());
+    ASSERT_EQ(JID("coven@mix.shakespeare.example"), *payload->getChannel());
+    ASSERT_FALSE(payload->getJID());
+    ASSERT_FALSE(payload->getForm());
+
+    const std::vector<MIXSubscribe::ref> items = payload->getSubscriptions();
+    ASSERT_EQ(static_cast<size_t>(4), items.size());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:messages"), items[0]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:presence"), items[1]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:participants"), items[2]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:config"), items[3]->getNode());
+}
+
+TEST(MIXJoinParserTest, XEP0369_Example23) {
+    PayloadsParserTester parser;
+    ASSERT_TRUE(parser.parse(
+                "<join xmlns=\"urn:xmpp:mix:1\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:participants\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:config\"/>"
+                "</join>"
+                ));
+
+    MIXJoin::ref payload = parser.getPayload<MIXJoin>();
+    ASSERT_TRUE(payload);
+
+    ASSERT_FALSE(payload->getChannel());
+    ASSERT_FALSE(payload->getJID());
+    ASSERT_FALSE(payload->getForm());
+
+    const std::vector<MIXSubscribe::ref> items = payload->getSubscriptions();
+    ASSERT_EQ(static_cast<size_t>(4), items.size());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:messages"), items[0]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:presence"), items[1]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:participants"), items[2]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:config"), items[3]->getNode());
+}
+
+TEST(MIXJoinParserTest, XEP0369_Example24) {
+    PayloadsParserTester parser;
+    ASSERT_TRUE(parser.parse(
+                "<join xmlns=\"urn:xmpp:mix:1\" jid=\"123456#coven@mix.shakespeare.example\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:participants\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:config\"/>"
+                "</join>"
+                ));
+
+    MIXJoin::ref payload = parser.getPayload<MIXJoin>();
+    ASSERT_TRUE(payload);
+
+    ASSERT_FALSE(payload->getChannel());
+    ASSERT_TRUE(payload->getJID());
+    ASSERT_EQ(JID("123456#coven@mix.shakespeare.example"), *payload->getJID());
+    ASSERT_FALSE(payload->getForm());
+
+    const std::vector<MIXSubscribe::ref> items = payload->getSubscriptions();
+    ASSERT_EQ(static_cast<size_t>(4), items.size());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:messages"), items[0]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:presence"), items[1]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:participants"), items[2]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:config"), items[3]->getNode());
+}
+
+TEST(MIXJoinParserTest, XEP0369_Example29) {
+    PayloadsParserTester parser;
+    ASSERT_TRUE(parser.parse(
+                "<join xmlns=\"urn:xmpp:mix:1\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+                    "<x xmlns=\"jabber:x:data\" type=\"submit\">"
+                        "<field var=\"FORM_TYPE\" type=\"hidden\">"
+                             "<value>urn:xmpp:mix:1</value>"
+                        "</field>"
+                        "<field var=\"JID Visibility\">"
+                            "<value>never</value>"
+                        "</field>"
+                    "</x>"
+                "</join>"));
+
+    MIXJoin::ref payload = parser.getPayload<MIXJoin>();
+    ASSERT_TRUE(payload);
+
+    ASSERT_FALSE(payload->getChannel());
+    ASSERT_FALSE(payload->getJID());
+    const std::vector<MIXSubscribe::ref> items = payload->getSubscriptions();
+    ASSERT_EQ(static_cast<size_t>(2), items.size());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:messages"), items[0]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:presence"), items[1]->getNode());
+
+    ASSERT_TRUE(payload->getForm());
+    ASSERT_EQ(Form::Type::SubmitType, payload->getForm()->getType());
+    std::shared_ptr<FormField> fieldType = payload->getForm()->getField("FORM_TYPE");
+    ASSERT_TRUE(fieldType);
+
+    std::shared_ptr<FormField> fieldJIDVisibility = payload->getForm()->getField("JID Visibility");
+    ASSERT_TRUE(fieldJIDVisibility);
+    ASSERT_EQ(std::string("never"), fieldJIDVisibility->getTextSingleValue());
+}
+
+TEST(MIXJoinParserTest, XEP0369_Example30) {
+    PayloadsParserTester parser;
+    ASSERT_TRUE(parser.parse(
+        "<join xmlns=\"urn:xmpp:mix:1\" jid=\"hag66@shakespeare.example\">"
+            "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+            "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+            "<x xmlns=\"jabber:x:data\" type=\"result\">"
+                "<field var=\"FORM_TYPE\" type=\"hidden\">"
+                    "<value>urn:xmpp:mix:1</value>"
+                "</field>"
+                "<field var=\"JID Visibility\">"
+                    "<value>never</value>"
+                "</field>"
+                "<field var=\"Private Messages\">"
+                    "<value>allow</value>"
+                "</field>"
+                "<field var=\"vCard\">"
+                    "<value>block</value>"
+                "</field>"
+            "</x>"
+        "</join>"));
+
+    MIXJoin::ref payload = parser.getPayload<MIXJoin>();
+    ASSERT_TRUE(payload);
+
+    ASSERT_FALSE(payload->getChannel());
+    ASSERT_TRUE(payload->getJID());
+    ASSERT_EQ(JID("hag66@shakespeare.example"), *payload->getJID());
+
+    const std::vector<MIXSubscribe::ref> items = payload->getSubscriptions();
+    ASSERT_EQ(static_cast<size_t>(2), items.size());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:messages"), items[0]->getNode());
+    ASSERT_EQ(std::string("urn:xmpp:mix:nodes:presence"), items[1]->getNode());
+
+    ASSERT_TRUE(payload->getForm());
+    ASSERT_EQ(Form::Type::ResultType, payload->getForm()->getType());
+    std::shared_ptr<FormField> fieldType = payload->getForm()->getField("FORM_TYPE");
+    ASSERT_TRUE(fieldType);
+
+    std::shared_ptr<FormField> fieldJIDVisibility = payload->getForm()->getField("JID Visibility");
+    ASSERT_TRUE(fieldJIDVisibility);
+    ASSERT_EQ(std::string("never"), fieldJIDVisibility->getTextSingleValue());
+
+    std::shared_ptr<FormField> fieldprivateMessages = payload->getForm()->getField("Private Messages");
+    ASSERT_TRUE(fieldprivateMessages);
+    ASSERT_EQ(std::string("allow"), fieldprivateMessages->getTextSingleValue());
+
+    std::shared_ptr<FormField> vCard = payload->getForm()->getField("vCard");
+    ASSERT_TRUE(vCard);
+    ASSERT_EQ(std::string("block"), vCard->getTextSingleValue());
+}
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index a01c1e1..55d7eaf 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -69,6 +69,8 @@ sources = [
         "PayloadParsers/DelayParser.cpp",
         "PayloadParsers/MIXParticipantParser.cpp",
         "PayloadParsers/MIXDestroyParser.cpp",
+        "PayloadParsers/MIXJoinParser.cpp",
+        "PayloadParsers/MIXSubscribeParser.cpp",
         "PayloadParsers/MUCUserPayloadParser.cpp",
         "PayloadParsers/MUCAdminPayloadParser.cpp",
         "PayloadParsers/MUCOwnerPayloadParser.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 8b9ef12..8d00db1 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -209,6 +209,8 @@ if env["SCONS_STAGE"] == "build" :
             "Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp",
             "Serializer/PayloadSerializers/MIXParticipantSerializer.cpp",
             "Serializer/PayloadSerializers/MIXDestroySerializer.cpp",
+            "Serializer/PayloadSerializers/MIXJoinSerializer.cpp",
+            "Serializer/PayloadSerializers/MIXSubscribeSerializer.cpp",
             "Serializer/PayloadSerializers/MUCPayloadSerializer.cpp",
             "Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp",
             "Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp",
@@ -452,6 +454,7 @@ if env["SCONS_STAGE"] == "build" :
             File("Parser/PayloadParsers/UnitTest/IdleParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/ResultSetParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/ForwardedParserTest.cpp"),
+            File("Parser/PayloadParsers/UnitTest/MIXJoinParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/MAMFinParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/MAMResultParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/MAMQueryParserTest.cpp"),
@@ -520,6 +523,7 @@ if env["SCONS_STAGE"] == "build" :
             File("Serializer/PayloadSerializers/UnitTest/IdleSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/ResultSetSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/ForwardedSerializerTest.cpp"),
+            File("Serializer/PayloadSerializers/UnitTest/MIXJoinSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/MAMFinSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/MAMResultSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/MAMQuerySerializerTest.cpp"),
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 080e3df..72b6128 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -45,6 +45,8 @@
 #include <Swiften/Serializer/PayloadSerializers/MAMFinSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MAMQuerySerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MAMResultSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/MIXDestroySerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MIXParticipantSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MUCDestroyPayloadSerializer.h>
@@ -156,6 +158,8 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
     serializers_.push_back(new ResultSetSerializer());
     serializers_.push_back(new ForwardedSerializer(this));
     serializers_.push_back(new MIXParticipantSerializer());
+    serializers_.push_back(new MIXJoinSerializer());
+    serializers_.push_back(new MIXDestroySerializer());
     serializers_.push_back(new MAMResultSerializer(this));
     serializers_.push_back(new MAMQuerySerializer());
     serializers_.push_back(new MAMFinSerializer());
diff --git a/Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.cpp
new file mode 100644
index 0000000..cae15c2
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.h>
+
+#include <memory>
+
+#include <Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/FormSerializer.h>
+#include <Swiften/Serializer/XML/XMLElement.h>
+#include <Swiften/Serializer/XML/XMLRawTextNode.h>
+
+using namespace Swift;
+
+MIXJoinSerializer::MIXJoinSerializer() {
+}
+
+MIXJoinSerializer::~MIXJoinSerializer() {
+}
+
+std::string MIXJoinSerializer::serializePayload(std::shared_ptr<MIXJoin> payload) const {
+    if (!payload) {
+        return "";
+    }
+    XMLElement element("join", "urn:xmpp:mix:1");
+    if (payload->getChannel()) {
+        element.setAttribute("channel", *payload->getChannel());
+    }
+    if (payload->getJID()) {
+        element.setAttribute("jid", *payload->getJID());
+    }
+    for (const auto& item : payload->getSubscriptions()) {
+        element.addNode(std::make_shared<XMLRawTextNode>(MIXSubscribeSerializer().serialize(item)));
+    }
+    if (payload->getForm()) {
+        element.addNode(std::make_shared<XMLRawTextNode>(FormSerializer().serialize(payload->getForm())));
+    }
+    return element.serialize();
+}
diff --git a/Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.h b/Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.h
new file mode 100644
index 0000000..a53fbe3
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Base/Override.h>
+#include <Swiften/Elements/MIXJoin.h>
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+
+namespace Swift {
+
+    class SWIFTEN_API MIXJoinSerializer : public GenericPayloadSerializer<MIXJoin> {
+        public:
+            MIXJoinSerializer();
+            virtual ~MIXJoinSerializer();
+
+            virtual std::string serializePayload(std::shared_ptr<MIXJoin>) const SWIFTEN_OVERRIDE;
+    };
+}
diff --git a/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.cpp
new file mode 100644
index 0000000..3ce6d89
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.h>
+
+#include <memory>
+
+#include <Swiften/Serializer/XML/XMLElement.h>
+#include <Swiften/Serializer/XML/XMLRawTextNode.h>
+
+using namespace Swift;
+
+MIXSubscribeSerializer::MIXSubscribeSerializer() {
+}
+
+MIXSubscribeSerializer::~MIXSubscribeSerializer() {
+}
+
+std::string MIXSubscribeSerializer::serializePayload(std::shared_ptr<MIXSubscribe> payload) const {
+    if (!payload) {
+        return "";
+    }
+    XMLElement element("subscribe");
+    element.setAttribute("node", payload->getNode());
+    return element.serialize();
+}
diff --git a/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.h b/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.h
new file mode 100644
index 0000000..b343f86
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Base/Override.h>
+#include <Swiften/Elements/MIXSubscribe.h>
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+
+namespace Swift {
+
+    class SWIFTEN_API MIXSubscribeSerializer : public GenericPayloadSerializer<MIXSubscribe> {
+        public:
+            MIXSubscribeSerializer();
+            virtual ~MIXSubscribeSerializer();
+
+            virtual std::string serializePayload(std::shared_ptr<MIXSubscribe>) const SWIFTEN_OVERRIDE;
+    };
+}
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/MIXJoinSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/MIXJoinSerializerTest.cpp
new file mode 100644
index 0000000..61d8a4a
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/MIXJoinSerializerTest.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <gtest/gtest.h>
+
+#include <Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.h>
+
+using namespace Swift;
+
+TEST(MIXJoinSerializerTest, XEP0369_Example22) {
+    MIXJoinSerializer testling;
+
+    auto join = std::make_shared<MIXJoin>();
+    join->setChannel(JID("coven@mix.shakespeare.example"));
+
+    std::shared_ptr<MIXSubscribe> node1(new MIXSubscribe());
+    node1->setNode(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(node1);
+    std::shared_ptr<MIXSubscribe> node2(new MIXSubscribe());
+    node2->setNode(std::string("urn:xmpp:mix:nodes:presence"));
+    join->addSubscription(node2);
+    std::shared_ptr<MIXSubscribe> node3(new MIXSubscribe());
+    node3->setNode(std::string("urn:xmpp:mix:nodes:participants"));
+    join->addSubscription(node3);
+    std::shared_ptr<MIXSubscribe> node4(new MIXSubscribe());
+    node4->setNode(std::string("urn:xmpp:mix:nodes:config"));
+    join->addSubscription(node4);
+
+    std::string expectedResult = "<join channel=\"coven@mix.shakespeare.example\" xmlns=\"urn:xmpp:mix:1\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:participants\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:config\"/>"
+                "</join>";
+    ASSERT_EQ(expectedResult, testling.serialize(join));
+}
+
+TEST(MIXJoinSerializerTest, XEP0369_Example23) {
+    MIXJoinSerializer testling;
+
+    std::shared_ptr<MIXJoin> join(new MIXJoin());
+
+    std::shared_ptr<MIXSubscribe> node1(new MIXSubscribe());
+    node1->setNode(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(node1);
+    std::shared_ptr<MIXSubscribe> node2(new MIXSubscribe());
+    node2->setNode(std::string("urn:xmpp:mix:nodes:presence"));
+    join->addSubscription(node2);
+    std::shared_ptr<MIXSubscribe> node3(new MIXSubscribe());
+    node3->setNode(std::string("urn:xmpp:mix:nodes:participants"));
+    join->addSubscription(node3);
+    std::shared_ptr<MIXSubscribe> node4(new MIXSubscribe());
+    node4->setNode(std::string("urn:xmpp:mix:nodes:config"));
+    join->addSubscription(node4);
+
+    std::string expectedResult = "<join xmlns=\"urn:xmpp:mix:1\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:participants\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:config\"/>"
+                "</join>";
+    ASSERT_EQ(expectedResult, testling.serialize(join));
+}
+
+TEST(MIXJoinSerializerTest, XEP0369_Example24) {
+    MIXJoinSerializer testling;
+
+    std::shared_ptr<MIXJoin> join(new MIXJoin());
+    join->setJID(JID("123456#coven@mix.shakespeare.example"));
+
+    std::shared_ptr<MIXSubscribe> node1(new MIXSubscribe());
+    node1->setNode(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(node1);
+    std::shared_ptr<MIXSubscribe> node2(new MIXSubscribe());
+    node2->setNode(std::string("urn:xmpp:mix:nodes:presence"));
+    join->addSubscription(node2);
+    std::shared_ptr<MIXSubscribe> node3(new MIXSubscribe());
+    node3->setNode(std::string("urn:xmpp:mix:nodes:participants"));
+    join->addSubscription(node3);
+    std::shared_ptr<MIXSubscribe> node4(new MIXSubscribe());
+    node4->setNode(std::string("urn:xmpp:mix:nodes:config"));
+    join->addSubscription(node4);
+
+    std::string expectedResult = "<join jid=\"123456#coven@mix.shakespeare.example\" xmlns=\"urn:xmpp:mix:1\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:participants\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:config\"/>"
+                "</join>";
+    ASSERT_EQ(expectedResult, testling.serialize(join));
+}
+
+TEST(MIXJoinSerializerTest, XEP0369_Example29) {
+    MIXJoinSerializer testling;
+
+    std::shared_ptr<MIXJoin> join(new MIXJoin());
+
+    std::shared_ptr<MIXSubscribe> node1(new MIXSubscribe());
+    node1->setNode(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(node1);
+    std::shared_ptr<MIXSubscribe> node2(new MIXSubscribe());
+    node2->setNode(std::string("urn:xmpp:mix:nodes:presence"));
+    join->addSubscription(node2);
+
+    std::shared_ptr<Form> parameters(std::make_shared<Form>());
+    parameters->setType(Form::Type::SubmitType);
+
+    std::shared_ptr<FormField> fieldType = std::make_shared<FormField>(FormField::HiddenType);
+    fieldType->setName("FORM_TYPE");
+    fieldType->addValue("urn:xmpp:mix:1");
+    parameters->addField(fieldType);
+
+    std::shared_ptr<FormField> fieldJIDVisibility = std::make_shared<FormField>();
+    fieldJIDVisibility->setName("JID Visibility");
+    fieldJIDVisibility->addValue("never");
+    parameters->addField(fieldJIDVisibility);
+
+    join->setForm(parameters);
+
+    std::string expectedResult = "<join xmlns=\"urn:xmpp:mix:1\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+                    "<x type=\"submit\" xmlns=\"jabber:x:data\">"
+                        "<field type=\"hidden\" var=\"FORM_TYPE\">"
+                             "<value>urn:xmpp:mix:1</value>"
+                        "</field>"
+                        "<field var=\"JID Visibility\">"
+                            "<value>never</value>"
+                        "</field>"
+                    "</x>"
+                "</join>";
+    ASSERT_EQ(expectedResult, testling.serialize(join));
+}
+
+TEST(MIXJoinSerializerTest, XEP0369_Example30) {
+    MIXJoinSerializer testling;
+
+    std::shared_ptr<MIXJoin> join(new MIXJoin());
+    join->setJID(JID("hag66@shakespeare.example"));
+
+    std::shared_ptr<MIXSubscribe> node1(new MIXSubscribe());
+    node1->setNode(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(node1);
+    std::shared_ptr<MIXSubscribe> node2(new MIXSubscribe());
+    node2->setNode(std::string("urn:xmpp:mix:nodes:presence"));
+    join->addSubscription(node2);
+
+    std::shared_ptr<Form> parameters(std::make_shared<Form>());
+    parameters->setType(Form::Type::ResultType);
+
+    std::shared_ptr<FormField> fieldType = std::make_shared<FormField>(FormField::HiddenType);
+    fieldType->setName("FORM_TYPE");
+    fieldType->addValue("urn:xmpp:mix:1");
+    parameters->addField(fieldType);
+
+    std::shared_ptr<FormField> fieldJIDVisibility = std::make_shared<FormField>();
+    fieldJIDVisibility->setName("JID Visibility");
+    fieldJIDVisibility->addValue("never");
+    parameters->addField(fieldJIDVisibility);
+
+    std::shared_ptr<FormField> fieldprivateMessages = std::make_shared<FormField>();
+    fieldprivateMessages->setName("Private Messages");
+    fieldprivateMessages->addValue("allow");
+    parameters->addField(fieldprivateMessages);
+
+    std::shared_ptr<FormField> vCard = std::make_shared<FormField>();
+    vCard->setName("vCard");
+    vCard->addValue("block");
+    parameters->addField(vCard);
+
+    join->setForm(parameters);
+
+    std::string expectedResult = "<join jid=\"hag66@shakespeare.example\" xmlns=\"urn:xmpp:mix:1\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
+                    "<x type=\"result\" xmlns=\"jabber:x:data\">"
+                        "<field type=\"hidden\" var=\"FORM_TYPE\">"
+                             "<value>urn:xmpp:mix:1</value>"
+                        "</field>"
+                        "<field var=\"JID Visibility\">"
+                            "<value>never</value>"
+                        "</field>"
+                        "<field var=\"Private Messages\">"
+                            "<value>allow</value>"
+                        "</field>"
+                        "<field var=\"vCard\">"
+                            "<value>block</value>"
+                        "</field>"
+                    "</x>"
+                "</join>";
+    ASSERT_EQ(expectedResult, testling.serialize(join));
+}
-- 
cgit v0.10.2-6-g49f6