commonware_storage/qmdb/immutable/operation/
variable.rs1use super::{COMMIT_CONTEXT, Operation, SET_CONTEXT};
2use crate::{
3 merkle::Family,
4 qmdb::{
5 any::{VariableValue, value::VariableEncoding},
6 operation::{
7 Key, commit_variable_payload_size, read_commit_variable, write_commit_variable,
8 },
9 },
10};
11use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt as _, Write};
12use commonware_runtime::{Buf, BufMut};
13
14impl<F: Family, K: Key, V: VariableValue> EncodeSize for Operation<F, K, VariableEncoding<V>> {
15 fn encode_size(&self) -> usize {
16 1 + match self {
17 Self::Set(k, v) => k.encode_size() + v.encode_size(),
18 Self::Commit(v, floor) => commit_variable_payload_size(v, *floor),
19 }
20 }
21}
22
23impl<F: Family, K: Key, V: VariableValue> Write for Operation<F, K, VariableEncoding<V>> {
24 fn write(&self, buf: &mut impl BufMut) {
25 match &self {
26 Self::Set(k, v) => {
27 SET_CONTEXT.write(buf);
28 k.write(buf);
29 v.write(buf);
30 }
31 Self::Commit(v, floor_loc) => {
32 COMMIT_CONTEXT.write(buf);
33 write_commit_variable(v, *floor_loc, buf);
34 }
35 }
36 }
37}
38
39impl<F: Family, K: Key, V: VariableValue> Read for Operation<F, K, VariableEncoding<V>> {
40 type Cfg = (<K as Read>::Cfg, <V as Read>::Cfg);
41
42 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
43 match u8::read(buf)? {
44 SET_CONTEXT => {
45 let key = K::read_cfg(buf, &cfg.0)?;
46 let value = V::read_cfg(buf, &cfg.1)?;
47 Ok(Self::Set(key, value))
48 }
49 COMMIT_CONTEXT => {
50 let (metadata, floor_loc) = read_commit_variable(buf, &cfg.1)?;
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::{Location, mmr};
62 use commonware_codec::{DecodeExt, Encode, EncodeSize, FixedSize as _, varint::UInt};
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 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 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 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 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 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 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}