1use std::sync::atomic::{AtomicU16, Ordering};
2
3use thiserror::Error;
4
5pub const HEADER_LEN: usize = 1 + 2 + 1 + 1 + 4 + 64;
7pub const HEADER_END_HOUR_OFFSET: usize = 4;
9pub const HEADER_LEN_OFFSET: usize = 5;
11pub const HEADER_CLIENT_PUBKEY_OFFSET: usize = 9;
13pub const TAILER_LEN: usize = 1;
15pub const MAGIC_END: u8 = 0x00;
17
18pub const MAGIC_SYNC_ZLIB_START: u8 = 0x06;
20pub const MAGIC_SYNC_NO_CRYPT_ZLIB_START: u8 = 0x08;
22pub const MAGIC_ASYNC_ZLIB_START: u8 = 0x07;
24pub const MAGIC_ASYNC_NO_CRYPT_ZLIB_START: u8 = 0x09;
26
27pub const MAGIC_SYNC_ZSTD_START: u8 = 0x0A;
29pub const MAGIC_SYNC_NO_CRYPT_ZSTD_START: u8 = 0x0B;
31pub const MAGIC_ASYNC_ZSTD_START: u8 = 0x0C;
33pub const MAGIC_ASYNC_NO_CRYPT_ZSTD_START: u8 = 0x0D;
35
36#[derive(Debug, Copy, Clone, PartialEq, Eq)]
37pub enum CompressionKind {
39 Zlib,
41 Zstd,
43}
44
45#[derive(Debug, Copy, Clone, PartialEq, Eq)]
46pub enum AppendMode {
48 Sync,
50 Async,
52}
53
54#[derive(Debug, Copy, Clone, PartialEq, Eq)]
55pub struct LogHeader {
57 pub magic: u8,
59 pub seq: u16,
61 pub begin_hour: u8,
63 pub end_hour: u8,
65 pub len: u32,
67 pub client_pubkey: [u8; 64],
69}
70
71#[derive(Debug, Error)]
72pub enum ProtocolError {
74 #[error("invalid header length")]
75 InvalidHeaderLen,
77 #[error("invalid magic byte: {0:#x}")]
78 InvalidMagic(u8),
80}
81
82impl LogHeader {
83 pub fn encode(self) -> [u8; HEADER_LEN] {
85 let mut out = [0u8; HEADER_LEN];
86 out[0] = self.magic;
87 out[1..3].copy_from_slice(&self.seq.to_le_bytes());
88 out[3] = self.begin_hour;
89 out[HEADER_END_HOUR_OFFSET] = self.end_hour;
90 out[HEADER_LEN_OFFSET..HEADER_LEN_OFFSET + 4].copy_from_slice(&self.len.to_le_bytes());
91 out[HEADER_CLIENT_PUBKEY_OFFSET..HEADER_CLIENT_PUBKEY_OFFSET + 64]
92 .copy_from_slice(&self.client_pubkey);
93 out
94 }
95
96 pub fn decode(buf: &[u8]) -> Result<Self, ProtocolError> {
98 if buf.len() < HEADER_LEN {
99 return Err(ProtocolError::InvalidHeaderLen);
100 }
101 let magic = buf[0];
102 if !magic_start_is_valid(magic) {
103 return Err(ProtocolError::InvalidMagic(magic));
104 }
105
106 let mut key = [0u8; 64];
107 key.copy_from_slice(&buf[HEADER_CLIENT_PUBKEY_OFFSET..HEADER_CLIENT_PUBKEY_OFFSET + 64]);
108 Ok(Self {
109 magic,
110 seq: u16::from_le_bytes([buf[1], buf[2]]),
111 begin_hour: buf[3],
112 end_hour: buf[HEADER_END_HOUR_OFFSET],
113 len: u32::from_le_bytes([
114 buf[HEADER_LEN_OFFSET],
115 buf[HEADER_LEN_OFFSET + 1],
116 buf[HEADER_LEN_OFFSET + 2],
117 buf[HEADER_LEN_OFFSET + 3],
118 ]),
119 client_pubkey: key,
120 })
121 }
122}
123
124pub fn select_magic(compress: CompressionKind, mode: AppendMode, crypt: bool) -> u8 {
126 match (compress, mode, crypt) {
127 (CompressionKind::Zlib, AppendMode::Sync, true) => MAGIC_SYNC_ZLIB_START,
128 (CompressionKind::Zlib, AppendMode::Sync, false) => MAGIC_SYNC_NO_CRYPT_ZLIB_START,
129 (CompressionKind::Zlib, AppendMode::Async, true) => MAGIC_ASYNC_ZLIB_START,
130 (CompressionKind::Zlib, AppendMode::Async, false) => MAGIC_ASYNC_NO_CRYPT_ZLIB_START,
131 (CompressionKind::Zstd, AppendMode::Sync, true) => MAGIC_SYNC_ZSTD_START,
132 (CompressionKind::Zstd, AppendMode::Sync, false) => MAGIC_SYNC_NO_CRYPT_ZSTD_START,
133 (CompressionKind::Zstd, AppendMode::Async, true) => MAGIC_ASYNC_ZSTD_START,
134 (CompressionKind::Zstd, AppendMode::Async, false) => MAGIC_ASYNC_NO_CRYPT_ZSTD_START,
135 }
136}
137
138pub fn magic_start_is_valid(magic: u8) -> bool {
140 matches!(
141 magic,
142 MAGIC_SYNC_ZLIB_START
143 | MAGIC_SYNC_NO_CRYPT_ZLIB_START
144 | MAGIC_ASYNC_ZLIB_START
145 | MAGIC_ASYNC_NO_CRYPT_ZLIB_START
146 | MAGIC_SYNC_ZSTD_START
147 | MAGIC_SYNC_NO_CRYPT_ZSTD_START
148 | MAGIC_ASYNC_ZSTD_START
149 | MAGIC_ASYNC_NO_CRYPT_ZSTD_START
150 )
151}
152
153pub fn update_log_len_in_place(buf: &mut [u8], add_len: u32) -> Result<u32, ProtocolError> {
157 if buf.len() < HEADER_LEN {
158 return Err(ProtocolError::InvalidHeaderLen);
159 }
160 let current = u32::from_le_bytes([
161 buf[HEADER_LEN_OFFSET],
162 buf[HEADER_LEN_OFFSET + 1],
163 buf[HEADER_LEN_OFFSET + 2],
164 buf[HEADER_LEN_OFFSET + 3],
165 ]);
166 let next = current.saturating_add(add_len);
167 buf[HEADER_LEN_OFFSET..HEADER_LEN_OFFSET + 4].copy_from_slice(&next.to_le_bytes());
168 Ok(next)
169}
170
171pub fn update_end_hour_in_place(buf: &mut [u8], hour: u8) -> Result<(), ProtocolError> {
173 if buf.len() < HEADER_LEN {
174 return Err(ProtocolError::InvalidHeaderLen);
175 }
176 buf[HEADER_END_HOUR_OFFSET] = hour;
177 Ok(())
178}
179
180pub struct SeqGenerator {
182 seq: AtomicU16,
183}
184
185impl Default for SeqGenerator {
186 fn default() -> Self {
187 Self {
188 seq: AtomicU16::new(0),
189 }
190 }
191}
192
193impl SeqGenerator {
194 pub fn with_seed(seed: u16) -> Self {
196 Self {
197 seq: AtomicU16::new(seed),
198 }
199 }
200
201 pub fn next_async(&self) -> u16 {
207 let mut next = self.seq.fetch_add(1, Ordering::Relaxed).wrapping_add(1);
208 if next == 0 {
209 next = self.seq.fetch_add(1, Ordering::Relaxed).wrapping_add(1);
210 if next == 0 {
211 next = 1;
212 self.seq.store(1, Ordering::Relaxed);
213 }
214 }
215 next
216 }
217
218 pub fn sync_seq() -> u16 {
220 0
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::{
227 update_end_hour_in_place, update_log_len_in_place, LogHeader, ProtocolError, SeqGenerator,
228 HEADER_LEN, MAGIC_ASYNC_NO_CRYPT_ZLIB_START,
229 };
230
231 #[test]
232 fn decode_rejects_short_buffers_and_invalid_magic() {
233 assert!(matches!(
234 LogHeader::decode(&[0u8; HEADER_LEN - 1]),
235 Err(ProtocolError::InvalidHeaderLen)
236 ));
237
238 let mut bytes = [0u8; HEADER_LEN];
239 bytes[0] = 0x05;
240 assert!(matches!(
241 LogHeader::decode(&bytes),
242 Err(ProtocolError::InvalidMagic(0x05))
243 ));
244 }
245
246 #[test]
247 fn in_place_updates_reject_short_buffers_and_saturate_length() {
248 let mut short = [0u8; HEADER_LEN - 1];
249 assert!(matches!(
250 update_log_len_in_place(&mut short, 1),
251 Err(ProtocolError::InvalidHeaderLen)
252 ));
253 assert!(matches!(
254 update_end_hour_in_place(&mut short, 1),
255 Err(ProtocolError::InvalidHeaderLen)
256 ));
257
258 let mut bytes = LogHeader {
259 magic: MAGIC_ASYNC_NO_CRYPT_ZLIB_START,
260 seq: 1,
261 begin_hour: 2,
262 end_hour: 2,
263 len: u32::MAX - 1,
264 client_pubkey: [0; 64],
265 }
266 .encode();
267 assert_eq!(update_log_len_in_place(&mut bytes, 10).unwrap(), u32::MAX);
268 update_end_hour_in_place(&mut bytes, 9).unwrap();
269 let decoded = LogHeader::decode(&bytes).unwrap();
270 assert_eq!(decoded.len, u32::MAX);
271 assert_eq!(decoded.end_hour, 9);
272 }
273
274 #[test]
275 fn seq_generator_wraps_without_emitting_zero() {
276 let seq = SeqGenerator::with_seed(u16::MAX - 1);
277 assert_eq!(seq.next_async(), u16::MAX);
278 assert_eq!(seq.next_async(), 1);
279 assert_eq!(seq.next_async(), 2);
280 assert_eq!(SeqGenerator::sync_seq(), 0);
281 }
282}