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