1use crate::btic::{Btic, SIGN_FLIP};
2use crate::error::BticError;
3
4pub fn encode(btic: &Btic) -> [u8; 24] {
9 let mut buf = [0u8; 24];
10 let lo_encoded = (btic.lo() as u64) ^ SIGN_FLIP;
11 let hi_encoded = (btic.hi() as u64) ^ SIGN_FLIP;
12 buf[0..8].copy_from_slice(&lo_encoded.to_be_bytes());
13 buf[8..16].copy_from_slice(&hi_encoded.to_be_bytes());
14 buf[16..24].copy_from_slice(&btic.meta().to_be_bytes());
15 buf
16}
17
18pub fn decode(bytes: &[u8; 24]) -> Result<Btic, BticError> {
22 fn word(slice: &[u8]) -> u64 {
25 let arr: [u8; 8] = slice
26 .try_into()
27 .expect("infallible: 8-byte slice from 24-byte array");
28 u64::from_be_bytes(arr)
29 }
30
31 let lo_encoded = word(&bytes[0..8]);
32 let hi_encoded = word(&bytes[8..16]);
33 let meta = word(&bytes[16..24]);
34
35 let lo = (lo_encoded ^ SIGN_FLIP) as i64;
36 let hi = (hi_encoded ^ SIGN_FLIP) as i64;
37
38 Btic::new(lo, hi, meta)
39}
40
41pub fn decode_slice(bytes: &[u8]) -> Result<Btic, BticError> {
43 if bytes.len() != 24 {
44 return Err(BticError::InvalidLength(bytes.len()));
45 }
46 let arr: &[u8; 24] = bytes
47 .try_into()
48 .expect("infallible: length validated above");
49 decode(arr)
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::btic::NEG_INF;
56 use crate::certainty::Certainty;
57 use crate::granularity::Granularity;
58
59 #[test]
60 fn roundtrip_basic() {
61 let meta = Btic::build_meta(
62 Granularity::Year,
63 Granularity::Year,
64 Certainty::Definite,
65 Certainty::Definite,
66 );
67 let original = Btic::new(473_385_600_000, 504_921_600_000, meta).unwrap();
68 let packed = encode(&original);
69 let decoded = decode(&packed).unwrap();
70 assert_eq!(original, decoded);
71 }
72
73 #[test]
74 fn roundtrip_negative_lo() {
75 let meta = Btic::build_meta(
76 Granularity::Year,
77 Granularity::Year,
78 Certainty::Approximate,
79 Certainty::Approximate,
80 );
81 let original = Btic::new(-77_914_137_600_000, -77_882_601_600_000, meta).unwrap();
83 let packed = encode(&original);
84 let decoded = decode(&packed).unwrap();
85 assert_eq!(original, decoded);
86 }
87
88 #[test]
89 fn roundtrip_sentinel_lo() {
90 let meta = Btic::build_meta(
91 Granularity::Millisecond,
92 Granularity::Month,
93 Certainty::Definite,
94 Certainty::Definite,
95 );
96 let original = Btic::new(NEG_INF, 481_161_600_000, meta).unwrap();
97 let packed = encode(&original);
98 let decoded = decode(&packed).unwrap();
99 assert_eq!(original, decoded);
100 }
101
102 #[test]
103 fn invalid_length_rejected() {
104 assert!(decode_slice(&[0u8; 23]).is_err());
105 assert!(decode_slice(&[0u8; 25]).is_err());
106 }
107
108 #[test]
109 fn memcmp_matches_ord() {
110 let meta = Btic::build_meta(
111 Granularity::Day,
112 Granularity::Day,
113 Certainty::Definite,
114 Certainty::Definite,
115 );
116 let a = Btic::new(100, 200, meta).unwrap();
117 let b = Btic::new(150, 200, meta).unwrap();
118 let packed_a = encode(&a);
119 let packed_b = encode(&b);
120
121 assert!(packed_a < packed_b);
123 assert!(a < b);
124 }
125}