Commit dd133eb6 authored by meacer's avatar meacer Committed by Commit bot

Implement main CertificateReportingService code and add unit tests.

This CL adds the actual CertificateReportingService code that's going to be
used to send reports. The service is still not wired to any other service, so is
off by default.

BUG=554323

Review-Url: https://codereview.chromium.org/2543523002
Cr-Commit-Position: refs/heads/master@{#438017}
parent d722a94d
...@@ -4209,6 +4209,8 @@ static_library("test_support") { ...@@ -4209,6 +4209,8 @@ static_library("test_support") {
# "Safe Browsing Basic" files used for safe browsing in full mode # "Safe Browsing Basic" files used for safe browsing in full mode
# (safe_browsing=1) and mobile (=2) # (safe_browsing=1) and mobile (=2)
sources += [ sources += [
"safe_browsing/certificate_reporting_service_test_utils.cc",
"safe_browsing/certificate_reporting_service_test_utils.h",
"safe_browsing/mock_permission_report_sender.cc", "safe_browsing/mock_permission_report_sender.cc",
"safe_browsing/mock_permission_report_sender.h", "safe_browsing/mock_permission_report_sender.h",
] ]
......
...@@ -2,12 +2,20 @@ ...@@ -2,12 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "base/bind_helpers.h"
#include "base/time/clock.h" #include "base/time/clock.h"
#include "base/time/default_clock.h" #include "base/time/default_clock.h"
#include "chrome/browser/safe_browsing/certificate_reporting_service.h" #include "chrome/browser/safe_browsing/certificate_reporting_service.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
namespace { namespace {
// URL to upload invalid certificate chain reports. An HTTP URL is used because
// a client seeing an invalid cert might not be able to make an HTTPS connection
// to report it.
const char kExtendedReportingUploadUrl[] =
"http://safebrowsing.googleusercontent.com/safebrowsing/clientreport/";
// Compare function that orders Reports in reverse chronological order (i.e. // Compare function that orders Reports in reverse chronological order (i.e.
// oldest item is last). // oldest item is last).
bool ReportCompareFunc(const CertificateReportingService::Report& item1, bool ReportCompareFunc(const CertificateReportingService::Report& item1,
...@@ -57,11 +65,13 @@ CertificateReportingService::Reporter::Reporter( ...@@ -57,11 +65,13 @@ CertificateReportingService::Reporter::Reporter(
std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter, std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter,
std::unique_ptr<BoundedReportList> retry_list, std::unique_ptr<BoundedReportList> retry_list,
base::Clock* clock, base::Clock* clock,
base::TimeDelta report_ttl) base::TimeDelta report_ttl,
bool retries_enabled)
: error_reporter_(std::move(error_reporter)), : error_reporter_(std::move(error_reporter)),
retry_list_(std::move(retry_list)), retry_list_(std::move(retry_list)),
test_clock_(clock), clock_(clock),
report_ttl_(report_ttl), report_ttl_(report_ttl),
retries_enabled_(retries_enabled),
current_report_id_(0), current_report_id_(0),
weak_factory_(this) {} weak_factory_(this) {}
...@@ -70,15 +80,15 @@ CertificateReportingService::Reporter::~Reporter() {} ...@@ -70,15 +80,15 @@ CertificateReportingService::Reporter::~Reporter() {}
void CertificateReportingService::Reporter::Send( void CertificateReportingService::Reporter::Send(
const std::string& serialized_report) { const std::string& serialized_report) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
base::Time now = SendInternal(Report(current_report_id_++, clock_->Now(), serialized_report));
test_clock_ ? test_clock_->Now() : base::Time::NowFromSystemTime();
SendInternal(Report(current_report_id_++, now, serialized_report));
} }
void CertificateReportingService::Reporter::SendPending() { void CertificateReportingService::Reporter::SendPending() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
base::Time now = if (!retries_enabled_) {
test_clock_ ? test_clock_->Now() : base::Time::NowFromSystemTime(); return;
}
const base::Time now = clock_->Now();
// Copy pending reports and clear the retry list. // Copy pending reports and clear the retry list.
std::vector<Report> items = retry_list_->items(); std::vector<Report> items = retry_list_->items();
retry_list_->Clear(); retry_list_->Clear();
...@@ -118,9 +128,11 @@ void CertificateReportingService::Reporter::ErrorCallback(int report_id, ...@@ -118,9 +128,11 @@ void CertificateReportingService::Reporter::ErrorCallback(int report_id,
const GURL& url, const GURL& url,
int error) { int error) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
auto it = inflight_reports_.find(report_id); if (retries_enabled_) {
DCHECK(it != inflight_reports_.end()); auto it = inflight_reports_.find(report_id);
retry_list_->Add(it->second); DCHECK(it != inflight_reports_.end());
retry_list_->Add(it->second);
}
CHECK_GT(inflight_reports_.erase(report_id), 0u); CHECK_GT(inflight_reports_.erase(report_id), 0u);
} }
...@@ -128,3 +140,159 @@ void CertificateReportingService::Reporter::SuccessCallback(int report_id) { ...@@ -128,3 +140,159 @@ void CertificateReportingService::Reporter::SuccessCallback(int report_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
CHECK_GT(inflight_reports_.erase(report_id), 0u); CHECK_GT(inflight_reports_.erase(report_id), 0u);
} }
CertificateReportingService::CertificateReportingService(
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
uint8_t server_public_key[/* 32 */],
uint32_t server_public_key_version,
size_t max_queued_report_count,
base::TimeDelta max_report_age,
std::unique_ptr<base::Clock> clock)
: enabled_(true),
url_request_context_(nullptr),
max_queued_report_count_(max_queued_report_count),
max_report_age_(max_report_age),
clock_(std::move(clock)),
made_send_attempt_(false),
server_public_key_(server_public_key),
server_public_key_version_(server_public_key_version) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&CertificateReportingService::InitializeOnIOThread,
base::Unretained(this), enabled_, url_request_context_getter,
max_queued_report_count_, max_report_age_, clock_.get(),
server_public_key_, server_public_key_version_));
}
CertificateReportingService::~CertificateReportingService() {
DCHECK(!reporter_);
}
void CertificateReportingService::Shutdown() {
// Shutdown will be called twice: Once after SafeBrowsing shuts down, and once
// when all KeyedServices shut down. All calls after the first one are no-op.
enabled_ = false;
Reset();
}
void CertificateReportingService::Send(const std::string& serialized_report) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
made_send_attempt_ = true;
if (!reporter_) {
return;
}
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&CertificateReportingService::Reporter::Send,
base::Unretained(reporter_.get()), serialized_report));
}
void CertificateReportingService::SendPending() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
made_send_attempt_ = true;
if (!reporter_) {
return;
}
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&CertificateReportingService::Reporter::SendPending,
base::Unretained(reporter_.get())));
}
void CertificateReportingService::InitializeOnIOThread(
bool enabled,
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
size_t max_queued_report_count,
base::TimeDelta max_report_age,
base::Clock* clock,
uint8_t* server_public_key,
uint32_t server_public_key_version) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!url_request_context_);
url_request_context_ = url_request_context_getter->GetURLRequestContext();
ResetOnIOThread(enabled, url_request_context_, max_queued_report_count,
max_report_age, clock, server_public_key,
server_public_key_version);
}
void CertificateReportingService::SetEnabled(bool enabled) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
enabled_ = enabled;
Reset();
}
CertificateReportingService::Reporter*
CertificateReportingService::GetReporterForTesting() const {
return reporter_.get();
}
void CertificateReportingService::SetMaxQueuedReportCountForTesting(
size_t count) {
DCHECK(!made_send_attempt_);
max_queued_report_count_ = count;
Reset();
}
void CertificateReportingService::SetClockForTesting(
std::unique_ptr<base::Clock> clock) {
DCHECK(!made_send_attempt_);
clock_ = std::move(clock);
Reset();
}
void CertificateReportingService::SetMaxReportAgeForTesting(
base::TimeDelta max_report_age) {
DCHECK(!made_send_attempt_);
max_report_age_ = max_report_age;
Reset();
}
// static
GURL CertificateReportingService::GetReportingURLForTesting() {
return GURL(kExtendedReportingUploadUrl);
}
void CertificateReportingService::Reset() {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&CertificateReportingService::ResetOnIOThread,
base::Unretained(this), enabled_, url_request_context_,
max_queued_report_count_, max_report_age_, clock_.get(),
server_public_key_, server_public_key_version_));
}
void CertificateReportingService::ResetOnIOThread(
bool enabled,
net::URLRequestContext* url_request_context,
size_t max_queued_report_count,
base::TimeDelta max_report_age,
base::Clock* clock,
uint8_t* const server_public_key,
uint32_t server_public_key_version) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// url_request_context_ is null during shutdown.
if (!enabled || !url_request_context) {
reporter_.reset(nullptr);
return;
}
std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter;
if (server_public_key) {
// Only used in tests.
std::unique_ptr<net::ReportSender> report_sender(new net::ReportSender(
url_request_context, net::ReportSender::DO_NOT_SEND_COOKIES));
error_reporter.reset(new certificate_reporting::ErrorReporter(
GURL(kExtendedReportingUploadUrl), server_public_key,
server_public_key_version, std::move(report_sender)));
} else {
error_reporter.reset(new certificate_reporting::ErrorReporter(
url_request_context, GURL(kExtendedReportingUploadUrl),
net::ReportSender::DO_NOT_SEND_COOKIES));
}
reporter_.reset(
new Reporter(std::move(error_reporter),
std::unique_ptr<BoundedReportList>(
new BoundedReportList(max_queued_report_count)),
clock, max_report_age, true /* retries_enabled */));
}
...@@ -16,16 +16,31 @@ ...@@ -16,16 +16,31 @@
#include "base/time/time.h" #include "base/time/time.h"
#include "components/certificate_reporting/error_reporter.h" #include "components/certificate_reporting/error_reporter.h"
#include "components/keyed_service/core/keyed_service.h" #include "components/keyed_service/core/keyed_service.h"
#include "net/url_request/url_request_context_getter.h"
namespace base { namespace base {
class Clock; class Clock;
} }
namespace net {
class URLRequestContextGetter;
}
// This service initiates uploads of invalid certificate reports and retries any // This service initiates uploads of invalid certificate reports and retries any
// failed uploads. // failed uploads. Each report is retried until it's older than a certain time
// to live (TTL). Reports older than this TTL are dropped and no more retried,
// so that the retry list doesn't grow indefinitely.
//
// Lifetime and dependencies:
//
// CertificateReportingService uses the url request context from SafeBrowsing
// service. SafeBrowsing service is created before CertificateReportingService,
// but is also shut down before any KeyedService is shut down. This means that
// CertificateReportingService cannot depend on SafeBrowsing's url request being
// available at all times, and it should know when SafeBrowsing shuts down.
class CertificateReportingService : public KeyedService { class CertificateReportingService : public KeyedService {
public: public:
// Represent a report to be sent. // Represents a report to be sent.
struct Report { struct Report {
int report_id; int report_id;
base::Time creation_time; base::Time creation_time;
...@@ -64,6 +79,8 @@ class CertificateReportingService : public KeyedService { ...@@ -64,6 +79,8 @@ class CertificateReportingService : public KeyedService {
std::vector<Report> items_; std::vector<Report> items_;
base::ThreadChecker thread_checker_; base::ThreadChecker thread_checker_;
DISALLOW_COPY_AND_ASSIGN(BoundedReportList);
}; };
// Class that handles report uploads and implements the upload retry logic. // Class that handles report uploads and implements the upload retry logic.
...@@ -73,7 +90,8 @@ class CertificateReportingService : public KeyedService { ...@@ -73,7 +90,8 @@ class CertificateReportingService : public KeyedService {
std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter_, std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter_,
std::unique_ptr<BoundedReportList> retry_list, std::unique_ptr<BoundedReportList> retry_list,
base::Clock* clock, base::Clock* clock,
base::TimeDelta report_ttl); base::TimeDelta report_ttl,
bool retries_enabled);
~Reporter(); ~Reporter();
// Sends a report. If the send fails, the report will be added to the retry // Sends a report. If the send fails, the report will be added to the retry
...@@ -96,16 +114,105 @@ class CertificateReportingService : public KeyedService { ...@@ -96,16 +114,105 @@ class CertificateReportingService : public KeyedService {
std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter_; std::unique_ptr<certificate_reporting::ErrorReporter> error_reporter_;
std::unique_ptr<BoundedReportList> retry_list_; std::unique_ptr<BoundedReportList> retry_list_;
base::Clock* test_clock_; base::Clock* clock_;
// Maximum age of a queued report. Reports older than this are discarded in
// the next SendPending() call.
const base::TimeDelta report_ttl_; const base::TimeDelta report_ttl_;
const bool retries_enabled_;
// Current report id, starting from zero and monotonically incrementing.
int current_report_id_; int current_report_id_;
std::map<int, Report> inflight_reports_; std::map<int, Report> inflight_reports_;
base::WeakPtrFactory<Reporter> weak_factory_; base::WeakPtrFactory<Reporter> weak_factory_;
DISALLOW_IMPLICIT_CONSTRUCTORS(Reporter); DISALLOW_COPY_AND_ASSIGN(Reporter);
}; };
CertificateReportingService(
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
uint8_t server_public_key[/* 32 */],
uint32_t server_public_key_version,
size_t max_queued_report_count,
base::TimeDelta max_report_age,
std::unique_ptr<base::Clock> clock);
~CertificateReportingService() override;
// KeyedService implementation:
void Shutdown() override;
// Sends a serialized report. If the report upload fails, the upload is
// retried at a future time.
void Send(const std::string& serialized_report);
// Sends pending reports that are in the retry queue.
void SendPending();
// Enables or disables reporting. When disabled, pending report queue is
// cleared and incoming reports are ignored. Reporting is enabled by default
// once the service is initialized.
void SetEnabled(bool enabled);
// Getters and setters for testing.
Reporter* GetReporterForTesting() const;
void SetMaxQueuedReportCountForTesting(size_t max_report_count);
void SetClockForTesting(std::unique_ptr<base::Clock> clock);
void SetMaxReportAgeForTesting(base::TimeDelta max_report_age);
static GURL GetReportingURLForTesting();
private:
void Reset();
void InitializeOnIOThread(
bool enabled,
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
size_t max_queued_report_count,
base::TimeDelta max_report_age,
base::Clock* clock,
uint8_t* server_public_key,
uint32_t server_public_key_version);
// Resets the reporter on the IO thread. Changes in SafeBrowsing or extended
// reporting enabled states cause the reporter to be reset.
// If |enabled| is false or |url_request_context_getter| is null, report is
// set to null, effectively cancelling all in flight uploads and clearing the
// pending reports queue.
void ResetOnIOThread(bool enabled,
net::URLRequestContext* url_request_context,
size_t max_queued_report_count,
base::TimeDelta max_report_age,
base::Clock* clock,
uint8_t* server_public_key,
uint32_t server_public_key_version);
// If true, reporting is enabled. When SafeBrowsing preferences change, this
// might be set to false.
bool enabled_;
net::URLRequestContext* url_request_context_;
std::unique_ptr<Reporter> reporter_;
// Maximum number of reports to be queued for retry.
size_t max_queued_report_count_;
// Maximum age of the reports to be queued for retry, from the time the
// certificate error was first encountered by the user. Any report older than
// this age is ignored and is not re-uploaded.
base::TimeDelta max_report_age_;
std::unique_ptr<base::Clock> clock_;
// Whether a send has ever been made. Used to verify that test setters are
// only called after initialization.
bool made_send_attempt_;
// Encryption parameters.
uint8_t* server_public_key_;
uint32_t server_public_key_version_;
DISALLOW_COPY_AND_ASSIGN(CertificateReportingService);
}; };
#endif // CHROME_BROWSER_SAFE_BROWSING_CERTIFICATE_REPORTING_SERVICE_H_ #endif // CHROME_BROWSER_SAFE_BROWSING_CERTIFICATE_REPORTING_SERVICE_H_
...@@ -29,7 +29,8 @@ CertificateReportingServiceFactory::~CertificateReportingServiceFactory() {} ...@@ -29,7 +29,8 @@ CertificateReportingServiceFactory::~CertificateReportingServiceFactory() {}
KeyedService* CertificateReportingServiceFactory::BuildServiceInstanceFor( KeyedService* CertificateReportingServiceFactory::BuildServiceInstanceFor(
content::BrowserContext* profile) const { content::BrowserContext* profile) const {
return new CertificateReportingService(); // TODO(crbug.com/554323): Create a real CertificateReportingService here.
return nullptr;
} }
content::BrowserContext* content::BrowserContext*
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/safe_browsing/certificate_reporting_service_test_utils.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/certificate_reporting/encrypted_cert_logger.pb.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/test_utils.h"
#include "crypto/curve25519.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_data_stream.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "net/test/url_request/url_request_mock_data_job.h"
#include "net/url_request/url_request_filter.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const uint32_t kServerPublicKeyTestVersion = 16;
void SetUpURLHandlersOnIOThread(
std::unique_ptr<net::URLRequestInterceptor> url_request_interceptor) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance();
filter->AddUrlInterceptor(
CertificateReportingService::GetReportingURLForTesting(),
std::move(url_request_interceptor));
}
std::string GetUploadData(net::URLRequest* request) {
const net::UploadDataStream* stream = request->get_upload();
EXPECT_TRUE(stream);
EXPECT_TRUE(stream->GetElementReaders());
EXPECT_EQ(1u, stream->GetElementReaders()->size());
const net::UploadBytesElementReader* reader =
(*stream->GetElementReaders())[0]->AsBytesReader();
return std::string(reader->bytes(), reader->length());
}
std::string GetReportContents(net::URLRequest* request,
const uint8_t* server_private_key) {
std::string serialized_report(GetUploadData(request));
certificate_reporting::EncryptedCertLoggerRequest encrypted_request;
EXPECT_TRUE(encrypted_request.ParseFromString(serialized_report));
EXPECT_EQ(kServerPublicKeyTestVersion,
encrypted_request.server_public_key_version());
EXPECT_EQ(certificate_reporting::EncryptedCertLoggerRequest::
AEAD_ECDH_AES_128_CTR_HMAC_SHA256,
encrypted_request.algorithm());
std::string decrypted_report;
certificate_reporting::ErrorReporter::DecryptErrorReport(
server_private_key, encrypted_request, &decrypted_report);
return decrypted_report;
}
} // namespace
namespace certificate_reporting_test_utils {
DelayableCertReportURLRequestJob::DelayableCertReportURLRequestJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate)
: net::URLRequestJob(request, network_delegate), weak_factory_(this) {}
DelayableCertReportURLRequestJob::~DelayableCertReportURLRequestJob() {}
base::WeakPtr<DelayableCertReportURLRequestJob>
DelayableCertReportURLRequestJob::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void DelayableCertReportURLRequestJob::Start() {
started_ = true;
if (delayed_) {
// Do nothing until Resume() is called.
return;
}
Resume();
}
int DelayableCertReportURLRequestJob::ReadRawData(net::IOBuffer* buf,
int buf_size) {
// Report sender ignores responses. Return empty response.
return 0;
}
int DelayableCertReportURLRequestJob::GetResponseCode() const {
// Report sender ignores responses. Return empty response.
return 200;
}
void DelayableCertReportURLRequestJob::GetResponseInfo(
net::HttpResponseInfo* info) {
// Report sender ignores responses. Return empty response.
}
void DelayableCertReportURLRequestJob::Resume() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(delayed_);
if (!started_) {
// If Start() hasn't been called yet, then unset |delayed_| so
// that when Start() is called, the request will begin
// immediately.
delayed_ = false;
return;
}
// Start reading asynchronously as would a normal network request.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&DelayableCertReportURLRequestJob::NotifyHeadersComplete,
weak_factory_.GetWeakPtr()));
}
CertReportJobInterceptor::CertReportJobInterceptor(
ReportSendingResult expected_report_result,
const uint8_t* server_private_key)
: expected_report_result_(expected_report_result),
server_private_key_(server_private_key),
weak_factory_(this) {}
CertReportJobInterceptor::~CertReportJobInterceptor() {}
net::URLRequestJob* CertReportJobInterceptor::MaybeInterceptRequest(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
const std::string uploaded_report =
GetReportContents(request, server_private_key_);
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&CertReportJobInterceptor::RequestCreated,
weak_factory_.GetWeakPtr(), uploaded_report,
expected_report_result_));
if (expected_report_result_ == REPORTS_FAIL) {
return new net::URLRequestFailedJob(request, network_delegate,
net::ERR_SSL_PROTOCOL_ERROR);
} else if (expected_report_result_ == REPORTS_DELAY) {
DCHECK(!delayed_request_) << "Supports only one delayed request at a time";
DelayableCertReportURLRequestJob* job =
new DelayableCertReportURLRequestJob(request, network_delegate);
delayed_request_ = job->GetWeakPtr();
return job;
}
// Successful url request job.
return new net::URLRequestMockDataJob(request, network_delegate, "some data",
1, false);
}
void CertReportJobInterceptor::SetFailureMode(
ReportSendingResult expected_report_result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&CertReportJobInterceptor::SetFailureModeOnIOThread,
weak_factory_.GetWeakPtr(), expected_report_result));
}
void CertReportJobInterceptor::Resume() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&CertReportJobInterceptor::ResumeOnIOThread,
base::Unretained(this)));
}
const std::set<std::string>& CertReportJobInterceptor::successful_reports()
const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return successful_reports_;
}
const std::set<std::string>& CertReportJobInterceptor::failed_reports() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return failed_reports_;
}
const std::set<std::string>& CertReportJobInterceptor::delayed_reports() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return delayed_reports_;
}
void CertReportJobInterceptor::ClearObservedReports() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
successful_reports_.clear();
failed_reports_.clear();
delayed_reports_.clear();
}
void CertReportJobInterceptor::SetFailureModeOnIOThread(
ReportSendingResult expected_report_result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
expected_report_result_ = expected_report_result;
}
void CertReportJobInterceptor::ResumeOnIOThread() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
EXPECT_EQ(REPORTS_DELAY, expected_report_result_);
if (delayed_request_)
delayed_request_->Resume();
}
void CertReportJobInterceptor::RequestCreated(
const std::string& uploaded_report,
ReportSendingResult expected_report_result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
switch (expected_report_result) {
case REPORTS_SUCCESSFUL:
successful_reports_.insert(uploaded_report);
break;
case REPORTS_FAIL:
failed_reports_.insert(uploaded_report);
break;
case REPORTS_DELAY:
delayed_reports_.insert(uploaded_report);
break;
}
}
CertificateReportingServiceTestNetworkDelegate::
CertificateReportingServiceTestNetworkDelegate(
const base::Callback<void()>& url_request_destroyed_callback)
: url_request_destroyed_callback_(url_request_destroyed_callback) {}
CertificateReportingServiceTestNetworkDelegate::
~CertificateReportingServiceTestNetworkDelegate() {}
void CertificateReportingServiceTestNetworkDelegate::OnURLRequestDestroyed(
net::URLRequest* request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
url_request_destroyed_callback_);
}
CertificateReportingServiceTestBase::ReportExpectation::ReportExpectation() {}
CertificateReportingServiceTestBase::ReportExpectation::ReportExpectation(
const ReportExpectation& other) = default;
CertificateReportingServiceTestBase::ReportExpectation::~ReportExpectation() {}
// static
CertificateReportingServiceTestBase::ReportExpectation
CertificateReportingServiceTestBase::ReportExpectation::Successful(
const std::set<std::string>& reports) {
ReportExpectation expectation;
expectation.successful_reports = reports;
return expectation;
}
// static
CertificateReportingServiceTestBase::ReportExpectation
CertificateReportingServiceTestBase::ReportExpectation::Failed(
const std::set<std::string>& reports) {
ReportExpectation expectation;
expectation.failed_reports = reports;
return expectation;
}
// static
CertificateReportingServiceTestBase::ReportExpectation
CertificateReportingServiceTestBase::ReportExpectation::Delayed(
const std::set<std::string>& reports) {
ReportExpectation expectation;
expectation.delayed_reports = reports;
return expectation;
}
CertificateReportingServiceTestBase::CertificateReportingServiceTestBase()
: num_request_deletions_to_wait_for_(0), num_deleted_requests_(0) {
memset(server_private_key_, 1, sizeof(server_private_key_));
crypto::curve25519::ScalarBaseMult(server_private_key_, server_public_key_);
}
CertificateReportingServiceTestBase::~CertificateReportingServiceTestBase() {}
void CertificateReportingServiceTestBase::SetUpInterceptor() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
url_request_interceptor_ =
new CertReportJobInterceptor(REPORTS_FAIL, server_private_key_);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(
&CertificateReportingServiceTestBase::SetUpInterceptorOnIOThread,
base::Unretained(this),
base::Passed(std::unique_ptr<net::URLRequestInterceptor>(
url_request_interceptor_))));
}
void CertificateReportingServiceTestBase::TearDownInterceptor() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(
&CertificateReportingServiceTestBase::TearDownInterceptorOnIOThread,
base::Unretained(this)));
}
// Changes the behavior of report uploads to fail or succeed.
void CertificateReportingServiceTestBase::SetFailureMode(
ReportSendingResult expected_report_result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
url_request_interceptor_->SetFailureMode(expected_report_result);
}
void CertificateReportingServiceTestBase::ResumeDelayedRequest() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
url_request_interceptor_->Resume();
}
void CertificateReportingServiceTestBase::WaitForRequestsDestroyed(
const ReportExpectation& expectation) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!run_loop_);
const int num_request_deletions_to_wait_for =
expectation.successful_reports.size() +
expectation.failed_reports.size() + expectation.delayed_reports.size();
ASSERT_LE(num_deleted_requests_, num_request_deletions_to_wait_for)
<< "Observed unexpected report";
if (num_deleted_requests_ < num_request_deletions_to_wait_for) {
num_request_deletions_to_wait_for_ = num_request_deletions_to_wait_for;
run_loop_.reset(new base::RunLoop());
run_loop_->Run();
run_loop_.reset(nullptr);
EXPECT_EQ(0, num_deleted_requests_);
EXPECT_EQ(0, num_request_deletions_to_wait_for_);
} else if (num_deleted_requests_ == num_request_deletions_to_wait_for) {
num_deleted_requests_ = 0;
num_request_deletions_to_wait_for_ = 0;
}
EXPECT_EQ(expectation.successful_reports,
url_request_interceptor_->successful_reports());
EXPECT_EQ(expectation.failed_reports,
url_request_interceptor_->failed_reports());
EXPECT_EQ(expectation.delayed_reports,
url_request_interceptor_->delayed_reports());
url_request_interceptor_->ClearObservedReports();
}
uint8_t* CertificateReportingServiceTestBase::server_public_key() {
return server_public_key_;
}
uint32_t CertificateReportingServiceTestBase::server_public_key_version()
const {
return kServerPublicKeyTestVersion;
}
net::NetworkDelegate* CertificateReportingServiceTestBase::network_delegate() {
return network_delegate_.get();
}
void CertificateReportingServiceTestBase::SetUpInterceptorOnIOThread(
std::unique_ptr<net::URLRequestInterceptor> url_request_interceptor) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
network_delegate_.reset(new CertificateReportingServiceTestNetworkDelegate(
base::Bind(&CertificateReportingServiceTestBase::OnURLRequestDestroyed,
base::Unretained(this))));
SetUpURLHandlersOnIOThread(std::move(url_request_interceptor));
}
void CertificateReportingServiceTestBase::TearDownInterceptorOnIOThread() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
network_delegate_.reset(nullptr);
}
void CertificateReportingServiceTestBase::OnURLRequestDestroyed() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
num_deleted_requests_++;
if (!run_loop_) {
return;
}
EXPECT_LE(num_deleted_requests_, num_request_deletions_to_wait_for_);
if (num_deleted_requests_ == num_request_deletions_to_wait_for_) {
num_request_deletions_to_wait_for_ = 0;
num_deleted_requests_ = 0;
run_loop_->Quit();
}
}
} // namespace certificate_reporting_test_utils
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_SAFE_BROWSING_CERTIFICATE_REPORTING_SERVICE_TEST_UTILS_H_
#define CHROME_BROWSER_SAFE_BROWSING_CERTIFICATE_REPORTING_SERVICE_TEST_UTILS_H_
#include <set>
#include "base/macros.h"
#include "base/run_loop.h"
#include "chrome/browser/safe_browsing/certificate_reporting_service.h"
#include "content/public/test/test_browser_thread.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/base/network_delegate_impl.h"
#include "net/url_request/url_request_interceptor.h"
#include "net/url_request/url_request_job.h"
namespace net {
class NetworkDelegate;
}
namespace certificate_reporting_test_utils {
// Failure mode of the report sending attempts.
enum ReportSendingResult {
// Report send attempts should be successful.
REPORTS_SUCCESSFUL,
// Report send attempts should fail.
REPORTS_FAIL,
// Report send attempts should hang until explicitly resumed.
REPORTS_DELAY,
};
// A URLRequestJob that can be delayed until Resume() is called. Returns an
// empty response. If Resume() is called before a request is made, then the
// request will not be delayed.
class DelayableCertReportURLRequestJob : public net::URLRequestJob {
public:
DelayableCertReportURLRequestJob(net::URLRequest* request,
net::NetworkDelegate* network_delegate);
~DelayableCertReportURLRequestJob() override;
base::WeakPtr<DelayableCertReportURLRequestJob> GetWeakPtr();
// net::URLRequestJob methods:
void Start() override;
int ReadRawData(net::IOBuffer* buf, int buf_size) override;
int GetResponseCode() const override;
void GetResponseInfo(net::HttpResponseInfo* info) override;
// Resumes a previously started request that was delayed. If no
// request has been started yet, then when Start() is called it will
// not delay.
void Resume();
private:
bool delayed_ = true;
bool started_ = false;
base::WeakPtrFactory<DelayableCertReportURLRequestJob> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(DelayableCertReportURLRequestJob);
};
// A job interceptor that returns a failed, succesful or delayed request job.
// Used to simulate report uploads that fail, succeed or hang.
class CertReportJobInterceptor : public net::URLRequestInterceptor {
public:
CertReportJobInterceptor(ReportSendingResult expected_report_result,
const uint8_t* server_private_key);
~CertReportJobInterceptor() override;
// net::URLRequestInterceptor method:
net::URLRequestJob* MaybeInterceptRequest(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override;
// Sets the failure mode for reports. Must be called on the UI thread.
void SetFailureMode(ReportSendingResult expected_report_result);
// Resumes any hanging URL request. Must be called on the UI thread.
void Resume();
// These must be called on the UI thread.
const std::set<std::string>& successful_reports() const;
const std::set<std::string>& failed_reports() const;
const std::set<std::string>& delayed_reports() const;
void ClearObservedReports();
private:
void SetFailureModeOnIOThread(ReportSendingResult expected_report_result);
void ResumeOnIOThread();
void RequestCreated(const std::string& uploaded_report,
ReportSendingResult expected_report_result);
std::set<std::string> successful_reports_;
std::set<std::string> failed_reports_;
std::set<std::string> delayed_reports_;
ReportSendingResult expected_report_result_;
// Private key to decrypt certificate reports.
const uint8_t* server_private_key_;
mutable base::WeakPtr<DelayableCertReportURLRequestJob> delayed_request_ =
nullptr;
mutable base::WeakPtrFactory<CertReportJobInterceptor> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(CertReportJobInterceptor);
};
// A network delegate used to observe URL request destructions. The tests check
// that no outstanding URL request is present during tear down.
class CertificateReportingServiceTestNetworkDelegate
: public net::NetworkDelegateImpl {
public:
CertificateReportingServiceTestNetworkDelegate(
const base::Callback<void()>& url_request_destroyed_callback);
~CertificateReportingServiceTestNetworkDelegate() override;
// net::NetworkDelegate method:
void OnURLRequestDestroyed(net::URLRequest* request) override;
private:
base::Callback<void()> url_request_destroyed_callback_;
};
// Base class for CertificateReportingService tests. Sets up an interceptor to
// keep track of reports that are being sent.
class CertificateReportingServiceTestBase {
protected:
CertificateReportingServiceTestBase();
virtual ~CertificateReportingServiceTestBase();
// Syntactic sugar for wrapping report expectations in a more readable way.
// Passed to WaitForRequestDeletions() as input.
// Example:
// The following expects report0 and report1 to be successfully sent and their
// URL requests to be deleted:
// WaitForRequestDeletions(
// ReportExpectation::Successful("report0, report1"));
struct ReportExpectation {
ReportExpectation();
ReportExpectation(const ReportExpectation& other);
~ReportExpectation();
// Returns an expectation where all reports in |reports| succeed.
static ReportExpectation Successful(const std::set<std::string>& reports);
// Returns an expectation where all reports in |reports| fail.
static ReportExpectation Failed(const std::set<std::string>& reports);
// Returns an expectation where all reports in |reports| are delayed.
static ReportExpectation Delayed(const std::set<std::string>& reports);
std::set<std::string> successful_reports;
std::set<std::string> failed_reports;
std::set<std::string> delayed_reports;
};
void SetUpInterceptor();
void TearDownInterceptor();
// Changes the behavior of report uploads to fail, succeed or hang.
void SetFailureMode(ReportSendingResult expected_report_result);
// Resumes delayed report request. Failure mode should be REPORTS_DELAY when
// calling this method.
void ResumeDelayedRequest();
// Waits for the URL requests for the expected reports to be destroyed.
// Doesn't block if all requests have already been destroyed.
void WaitForRequestsDestroyed(const ReportExpectation& expectation);
uint8_t* server_public_key();
uint32_t server_public_key_version() const;
net::NetworkDelegate* network_delegate();
CertReportJobInterceptor* interceptor() { return url_request_interceptor_; }
private:
void SetUpInterceptorOnIOThread(
std::unique_ptr<net::URLRequestInterceptor> url_request_interceptor);
void TearDownInterceptorOnIOThread();
void OnURLRequestDestroyed();
CertReportJobInterceptor* url_request_interceptor_;
uint8_t server_public_key_[32];
uint8_t server_private_key_[32];
std::unique_ptr<CertificateReportingServiceTestNetworkDelegate>
network_delegate_;
int num_request_deletions_to_wait_for_;
int num_deleted_requests_;
std::unique_ptr<base::RunLoop> run_loop_;
DISALLOW_COPY_AND_ASSIGN(CertificateReportingServiceTestBase);
};
} // namespace certificate_reporting_test_utils
#endif // CHROME_BROWSER_SAFE_BROWSING_CERTIFICATE_REPORTING_SERVICE_TEST_UTILS_H_
...@@ -6,18 +6,38 @@ ...@@ -6,18 +6,38 @@
#include <string> #include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/simple_test_clock.h" #include "base/test/simple_test_clock.h"
#include "base/test/thread_test_helper.h"
#include "base/time/clock.h" #include "base/time/clock.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "chrome/browser/safe_browsing/certificate_reporting_service_test_utils.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/test_browser_thread.h" #include "content/public/test/test_browser_thread.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/base/network_delegate_impl.h" #include "net/base/network_delegate_impl.h"
#include "net/test/url_request/url_request_failed_job.h" #include "net/test/url_request/url_request_failed_job.h"
#include "net/test/url_request/url_request_mock_data_job.h" #include "net/test/url_request/url_request_mock_data_job.h"
#include "net/url_request/url_request_filter.h"
#include "net/url_request/url_request_test_util.h" #include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
TEST(CertificateReportingServiceTest, BoundedReportList) { namespace {
// Maximum number of reports kept in the certificate reporting service's retry
// queue.
const size_t kMaxReportCountInQueue = 3;
void ClearURLHandlers() {
net::URLRequestFilter::GetInstance()->ClearHandlers();
}
} // namespace
TEST(CertificateReportingServiceReportListTest, BoundedReportList) {
// Create a report list with maximum of 2 items. // Create a report list with maximum of 2 items.
CertificateReportingService::BoundedReportList list(2 /* max_size */); CertificateReportingService::BoundedReportList list(2 /* max_size */);
EXPECT_EQ(0u, list.items().size()); EXPECT_EQ(0u, list.items().size());
...@@ -56,7 +76,8 @@ TEST(CertificateReportingServiceTest, BoundedReportList) { ...@@ -56,7 +76,8 @@ TEST(CertificateReportingServiceTest, BoundedReportList) {
EXPECT_EQ("report2_five_minutes_old", list.items()[1].serialized_report); EXPECT_EQ("report2_five_minutes_old", list.items()[1].serialized_report);
} }
class CertificateReportingServiceReporterTest : public ::testing::Test { class CertificateReportingServiceReporterOnIOThreadTest
: public ::testing::Test {
public: public:
void SetUp() override { void SetUp() override {
message_loop_.reset(new base::MessageLoopForIO()); message_loop_.reset(new base::MessageLoopForIO());
...@@ -68,6 +89,8 @@ class CertificateReportingServiceReporterTest : public ::testing::Test { ...@@ -68,6 +89,8 @@ class CertificateReportingServiceReporterTest : public ::testing::Test {
net::URLRequestMockDataJob::AddUrlHandler(); net::URLRequestMockDataJob::AddUrlHandler();
} }
void TearDown() override { ClearURLHandlers(); }
protected: protected:
net::URLRequestContextGetter* url_request_context_getter() { net::URLRequestContextGetter* url_request_context_getter() {
return url_request_context_getter_.get(); return url_request_context_getter_.get();
...@@ -80,7 +103,8 @@ class CertificateReportingServiceReporterTest : public ::testing::Test { ...@@ -80,7 +103,8 @@ class CertificateReportingServiceReporterTest : public ::testing::Test {
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_; scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
}; };
TEST_F(CertificateReportingServiceReporterTest, Reporter) { TEST_F(CertificateReportingServiceReporterOnIOThreadTest,
Reporter_RetriesEnabled) {
std::unique_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock()); std::unique_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock());
base::Time reference_time = base::Time::Now(); base::Time reference_time = base::Time::Now();
clock->SetNow(reference_time); clock->SetNow(reference_time);
...@@ -95,11 +119,13 @@ TEST_F(CertificateReportingServiceReporterTest, Reporter) { ...@@ -95,11 +119,13 @@ TEST_F(CertificateReportingServiceReporterTest, Reporter) {
CertificateReportingService::BoundedReportList* list = CertificateReportingService::BoundedReportList* list =
new CertificateReportingService::BoundedReportList(2); new CertificateReportingService::BoundedReportList(2);
// Create a reporter with retries enabled.
CertificateReportingService::Reporter reporter( CertificateReportingService::Reporter reporter(
std::unique_ptr<certificate_reporting::ErrorReporter>( std::unique_ptr<certificate_reporting::ErrorReporter>(
certificate_error_reporter), certificate_error_reporter),
std::unique_ptr<CertificateReportingService::BoundedReportList>(list), std::unique_ptr<CertificateReportingService::BoundedReportList>(list),
clock.get(), base::TimeDelta::FromSeconds(100)); clock.get(), base::TimeDelta::FromSeconds(100),
true /* retries_enabled */);
EXPECT_EQ(0u, list->items().size()); EXPECT_EQ(0u, list->items().size());
EXPECT_EQ(0u, reporter.inflight_report_count_for_testing()); EXPECT_EQ(0u, reporter.inflight_report_count_for_testing());
...@@ -172,3 +198,371 @@ TEST_F(CertificateReportingServiceReporterTest, Reporter) { ...@@ -172,3 +198,371 @@ TEST_F(CertificateReportingServiceReporterTest, Reporter) {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, list->items().size()); EXPECT_EQ(0u, list->items().size());
} }
// Same as above, but retries are disabled.
TEST_F(CertificateReportingServiceReporterOnIOThreadTest,
Reporter_RetriesDisabled) {
std::unique_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock());
base::Time reference_time = base::Time::Now();
clock->SetNow(reference_time);
const GURL kFailureURL =
net::URLRequestFailedJob::GetMockHttpsUrl(net::ERR_SSL_PROTOCOL_ERROR);
certificate_reporting::ErrorReporter* certificate_error_reporter =
new certificate_reporting::ErrorReporter(
url_request_context_getter()->GetURLRequestContext(), kFailureURL,
net::ReportSender::DO_NOT_SEND_COOKIES);
CertificateReportingService::BoundedReportList* list =
new CertificateReportingService::BoundedReportList(2);
// Create a reporter with retries disabled.
CertificateReportingService::Reporter reporter(
std::unique_ptr<certificate_reporting::ErrorReporter>(
certificate_error_reporter),
std::unique_ptr<CertificateReportingService::BoundedReportList>(list),
clock.get(), base::TimeDelta::FromSeconds(100),
false /* retries_enabled */);
EXPECT_EQ(0u, list->items().size());
EXPECT_EQ(0u, reporter.inflight_report_count_for_testing());
// Sending a failed report will not put the report in the retry list.
reporter.Send("report1");
EXPECT_EQ(1u, reporter.inflight_report_count_for_testing());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, reporter.inflight_report_count_for_testing());
ASSERT_EQ(0u, list->items().size());
// Sending a second failed report will also not put it in the retry list.
clock->Advance(base::TimeDelta::FromSeconds(10));
reporter.Send("report2");
base::RunLoop().RunUntilIdle();
ASSERT_EQ(0u, list->items().size());
// Send pending reports. Nothing should be sent.
clock->Advance(base::TimeDelta::FromSeconds(10));
reporter.SendPending();
base::RunLoop().RunUntilIdle();
ASSERT_EQ(0u, list->items().size());
}
class CertificateReportingServiceTest
: public ::testing::Test,
public certificate_reporting_test_utils::
CertificateReportingServiceTestBase {
public:
CertificateReportingServiceTest()
: CertificateReportingServiceTestBase(),
thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD),
io_task_runner_(content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::IO)) {}
~CertificateReportingServiceTest() override {}
void SetUp() override {
SetUpInterceptor();
WaitForIOThread();
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(
&CertificateReportingServiceTest::SetUpURLRequestContextOnIOThread,
base::Unretained(this)));
WaitForIOThread();
clock_ = new base::SimpleTestClock();
service_.reset(new CertificateReportingService(
url_request_context_getter(), server_public_key(),
server_public_key_version(), kMaxReportCountInQueue,
base::TimeDelta::FromHours(24), std::unique_ptr<base::Clock>(clock_)));
// Wait for service reset.
WaitForIOThread();
}
void SetUpURLRequestContextOnIOThread() {
std::unique_ptr<net::TestURLRequestContext> url_request_context(
new net::TestURLRequestContext(true));
url_request_context->set_network_delegate(network_delegate());
url_request_context->Init();
url_request_context_getter_ = new net::TestURLRequestContextGetter(
io_task_runner_, std::move(url_request_context));
}
void TearDown() override {
WaitForIOThread();
EXPECT_TRUE(interceptor()->successful_reports().empty());
EXPECT_TRUE(interceptor()->failed_reports().empty());
EXPECT_TRUE(interceptor()->delayed_reports().empty());
EXPECT_EQ(0u, service()
->GetReporterForTesting()
->inflight_report_count_for_testing());
service_->Shutdown();
WaitForIOThread();
service_.reset(nullptr);
content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
base::Bind(&ClearURLHandlers));
TearDownInterceptor();
}
protected:
net::URLRequestContextGetter* url_request_context_getter() {
return url_request_context_getter_.get();
}
void WaitForIOThread() {
scoped_refptr<base::ThreadTestHelper> io_helper(
new base::ThreadTestHelper(io_task_runner_));
ASSERT_TRUE(io_helper->Run());
}
// Sets service enabled state and waits for a reset event.
void SetServiceEnabledAndWait(bool enabled) {
service()->SetEnabled(enabled);
WaitForIOThread();
}
void AdvanceClock(base::TimeDelta delta) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&base::SimpleTestClock::Advance, base::Unretained(clock_),
delta));
}
void SetNow(base::Time now) {
content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
base::Bind(&base::SimpleTestClock::SetNow,
base::Unretained(clock_), now));
}
CertificateReportingService* service() { return service_.get(); }
private:
// Must be initialized before url_request_context_getter_
content::TestBrowserThreadBundle thread_bundle_;
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
std::unique_ptr<CertificateReportingService> service_;
base::SimpleTestClock* clock_;
};
TEST_F(CertificateReportingServiceTest, Send) {
WaitForIOThread();
// Let all reports fail.
SetFailureMode(
certificate_reporting_test_utils::ReportSendingResult::REPORTS_FAIL);
// Send two reports. Both should fail and get queued.
service()->Send("report0");
WaitForRequestsDestroyed(ReportExpectation::Failed({"report0"}));
service()->Send("report1");
WaitForRequestsDestroyed(ReportExpectation::Failed({"report1"}));
// Send pending reports. Previously queued reports should be observed. They
// will also be queued again.
service()->SendPending();
WaitForRequestsDestroyed(ReportExpectation::Failed({"report0", "report1"}));
// Let all reports succeed.
SetFailureMode(certificate_reporting_test_utils::ReportSendingResult::
REPORTS_SUCCESSFUL);
// Send a third report. This should not be queued.
service()->Send("report2");
WaitForRequestsDestroyed(ReportExpectation::Successful({"report2"}));
// Send pending reports. Previously failed and queued two reports should be
// observed.
service()->SendPending();
WaitForRequestsDestroyed(
ReportExpectation::Successful({"report0", "report1"}));
}
TEST_F(CertificateReportingServiceTest, Disabled_ShouldNotSend) {
// Let all reports succeed.
SetFailureMode(certificate_reporting_test_utils::ReportSendingResult::
REPORTS_SUCCESSFUL);
// Disable the service.
SetServiceEnabledAndWait(false);
// Send a report. Report attempt should be cancelled and no sent reports
// should be observed.
service()->Send("report0");
// Enable the service and send a report again.
SetServiceEnabledAndWait(true);
service()->Send("report1");
WaitForRequestsDestroyed(ReportExpectation::Successful({"report1"}));
}
TEST_F(CertificateReportingServiceTest, Disabled_ShouldClearPendingReports) {
// Let all reports fail.
SetFailureMode(
certificate_reporting_test_utils::ReportSendingResult::REPORTS_FAIL);
service()->Send("report0");
WaitForRequestsDestroyed(ReportExpectation::Failed({"report0"}));
// Disable the service.
SetServiceEnabledAndWait(false);
// Sending has no effect while disabled, wait for a single cancelled event.
service()->SendPending();
// Re-enable the service and send pending reports. Pending reports should have
// been cleared when the service was disabled, so no report should be seen.
SetServiceEnabledAndWait(true);
// Sending with empty queue has no effect.
service()->SendPending();
}
TEST_F(CertificateReportingServiceTest, DontSendOldReports) {
SetNow(base::Time::Now());
// Let all reports fail.
SetFailureMode(
certificate_reporting_test_utils::ReportSendingResult::REPORTS_FAIL);
// Send a report.
service()->Send("report0");
WaitForRequestsDestroyed(ReportExpectation::Failed({"report0"}));
// Advance the clock a bit and trigger another report.
AdvanceClock(base::TimeDelta::FromHours(5));
service()->Send("report1");
WaitForRequestsDestroyed(ReportExpectation::Failed({"report1"}));
// Advance the clock to 20 hours, putting it 25 hours ahead of the reference
// time. This makes the report0 older than max age (24 hours). The report1 is
// now 20 hours old.
AdvanceClock(base::TimeDelta::FromHours(20));
// Send pending reports. report0 should be discarded since it's too old.
// report1 should be queued again.
service()->SendPending();
WaitForRequestsDestroyed(ReportExpectation::Failed({"report1"}));
// Send a third report.
service()->Send("report2");
WaitForRequestsDestroyed(ReportExpectation::Failed({"report2"}));
// Advance the clock 5 hours. The report1 will now be 25 hours old.
AdvanceClock(base::TimeDelta::FromHours(5));
// Send pending reports. report1 should be discarded since it's too old.
// report2 should be queued again.
service()->SendPending();
WaitForRequestsDestroyed(ReportExpectation::Failed({"report2"}));
// Advance the clock 20 hours again so that report2 is 25 hours old and is
// older than max age (24 hours)
AdvanceClock(base::TimeDelta::FromHours(20));
// Send pending reports. report2 should be discarded since it's too old. No
// other reports remain.
service()->SendPending();
}
TEST_F(CertificateReportingServiceTest, DiscardOldReports) {
SetNow(base::Time::Now());
// Let all reports fail.
SetFailureMode(
certificate_reporting_test_utils::ReportSendingResult::REPORTS_FAIL);
// Send a failed report.
service()->Send("report0");
WaitForRequestsDestroyed(ReportExpectation::Failed({"report0"}));
// Send three more reports within five hours of each other. After this:
// report0 is 0 hours after reference time (15 hours old).
// report1 is 5 hours after reference time (10 hours old).
// report2 is 10 hours after reference time (5 hours old).
// report3 is 15 hours after reference time (0 hours old).
AdvanceClock(base::TimeDelta::FromHours(5));
service()->Send("report1");
AdvanceClock(base::TimeDelta::FromHours(5));
service()->Send("report2");
AdvanceClock(base::TimeDelta::FromHours(5));
service()->Send("report3");
WaitForRequestsDestroyed(
ReportExpectation::Failed({"report1", "report2", "report3"}));
// Send pending reports. Four reports were generated above, but the service
// only queues three reports, so the very first one should be dropped since
// it's the oldest.
service()->SendPending();
WaitForRequestsDestroyed(
ReportExpectation::Failed({"report1", "report2", "report3"}));
// Let all reports succeed.
SetFailureMode(certificate_reporting_test_utils::ReportSendingResult::
REPORTS_SUCCESSFUL);
// Advance the clock by 15 hours. Current time is now 30 hours after reference
// time. The ages of reports are now as follows:
// report1 is 25 hours old.
// report2 is 20 hours old.
// report3 is 15 hours old.
AdvanceClock(base::TimeDelta::FromHours(15));
// Send pending reports. Only report2 and report3 should be sent, report1
// should be ignored because it's too old.
service()->SendPending();
WaitForRequestsDestroyed(
ReportExpectation::Successful({"report2", "report3"}));
// Do a final send. No reports should be sent.
service()->SendPending();
}
// A delayed report should successfully upload when it's resumed.
TEST_F(CertificateReportingServiceTest, Delayed_Resumed) {
// Let reports hang.
SetFailureMode(
certificate_reporting_test_utils::ReportSendingResult::REPORTS_DELAY);
// Send a report. The report upload hangs, so no error or success callbacks
// should be called.
service()->Send("report0");
// Resume the report upload and run the callbacks. The report should be
// successfully sent.
ResumeDelayedRequest();
WaitForRequestsDestroyed(ReportExpectation::Delayed({"report0"}));
}
// Delayed reports should cleaned when the service is reset.
TEST_F(CertificateReportingServiceTest, Delayed_Reset) {
// Let reports hang.
SetFailureMode(
certificate_reporting_test_utils::ReportSendingResult::REPORTS_DELAY);
// Send a report. The report is triggered but hangs, so no error or success
// callbacks should be called.
service()->Send("report0");
// Disable the service. This should reset the reporting service and
// clear all pending reports.
SetServiceEnabledAndWait(false);
// Resume delayed report. No report should be observed since the service
// should have reset and all pending reports should be cleared. If any report
// is observed, the next WaitForRequestsDestroyed() will fail.
ResumeDelayedRequest();
// Enable the service.
SetServiceEnabledAndWait(true);
// Send a report. The report is triggered but hangs, so no error or success
// callbacks should be called. The report id is again 0 since the pending
// report queue has been cleared above.
service()->Send("report1");
// Resume delayed report. The report should be observed.
ResumeDelayedRequest();
WaitForRequestsDestroyed(ReportExpectation::Delayed({"report0", "report1"}));
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment