#include "chrome/browser/safe_browsing/prefix_set.h"
#include <algorithm>
#include <math.h>
#include "base/file_util.h"
#include "base/logging.h"
#include "base/md5.h"
#include "base/metrics/histogram.h"
namespace {
static uint32 kMagic = 0x864088dd;
static uint32 kVersion = 0x1;
typedef struct {
uint32 magic;
uint32 version;
uint32 index_size;
uint32 deltas_size;
} FileHeader;
bool PrefixLess(const std::pair<SBPrefix,size_t>& a,
const std::pair<SBPrefix,size_t>& b) {
return a.first < b.first;
}
}
namespace safe_browsing {
PrefixSet::PrefixSet(const std::vector<SBPrefix>& sorted_prefixes) {
if (sorted_prefixes.size()) {
const size_t min_runs = sorted_prefixes.size() / kMaxRun;
index_.reserve(min_runs);
deltas_.reserve(sorted_prefixes.size() - min_runs);
SBPrefix prev_prefix = sorted_prefixes[0];
size_t run_length = 0;
index_.push_back(std::make_pair(prev_prefix, deltas_.size()));
for (size_t i = 1; i < sorted_prefixes.size(); ++i) {
if (sorted_prefixes[i] == prev_prefix)
continue;
DCHECK_GT(sorted_prefixes[i], prev_prefix);
const unsigned delta = sorted_prefixes[i] - prev_prefix;
const uint16 delta16 = static_cast<uint16>(delta);
if (delta != static_cast<unsigned>(delta16) || run_length >= kMaxRun) {
index_.push_back(std::make_pair(sorted_prefixes[i], deltas_.size()));
run_length = 0;
} else {
deltas_.push_back(delta16);
DCHECK_EQ(static_cast<unsigned>(deltas_.back()), delta);
++run_length;
}
prev_prefix = sorted_prefixes[i];
}
const size_t bits_used = index_.size() * sizeof(index_[0]) * CHAR_BIT +
deltas_.size() * sizeof(deltas_[0]) * CHAR_BIT;
const size_t unique_prefixes = index_.size() + deltas_.size();
static const size_t kMaxBitsPerPrefix = sizeof(SBPrefix) * CHAR_BIT;
UMA_HISTOGRAM_ENUMERATION("SB2.PrefixSetBitsPerPrefix",
bits_used / unique_prefixes,
kMaxBitsPerPrefix);
}
}
PrefixSet::PrefixSet(std::vector<std::pair<SBPrefix,size_t> > *index,
std::vector<uint16> *deltas) {
DCHECK(index && deltas);
index_.swap(*index);
deltas_.swap(*deltas);
}
PrefixSet::~PrefixSet() {}
bool PrefixSet::Exists(SBPrefix prefix) const {
if (index_.empty())
return false;
std::vector<std::pair<SBPrefix,size_t> >::const_iterator
iter = std::upper_bound(index_.begin(), index_.end(),
std::pair<SBPrefix,size_t>(prefix, 0),
PrefixLess);
if (iter == index_.begin())
return false;
const size_t bound = (iter == index_.end() ? deltas_.size() : iter->second);
--iter;
SBPrefix current = iter->first;
if (current == prefix)
return true;
for (size_t di = iter->second; di < bound && current < prefix; ++di) {
current += deltas_[di];
}
return current == prefix;
}
void PrefixSet::GetPrefixes(std::vector<SBPrefix>* prefixes) const {
prefixes->reserve(index_.size() + deltas_.size());
for (size_t ii = 0; ii < index_.size(); ++ii) {
const size_t deltas_end =
(ii + 1 < index_.size()) ? index_[ii + 1].second : deltas_.size();
SBPrefix current = index_[ii].first;
prefixes->push_back(current);
for (size_t di = index_[ii].second; di < deltas_end; ++di) {
current += deltas_[di];
prefixes->push_back(current);
}
}
}
PrefixSet* PrefixSet::LoadFile(const FilePath& filter_name) {
int64 size_64;
if (!file_util::GetFileSize(filter_name, &size_64))
return NULL;
using base::MD5Digest;
if (size_64 < static_cast<int64>(sizeof(FileHeader) + sizeof(MD5Digest)))
return NULL;
file_util::ScopedFILE file(file_util::OpenFile(filter_name, "rb"));
if (!file.get())
return NULL;
FileHeader header;
size_t read = fread(&header, sizeof(header), 1, file.get());
if (read != 1)
return NULL;
if (header.magic != kMagic || header.version != kVersion)
return NULL;
std::vector<std::pair<SBPrefix,size_t> > index;
const size_t index_bytes = sizeof(index[0]) * header.index_size;
std::vector<uint16> deltas;
const size_t deltas_bytes = sizeof(deltas[0]) * header.deltas_size;
const size_t expected_bytes =
sizeof(header) + index_bytes + deltas_bytes + sizeof(MD5Digest);
if (static_cast<int64>(expected_bytes) != size_64)
return NULL;
base::MD5Context context;
base::MD5Init(&context);
base::MD5Update(&context, base::StringPiece(reinterpret_cast<char*>(&header),
sizeof(header)));
if (header.index_size) {
index.resize(header.index_size);
read = fread(&(index[0]), sizeof(index[0]), index.size(), file.get());
if (read != index.size())
return NULL;
base::MD5Update(&context,
base::StringPiece(reinterpret_cast<char*>(&(index[0])),
index_bytes));
}
if (header.deltas_size) {
deltas.resize(header.deltas_size);
read = fread(&(deltas[0]), sizeof(deltas[0]), deltas.size(), file.get());
if (read != deltas.size())
return NULL;
base::MD5Update(&context,
base::StringPiece(reinterpret_cast<char*>(&(deltas[0])),
deltas_bytes));
}
base::MD5Digest calculated_digest;
base::MD5Final(&calculated_digest, &context);
base::MD5Digest file_digest;
read = fread(&file_digest, sizeof(file_digest), 1, file.get());
if (read != 1)
return NULL;
if (0 != memcmp(&file_digest, &calculated_digest, sizeof(file_digest)))
return NULL;
return new PrefixSet(&index, &deltas);
}
bool PrefixSet::WriteFile(const FilePath& filter_name) const {
FileHeader header;
header.magic = kMagic;
header.version = kVersion;
header.index_size = static_cast<uint32>(index_.size());
header.deltas_size = static_cast<uint32>(deltas_.size());
if (static_cast<size_t>(header.index_size) != index_.size() ||
static_cast<size_t>(header.deltas_size) != deltas_.size()) {
NOTREACHED();
return false;
}
file_util::ScopedFILE file(file_util::OpenFile(filter_name, "wb"));
if (!file.get())
return false;
base::MD5Context context;
base::MD5Init(&context);
size_t written = fwrite(&header, sizeof(header), 1, file.get());
if (written != 1)
return false;
base::MD5Update(&context, base::StringPiece(reinterpret_cast<char*>(&header),
sizeof(header)));
if (index_.size()) {
const size_t index_bytes = sizeof(index_[0]) * index_.size();
written = fwrite(&(index_[0]), sizeof(index_[0]), index_.size(),
file.get());
if (written != index_.size())
return false;
base::MD5Update(&context,
base::StringPiece(
reinterpret_cast<const char*>(&(index_[0])),
index_bytes));
}
if (deltas_.size()) {
const size_t deltas_bytes = sizeof(deltas_[0]) * deltas_.size();
written = fwrite(&(deltas_[0]), sizeof(deltas_[0]), deltas_.size(),
file.get());
if (written != deltas_.size())
return false;
base::MD5Update(&context,
base::StringPiece(
reinterpret_cast<const char*>(&(deltas_[0])),
deltas_bytes));
}
base::MD5Digest digest;
base::MD5Final(&digest, &context);
written = fwrite(&digest, sizeof(digest), 1, file.get());
if (written != 1)
return false;
file.reset();
return true;
}
}