buffet: Add an XMPP client class
This is a relatively simple XMPP client class, which at this point,
is only used to keep the XMPP connection open to GCD.
TEST=FEATURES=test emerge buffet
BUG=brillo:95
Change-Id: I2e7c8d7352892bd7c94e630cc7872f32f2298ae4
Reviewed-on: https://chromium-review.googlesource.com/248351
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Nathan Bullock <nathanbullock@google.com>
Tested-by: Nathan Bullock <nathanbullock@google.com>
diff --git a/buffet/xmpp/xmpp_client_unittest.cc b/buffet/xmpp/xmpp_client_unittest.cc
new file mode 100644
index 0000000..9736349
--- /dev/null
+++ b/buffet/xmpp/xmpp_client_unittest.cc
@@ -0,0 +1,208 @@
+// 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/device_registration_info.h"
+
+#include <base/values.h>
+#include <chromeos/key_value_store.h>
+#include <chromeos/http/curl_api.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "buffet/xmpp/xmpp_client.h"
+#include "buffet/xmpp/xmpp_connection.h"
+
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::_;
+
+namespace buffet {
+
+namespace {
+
+constexpr char kAccountName[] = "Account@Name";
+constexpr char kAccessToken[] = "AccessToken";
+
+constexpr char kStartStreamResponse[] =
+ "<stream:stream from=\"clouddevices.gserviceaccount.com\" "
+ "id=\"0CCF520913ABA04B\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">"
+ "<stream:features><starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">"
+ "<required/></starttls><mechanisms "
+ "xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><mechanism>X-OAUTH2</mechanism>"
+ "<mechanism>X-GOOGLE-TOKEN</mechanism></mechanisms></stream:features>";
+constexpr char kAuthenticationResponse[] =
+ "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>";
+constexpr char kRestartStreamResponse[] =
+ "<stream:stream from=\"clouddevices.gserviceaccount.com\" "
+ "id=\"BE7D34E0B7589E2A\" version=\"1.0\" "
+ "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ "xmlns=\"jabber:client\">"
+ "<stream:features><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"
+ "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
+ "</stream:features>";
+constexpr char kBindResponse[] =
+ "<iq id=\"0\" type=\"result\">"
+ "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
+ "<jid>110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com"
+ "/19853128</jid></bind></iq>";
+constexpr char kSessionResponse[] =
+ "<iq type=\"result\" id=\"1\"/>";
+constexpr char kSubscribedResponse[] =
+ "<iq to=\""
+ "110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com/"
+ "19853128\" from=\""
+ "110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com\" "
+ "id=\"pushsubscribe1\" type=\"result\"/>";
+
+constexpr char kStartStreamMessage[] =
+ "<stream:stream to='clouddevices.gserviceaccount.com' "
+ "xmlns:stream='http://etherx.jabber.org/streams' xml:lang='*' "
+ "version='1.0' xmlns='jabber:client'>";
+constexpr char kAuthenticationMessage[] =
+ "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='X-OAUTH2' "
+ "auth:service='oauth2' auth:allow-non-google-login='true' "
+ "auth:client-uses-full-bind-result='true' "
+ "xmlns:auth='http://www.google.com/talk/protocol/auth'>"
+ "AEFjY291bnRATmFtZQBBY2Nlc3NUb2tlbg==</auth>";
+constexpr char kBindMessage[] =
+ "<iq type='set' id='0'><bind "
+ "xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>";
+constexpr char kSessionMessage[] =
+ "<iq type='set' id='1'><session "
+ "xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>";
+constexpr char kSubscribeMessage[] =
+ "<iq type='set' to='Account@Name' id='pushsubscribe1'>"
+ "<subscribe xmlns='google:push'><item channel='cloud_devices' from=''/>"
+ "</subscribe></iq>";
+
+class MockXmppConnection : public XmppConnection {
+ public:
+ MockXmppConnection() = default;
+
+ MOCK_CONST_METHOD1(Read, bool(std::string* msg));
+ MOCK_CONST_METHOD1(Write, bool(const std::string& msg));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockXmppConnection);
+};
+
+} // namespace
+
+class TestHelper {
+ public:
+ static void SetState(XmppClient* client, XmppClient::XmppState state) {
+ client->state_ = state;
+ }
+
+ static XmppClient::XmppState GetState(const XmppClient& client) {
+ return client.state_;
+ }
+
+ static XmppConnection* GetConnection(const XmppClient& client) {
+ return client.connection_.get();
+ }
+};
+
+class XmppClientTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ std::unique_ptr<XmppConnection> connection(new MockXmppConnection());
+ xmpp_client_.reset(new XmppClient(kAccountName, kAccessToken,
+ std::move(connection)));
+ connection_ = static_cast<MockXmppConnection*>(
+ TestHelper::GetConnection(*xmpp_client_));
+ }
+
+ std::unique_ptr<XmppClient> xmpp_client_;
+ MockXmppConnection* connection_; // xmpp_client_ owns this.
+};
+
+TEST_F(XmppClientTest, StartStream) {
+ EXPECT_EQ(TestHelper::GetState(*xmpp_client_),
+ XmppClient::XmppState::kNotStarted);
+ EXPECT_CALL(*connection_, Write(kStartStreamMessage))
+ .WillOnce(Return(true));
+ xmpp_client_->StartStream();
+ EXPECT_EQ(TestHelper::GetState(*xmpp_client_),
+ XmppClient::XmppState::kStarted);
+}
+
+TEST_F(XmppClientTest, HandleStartedResponse) {
+ TestHelper::SetState(xmpp_client_.get(),
+ XmppClient::XmppState::kStarted);
+ EXPECT_CALL(*connection_, Read(_))
+ .WillOnce(DoAll(SetArgPointee<0>(kStartStreamResponse), Return(true)));
+ EXPECT_CALL(*connection_, Write(kAuthenticationMessage))
+ .WillOnce(Return(true));
+ xmpp_client_->Read();
+ EXPECT_EQ(TestHelper::GetState(*xmpp_client_),
+ XmppClient::XmppState::kAuthenticationStarted);
+}
+
+TEST_F(XmppClientTest, HandleAuthenticationResponse) {
+ TestHelper::SetState(
+ xmpp_client_.get(),
+ XmppClient::XmppState::kAuthenticationStarted);
+ EXPECT_CALL(*connection_, Read(_))
+ .WillOnce(DoAll(SetArgPointee<0>(kAuthenticationResponse), Return(true)));
+ EXPECT_CALL(*connection_, Write(kStartStreamMessage))
+ .WillOnce(Return(true));
+ xmpp_client_->Read();
+ EXPECT_EQ(TestHelper::GetState(*xmpp_client_),
+ XmppClient::XmppState::kStreamRestartedPostAuthentication);
+}
+
+TEST_F(XmppClientTest, HandleStreamRestartedResponse) {
+ TestHelper::SetState(
+ xmpp_client_.get(),
+ XmppClient::XmppState::kStreamRestartedPostAuthentication);
+ EXPECT_CALL(*connection_, Read(_))
+ .WillOnce(DoAll(SetArgPointee<0>(kRestartStreamResponse), Return(true)));
+ EXPECT_CALL(*connection_, Write(kBindMessage))
+ .WillOnce(Return(true));
+ xmpp_client_->Read();
+ EXPECT_EQ(TestHelper::GetState(*xmpp_client_),
+ XmppClient::XmppState::kBindSent);
+}
+
+TEST_F(XmppClientTest, HandleBindResponse) {
+ TestHelper::SetState(xmpp_client_.get(),
+ XmppClient::XmppState::kBindSent);
+ EXPECT_CALL(*connection_, Read(_))
+ .WillOnce(DoAll(SetArgPointee<0>(kBindResponse), Return(true)));
+ EXPECT_CALL(*connection_, Write(kSessionMessage))
+ .WillOnce(Return(true));
+ xmpp_client_->Read();
+ EXPECT_EQ(TestHelper::GetState(*xmpp_client_),
+ XmppClient::XmppState::kSessionStarted);
+}
+
+TEST_F(XmppClientTest, HandleSessionResponse) {
+ TestHelper::SetState(xmpp_client_.get(),
+ XmppClient::XmppState::kSessionStarted);
+ EXPECT_CALL(*connection_, Read(_))
+ .WillOnce(DoAll(SetArgPointee<0>(kSessionResponse), Return(true)));
+ EXPECT_CALL(*connection_, Write(kSubscribeMessage))
+ .WillOnce(Return(true));
+ xmpp_client_->Read();
+ EXPECT_EQ(TestHelper::GetState(*xmpp_client_),
+ XmppClient::XmppState::kSubscribeStarted);
+}
+
+TEST_F(XmppClientTest, HandleSubscribeResponse) {
+ TestHelper::SetState(xmpp_client_.get(),
+ XmppClient::XmppState::kSubscribeStarted);
+ EXPECT_CALL(*connection_, Read(_))
+ .WillOnce(DoAll(SetArgPointee<0>(kSubscribedResponse), Return(true)));
+ EXPECT_CALL(*connection_, Write(_))
+ .Times(0);
+ xmpp_client_->Read();
+ EXPECT_EQ(TestHelper::GetState(*xmpp_client_),
+ XmppClient::XmppState::kSubscribed);
+}
+
+} // namespace buffet