deep_time/dt/
to_ccsds_bin.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(
18 &self,
19 n_coarse: u8,
20 n_frac: u8,
21 extension: bool,
22 ) -> Result<([u8; Self::CCSDS_C_AND_D_MAX_SIZE], usize), DtErr> {
23 if !(1..=7).contains(&n_coarse) {
24 return Err(an_err!(DtErrKind::OutOfRange, "coarse: {}", n_coarse,));
25 } else if n_frac > 10 {
26 return Err(an_err!(DtErrKind::OutOfRange, "frac: {}", n_frac));
27 }
28
29 const EPOCH_OFFSET: i64 = 1_325_419_167;
30 let total_tai_seconds = self.sec + EPOCH_OFFSET;
31
32 let frac_scaled = if n_frac == 0 {
33 0u128
34 } else {
35 let scale = 1u128 << (8 * n_frac as u32);
36 (self.attos as u128 * scale + 500_000_000_000_000_000) / 1_000_000_000_000_000_000
37 };
38
39 let mut buf = [0u8; Self::CCSDS_C_AND_D_MAX_SIZE];
40 let mut pos = 0usize;
41
42 let needs_extension = n_coarse > 4 || n_frac > 3 || extension;
44
45 let base_coarse = if n_coarse <= 4 { n_coarse - 1 } else { 3 };
47 let base_frac = if n_frac <= 3 { n_frac } else { 3 };
48
49 let mut p1 = 0b0001_0000u8; p1 |= (base_coarse << 2) & 0b0000_1100;
52 p1 |= base_frac & 0b0000_0011;
53 if needs_extension {
54 p1 |= 0b1000_0000;
55 }
56 buf[pos] = p1;
57 pos += 1;
58
59 if needs_extension {
60 let add_coarse = n_coarse.saturating_sub(4); let add_frac = n_frac.saturating_sub(3); let mut p2 = 0u8;
65 p2 |= (add_coarse & 0b11) << 5; p2 |= (add_frac & 0b111) << 2; buf[pos] = p2;
70 pos += 1;
71 }
72
73 let coarse = total_tai_seconds as u64;
75 for i in (0..n_coarse).rev() {
76 buf[pos] = (coarse >> (i as u32 * 8)) as u8;
77 pos += 1;
78 }
79
80 for i in (0..n_frac).rev() {
82 buf[pos] = (frac_scaled >> (i as u32 * 8)) as u8;
83 pos += 1;
84 }
85
86 Ok((buf, pos))
87 }
88
89 pub fn to_ccsds_d(
94 &self,
95 n_day: u8,
96 sub_ms_code: u8,
97 extension: bool,
98 ) -> Result<([u8; Self::CCSDS_C_AND_D_MAX_SIZE], usize), DtErr> {
99 if !matches!(n_day, 2 | 3) {
100 return Err(an_err!(DtErrKind::InvalidNumber, "n_day: {}", n_day));
101 } else if !matches!(sub_ms_code, 0 | 1 | 2) {
102 return Err(an_err!(DtErrKind::InvalidItem, "sub-millisecond code"));
103 }
104
105 let utc = self.to(Scale::UTC);
106
107 const EPOCH_OFFSET: i64 = 1_325_419_135;
110 let total_utc_seconds = utc.sec + EPOCH_OFFSET;
111
112 let day_count = (total_utc_seconds / SEC_PER_DAYI64) as u64;
113 let sec_of_day = (total_utc_seconds % SEC_PER_DAYI64) as u64;
114
115 let additional_ms =
117 ((utc.attos as u128 + 500_000_000_000_000) / 1_000_000_000_000_000) as u64;
118 let millis_of_day = sec_of_day * 1000 + additional_ms;
119
120 let remaining_attos_in_ms = (utc.attos as u128) % 1_000_000_000_000_000;
122
123 let frac_scaled = match sub_ms_code {
124 0 => 0u64,
125 1 => ((remaining_attos_in_ms * 65_536u128) / 1_000_000_000_000_000u128) as u64,
126 2 => {
127 const PS_SCALE: u128 = 1u128 << 32;
128 ((remaining_attos_in_ms * PS_SCALE) / 1_000_000_000_000_000u128) as u64
129 }
130 _ => return Err(an_err!(DtErrKind::InvalidItem, "sub-millisecond code")),
131 };
132
133 let mut buf = [0u8; Self::CCSDS_C_AND_D_MAX_SIZE];
134 let mut pos = 0usize;
135
136 let mut p1 = 0b0100_0000u8;
137 if extension {
138 p1 |= 0b1000_0000;
139 }
140 if n_day == 3 {
141 p1 |= 0b0000_0100;
142 }
143 p1 |= sub_ms_code;
144 buf[pos] = p1;
145 pos += 1;
146
147 if extension {
148 buf[pos] = 0;
149 pos += 1;
150 }
151
152 for i in (0..n_day).rev() {
153 buf[pos] = (day_count >> (i * 8)) as u8;
154 pos += 1;
155 }
156
157 for i in (0..4).rev() {
158 buf[pos] = (millis_of_day >> (i * 8)) as u8;
159 pos += 1;
160 }
161
162 let n_frac = match sub_ms_code {
163 0 => 0,
164 1 => 2,
165 2 => 4,
166 _ => return Err(an_err!(DtErrKind::InvalidItem, "sub-millisecond code")),
167 };
168 for i in (0..n_frac).rev() {
169 buf[pos] = (frac_scaled >> (i * 8)) as u8;
170 pos += 1;
171 }
172
173 Ok((buf, pos))
174 }
175
176 pub const CCSDS_CCS_MAX_SIZE: usize = 14; pub fn to_ccsds_ccs(
194 &self,
195 use_doy: bool,
196 n_subsec: u8,
197 ) -> Result<([u8; Self::CCSDS_CCS_MAX_SIZE], usize), DtErr> {
198 if n_subsec > 6 {
199 return Err(an_err!(DtErrKind::OutOfRange, "n_subsec: {}", n_subsec));
200 }
201
202 let gt = self.to_gregorian_time();
204
205 let mut buf = [0u8; Self::CCSDS_CCS_MAX_SIZE];
206 let mut pos = 0usize;
207
208 let mut p1 = 0b0101_0000u8; if use_doy {
211 p1 |= 0b0000_1000; }
213 p1 |= n_subsec & 0b0000_0111; buf[pos] = p1;
215 pos += 1;
216
217 let bcd = |val: u32| -> u8 {
219 let hi = (val / 10) as u8;
220 let lo = (val % 10) as u8;
221 (hi << 4) | lo
222 };
223
224 let year = gt.yr as u32;
226 let y_hi = year / 100;
227 let y_lo = year % 100;
228 buf[pos] = bcd(y_hi);
229 buf[pos + 1] = bcd(y_lo);
230 pos += 2;
231
232 if !use_doy {
234 buf[pos] = bcd(gt.mo as u32);
236 buf[pos + 1] = bcd(gt.day as u32);
237 } else {
238 let doy = gt.day_of_yr as u32;
240 buf[pos] = bcd(doy / 100); buf[pos + 1] = bcd(doy % 100);
242 }
243 pos += 2;
244
245 buf[pos] = bcd(gt.hr as u32);
247 buf[pos + 1] = bcd(gt.min as u32);
248 buf[pos + 2] = bcd(gt.sec as u32); pos += 3;
250
251 if n_subsec > 0 {
253 let decimal_places = (2 * n_subsec) as u32;
254 let scale = 10u128.pow(decimal_places);
255
256 let frac_scaled = ((gt.attos as u128 * scale + 500_000_000_000_000_000)
258 / 1_000_000_000_000_000_000) as u128;
259
260 let mut remaining = frac_scaled;
261 for i in (0..n_subsec).rev() {
262 let pair = (remaining % 100) as u32;
263 remaining /= 100;
264 buf[pos + i as usize] = bcd(pair);
265 }
266 pos += n_subsec as usize;
267 }
268
269 Ok((buf, pos))
270 }
271
272 #[inline]
279 pub fn to_ccsds_bin(
280 &self,
281 scale: Scale,
282 ) -> Result<([u8; Self::CCSDS_C_AND_D_MAX_SIZE], usize), DtErr> {
283 match scale {
284 Scale::TAI => self.to_ccsds_c(4, 4, false),
285 _ => self.to_ccsds_d(2, 1, false),
286 }
287 }
288}