/*
 * check_continuity.c - periodically check local clock deltas
 * Copyright (c) 2013 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 "config.h"

#include <time.h>
#include <event2/event.h>

#include "src/conf.h"
#include "src/tlsdate.h"
#include "src/util.h"


/* Returns < 0 on error,
 *           0 on sync'd,
 * and     > 0 on desync'd.
 * Old delta is in |delta|. |delta| is overwritten
 * if >= 0 is returned.
 *
 * This event catches any sort of real-time clock jump.  A jump is observed
 * when settimeofday() or adjtimex() is called, or if the RTC misbehaves on
 * return from suspend.  If a jump is detected between a cycle-oriented clock
 * (MONOTONIC_RAW) and a potentially RTC managed clock (REALTIME), then a
 * network resynchronization will be required.  To avoid requiring this on
 * every resume-from-suspend, a larger delta represents the largest time jump
 * allowed before needing a resync.
 *
 * Note, CLOCK_BOOTTIME does not resolve this on platforms without a persistent
 * clock because the RTC still determines the time considered "suspend time".
 */
int
check_continuity (time_t *delta)
{
  time_t new_delta;
  struct timespec monotonic, real;
  if (clock_gettime (CLOCK_REALTIME, &real) < 0)
    return -1;
  if (clock_gettime (CLOCK_MONOTONIC_RAW, &monotonic) < 0)
    return -1;
  new_delta = real.tv_sec - monotonic.tv_sec;
  if (*delta)
    {
      /* The allowed delta matches the interval for now. */
      static const time_t kDelta = CONTINUITY_INTERVAL;
      if (new_delta < *delta - kDelta || new_delta > *delta + kDelta)
        {
          *delta = new_delta;
          return  1;
        }
    }
  /* First delta after de-sync. */
  *delta = new_delta;
  return 0;
}

/* Sets up a wake event just in case there has not been a wake event
 * recently enough to catch clock desynchronization.  This does not
 * invalidate the time like the action_invalidate_time event.
 */
int setup_event_timer_continuity (struct state *state)
{
  struct event *event;
  struct timeval interval = { state->opts.continuity_interval, 0 };
  event = event_new (state->base, -1, EV_TIMEOUT|EV_PERSIST,
                     action_kickoff_time_sync, state);
  if (!event)
    {
      error ("Failed to create interval event");
      return 1;
    }
  event_priority_set (event, PRI_WAKE);
  return event_add (event, &interval);
}