commonware_storage/qmdb/keyless/operation/
fixed.rs1use crate::{
2 merkle::{Family, Location},
3 qmdb::{
4 any::{value::FixedEncoding, FixedValue},
5 keyless::operation::{Codec, Operation, APPEND_CONTEXT, COMMIT_CONTEXT},
6 },
7};
8use commonware_codec::{
9 util::{at_least, ensure_zeros},
10 Error as CodecError, FixedSize, ReadExt as _, Write,
11};
12use commonware_runtime::{Buf, BufMut};
13
14const fn op_size<V: FixedSize>() -> usize {
21 2 + V::SIZE + u64::SIZE
22}
23
24impl<V: FixedValue> Codec for FixedEncoding<V> {
25 type ReadCfg = ();
26
27 fn write_operation<F: Family>(op: &Operation<F, Self>, buf: &mut impl BufMut) {
28 let total = op_size::<V>();
29 match op {
30 Operation::Append(value) => {
31 APPEND_CONTEXT.write(buf);
32 value.write(buf);
33 buf.put_bytes(0, total - 1 - V::SIZE);
35 }
36 Operation::Commit(metadata, floor) => {
37 COMMIT_CONTEXT.write(buf);
38 if let Some(metadata) = metadata {
39 true.write(buf);
40 metadata.write(buf);
41 } else {
42 buf.put_bytes(0, 1 + V::SIZE);
43 }
44 buf.put_slice(&floor.as_u64().to_be_bytes());
45 }
46 }
47 }
48
49 fn read_operation<F: Family>(
50 buf: &mut impl Buf,
51 _cfg: &Self::ReadCfg,
52 ) -> Result<Operation<F, Self>, CodecError> {
53 let total = op_size::<V>();
54 at_least(buf, total)?;
55
56 match u8::read(buf)? {
57 APPEND_CONTEXT => {
58 let value = V::read(buf)?;
59 ensure_zeros(buf, total - 1 - V::SIZE)?;
60 Ok(Operation::Append(value))
61 }
62 COMMIT_CONTEXT => {
63 let is_some = bool::read(buf)?;
64 let metadata = if is_some {
65 Some(V::read(buf)?)
66 } else {
67 ensure_zeros(buf, V::SIZE)?;
68 None
69 };
70 let floor = Location::<F>::new(u64::read(buf)?);
71 if !floor.is_valid() {
72 return Err(CodecError::Invalid(
73 "storage::qmdb::keyless::operation::fixed::Operation",
74 "commit floor location overflow",
75 ));
76 }
77 Ok(Operation::Commit(metadata, floor))
78 }
79 e => Err(CodecError::InvalidEnum(e)),
80 }
81 }
82}
83
84impl<F: Family, V: FixedValue> FixedSize for Operation<F, FixedEncoding<V>> {
85 const SIZE: usize = op_size::<V>();
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::merkle::mmr;
92 use commonware_codec::{DecodeExt, Encode, FixedSize};
93 use commonware_utils::sequence::U64;
94
95 type Op = Operation<mmr::Family, FixedEncoding<U64>>;
96
97 #[test]
98 fn all_variants_have_same_encoded_size() {
99 let append = Op::Append(U64::new(42));
100 let commit_some = Op::Commit(Some(U64::new(99)), Location::new(5));
101 let commit_none = Op::Commit(None, Location::new(0));
102
103 let a = append.encode();
104 let b = commit_some.encode();
105 let c = commit_none.encode();
106
107 assert_eq!(a.len(), Op::SIZE);
108 assert_eq!(b.len(), Op::SIZE);
109 assert_eq!(c.len(), Op::SIZE);
110 assert_eq!(Op::SIZE, 2 + U64::SIZE + u64::SIZE);
111 }
112
113 #[test]
114 fn append_roundtrip() {
115 let op = Op::Append(U64::new(12345));
116 let decoded = Op::decode(op.encode()).unwrap();
117 assert_eq!(op, decoded);
118 }
119
120 #[test]
121 fn commit_some_roundtrip() {
122 let op = Op::Commit(Some(U64::new(999)), Location::new(77));
123 let decoded = Op::decode(op.encode()).unwrap();
124 assert_eq!(op, decoded);
125 }
126
127 #[test]
128 fn commit_none_roundtrip() {
129 let op = Op::Commit(None, Location::new(42));
130 let decoded = Op::decode(op.encode()).unwrap();
131 assert_eq!(op, decoded);
132 }
133
134 #[test]
135 fn invalid_context_byte_rejected() {
136 let mut buf = vec![0u8; Op::SIZE];
137 buf[0] = 0xFF;
138 assert!(matches!(
139 Op::decode(buf.as_ref()).unwrap_err(),
140 CodecError::InvalidEnum(0xFF)
141 ));
142 }
143
144 #[test]
145 fn non_zero_padding_rejected() {
146 let op = Op::Append(U64::new(1));
148 let mut buf: Vec<u8> = op.encode().to_vec();
149 *buf.last_mut().unwrap() = 0x01;
151 assert!(Op::decode(buf.as_ref()).is_err());
152 }
153
154 #[test]
155 fn truncated_input_rejected() {
156 let op = Op::Append(U64::new(1));
157 let buf = op.encode();
158 assert!(Op::decode(&buf[..buf.len() - 1]).is_err());
160 }
161
162 #[test]
163 fn commit_none_has_zero_value_bytes() {
164 let op = Op::Commit(None, Location::new(0));
165 let buf: Vec<u8> = op.encode().to_vec();
166 assert!(buf[2..].iter().all(|&b| b == 0));
169 }
170
171 #[test]
172 fn commit_floor_overflow_rejected() {
173 let mut buf = vec![0u8; Op::SIZE];
175 buf[0] = COMMIT_CONTEXT;
176 let floor_bytes = u64::MAX.to_be_bytes();
179 let floor_offset = Op::SIZE - u64::SIZE;
180 buf[floor_offset..].copy_from_slice(&floor_bytes);
181 assert!(matches!(
182 Op::decode(buf.as_ref()).unwrap_err(),
183 CodecError::Invalid(_, _)
184 ));
185 }
186}