From f0db4d39912e773208e9db2a3da3b68ac92ba17b Mon Sep 17 00:00:00 2001
From: Tarun Gupta <tarun1995gupta@gmail.com>
Date: Sat, 8 Jul 2017 02:39:17 +0530
Subject: Add MIXUpdateSubscription Element, its Parser and Serializer

Updates MIXJoin Element list of subscriptions to an unordered_set.
Remove redundant MIXSubscribe Element.

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

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

Change-Id: Ied06269cf329b4fdffdde4ace67ebffa4089fbde

diff --git a/Swiften/Elements/MIXJoin.h b/Swiften/Elements/MIXJoin.h
index 2d82adc..ecd7b12 100644
--- a/Swiften/Elements/MIXJoin.h
+++ b/Swiften/Elements/MIXJoin.h
@@ -8,12 +8,12 @@
 
 #include <memory>
 #include <string>
+#include <unordered_set>
 
 #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>
 
@@ -31,7 +31,7 @@ namespace Swift {
                 return channel_;
             }
 
-            void setChannel(const JID& channel) {
+            void setChannel(JID channel) {
                 channel_ = channel;
             }
 
@@ -39,20 +39,24 @@ namespace Swift {
                 return jid_;
             }
 
-            void setJID(const JID& jid) {
+            void setJID(JID jid) {
                 jid_ = jid;
             }
 
-            const std::vector<MIXSubscribe::ref>& getSubscriptions() const {
+            const std::unordered_set<std::string>& getSubscriptions() const {
                 return subscribeItems_;
             }
 
-            void setSubscriptions(const std::vector<MIXSubscribe::ref>& value) {
-                subscribeItems_ = value ;
+            void setSubscriptions(std::unordered_set<std::string> values) {
+                subscribeItems_ = values ;
             }
 
-            void addSubscription(MIXSubscribe::ref value) {
-                subscribeItems_.push_back(value);
+            void addSubscription(std::string value) {
+                subscribeItems_.insert(value);
+            }
+
+            bool hasSubscription(const std::string& value) const {
+                return std::find(subscribeItems_.begin(), subscribeItems_.end(), value) != subscribeItems_.end();
             }
 
             void setForm(std::shared_ptr<Form> form) {
@@ -66,7 +70,7 @@ namespace Swift {
         private:
             boost::optional<JID> jid_;
             boost::optional<JID> channel_;
-            std::vector<MIXSubscribe::ref> subscribeItems_;
+            std::unordered_set<std::string> 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
deleted file mode 100644
index eaf1aa9..0000000
--- a/Swiften/Elements/MIXSubscribe.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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/Elements/MIXUpdateSubscription.h b/Swiften/Elements/MIXUpdateSubscription.h
new file mode 100644
index 0000000..dc6ed27
--- /dev/null
+++ b/Swiften/Elements/MIXUpdateSubscription.h
@@ -0,0 +1,56 @@
+/*
+ * 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 <unordered_set>
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Elements/Payload.h>
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+    class SWIFTEN_API MIXUpdateSubscription : public Payload {
+
+        public:
+            using ref = std::shared_ptr<MIXUpdateSubscription>;
+
+        public:
+
+            MIXUpdateSubscription() {}
+
+            const boost::optional<JID>& getJID() const {
+                return jid_;
+            }
+
+            void setJID(JID jid) {
+                jid_ = jid;
+            }
+
+            const std::unordered_set<std::string>& getSubscriptions() const {
+                return subscribeItems_;
+            }
+
+            void setSubscriptions(std::unordered_set<std::string> values) {
+                subscribeItems_ = values ;
+            }
+
+            void addSubscription(std::string value) {
+                subscribeItems_.insert(value);
+            }
+
+            bool hasSubscription(const std::string& value) const {
+                return std::find(subscribeItems_.begin(), subscribeItems_.end(), value) != subscribeItems_.end();
+            }
+
+        private:
+            boost::optional<JID> jid_;
+            std::unordered_set<std::string> subscribeItems_;
+    };
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index 8f57704..43c4ebb 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -56,6 +56,7 @@
 #include <Swiften/Parser/PayloadParsers/MIXRegisterNickParserFactory.h>
 #include <Swiften/Parser/PayloadParsers/MIXSetNickParserFactory.h>
 #include <Swiften/Parser/PayloadParsers/MIXDestroyParser.h>
+#include <Swiften/Parser/PayloadParsers/MIXUpdateSubscriptionParser.h>
 #include <Swiften/Parser/PayloadParsers/MIXJoinParserFactory.h>
 #include <Swiften/Parser/PayloadParsers/MIXPayloadParserFactory.h>
 #include <Swiften/Parser/PayloadParsers/MIXUserPreferenceParser.h>
@@ -143,6 +144,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
     factories_.push_back(std::make_shared<MIXRegisterNickParserFactory>());
     factories_.push_back(std::make_shared<MIXSetNickParserFactory>());
     factories_.push_back(std::make_shared<GenericPayloadParserFactory<MIXCreateParser> >("create", "urn:xmpp:mix:0"));
+    factories_.push_back(std::make_shared<GenericPayloadParserFactory<MIXUpdateSubscriptionParser> >("update-subscription", "urn:xmpp:mix:0"));
     factories_.push_back(std::make_shared<GenericPayloadParserFactory<MIXUserPreferenceParser> >("user-preference", "urn:xmpp:mix:0"));
     factories_.push_back(std::make_shared<MIXPayloadParserFactory>());
     factories_.push_back(std::make_shared<GenericPayloadParserFactory<MIXLeaveParser> >("leave", "urn:xmpp:mix:0"));
diff --git a/Swiften/Parser/PayloadParsers/MIXJoinParser.cpp b/Swiften/Parser/PayloadParsers/MIXJoinParser.cpp
index 0c09c44..6e72f90 100644
--- a/Swiften/Parser/PayloadParsers/MIXJoinParser.cpp
+++ b/Swiften/Parser/PayloadParsers/MIXJoinParser.cpp
@@ -9,7 +9,6 @@
 #include <boost/optional.hpp>
 
 #include <Swiften/Parser/PayloadParserFactory.h>
-#include <Swiften/Parser/PayloadParsers/MIXSubscribeParser.h>
 #include <Swiften/Parser/PayloadParsers/FormParser.h>
 
 using namespace Swift;
@@ -36,7 +35,9 @@ void MIXJoinParser::handleStartElement(const std::string& element, const std::st
 
     if (level_ == 1) {
         if (element == "subscribe" && ns == "urn:xmpp:mix:0") {
-            currentPayloadParser_ = std::make_shared<MIXSubscribeParser>();
+            if (boost::optional<std::string> attributeValue = attributes.getAttributeValue("node")) {
+                getPayloadInternal()->addSubscription(*attributeValue);
+            }
         }
         if (element == "x" && ns == "jabber:x:data") {
             currentPayloadParser_ = std::make_shared<FormParser>();
@@ -57,9 +58,6 @@ void MIXJoinParser::handleEndElement(const std::string& element, const std::stri
         }
 
         if (level_ == 1) {
-            if (element == "subscribe" && ns == "urn:xmpp:mix:0") {
-                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()));
             }
diff --git a/Swiften/Parser/PayloadParsers/MIXSubscribeParser.cpp b/Swiften/Parser/PayloadParsers/MIXSubscribeParser.cpp
deleted file mode 100644
index 1500716..0000000
--- a/Swiften/Parser/PayloadParsers/MIXSubscribeParser.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 95d6d74..0000000
--- a/Swiften/Parser/PayloadParsers/MIXSubscribeParser.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2017 Tarun Gupta
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2017 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#pragma once
-
-#include <memory>
-
-#include <Swiften/Base/API.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() override;
-
-            virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) override;
-            virtual void handleEndElement(const std::string& element, const std::string&) override;
-            virtual void handleCharacterData(const std::string& data) override;
-
-        private:
-            int level_;
-    };
-}
diff --git a/Swiften/Parser/PayloadParsers/MIXUpdateSubscriptionParser.cpp b/Swiften/Parser/PayloadParsers/MIXUpdateSubscriptionParser.cpp
new file mode 100644
index 0000000..d530e49
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MIXUpdateSubscriptionParser.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/MIXUpdateSubscriptionParser.h>
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Parser/PayloadParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/FormParser.h>
+
+namespace Swift {
+
+MIXUpdateSubscriptionParser::MIXUpdateSubscriptionParser() : level_(0) {
+}
+
+MIXUpdateSubscriptionParser::~MIXUpdateSubscriptionParser() {
+}
+
+void MIXUpdateSubscriptionParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) {
+    if (level_ == 0) {
+        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:0") {
+            if (boost::optional<std::string> attributeValue = attributes.getAttributeValue("node")) {
+                getPayloadInternal()->addSubscription(*attributeValue);
+            }
+        }
+    }
+
+    ++level_;
+}
+
+void MIXUpdateSubscriptionParser::handleEndElement(const std::string&, const std::string&) {
+    --level_;
+}
+
+void MIXUpdateSubscriptionParser::handleCharacterData(const std::string&) {
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/MIXUpdateSubscriptionParser.h b/Swiften/Parser/PayloadParsers/MIXUpdateSubscriptionParser.h
new file mode 100644
index 0000000..9975411
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MIXUpdateSubscriptionParser.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Elements/MIXUpdateSubscription.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+
+namespace Swift {
+    class PayloadParser;
+
+    class SWIFTEN_API MIXUpdateSubscriptionParser : public GenericPayloadParser<MIXUpdateSubscription> {
+        public:
+            MIXUpdateSubscriptionParser();
+            virtual ~MIXUpdateSubscriptionParser();
+
+            virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) override;
+            virtual void handleEndElement(const std::string& element, const std::string&) override;
+            virtual void handleCharacterData(const std::string& data) override;
+
+        private:
+            int level_;
+    };
+}
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/MIXJoinParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/MIXJoinParserTest.cpp
index 88d8fc1..0ad4589 100644
--- a/Swiften/Parser/PayloadParsers/UnitTest/MIXJoinParserTest.cpp
+++ b/Swiften/Parser/PayloadParsers/UnitTest/MIXJoinParserTest.cpp
@@ -30,12 +30,11 @@ TEST(MIXJoinParserTest, XEP0369_Example22) {
     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());
+    ASSERT_EQ(static_cast<size_t>(4), payload->getSubscriptions().size());
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:messages")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:presence")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:participants")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:config")));
 }
 
 TEST(MIXJoinParserTest, XEP0369_Example23) {
@@ -56,12 +55,11 @@ TEST(MIXJoinParserTest, XEP0369_Example23) {
     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());
+    ASSERT_EQ(static_cast<size_t>(4), payload->getSubscriptions().size());
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:messages")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:presence")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:participants")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:config")));
 }
 
 TEST(MIXJoinParserTest, XEP0369_Example24) {
@@ -83,12 +81,11 @@ TEST(MIXJoinParserTest, XEP0369_Example24) {
     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());
+    ASSERT_EQ(static_cast<size_t>(4), payload->getSubscriptions().size());
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:messages")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:presence")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:participants")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:config")));
 }
 
 TEST(MIXJoinParserTest, XEP0369_Example29) {
@@ -112,10 +109,10 @@ TEST(MIXJoinParserTest, XEP0369_Example29) {
 
     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_EQ(static_cast<size_t>(2), payload->getSubscriptions().size());
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:messages")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:presence")));
 
     ASSERT_TRUE(payload->getForm());
     ASSERT_EQ(Form::Type::SubmitType, payload->getForm()->getType());
@@ -156,10 +153,9 @@ TEST(MIXJoinParserTest, XEP0369_Example30) {
     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_EQ(static_cast<size_t>(2), payload->getSubscriptions().size());
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:messages")));
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:presence")));
 
     ASSERT_TRUE(payload->getForm());
     ASSERT_EQ(Form::Type::ResultType, payload->getForm()->getType());
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/MIXUpdateSubscriptionParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/MIXUpdateSubscriptionParserTest.cpp
new file mode 100644
index 0000000..985a34b
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/UnitTest/MIXUpdateSubscriptionParserTest.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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/MIXUpdateSubscription.h>
+#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h>
+
+using namespace Swift;
+
+TEST(MIXUpdateSubscriptionParserTest, XEP0369_Example28) {
+    PayloadsParserTester parser;
+    ASSERT_TRUE(parser.parse(
+                "<update-subscription xmlns=\"urn:xmpp:mix:0\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                "</update-subscription>"
+                ));
+
+    auto payload = parser.getPayload<MIXUpdateSubscription>();
+    ASSERT_TRUE(payload);
+
+    ASSERT_FALSE(payload->getJID());
+
+    auto items = payload->getSubscriptions();
+    ASSERT_EQ(static_cast<size_t>(1), items.size());
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:messages")));
+}
+
+TEST(MIXUpdateSubscriptionParserTest, XEP0369_Example28_WithJID) {
+    PayloadsParserTester parser;
+    ASSERT_TRUE(parser.parse(
+                "<update-subscription xmlns=\"urn:xmpp:mix:0\" jid=\"hag66@shakespeare.example\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                "</update-subscription>"
+                ));
+
+    auto payload = parser.getPayload<MIXUpdateSubscription>();
+    ASSERT_TRUE(payload);
+
+    ASSERT_TRUE(payload->getJID());
+    ASSERT_EQ(JID("hag66@shakespeare.example"), *payload->getJID());
+
+    auto items = payload->getSubscriptions();
+    ASSERT_EQ(static_cast<size_t>(1), items.size());
+    ASSERT_TRUE(payload->hasSubscription(std::string("urn:xmpp:mix:nodes:messages")));
+}
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index bfc7850..30c61a7 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -75,8 +75,8 @@ sources = [
         "PayloadParsers/MIXPayloadParser.cpp",
         "PayloadParsers/MIXLeaveParser.cpp",
         "PayloadParsers/MIXJoinParser.cpp",
-        "PayloadParsers/MIXSubscribeParser.cpp",
         "PayloadParsers/MIXUserPreferenceParser.cpp",
+        "PayloadParsers/MIXUpdateSubscriptionParser.cpp",
         "PayloadParsers/MUCUserPayloadParser.cpp",
         "PayloadParsers/MUCAdminPayloadParser.cpp",
         "PayloadParsers/MUCOwnerPayloadParser.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index bd1faf9..7f2a92b 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -216,7 +216,7 @@ if env["SCONS_STAGE"] == "build" :
             "Serializer/PayloadSerializers/MIXUserPreferenceSerializer.cpp",
             "Serializer/PayloadSerializers/MIXLeaveSerializer.cpp",
             "Serializer/PayloadSerializers/MIXJoinSerializer.cpp",
-            "Serializer/PayloadSerializers/MIXSubscribeSerializer.cpp",
+            "Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.cpp",
             "Serializer/PayloadSerializers/MUCPayloadSerializer.cpp",
             "Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp",
             "Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp",
@@ -442,6 +442,7 @@ if env["SCONS_STAGE"] == "build" :
             File("Parser/PayloadParsers/UnitTest/JingleParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/MIXParticipantParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/MIXPayloadParserTest.cpp"),
+            File("Parser/PayloadParsers/UnitTest/MIXUpdateSubscriptionParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/MIXRegisterNickParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/MIXSetNickParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/MIXDestroyParserTest.cpp"),
@@ -530,6 +531,7 @@ if env["SCONS_STAGE"] == "build" :
             File("Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/MIXParticipantSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/MIXSetNickSerializerTest.cpp"),
+            File("Serializer/PayloadSerializers/UnitTest/MIXUpdateSubscriptionSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/MIXRegisterNickSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/MIXDestroySerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/MIXCreateSerializerTest.cpp"),
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index d1b2e97..428ce76 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -47,6 +47,7 @@
 #include <Swiften/Serializer/PayloadSerializers/MAMResultSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MIXDestroySerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MIXParticipantSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MIXRegisterNickSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/MIXCreateSerializer.h>
@@ -172,6 +173,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
     serializers_.push_back(new MIXLeaveSerializer());
     serializers_.push_back(new MIXJoinSerializer());
     serializers_.push_back(new MIXDestroySerializer());
+    serializers_.push_back(new MIXUpdateSubscriptionSerializer());
     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
index cfb4d31..509e41e 100644
--- a/Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.cpp
@@ -7,8 +7,8 @@
 #include <Swiften/Serializer/PayloadSerializers/MIXJoinSerializer.h>
 
 #include <memory>
+#include <string>
 
-#include <Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/FormSerializer.h>
 #include <Swiften/Serializer/XML/XMLElement.h>
 #include <Swiften/Serializer/XML/XMLRawTextNode.h>
@@ -32,9 +32,16 @@ std::string MIXJoinSerializer::serializePayload(std::shared_ptr<MIXJoin> payload
     if (payload->getJID()) {
         element.setAttribute("jid", *payload->getJID());
     }
-    for (const auto& item : payload->getSubscriptions()) {
-        element.addNode(std::make_shared<XMLRawTextNode>(MIXSubscribeSerializer().serialize(item)));
+    auto subscriptionData = payload->getSubscriptions();
+    std::vector<std::string> subscriptions(subscriptionData.begin(), subscriptionData.end());
+    std::sort(subscriptions.begin(), subscriptions.end());
+
+    for (const auto& item : subscriptions) {
+        auto subscribeElement = std::make_shared<XMLElement>("subscribe");
+        subscribeElement->setAttribute("node", item);
+        element.addNode(subscribeElement);
     }
+
     if (payload->getForm()) {
         element.addNode(std::make_shared<XMLRawTextNode>(FormSerializer().serialize(payload->getForm())));
     }
diff --git a/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.cpp
deleted file mode 100644
index 3ce6d89..0000000
--- a/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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
deleted file mode 100644
index a63a2be..0000000
--- a/Swiften/Serializer/PayloadSerializers/MIXSubscribeSerializer.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2017 Tarun Gupta
- * Licensed under the simplified BSD license.
- * See Documentation/Licenses/BSD-simplified.txt for more information.
- */
-
-/*
- * Copyright (c) 2017 Isode Limited.
- * All rights reserved.
- * See the COPYING file for more information.
- */
-
-#pragma once
-
-#include <memory>
-
-#include <Swiften/Base/API.h>
-#include <Swiften/Elements/MIXSubscribe.h>
-#include <Swiften/Serializer/GenericPayloadSerializer.h>
-
-namespace Swift {
-
-    class SWIFTEN_API MIXSubscribeSerializer : public GenericPayloadSerializer<MIXSubscribe> {
-        public:
-            MIXSubscribeSerializer();
-            virtual ~MIXSubscribeSerializer() override;
-
-            virtual std::string serializePayload(std::shared_ptr<MIXSubscribe>) const override;
-    };
-}
diff --git a/Swiften/Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.cpp
new file mode 100644
index 0000000..8e95474
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.h>
+
+#include <memory>
+
+#include <Swiften/Serializer/PayloadSerializers/FormSerializer.h>
+#include <Swiften/Serializer/XML/XMLElement.h>
+#include <Swiften/Serializer/XML/XMLRawTextNode.h>
+
+namespace Swift {
+
+MIXUpdateSubscriptionSerializer::MIXUpdateSubscriptionSerializer() {
+}
+
+MIXUpdateSubscriptionSerializer::~MIXUpdateSubscriptionSerializer() {
+}
+
+std::string MIXUpdateSubscriptionSerializer::serializePayload(std::shared_ptr<MIXUpdateSubscription> payload) const {
+    if (!payload) {
+        return "";
+    }
+    XMLElement element("update-subscription", "urn:xmpp:mix:0");
+    if (payload->getJID()) {
+        element.setAttribute("jid", *payload->getJID());
+    }
+    auto subscriptionData = payload->getSubscriptions();
+    std::vector<std::string> subscriptions(subscriptionData.begin(), subscriptionData.end());
+    std::sort(subscriptions.begin(), subscriptions.end());
+
+    for (const auto& item : subscriptions) {
+        auto subscribeElement = std::make_shared<XMLElement>("subscribe");
+        subscribeElement->setAttribute("node", item);
+        element.addNode(subscribeElement);
+    }
+    return element.serialize();
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.h b/Swiften/Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.h
new file mode 100644
index 0000000..ee59bf7
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017 Tarun Gupta
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2017 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Elements/MIXUpdateSubscription.h>
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+
+namespace Swift {
+
+    class SWIFTEN_API MIXUpdateSubscriptionSerializer : public GenericPayloadSerializer<MIXUpdateSubscription> {
+        public:
+            MIXUpdateSubscriptionSerializer();
+            virtual ~MIXUpdateSubscriptionSerializer();
+
+            virtual std::string serializePayload(std::shared_ptr<MIXUpdateSubscription>) const override;
+    };
+}
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/MIXJoinSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/MIXJoinSerializerTest.cpp
index ff8aa70..2987a20 100644
--- a/Swiften/Serializer/PayloadSerializers/UnitTest/MIXJoinSerializerTest.cpp
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/MIXJoinSerializerTest.cpp
@@ -16,24 +16,16 @@ TEST(MIXJoinSerializerTest, XEP0369_Example22) {
     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);
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:presence"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:participants"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:config"));
 
     std::string expectedResult = "<join channel=\"coven@mix.shakespeare.example\" xmlns=\"urn:xmpp:mix:0\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:config\"/>"
                     "<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\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
                 "</join>";
     ASSERT_EQ(expectedResult, testling.serialize(join));
 }
@@ -43,24 +35,16 @@ TEST(MIXJoinSerializerTest, XEP0369_Example23) {
 
     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);
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:presence"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:participants"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:config"));
 
     std::string expectedResult = "<join xmlns=\"urn:xmpp:mix:0\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:config\"/>"
                     "<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\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
                 "</join>";
     ASSERT_EQ(expectedResult, testling.serialize(join));
 }
@@ -71,24 +55,16 @@ TEST(MIXJoinSerializerTest, XEP0369_Example24) {
     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);
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:presence"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:participants"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:config"));
 
     std::string expectedResult = "<join jid=\"123456#coven@mix.shakespeare.example\" xmlns=\"urn:xmpp:mix:0\">"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:config\"/>"
                     "<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\"/>"
+                    "<subscribe node=\"urn:xmpp:mix:nodes:presence\"/>"
                 "</join>";
     ASSERT_EQ(expectedResult, testling.serialize(join));
 }
@@ -98,12 +74,8 @@ TEST(MIXJoinSerializerTest, XEP0369_Example29) {
 
     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);
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:presence"));
 
     std::shared_ptr<Form> parameters(std::make_shared<Form>());
     parameters->setType(Form::Type::SubmitType);
@@ -141,12 +113,8 @@ TEST(MIXJoinSerializerTest, XEP0369_Example30) {
     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);
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:messages"));
+    join->addSubscription(std::string("urn:xmpp:mix:nodes:presence"));
 
     std::shared_ptr<Form> parameters(std::make_shared<Form>());
     parameters->setType(Form::Type::ResultType);
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/MIXUpdateSubscriptionSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/MIXUpdateSubscriptionSerializerTest.cpp
new file mode 100644
index 0000000..3e35a82
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/MIXUpdateSubscriptionSerializerTest.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 <gtest/gtest.h>
+
+#include <Swiften/Serializer/PayloadSerializers/MIXUpdateSubscriptionSerializer.h>
+
+using namespace Swift;
+
+TEST(MIXUpdateSubscriptionSerializerTest, XEP0369_Example28) {
+    MIXUpdateSubscriptionSerializer testling;
+
+    auto update = std::make_shared<MIXUpdateSubscription>();
+    update->addSubscription(std::string("urn:xmpp:mix:nodes:messages"));
+
+    std::string expectedResult = "<update-subscription xmlns=\"urn:xmpp:mix:0\">"
+                                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                                "</update-subscription>";
+    ASSERT_EQ(expectedResult, testling.serialize(update));
+}
+
+TEST(MIXUpdateSubscriptionSerializerTest, XEP0369_Example28WithJID) {
+    MIXUpdateSubscriptionSerializer testling;
+
+    auto update = std::make_shared<MIXUpdateSubscription>();
+    update->setJID(JID("hag66@shakespeare.example"));
+    update->addSubscription(std::string("urn:xmpp:mix:nodes:messages"));
+
+    std::string expectedResult = "<update-subscription jid=\"hag66@shakespeare.example\" xmlns=\"urn:xmpp:mix:0\">"
+                                    "<subscribe node=\"urn:xmpp:mix:nodes:messages\"/>"
+                                "</update-subscription>";
+    ASSERT_EQ(expectedResult, testling.serialize(update));
+}
-- 
cgit v0.10.2-6-g49f6