commonware_storage/qmdb/immutable/operation/
fixed.rs1use 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
16const 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 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 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}