root/chrome/browser/safe_browsing/safe_browsing_store_file_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. SetUp
  2. TearDown
  3. OnCorruptionDetected
  4. PopulateStore
  5. ReadStride
  6. TEST_F
  7. TEST_F
  8. TEST_F
  9. TEST_F
  10. TEST_F
  11. TEST_F
  12. TEST_F
  13. TEST_F
  14. TEST_F
  15. TEST_F
  16. TEST_F
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F

// Copyright (c) 2012 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/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 "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 SBPrefix kMinSBPrefix = 0u;
const SBPrefix kMaxSBPrefix = ~kMinSBPrefix;

class SafeBrowsingStoreFileTest : public PlatformTest {
 public:
  virtual void SetUp() {
    PlatformTest::SetUp();

    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());

    filename_ = temp_dir_.path();
    filename_ = filename_.AppendASCII("SafeBrowsingTestStore");

    store_.reset(new SafeBrowsingStoreFile());
    store_->Init(filename_,
                 base::Bind(&SafeBrowsingStoreFileTest::OnCorruptionDetected,
                            base::Unretained(this)));
    corruption_detected_ = false;
  }
  virtual void TearDown() {
    if (store_.get())
      store_->Delete();
    store_.reset();

    PlatformTest::TearDown();
  }

  void OnCorruptionDetected() {
    corruption_detected_ = true;
  }

  // Populate the store with some testing data.
  void PopulateStore(const base::Time& now) {
    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_->WriteAddHash(kAddChunk1, now, kHash2));

    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<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;

    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &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 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));

  std::vector<SBAddFullHash> pending_adds;
  safe_browsing::PrefixSetBuilder builder;
  std::vector<SBAddFullHash> add_full_hashes_result;

  EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                   &builder,
                                   &add_full_hashes_result));
  EXPECT_TRUE(add_full_hashes_result.empty());

  std::vector<SBPrefix> prefixes_result;
  builder.GetPrefixSet()->GetPrefixes(&prefixes_result);
  EXPECT_TRUE(prefixes_result.empty());
}

// Write some prefix data to the store and verify that it looks like
// it is still there after the transaction completes.
TEST_F(SafeBrowsingStoreFileTest, StorePrefix) {
  const base::Time now = base::Time::Now();
  PopulateStore(now);

  ASSERT_TRUE(store_->BeginUpdate());

  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]);

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->GetPrefixes(&prefixes_result);
    ASSERT_EQ(2U, prefixes_result.size());
    EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
    EXPECT_EQ(kHash2.prefix, prefixes_result[1]);

    ASSERT_EQ(1U, add_full_hashes_result.size());
    EXPECT_EQ(kAddChunk1, add_full_hashes_result[0].chunk_id);
    // EXPECT_TRUE(add_full_hashes_result[0].received == now)?
    EXPECT_EQ(now.ToTimeT(), add_full_hashes_result[0].received);
    EXPECT_TRUE(SBFullHashEqual(kHash2, add_full_hashes_result[0].full_hash));
  }

  ASSERT_TRUE(store_->BeginUpdate());

  // Still has the chunks expected in the next update.
  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));

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    // Still has the expected contents.
    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->GetPrefixes(&prefixes_result);
    ASSERT_EQ(2U, prefixes_result.size());
    EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
    EXPECT_EQ(kHash2.prefix, prefixes_result[1]);

    ASSERT_EQ(1U, add_full_hashes_result.size());
    EXPECT_EQ(kAddChunk1, add_full_hashes_result[0].chunk_id);
    EXPECT_EQ(now.ToTimeT(), add_full_hashes_result[0].received);
    EXPECT_TRUE(SBFullHashEqual(kHash2, add_full_hashes_result[0].full_hash));
  }
}

// Verify that the min and max prefixes are stored and operated on.
TEST_F(SafeBrowsingStoreFileTest, PrefixMinMax) {
  const base::Time now = base::Time::Now();
  PopulateStore(now);

  ASSERT_TRUE(store_->BeginUpdate());

  EXPECT_TRUE(store_->BeginChunk());
  store_->SetAddChunk(kAddChunk2);
  EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk2, kMinSBPrefix));
  EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk2, kMaxSBPrefix));
  EXPECT_TRUE(store_->FinishChunk());

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->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, kAddChunk2, kMinSBPrefix));
  EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk2, kAddChunk2, kMaxSBPrefix));
  EXPECT_TRUE(store_->FinishChunk());

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->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());

  const base::Time now = base::Time::Now();

  EXPECT_TRUE(store_->BeginChunk());
  store_->SetAddChunk(kAddChunk1);
  EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash1.prefix));
  EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk1, kHash2.prefix));
  EXPECT_TRUE(store_->WriteAddHash(kAddChunk1, now, kHash2));

  store_->SetSubChunk(kSubChunk1);
  EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk1, kAddChunk3, kHash3.prefix));
  EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk1, kAddChunk1, kHash2.prefix));
  EXPECT_TRUE(store_->FinishChunk());

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    // Knocked out the chunk expected.
    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->GetPrefixes(&prefixes_result);
    ASSERT_EQ(1U, prefixes_result.size());
    EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
    EXPECT_TRUE(add_full_hashes_result.empty());
  }

  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());

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->GetPrefixes(&prefixes_result);
    EXPECT_EQ(1U, prefixes_result.size());
    EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
    EXPECT_TRUE(add_full_hashes_result.empty());
  }

  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());

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->GetPrefixes(&prefixes_result);
    ASSERT_EQ(2U, prefixes_result.size());
    EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
    EXPECT_EQ(kHash3.prefix, prefixes_result[1]);
    EXPECT_TRUE(add_full_hashes_result.empty());
  }
}

// Test that deletes delete the chunk's data.
TEST_F(SafeBrowsingStoreFileTest, DeleteChunks) {
  ASSERT_TRUE(store_->BeginUpdate());

  const base::Time now = base::Time::Now();

  // A 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_->WriteAddHash(kAddChunk1, now, kHash2));
  EXPECT_TRUE(store_->FinishChunk());

  // Another which won't.
  EXPECT_FALSE(store_->CheckAddChunk(kAddChunk2));
  store_->SetAddChunk(kAddChunk2);
  EXPECT_TRUE(store_->BeginChunk());
  EXPECT_TRUE(store_->WriteAddPrefix(kAddChunk2, kHash3.prefix));
  EXPECT_TRUE(store_->WriteAddHash(kAddChunk2, now, kHash3));
  EXPECT_TRUE(store_->FinishChunk());

  // A sub chunk to delete.
  EXPECT_FALSE(store_->CheckSubChunk(kSubChunk1));
  store_->SetSubChunk(kSubChunk1);
  EXPECT_TRUE(store_->BeginChunk());
  EXPECT_TRUE(store_->WriteSubPrefix(kSubChunk1, kAddChunk3, kHash4.prefix));
  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_->WriteSubPrefix(kSubChunk2, kAddChunk4, kHash5.prefix));
  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_->CheckSubChunk(kSubChunk1));
  EXPECT_TRUE(store_->CheckSubChunk(kSubChunk2));

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->GetPrefixes(&prefixes_result);
    EXPECT_EQ(1U, prefixes_result.size());
    EXPECT_EQ(kHash3.prefix, prefixes_result[0]);

    EXPECT_EQ(1U, add_full_hashes_result.size());
    EXPECT_EQ(kAddChunk2, add_full_hashes_result[0].chunk_id);
    EXPECT_EQ(now.ToTimeT(), add_full_hashes_result[0].received);
    EXPECT_TRUE(SBFullHashEqual(kHash3, 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_FALSE(store_->CheckSubChunk(kSubChunk1));
  EXPECT_TRUE(store_->CheckSubChunk(kSubChunk2));

  // Delete them, too.
  store_->DeleteAddChunk(kAddChunk2);
  store_->DeleteSubChunk(kSubChunk2);

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));
  }

  // Expect no more chunks.
  ASSERT_TRUE(store_->BeginUpdate());
  EXPECT_FALSE(store_->CheckAddChunk(kAddChunk1));
  EXPECT_FALSE(store_->CheckAddChunk(kAddChunk2));
  EXPECT_FALSE(store_->CheckSubChunk(kSubChunk1));
  EXPECT_FALSE(store_->CheckSubChunk(kSubChunk2));

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->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(base::Time::Now());

  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) {
  const base::FilePath temp_file =
      SafeBrowsingStoreFile::TemporaryFileForFilename(filename_);

  EXPECT_FALSE(base::PathExists(filename_));
  EXPECT_FALSE(base::PathExists(temp_file));

  // Starting a transaction creates a temporary file.
  ASSERT_TRUE(store_->BeginUpdate());
  EXPECT_TRUE(base::PathExists(temp_file));

  // Pull the rug out from under the existing store, simulating a
  // crash.
  store_.reset(new SafeBrowsingStoreFile());
  store_->Init(filename_, base::Closure());
  EXPECT_FALSE(base::PathExists(filename_));
  EXPECT_TRUE(base::PathExists(temp_file));

  // Make sure the temporary file is deleted.
  EXPECT_TRUE(store_->Delete());
  EXPECT_FALSE(base::PathExists(filename_));
  EXPECT_FALSE(base::PathExists(temp_file));
}

// Test basic corruption-handling.
TEST_F(SafeBrowsingStoreFileTest, DetectsCorruption) {
  // Load a store with some data.
  PopulateStore(base::Time::Now());

  // Can successfully open and read the store.
  {
    std::vector<SBAddFullHash> pending_adds;
    std::vector<SBPrefix> orig_prefixes;
    std::vector<SBAddFullHash> orig_hashes;
    safe_browsing::PrefixSetBuilder builder;
    ASSERT_TRUE(store_->BeginUpdate());
    EXPECT_TRUE(store_->FinishUpdate(pending_adds, &builder, &orig_hashes));
    builder.GetPrefixSet()->GetPrefixes(&orig_prefixes);
    EXPECT_GT(orig_prefixes.size(), 0U);
    EXPECT_GT(orig_hashes.size(), 0U);
    EXPECT_FALSE(corruption_detected_);
  }

  // Corrupt the store.
  base::ScopedFILE file(base::OpenFile(filename_, "rb+"));
  const long kOffset = 60;
  EXPECT_EQ(fseek(file.get(), kOffset, SEEK_SET), 0);
  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);
  EXPECT_EQ(fwrite(&kZero, sizeof(kZero), 1, file.get()), 1U);
  file.reset();

  // Update fails and corruption callback is called.
  std::vector<SBAddFullHash> add_hashes;
  corruption_detected_ = false;
  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    ASSERT_TRUE(store_->BeginUpdate());
    EXPECT_FALSE(store_->FinishUpdate(pending_adds, &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);
  const int32 kLargeCount = 1000 * 1000 * 1000;
  file.reset(base::OpenFile(filename_, "rb+"));
  EXPECT_EQ(fseek(file.get(), kAddChunkCountOffset, SEEK_SET), 0);
  EXPECT_EQ(fwrite(&kLargeCount, sizeof(kLargeCount), 1, file.get()), 1U);
  file.reset();

  // Detects corruption and fails to even begin the update.
  corruption_detected_ = false;
  EXPECT_FALSE(store_->BeginUpdate());
  EXPECT_TRUE(corruption_detected_);
}

TEST_F(SafeBrowsingStoreFileTest, CheckValidity) {
  // Empty store is valid.
  EXPECT_FALSE(base::PathExists(filename_));
  ASSERT_TRUE(store_->BeginUpdate());
  EXPECT_FALSE(corruption_detected_);
  EXPECT_TRUE(store_->CheckValidity());
  EXPECT_FALSE(corruption_detected_);
  EXPECT_TRUE(store_->CancelUpdate());

  // A store with some data is valid.
  EXPECT_FALSE(base::PathExists(filename_));
  PopulateStore(base::Time::Now());
  EXPECT_TRUE(base::PathExists(filename_));
  ASSERT_TRUE(store_->BeginUpdate());
  EXPECT_FALSE(corruption_detected_);
  EXPECT_TRUE(store_->CheckValidity());
  EXPECT_FALSE(corruption_detected_);
  EXPECT_TRUE(store_->CancelUpdate());
}

// Corrupt the header.
TEST_F(SafeBrowsingStoreFileTest, CheckValidityHeader) {
  PopulateStore(base::Time::Now());
  EXPECT_TRUE(base::PathExists(filename_));

  // 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;

  {
    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(base::Time::Now());
  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);
  }
  ASSERT_TRUE(store_->BeginUpdate());
  EXPECT_FALSE(corruption_detected_);
  EXPECT_FALSE(store_->CheckValidity());
  EXPECT_TRUE(corruption_detected_);
  EXPECT_TRUE(store_->CancelUpdate());
}

// Corrupt the checksum.
TEST_F(SafeBrowsingStoreFileTest, CheckValidityChecksum) {
  PopulateStore(base::Time::Now());
  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));

  {
    base::ScopedFILE file(base::OpenFile(filename_, "rb+"));
    EXPECT_EQ(0, fseek(file.get(), kOffset, SEEK_END));
    EXPECT_GE(fputs("hello", file.get()), 0);
  }
  ASSERT_TRUE(store_->BeginUpdate());
  EXPECT_FALSE(corruption_detected_);
  EXPECT_FALSE(store_->CheckValidity());
  EXPECT_TRUE(corruption_detected_);
  EXPECT_TRUE(store_->CancelUpdate());
}

TEST_F(SafeBrowsingStoreFileTest, GetAddPrefixesAndHashes) {
  ASSERT_TRUE(store_->BeginUpdate());

  const base::Time now = base::Time::Now();

  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_->WriteAddHash(kAddChunk1, now, kHash2));

  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(1U, chunks.size());
  EXPECT_EQ(kAddChunk1, chunks[0]);

  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(std::vector<SBAddFullHash>(),
                                   &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(kAddChunk1, add_hashes[0].chunk_id);
  EXPECT_TRUE(SBFullHashEqual(kHash2, 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());

    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &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);

    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &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());

  {
    std::vector<SBAddFullHash> pending_adds;
    safe_browsing::PrefixSetBuilder builder;
    std::vector<SBAddFullHash> add_full_hashes_result;
    EXPECT_TRUE(store_->FinishUpdate(pending_adds,
                                     &builder,
                                     &add_full_hashes_result));

    // The sub'ed prefix and hash are gone.
    std::vector<SBPrefix> prefixes_result;
    builder.GetPrefixSet()->GetPrefixes(&prefixes_result);
    ASSERT_EQ(1U, prefixes_result.size());
    EXPECT_EQ(kHash1.prefix, prefixes_result[0]);
    EXPECT_TRUE(add_full_hashes_result.empty());
  }
}
#endif

}  // namespace

/* [<][>][^][v][top][bottom][index][help] */