buffet: Add proper XML parsing for XMPP streams

For large command notifications, a single XMPP stanza can be split
into a number of TCP packets. In order to handle large stanzas and
in order to help with implementing TLS support for XMPP, added an
expat-based XML parser on top of XMPP stream, to make sure that
the stanzas are processed when all the data for a complete XML tag
has arrived.

BUG=brillo:458
TEST=`FEATURES=test emerge-link buffet`

Change-Id: I560f40dafb31c6e6b9e645d232453338ee4fbbef
Reviewed-on: https://chromium-review.googlesource.com/271592
Trybot-Ready: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/notification/xml_node_unittest.cc b/buffet/notification/xml_node_unittest.cc
new file mode 100644
index 0000000..8b2ed5c
--- /dev/null
+++ b/buffet/notification/xml_node_unittest.cc
@@ -0,0 +1,211 @@
+// Copyright 2015 The Chromium OS 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 "buffet/notification/xml_node.h"
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include "buffet/notification/xmpp_stream_parser.h"
+
+namespace buffet {
+namespace {
+
+class XmlParser : public XmppStreamParser::Delegate {
+ public:
+  std::unique_ptr<XmlNode> Parse(const std::string& xml) {
+    parser_.ParseData(xml);
+    return std::move(node_);
+  }
+
+ private:
+  // Overrides from XmppStreamParser::Delegate.
+  void OnStreamStart(const std::string& node_name,
+                     std::map<std::string, std::string> attributes) override {
+    node_.reset(new XmlNode{node_name, std::move(attributes)});
+  }
+
+  void OnStreamEnd(const std::string& node_name) override {}
+
+  void OnStanza(std::unique_ptr<XmlNode> stanza) override {
+    node_->AddChild(std::move(stanza));
+  }
+
+  std::unique_ptr<XmlNode> node_;
+  XmppStreamParser parser_{this};
+};
+
+}  // anonymous namespace
+
+class XmlNodeTest : public testing::Test {
+ public:
+  void SetUp() override {
+    node_.reset(new XmlNode{"test_node",
+                            {{"attr1", "val1"}, {"attr2", "val2"}}});
+  }
+
+  // Accessor helpers for private members of XmlNode.
+  static const XmlNode* GetParent(const XmlNode& node) {
+    return node.parent_;
+  }
+
+  static void SetText(XmlNode* node, const std::string& text) {
+    node->SetText(text);
+  }
+
+  static void AppendText(XmlNode* node, const std::string& text) {
+    node->AppendText(text);
+  }
+
+  void CreateNodeTree() {
+    node_ = XmlParser{}.Parse(R"(
+        <top>
+          <node1 id="1"><node2 id="2"><node3 id="3"/></node2></node1>
+          <node2 id="4"><node3 id="5"/></node2>
+          <node3 id="6"/>
+          <node2 id="7"><node4 id="8"><node3 id="9"/></node4></node2>
+        </top>
+        )");
+  }
+
+  std::unique_ptr<XmlNode> node_;
+};
+
+TEST_F(XmlNodeTest, DefaultConstruction) {
+  EXPECT_EQ("test_node", node_->name());
+  EXPECT_TRUE(node_->children().empty());
+  EXPECT_TRUE(node_->text().empty());
+}
+
+TEST_F(XmlNodeTest, SetText) {
+  SetText(node_.get(), "foobar");
+  EXPECT_EQ("foobar", node_->text());
+}
+
+TEST_F(XmlNodeTest, AppendText) {
+  SetText(node_.get(), "foobar");
+  AppendText(node_.get(), "-baz");
+  EXPECT_EQ("foobar-baz", node_->text());
+}
+
+TEST_F(XmlNodeTest, AddChild) {
+  std::unique_ptr<XmlNode> child{new XmlNode{"child", {}}};
+  node_->AddChild(std::move(child));
+  EXPECT_EQ(1u, node_->children().size());
+  EXPECT_EQ("child", node_->children().front()->name());
+  EXPECT_EQ(node_.get(), GetParent(*node_->children().front().get()));
+}
+
+TEST_F(XmlNodeTest, Attributes) {
+  const std::map<std::string, std::string> expected_attrs{
+      {"attr1", "val1"},
+      {"attr2", "val2"}
+  };
+  EXPECT_EQ(expected_attrs, node_->attributes());
+  std::string attr = "bar";
+  EXPECT_FALSE(node_->GetAttribute("foo", &attr));
+  EXPECT_EQ("bar", attr);  // Shouldn't be changed by failed GetAttribute().
+  EXPECT_TRUE(node_->GetAttribute("attr1", &attr));
+  EXPECT_EQ("val1", attr);
+  EXPECT_TRUE(node_->GetAttribute("attr2", &attr));
+  EXPECT_EQ("val2", attr);
+
+  XmlNode new_node{"node", {}};
+  EXPECT_FALSE(new_node.GetAttribute("attr1", &attr));
+}
+
+TEST_F(XmlNodeTest, FindFirstChild_SingleNode) {
+  CreateNodeTree();
+  const XmlNode* node = node_->FindFirstChild("node3", false);
+  ASSERT_NE(nullptr, node);
+  EXPECT_EQ("node3", node->name());
+  EXPECT_EQ("6", node->GetAttributeOrEmpty("id"));
+
+  node = node_->FindFirstChild("node3", true);
+  ASSERT_NE(nullptr, node);
+  EXPECT_EQ("node3", node->name());
+  EXPECT_EQ("3", node->GetAttributeOrEmpty("id"));
+
+  node = node_->FindFirstChild("foo", true);
+  ASSERT_EQ(nullptr, node);
+}
+
+TEST_F(XmlNodeTest, FindFirstChild_Path) {
+  CreateNodeTree();
+  const XmlNode* node = node_->FindFirstChild("node2/node3", false);
+  ASSERT_NE(nullptr, node);
+  EXPECT_EQ("node3", node->name());
+  EXPECT_EQ("5", node->GetAttributeOrEmpty("id"));
+
+  node = node_->FindFirstChild("node2/node3", true);
+  ASSERT_NE(nullptr, node);
+  EXPECT_EQ("node3", node->name());
+  EXPECT_EQ("3", node->GetAttributeOrEmpty("id"));
+
+  node = node_->FindFirstChild("node1/node2/node3", false);
+  ASSERT_NE(nullptr, node);
+  EXPECT_EQ("node3", node->name());
+  EXPECT_EQ("3", node->GetAttributeOrEmpty("id"));
+
+  node = node_->FindFirstChild("node1/node2/node3", true);
+  ASSERT_NE(nullptr, node);
+  EXPECT_EQ("node3", node->name());
+  EXPECT_EQ("3", node->GetAttributeOrEmpty("id"));
+
+  node = node_->FindFirstChild("foo/node3", true);
+  ASSERT_EQ(nullptr, node);
+}
+
+TEST_F(XmlNodeTest, FindChildren_SingleNode) {
+  CreateNodeTree();
+  auto children = node_->FindChildren("node3", false);
+  ASSERT_EQ(1u, children.size());
+  EXPECT_EQ("node3", children[0]->name());
+  EXPECT_EQ("6", children[0]->GetAttributeOrEmpty("id"));
+
+  children = node_->FindChildren("node3", true);
+  ASSERT_EQ(4u, children.size());
+  EXPECT_EQ("node3", children[0]->name());
+  EXPECT_EQ("3", children[0]->GetAttributeOrEmpty("id"));
+  EXPECT_EQ("node3", children[1]->name());
+  EXPECT_EQ("5", children[1]->GetAttributeOrEmpty("id"));
+  EXPECT_EQ("node3", children[2]->name());
+  EXPECT_EQ("6", children[2]->GetAttributeOrEmpty("id"));
+  EXPECT_EQ("node3", children[3]->name());
+  EXPECT_EQ("9", children[3]->GetAttributeOrEmpty("id"));
+}
+
+TEST_F(XmlNodeTest, FindChildren_Path) {
+  CreateNodeTree();
+  auto children = node_->FindChildren("node2/node3", false);
+  ASSERT_EQ(1u, children.size());
+  EXPECT_EQ("node3", children[0]->name());
+  EXPECT_EQ("5", children[0]->GetAttributeOrEmpty("id"));
+
+  children = node_->FindChildren("node2/node3", true);
+  ASSERT_EQ(2u, children.size());
+  EXPECT_EQ("node3", children[0]->name());
+  EXPECT_EQ("3", children[0]->GetAttributeOrEmpty("id"));
+  EXPECT_EQ("node3", children[1]->name());
+  EXPECT_EQ("5", children[1]->GetAttributeOrEmpty("id"));
+
+  children = node_->FindChildren("node1/node2/node3", false);
+  ASSERT_EQ(1u, children.size());
+  EXPECT_EQ("node3", children[0]->name());
+  EXPECT_EQ("3", children[0]->GetAttributeOrEmpty("id"));
+
+  children = node_->FindChildren("node1/node2/node3", true);
+  ASSERT_EQ(1u, children.size());
+  EXPECT_EQ("node3", children[0]->name());
+  EXPECT_EQ("3", children[0]->GetAttributeOrEmpty("id"));
+
+  children = node_->FindChildren("foo/bar", false);
+  ASSERT_EQ(0u, children.size());
+
+  children = node_->FindChildren("node2/baz", false);
+  ASSERT_EQ(0u, children.size());
+}
+
+}  // namespace buffet