1#[cfg(feature = "alloc")]
6use alloc::{boxed::Box, string::String};
7use core::fmt;
8
9use internals::write_err;
10
11#[cfg(feature = "alloc")]
12use crate::parse;
13use crate::parse::ParseIntError;
14
15pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub struct Height(u32);
31
32impl Height {
33 pub const ZERO: Self = Height(0);
35
36 pub const MIN: Self = Self::ZERO;
38
39 pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1);
41
42 pub fn from_hex(s: &str) -> Result<Self, ParseHeightError> {
46 parse_hex(s, Self::from_consensus)
47 }
48
49 #[inline]
65 pub const fn from_consensus(n: u32) -> Result<Height, ConversionError> {
66 if is_block_height(n) {
67 Ok(Self(n))
68 } else {
69 Err(ConversionError::invalid_height(n))
70 }
71 }
72
73 #[inline]
75 pub const fn to_consensus_u32(self) -> u32 { self.0 }
76}
77
78impl fmt::Display for Height {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
80}
81
82crate::impl_parse_str!(Height, ParseHeightError, parser(Height::from_consensus));
83
84#[derive(Debug, Clone, Eq, PartialEq)]
86pub struct ParseHeightError(ParseError);
87
88impl fmt::Display for ParseHeightError {
89 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90 self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1)
91 }
92}
93
94#[cfg(feature = "std")]
95impl std::error::Error for ParseHeightError {
96 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
98}
99
100impl From<ParseError> for ParseHeightError {
101 fn from(value: ParseError) -> Self { Self(value) }
102}
103
104#[cfg(feature = "serde")]
105impl<'de> serde::Deserialize<'de> for Height {
106 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107 where
108 D: serde::Deserializer<'de>,
109 {
110 let u = u32::deserialize(deserializer)?;
111 Ok(Height::from_consensus(u).map_err(serde::de::Error::custom)?)
112 }
113}
114
115#[cfg(feature = "serde")]
116impl serde::Serialize for Height {
117 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
118 where
119 S: serde::Serializer,
120 {
121 self.to_consensus_u32().serialize(serializer)
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
131pub struct Time(u32);
132
133impl Time {
134 pub const MIN: Self = Time(LOCK_TIME_THRESHOLD);
136
137 pub const MAX: Self = Time(u32::MAX);
139
140 pub fn from_hex(s: &str) -> Result<Self, ParseTimeError> { parse_hex(s, Self::from_consensus) }
144
145 #[inline]
161 pub const fn from_consensus(n: u32) -> Result<Time, ConversionError> {
162 if is_block_time(n) {
163 Ok(Self(n))
164 } else {
165 Err(ConversionError::invalid_time(n))
166 }
167 }
168
169 #[inline]
171 pub const fn to_consensus_u32(self) -> u32 { self.0 }
172}
173
174impl fmt::Display for Time {
175 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
176}
177
178crate::impl_parse_str!(Time, ParseTimeError, parser(Time::from_consensus));
179
180#[cfg(feature = "serde")]
181impl<'de> serde::Deserialize<'de> for Time {
182 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
183 where
184 D: serde::Deserializer<'de>,
185 {
186 let u = u32::deserialize(deserializer)?;
187 Ok(Time::from_consensus(u).map_err(serde::de::Error::custom)?)
188 }
189}
190
191#[cfg(feature = "serde")]
192impl serde::Serialize for Time {
193 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
194 where
195 S: serde::Serializer,
196 {
197 self.to_consensus_u32().serialize(serializer)
198 }
199}
200
201#[derive(Debug, Clone, Eq, PartialEq)]
203pub struct ParseTimeError(ParseError);
204
205impl fmt::Display for ParseTimeError {
206 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
207 self.0.display(f, "block height", LOCK_TIME_THRESHOLD, u32::MAX)
208 }
209}
210
211#[cfg(feature = "std")]
212impl std::error::Error for ParseTimeError {
213 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
215}
216
217impl From<ParseError> for ParseTimeError {
218 fn from(value: ParseError) -> Self { Self(value) }
219}
220
221fn parser<T, E, S, F>(f: F) -> impl FnOnce(S) -> Result<T, E>
222where
223 E: From<ParseError>,
224 S: AsRef<str> + Into<String>,
225 F: FnOnce(u32) -> Result<T, ConversionError>,
226{
227 move |s| {
228 let n = s.as_ref().parse::<i64>().map_err(ParseError::invalid_int(s))?;
229 let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?;
230 f(n).map_err(ParseError::from).map_err(Into::into)
231 }
232}
233
234fn parse_hex<T, E, S, F>(s: S, f: F) -> Result<T, E>
235where
236 E: From<ParseError>,
237 S: AsRef<str> + Into<String>,
238 F: FnOnce(u32) -> Result<T, ConversionError>,
239{
240 let n = i64::from_str_radix(parse::hex_remove_optional_prefix(s.as_ref()), 16)
241 .map_err(ParseError::invalid_int(s))?;
242 let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?;
243 f(n).map_err(ParseError::from).map_err(Into::into)
244}
245
246pub const fn is_block_height(n: u32) -> bool { n < LOCK_TIME_THRESHOLD }
248
249pub const fn is_block_time(n: u32) -> bool { n >= LOCK_TIME_THRESHOLD }
251
252#[derive(Debug, Clone, PartialEq, Eq)]
254#[non_exhaustive]
255pub struct ConversionError {
256 unit: LockTimeUnit,
258 input: u32,
260}
261
262impl ConversionError {
263 const fn invalid_height(n: u32) -> Self { Self { unit: LockTimeUnit::Blocks, input: n } }
265
266 const fn invalid_time(n: u32) -> Self { Self { unit: LockTimeUnit::Seconds, input: n } }
268}
269
270impl fmt::Display for ConversionError {
271 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
272 write!(f, "invalid lock time value {}, {}", self.input, self.unit)
273 }
274}
275
276#[cfg(feature = "std")]
277impl std::error::Error for ConversionError {
278 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
279}
280
281#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
283enum LockTimeUnit {
284 Blocks,
286 Seconds,
288}
289
290impl fmt::Display for LockTimeUnit {
291 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
292 use LockTimeUnit::*;
293
294 match *self {
295 Blocks => write!(f, "expected lock-by-blockheight (must be < {})", LOCK_TIME_THRESHOLD),
296 Seconds => write!(f, "expected lock-by-blocktime (must be >= {})", LOCK_TIME_THRESHOLD),
297 }
298 }
299}
300
301#[derive(Debug, Clone, Eq, PartialEq)]
303enum ParseError {
304 InvalidInteger { source: core::num::ParseIntError, input: String },
305 Conversion(i64),
308}
309
310internals::impl_from_infallible!(ParseError);
311
312impl ParseError {
313 fn invalid_int<S: Into<String>>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self {
314 move |source| Self::InvalidInteger { source, input: s.into() }
315 }
316
317 fn display(
318 &self,
319 f: &mut fmt::Formatter<'_>,
320 subject: &str,
321 lower_bound: u32,
322 upper_bound: u32,
323 ) -> fmt::Result {
324 use core::num::IntErrorKind;
325
326 use ParseError::*;
327
328 match self {
329 InvalidInteger { source, input } if *source.kind() == IntErrorKind::PosOverflow => {
330 write!(f, "{} {} is above limit {}", subject, input, upper_bound)
331 }
332 InvalidInteger { source, input } if *source.kind() == IntErrorKind::NegOverflow => {
333 write!(f, "{} {} is below limit {}", subject, input, lower_bound)
334 }
335 InvalidInteger { source, input } => {
336 write_err!(f, "failed to parse {} as {}", input, subject; source)
337 }
338 Conversion(value) if *value < i64::from(lower_bound) => {
339 write!(f, "{} {} is below limit {}", subject, value, lower_bound)
340 }
341 Conversion(value) => {
342 write!(f, "{} {} is above limit {}", subject, value, upper_bound)
343 }
344 }
345 }
346
347 #[cfg(feature = "std")]
349 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
350 use core::num::IntErrorKind;
351
352 use ParseError::*;
353
354 match self {
355 InvalidInteger { source, .. } if *source.kind() == IntErrorKind::PosOverflow => None,
356 InvalidInteger { source, .. } if *source.kind() == IntErrorKind::NegOverflow => None,
357 InvalidInteger { source, .. } => Some(source),
358 Conversion(_) => None,
359 }
360 }
361}
362
363impl From<ParseIntError> for ParseError {
364 fn from(value: ParseIntError) -> Self {
365 Self::InvalidInteger { source: value.source, input: value.input }
366 }
367}
368
369impl From<ConversionError> for ParseError {
370 fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
371}
372
373#[cfg(test)]
374mod tests {
375 #[cfg(feature = "serde")]
376 use internals::serde_round_trip;
377
378 use super::*;
379
380 #[test]
381 fn time_from_str_hex_happy_path() {
382 let actual = Time::from_hex("0x6289C350").unwrap();
383 let expected = Time::from_consensus(0x6289C350).unwrap();
384 assert_eq!(actual, expected);
385 }
386
387 #[test]
388 fn time_from_str_hex_no_prefix_happy_path() {
389 let time = Time::from_hex("6289C350").unwrap();
390 assert_eq!(time, Time(0x6289C350));
391 }
392
393 #[test]
394 fn time_from_str_hex_invalid_hex_should_err() {
395 let hex = "0xzb93";
396 let result = Time::from_hex(hex);
397 assert!(result.is_err());
398 }
399
400 #[test]
401 fn height_from_str_hex_happy_path() {
402 let actual = Height::from_hex("0xBA70D").unwrap();
403 let expected = Height(0xBA70D);
404 assert_eq!(actual, expected);
405 }
406
407 #[test]
408 fn height_from_str_hex_no_prefix_happy_path() {
409 let height = Height::from_hex("BA70D").unwrap();
410 assert_eq!(height, Height(0xBA70D));
411 }
412
413 #[test]
414 fn height_from_str_hex_invalid_hex_should_err() {
415 let hex = "0xzb93";
416 let result = Height::from_hex(hex);
417 assert!(result.is_err());
418 }
419
420 #[test]
421 #[cfg(feature = "serde")]
422 pub fn encode_decode_height() {
423 serde_round_trip!(Height::ZERO);
424 serde_round_trip!(Height::MIN);
425 serde_round_trip!(Height::MAX);
426 }
427
428 #[test]
429 #[cfg(feature = "serde")]
430 pub fn encode_decode_time() {
431 serde_round_trip!(Time::MIN);
432 serde_round_trip!(Time::MAX);
433 }
434}