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}