1use bytes::{Buf, BufMut};
7use commonware_codec::{
8 util::at_least, Codec, EncodeSize, Error as CodecError, FixedSize, Read, ReadExt, Write,
9};
10use commonware_utils::Array;
11use std::{
12 cmp::{Ord, PartialOrd},
13 fmt::{Debug, Display},
14 hash::Hash,
15};
16use thiserror::Error;
17
18const DELETE_CONTEXT: u8 = 0;
20const UPDATE_CONTEXT: u8 = 1;
21const COMMIT_FLOOR_CONTEXT: u8 = 2;
22const SET_CONTEXT: u8 = 3;
23const COMMIT_CONTEXT: u8 = 4;
24
25#[derive(Error, Debug)]
27pub enum Error {
28 #[error("invalid length")]
29 InvalidLength,
30 #[error("invalid key: {0}")]
31 InvalidKey(CodecError),
32 #[error("invalid value: {0}")]
33 InvalidValue(CodecError),
34 #[error("invalid context byte")]
35 InvalidContextByte,
36 #[error("delete operation has non-zero value")]
37 InvalidDeleteOp,
38 #[error("commit floor operation has non-zero bytes after location")]
39 InvalidCommitFloorOp,
40}
41
42#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
44pub enum Fixed<K: Array, V: Array> {
45 Delete(K),
47
48 Update(K, V),
50
51 CommitFloor(u64),
54}
55
56#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
58pub enum Variable<K: Array, V: Codec> {
59 Set(K, V),
61 Commit(),
62 Delete(K),
64 Update(K, V),
65 CommitFloor(u64),
66}
67
68impl<K: Array, V: Array> FixedSize for Fixed<K, V> {
69 const SIZE: usize = u8::SIZE + K::SIZE + V::SIZE;
70}
71
72impl<K: Array, V: Codec> EncodeSize for Variable<K, V> {
73 fn encode_size(&self) -> usize {
74 match self {
75 Variable::Delete(_) => 1 + K::SIZE,
76 Variable::Update(_, v) => 1 + K::SIZE + v.encode_size(),
77 Variable::CommitFloor(_) => 1 + u64::SIZE,
78 Variable::Set(_, v) => 1 + K::SIZE + v.encode_size(),
79 Variable::Commit() => 1,
80 }
81 }
82}
83
84impl<K: Array, V: Array> Fixed<K, V> {
85 const _MIN_OPERATION_LEN: usize = 9;
88 const _COMMIT_OP_ASSERT: () = assert!(
89 Self::SIZE >= Self::_MIN_OPERATION_LEN,
90 "array size too small for commit op"
91 );
92
93 pub fn to_key(&self) -> Option<&K> {
96 match self {
97 Fixed::Delete(key) => Some(key),
98 Fixed::Update(key, _) => Some(key),
99 Fixed::CommitFloor(_) => None,
100 }
101 }
102
103 pub fn to_value(&self) -> Option<&V> {
106 match self {
107 Fixed::Delete(_) => None,
108 Fixed::Update(_, value) => Some(value),
109 Fixed::CommitFloor(_) => None,
110 }
111 }
112}
113
114impl<K: Array, V: Codec> Variable<K, V> {
115 pub fn to_key(&self) -> Option<&K> {
117 match self {
118 Variable::Set(key, _) => Some(key),
119 Variable::Commit() => None,
120 Variable::Delete(key) => Some(key),
121 Variable::Update(key, _) => Some(key),
122 Variable::CommitFloor(_) => None,
123 }
124 }
125
126 pub fn to_value(&self) -> Option<&V> {
128 match self {
129 Variable::Set(_, value) => Some(value),
130 Variable::Commit() => None,
131 Variable::Delete(_) => None,
132 Variable::Update(_, value) => Some(value),
133 Variable::CommitFloor(_) => None,
134 }
135 }
136}
137
138impl<K: Array, V: Array> Write for Fixed<K, V> {
139 fn write(&self, buf: &mut impl BufMut) {
140 match &self {
141 Fixed::Delete(k) => {
142 buf.put_u8(DELETE_CONTEXT);
143 k.write(buf);
144 buf.put_bytes(0, V::SIZE);
146 }
147 Fixed::Update(k, v) => {
148 buf.put_u8(UPDATE_CONTEXT);
149 k.write(buf);
150 v.write(buf);
151 }
152 Fixed::CommitFloor(floor_loc) => {
153 buf.put_u8(COMMIT_FLOOR_CONTEXT);
154 buf.put_slice(&floor_loc.to_be_bytes());
155 buf.put_bytes(0, Self::SIZE - 1 - u64::SIZE);
157 }
158 }
159 }
160}
161
162impl<K: Array, V: Codec> Write for Variable<K, V> {
163 fn write(&self, buf: &mut impl BufMut) {
164 match &self {
165 Variable::Set(k, v) => {
166 buf.put_u8(SET_CONTEXT);
167 k.write(buf);
168 v.write(buf);
169 }
170 Variable::Commit() => {
171 buf.put_u8(COMMIT_CONTEXT);
172 }
173 Variable::Delete(k) => {
174 buf.put_u8(DELETE_CONTEXT);
175 k.write(buf);
176 }
177 Variable::Update(k, v) => {
178 buf.put_u8(UPDATE_CONTEXT);
179 k.write(buf);
180 v.write(buf);
181 }
182 Variable::CommitFloor(floor_loc) => {
183 buf.put_u8(COMMIT_FLOOR_CONTEXT);
184 buf.put_slice(&floor_loc.to_be_bytes());
185 }
186 }
187 }
188}
189
190impl<K: Array, V: Array> Read for Fixed<K, V> {
191 type Cfg = ();
192
193 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
194 at_least(buf, Self::SIZE)?;
195
196 match u8::read(buf)? {
197 UPDATE_CONTEXT => {
198 let key = K::read(buf)?;
199 let value = V::read(buf)?;
200 Ok(Self::Update(key, value))
201 }
202 DELETE_CONTEXT => {
203 let key = K::read(buf)?;
204 for _ in 0..V::SIZE {
206 if u8::read(buf)? != 0 {
207 return Err(CodecError::Invalid(
208 "storage::adb::Operation",
209 "delete value non-zero",
210 ));
211 }
212 }
213 Ok(Self::Delete(key))
214 }
215 COMMIT_FLOOR_CONTEXT => {
216 let floor_loc = u64::read(buf)?;
217 for _ in 0..(Self::SIZE - 1 - u64::SIZE) {
218 if u8::read(buf)? != 0 {
219 return Err(CodecError::Invalid(
220 "storage::adb::Operation",
221 "commit value non-zero",
222 ));
223 }
224 }
225 Ok(Self::CommitFloor(floor_loc))
226 }
227 e => Err(CodecError::InvalidEnum(e)),
228 }
229 }
230}
231
232impl<K: Array, V: Codec> Read for Variable<K, V> {
233 type Cfg = <V as Read>::Cfg;
234
235 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
236 match u8::read(buf)? {
237 SET_CONTEXT => {
238 let key = K::read(buf)?;
239 let value = V::read_cfg(buf, cfg)?;
240 Ok(Self::Set(key, value))
241 }
242 COMMIT_CONTEXT => Ok(Self::Commit()),
243 DELETE_CONTEXT => {
244 let key = K::read(buf)?;
245 Ok(Self::Delete(key))
246 }
247 UPDATE_CONTEXT => {
248 let key = K::read(buf)?;
249 let value = V::read_cfg(buf, cfg)?;
250 Ok(Self::Update(key, value))
251 }
252 COMMIT_FLOOR_CONTEXT => {
253 let floor_loc = u64::read(buf)?;
254 Ok(Self::CommitFloor(floor_loc))
255 }
256 e => Err(CodecError::InvalidEnum(e)),
257 }
258 }
259}
260
261impl<K: Array, V: Array> Display for Fixed<K, V> {
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263 match self {
264 Fixed::Delete(key) => write!(f, "[key:{key} <deleted>]"),
265 Fixed::Update(key, value) => write!(f, "[key:{key} value:{value}]"),
266 Fixed::CommitFloor(loc) => write!(f, "[commit with inactivity floor: {loc}]"),
267 }
268 }
269}
270
271impl<K: Array, V: Array> Display for Variable<K, V> {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 match self {
274 Variable::Set(key, value) => write!(f, "[key:{key} value:{value}]"),
275 Variable::Commit() => write!(f, "[commit]"),
276 Variable::Delete(key) => write!(f, "[key:{key} <deleted>]"),
277 Variable::Update(key, value) => write!(f, "[key:{key} value:{value}]"),
278 Variable::CommitFloor(loc) => write!(f, "[commit with inactivity floor: {loc}]"),
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use commonware_codec::{DecodeExt, Encode};
287 use commonware_utils::sequence::U64;
288
289 #[test]
290 fn test_to_key() {
291 let key = U64::new(1234);
292 let value = U64::new(56789);
293
294 let update_op = Fixed::Update(key.clone(), value.clone());
295 assert_eq!(&key, update_op.to_key().unwrap());
296
297 let delete_op = Fixed::<U64, U64>::Delete(key.clone());
298 assert_eq!(&key, delete_op.to_key().unwrap());
299
300 let commit_op = Fixed::<U64, U64>::CommitFloor(42);
301 assert_eq!(None, commit_op.to_key());
302 }
303
304 #[test]
305 fn test_to_value() {
306 let key = U64::new(1234);
307 let value = U64::new(56789);
308
309 let update_op = Fixed::Update(key.clone(), value.clone());
310 assert_eq!(&value, update_op.to_value().unwrap());
311
312 let delete_op = Fixed::<U64, U64>::Delete(key.clone());
313 assert_eq!(None, delete_op.to_value());
314
315 let commit_op = Fixed::<U64, U64>::CommitFloor(42);
316 assert_eq!(None, commit_op.to_value());
317 }
318
319 #[test]
320 fn test_operation_array_basic() {
321 let key = U64::new(1234);
322 let value = U64::new(56789);
323
324 let update_op = Fixed::Update(key.clone(), value.clone());
325 assert_eq!(&key, update_op.to_key().unwrap());
326 assert_eq!(&value, update_op.to_value().unwrap());
327
328 let from = Fixed::<U64, U64>::decode(update_op.encode()).unwrap();
329 assert_eq!(&key, from.to_key().unwrap());
330 assert_eq!(&value, from.to_value().unwrap());
331 assert_eq!(update_op, from);
332
333 let key2 = U64::new(42);
334 let delete_op = Fixed::<U64, U64>::Delete(key2.clone());
335 let from = Fixed::<U64, U64>::decode(delete_op.encode()).unwrap();
336 assert_eq!(&key2, from.to_key().unwrap());
337 assert_eq!(None, from.to_value());
338 assert_eq!(delete_op, from);
339
340 let commit_op = Fixed::<U64, U64>::CommitFloor(42);
341 let from = Fixed::<U64, U64>::decode(commit_op.encode()).unwrap();
342 assert_eq!(None, from.to_value());
343 assert!(matches!(from, Fixed::CommitFloor(42)));
344 assert_eq!(commit_op, from);
345
346 let mut invalid = delete_op.encode();
348 invalid[U64::SIZE + 4] = 0xFF;
349 let decoded = Fixed::<U64, U64>::decode(invalid.as_ref());
350 assert!(matches!(decoded.unwrap_err(), CodecError::Invalid(_, _)));
351
352 let mut invalid = delete_op.encode();
354 invalid[0] = 0xFF;
355 let decoded = Fixed::<U64, U64>::decode(invalid.as_ref());
356 assert!(matches!(
357 decoded.unwrap_err(),
358 CodecError::InvalidEnum(0xFF)
359 ));
360
361 let mut invalid = delete_op.encode().to_vec();
363 invalid.pop();
364 let decoded = Fixed::<U64, U64>::decode(invalid.as_ref());
365 assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
366 }
367
368 #[test]
369 fn test_operation_array_display() {
370 let key = U64::new(1234);
371 let value = U64::new(56789);
372 let update_op = Fixed::Update(key.clone(), value.clone());
373 assert_eq!(format!("{update_op}"), format!("[key:{key} value:{value}]"));
374
375 let key2 = U64::new(42);
376 let delete_op = Fixed::<U64, U64>::Delete(key2.clone());
377 assert_eq!(format!("{delete_op}"), format!("[key:{key2} <deleted>]"));
378 }
379
380 #[test]
381 fn test_operation_array_codec() {
382 let key = U64::new(1234);
383 let value = U64::new(5678);
384 let update_op = Fixed::Update(key, value);
385
386 let encoded = update_op.encode();
387 assert_eq!(encoded.len(), Fixed::<U64, U64>::SIZE);
388
389 let decoded = Fixed::<U64, U64>::decode(encoded).unwrap();
390 assert_eq!(update_op, decoded);
391 }
392}