use std::io;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use chrono::{DateTime, TimeZone, Utc};
use crate::{
block::{self, Height},
serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum LockTime {
Height(block::Height),
Time(DateTime<Utc>),
}
impl LockTime {
pub const MIN_TIMESTAMP: i64 = 500_000_000;
pub const MAX_TIMESTAMP: i64 = u32::MAX as i64;
pub const MAX_HEIGHT: Height = Height((Self::MIN_TIMESTAMP - 1) as u32);
pub fn unlocked() -> Self {
LockTime::Height(block::Height(0))
}
pub fn min_lock_time_timestamp() -> LockTime {
LockTime::Time(
Utc.timestamp_opt(Self::MIN_TIMESTAMP, 0)
.single()
.expect("in-range number of seconds and valid nanosecond"),
)
}
pub fn max_lock_time_timestamp() -> LockTime {
LockTime::Time(
Utc.timestamp_opt(Self::MAX_TIMESTAMP, 0)
.single()
.expect("in-range number of seconds and valid nanosecond"),
)
}
pub fn is_time(&self) -> bool {
matches!(self, LockTime::Time(_))
}
}
impl ZcashSerialize for LockTime {
#[allow(clippy::unwrap_in_result)]
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
match self {
LockTime::Height(block::Height(n)) => writer.write_u32::<LittleEndian>(*n)?,
LockTime::Time(t) => writer
.write_u32::<LittleEndian>(t.timestamp().try_into().expect("time is in range"))?,
}
Ok(())
}
}
impl ZcashDeserialize for LockTime {
#[allow(clippy::unwrap_in_result)]
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
let n = reader.read_u32::<LittleEndian>()?;
if n < Self::MIN_TIMESTAMP.try_into().expect("fits in u32") {
Ok(LockTime::Height(block::Height(n)))
} else {
Ok(LockTime::Time(
Utc.timestamp_opt(n.into(), 0)
.single()
.expect("in-range number of seconds and valid nanosecond"),
))
}
}
}