Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / safe_browsing / safe_browsing_database_unittest.cc
index 70d2b01..44d9bdb 100644 (file)
@@ -4,13 +4,18 @@
 //
 // Unit tests for the SafeBrowsing storage system.
 
-#include "base/file_util.h"
+#include "chrome/browser/safe_browsing/safe_browsing_database.h"
+
+#include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/logging.h"
+#include "base/memory/scoped_vector.h"
 #include "base/message_loop/message_loop.h"
 #include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
 #include "base/time/time.h"
-#include "chrome/browser/safe_browsing/safe_browsing_database.h"
+#include "chrome/browser/safe_browsing/chunk.pb.h"
 #include "chrome/browser/safe_browsing/safe_browsing_store_file.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "crypto/sha2.h"
 #include "url/gurl.h"
 
 using base::Time;
+using base::TimeDelta;
 
 namespace {
 
+const TimeDelta kCacheLifetime = TimeDelta::FromMinutes(45);
+
 SBPrefix SBPrefixForString(const std::string& str) {
   return SBFullHashForString(str).prefix;
 }
 
+// Construct a full hash which has the given prefix, with the given
+// suffix data coming after the prefix.
+SBFullHash SBFullHashForPrefixAndSuffix(SBPrefix prefix,
+                                        const base::StringPiece& suffix) {
+  SBFullHash full_hash;
+  memset(&full_hash, 0, sizeof(SBFullHash));
+  full_hash.prefix = prefix;
+  CHECK_LE(suffix.size() + sizeof(SBPrefix), sizeof(SBFullHash));
+  memcpy(full_hash.full_hash + sizeof(SBPrefix), suffix.data(), suffix.size());
+  return full_hash;
+}
+
 std::string HashedIpPrefix(const std::string& ip_prefix, size_t prefix_size) {
   net::IPAddressNumber ip_number;
   EXPECT_TRUE(net::ParseIPLiteralToNumber(ip_prefix, &ip_number));
@@ -41,167 +61,160 @@ std::string HashedIpPrefix(const std::string& ip_prefix, size_t prefix_size) {
   return hash;
 }
 
-// Same as InsertAddChunkHostPrefixUrl, but with pre-computed
-// prefix values.
-void InsertAddChunkHostPrefixValue(SBChunk* chunk,
-                                   int chunk_number,
-                                   const SBPrefix& host_prefix,
-                                   const SBPrefix& url_prefix) {
-  chunk->chunk_number = chunk_number;
-  chunk->is_add = true;
-  SBChunkHost host;
-  host.host = host_prefix;
-  host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 1);
-  host.entry->set_chunk_id(chunk->chunk_number);
-  host.entry->SetPrefixAt(0, url_prefix);
-  chunk->hosts.push_back(host);
+// Helper to build a chunk.  Caller takes ownership.
+SBChunkData* BuildChunk(int chunk_number,
+                        safe_browsing::ChunkData::ChunkType chunk_type,
+                        safe_browsing::ChunkData::PrefixType prefix_type,
+                        const void* data, size_t data_size,
+                        const std::vector<int>& add_chunk_numbers) {
+  scoped_ptr<safe_browsing::ChunkData> raw_data(new safe_browsing::ChunkData);
+  raw_data->set_chunk_number(chunk_number);
+  raw_data->set_chunk_type(chunk_type);
+  raw_data->set_prefix_type(prefix_type);
+  raw_data->set_hashes(data, data_size);
+  raw_data->clear_add_numbers();
+  for (size_t i = 0; i < add_chunk_numbers.size(); ++i) {
+    raw_data->add_add_numbers(add_chunk_numbers[i]);
+  }
+
+  return new SBChunkData(raw_data.release());
 }
 
-// A helper function that appends one AddChunkHost to chunk with
-// one url for prefix.
-void InsertAddChunkHostPrefixUrl(SBChunk* chunk,
-                                 int chunk_number,
-                                 const std::string& host_name,
-                                 const std::string& url) {
-  InsertAddChunkHostPrefixValue(chunk, chunk_number,
-                                SBPrefixForString(host_name),
-                                SBPrefixForString(url));
+// Create add chunk with a single prefix.
+SBChunkData* AddChunkPrefix(int chunk_number,  SBPrefix prefix) {
+  return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD,
+                    safe_browsing::ChunkData::PREFIX_4B,
+                    &prefix, sizeof(prefix),
+                    std::vector<int>());
 }
 
-// Same as InsertAddChunkHostPrefixUrl, but with full hashes.
-void InsertAddChunkHostFullHashes(SBChunk* chunk,
-                                  int chunk_number,
-                                  const std::string& host_name,
-                                  const std::string& url) {
-  chunk->chunk_number = chunk_number;
-  chunk->is_add = true;
-  SBChunkHost host;
-  host.host = SBPrefixForString(host_name);
-  host.entry = SBEntry::Create(SBEntry::ADD_FULL_HASH, 1);
-  host.entry->set_chunk_id(chunk->chunk_number);
-  host.entry->SetFullHashAt(0, SBFullHashForString(url));
-  chunk->hosts.push_back(host);
+// Create add chunk with a single prefix generated from |value|.
+SBChunkData* AddChunkPrefixValue(int chunk_number,
+                                 const std::string& value) {
+  return AddChunkPrefix(chunk_number, SBPrefixForString(value));
 }
 
-void InsertAddChunkFullHash(SBChunk* chunk,
-                            int chunk_number,
-                            const std::string& ip_str,
-                            size_t prefix_size) {
-  const std::string full_hash_str = HashedIpPrefix(ip_str, prefix_size);
-  EXPECT_EQ(sizeof(SBFullHash), full_hash_str.size());
-  SBFullHash full_hash;
-  std::memcpy(&(full_hash.full_hash), full_hash_str.data(), sizeof(SBFullHash));
+// Generate an add chunk with two prefixes.
+SBChunkData* AddChunkPrefix2Value(int chunk_number,
+                                  const std::string& value1,
+                                  const std::string& value2) {
+  const SBPrefix prefixes[2] = {
+    SBPrefixForString(value1),
+    SBPrefixForString(value2),
+  };
+  return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD,
+                    safe_browsing::ChunkData::PREFIX_4B,
+                    &prefixes[0], sizeof(prefixes),
+                    std::vector<int>());
+}
+
+// Generate an add chunk with four prefixes.
+SBChunkData* AddChunkPrefix4Value(int chunk_number,
+                                  const std::string& value1,
+                                  const std::string& value2,
+                                  const std::string& value3,
+                                  const std::string& value4) {
+  const SBPrefix prefixes[4] = {
+    SBPrefixForString(value1),
+    SBPrefixForString(value2),
+    SBPrefixForString(value3),
+    SBPrefixForString(value4),
+  };
+  return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD,
+                    safe_browsing::ChunkData::PREFIX_4B,
+                    &prefixes[0], sizeof(prefixes),
+                    std::vector<int>());
+}
 
-  chunk->chunk_number = chunk_number;
-  chunk->is_add = true;
-  SBChunkHost host;
-  host.host = full_hash.prefix;
-  host.entry = SBEntry::Create(SBEntry::ADD_FULL_HASH, 1);
-  host.entry->set_chunk_id(chunk->chunk_number);
-  host.entry->SetFullHashAt(0, full_hash);
-  chunk->hosts.push_back(host);
+// Generate an add chunk with a full hash.
+SBChunkData* AddChunkFullHash(int chunk_number, SBFullHash full_hash) {
+  return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD,
+                    safe_browsing::ChunkData::FULL_32B,
+                    &full_hash, sizeof(full_hash),
+                    std::vector<int>());
 }
 
-// Same as InsertAddChunkHostPrefixUrl, but with two urls for prefixes.
-void InsertAddChunkHost2PrefixUrls(SBChunk* chunk,
-                                   int chunk_number,
-                                   const std::string& host_name,
-                                   const std::string& url1,
-                                   const std::string& url2) {
-  chunk->chunk_number = chunk_number;
-  chunk->is_add = true;
-  SBChunkHost host;
-  host.host = SBPrefixForString(host_name);
-  host.entry = SBEntry::Create(SBEntry::ADD_PREFIX, 2);
-  host.entry->set_chunk_id(chunk->chunk_number);
-  host.entry->SetPrefixAt(0, SBPrefixForString(url1));
-  host.entry->SetPrefixAt(1, SBPrefixForString(url2));
-  chunk->hosts.push_back(host);
+// Generate an add chunk with a full hash generated from |value|.
+SBChunkData* AddChunkFullHashValue(int chunk_number,
+                                   const std::string& value) {
+  return AddChunkFullHash(chunk_number, SBFullHashForString(value));
 }
 
-// Same as InsertAddChunkHost2PrefixUrls, but with full hashes.
-void InsertAddChunkHost2FullHashes(SBChunk* chunk,
-                                   int chunk_number,
-                                   const std::string& host_name,
-                                   const std::string& url1,
-                                   const std::string& url2) {
-  chunk->chunk_number = chunk_number;
-  chunk->is_add = true;
-  SBChunkHost host;
-  host.host = SBPrefixForString(host_name);
-  host.entry = SBEntry::Create(SBEntry::ADD_FULL_HASH, 2);
-  host.entry->set_chunk_id(chunk->chunk_number);
-  host.entry->SetFullHashAt(0, SBFullHashForString(url1));
-  host.entry->SetFullHashAt(1, SBFullHashForString(url2));
-  chunk->hosts.push_back(host);
+// Generate an add chunk with two full hashes.
+SBChunkData* AddChunkFullHash2Value(int chunk_number,
+                                   const std::string& value1,
+                                   const std::string& value2) {
+  const SBFullHash full_hashes[2] = {
+    SBFullHashForString(value1),
+    SBFullHashForString(value2),
+  };
+  return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD,
+                    safe_browsing::ChunkData::FULL_32B,
+                    &full_hashes[0], sizeof(full_hashes),
+                    std::vector<int>());
 }
 
-// Same as InsertSubChunkHostPrefixUrl, but with pre-computed
-// prefix values.
-void InsertSubChunkHostPrefixValue(SBChunk* chunk,
-                                   int chunk_number,
-                                   int chunk_id_to_sub,
-                                   const SBPrefix& host_prefix,
-                                   const SBPrefix& url_prefix) {
-  chunk->chunk_number = chunk_number;
-  chunk->is_add = false;
-  SBChunkHost host;
-  host.host = host_prefix;
-  host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 1);
-  host.entry->set_chunk_id(chunk->chunk_number);
-  host.entry->SetChunkIdAtPrefix(0, chunk_id_to_sub);
-  host.entry->SetPrefixAt(0, url_prefix);
-  chunk->hosts.push_back(host);
+// Generate a sub chunk with a prefix generated from |value|.
+SBChunkData* SubChunkPrefixValue(int chunk_number,
+                                 const std::string& value,
+                                 int add_chunk_number) {
+  const SBPrefix prefix = SBPrefixForString(value);
+  return BuildChunk(chunk_number, safe_browsing::ChunkData::SUB,
+                    safe_browsing::ChunkData::PREFIX_4B,
+                    &prefix, sizeof(prefix),
+                    std::vector<int>(1, add_chunk_number));
 }
 
-// A helper function that adds one SubChunkHost to chunk with
-// one url for prefix.
-void InsertSubChunkHostPrefixUrl(SBChunk* chunk,
-                                 int chunk_number,
-                                 int chunk_id_to_sub,
-                                 const std::string& host_name,
-                                 const std::string& url) {
-  InsertSubChunkHostPrefixValue(chunk, chunk_number,
-                                chunk_id_to_sub,
-                                SBPrefixForString(host_name),
-                                SBPrefixForString(url));
+// Generate a sub chunk with two prefixes.
+SBChunkData* SubChunkPrefix2Value(int chunk_number,
+                                  const std::string& value1,
+                                  int add_chunk_number1,
+                                  const std::string& value2,
+                                  int add_chunk_number2) {
+  const SBPrefix prefixes[2] = {
+    SBPrefixForString(value1),
+    SBPrefixForString(value2),
+  };
+  std::vector<int> add_chunk_numbers;
+  add_chunk_numbers.push_back(add_chunk_number1);
+  add_chunk_numbers.push_back(add_chunk_number2);
+  return BuildChunk(chunk_number, safe_browsing::ChunkData::SUB,
+                    safe_browsing::ChunkData::PREFIX_4B,
+                    &prefixes[0], sizeof(prefixes),
+                    add_chunk_numbers);
 }
 
-// Same as InsertSubChunkHostPrefixUrl, but with two urls for prefixes.
-void InsertSubChunkHost2PrefixUrls(SBChunk* chunk,
-                                   int chunk_number,
-                                   int chunk_id_to_sub,
-                                   const std::string& host_name,
-                                   const std::string& url1,
-                                   const std::string& url2) {
-  chunk->chunk_number = chunk_number;
-  chunk->is_add = false;
-  SBChunkHost host;
-  host.host = SBPrefixForString(host_name);
-  host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 2);
-  host.entry->set_chunk_id(chunk->chunk_number);
-  host.entry->SetPrefixAt(0, SBPrefixForString(url1));
-  host.entry->SetChunkIdAtPrefix(0, chunk_id_to_sub);
-  host.entry->SetPrefixAt(1, SBPrefixForString(url2));
-  host.entry->SetChunkIdAtPrefix(1, chunk_id_to_sub);
-  chunk->hosts.push_back(host);
+// Generate a sub chunk with a full hash.
+SBChunkData* SubChunkFullHash(int chunk_number,
+                              SBFullHash full_hash,
+                              int add_chunk_number) {
+  return BuildChunk(chunk_number, safe_browsing::ChunkData::SUB,
+                    safe_browsing::ChunkData::FULL_32B,
+                    &full_hash, sizeof(full_hash),
+                    std::vector<int>(1, add_chunk_number));
 }
 
-// Same as InsertSubChunkHost2PrefixUrls, but with full hashes.
-void InsertSubChunkHostFullHash(SBChunk* chunk,
-                                int chunk_number,
-                                int chunk_id_to_sub,
-                                const std::string& host_name,
-                                const std::string& url) {
-  chunk->chunk_number = chunk_number;
-  chunk->is_add = false;
-  SBChunkHost host;
-  host.host = SBPrefixForString(host_name);
-  host.entry = SBEntry::Create(SBEntry::SUB_FULL_HASH, 2);
-  host.entry->set_chunk_id(chunk->chunk_number);
-  host.entry->SetFullHashAt(0, SBFullHashForString(url));
-  host.entry->SetChunkIdAtPrefix(0, chunk_id_to_sub);
-  chunk->hosts.push_back(host);
+// Generate a sub chunk with a full hash generated from |value|.
+SBChunkData* SubChunkFullHashValue(int chunk_number,
+                                   const std::string& value,
+                                   int add_chunk_number) {
+  return SubChunkFullHash(chunk_number,
+                          SBFullHashForString(value),
+                          add_chunk_number);
+}
+
+// Generate an add chunk with a single full hash for the ip blacklist.
+SBChunkData* AddChunkHashedIpValue(int chunk_number,
+                                   const std::string& ip_str,
+                                   size_t prefix_size) {
+  const std::string full_hash_str = HashedIpPrefix(ip_str, prefix_size);
+  EXPECT_EQ(sizeof(SBFullHash), full_hash_str.size());
+  SBFullHash full_hash;
+  std::memcpy(&(full_hash.full_hash), full_hash_str.data(), sizeof(SBFullHash));
+  return BuildChunk(chunk_number, safe_browsing::ChunkData::ADD,
+                    safe_browsing::ChunkData::FULL_32B,
+                    &full_hash, sizeof(full_hash),
+                    std::vector<int>());
 }
 
 // Prevent DCHECK from killing tests.
@@ -260,7 +273,7 @@ class SafeBrowsingDatabaseTest : public PlatformTest {
 
   void GetListsInfo(std::vector<SBListChunkRanges>* lists) {
     lists->clear();
-    EXPECT_TRUE(database_->UpdateStarted(lists));
+    ASSERT_TRUE(database_->UpdateStarted(lists));
     database_->UpdateFinished(true);
   }
 
@@ -295,93 +308,65 @@ class SafeBrowsingDatabaseTest : public PlatformTest {
 
 // Tests retrieving list name information.
 TEST_F(SafeBrowsingDatabaseTest, ListNameForBrowse) {
-  SBChunkList chunks;
-  SBChunk chunk;
-
-  InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/",
-                              "www.evil.com/malware.html");
-  chunks.clear();
-  chunks.push_back(chunk);
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ScopedVector<SBChunkData> chunks;
 
-  chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&chunk, 2, "www.foo.com/",
-                              "www.foo.com/malware.html");
-  chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html"));
+  chunks.push_back(AddChunkPrefixValue(2, "www.foo.com/malware.html"));
+  chunks.push_back(AddChunkPrefixValue(3, "www.whatever.com/malware.html"));
 
-  chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&chunk, 3, "www.whatever.com/",
-                              "www.whatever.com/malware.html");
-  chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].adds, "1-3");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1-3", lists[0].adds);
   EXPECT_TRUE(lists[0].subs.empty());
 
   // Insert a malware sub chunk.
-  chunk.hosts.clear();
-  InsertSubChunkHostPrefixUrl(&chunk, 7, 19, "www.subbed.com/",
-                              "www.subbed.com/noteveil1.html");
   chunks.clear();
-  chunks.push_back(chunk);
+  chunks.push_back(SubChunkPrefixValue(7, "www.subbed.com/noteveil1.html", 19));
 
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].adds, "1-3");
-  EXPECT_EQ(lists[0].subs, "7");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1-3", lists[0].adds);
+  EXPECT_EQ("7", lists[0].subs);
   if (lists.size() == 2) {
     // Old style database won't have the second entry since it creates the lists
     // when it receives an update containing that list. The filter-based
     // database has these values hard coded.
-    EXPECT_TRUE(lists[1].name == safe_browsing_util::kPhishingList);
+    EXPECT_EQ(safe_browsing_util::kPhishingList, lists[1].name);
     EXPECT_TRUE(lists[1].adds.empty());
     EXPECT_TRUE(lists[1].subs.empty());
   }
 
-  // Add a phishing add chunk.
-  chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&chunk, 47, "www.evil.com/",
-                              "www.evil.com/phishing.html");
-  chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks);
-
-  // Insert some phishing sub chunks.
-  chunk.hosts.clear();
-  InsertSubChunkHostPrefixUrl(&chunk, 200, 1999, "www.phishy.com/",
-                              "www.phishy.com/notevil1.html");
-  chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks);
-
-  chunk.hosts.clear();
-  InsertSubChunkHostPrefixUrl(&chunk, 201, 1999, "www.phishy2.com/",
-                              "www.phishy2.com/notevil1.html");
+  // Add phishing chunks.
   chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks);
+  chunks.push_back(AddChunkPrefixValue(47, "www.evil.com/phishing.html"));
+  chunks.push_back(
+      SubChunkPrefixValue(200, "www.phishy.com/notevil1.html", 1999));
+  chunks.push_back(
+      SubChunkPrefixValue(201, "www.phishy2.com/notevil1.html", 1999));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks.get());
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].adds, "1-3");
-  EXPECT_EQ(lists[0].subs, "7");
-  EXPECT_TRUE(lists[1].name == safe_browsing_util::kPhishingList);
-  EXPECT_EQ(lists[1].adds, "47");
-  EXPECT_EQ(lists[1].subs, "200-201");
+  ASSERT_EQ(2U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1-3", lists[0].adds);
+  EXPECT_EQ("7", lists[0].subs);
+  EXPECT_EQ(safe_browsing_util::kPhishingList, lists[1].name);
+  EXPECT_EQ("47", lists[1].adds);
+  EXPECT_EQ("200-201", lists[1].subs);
 }
 
 TEST_F(SafeBrowsingDatabaseTest, ListNameForBrowseAndDownload) {
@@ -403,86 +388,66 @@ TEST_F(SafeBrowsingDatabaseTest, ListNameForBrowseAndDownload) {
                                               ip_blacklist_store));
   database_->Init(database_filename_);
 
-  SBChunkList chunks;
-  SBChunk chunk;
+  ScopedVector<SBChunkData> chunks;
 
-  // Insert malware, phish, binurl and bindownload add chunks.
-  InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/",
-                              "www.evil.com/malware.html");
-  chunks.push_back(chunk);
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
 
-  chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&chunk, 2, "www.foo.com/",
-                              "www.foo.com/malware.html");
-  chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks);
+  // Insert malware, phish, binurl and bindownload add chunks.
+  chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html"));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
 
-  chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&chunk, 3, "www.whatever.com/",
-                              "www.whatever.com/download.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kBinUrlList, chunks);
+  chunks.push_back(AddChunkPrefixValue(2, "www.foo.com/malware.html"));
+  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks.get());
 
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(&chunk, 5, "www.forwhitelist.com/",
-                               "www.forwhitelist.com/a.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kCsdWhiteList, chunks);
-
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(&chunk, 6, "www.download.com/",
-                               "www.download.com/");
+  chunks.push_back(AddChunkPrefixValue(3, "www.whatever.com/download.html"));
+  database_->InsertChunks(safe_browsing_util::kBinUrlList, chunks.get());
 
   chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kDownloadWhiteList, chunks);
-
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(&chunk, 8,
-                               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
-                               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+  chunks.push_back(AddChunkFullHashValue(5, "www.forwhitelist.com/a.html"));
+  database_->InsertChunks(safe_browsing_util::kCsdWhiteList, chunks.get());
 
   chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kExtensionBlacklist, chunks);
+  chunks.push_back(AddChunkFullHashValue(6, "www.download.com/"));
+  database_->InsertChunks(safe_browsing_util::kDownloadWhiteList, chunks.get());
 
-  chunk.hosts.clear();
-  InsertAddChunkFullHash(&chunk, 9, "::ffff:192.168.1.0", 120);
+  chunks.clear();
+  chunks.push_back(AddChunkFullHashValue(8,
+                                         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+                                         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
+  database_->InsertChunks(safe_browsing_util::kExtensionBlacklist,
+                          chunks.get());
 
   chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks);
+  chunks.push_back(AddChunkHashedIpValue(9, "::ffff:192.168.1.0", 120));
+  database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks.get());
 
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
   ASSERT_EQ(7U, lists.size());
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].adds, "1");
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1", lists[0].adds);
   EXPECT_TRUE(lists[0].subs.empty());
-  EXPECT_TRUE(lists[1].name == safe_browsing_util::kPhishingList);
-  EXPECT_EQ(lists[1].adds, "2");
+  EXPECT_EQ(safe_browsing_util::kPhishingList, lists[1].name);
+  EXPECT_EQ("2", lists[1].adds);
   EXPECT_TRUE(lists[1].subs.empty());
-  EXPECT_TRUE(lists[2].name == safe_browsing_util::kBinUrlList);
-  EXPECT_EQ(lists[2].adds, "3");
+  EXPECT_EQ(safe_browsing_util::kBinUrlList, lists[2].name);
+  EXPECT_EQ("3", lists[2].adds);
   EXPECT_TRUE(lists[2].subs.empty());
-  EXPECT_TRUE(lists[3].name == safe_browsing_util::kCsdWhiteList);
-  EXPECT_EQ(lists[3].adds, "5");
+  EXPECT_EQ(safe_browsing_util::kCsdWhiteList, lists[3].name);
+  EXPECT_EQ("5", lists[3].adds);
   EXPECT_TRUE(lists[3].subs.empty());
-  EXPECT_TRUE(lists[4].name == safe_browsing_util::kDownloadWhiteList);
-  EXPECT_EQ(lists[4].adds, "6");
+  EXPECT_EQ(safe_browsing_util::kDownloadWhiteList, lists[4].name);
+  EXPECT_EQ("6", lists[4].adds);
   EXPECT_TRUE(lists[4].subs.empty());
-  EXPECT_TRUE(lists[5].name == safe_browsing_util::kExtensionBlacklist);
-  EXPECT_EQ(lists[5].adds, "8");
+  EXPECT_EQ(safe_browsing_util::kExtensionBlacklist, lists[5].name);
+  EXPECT_EQ("8", lists[5].adds);
   EXPECT_TRUE(lists[5].subs.empty());
-  EXPECT_TRUE(lists[6].name == safe_browsing_util::kIPBlacklist);
-  EXPECT_EQ(lists[6].adds, "9");
+  EXPECT_EQ(safe_browsing_util::kIPBlacklist, lists[6].name);
+  EXPECT_EQ("9", lists[6].adds);
   EXPECT_TRUE(lists[6].subs.empty());
 
   database_.reset();
@@ -490,207 +455,161 @@ TEST_F(SafeBrowsingDatabaseTest, ListNameForBrowseAndDownload) {
 
 // Checks database reading and writing for browse.
 TEST_F(SafeBrowsingDatabaseTest, BrowseDatabase) {
-  SBChunkList chunks;
-  SBChunk chunk;
-
-  // Add a simple chunk with one hostkey.
-  InsertAddChunkHost2PrefixUrls(&chunk, 1, "www.evil.com/",
-                                "www.evil.com/phishing.html",
-                                "www.evil.com/malware.html");
-  chunks.push_back(chunk);
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
-
-  chunk.hosts.clear();
-  InsertAddChunkHost2PrefixUrls(&chunk, 2, "www.evil.com/",
-                                "www.evil.com/notevil1.html",
-                                "www.evil.com/notevil2.html");
-  InsertAddChunkHost2PrefixUrls(&chunk, 2, "www.good.com/",
-                                "www.good.com/good1.html",
-                                "www.good.com/good2.html");
-  chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
-
-  // and a chunk with an IP-based host
-  chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&chunk, 3, "192.168.0.1/",
-                              "192.168.0.1/malware.html");
-  chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ScopedVector<SBChunkData> chunks;
+
+  chunks.push_back(AddChunkPrefix2Value(1,
+                                        "www.evil.com/phishing.html",
+                                        "www.evil.com/malware.html"));
+  chunks.push_back(AddChunkPrefix4Value(2,
+                                        "www.evil.com/notevil1.html",
+                                        "www.evil.com/notevil2.html",
+                                        "www.good.com/good1.html",
+                                        "www.good.com/good2.html"));
+  chunks.push_back(AddChunkPrefixValue(3, "192.168.0.1/malware.html"));
+  chunks.push_back(AddChunkFullHashValue(7, "www.evil.com/evil.html"));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   // Make sure they were added correctly.
   GetListsInfo(&lists);
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].adds, "1-3");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1-3,7", lists[0].adds);
   EXPECT_TRUE(lists[0].subs.empty());
 
-  const Time now = Time::Now();
-  std::vector<SBFullHashResult> full_hashes;
   std::vector<SBPrefix> prefix_hits;
-  std::string matching_list;
+  std::vector<SBFullHashResult> cache_hits;
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString("www.evil.com/phishing.html"));
-  EXPECT_EQ(prefix_hits.size(), 1U);
+      GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.evil.com/phishing.html"), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits));
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/notevil1.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.evil.com/notevil1.html"), &prefix_hits, &cache_hits));
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/notevil2.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.evil.com/notevil2.html"), &prefix_hits, &cache_hits));
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.good.com/good1.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.good.com/good1.html"), &prefix_hits, &cache_hits));
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.good.com/good2.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.good.com/good2.html"), &prefix_hits, &cache_hits));
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://192.168.0.1/malware.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://192.168.0.1/malware.html"), &prefix_hits, &cache_hits));
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.evil.com/"), &prefix_hits, &cache_hits));
   EXPECT_TRUE(prefix_hits.empty());
+  EXPECT_TRUE(cache_hits.empty());
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/robots.txt"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.evil.com/robots.txt"), &prefix_hits, &cache_hits));
+
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/evil.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.evil.com/evil.html"), prefix_hits[0]);
 
   // Attempt to re-add the first chunk (should be a no-op).
   // see bug: http://code.google.com/p/chromium/issues/detail?id=4522
-  chunk.hosts.clear();
-  InsertAddChunkHost2PrefixUrls(&chunk, 1, "www.evil.com/",
-                                "www.evil.com/phishing.html",
-                                "www.evil.com/malware.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(AddChunkPrefix2Value(1,
+                                        "www.evil.com/phishing.html",
+                                        "www.evil.com/malware.html"));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].adds, "1-3");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1-3,7", lists[0].adds);
   EXPECT_TRUE(lists[0].subs.empty());
 
   // Test removing a single prefix from the add chunk.
-  chunk.hosts.clear();
-  InsertSubChunkHostPrefixUrl(&chunk, 4, 2, "www.evil.com/",
-                              "www.evil.com/notevil1.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
-
+  chunks.push_back(SubChunkPrefixValue(4, "www.evil.com/notevil1.html", 2));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString("www.evil.com/phishing.html"));
-  EXPECT_EQ(prefix_hits.size(), 1U);
+      GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.evil.com/phishing.html"), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/notevil1.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.evil.com/notevil1.html"), &prefix_hits, &cache_hits));
   EXPECT_TRUE(prefix_hits.empty());
+  EXPECT_TRUE(cache_hits.empty());
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/notevil2.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.evil.com/notevil2.html"), &prefix_hits, &cache_hits));
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.good.com/good1.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.good.com/good1.html"), &prefix_hits, &cache_hits));
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.good.com/good2.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.good.com/good2.html"), &prefix_hits, &cache_hits));
 
   GetListsInfo(&lists);
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].subs, "4");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1-3,7", lists[0].adds);
+  EXPECT_EQ("4", lists[0].subs);
 
   // Test the same sub chunk again.  This should be a no-op.
   // see bug: http://code.google.com/p/chromium/issues/detail?id=4522
-  chunk.hosts.clear();
-  InsertSubChunkHostPrefixUrl(&chunk, 4, 2, "www.evil.com/",
-                              "www.evil.com/notevil1.html");
   chunks.clear();
-  chunks.push_back(chunk);
+  chunks.push_back(SubChunkPrefixValue(4, "www.evil.com/notevil1.html", 2));
 
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].subs, "4");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1-3,7", lists[0].adds);
+  EXPECT_EQ("4", lists[0].subs);
 
   // Test removing all the prefixes from an add chunk.
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
   AddDelChunk(safe_browsing_util::kMalwareList, 2);
   database_->UpdateFinished(true);
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/notevil2.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.evil.com/notevil2.html"), &prefix_hits, &cache_hits));
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.good.com/good1.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.good.com/good1.html"), &prefix_hits, &cache_hits));
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.good.com/good2.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.good.com/good2.html"), &prefix_hits, &cache_hits));
 
   GetListsInfo(&lists);
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].adds, "1,3");
-  EXPECT_EQ(lists[0].subs, "4");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1,3,7", lists[0].adds);
+  EXPECT_EQ("4", lists[0].subs);
 
   // The adddel command exposed a bug in the transaction code where any
   // transaction after it would fail.  Add a dummy entry and remove it to
   // make sure the transcation works fine.
-  chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&chunk, 44, "www.redherring.com/",
-                              "www.redherring.com/index.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(AddChunkPrefixValue(44, "www.redherring.com/index.html"));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
 
   // Now remove the dummy entry.  If there are any problems with the
   // transactions, asserts will fire.
@@ -701,407 +620,423 @@ TEST_F(SafeBrowsingDatabaseTest, BrowseDatabase) {
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_TRUE(lists[0].name == safe_browsing_util::kMalwareList);
-  EXPECT_EQ(lists[0].adds, "1,3");
-  EXPECT_EQ(lists[0].subs, "");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1,3,7", lists[0].adds);
+  EXPECT_TRUE(lists[0].subs.empty());
 
   // Test a sub command coming in before the add.
-  chunk.hosts.clear();
-  InsertSubChunkHost2PrefixUrls(&chunk, 5, 10,
-                                "www.notevilanymore.com/",
-                                "www.notevilanymore.com/index.html",
-                                "www.notevilanymore.com/good.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(SubChunkPrefix2Value(5,
+                                        "www.notevilanymore.com/index.html",
+                                        10,
+                                        "www.notevilanymore.com/good.html",
+                                        10));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
       GURL("http://www.notevilanymore.com/index.html"),
-      &matching_list, &prefix_hits, &full_hashes, now));
+      &prefix_hits,
+      &cache_hits));
 
   // Now insert the tardy add chunk and we don't expect them to appear
   // in database because of the previous sub chunk.
-  chunk.hosts.clear();
-  InsertAddChunkHost2PrefixUrls(&chunk, 10, "www.notevilanymore.com/",
-                                "www.notevilanymore.com/index.html",
-                                "www.notevilanymore.com/good.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(AddChunkPrefix2Value(10,
+                                        "www.notevilanymore.com/index.html",
+                                        "www.notevilanymore.com/good.html"));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
       GURL("http://www.notevilanymore.com/index.html"),
-      &matching_list, &prefix_hits, &full_hashes, now));
+      &prefix_hits,
+      &cache_hits));
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
       GURL("http://www.notevilanymore.com/good.html"),
-      &matching_list, &prefix_hits, &full_hashes, now));
-}
+      &prefix_hits,
+      &cache_hits));
 
+  // Reset and reload the database.  The database will rely on the prefix set.
+  database_.reset(new SafeBrowsingDatabaseNew);
+  database_->Init(database_filename_);
 
-// Test adding zero length chunks to the database.
-TEST_F(SafeBrowsingDatabaseTest, ZeroSizeChunk) {
-  SBChunkList chunks;
-  SBChunk chunk;
+  // Check that a prefix still hits.
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.evil.com/phishing.html"), prefix_hits[0]);
 
-  // Populate with a couple of normal chunks.
-  InsertAddChunkHost2PrefixUrls(&chunk, 1, "www.test.com/",
-                                "www.test.com/test1.html",
-                                "www.test.com/test2.html");
-  chunks.clear();
-  chunks.push_back(chunk);
+  // Also check that it's not just always returning true in this case.
+  EXPECT_FALSE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/"), &prefix_hits, &cache_hits));
 
-  chunk.hosts.clear();
-  InsertAddChunkHost2PrefixUrls(&chunk, 10, "www.random.com/",
-                                "www.random.com/random1.html",
-                                "www.random.com/random2.html");
-  chunks.push_back(chunk);
+  // Check that the full hash is still present.
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/evil.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.evil.com/evil.html"), prefix_hits[0]);
+}
 
+// Test adding zero length chunks to the database.
+TEST_F(SafeBrowsingDatabaseTest, ZeroSizeChunk) {
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ScopedVector<SBChunkData> chunks;
+
+  // Populate with a couple of normal chunks.
+  chunks.push_back(AddChunkPrefix2Value(1,
+                                        "www.test.com/test1.html",
+                                        "www.test.com/test2.html"));
+  chunks.push_back(AddChunkPrefix2Value(10,
+                                        "www.random.com/random1.html",
+                                        "www.random.com/random2.html"));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   // Add an empty ADD and SUB chunk.
   GetListsInfo(&lists);
-  EXPECT_EQ(lists[0].adds, "1,10");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1,10", lists[0].adds);
+  EXPECT_TRUE(lists[0].subs.empty());
 
-  SBChunk empty_chunk;
-  empty_chunk.chunk_number = 19;
-  empty_chunk.is_add = true;
-  chunks.clear();
-  chunks.push_back(empty_chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
   chunks.clear();
-  empty_chunk.chunk_number = 7;
-  empty_chunk.is_add = false;
-  chunks.push_back(empty_chunk);
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(BuildChunk(19, safe_browsing::ChunkData::ADD,
+                              safe_browsing::ChunkData::PREFIX_4B,
+                              NULL, 0, std::vector<int>()));
+  chunks.push_back(BuildChunk(7, safe_browsing::ChunkData::SUB,
+                              safe_browsing::ChunkData::PREFIX_4B,
+                              NULL, 0, std::vector<int>()));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_EQ(lists[0].adds, "1,10,19");
-  EXPECT_EQ(lists[0].subs, "7");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1,10,19", lists[0].adds);
+  EXPECT_EQ("7", lists[0].subs);
 
   // Add an empty chunk along with a couple that contain data. This should
   // result in the chunk range being reduced in size.
-  empty_chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&empty_chunk, 20, "www.notempy.com/",
-                              "www.notempty.com/full1.html");
   chunks.clear();
-  chunks.push_back(empty_chunk);
-
-  empty_chunk.chunk_number = 21;
-  empty_chunk.is_add = true;
-  empty_chunk.hosts.clear();
-  chunks.push_back(empty_chunk);
-
-  empty_chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&empty_chunk, 22, "www.notempy.com/",
-                              "www.notempty.com/full2.html");
-  chunks.push_back(empty_chunk);
-
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(AddChunkPrefixValue(20, "www.notempty.com/full1.html"));
+  chunks.push_back(BuildChunk(21, safe_browsing::ChunkData::ADD,
+                              safe_browsing::ChunkData::PREFIX_4B,
+                              NULL, 0, std::vector<int>()));
+  chunks.push_back(AddChunkPrefixValue(22, "www.notempty.com/full2.html"));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
-  const Time now = Time::Now();
-  std::vector<SBFullHashResult> full_hashes;
   std::vector<SBPrefix> prefix_hits;
-  std::string matching_list;
+  std::vector<SBFullHashResult> cache_hits;
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.notempty.com/full1.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.notempty.com/full1.html"), &prefix_hits, &cache_hits));
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.notempty.com/full2.html"),
-      &matching_list, &prefix_hits,
-      &full_hashes, now));
+      GURL("http://www.notempty.com/full2.html"), &prefix_hits, &cache_hits));
 
   GetListsInfo(&lists);
-  EXPECT_EQ(lists[0].adds, "1,10,19-22");
-  EXPECT_EQ(lists[0].subs, "7");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1,10,19-22", lists[0].adds);
+  EXPECT_EQ("7", lists[0].subs);
 
   // Handle AddDel and SubDel commands for empty chunks.
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
   AddDelChunk(safe_browsing_util::kMalwareList, 21);
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_EQ(lists[0].adds, "1,10,19-20,22");
-  EXPECT_EQ(lists[0].subs, "7");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1,10,19-20,22", lists[0].adds);
+  EXPECT_EQ("7", lists[0].subs);
 
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
   SubDelChunk(safe_browsing_util::kMalwareList, 7);
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_EQ(lists[0].adds, "1,10,19-20,22");
-  EXPECT_EQ(lists[0].subs, "");
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
+  EXPECT_EQ("1,10,19-20,22", lists[0].adds);
+  EXPECT_TRUE(lists[0].subs.empty());
 }
 
 // Utility function for setting up the database for the caching test.
 void SafeBrowsingDatabaseTest::PopulateDatabaseForCacheTest() {
-  SBChunkList chunks;
-  SBChunk chunk;
-  // Add a simple chunk with one hostkey and cache it.
-  InsertAddChunkHost2PrefixUrls(&chunk, 1, "www.evil.com/",
-                                "www.evil.com/phishing.html",
-                                "www.evil.com/malware.html");
-  chunks.push_back(chunk);
+  // Add a couple prefixes.
+  ScopedVector<SBChunkData> chunks;
+  chunks.push_back(AddChunkPrefix2Value(1,
+                                        "www.evil.com/phishing.html",
+                                        "www.evil.com/malware.html"));
 
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
-  // Add the GetHash results to the cache.
+  // Cache should be cleared after updating.
+  EXPECT_TRUE(database_->browse_gethash_cache_.empty());
+
   SBFullHashResult full_hash;
-  full_hash.hash = SBFullHashForString("www.evil.com/phishing.html");
-  full_hash.list_name = safe_browsing_util::kMalwareList;
-  full_hash.add_chunk_id = 1;
+  full_hash.list_id = safe_browsing_util::MALWARE;
 
   std::vector<SBFullHashResult> results;
+  std::vector<SBPrefix> prefixes;
+
+  // Add a fullhash result for each prefix.
+  full_hash.hash = SBFullHashForString("www.evil.com/phishing.html");
   results.push_back(full_hash);
+  prefixes.push_back(full_hash.hash.prefix);
 
   full_hash.hash = SBFullHashForString("www.evil.com/malware.html");
   results.push_back(full_hash);
+  prefixes.push_back(full_hash.hash.prefix);
 
-  std::vector<SBPrefix> prefixes;
-  database_->CacheHashResults(prefixes, results);
+  database_->CacheHashResults(prefixes, results, kCacheLifetime);
 }
 
 TEST_F(SafeBrowsingDatabaseTest, HashCaching) {
   PopulateDatabaseForCacheTest();
 
   // We should have both full hashes in the cache.
-  EXPECT_EQ(database_->pending_browse_hashes_.size(), 2U);
+  EXPECT_EQ(2U, database_->browse_gethash_cache_.size());
 
   // Test the cache lookup for the first prefix.
-  std::string listname;
-  std::vector<SBPrefix> prefixes;
-  std::vector<SBFullHashResult> full_hashes;
-  database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing.html"),
-      &listname, &prefixes, &full_hashes, Time::Now());
-  EXPECT_EQ(full_hashes.size(), 1U);
-  EXPECT_TRUE(
-      SBFullHashEqual(full_hashes[0].hash,
-                      SBFullHashForString("www.evil.com/phishing.html")));
+  std::vector<SBPrefix> prefix_hits;
+  std::vector<SBFullHashResult> cache_hits;
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits));
+  EXPECT_TRUE(prefix_hits.empty());
+  ASSERT_EQ(1U, cache_hits.size());
+  EXPECT_TRUE(SBFullHashEqual(
+      cache_hits[0].hash, SBFullHashForString("www.evil.com/phishing.html")));
 
-  prefixes.clear();
-  full_hashes.clear();
+  prefix_hits.clear();
+  cache_hits.clear();
 
   // Test the cache lookup for the second prefix.
-  database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware.html"),
-      &listname, &prefixes, &full_hashes, Time::Now());
-  EXPECT_EQ(full_hashes.size(), 1U);
-  EXPECT_TRUE(
-      SBFullHashEqual(full_hashes[0].hash,
-                      SBFullHashForString("www.evil.com/malware.html")));
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits));
+  EXPECT_TRUE(prefix_hits.empty());
+  ASSERT_EQ(1U, cache_hits.size());
+  EXPECT_TRUE(SBFullHashEqual(
+      cache_hits[0].hash, SBFullHashForString("www.evil.com/malware.html")));
 
-  prefixes.clear();
-  full_hashes.clear();
+  prefix_hits.clear();
+  cache_hits.clear();
 
   // Test removing a prefix via a sub chunk.
-  SBChunk chunk;
-  SBChunkList chunks;
-  InsertSubChunkHostPrefixUrl(&chunk, 2, 1, "www.evil.com/",
-                              "www.evil.com/phishing.html");
-  chunks.push_back(chunk);
+  ScopedVector<SBChunkData> chunks;
+  chunks.push_back(SubChunkPrefixValue(2, "www.evil.com/phishing.html", 1));
 
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
-  // This prefix should still be there.
-  database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware.html"),
-      &listname, &prefixes, &full_hashes, Time::Now());
-  EXPECT_EQ(full_hashes.size(), 1U);
-  EXPECT_TRUE(
-      SBFullHashEqual(full_hashes[0].hash,
-                      SBFullHashForString("www.evil.com/malware.html")));
-  prefixes.clear();
-  full_hashes.clear();
+  // This prefix should still be there, but cached fullhash should be gone.
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.evil.com/malware.html"), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
+  prefix_hits.clear();
+  cache_hits.clear();
 
   // This prefix should be gone.
-  database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing.html"),
-      &listname, &prefixes, &full_hashes, Time::Now());
-  EXPECT_TRUE(full_hashes.empty());
-
-  prefixes.clear();
-  full_hashes.clear();
+  EXPECT_FALSE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits));
+  prefix_hits.clear();
+  cache_hits.clear();
 
   // Test that an AddDel for the original chunk removes the last cached entry.
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
   AddDelChunk(safe_browsing_util::kMalwareList, 1);
   database_->UpdateFinished(true);
-  database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware.html"),
-      &listname, &prefixes, &full_hashes, Time::Now());
-  EXPECT_TRUE(full_hashes.empty());
-  EXPECT_TRUE(database_->full_browse_hashes_.empty());
-  EXPECT_TRUE(database_->pending_browse_hashes_.empty());
-
-  prefixes.clear();
-  full_hashes.clear();
+  EXPECT_FALSE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits));
+  EXPECT_TRUE(database_->browse_gethash_cache_.empty());
+  prefix_hits.clear();
+  cache_hits.clear();
 
   // Test that the cache won't return expired values. First we have to adjust
   // the cached entries' received time to make them older, since the database
   // cache insert uses Time::Now(). First, store some entries.
   PopulateDatabaseForCacheTest();
 
-  std::vector<SBAddFullHash>* hash_cache = &database_->pending_browse_hashes_;
-  EXPECT_EQ(hash_cache->size(), 2U);
+  std::map<SBPrefix, SBCachedFullHashResult>* hash_cache =
+      &database_->browse_gethash_cache_;
+  EXPECT_EQ(2U, hash_cache->size());
 
   // Now adjust one of the entries times to be in the past.
-  base::Time expired = base::Time::Now() - base::TimeDelta::FromMinutes(60);
   const SBPrefix key = SBPrefixForString("www.evil.com/malware.html");
-  std::vector<SBAddFullHash>::iterator iter;
-  for (iter = hash_cache->begin(); iter != hash_cache->end(); ++iter) {
-    if (iter->full_hash.prefix == key) {
-      iter->received = static_cast<int32>(expired.ToTimeT());
-      break;
-    }
-  }
-  EXPECT_TRUE(iter != hash_cache->end());
+  std::map<SBPrefix, SBCachedFullHashResult>::iterator iter =
+      hash_cache->find(key);
+  ASSERT_TRUE(iter != hash_cache->end());
+  iter->second.expire_after = Time::Now() - TimeDelta::FromMinutes(1);
 
-  database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware.html"),
-      &listname, &prefixes, &full_hashes, expired);
-  EXPECT_TRUE(full_hashes.empty());
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits));
+  EXPECT_EQ(1U, prefix_hits.size());
+  EXPECT_TRUE(cache_hits.empty());
+  // Expired entry should have been removed from cache.
+  EXPECT_EQ(1U, hash_cache->size());
 
   // This entry should still exist.
-  database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing.html"),
-      &listname, &prefixes, &full_hashes, expired);
-  EXPECT_EQ(full_hashes.size(), 1U);
-
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits));
+  EXPECT_TRUE(prefix_hits.empty());
+  EXPECT_EQ(1U, cache_hits.size());
 
   // Testing prefix miss caching. First, we clear out the existing database,
   // Since PopulateDatabaseForCacheTest() doesn't handle adding duplicate
   // chunks.
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
   AddDelChunk(safe_browsing_util::kMalwareList, 1);
   database_->UpdateFinished(true);
 
+  // Cache should be cleared after updating.
+  EXPECT_TRUE(hash_cache->empty());
+
   std::vector<SBPrefix> prefix_misses;
   std::vector<SBFullHashResult> empty_full_hash;
   prefix_misses.push_back(SBPrefixForString("http://www.bad.com/malware.html"));
   prefix_misses.push_back(
       SBPrefixForString("http://www.bad.com/phishing.html"));
-  database_->CacheHashResults(prefix_misses, empty_full_hash);
+  database_->CacheHashResults(prefix_misses, empty_full_hash, kCacheLifetime);
 
   // Prefixes with no full results are misses.
-  EXPECT_EQ(database_->prefix_miss_cache_.size(), 2U);
+  EXPECT_EQ(hash_cache->size(), prefix_misses.size());
+  ASSERT_TRUE(
+      hash_cache->count(SBPrefixForString("http://www.bad.com/malware.html")));
+  EXPECT_TRUE(
+      hash_cache->find(SBPrefixForString("http://www.bad.com/malware.html"))
+          ->second.full_hashes.empty());
+  ASSERT_TRUE(
+      hash_cache->count(SBPrefixForString("http://www.bad.com/phishing.html")));
+  EXPECT_TRUE(
+      hash_cache->find(SBPrefixForString("http://www.bad.com/phishing.html"))
+          ->second.full_hashes.empty());
 
   // Update the database.
   PopulateDatabaseForCacheTest();
 
-  // Prefix miss cache should be cleared.
-  EXPECT_TRUE(database_->prefix_miss_cache_.empty());
-
   // Cache a GetHash miss for a particular prefix, and even though the prefix is
   // in the database, it is flagged as a miss so looking up the associated URL
   // will not succeed.
-  prefixes.clear();
-  full_hashes.clear();
+  prefix_hits.clear();
+  cache_hits.clear();
   prefix_misses.clear();
   empty_full_hash.clear();
   prefix_misses.push_back(SBPrefixForString("www.evil.com/phishing.html"));
-  database_->CacheHashResults(prefix_misses, empty_full_hash);
+  database_->CacheHashResults(prefix_misses, empty_full_hash, kCacheLifetime);
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing.html"),
-      &listname, &prefixes,
-      &full_hashes, Time::Now()));
-
-  prefixes.clear();
-  full_hashes.clear();
+      GURL("http://www.evil.com/phishing.html"), &prefix_hits, &cache_hits));
+  prefix_hits.clear();
+  cache_hits.clear();
 
   // Test receiving a full add chunk.
-  chunk.hosts.clear();
-  InsertAddChunkHost2FullHashes(&chunk, 20, "www.fullevil.com/",
-                                "www.fullevil.com/bad1.html",
-                                "www.fullevil.com/bad2.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(AddChunkFullHash2Value(20,
+                                          "www.fullevil.com/bad1.html",
+                                          "www.fullevil.com/bad2.html"));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.fullevil.com/bad1.html"),
-      &listname, &prefixes, &full_hashes,
-      Time::Now()));
-  EXPECT_EQ(full_hashes.size(), 1U);
-  EXPECT_TRUE(
-      SBFullHashEqual(full_hashes[0].hash,
-                      SBFullHashForString("www.fullevil.com/bad1.html")));
-  prefixes.clear();
-  full_hashes.clear();
+      GURL("http://www.fullevil.com/bad1.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.fullevil.com/bad1.html"), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
+  prefix_hits.clear();
+  cache_hits.clear();
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.fullevil.com/bad2.html"),
-      &listname, &prefixes, &full_hashes,
-      Time::Now()));
-  EXPECT_EQ(full_hashes.size(), 1U);
-  EXPECT_TRUE(
-      SBFullHashEqual(full_hashes[0].hash,
-                      SBFullHashForString("www.fullevil.com/bad2.html")));
-  prefixes.clear();
-  full_hashes.clear();
+      GURL("http://www.fullevil.com/bad2.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.fullevil.com/bad2.html"), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
+  prefix_hits.clear();
+  cache_hits.clear();
 
   // Test receiving a full sub chunk, which will remove one of the full adds.
-  chunk.hosts.clear();
-  InsertSubChunkHostFullHash(&chunk, 200, 20,
-                             "www.fullevil.com/",
-                             "www.fullevil.com/bad1.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(SubChunkFullHashValue(200,
+                                         "www.fullevil.com/bad1.html",
+                                         20));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.fullevil.com/bad1.html"),
-      &listname, &prefixes, &full_hashes,
-      Time::Now()));
-  EXPECT_TRUE(full_hashes.empty());
+      GURL("http://www.fullevil.com/bad1.html"), &prefix_hits, &cache_hits));
 
   // There should be one remaining full add.
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.fullevil.com/bad2.html"),
-      &listname, &prefixes, &full_hashes,
-      Time::Now()));
-  EXPECT_EQ(full_hashes.size(), 1U);
-  EXPECT_TRUE(
-      SBFullHashEqual(full_hashes[0].hash,
-                      SBFullHashForString("www.fullevil.com/bad2.html")));
-  prefixes.clear();
-  full_hashes.clear();
+      GURL("http://www.fullevil.com/bad2.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.fullevil.com/bad2.html"), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
+  prefix_hits.clear();
+  cache_hits.clear();
 
   // Now test an AddDel for the remaining full add.
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
   AddDelChunk(safe_browsing_util::kMalwareList, 20);
   database_->UpdateFinished(true);
 
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.fullevil.com/bad1.html"),
-      &listname, &prefixes, &full_hashes,
-      Time::Now()));
+      GURL("http://www.fullevil.com/bad1.html"), &prefix_hits, &cache_hits));
+  EXPECT_FALSE(database_->ContainsBrowseUrl(
+      GURL("http://www.fullevil.com/bad2.html"), &prefix_hits, &cache_hits));
+
+  // Add a fullhash which has a prefix collision for a known url.
+  static const char kExampleFine[] = "www.example.com/fine.html";
+  static const char kExampleCollision[] =
+      "www.example.com/3123364814/malware.htm";
+  ASSERT_EQ(SBPrefixForString(kExampleFine),
+            SBPrefixForString(kExampleCollision));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  {
+    ScopedVector<SBChunkData> chunks;
+    chunks.push_back(AddChunkPrefixValue(21, kExampleCollision));
+    database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  }
+  database_->UpdateFinished(true);
+
+  // Expect a prefix hit due to the collision between |kExampleFine| and
+  // |kExampleCollision|.
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL(std::string("http://") + kExampleFine), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kExampleFine), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
+
+  // Cache gethash response for |kExampleCollision|.
+  {
+    SBFullHashResult result;
+    result.hash = SBFullHashForString(kExampleCollision);
+    result.list_id = safe_browsing_util::MALWARE;
+    database_->CacheHashResults(std::vector<SBPrefix>(1, result.hash.prefix),
+                                std::vector<SBFullHashResult>(1, result),
+                                kCacheLifetime);
+  }
+
+  // The cached response means the collision no longer causes a hit.
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.fullevil.com/bad2.html"),
-      &listname, &prefixes, &full_hashes,
-      Time::Now()));
+      GURL(std::string("http://") + kExampleFine), &prefix_hits, &cache_hits));
 }
 
 // Test that corrupt databases are appropriately handled, even if the
@@ -1121,24 +1056,14 @@ TEST_F(SafeBrowsingDatabaseTest, DISABLED_FileCorruptionHandling) {
 
   // This will cause an empty database to be created.
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
   database_->UpdateFinished(true);
 
   // Create a sub chunk to insert.
-  SBChunkList chunks;
-  SBChunk chunk;
-  SBChunkHost host;
-  host.host = SBPrefixForString("www.subbed.com/");
-  host.entry = SBEntry::Create(SBEntry::SUB_PREFIX, 1);
-  host.entry->set_chunk_id(7);
-  host.entry->SetChunkIdAtPrefix(0, 19);
-  host.entry->SetPrefixAt(0, SBPrefixForString("www.subbed.com/notevil1.html"));
-  chunk.chunk_number = 7;
-  chunk.is_add = false;
-  chunk.hosts.clear();
-  chunk.hosts.push_back(host);
-  chunks.clear();
-  chunks.push_back(chunk);
+  ScopedVector<SBChunkData> chunks;
+  chunks.push_back(SubChunkPrefixValue(7,
+                                       "www.subbed.com/notevil1.html",
+                                       19));
 
   // Corrupt the file by corrupting the checksum, which is not checked
   // until the entire table is read in |UpdateFinished()|.
@@ -1155,8 +1080,8 @@ TEST_F(SafeBrowsingDatabaseTest, DISABLED_FileCorruptionHandling) {
     ScopedLogMessageIgnorer ignorer;
 
     // Start an update.  The insert will fail due to corruption.
-    EXPECT_TRUE(database_->UpdateStarted(&lists));
-    database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+    ASSERT_TRUE(database_->UpdateStarted(&lists));
+    database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
     database_->UpdateFinished(true);
 
     // Database file still exists until the corruption handler has run.
@@ -1171,8 +1096,8 @@ TEST_F(SafeBrowsingDatabaseTest, DISABLED_FileCorruptionHandling) {
   EXPECT_FALSE(base::PathExists(database_filename_));
 
   // Run the update again successfully.
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
   EXPECT_TRUE(base::PathExists(database_filename_));
 
@@ -1195,19 +1120,16 @@ TEST_F(SafeBrowsingDatabaseTest, ContainsDownloadUrl) {
                                               NULL));
   database_->Init(database_filename_);
 
-  const char kEvil1Host[] = "www.evil1.com/";
   const char kEvil1Url1[] = "www.evil1.com/download1/";
   const char kEvil1Url2[] = "www.evil1.com/download2.html";
 
-  SBChunkList chunks;
-  SBChunk chunk;
   // Add a simple chunk with one hostkey for download url list.
-  InsertAddChunkHost2PrefixUrls(&chunk, 1, kEvil1Host,
-                                kEvil1Url1, kEvil1Url2);
-  chunks.push_back(chunk);
+  ScopedVector<SBChunkData> chunks;
+  chunks.push_back(AddChunkPrefix2Value(1, kEvil1Url1, kEvil1Url2));
+
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kBinUrlList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kBinUrlList, chunks.get());
   database_->UpdateFinished(true);
 
   std::vector<SBPrefix> prefix_hits;
@@ -1215,23 +1137,23 @@ TEST_F(SafeBrowsingDatabaseTest, ContainsDownloadUrl) {
 
   urls[0] = GURL(std::string("http://") + kEvil1Url1);
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 1U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url1));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]);
 
   urls[0] = GURL(std::string("http://") + kEvil1Url2);
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 1U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url2));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[0]);
 
   urls[0] = GURL(std::string("https://") + kEvil1Url2);
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 1U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url2));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[0]);
 
   urls[0] = GURL(std::string("ftp://") + kEvil1Url2);
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 1U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url2));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[0]);
 
   urls[0] = GURL("http://www.randomevil.com");
   EXPECT_FALSE(database_->ContainsDownloadUrl(urls, &prefix_hits));
@@ -1239,22 +1161,22 @@ TEST_F(SafeBrowsingDatabaseTest, ContainsDownloadUrl) {
   // Should match with query args stripped.
   urls[0] = GURL(std::string("http://") + kEvil1Url2 + "?blah");
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 1U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url2));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[0]);
 
   // Should match with extra path stuff and query args stripped.
   urls[0] = GURL(std::string("http://") + kEvil1Url1 + "foo/bar?blah");
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 1U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url1));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]);
 
   // First hit in redirect chain is malware.
   urls.clear();
   urls.push_back(GURL(std::string("http://") + kEvil1Url1));
   urls.push_back(GURL("http://www.randomevil.com"));
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 1U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url1));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]);
 
   // Middle hit in redirect chain is malware.
   urls.clear();
@@ -1262,31 +1184,32 @@ TEST_F(SafeBrowsingDatabaseTest, ContainsDownloadUrl) {
   urls.push_back(GURL(std::string("http://") + kEvil1Url1));
   urls.push_back(GURL("http://www.randomevil2.com"));
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 1U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url1));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]);
 
   // Final hit in redirect chain is malware.
   urls.clear();
   urls.push_back(GURL("http://www.randomevil.com"));
   urls.push_back(GURL(std::string("http://") + kEvil1Url1));
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 1U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url1));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]);
 
   // Multiple hits in redirect chain are in malware list.
   urls.clear();
   urls.push_back(GURL(std::string("http://") + kEvil1Url1));
   urls.push_back(GURL(std::string("https://") + kEvil1Url2));
   EXPECT_TRUE(database_->ContainsDownloadUrl(urls, &prefix_hits));
-  ASSERT_EQ(prefix_hits.size(), 2U);
-  EXPECT_EQ(prefix_hits[0], SBPrefixForString(kEvil1Url1));
-  EXPECT_EQ(prefix_hits[1], SBPrefixForString(kEvil1Url2));
+  ASSERT_EQ(2U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kEvil1Url1), prefix_hits[0]);
+  EXPECT_EQ(SBPrefixForString(kEvil1Url2), prefix_hits[1]);
   database_.reset();
 }
 
 // Checks that the whitelists are handled properly.
 TEST_F(SafeBrowsingDatabaseTest, Whitelists) {
   database_.reset();
+
   // We expect all calls to ContainsCsdWhitelistedUrl in particular to be made
   // from the IO thread.  In general the whitelist lookups are thread-safe.
   content::TestBrowserThreadBundle thread_bundle_;
@@ -1318,44 +1241,28 @@ TEST_F(SafeBrowsingDatabaseTest, Whitelists) {
   const char kGood1Url1[] = "www.good1.com/a/b.html";
   const char kGood1Url2[] = "www.good1.com/b/";
 
-  const char kGood2Host[] = "www.good2.com/";
   const char kGood2Url1[] = "www.good2.com/c";  // Should match '/c/bla'.
 
   // good3.com/a/b/c/d/e/f/g/ should match because it's a whitelist.
-  const char kGood3Host[] = "good3.com/";
   const char kGood3Url1[] = "good3.com/";
 
   const char kGoodString[] = "good_string";
 
-  SBChunkList download_chunks, csd_chunks;
-  SBChunk chunk;
-  // Add two simple chunks to the csd whitelist.
-  InsertAddChunkHost2FullHashes(&chunk, 1, kGood1Host,
-                                kGood1Url1, kGood1Url2);
-  csd_chunks.push_back(chunk);
-
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(&chunk, 2, kGood2Host, kGood2Url1);
-  csd_chunks.push_back(chunk);
-
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(&chunk, 2, kGood2Host, kGood2Url1);
-  download_chunks.push_back(chunk);
+  ScopedVector<SBChunkData> csd_chunks;
+  ScopedVector<SBChunkData> download_chunks;
 
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(&chunk, 3, kGoodString, kGoodString);
-  download_chunks.push_back(chunk);
-
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(&chunk, 4, kGood3Host, kGood3Url1);
-  download_chunks.push_back(chunk);
+  // Add two simple chunks to the csd whitelist.
+  csd_chunks.push_back(AddChunkFullHash2Value(1, kGood1Url1, kGood1Url2));
+  csd_chunks.push_back(AddChunkFullHashValue(2, kGood2Url1));
+  download_chunks.push_back(AddChunkFullHashValue(2, kGood2Url1));
+  download_chunks.push_back(AddChunkFullHashValue(3, kGoodString));
+  download_chunks.push_back(AddChunkFullHashValue(4, kGood3Url1));
 
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kCsdWhiteList,
-                          csd_chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks.get());
   database_->InsertChunks(safe_browsing_util::kDownloadWhiteList,
-                          download_chunks);
+                          download_chunks.get());
   database_->UpdateFinished(true);
 
   EXPECT_FALSE(database_->ContainsCsdWhitelistedUrl(
@@ -1402,38 +1309,39 @@ TEST_F(SafeBrowsingDatabaseTest, Whitelists) {
   EXPECT_FALSE(database_->ContainsDownloadWhitelistedUrl(
       GURL(std::string("http://www.google.com/"))));
 
+  // The CSD whitelist killswitch is not present.
+  EXPECT_FALSE(database_->IsCsdWhitelistKillSwitchOn());
+
   // Test only add the malware IP killswitch
   csd_chunks.clear();
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(
-      &chunk, 15, "sb-ssl.google.com/",
-      "sb-ssl.google.com/safebrowsing/csd/killswitch_malware");
-  csd_chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks);
+  csd_chunks.push_back(AddChunkFullHashValue(
+      15, "sb-ssl.google.com/safebrowsing/csd/killswitch_malware"));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks.get());
   database_->UpdateFinished(true);
 
   EXPECT_TRUE(database_->IsMalwareIPMatchKillSwitchOn());
+  // The CSD whitelist killswitch is not present.
+  EXPECT_FALSE(database_->IsCsdWhitelistKillSwitchOn());
 
   // Test that the kill-switch works as intended.
   csd_chunks.clear();
   download_chunks.clear();
   lists.clear();
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(&chunk, 5, "sb-ssl.google.com/",
-                               "sb-ssl.google.com/safebrowsing/csd/killswitch");
-  csd_chunks.push_back(chunk);
-  chunk.hosts.clear();
-  InsertAddChunkHostFullHashes(&chunk, 5, "sb-ssl.google.com/",
-                               "sb-ssl.google.com/safebrowsing/csd/killswitch");
-  download_chunks.push_back(chunk);
-
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks);
+  csd_chunks.push_back(AddChunkFullHashValue(
+      5, "sb-ssl.google.com/safebrowsing/csd/killswitch"));
+  download_chunks.push_back(AddChunkFullHashValue(
+      5, "sb-ssl.google.com/safebrowsing/csd/killswitch"));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks.get());
   database_->InsertChunks(safe_browsing_util::kDownloadWhiteList,
-                          download_chunks);
+                          download_chunks.get());
   database_->UpdateFinished(true);
 
+  // The CSD whitelist killswitch is present.
+  EXPECT_TRUE(database_->IsCsdWhitelistKillSwitchOn());
   EXPECT_TRUE(database_->IsMalwareIPMatchKillSwitchOn());
   EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl(
       GURL(std::string("https://") + kGood1Url2 + "/c.html")));
@@ -1456,31 +1364,21 @@ TEST_F(SafeBrowsingDatabaseTest, Whitelists) {
   csd_chunks.clear();
   download_chunks.clear();
   lists.clear();
-  SBChunk sub_chunk;
-  InsertSubChunkHostFullHash(&sub_chunk, 1, 5,
-                             "sb-ssl.google.com/",
-                             "sb-ssl.google.com/safebrowsing/csd/killswitch");
-  csd_chunks.push_back(sub_chunk);
-
-  sub_chunk.hosts.clear();
-  InsertSubChunkHostFullHash(
-      &sub_chunk, 10, 15, "sb-ssl.google.com/",
-      "sb-ssl.google.com/safebrowsing/csd/killswitch_malware");
-  csd_chunks.push_back(sub_chunk);
-
-  sub_chunk.hosts.clear();
-  InsertSubChunkHostFullHash(&sub_chunk, 1, 5,
-                             "sb-ssl.google.com/",
-                             "sb-ssl.google.com/safebrowsing/csd/killswitch");
-  download_chunks.push_back(sub_chunk);
-
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks);
+  csd_chunks.push_back(SubChunkFullHashValue(
+      1, "sb-ssl.google.com/safebrowsing/csd/killswitch", 5));
+  csd_chunks.push_back(SubChunkFullHashValue(
+      10, "sb-ssl.google.com/safebrowsing/csd/killswitch_malware", 15));
+  download_chunks.push_back(SubChunkFullHashValue(
+      1, "sb-ssl.google.com/safebrowsing/csd/killswitch", 5));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kCsdWhiteList, csd_chunks.get());
   database_->InsertChunks(safe_browsing_util::kDownloadWhiteList,
-                          download_chunks);
+                          download_chunks.get());
   database_->UpdateFinished(true);
 
   EXPECT_FALSE(database_->IsMalwareIPMatchKillSwitchOn());
+  EXPECT_FALSE(database_->IsCsdWhitelistKillSwitchOn());
   EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl(
       GURL(std::string("https://") + kGood1Url2 + "/c.html")));
   EXPECT_TRUE(database_->ContainsCsdWhitelistedUrl(
@@ -1507,141 +1405,112 @@ TEST_F(SafeBrowsingDatabaseTest, Whitelists) {
 // Test to make sure we could insert chunk list that
 // contains entries for the same host.
 TEST_F(SafeBrowsingDatabaseTest, SameHostEntriesOkay) {
-  SBChunk chunk;
+  ScopedVector<SBChunkData> chunks;
 
   // Add a malware add chunk with two entries of the same host.
-  InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/",
-                              "www.evil.com/malware1.html");
-  InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/",
-                              "www.evil.com/malware2.html");
-  SBChunkList chunks;
-  chunks.push_back(chunk);
+  chunks.push_back(AddChunkPrefix2Value(1,
+                                        "www.evil.com/malware1.html",
+                                        "www.evil.com/malware2.html"));
 
   // Insert the testing chunks into database.
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_EQ(std::string(safe_browsing_util::kMalwareList), lists[0].name);
+  ASSERT_LE(1U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
   EXPECT_EQ("1", lists[0].adds);
   EXPECT_TRUE(lists[0].subs.empty());
 
   // Add a phishing add chunk with two entries of the same host.
-  chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&chunk, 47, "www.evil.com/",
-                              "www.evil.com/phishing1.html");
-  InsertAddChunkHostPrefixUrl(&chunk, 47, "www.evil.com/",
-                              "www.evil.com/phishing2.html");
   chunks.clear();
-  chunks.push_back(chunk);
+  chunks.push_back(AddChunkPrefix2Value(47,
+                                        "www.evil.com/phishing1.html",
+                                        "www.evil.com/phishing2.html"));
 
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks.get());
   database_->UpdateFinished(true);
 
   GetListsInfo(&lists);
-  EXPECT_EQ(std::string(safe_browsing_util::kMalwareList), lists[0].name);
+  ASSERT_EQ(2U, lists.size());
+  EXPECT_EQ(safe_browsing_util::kMalwareList, lists[0].name);
   EXPECT_EQ("1", lists[0].adds);
-  EXPECT_EQ(std::string(safe_browsing_util::kPhishingList), lists[1].name);
+  EXPECT_TRUE(lists[0].subs.empty());
+  EXPECT_EQ(safe_browsing_util::kPhishingList, lists[1].name);
   EXPECT_EQ("47", lists[1].adds);
+  EXPECT_TRUE(lists[1].subs.empty());
 
-  const Time now = Time::Now();
-  std::vector<SBPrefix> prefixes;
-  std::vector<SBFullHashResult> full_hashes;
   std::vector<SBPrefix> prefix_hits;
-  std::string matching_list;
-  std::string listname;
+  std::vector<SBFullHashResult> cache_hits;
 
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware1.html"),
-      &listname, &prefixes, &full_hashes, now));
+      GURL("http://www.evil.com/malware1.html"), &prefix_hits, &cache_hits));
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware2.html"),
-      &listname, &prefixes, &full_hashes, now));
+      GURL("http://www.evil.com/malware2.html"), &prefix_hits, &cache_hits));
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing1.html"),
-      &listname, &prefixes, &full_hashes, now));
+      GURL("http://www.evil.com/phishing1.html"), &prefix_hits, &cache_hits));
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing2.html"),
-      &listname, &prefixes, &full_hashes, now));
+      GURL("http://www.evil.com/phishing2.html"), &prefix_hits, &cache_hits));
 
   // Test removing a single prefix from the add chunk.
   // Remove the prefix that added first.
-  chunk.hosts.clear();
-  InsertSubChunkHostPrefixUrl(&chunk, 4, 1, "www.evil.com/",
-                              "www.evil.com/malware1.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  chunks.push_back(SubChunkPrefixValue(4, "www.evil.com/malware1.html", 1));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   // Remove the prefix that added last.
-  chunk.hosts.clear();
-  InsertSubChunkHostPrefixUrl(&chunk, 5, 47, "www.evil.com/",
-                              "www.evil.com/phishing2.html");
   chunks.clear();
-  chunks.push_back(chunk);
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks);
+  chunks.push_back(SubChunkPrefixValue(5, "www.evil.com/phishing2.html", 47));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kPhishingList, chunks.get());
   database_->UpdateFinished(true);
 
   // Verify that the database contains urls expected.
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware1.html"),
-      &listname, &prefixes, &full_hashes, now));
+      GURL("http://www.evil.com/malware1.html"), &prefix_hits, &cache_hits));
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware2.html"),
-      &listname, &prefixes, &full_hashes, now));
+      GURL("http://www.evil.com/malware2.html"), &prefix_hits, &cache_hits));
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing1.html"),
-      &listname, &prefixes, &full_hashes, now));
+      GURL("http://www.evil.com/phishing1.html"), &prefix_hits, &cache_hits));
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/phishing2.html"),
-      &listname, &prefixes, &full_hashes, now));
+      GURL("http://www.evil.com/phishing2.html"), &prefix_hits, &cache_hits));
 }
 
 // Test that an empty update doesn't actually update the database.
 // This isn't a functionality requirement, but it is a useful
 // optimization.
 TEST_F(SafeBrowsingDatabaseTest, EmptyUpdate) {
-  SBChunkList chunks;
-  SBChunk chunk;
+  ScopedVector<SBChunkData> chunks;
 
   base::FilePath filename = database_->BrowseDBFilename(database_filename_);
 
   // Prime the database.
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-
-  InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/",
-                              "www.evil.com/malware.html");
-  chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html"));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
 
   // Get an older time to reset the lastmod time for detecting whether
   // the file has been updated.
   base::File::Info before_info, after_info;
   ASSERT_TRUE(base::GetFileInfo(filename, &before_info));
-  const base::Time old_last_modified =
-      before_info.last_modified - base::TimeDelta::FromSeconds(10);
+  const Time old_last_modified =
+      before_info.last_modified - TimeDelta::FromSeconds(10);
 
   // Inserting another chunk updates the database file.  The sleep is
   // needed because otherwise the entire test can finish w/in the
   // resolution of the lastmod time.
   ASSERT_TRUE(base::TouchFile(filename, old_last_modified, old_last_modified));
   ASSERT_TRUE(base::GetFileInfo(filename, &before_info));
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  chunk.hosts.clear();
-  InsertAddChunkHostPrefixUrl(&chunk, 2, "www.foo.com/",
-                              "www.foo.com/malware.html");
-  chunks.clear();
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  chunks.push_back(AddChunkPrefixValue(2, "www.foo.com/malware.html"));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
   database_->UpdateFinished(true);
   ASSERT_TRUE(base::GetFileInfo(filename, &after_info));
   EXPECT_LT(before_info.last_modified, after_info.last_modified);
@@ -1649,8 +1518,8 @@ TEST_F(SafeBrowsingDatabaseTest, EmptyUpdate) {
   // Deleting a chunk updates the database file.
   ASSERT_TRUE(base::TouchFile(filename, old_last_modified, old_last_modified));
   ASSERT_TRUE(base::GetFileInfo(filename, &before_info));
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
-  AddDelChunk(safe_browsing_util::kMalwareList, chunk.chunk_number);
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  AddDelChunk(safe_browsing_util::kMalwareList, 2);
   database_->UpdateFinished(true);
   ASSERT_TRUE(base::GetFileInfo(filename, &after_info));
   EXPECT_LT(before_info.last_modified, after_info.last_modified);
@@ -1659,7 +1528,7 @@ TEST_F(SafeBrowsingDatabaseTest, EmptyUpdate) {
   // update the database file.
   ASSERT_TRUE(base::TouchFile(filename, old_last_modified, old_last_modified));
   ASSERT_TRUE(base::GetFileInfo(filename, &before_info));
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
   database_->UpdateFinished(true);
   ASSERT_TRUE(base::GetFileInfo(filename, &after_info));
   EXPECT_EQ(before_info.last_modified, after_info.last_modified);
@@ -1670,32 +1539,23 @@ TEST_F(SafeBrowsingDatabaseTest, EmptyUpdate) {
 TEST_F(SafeBrowsingDatabaseTest, FilterFile) {
   // Create a database with trivial example data and write it out.
   {
-    SBChunkList chunks;
-    SBChunk chunk;
-
     // Prime the database.
     std::vector<SBListChunkRanges> lists;
-    EXPECT_TRUE(database_->UpdateStarted(&lists));
+    ASSERT_TRUE(database_->UpdateStarted(&lists));
 
-    InsertAddChunkHostPrefixUrl(&chunk, 1, "www.evil.com/",
-                                "www.evil.com/malware.html");
-    chunks.clear();
-    chunks.push_back(chunk);
-    database_->InsertChunks(safe_browsing_util::kMalwareList, chunks);
+    ScopedVector<SBChunkData> chunks;
+    chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/malware.html"));
+    database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
     database_->UpdateFinished(true);
   }
 
   // Find the malware url in the database, don't find a good url.
-  const Time now = Time::Now();
-  std::vector<SBFullHashResult> full_hashes;
   std::vector<SBPrefix> prefix_hits;
-  std::string matching_list;
+  std::vector<SBFullHashResult> cache_hits;
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware.html"),
-      &matching_list, &prefix_hits, &full_hashes, now));
+      GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits));
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.good.com/goodware.html"),
-      &matching_list, &prefix_hits, &full_hashes, now));
+      GURL("http://www.good.com/goodware.html"), &prefix_hits, &cache_hits));
 
   base::FilePath filter_file = database_->PrefixSetForFilename(
       database_->BrowseDBFilename(database_filename_));
@@ -1706,11 +1566,9 @@ TEST_F(SafeBrowsingDatabaseTest, FilterFile) {
   database_.reset(new SafeBrowsingDatabaseNew);
   database_->Init(database_filename_);
   EXPECT_TRUE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware.html"),
-      &matching_list, &prefix_hits, &full_hashes, now));
+      GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits));
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.good.com/goodware.html"),
-      &matching_list, &prefix_hits, &full_hashes, now));
+      GURL("http://www.good.com/goodware.html"), &prefix_hits, &cache_hits));
 
   // If there is no filter file, the database cannot find malware urls.
   base::DeleteFile(filter_file, false);
@@ -1718,11 +1576,418 @@ TEST_F(SafeBrowsingDatabaseTest, FilterFile) {
   database_.reset(new SafeBrowsingDatabaseNew);
   database_->Init(database_filename_);
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.evil.com/malware.html"),
-      &matching_list, &prefix_hits, &full_hashes, now));
+      GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits));
   EXPECT_FALSE(database_->ContainsBrowseUrl(
-      GURL("http://www.good.com/goodware.html"),
-      &matching_list, &prefix_hits, &full_hashes, now));
+      GURL("http://www.good.com/goodware.html"), &prefix_hits, &cache_hits));
+}
+
+TEST_F(SafeBrowsingDatabaseTest, CachedFullMiss) {
+  const SBPrefix kPrefix1 = 1001U;
+  const SBFullHash kFullHash1_1 =
+      SBFullHashForPrefixAndSuffix(kPrefix1, "\x01");
+
+  const SBPrefix kPrefix2 = 1002U;
+  const SBFullHash kFullHash2_1 =
+      SBFullHashForPrefixAndSuffix(kPrefix2, "\x01");
+
+  // Insert prefix kPrefix1 and kPrefix2 into database.
+  ScopedVector<SBChunkData> chunks;
+  chunks.push_back(AddChunkPrefix(1, kPrefix1));
+  chunks.push_back(AddChunkPrefix(2, kPrefix2));
+
+  std::vector<SBListChunkRanges> lists;
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  database_->UpdateFinished(true);
+
+  {
+    // Cache a full miss result for kPrefix1.
+    std::vector<SBPrefix> prefixes(1, kPrefix1);
+    std::vector<SBFullHashResult> cache_results;
+    database_->CacheHashResults(prefixes, cache_results, kCacheLifetime);
+  }
+
+  {
+    // kFullHash1_1 gets no prefix hit because of the cached item, and also does
+    // not have a cache hit.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_1);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_FALSE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+
+    // kFullHash2_1 gets a hit from the prefix in the database.
+    full_hashes.push_back(kFullHash2_1);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix2, prefix_hits[0]);
+    EXPECT_TRUE(cache_hits.empty());
+  }
+}
+
+TEST_F(SafeBrowsingDatabaseTest, CachedPrefixHitFullMiss) {
+  const SBPrefix kPrefix1 = 1001U;
+  const SBFullHash kFullHash1_1 =
+      SBFullHashForPrefixAndSuffix(kPrefix1, "\x01");
+  const SBFullHash kFullHash1_2 =
+      SBFullHashForPrefixAndSuffix(kPrefix1, "\x02");
+  const SBFullHash kFullHash1_3 =
+      SBFullHashForPrefixAndSuffix(kPrefix1, "\x03");
+
+  const SBPrefix kPrefix2 = 1002U;
+  const SBFullHash kFullHash2_1 =
+      SBFullHashForPrefixAndSuffix(kPrefix2, "\x01");
+
+  const SBPrefix kPrefix3 = 1003U;
+  const SBFullHash kFullHash3_1 =
+      SBFullHashForPrefixAndSuffix(kPrefix3, "\x01");
+
+  // Insert prefix kPrefix1 and kPrefix2 into database.
+  ScopedVector<SBChunkData> chunks;
+  chunks.push_back(AddChunkPrefix(1, kPrefix1));
+  chunks.push_back(AddChunkPrefix(2, kPrefix2));
+
+  std::vector<SBListChunkRanges> lists;
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  database_->UpdateFinished(true);
+
+  {
+    // kFullHash1_1 has a prefix hit of kPrefix1.
+    std::vector<SBFullHash> full_hashes;
+    full_hashes.push_back(kFullHash1_1);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix1, prefix_hits[0]);
+    EXPECT_TRUE(cache_hits.empty());
+
+    // kFullHash2_1 has a prefix hit of kPrefix2.
+    full_hashes.push_back(kFullHash2_1);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(2U, prefix_hits.size());
+    EXPECT_EQ(kPrefix1, prefix_hits[0]);
+    EXPECT_EQ(kPrefix2, prefix_hits[1]);
+    EXPECT_TRUE(cache_hits.empty());
+
+    // kFullHash3_1 has no hits.
+    full_hashes.push_back(kFullHash3_1);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(2U, prefix_hits.size());
+    EXPECT_EQ(kPrefix1, prefix_hits[0]);
+    EXPECT_EQ(kPrefix2, prefix_hits[1]);
+    EXPECT_TRUE(cache_hits.empty());
+  }
+
+  {
+    // Cache a fullhash result for two kPrefix1 full hashes.
+    std::vector<SBPrefix> prefixes(1, kPrefix1);
+    std::vector<SBFullHashResult> cache_results;
+
+    SBFullHashResult full_hash_result;
+    full_hash_result.list_id = safe_browsing_util::MALWARE;
+
+    full_hash_result.hash = kFullHash1_1;
+    cache_results.push_back(full_hash_result);
+
+    full_hash_result.hash = kFullHash1_3;
+    cache_results.push_back(full_hash_result);
+
+    database_->CacheHashResults(prefixes, cache_results, kCacheLifetime);
+  }
+
+  {
+    // kFullHash1_1 should now see a cache hit.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_1);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    EXPECT_TRUE(prefix_hits.empty());
+    ASSERT_EQ(1U, cache_hits.size());
+    EXPECT_TRUE(SBFullHashEqual(kFullHash1_1, cache_hits[0].hash));
+
+    // Adding kFullHash2_1 will see the existing cache hit plus the prefix hit
+    // for kPrefix2.
+    full_hashes.push_back(kFullHash2_1);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix2, prefix_hits[0]);
+    ASSERT_EQ(1U, cache_hits.size());
+    EXPECT_TRUE(SBFullHashEqual(kFullHash1_1, cache_hits[0].hash));
+
+    // kFullHash1_3 also gets a cache hit.
+    full_hashes.push_back(kFullHash1_3);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix2, prefix_hits[0]);
+    ASSERT_EQ(2U, cache_hits.size());
+    EXPECT_TRUE(SBFullHashEqual(kFullHash1_1, cache_hits[0].hash));
+    EXPECT_TRUE(SBFullHashEqual(kFullHash1_3, cache_hits[1].hash));
+  }
+
+  {
+    // Check if DB contains only kFullHash1_3. Should return a cache hit.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_3);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    EXPECT_TRUE(prefix_hits.empty());
+    ASSERT_EQ(1U, cache_hits.size());
+    EXPECT_TRUE(SBFullHashEqual(kFullHash1_3, cache_hits[0].hash));
+  }
+
+  {
+    // kFullHash1_2 has no cache hit, and no prefix hit because of the cache for
+    // kPrefix1.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_2);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_FALSE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+
+    // Other prefix hits possible when kFullHash1_2 hits nothing.
+    full_hashes.push_back(kFullHash2_1);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix2, prefix_hits[0]);
+    EXPECT_TRUE(cache_hits.empty());
+  }
+}
+
+TEST_F(SafeBrowsingDatabaseTest, BrowseFullHashMatching) {
+  const SBPrefix kPrefix1 = 1001U;
+  const SBFullHash kFullHash1_1 =
+      SBFullHashForPrefixAndSuffix(kPrefix1, "\x01");
+  const SBFullHash kFullHash1_2 =
+      SBFullHashForPrefixAndSuffix(kPrefix1, "\x02");
+  const SBFullHash kFullHash1_3 =
+      SBFullHashForPrefixAndSuffix(kPrefix1, "\x03");
+
+  // Insert two full hashes with a shared prefix.
+  ScopedVector<SBChunkData> chunks;
+  chunks.push_back(AddChunkFullHash(1, kFullHash1_1));
+  chunks.push_back(AddChunkFullHash(2, kFullHash1_2));
+
+  std::vector<SBListChunkRanges> lists;
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  database_->UpdateFinished(true);
+
+  {
+    // Check a full hash which isn't present.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_3);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_FALSE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+
+    // Also one which is present, should have a prefix hit.
+    full_hashes.push_back(kFullHash1_1);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix1, prefix_hits[0]);
+    EXPECT_TRUE(cache_hits.empty());
+
+    // Two full hash matches with the same prefix should return one prefix hit.
+    full_hashes.push_back(kFullHash1_2);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix1, prefix_hits[0]);
+    EXPECT_TRUE(cache_hits.empty());
+  }
+
+  {
+    // Cache a gethash result for kFullHash1_2.
+    SBFullHashResult full_hash_result;
+    full_hash_result.list_id = safe_browsing_util::MALWARE;
+    full_hash_result.hash = kFullHash1_2;
+
+    std::vector<SBPrefix> prefixes(1, kPrefix1);
+    std::vector<SBFullHashResult> cache_results(1, full_hash_result);
+
+    database_->CacheHashResults(prefixes, cache_results, kCacheLifetime);
+  }
+
+  {
+    // kFullHash1_3 should still return false, because the cached
+    // result for kPrefix1 doesn't contain kFullHash1_3.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_3);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_FALSE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+
+    // kFullHash1_1 is also not in the cached result, which takes
+    // priority over the database.
+    prefix_hits.clear();
+    full_hashes.push_back(kFullHash1_1);
+    cache_hits.clear();
+    EXPECT_FALSE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+
+    // kFullHash1_2 is in the cached result.
+    full_hashes.push_back(kFullHash1_2);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    EXPECT_TRUE(prefix_hits.empty());
+    ASSERT_EQ(1U, cache_hits.size());
+    EXPECT_TRUE(SBFullHashEqual(kFullHash1_2, cache_hits[0].hash));
+  }
+
+  // Remove kFullHash1_1 from the database.
+  chunks.clear();
+  chunks.push_back(SubChunkFullHash(11, kFullHash1_1, 1));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  database_->UpdateFinished(true);
+
+  // Cache should be cleared after updating.
+  EXPECT_TRUE(database_->browse_gethash_cache_.empty());
+
+  {
+    // Now the database doesn't contain kFullHash1_1.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_1);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_FALSE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+
+    // Nor kFullHash1_3.
+    full_hashes.push_back(kFullHash1_3);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_FALSE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+
+    // Still has kFullHash1_2.
+    full_hashes.push_back(kFullHash1_2);
+    prefix_hits.clear();
+    cache_hits.clear();
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix1, prefix_hits[0]);
+    EXPECT_TRUE(cache_hits.empty());
+  }
+
+  // Remove kFullHash1_2 from the database.
+  chunks.clear();
+  chunks.push_back(SubChunkFullHash(12, kFullHash1_2, 2));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  database_->UpdateFinished(true);
+
+  // Cache should be cleared after updating.
+  EXPECT_TRUE(database_->browse_gethash_cache_.empty());
+
+  {
+    // None are present.
+    std::vector<SBFullHash> full_hashes;
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    full_hashes.push_back(kFullHash1_1);
+    full_hashes.push_back(kFullHash1_2);
+    full_hashes.push_back(kFullHash1_3);
+    EXPECT_FALSE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+  }
+}
+
+TEST_F(SafeBrowsingDatabaseTest, BrowseFullHashAndPrefixMatching) {
+  const SBPrefix kPrefix1 = 1001U;
+  const SBFullHash kFullHash1_1 =
+      SBFullHashForPrefixAndSuffix(kPrefix1, "\x01");
+  const SBFullHash kFullHash1_2 =
+      SBFullHashForPrefixAndSuffix(kPrefix1, "\x02");
+
+  ScopedVector<SBChunkData> chunks;
+  chunks.push_back(AddChunkFullHash(1, kFullHash1_1));
+
+  std::vector<SBListChunkRanges> lists;
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  database_->UpdateFinished(true);
+
+  {
+    // kFullHash1_2 does not match kFullHash1_1.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_2);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_FALSE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+  }
+
+  // Add a prefix match.
+  chunks.clear();
+  chunks.push_back(AddChunkPrefix(2, kPrefix1));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  database_->UpdateFinished(true);
+
+  {
+    // kFullHash1_2 does match kPrefix1.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_2);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix1, prefix_hits[0]);
+    EXPECT_TRUE(cache_hits.empty());
+  }
+
+  // Remove the full hash.
+  chunks.clear();
+  chunks.push_back(SubChunkFullHash(11, kFullHash1_1, 1));
+
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+  database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  database_->UpdateFinished(true);
+
+  {
+    // kFullHash1_2 still returns true due to the prefix hit.
+    std::vector<SBFullHash> full_hashes(1, kFullHash1_2);
+    std::vector<SBPrefix> prefix_hits;
+    std::vector<SBFullHashResult> cache_hits;
+    EXPECT_TRUE(database_->ContainsBrowseUrlHashes(
+        full_hashes, &prefix_hits, &cache_hits));
+    ASSERT_EQ(1U, prefix_hits.size());
+    EXPECT_EQ(kPrefix1, prefix_hits[0]);
+    EXPECT_TRUE(cache_hits.empty());
+  }
 }
 
 TEST_F(SafeBrowsingDatabaseTest, MalwareIpBlacklist) {
@@ -1738,50 +2003,29 @@ TEST_F(SafeBrowsingDatabaseTest, MalwareIpBlacklist) {
                                               ip_blacklist_store));
   database_->Init(database_filename_);
   std::vector<SBListChunkRanges> lists;
-  EXPECT_TRUE(database_->UpdateStarted(&lists));
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+
+  ScopedVector<SBChunkData> chunks;
 
   // IPv4 prefix match for ::ffff:192.168.1.0/120.
-  SBChunkList chunks;
-  SBChunk chunk;
-  InsertAddChunkFullHash(&chunk, 1, "::ffff:192.168.1.0", 120);
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks);
+  chunks.push_back(AddChunkHashedIpValue(1, "::ffff:192.168.1.0", 120));
 
   // IPv4 exact match for ::ffff:192.1.1.1.
-  chunks.clear();
-  chunk.hosts.clear();
-  InsertAddChunkFullHash(&chunk, 2, "::ffff:192.1.1.1", 128);
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks);
+  chunks.push_back(AddChunkHashedIpValue(2, "::ffff:192.1.1.1", 128));
 
   // IPv6 exact match for: fe80::31a:a0ff:fe10:786e/128.
-  chunks.clear();
-  chunk.hosts.clear();
-  InsertAddChunkFullHash(&chunk, 3, "fe80::31a:a0ff:fe10:786e", 128);
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks);
+  chunks.push_back(AddChunkHashedIpValue(3, "fe80::31a:a0ff:fe10:786e", 128));
 
   // IPv6 prefix match for: 2620:0:1000:3103::/64.
-  chunks.clear();
-  chunk.hosts.clear();
-  InsertAddChunkFullHash(&chunk, 4, "2620:0:1000:3103::", 64);
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks);
+  chunks.push_back(AddChunkHashedIpValue(4, "2620:0:1000:3103::", 64));
 
   // IPv4 prefix match for ::ffff:192.1.122.0/119.
-  chunks.clear();
-  chunk.hosts.clear();
-  InsertAddChunkFullHash(&chunk, 5, "::ffff:192.1.122.0", 119);
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks);
+  chunks.push_back(AddChunkHashedIpValue(5, "::ffff:192.1.122.0", 119));
 
   // IPv4 prefix match for ::ffff:192.1.128.0/113.
-  chunks.clear();
-  chunk.hosts.clear();
-  InsertAddChunkFullHash(&chunk, 6, "::ffff:192.1.128.0", 113);
-  chunks.push_back(chunk);
-  database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks);
+  chunks.push_back(AddChunkHashedIpValue(6, "::ffff:192.1.128.0", 113));
 
+  database_->InsertChunks(safe_browsing_util::kIPBlacklist, chunks.get());
   database_->UpdateFinished(true);
 
   EXPECT_FALSE(database_->ContainsMalwareIP("192.168.0.255"));
@@ -1822,3 +2066,73 @@ TEST_F(SafeBrowsingDatabaseTest, MalwareIpBlacklist) {
   EXPECT_TRUE(database_->ContainsMalwareIP("192.1.255.255"));
   EXPECT_FALSE(database_->ContainsMalwareIP("192.2.0.0"));
 }
+
+TEST_F(SafeBrowsingDatabaseTest, ContainsBrowseURL) {
+  std::vector<SBListChunkRanges> lists;
+  ASSERT_TRUE(database_->UpdateStarted(&lists));
+
+  // Add a host-level hit.
+  {
+    ScopedVector<SBChunkData> chunks;
+    chunks.push_back(AddChunkPrefixValue(1, "www.evil.com/"));
+    database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  }
+
+  // Add a specific fullhash.
+  static const char kWhateverMalware[] = "www.whatever.com/malware.html";
+  {
+    ScopedVector<SBChunkData> chunks;
+    chunks.push_back(AddChunkFullHashValue(2, kWhateverMalware));
+    database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  }
+
+  // Add a fullhash which has a prefix collision for a known url.
+  static const char kExampleFine[] = "www.example.com/fine.html";
+  static const char kExampleCollision[] =
+      "www.example.com/3123364814/malware.htm";
+  ASSERT_EQ(SBPrefixForString(kExampleFine),
+            SBPrefixForString(kExampleCollision));
+  {
+    ScopedVector<SBChunkData> chunks;
+    chunks.push_back(AddChunkFullHashValue(3, kExampleCollision));
+    database_->InsertChunks(safe_browsing_util::kMalwareList, chunks.get());
+  }
+
+  database_->UpdateFinished(true);
+
+  std::vector<SBPrefix> prefix_hits;
+  std::vector<SBFullHashResult> cache_hits;
+
+  // Anything will hit the host prefix.
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL("http://www.evil.com/malware.html"), &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString("www.evil.com/"), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
+
+  // Hit the specific URL prefix.
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL(std::string("http://") + kWhateverMalware),
+      &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kWhateverMalware), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
+
+  // Other URLs at that host are fine.
+  EXPECT_FALSE(database_->ContainsBrowseUrl(
+      GURL("http://www.whatever.com/fine.html"), &prefix_hits, &cache_hits));
+  EXPECT_TRUE(prefix_hits.empty());
+  EXPECT_TRUE(cache_hits.empty());
+
+  // Hit the specific URL full hash.
+  EXPECT_TRUE(database_->ContainsBrowseUrl(
+      GURL(std::string("http://") + kExampleCollision),
+      &prefix_hits, &cache_hits));
+  ASSERT_EQ(1U, prefix_hits.size());
+  EXPECT_EQ(SBPrefixForString(kExampleCollision), prefix_hits[0]);
+  EXPECT_TRUE(cache_hits.empty());
+
+  // This prefix collides, but no full hash match.
+  EXPECT_FALSE(database_->ContainsBrowseUrl(
+      GURL(std::string("http://") + kExampleFine), &prefix_hits, &cache_hits));
+}