1use crate::utils::get_bits_u8;
33use crate::utils::set_bits_u8;
34
35pub const MS_PER_DAY: u32 = 86_400_000;
37
38const TIME_CODE_ID: u8 = 0b100;
40
41#[rustfmt::skip]
43mod bitmask {
44 pub const TIME_CODE_ID_MASK: u8 = 0b_0111_0000;
46 pub const EPOCH_ID_MASK: u8 = 0b_0000_1000;
48 pub const DAY_SEG_MASK: u8 = 0b_0000_0100;
50 pub const SUB_MILLIS_MASK: u8 = 0b_0000_0011;
52}
53
54use bitmask::*;
55
56#[derive(Debug, Copy, Clone, Eq, PartialEq)]
58pub enum SubMillis {
59 None,
61 Microseconds,
63 Picoseconds,
65}
66
67impl SubMillis {
68 pub const fn field_len(self) -> usize {
70 match self {
71 Self::None => 0,
72 Self::Microseconds => 2,
73 Self::Picoseconds => 4,
74 }
75 }
76
77 const fn code(self) -> u8 {
79 match self {
80 Self::None => 0b00,
81 Self::Microseconds => 0b01,
82 Self::Picoseconds => 0b10,
83 }
84 }
85
86 const fn from_code(code: u8) -> Result<Self, CdsError> {
88 match code {
89 0b00 => Ok(Self::None),
90 0b01 => Ok(Self::Microseconds),
91 0b10 => Ok(Self::Picoseconds),
92 _ => Err(CdsError::InvalidSubMillisCode(code)),
93 }
94 }
95}
96
97#[derive(Debug, Copy, Clone, Eq, PartialEq)]
99pub enum EpochId {
100 Ccsds,
102 Agency,
104}
105
106#[derive(Debug, Copy, Clone, Eq, PartialEq)]
108pub struct CdsConfig {
109 pub epoch: EpochId,
111 pub day_24bit: bool,
113 pub sub_millis: SubMillis,
115}
116
117impl CdsConfig {
118 pub const CCSDS_16: Self = Self {
120 epoch: EpochId::Ccsds,
121 day_24bit: false,
122 sub_millis: SubMillis::None,
123 };
124
125 pub const CCSDS_16_US: Self = Self {
127 epoch: EpochId::Ccsds,
128 day_24bit: false,
129 sub_millis: SubMillis::Microseconds,
130 };
131
132 pub const fn day_len(&self) -> usize {
134 if self.day_24bit { 3 } else { 2 }
135 }
136
137 pub const fn t_field_len(&self) -> usize {
139 self.day_len() + 4 + self.sub_millis.field_len()
140 }
141
142 pub const fn encoded_len(&self) -> usize {
144 1 + self.t_field_len()
145 }
146
147 pub const fn p_field(&self) -> u8 {
149 let epoch_bit = match self.epoch {
150 EpochId::Ccsds => 0,
151 EpochId::Agency => 1,
152 };
153 let day_bit = if self.day_24bit { 1 } else { 0 };
154 let mut pf = 0u8;
155 set_bits_u8(&mut pf, TIME_CODE_ID_MASK, TIME_CODE_ID);
156 set_bits_u8(&mut pf, EPOCH_ID_MASK, epoch_bit);
157 set_bits_u8(&mut pf, DAY_SEG_MASK, day_bit);
158 set_bits_u8(&mut pf, SUB_MILLIS_MASK, self.sub_millis.code());
159 pf
160 }
161
162 pub const fn from_p_field(pf: u8) -> Result<Self, CdsError> {
164 let id = get_bits_u8(pf, TIME_CODE_ID_MASK);
165 if id != TIME_CODE_ID {
166 return Err(CdsError::NotCds(id));
167 }
168 let epoch_bit = get_bits_u8(pf, EPOCH_ID_MASK);
169 let day_bit = get_bits_u8(pf, DAY_SEG_MASK);
170 let sub_code = get_bits_u8(pf, SUB_MILLIS_MASK);
171
172 let epoch = if epoch_bit == 0 {
173 EpochId::Ccsds
174 } else {
175 EpochId::Agency
176 };
177
178 let sub_millis = match SubMillis::from_code(sub_code) {
179 Ok(s) => s,
180 Err(e) => return Err(e),
181 };
182
183 Ok(Self {
184 epoch,
185 day_24bit: day_bit == 1,
186 sub_millis,
187 })
188 }
189}
190
191#[derive(Debug, Copy, Clone, Eq, PartialEq)]
193pub struct CdsTime {
194 pub config: CdsConfig,
196 pub day: u32,
198 pub ms_of_day: u32,
200 pub sub_ms: u32,
202}
203
204#[derive(Debug, Copy, Clone, Eq, PartialEq)]
206pub enum CdsError {
207 NotCds(u8),
209 InvalidSubMillisCode(u8),
211 BufferTooShort {
213 required: usize,
215 provided: usize,
217 },
218 MsOutOfRange(u32),
220}
221
222impl CdsTime {
223 pub const fn new(
225 config: CdsConfig,
226 day: u32,
227 ms_of_day: u32,
228 sub_ms: u32,
229 ) -> Self {
230 Self { config, day, ms_of_day, sub_ms }
231 }
232
233 pub fn from_seconds(config: CdsConfig, seconds: f64) -> Self {
235 let total_ms = (seconds * 1000.0) as u64;
236 let day = (total_ms / MS_PER_DAY as u64) as u32;
237 let ms_of_day = (total_ms % MS_PER_DAY as u64) as u32;
238
239 let frac_ms = seconds * 1000.0 - (total_ms as f64);
240 let sub_ms = match config.sub_millis {
241 SubMillis::None => 0,
242 SubMillis::Microseconds => {
243 (frac_ms * 1000.0) as u32
244 }
245 SubMillis::Picoseconds => {
246 (frac_ms * 1_000_000_000.0) as u32
247 }
248 };
249
250 Self { config, day, ms_of_day, sub_ms }
251 }
252
253 pub fn to_seconds(&self) -> f64 {
255 let day_secs = self.day as f64 * 86_400.0;
256 let ms_secs = self.ms_of_day as f64 / 1000.0;
257 let sub_secs = match self.config.sub_millis {
258 SubMillis::None => 0.0,
259 SubMillis::Microseconds => {
260 self.sub_ms as f64 / 1_000_000.0
261 }
262 SubMillis::Picoseconds => {
263 self.sub_ms as f64 / 1_000_000_000_000.0
264 }
265 };
266 day_secs + ms_secs + sub_secs
267 }
268
269 pub fn encode(&self, buf: &mut [u8]) -> Result<usize, CdsError> {
271 let total = self.config.encoded_len();
272 if buf.len() < total {
273 return Err(CdsError::BufferTooShort {
274 required: total,
275 provided: buf.len(),
276 });
277 }
278
279 buf[0] = self.config.p_field();
280 self.write_t_field(&mut buf[1..])?;
281 Ok(total)
282 }
283
284 pub fn encode_t_field(
286 &self,
287 buf: &mut [u8],
288 ) -> Result<usize, CdsError> {
289 let t_len = self.config.t_field_len();
290 if buf.len() < t_len {
291 return Err(CdsError::BufferTooShort {
292 required: t_len,
293 provided: buf.len(),
294 });
295 }
296 self.write_t_field(buf)?;
297 Ok(t_len)
298 }
299
300 fn write_t_field(&self, buf: &mut [u8]) -> Result<(), CdsError> {
301 let mut pos = 0;
302
303 let day_bytes = self.day.to_be_bytes();
305 if self.config.day_24bit {
306 buf[pos..pos + 3].copy_from_slice(&day_bytes[1..4]);
307 pos += 3;
308 } else {
309 buf[pos..pos + 2].copy_from_slice(&day_bytes[2..4]);
310 pos += 2;
311 }
312
313 let ms_bytes = self.ms_of_day.to_be_bytes();
315 buf[pos..pos + 4].copy_from_slice(&ms_bytes);
316 pos += 4;
317
318 match self.config.sub_millis {
320 SubMillis::None => {}
321 SubMillis::Microseconds => {
322 let us_bytes = (self.sub_ms as u16).to_be_bytes();
323 buf[pos..pos + 2].copy_from_slice(&us_bytes);
324 }
325 SubMillis::Picoseconds => {
326 let ps_bytes = self.sub_ms.to_be_bytes();
327 buf[pos..pos + 4].copy_from_slice(&ps_bytes);
328 }
329 }
330
331 Ok(())
332 }
333
334 pub fn decode(buf: &[u8]) -> Result<Self, CdsError> {
336 if buf.is_empty() {
337 return Err(CdsError::BufferTooShort {
338 required: 1,
339 provided: 0,
340 });
341 }
342
343 let config = CdsConfig::from_p_field(buf[0])?;
344 let total = config.encoded_len();
345 if buf.len() < total {
346 return Err(CdsError::BufferTooShort {
347 required: total,
348 provided: buf.len(),
349 });
350 }
351
352 Self::decode_t_field(&config, &buf[1..])
353 }
354
355 pub fn decode_t_field(
357 config: &CdsConfig,
358 buf: &[u8],
359 ) -> Result<Self, CdsError> {
360 let t_len = config.t_field_len();
361 if buf.len() < t_len {
362 return Err(CdsError::BufferTooShort {
363 required: t_len,
364 provided: buf.len(),
365 });
366 }
367
368 let mut pos = 0;
369
370 let day = if config.day_24bit {
372 let mut d = [0u8; 4];
373 d[1..4].copy_from_slice(&buf[pos..pos + 3]);
374 pos += 3;
375 u32::from_be_bytes(d)
376 } else {
377 let mut d = [0u8; 4];
378 d[2..4].copy_from_slice(&buf[pos..pos + 2]);
379 pos += 2;
380 u32::from_be_bytes(d)
381 };
382
383 let ms_of_day = u32::from_be_bytes([
385 buf[pos],
386 buf[pos + 1],
387 buf[pos + 2],
388 buf[pos + 3],
389 ]);
390 pos += 4;
391
392 let sub_ms = match config.sub_millis {
394 SubMillis::None => 0,
395 SubMillis::Microseconds => {
396 let v = u16::from_be_bytes([buf[pos], buf[pos + 1]]);
397 v as u32
398 }
399 SubMillis::Picoseconds => {
400 u32::from_be_bytes([
401 buf[pos],
402 buf[pos + 1],
403 buf[pos + 2],
404 buf[pos + 3],
405 ])
406 }
407 };
408
409 Ok(Self {
410 config: *config,
411 day,
412 ms_of_day,
413 sub_ms,
414 })
415 }
416}
417
418impl core::fmt::Display for CdsTime {
419 fn fmt(
420 &self,
421 f: &mut core::fmt::Formatter<'_>,
422 ) -> core::fmt::Result {
423 let h = self.ms_of_day / 3_600_000;
424 let m = (self.ms_of_day % 3_600_000) / 60_000;
425 let s = (self.ms_of_day % 60_000) / 1000;
426 let ms = self.ms_of_day % 1000;
427 write!(
428 f,
429 "CDS(day={}, {:02}:{:02}:{:02}.{:03}",
430 self.day, h, m, s, ms
431 )?;
432 match self.config.sub_millis {
433 SubMillis::None => {}
434 SubMillis::Microseconds => {
435 write!(f, ".{:03}µs", self.sub_ms)?;
436 }
437 SubMillis::Picoseconds => {
438 write!(f, ".{:09}ps", self.sub_ms)?;
439 }
440 }
441 write!(f, ")")
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 #[test]
450 fn p_field_roundtrip() {
451 let config = CdsConfig::CCSDS_16;
452 let pf = config.p_field();
453 let parsed = CdsConfig::from_p_field(pf).unwrap();
454 assert_eq!(parsed, config);
455 }
456
457 #[test]
458 fn p_field_with_us() {
459 let config = CdsConfig::CCSDS_16_US;
460 let pf = config.p_field();
461 let parsed = CdsConfig::from_p_field(pf).unwrap();
462 assert_eq!(parsed, config);
463 assert_eq!(parsed.sub_millis, SubMillis::Microseconds);
464 }
465
466 #[test]
467 fn p_field_24bit_day() {
468 let config = CdsConfig {
469 epoch: EpochId::Ccsds,
470 day_24bit: true,
471 sub_millis: SubMillis::None,
472 };
473 let pf = config.p_field();
474 let parsed = CdsConfig::from_p_field(pf).unwrap();
475 assert!(parsed.day_24bit);
476 }
477
478 #[test]
479 fn p_field_agency_epoch() {
480 let config = CdsConfig {
481 epoch: EpochId::Agency,
482 day_24bit: false,
483 sub_millis: SubMillis::None,
484 };
485 let pf = config.p_field();
486 assert_eq!(get_bits_u8(pf, EPOCH_ID_MASK), 1);
487 let parsed = CdsConfig::from_p_field(pf).unwrap();
488 assert_eq!(parsed.epoch, EpochId::Agency);
489 }
490
491 #[test]
492 fn p_field_value() {
493 assert_eq!(CdsConfig::CCSDS_16.p_field(), 0x40);
496 assert_eq!(CdsConfig::CCSDS_16_US.p_field(), 0x41);
499 }
500
501 #[test]
502 fn encode_decode_16bit_no_subms() {
503 let t = CdsTime::new(CdsConfig::CCSDS_16, 1000, 43_200_000, 0);
504 let mut buf = [0u8; 16];
505 let len = t.encode(&mut buf).unwrap();
506 assert_eq!(len, 7);
508
509 let decoded = CdsTime::decode(&buf[..len]).unwrap();
510 assert_eq!(decoded.day, 1000);
511 assert_eq!(decoded.ms_of_day, 43_200_000);
512 assert_eq!(decoded.sub_ms, 0);
513 }
514
515 #[test]
516 fn encode_decode_16bit_us() {
517 let t = CdsTime::new(CdsConfig::CCSDS_16_US, 500, 1000, 750);
518 let mut buf = [0u8; 16];
519 let len = t.encode(&mut buf).unwrap();
520 assert_eq!(len, 9);
522
523 let decoded = CdsTime::decode(&buf[..len]).unwrap();
524 assert_eq!(decoded.day, 500);
525 assert_eq!(decoded.ms_of_day, 1000);
526 assert_eq!(decoded.sub_ms, 750);
527 }
528
529 #[test]
530 fn encode_decode_24bit_ps() {
531 let config = CdsConfig {
532 epoch: EpochId::Ccsds,
533 day_24bit: true,
534 sub_millis: SubMillis::Picoseconds,
535 };
536 let t = CdsTime::new(config, 100_000, 50_000_000, 123_456_789);
537 let mut buf = [0u8; 16];
538 let len = t.encode(&mut buf).unwrap();
539 assert_eq!(len, 12);
541
542 let decoded = CdsTime::decode(&buf[..len]).unwrap();
543 assert_eq!(decoded.day, 100_000);
544 assert_eq!(decoded.ms_of_day, 50_000_000);
545 assert_eq!(decoded.sub_ms, 123_456_789);
546 }
547
548 #[test]
549 fn t_field_only() {
550 let config = CdsConfig::CCSDS_16;
551 let t = CdsTime::new(config, 365, 72_000_000, 0);
552 let mut buf = [0u8; 8];
553 let len = t.encode_t_field(&mut buf).unwrap();
554 assert_eq!(len, 6); let decoded =
557 CdsTime::decode_t_field(&config, &buf[..len]).unwrap();
558 assert_eq!(decoded.day, 365);
559 assert_eq!(decoded.ms_of_day, 72_000_000);
560 }
561
562 #[test]
563 fn from_seconds_and_back() {
564 let config = CdsConfig::CCSDS_16;
565 let t = CdsTime::from_seconds(config, 129_600.0);
567 assert_eq!(t.day, 1);
568 assert_eq!(t.ms_of_day, 43_200_000); let secs = t.to_seconds();
571 assert!((secs - 129_600.0).abs() < 0.001);
572 }
573
574 #[test]
575 fn from_seconds_with_us() {
576 let config = CdsConfig::CCSDS_16_US;
577 let t = CdsTime::from_seconds(config, 0.001_500_5);
579 assert_eq!(t.day, 0);
580 assert_eq!(t.ms_of_day, 1);
581 assert_eq!(t.sub_ms, 500);
582 }
583
584 #[test]
585 fn buffer_too_short() {
586 let t = CdsTime::new(CdsConfig::CCSDS_16, 0, 0, 0);
587 let mut buf = [0u8; 3];
588 assert!(matches!(
589 t.encode(&mut buf),
590 Err(CdsError::BufferTooShort { required: 7, .. })
591 ));
592 }
593
594 #[test]
595 fn not_cds_p_field() {
596 let err = CdsConfig::from_p_field(0x2E);
598 assert!(matches!(err, Err(CdsError::NotCds(0b010))));
599 }
600
601 #[test]
602 fn max_16bit_day() {
603 let t = CdsTime::new(CdsConfig::CCSDS_16, 65535, 0, 0);
604 let mut buf = [0u8; 8];
605 let len = t.encode(&mut buf).unwrap();
606 let decoded = CdsTime::decode(&buf[..len]).unwrap();
607 assert_eq!(decoded.day, 65535);
608 }
609
610 #[test]
611 fn max_24bit_day() {
612 let config = CdsConfig {
613 epoch: EpochId::Ccsds,
614 day_24bit: true,
615 sub_millis: SubMillis::None,
616 };
617 let t = CdsTime::new(config, 0xFF_FFFF, 0, 0);
618 let mut buf = [0u8; 12];
619 let len = t.encode(&mut buf).unwrap();
620 let decoded = CdsTime::decode(&buf[..len]).unwrap();
621 assert_eq!(decoded.day, 0xFF_FFFF);
622 }
623
624 #[test]
625 fn midnight_and_end_of_day() {
626 let config = CdsConfig::CCSDS_16;
627
628 let midnight = CdsTime::new(config, 0, 0, 0);
629 assert_eq!(midnight.ms_of_day, 0);
630
631 let end = CdsTime::new(config, 0, MS_PER_DAY - 1, 0);
632 assert_eq!(end.ms_of_day, 86_399_999);
633
634 let mut buf = [0u8; 8];
635 end.encode(&mut buf).unwrap();
636 let decoded = CdsTime::decode(&buf).unwrap();
637 assert_eq!(decoded.ms_of_day, 86_399_999);
638 }
639
640 #[test]
641 fn encoded_len_values() {
642 assert_eq!(CdsConfig::CCSDS_16.encoded_len(), 7);
643 assert_eq!(CdsConfig::CCSDS_16_US.encoded_len(), 9);
644 let ps_24 = CdsConfig {
645 epoch: EpochId::Ccsds,
646 day_24bit: true,
647 sub_millis: SubMillis::Picoseconds,
648 };
649 assert_eq!(ps_24.encoded_len(), 12);
650 }
651}