1pub const KEY_SIZE: usize = 20;
26
27pub const PREFIX_SIZE: usize = 12;
29
30pub fn encode_key(entity_id: u64, space_id: u32, timestamp: i64) -> [u8; KEY_SIZE] {
32 let mut key = [0u8; KEY_SIZE];
33 key[0..8].copy_from_slice(&entity_id.to_be_bytes());
34 key[8..12].copy_from_slice(&space_id.to_be_bytes());
35 key[12..20].copy_from_slice(&encode_timestamp(timestamp));
36 key
37}
38
39pub fn decode_key(key: &[u8]) -> (u64, u32, i64) {
41 let entity_id = u64::from_be_bytes(key[0..8].try_into().unwrap());
42 let space_id = u32::from_be_bytes(key[8..12].try_into().unwrap());
43 let timestamp = decode_timestamp(key[12..20].try_into().unwrap());
44 (entity_id, space_id, timestamp)
45}
46
47pub fn encode_prefix(entity_id: u64, space_id: u32) -> [u8; PREFIX_SIZE] {
49 let mut prefix = [0u8; PREFIX_SIZE];
50 prefix[0..8].copy_from_slice(&entity_id.to_be_bytes());
51 prefix[8..12].copy_from_slice(&space_id.to_be_bytes());
52 prefix
53}
54
55fn encode_timestamp(ts: i64) -> [u8; 8] {
57 let mut bytes = ts.to_be_bytes();
58 bytes[0] ^= 0x80;
59 bytes
60}
61
62fn decode_timestamp(bytes: &[u8; 8]) -> i64 {
64 let mut b = *bytes;
65 b[0] ^= 0x80;
66 i64::from_be_bytes(b)
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn roundtrip_basic() {
75 let (e, s, t) = (42u64, 1u32, 1_000_000i64);
76 let key = encode_key(e, s, t);
77 let (e2, s2, t2) = decode_key(&key);
78 assert_eq!((e, s, t), (e2, s2, t2));
79 }
80
81 #[test]
82 fn roundtrip_negative_timestamp() {
83 let key = encode_key(1, 0, -5_000_000);
84 let (_, _, ts) = decode_key(&key);
85 assert_eq!(ts, -5_000_000);
86 }
87
88 #[test]
89 fn roundtrip_extremes() {
90 for ts in [i64::MIN, i64::MIN + 1, -1, 0, 1, i64::MAX - 1, i64::MAX] {
91 let key = encode_key(0, 0, ts);
92 let (_, _, decoded) = decode_key(&key);
93 assert_eq!(ts, decoded, "failed for ts={ts}");
94 }
95 }
96
97 #[test]
98 fn timestamp_ordering_preserved() {
99 let timestamps = [i64::MIN, -1_000_000, -1, 0, 1, 1_000_000, i64::MAX];
100 let keys: Vec<[u8; KEY_SIZE]> = timestamps.iter().map(|&ts| encode_key(1, 0, ts)).collect();
101
102 for window in keys.windows(2) {
103 assert!(
104 window[0] < window[1],
105 "ordering broken: {:?} should be < {:?}",
106 window[0],
107 window[1]
108 );
109 }
110 }
111
112 #[test]
113 fn entity_ordering_preserved() {
114 let k1 = encode_key(1, 0, 0);
115 let k2 = encode_key(2, 0, 0);
116 assert!(k1 < k2);
117 }
118
119 #[test]
120 fn space_ordering_preserved() {
121 let k1 = encode_key(1, 0, 0);
122 let k2 = encode_key(1, 1, 0);
123 assert!(k1 < k2);
124 }
125
126 #[test]
127 fn prefix_matches_key_start() {
128 let key = encode_key(42, 5, 1000);
129 let prefix = encode_prefix(42, 5);
130 assert_eq!(&key[..PREFIX_SIZE], &prefix);
131 }
132
133 #[test]
134 fn key_size_is_20() {
135 assert_eq!(KEY_SIZE, 20);
136 assert_eq!(encode_key(0, 0, 0).len(), 20);
137 }
138}
139
140#[cfg(test)]
141mod proptests {
142 use super::*;
143 use proptest::prelude::*;
144
145 proptest! {
146 #[test]
147 fn roundtrip_arbitrary(
148 entity_id in any::<u64>(),
149 space_id in any::<u32>(),
150 timestamp in any::<i64>(),
151 ) {
152 let key = encode_key(entity_id, space_id, timestamp);
153 let (e, s, t) = decode_key(&key);
154 prop_assert_eq!(entity_id, e);
155 prop_assert_eq!(space_id, s);
156 prop_assert_eq!(timestamp, t);
157 }
158
159 #[test]
160 fn ordering_preserved(
161 entity_id in any::<u64>(),
162 space_id in any::<u32>(),
163 t1 in any::<i64>(),
164 t2 in any::<i64>(),
165 ) {
166 let k1 = encode_key(entity_id, space_id, t1);
167 let k2 = encode_key(entity_id, space_id, t2);
168 prop_assert_eq!(t1.cmp(&t2), k1.cmp(&k2));
169 }
170 }
171}