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

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

Issue 713643002: Revert "Adding GCM support to the copresence component. The Copresence server can push directives (… (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"
25 #include "components/copresence/proto/codes.pb.h" 24 #include "components/copresence/proto/codes.pb.h"
26 #include "components/copresence/proto/data.pb.h" 25 #include "components/copresence/proto/data.pb.h"
27 #include "components/copresence/proto/rpcs.pb.h" 26 #include "components/copresence/proto/rpcs.pb.h"
28 #include "components/copresence/public/copresence_constants.h" 27 #include "components/copresence/public/copresence_constants.h"
29 #include "components/copresence/public/copresence_delegate.h" 28 #include "components/copresence/public/copresence_delegate.h"
30 #include "components/copresence/rpc/http_post.h" 29 #include "components/copresence/rpc/http_post.h"
31 #include "net/http/http_status_code.h" 30 #include "net/http/http_status_code.h"
32 31
33 // TODO(ckehoe): Return error messages for bad requests. 32 // TODO(ckehoe): Return error messages for bad requests.
34 33
35 namespace copresence { 34 namespace copresence {
36 35
37 using google::protobuf::MessageLite; 36 using google::protobuf::MessageLite;
38 using google::protobuf::RepeatedPtrField; 37 using google::protobuf::RepeatedPtrField;
39 38
40 const char RpcHandler::kReportRequestRpcName[] = "report"; 39 const char RpcHandler::kReportRequestRpcName[] = "report";
41 40
41 // Number of characters of suffix to log for auth tokens
42 const int kTokenSuffix = 5;
43
42 namespace { 44 namespace {
43 45
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";
50
51 // UrlSafe is defined as: 46 // UrlSafe is defined as:
52 // '/' represented by a '_' and '+' represented by a '-' 47 // '/' represented by a '_' and '+' represented by a '-'
53 // TODO(rkc): Move this to the wrapper. 48 // TODO(rkc): Move this to the wrapper.
54 std::string ToUrlSafe(std::string token) { 49 std::string ToUrlSafe(std::string token) {
55 base::ReplaceChars(token, "+", "-", &token); 50 base::ReplaceChars(token, "+", "-", &token);
56 base::ReplaceChars(token, "/", "_", &token); 51 base::ReplaceChars(token, "/", "_", &token);
57 return token; 52 return token;
58 } 53 }
59 54
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
107 // Request construction 97 // Request construction
108 // TODO(ckehoe): Move these into a separate file? 98 // TODO(ckehoe): Move these into a separate file?
109 99
110 template <typename T> 100 template <typename T>
111 BroadcastScanConfiguration GetBroadcastScanConfig(const T& msg) { 101 BroadcastScanConfiguration GetBroadcastScanConfig(const T& msg) {
112 if (msg.has_token_exchange_strategy() && 102 if (msg.has_token_exchange_strategy() &&
113 msg.token_exchange_strategy().has_broadcast_scan_configuration()) { 103 msg.token_exchange_strategy().has_broadcast_scan_configuration()) {
114 return msg.token_exchange_strategy().broadcast_scan_configuration(); 104 return msg.token_exchange_strategy().broadcast_scan_configuration();
115 } 105 }
116 return BROADCAST_SCAN_CONFIGURATION_UNKNOWN; 106 return BROADCAST_SCAN_CONFIGURATION_UNKNOWN;
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
152 TokenObservation* token_observation = 142 TokenObservation* token_observation =
153 request->mutable_update_signals_request()->add_token_observation(); 143 request->mutable_update_signals_request()->add_token_observation();
154 token_observation->set_token_id(ToUrlSafe(token.token)); 144 token_observation->set_token_id(ToUrlSafe(token.token));
155 145
156 TokenSignals* signals = token_observation->add_signals(); 146 TokenSignals* signals = token_observation->add_signals();
157 signals->set_medium(token.audible ? AUDIO_AUDIBLE_DTMF 147 signals->set_medium(token.audible ? AUDIO_AUDIBLE_DTMF
158 : AUDIO_ULTRASOUND_PASSBAND); 148 : AUDIO_ULTRASOUND_PASSBAND);
159 signals->set_observed_time_millis(base::Time::Now().ToJsTime()); 149 signals->set_observed_time_millis(base::Time::Now().ToJsTime());
160 } 150 }
161 151
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
162 } // namespace 160 } // namespace
163 161
164 162
165 // Public functions. 163 // Public functions.
166 164
167 RpcHandler::RpcHandler(CopresenceDelegate* delegate, 165 RpcHandler::RpcHandler(CopresenceDelegate* delegate,
168 DirectiveHandler* directive_handler, 166 DirectiveHandler* directive_handler,
169 GCMHandler* gcm_handler,
170 const PostCallback& server_post_callback) 167 const PostCallback& server_post_callback)
171 : delegate_(delegate), 168 : delegate_(delegate),
172 directive_handler_(directive_handler), 169 directive_handler_(directive_handler),
173 gcm_handler_(gcm_handler),
174 server_post_callback_(server_post_callback), 170 server_post_callback_(server_post_callback),
175 invalid_audio_token_cache_( 171 invalid_audio_token_cache_(
176 base::TimeDelta::FromMilliseconds(kInvalidTokenExpiryTimeMs), 172 base::TimeDelta::FromMilliseconds(kInvalidTokenExpiryTimeMs),
177 kMaxInvalidTokens) { 173 kMaxInvalidTokens) {
178 DCHECK(delegate_); 174 DCHECK(delegate_);
179 DCHECK(directive_handler_); 175 DCHECK(directive_handler_);
180 // |gcm_handler_| is optional.
181 176
182 if (server_post_callback_.is_null()) { 177 if (server_post_callback_.is_null()) {
183 server_post_callback_ = 178 server_post_callback_ =
184 base::Bind(&RpcHandler::SendHttpPost, base::Unretained(this)); 179 base::Bind(&RpcHandler::SendHttpPost, base::Unretained(this));
185 } 180 }
186
187 if (gcm_handler_) {
188 gcm_handler_->GetGcmId(
189 base::Bind(&RpcHandler::RegisterGcmId, base::Unretained(this)));
190 }
191 } 181 }
192 182
193 RpcHandler::~RpcHandler() { 183 RpcHandler::~RpcHandler() {
194 // Do not use |directive_handler_| or |gcm_handler_| here. 184 // Do not use |directive_handler_| here.
195 // They will already have been destructed. 185 // It will already have been destructed.
196 for (HttpPost* post : pending_posts_) 186 for (HttpPost* post : pending_posts_)
197 delete post; 187 delete post;
198 } 188 }
199 189
200 void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request, 190 void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request,
201 const std::string& app_id, 191 const std::string& app_id,
202 const std::string& auth_token, 192 const std::string& auth_token,
203 const StatusCallback& status_callback) { 193 const StatusCallback& status_callback) {
204 DCHECK(request.get()); 194 DCHECK(request.get());
205 195
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
261 AddTokenToRequest(token, &request); 251 AddTokenToRequest(token, &request);
262 } 252 }
263 253
264 // Report under all active tokens. 254 // Report under all active tokens.
265 for (const auto& registration : device_id_by_auth_token_) { 255 for (const auto& registration : device_id_by_auth_token_) {
266 SendReportRequest(make_scoped_ptr(new ReportRequest(request)), 256 SendReportRequest(make_scoped_ptr(new ReportRequest(request)),
267 registration.first); 257 registration.first);
268 } 258 }
269 } 259 }
270 260
271 261 // Private methods
272 // Private functions.
273 262
274 RpcHandler::PendingRequest::PendingRequest(scoped_ptr<ReportRequest> report, 263 RpcHandler::PendingRequest::PendingRequest(scoped_ptr<ReportRequest> report,
275 const std::string& app_id, 264 const std::string& app_id,
276 const std::string& auth_token, 265 const std::string& auth_token,
277 const StatusCallback& callback) 266 const StatusCallback& callback)
278 : report(report.Pass()), 267 : report(report.Pass()),
279 app_id(app_id), 268 app_id(app_id),
280 auth_token(auth_token), 269 auth_token(auth_token),
281 callback(callback) {} 270 callback(callback) {}
282 271
283 RpcHandler::PendingRequest::~PendingRequest() {} 272 RpcHandler::PendingRequest::~PendingRequest() {}
284 273
285 void RpcHandler::RegisterForToken(const std::string& auth_token) { 274 void RpcHandler::RegisterForToken(const std::string& auth_token) {
286 DVLOG(2) << "Sending " << LoggingStrForToken(auth_token) 275 DVLOG(2) << "Sending " << LoggingStrForToken(auth_token)
287 << " registration to server."; 276 << " registration to server.";
288 277
278 // Mark registration as in progress.
279 device_id_by_auth_token_[auth_token] = "";
280
289 scoped_ptr<RegisterDeviceRequest> request(new RegisterDeviceRequest); 281 scoped_ptr<RegisterDeviceRequest> request(new RegisterDeviceRequest);
290 282 request->mutable_push_service()->set_service(PUSH_SERVICE_NONE);
291 // Add a GCM ID for authenticated registration, if we have one.
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 }
300 283
301 // Only identify as a Chrome device if we're in anonymous mode. 284 // Only identify as a Chrome device if we're in anonymous mode.
302 // Authenticated calls come from a "GAIA device". 285 // Authenticated calls come from a "GAIA device".
303 if (auth_token.empty()) { 286 if (auth_token.empty()) {
304 Identity* identity = 287 Identity* identity =
305 request->mutable_device_identifiers()->mutable_registrant(); 288 request->mutable_device_identifiers()->mutable_registrant();
306 identity->set_type(CHROME); 289 identity->set_type(CHROME);
307 identity->set_chrome_id(base::GenerateGUID()); 290 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";
313 } 291 }
314 292
315 bool gcm_pending = !auth_token.empty() && gcm_handler_ && gcm_id_.empty();
316 SendServerRequest( 293 SendServerRequest(
317 kRegisterDeviceRpcName, 294 kRegisterDeviceRpcName,
318 // This will have the side effect of populating an empty device ID 295 std::string(), // 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],
322 std::string(), // app ID 296 std::string(), // app ID
323 auth_token, 297 auth_token,
324 request.Pass(), 298 request.Pass(),
325 base::Bind(&RpcHandler::RegisterResponseHandler, 299 base::Bind(&RpcHandler::RegisterResponseHandler,
326 // On destruction, this request will be cancelled. 300 // On destruction, this request will be cancelled.
327 base::Unretained(this), 301 base::Unretained(this),
328 auth_token, 302 auth_token));
329 gcm_pending));
330 } 303 }
331 304
332 void RpcHandler::ProcessQueuedRequests(const std::string& auth_token) { 305 void RpcHandler::ProcessQueuedRequests(const std::string& auth_token) {
333 // Track requests that are not on this auth token. 306 // Track requests that are not on this auth token.
334 ScopedVector<PendingRequest> still_pending_requests; 307 ScopedVector<PendingRequest> still_pending_requests;
335 308
336 // If there is no device ID for this auth token, registration failed. 309 // If there is no device ID for this auth token, registration failed.
337 bool registration_failed = 310 bool registration_failed =
338 (device_id_by_auth_token_.count(auth_token) == 0); 311 (device_id_by_auth_token_.count(auth_token) == 0);
339 312
(...skipping 23 matching lines...) Expand all
363 } 336 }
364 337
365 void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request, 338 void RpcHandler::SendReportRequest(scoped_ptr<ReportRequest> request,
366 const std::string& auth_token) { 339 const std::string& auth_token) {
367 SendReportRequest(request.Pass(), 340 SendReportRequest(request.Pass(),
368 std::string(), 341 std::string(),
369 auth_token, 342 auth_token,
370 StatusCallback()); 343 StatusCallback());
371 } 344 }
372 345
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
405 void RpcHandler::RegisterResponseHandler( 346 void RpcHandler::RegisterResponseHandler(
406 const std::string& auth_token, 347 const std::string& auth_token,
407 bool gcm_pending,
408 HttpPost* completed_post, 348 HttpPost* completed_post,
409 int http_status_code, 349 int http_status_code,
410 const std::string& response_data) { 350 const std::string& response_data) {
411 if (completed_post) { 351 if (completed_post) {
412 int elements_erased = pending_posts_.erase(completed_post); 352 int elements_erased = pending_posts_.erase(completed_post);
413 DCHECK_GT(elements_erased, 0); 353 DCHECK_GT(elements_erased, 0);
414 delete completed_post; 354 delete completed_post;
415 } 355 }
416 356
417 // Registration is no longer in progress. 357 // Registration is no longer in progress.
418 // If it was successful, we'll update below. 358 // If it was successful, we'll update below.
419 device_id_by_auth_token_.erase(auth_token); 359 device_id_by_auth_token_.erase(auth_token);
420 360
421 RegisterDeviceResponse response; 361 RegisterDeviceResponse response;
422 if (http_status_code != net::HTTP_OK) { 362 if (http_status_code != net::HTTP_OK) {
423 // TODO(ckehoe): Retry registration if appropriate. 363 // TODO(ckehoe): Retry registration if appropriate.
424 LOG(ERROR) << LoggingStrForToken(auth_token) 364 LOG(ERROR) << LoggingStrForToken(auth_token)
425 << " device registration failed"; 365 << " device registration failed";
426 } else if (!response.ParseFromString(response_data)) { 366 } else if (!response.ParseFromString(response_data)) {
427 LOG(ERROR) << "Invalid RegisterDeviceResponse:\n" << response_data; 367 LOG(ERROR) << "Invalid RegisterDeviceResponse:\n" << response_data;
428 } else if (!IsErrorStatus(response.header().status())) { 368 } else if (!IsErrorStatus(response.header().status())) {
429 const std::string& device_id = response.registered_device_id(); 369 const std::string& device_id = response.registered_device_id();
430 DCHECK(!device_id.empty()); 370 DCHECK(!device_id.empty());
431 device_id_by_auth_token_[auth_token] = device_id; 371 device_id_by_auth_token_[auth_token] = device_id;
432 DVLOG(2) << LoggingStrForToken(auth_token) 372 DVLOG(2) << LoggingStrForToken(auth_token)
433 << " device registration successful. Id: " << device_id; 373 << " 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);
438 } 374 }
439 375
440 // Send or fail requests on this auth token. 376 // Send or fail requests on this auth token.
441 ProcessQueuedRequests(auth_token); 377 ProcessQueuedRequests(auth_token);
442 } 378 }
443 379
444 void RpcHandler::ReportResponseHandler(const StatusCallback& status_callback, 380 void RpcHandler::ReportResponseHandler(const StatusCallback& status_callback,
445 HttpPost* completed_post, 381 HttpPost* completed_post,
446 int http_status_code, 382 int http_status_code,
447 const std::string& response_data) { 383 const std::string& response_data) {
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after
576 const std::string& device_id) const { 512 const std::string& device_id) const {
577 RequestHeader* header = new RequestHeader; 513 RequestHeader* header = new RequestHeader;
578 514
579 header->set_allocated_framework_version(CreateVersion( 515 header->set_allocated_framework_version(CreateVersion(
580 "Chrome", delegate_->GetPlatformVersionString())); 516 "Chrome", delegate_->GetPlatformVersionString()));
581 if (!client_name.empty()) { 517 if (!client_name.empty()) {
582 header->set_allocated_client_version( 518 header->set_allocated_client_version(
583 CreateVersion(client_name, std::string())); 519 CreateVersion(client_name, std::string()));
584 } 520 }
585 header->set_current_time_millis(base::Time::Now().ToJsTime()); 521 header->set_current_time_millis(base::Time::Now().ToJsTime());
586 if (!device_id.empty()) 522 header->set_registered_device_id(device_id);
587 header->set_registered_device_id(device_id);
588 523
589 DeviceFingerprint* fingerprint = new DeviceFingerprint; 524 DeviceFingerprint* fingerprint = new DeviceFingerprint;
590 fingerprint->set_platform_version(delegate_->GetPlatformVersionString()); 525 fingerprint->set_platform_version(delegate_->GetPlatformVersionString());
591 fingerprint->set_type(CHROME_PLATFORM_TYPE); 526 fingerprint->set_type(CHROME_PLATFORM_TYPE);
592 header->set_allocated_device_fingerprint(fingerprint); 527 header->set_allocated_device_fingerprint(fingerprint);
593 528
594 return header; 529 return header;
595 } 530 }
596 531
597 template <class T> 532 template <class T>
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
632 api_key, 567 api_key,
633 auth_token, 568 auth_token,
634 command_line->GetSwitchValueASCII(switches::kCopresenceTracingToken), 569 command_line->GetSwitchValueASCII(switches::kCopresenceTracingToken),
635 *request_proto); 570 *request_proto);
636 571
637 http_post->Start(base::Bind(callback, http_post)); 572 http_post->Start(base::Bind(callback, http_post));
638 pending_posts_.insert(http_post); 573 pending_posts_.insert(http_post);
639 } 574 }
640 575
641 } // namespace copresence 576 } // 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