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