Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Chromium OS 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 | |
Vitaly Buka | 912b698 | 2015-07-06 11:13:03 -0700 | [diff] [blame] | 5 | #include "libweave/src/notification/xmpp_channel.h" |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 6 | |
| 7 | #include <string> |
| 8 | |
| 9 | #include <base/bind.h> |
Vitaly Buka | 3ab6f6e | 2015-09-24 13:16:16 -0700 | [diff] [blame^] | 10 | #include <weave/network_provider.h> |
Vitaly Buka | f9630fb | 2015-08-12 21:15:40 -0700 | [diff] [blame] | 11 | #include <weave/task_runner.h> |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 12 | |
Vitaly Buka | 0f80f7c | 2015-08-13 00:57:25 -0700 | [diff] [blame] | 13 | #include "libweave/src/backoff_entry.h" |
Vitaly Buka | 7d55639 | 2015-08-13 20:06:48 -0700 | [diff] [blame] | 14 | #include "libweave/src/data_encoding.h" |
Vitaly Buka | 912b698 | 2015-07-06 11:13:03 -0700 | [diff] [blame] | 15 | #include "libweave/src/notification/notification_delegate.h" |
| 16 | #include "libweave/src/notification/notification_parser.h" |
| 17 | #include "libweave/src/notification/xml_node.h" |
Vitaly Buka | a04405e | 2015-08-13 18:28:14 -0700 | [diff] [blame] | 18 | #include "libweave/src/privet/openssl_utils.h" |
Vitaly Buka | 912b698 | 2015-07-06 11:13:03 -0700 | [diff] [blame] | 19 | #include "libweave/src/utils.h" |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 20 | |
Vitaly Buka | b6f015a | 2015-07-09 14:59:23 -0700 | [diff] [blame] | 21 | namespace weave { |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 22 | |
| 23 | namespace { |
| 24 | |
| 25 | std::string BuildXmppStartStreamCommand() { |
| 26 | return "<stream:stream to='clouddevices.gserviceaccount.com' " |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 27 | "xmlns:stream='http://etherx.jabber.org/streams' " |
| 28 | "xml:lang='*' version='1.0' xmlns='jabber:client'>"; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 29 | } |
| 30 | |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 31 | std::string BuildXmppAuthenticateCommand(const std::string& account, |
| 32 | const std::string& token) { |
Vitaly Buka | a04405e | 2015-08-13 18:28:14 -0700 | [diff] [blame] | 33 | std::vector<uint8_t> credentials; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 34 | credentials.push_back(0); |
| 35 | credentials.insert(credentials.end(), account.begin(), account.end()); |
| 36 | credentials.push_back(0); |
| 37 | credentials.insert(credentials.end(), token.begin(), token.end()); |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 38 | std::string msg = |
| 39 | "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' " |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 40 | "mechanism='X-OAUTH2' auth:service='oauth2' " |
| 41 | "auth:allow-non-google-login='true' " |
| 42 | "auth:client-uses-full-bind-result='true' " |
| 43 | "xmlns:auth='http://www.google.com/talk/protocol/auth'>" + |
Vitaly Buka | 7d55639 | 2015-08-13 20:06:48 -0700 | [diff] [blame] | 44 | Base64Encode(credentials) + "</auth>"; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 45 | return msg; |
| 46 | } |
| 47 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 48 | // Backoff policy. |
| 49 | // Note: In order to ensure a minimum of 20 seconds between server errors, |
| 50 | // we have a 30s +- 10s (33%) jitter initial backoff. |
Vitaly Buka | 0f80f7c | 2015-08-13 00:57:25 -0700 | [diff] [blame] | 51 | const BackoffEntry::Policy kDefaultBackoffPolicy = { |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 52 | // Number of initial errors (in sequence) to ignore before applying |
| 53 | // exponential back-off rules. |
| 54 | 0, |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 55 | |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 56 | // Initial delay for exponential back-off in ms. |
| 57 | 30 * 1000, // 30 seconds. |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 58 | |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 59 | // Factor by which the waiting time will be multiplied. |
| 60 | 2, |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 61 | |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 62 | // Fuzzing percentage. ex: 10% will spread requests randomly |
| 63 | // between 90%-100% of the calculated time. |
| 64 | 0.33, // 33%. |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 65 | |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 66 | // Maximum amount of time we are willing to delay our request in ms. |
| 67 | 10 * 60 * 1000, // 10 minutes. |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 68 | |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 69 | // Time to keep an entry from being discarded even when it |
| 70 | // has no significant state, -1 to never discard. |
| 71 | -1, |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 72 | |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 73 | // Don't use initial delay unless the last request was an error. |
| 74 | false, |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 75 | }; |
| 76 | |
| 77 | const char kDefaultXmppHost[] = "talk.google.com"; |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 78 | const uint16_t kDefaultXmppPort = 5223; |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 79 | |
| 80 | // Used for keeping connection alive. |
| 81 | const int kRegularPingIntervalSeconds = 60; |
| 82 | const int kRegularPingTimeoutSeconds = 30; |
| 83 | |
| 84 | // Used for diagnostic when connectivity changed. |
| 85 | const int kAgressivePingIntervalSeconds = 5; |
| 86 | const int kAgressivePingTimeoutSeconds = 10; |
| 87 | |
| 88 | const int kConnectingTimeoutAfterNetChangeSeconds = 30; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 89 | |
| 90 | } // namespace |
| 91 | |
Vitaly Buka | 10c69ec | 2015-08-12 16:17:16 -0700 | [diff] [blame] | 92 | XmppChannel::XmppChannel(const std::string& account, |
| 93 | const std::string& access_token, |
Vitaly Buka | f9630fb | 2015-08-12 21:15:40 -0700 | [diff] [blame] | 94 | TaskRunner* task_runner, |
Vitaly Buka | 3ab6f6e | 2015-09-24 13:16:16 -0700 | [diff] [blame^] | 95 | NetworkProvider* network) |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 96 | : account_{account}, |
| 97 | access_token_{access_token}, |
Vitaly Buka | 10c69ec | 2015-08-12 16:17:16 -0700 | [diff] [blame] | 98 | network_{network}, |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 99 | backoff_entry_{&kDefaultBackoffPolicy}, |
Vitaly Buka | f9630fb | 2015-08-12 21:15:40 -0700 | [diff] [blame] | 100 | task_runner_{task_runner}, |
| 101 | iq_stanza_handler_{new IqStanzaHandler{this, task_runner}} { |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 102 | read_socket_data_.resize(4096); |
Vitaly Buka | 387b4f4 | 2015-07-30 23:55:13 -0700 | [diff] [blame] | 103 | if (network) { |
Vitaly Buka | 3ab6f6e | 2015-09-24 13:16:16 -0700 | [diff] [blame^] | 104 | network->AddConnectionChangedCallback(base::Bind( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 105 | &XmppChannel::OnConnectivityChanged, weak_ptr_factory_.GetWeakPtr())); |
| 106 | } |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 107 | } |
| 108 | |
| 109 | void XmppChannel::OnMessageRead(size_t size) { |
| 110 | std::string msg(read_socket_data_.data(), size); |
Vitaly Buka | 60d4509 | 2015-09-14 11:28:58 -0700 | [diff] [blame] | 111 | VLOG(2) << "Received XMPP packet: '" << msg << "'"; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 112 | read_pending_ = false; |
Vitaly Buka | 60d4509 | 2015-09-14 11:28:58 -0700 | [diff] [blame] | 113 | |
| 114 | if (!size) |
| 115 | return Restart(); |
| 116 | |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 117 | stream_parser_.ParseData(msg); |
| 118 | WaitForMessage(); |
| 119 | } |
| 120 | |
| 121 | void XmppChannel::OnStreamStart(const std::string& node_name, |
| 122 | std::map<std::string, std::string> attributes) { |
| 123 | VLOG(2) << "XMPP stream start: " << node_name; |
| 124 | } |
| 125 | |
| 126 | void XmppChannel::OnStreamEnd(const std::string& node_name) { |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 127 | VLOG(2) << "XMPP stream ended: " << node_name; |
Alex Vakulenko | 94fe16c | 2015-06-23 12:30:11 -0700 | [diff] [blame] | 128 | Stop(); |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 129 | if (IsConnected()) { |
| 130 | // If we had a fully-established connection, restart it now. |
| 131 | // However, if the connection has never been established yet (e.g. |
| 132 | // authorization failed), do not restart right now. Wait till we get |
| 133 | // new credentials. |
Vitaly Buka | 823fdda | 2015-08-13 00:33:00 -0700 | [diff] [blame] | 134 | task_runner_->PostDelayedTask( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 135 | FROM_HERE, |
Vitaly Buka | 823fdda | 2015-08-13 00:33:00 -0700 | [diff] [blame] | 136 | base::Bind(&XmppChannel::Restart, task_ptr_factory_.GetWeakPtr()), {}); |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 137 | } else if (delegate_) { |
| 138 | delegate_->OnPermanentFailure(); |
| 139 | } |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 140 | } |
| 141 | |
| 142 | void XmppChannel::OnStanza(std::unique_ptr<XmlNode> stanza) { |
| 143 | // Handle stanza asynchronously, since XmppChannel::OnStanza() is a callback |
| 144 | // from expat XML parser and some stanza could cause the XMPP stream to be |
| 145 | // reset and the parser to be re-initialized. We don't want to destroy the |
| 146 | // parser while it is performing a callback invocation. |
Vitaly Buka | 823fdda | 2015-08-13 00:33:00 -0700 | [diff] [blame] | 147 | task_runner_->PostDelayedTask( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 148 | FROM_HERE, |
| 149 | base::Bind(&XmppChannel::HandleStanza, task_ptr_factory_.GetWeakPtr(), |
Vitaly Buka | 823fdda | 2015-08-13 00:33:00 -0700 | [diff] [blame] | 150 | base::Passed(std::move(stanza))), |
| 151 | {}); |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 152 | } |
| 153 | |
| 154 | void XmppChannel::HandleStanza(std::unique_ptr<XmlNode> stanza) { |
| 155 | VLOG(2) << "XMPP stanza received: " << stanza->ToString(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 156 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 157 | switch (state_) { |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 158 | case XmppState::kConnected: |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 159 | if (stanza->name() == "stream:features") { |
| 160 | auto children = stanza->FindChildren("mechanisms/mechanism", false); |
| 161 | for (const auto& child : children) { |
Alex Vakulenko | 0444c60 | 2015-05-22 10:57:09 -0700 | [diff] [blame] | 162 | if (child->text() == "X-OAUTH2") { |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 163 | state_ = XmppState::kAuthenticationStarted; |
| 164 | SendMessage(BuildXmppAuthenticateCommand(account_, access_token_)); |
| 165 | return; |
| 166 | } |
| 167 | } |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 168 | } |
| 169 | break; |
| 170 | case XmppState::kAuthenticationStarted: |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 171 | if (stanza->name() == "success") { |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 172 | state_ = XmppState::kStreamRestartedPostAuthentication; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 173 | RestartXmppStream(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 174 | return; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 175 | } else if (stanza->name() == "failure") { |
| 176 | if (stanza->FindFirstChild("not-authorized", false)) { |
| 177 | state_ = XmppState::kAuthenticationFailed; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 178 | return; |
| 179 | } |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 180 | } |
| 181 | break; |
| 182 | case XmppState::kStreamRestartedPostAuthentication: |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 183 | if (stanza->name() == "stream:features" && |
| 184 | stanza->FindFirstChild("bind", false)) { |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 185 | state_ = XmppState::kBindSent; |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 186 | iq_stanza_handler_->SendRequest( |
| 187 | "set", "", "", "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>", |
| 188 | base::Bind(&XmppChannel::OnBindCompleted, |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 189 | task_ptr_factory_.GetWeakPtr()), |
| 190 | base::Bind(&XmppChannel::Restart, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 191 | return; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 192 | } |
| 193 | break; |
| 194 | default: |
Alex Vakulenko | 6e3c30e | 2015-05-21 17:39:25 -0700 | [diff] [blame] | 195 | if (stanza->name() == "message") { |
| 196 | HandleMessageStanza(std::move(stanza)); |
| 197 | return; |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 198 | } else if (stanza->name() == "iq") { |
| 199 | if (!iq_stanza_handler_->HandleIqStanza(std::move(stanza))) { |
| 200 | LOG(ERROR) << "Failed to handle IQ stanza"; |
| 201 | CloseStream(); |
| 202 | } |
| 203 | return; |
Alex Vakulenko | 6e3c30e | 2015-05-21 17:39:25 -0700 | [diff] [blame] | 204 | } |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 205 | LOG(INFO) << "Unexpected XMPP stanza ignored: " << stanza->ToString(); |
| 206 | return; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 207 | } |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 208 | // Something bad happened. Close the stream and start over. |
| 209 | LOG(ERROR) << "Error condition occurred handling stanza: " |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 210 | << stanza->ToString() << " in state: " << static_cast<int>(state_); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 211 | CloseStream(); |
| 212 | } |
| 213 | |
| 214 | void XmppChannel::CloseStream() { |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 215 | SendMessage("</stream:stream>"); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 216 | } |
| 217 | |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 218 | void XmppChannel::OnBindCompleted(std::unique_ptr<XmlNode> reply) { |
| 219 | if (reply->GetAttributeOrEmpty("type") != "result") { |
| 220 | CloseStream(); |
| 221 | return; |
| 222 | } |
| 223 | const XmlNode* jid_node = reply->FindFirstChild("bind/jid", false); |
| 224 | if (!jid_node) { |
| 225 | LOG(ERROR) << "XMPP Bind response is missing JID"; |
| 226 | CloseStream(); |
| 227 | return; |
| 228 | } |
| 229 | |
| 230 | jid_ = jid_node->text(); |
| 231 | state_ = XmppState::kSessionStarted; |
| 232 | iq_stanza_handler_->SendRequest( |
| 233 | "set", "", "", "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>", |
| 234 | base::Bind(&XmppChannel::OnSessionEstablished, |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 235 | task_ptr_factory_.GetWeakPtr()), |
| 236 | base::Bind(&XmppChannel::Restart, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 237 | } |
| 238 | |
| 239 | void XmppChannel::OnSessionEstablished(std::unique_ptr<XmlNode> reply) { |
| 240 | if (reply->GetAttributeOrEmpty("type") != "result") { |
| 241 | CloseStream(); |
| 242 | return; |
| 243 | } |
| 244 | state_ = XmppState::kSubscribeStarted; |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 245 | std::string body = |
| 246 | "<subscribe xmlns='google:push'>" |
| 247 | "<item channel='cloud_devices' from=''/></subscribe>"; |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 248 | iq_stanza_handler_->SendRequest( |
| 249 | "set", "", account_, body, |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 250 | base::Bind(&XmppChannel::OnSubscribed, task_ptr_factory_.GetWeakPtr()), |
| 251 | base::Bind(&XmppChannel::Restart, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 252 | } |
| 253 | |
| 254 | void XmppChannel::OnSubscribed(std::unique_ptr<XmlNode> reply) { |
| 255 | if (reply->GetAttributeOrEmpty("type") != "result") { |
| 256 | CloseStream(); |
| 257 | return; |
| 258 | } |
| 259 | state_ = XmppState::kSubscribed; |
| 260 | if (delegate_) |
| 261 | delegate_->OnConnected(GetName()); |
| 262 | } |
| 263 | |
Alex Vakulenko | 6e3c30e | 2015-05-21 17:39:25 -0700 | [diff] [blame] | 264 | void XmppChannel::HandleMessageStanza(std::unique_ptr<XmlNode> stanza) { |
| 265 | const XmlNode* node = stanza->FindFirstChild("push:push/push:data", true); |
| 266 | if (!node) { |
| 267 | LOG(WARNING) << "XMPP message stanza is missing <push:data> element"; |
| 268 | return; |
| 269 | } |
| 270 | std::string data = node->text(); |
| 271 | std::string json_data; |
Vitaly Buka | 7d55639 | 2015-08-13 20:06:48 -0700 | [diff] [blame] | 272 | if (!Base64Decode(data, &json_data)) { |
Alex Vakulenko | 6e3c30e | 2015-05-21 17:39:25 -0700 | [diff] [blame] | 273 | LOG(WARNING) << "Failed to decode base64-encoded message payload: " << data; |
| 274 | return; |
| 275 | } |
| 276 | |
| 277 | VLOG(2) << "XMPP push notification data: " << json_data; |
| 278 | auto json_dict = LoadJsonDict(json_data, nullptr); |
| 279 | if (json_dict && delegate_) |
| 280 | ParseNotificationJson(*json_dict, delegate_); |
| 281 | } |
| 282 | |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 283 | void XmppChannel::CreateSslSocket() { |
| 284 | CHECK(!stream_); |
| 285 | state_ = XmppState::kConnecting; |
Vitaly Buka | 4ebd329 | 2015-09-23 18:04:17 -0700 | [diff] [blame] | 286 | LOG(INFO) << "Starting XMPP connection to " << kDefaultXmppHost << ":" |
| 287 | << kDefaultXmppPort; |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 288 | |
| 289 | network_->OpenSslSocket( |
| 290 | kDefaultXmppHost, kDefaultXmppPort, |
| 291 | base::Bind(&XmppChannel::OnSslSocketReady, |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 292 | task_ptr_factory_.GetWeakPtr()), |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 293 | base::Bind(&XmppChannel::OnSslError, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | 2684b51 | 2015-05-19 13:42:10 -0700 | [diff] [blame] | 294 | } |
| 295 | |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 296 | void XmppChannel::OnSslSocketReady(std::unique_ptr<Stream> stream) { |
| 297 | CHECK(XmppState::kConnecting == state_); |
| 298 | backoff_entry_.InformOfRequest(true); |
| 299 | stream_ = std::move(stream); |
| 300 | state_ = XmppState::kConnected; |
Alex Vakulenko | 2684b51 | 2015-05-19 13:42:10 -0700 | [diff] [blame] | 301 | RestartXmppStream(); |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 302 | ScheduleRegularPing(); |
Alex Vakulenko | 2684b51 | 2015-05-19 13:42:10 -0700 | [diff] [blame] | 303 | } |
| 304 | |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 305 | void XmppChannel::OnSslError(const Error* error) { |
Alex Vakulenko | 2684b51 | 2015-05-19 13:42:10 -0700 | [diff] [blame] | 306 | LOG(ERROR) << "TLS handshake failed. Restarting XMPP connection"; |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 307 | backoff_entry_.InformOfRequest(false); |
| 308 | |
| 309 | LOG(INFO) << "Delaying connection to XMPP server for " |
| 310 | << backoff_entry_.GetTimeUntilRelease(); |
| 311 | task_runner_->PostDelayedTask( |
| 312 | FROM_HERE, |
| 313 | base::Bind(&XmppChannel::CreateSslSocket, task_ptr_factory_.GetWeakPtr()), |
| 314 | backoff_entry_.GetTimeUntilRelease()); |
Alex Vakulenko | 2684b51 | 2015-05-19 13:42:10 -0700 | [diff] [blame] | 315 | } |
| 316 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 317 | void XmppChannel::SendMessage(const std::string& message) { |
Alex Vakulenko | e63dde6 | 2015-07-08 10:58:01 -0700 | [diff] [blame] | 318 | CHECK(stream_) << "No XMPP socket stream available"; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 319 | if (write_pending_) { |
| 320 | queued_write_data_ += message; |
| 321 | return; |
| 322 | } |
| 323 | write_socket_data_ = queued_write_data_ + message; |
| 324 | queued_write_data_.clear(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 325 | VLOG(2) << "Sending XMPP message: " << message; |
| 326 | |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 327 | write_pending_ = true; |
Vitaly Buka | da98728 | 2015-09-23 16:50:40 -0700 | [diff] [blame] | 328 | stream_->Write( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 329 | write_socket_data_.data(), write_socket_data_.size(), |
| 330 | base::Bind(&XmppChannel::OnMessageSent, task_ptr_factory_.GetWeakPtr()), |
Vitaly Buka | 0684cfe | 2015-09-15 21:32:37 -0700 | [diff] [blame] | 331 | base::Bind(&XmppChannel::OnWriteError, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 332 | } |
| 333 | |
| 334 | void XmppChannel::OnMessageSent() { |
Vitaly Buka | 0801a1f | 2015-08-14 10:03:46 -0700 | [diff] [blame] | 335 | ErrorPtr error; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 336 | write_pending_ = false; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 337 | if (queued_write_data_.empty()) { |
| 338 | WaitForMessage(); |
| 339 | } else { |
| 340 | SendMessage(std::string{}); |
| 341 | } |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 342 | } |
| 343 | |
| 344 | void XmppChannel::WaitForMessage() { |
Alex Vakulenko | 94fe16c | 2015-06-23 12:30:11 -0700 | [diff] [blame] | 345 | if (read_pending_ || !stream_) |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 346 | return; |
| 347 | |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 348 | read_pending_ = true; |
Vitaly Buka | da98728 | 2015-09-23 16:50:40 -0700 | [diff] [blame] | 349 | stream_->Read( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 350 | read_socket_data_.data(), read_socket_data_.size(), |
| 351 | base::Bind(&XmppChannel::OnMessageRead, task_ptr_factory_.GetWeakPtr()), |
Vitaly Buka | 0684cfe | 2015-09-15 21:32:37 -0700 | [diff] [blame] | 352 | base::Bind(&XmppChannel::OnReadError, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 353 | } |
| 354 | |
Vitaly Buka | 0801a1f | 2015-08-14 10:03:46 -0700 | [diff] [blame] | 355 | void XmppChannel::OnReadError(const Error* error) { |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 356 | read_pending_ = false; |
| 357 | Restart(); |
| 358 | } |
| 359 | |
Vitaly Buka | 0801a1f | 2015-08-14 10:03:46 -0700 | [diff] [blame] | 360 | void XmppChannel::OnWriteError(const Error* error) { |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 361 | write_pending_ = false; |
| 362 | Restart(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 363 | } |
| 364 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 365 | std::string XmppChannel::GetName() const { |
| 366 | return "xmpp"; |
| 367 | } |
| 368 | |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 369 | bool XmppChannel::IsConnected() const { |
| 370 | return state_ == XmppState::kSubscribed; |
| 371 | } |
| 372 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 373 | void XmppChannel::AddChannelParameters(base::DictionaryValue* channel_json) { |
| 374 | // No extra parameters needed for XMPP. |
| 375 | } |
| 376 | |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 377 | void XmppChannel::Restart() { |
Alex Vakulenko | bfdd310 | 2015-09-08 15:10:54 -0700 | [diff] [blame] | 378 | LOG(INFO) << "Restarting XMPP"; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 379 | Stop(); |
| 380 | Start(delegate_); |
| 381 | } |
| 382 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 383 | void XmppChannel::Start(NotificationDelegate* delegate) { |
| 384 | CHECK(state_ == XmppState::kNotStarted); |
| 385 | delegate_ = delegate; |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 386 | |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 387 | CreateSslSocket(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 388 | } |
| 389 | |
| 390 | void XmppChannel::Stop() { |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 391 | if (IsConnected() && delegate_) |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 392 | delegate_->OnDisconnected(); |
| 393 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 394 | task_ptr_factory_.InvalidateWeakPtrs(); |
| 395 | ping_ptr_factory_.InvalidateWeakPtrs(); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 396 | |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 397 | stream_.reset(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 398 | state_ = XmppState::kNotStarted; |
| 399 | } |
| 400 | |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 401 | void XmppChannel::RestartXmppStream() { |
| 402 | stream_parser_.Reset(); |
Vitaly Buka | da98728 | 2015-09-23 16:50:40 -0700 | [diff] [blame] | 403 | stream_->CancelPendingOperations(); |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 404 | read_pending_ = false; |
| 405 | write_pending_ = false; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 406 | SendMessage(BuildXmppStartStreamCommand()); |
| 407 | } |
| 408 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 409 | void XmppChannel::SchedulePing(base::TimeDelta interval, |
| 410 | base::TimeDelta timeout) { |
| 411 | VLOG(1) << "Next XMPP ping in " << interval << " with timeout " << timeout; |
| 412 | ping_ptr_factory_.InvalidateWeakPtrs(); |
Vitaly Buka | f9630fb | 2015-08-12 21:15:40 -0700 | [diff] [blame] | 413 | task_runner_->PostDelayedTask( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 414 | FROM_HERE, base::Bind(&XmppChannel::PingServer, |
| 415 | ping_ptr_factory_.GetWeakPtr(), timeout), |
| 416 | interval); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 417 | } |
| 418 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 419 | void XmppChannel::ScheduleRegularPing() { |
| 420 | SchedulePing(base::TimeDelta::FromSeconds(kRegularPingIntervalSeconds), |
| 421 | base::TimeDelta::FromSeconds(kRegularPingTimeoutSeconds)); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 422 | } |
| 423 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 424 | void XmppChannel::ScheduleFastPing() { |
| 425 | SchedulePing(base::TimeDelta::FromSeconds(kAgressivePingIntervalSeconds), |
| 426 | base::TimeDelta::FromSeconds(kAgressivePingTimeoutSeconds)); |
| 427 | } |
| 428 | |
| 429 | void XmppChannel::PingServer(base::TimeDelta timeout) { |
| 430 | VLOG(1) << "Sending XMPP ping"; |
Alex Vakulenko | e63dde6 | 2015-07-08 10:58:01 -0700 | [diff] [blame] | 431 | if (!IsConnected()) { |
| 432 | LOG(WARNING) << "XMPP channel is not connected"; |
| 433 | Restart(); |
| 434 | return; |
| 435 | } |
| 436 | |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 437 | // Send an XMPP Ping request as defined in XEP-0199 extension: |
| 438 | // http://xmpp.org/extensions/xep-0199.html |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 439 | iq_stanza_handler_->SendRequestWithCustomTimeout( |
| 440 | "get", jid_, account_, "<ping xmlns='urn:xmpp:ping'/>", timeout, |
| 441 | base::Bind(&XmppChannel::OnPingResponse, task_ptr_factory_.GetWeakPtr(), |
| 442 | base::Time::Now()), |
| 443 | base::Bind(&XmppChannel::OnPingTimeout, task_ptr_factory_.GetWeakPtr(), |
| 444 | base::Time::Now())); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 445 | } |
| 446 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 447 | void XmppChannel::OnPingResponse(base::Time sent_time, |
| 448 | std::unique_ptr<XmlNode> reply) { |
| 449 | VLOG(1) << "XMPP response received after " << (base::Time::Now() - sent_time); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 450 | // Ping response received from server. Everything seems to be in order. |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 451 | // Reschedule with default intervals. |
| 452 | ScheduleRegularPing(); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 453 | } |
| 454 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 455 | void XmppChannel::OnPingTimeout(base::Time sent_time) { |
| 456 | LOG(WARNING) << "XMPP channel seems to be disconnected. Ping timed out after " |
| 457 | << (base::Time::Now() - sent_time); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 458 | Restart(); |
| 459 | } |
| 460 | |
Vitaly Buka | bf6840a | 2015-09-21 13:38:56 -0700 | [diff] [blame] | 461 | void XmppChannel::OnConnectivityChanged() { |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 462 | if (state_ == XmppState::kNotStarted) |
| 463 | return; |
| 464 | |
| 465 | if (state_ == XmppState::kConnecting && |
| 466 | backoff_entry_.GetTimeUntilRelease() < |
| 467 | base::TimeDelta::FromSeconds( |
| 468 | kConnectingTimeoutAfterNetChangeSeconds)) { |
| 469 | VLOG(1) << "Next reconnect in " << backoff_entry_.GetTimeUntilRelease(); |
| 470 | return; |
| 471 | } |
| 472 | |
| 473 | ScheduleFastPing(); |
| 474 | } |
| 475 | |
Vitaly Buka | b6f015a | 2015-07-09 14:59:23 -0700 | [diff] [blame] | 476 | } // namespace weave |