leodos_protocols/misc/time/
cuc.rs1use crate::utils::get_bits_u8;
34use crate::utils::set_bits_u8;
35
36pub const MAX_COARSE_BYTES: u8 = 4;
38
39pub const MAX_FINE_BYTES: u8 = 3;
41
42pub const CCSDS_EPOCH_UNIX_OFFSET: i64 = -378_691_200;
46
47#[derive(Debug, Copy, Clone, Eq, PartialEq)]
49#[repr(u8)]
50pub enum TimeCodeId {
51 AgencyEpoch = 0b001,
53 CcsdsEpoch = 0b010,
55}
56
57#[rustfmt::skip]
59mod bitmask {
60 pub const TIME_CODE_ID_MASK: u8 = 0b_0111_0000;
62 pub const COARSE_LEN_MASK: u8 = 0b_0000_1100;
64 pub const FINE_LEN_MASK: u8 = 0b_0000_0011;
66}
67
68use bitmask::*;
69
70#[derive(Debug, Copy, Clone, Eq, PartialEq)]
72pub struct CucConfig {
73 pub time_code_id: TimeCodeId,
75 pub coarse_len: u8,
77 pub fine_len: u8,
79}
80
81impl CucConfig {
82 pub const fn new(time_code_id: TimeCodeId, coarse_len: u8, fine_len: u8) -> Self {
86 assert!(coarse_len >= 1 && coarse_len <= MAX_COARSE_BYTES);
87 assert!(fine_len <= MAX_FINE_BYTES);
88 Self {
89 time_code_id,
90 coarse_len,
91 fine_len,
92 }
93 }
94
95 pub const CCSDS_4_2: Self = Self::new(TimeCodeId::CcsdsEpoch, 4, 2);
98
99 pub const CCSDS_4_0: Self = Self::new(TimeCodeId::CcsdsEpoch, 4, 0);
101
102 pub const fn p_field(&self) -> u8 {
107 let id = self.time_code_id as u8;
108 let coarse_code = self.coarse_len - 1;
109 let fine_code = self.fine_len;
110 let mut pf = 0u8;
111 set_bits_u8(&mut pf, TIME_CODE_ID_MASK, id);
112 set_bits_u8(&mut pf, COARSE_LEN_MASK, coarse_code);
113 set_bits_u8(&mut pf, FINE_LEN_MASK, fine_code);
114 pf
115 }
116
117 pub const fn from_p_field(pf: u8) -> Result<Self, Error> {
119 let id_bits = get_bits_u8(pf, TIME_CODE_ID_MASK);
120 let coarse_code = get_bits_u8(pf, COARSE_LEN_MASK);
121 let fine_code = get_bits_u8(pf, FINE_LEN_MASK);
122
123 let time_code_id = match id_bits {
124 0b001 => TimeCodeId::AgencyEpoch,
125 0b010 => TimeCodeId::CcsdsEpoch,
126 _ => return Err(Error::InvalidTimeCodeId(id_bits)),
127 };
128
129 Ok(Self {
130 time_code_id,
131 coarse_len: coarse_code + 1,
132 fine_len: fine_code,
133 })
134 }
135
136 pub const fn t_field_len(&self) -> usize {
138 self.coarse_len as usize + self.fine_len as usize
139 }
140
141 pub const fn encoded_len(&self) -> usize {
143 1 + self.t_field_len()
144 }
145}
146
147#[derive(Debug, Copy, Clone, Eq, PartialEq)]
149pub struct CucTime {
150 pub config: CucConfig,
152 pub coarse: u32,
154 pub fine: u32,
157}
158
159#[derive(Debug, Copy, Clone, Eq, PartialEq, thiserror::Error)]
161pub enum Error {
162 #[error("Invalid time code ID in P-field: {0:#03b}")]
164 InvalidTimeCodeId(u8),
165 #[error("Buffer too short: required {required} bytes, but provided {provided} bytes")]
167 BufferTooShort {
168 required: usize,
170 provided: usize,
172 },
173}
174
175impl CucTime {
176 pub const fn new(config: CucConfig, coarse: u32, fine: u32) -> Self {
178 Self {
179 config,
180 coarse,
181 fine,
182 }
183 }
184
185 pub fn from_seconds(config: CucConfig, seconds: f64) -> Self {
190 let coarse = seconds as u32;
191 let frac = seconds - coarse as f64;
192 let fine_bits = config.fine_len as u32 * 8;
193 let fine = if fine_bits > 0 {
194 (frac * (1u64 << fine_bits) as f64) as u32
195 } else {
196 0
197 };
198 Self {
199 config,
200 coarse,
201 fine,
202 }
203 }
204
205 pub fn to_seconds(&self) -> f64 {
207 let fine_bits = self.config.fine_len as u32 * 8;
208 let frac = if fine_bits > 0 {
209 self.fine as f64 / (1u64 << fine_bits) as f64
210 } else {
211 0.0
212 };
213 self.coarse as f64 + frac
214 }
215
216 pub fn resolution(&self) -> f64 {
218 let fine_bits = self.config.fine_len as u32 * 8;
219 if fine_bits > 0 {
220 1.0 / (1u64 << fine_bits) as f64
221 } else {
222 1.0
223 }
224 }
225
226 pub fn encode(&self, buf: &mut [u8]) -> Result<usize, Error> {
230 let total = self.config.encoded_len();
231 if buf.len() < total {
232 return Err(Error::BufferTooShort {
233 required: total,
234 provided: buf.len(),
235 });
236 }
237
238 buf[0] = self.config.p_field();
239 let mut pos = 1;
240
241 let coarse_bytes = self.coarse.to_be_bytes();
243 let coarse_len = self.config.coarse_len as usize;
244 let coarse_start = 4 - coarse_len;
245 buf[pos..pos + coarse_len].copy_from_slice(&coarse_bytes[coarse_start..]);
246 pos += coarse_len;
247
248 let fine_len = self.config.fine_len as usize;
250 if fine_len > 0 {
251 let fine_bytes = self.fine.to_be_bytes();
252 buf[pos..pos + fine_len].copy_from_slice(&fine_bytes[..fine_len]);
253 pos += fine_len;
254 }
255
256 Ok(pos)
257 }
258
259 pub fn encode_t_field(&self, buf: &mut [u8]) -> Result<usize, Error> {
263 let t_len = self.config.t_field_len();
264 if buf.len() < t_len {
265 return Err(Error::BufferTooShort {
266 required: t_len,
267 provided: buf.len(),
268 });
269 }
270
271 let mut pos = 0;
272
273 let coarse_bytes = self.coarse.to_be_bytes();
274 let coarse_len = self.config.coarse_len as usize;
275 let coarse_start = 4 - coarse_len;
276 buf[pos..pos + coarse_len].copy_from_slice(&coarse_bytes[coarse_start..]);
277 pos += coarse_len;
278
279 let fine_len = self.config.fine_len as usize;
280 if fine_len > 0 {
281 let fine_bytes = self.fine.to_be_bytes();
282 buf[pos..pos + fine_len].copy_from_slice(&fine_bytes[..fine_len]);
283 pos += fine_len;
284 }
285
286 Ok(pos)
287 }
288
289 pub fn decode(buf: &[u8]) -> Result<Self, Error> {
291 if buf.is_empty() {
292 return Err(Error::BufferTooShort {
293 required: 1,
294 provided: 0,
295 });
296 }
297
298 let config = CucConfig::from_p_field(buf[0])?;
299 let total = config.encoded_len();
300 if buf.len() < total {
301 return Err(Error::BufferTooShort {
302 required: total,
303 provided: buf.len(),
304 });
305 }
306
307 Self::decode_t_field(&config, &buf[1..])
308 }
309
310 pub fn decode_t_field(config: &CucConfig, buf: &[u8]) -> Result<Self, Error> {
314 let t_len = config.t_field_len();
315 if buf.len() < t_len {
316 return Err(Error::BufferTooShort {
317 required: t_len,
318 provided: buf.len(),
319 });
320 }
321
322 let mut pos = 0;
323 let coarse_len = config.coarse_len as usize;
324 let mut coarse_buf = [0u8; 4];
325 let coarse_start = 4 - coarse_len;
326 coarse_buf[coarse_start..].copy_from_slice(&buf[pos..pos + coarse_len]);
327 let coarse = u32::from_be_bytes(coarse_buf);
328 pos += coarse_len;
329
330 let fine_len = config.fine_len as usize;
331 let fine = if fine_len > 0 {
332 let mut fine_buf = [0u8; 4];
333 fine_buf[..fine_len].copy_from_slice(&buf[pos..pos + fine_len]);
334 u32::from_be_bytes(fine_buf)
335 } else {
336 0
337 };
338
339 Ok(Self {
340 config: *config,
341 coarse,
342 fine,
343 })
344 }
345}
346
347impl core::fmt::Display for CucTime {
348 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
349 write!(f, "CUC({:.6}s)", self.to_seconds())
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn p_field_roundtrip() {
359 let config = CucConfig::CCSDS_4_2;
360 let pf = config.p_field();
361 let parsed = CucConfig::from_p_field(pf).unwrap();
362 assert_eq!(parsed, config);
363 }
364
365 #[test]
366 fn p_field_values() {
367 let config = CucConfig::CCSDS_4_2;
370 assert_eq!(config.p_field(), 0x2E);
371
372 let config = CucConfig::CCSDS_4_0;
375 assert_eq!(config.p_field(), 0x2C);
376 }
377
378 #[test]
379 fn encode_decode_roundtrip_4_2() {
380 let config = CucConfig::CCSDS_4_2;
381 let t = CucTime::new(config, 1_000_000, 0x8000_0000);
382
383 let mut buf = [0u8; 16];
384 let len = t.encode(&mut buf).unwrap();
385 assert_eq!(len, 7); let decoded = CucTime::decode(&buf[..len]).unwrap();
388 assert_eq!(decoded.coarse, 1_000_000);
389 assert_eq!(decoded.fine, 0x8000_0000);
392 }
393
394 #[test]
395 fn encode_decode_roundtrip_4_0() {
396 let config = CucConfig::CCSDS_4_0;
397 let t = CucTime::new(config, 42, 0);
398
399 let mut buf = [0u8; 8];
400 let len = t.encode(&mut buf).unwrap();
401 assert_eq!(len, 5); let decoded = CucTime::decode(&buf[..len]).unwrap();
404 assert_eq!(decoded.coarse, 42);
405 assert_eq!(decoded.fine, 0);
406 }
407
408 #[test]
409 fn t_field_only() {
410 let config = CucConfig::CCSDS_4_2;
411 let t = CucTime::new(config, 12345, 0xABCD_0000);
412
413 let mut buf = [0u8; 8];
414 let len = t.encode_t_field(&mut buf).unwrap();
415 assert_eq!(len, 6); let decoded = CucTime::decode_t_field(&config, &buf[..len]).unwrap();
418 assert_eq!(decoded.coarse, 12345);
419 assert_eq!(decoded.fine, 0xABCD_0000);
420 }
421
422 #[test]
423 fn from_seconds_and_back() {
424 let config = CucConfig::CCSDS_4_2;
425 let t = CucTime::from_seconds(config, 100.5);
426
427 assert_eq!(t.coarse, 100);
428 assert_eq!(t.fine, 0x8000);
430
431 let secs = t.to_seconds();
432 let diff = (secs - 100.5).abs();
433 assert!(diff < 0.001);
434 }
435
436 #[test]
437 fn resolution_values() {
438 let c0 = CucConfig::new(TimeCodeId::CcsdsEpoch, 4, 0);
439 assert_eq!(c0.fine_len, 0);
440 assert_eq!(CucTime::new(c0, 0, 0).resolution(), 1.0);
441
442 let c1 = CucConfig::new(TimeCodeId::CcsdsEpoch, 4, 1);
443 let r1 = CucTime::new(c1, 0, 0).resolution();
444 let diff1 = (r1 - 1.0 / 256.0).abs();
445 assert!(diff1 < 1e-10);
446
447 let c2 = CucConfig::CCSDS_4_2;
448 let r2 = CucTime::new(c2, 0, 0).resolution();
449 let diff2 = (r2 - 1.0 / 65536.0).abs();
450 assert!(diff2 < 1e-12);
451
452 let c3 = CucConfig::new(TimeCodeId::CcsdsEpoch, 4, 3);
453 let r3 = CucTime::new(c3, 0, 0).resolution();
454 let diff3 = (r3 - 1.0 / 16777216.0).abs();
455 assert!(diff3 < 1e-15);
456 }
457
458 #[test]
459 fn agency_epoch() {
460 let config = CucConfig::new(TimeCodeId::AgencyEpoch, 2, 1);
461 let t = CucTime::new(config, 300, 0x8000_0000);
462
463 let mut buf = [0u8; 8];
464 let len = t.encode(&mut buf).unwrap();
465 assert_eq!(len, 4); let decoded = CucTime::decode(&buf[..len]).unwrap();
468 assert_eq!(decoded.config.time_code_id, TimeCodeId::AgencyEpoch);
469 assert_eq!(decoded.coarse, 300);
470 assert_eq!(decoded.fine, 0x8000_0000);
471 }
472
473 #[test]
474 fn buffer_too_short() {
475 let t = CucTime::new(CucConfig::CCSDS_4_2, 0, 0);
476 let mut buf = [0u8; 3]; let err = t.encode(&mut buf);
478 assert!(matches!(
479 err,
480 Err(Error::BufferTooShort {
481 required: 7,
482 provided: 3,
483 })
484 ));
485 }
486
487 #[test]
488 fn invalid_time_code_id() {
489 let err = CucConfig::from_p_field(0x00); assert!(matches!(err, Err(Error::InvalidTimeCodeId(0))));
491 }
492
493 #[test]
494 fn small_coarse_field() {
495 let config = CucConfig::new(TimeCodeId::CcsdsEpoch, 1, 0);
497 let t = CucTime::new(config, 200, 0);
498
499 let mut buf = [0u8; 4];
500 let len = t.encode(&mut buf).unwrap();
501 assert_eq!(len, 2); let decoded = CucTime::decode(&buf[..len]).unwrap();
504 assert_eq!(decoded.coarse, 200);
505 }
506}