| // 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 |