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