Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(490)

Side by Side Diff: components/copresence/rpc/rpc_handler.cc

Issue 2130803002: Deleting the copresence API. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: . Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium 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 "components/copresence/rpc/rpc_handler.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <utility>
11
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/logging.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 // TODO(ckehoe): time.h includes windows.h, which #defines DeviceCapabilities
19 // to DeviceCapabilitiesW. This breaks the pb.h headers below. For now,
20 // we fix this with an #undef.
21 #include "base/time/time.h"
22 #include "build/build_config.h"
23 #if defined(OS_WIN)
24 #undef DeviceCapabilities
25 #endif
26
27 #include "components/audio_modem/public/audio_modem_types.h"
28 #include "components/copresence/copresence_state_impl.h"
29 #include "components/copresence/copresence_switches.h"
30 #include "components/copresence/handlers/directive_handler.h"
31 #include "components/copresence/handlers/gcm_handler.h"
32 #include "components/copresence/proto/codes.pb.h"
33 #include "components/copresence/proto/data.pb.h"
34 #include "components/copresence/proto/rpcs.pb.h"
35 #include "components/copresence/public/copresence_constants.h"
36 #include "components/copresence/public/copresence_delegate.h"
37 #include "components/copresence/rpc/http_post.h"
38 #include "net/http/http_status_code.h"
39
40 using google::protobuf::MessageLite;
41
42 using audio_modem::AUDIBLE;
43 using audio_modem::AudioToken;
44 using audio_modem::INAUDIBLE;
45
46 // TODO(ckehoe): Return error messages for bad requests.
47
48 namespace copresence {
49
50 const char RpcHandler::kReportRequestRpcName[] = "report";
51
52 namespace {
53
54 const int kTokenLoggingSuffix = 5;
55 const int kInvalidTokenExpiryTimeMinutes = 10;
56 const int kMaxInvalidTokens = 10000;
57 const char kRegisterDeviceRpcName[] = "registerdevice";
58 const char kDefaultCopresenceServer[] =
59 "https://www.googleapis.com/copresence/v2/copresence";
60
61 // UrlSafe is defined as:
62 // '/' represented by a '_' and '+' represented by a '-'
63 // TODO(rkc): Move this to the wrapper.
64 std::string ToUrlSafe(std::string token) {
65 base::ReplaceChars(token, "+", "-", &token);
66 base::ReplaceChars(token, "/", "_", &token);
67 return token;
68 }
69
70 // Logging
71
72 // Checks for a copresence error. If there is one, logs it and returns true.
73 bool IsErrorStatus(const Status& status) {
74 if (status.code() != OK) {
75 LOG(ERROR) << "Copresence error code " << status.code()
76 << (status.message().empty() ? "" : ": " + status.message());
77 }
78 return status.code() != OK;
79 }
80
81 void LogIfErrorStatus(const util::error::Code& code,
82 const std::string& context) {
83 LOG_IF(ERROR, code != util::error::OK)
84 << context << " error " << code << ". See "
85 << "cs/google3/util/task/codes.proto for more info.";
86 }
87
88 // If any errors occurred, logs them and returns true.
89 bool ReportErrorLogged(const ReportResponse& response) {
90 bool result = IsErrorStatus(response.header().status());
91
92 // The Report fails or succeeds as a unit. If any responses had errors,
93 // the header will too. Thus we don't need to propagate individual errors.
94 if (response.has_update_signals_response())
95 LogIfErrorStatus(response.update_signals_response().status(), "Update");
96 if (response.has_manage_messages_response())
97 LogIfErrorStatus(response.manage_messages_response().status(), "Publish");
98 if (response.has_manage_subscriptions_response()) {
99 LogIfErrorStatus(response.manage_subscriptions_response().status(),
100 "Subscribe");
101 }
102
103 return result;
104 }
105
106 const std::string LoggingStrForToken(const std::string& auth_token) {
107 if (auth_token.empty())
108 return "anonymous";
109
110 std::string token_suffix = auth_token.substr(
111 auth_token.length() - kTokenLoggingSuffix, kTokenLoggingSuffix);
112 return "token ..." + token_suffix;
113 }
114
115
116 // Request construction
117
118 template <typename T>
119 BroadcastScanConfiguration GetBroadcastScanConfig(const T& msg) {
120 if (msg.has_token_exchange_strategy() &&
121 msg.token_exchange_strategy().has_broadcast_scan_configuration()) {
122 return msg.token_exchange_strategy().broadcast_scan_configuration();
123 }
124 return BROADCAST_SCAN_CONFIGURATION_UNKNOWN;
125 }
126
127 std::unique_ptr<DeviceState> GetDeviceCapabilities(
128 const ReportRequest& request) {
129 std::unique_ptr<DeviceState> state(new DeviceState);
130
131 TokenTechnology* ultrasound =
132 state->mutable_capabilities()->add_token_technology();
133 ultrasound->set_medium(AUDIO_ULTRASOUND_PASSBAND);
134 ultrasound->add_instruction_type(TRANSMIT);
135 ultrasound->add_instruction_type(RECEIVE);
136
137 TokenTechnology* audible =
138 state->mutable_capabilities()->add_token_technology();
139 audible->set_medium(AUDIO_AUDIBLE_DTMF);
140 audible->add_instruction_type(TRANSMIT);
141 audible->add_instruction_type(RECEIVE);
142
143 return state;
144 }
145
146 // TODO(ckehoe): We're keeping this code in a separate function for now
147 // because we get a version string from Chrome, but the proto expects
148 // an int64_t version. We should probably change the version proto
149 // to handle a more detailed version.
150 ClientVersion* CreateVersion(const std::string& client,
151 const std::string& version_name) {
152 ClientVersion* version = new ClientVersion;
153 version->set_client(client);
154 version->set_version_name(version_name);
155 return version;
156 }
157
158 void AddTokenToRequest(const AudioToken& token, ReportRequest* request) {
159 TokenObservation* token_observation =
160 request->mutable_update_signals_request()->add_token_observation();
161 token_observation->set_token_id(ToUrlSafe(token.token));
162
163 TokenSignals* signals = token_observation->add_signals();
164 signals->set_medium(token.audible ? AUDIO_AUDIBLE_DTMF
165 : AUDIO_ULTRASOUND_PASSBAND);
166 signals->set_observed_time_millis(base::Time::Now().ToJsTime());
167 }
168
169 } // namespace
170
171
172 // Public functions.
173
174 RpcHandler::RpcHandler(CopresenceDelegate* delegate,
175 DirectiveHandler* directive_handler,
176 CopresenceStateImpl* state,
177 GCMHandler* gcm_handler,
178 const MessagesCallback& new_messages_callback,
179 const PostCallback& server_post_callback)
180 : delegate_(delegate),
181 directive_handler_(directive_handler),
182 state_(state),
183 gcm_handler_(gcm_handler),
184 new_messages_callback_(new_messages_callback),
185 server_post_callback_(server_post_callback),
186 invalid_audio_token_cache_(
187 base::TimeDelta::FromMinutes(kInvalidTokenExpiryTimeMinutes),
188 kMaxInvalidTokens) {
189 DCHECK(delegate_);
190 DCHECK(directive_handler_);
191 // |gcm_handler_| is optional.
192
193 if (server_post_callback_.is_null()) {
194 server_post_callback_ =
195 base::Bind(&RpcHandler::SendHttpPost, base::Unretained(this));
196 }
197
198 if (gcm_handler_) {
199 gcm_handler_->GetGcmId(
200 base::Bind(&RpcHandler::RegisterGcmId, base::Unretained(this)));
201 }
202 }
203
204 RpcHandler::~RpcHandler() {
205 // TODO(ckehoe): Cancel the GCM callback?
206 for (HttpPost* post : pending_posts_)
207 delete post;
208 }
209
210 void RpcHandler::SendReportRequest(std::unique_ptr<ReportRequest> request,
211 const std::string& app_id,
212 const std::string& auth_token,
213 const StatusCallback& status_callback) {
214 DCHECK(request.get());
215
216 // Check that the app, if any, has some kind of authentication token.
217 // Don't allow it to piggyback on Chrome's credentials.
218 if (!app_id.empty() && delegate_->GetAPIKey(app_id).empty() &&
219 auth_token.empty()) {
220 LOG(ERROR) << "App " << app_id << " has no API key or auth token";
221 status_callback.Run(FAIL);
222 return;
223 }
224
225 // Store just one auth token since we should have only one account
226 // per instance of the copresence component.
227 // TODO(ckehoe): We may eventually need to support multiple auth tokens.
228 const bool authenticated = !auth_token.empty();
229 if (authenticated && auth_token != auth_token_) {
230 LOG_IF(ERROR, !auth_token_.empty())
231 << "Overwriting old auth token: " << LoggingStrForToken(auth_token);
232 auth_token_ = auth_token;
233 }
234
235 // Check that we have a "device" registered for this authentication state.
236 bool queue_request;
237 const std::string device_id = delegate_->GetDeviceId(authenticated);
238 if (device_id.empty()) {
239 queue_request = true;
240 if (pending_registrations_.count(authenticated) == 0)
241 RegisterDevice(authenticated);
242 // else, registration is already in progress.
243 } else {
244 queue_request = false;
245 }
246
247 // We're not registered, or registration is in progress.
248 if (queue_request) {
249 pending_requests_queue_.push_back(new PendingRequest(
250 std::move(request), app_id, authenticated, status_callback));
251 return;
252 }
253
254 DVLOG(3) << "Sending ReportRequest to server.";
255
256 // If we are unpublishing or unsubscribing, we need to stop those publish or
257 // subscribes right away, we don't need to wait for the server to tell us.
258 ProcessRemovedOperations(*request);
259
260 request->mutable_update_signals_request()->set_allocated_state(
261 GetDeviceCapabilities(*request).release());
262
263 AddPlayingTokens(request.get());
264
265 request->set_allocated_header(CreateRequestHeader(app_id, device_id));
266 server_post_callback_.Run(
267 delegate_->GetRequestContext(), kReportRequestRpcName,
268 delegate_->GetAPIKey(app_id), auth_token,
269 base::WrapUnique<MessageLite>(request.release()),
270 // On destruction, this request will be cancelled.
271 base::Bind(&RpcHandler::ReportResponseHandler, base::Unretained(this),
272 status_callback));
273 }
274
275 void RpcHandler::ReportTokens(const std::vector<AudioToken>& tokens) {
276 DCHECK(!tokens.empty());
277
278 std::unique_ptr<ReportRequest> request(new ReportRequest);
279 for (const AudioToken& token : tokens) {
280 if (invalid_audio_token_cache_.HasKey(ToUrlSafe(token.token)))
281 continue;
282 DVLOG(3) << "Sending token " << token.token << " to server";
283 AddTokenToRequest(token, request.get());
284 }
285
286 ReportOnAllDevices(std::move(request));
287 }
288
289
290 // Private functions.
291
292 RpcHandler::PendingRequest::PendingRequest(
293 std::unique_ptr<ReportRequest> report,
294 const std::string& app_id,
295 bool authenticated,
296 const StatusCallback& callback)
297 : report(std::move(report)),
298 app_id(app_id),
299 authenticated(authenticated),
300 callback(callback) {}
301
302 RpcHandler::PendingRequest::~PendingRequest() {}
303
304 void RpcHandler::RegisterDevice(const bool authenticated) {
305 DVLOG(2) << "Sending " << (authenticated ? "authenticated" : "anonymous")
306 << " registration to server.";
307
308 std::unique_ptr<RegisterDeviceRequest> request(new RegisterDeviceRequest);
309
310 // Add a GCM ID for authenticated registration, if we have one.
311 if (!authenticated || gcm_id_.empty()) {
312 request->mutable_push_service()->set_service(PUSH_SERVICE_NONE);
313 } else {
314 DVLOG(2) << "Registering GCM ID with " << LoggingStrForToken(auth_token_);
315 request->mutable_push_service()->set_service(GCM);
316 request->mutable_push_service()->mutable_gcm_registration()
317 ->set_device_token(gcm_id_);
318 }
319
320 // Only identify as a Chrome device if we're in anonymous mode.
321 // Authenticated calls come from a "GAIA device".
322 if (!authenticated) {
323 // Make sure this isn't a duplicate anonymous registration.
324 // Duplicate authenticated registrations are allowed, to update the GCM ID.
325 DCHECK(delegate_->GetDeviceId(false).empty())
326 << "Attempted anonymous re-registration";
327
328 Identity* identity =
329 request->mutable_device_identifiers()->mutable_registrant();
330 identity->set_type(CHROME);
331 }
332
333 bool gcm_pending = authenticated && gcm_handler_ && gcm_id_.empty();
334 pending_registrations_.insert(authenticated);
335 request->set_allocated_header(CreateRequestHeader(
336 // The device is empty on first registration.
337 // When re-registering to pass on the GCM ID, it will be present.
338 std::string(), delegate_->GetDeviceId(authenticated)));
339 if (authenticated)
340 DCHECK(!auth_token_.empty());
341 server_post_callback_.Run(
342 delegate_->GetRequestContext(), kRegisterDeviceRpcName, std::string(),
343 authenticated ? auth_token_ : std::string(),
344 base::WrapUnique<MessageLite>(request.release()),
345 // On destruction, this request will be cancelled.
346 base::Bind(&RpcHandler::RegisterResponseHandler, base::Unretained(this),
347 authenticated, gcm_pending));
348 }
349
350 void RpcHandler::ProcessQueuedRequests(const bool authenticated) {
351 // If there is no device ID for this auth state, registration failed.
352 bool registration_failed = delegate_->GetDeviceId(authenticated).empty();
353
354 // We momentarily take ownership of all the pointers in the queue.
355 // They are either deleted here or passed on to a new queue.
356 ScopedVector<PendingRequest> requests_being_processed;
357 std::swap(requests_being_processed, pending_requests_queue_);
358 for (PendingRequest* request : requests_being_processed) {
359 if (request->authenticated == authenticated) {
360 if (registration_failed) {
361 request->callback.Run(FAIL);
362 } else {
363 if (request->authenticated)
364 DCHECK(!auth_token_.empty());
365 SendReportRequest(std::move(request->report), request->app_id,
366 request->authenticated ? auth_token_ : std::string(),
367 request->callback);
368 }
369 delete request;
370 } else {
371 // The request is in a different auth state.
372 pending_requests_queue_.push_back(request);
373 }
374 }
375
376 // Only keep the requests that weren't processed.
377 // All the pointers in the queue are now spoken for.
378 requests_being_processed.weak_clear();
379 }
380
381 void RpcHandler::ReportOnAllDevices(std::unique_ptr<ReportRequest> request) {
382 std::vector<bool> auth_states;
383 if (!auth_token_.empty() && !delegate_->GetDeviceId(true).empty())
384 auth_states.push_back(true);
385 if (!delegate_->GetDeviceId(false).empty())
386 auth_states.push_back(false);
387 if (auth_states.empty()) {
388 VLOG(2) << "Skipping reporting because no device IDs are registered";
389 return;
390 }
391
392 for (bool authenticated : auth_states) {
393 SendReportRequest(
394 base::WrapUnique(new ReportRequest(*request)), std::string(),
395 authenticated ? auth_token_ : std::string(), StatusCallback());
396 }
397 }
398
399 // Store a GCM ID and send it to the server if needed. The constructor passes
400 // this callback to the GCMHandler to receive the ID whenever it's ready.
401 // It may be returned immediately, if the ID is cached, or require a server
402 // round-trip. This ID must then be passed along to the copresence server.
403 // There are a few ways this can happen:
404 //
405 // 1. The GCM ID is available when we first register, and is passed along
406 // with the RegisterDeviceRequest.
407 //
408 // 2. The GCM ID becomes available after the RegisterDeviceRequest has
409 // completed. Then this function will invoke RegisterDevice()
410 // again to pass on the ID.
411 //
412 // 3. The GCM ID becomes available after the RegisterDeviceRequest is sent,
413 // but before it completes. In this case, the gcm_pending flag is passed
414 // through to the RegisterResponseHandler, which invokes RegisterDevice()
415 // again to pass on the ID. This function must skip pending registrations,
416 // as the device ID will be empty.
417 //
418 // TODO(ckehoe): Add tests for these scenarios.
419 void RpcHandler::RegisterGcmId(const std::string& gcm_id) {
420 gcm_id_ = gcm_id;
421 if (!gcm_id.empty()) {
422 const std::string& device_id = delegate_->GetDeviceId(true);
423 if (!auth_token_.empty() && !device_id.empty())
424 RegisterDevice(true);
425 }
426 }
427
428 void RpcHandler::RegisterResponseHandler(
429 bool authenticated,
430 bool gcm_pending,
431 HttpPost* completed_post,
432 int http_status_code,
433 const std::string& response_data) {
434 if (completed_post) {
435 size_t elements_erased = pending_posts_.erase(completed_post);
436 DCHECK_GT(elements_erased, 0u);
437 }
438
439 size_t registrations_completed = pending_registrations_.erase(authenticated);
440 DCHECK_GT(registrations_completed, 0u);
441
442 RegisterDeviceResponse response;
443 const std::string token_str =
444 LoggingStrForToken(authenticated ? auth_token_ : std::string());
445 if (http_status_code != net::HTTP_OK) {
446 // TODO(ckehoe): Retry registration if appropriate.
447 LOG(ERROR) << token_str << " device registration failed";
448 } else if (!response.ParseFromString(response_data)) {
449 LOG(ERROR) << "Invalid RegisterDeviceResponse:\n" << response_data;
450 } else if (!IsErrorStatus(response.header().status())) {
451 const std::string& device_id = response.registered_device_id();
452 DCHECK(!device_id.empty());
453 delegate_->SaveDeviceId(authenticated, device_id);
454 DVLOG(2) << token_str << " device registration successful. Id: "
455 << device_id;
456
457 // If we have a GCM ID now, and didn't before, pass it on to the server.
458 if (gcm_pending && !gcm_id_.empty())
459 RegisterDevice(authenticated);
460 }
461
462 // Send or fail requests on this auth token.
463 ProcessQueuedRequests(authenticated);
464 }
465
466 void RpcHandler::ReportResponseHandler(const StatusCallback& status_callback,
467 HttpPost* completed_post,
468 int http_status_code,
469 const std::string& response_data) {
470 if (completed_post) {
471 size_t elements_erased = pending_posts_.erase(completed_post);
472 DCHECK_GT(elements_erased, 0u);
473 }
474
475 if (http_status_code != net::HTTP_OK) {
476 if (!status_callback.is_null())
477 status_callback.Run(FAIL);
478 return;
479 }
480
481 DVLOG(3) << "Received ReportResponse.";
482 ReportResponse response;
483 if (!response.ParseFromString(response_data)) {
484 LOG(ERROR) << "Invalid ReportResponse";
485 if (!status_callback.is_null())
486 status_callback.Run(FAIL);
487 return;
488 }
489
490 if (ReportErrorLogged(response)) {
491 if (!status_callback.is_null())
492 status_callback.Run(FAIL);
493 return;
494 }
495
496 for (const MessageResult& result :
497 response.manage_messages_response().published_message_result()) {
498 DVLOG(2) << "Published message with id " << result.published_message_id();
499 }
500
501 for (const SubscriptionResult& result :
502 response.manage_subscriptions_response().subscription_result()) {
503 DVLOG(2) << "Created subscription with id " << result.subscription_id();
504 }
505
506 if (response.has_update_signals_response()) {
507 const UpdateSignalsResponse& update_response =
508 response.update_signals_response();
509 new_messages_callback_.Run(update_response.message());
510
511 for (const Directive& directive : update_response.directive())
512 directive_handler_->AddDirective(directive);
513
514 for (const Token& token : update_response.token()) {
515 if (state_)
516 state_->UpdateTokenStatus(token.id(), token.status());
517 switch (token.status()) {
518 case VALID:
519 // TODO(rkc/ckehoe): Store the token in a |valid_token_cache_| with a
520 // short TTL (like 10s) and send it up with every report request.
521 // Then we'll still get messages while we're waiting to hear it again.
522 VLOG(1) << "Got valid token " << token.id();
523 break;
524 case INVALID:
525 DVLOG(3) << "Discarding invalid token " << token.id();
526 invalid_audio_token_cache_.Add(token.id(), true);
527 break;
528 default:
529 DVLOG(2) << "Token " << token.id() << " has status code "
530 << token.status();
531 }
532 }
533 }
534
535 // TODO(ckehoe): Return a more detailed status response.
536 if (!status_callback.is_null())
537 status_callback.Run(SUCCESS);
538 }
539
540 void RpcHandler::ProcessRemovedOperations(const ReportRequest& request) {
541 // Remove unpublishes.
542 if (request.has_manage_messages_request()) {
543 for (const std::string& unpublish :
544 request.manage_messages_request().id_to_unpublish()) {
545 directive_handler_->RemoveDirectives(unpublish);
546 }
547 }
548
549 // Remove unsubscribes.
550 if (request.has_manage_subscriptions_request()) {
551 for (const std::string& unsubscribe :
552 request.manage_subscriptions_request().id_to_unsubscribe()) {
553 directive_handler_->RemoveDirectives(unsubscribe);
554 }
555 }
556 }
557
558 void RpcHandler::AddPlayingTokens(ReportRequest* request) {
559 const std::string& audible_token =
560 directive_handler_->GetCurrentAudioToken(AUDIBLE);
561 const std::string& inaudible_token =
562 directive_handler_->GetCurrentAudioToken(INAUDIBLE);
563
564 if (!audible_token.empty())
565 AddTokenToRequest(AudioToken(audible_token, true), request);
566 if (!inaudible_token.empty())
567 AddTokenToRequest(AudioToken(inaudible_token, false), request);
568 }
569
570 // TODO(ckehoe): Pass in the version string and
571 // group this with the local functions up top.
572 RequestHeader* RpcHandler::CreateRequestHeader(
573 const std::string& app_id,
574 const std::string& device_id) const {
575 RequestHeader* header = new RequestHeader;
576
577 header->set_allocated_framework_version(CreateVersion(
578 "Chrome", delegate_->GetPlatformVersionString()));
579 if (!app_id.empty())
580 header->set_allocated_client_version(CreateVersion(app_id, std::string()));
581 header->set_current_time_millis(base::Time::Now().ToJsTime());
582 if (!device_id.empty())
583 header->set_registered_device_id(device_id);
584
585 DeviceFingerprint* fingerprint = new DeviceFingerprint;
586 fingerprint->set_platform_version(delegate_->GetPlatformVersionString());
587 fingerprint->set_type(CHROME_PLATFORM_TYPE);
588 header->set_allocated_device_fingerprint(fingerprint);
589
590 return header;
591 }
592
593 void RpcHandler::SendHttpPost(net::URLRequestContextGetter* url_context_getter,
594 const std::string& rpc_name,
595 const std::string& api_key,
596 const std::string& auth_token,
597 std::unique_ptr<MessageLite> request_proto,
598 const PostCleanupCallback& callback) {
599 // Create the base URL to call.
600 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
601 const std::string copresence_server_host =
602 command_line->HasSwitch(switches::kCopresenceServer) ?
603 command_line->GetSwitchValueASCII(switches::kCopresenceServer) :
604 kDefaultCopresenceServer;
605
606 // Create the request and keep a pointer until it completes.
607 HttpPost* http_post = new HttpPost(
608 url_context_getter,
609 copresence_server_host,
610 rpc_name,
611 api_key,
612 auth_token,
613 command_line->GetSwitchValueASCII(switches::kCopresenceTracingToken),
614 *request_proto);
615
616 http_post->Start(base::Bind(callback, http_post));
617 pending_posts_.insert(http_post);
618 }
619
620 } // namespace copresence
OLDNEW
« no previous file with comments | « components/copresence/rpc/rpc_handler.h ('k') | components/copresence/rpc/rpc_handler_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698