1use alloc::vec::Vec;
7use core::fmt;
8
9pub const MAGIC_BYTE: u8 = 0xCF;
11
12pub const ENVELOPE_HEADER_SIZE: usize = 3;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17#[repr(u8)]
18pub enum CrdtType {
19 GCounter = 1,
21 PNCounter = 2,
23 GSet = 3,
25 TwoPSet = 4,
27 LWWRegister = 5,
29 MVRegister = 6,
31 ORSet = 7,
33 Rga = 8,
35 TextCrdt = 9,
37 LWWMap = 10,
39 AWMap = 11,
41}
42
43impl CrdtType {
44 pub fn from_byte(b: u8) -> Option<Self> {
46 match b {
47 1 => Some(Self::GCounter),
48 2 => Some(Self::PNCounter),
49 3 => Some(Self::GSet),
50 4 => Some(Self::TwoPSet),
51 5 => Some(Self::LWWRegister),
52 6 => Some(Self::MVRegister),
53 7 => Some(Self::ORSet),
54 8 => Some(Self::Rga),
55 9 => Some(Self::TextCrdt),
56 10 => Some(Self::LWWMap),
57 11 => Some(Self::AWMap),
58 _ => None,
59 }
60 }
61}
62
63pub trait Versioned: Sized {
68 const CURRENT_VERSION: u8;
70
71 const CRDT_TYPE: CrdtType;
73}
74
75#[derive(Debug, Clone)]
77pub enum VersionError {
78 Serialize(alloc::string::String),
80 Deserialize(alloc::string::String),
82}
83
84impl fmt::Display for VersionError {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 Self::Serialize(msg) => write!(f, "serialization error: {msg}"),
88 Self::Deserialize(msg) => write!(f, "deserialization error: {msg}"),
89 }
90 }
91}
92
93#[cfg(feature = "std")]
94impl std::error::Error for VersionError {}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
100pub enum EnvelopeError {
101 TooShort,
103 InvalidMagic(u8),
105 UnknownCrdtType(u8),
107}
108
109impl fmt::Display for EnvelopeError {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 Self::TooShort => write!(f, "data too short for version envelope"),
113 Self::InvalidMagic(b) => write!(f, "invalid magic byte: 0x{b:02X}, expected 0xCF"),
114 Self::UnknownCrdtType(b) => write!(f, "unknown CRDT type: {b}"),
115 }
116 }
117}
118
119#[cfg(feature = "std")]
120impl std::error::Error for EnvelopeError {}
121
122#[derive(Debug, Clone, PartialEq)]
145pub struct VersionedEnvelope {
146 pub version: u8,
148 pub crdt_type: CrdtType,
150 pub payload: Vec<u8>,
152}
153
154impl VersionedEnvelope {
155 pub fn new(version: u8, crdt_type: CrdtType, payload: Vec<u8>) -> Self {
157 Self {
158 version,
159 crdt_type,
160 payload,
161 }
162 }
163
164 #[must_use]
166 pub fn to_bytes(&self) -> Vec<u8> {
167 let mut bytes = Vec::with_capacity(ENVELOPE_HEADER_SIZE + self.payload.len());
168 bytes.push(MAGIC_BYTE);
169 bytes.push(self.version);
170 bytes.push(self.crdt_type as u8);
171 bytes.extend_from_slice(&self.payload);
172 bytes
173 }
174
175 pub fn from_bytes(data: &[u8]) -> Result<Self, EnvelopeError> {
177 if data.len() < ENVELOPE_HEADER_SIZE {
178 return Err(EnvelopeError::TooShort);
179 }
180 if data[0] != MAGIC_BYTE {
181 return Err(EnvelopeError::InvalidMagic(data[0]));
182 }
183 let version = data[1];
184 let crdt_type =
185 CrdtType::from_byte(data[2]).ok_or(EnvelopeError::UnknownCrdtType(data[2]))?;
186 let payload = data[ENVELOPE_HEADER_SIZE..].to_vec();
187 Ok(Self {
188 version,
189 crdt_type,
190 payload,
191 })
192 }
193
194 pub fn peek_version(data: &[u8]) -> Result<u8, EnvelopeError> {
196 if data.len() < 2 {
197 return Err(EnvelopeError::TooShort);
198 }
199 if data[0] != MAGIC_BYTE {
200 return Err(EnvelopeError::InvalidMagic(data[0]));
201 }
202 Ok(data[1])
203 }
204
205 #[must_use]
207 pub fn is_versioned(data: &[u8]) -> bool {
208 data.first() == Some(&MAGIC_BYTE)
209 }
210}
211
212impl Versioned for crate::GCounter {
215 const CURRENT_VERSION: u8 = 1;
216 const CRDT_TYPE: CrdtType = CrdtType::GCounter;
217}
218
219impl Versioned for crate::PNCounter {
220 const CURRENT_VERSION: u8 = 1;
221 const CRDT_TYPE: CrdtType = CrdtType::PNCounter;
222}
223
224impl<T: Ord + Clone> Versioned for crate::GSet<T> {
225 const CURRENT_VERSION: u8 = 1;
226 const CRDT_TYPE: CrdtType = CrdtType::GSet;
227}
228
229impl<T: Ord + Clone> Versioned for crate::TwoPSet<T> {
230 const CURRENT_VERSION: u8 = 1;
231 const CRDT_TYPE: CrdtType = CrdtType::TwoPSet;
232}
233
234impl<T: Clone> Versioned for crate::LWWRegister<T> {
235 const CURRENT_VERSION: u8 = 1;
236 const CRDT_TYPE: CrdtType = CrdtType::LWWRegister;
237}
238
239impl<T: Clone + Ord> Versioned for crate::MVRegister<T> {
240 const CURRENT_VERSION: u8 = 1;
241 const CRDT_TYPE: CrdtType = CrdtType::MVRegister;
242}
243
244impl<T: Ord + Clone> Versioned for crate::ORSet<T> {
245 const CURRENT_VERSION: u8 = 1;
246 const CRDT_TYPE: CrdtType = CrdtType::ORSet;
247}
248
249impl<T: Clone + Ord> Versioned for crate::Rga<T> {
250 const CURRENT_VERSION: u8 = 1;
251 const CRDT_TYPE: CrdtType = CrdtType::Rga;
252}
253
254impl Versioned for crate::TextCrdt {
255 const CURRENT_VERSION: u8 = 1;
256 const CRDT_TYPE: CrdtType = CrdtType::TextCrdt;
257}
258
259impl<K: Ord + Clone, V: Clone> Versioned for crate::LWWMap<K, V> {
260 const CURRENT_VERSION: u8 = 1;
261 const CRDT_TYPE: CrdtType = CrdtType::LWWMap;
262}
263
264impl<K: Ord + Clone, V: Clone + Eq> Versioned for crate::AWMap<K, V> {
265 const CURRENT_VERSION: u8 = 1;
266 const CRDT_TYPE: CrdtType = CrdtType::AWMap;
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn envelope_roundtrip() {
275 let original = VersionedEnvelope::new(3, CrdtType::ORSet, b"test-payload".to_vec());
276 let bytes = original.to_bytes();
277 let decoded = VersionedEnvelope::from_bytes(&bytes).unwrap();
278 assert_eq!(original, decoded);
279 }
280
281 #[test]
282 fn envelope_header_size() {
283 let envelope = VersionedEnvelope::new(1, CrdtType::GCounter, vec![]);
284 let bytes = envelope.to_bytes();
285 assert_eq!(bytes.len(), ENVELOPE_HEADER_SIZE);
286 }
287
288 #[test]
289 fn envelope_peek_version() {
290 let envelope = VersionedEnvelope::new(42, CrdtType::TextCrdt, b"data".to_vec());
291 let bytes = envelope.to_bytes();
292 assert_eq!(VersionedEnvelope::peek_version(&bytes).unwrap(), 42);
293 }
294
295 #[test]
296 fn envelope_is_versioned() {
297 assert!(VersionedEnvelope::is_versioned(&[MAGIC_BYTE, 1, 1]));
298 assert!(!VersionedEnvelope::is_versioned(&[0x00, 1, 1]));
299 assert!(!VersionedEnvelope::is_versioned(&[]));
300 }
301
302 #[test]
303 fn envelope_error_too_short() {
304 assert_eq!(
305 VersionedEnvelope::from_bytes(&[MAGIC_BYTE]),
306 Err(EnvelopeError::TooShort)
307 );
308 }
309
310 #[test]
311 fn envelope_error_invalid_magic() {
312 assert_eq!(
313 VersionedEnvelope::from_bytes(&[0xAB, 1, 1]),
314 Err(EnvelopeError::InvalidMagic(0xAB))
315 );
316 }
317
318 #[test]
319 fn envelope_error_unknown_crdt_type() {
320 assert_eq!(
321 VersionedEnvelope::from_bytes(&[MAGIC_BYTE, 1, 200]),
322 Err(EnvelopeError::UnknownCrdtType(200))
323 );
324 }
325
326 #[test]
327 fn all_crdt_types_roundtrip() {
328 let types = [
329 CrdtType::GCounter,
330 CrdtType::PNCounter,
331 CrdtType::GSet,
332 CrdtType::TwoPSet,
333 CrdtType::LWWRegister,
334 CrdtType::MVRegister,
335 CrdtType::ORSet,
336 CrdtType::Rga,
337 CrdtType::TextCrdt,
338 CrdtType::LWWMap,
339 CrdtType::AWMap,
340 ];
341 for ct in types {
342 let envelope = VersionedEnvelope::new(1, ct, b"x".to_vec());
343 let decoded = VersionedEnvelope::from_bytes(&envelope.to_bytes()).unwrap();
344 assert_eq!(decoded.crdt_type, ct);
345 }
346 }
347
348 #[test]
349 fn crdt_type_from_byte_unknown() {
350 assert_eq!(CrdtType::from_byte(0), None);
351 assert_eq!(CrdtType::from_byte(12), None);
352 assert_eq!(CrdtType::from_byte(255), None);
353 }
354}