Skip to main content

commonware_storage/qmdb/keyless/operation/
variable.rs

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