Chromium Code Reviews| Index: chrome/browser/safe_browsing/certificate_reporting_service_test_utils.cc |
| diff --git a/chrome/browser/safe_browsing/certificate_reporting_service_test_utils.cc b/chrome/browser/safe_browsing/certificate_reporting_service_test_utils.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7c0b4c2341da53a77cb34ecb086f304b326282ee |
| --- /dev/null |
| +++ b/chrome/browser/safe_browsing/certificate_reporting_service_test_utils.cc |
| @@ -0,0 +1,346 @@ |
| +// 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) { |
| + net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); |
| + filter->AddUrlInterceptor( |
| + GURL(CertificateReportingService::kExtendedReportingUploadUrlInsecure), |
| + 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 { |
| + |
| +CertificateReportingServiceTestNetworkDelegate:: |
|
estark
2016/12/07 00:42:55
nit: I think the order of things here doesn't matc
meacer
2016/12/07 21:37:33
Done.
|
| + CertificateReportingServiceTestNetworkDelegate( |
| + const base::Callback<void(net::URLRequest* request)>& |
| + 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, |
| + base::Bind(url_request_destroyed_callback_, request)); |
|
estark
2016/12/07 00:42:55
Is it safe to pass URLRequests to the UI thread?
meacer
2016/12/07 21:37:33
Removed the request.
|
| +} |
| + |
| +DelayableCertReportURLRequestJob::DelayableCertReportURLRequestJob( |
| + net::URLRequest* request, |
| + net::NetworkDelegate* network_delegate) |
| + : net::URLRequestJob(request, network_delegate), weak_factory_(this) {} |
| + |
| +DelayableCertReportURLRequestJob::~DelayableCertReportURLRequestJob() {} |
| + |
| +void DelayableCertReportURLRequestJob::Start() { |
| + started_ = true; |
| + if (delayed_) { |
| + // Do nothing until Resume() is called. |
| + return; |
| + } |
| + Resume(); |
| +} |
| + |
| +int DelayableCertReportURLRequestJob::ReadRawData(net::IOBuffer* buf, |
| + int buf_size) { |
| + return 0; |
| +} |
| + |
| +int DelayableCertReportURLRequestJob::GetResponseCode() const { |
| + return 200; |
| +} |
| + |
| +void DelayableCertReportURLRequestJob::GetResponseInfo( |
| + net::HttpResponseInfo* info) {} |
|
estark
2016/12/07 00:42:55
nit: maybe put a comment here explaining that the
meacer
2016/12/07 21:37:33
Done.
|
| + |
| +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())); |
| +} |
| + |
| +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; |
| + } |
| +} |
| + |
| +void CertReportJobInterceptor::Clear() { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + successful_reports_.clear(); |
| + failed_reports_.clear(); |
| + delayed_reports_.clear(); |
| +} |
| + |
| +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, |
| + weak_factory_.GetWeakPtr())); |
| +} |
| + |
| +const std::set<std::string>& CertReportJobInterceptor::successful_reports() |
| + const { |
| + return successful_reports_; |
| +} |
| + |
| +const std::set<std::string>& CertReportJobInterceptor::failed_reports() const { |
| + return failed_reports_; |
| +} |
| + |
| +const std::set<std::string>& CertReportJobInterceptor::delayed_reports() const { |
| + return delayed_reports_; |
| +} |
| + |
| +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(); |
| +} |
| + |
| +CertificateReportingServiceTestBase::CertificateReportingServiceTestBase() |
| + : network_delegate_( |
| + base::Bind(&CertificateReportingServiceTestBase::OnRequestDeleted, |
| + base::Unretained(this))), |
| + 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 certificate_reporting_test_utils::CertReportJobInterceptor( |
| + certificate_reporting_test_utils::REPORTS_FAIL, server_private_key_); |
| + |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::IO, FROM_HERE, |
| + base::Bind(&SetUpURLHandlersOnIOThread, |
| + base::Passed(std::unique_ptr<net::URLRequestInterceptor>( |
| + url_request_interceptor_)))); |
| +} |
| + |
| +void CertificateReportingServiceTestBase::ClearObservedReports() { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + url_request_interceptor_->Clear(); |
| +} |
| + |
| +uint32_t CertificateReportingServiceTestBase::server_public_key_version() |
| + const { |
| + return kServerPublicKeyTestVersion; |
| +} |
| + |
| +// Changes the behavior of report uploads to fail or succeed. |
| +void CertificateReportingServiceTestBase::SetFailureMode( |
| + certificate_reporting_test_utils::ReportSendingResult |
| + expected_report_result) { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + url_request_interceptor_->SetFailureMode(expected_report_result); |
| +} |
| + |
| +void CertificateReportingServiceTestBase::OnRequestDeleted( |
| + net::URLRequest* request) { |
| + 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(); |
| + } |
| +} |
| + |
| +CertificateReportingServiceTestBase::ReportExpectation::ReportExpectation() {} |
| + |
| +CertificateReportingServiceTestBase::ReportExpectation::~ReportExpectation() {} |
| + |
| +CertificateReportingServiceTestBase::ReportExpectation& |
| +CertificateReportingServiceTestBase::ReportExpectation::Successful( |
|
estark
2016/12/07 00:42:55
Oh, cool, I've never seen this pattern in chrome b
meacer
2016/12/07 21:37:33
Yes, but the unit tests only check for a single ty
|
| + const std::set<std::string>& reports) { |
| + successful_reports = reports; |
| + return *this; |
| +} |
| + |
| +CertificateReportingServiceTestBase::ReportExpectation& |
| +CertificateReportingServiceTestBase::ReportExpectation::Failed( |
| + const std::set<std::string>& reports) { |
| + failed_reports = reports; |
| + return *this; |
| +} |
| + |
| +CertificateReportingServiceTestBase::ReportExpectation& |
| +CertificateReportingServiceTestBase::ReportExpectation::Delayed( |
| + const std::set<std::string>& reports) { |
| + delayed_reports = reports; |
| + return *this; |
| +} |
| + |
| +void CertificateReportingServiceTestBase::WaitForRequestDeletions( |
| + 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(); |
| + |
| + 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(); |
| + 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; |
| + } else { |
| + NOTREACHED() << "Observed unexpected report"; |
|
estark
2016/12/07 00:42:55
nit: maybe make this an ASSERT_LE above line 319
meacer
2016/12/07 21:37:33
Done.
|
| + } |
| + |
| + 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()); |
| + ClearObservedReports(); |
| +} |
| + |
| +void CertificateReportingServiceTestBase::ResumeDelayedRequest() { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + url_request_interceptor_->Resume(); |
| +} |
| + |
| +} // namespace certificate_reporting_test_utils |