Skip to main content

commonware_storage/qmdb/immutable/operation/
fixed.rs

1use super::{Operation, COMMIT_CONTEXT, SET_CONTEXT};
2use crate::qmdb::any::{value::FixedEncoding, FixedValue};
3use commonware_codec::{
4    util::{at_least, ensure_zeros},
5    Error as CodecError, FixedSize, Read, ReadExt as _, Write,
6};
7use commonware_runtime::{Buf, BufMut};
8use commonware_utils::Array;
9
10/// `max(a, b)` in a const context.
11const fn const_max(a: usize, b: usize) -> usize {
12    if a > b {
13        a
14    } else {
15        b
16    }
17}
18
19const fn set_op_size<K: Array, V: FixedSize>() -> usize {
20    1 + K::SIZE + V::SIZE
21}
22
23const fn commit_op_size<V: FixedSize>() -> usize {
24    1 + 1 + V::SIZE
25}
26
27const fn total_op_size<K: Array, V: FixedSize>() -> usize {
28    const_max(set_op_size::<K, V>(), commit_op_size::<V>())
29}
30
31impl<K: Array, V: FixedValue> FixedSize for Operation<K, FixedEncoding<V>> {
32    const SIZE: usize = total_op_size::<K, V>();
33}
34
35impl<K: Array, V: FixedValue> Write for Operation<K, FixedEncoding<V>> {
36    fn write(&self, buf: &mut impl BufMut) {
37        let total = total_op_size::<K, V>();
38        match &self {
39            Self::Set(k, v) => {
40                SET_CONTEXT.write(buf);
41                k.write(buf);
42                v.write(buf);
43                buf.put_bytes(0, total - set_op_size::<K, V>());
44            }
45            Self::Commit(v) => {
46                COMMIT_CONTEXT.write(buf);
47                if let Some(v) = v {
48                    true.write(buf);
49                    v.write(buf);
50                } else {
51                    buf.put_bytes(0, 1 + V::SIZE);
52                }
53                buf.put_bytes(0, total - commit_op_size::<V>());
54            }
55        }
56    }
57}
58
59impl<K: Array, V: FixedValue> Read for Operation<K, FixedEncoding<V>> {
60    type Cfg = ();
61
62    fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result<Self, CodecError> {
63        let total = total_op_size::<K, V>();
64        at_least(buf, total)?;
65
66        match u8::read(buf)? {
67            SET_CONTEXT => {
68                let key = K::read(buf)?;
69                let value = V::read(buf)?;
70                ensure_zeros(buf, total - set_op_size::<K, V>())?;
71                Ok(Self::Set(key, value))
72            }
73            COMMIT_CONTEXT => {
74                let is_some = bool::read(buf)?;
75                let value = if is_some {
76                    Some(V::read(buf)?)
77                } else {
78                    ensure_zeros(buf, V::SIZE)?;
79                    None
80                };
81                ensure_zeros(buf, total - commit_op_size::<V>())?;
82                Ok(Self::Commit(value))
83            }
84            e => Err(CodecError::InvalidEnum(e)),
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use commonware_codec::{DecodeExt, Encode};
93    use commonware_utils::sequence::U64;
94
95    type FixedOp = Operation<U64, FixedEncoding<U64>>;
96
97    #[test]
98    fn test_fixed_size() {
99        // Set: 1 + 8 + 8 = 17
100        // Commit: 1 + 1 + 8 = 10
101        // Max = 17
102        assert_eq!(FixedOp::SIZE, 17);
103    }
104
105    #[test]
106    fn test_uniform_encoding_size() {
107        let set_op = FixedOp::Set(U64::new(1), U64::new(2));
108        let commit_some = FixedOp::Commit(Some(U64::new(3)));
109        let commit_none = FixedOp::Commit(None);
110
111        assert_eq!(set_op.encode().len(), FixedOp::SIZE);
112        assert_eq!(commit_some.encode().len(), FixedOp::SIZE);
113        assert_eq!(commit_none.encode().len(), FixedOp::SIZE);
114    }
115
116    #[test]
117    fn test_roundtrip() {
118        let operations: Vec<FixedOp> = vec![
119            FixedOp::Set(U64::new(1234), U64::new(56789)),
120            FixedOp::Commit(Some(U64::new(42))),
121            FixedOp::Commit(None),
122        ];
123
124        for op in operations {
125            let encoded = op.encode();
126            assert_eq!(encoded.len(), FixedOp::SIZE);
127            let decoded = FixedOp::decode(encoded).unwrap();
128            assert_eq!(op, decoded, "Failed to roundtrip: {op:?}");
129        }
130    }
131
132    #[test]
133    fn test_invalid_context() {
134        let mut invalid = vec![0xFF];
135        invalid.resize(FixedOp::SIZE, 0);
136        let decoded = FixedOp::decode(invalid.as_ref());
137        assert!(matches!(
138            decoded.unwrap_err(),
139            CodecError::InvalidEnum(0xFF)
140        ));
141    }
142
143    #[test]
144    fn test_insufficient_buffer() {
145        let invalid = vec![SET_CONTEXT];
146        let decoded = FixedOp::decode(invalid.as_ref());
147        assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
148    }
149
150    #[test]
151    fn test_nonzero_padding_rejected() {
152        let op = FixedOp::Set(U64::new(1), U64::new(2));
153        let mut encoded: Vec<u8> = op.encode().to_vec();
154        // Corrupt padding byte (only if there is padding)
155        if set_op_size::<U64, U64>() < total_op_size::<U64, U64>() {
156            let last = encoded.len() - 1;
157            encoded[last] = 0xFF;
158            let decoded = FixedOp::decode(encoded.as_ref());
159            assert!(decoded.is_err());
160        }
161    }
162
163    #[cfg(feature = "arbitrary")]
164    mod conformance {
165        use super::*;
166        use commonware_codec::conformance::CodecConformance;
167
168        commonware_conformance::conformance_tests! {
169            CodecConformance<FixedOp>
170        }
171    }
172}