Vitaly Buka | 4615e0d | 2015-10-14 15:35:12 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Weave Authors. All rights reserved. |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Stefan Sauer | 2d16dfa | 2015-09-25 17:08:35 +0200 | [diff] [blame] | 5 | #include "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 | 1e36367 | 2015-09-25 14:01:16 -0700 | [diff] [blame] | 10 | #include <weave/provider/network.h> |
| 11 | #include <weave/provider/task_runner.h> |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 12 | |
Stefan Sauer | 2d16dfa | 2015-09-25 17:08:35 +0200 | [diff] [blame] | 13 | #include "src/backoff_entry.h" |
| 14 | #include "src/data_encoding.h" |
| 15 | #include "src/notification/notification_delegate.h" |
| 16 | #include "src/notification/notification_parser.h" |
| 17 | #include "src/notification/xml_node.h" |
| 18 | #include "src/privet/openssl_utils.h" |
| 19 | #include "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 | 1e36367 | 2015-09-25 14:01:16 -0700 | [diff] [blame] | 94 | provider::TaskRunner* task_runner, |
| 95 | provider::Network* 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 | |
Vitaly Buka | 7476342 | 2015-10-11 00:39:52 -0700 | [diff] [blame] | 109 | void XmppChannel::OnMessageRead(size_t size, ErrorPtr error) { |
| 110 | read_pending_ = false; |
| 111 | if (error) |
| 112 | return Restart(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 113 | std::string msg(read_socket_data_.data(), size); |
Vitaly Buka | 60d4509 | 2015-09-14 11:28:58 -0700 | [diff] [blame] | 114 | VLOG(2) << "Received XMPP packet: '" << msg << "'"; |
Vitaly Buka | 60d4509 | 2015-09-14 11:28:58 -0700 | [diff] [blame] | 115 | |
| 116 | if (!size) |
| 117 | return Restart(); |
| 118 | |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 119 | stream_parser_.ParseData(msg); |
| 120 | WaitForMessage(); |
| 121 | } |
| 122 | |
| 123 | void XmppChannel::OnStreamStart(const std::string& node_name, |
| 124 | std::map<std::string, std::string> attributes) { |
| 125 | VLOG(2) << "XMPP stream start: " << node_name; |
| 126 | } |
| 127 | |
| 128 | void XmppChannel::OnStreamEnd(const std::string& node_name) { |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 129 | VLOG(2) << "XMPP stream ended: " << node_name; |
Alex Vakulenko | 94fe16c | 2015-06-23 12:30:11 -0700 | [diff] [blame] | 130 | Stop(); |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 131 | if (IsConnected()) { |
| 132 | // If we had a fully-established connection, restart it now. |
| 133 | // However, if the connection has never been established yet (e.g. |
| 134 | // authorization failed), do not restart right now. Wait till we get |
| 135 | // new credentials. |
Vitaly Buka | 823fdda | 2015-08-13 00:33:00 -0700 | [diff] [blame] | 136 | task_runner_->PostDelayedTask( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 137 | FROM_HERE, |
Vitaly Buka | 823fdda | 2015-08-13 00:33:00 -0700 | [diff] [blame] | 138 | base::Bind(&XmppChannel::Restart, task_ptr_factory_.GetWeakPtr()), {}); |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 139 | } else if (delegate_) { |
| 140 | delegate_->OnPermanentFailure(); |
| 141 | } |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 142 | } |
| 143 | |
| 144 | void XmppChannel::OnStanza(std::unique_ptr<XmlNode> stanza) { |
| 145 | // Handle stanza asynchronously, since XmppChannel::OnStanza() is a callback |
| 146 | // from expat XML parser and some stanza could cause the XMPP stream to be |
| 147 | // reset and the parser to be re-initialized. We don't want to destroy the |
| 148 | // parser while it is performing a callback invocation. |
Vitaly Buka | 823fdda | 2015-08-13 00:33:00 -0700 | [diff] [blame] | 149 | task_runner_->PostDelayedTask( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 150 | FROM_HERE, |
| 151 | base::Bind(&XmppChannel::HandleStanza, task_ptr_factory_.GetWeakPtr(), |
Vitaly Buka | 823fdda | 2015-08-13 00:33:00 -0700 | [diff] [blame] | 152 | base::Passed(std::move(stanza))), |
| 153 | {}); |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 154 | } |
| 155 | |
| 156 | void XmppChannel::HandleStanza(std::unique_ptr<XmlNode> stanza) { |
| 157 | VLOG(2) << "XMPP stanza received: " << stanza->ToString(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 158 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 159 | switch (state_) { |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 160 | case XmppState::kConnected: |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 161 | if (stanza->name() == "stream:features") { |
| 162 | auto children = stanza->FindChildren("mechanisms/mechanism", false); |
| 163 | for (const auto& child : children) { |
Alex Vakulenko | 0444c60 | 2015-05-22 10:57:09 -0700 | [diff] [blame] | 164 | if (child->text() == "X-OAUTH2") { |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 165 | state_ = XmppState::kAuthenticationStarted; |
| 166 | SendMessage(BuildXmppAuthenticateCommand(account_, access_token_)); |
| 167 | return; |
| 168 | } |
| 169 | } |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 170 | } |
| 171 | break; |
| 172 | case XmppState::kAuthenticationStarted: |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 173 | if (stanza->name() == "success") { |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 174 | state_ = XmppState::kStreamRestartedPostAuthentication; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 175 | RestartXmppStream(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 176 | return; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 177 | } else if (stanza->name() == "failure") { |
| 178 | if (stanza->FindFirstChild("not-authorized", false)) { |
| 179 | state_ = XmppState::kAuthenticationFailed; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 180 | return; |
| 181 | } |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 182 | } |
| 183 | break; |
| 184 | case XmppState::kStreamRestartedPostAuthentication: |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 185 | if (stanza->name() == "stream:features" && |
| 186 | stanza->FindFirstChild("bind", false)) { |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 187 | state_ = XmppState::kBindSent; |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 188 | iq_stanza_handler_->SendRequest( |
| 189 | "set", "", "", "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>", |
| 190 | base::Bind(&XmppChannel::OnBindCompleted, |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 191 | task_ptr_factory_.GetWeakPtr()), |
| 192 | base::Bind(&XmppChannel::Restart, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 193 | return; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 194 | } |
| 195 | break; |
| 196 | default: |
Alex Vakulenko | 6e3c30e | 2015-05-21 17:39:25 -0700 | [diff] [blame] | 197 | if (stanza->name() == "message") { |
| 198 | HandleMessageStanza(std::move(stanza)); |
| 199 | return; |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 200 | } else if (stanza->name() == "iq") { |
| 201 | if (!iq_stanza_handler_->HandleIqStanza(std::move(stanza))) { |
| 202 | LOG(ERROR) << "Failed to handle IQ stanza"; |
| 203 | CloseStream(); |
| 204 | } |
| 205 | return; |
Alex Vakulenko | 6e3c30e | 2015-05-21 17:39:25 -0700 | [diff] [blame] | 206 | } |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 207 | LOG(INFO) << "Unexpected XMPP stanza ignored: " << stanza->ToString(); |
| 208 | return; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 209 | } |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 210 | // Something bad happened. Close the stream and start over. |
| 211 | LOG(ERROR) << "Error condition occurred handling stanza: " |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 212 | << stanza->ToString() << " in state: " << static_cast<int>(state_); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 213 | CloseStream(); |
| 214 | } |
| 215 | |
| 216 | void XmppChannel::CloseStream() { |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 217 | SendMessage("</stream:stream>"); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 218 | } |
| 219 | |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 220 | void XmppChannel::OnBindCompleted(std::unique_ptr<XmlNode> reply) { |
| 221 | if (reply->GetAttributeOrEmpty("type") != "result") { |
| 222 | CloseStream(); |
| 223 | return; |
| 224 | } |
| 225 | const XmlNode* jid_node = reply->FindFirstChild("bind/jid", false); |
| 226 | if (!jid_node) { |
| 227 | LOG(ERROR) << "XMPP Bind response is missing JID"; |
| 228 | CloseStream(); |
| 229 | return; |
| 230 | } |
| 231 | |
| 232 | jid_ = jid_node->text(); |
| 233 | state_ = XmppState::kSessionStarted; |
| 234 | iq_stanza_handler_->SendRequest( |
| 235 | "set", "", "", "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>", |
| 236 | base::Bind(&XmppChannel::OnSessionEstablished, |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 237 | task_ptr_factory_.GetWeakPtr()), |
| 238 | base::Bind(&XmppChannel::Restart, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 239 | } |
| 240 | |
| 241 | void XmppChannel::OnSessionEstablished(std::unique_ptr<XmlNode> reply) { |
| 242 | if (reply->GetAttributeOrEmpty("type") != "result") { |
| 243 | CloseStream(); |
| 244 | return; |
| 245 | } |
| 246 | state_ = XmppState::kSubscribeStarted; |
Vitaly Buka | a647c85 | 2015-07-06 14:51:01 -0700 | [diff] [blame] | 247 | std::string body = |
| 248 | "<subscribe xmlns='google:push'>" |
| 249 | "<item channel='cloud_devices' from=''/></subscribe>"; |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 250 | iq_stanza_handler_->SendRequest( |
| 251 | "set", "", account_, body, |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 252 | base::Bind(&XmppChannel::OnSubscribed, task_ptr_factory_.GetWeakPtr()), |
| 253 | base::Bind(&XmppChannel::Restart, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | dea76b2 | 2015-06-01 13:18:06 -0700 | [diff] [blame] | 254 | } |
| 255 | |
| 256 | void XmppChannel::OnSubscribed(std::unique_ptr<XmlNode> reply) { |
| 257 | if (reply->GetAttributeOrEmpty("type") != "result") { |
| 258 | CloseStream(); |
| 259 | return; |
| 260 | } |
| 261 | state_ = XmppState::kSubscribed; |
| 262 | if (delegate_) |
| 263 | delegate_->OnConnected(GetName()); |
| 264 | } |
| 265 | |
Alex Vakulenko | 6e3c30e | 2015-05-21 17:39:25 -0700 | [diff] [blame] | 266 | void XmppChannel::HandleMessageStanza(std::unique_ptr<XmlNode> stanza) { |
| 267 | const XmlNode* node = stanza->FindFirstChild("push:push/push:data", true); |
| 268 | if (!node) { |
| 269 | LOG(WARNING) << "XMPP message stanza is missing <push:data> element"; |
| 270 | return; |
| 271 | } |
| 272 | std::string data = node->text(); |
| 273 | std::string json_data; |
Vitaly Buka | 7d55639 | 2015-08-13 20:06:48 -0700 | [diff] [blame] | 274 | if (!Base64Decode(data, &json_data)) { |
Alex Vakulenko | 6e3c30e | 2015-05-21 17:39:25 -0700 | [diff] [blame] | 275 | LOG(WARNING) << "Failed to decode base64-encoded message payload: " << data; |
| 276 | return; |
| 277 | } |
| 278 | |
| 279 | VLOG(2) << "XMPP push notification data: " << json_data; |
| 280 | auto json_dict = LoadJsonDict(json_data, nullptr); |
| 281 | if (json_dict && delegate_) |
Alex Vakulenko | e07c29d | 2015-10-22 10:31:12 -0700 | [diff] [blame] | 282 | ParseNotificationJson(*json_dict, delegate_, GetName()); |
Alex Vakulenko | 6e3c30e | 2015-05-21 17:39:25 -0700 | [diff] [blame] | 283 | } |
| 284 | |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 285 | void XmppChannel::CreateSslSocket() { |
| 286 | CHECK(!stream_); |
| 287 | state_ = XmppState::kConnecting; |
Vitaly Buka | 4ebd329 | 2015-09-23 18:04:17 -0700 | [diff] [blame] | 288 | LOG(INFO) << "Starting XMPP connection to " << kDefaultXmppHost << ":" |
| 289 | << kDefaultXmppPort; |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 290 | |
Vitaly Buka | 7476342 | 2015-10-11 00:39:52 -0700 | [diff] [blame] | 291 | network_->OpenSslSocket(kDefaultXmppHost, kDefaultXmppPort, |
| 292 | base::Bind(&XmppChannel::OnSslSocketReady, |
| 293 | task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | 2684b51 | 2015-05-19 13:42:10 -0700 | [diff] [blame] | 294 | } |
| 295 | |
Vitaly Buka | 7476342 | 2015-10-11 00:39:52 -0700 | [diff] [blame] | 296 | void XmppChannel::OnSslSocketReady(std::unique_ptr<Stream> stream, |
| 297 | ErrorPtr error) { |
| 298 | if (error) { |
| 299 | LOG(ERROR) << "TLS handshake failed. Restarting XMPP connection"; |
| 300 | backoff_entry_.InformOfRequest(false); |
| 301 | |
| 302 | LOG(INFO) << "Delaying connection to XMPP server for " |
| 303 | << backoff_entry_.GetTimeUntilRelease(); |
| 304 | return task_runner_->PostDelayedTask( |
| 305 | FROM_HERE, base::Bind(&XmppChannel::CreateSslSocket, |
| 306 | task_ptr_factory_.GetWeakPtr()), |
| 307 | backoff_entry_.GetTimeUntilRelease()); |
| 308 | } |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 309 | CHECK(XmppState::kConnecting == state_); |
| 310 | backoff_entry_.InformOfRequest(true); |
| 311 | stream_ = std::move(stream); |
| 312 | state_ = XmppState::kConnected; |
Alex Vakulenko | 2684b51 | 2015-05-19 13:42:10 -0700 | [diff] [blame] | 313 | RestartXmppStream(); |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 314 | ScheduleRegularPing(); |
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(), |
Vitaly Buka | 7476342 | 2015-10-11 00:39:52 -0700 | [diff] [blame] | 330 | base::Bind(&XmppChannel::OnMessageSent, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 331 | } |
| 332 | |
Vitaly Buka | 7476342 | 2015-10-11 00:39:52 -0700 | [diff] [blame] | 333 | void XmppChannel::OnMessageSent(ErrorPtr error) { |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 334 | write_pending_ = false; |
Vitaly Buka | 7476342 | 2015-10-11 00:39:52 -0700 | [diff] [blame] | 335 | if (error) |
| 336 | return Restart(); |
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(), |
Vitaly Buka | 7476342 | 2015-10-11 00:39:52 -0700 | [diff] [blame] | 351 | base::Bind(&XmppChannel::OnMessageRead, task_ptr_factory_.GetWeakPtr())); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 352 | } |
| 353 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 354 | std::string XmppChannel::GetName() const { |
| 355 | return "xmpp"; |
| 356 | } |
| 357 | |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 358 | bool XmppChannel::IsConnected() const { |
| 359 | return state_ == XmppState::kSubscribed; |
| 360 | } |
| 361 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 362 | void XmppChannel::AddChannelParameters(base::DictionaryValue* channel_json) { |
| 363 | // No extra parameters needed for XMPP. |
| 364 | } |
| 365 | |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 366 | void XmppChannel::Restart() { |
Alex Vakulenko | bfdd310 | 2015-09-08 15:10:54 -0700 | [diff] [blame] | 367 | LOG(INFO) << "Restarting XMPP"; |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 368 | Stop(); |
| 369 | Start(delegate_); |
| 370 | } |
| 371 | |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 372 | void XmppChannel::Start(NotificationDelegate* delegate) { |
| 373 | CHECK(state_ == XmppState::kNotStarted); |
| 374 | delegate_ = delegate; |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 375 | |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 376 | CreateSslSocket(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 377 | } |
| 378 | |
| 379 | void XmppChannel::Stop() { |
Alex Vakulenko | 6b028ae | 2015-05-29 09:38:59 -0700 | [diff] [blame] | 380 | if (IsConnected() && delegate_) |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 381 | delegate_->OnDisconnected(); |
| 382 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 383 | task_ptr_factory_.InvalidateWeakPtrs(); |
| 384 | ping_ptr_factory_.InvalidateWeakPtrs(); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 385 | |
Vitaly Buka | a4b3983 | 2015-09-09 02:11:03 -0700 | [diff] [blame] | 386 | stream_.reset(); |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 387 | state_ = XmppState::kNotStarted; |
| 388 | } |
| 389 | |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 390 | void XmppChannel::RestartXmppStream() { |
| 391 | stream_parser_.Reset(); |
Vitaly Buka | da98728 | 2015-09-23 16:50:40 -0700 | [diff] [blame] | 392 | stream_->CancelPendingOperations(); |
Alex Vakulenko | bf71f70 | 2015-05-18 14:30:56 -0700 | [diff] [blame] | 393 | read_pending_ = false; |
| 394 | write_pending_ = false; |
Alex Vakulenko | eedf3be | 2015-05-13 17:52:02 -0700 | [diff] [blame] | 395 | SendMessage(BuildXmppStartStreamCommand()); |
| 396 | } |
| 397 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 398 | void XmppChannel::SchedulePing(base::TimeDelta interval, |
| 399 | base::TimeDelta timeout) { |
| 400 | VLOG(1) << "Next XMPP ping in " << interval << " with timeout " << timeout; |
| 401 | ping_ptr_factory_.InvalidateWeakPtrs(); |
Vitaly Buka | f9630fb | 2015-08-12 21:15:40 -0700 | [diff] [blame] | 402 | task_runner_->PostDelayedTask( |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 403 | FROM_HERE, base::Bind(&XmppChannel::PingServer, |
| 404 | ping_ptr_factory_.GetWeakPtr(), timeout), |
| 405 | interval); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 406 | } |
| 407 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 408 | void XmppChannel::ScheduleRegularPing() { |
| 409 | SchedulePing(base::TimeDelta::FromSeconds(kRegularPingIntervalSeconds), |
| 410 | base::TimeDelta::FromSeconds(kRegularPingTimeoutSeconds)); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 411 | } |
| 412 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 413 | void XmppChannel::ScheduleFastPing() { |
| 414 | SchedulePing(base::TimeDelta::FromSeconds(kAgressivePingIntervalSeconds), |
| 415 | base::TimeDelta::FromSeconds(kAgressivePingTimeoutSeconds)); |
| 416 | } |
| 417 | |
| 418 | void XmppChannel::PingServer(base::TimeDelta timeout) { |
| 419 | VLOG(1) << "Sending XMPP ping"; |
Alex Vakulenko | e63dde6 | 2015-07-08 10:58:01 -0700 | [diff] [blame] | 420 | if (!IsConnected()) { |
| 421 | LOG(WARNING) << "XMPP channel is not connected"; |
| 422 | Restart(); |
| 423 | return; |
| 424 | } |
| 425 | |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 426 | // Send an XMPP Ping request as defined in XEP-0199 extension: |
| 427 | // http://xmpp.org/extensions/xep-0199.html |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 428 | iq_stanza_handler_->SendRequestWithCustomTimeout( |
| 429 | "get", jid_, account_, "<ping xmlns='urn:xmpp:ping'/>", timeout, |
| 430 | base::Bind(&XmppChannel::OnPingResponse, task_ptr_factory_.GetWeakPtr(), |
| 431 | base::Time::Now()), |
| 432 | base::Bind(&XmppChannel::OnPingTimeout, task_ptr_factory_.GetWeakPtr(), |
| 433 | base::Time::Now())); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 434 | } |
| 435 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 436 | void XmppChannel::OnPingResponse(base::Time sent_time, |
| 437 | std::unique_ptr<XmlNode> reply) { |
| 438 | VLOG(1) << "XMPP response received after " << (base::Time::Now() - sent_time); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 439 | // Ping response received from server. Everything seems to be in order. |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 440 | // Reschedule with default intervals. |
| 441 | ScheduleRegularPing(); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 442 | } |
| 443 | |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 444 | void XmppChannel::OnPingTimeout(base::Time sent_time) { |
| 445 | LOG(WARNING) << "XMPP channel seems to be disconnected. Ping timed out after " |
| 446 | << (base::Time::Now() - sent_time); |
Alex Vakulenko | 5cd7997 | 2015-06-01 13:21:18 -0700 | [diff] [blame] | 447 | Restart(); |
| 448 | } |
| 449 | |
Vitaly Buka | bf6840a | 2015-09-21 13:38:56 -0700 | [diff] [blame] | 450 | void XmppChannel::OnConnectivityChanged() { |
Vitaly Buka | 63cc3d2 | 2015-06-23 20:11:36 -0700 | [diff] [blame] | 451 | if (state_ == XmppState::kNotStarted) |
| 452 | return; |
| 453 | |
| 454 | if (state_ == XmppState::kConnecting && |
| 455 | backoff_entry_.GetTimeUntilRelease() < |
| 456 | base::TimeDelta::FromSeconds( |
| 457 | kConnectingTimeoutAfterNetChangeSeconds)) { |
| 458 | VLOG(1) << "Next reconnect in " << backoff_entry_.GetTimeUntilRelease(); |
| 459 | return; |
| 460 | } |
| 461 | |
| 462 | ScheduleFastPing(); |
| 463 | } |
| 464 | |
Vitaly Buka | b6f015a | 2015-07-09 14:59:23 -0700 | [diff] [blame] | 465 | } // namespace weave |