Skip to content

Commit 5039617

Browse files
committed
tai64: add TryFrom<Tai64N> for SystemTime to avoid panic
1 parent 686de48 commit 5039617

File tree

1 file changed

+63
-1
lines changed

1 file changed

+63
-1
lines changed

tai64/src/lib.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,15 @@ impl Tai64N {
203203
}
204204
}
205205

206-
/// Convert `TAI64N`to `SystemTime`.
206+
/// Convert `TAI64N` to `SystemTime`.
207+
///
208+
/// # Panics
209+
///
210+
/// Panics if the timestamp cannot be represented as `SystemTime`. This can
211+
/// occur when the `Tai64N` value is outside the range representable by the
212+
/// platform's `SystemTime` (typically backed by `i64` seconds from Unix epoch).
213+
///
214+
/// For a non-panicking alternative, use `SystemTime::try_from(tai64n)`.
207215
#[cfg(feature = "std")]
208216
pub fn to_system_time(self) -> SystemTime {
209217
match self.duration_since(&Self::UNIX_EPOCH) {
@@ -265,6 +273,19 @@ impl From<SystemTime> for Tai64N {
265273
}
266274
}
267275

276+
#[cfg(feature = "std")]
277+
impl TryFrom<Tai64N> for SystemTime {
278+
type Error = Error;
279+
280+
fn try_from(tai: Tai64N) -> Result<Self, Self::Error> {
281+
match tai.duration_since(&Tai64N::UNIX_EPOCH) {
282+
Ok(d) => UNIX_EPOCH.checked_add(d),
283+
Err(d) => UNIX_EPOCH.checked_sub(d),
284+
}
285+
.ok_or(Error::TimestampOverflow)
286+
}
287+
}
288+
268289
#[allow(clippy::suspicious_arithmetic_impl)]
269290
impl ops::Add<Duration> for Tai64N {
270291
type Output = Self;
@@ -320,13 +341,17 @@ pub enum Error {
320341

321342
/// Nanosecond part must be <= 999999999.
322343
NanosInvalid,
344+
345+
/// Timestamp cannot be represented as `SystemTime` (overflow/underflow).
346+
TimestampOverflow,
323347
}
324348

325349
impl fmt::Display for Error {
326350
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327351
let description = match self {
328352
Error::LengthInvalid => "length invalid",
329353
Error::NanosInvalid => "invalid number of nanoseconds",
354+
Error::TimestampOverflow => "timestamp cannot be represented as SystemTime",
330355
};
331356

332357
write!(f, "{description}")
@@ -337,6 +362,7 @@ impl fmt::Display for Error {
337362
impl std::error::Error for Error {}
338363

339364
#[cfg(all(test, feature = "std"))]
365+
#[allow(clippy::unwrap_used)]
340366
mod tests {
341367
use super::*;
342368

@@ -360,4 +386,40 @@ mod tests {
360386

361387
assert_eq!(t, t1);
362388
}
389+
390+
#[test]
391+
#[should_panic(expected = "overflow when adding duration to instant")]
392+
fn to_system_time_panics_from_slice() {
393+
let malicious_timestamp: [u8; 12] = [
394+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
395+
];
396+
397+
let timestamp = Tai64N::from_slice(&malicious_timestamp).unwrap();
398+
let _ = timestamp.to_system_time(); // panics here
399+
}
400+
401+
#[test]
402+
fn try_into_system_time_success() {
403+
let tai = Tai64N::now();
404+
let result: Result<SystemTime, _> = tai.try_into();
405+
assert!(result.is_ok());
406+
}
407+
408+
#[test]
409+
fn try_into_system_time_overflow() {
410+
let malicious: [u8; 12] = [
411+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
412+
];
413+
let tai = Tai64N::from_slice(&malicious).unwrap();
414+
let result: Result<SystemTime, _> = tai.try_into();
415+
assert_eq!(result, Err(Error::TimestampOverflow));
416+
}
417+
418+
#[test]
419+
fn try_into_system_time_roundtrip() {
420+
let original = SystemTime::now();
421+
let tai = Tai64N::from(original);
422+
let recovered: SystemTime = tai.try_into().expect("should be representable");
423+
assert_eq!(original, recovered);
424+
}
363425
}

0 commit comments

Comments
 (0)