1use super::cmp::CanonicalOrd;
9use super::scan::{Scan, Scanner};
10use super::wire::{Compose, Composer, Parse, ParseError};
11#[cfg(feature = "chrono")]
12use chrono::{DateTime, TimeZone};
13use core::cmp::Ordering;
14use core::{cmp, fmt, str};
15#[cfg(all(feature = "std", test))]
16use mock_instant::thread_local::{SystemTime, UNIX_EPOCH};
17use octseq::parse::Parser;
18#[cfg(all(feature = "std", not(test)))]
19use std::time::{SystemTime, UNIX_EPOCH};
20
21#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct Serial(pub u32);
50
51impl Serial {
52 #[cfg(feature = "std")]
54 #[must_use]
55 pub fn now() -> Self {
56 let now = SystemTime::now();
57 let value = match now.duration_since(UNIX_EPOCH) {
58 Ok(value) => value,
59 Err(_) => UNIX_EPOCH.duration_since(now).unwrap(),
60 };
61 Self(value.as_secs() as u32)
62 }
63
64 #[must_use]
66 pub fn from_be_bytes(bytes: [u8; 4]) -> Self {
67 Self(u32::from_be_bytes(bytes))
68 }
69
70 #[must_use]
72 pub fn into_int(self) -> u32 {
73 self.0
74 }
75
76 #[allow(clippy::should_implement_trait)]
87 #[must_use]
88 pub fn add(self, other: u32) -> Self {
89 assert!(other <= 0x7FFF_FFFF);
90 Serial(self.0.wrapping_add(other))
91 }
92
93 pub fn scan<S: Scanner>(scanner: &mut S) -> Result<Self, S::Error> {
94 u32::scan(scanner).map(Into::into)
95 }
96}
97
98impl Serial {
101 pub const COMPOSE_LEN: u16 = u32::COMPOSE_LEN;
102
103 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
104 parser: &mut Parser<'_, Octs>,
105 ) -> Result<Self, ParseError> {
106 u32::parse(parser).map(Into::into)
107 }
108
109 pub fn compose<Target: Composer + ?Sized>(
110 &self,
111 target: &mut Target,
112 ) -> Result<(), Target::AppendError> {
113 self.0.compose(target)
114 }
115}
116
117impl From<u32> for Serial {
120 fn from(value: u32) -> Serial {
121 Serial(value)
122 }
123}
124
125impl From<Serial> for u32 {
126 fn from(serial: Serial) -> u32 {
127 serial.0
128 }
129}
130
131impl From<jiff::Timestamp> for Serial {
132 fn from(value: jiff::Timestamp) -> Self {
133 Self(value.as_second() as u32)
134 }
135}
136
137#[cfg(feature = "chrono")]
138#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
139impl<T: TimeZone> From<DateTime<T>> for Serial {
140 fn from(value: DateTime<T>) -> Self {
141 Self(value.timestamp() as u32)
142 }
143}
144
145impl str::FromStr for Serial {
146 type Err = <u32 as str::FromStr>::Err;
147
148 fn from_str(s: &str) -> Result<Self, Self::Err> {
149 <u32 as str::FromStr>::from_str(s).map(Into::into)
150 }
151}
152
153impl fmt::Display for Serial {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 write!(f, "{}", self.0)
158 }
159}
160
161impl cmp::PartialOrd for Serial {
164 fn partial_cmp(&self, other: &Serial) -> Option<cmp::Ordering> {
165 match self.0.cmp(&other.0) {
166 Ordering::Equal => Some(Ordering::Equal),
167 Ordering::Less => {
168 let sub = other.0 - self.0;
169 match sub.cmp(&0x8000_0000) {
170 Ordering::Less => Some(Ordering::Less),
171 Ordering::Greater => Some(Ordering::Greater),
172 Ordering::Equal => None,
173 }
174 }
175 Ordering::Greater => {
176 let sub = self.0 - other.0;
177 match sub.cmp(&0x8000_0000) {
178 Ordering::Less => Some(Ordering::Greater),
179 Ordering::Greater => Some(Ordering::Less),
180 Ordering::Equal => None,
181 }
182 }
183 }
184 }
185}
186
187impl CanonicalOrd for Serial {
188 fn canonical_cmp(&self, other: &Self) -> cmp::Ordering {
189 self.0.cmp(&other.0)
190 }
191}
192
193#[derive(Clone, Copy, Debug)]
196pub struct IllegalSignatureTime(());
197
198impl fmt::Display for IllegalSignatureTime {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 f.write_str("illegal signature time")
201 }
202}
203
204impl core::error::Error for IllegalSignatureTime {}
205
206#[cfg(test)]
209mod test {
210 use super::*;
211
212 #[test]
213 fn good_addition() {
214 assert_eq!(Serial(0).add(4), Serial(4));
215 assert_eq!(
216 Serial(0xFF00_0000).add(0x0F00_0000),
217 Serial(
218 ((0xFF00_0000u64 + 0x0F00_0000u64) % 0x1_0000_0000) as u32
219 )
220 );
221 }
222
223 #[test]
224 #[should_panic]
225 fn bad_addition() {
226 let _ = Serial(0).add(0x8000_0000);
227 }
228
229 #[test]
230 fn comparison() {
231 use core::cmp::Ordering::*;
232
233 assert_eq!(Serial(12), Serial(12));
234 assert_ne!(Serial(12), Serial(112));
235
236 assert_eq!(Serial(12).partial_cmp(&Serial(12)), Some(Equal));
237
238 assert_eq!(Serial(12).partial_cmp(&Serial(13)), Some(Less));
241 assert_ne!(
242 Serial(12).partial_cmp(&Serial(3_000_000_012)),
243 Some(Less)
244 );
245
246 assert_eq!(
248 Serial(3_000_000_012).partial_cmp(&Serial(12)),
249 Some(Less)
250 );
251 assert_ne!(Serial(13).partial_cmp(&Serial(12)), Some(Less));
252
253 assert_eq!(
256 Serial(12).partial_cmp(&Serial(3_000_000_012)),
257 Some(Greater)
258 );
259 assert_ne!(Serial(12).partial_cmp(&Serial(13)), Some(Greater));
260
261 assert_eq!(Serial(13).partial_cmp(&Serial(12)), Some(Greater));
263 assert_ne!(
264 Serial(3_000_000_012).partial_cmp(&Serial(12)),
265 Some(Greater)
266 );
267
268 assert_eq!(Serial(1).partial_cmp(&Serial(0x8000_0001)), None);
270 assert_eq!(Serial(0x8000_0001).partial_cmp(&Serial(1)), None);
271 }
272}