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