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