#include "base/logging.h"
#include "base/md5.h"
#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "chrome/common/chrome_paths.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
const SBPrefix kHighBitClear = 1000u * 1000u * 1000u;
const SBPrefix kHighBitSet = 3u * 1000u * 1000u * 1000u;
+} // namespace
+
+namespace safe_browsing {
+
class PrefixSetTest : public PlatformTest {
protected:
// Constants for the v1 format.
static const size_t kVersionOffset = 1 * sizeof(uint32);
static const size_t kIndexSizeOffset = 2 * sizeof(uint32);
static const size_t kDeltasSizeOffset = 3 * sizeof(uint32);
- static const size_t kPayloadOffset = 4 * sizeof(uint32);
+ static const size_t kFullHashesSizeOffset = 4 * sizeof(uint32);
+ static const size_t kPayloadOffset = 5 * sizeof(uint32);
// Generate a set of random prefixes to share between tests. For
// most tests this generation was a large fraction of the test time.
// Check that all elements of |prefixes| are in |prefix_set|, and
// that nearby elements are not (for lack of a more sensible set of
// items to check for absence).
- static void CheckPrefixes(const safe_browsing::PrefixSet& prefix_set,
+ static void CheckPrefixes(const PrefixSet& prefix_set,
const std::vector<SBPrefix> &prefixes) {
// The set can generate the prefixes it believes it has, so that's
// a good starting point.
EXPECT_TRUE(std::equal(check.begin(), check.end(), prefixes_copy.begin()));
for (size_t i = 0; i < prefixes.size(); ++i) {
- EXPECT_TRUE(prefix_set.Exists(prefixes[i]));
+ EXPECT_TRUE(prefix_set.PrefixExists(prefixes[i]));
const SBPrefix left_sibling = prefixes[i] - 1;
if (check.count(left_sibling) == 0)
- EXPECT_FALSE(prefix_set.Exists(left_sibling));
+ EXPECT_FALSE(prefix_set.PrefixExists(left_sibling));
const SBPrefix right_sibling = prefixes[i] + 1;
if (check.count(right_sibling) == 0)
- EXPECT_FALSE(prefix_set.Exists(right_sibling));
+ EXPECT_FALSE(prefix_set.PrefixExists(right_sibling));
}
}
base::FilePath filename = temp_dir_.path().AppendASCII("PrefixSetTest");
- safe_browsing::PrefixSetBuilder builder(shared_prefixes_);
- if (!builder.GetPrefixSet()->WriteFile(filename))
+ PrefixSetBuilder builder(shared_prefixes_);
+ if (!builder.GetPrefixSetNoHashes()->WriteFile(filename))
return false;
*filenamep = filename;
ASSERT_EQ(new_size_64, size_64);
}
+ // Fill |prefixes| with values read from a reference file. The reference file
+ // was generated from a specific |shared_prefixes_|.
+ bool ReadReferencePrefixes(std::vector<SBPrefix>* prefixes) {
+ const char kRefname[] = "PrefixSetRef";
+ base::FilePath ref_path;
+ if (!PathService::Get(chrome::DIR_TEST_DATA, &ref_path))
+ return false;
+ ref_path = ref_path.AppendASCII("SafeBrowsing");
+ ref_path = ref_path.AppendASCII(kRefname);
+
+ base::ScopedFILE file(base::OpenFile(ref_path, "r"));
+ if (!file.get())
+ return false;
+ char buf[1024];
+ while (fgets(buf, sizeof(buf), file.get())) {
+ std::string trimmed;
+ if (base::TRIM_TRAILING !=
+ base::TrimWhitespace(buf, base::TRIM_ALL, &trimmed))
+ return false;
+ unsigned prefix;
+ if (!base::StringToUint(trimmed, &prefix))
+ return false;
+ prefixes->push_back(prefix);
+ }
+ return true;
+ }
+
// Tests should not modify this shared resource.
static std::vector<SBPrefix> shared_prefixes_;
// Test that a small sparse random input works.
TEST_F(PrefixSetTest, Baseline) {
- safe_browsing::PrefixSetBuilder builder(shared_prefixes_);
- CheckPrefixes(*builder.GetPrefixSet(), shared_prefixes_);
+ PrefixSetBuilder builder(shared_prefixes_);
+ CheckPrefixes(*builder.GetPrefixSetNoHashes(), shared_prefixes_);
}
// Test that the empty set doesn't appear to have anything in it.
TEST_F(PrefixSetTest, Empty) {
const std::vector<SBPrefix> empty;
- safe_browsing::PrefixSetBuilder builder(empty);
- scoped_ptr<safe_browsing::PrefixSet> prefix_set = builder.GetPrefixSet();
+ PrefixSetBuilder builder(empty);
+ scoped_ptr<PrefixSet> prefix_set = builder.GetPrefixSetNoHashes();
for (size_t i = 0; i < shared_prefixes_.size(); ++i) {
- EXPECT_FALSE(prefix_set->Exists(shared_prefixes_[i]));
+ EXPECT_FALSE(prefix_set->PrefixExists(shared_prefixes_[i]));
}
}
// Single-element set should work fine.
TEST_F(PrefixSetTest, OneElement) {
const std::vector<SBPrefix> prefixes(100, 0u);
- safe_browsing::PrefixSetBuilder builder(prefixes);
- scoped_ptr<safe_browsing::PrefixSet> prefix_set = builder.GetPrefixSet();
- EXPECT_FALSE(prefix_set->Exists(static_cast<SBPrefix>(-1)));
- EXPECT_TRUE(prefix_set->Exists(prefixes[0]));
- EXPECT_FALSE(prefix_set->Exists(1u));
+ PrefixSetBuilder builder(prefixes);
+ scoped_ptr<PrefixSet> prefix_set = builder.GetPrefixSetNoHashes();
+ EXPECT_FALSE(prefix_set->PrefixExists(static_cast<SBPrefix>(-1)));
+ EXPECT_TRUE(prefix_set->PrefixExists(prefixes[0]));
+ EXPECT_FALSE(prefix_set->PrefixExists(1u));
// Check that |GetPrefixes()| returns the same set of prefixes as
// was passed in.
prefixes.push_back(0xFFFFFFFF);
std::sort(prefixes.begin(), prefixes.end());
- safe_browsing::PrefixSetBuilder builder(prefixes);
- scoped_ptr<safe_browsing::PrefixSet> prefix_set = builder.GetPrefixSet();
+ PrefixSetBuilder builder(prefixes);
+ scoped_ptr<PrefixSet> prefix_set = builder.GetPrefixSetNoHashes();
// Check that |GetPrefixes()| returns the same set of prefixes as
// was passed in.
}
std::sort(prefixes.begin(), prefixes.end());
- safe_browsing::PrefixSetBuilder builder(prefixes);
- scoped_ptr<safe_browsing::PrefixSet> prefix_set = builder.GetPrefixSet();
+ PrefixSetBuilder builder(prefixes);
+ scoped_ptr<PrefixSet> prefix_set = builder.GetPrefixSetNoHashes();
// Check that |GetPrefixes()| returns the same set of prefixes as
// was passed in.
prefixes_copy.begin()));
}
-// Use artificial inputs to test various edge cases in Exists().
-// Items before the lowest item aren't present. Items after the
-// largest item aren't present. Create a sequence of items with
-// deltas above and below 2^16, and make sure they're all present.
-// Create a very long sequence with deltas below 2^16 to test crossing
-// |kMaxRun|.
+// Use artificial inputs to test various edge cases in PrefixExists(). Items
+// before the lowest item aren't present. Items after the largest item aren't
+// present. Create a sequence of items with deltas above and below 2^16, and
+// make sure they're all present. Create a very long sequence with deltas below
+// 2^16 to test crossing |kMaxRun|.
TEST_F(PrefixSetTest, EdgeCases) {
std::vector<SBPrefix> prefixes;
}
std::sort(prefixes.begin(), prefixes.end());
- safe_browsing::PrefixSetBuilder builder(prefixes);
- scoped_ptr<safe_browsing::PrefixSet> prefix_set = builder.GetPrefixSet();
+ PrefixSetBuilder builder(prefixes);
+ scoped_ptr<PrefixSet> prefix_set = builder.GetPrefixSetNoHashes();
// Check that |GetPrefixes()| returns the same set of prefixes as
// was passed in.
prefixes_copy.begin()));
// Items before and after the set are not present, and don't crash.
- EXPECT_FALSE(prefix_set->Exists(kHighBitSet - 100));
- EXPECT_FALSE(prefix_set->Exists(kHighBitClear + 100));
+ EXPECT_FALSE(prefix_set->PrefixExists(kHighBitSet - 100));
+ EXPECT_FALSE(prefix_set->PrefixExists(kHighBitClear + 100));
// Check that the set correctly flags all of the inputs, and also
// check items just above and below the inputs to make sure they
// aren't present.
for (size_t i = 0; i < prefixes.size(); ++i) {
- EXPECT_TRUE(prefix_set->Exists(prefixes[i]));
+ EXPECT_TRUE(prefix_set->PrefixExists(prefixes[i]));
- EXPECT_FALSE(prefix_set->Exists(prefixes[i] - 1));
- EXPECT_FALSE(prefix_set->Exists(prefixes[i] + 1));
+ EXPECT_FALSE(prefix_set->PrefixExists(prefixes[i] - 1));
+ EXPECT_FALSE(prefix_set->PrefixExists(prefixes[i] + 1));
}
}
// the prefixes. Leaves the path in |filename|.
{
ASSERT_TRUE(GetPrefixSetFile(&filename));
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_TRUE(prefix_set.get());
CheckPrefixes(*prefix_set, shared_prefixes_);
}
prefixes.push_back(kHighBitClear);
prefixes.push_back(kHighBitSet);
- safe_browsing::PrefixSetBuilder builder(prefixes);
- ASSERT_TRUE(builder.GetPrefixSet()->WriteFile(filename));
+ PrefixSetBuilder builder(prefixes);
+ ASSERT_TRUE(builder.GetPrefixSetNoHashes()->WriteFile(filename));
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_TRUE(prefix_set.get());
CheckPrefixes(*prefix_set, prefixes);
}
// Test writing and reading an empty set.
{
std::vector<SBPrefix> prefixes;
- safe_browsing::PrefixSetBuilder builder(prefixes);
- ASSERT_TRUE(builder.GetPrefixSet()->WriteFile(filename));
+ PrefixSetBuilder builder(prefixes);
+ ASSERT_TRUE(builder.GetPrefixSetNoHashes()->WriteFile(filename));
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_TRUE(prefix_set.get());
CheckPrefixes(*prefix_set, prefixes);
}
+
+ // Test that full hashes are persisted.
+ {
+ std::vector<SBFullHash> hashes;
+ hashes.push_back(SBFullHashForString("one"));
+ hashes.push_back(SBFullHashForString("two"));
+ hashes.push_back(SBFullHashForString("three"));
+
+ std::vector<SBPrefix> prefixes(shared_prefixes_);
+
+ // Remove any collisions from the prefixes.
+ for (size_t i = 0; i < hashes.size(); ++i) {
+ std::vector<SBPrefix>::iterator iter =
+ std::lower_bound(prefixes.begin(), prefixes.end(), hashes[i].prefix);
+ if (iter != prefixes.end() && *iter == hashes[i].prefix)
+ prefixes.erase(iter);
+ }
+
+ PrefixSetBuilder builder(prefixes);
+ ASSERT_TRUE(builder.GetPrefixSet(hashes)->WriteFile(filename));
+
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
+ ASSERT_TRUE(prefix_set.get());
+ CheckPrefixes(*prefix_set, prefixes);
+
+ EXPECT_TRUE(prefix_set->Exists(hashes[0]));
+ EXPECT_TRUE(prefix_set->Exists(hashes[1]));
+ EXPECT_TRUE(prefix_set->Exists(hashes[2]));
+ EXPECT_FALSE(prefix_set->PrefixExists(hashes[0].prefix));
+ EXPECT_FALSE(prefix_set->PrefixExists(hashes[1].prefix));
+ EXPECT_FALSE(prefix_set->PrefixExists(hashes[2].prefix));
+ }
}
// Check that |CleanChecksum()| makes an acceptable checksum.
base::ScopedFILE file(base::OpenFile(filename, "r+b"));
IncrementIntAt(file.get(), kPayloadOffset, 1);
file.reset();
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_FALSE(prefix_set.get());
// Fix up the checksum and it will read successfully (though the
file.reset(base::OpenFile(filename, "r+b"));
CleanChecksum(file.get());
file.reset();
- prefix_set = safe_browsing::PrefixSet::LoadFile(filename);
+ prefix_set = PrefixSet::LoadFile(filename);
ASSERT_TRUE(prefix_set.get());
}
-// Bad |index_| size is caught by the sanity check.
+// Bad magic is caught by the sanity check.
TEST_F(PrefixSetTest, CorruptionMagic) {
base::FilePath filename;
ASSERT_TRUE(GetPrefixSetFile(&filename));
ASSERT_NO_FATAL_FAILURE(
ModifyAndCleanChecksum(filename, kMagicOffset, 1));
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_FALSE(prefix_set.get());
}
-// Bad |index_| size is caught by the sanity check.
+// Bad version is caught by the sanity check.
TEST_F(PrefixSetTest, CorruptionVersion) {
base::FilePath filename;
ASSERT_TRUE(GetPrefixSetFile(&filename));
ASSERT_NO_FATAL_FAILURE(
- ModifyAndCleanChecksum(filename, kVersionOffset, 1));
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ ModifyAndCleanChecksum(filename, kVersionOffset, 10));
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_FALSE(prefix_set.get());
}
ASSERT_NO_FATAL_FAILURE(
ModifyAndCleanChecksum(filename, kIndexSizeOffset, 1));
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_FALSE(prefix_set.get());
}
ASSERT_NO_FATAL_FAILURE(
ModifyAndCleanChecksum(filename, kDeltasSizeOffset, 1));
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
+ ASSERT_FALSE(prefix_set.get());
+}
+
+// Bad |full_hashes_| size is caught by the sanity check.
+TEST_F(PrefixSetTest, CorruptionFullHashesSize) {
+ base::FilePath filename;
+ ASSERT_TRUE(GetPrefixSetFile(&filename));
+
+ ASSERT_NO_FATAL_FAILURE(
+ ModifyAndCleanChecksum(filename, kFullHashesSizeOffset, 1));
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_FALSE(prefix_set.get());
}
base::ScopedFILE file(base::OpenFile(filename, "r+b"));
ASSERT_NO_FATAL_FAILURE(IncrementIntAt(file.get(), 666, 1));
file.reset();
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_FALSE(prefix_set.get());
}
long digest_offset = static_cast<long>(size_64 - sizeof(base::MD5Digest));
ASSERT_NO_FATAL_FAILURE(IncrementIntAt(file.get(), digest_offset, 1));
file.reset();
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_FALSE(prefix_set.get());
}
const char buf[] = "im in ur base, killing ur d00dz.";
ASSERT_EQ(strlen(buf), fwrite(buf, 1, strlen(buf), file.get()));
file.reset();
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_FALSE(prefix_set.get());
}
CleanChecksum(file.get());
file.reset(); // Flush updates.
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_FALSE(prefix_set.get());
}
+// Test Exists() against full hashes passed to builder.
+TEST_F(PrefixSetTest, FullHashBuild) {
+ 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");
+
+ std::vector<SBPrefix> prefixes;
+ prefixes.push_back(kHash1.prefix);
+ prefixes.push_back(kHash2.prefix);
+ std::sort(prefixes.begin(), prefixes.end());
+
+ std::vector<SBFullHash> hashes;
+ hashes.push_back(kHash4);
+ hashes.push_back(kHash5);
+
+ PrefixSetBuilder builder(prefixes);
+ scoped_ptr<PrefixSet> prefix_set = builder.GetPrefixSet(hashes);
+
+ EXPECT_TRUE(prefix_set->Exists(kHash1));
+ EXPECT_TRUE(prefix_set->Exists(kHash2));
+ EXPECT_FALSE(prefix_set->Exists(kHash3));
+ EXPECT_TRUE(prefix_set->Exists(kHash4));
+ EXPECT_TRUE(prefix_set->Exists(kHash5));
+ EXPECT_FALSE(prefix_set->Exists(kHash6));
+
+ EXPECT_TRUE(prefix_set->PrefixExists(kHash1.prefix));
+ EXPECT_TRUE(prefix_set->PrefixExists(kHash2.prefix));
+ EXPECT_FALSE(prefix_set->PrefixExists(kHash3.prefix));
+ EXPECT_FALSE(prefix_set->PrefixExists(kHash4.prefix));
+ EXPECT_FALSE(prefix_set->PrefixExists(kHash5.prefix));
+ EXPECT_FALSE(prefix_set->PrefixExists(kHash6.prefix));
+}
+
// Test that a version 1 file is re-ordered correctly on read.
TEST_F(PrefixSetTest, ReadWriteSigned) {
base::FilePath filename;
CleanChecksum(file.get());
file.reset(); // Flush updates.
- scoped_ptr<safe_browsing::PrefixSet> prefix_set =
- safe_browsing::PrefixSet::LoadFile(filename);
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(filename);
ASSERT_TRUE(prefix_set.get());
- // |Exists()| uses |std::upper_bound()| to find a starting point, which
+ // |PrefixExists()| uses |std::upper_bound()| to find a starting point, which
// assumes |index_| is sorted. Depending on how |upper_bound()| is
// implemented, if the actual list is sorted by |int32|, then one of these
// test pairs should fail.
- EXPECT_TRUE(prefix_set->Exists(1000u));
- EXPECT_TRUE(prefix_set->Exists(1023u));
- EXPECT_TRUE(prefix_set->Exists(static_cast<uint32>(-1000)));
- EXPECT_TRUE(prefix_set->Exists(static_cast<uint32>(-1000 + 23)));
+ EXPECT_TRUE(prefix_set->PrefixExists(1000u));
+ EXPECT_TRUE(prefix_set->PrefixExists(1023u));
+ EXPECT_TRUE(prefix_set->PrefixExists(static_cast<uint32>(-1000)));
+ EXPECT_TRUE(prefix_set->PrefixExists(static_cast<uint32>(-1000 + 23)));
std::vector<SBPrefix> prefixes_copy;
prefix_set->GetPrefixes(&prefixes_copy);
EXPECT_EQ(prefixes_copy[3], static_cast<uint32>(-1000 + 23));
}
-} // namespace
+// Test that a golden v2 file can be read by the current code. All platforms
+// generating v2 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(PrefixSetTest, Version2) {
+ std::vector<SBPrefix> ref_prefixes;
+ ASSERT_TRUE(ReadReferencePrefixes(&ref_prefixes));
+
+ const char kBasename[] = "PrefixSetVersion2";
+ 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);
+
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(golden_path);
+ ASSERT_TRUE(prefix_set.get());
+ CheckPrefixes(*prefix_set, ref_prefixes);
+}
+#endif
+
+// Test that a golden v3 file can be read by the current code. All platforms
+// generating v3 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(PrefixSetTest, Version3) {
+ std::vector<SBPrefix> ref_prefixes;
+ ASSERT_TRUE(ReadReferencePrefixes(&ref_prefixes));
+
+ const char kBasename[] = "PrefixSetVersion3";
+ 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);
+
+ scoped_ptr<PrefixSet> prefix_set = PrefixSet::LoadFile(golden_path);
+ ASSERT_TRUE(prefix_set.get());
+ CheckPrefixes(*prefix_set, ref_prefixes);
+
+ const SBFullHash kHash1 = SBFullHashForString("www.evil.com/malware.html");
+ const SBFullHash kHash2 = SBFullHashForString("www.evil.com/phishing.html");
+
+ EXPECT_TRUE(prefix_set->Exists(kHash1));
+ EXPECT_TRUE(prefix_set->Exists(kHash2));
+ EXPECT_FALSE(prefix_set->PrefixExists(kHash1.prefix));
+ EXPECT_FALSE(prefix_set->PrefixExists(kHash2.prefix));
+}
+#endif
+
+} // namespace safe_browsing