blob: 064ed479f67f4ce0e7d43deb0a77a3948032c84a [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vclock
import (
// PeerSyncData contains data collected during a GetTime RPC to a peer.
type PeerSyncData struct {
MySendTs time.Time // when we sent request (our vclock time)
RecvTs time.Time // when peer received request (their vclock time)
SendTs time.Time // when peer sent response (their vclock time)
MyRecvTs time.Time // when we received response (our vclock time)
// Select fields from peer's VClockData.
LastNtpTs time.Time
NumReboots uint16
NumHops uint16
// NoUpdateErr is an error indicating that no update should be performed to
// VClockData. We define this error type explicitly so that the calling code
// (syncWithPeer) knows not to log the error with vlog.Errorf.
type NoUpdateErr struct {
msg string
func (e *NoUpdateErr) Error() string {
return e.msg
func nue(msg string) error {
return &NoUpdateErr{msg: msg}
// MaybeUpdateFromPeerData updates data (the local VClockData) based on the
// given PeerSyncData. Returns nil if data has been updated; otherwise, the
// returned error specifies why the data was not updated.
// TODO(sadovsky): This design assumes trust across syncgroups, which is
// generally not desirable. Eventually we may need to perform
// MaybeUpdateFromPeerData separately for each db or syncgroup, or something
// along these lines.
func MaybeUpdateFromPeerData(c *VClock, data *VClockData, psd *PeerSyncData) (*VClockData, error) {
// Same skew calculation as in NTP.
skew := (psd.RecvTs.Sub(psd.MySendTs) + psd.SendTs.Sub(psd.MyRecvTs)) / 2
vlog.VI(2).Infof("vclock: MaybeUpdateFromPeerData: skew: %v", skew)
if abs(skew) < PeerSyncSkewThreshold {
// Avoid constant tweaking of the clock.
return nil, nue("abs(skew) is less than PeerSyncSkewThreshold")
if psd.LastNtpTs.IsZero() {
return nil, nue("peer's clock has not synced to NTP")
if psd.LastNtpTs.Before(data.LastNtpTs) {
return nil, nue("peer's NTP is less recent than local")
// TODO(sadovsky): Do we really need the abs(skew) > RebootSkewThreshold part?
if psd.NumReboots > 0 && abs(skew) > RebootSkewThreshold {
return nil, nue("peer's vclock exceeds reboot tolerance")
if psd.NumHops+1 > MaxNumHops {
return nil, nue("peer's NTP is from too many hops away")
// All checks have passed. Update VClockData based on PeerSyncData.
now, elapsedTime, err := c.SysClockVals()
if err != nil {
vlog.Errorf("vclock: MaybeUpdateFromPeerData: SysClockVals failed: %v", err)
return nil, err
return &VClockData{
SystemTimeAtBoot: now.Add(-elapsedTime),
Skew: data.Skew + skew,
ElapsedTimeSinceBoot: elapsedTime,
LastNtpTs: psd.LastNtpTs,
NumReboots: psd.NumReboots,
NumHops: psd.NumHops + 1,
}, nil