Performance histograms for extension content verification
[chromium-blink-merge.git] / extensions / browser / content_verify_job.cc
blob1bf68cb6a6bada05f471d5ed5c8fe173390dc1d3
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
3 // found in the LICENSE file.
5 #include "extensions/browser/content_verify_job.h"
7 #include <algorithm>
9 #include "base/metrics/histogram.h"
10 #include "base/stl_util.h"
11 #include "base/task_runner_util.h"
12 #include "base/timer/elapsed_timer.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "crypto/secure_hash.h"
15 #include "crypto/sha2.h"
16 #include "extensions/browser/content_hash_reader.h"
18 namespace extensions {
20 namespace {
22 ContentVerifyJob::TestDelegate* g_test_delegate = NULL;
24 class ScopedElapsedTimer {
25 public:
26 ScopedElapsedTimer(base::TimeDelta* total) : total_(total) { DCHECK(total_); }
28 ~ScopedElapsedTimer() { *total_ += timer.Elapsed(); }
30 private:
31 // Some total amount of time we should add our elapsed time to at
32 // destruction.
33 base::TimeDelta* total_;
35 // A timer for how long this object has been alive.
36 base::ElapsedTimer timer;
39 } // namespace
41 ContentVerifyJob::ContentVerifyJob(ContentHashReader* hash_reader,
42 const FailureCallback& failure_callback)
43 : done_reading_(false),
44 hashes_ready_(false),
45 total_bytes_read_(0),
46 current_block_(0),
47 current_hash_byte_count_(0),
48 hash_reader_(hash_reader),
49 failure_callback_(failure_callback),
50 failed_(false) {
51 // It's ok for this object to be constructed on a different thread from where
52 // it's used.
53 thread_checker_.DetachFromThread();
56 ContentVerifyJob::~ContentVerifyJob() {
57 UMA_HISTOGRAM_COUNTS("ExtensionContentVerifyJob.TimeSpentUS",
58 time_spent_.InMicroseconds());
61 void ContentVerifyJob::Start() {
62 DCHECK(thread_checker_.CalledOnValidThread());
63 base::PostTaskAndReplyWithResult(
64 content::BrowserThread::GetBlockingPool(),
65 FROM_HERE,
66 base::Bind(&ContentHashReader::Init, hash_reader_),
67 base::Bind(&ContentVerifyJob::OnHashesReady, this));
70 void ContentVerifyJob::BytesRead(int count, const char* data) {
71 ScopedElapsedTimer timer(&time_spent_);
72 DCHECK(thread_checker_.CalledOnValidThread());
73 if (failed_)
74 return;
75 if (g_test_delegate) {
76 FailureReason reason =
77 g_test_delegate->BytesRead(hash_reader_->extension_id(), count, data);
78 if (reason != NONE)
79 return DispatchFailureCallback(reason);
81 if (!hashes_ready_) {
82 queue_.append(data, count);
83 return;
85 DCHECK_GE(count, 0);
86 int bytes_added = 0;
88 while (bytes_added < count) {
89 if (current_block_ >= hash_reader_->block_count())
90 return DispatchFailureCallback(HASH_MISMATCH);
92 if (!current_hash_.get()) {
93 current_hash_byte_count_ = 0;
94 current_hash_.reset(
95 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
97 // Compute how many bytes we should hash, and add them to the current hash.
98 int bytes_to_hash =
99 std::min(hash_reader_->block_size() - current_hash_byte_count_,
100 count - bytes_added);
101 DCHECK(bytes_to_hash > 0);
102 current_hash_->Update(data + bytes_added, bytes_to_hash);
103 bytes_added += bytes_to_hash;
104 current_hash_byte_count_ += bytes_to_hash;
105 total_bytes_read_ += bytes_to_hash;
107 // If we finished reading a block worth of data, finish computing the hash
108 // for it and make sure the expected hash matches.
109 if (current_hash_byte_count_ == hash_reader_->block_size() &&
110 !FinishBlock()) {
111 DispatchFailureCallback(HASH_MISMATCH);
112 return;
117 void ContentVerifyJob::DoneReading() {
118 ScopedElapsedTimer timer(&time_spent_);
119 DCHECK(thread_checker_.CalledOnValidThread());
120 if (failed_)
121 return;
122 if (g_test_delegate) {
123 FailureReason reason =
124 g_test_delegate->DoneReading(hash_reader_->extension_id());
125 if (reason != NONE) {
126 DispatchFailureCallback(reason);
127 return;
130 done_reading_ = true;
131 if (hashes_ready_ && !FinishBlock())
132 DispatchFailureCallback(HASH_MISMATCH);
135 bool ContentVerifyJob::FinishBlock() {
136 if (current_hash_byte_count_ <= 0)
137 return true;
138 std::string final(crypto::kSHA256Length, 0);
139 current_hash_->Finish(string_as_array(&final), final.size());
141 const std::string* expected_hash = NULL;
142 if (!hash_reader_->GetHashForBlock(current_block_, &expected_hash) ||
143 *expected_hash != final)
144 return false;
146 current_hash_.reset();
147 current_hash_byte_count_ = 0;
148 current_block_++;
149 return true;
152 void ContentVerifyJob::OnHashesReady(bool success) {
153 if (!success && !g_test_delegate) {
154 if (hash_reader_->have_verified_contents() &&
155 hash_reader_->have_computed_hashes())
156 DispatchFailureCallback(NO_HASHES_FOR_FILE);
157 else
158 DispatchFailureCallback(MISSING_ALL_HASHES);
159 return;
162 hashes_ready_ = true;
163 if (!queue_.empty()) {
164 std::string tmp;
165 queue_.swap(tmp);
166 BytesRead(tmp.size(), string_as_array(&tmp));
168 if (done_reading_) {
169 ScopedElapsedTimer timer(&time_spent_);
170 if (!FinishBlock())
171 DispatchFailureCallback(HASH_MISMATCH);
175 // static
176 void ContentVerifyJob::SetDelegateForTests(TestDelegate* delegate) {
177 g_test_delegate = delegate;
180 void ContentVerifyJob::DispatchFailureCallback(FailureReason reason) {
181 DCHECK(!failed_);
182 failed_ = true;
183 if (!failure_callback_.is_null()) {
184 VLOG(1) << "job failed for " << hash_reader_->extension_id() << " "
185 << hash_reader_->relative_path().MaybeAsASCII()
186 << " reason:" << reason;
187 failure_callback_.Run(reason);
188 failure_callback_.Reset();
192 } // namespace extensions