Vitaly Buka | 4615e0d | 2015-10-14 15:35:12 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Weave Authors. All rights reserved. |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Stefan Sauer | 2d16dfa | 2015-09-25 17:08:35 +0200 | [diff] [blame] | 5 | #include "src/notification/xmpp_stream_parser.h" |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 6 | |
Vitaly Buka | 34668e7 | 2015-12-15 14:46:47 -0800 | [diff] [blame] | 7 | #include <gtest/gtest.h> |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 8 | #include <memory> |
| 9 | #include <vector> |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 10 | |
Stefan Sauer | 2d16dfa | 2015-09-25 17:08:35 +0200 | [diff] [blame] | 11 | #include "src/notification/xml_node.h" |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 12 | |
Vitaly Buka | b6f015a | 2015-07-09 14:59:23 -0700 | [diff] [blame] | 13 | namespace weave { |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 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: |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 46 | void SetUp() override { parser_.reset(new XmppStreamParser{this}); } |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 47 | |
| 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 | |
| 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 | |
| 62 | void OnStanza(std::unique_ptr<XmlNode> stanza) override { |
| 63 | stanzas_.push_back(std::move(stanza)); |
| 64 | } |
| 65 | |
| 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 | |
| 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 | |
| 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_); |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 92 | const std::map<std::string, std::string> expected_attrs{{"bar", "baz"}, |
| 93 | {"quux", "1"}}; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 94 | EXPECT_EQ(expected_attrs, stream_start_node_attributes_); |
| 95 | } |
| 96 | |
| 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_); |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 108 | const std::map<std::string, std::string> expected_attrs{{"bar", "baz"}, |
| 109 | {"quux", "1"}}; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 110 | EXPECT_EQ(expected_attrs, stream_start_node_attributes_); |
| 111 | } |
| 112 | |
| 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"}, |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 121 | {"xmlns", "jabber:client"}}; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 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)); |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 155 | EXPECT_EQ( |
| 156 | "cloud-devices@clouddevices.google.com/" |
| 157 | "srvenc-xgbCfg9hX6tCpxoMYsExqg==", |
| 158 | value); |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 159 | EXPECT_TRUE(stanza2->GetAttribute("to", &value)); |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 160 | EXPECT_EQ( |
| 161 | "4783f652b387449fc52a76f9a16e616f@clouddevices.gserviceaccount." |
| 162 | "com/5A85ED9C", |
| 163 | value); |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 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)); |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 179 | EXPECT_EQ( |
| 180 | "4783f652b387449fc52a76f9a16e616f@clouddevices.gserviceaccount." |
| 181 | "com", |
| 182 | value); |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 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()); |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 189 | const std::string expected_data = |
| 190 | "eyJraW5kIjoiY2xvdWRkZXZpY2VzI25vdGlmaWNh" |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 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 | |
Vitaly Buka | b6f015a | 2015-07-09 14:59:23 -0700 | [diff] [blame] | 205 | } // namespace weave |