| // Copyright 2015 The Weave Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/notification/xmpp_stream_parser.h" | |
| #include <gtest/gtest.h> | |
| #include <memory> | |
| #include <vector> | |
| #include "src/notification/xml_node.h" | |
| namespace weave { | |
| namespace { | |
| // Use some real-world XMPP stream snippet to make sure all the expected | |
| // elements are parsed properly. | |
| const char kXmppStreamData[] = | |
| "<stream:stream from=\"clouddevices.gserviceaccount.com\" id=\"76EEB8FDB449" | |
| "5558\" version=\"1.0\" xmlns:stream=\"http://etherx.jabber.org/streams\" x" | |
| "mlns=\"jabber:client\">" | |
| "<stream:features><starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"><requ" | |
| "ired/></starttls><mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><m" | |
| "echanism>X-OAUTH2</mechanism><mechanism>X-GOOGLE-TOKEN</mechanism></mechan" | |
| "isms></stream:features>" | |
| "<message from=\"cloud-devices@clouddevices.google.com/srvenc-xgbCfg9hX6tCp" | |
| "xoMYsExqg==\" to=\"4783f652b387449fc52a76f9a16e616f@clouddevices.gservicea" | |
| "ccount.com/5A85ED9C\"><push:push channel=\"cloud_devices\" xmlns:push=\"go" | |
| "ogle:push\"><push:recipient to=\"4783f652b387449fc52a76f9a16e616f@clouddev" | |
| "ices.gserviceaccount.com\"></push:recipient><push:data>eyJraW5kIjoiY2xvdWR" | |
| "kZXZpY2VzI25vdGlmaWNhdGlvbiIsInR5cGUiOiJDT01NQU5EX0NSRUFURUQiLCJjb21tYW5kS" | |
| "WQiOiIwNWE3MTA5MC1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01NjExLWI" | |
| "5ODAtOTkyMy0yNjc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgtYzM3NC05N" | |
| "jA1LTFlNGFjYmE0NGYzZiIsImNvbW1hbmQiOnsia2luZCI6ImNsb3VkZGV2aWNlcyNjb21tYW5" | |
| "kIiwiaWQiOiIwNWE3MTA5MC1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01N" | |
| "jExLWI5ODAtOTkyMy0yNjc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgtYzM" | |
| "3NC05NjA1LTFlNGFjYmE0NGYzZiIsIm5hbWUiOiJiYXNlLl9qdW1wIiwic3RhdGUiOiJxdWV1Z" | |
| "WQiLCJlcnJvciI6eyJhcmd1bWVudHMiOltdfSwiY3JlYXRpb25UaW1lTXMiOiIxNDMxNTY0NDY" | |
| "4MjI3IiwiZXhwaXJhdGlvblRpbWVNcyI6IjE0MzE1NjgwNjgyMjciLCJleHBpcmF0aW9uVGltZ" | |
| "W91dE1zIjoiMzYwMDAwMCJ9fQ==</push:data></push:push></message>"; | |
| } // anonymous namespace | |
| class XmppStreamParserTest : public testing::Test, | |
| public XmppStreamParser::Delegate { | |
| public: | |
| void SetUp() override { parser_.reset(new XmppStreamParser{this}); } | |
| void OnStreamStart(const std::string& node_name, | |
| std::map<std::string, std::string> attributes) override { | |
| EXPECT_FALSE(stream_started_); | |
| stream_started_ = true; | |
| stream_start_node_name_ = node_name; | |
| stream_start_node_attributes_ = std::move(attributes); | |
| } | |
| void OnStreamEnd(const std::string& node_name) override { | |
| EXPECT_TRUE(stream_started_); | |
| EXPECT_EQ(stream_start_node_name_, node_name); | |
| stream_started_ = false; | |
| } | |
| void OnStanza(std::unique_ptr<XmlNode> stanza) override { | |
| stanzas_.push_back(std::move(stanza)); | |
| } | |
| void Reset() { | |
| parser_.reset(new XmppStreamParser{this}); | |
| stream_started_ = false; | |
| stream_start_node_name_.clear(); | |
| stream_start_node_attributes_.clear(); | |
| stanzas_.clear(); | |
| } | |
| std::unique_ptr<XmppStreamParser> parser_; | |
| bool stream_started_{false}; | |
| std::string stream_start_node_name_; | |
| std::map<std::string, std::string> stream_start_node_attributes_; | |
| std::vector<std::unique_ptr<XmlNode>> stanzas_; | |
| }; | |
| TEST_F(XmppStreamParserTest, InitialState) { | |
| EXPECT_FALSE(stream_started_); | |
| EXPECT_TRUE(stream_start_node_name_.empty()); | |
| EXPECT_TRUE(stream_start_node_attributes_.empty()); | |
| EXPECT_TRUE(stanzas_.empty()); | |
| } | |
| TEST_F(XmppStreamParserTest, FullStartElement) { | |
| parser_->ParseData("<foo bar=\"baz\" quux=\"1\">"); | |
| EXPECT_TRUE(stream_started_); | |
| EXPECT_EQ("foo", stream_start_node_name_); | |
| const std::map<std::string, std::string> expected_attrs{{"bar", "baz"}, | |
| {"quux", "1"}}; | |
| EXPECT_EQ(expected_attrs, stream_start_node_attributes_); | |
| } | |
| TEST_F(XmppStreamParserTest, PartialStartElement) { | |
| parser_->ParseData("<foo bar=\"baz"); | |
| EXPECT_FALSE(stream_started_); | |
| EXPECT_TRUE(stream_start_node_name_.empty()); | |
| EXPECT_TRUE(stream_start_node_attributes_.empty()); | |
| EXPECT_TRUE(stanzas_.empty()); | |
| parser_->ParseData("\" quux"); | |
| EXPECT_FALSE(stream_started_); | |
| parser_->ParseData("=\"1\">"); | |
| EXPECT_TRUE(stream_started_); | |
| EXPECT_EQ("foo", stream_start_node_name_); | |
| const std::map<std::string, std::string> expected_attrs{{"bar", "baz"}, | |
| {"quux", "1"}}; | |
| EXPECT_EQ(expected_attrs, stream_start_node_attributes_); | |
| } | |
| TEST_F(XmppStreamParserTest, VariableLengthPackets) { | |
| std::string value; | |
| const std::string xml_data = kXmppStreamData; | |
| const std::map<std::string, std::string> expected_stream_attrs{ | |
| {"from", "clouddevices.gserviceaccount.com"}, | |
| {"id", "76EEB8FDB4495558"}, | |
| {"version", "1.0"}, | |
| {"xmlns:stream", "http://etherx.jabber.org/streams"}, | |
| {"xmlns", "jabber:client"}}; | |
| // Try splitting the data into pieces from 1 character in size to the whole | |
| // data block and verify that we still can parse the whole message correctly. | |
| // Here |step| is the size of each individual data chunk. | |
| for (size_t step = 1; step <= xml_data.size(); step++) { | |
| // Feed each individual chunk to the parser and hope it can piece everything | |
| // together correctly. | |
| for (size_t pos = 0; pos < xml_data.size(); pos += step) { | |
| parser_->ParseData(xml_data.substr(pos, step)); | |
| } | |
| EXPECT_TRUE(stream_started_); | |
| EXPECT_EQ("stream:stream", stream_start_node_name_); | |
| EXPECT_EQ(expected_stream_attrs, stream_start_node_attributes_); | |
| EXPECT_EQ(2u, stanzas_.size()); | |
| const XmlNode* stanza1 = stanzas_[0].get(); | |
| EXPECT_EQ("stream:features", stanza1->name()); | |
| ASSERT_EQ(2u, stanza1->children().size()); | |
| const XmlNode* child1 = stanza1->children()[0].get(); | |
| EXPECT_EQ("starttls", child1->name()); | |
| ASSERT_EQ(1u, child1->children().size()); | |
| EXPECT_EQ("required", child1->children()[0]->name()); | |
| const XmlNode* child2 = stanza1->children()[1].get(); | |
| EXPECT_EQ("mechanisms", child2->name()); | |
| ASSERT_EQ(2u, child2->children().size()); | |
| EXPECT_EQ("mechanism", child2->children()[0]->name()); | |
| EXPECT_EQ("X-OAUTH2", child2->children()[0]->text()); | |
| EXPECT_EQ("mechanism", child2->children()[1]->name()); | |
| EXPECT_EQ("X-GOOGLE-TOKEN", child2->children()[1]->text()); | |
| const XmlNode* stanza2 = stanzas_[1].get(); | |
| EXPECT_EQ("message", stanza2->name()); | |
| ASSERT_EQ(2u, stanza2->attributes().size()); | |
| EXPECT_TRUE(stanza2->GetAttribute("from", &value)); | |
| EXPECT_EQ( | |
| "cloud-devices@clouddevices.google.com/" | |
| "srvenc-xgbCfg9hX6tCpxoMYsExqg==", | |
| value); | |
| EXPECT_TRUE(stanza2->GetAttribute("to", &value)); | |
| EXPECT_EQ( | |
| "4783f652b387449fc52a76f9a16e616f@clouddevices.gserviceaccount." | |
| "com/5A85ED9C", | |
| value); | |
| ASSERT_EQ(1u, stanza2->children().size()); | |
| const XmlNode* child = stanza2->children().back().get(); | |
| EXPECT_EQ("push:push", child->name()); | |
| ASSERT_EQ(2u, child->attributes().size()); | |
| EXPECT_TRUE(child->GetAttribute("channel", &value)); | |
| EXPECT_EQ("cloud_devices", value); | |
| EXPECT_TRUE(child->GetAttribute("xmlns:push", &value)); | |
| EXPECT_EQ("google:push", value); | |
| ASSERT_EQ(2u, child->children().size()); | |
| child1 = child->children()[0].get(); | |
| EXPECT_EQ("push:recipient", child1->name()); | |
| ASSERT_EQ(1u, child1->attributes().size()); | |
| EXPECT_TRUE(child1->GetAttribute("to", &value)); | |
| EXPECT_EQ( | |
| "4783f652b387449fc52a76f9a16e616f@clouddevices.gserviceaccount." | |
| "com", | |
| value); | |
| EXPECT_TRUE(child1->children().empty()); | |
| child2 = child->children()[1].get(); | |
| EXPECT_EQ("push:data", child2->name()); | |
| EXPECT_TRUE(child2->attributes().empty()); | |
| EXPECT_TRUE(child2->children().empty()); | |
| const std::string expected_data = | |
| "eyJraW5kIjoiY2xvdWRkZXZpY2VzI25vdGlmaWNh" | |
| "dGlvbiIsInR5cGUiOiJDT01NQU5EX0NSRUFURUQiLCJjb21tYW5kSWQiOiIwNWE3MTA5MC" | |
| "1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01NjExLWI5ODAtOTkyMy0y" | |
| "Njc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgtYzM3NC05NjA1LTFlNG" | |
| "FjYmE0NGYzZiIsImNvbW1hbmQiOnsia2luZCI6ImNsb3VkZGV2aWNlcyNjb21tYW5kIiwi" | |
| "aWQiOiIwNWE3MTA5MC1hZWE4LWMzNzQtOTYwNS0xZTRhY2JhNDRmM2Y4OTAzZmM3Yy01Nj" | |
| "ExLWI5ODAtOTkyMy0yNjc2YjYwYzkxMGMiLCJkZXZpY2VJZCI6IjA1YTcxMDkwLWFlYTgt" | |
| "YzM3NC05NjA1LTFlNGFjYmE0NGYzZiIsIm5hbWUiOiJiYXNlLl9qdW1wIiwic3RhdGUiOi" | |
| "JxdWV1ZWQiLCJlcnJvciI6eyJhcmd1bWVudHMiOltdfSwiY3JlYXRpb25UaW1lTXMiOiIx" | |
| "NDMxNTY0NDY4MjI3IiwiZXhwaXJhdGlvblRpbWVNcyI6IjE0MzE1NjgwNjgyMjciLCJleH" | |
| "BpcmF0aW9uVGltZW91dE1zIjoiMzYwMDAwMCJ9fQ=="; | |
| EXPECT_EQ(expected_data, child2->text()); | |
| } | |
| } | |
| } // namespace weave |