deep_time/dt/
to_bin_ccsds.rs1use crate::{Dt, DtErr, DtErrKind, SEC_PER_DAYI64, Scale, an_err};
2
3impl Dt {
4 pub const CCSDS_C_AND_D_MAX_SIZE: usize = 32;
6
7 pub fn to_ccsds_c(
19 &self,
20 current: Scale,
21 n_coarse: u8,
22 n_frac: u8,
23 extension: bool,
24 ) -> Result<([u8; Self::CCSDS_C_AND_D_MAX_SIZE], usize), DtErr> {
25 if !(1..=7).contains(&n_coarse) {
26 return Err(an_err!(DtErrKind::OutOfRange, "coarse: {}", n_coarse,));
27 } else if n_frac > 10 {
28 return Err(an_err!(DtErrKind::OutOfRange, "frac: {}", n_frac));
29 }
30
31 let tai = self.to(current, Scale::TAI);
32
33 const EPOCH_OFFSET: i64 = 1_325_419_167;
34 let total_tai_seconds = tai.sec + EPOCH_OFFSET;
35
36 let frac_scaled = if n_frac == 0 {
37 0u128
38 } else {
39 let scale = 1u128 << (8 * n_frac as u32);
40 (tai.attos as u128 * scale + 500_000_000_000_000_000) / 1_000_000_000_000_000_000
41 };
42
43 let mut buf = [0u8; Self::CCSDS_C_AND_D_MAX_SIZE];
44 let mut pos = 0usize;
45
46 let needs_extension = n_coarse > 4 || n_frac > 3 || extension;
48
49 let base_coarse = if n_coarse <= 4 { n_coarse - 1 } else { 3 };
51 let base_frac = if n_frac <= 3 { n_frac } else { 3 };
52
53 let mut p1 = 0b0001_0000u8; p1 |= (base_coarse << 2) & 0b0000_1100;
56 p1 |= base_frac & 0b0000_0011;
57 if needs_extension {
58 p1 |= 0b1000_0000;
59 }
60 buf[pos] = p1;
61 pos += 1;
62
63 if needs_extension {
64 let add_coarse = n_coarse.saturating_sub(4); let add_frac = n_frac.saturating_sub(3); let mut p2 = 0u8;
69 p2 |= (add_coarse & 0b11) << 5; p2 |= (add_frac & 0b111) << 2; buf[pos] = p2;
74 pos += 1;
75 }
76
77 let coarse = total_tai_seconds as u64;
79 for i in (0..n_coarse).rev() {
80 buf[pos] = (coarse >> (i as u32 * 8)) as u8;
81 pos += 1;
82 }
83
84 for i in (0..n_frac).rev() {
86 buf[pos] = (frac_scaled >> (i as u32 * 8)) as u8;
87 pos += 1;
88 }
89
90 Ok((buf, pos))
91 }
92
93 pub fn to_ccsds_d(
99 &self,
100 current: Scale,
101 n_day: u8,
102 sub_ms_code: u8,
103 extension: bool,
104 ) -> Result<([u8; Self::CCSDS_C_AND_D_MAX_SIZE], usize), DtErr> {
105 if !matches!(n_day, 2 | 3) {
106 return Err(an_err!(DtErrKind::InvalidNumber, "n_day: {}", n_day));
107 } else if !matches!(sub_ms_code, 0..=2) {
108 return Err(an_err!(DtErrKind::InvalidItem, "sub-millisecond code"));
109 }
110
111 let utc = self.to(current, Scale::UTC);
112
113 const EPOCH_OFFSET: i64 = 1_325_419_135;
116 let total_utc_seconds = utc.sec + EPOCH_OFFSET;
117
118 let day_count = (total_utc_seconds / SEC_PER_DAYI64) as u64;
119 let sec_of_day = (total_utc_seconds % SEC_PER_DAYI64) as u64;
120
121 let additional_ms =
123 ((utc.attos as u128 + 500_000_000_000_000) / 1_000_000_000_000_000) as u64;
124 let millis_of_day = sec_of_day * 1000 + additional_ms;
125
126 let remaining_attos_in_ms = (utc.attos as u128) % 1_000_000_000_000_000;
128
129 let frac_scaled = match sub_ms_code {
130 0 => 0u64,
131 1 => ((remaining_attos_in_ms * 65_536u128) / 1_000_000_000_000_000u128) as u64,
132 2 => {
133 const PS_SCALE: u128 = 1u128 << 32;
134 ((remaining_attos_in_ms * PS_SCALE) / 1_000_000_000_000_000u128) as u64
135 }
136 _ => return Err(an_err!(DtErrKind::InvalidItem, "sub-millisecond code")),
137 };
138
139 let mut buf = [0u8; Self::CCSDS_C_AND_D_MAX_SIZE];
140 let mut pos = 0usize;
141
142 let mut p1 = 0b0100_0000u8;
143 if extension {
144 p1 |= 0b1000_0000;
145 }
146 if n_day == 3 {
147 p1 |= 0b0000_0100;
148 }
149 p1 |= sub_ms_code;
150 buf[pos] = p1;
151 pos += 1;
152
153 if extension {
154 buf[pos] = 0;
155 pos += 1;
156 }
157
158 for i in (0..n_day).rev() {
159 buf[pos] = (day_count >> (i * 8)) as u8;
160 pos += 1;
161 }
162
163 for i in (0..4).rev() {
164 buf[pos] = (millis_of_day >> (i * 8)) as u8;
165 pos += 1;
166 }
167
168 let n_frac = match sub_ms_code {
169 0 => 0,
170 1 => 2,
171 2 => 4,
172 _ => return Err(an_err!(DtErrKind::InvalidItem, "sub-millisecond code")),
173 };
174 for i in (0..n_frac).rev() {
175 buf[pos] = (frac_scaled >> (i * 8)) as u8;
176 pos += 1;
177 }
178
179 Ok((buf, pos))
180 }
181
182 pub const CCSDS_CCS_MAX_SIZE: usize = 14; pub fn to_ccsds_ccs(
203 &self,
204 current: Scale,
205 use_doy: bool,
206 n_subsec: u8,
207 ) -> Result<([u8; Self::CCSDS_CCS_MAX_SIZE], usize), DtErr> {
208 if n_subsec > 6 {
209 return Err(an_err!(DtErrKind::OutOfRange, "n_subsec: {}", n_subsec));
210 }
211
212 let gt = if current.uses_leap_seconds() {
214 self.to_ymdhms_rich_on(current, current)
215 } else {
216 let utc = self.to(current, Scale::UTC);
217 utc.to_ymdhms_rich_on(Scale::UTC, Scale::UTC)
218 };
219
220 let mut buf = [0u8; Self::CCSDS_CCS_MAX_SIZE];
221 let mut pos = 0usize;
222
223 let mut p1 = 0b0101_0000u8; if use_doy {
226 p1 |= 0b0000_1000; }
228 p1 |= n_subsec & 0b0000_0111; buf[pos] = p1;
230 pos += 1;
231
232 let bcd = |val: u32| -> u8 {
234 let hi = (val / 10) as u8;
235 let lo = (val % 10) as u8;
236 (hi << 4) | lo
237 };
238
239 let year = gt.yr as u32;
241 let y_hi = year / 100;
242 let y_lo = year % 100;
243 buf[pos] = bcd(y_hi);
244 buf[pos + 1] = bcd(y_lo);
245 pos += 2;
246
247 if !use_doy {
249 buf[pos] = bcd(gt.mo as u32);
251 buf[pos + 1] = bcd(gt.day as u32);
252 } else {
253 let doy = gt.day_of_yr as u32;
255 buf[pos] = bcd(doy / 100); buf[pos + 1] = bcd(doy % 100);
257 }
258 pos += 2;
259
260 buf[pos] = bcd(gt.hr as u32);
262 buf[pos + 1] = bcd(gt.min as u32);
263 buf[pos + 2] = bcd(gt.sec as u32); pos += 3;
265
266 if n_subsec > 0 {
268 let decimal_places = (2 * n_subsec) as u32;
269 let scale = 10u128.pow(decimal_places);
270
271 let frac_scaled =
273 (gt.attos as u128 * scale + 500_000_000_000_000_000) / 1_000_000_000_000_000_000;
274
275 let mut remaining = frac_scaled;
276 for i in (0..n_subsec).rev() {
277 let pair = (remaining % 100) as u32;
278 remaining /= 100;
279 buf[pos + i as usize] = bcd(pair);
280 }
281 pos += n_subsec as usize;
282 }
283
284 Ok((buf, pos))
285 }
286
287 #[inline]
293 pub fn to_ccsds_bin(
294 &self,
295 current: Scale,
296 ) -> Result<([u8; Self::CCSDS_C_AND_D_MAX_SIZE], usize), DtErr> {
297 if current.uses_leap_seconds() {
298 self.to_ccsds_d(current, 2, 1, false)
299 } else {
300 self.to_ccsds_c(current, 4, 4, false)
301 }
302 }
303}