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