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

Side by Side Diff: components/safe_browsing_db/v4_get_hash_protocol_manager.cc

Issue 2233103002: Move full hash caching logic to v4_get_hash_protocol_manager (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 4 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
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 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/safe_browsing_db/v4_get_hash_protocol_manager.h" 5 #include "components/safe_browsing_db/v4_get_hash_protocol_manager.h"
6 6
7 #include <utility> 7 #include <utility>
8 8
9 #include "base/base64url.h" 9 #include "base/base64url.h"
10 #include "base/macros.h" 10 #include "base/macros.h"
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
45 INCONSISTENT_THREAT_TYPE_ERROR = 5, 45 INCONSISTENT_THREAT_TYPE_ERROR = 5,
46 46
47 // A match in the response contained a metadata, but the metadata is invalid. 47 // A match in the response contained a metadata, but the metadata is invalid.
48 UNEXPECTED_METADATA_VALUE_ERROR = 6, 48 UNEXPECTED_METADATA_VALUE_ERROR = 6,
49 49
50 // Memory space for histograms is determined by the max. ALWAYS 50 // Memory space for histograms is determined by the max. ALWAYS
51 // ADD NEW VALUES BEFORE THIS ONE. 51 // ADD NEW VALUES BEFORE THIS ONE.
52 PARSE_RESULT_TYPE_MAX = 7, 52 PARSE_RESULT_TYPE_MAX = 7,
53 }; 53 };
54 54
55 // Enumerate full hash cache hits/misses for histogramming purposes.
56 // DO NOT CHANGE THE ORDERING OF THESE VALUES.
57 enum V4FullHashCacheResultType {
58 // Full hashes for which there is no cache hit.
59 FULL_HASH_CACHE_MISS = 0,
60
61 // Full hashes with a cache hit.
62 FULL_HASH_CACHE_HIT = 1,
63
64 // Full hashes with a negative cache hit.
65 FULL_HASH_NEGATIVE_CACHE_HIT = 2,
66
67 // Memory space for histograms is determined by the max. ALWAYS
68 // ADD NEW VALUES BEFORE THIS ONE.
69 FULL_HASH_CACHE_RESULT_MAX
70 };
71
55 // Record parsing errors of a GetHash result. 72 // Record parsing errors of a GetHash result.
56 void RecordParseGetHashResult(ParseResultType result_type) { 73 void RecordParseGetHashResult(ParseResultType result_type) {
57 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4HashResult", result_type, 74 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.ParseV4HashResult", result_type,
58 PARSE_RESULT_TYPE_MAX); 75 PARSE_RESULT_TYPE_MAX);
59 } 76 }
60 77
61 // Record a GetHash result. 78 // Record a GetHash result.
62 void RecordGetHashResult(safe_browsing::V4OperationResult result) { 79 void RecordGetHashResult(safe_browsing::V4OperationResult result) {
63 UMA_HISTOGRAM_ENUMERATION( 80 UMA_HISTOGRAM_ENUMERATION(
64 "SafeBrowsing.GetV4HashResult", result, 81 "SafeBrowsing.GetV4HashResult", result,
65 safe_browsing::V4OperationResult::OPERATION_RESULT_MAX); 82 safe_browsing::V4OperationResult::OPERATION_RESULT_MAX);
66 } 83 }
67 84
85 // Record a full hash cache hit result.
86 void RecordV4FullHashCacheResult(
87 V4FullHashCacheResultType result_type) {
88 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4FullHashCacheResult", result_type,
89 FULL_HASH_CACHE_RESULT_MAX);
90 }
91
68 } // namespace 92 } // namespace
69 93
70 namespace safe_browsing { 94 namespace safe_browsing {
71 95
72 const char kUmaV4HashResponseMetricName[] = 96 const char kUmaV4HashResponseMetricName[] =
73 "SafeBrowsing.GetV4HashHttpResponseOrErrorCode"; 97 "SafeBrowsing.GetV4HashHttpResponseOrErrorCode";
74 98
75 // The default V4GetHashProtocolManagerFactory. 99 // The default V4GetHashProtocolManagerFactory.
76 class V4GetHashProtocolManagerFactoryImpl 100 class V4GetHashProtocolManagerFactoryImpl
77 : public V4GetHashProtocolManagerFactory { 101 : public V4GetHashProtocolManagerFactory {
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
113 : gethash_error_count_(0), 137 : gethash_error_count_(0),
114 gethash_back_off_mult_(1), 138 gethash_back_off_mult_(1),
115 next_gethash_time_(Time::FromDoubleT(0)), 139 next_gethash_time_(Time::FromDoubleT(0)),
116 config_(config), 140 config_(config),
117 request_context_getter_(request_context_getter), 141 request_context_getter_(request_context_getter),
118 url_fetcher_id_(0), 142 url_fetcher_id_(0),
119 clock_(new base::DefaultClock()) {} 143 clock_(new base::DefaultClock()) {}
120 144
121 V4GetHashProtocolManager::~V4GetHashProtocolManager() { 145 V4GetHashProtocolManager::~V4GetHashProtocolManager() {
122 // Delete in-progress SafeBrowsing requests. 146 // Delete in-progress SafeBrowsing requests.
123 STLDeleteContainerPairFirstPointers(hash_requests_.begin(), 147 STLDeleteContainerPairFirstPointers(pending_hash_requests_.begin(),
124 hash_requests_.end()); 148 pending_hash_requests_.end());
125 hash_requests_.clear(); 149 pending_hash_requests_.clear();
126 } 150 }
127 151
128 // static 152 // static
129 void V4GetHashProtocolManager::RegisterFactory( 153 void V4GetHashProtocolManager::RegisterFactory(
130 std::unique_ptr<V4GetHashProtocolManagerFactory> factory) { 154 std::unique_ptr<V4GetHashProtocolManagerFactory> factory) {
131 if (factory_) 155 if (factory_)
132 delete factory_; 156 delete factory_;
133 factory_ = factory.release(); 157 factory_ = factory.release();
134 } 158 }
135 159
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
205 if (expected_threat_type == THREAT_TYPE_UNSPECIFIED) { 229 if (expected_threat_type == THREAT_TYPE_UNSPECIFIED) {
206 expected_threat_type = match.threat_type(); 230 expected_threat_type = match.threat_type();
207 } else if (match.threat_type() != expected_threat_type) { 231 } else if (match.threat_type() != expected_threat_type) {
208 RecordParseGetHashResult(INCONSISTENT_THREAT_TYPE_ERROR); 232 RecordParseGetHashResult(INCONSISTENT_THREAT_TYPE_ERROR);
209 return false; 233 return false;
210 } 234 }
211 235
212 // Fill in the full hash. 236 // Fill in the full hash.
213 SBFullHashResult result; 237 SBFullHashResult result;
214 result.hash = StringToSBFullHash(match.threat().hash()); 238 result.hash = StringToSBFullHash(match.threat().hash());
215
216 if (match.has_cache_duration()) { 239 if (match.has_cache_duration()) {
217 // Seconds resolution is good enough so we ignore the nanos field. 240 // Seconds resolution is good enough so we ignore the nanos field.
218 result.cache_expire_after = 241 result.cache_expire_after =
219 clock_->Now() + 242 clock_->Now() +
220 base::TimeDelta::FromSeconds(match.cache_duration().seconds()); 243 base::TimeDelta::FromSeconds(match.cache_duration().seconds());
221 } else { 244 } else {
222 result.cache_expire_after = clock_->Now(); 245 result.cache_expire_after = clock_->Now();
223 } 246 }
224 247
225 // Different threat types will handle the metadata differently. 248 // Different threat types will handle the metadata differently.
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after
321 std::string req_base64 = GetHashRequest(prefixes, platforms, threat_type); 344 std::string req_base64 = GetHashRequest(prefixes, platforms, threat_type);
322 GURL gethash_url; 345 GURL gethash_url;
323 net::HttpRequestHeaders headers; 346 net::HttpRequestHeaders headers;
324 GetHashUrlAndHeaders(req_base64, &gethash_url, &headers); 347 GetHashUrlAndHeaders(req_base64, &gethash_url, &headers);
325 348
326 net::URLFetcher* fetcher = 349 net::URLFetcher* fetcher =
327 net::URLFetcher::Create(url_fetcher_id_++, gethash_url, 350 net::URLFetcher::Create(url_fetcher_id_++, gethash_url,
328 net::URLFetcher::GET, this) 351 net::URLFetcher::GET, this)
329 .release(); 352 .release();
330 fetcher->SetExtraRequestHeaders(headers.ToString()); 353 fetcher->SetExtraRequestHeaders(headers.ToString());
331 hash_requests_[fetcher] = callback; 354 pending_hash_requests_[fetcher] = std::make_pair(prefixes, callback);
332 355
333 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE); 356 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE);
334 fetcher->SetRequestContext(request_context_getter_.get()); 357 fetcher->SetRequestContext(request_context_getter_.get());
335 fetcher->Start(); 358 fetcher->Start();
336 } 359 }
337 360
338 void V4GetHashProtocolManager::GetFullHashesWithApis( 361 void V4GetHashProtocolManager::GetFullHashesWithApis(
339 const std::vector<SBPrefix>& prefixes, 362 const std::vector<SBPrefix>& prefixes,
340 FullHashCallback callback) { 363 FullHashCallback callback) {
341 std::vector<PlatformType> platform = {CHROME_PLATFORM}; 364 std::vector<PlatformType> platform = {CHROME_PLATFORM};
342 GetFullHashes(prefixes, platform, API_ABUSE, callback); 365 GetFullHashes(prefixes, platform, API_ABUSE, callback);
343 } 366 }
344 367
345 void V4GetHashProtocolManager::SetClockForTests( 368 void V4GetHashProtocolManager::SetClockForTests(
346 std::unique_ptr<base::Clock> clock) { 369 std::unique_ptr<base::Clock> clock) {
347 clock_ = std::move(clock); 370 clock_ = std::move(clock);
348 } 371 }
349 372
350 // net::URLFetcherDelegate implementation ---------------------------------- 373 // net::URLFetcherDelegate implementation ----------------------------------
351 374
352 // SafeBrowsing request responses are handled here. 375 // SafeBrowsing request responses are handled here.
353 void V4GetHashProtocolManager::OnURLFetchComplete( 376 void V4GetHashProtocolManager::OnURLFetchComplete(
354 const net::URLFetcher* source) { 377 const net::URLFetcher* source) {
355 DCHECK(CalledOnValidThread()); 378 DCHECK(CalledOnValidThread());
356 379
357 HashRequests::iterator it = hash_requests_.find(source); 380 PendingHashRequests::iterator it = pending_hash_requests_.find(source);
358 DCHECK(it != hash_requests_.end()) << "Request not found"; 381 DCHECK(it != pending_hash_requests_.end()) << "Request not found";
359 382
360 // FindFullHashes response. 383 // FindFullHashes response.
361 // Reset the scoped pointer so the fetcher gets destroyed properly. 384 // Reset the scoped pointer so the fetcher gets destroyed properly.
362 std::unique_ptr<const net::URLFetcher> fetcher(it->first); 385 std::unique_ptr<const net::URLFetcher> fetcher(it->first);
363 386
364 int response_code = source->GetResponseCode(); 387 int response_code = source->GetResponseCode();
365 net::URLRequestStatus status = source->GetStatus(); 388 net::URLRequestStatus status = source->GetStatus();
366 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode( 389 V4ProtocolManagerUtil::RecordHttpResponseOrErrorCode(
367 kUmaV4HashResponseMetricName, status, response_code); 390 kUmaV4HashResponseMetricName, status, response_code);
368 391
369 const FullHashCallback& callback = it->second; 392 const FullHashCallback& callback = it->second.second;
Nathan Parker 2016/08/15 19:54:47 nit: I might vote for a simple struct rather than
370 std::vector<SBFullHashResult> full_hashes; 393 std::vector<SBFullHashResult> full_hashes;
371 base::Time negative_cache_expire; 394 base::Time negative_cache_expire;
372 if (status.is_success() && response_code == net::HTTP_OK) { 395 if (status.is_success() && response_code == net::HTTP_OK) {
373 RecordGetHashResult(V4OperationResult::STATUS_200); 396 RecordGetHashResult(V4OperationResult::STATUS_200);
374 ResetGetHashErrors(); 397 ResetGetHashErrors();
375 std::string data; 398 std::string data;
376 source->GetResponseAsString(&data); 399 source->GetResponseAsString(&data);
377 if (!ParseHashResponse(data, &full_hashes, &negative_cache_expire)) { 400 if (!ParseHashResponse(data, &full_hashes, &negative_cache_expire)) {
378 full_hashes.clear(); 401 full_hashes.clear();
379 RecordGetHashResult(V4OperationResult::PARSE_ERROR); 402 RecordGetHashResult(V4OperationResult::PARSE_ERROR);
380 } 403 }
381 } else { 404 } else {
382 HandleGetHashError(clock_->Now()); 405 HandleGetHashError(clock_->Now());
383 406
384 DVLOG(1) << "SafeBrowsing GetEncodedFullHashes request for: " 407 DVLOG(1) << "SafeBrowsing GetEncodedFullHashes request for: "
385 << source->GetURL() << " failed with error: " << status.error() 408 << source->GetURL() << " failed with error: " << status.error()
386 << " and response code: " << response_code; 409 << " and response code: " << response_code;
387 410
388 if (status.status() == net::URLRequestStatus::FAILED) { 411 if (status.status() == net::URLRequestStatus::FAILED) {
389 RecordGetHashResult(V4OperationResult::NETWORK_ERROR); 412 RecordGetHashResult(V4OperationResult::NETWORK_ERROR);
390 } else { 413 } else {
391 RecordGetHashResult(V4OperationResult::HTTP_ERROR); 414 RecordGetHashResult(V4OperationResult::HTTP_ERROR);
392 } 415 }
393 } 416 }
394 417
418 const std::vector<SBPrefix>& prefixes = it->second.first;
419 // If the time is uninitialized, don't cache the results.
420 if (!negative_cache_expire.is_null()) {
421 // Cache the results.
422 // Create or reset all cached results for this prefix.
423 for (const SBPrefix& prefix : prefixes) {
424 v4_full_hash_cache_[SB_THREAT_TYPE_API_ABUSE][prefix] =
Nathan Parker 2016/08/15 19:54:47 The threat type shouldn't be hardcoded here... it
425 SBCachedFullHashResult(negative_cache_expire);
426 }
427 // Insert any full hash hits. Note that there may be one, multiple, or no
428 // full hashes for any given prefix.
429 for (const SBFullHashResult& result : full_hashes) {
430 v4_full_hash_cache_[SB_THREAT_TYPE_API_ABUSE][result.hash.prefix].
431 full_hashes.push_back(result);
432 }
433 }
434
395 // Invoke the callback with full_hashes, even if there was a parse error or 435 // Invoke the callback with full_hashes, even if there was a parse error or
396 // an error response code (in which case full_hashes will be empty). The 436 // an error response code (in which case full_hashes will be empty). The
397 // caller can't be blocked indefinitely. 437 // caller can't be blocked indefinitely.
398 callback.Run(full_hashes, negative_cache_expire); 438 callback.Run(full_hashes, negative_cache_expire);
399 439
400 hash_requests_.erase(it); 440 pending_hash_requests_.erase(it);
401 } 441 }
402 442
403 void V4GetHashProtocolManager::HandleGetHashError(const Time& now) { 443 void V4GetHashProtocolManager::HandleGetHashError(const Time& now) {
404 DCHECK(CalledOnValidThread()); 444 DCHECK(CalledOnValidThread());
405 base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( 445 base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval(
406 &gethash_error_count_, &gethash_back_off_mult_); 446 &gethash_error_count_, &gethash_back_off_mult_);
407 next_gethash_time_ = now + next; 447 next_gethash_time_ = now + next;
408 } 448 }
409 449
410 void V4GetHashProtocolManager::GetHashUrlAndHeaders( 450 void V4GetHashProtocolManager::GetHashUrlAndHeaders(
411 const std::string& req_base64, 451 const std::string& req_base64,
412 GURL* gurl, 452 GURL* gurl,
413 net::HttpRequestHeaders* headers) const { 453 net::HttpRequestHeaders* headers) const {
414 V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find", 454 V4ProtocolManagerUtil::GetRequestUrlAndHeaders(req_base64, "fullHashes:find",
415 config_, gurl, headers); 455 config_, gurl, headers);
416 } 456 }
417 457
458 void V4GetHashProtocolManager::GetFullHashCachedResults(
459 const SBThreatType& threat_type,
460 const std::vector<SBFullHash>& full_hashes,
461 base::Time now,
462 std::vector<SBPrefix>* prefixes_needing_reqs,
463 std::vector<SBFullHashResult>* cached_results) {
464 DCHECK(prefixes_needing_reqs);
465 prefixes_needing_reqs->clear();
466 DCHECK(cached_results);
467 cached_results->clear();
468
469 // Caching behavior is documented here:
470 // https://developers.google.com/safe-browsing/v4/caching#about-caching
471 //
472 // The cache operates as follows:
473 // Lookup:
474 // Case 1: The prefix is in the cache.
475 // Case a: The full hash is in the cache.
476 // Case i : The positive full hash result has not expired.
477 // The result is unsafe and we do not need to send a new
478 // request.
479 // Case ii: The positive full hash result has expired.
480 // We need to send a request for full hashes.
481 // Case b: The full hash is not in the cache.
482 // Case i : The negative cache entry has not expired.
483 // The result is still safe and we do not need to send a
484 // new request.
485 // Case ii: The negative cache entry has expired.
486 // We need to send a request for full hashes.
487 // Case 2: The prefix is not in the cache.
488 // We need to send a request for full hashes.
489 //
490 // Eviction:
491 // SBCachedFullHashResult entries can be removed from the cache only when
492 // the negative cache expire time and the cache expire time of all full
493 // hash results for that prefix have expired.
494 // Individual full hash results can be removed from the prefix's
495 // cache entry if they expire AND their expire time is after the negative
496 // cache expire time.
497 for (const SBFullHash& full_hash : full_hashes) {
498 auto entry = v4_full_hash_cache_[threat_type].find(full_hash.prefix);
499 if (entry != v4_full_hash_cache_[threat_type].end()) {
500 // Case 1.
501 SBCachedFullHashResult& cache_result = entry->second;
502
503 const SBFullHashResult* found_full_hash = nullptr;
504 size_t matched_idx = 0;
505 for (const SBFullHashResult& hash_result : cache_result.full_hashes) {
506 if (SBFullHashEqual(full_hash, hash_result.hash)) {
507 found_full_hash = &hash_result;
508 break;
509 }
510 ++matched_idx;
511 }
512
513 if (found_full_hash) {
514 // Case a.
515 if (found_full_hash->cache_expire_after > now) {
516 // Case i.
517 cached_results->push_back(*found_full_hash);
518 RecordV4FullHashCacheResult(FULL_HASH_CACHE_HIT);
519 } else {
520 // Case ii.
521 prefixes_needing_reqs->push_back(full_hash.prefix);
522 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS);
523 // If the negative cache expire time has passed, evict this full hash
524 // result from the cache.
525 if (cache_result.expire_after <= now) {
526 cache_result.full_hashes.erase(
527 cache_result.full_hashes.begin() + matched_idx);
528 // If there are no more full hashes, we can evict the entire entry.
529 if (cache_result.full_hashes.empty()) {
530 v4_full_hash_cache_[threat_type].erase(entry);
531 }
532 }
533 }
534 } else {
535 // Case b.
536 if (cache_result.expire_after > now) {
537 // Case i.
538 RecordV4FullHashCacheResult(FULL_HASH_NEGATIVE_CACHE_HIT);
539 } else {
540 // Case ii.
541 prefixes_needing_reqs->push_back(full_hash.prefix);
542 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS);
543 }
544 }
545 } else {
546 // Case 2.
547 prefixes_needing_reqs->push_back(full_hash.prefix);
548 RecordV4FullHashCacheResult(FULL_HASH_CACHE_MISS);
549 }
550 }
551
552 // Multiple full hashes could share a prefix, remove duplicates.
553 // TODO(kcarattini): Make |prefixes_needing_reqs| a set.
554 std::sort(prefixes_needing_reqs->begin(), prefixes_needing_reqs->end());
555 prefixes_needing_reqs->erase(std::unique(prefixes_needing_reqs->begin(),
556 prefixes_needing_reqs->end()), prefixes_needing_reqs->end());
557 }
558
418 } // namespace safe_browsing 559 } // namespace safe_browsing
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698