Skip to main content

tsoracle_driver_file/
record.rs

1//
2//  ░▀█▀░█▀▀░█▀█░█▀▄░█▀█░█▀▀░█░░░█▀▀
3//  ░░█░░▀▀█░█░█░█▀▄░█▀█░█░░░█░░░█▀▀
4//  ░░▀░░▀▀▀░▀▀▀░▀░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀
5//
6//  tsoracle — Distributed Timestamp Oracle
7//  https://www.tsoracle.rs
8//
9//  Copyright (c) 2026 Prisma Risk
10//
11//  Licensed under the Apache License, Version 2.0 (the "License");
12//  you may not use this file except in compliance with the License.
13//  You may obtain a copy of the License at
14//
15//      https://www.apache.org/licenses/LICENSE-2.0
16//
17//  Unless required by applicable law or agreed to in writing, software
18//  distributed under the License is distributed on an "AS IS" BASIS,
19//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20//  See the License for the specific language governing permissions and
21//  limitations under the License.
22//
23
24//! 17-byte on-disk record:  "TSOR" | u8 version | u64 high_water | u32 crc32c
25
26pub const MAGIC: &[u8; 4] = b"TSOR";
27pub const VERSION: u8 = 1;
28pub const RECORD_LEN: usize = 4 + 1 + 8 + 4;
29
30#[derive(Debug, thiserror::Error)]
31pub enum RecordError {
32    #[error("file too short: {0} bytes (expected 17)")]
33    TooShort(usize),
34    #[error("bad magic: {0:?}")]
35    BadMagic([u8; 4]),
36    #[error("unsupported version: {0}")]
37    UnsupportedVersion(u8),
38    #[error("crc mismatch: expected {expected:#010x}, computed {computed:#010x}")]
39    CrcMismatch { expected: u32, computed: u32 },
40    #[error("file too long: {len} bytes (expected 17)")]
41    TrailingBytes { len: usize },
42}
43
44pub fn encode(high_water: u64) -> [u8; RECORD_LEN] {
45    let mut buf = [0u8; RECORD_LEN];
46    buf[0..4].copy_from_slice(MAGIC);
47    buf[4] = VERSION;
48    buf[5..13].copy_from_slice(&high_water.to_le_bytes());
49    let crc = crc32c::crc32c(&buf[0..13]);
50    buf[13..17].copy_from_slice(&crc.to_le_bytes());
51    buf
52}
53
54pub fn decode(bytes: &[u8]) -> Result<u64, RecordError> {
55    if bytes.len() > RECORD_LEN {
56        return Err(RecordError::TrailingBytes { len: bytes.len() });
57    }
58    let magic: [u8; 4] = read_array(bytes, 0)?;
59    if &magic != MAGIC {
60        return Err(RecordError::BadMagic(magic));
61    }
62    let version = *bytes.get(4).ok_or(RecordError::TooShort(bytes.len()))?;
63    if version != VERSION {
64        return Err(RecordError::UnsupportedVersion(version));
65    }
66    let high_water = u64::from_le_bytes(read_array(bytes, 5)?);
67    let crc_recorded = u32::from_le_bytes(read_array(bytes, 13)?);
68    let crc_computed = crc32c::crc32c(&bytes[0..13]);
69    if crc_recorded != crc_computed {
70        return Err(RecordError::CrcMismatch {
71            expected: crc_recorded,
72            computed: crc_computed,
73        });
74    }
75    Ok(high_water)
76}
77
78fn read_array<const N: usize>(bytes: &[u8], start: usize) -> Result<[u8; N], RecordError> {
79    let end = start
80        .checked_add(N)
81        .ok_or(RecordError::TooShort(bytes.len()))?;
82    bytes
83        .get(start..end)
84        .ok_or(RecordError::TooShort(bytes.len()))?
85        .try_into()
86        .map_err(|_| RecordError::TooShort(bytes.len()))
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn roundtrip() {
95        for v in [0u64, 1, 1_700_000_000_000, u64::MAX >> 18] {
96            let buf = encode(v);
97            assert_eq!(decode(&buf).unwrap(), v);
98        }
99    }
100
101    #[test]
102    fn detects_bad_magic() {
103        let mut buf = encode(42);
104        buf[0] = b'X';
105        assert!(matches!(decode(&buf), Err(RecordError::BadMagic(_))));
106    }
107
108    #[test]
109    fn detects_bad_crc() {
110        let mut buf = encode(42);
111        buf[5] ^= 0x01;
112        assert!(matches!(decode(&buf), Err(RecordError::CrcMismatch { .. })));
113    }
114
115    #[test]
116    fn detects_short() {
117        let buf = encode(42);
118        assert!(matches!(decode(&buf[..10]), Err(RecordError::TooShort(10))));
119    }
120
121    #[test]
122    fn detects_short_magic_slice() {
123        assert!(matches!(decode(b"TSO"), Err(RecordError::TooShort(3))));
124    }
125
126    #[test]
127    fn detects_short_version_byte() {
128        assert!(matches!(decode(MAGIC), Err(RecordError::TooShort(4))));
129    }
130
131    #[test]
132    fn detects_short_high_water_slice() {
133        let bytes = [MAGIC.as_slice(), &[VERSION], &[0; 7]].concat();
134        assert!(matches!(decode(&bytes), Err(RecordError::TooShort(12))));
135    }
136
137    #[test]
138    fn detects_short_crc_slice() {
139        let bytes = [MAGIC.as_slice(), &[VERSION], &[0; 8], &[0; 3]].concat();
140        assert!(matches!(decode(&bytes), Err(RecordError::TooShort(16))));
141    }
142
143    #[test]
144    fn detects_trailing_bytes() {
145        let valid = encode(42);
146        let mut overlong = valid.to_vec();
147        overlong.push(0xAB);
148        assert!(matches!(
149            decode(&overlong),
150            Err(RecordError::TrailingBytes { len: 18 })
151        ));
152    }
153
154    use proptest::prelude::*;
155
156    proptest! {
157        // Roundtrip over the full u64 domain. high_water on disk is unconstrained
158        // (the physical_ms 46-bit cap is an algorithm invariant, not a storage one),
159        // so the encoder/decoder must handle arbitrary values.
160        #[test]
161        fn encode_decode_roundtrip(high_water in any::<u64>()) {
162            let encoded = encode(high_water);
163            prop_assert_eq!(decode(&encoded).unwrap(), high_water);
164        }
165
166        // Single-bit corruption is always caught: flipping any one bit in the
167        // 17-byte record makes decode either return Err or return a different
168        // high_water. The CRC's whole purpose is the "always Err for payload
169        // flips" half; the "different value" alternative covers the
170        // theoretically-possible (but vanishingly improbable for crc32c) case
171        // where a bit flip in the high_water bytes happens to match a flip in
172        // the CRC bytes. Stated this way the property is exactly true.
173        #[test]
174        fn any_single_bit_flip_is_detected(
175            high_water in any::<u64>(),
176            byte_index in 0usize..RECORD_LEN,
177            bit_index in 0u8..8,
178        ) {
179            let original = encode(high_water);
180            let mut corrupted = original;
181            corrupted[byte_index] ^= 1 << bit_index;
182            prop_assert_ne!(corrupted, original);
183            match decode(&corrupted) {
184                Err(_) => {} // Detected — the common case.
185                Ok(decoded) => prop_assert_ne!(decoded, high_water),
186            }
187        }
188
189        // Any encoded record decodes back to its input — phrased over the
190        // decoder's reject set: a well-formed 17-byte slice with our magic and
191        // version is never spuriously rejected. Complements the corruption test.
192        #[test]
193        fn well_formed_record_is_never_rejected(high_water in any::<u64>()) {
194            let encoded = encode(high_water);
195            prop_assert!(decode(&encoded).is_ok());
196        }
197    }
198}