Skip to content

Commit 357ce4f

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

1 file changed

Lines changed: 64 additions & 1 deletion

File tree

tai64/src/lib.rs

Lines changed: 64 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}")
@@ -360,4 +385,42 @@ mod tests {
360385

361386
assert_eq!(t, t1);
362387
}
388+
389+
#[test]
390+
#[should_panic(expected = "overflow when adding duration to instant")]
391+
fn to_system_time_panics_from_slice() {
392+
let malicious_timestamp: [u8; 12] = [
393+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
394+
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,
412+
0x00, 0x00, 0x00, 0x00,
413+
];
414+
let tai = Tai64N::from_slice(&malicious).unwrap();
415+
let result: Result<SystemTime, _> = tai.try_into();
416+
assert_eq!(result, Err(Error::TimestampOverflow));
417+
}
418+
419+
#[test]
420+
fn try_into_system_time_roundtrip() {
421+
let original = SystemTime::now();
422+
let tai = Tai64N::from(original);
423+
let recovered: SystemTime = tai.try_into().expect("should be representable");
424+
assert_eq!(original, recovered);
425+
}
363426
}

0 commit comments

Comments
 (0)