#[cfg(feature = "alloc")]
use alloc::{boxed::Box, string::String};
use core::fmt;
use internals::write_err;
#[cfg(feature = "alloc")]
use crate::parse;
use crate::parse::ParseIntError;
pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Height(u32);
impl Height {
pub const ZERO: Self = Height(0);
pub const MIN: Self = Self::ZERO;
pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1);
pub fn from_hex(s: &str) -> Result<Self, ParseHeightError> {
parse_hex(s, Self::from_consensus)
}
#[inline]
pub const fn from_consensus(n: u32) -> Result<Height, ConversionError> {
if is_block_height(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_height(n))
}
}
#[inline]
pub const fn to_consensus_u32(self) -> u32 { self.0 }
}
impl fmt::Display for Height {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
crate::impl_parse_str!(Height, ParseHeightError, parser(Height::from_consensus));
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParseHeightError(ParseError);
impl fmt::Display for ParseHeightError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseHeightError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
}
impl From<ParseError> for ParseHeightError {
fn from(value: ParseError) -> Self { Self(value) }
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Height {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let u = u32::deserialize(deserializer)?;
Ok(Height::from_consensus(u).map_err(serde::de::Error::custom)?)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Height {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_consensus_u32().serialize(serializer)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Time(u32);
impl Time {
pub const MIN: Self = Time(LOCK_TIME_THRESHOLD);
pub const MAX: Self = Time(u32::MAX);
pub fn from_hex(s: &str) -> Result<Self, ParseTimeError> { parse_hex(s, Self::from_consensus) }
#[inline]
pub const fn from_consensus(n: u32) -> Result<Time, ConversionError> {
if is_block_time(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_time(n))
}
}
#[inline]
pub const fn to_consensus_u32(self) -> u32 { self.0 }
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
crate::impl_parse_str!(Time, ParseTimeError, parser(Time::from_consensus));
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Time {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let u = u32::deserialize(deserializer)?;
Ok(Time::from_consensus(u).map_err(serde::de::Error::custom)?)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Time {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_consensus_u32().serialize(serializer)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParseTimeError(ParseError);
impl fmt::Display for ParseTimeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.display(f, "block height", LOCK_TIME_THRESHOLD, u32::MAX)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseTimeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
}
impl From<ParseError> for ParseTimeError {
fn from(value: ParseError) -> Self { Self(value) }
}
fn parser<T, E, S, F>(f: F) -> impl FnOnce(S) -> Result<T, E>
where
E: From<ParseError>,
S: AsRef<str> + Into<String>,
F: FnOnce(u32) -> Result<T, ConversionError>,
{
move |s| {
let n = s.as_ref().parse::<i64>().map_err(ParseError::invalid_int(s))?;
let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?;
f(n).map_err(ParseError::from).map_err(Into::into)
}
}
fn parse_hex<T, E, S, F>(s: S, f: F) -> Result<T, E>
where
E: From<ParseError>,
S: AsRef<str> + Into<String>,
F: FnOnce(u32) -> Result<T, ConversionError>,
{
let n = i64::from_str_radix(parse::hex_remove_optional_prefix(s.as_ref()), 16)
.map_err(ParseError::invalid_int(s))?;
let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?;
f(n).map_err(ParseError::from).map_err(Into::into)
}
pub const fn is_block_height(n: u32) -> bool { n < LOCK_TIME_THRESHOLD }
pub const fn is_block_time(n: u32) -> bool { n >= LOCK_TIME_THRESHOLD }
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ConversionError {
unit: LockTimeUnit,
input: u32,
}
impl ConversionError {
const fn invalid_height(n: u32) -> Self { Self { unit: LockTimeUnit::Blocks, input: n } }
const fn invalid_time(n: u32) -> Self { Self { unit: LockTimeUnit::Seconds, input: n } }
}
impl fmt::Display for ConversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid lock time value {}, {}", self.input, self.unit)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ConversionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum LockTimeUnit {
Blocks,
Seconds,
}
impl fmt::Display for LockTimeUnit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTimeUnit::*;
match *self {
Blocks => write!(f, "expected lock-by-blockheight (must be < {})", LOCK_TIME_THRESHOLD),
Seconds => write!(f, "expected lock-by-blocktime (must be >= {})", LOCK_TIME_THRESHOLD),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
enum ParseError {
InvalidInteger { source: core::num::ParseIntError, input: String },
Conversion(i64),
}
internals::impl_from_infallible!(ParseError);
impl ParseError {
fn invalid_int<S: Into<String>>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self {
move |source| Self::InvalidInteger { source, input: s.into() }
}
fn display(
&self,
f: &mut fmt::Formatter<'_>,
subject: &str,
lower_bound: u32,
upper_bound: u32,
) -> fmt::Result {
use core::num::IntErrorKind;
use ParseError::*;
match self {
InvalidInteger { source, input } if *source.kind() == IntErrorKind::PosOverflow => {
write!(f, "{} {} is above limit {}", subject, input, upper_bound)
}
InvalidInteger { source, input } if *source.kind() == IntErrorKind::NegOverflow => {
write!(f, "{} {} is below limit {}", subject, input, lower_bound)
}
InvalidInteger { source, input } => {
write_err!(f, "failed to parse {} as {}", input, subject; source)
}
Conversion(value) if *value < i64::from(lower_bound) => {
write!(f, "{} {} is below limit {}", subject, value, lower_bound)
}
Conversion(value) => {
write!(f, "{} {} is above limit {}", subject, value, upper_bound)
}
}
}
#[cfg(feature = "std")]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use core::num::IntErrorKind;
use ParseError::*;
match self {
InvalidInteger { source, .. } if *source.kind() == IntErrorKind::PosOverflow => None,
InvalidInteger { source, .. } if *source.kind() == IntErrorKind::NegOverflow => None,
InvalidInteger { source, .. } => Some(source),
Conversion(_) => None,
}
}
}
impl From<ParseIntError> for ParseError {
fn from(value: ParseIntError) -> Self {
Self::InvalidInteger { source: value.source, input: value.input }
}
}
impl From<ConversionError> for ParseError {
fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
}
#[cfg(test)]
mod tests {
#[cfg(feature = "serde")]
use internals::serde_round_trip;
use super::*;
#[test]
fn time_from_str_hex_happy_path() {
let actual = Time::from_hex("0x6289C350").unwrap();
let expected = Time::from_consensus(0x6289C350).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn time_from_str_hex_no_prefix_happy_path() {
let time = Time::from_hex("6289C350").unwrap();
assert_eq!(time, Time(0x6289C350));
}
#[test]
fn time_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93";
let result = Time::from_hex(hex);
assert!(result.is_err());
}
#[test]
fn height_from_str_hex_happy_path() {
let actual = Height::from_hex("0xBA70D").unwrap();
let expected = Height(0xBA70D);
assert_eq!(actual, expected);
}
#[test]
fn height_from_str_hex_no_prefix_happy_path() {
let height = Height::from_hex("BA70D").unwrap();
assert_eq!(height, Height(0xBA70D));
}
#[test]
fn height_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93";
let result = Height::from_hex(hex);
assert!(result.is_err());
}
#[test]
#[cfg(feature = "serde")]
pub fn encode_decode_height() {
serde_round_trip!(Height::ZERO);
serde_round_trip!(Height::MIN);
serde_round_trip!(Height::MAX);
}
#[test]
#[cfg(feature = "serde")]
pub fn encode_decode_time() {
serde_round_trip!(Time::MIN);
serde_round_trip!(Time::MAX);
}
}