use crate::UefiDevice;
use guestmem::GuestMemoryError;
use inspect::InspectMut;
use local_clock::InspectableLocalClock;
use thiserror::Error;
use time::OffsetDateTime;
use uefi_specs::hyperv::time::VmEfiTime;
use uefi_specs::uefi::common::EfiStatus;
use uefi_specs::uefi::time::EfiDaylight;
use uefi_specs::uefi::time::EfiTimezone;
use uefi_specs::uefi::time::EFI_TIME;
#[derive(Debug, Error)]
pub enum TimeServiceError {
#[error("Invalid Argument")]
InvalidArg,
#[error("Time")]
Time(#[from] time::Error),
#[error("Overflow")]
Overflow,
}
struct EfiOffsetDateTime {
datetime: OffsetDateTime,
timezone: EfiTimezone,
daylight: EfiDaylight,
}
impl TryFrom<EFI_TIME> for EfiOffsetDateTime {
type Error = time::Error;
fn try_from(v: EFI_TIME) -> Result<Self, Self::Error> {
Ok(Self {
datetime: OffsetDateTime::from_unix_timestamp(0)?
.replace_year(v.year as i32)?
.replace_month(v.month.try_into()?)?
.replace_day(v.day)?
.replace_hour(v.hour)?
.replace_minute(v.minute)?
.replace_second(v.second)?
.replace_nanosecond(v.nanosecond)?,
timezone: v.timezone,
daylight: v.daylight,
})
}
}
impl From<EfiOffsetDateTime> for EFI_TIME {
fn from(v: EfiOffsetDateTime) -> Self {
Self {
year: v.datetime.year() as u16,
month: v.datetime.month().into(),
day: v.datetime.day(),
hour: v.datetime.hour(),
minute: v.datetime.minute(),
second: v.datetime.second(),
pad1: 0,
nanosecond: v.datetime.nanosecond(),
timezone: v.timezone,
daylight: v.daylight,
pad2: 0,
}
}
}
#[derive(InspectMut)]
pub struct TimeServices {
clock: Box<dyn InspectableLocalClock>,
timezone: EfiTimezone,
daylight: EfiDaylight,
}
impl TimeServices {
pub fn new(clock: Box<dyn InspectableLocalClock>) -> Self {
Self {
clock,
timezone: EfiTimezone(0),
daylight: EfiDaylight::new(),
}
}
pub fn get_time(&mut self) -> Result<EFI_TIME, TimeServiceError> {
if !self.daylight.valid() || !self.timezone.valid() {
return Err(TimeServiceError::InvalidArg);
}
let datetime: OffsetDateTime = self
.clock
.get_time()
.try_into()
.map_err(|_| TimeServiceError::Overflow)?;
Ok(EfiOffsetDateTime {
datetime,
timezone: self.timezone,
daylight: self.daylight,
}
.into())
}
pub fn set_time(&mut self, new_time: EFI_TIME) -> Result<(), TimeServiceError> {
let EfiOffsetDateTime {
datetime,
timezone,
daylight,
} = new_time.try_into()?;
if !daylight.valid() || !timezone.valid() {
return Err(TimeServiceError::InvalidArg);
}
self.timezone = timezone;
self.daylight = daylight;
self.clock.set_time(datetime.into());
Ok(())
}
}
impl UefiDevice {
pub(crate) fn get_time(&mut self, gpa: u64) -> Result<(), GuestMemoryError> {
let vm_time = match self.service.time.get_time() {
Ok(time) => VmEfiTime {
status: EfiStatus::SUCCESS.into(),
time,
},
Err(e) => {
tracing::error!("get_time: {}", e);
VmEfiTime {
status: EfiStatus::DEVICE_ERROR.into(),
time: Default::default(),
}
}
};
tracing::debug!("get_time: {:?}", vm_time);
self.gm.write_plain(gpa, &vm_time)
}
pub(crate) fn set_time(&mut self, gpa: u64) -> Result<(), GuestMemoryError> {
let vm_time = self.gm.read_plain::<VmEfiTime>(gpa)?;
let status = match self.service.time.set_time(vm_time.time) {
Ok(_) => EfiStatus::SUCCESS,
Err(e) => {
tracing::error!("set_time: {}", e);
match e {
TimeServiceError::InvalidArg => EfiStatus::INVALID_PARAMETER,
_ => EfiStatus::DEVICE_ERROR,
}
}
};
let vm_time = VmEfiTime {
time: vm_time.time,
status: status.into(),
};
tracing::debug!("set_time: {:?}", vm_time);
self.gm.write_plain(gpa, &vm_time)
}
}
mod save_restore {
use super::*;
use vmcore::save_restore::RestoreError;
use vmcore::save_restore::SaveError;
use vmcore::save_restore::SaveRestore;
mod state {
use mesh::payload::Protobuf;
use vmcore::save_restore::SavedStateRoot;
#[derive(Protobuf, SavedStateRoot)]
#[mesh(package = "firmware.uefi.time")]
pub struct SavedState {
#[mesh(1)]
pub timezone: i16,
#[mesh(2)]
pub daylight: u8,
}
}
impl SaveRestore for TimeServices {
type SavedState = state::SavedState;
fn save(&mut self) -> Result<Self::SavedState, SaveError> {
Ok(state::SavedState {
timezone: self.timezone.0,
daylight: self.daylight.into(),
})
}
fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
let state::SavedState { timezone, daylight } = state;
self.timezone = EfiTimezone(timezone);
self.daylight = EfiDaylight::from(daylight);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use local_clock::MockLocalClock;
use local_clock::MockLocalClockAccessor;
use std::time::Duration;
use test_with_tracing::test;
fn new_test_time_service() -> (MockLocalClockAccessor, TimeServices) {
let time = MockLocalClock::new();
let time_access = time.accessor();
let service = TimeServices::new(Box::new(time));
(time_access, service)
}
#[test]
fn test_basic_get_time_set_time() {
let (time_access, mut service) = new_test_time_service();
let mut init_time = EFI_TIME {
year: 2023,
month: 11,
day: 9,
hour: 10,
minute: 42,
second: 27,
pad1: 0,
nanosecond: 0,
timezone: EfiTimezone(-480),
daylight: EfiDaylight::new().with_adjust_daylight(true),
pad2: 0,
};
service.set_time(init_time).unwrap();
time_access.tick(Duration::from_secs(2));
init_time.second += 2;
let new_time = service.get_time().unwrap();
assert_eq!(init_time, new_time);
}
#[test]
#[should_panic]
fn test_validate_timezone() {
let (_, mut service) = new_test_time_service();
let init_time = EFI_TIME {
year: 2023,
month: 11,
day: 9,
hour: 10,
minute: 42,
second: 27,
pad1: 0,
nanosecond: 0,
timezone: EfiTimezone(1500),
daylight: EfiDaylight::new().with_adjust_daylight(true),
pad2: 0,
};
service.set_time(init_time).unwrap();
}
#[test]
#[should_panic]
fn test_validate_daylight() {
let (_, mut service) = new_test_time_service();
let init_time = EFI_TIME {
year: 2023,
month: 11,
day: 9,
hour: 10,
minute: 42,
second: 27,
pad1: 0,
nanosecond: 0,
timezone: EfiTimezone(-480),
daylight: EfiDaylight::from(4),
pad2: 0,
};
service.set_time(init_time).unwrap();
}
}