Skip to main content

commonware_storage/qmdb/immutable/operation/
fixed.rs

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