blob: e3dc58ecd754f6ab26550ecb56db4d9a9425a092 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/network_time/network_time_tracker.h"
#include <stdint.h>
#include <utility>
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/tick_clock.h"
#include "build/build_config.h"
#include "components/network_time/network_time_pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
namespace network_time {
namespace {
// Number of time measurements performed in a given network time calculation.
const uint32_t kNumTimeMeasurements = 7;
// Amount of divergence allowed between wall clock and tick clock.
const uint32_t kClockDivergenceSeconds = 60;
// Maximum time lapse before deserialized data are considered stale.
const uint32_t kSerializedDataMaxAgeDays = 7;
// Name of a pref that stores the wall clock time, via |ToJsTime|.
const char kPrefTime[] = "local";
// Name of a pref that stores the tick clock time, via |ToInternalValue|.
const char kPrefTicks[] = "ticks";
// Name of a pref that stores the time uncertainty, via |ToInternalValue|.
const char kPrefUncertainty[] = "uncertainty";
// Name of a pref that stores the network time via |ToJsTime|.
const char kPrefNetworkTime[] = "network";
} // namespace
// static
void NetworkTimeTracker::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kNetworkTimeMapping,
new base::DictionaryValue());
}
NetworkTimeTracker::NetworkTimeTracker(scoped_ptr<base::Clock> clock,
scoped_ptr<base::TickClock> tick_clock,
PrefService* pref_service)
: clock_(std::move(clock)),
tick_clock_(std::move(tick_clock)),
pref_service_(pref_service) {
const base::DictionaryValue* time_mapping =
pref_service_->GetDictionary(prefs::kNetworkTimeMapping);
double time_js = 0;
double ticks_js = 0;
double network_time_js = 0;
double uncertainty_js = 0;
if (time_mapping->GetDouble(kPrefTime, &time_js) &&
time_mapping->GetDouble(kPrefTicks, &ticks_js) &&
time_mapping->GetDouble(kPrefUncertainty, &uncertainty_js) &&
time_mapping->GetDouble(kPrefNetworkTime, &network_time_js)) {
time_at_last_measurement_ = base::Time::FromJsTime(time_js);
ticks_at_last_measurement_ = base::TimeTicks::FromInternalValue(
static_cast<int64_t>(ticks_js));
network_time_uncertainty_ = base::TimeDelta::FromInternalValue(
static_cast<int64_t>(uncertainty_js));
network_time_at_last_measurement_ = base::Time::FromJsTime(network_time_js);
}
base::Time now = clock_->Now();
if (ticks_at_last_measurement_ > tick_clock_->NowTicks() ||
time_at_last_measurement_ > now ||
now - time_at_last_measurement_ >
base::TimeDelta::FromDays(kSerializedDataMaxAgeDays)) {
// Drop saved mapping if either clock has run backward, or the data are too
// old.
pref_service_->ClearPref(prefs::kNetworkTimeMapping);
network_time_at_last_measurement_ = base::Time(); // Reset.
}
}
NetworkTimeTracker::~NetworkTimeTracker() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void NetworkTimeTracker::UpdateNetworkTime(base::Time network_time,
base::TimeDelta resolution,
base::TimeDelta latency,
base::TimeTicks post_time) {
DCHECK(thread_checker_.CalledOnValidThread());
DVLOG(1) << "Network time updating to "
<< base::UTF16ToUTF8(
base::TimeFormatFriendlyDateAndTime(network_time));
// Update network time on every request to limit dependency on ticks lag.
// TODO(mad): Find a heuristic to avoid augmenting the
// network_time_uncertainty_ too much by a particularly long latency.
// Maybe only update when the the new time either improves in accuracy or
// drifts too far from |network_time_at_last_measurement_|.
network_time_at_last_measurement_ = network_time;
// Calculate the delay since the network time was received.
base::TimeTicks now_ticks = tick_clock_->NowTicks();
base::TimeDelta task_delay = now_ticks - post_time;
DCHECK_GE(task_delay.InMilliseconds(), 0);
DCHECK_GE(latency.InMilliseconds(), 0);
// Estimate that the time was set midway through the latency time.
base::TimeDelta offset = task_delay + latency / 2;
ticks_at_last_measurement_ = now_ticks - offset;
time_at_last_measurement_ = clock_->Now() - offset;
// Can't assume a better time than the resolution of the given time and the
// ticks measurements involved, each with their own uncertainty. 1 & 2 are
// the ones used to compute the latency, 3 is the Now() from when this task
// was posted, 4 and 5 are the Now() and NowTicks() above, and 6 and 7 will be
// the Now() and NowTicks() in GetNetworkTime().
network_time_uncertainty_ =
resolution + latency + kNumTimeMeasurements *
base::TimeDelta::FromMilliseconds(kTicksResolutionMs);
base::DictionaryValue time_mapping;
time_mapping.SetDouble(kPrefTime, time_at_last_measurement_.ToJsTime());
time_mapping.SetDouble(kPrefTicks, static_cast<double>(
ticks_at_last_measurement_.ToInternalValue()));
time_mapping.SetDouble(kPrefUncertainty, static_cast<double>(
network_time_uncertainty_.ToInternalValue()));
time_mapping.SetDouble(kPrefNetworkTime,
network_time_at_last_measurement_.ToJsTime());
pref_service_->Set(prefs::kNetworkTimeMapping, time_mapping);
}
bool NetworkTimeTracker::GetNetworkTime(base::Time* network_time,
base::TimeDelta* uncertainty) const {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(network_time);
if (network_time_at_last_measurement_.is_null()) {
return false;
}
DCHECK(!ticks_at_last_measurement_.is_null());
DCHECK(!time_at_last_measurement_.is_null());
base::TimeDelta tick_delta =
tick_clock_->NowTicks() - ticks_at_last_measurement_;
base::TimeDelta time_delta = clock_->Now() - time_at_last_measurement_;
if (time_delta.InMilliseconds() < 0) { // Has wall clock run backward?
DVLOG(1) << "Discarding network time due to wall clock running backward";
network_time_at_last_measurement_ = base::Time();
return false;
}
// Now we know that both |tick_delta| and |time_delta| are positive.
base::TimeDelta divergence = (tick_delta - time_delta).magnitude();
if (divergence > base::TimeDelta::FromSeconds(kClockDivergenceSeconds)) {
// Most likely either the machine has suspended, or the wall clock has been
// reset.
DVLOG(1) << "Discarding network time due to clocks diverging";
network_time_at_last_measurement_ = base::Time();
return false;
}
*network_time = network_time_at_last_measurement_ + tick_delta;
if (uncertainty) {
*uncertainty = network_time_uncertainty_ + divergence;
}
return true;
}
} // namespace network_time