/*
 * Returns 1 if success, 0 in case of QueryPerformanceCounter is not supported.
 *
 * Calibration is done as described above.
 *
 * To ensure that the calibration is accurate, we interleave sampling
 * the microsecond resolution counter via QueryPerformanceCounter with the 1
 * millisecond resolution system clock via GetSystemTimeAsFileTime.
 * When we see the edge transition where the system clock ticks, we
 * compare the before and after microsecond counter values.  If it is
 * within a calibration threshold (kMaxCalibrationDiff), then we
 * record the instantaneous system time (1 msresolution) and the
 * 64-bit counter value (~0.5 mks reslution).  Since we have a before and
 * after counter value, we interpolate it to get the "average" counter
 * value to associate with the system time.
 */
static int NaClCalibrateWindowsClockQpc(struct NaClTimeState *ntsp) {
  FILETIME  ft_start;
  FILETIME  ft_prev;
  FILETIME  ft_now;
  LARGE_INTEGER  counter_before;
  LARGE_INTEGER  counter_after;
  int64_t  counter_diff;
  int64_t  counter_diff_ms;
  uint64_t end_of_calibrate;
  int sys_time_changed;
  int calibration_success = 0;

  NaClLog(5, "Entered NaClCalibrateWindowsClockQpc\n");

  GetSystemTimeAsFileTime(&ft_start);
  ft_prev = ft_start;
  end_of_calibrate = NaClFileTimeToMs(&ft_start) + kCalibrateTimeoutMs;

  do {
    if (!QueryPerformanceCounter(&counter_before))
      return 0;
    GetSystemTimeAsFileTime(&ft_now);
    if (!QueryPerformanceCounter(&counter_after))
      return 0;

    counter_diff = counter_after.QuadPart - counter_before.QuadPart;
    counter_diff_ms =
        (counter_diff * kMillisecondsPerSecond) / ntsp->qpc_frequency;
    sys_time_changed = (ft_now.dwHighDateTime != ft_prev.dwHighDateTime) ||
        (ft_now.dwLowDateTime != ft_prev.dwLowDateTime);

    if ((counter_diff >= 0) &&
        (counter_diff_ms <= kMaxCalibrationDiff) &&
        sys_time_changed) {
      calibration_success = 1;
      break;
    }
    if (sys_time_changed)
      ft_prev = ft_now;
  } while (NaClFileTimeToMs(&ft_now) < end_of_calibrate);

  ntsp->system_time_start_ms = NaClFileTimeToMs(&ft_now);
  ntsp->qpc_start = counter_before.QuadPart + (counter_diff / 2);
  ntsp->last_qpc = counter_after.QuadPart;

  NaClLog(5,
          "Leaving NaClCalibrateWindowsClockQpc : %d\n",
          calibration_success);
  return calibration_success;
}
/*
 * Calibration is done as described above.
 *
 * To ensure that the calibration is accurate, we interleave sampling
 * the millisecond resolution counter via timeGetTime with the 10-55
 * millisecond resolution system clock via GetSystemTimeAsFileTime.
 * When we see the edge transition where the system clock ticks, we
 * compare the before and after millisecond counter values.  If it is
 * within a calibration threshold (kMaxCalibrationDiff), then we
 * record the instantaneous system time (10-55 msresolution) and the
 * 32-bit counter value (1ms reslution).  Since we have a before and
 * after counter value, we interpolate it to get the "average" counter
 * value to associate with the system time.
 */
static void NaClCalibrateWindowsClockMu(struct NaClTimeState *ntsp) {
  FILETIME  ft_start;
  FILETIME  ft_now;
  DWORD     ms_counter_before;
  DWORD     ms_counter_after;
  uint32_t  ms_counter_diff;

  NaClLog(4, "Entered NaClCalibrateWindowsClockMu\n");
  GetSystemTimeAsFileTime(&ft_start);
  ms_counter_before = timeGetTime();
  for (;;) {
    GetSystemTimeAsFileTime(&ft_now);
    ms_counter_after = timeGetTime();
    ms_counter_diff = ms_counter_after - (uint32_t) ms_counter_before;
    NaClLog(4, "ms_counter_diff %u\n", ms_counter_diff);
    if (ms_counter_diff <= kMaxCalibrationDiff &&
        (ft_now.dwHighDateTime != ft_start.dwHighDateTime ||
         ft_now.dwLowDateTime != ft_start.dwLowDateTime)) {
      break;
    }
    ms_counter_before = ms_counter_after;
  }
  ntsp->system_time_start_ms = NaClFileTimeToMs(&ft_now);
  /*
   * Average the counter values.  Note unsigned computation of
   * ms_counter_diff, so that was mod 2**32 arithmetic, and the
   * addition of half the difference is numerically correct, whereas
   * (ms_counter_before + ms_counter_after)/2 is wrong due to
   * overflow.
   */
  ntsp->ms_counter_start = (DWORD) (ms_counter_before + (ms_counter_diff / 2));

  NaClLog(4, "Leaving NaClCalibrateWindowsClockMu\n");
}
int NaClGetTimeOfDayIntern(struct nacl_abi_timeval *tv,
                           struct NaClTimeState    *ntsp) {
  FILETIME  ft_now;
  DWORD     ms_counter_now;
  uint64_t  t_ms;
  DWORD     ms_counter_at_ft_now;
  uint32_t  ms_counter_diff;
  uint64_t  unix_time_ms;

  if (ntsp->can_use_qpc)
    return NaClGetTimeOfDayInternQpc(tv, ntsp, 1);

  GetSystemTimeAsFileTime(&ft_now);
  ms_counter_now = timeGetTime();
  t_ms = NaClFileTimeToMs(&ft_now);

  NaClXMutexLock(&ntsp->mu);

  if (!ntsp->allow_low_resolution) {
    NaClLog(5, "ms_counter_now       %"NACL_PRIu32"\n",
            (uint32_t) ms_counter_now);
    NaClLog(5, "t_ms                 %"NACL_PRId64"\n", t_ms);
    NaClLog(5, "system_time_start_ms %"NACL_PRIu64"\n",
            ntsp->system_time_start_ms);

    ms_counter_at_ft_now = (DWORD)
        (ntsp->ms_counter_start +
         (uint32_t) (t_ms - ntsp->system_time_start_ms));

    NaClLog(5, "ms_counter_at_ft_now %"NACL_PRIu32"\n",
            (uint32_t) ms_counter_at_ft_now);

    ms_counter_diff = ms_counter_now - (uint32_t) ms_counter_at_ft_now;

    NaClLog(5, "ms_counter_diff      %"NACL_PRIu32"\n", ms_counter_diff);

    if (ms_counter_diff <= kMaxMillsecondDriftBeforeRecalibration) {
      t_ms = t_ms + ms_counter_diff;
    } else {
      NaClCalibrateWindowsClockMu(ntsp);
      t_ms = ntsp->system_time_start_ms;
    }

    NaClLog(5, "adjusted t_ms =      %"NACL_PRIu64"\n", t_ms);
  }

  unix_time_ms = t_ms - ntsp->epoch_start_ms;

  NaClXMutexUnlock(&ntsp->mu);

  NaClLog(5, "unix_time_ms  =      %"NACL_PRId64"\n", unix_time_ms);
  /*
   * Unix time is measured relative to a different epoch, Jan 1, 1970.
   * See the module initialization for epoch_start_ms.
   */

  tv->nacl_abi_tv_sec = (nacl_abi_time_t) (unix_time_ms / 1000);
  tv->nacl_abi_tv_usec = (nacl_abi_suseconds_t) ((unix_time_ms % 1000) * 1000);
  return 0;
}
void NaClTimeInternalInit(struct NaClTimeState *ntsp) {
  TIMECAPS    tc;
  SYSTEMTIME  st;
  FILETIME    ft;

  /*
   * Maximize timer/Sleep resolution.
   */
  timeGetDevCaps(&tc, sizeof tc);
  ntsp->wPeriodMin = tc.wPeriodMin;
  ntsp->time_resolution_ns = tc.wPeriodMin * NACL_NANOS_PER_MILLI;
  NaClLog(0, "NaClTimeInternalInit: timeBeginPeriod(%u)\n", tc.wPeriodMin);
  timeBeginPeriod(ntsp->wPeriodMin);

  /*
   * Compute Unix epoch start; calibrate high resolution clock.
   */
  st.wYear = 1970;
  st.wMonth = 1;
  st.wDay = 1;
  st.wHour = 0;
  st.wMinute = 0;
  st.wSecond = 0;
  st.wMilliseconds = 0;
  SystemTimeToFileTime(&st, &ft);
  ntsp->epoch_start_ms = NaClFileTimeToMs(&ft);
  ntsp->last_reported_time_ms = 0;
  NaClLog(0, "Unix epoch start is  %"NACL_PRIu64"ms in Windows epoch time\n",
          ntsp->epoch_start_ms);
  NaClMutexCtor(&ntsp->mu);
  /*
   * We don't actually grab the lock, since the module initializer
   * should be called before going threaded.
   */
  NaClCalibrateWindowsClockMu(ntsp);
}
int NaClGetTimeOfDayInternQpc(struct nacl_abi_timeval *tv,
                              struct NaClTimeState    *ntsp,
                              int allow_calibration) {
  FILETIME  ft_now;
  int64_t sys_now_mks;
  LARGE_INTEGER qpc;
  int64_t qpc_diff;
  int64_t qpc_diff_mks;
  int64_t qpc_now_mks;
  int64_t drift_mks;
  int64_t drift_ms;

  NaClLog(5, "Entered NaClGetTimeOfDayInternQpc\n");

  NaClXMutexLock(&ntsp->mu);

  GetSystemTimeAsFileTime(&ft_now);
  QueryPerformanceCounter(&qpc);
  sys_now_mks = NaClFileTimeToMs(&ft_now) * kMicrosecondsPerMillisecond;
  NaClLog(5, " sys_now_mks = %"NACL_PRId64" (us)\n", sys_now_mks);
  qpc_diff = qpc.QuadPart - ntsp->qpc_start;
  NaClLog(5, " qpc_diff = %"NACL_PRId64" (counts)\n", qpc_diff);
  /*
   * Coarse qpc_now_mks to 10 microseconds resolution,
   * to match the other platforms and not make a side-channel
   * attack any easier than it needs to be.
   */
  qpc_diff_mks = ((qpc_diff * (kMicrosecondsPerSecond / kCoarseMks)) /
      ntsp->qpc_frequency) * kCoarseMks;
  NaClLog(5, " qpc_diff_mks = %"NACL_PRId64" (us)\n", qpc_diff_mks);

  qpc_now_mks = (ntsp->system_time_start_ms * kMicrosecondsPerMillisecond) +
      qpc_diff_mks;
  NaClLog(5, " system_time_start_ms %"NACL_PRIu64"\n",
          ntsp->system_time_start_ms);
  NaClLog(5, " qpc_now_mks = %"NACL_PRId64" (us)\n", qpc_now_mks);

  if ((qpc_diff < 0) || (qpc.QuadPart < ntsp->last_qpc)) {
    NaClLog(5, " need recalibration\n");
    if (allow_calibration) {
      NaClCalibrateWindowsClockQpc(ntsp);
      NaClXMutexUnlock(&ntsp->mu);
      return NaClGetTimeOfDayInternQpc(tv, ntsp, 0);
    } else {
      NaClLog(5, " ... but using coarse, system time instead.\n");
      /* use GetSystemTimeAsFileTime(), not QPC */
      qpc_now_mks = sys_now_mks;
    }
  }
  ntsp->last_qpc = qpc.QuadPart;
  drift_mks = sys_now_mks - qpc_now_mks;
  if (qpc_now_mks > sys_now_mks)
    drift_mks = qpc_now_mks - sys_now_mks;

  drift_ms = drift_mks / kMicrosecondsPerMillisecond;
  NaClLog(5, " drift_ms = %"NACL_PRId64"\n", drift_ms);

  if (allow_calibration &&
      (drift_ms > kMaxMillsecondDriftBeforeRecalibration)) {
    NaClLog(5, "drift_ms recalibration\n");
    NaClCalibrateWindowsClockQpc(ntsp);
    NaClXMutexUnlock(&ntsp->mu);
    return NaClGetTimeOfDayInternQpc(tv, ntsp, 0);
  }

  NaClXMutexUnlock(&ntsp->mu);

  /* translate to unix time base */
  qpc_now_mks = qpc_now_mks
      - ntsp->epoch_start_ms * kMicrosecondsPerMillisecond;

  tv->nacl_abi_tv_sec =
      (nacl_abi_time_t)(qpc_now_mks / kMicrosecondsPerSecond);
  tv->nacl_abi_tv_usec =
      (nacl_abi_suseconds_t)(qpc_now_mks % kMicrosecondsPerSecond);
  return 0;
}
void NaClTimeInternalInit(struct NaClTimeState *ntsp) {
  TIMECAPS    tc;
  SYSTEMTIME  st;
  FILETIME    ft;
  LARGE_INTEGER qpc_freq;

  /*
   * Maximize timer/Sleep resolution.
   */
  timeGetDevCaps(&tc, sizeof tc);

  if (ntsp->allow_low_resolution) {
    /* Set resolution to max so we don't over-promise. */
    ntsp->wPeriodMin = tc.wPeriodMax;
  } else {
    ntsp->wPeriodMin = tc.wPeriodMin;
    timeBeginPeriod(ntsp->wPeriodMin);
    NaClLog(4, "NaClTimeInternalInit: timeBeginPeriod(%u)\n", ntsp->wPeriodMin);
  }
  ntsp->time_resolution_ns = ntsp->wPeriodMin * NACL_NANOS_PER_MILLI;

  /*
   * Compute Unix epoch start; calibrate high resolution clock.
   */
  st.wYear = 1970;
  st.wMonth = 1;
  st.wDay = 1;
  st.wHour = 0;
  st.wMinute = 0;
  st.wSecond = 0;
  st.wMilliseconds = 0;
  SystemTimeToFileTime(&st, &ft);
  ntsp->epoch_start_ms = NaClFileTimeToMs(&ft);
  NaClLog(4, "Unix epoch start is  %"NACL_PRIu64"ms in Windows epoch time\n",
          ntsp->epoch_start_ms);

  NaClMutexCtor(&ntsp->mu);

  /*
   * We don't actually grab the lock, since the module initializer
   * should be called before going threaded.
   */
  ntsp->can_use_qpc = 0;
  if (!ntsp->allow_low_resolution) {
    ntsp->can_use_qpc = QueryPerformanceFrequency(&qpc_freq);
    /*
     * On Athlon X2 CPUs (e.g. model 15) QueryPerformanceCounter is
     * unreliable.  Fallback to low-res clock.
     */
    if (strstr(CPU_GetBrandString(), "AuthenticAMD") && (CPU_GetFamily() == 15))
        ntsp->can_use_qpc = 0;

    NaClLog(4,
            "CPU_GetBrandString->[%s] ntsp->can_use_qpc=%d\n",
            CPU_GetBrandString(),
            ntsp->can_use_qpc);

    if (ntsp->can_use_qpc) {
      ntsp->qpc_frequency = qpc_freq.QuadPart;
      NaClLog(4, "qpc_frequency = %"NACL_PRId64" (counts/s)\n",
              ntsp->qpc_frequency);
      if (!NaClCalibrateWindowsClockQpc(ntsp))
        ntsp->can_use_qpc = 0;
    }
    if (!ntsp->can_use_qpc)
      NaClCalibrateWindowsClockMu(ntsp);
  }
}