Skip to main content

commonware_storage/qmdb/immutable/operation/
variable.rs

1use super::{Operation, COMMIT_CONTEXT, SET_CONTEXT};
2use crate::{
3    merkle::{Family, Location},
4    qmdb::{
5        any::{value::VariableEncoding, VariableValue},
6        operation::Key,
7    },
8};
9use commonware_codec::{varint::UInt, EncodeSize, Error as CodecError, Read, ReadExt as _, Write};
10use commonware_runtime::{Buf, BufMut};
11
12impl<F: Family, K: Key, V: VariableValue> EncodeSize for Operation<F, K, VariableEncoding<V>> {
13    fn encode_size(&self) -> usize {
14        1 + match self {
15            Self::Set(k, v) => k.encode_size() + v.encode_size(),
16            Self::Commit(v, floor) => v.encode_size() + UInt(**floor).encode_size(),
17        }
18    }
19}
20
21impl<F: Family, K: Key, V: VariableValue> Write for Operation<F, K, VariableEncoding<V>> {
22    fn write(&self, buf: &mut impl BufMut) {
23        match &self {
24            Self::Set(k, v) => {
25                SET_CONTEXT.write(buf);
26                k.write(buf);
27                v.write(buf);
28            }
29            Self::Commit(v, floor_loc) => {
30                COMMIT_CONTEXT.write(buf);
31                v.write(buf);
32                UInt(**floor_loc).write(buf);
33            }
34        }
35    }
36}
37
38impl<F: Family, K: Key, V: VariableValue> Read for Operation<F, K, VariableEncoding<V>> {
39    type Cfg = (<K as Read>::Cfg, <V as Read>::Cfg);
40
41    fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
42        match u8::read(buf)? {
43            SET_CONTEXT => {
44                let key = K::read_cfg(buf, &cfg.0)?;
45                let value = V::read_cfg(buf, &cfg.1)?;
46                Ok(Self::Set(key, value))
47            }
48            COMMIT_CONTEXT => {
49                let metadata = Option::<V>::read_cfg(buf, &cfg.1)?;
50                let floor_loc = Location::read(buf)?;
51                Ok(Self::Commit(metadata, floor_loc))
52            }
53            e => Err(CodecError::InvalidEnum(e)),
54        }
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::merkle::mmr;
62    use commonware_codec::{DecodeExt, Encode, EncodeSize, FixedSize as _};
63    use commonware_utils::sequence::U64;
64
65    type VarOp = Operation<mmr::Family, U64, VariableEncoding<U64>>;
66
67    #[test]
68    fn test_operation_encode_decode() {
69        let key = U64::new(1234);
70        let value = U64::new(56789);
71
72        // Test Set operation
73        let set_op = VarOp::Set(key, value.clone());
74        let encoded = set_op.encode();
75        let decoded = VarOp::decode(encoded).unwrap();
76        assert_eq!(set_op, decoded);
77
78        // Test Commit operation with value
79        let commit_op = VarOp::Commit(Some(value), Location::new(100));
80        let encoded = commit_op.encode();
81        let decoded = VarOp::decode(encoded).unwrap();
82        assert_eq!(commit_op, decoded);
83
84        // Test Commit operation without value
85        let commit_op = VarOp::Commit(None, Location::new(0));
86        let encoded = commit_op.encode();
87        let decoded = VarOp::decode(encoded).unwrap();
88        assert_eq!(commit_op, decoded);
89    }
90
91    #[test]
92    fn test_operation_encode_size() {
93        let key = U64::new(1234);
94        let value = U64::new(56789);
95
96        let set_op = VarOp::Set(key, value.clone());
97        assert_eq!(set_op.encode_size(), 1 + U64::SIZE + value.encode_size());
98        assert_eq!(set_op.encode().len(), set_op.encode_size());
99
100        let floor = Location::new(100);
101        let commit_op = VarOp::Commit(Some(value.clone()), floor);
102        assert_eq!(
103            commit_op.encode_size(),
104            1 + Some(value).encode_size() + UInt(*floor).encode_size()
105        );
106        assert_eq!(commit_op.encode().len(), commit_op.encode_size());
107
108        let commit_op = VarOp::Commit(None, Location::new(0));
109        assert_eq!(
110            commit_op.encode_size(),
111            1 + Option::<U64>::None.encode_size() + UInt(0u64).encode_size()
112        );
113        assert_eq!(commit_op.encode().len(), commit_op.encode_size());
114    }
115
116    #[test]
117    fn test_operation_invalid_context() {
118        let invalid = vec![0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0];
119        let decoded = VarOp::decode(invalid.as_ref());
120        assert!(matches!(
121            decoded.unwrap_err(),
122            CodecError::InvalidEnum(0xFF)
123        ));
124    }
125
126    #[test]
127    fn test_operation_insufficient_buffer() {
128        let invalid = vec![SET_CONTEXT];
129        let decoded = VarOp::decode(invalid.as_ref());
130        assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
131
132        let invalid = vec![COMMIT_CONTEXT];
133        let decoded = VarOp::decode(invalid.as_ref());
134        assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
135    }
136
137    #[test]
138    fn test_operation_roundtrip_all_variants() {
139        let key = U64::new(100);
140        let value = U64::new(1000);
141
142        let operations: Vec<VarOp> = vec![
143            VarOp::Set(key, value.clone()),
144            VarOp::Commit(Some(value), Location::new(50)),
145            VarOp::Commit(None, Location::new(0)),
146        ];
147
148        for op in operations {
149            let encoded = op.encode();
150            let decoded = VarOp::decode(encoded.clone()).unwrap();
151            assert_eq!(op, decoded, "Failed to roundtrip: {op:?}");
152            assert_eq!(encoded.len(), op.encode_size(), "Size mismatch for: {op:?}");
153        }
154    }
155
156    #[test]
157    fn test_operation_variable_key_roundtrip() {
158        use commonware_codec::Decode as _;
159
160        type VecOp = Operation<mmr::Family, Vec<u8>, VariableEncoding<U64>>;
161
162        let key = vec![1u8, 2, 3, 4, 5];
163        let cfg = ((commonware_codec::RangeCfg::from(0..=100usize), ()), ());
164
165        // Test Set with variable-length key
166        let set_op = VecOp::Set(key, U64::new(42));
167        let encoded = set_op.encode();
168        assert_eq!(encoded.len(), set_op.encode_size());
169        let decoded = VecOp::decode_cfg(encoded, &cfg).unwrap();
170        assert_eq!(set_op, decoded);
171
172        // Test Commit (key-independent, should work the same)
173        let commit_op = VecOp::Commit(Some(U64::new(42)), Location::new(10));
174        let encoded = commit_op.encode();
175        let decoded = VecOp::decode_cfg(encoded, &cfg).unwrap();
176        assert_eq!(commit_op, decoded);
177
178        // Test empty key
179        let empty_key_op = VecOp::Set(vec![], U64::new(99));
180        let encoded = empty_key_op.encode();
181        let decoded = VecOp::decode_cfg(encoded, &cfg).unwrap();
182        assert_eq!(empty_key_op, decoded);
183    }
184
185    #[cfg(feature = "arbitrary")]
186    mod conformance {
187        use super::*;
188        use commonware_codec::conformance::CodecConformance;
189
190        type VarKeyOp = Operation<mmr::Family, Vec<u8>, VariableEncoding<U64>>;
191
192        commonware_conformance::conformance_tests! {
193            CodecConformance<VarOp>,
194            CodecConformance<VarKeyOp>
195        }
196    }
197}