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

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

Issue 712833002: Undoing revert, cl is not the cause of gcm crash on canary. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@2214
Patch Set: Created 6 years, 1 month 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
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/copresence/rpc/rpc_handler.h" 5 #include "components/copresence/rpc/rpc_handler.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/command_line.h" 8 #include "base/command_line.h"
9 #include "base/guid.h" 9 #include "base/guid.h"
10 #include "base/logging.h" 10 #include "base/logging.h"
11 #include "base/strings/string_util.h" 11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h" 12 #include "base/strings/stringprintf.h"
13 13
14 // TODO(ckehoe): time.h includes windows.h, which #defines DeviceCapabilities 14 // TODO(ckehoe): time.h includes windows.h, which #defines DeviceCapabilities
15 // to DeviceCapabilitiesW. This breaks the pb.h headers below. For now, 15 // to DeviceCapabilitiesW. This breaks the pb.h headers below. For now,
16 // we fix this with an #undef. 16 // we fix this with an #undef.
17 #include "base/time/time.h" 17 #include "base/time/time.h"
18 #if defined(OS_WIN) 18 #if defined(OS_WIN)
19 #undef DeviceCapabilities 19 #undef DeviceCapabilities
20 #endif 20 #endif
21 21
22 #include "components/copresence/copresence_switches.h" 22 #include "components/copresence/copresence_switches.h"
23 #include "components/copresence/handlers/directive_handler.h" 23 #include "components/copresence/handlers/directive_handler.h"
24 #include "components/copresence/handlers/gcm_handler.h"
24 #include "components/copresence/proto/codes.pb.h" 25 #include "components/copresence/proto/codes.pb.h"
25 #include "components/copresence/proto/data.pb.h" 26 #include "components/copresence/proto/data.pb.h"
26 #include "components/copresence/proto/rpcs.pb.h" 27 #include "components/copresence/proto/rpcs.pb.h"
27 #include "components/copresence/public/copresence_constants.h" 28 #include "components/copresence/public/copresence_constants.h"
28 #include "components/copresence/public/copresence_delegate.h" 29 #include "components/copresence/public/copresence_delegate.h"
29 #include "components/copresence/rpc/http_post.h" 30 #include "components/copresence/rpc/http_post.h"
30 #include "net/http/http_status_code.h" 31 #include "net/http/http_status_code.h"
31 32
32 // TODO(ckehoe): Return error messages for bad requests. 33 // TODO(ckehoe): Return error messages for bad requests.
33 34
34 namespace copresence { 35 namespace copresence {
35 36
36 using google::protobuf::MessageLite; 37 using google::protobuf::MessageLite;
37 using google::protobuf::RepeatedPtrField; 38 using google::protobuf::RepeatedPtrField;
38 39
39 const char RpcHandler::kReportRequestRpcName[] = "report"; 40 const char RpcHandler::kReportRequestRpcName[] = "report";
40 41
41 // Number of characters of suffix to log for auth tokens 42 namespace {
42 const int kTokenSuffix = 5;
43 43
44 namespace { 44 const int kTokenLoggingSuffix = 5;
45 const int kInvalidTokenExpiryTimeMs = 10 * 60 * 1000; // 10 minutes.
46 const int kMaxInvalidTokens = 10000;
47 const char kRegisterDeviceRpcName[] = "registerdevice";
48 const char kDefaultCopresenceServer[] =
49 "https://www.googleapis.com/copresence/v2/copresence";
45 50
46 // UrlSafe is defined as: 51 // UrlSafe is defined as:
47 // '/' represented by a '_' and '+' represented by a '-' 52 // '/' represented by a '_' and '+' represented by a '-'
48 // TODO(rkc): Move this to the wrapper. 53 // TODO(rkc): Move this to the wrapper.
49 std::string ToUrlSafe(std::string token) { 54 std::string ToUrlSafe(std::string token) {
50 base::ReplaceChars(token, "+", "-", &token); 55 base::ReplaceChars(token, "+", "-", &token);
51 base::ReplaceChars(token, "/", "_", &token); 56 base::ReplaceChars(token, "/", "_", &token);
52 return token; 57 return token;
53 } 58 }
54 59
55 const int kInvalidTokenExpiryTimeMs = 10 * 60 * 1000; // 10 minutes.
56 const int kMaxInvalidTokens = 10000;
57 const char kRegisterDeviceRpcName[] = "registerdevice";
58 const char kDefaultCopresenceServer[] =
59 "https://www.googleapis.com/copresence/v2/copresence";
60 60
61 // Logging 61 // Logging
62 62
63 // Checks for a copresence error. If there is one, logs it and returns true. 63 // Checks for a copresence error. If there is one, logs it and returns true.
64 bool IsErrorStatus(const Status& status) { 64 bool IsErrorStatus(const Status& status) {
65 if (status.code() != OK) { 65 if (status.code() != OK) {
66 LOG(ERROR) << "Copresence error code " << status.code() 66 LOG(ERROR) << "Copresence error code " << status.code()
67 << (status.message().empty() ? "" : ": " + status.message()); 67 << (status.message().empty() ? "" : ": " + status.message());
68 } 68 }
69 return status.code() != OK; 69 return status.code() != OK;
(...skipping 17 matching lines...) Expand all
87 if (response.has_manage_messages_response()) 87 if (response.has_manage_messages_response())
88 LogIfErrorStatus(response.manage_messages_response().status(), "Publish"); 88 LogIfErrorStatus(response.manage_messages_response().status(), "Publish");
89 if (response.has_manage_subscriptions_response()) { 89 if (response.has_manage_subscriptions_response()) {
90 LogIfErrorStatus(response.manage_subscriptions_response().status(), 90 LogIfErrorStatus(response.manage_subscriptions_response().status(),
91 "Subscribe"); 91 "Subscribe");
92 } 92 }
93 93
94 return result; 94 return result;
95 } 95 }
96 96
97 const std::string LoggingStrForToken(const std::string& auth_token) {
98 if (auth_token.empty())
99 return "anonymous";
100
101 std::string token_suffix = auth_token.substr(
102 auth_token.length() - kTokenLoggingSuffix, kTokenLoggingSuffix);
103 return base::StringPrintf("token ...%s", token_suffix.c_str());
104 }
105
106
97 // Request construction 107 // Request construction
98 // TODO(ckehoe): Move these into a separate file? 108 // TODO(ckehoe): Move these into a separate file?
99 109
100 template <typename T> 110 template <typename T>
101 BroadcastScanConfiguration GetBroadcastScanConfig(const T& msg) { 111 BroadcastScanConfiguration GetBroadcastScanConfig(const T& msg) {
102 if (msg.has_token_exchange_strategy() && 112 if (msg.has_token_exchange_strategy() &&
103 msg.token_exchange_strategy().has_broadcast_scan_configuration()) { 113 msg.token_exchange_strategy().has_broadcast_scan_configuration()) {
104 return msg.token_exchange_strategy().broadcast_scan_configuration(); 114 return msg.token_exchange_strategy().broadcast_scan_configuration();
105 } 115 }
106 return BROADCAST_SCAN_CONFIGURATION_UNKNOWN; 116 return BROADCAST_SCAN_CONFIGURATION_UNKNOWN;
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
142 TokenObservation* token_observation = 152 TokenObservation* token_observation =
143 request->mutable_update_signals_request()->add_token_observation(); 153 request->mutable_update_signals_request()->add_token_observation();
144 token_observation->set_token_id(ToUrlSafe(token.token)); 154 token_observation->set_token_id(ToUrlSafe(token.token));
145 155
146 TokenSignals* signals = token_observation->add_signals(); 156 TokenSignals* signals = token_observation->add_signals();
147 signals->set_medium(token.audible ? AUDIO_AUDIBLE_DTMF 157 signals->set_medium(token.audible ? AUDIO_AUDIBLE_DTMF
148 : AUDIO_ULTRASOUND_PASSBAND); 158 : AUDIO_ULTRASOUND_PASSBAND);
149 signals->set_observed_time_millis(base::Time::Now().ToJsTime()); 159 signals->set_observed_time_millis(base::Time::Now().ToJsTime());
150 } 160 }
151 161
152 const std::string LoggingStrForToken(const std::string& auth_token) {
153 std::string token_str = auth_token.empty() ? "anonymous" :
154 base::StringPrintf("token ...%s",
155 auth_token.substr(auth_token.length() - kTokenSuffix,
156 kTokenSuffix).c_str());
157 return token_str;
158 }
159
160 } // namespace 162 } // namespace
161 163
162 164
163 // Public functions. 165 // Public functions.
164 166
165 RpcHandler::RpcHandler(CopresenceDelegate* delegate, 167 RpcHandler::RpcHandler(CopresenceDelegate* delegate,
166 DirectiveHandler* directive_handler, 168 DirectiveHandler* directive_handler,
169 GCMHandler* gcm_handler,
167 const PostCallback& server_post_callback) 170 const PostCallback& server_post_callback)
168 : delegate_(delegate), 171 : delegate_(delegate),
169 directive_handler_(directive_handler), 172 directive_handler_(directive_handler),
173 gcm_handler_(gcm_handler),
170 server_post_callback_(server_post_callback), 174 server_post_callback_(server_post_callback),
171 invalid_audio_token_cache_( 175 invalid_audio_token_cache_(
172 base::TimeDelta::FromMilliseconds(kInvalidTokenExpiryTimeMs), 176 base::TimeDelta::FromMilliseconds(kInvalidTokenExpiryTimeMs),
173 kMaxInvalidTokens) { 177 kMaxInvalidTokens) {
174 DCHECK(delegate_); 178 DCHECK(delegate_);
175 DCHECK(directive_handler_); 179 DCHECK(directive_handler_);
180 // |gcm_handler_| is optional.
176 181
177 if (server_post_callback_.is_null()) { 182 if (server_post_callback_.is_null()) {
178 server_post_callback_ = 183 server_post_callback_ =
179 base::Bind(&RpcHandler::SendHttpPost, base::Unretained(this)); 184 base::Bind(&RpcHandler::SendHttpPost, base::Unretained(this));
180 } 185 }
186
187 if (gcm_handler_) {
188 gcm_handler_->GetGcmId(
189 base::Bind(&RpcHandler::RegisterGcmId, base::Unretained(this)));
190 }
181 } 191 }
182 192
183 RpcHandler::~RpcHandler() { 193 RpcHandler::~RpcHandler() {
184 // Do not use |directive_handler_| here. 194 // Do not use |directive_handler_| or |gcm_handler_| here.
185 // It will already have been destructed. 195 // They will already have been destructed.
186 for (HttpPost* post : pending_posts_) 196 for (HttpPost* post : pending_posts_)
187 delete post; 197 delete post;
188 } 198 }
189 199
190 void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request, 200 void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request,
191 const std::string& app_id, 201 const std::string& app_id,
192 const std::string& auth_token, 202 const std::string& auth_token,
193 const StatusCallback& status_callback) { 203 const StatusCallback& status_callback) {
194 DCHECK(request.get()); 204 DCHECK(request.get());
195 205
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
251 AddTokenToRequest(token, &request); 261 AddTokenToRequest(token, &request);
252 } 262 }
253 263
254 // Report under all active tokens. 264 // Report under all active tokens.
255 for (const auto& registration : device_id_by_auth_token_) { 265 for (const auto& registration : device_id_by_auth_token_) {
256 SendReportRequest(make_scoped_ptr(new ReportRequest(request)), 266 SendReportRequest(make_scoped_ptr(new ReportRequest(request)),
257 registration.first); 267 registration.first);
258 } 268 }
259 } 269 }
260 270
261 // Private methods 271
272 // Private functions.
262 273
263 RpcHandler::PendingRequest::PendingRequest(scoped_ptr<ReportRequest> report, 274 RpcHandler::PendingRequest::PendingRequest(scoped_ptr<ReportRequest> report,
264 const std::string& app_id, 275 const std::string& app_id,
265 const std::string& auth_token, 276 const std::string& auth_token,
266 const StatusCallback& callback) 277 const StatusCallback& callback)
267 : report(report.Pass()), 278 : report(report.Pass()),
268 app_id(app_id), 279 app_id(app_id),
269 auth_token(auth_token), 280 auth_token(auth_token),
270 callback(callback) {} 281 callback(callback) {}
271 282
272 RpcHandler::PendingRequest::~PendingRequest() {} 283 RpcHandler::PendingRequest::~PendingRequest() {}
273 284
274 void RpcHandler::RegisterForToken(const std::string& auth_token) { 285 void RpcHandler::RegisterForToken(const std::string& auth_token) {
275 DVLOG(2) << "Sending " << LoggingStrForToken(auth_token) 286 DVLOG(2) << "Sending " << LoggingStrForToken(auth_token)
276 << " registration to server."; 287 << " registration to server.";
277 288
278 // Mark registration as in progress. 289 scoped_ptr<RegisterDeviceRequest> request(new RegisterDeviceRequest);
279 device_id_by_auth_token_[auth_token] = "";
280 290
281 scoped_ptr<RegisterDeviceRequest> request(new RegisterDeviceRequest); 291 // Add a GCM ID for authenticated registration, if we have one.
282 request->mutable_push_service()->set_service(PUSH_SERVICE_NONE); 292 if (auth_token.empty() || gcm_id_.empty()) {
293 request->mutable_push_service()->set_service(PUSH_SERVICE_NONE);
294 } else {
295 DVLOG(2) << "Registering GCM ID with " << LoggingStrForToken(auth_token);
296 request->mutable_push_service()->set_service(GCM);
297 request->mutable_push_service()->mutable_gcm_registration()
298 ->set_device_token(gcm_id_);
299 }
283 300
284 // Only identify as a Chrome device if we're in anonymous mode. 301 // Only identify as a Chrome device if we're in anonymous mode.
285 // Authenticated calls come from a "GAIA device". 302 // Authenticated calls come from a "GAIA device".
286 if (auth_token.empty()) { 303 if (auth_token.empty()) {
287 Identity* identity = 304 Identity* identity =
288 request->mutable_device_identifiers()->mutable_registrant(); 305 request->mutable_device_identifiers()->mutable_registrant();
289 identity->set_type(CHROME); 306 identity->set_type(CHROME);
290 identity->set_chrome_id(base::GenerateGUID()); 307 identity->set_chrome_id(base::GenerateGUID());
308
309 // Since we're generating a new "Chrome ID" here,
310 // we need to make sure this isn't a duplicate registration.
311 DCHECK_EQ(0u, device_id_by_auth_token_.count(std::string()))
312 << "Attempted anonymous re-registration";
291 } 313 }
292 314
315 bool gcm_pending = !auth_token.empty() && gcm_handler_ && gcm_id_.empty();
293 SendServerRequest( 316 SendServerRequest(
294 kRegisterDeviceRpcName, 317 kRegisterDeviceRpcName,
295 std::string(), // device ID 318 // This will have the side effect of populating an empty device ID
319 // for this auth token in the map. This is what we want,
320 // to mark registration as being in progress.
321 device_id_by_auth_token_[auth_token],
296 std::string(), // app ID 322 std::string(), // app ID
297 auth_token, 323 auth_token,
298 request.Pass(), 324 request.Pass(),
299 base::Bind(&RpcHandler::RegisterResponseHandler, 325 base::Bind(&RpcHandler::RegisterResponseHandler,
300 // On destruction, this request will be cancelled. 326 // On destruction, this request will be cancelled.
301 base::Unretained(this), 327 base::Unretained(this),
302 auth_token)); 328 auth_token,
329 gcm_pending));
303 } 330 }
304 331
305 void RpcHandler::ProcessQueuedRequests(const std::string& auth_token) { 332 void RpcHandler::ProcessQueuedRequests(const std::string& auth_token) {
306 // Track requests that are not on this auth token. 333 // Track requests that are not on this auth token.
307 ScopedVector<PendingRequest> still_pending_requests; 334 ScopedVector<PendingRequest> still_pending_requests;
308 335
309 // If there is no device ID for this auth token, registration failed. 336 // If there is no device ID for this auth token, registration failed.
310 bool registration_failed = 337 bool registration_failed =
311 (device_id_by_auth_token_.count(auth_token) == 0); 338 (device_id_by_auth_token_.count(auth_token) == 0);
312 339
(...skipping 23 matching lines...) Expand all
336 } 363 }
337 364
338 void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request, 365 void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request,
339 const std::string& auth_token) { 366 const std::string& auth_token) {
340 SendReportRequest(request.Pass(), 367 SendReportRequest(request.Pass(),
341 std::string(), 368 std::string(),
342 auth_token, 369 auth_token,
343 StatusCallback()); 370 StatusCallback());
344 } 371 }
345 372
373 // Store a GCM ID and send it to the server if needed. The constructor passes
374 // this callback to the GCMHandler to receive the ID whenever it's ready.
375 // It may be returned immediately, if the ID is cached, or require a server
376 // round-trip. This ID must then be passed along to the copresence server.
377 // There are a few ways this can happen for each auth token:
378 //
379 // 1. The GCM ID is available when we first register, and is passed along
380 // with the RegisterDeviceRequest.
381 //
382 // 2. The GCM ID becomes available after the RegisterDeviceRequest has
383 // completed. Then the loop in this function will invoke RegisterForToken()
384 // again to pass on the ID.
385 //
386 // 3. The GCM ID becomes available after the RegisterDeviceRequest is sent,
387 // but before it completes. In this case, the gcm_pending flag is passed
388 // through to the RegisterResponseHandler, which invokes RegisterForToken()
389 // again to pass on the ID. The loop here must skip pending registrations,
390 // as the device ID will be empty.
391 //
392 // TODO(ckehoe): Add tests for these scenarios.
393 void RpcHandler::RegisterGcmId(const std::string& gcm_id) {
394 gcm_id_ = gcm_id;
395 if (!gcm_id.empty()) {
396 for (const auto& registration : device_id_by_auth_token_) {
397 const std::string& auth_token = registration.first;
398 const std::string& device_id = registration.second;
399 if (!auth_token.empty() && !device_id.empty())
400 RegisterForToken(auth_token);
401 }
402 }
403 }
404
346 void RpcHandler::RegisterResponseHandler( 405 void RpcHandler::RegisterResponseHandler(
347 const std::string& auth_token, 406 const std::string& auth_token,
407 bool gcm_pending,
348 HttpPost* completed_post, 408 HttpPost* completed_post,
349 int http_status_code, 409 int http_status_code,
350 const std::string& response_data) { 410 const std::string& response_data) {
351 if (completed_post) { 411 if (completed_post) {
352 int elements_erased = pending_posts_.erase(completed_post); 412 int elements_erased = pending_posts_.erase(completed_post);
353 DCHECK_GT(elements_erased, 0); 413 DCHECK_GT(elements_erased, 0);
354 delete completed_post; 414 delete completed_post;
355 } 415 }
356 416
357 // Registration is no longer in progress. 417 // Registration is no longer in progress.
358 // If it was successful, we'll update below. 418 // If it was successful, we'll update below.
359 device_id_by_auth_token_.erase(auth_token); 419 device_id_by_auth_token_.erase(auth_token);
360 420
361 RegisterDeviceResponse response; 421 RegisterDeviceResponse response;
362 if (http_status_code != net::HTTP_OK) { 422 if (http_status_code != net::HTTP_OK) {
363 // TODO(ckehoe): Retry registration if appropriate. 423 // TODO(ckehoe): Retry registration if appropriate.
364 LOG(ERROR) << LoggingStrForToken(auth_token) 424 LOG(ERROR) << LoggingStrForToken(auth_token)
365 << " device registration failed"; 425 << " device registration failed";
366 } else if (!response.ParseFromString(response_data)) { 426 } else if (!response.ParseFromString(response_data)) {
367 LOG(ERROR) << "Invalid RegisterDeviceResponse:\n" << response_data; 427 LOG(ERROR) << "Invalid RegisterDeviceResponse:\n" << response_data;
368 } else if (!IsErrorStatus(response.header().status())) { 428 } else if (!IsErrorStatus(response.header().status())) {
369 const std::string& device_id = response.registered_device_id(); 429 const std::string& device_id = response.registered_device_id();
370 DCHECK(!device_id.empty()); 430 DCHECK(!device_id.empty());
371 device_id_by_auth_token_[auth_token] = device_id; 431 device_id_by_auth_token_[auth_token] = device_id;
372 DVLOG(2) << LoggingStrForToken(auth_token) 432 DVLOG(2) << LoggingStrForToken(auth_token)
373 << " device registration successful. Id: " << device_id; 433 << " device registration successful. Id: " << device_id;
434
435 // If we have a GCM ID now, and didn't before, pass it on to the server.
436 if (gcm_pending && !gcm_id_.empty())
437 RegisterForToken(auth_token);
374 } 438 }
375 439
376 // Send or fail requests on this auth token. 440 // Send or fail requests on this auth token.
377 ProcessQueuedRequests(auth_token); 441 ProcessQueuedRequests(auth_token);
378 } 442 }
379 443
380 void RpcHandler::ReportResponseHandler(const StatusCallback& status_callback, 444 void RpcHandler::ReportResponseHandler(const StatusCallback& status_callback,
381 HttpPost* completed_post, 445 HttpPost* completed_post,
382 int http_status_code, 446 int http_status_code,
383 const std::string& response_data) { 447 const std::string& response_data) {
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after
512 const std::string& device_id) const { 576 const std::string& device_id) const {
513 RequestHeader* header = new RequestHeader; 577 RequestHeader* header = new RequestHeader;
514 578
515 header->set_allocated_framework_version(CreateVersion( 579 header->set_allocated_framework_version(CreateVersion(
516 "Chrome", delegate_->GetPlatformVersionString())); 580 "Chrome", delegate_->GetPlatformVersionString()));
517 if (!client_name.empty()) { 581 if (!client_name.empty()) {
518 header->set_allocated_client_version( 582 header->set_allocated_client_version(
519 CreateVersion(client_name, std::string())); 583 CreateVersion(client_name, std::string()));
520 } 584 }
521 header->set_current_time_millis(base::Time::Now().ToJsTime()); 585 header->set_current_time_millis(base::Time::Now().ToJsTime());
522 header->set_registered_device_id(device_id); 586 if (!device_id.empty())
587 header->set_registered_device_id(device_id);
523 588
524 DeviceFingerprint* fingerprint = new DeviceFingerprint; 589 DeviceFingerprint* fingerprint = new DeviceFingerprint;
525 fingerprint->set_platform_version(delegate_->GetPlatformVersionString()); 590 fingerprint->set_platform_version(delegate_->GetPlatformVersionString());
526 fingerprint->set_type(CHROME_PLATFORM_TYPE); 591 fingerprint->set_type(CHROME_PLATFORM_TYPE);
527 header->set_allocated_device_fingerprint(fingerprint); 592 header->set_allocated_device_fingerprint(fingerprint);
528 593
529 return header; 594 return header;
530 } 595 }
531 596
532 template <class T> 597 template <class T>
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
567 api_key, 632 api_key,
568 auth_token, 633 auth_token,
569 command_line->GetSwitchValueASCII(switches::kCopresenceTracingToken), 634 command_line->GetSwitchValueASCII(switches::kCopresenceTracingToken),
570 *request_proto); 635 *request_proto);
571 636
572 http_post->Start(base::Bind(callback, http_post)); 637 http_post->Start(base::Bind(callback, http_post));
573 pending_posts_.insert(http_post); 638 pending_posts_.insert(http_post);
574 } 639 }
575 640
576 } // namespace copresence 641 } // 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