1 // Copyright 2015 The Weave Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/notification/xmpp_channel.h"
6 
7 #include <algorithm>
8 #include <queue>
9 
10 #include <gtest/gtest.h>
11 #include <weave/provider/test/fake_task_runner.h>
12 #include <weave/provider/test/mock_network.h>
13 #include <weave/test/fake_stream.h>
14 
15 #include "src/bind_lambda.h"
16 
17 using testing::_;
18 using testing::Invoke;
19 using testing::Return;
20 using testing::StrictMock;
21 using testing::WithArgs;
22 
23 namespace weave {
24 
25 namespace {
26 
27 constexpr char kAccountName[] = "Account@Name";
28 constexpr char kAccessToken[] = "AccessToken";
29 constexpr char kEndpoint[] = "endpoint:456";
30 
31 constexpr char kStartStreamMessage[] =
32     "<stream:stream to='clouddevices.gserviceaccount.com' "
33     "xmlns:stream='http://etherx.jabber.org/streams' xml:lang='*' "
34     "version='1.0' xmlns='jabber:client'>";
35 constexpr char kStartStreamResponse[] =
36     "<stream:stream from=\"clouddevices.gserviceaccount.com\" "
37     "id=\"0CCF520913ABA04B\" version=\"1.0\" "
38     "xmlns:stream=\"http://etherx.jabber.org/streams\" "
39     "xmlns=\"jabber:client\">";
40 constexpr char kAuthenticationMessage[] =
41     "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='X-OAUTH2' "
42     "auth:service='oauth2' auth:allow-non-google-login='true' "
43     "auth:client-uses-full-bind-result='true' "
44     "xmlns:auth='http://www.google.com/talk/protocol/auth'>"
45     "AEFjY291bnRATmFtZQBBY2Nlc3NUb2tlbg==</auth>";
46 constexpr char kConnectedResponse[] =
47     "<stream:features><mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"
48     "<mechanism>X-OAUTH2</mechanism>"
49     "<mechanism>X-GOOGLE-TOKEN</mechanism></mechanisms></stream:features>";
50 constexpr char kAuthenticationSucceededResponse[] =
51     "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>";
52 constexpr char kAuthenticationFailedResponse[] =
53     "<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><not-authorized/>"
54     "</failure>";
55 constexpr char kRestartStreamResponse[] =
56     "<stream:features><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"
57     "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>"
58     "</stream:features>";
59 constexpr char kBindResponse[] =
60     "<iq id=\"1\" type=\"result\">"
61     "<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">"
62     "<jid>110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com"
63     "/19853128</jid></bind></iq>";
64 constexpr char kSessionResponse[] = "<iq type=\"result\" id=\"2\"/>";
65 constexpr char kSubscribedResponse[] =
66     "<iq to=\""
67     "110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com/"
68     "19853128\" from=\""
69     "110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com\" "
70     "id=\"3\" type=\"result\"/>";
71 constexpr char kBindMessage[] =
72     "<iq id='1' type='set'><bind "
73     "xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>";
74 constexpr char kSessionMessage[] =
75     "<iq id='2' type='set'><session "
76     "xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>";
77 constexpr char kSubscribeMessage[] =
78     "<iq id='3' type='set' to='Account@Name'>"
79     "<subscribe xmlns='google:push'><item channel='cloud_devices' from=''/>"
80     "</subscribe></iq>";
81 
82 }  // namespace
83 
84 class FakeXmppChannel : public XmppChannel {
85  public:
FakeXmppChannel(provider::TaskRunner * task_runner,provider::Network * network)86   explicit FakeXmppChannel(provider::TaskRunner* task_runner,
87                            provider::Network* network)
88       : XmppChannel{kAccountName, kAccessToken, kEndpoint, task_runner,
89                     network},
90         stream_{new test::FakeStream{task_runner_}},
91         fake_stream_{stream_.get()} {}
92 
Connect(const base::Callback<void (std::unique_ptr<Stream>,ErrorPtr error)> & callback)93   void Connect(const base::Callback<void(std::unique_ptr<Stream>,
94                                          ErrorPtr error)>& callback) {
95     callback.Run(std::move(stream_), nullptr);
96   }
97 
state() const98   XmppState state() const { return state_; }
set_state(XmppState state)99   void set_state(XmppState state) { state_ = state; }
100 
SchedulePing(base::TimeDelta interval,base::TimeDelta timeout)101   void SchedulePing(base::TimeDelta interval,
102                     base::TimeDelta timeout) override {}
103 
ExpectWritePacketString(base::TimeDelta delta,const std::string & data)104   void ExpectWritePacketString(base::TimeDelta delta, const std::string& data) {
105     fake_stream_->ExpectWritePacketString(delta, data);
106   }
107 
AddReadPacketString(base::TimeDelta delta,const std::string & data)108   void AddReadPacketString(base::TimeDelta delta, const std::string& data) {
109     fake_stream_->AddReadPacketString(delta, data);
110   }
111 
112   std::unique_ptr<test::FakeStream> stream_;
113   test::FakeStream* fake_stream_{nullptr};
114 };
115 
116 class MockNetwork : public provider::test::MockNetwork {
117  public:
MockNetwork()118   MockNetwork() {
119     EXPECT_CALL(*this, AddConnectionChangedCallback(_))
120         .WillRepeatedly(Return());
121   }
122 };
123 
124 class XmppChannelTest : public ::testing::Test {
125  protected:
XmppChannelTest()126   XmppChannelTest() {
127     EXPECT_CALL(network_, OpenSslSocket("endpoint", 456, _))
128         .WillOnce(
129             WithArgs<2>(Invoke(&xmpp_client_, &FakeXmppChannel::Connect)));
130   }
131 
StartStream()132   void StartStream() {
133     xmpp_client_.ExpectWritePacketString({}, kStartStreamMessage);
134     xmpp_client_.AddReadPacketString({}, kStartStreamResponse);
135     xmpp_client_.Start(nullptr);
136     RunUntil(XmppChannel::XmppState::kConnected);
137   }
138 
StartWithState(XmppChannel::XmppState state)139   void StartWithState(XmppChannel::XmppState state) {
140     StartStream();
141     xmpp_client_.set_state(state);
142   }
143 
RunUntil(XmppChannel::XmppState st)144   void RunUntil(XmppChannel::XmppState st) {
145     for (size_t n = 15; n && xmpp_client_.state() != st; --n)
146       task_runner_.RunOnce();
147     EXPECT_EQ(st, xmpp_client_.state());
148   }
149 
150   StrictMock<provider::test::FakeTaskRunner> task_runner_;
151   StrictMock<MockNetwork> network_;
152   FakeXmppChannel xmpp_client_{&task_runner_, &network_};
153 };
154 
TEST_F(XmppChannelTest,StartStream)155 TEST_F(XmppChannelTest, StartStream) {
156   EXPECT_EQ(XmppChannel::XmppState::kNotStarted, xmpp_client_.state());
157   xmpp_client_.ExpectWritePacketString({}, kStartStreamMessage);
158   xmpp_client_.Start(nullptr);
159   RunUntil(XmppChannel::XmppState::kConnected);
160 }
161 
TEST_F(XmppChannelTest,HandleStartedResponse)162 TEST_F(XmppChannelTest, HandleStartedResponse) {
163   StartStream();
164 }
165 
TEST_F(XmppChannelTest,HandleConnected)166 TEST_F(XmppChannelTest, HandleConnected) {
167   StartWithState(XmppChannel::XmppState::kConnected);
168   xmpp_client_.AddReadPacketString({}, kConnectedResponse);
169   xmpp_client_.ExpectWritePacketString({}, kAuthenticationMessage);
170   RunUntil(XmppChannel::XmppState::kAuthenticationStarted);
171 }
172 
TEST_F(XmppChannelTest,HandleAuthenticationSucceededResponse)173 TEST_F(XmppChannelTest, HandleAuthenticationSucceededResponse) {
174   StartWithState(XmppChannel::XmppState::kAuthenticationStarted);
175   xmpp_client_.AddReadPacketString({}, kAuthenticationSucceededResponse);
176   xmpp_client_.ExpectWritePacketString({}, kStartStreamMessage);
177   RunUntil(XmppChannel::XmppState::kStreamRestartedPostAuthentication);
178 }
179 
TEST_F(XmppChannelTest,HandleAuthenticationFailedResponse)180 TEST_F(XmppChannelTest, HandleAuthenticationFailedResponse) {
181   StartWithState(XmppChannel::XmppState::kAuthenticationStarted);
182   xmpp_client_.AddReadPacketString({}, kAuthenticationFailedResponse);
183   RunUntil(XmppChannel::XmppState::kAuthenticationFailed);
184 }
185 
TEST_F(XmppChannelTest,HandleStreamRestartedResponse)186 TEST_F(XmppChannelTest, HandleStreamRestartedResponse) {
187   StartWithState(XmppChannel::XmppState::kStreamRestartedPostAuthentication);
188   xmpp_client_.AddReadPacketString({}, kRestartStreamResponse);
189   xmpp_client_.ExpectWritePacketString({}, kBindMessage);
190   RunUntil(XmppChannel::XmppState::kBindSent);
191   EXPECT_TRUE(xmpp_client_.jid().empty());
192 
193   xmpp_client_.AddReadPacketString({}, kBindResponse);
194   xmpp_client_.ExpectWritePacketString({}, kSessionMessage);
195   RunUntil(XmppChannel::XmppState::kSessionStarted);
196   EXPECT_EQ(
197       "110cc78f78d7032cc7bf2c6e14c1fa7d@clouddevices.gserviceaccount.com"
198       "/19853128",
199       xmpp_client_.jid());
200 
201   xmpp_client_.AddReadPacketString({}, kSessionResponse);
202   xmpp_client_.ExpectWritePacketString({}, kSubscribeMessage);
203   RunUntil(XmppChannel::XmppState::kSubscribeStarted);
204 
205   xmpp_client_.AddReadPacketString({}, kSubscribedResponse);
206   RunUntil(XmppChannel::XmppState::kSubscribed);
207 }
208 
209 }  // namespace weave
210