Skip to main content

deep_time/
clock_model.rs

1use crate::{Drift, Dt, Scale};
2
3/// A fully self-describing custom relativistic time scale.
4///
5/// Bundles a base `Scale` (`Custom`) with the quadratic
6/// polynomial and reference epoch needed for exact conversion to any other scale
7/// (typically TT or TDB).
8#[derive(Copy, Clone, Debug, PartialEq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[cfg_attr(feature = "js", derive(tsify::Tsify))]
11pub struct ClockModel {
12    /// Base scale (usually `Custom`)
13    pub base: Scale,
14    /// Epoch at which the polynomial was defined (e.g. last ground contact)
15    pub reference: Dt,
16    /// Offset
17    pub drift: Drift,
18}
19
20impl ClockModel {
21    /// Creates a new self-describing scale (most common for Proper time).
22    #[inline]
23    pub const fn new(base: Scale, reference: Dt, drift: Drift) -> Self {
24        Self {
25            base,
26            reference,
27            drift,
28        }
29    }
30}
31
32#[cfg(feature = "wire")]
33impl ClockModel {
34    /// Current wire format version.
35    pub const WIRE_VERSION: u8 = 1;
36
37    /// Size of the canonical wire representation in bytes.
38    pub const WIRE_SIZE: usize = 1 + Scale::WIRE_SIZE + Dt::WIRE_SIZE + Drift::WIRE_SIZE;
39
40    /// Serializes this self-describing `ClockModel` into a fixed buffer.
41    ///
42    /// # Wire Format
43    ///
44    /// - Byte `0`: Version (`WIRE_VERSION`)
45    /// - Byte `1`: `base` (`Scale`)
46    /// - Bytes `2..20`: `reference` (`Dt`)
47    /// - Bytes `20..71`: `drift` (`Drift`)
48    pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
49        let mut buf = [0u8; Self::WIRE_SIZE];
50        buf[0] = Self::WIRE_VERSION;
51        buf[1] = self.base as u8;
52
53        let tp = self.reference.to_wire_bytes();
54        buf[2..2 + Dt::WIRE_SIZE].copy_from_slice(&tp);
55
56        let cd = self.drift.to_wire_bytes();
57        buf[2 + Dt::WIRE_SIZE..].copy_from_slice(&cd);
58
59        buf
60    }
61
62    /// Deserializes a `ClockModel` from exactly `WIRE_SIZE` bytes of wire data.
63    ///
64    /// Returns `None` if the version byte is unknown or any nested component
65    /// fails validation.
66    ///
67    /// ## Security
68    ///
69    /// This function is safe to call with arbitrary untrusted data because:
70    /// - Fixed total size eliminates length-prefix vulnerabilities
71    /// - Validation is performed at every layer
72    /// - No allocation, no `unsafe`, no possibility of code execution
73    /// - Returns `None` on any invalid or malicious input
74    pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
75        if bytes.len() != Self::WIRE_SIZE {
76            return None;
77        }
78
79        if bytes[0] != Self::WIRE_VERSION {
80            return None;
81        }
82
83        let base = Scale::from_u8(bytes[1]);
84        let reference = Dt::from_wire_bytes(&bytes[2..2 + Dt::WIRE_SIZE])?;
85        let drift = Drift::from_wire_bytes(&bytes[2 + Dt::WIRE_SIZE..])?;
86
87        Some(Self {
88            base,
89            reference,
90            drift,
91        })
92    }
93}
94
95impl Dt {
96    /// Creates a new custom time model using this exact instant as the reference epoch.
97    /// - The supplied [`Drift`] defines the relativistic model for the timescale.
98    /// - The returned [`ClockModel`] can be used to convert to or from the custom timescale.
99    #[inline]
100    pub const fn to_model_as_epoch(self, drift: Drift) -> ClockModel {
101        ClockModel::new(Scale::Custom, self, drift)
102    }
103}