commonware_storage/qmdb/keyless/operation/
variable.rs1use crate::qmdb::{
2 any::{value::VariableEncoding, VariableValue},
3 keyless::operation::{Codec, Operation, APPEND_CONTEXT, COMMIT_CONTEXT},
4};
5use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt as _, Write};
6use commonware_runtime::{Buf, BufMut};
7
8impl<V: VariableValue> Codec for VariableEncoding<V> {
9 type ReadCfg = <V as Read>::Cfg;
10
11 fn write_operation(op: &Operation<Self>, buf: &mut impl BufMut) {
12 match op {
13 Operation::Append(value) => {
14 APPEND_CONTEXT.write(buf);
15 value.write(buf);
16 }
17 Operation::Commit(metadata) => {
18 COMMIT_CONTEXT.write(buf);
19 metadata.write(buf);
20 }
21 }
22 }
23
24 fn read_operation(
25 buf: &mut impl Buf,
26 cfg: &Self::ReadCfg,
27 ) -> Result<Operation<Self>, CodecError> {
28 match u8::read(buf)? {
29 APPEND_CONTEXT => Ok(Operation::Append(V::read_cfg(buf, cfg)?)),
30 COMMIT_CONTEXT => Ok(Operation::Commit(Option::<V>::read_cfg(buf, cfg)?)),
31 e => Err(CodecError::InvalidEnum(e)),
32 }
33 }
34}
35
36impl<V: VariableValue> EncodeSize for Operation<VariableEncoding<V>> {
37 fn encode_size(&self) -> usize {
38 1 + match self {
39 Self::Append(v) => v.encode_size(),
40 Self::Commit(v) => v.encode_size(),
41 }
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use commonware_codec::{DecodeExt, Encode, EncodeSize};
49 use commonware_utils::sequence::U64;
50
51 type Op = Operation<VariableEncoding<U64>>;
53
54 #[test]
55 fn append_roundtrip() {
56 let op = Op::Append(U64::new(12345));
57 let decoded = Op::decode(op.encode()).unwrap();
58 assert_eq!(op, decoded);
59 }
60
61 #[test]
62 fn commit_some_roundtrip() {
63 let op = Op::Commit(Some(U64::new(999)));
64 let decoded = Op::decode(op.encode()).unwrap();
65 assert_eq!(op, decoded);
66 }
67
68 #[test]
69 fn commit_none_roundtrip() {
70 let op = Op::Commit(None);
71 let decoded = Op::decode(op.encode()).unwrap();
72 assert_eq!(op, decoded);
73 }
74
75 #[test]
76 fn encode_size_matches_encoded_len() {
77 let cases: Vec<Op> = vec![
78 Op::Append(U64::new(0)),
79 Op::Append(U64::new(u64::MAX)),
80 Op::Commit(None),
81 Op::Commit(Some(U64::new(42))),
82 ];
83 for op in cases {
84 assert_eq!(op.encode_size(), op.encode().len(), "mismatch for {op:?}");
85 }
86 }
87
88 #[test]
89 fn invalid_context_byte_rejected() {
90 let op = Op::Append(U64::new(1));
91 let mut buf: Vec<u8> = op.encode().to_vec();
92 buf[0] = 0xFF;
93 assert!(matches!(
94 Op::decode(buf.as_ref()).unwrap_err(),
95 CodecError::InvalidEnum(0xFF)
96 ));
97 }
98
99 #[test]
100 fn empty_input_rejected() {
101 assert!(Op::decode(&[] as &[u8]).is_err());
102 }
103
104 #[test]
105 fn append_and_commit_have_different_encodings() {
106 let append = Op::Append(U64::new(1));
107 let commit = Op::Commit(Some(U64::new(1)));
108 assert_ne!(append.encode().as_ref(), commit.encode().as_ref());
109 }
110
111 #[test]
112 fn context_byte_is_first() {
113 let append = Op::Append(U64::new(0));
114 let commit = Op::Commit(None);
115 assert_eq!(append.encode()[0], APPEND_CONTEXT);
116 assert_eq!(commit.encode()[0], COMMIT_CONTEXT);
117 }
118}