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/xmpp_stream_parser.h b/buffet/notification/xmpp_stream_parser.h
new file mode 100644
index 0000000..6bb39c7
--- /dev/null
+++ b/buffet/notification/xmpp_stream_parser.h
@@ -0,0 +1,86 @@
+// 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.
+
+#ifndef BUFFET_NOTIFICATION_XMPP_STREAM_PARSER_H_
+#define BUFFET_NOTIFICATION_XMPP_STREAM_PARSER_H_
+
+#include <expat.h>
+
+#include <map>
+#include <memory>
+#include <stack>
+#include <string>
+
+#include <base/macros.h>
+
+namespace buffet {
+
+class XmlNode;
+
+// A simple XML stream parser. As the XML data is being read from a data source
+// (for example, a socket), XmppStreamParser::ParseData() should be called.
+// This method parses the provided XML data chunk and if it finds complete
+// XML elements, it will call internal OnOpenElement(), OnCloseElement() and
+// OnCharData() member functions. These will track the element nesting level.
+// When a top-level element starts, the parser will call Delegate::OnStreamStart
+// method. Once this happens, every complete XML element (including its children
+// if they are present) will trigger Delegate::OnStanze() callback.
+// Finally, when top-level element is closed, Delegate::OnStreamEnd() is called.
+// This class is specifically tailored to XMPP streams which look like this:
+// B: <stream:stream to='example.com' xmlns='jabber:client' version='1.0'>
+// S: <presence><show/></presence>
+// S: <message to='foo'><body/></message>
+// S: <iq to='bar'><query/></iq>
+// S: ...
+// E: </stream:stream>
+// Here, "B:" will trigger OnStreamStart(), "S:" will result in OnStanza() and
+// "E:" will result in OnStreamEnd().
+class XmppStreamParser final {
+ public:
+ // Delegate interface that interested parties implement to receive
+ // notifications of stream opening/closing and on new stanzas arriving.
+ class Delegate {
+ public:
+ virtual void OnStreamStart(
+ const std::string& node_name,
+ std::map<std::string, std::string> attributes) = 0;
+ virtual void OnStreamEnd(const std::string& node_name) = 0;
+ virtual void OnStanza(std::unique_ptr<XmlNode> stanza) = 0;
+ };
+
+ explicit XmppStreamParser(Delegate* delegate);
+ ~XmppStreamParser();
+
+ // Parses additional XML data received from an input stream.
+ void ParseData(const std::string& data);
+
+ // Resets the parser to expect the top-level stream node again.
+ void Reset();
+
+ private:
+ // Raw expat callbacks.
+ static void HandleElementStart(void* user_data,
+ const XML_Char* element,
+ const XML_Char** attr);
+ static void HandleElementEnd(void* user_data, const XML_Char* element);
+ static void HandleCharData(void* user_data, const char* content, int length);
+
+ // Reinterpreted callbacks from expat with some data pre-processed.
+ void OnOpenElement(const std::string& node_name,
+ std::map<std::string, std::string> attributes);
+ void OnCloseElement(const std::string& node_name);
+ void OnCharData(const std::string& text);
+
+ Delegate* delegate_;
+ XML_Parser parser_{nullptr};
+ bool started_{false};
+ std::stack<std::unique_ptr<XmlNode>> node_stack_;
+
+ DISALLOW_COPY_AND_ASSIGN(XmppStreamParser);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_NOTIFICATION_XMPP_STREAM_PARSER_H_
+