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