1 // Copyright 2015 The Weave Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/notification/xmpp_stream_parser.h"
6 
7 #include <gtest/gtest.h>
8 #include <memory>
9 #include <vector>
10 
11 #include "src/notification/xml_node.h"
12 
13 namespace weave {
14 namespace {
15 // Use some real-world XMPP stream snippet to make sure all the expected
16 // elements are parsed properly.
17 const char kXmppStreamData[] =
18     "<stream:stream from=\"clouddevices.gserviceaccount.com\" id=\"76EEB8FDB449"
19     "5558\" version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" x"
20     "mlns=\"jabber:client\">"
21     "<stream:features><starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"><requ"
22     "ired/></starttls><mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><m"
23     "echanism>X-OAUTH2</mechanism><mechanism>X-GOOGLE-TOKEN</mechanism></mechan"
24     "isms></stream:features>"
25     "<message from=\"cloud-devices@clouddevices.google.com/srvenc-xgbCfg9hX6tCp"
26     "xoMYsExqg==\" to=\"4783f652b387449fc52a76f9a16e616f@clouddevices.gservicea"
27     "ccount.com/5A85ED9C\"><push:push channel=\"cloud_devices\" xmlns:push=\"go"
28     "ogle:push\"><push:recipient to=\"4783f652b387449fc52a76f9a16e616f@clouddev"
29     "ices.gserviceaccount.com\"></push:recipient><push:data>eyJraW5kIjoiY2xvdWR"
30     "kZXZpY2VzI25vdGlmaWNhdGlvbiIsInR5cGUiOiJDT01NQU5EX0NSRUFURUQiLCJjb21tYW5kS"
31     "WQiOiIwNWE3MTA5MC1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01NjExLWI"
32     "5ODAtOTkyMy0yNjc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgtYzM3NC05N"
33     "jA1LTFlNGFjYmE0NGYzZiIsImNvbW1hbmQiOnsia2luZCI6ImNsb3VkZGV2aWNlcyNjb21tYW5"
34     "kIiwiaWQiOiIwNWE3MTA5MC1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01N"
35     "jExLWI5ODAtOTkyMy0yNjc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgtYzM"
36     "3NC05NjA1LTFlNGFjYmE0NGYzZiIsIm5hbWUiOiJiYXNlLl9qdW1wIiwic3RhdGUiOiJxdWV1Z"
37     "WQiLCJlcnJvciI6eyJhcmd1bWVudHMiOltdfSwiY3JlYXRpb25UaW1lTXMiOiIxNDMxNTY0NDY"
38     "4MjI3IiwiZXhwaXJhdGlvblRpbWVNcyI6IjE0MzE1NjgwNjgyMjciLCJleHBpcmF0aW9uVGltZ"
39     "W91dE1zIjoiMzYwMDAwMCJ9fQ==</push:data></push:push></message>";
40 
41 }  // anonymous namespace
42 
43 class XmppStreamParserTest : public testing::Test,
44                              public XmppStreamParser::Delegate {
45  public:
SetUp()46   void SetUp() override { parser_.reset(new XmppStreamParser{this}); }
47 
OnStreamStart(const std::string & node_name,std::map<std::string,std::string> attributes)48   void OnStreamStart(const std::string& node_name,
49                      std::map<std::string, std::string> attributes) override {
50     EXPECT_FALSE(stream_started_);
51     stream_started_ = true;
52     stream_start_node_name_ = node_name;
53     stream_start_node_attributes_ = std::move(attributes);
54   }
55 
OnStreamEnd(const std::string & node_name)56   void OnStreamEnd(const std::string& node_name) override {
57     EXPECT_TRUE(stream_started_);
58     EXPECT_EQ(stream_start_node_name_, node_name);
59     stream_started_ = false;
60   }
61 
OnStanza(std::unique_ptr<XmlNode> stanza)62   void OnStanza(std::unique_ptr<XmlNode> stanza) override {
63     stanzas_.push_back(std::move(stanza));
64   }
65 
Reset()66   void Reset() {
67     parser_.reset(new XmppStreamParser{this});
68     stream_started_ = false;
69     stream_start_node_name_.clear();
70     stream_start_node_attributes_.clear();
71     stanzas_.clear();
72   }
73 
74   std::unique_ptr<XmppStreamParser> parser_;
75   bool stream_started_{false};
76   std::string stream_start_node_name_;
77   std::map<std::string, std::string> stream_start_node_attributes_;
78   std::vector<std::unique_ptr<XmlNode>> stanzas_;
79 };
80 
TEST_F(XmppStreamParserTest,InitialState)81 TEST_F(XmppStreamParserTest, InitialState) {
82   EXPECT_FALSE(stream_started_);
83   EXPECT_TRUE(stream_start_node_name_.empty());
84   EXPECT_TRUE(stream_start_node_attributes_.empty());
85   EXPECT_TRUE(stanzas_.empty());
86 }
87 
TEST_F(XmppStreamParserTest,FullStartElement)88 TEST_F(XmppStreamParserTest, FullStartElement) {
89   parser_->ParseData("<foo bar=\"baz\" quux=\"1\">");
90   EXPECT_TRUE(stream_started_);
91   EXPECT_EQ("foo", stream_start_node_name_);
92   const std::map<std::string, std::string> expected_attrs{{"bar", "baz"},
93                                                           {"quux", "1"}};
94   EXPECT_EQ(expected_attrs, stream_start_node_attributes_);
95 }
96 
TEST_F(XmppStreamParserTest,PartialStartElement)97 TEST_F(XmppStreamParserTest, PartialStartElement) {
98   parser_->ParseData("<foo bar=\"baz");
99   EXPECT_FALSE(stream_started_);
100   EXPECT_TRUE(stream_start_node_name_.empty());
101   EXPECT_TRUE(stream_start_node_attributes_.empty());
102   EXPECT_TRUE(stanzas_.empty());
103   parser_->ParseData("\" quux");
104   EXPECT_FALSE(stream_started_);
105   parser_->ParseData("=\"1\">");
106   EXPECT_TRUE(stream_started_);
107   EXPECT_EQ("foo", stream_start_node_name_);
108   const std::map<std::string, std::string> expected_attrs{{"bar", "baz"},
109                                                           {"quux", "1"}};
110   EXPECT_EQ(expected_attrs, stream_start_node_attributes_);
111 }
112 
TEST_F(XmppStreamParserTest,VariableLengthPackets)113 TEST_F(XmppStreamParserTest, VariableLengthPackets) {
114   std::string value;
115   const std::string xml_data = kXmppStreamData;
116   const std::map<std::string, std::string> expected_stream_attrs{
117       {"from", "clouddevices.gserviceaccount.com"},
118       {"id", "76EEB8FDB4495558"},
119       {"version", "1.0"},
120       {"xmlns:stream", "http://etherx.jabber.org/streams"},
121       {"xmlns", "jabber:client"}};
122   // Try splitting the data into pieces from 1 character in size to the whole
123   // data block and verify that we still can parse the whole message correctly.
124   // Here |step| is the size of each individual data chunk.
125   for (size_t step = 1; step <= xml_data.size(); step++) {
126     // Feed each individual chunk to the parser and hope it can piece everything
127     // together correctly.
128     for (size_t pos = 0; pos < xml_data.size(); pos += step) {
129       parser_->ParseData(xml_data.substr(pos, step));
130     }
131     EXPECT_TRUE(stream_started_);
132     EXPECT_EQ("stream:stream", stream_start_node_name_);
133     EXPECT_EQ(expected_stream_attrs, stream_start_node_attributes_);
134     EXPECT_EQ(2u, stanzas_.size());
135 
136     const XmlNode* stanza1 = stanzas_[0].get();
137     EXPECT_EQ("stream:features", stanza1->name());
138     ASSERT_EQ(2u, stanza1->children().size());
139     const XmlNode* child1 = stanza1->children()[0].get();
140     EXPECT_EQ("starttls", child1->name());
141     ASSERT_EQ(1u, child1->children().size());
142     EXPECT_EQ("required", child1->children()[0]->name());
143     const XmlNode* child2 = stanza1->children()[1].get();
144     EXPECT_EQ("mechanisms", child2->name());
145     ASSERT_EQ(2u, child2->children().size());
146     EXPECT_EQ("mechanism", child2->children()[0]->name());
147     EXPECT_EQ("X-OAUTH2", child2->children()[0]->text());
148     EXPECT_EQ("mechanism", child2->children()[1]->name());
149     EXPECT_EQ("X-GOOGLE-TOKEN", child2->children()[1]->text());
150 
151     const XmlNode* stanza2 = stanzas_[1].get();
152     EXPECT_EQ("message", stanza2->name());
153     ASSERT_EQ(2u, stanza2->attributes().size());
154     EXPECT_TRUE(stanza2->GetAttribute("from", &value));
155     EXPECT_EQ(
156         "cloud-devices@clouddevices.google.com/"
157         "srvenc-xgbCfg9hX6tCpxoMYsExqg==",
158         value);
159     EXPECT_TRUE(stanza2->GetAttribute("to", &value));
160     EXPECT_EQ(
161         "4783f652b387449fc52a76f9a16e616f@clouddevices.gserviceaccount."
162         "com/5A85ED9C",
163         value);
164     ASSERT_EQ(1u, stanza2->children().size());
165 
166     const XmlNode* child = stanza2->children().back().get();
167     EXPECT_EQ("push:push", child->name());
168     ASSERT_EQ(2u, child->attributes().size());
169     EXPECT_TRUE(child->GetAttribute("channel", &value));
170     EXPECT_EQ("cloud_devices", value);
171     EXPECT_TRUE(child->GetAttribute("xmlns:push", &value));
172     EXPECT_EQ("google:push", value);
173     ASSERT_EQ(2u, child->children().size());
174 
175     child1 = child->children()[0].get();
176     EXPECT_EQ("push:recipient", child1->name());
177     ASSERT_EQ(1u, child1->attributes().size());
178     EXPECT_TRUE(child1->GetAttribute("to", &value));
179     EXPECT_EQ(
180         "4783f652b387449fc52a76f9a16e616f@clouddevices.gserviceaccount."
181         "com",
182         value);
183     EXPECT_TRUE(child1->children().empty());
184 
185     child2 = child->children()[1].get();
186     EXPECT_EQ("push:data", child2->name());
187     EXPECT_TRUE(child2->attributes().empty());
188     EXPECT_TRUE(child2->children().empty());
189     const std::string expected_data =
190         "eyJraW5kIjoiY2xvdWRkZXZpY2VzI25vdGlmaWNh"
191         "dGlvbiIsInR5cGUiOiJDT01NQU5EX0NSRUFURUQiLCJjb21tYW5kSWQiOiIwNWE3MTA5MC"
192         "1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01NjExLWI5ODAtOTkyMy0y"
193         "Njc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgtYzM3NC05NjA1LTFlNG"
194         "FjYmE0NGYzZiIsImNvbW1hbmQiOnsia2luZCI6ImNsb3VkZGV2aWNlcyNjb21tYW5kIiwi"
195         "aWQiOiIwNWE3MTA5MC1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01Nj"
196         "ExLWI5ODAtOTkyMy0yNjc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgt"
197         "YzM3NC05NjA1LTFlNGFjYmE0NGYzZiIsIm5hbWUiOiJiYXNlLl9qdW1wIiwic3RhdGUiOi"
198         "JxdWV1ZWQiLCJlcnJvciI6eyJhcmd1bWVudHMiOltdfSwiY3JlYXRpb25UaW1lTXMiOiIx"
199         "NDMxNTY0NDY4MjI3IiwiZXhwaXJhdGlvblRpbWVNcyI6IjE0MzE1NjgwNjgyMjciLCJleH"
200         "BpcmF0aW9uVGltZW91dE1zIjoiMzYwMDAwMCJ9fQ==";
201     EXPECT_EQ(expected_data, child2->text());
202   }
203 }
204 
205 }  // namespace weave
206