#include "chrome/browser/safe_browsing/safe_browsing_store_file.h"
#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/md5.h"
-#include "chrome/browser/safe_browsing/safe_browsing_store_unittest_helper.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_paths.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
namespace {
+const int kAddChunk1 = 1;
+const int kAddChunk2 = 3;
+const int kAddChunk3 = 5;
+const int kAddChunk4 = 7;
+// Disjoint chunk numbers for subs to flush out typos.
+const int kSubChunk1 = 2;
+const int kSubChunk2 = 4;
+
+const SBFullHash kHash1 = SBFullHashForString("one");
+const SBFullHash kHash2 = SBFullHashForString("two");
+const SBFullHash kHash3 = SBFullHashForString("three");
+const SBFullHash kHash4 = SBFullHashForString("four");
+const SBFullHash kHash5 = SBFullHashForString("five");
+const SBFullHash kHash6 = SBFullHashForString("six");
+
+const SBPrefix kMinSBPrefix = 0u;
+const SBPrefix kMaxSBPrefix = ~kMinSBPrefix;
+
+} // namespace
+
+namespace safe_browsing {
+
class SafeBrowsingStoreFileTest : public PlatformTest {
public:
virtual void SetUp() {
corruption_detected_ = true;
}
+ // Populate the store with some testing data.
+ void PopulateStore() {
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk1);
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk1));
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash1.prefix));
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash2.prefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetSubChunk(kSubChunk1);
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk1));
+ EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk1, kAddChunk3, kHash3.prefix));
+ EXPECT_TRUE(store_->WriteSubHash(kSubChunk1, kAddChunk3, kHash3));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk2);
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk2));
+ EXPECT_TRUE(store_->WriteAddHash(kAddChunk2, kHash4));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ // Chunk numbers shouldn't leak over.
+ EXPECT_FALSE(store_->CheckAddChunk(kSubChunk1));
+ EXPECT_FALSE(store_->CheckAddChunk(kAddChunk3));
+ EXPECT_FALSE(store_->CheckSubChunk(kAddChunk1));
+ EXPECT_FALSE(store_->CheckSubChunk(kAddChunk2));
+
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+ }
+
+ // Manually read the shard stride info from the file.
+ uint32 ReadStride() {
+ base::ScopedFILE file(base::OpenFile(filename_, "rb"));
+ const long kOffset = 4 * sizeof(uint32);
+ EXPECT_EQ(fseek(file.get(), kOffset, SEEK_SET), 0);
+ uint32 shard_stride = 0;
+ EXPECT_EQ(fread(&shard_stride, sizeof(shard_stride), 1, file.get()), 1U);
+ return shard_stride;
+ }
+
base::ScopedTempDir temp_dir_;
base::FilePath filename_;
scoped_ptr<SafeBrowsingStoreFile> store_;
bool corruption_detected_;
};
-TEST_STORE(SafeBrowsingStoreFileTest, store_.get(), filename_);
+// Test that the empty store looks empty.
+TEST_F(SafeBrowsingStoreFileTest, Empty) {
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ std::vector<int> chunks;
+ store_->GetAddChunks(&chunks);
+ EXPECT_TRUE(chunks.empty());
+ store_->GetSubChunks(&chunks);
+ EXPECT_TRUE(chunks.empty());
+
+ // Shouldn't see anything, but anything is a big set to test.
+ EXPECT_FALSE(store_->CheckAddChunk(0));
+ EXPECT_FALSE(store_->CheckAddChunk(1));
+ EXPECT_FALSE(store_->CheckAddChunk(-1));
+
+ EXPECT_FALSE(store_->CheckSubChunk(0));
+ EXPECT_FALSE(store_->CheckSubChunk(1));
+ EXPECT_FALSE(store_->CheckSubChunk(-1));
+
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+ EXPECT_TRUE(add_full_hashes_result.empty());
+
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ EXPECT_TRUE(prefixes_result.empty());
+}
+
+// Write some prefix and hash data to the store, add more data in another
+// transaction, then verify that the union of all the data is present.
+TEST_F(SafeBrowsingStoreFileTest, BasicStore) {
+ PopulateStore();
+
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ std::vector<int> chunks;
+ store_->GetAddChunks(&chunks);
+ ASSERT_EQ(2U, chunks.size());
+ EXPECT_EQ(kAddChunk1, chunks[0]);
+ EXPECT_EQ(kAddChunk2, chunks[1]);
+
+ store_->GetSubChunks(&chunks);
+ ASSERT_EQ(1U, chunks.size());
+ EXPECT_EQ(kSubChunk1, chunks[0]);
+
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk1));
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk1));
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk3);
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk3, kHash5.prefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ // Still has the chunks expected in the next update.
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ ASSERT_EQ(3U, prefixes_result.size());
+ EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
+ EXPECT_EQ(kHash5.prefix, prefixes_result[1]);
+ EXPECT_EQ(kHash2.prefix, prefixes_result[2]);
+
+ ASSERT_EQ(1U, add_full_hashes_result.size());
+ EXPECT_EQ(kAddChunk2, add_full_hashes_result[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash4, add_full_hashes_result[0].full_hash));
+ }
+}
+
+// Verify that the min and max prefixes are stored and operated on.
+TEST_F(SafeBrowsingStoreFileTest, PrefixMinMax) {
+ PopulateStore();
+
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk3);
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk3, kMinSBPrefix));
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk3, kMaxSBPrefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ ASSERT_EQ(4U, prefixes_result.size());
+ EXPECT_EQ(kMinSBPrefix, prefixes_result[0]);
+ EXPECT_EQ(kHash1.prefix, prefixes_result[1]);
+ EXPECT_EQ(kHash2.prefix, prefixes_result[2]);
+ EXPECT_EQ(kMaxSBPrefix, prefixes_result[3]);
+ }
+
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kSubChunk2);
+ EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk2, kAddChunk3, kMinSBPrefix));
+ EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk2, kAddChunk3, kMaxSBPrefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ ASSERT_EQ(2U, prefixes_result.size());
+ EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
+ EXPECT_EQ(kHash2.prefix, prefixes_result[1]);
+ }
+}
+
+// Test that subs knockout adds.
+TEST_F(SafeBrowsingStoreFileTest, SubKnockout) {
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk1);
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash1.prefix));
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash2.prefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk2);
+ EXPECT_TRUE(store_->WriteAddHash(kAddChunk2, kHash4));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetSubChunk(kSubChunk1);
+ EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk1, kAddChunk3, kHash3.prefix));
+ EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk1, kAddChunk1, kHash2.prefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ // Knocked out the chunk expected.
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ ASSERT_EQ(1U, prefixes_result.size());
+ EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
+
+ ASSERT_EQ(1U, add_full_hashes_result.size());
+ EXPECT_EQ(kAddChunk2, add_full_hashes_result[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash4, add_full_hashes_result[0].full_hash));
+ }
+
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ // This add should be knocked out by an existing sub.
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk3);
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk3, kHash3.prefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ ASSERT_EQ(1U, prefixes_result.size());
+ EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
+
+ ASSERT_EQ(1U, add_full_hashes_result.size());
+ EXPECT_EQ(kAddChunk2, add_full_hashes_result[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash4, add_full_hashes_result[0].full_hash));
+ }
+
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ // But by here the sub should be gone, so it should stick this time.
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk3);
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk3, kHash3.prefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ ASSERT_EQ(2U, prefixes_result.size());
+ EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
+ EXPECT_EQ(kHash3.prefix, prefixes_result[1]);
+
+ ASSERT_EQ(1U, add_full_hashes_result.size());
+ EXPECT_EQ(kAddChunk2, add_full_hashes_result[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash4, add_full_hashes_result[0].full_hash));
+ }
+}
+
+// Test that deletes delete the chunk's data.
+TEST_F(SafeBrowsingStoreFileTest, DeleteChunks) {
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ // A prefix chunk which will be deleted.
+ EXPECT_FALSE(store_->CheckAddChunk(kAddChunk1));
+ store_->SetAddChunk(kAddChunk1);
+ EXPECT_TRUE(store_->BeginChunk());
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash1.prefix));
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash2.prefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ // A prefix chunk which won't be deleted.
+ EXPECT_FALSE(store_->CheckAddChunk(kAddChunk2));
+ store_->SetAddChunk(kAddChunk2);
+ EXPECT_TRUE(store_->BeginChunk());
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk2, kHash3.prefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ // A full-hash chunk which won't be deleted.
+ EXPECT_FALSE(store_->CheckAddChunk(kAddChunk3));
+ store_->SetAddChunk(kAddChunk3);
+ EXPECT_TRUE(store_->BeginChunk());
+ EXPECT_TRUE(store_->WriteAddHash(kAddChunk3, kHash6));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ // A sub chunk to delete.
+ EXPECT_FALSE(store_->CheckSubChunk(kSubChunk1));
+ store_->SetSubChunk(kSubChunk1);
+ EXPECT_TRUE(store_->BeginChunk());
+ EXPECT_TRUE(store_->WriteSubHash(kSubChunk1, kAddChunk3, kHash4));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ // A sub chunk to keep.
+ EXPECT_FALSE(store_->CheckSubChunk(kSubChunk2));
+ store_->SetSubChunk(kSubChunk2);
+ EXPECT_TRUE(store_->BeginChunk());
+ EXPECT_TRUE(store_->WriteSubHash(kSubChunk2, kAddChunk4, kHash5));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ store_->DeleteAddChunk(kAddChunk1);
+ store_->DeleteSubChunk(kSubChunk1);
+
+ // Not actually deleted until finish.
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk1));
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk2));
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk3));
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk1));
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk2));
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ ASSERT_EQ(1U, prefixes_result.size());
+ EXPECT_EQ(kHash3.prefix, prefixes_result[0]);
+
+ ASSERT_EQ(1U, add_full_hashes_result.size());
+ EXPECT_EQ(kAddChunk3, add_full_hashes_result[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash6, add_full_hashes_result[0].full_hash));
+ }
+
+ // Expected chunks are there in another update.
+ ASSERT_TRUE(store_->BeginUpdate());
+ EXPECT_FALSE(store_->CheckAddChunk(kAddChunk1));
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk2));
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk3));
+ EXPECT_FALSE(store_->CheckSubChunk(kSubChunk1));
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk2));
+
+ // Delete them, too.
+ store_->DeleteAddChunk(kAddChunk2);
+ store_->DeleteAddChunk(kAddChunk3);
+ store_->DeleteSubChunk(kSubChunk2);
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ EXPECT_TRUE(prefixes_result.empty());
+ EXPECT_TRUE(add_full_hashes_result.empty());
+ }
+
+ // Expect no more chunks.
+ ASSERT_TRUE(store_->BeginUpdate());
+ EXPECT_FALSE(store_->CheckAddChunk(kAddChunk1));
+ EXPECT_FALSE(store_->CheckAddChunk(kAddChunk2));
+ EXPECT_FALSE(store_->CheckAddChunk(kAddChunk3));
+ EXPECT_FALSE(store_->CheckSubChunk(kSubChunk1));
+ EXPECT_FALSE(store_->CheckSubChunk(kSubChunk2));
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ EXPECT_TRUE(prefixes_result.empty());
+ EXPECT_TRUE(add_full_hashes_result.empty());
+ }
+}
+
+// Test that deleting the store deletes the store.
+TEST_F(SafeBrowsingStoreFileTest, Delete) {
+ // Delete should work if the file wasn't there in the first place.
+ EXPECT_FALSE(base::PathExists(filename_));
+ EXPECT_TRUE(store_->Delete());
+
+ // Create a store file.
+ PopulateStore();
+
+ EXPECT_TRUE(base::PathExists(filename_));
+ EXPECT_TRUE(store_->Delete());
+ EXPECT_FALSE(base::PathExists(filename_));
+}
// Test that Delete() deletes the temporary store, if present.
TEST_F(SafeBrowsingStoreFileTest, DeleteTemp) {
EXPECT_FALSE(base::PathExists(temp_file));
// Starting a transaction creates a temporary file.
- EXPECT_TRUE(store_->BeginUpdate());
+ ASSERT_TRUE(store_->BeginUpdate());
EXPECT_TRUE(base::PathExists(temp_file));
// Pull the rug out from under the existing store, simulating a
// Test basic corruption-handling.
TEST_F(SafeBrowsingStoreFileTest, DetectsCorruption) {
// Load a store with some data.
- SafeBrowsingStoreTestStorePrefix(store_.get());
+ PopulateStore();
// Can successfully open and read the store.
- std::vector<SBAddFullHash> pending_adds;
- SBAddPrefixes orig_prefixes;
- std::vector<SBAddFullHash> orig_hashes;
- EXPECT_TRUE(store_->BeginUpdate());
- EXPECT_TRUE(store_->FinishUpdate(pending_adds, &orig_prefixes, &orig_hashes));
- EXPECT_GT(orig_prefixes.size(), 0U);
- EXPECT_GT(orig_hashes.size(), 0U);
- EXPECT_FALSE(corruption_detected_);
+ {
+ std::vector<SBPrefix> orig_prefixes;
+ std::vector<SBAddFullHash> orig_hashes;
+ safe_browsing::PrefixSetBuilder builder;
+ ASSERT_TRUE(store_->BeginUpdate());
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &orig_hashes));
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&orig_prefixes);
+ EXPECT_GT(orig_prefixes.size(), 0U);
+ EXPECT_GT(orig_hashes.size(), 0U);
+ EXPECT_FALSE(corruption_detected_);
+ }
// Corrupt the store.
- file_util::ScopedFILE file(base::OpenFile(filename_, "rb+"));
+ base::ScopedFILE file(base::OpenFile(filename_, "rb+"));
const long kOffset = 60;
EXPECT_EQ(fseek(file.get(), kOffset, SEEK_SET), 0);
- const int32 kZero = 0;
- int32 previous = kZero;
+ const uint32 kZero = 0;
+ uint32 previous = kZero;
EXPECT_EQ(fread(&previous, sizeof(previous), 1, file.get()), 1U);
EXPECT_NE(previous, kZero);
EXPECT_EQ(fseek(file.get(), kOffset, SEEK_SET), 0);
file.reset();
// Update fails and corruption callback is called.
- SBAddPrefixes add_prefixes;
std::vector<SBAddFullHash> add_hashes;
corruption_detected_ = false;
- EXPECT_TRUE(store_->BeginUpdate());
- EXPECT_FALSE(store_->FinishUpdate(pending_adds, &add_prefixes, &add_hashes));
- EXPECT_TRUE(corruption_detected_);
- EXPECT_EQ(add_prefixes.size(), 0U);
- EXPECT_EQ(add_hashes.size(), 0U);
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ ASSERT_TRUE(store_->BeginUpdate());
+ EXPECT_FALSE(store_->FinishUpdate(&builder, &add_hashes));
+ EXPECT_TRUE(corruption_detected_);
+ }
// Make it look like there is a lot of add-chunks-seen data.
const long kAddChunkCountOffset = 2 * sizeof(int32);
// A store with some data is valid.
EXPECT_FALSE(base::PathExists(filename_));
- SafeBrowsingStoreTestStorePrefix(store_.get());
+ PopulateStore();
EXPECT_TRUE(base::PathExists(filename_));
ASSERT_TRUE(store_->BeginUpdate());
EXPECT_FALSE(corruption_detected_);
EXPECT_TRUE(store_->CancelUpdate());
}
-// Corrupt the payload.
-TEST_F(SafeBrowsingStoreFileTest, CheckValidityPayload) {
- SafeBrowsingStoreTestStorePrefix(store_.get());
+// Corrupt the header.
+TEST_F(SafeBrowsingStoreFileTest, CheckValidityHeader) {
+ PopulateStore();
EXPECT_TRUE(base::PathExists(filename_));
- // 37 is the most random prime number. It's also past the header,
- // as corrupting the header would fail BeginUpdate() in which case
- // CheckValidity() cannot be called.
+ // 37 is the most random prime number. It's also past the initial header
+ // struct, somewhere in the chunk list.
const size_t kOffset = 37;
{
- file_util::ScopedFILE file(base::OpenFile(filename_, "rb+"));
+ base::ScopedFILE file(base::OpenFile(filename_, "rb+"));
+ EXPECT_EQ(0, fseek(file.get(), kOffset, SEEK_SET));
+ EXPECT_GE(fputs("hello", file.get()), 0);
+ }
+ ASSERT_FALSE(store_->BeginUpdate());
+ EXPECT_TRUE(corruption_detected_);
+}
+
+// Corrupt the prefix payload.
+TEST_F(SafeBrowsingStoreFileTest, CheckValidityPayload) {
+ PopulateStore();
+ EXPECT_TRUE(base::PathExists(filename_));
+
+ // 137 is the second most random prime number. It's also past the header and
+ // chunk-id area. Corrupting the header would fail BeginUpdate() in which
+ // case CheckValidity() cannot be called.
+ const size_t kOffset = 137;
+
+ {
+ base::ScopedFILE file(base::OpenFile(filename_, "rb+"));
EXPECT_EQ(0, fseek(file.get(), kOffset, SEEK_SET));
EXPECT_GE(fputs("hello", file.get()), 0);
}
// Corrupt the checksum.
TEST_F(SafeBrowsingStoreFileTest, CheckValidityChecksum) {
- SafeBrowsingStoreTestStorePrefix(store_.get());
+ PopulateStore();
EXPECT_TRUE(base::PathExists(filename_));
// An offset from the end of the file which is in the checksum.
const int kOffset = -static_cast<int>(sizeof(base::MD5Digest));
{
- file_util::ScopedFILE file(base::OpenFile(filename_, "rb+"));
+ base::ScopedFILE file(base::OpenFile(filename_, "rb+"));
EXPECT_EQ(0, fseek(file.get(), kOffset, SEEK_END));
EXPECT_GE(fputs("hello", file.get()), 0);
}
EXPECT_TRUE(store_->CancelUpdate());
}
-} // namespace
+TEST_F(SafeBrowsingStoreFileTest, GetAddPrefixesAndHashes) {
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk1);
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk1));
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash1.prefix));
+ EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash2.prefix));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(kAddChunk2);
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk2));
+ EXPECT_TRUE(store_->WriteAddHash(kAddChunk2, kHash4));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ store_->SetSubChunk(kSubChunk1);
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk1));
+ EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk1, kAddChunk3, kHash3.prefix));
+ EXPECT_TRUE(store_->WriteSubHash(kSubChunk1, kAddChunk3, kHash3));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ // Chunk numbers shouldn't leak over.
+ EXPECT_FALSE(store_->CheckAddChunk(kSubChunk1));
+ EXPECT_FALSE(store_->CheckAddChunk(kAddChunk3));
+ EXPECT_FALSE(store_->CheckSubChunk(kAddChunk1));
+
+ std::vector<int> chunks;
+ store_->GetAddChunks(&chunks);
+ ASSERT_EQ(2U, chunks.size());
+ EXPECT_EQ(kAddChunk1, chunks[0]);
+ EXPECT_EQ(kAddChunk2, chunks[1]);
+
+ store_->GetSubChunks(&chunks);
+ ASSERT_EQ(1U, chunks.size());
+ EXPECT_EQ(kSubChunk1, chunks[0]);
+
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ SBAddPrefixes add_prefixes;
+ EXPECT_TRUE(store_->GetAddPrefixes(&add_prefixes));
+ ASSERT_EQ(2U, add_prefixes.size());
+ EXPECT_EQ(kAddChunk1, add_prefixes[0].chunk_id);
+ EXPECT_EQ(kHash1.prefix, add_prefixes[0].prefix);
+ EXPECT_EQ(kAddChunk1, add_prefixes[1].chunk_id);
+ EXPECT_EQ(kHash2.prefix, add_prefixes[1].prefix);
+
+ std::vector<SBAddFullHash> add_hashes;
+ EXPECT_TRUE(store_->GetAddFullHashes(&add_hashes));
+ ASSERT_EQ(1U, add_hashes.size());
+ EXPECT_EQ(kAddChunk2, add_hashes[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash4, add_hashes[0].full_hash));
+}
+
+// Test that the database handles resharding correctly, both when growing and
+// which shrinking.
+TEST_F(SafeBrowsingStoreFileTest, Resharding) {
+ // Loop through multiple stride boundaries (1<<32, 1<<31, 1<<30, 1<<29).
+ const uint32 kTargetStride = 1 << 29;
+
+ // Each chunk will require 8 bytes per prefix, plus 4 bytes for chunk
+ // information. It should be less than |kTargetFootprint| in the
+ // implementation, but high enough to keep the number of rewrites modest (to
+ // keep the test fast).
+ const size_t kPrefixesPerChunk = 10000;
+
+ uint32 shard_stride = 0;
+ int chunk_id = 1;
+
+ // Add a series of chunks, tracking that the stride size changes in a
+ // direction appropriate to increasing file size.
+ do {
+ ASSERT_TRUE(store_->BeginUpdate());
+
+ EXPECT_TRUE(store_->BeginChunk());
+ store_->SetAddChunk(chunk_id);
+ EXPECT_TRUE(store_->CheckAddChunk(chunk_id));
+ for (size_t i = 0; i < kPrefixesPerChunk; ++i) {
+ EXPECT_TRUE(store_->WriteAddPrefix(chunk_id, static_cast<SBPrefix>(i)));
+ }
+ EXPECT_TRUE(store_->FinishChunk());
+
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ SBAddPrefixes add_prefixes;
+ EXPECT_TRUE(store_->GetAddPrefixes(&add_prefixes));
+ ASSERT_EQ(chunk_id * kPrefixesPerChunk, add_prefixes.size());
+
+ // New stride should be the same, or shifted one right.
+ const uint32 new_shard_stride = ReadStride();
+ EXPECT_TRUE((new_shard_stride == shard_stride) ||
+ ((new_shard_stride << 1) == shard_stride));
+ shard_stride = new_shard_stride;
+ ++chunk_id;
+ } while (!shard_stride || shard_stride > kTargetStride);
+
+ // Guard against writing too many chunks. If this gets too big, adjust
+ // |kPrefixesPerChunk|.
+ EXPECT_LT(chunk_id, 20);
+
+ // Remove each chunk and check that the stride goes back to 0.
+ while (--chunk_id) {
+ ASSERT_TRUE(store_->BeginUpdate());
+ EXPECT_TRUE(store_->CheckAddChunk(chunk_id));
+ EXPECT_FALSE(store_->CheckAddChunk(chunk_id + 1));
+ store_->DeleteAddChunk(chunk_id);
+
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ // New stride should be the same, or shifted one left.
+ const uint32 new_shard_stride = ReadStride();
+ EXPECT_TRUE((new_shard_stride == shard_stride) ||
+ (new_shard_stride == (shard_stride << 1)));
+ shard_stride = new_shard_stride;
+ }
+ EXPECT_EQ(0u, shard_stride);
+}
+
+// Test that a golden v7 file can be read by the current code. All platforms
+// generating v7 files are little-endian, so there is no point to testing this
+// transition if/when a big-endian port is added.
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+TEST_F(SafeBrowsingStoreFileTest, Version7) {
+ store_.reset();
+
+ // Copy the golden file into temporary storage. The golden file contains:
+ // - Add chunk kAddChunk1 containing kHash1.prefix and kHash2.
+ // - Sub chunk kSubChunk1 containing kHash3.
+ const char kBasename[] = "FileStoreVersion7";
+ base::FilePath golden_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &golden_path));
+ golden_path = golden_path.AppendASCII("SafeBrowsing");
+ golden_path = golden_path.AppendASCII(kBasename);
+ ASSERT_TRUE(base::CopyFile(golden_path, filename_));
+
+ // Reset the store to make sure it re-reads the file.
+ store_.reset(new SafeBrowsingStoreFile());
+ store_->Init(filename_,
+ base::Bind(&SafeBrowsingStoreFileTest::OnCorruptionDetected,
+ base::Unretained(this)));
+
+ // Check that the expected prefixes and hashes are in place.
+ SBAddPrefixes add_prefixes;
+ EXPECT_TRUE(store_->GetAddPrefixes(&add_prefixes));
+ ASSERT_EQ(2U, add_prefixes.size());
+ EXPECT_EQ(kAddChunk1, add_prefixes[0].chunk_id);
+ EXPECT_EQ(kHash1.prefix, add_prefixes[0].prefix);
+ EXPECT_EQ(kAddChunk1, add_prefixes[1].chunk_id);
+ EXPECT_EQ(kHash2.prefix, add_prefixes[1].prefix);
+
+ std::vector<SBAddFullHash> add_hashes;
+ EXPECT_TRUE(store_->GetAddFullHashes(&add_hashes));
+ ASSERT_EQ(1U, add_hashes.size());
+ EXPECT_EQ(kAddChunk1, add_hashes[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash2, add_hashes[0].full_hash));
+
+ // Attempt an update to make sure things work end-to-end.
+ EXPECT_TRUE(store_->BeginUpdate());
+
+ // Still has the chunks expected in the next update.
+ std::vector<int> chunks;
+ store_->GetAddChunks(&chunks);
+ ASSERT_EQ(1U, chunks.size());
+ EXPECT_EQ(kAddChunk1, chunks[0]);
+
+ store_->GetSubChunks(&chunks);
+ ASSERT_EQ(1U, chunks.size());
+ EXPECT_EQ(kSubChunk1, chunks[0]);
+
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk1));
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk1));
+
+ // Sub chunk kAddChunk1 hash kHash2.
+ store_->SetSubChunk(kSubChunk2);
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk1));
+ EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk1, kAddChunk1, kHash2.prefix));
+ EXPECT_TRUE(store_->WriteSubHash(kSubChunk1, kAddChunk1, kHash2));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ // The sub'ed prefix and hash are gone.
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ ASSERT_EQ(1U, prefixes_result.size());
+ EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
+ EXPECT_TRUE(add_full_hashes_result.empty());
+ }
+}
+#endif
+
+// Test that a golden v8 file can be read by the current code. All platforms
+// generating v8 files are little-endian, so there is no point to testing this
+// transition if/when a big-endian port is added.
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+TEST_F(SafeBrowsingStoreFileTest, Version8) {
+ store_.reset();
+
+ // Copy the golden file into temporary storage. The golden file contains:
+ // - Add chunk kAddChunk1 containing kHash1.prefix and kHash2.
+ // - Sub chunk kSubChunk1 containing kHash3.
+ const char kBasename[] = "FileStoreVersion8";
+ base::FilePath golden_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &golden_path));
+ golden_path = golden_path.AppendASCII("SafeBrowsing");
+ golden_path = golden_path.AppendASCII(kBasename);
+ ASSERT_TRUE(base::CopyFile(golden_path, filename_));
+
+ // Reset the store to make sure it re-reads the file.
+ store_.reset(new SafeBrowsingStoreFile());
+ store_->Init(filename_,
+ base::Bind(&SafeBrowsingStoreFileTest::OnCorruptionDetected,
+ base::Unretained(this)));
+
+ // Check that the expected prefixes and hashes are in place.
+ SBAddPrefixes add_prefixes;
+ EXPECT_TRUE(store_->GetAddPrefixes(&add_prefixes));
+ ASSERT_EQ(2U, add_prefixes.size());
+ EXPECT_EQ(kAddChunk1, add_prefixes[0].chunk_id);
+ EXPECT_EQ(kHash1.prefix, add_prefixes[0].prefix);
+ EXPECT_EQ(kAddChunk1, add_prefixes[1].chunk_id);
+ EXPECT_EQ(kHash2.prefix, add_prefixes[1].prefix);
+
+ std::vector<SBAddFullHash> add_hashes;
+ EXPECT_TRUE(store_->GetAddFullHashes(&add_hashes));
+ ASSERT_EQ(1U, add_hashes.size());
+ EXPECT_EQ(kAddChunk1, add_hashes[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash2, add_hashes[0].full_hash));
+
+ // Attempt an update to make sure things work end-to-end.
+ EXPECT_TRUE(store_->BeginUpdate());
+
+ // Still has the chunks expected in the next update.
+ std::vector<int> chunks;
+ store_->GetAddChunks(&chunks);
+ ASSERT_EQ(1U, chunks.size());
+ EXPECT_EQ(kAddChunk1, chunks[0]);
+
+ store_->GetSubChunks(&chunks);
+ ASSERT_EQ(1U, chunks.size());
+ EXPECT_EQ(kSubChunk1, chunks[0]);
+
+ EXPECT_TRUE(store_->CheckAddChunk(kAddChunk1));
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk1));
+
+ // Sub chunk kAddChunk1 hash kHash2.
+ store_->SetSubChunk(kSubChunk2);
+ EXPECT_TRUE(store_->CheckSubChunk(kSubChunk1));
+ EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk1, kAddChunk1, kHash2.prefix));
+ EXPECT_TRUE(store_->WriteSubHash(kSubChunk1, kAddChunk1, kHash2));
+ EXPECT_TRUE(store_->FinishChunk());
+
+ {
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ EXPECT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+
+ // The sub'ed prefix and hash are gone.
+ std::vector<SBPrefix> prefixes_result;
+ builder.GetPrefixSetNoHashes()->GetPrefixes(&prefixes_result);
+ ASSERT_EQ(1U, prefixes_result.size());
+ EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
+ EXPECT_TRUE(add_full_hashes_result.empty());
+ }
+}
+#endif
+
+// Test that when the v8 golden file is updated, the add prefix injected from
+// the full hash is removed. All platforms generating v8 files are
+// little-endian, so there is no point to testing this transition if/when a
+// big-endian port is added.
+#if defined(ARCH_CPU_LITTLE_ENDIAN)
+TEST_F(SafeBrowsingStoreFileTest, KnockoutPrefixVolunteers) {
+ store_.reset();
+
+ // Copy the golden file into temporary storage. The golden file contains:
+ // - Add chunk kAddChunk1 containing kHash1.prefix and kHash2.
+ // - Sub chunk kSubChunk1 containing kHash3.
+ const char kBasename[] = "FileStoreVersion8";
+ base::FilePath golden_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &golden_path));
+ golden_path = golden_path.AppendASCII("SafeBrowsing");
+ golden_path = golden_path.AppendASCII(kBasename);
+ ASSERT_TRUE(base::CopyFile(golden_path, filename_));
+
+ // Reset the store to make sure it re-reads the file.
+ store_.reset(new SafeBrowsingStoreFile());
+ store_->Init(filename_,
+ base::Bind(&SafeBrowsingStoreFileTest::OnCorruptionDetected,
+ base::Unretained(this)));
+
+ // Check that the expected prefixes and hashes are in place.
+ {
+ SBAddPrefixes add_prefixes;
+ EXPECT_TRUE(store_->GetAddPrefixes(&add_prefixes));
+ ASSERT_EQ(2U, add_prefixes.size());
+ EXPECT_EQ(kAddChunk1, add_prefixes[0].chunk_id);
+ EXPECT_EQ(kHash1.prefix, add_prefixes[0].prefix);
+ EXPECT_EQ(kAddChunk1, add_prefixes[1].chunk_id);
+ EXPECT_EQ(kHash2.prefix, add_prefixes[1].prefix);
+
+ std::vector<SBAddFullHash> add_hashes;
+ EXPECT_TRUE(store_->GetAddFullHashes(&add_hashes));
+ ASSERT_EQ(1U, add_hashes.size());
+ EXPECT_EQ(kAddChunk1, add_hashes[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash2, add_hashes[0].full_hash));
+ }
+
+ // Update the store.
+ {
+ EXPECT_TRUE(store_->BeginUpdate());
+
+ safe_browsing::PrefixSetBuilder builder;
+ std::vector<SBAddFullHash> add_full_hashes_result;
+ ASSERT_TRUE(store_->FinishUpdate(&builder, &add_full_hashes_result));
+ }
+
+ // Reset the store to make sure it re-reads the file.
+ store_.reset(new SafeBrowsingStoreFile());
+ store_->Init(filename_,
+ base::Bind(&SafeBrowsingStoreFileTest::OnCorruptionDetected,
+ base::Unretained(this)));
+
+ // |kHash2.prefix| should have dropped.
+ {
+ SBAddPrefixes add_prefixes;
+ EXPECT_TRUE(store_->GetAddPrefixes(&add_prefixes));
+ ASSERT_EQ(1U, add_prefixes.size());
+ EXPECT_EQ(kAddChunk1, add_prefixes[0].chunk_id);
+ EXPECT_EQ(kHash1.prefix, add_prefixes[0].prefix);
+
+ std::vector<SBAddFullHash> add_hashes;
+ EXPECT_TRUE(store_->GetAddFullHashes(&add_hashes));
+ ASSERT_EQ(1U, add_hashes.size());
+ EXPECT_EQ(kAddChunk1, add_hashes[0].chunk_id);
+ EXPECT_TRUE(SBFullHashEqual(kHash2, add_hashes[0].full_hash));
+ }
+}
+#endif
+
+} // namespace safe_browsing