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

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

Issue 2128173003: Merge the hash prefixes from the old store and the additions in the partial (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Nit: s/soace/space Created 4 years, 5 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 "base/base64.h" 5 #include "base/base64.h"
6 #include "base/bind.h" 6 #include "base/bind.h"
7 #include "base/files/file_util.h" 7 #include "base/files/file_util.h"
8 #include "base/metrics/histogram_macros.h" 8 #include "base/metrics/histogram_macros.h"
9 #include "base/metrics/sparse_histogram.h" 9 #include "base/metrics/sparse_histogram.h"
10 #include "base/strings/stringprintf.h" 10 #include "base/strings/stringprintf.h"
11 #include "components/safe_browsing_db/v4_store.h" 11 #include "components/safe_browsing_db/v4_store.h"
12 #include "components/safe_browsing_db/v4_store.pb.h" 12 #include "components/safe_browsing_db/v4_store.pb.h"
13 13
14 namespace safe_browsing { 14 namespace safe_browsing {
15 15
16 namespace { 16 namespace {
17 const uint32_t kFileMagic = 0x600D71FE; 17 const uint32_t kFileMagic = 0x600D71FE;
18 18
19 const uint32_t kFileVersion = 9; 19 const uint32_t kFileVersion = 9;
20 20
21 // The minimum expected size (in bytes) of a hash-prefix.
22 const uint32_t kMinHashPrefixLength = 4;
23
24 // The maximum expected size (in bytes) of a hash-prefix. This represents the
25 // length of a SHA256 hash.
26 const uint32_t kMaxHashPrefixLength = 32;
27
21 void RecordStoreReadResult(StoreReadResult result) { 28 void RecordStoreReadResult(StoreReadResult result) {
22 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreReadResult", result, 29 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreReadResult", result,
23 STORE_READ_RESULT_MAX); 30 STORE_READ_RESULT_MAX);
24 } 31 }
25 32
26 void RecordStoreWriteResult(StoreWriteResult result) { 33 void RecordStoreWriteResult(StoreWriteResult result) {
27 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreWriteResult", result, 34 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4StoreWriteResult", result,
28 STORE_WRITE_RESULT_MAX); 35 STORE_WRITE_RESULT_MAX);
29 } 36 }
30 37
38 void RecordApplyUpdateResult(ApplyUpdateResult result) {
39 UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.V4ApplyUpdateResult", result,
40 APPLY_UPDATE_RESULT_MAX);
41 }
42
31 // Returns the name of the temporary file used to buffer data for 43 // Returns the name of the temporary file used to buffer data for
32 // |filename|. Exported for unit tests. 44 // |filename|. Exported for unit tests.
33 const base::FilePath TemporaryFileForFilename(const base::FilePath& filename) { 45 const base::FilePath TemporaryFileForFilename(const base::FilePath& filename) {
34 return base::FilePath(filename.value() + FILE_PATH_LITERAL("_new")); 46 return base::FilePath(filename.value() + FILE_PATH_LITERAL("_new"));
35 } 47 }
36 48
37 } // namespace 49 } // namespace
38 50
39 std::ostream& operator<<(std::ostream& os, const V4Store& store) { 51 std::ostream& operator<<(std::ostream& os, const V4Store& store) {
40 os << store.DebugString(); 52 os << store.DebugString();
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
76 state_ = ""; 88 state_ = "";
77 return true; 89 return true;
78 } 90 }
79 91
80 void V4Store::ApplyUpdate( 92 void V4Store::ApplyUpdate(
81 std::unique_ptr<ListUpdateResponse> response, 93 std::unique_ptr<ListUpdateResponse> response,
82 const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner, 94 const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner,
83 UpdatedStoreReadyCallback callback) { 95 UpdatedStoreReadyCallback callback) {
84 std::unique_ptr<V4Store> new_store( 96 std::unique_ptr<V4Store> new_store(
85 new V4Store(this->task_runner_, this->store_path_)); 97 new V4Store(this->task_runner_, this->store_path_));
86
87 new_store->state_ = response->new_client_state(); 98 new_store->state_ = response->new_client_state();
88 // TODO(vakh): 99 // TODO(vakh):
89 // 1. Merge the old store and the new update in new_store. 100 // 1. Merge the old store and the new update in new_store.
90 // 2. Create a ListUpdateResponse containing RICE encoded hash-prefixes and 101 // 2. Create a ListUpdateResponse containing RICE encoded hash-prefixes and
91 // response_type as FULL_UPDATE, and write that to disk. 102 // response_type as FULL_UPDATE, and write that to disk.
92 // 3. Remove this if condition after completing 1. and 2. 103 // 3. Remove this if condition after completing 1. and 2.
93 if (response->has_response_type() && 104 HashPrefixMap hash_prefix_map;
94 response->response_type() == ListUpdateResponse::FULL_UPDATE) { 105 ApplyUpdateResult apply_update_result;
95 StoreWriteResult result = new_store->WriteToDisk(std::move(response)); 106 if (response->response_type() == ListUpdateResponse::PARTIAL_UPDATE) {
96 RecordStoreWriteResult(result); 107 for (const auto& removal : response->removals()) {
97 } 108 // TODO(vakh): Allow other compression types.
98 109 // See: https://bugs.chromium.org/p/chromium/issues/detail?id=624567
99 // new_store is done updating, pass it to the callback. 110 DCHECK_EQ(RAW, removal.compression_type());
100 callback_task_runner->PostTask( 111 }
101 FROM_HERE, base::Bind(callback, base::Passed(&new_store))); 112
113 apply_update_result = UpdateHashPrefixMapFromAdditions(
114 response->additions(), &hash_prefix_map);
115 if (apply_update_result == APPLY_UPDATE_SUCCESS) {
116 apply_update_result =
117 new_store->MergeUpdate(hash_prefix_map_, hash_prefix_map);
118 }
119 // TODO(vakh): Generate the updated ListUpdateResponse to write to disk.
120 } else if (response->response_type() == ListUpdateResponse::FULL_UPDATE) {
121 apply_update_result = UpdateHashPrefixMapFromAdditions(
122 response->additions(), &hash_prefix_map);
123 if (apply_update_result == APPLY_UPDATE_SUCCESS) {
124 new_store->hash_prefix_map_ = hash_prefix_map;
125 RecordStoreWriteResult(new_store->WriteToDisk(std::move(response)));
126 }
127 } else {
128 apply_update_result = UNEXPECTED_RESPONSE_TYPE_FAILURE;
129 NOTREACHED() << "Unexpected response type: " << response->response_type();
130 }
131
132 if (apply_update_result == APPLY_UPDATE_SUCCESS) {
133 // new_store is done updating, pass it to the callback.
134 callback_task_runner->PostTask(
135 FROM_HERE, base::Bind(callback, base::Passed(&new_store)));
136 } else {
137 // new_store failed updating. Pass a nullptr to the callback.
138 callback_task_runner->PostTask(FROM_HERE, base::Bind(callback, nullptr));
139 }
140
141 RecordApplyUpdateResult(apply_update_result);
142 }
143
144 // static
145 ApplyUpdateResult V4Store::UpdateHashPrefixMapFromAdditions(
146 const ::google::protobuf::RepeatedPtrField<ThreatEntrySet>& additions,
147 HashPrefixMap* additions_map) {
148 for (const auto& addition : additions) {
149 // TODO(vakh): Allow other compression types.
150 // See: https://bugs.chromium.org/p/chromium/issues/detail?id=624567
151 DCHECK_EQ(RAW, addition.compression_type());
152
153 DCHECK(addition.has_raw_hashes());
154 DCHECK(addition.raw_hashes().has_raw_hashes());
155
156 PrefixSize prefix_size = addition.raw_hashes().prefix_size();
157 ApplyUpdateResult result = AddUnlumpedHashes(
158 prefix_size, addition.raw_hashes().raw_hashes(), additions_map);
159 if (result != APPLY_UPDATE_SUCCESS) {
160 // If there was an error in updating the map, discard the update entirely.
161 return result;
162 }
163 }
164 return APPLY_UPDATE_SUCCESS;
165 }
166
167 // static
168 ApplyUpdateResult V4Store::AddUnlumpedHashes(PrefixSize prefix_size,
169 const std::string& lumped_hashes,
170 HashPrefixMap* additions_map) {
171 if (prefix_size < kMinHashPrefixLength) {
172 NOTREACHED();
173 return PREFIX_SIZE_TOO_SMALL_FAILURE;
174 }
175 if (prefix_size > kMaxHashPrefixLength) {
176 NOTREACHED();
177 return PREFIX_SIZE_TOO_LARGE_FAILURE;
178 }
179 if (lumped_hashes.size() % prefix_size != 0) {
180 return ADDITIONS_SIZE_UNEXPECTED_FAILURE;
181 }
182 // TODO(vakh): Figure out a way to avoid the following copy operation.
183 (*additions_map)[prefix_size] = lumped_hashes;
184 return APPLY_UPDATE_SUCCESS;
185 }
186
187 // static
188 bool V4Store::GetNextSmallestPrefixSize(const HashPrefixMap& hash_prefix_map,
189 const CounterMap& counter_map,
190 PrefixSize* smallest_prefix_size) {
191 HashPrefix smallest_prefix, current_prefix;
192 for (const auto& counter_pair : counter_map) {
193 PrefixSize prefix_size = counter_pair.first;
194 size_t index = counter_pair.second;
195 size_t sized_index = prefix_size * index;
196
197 const HashPrefixes& hash_prefixes = hash_prefix_map.at(prefix_size);
198 if (sized_index < hash_prefixes.size()) {
199 current_prefix = hash_prefixes.substr(sized_index, prefix_size);
200 if (smallest_prefix.empty() || smallest_prefix > current_prefix) {
201 smallest_prefix = current_prefix;
202 *smallest_prefix_size = prefix_size;
203 }
204 }
205 }
206 return !smallest_prefix.empty();
207 }
208
209 // static
210 void V4Store::InitializeCounterMap(const HashPrefixMap& hash_prefix_map,
211 CounterMap* counter_map) {
212 for (const auto& map_pair : hash_prefix_map) {
213 (*counter_map)[map_pair.first] = 0;
214 }
215 }
216
217 // static
218 HashPrefix V4Store::GetNextUnmergedPrefixForSize(
219 PrefixSize prefix_size,
220 const HashPrefixMap& hash_prefix_map,
221 const CounterMap& counter_map) {
222 const HashPrefixes& hash_prefixes = hash_prefix_map.at(prefix_size);
223 size_t index_within_list = counter_map.at(prefix_size);
224 size_t sized_index = prefix_size * index_within_list;
225 return hash_prefixes.substr(sized_index, prefix_size);
226 }
227
228 // static
229 void V4Store::ReserveSpaceInPrefixMap(const HashPrefixMap& other_prefixes_map,
230 HashPrefixMap* prefix_map_to_update) {
231 for (const auto& pair : other_prefixes_map) {
232 PrefixSize prefix_size = pair.first;
233 size_t prefix_length_to_add = pair.second.length();
234
235 const HashPrefixes& existing_prefixes =
236 (*prefix_map_to_update)[prefix_size];
237 size_t existing_capacity = existing_prefixes.capacity();
238
239 (*prefix_map_to_update)[prefix_size].reserve(existing_capacity +
240 prefix_length_to_add);
241 }
242 }
243
244 ApplyUpdateResult V4Store::MergeUpdate(const HashPrefixMap& old_prefixes_map,
245 const HashPrefixMap& additions_map) {
246 DCHECK(hash_prefix_map_.empty());
247 hash_prefix_map_.clear();
248 ReserveSpaceInPrefixMap(old_prefixes_map, &hash_prefix_map_);
249 ReserveSpaceInPrefixMap(additions_map, &hash_prefix_map_);
250
251 PrefixSize next_size_for_old;
252 CounterMap old_counter_map;
253 InitializeCounterMap(old_prefixes_map, &old_counter_map);
254 bool old_has_unmerged = GetNextSmallestPrefixSize(
255 old_prefixes_map, old_counter_map, &next_size_for_old);
256
257 PrefixSize next_size_for_additions;
258 CounterMap additions_counter_map;
259 InitializeCounterMap(additions_map, &additions_counter_map);
260 bool additions_has_unmerged = GetNextSmallestPrefixSize(
261 additions_map, additions_counter_map, &next_size_for_additions);
262
263 // Classical merge sort.
264 // The two constructs to merge are maps: old_prefixes_map, additions_map.
265
266 HashPrefix next_smallest_prefix_old, next_smallest_prefix_additions;
267 // At least one of the maps still has elements that need to be merged into the
268 // new store.
269 while (old_has_unmerged || additions_has_unmerged) {
270 // Old map still has elements that need to be merged.
271 if (old_has_unmerged) {
272 // Get the lexographically smallest hash prefix from the old store.
273 next_smallest_prefix_old = GetNextUnmergedPrefixForSize(
274 next_size_for_old, old_prefixes_map, old_counter_map);
275 }
276
277 // Additions map still has elements that need to be merged.
278 if (additions_has_unmerged) {
279 // Get the lexographically smallest hash prefix from the additions in the
280 // latest update from the server.
281 next_smallest_prefix_additions = GetNextUnmergedPrefixForSize(
282 next_size_for_additions, additions_map, additions_counter_map);
283 }
284
285 // If the same hash prefix appears in the existing store and the additions
286 // list, something is clearly wrong. Discard the update.
287 if (old_has_unmerged && additions_has_unmerged &&
288 next_smallest_prefix_old == next_smallest_prefix_additions) {
289 return ADDITIONS_HAS_EXISTING_PREFIX_FAILURE;
290 }
291
292 // Select which map to pick the next hash prefix from to keep the result in
293 // lexographically sorted order.
294 bool pick_from_old =
295 old_has_unmerged &&
296 (!additions_has_unmerged ||
297 (next_smallest_prefix_old < next_smallest_prefix_additions));
298
299 if (pick_from_old) {
300 hash_prefix_map_[next_size_for_old] += next_smallest_prefix_old;
301
302 // Update the counter map, which means that we have merged one hash
303 // prefix of size |next_size_for_old| from the old store.
304 old_counter_map[next_size_for_old]++;
305
306 // Find the next smallest unmerged element in the old store's map.
307 old_has_unmerged = GetNextSmallestPrefixSize(
308 old_prefixes_map, old_counter_map, &next_size_for_old);
309 } else {
310 hash_prefix_map_[next_size_for_additions] +=
311 next_smallest_prefix_additions;
312
313 // Update the counter map, which means that we have merged one hash
314 // prefix of size |next_size_for_additions| from the update.
315 additions_counter_map[next_size_for_additions]++;
316
317 // Find the next smallest unmerged element in the additions map.
318 additions_has_unmerged = GetNextSmallestPrefixSize(
319 additions_map, additions_counter_map, &next_size_for_additions);
320 }
321 }
322
323 return APPLY_UPDATE_SUCCESS;
102 } 324 }
103 325
104 StoreReadResult V4Store::ReadFromDisk() { 326 StoreReadResult V4Store::ReadFromDisk() {
105 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 327 DCHECK(task_runner_->RunsTasksOnCurrentThread());
106 328
107 std::string contents; 329 std::string contents;
108 bool read_success = base::ReadFileToString(store_path_, &contents); 330 bool read_success = base::ReadFileToString(store_path_, &contents);
109 if (!read_success) { 331 if (!read_success) {
110 return FILE_UNREADABLE_FAILURE; 332 return FILE_UNREADABLE_FAILURE;
111 } 333 }
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
174 396
175 if (!base::Move(new_filename, store_path_)) { 397 if (!base::Move(new_filename, store_path_)) {
176 DVLOG(1) << "store_path_: " << store_path_.value(); 398 DVLOG(1) << "store_path_: " << store_path_.value();
177 return UNABLE_TO_RENAME_FAILURE; 399 return UNABLE_TO_RENAME_FAILURE;
178 } 400 }
179 401
180 return WRITE_SUCCESS; 402 return WRITE_SUCCESS;
181 } 403 }
182 404
183 } // namespace safe_browsing 405 } // namespace safe_browsing
OLDNEW
« no previous file with comments | « components/safe_browsing_db/v4_store.h ('k') | components/safe_browsing_db/v4_store_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698