blob: a1cf85d3616c670d4af09267e729c369fc22199c [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "include/librtc.h"
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
enum months {
JANUARY = 1, // 31 days
FEBRUARY, // 28 or 29
MARCH, // 31
APRIL, // 30
MAY, // 31
JUNE, // 30
JULY, // 31
AUGUST, // 31
SEPTEMBER, // 30
OCTOBER, // 31
NOVEMBER, // 30
DECEMBER // 31
};
// Leading 0 allows using the 1-indexed month values from rtc.
static const uint32_t days_in_month[] = {
0,
31, // January
28, // February (not leap year)
31, // March
30, // April
31, // May
30, // June
31, // July
31, // August
30, // September
31, // October
30, // November
31, // December
};
// Start with seconds from the Unix epoch to 2000/1/1T00:00:00.
static const uint64_t local_epoch = 946684800;
static const uint16_t local_epoch_year = 2000;
static const uint16_t default_year = 2019;
static const uint16_t max_year = 2099;
static bool is_leap_year(uint16_t year) {
return ((year % 4) == 0 && (year % 100) != 0) || ((year % 400) == 0);
}
uint64_t seconds_since_epoch(const fuchsia_hardware_rtc_Time* rtc) {
// First add all of the prior years.
uint64_t days_since_local_epoch = 0;
for (uint16_t year = local_epoch_year; year < rtc->year; year++) {
days_since_local_epoch += is_leap_year(year) ? 366 : 365;
}
// Next add all the prior complete months this year.
for (size_t month = JANUARY; month < rtc->month; month++) {
days_since_local_epoch += days_in_month[month];
}
if (rtc->month > FEBRUARY && is_leap_year(rtc->year)) {
days_since_local_epoch++;
}
// Add all the prior complete days.
days_since_local_epoch += rtc->day - 1;
// Hours, minutes, and seconds are 0 indexed.
uint64_t hours_since_local_epoch = (days_since_local_epoch * 24) + rtc->hours;
uint64_t minutes_since_local_epoch = (hours_since_local_epoch * 60) + rtc->minutes;
uint64_t seconds_since_local_epoch = (minutes_since_local_epoch * 60) + rtc->seconds;
uint64_t rtc_seconds = local_epoch + seconds_since_local_epoch;
return rtc_seconds;
}
void seconds_to_rtc(uint64_t seconds, fuchsia_hardware_rtc_Time* rtc) {
// subtract local epoch offset to get to rtc time
uint64_t epoch = seconds - local_epoch;
rtc->seconds = epoch % 60;
epoch /= 60;
rtc->minutes = epoch % 60;
epoch /= 60;
rtc->hours = epoch % 24;
epoch /= 24;
for (rtc->year = local_epoch_year;; rtc->year++) {
uint32_t days_per_year = 365;
if (is_leap_year(rtc->year)) {
days_per_year++;
}
if (epoch < days_per_year) {
break;
}
epoch -= days_per_year;
}
for (rtc->month = 1;; rtc->month++) {
uint32_t days_per_month = days_in_month[rtc->month];
if ((rtc->month == FEBRUARY) && is_leap_year(rtc->year)) {
days_per_month++;
}
if (epoch < days_per_month) {
break;
}
epoch -= days_per_month;
}
// remaining epoch is a whole number of days so just make it one-indexed
rtc->day = (uint8_t)(epoch + 1);
}
uint8_t to_bcd(uint8_t binary) { return (uint8_t)(((binary / 10) << 4) | (binary % 10)); }
uint8_t from_bcd(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0xf); }
// If "clock.backstop" is set in the environment, it is parsed as seconds
// since the Unix epoch and returned. If it is un-set, or parsing fails, 0 is
// returned.
uint64_t rtc_backstop_seconds(void) {
const char* str = getenv("clock.backstop");
if (str == NULL) {
return 0;
}
return strtoll(str, NULL, 10);
}
// TODO: eventually swap rtc_is_invalid() for the positive version.
static bool rtc_is_valid(const fuchsia_hardware_rtc_Time* rtc) {
if (rtc->year < local_epoch_year || rtc->year > max_year)
return false;
if (rtc->month < JANUARY || rtc->month > DECEMBER)
return false;
if (rtc->day == 0)
return false;
switch (rtc->month) {
case JANUARY:
case MARCH:
case MAY:
case JULY:
case AUGUST:
case OCTOBER:
case DECEMBER:
if (rtc->day > 31)
return false;
break;
case FEBRUARY:
if (rtc->day > (is_leap_year(rtc->year) ? 29 : 28))
return false;
break;
default:
if (rtc->day > 30)
return false;
break;
}
if (rtc->hours > 23 || rtc->minutes > 59 || rtc->seconds > 59)
return false;
return true;
}
bool rtc_is_invalid(const fuchsia_hardware_rtc_Time* rtc) { return !rtc_is_valid(rtc); }
// Validate that the RTC is set to a valid time, and to a relatively
// sane one. Report the validated or reset time back via rtc.
void sanitize_rtc(void* ctx, fuchsia_hardware_rtc_Time* rtc,
zx_status_t (*rtc_get)(void*, fuchsia_hardware_rtc_Time*),
zx_status_t (*rtc_set)(void*, const fuchsia_hardware_rtc_Time*)) {
// January 1, 2019 00:00:00
fuchsia_hardware_rtc_Time backstop_rtc = {
.day = 1,
.month = JANUARY,
.year = default_year,
.seconds = 0,
.minutes = 0,
.hours = 0,
};
zx_status_t result = rtc_get(ctx, rtc);
if (result != ZX_OK) {
fprintf(stderr, "sanitize_rtc: could not get RTC value (%d)\n", result);
return;
};
uint64_t backstop = rtc_backstop_seconds();
if (rtc_is_invalid(rtc) || rtc->year < default_year || seconds_since_epoch(rtc) < backstop) {
if (backstop > 0) {
fprintf(stderr, "sanitize_rtc: clock set to clock.backstop=%ld\n", backstop);
seconds_to_rtc(backstop, &backstop_rtc);
} else {
fprintf(stderr, "sanitize_rtc: clock set to constant default, set clock.backstop\n");
}
result = rtc_set(ctx, &backstop_rtc);
if (result != ZX_OK) {
fprintf(stderr, "sanitize_rtc: could not set RTC value (%d)\n", result);
return;
}
*rtc = backstop_rtc;
}
}