1use bytes::{Buf, BufMut};
7use commonware_codec::{
8 util::at_least, varint::UInt, Codec, CodecFixed, EncodeSize, Error as CodecError, FixedSize,
9 Read, ReadExt, Write,
10};
11use commonware_utils::{hex, Array};
12use std::{
13 cmp::{Ord, PartialOrd},
14 fmt::{Debug, Display},
15 hash::Hash,
16};
17use thiserror::Error;
18
19const DELETE_CONTEXT: u8 = 0;
21const UPDATE_CONTEXT: u8 = 1;
22const COMMIT_FLOOR_CONTEXT: u8 = 2;
23const SET_CONTEXT: u8 = 3;
24const COMMIT_CONTEXT: u8 = 4;
25const APPEND_CONTEXT: u8 = 5;
26
27#[derive(Error, Debug)]
29pub enum Error {
30 #[error("invalid length")]
31 InvalidLength,
32 #[error("invalid key: {0}")]
33 InvalidKey(CodecError),
34 #[error("invalid value: {0}")]
35 InvalidValue(CodecError),
36 #[error("invalid context byte")]
37 InvalidContextByte,
38 #[error("delete operation has non-zero value")]
39 InvalidDeleteOp,
40 #[error("commit floor operation has non-zero bytes after location")]
41 InvalidCommitFloorOp,
42}
43
44#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
46pub enum Fixed<K: Array, V: CodecFixed> {
47 Delete(K),
49
50 Update(K, V),
52
53 CommitFloor(u64),
56}
57
58#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
60pub enum Keyless<V: Codec> {
61 Append(V),
63
64 Commit(Option<V>),
66}
67
68#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
70pub enum Variable<K: Array, V: Codec> {
71 Set(K, V),
73 Commit(Option<V>),
74 Delete(K),
76 Update(K, V),
77 CommitFloor(Option<V>, u64),
78}
79
80impl<K: Array, V: CodecFixed> FixedSize for Fixed<K, V> {
81 const SIZE: usize = u8::SIZE + K::SIZE + V::SIZE;
82}
83
84impl<K: Array, V: Codec> EncodeSize for Variable<K, V> {
85 fn encode_size(&self) -> usize {
86 1 + match self {
87 Variable::Delete(_) => K::SIZE,
88 Variable::Update(_, v) => K::SIZE + v.encode_size(),
89 Variable::CommitFloor(v, floor_loc) => v.encode_size() + UInt(*floor_loc).encode_size(),
90 Variable::Set(_, v) => K::SIZE + v.encode_size(),
91 Variable::Commit(v) => v.encode_size(),
92 }
93 }
94}
95
96impl<V: Codec> EncodeSize for Keyless<V> {
97 fn encode_size(&self) -> usize {
98 1 + match self {
99 Keyless::Append(v) => v.encode_size(),
100 Keyless::Commit(v) => v.encode_size(),
101 }
102 }
103}
104
105impl<K: Array, V: CodecFixed> Fixed<K, V> {
106 const _MIN_OPERATION_LEN: usize = 9;
109 const _COMMIT_OP_ASSERT: () = assert!(
110 Self::SIZE >= Self::_MIN_OPERATION_LEN,
111 "array size too small for commit op"
112 );
113
114 pub fn key(&self) -> Option<&K> {
117 match self {
118 Fixed::Delete(key) => Some(key),
119 Fixed::Update(key, _) => Some(key),
120 Fixed::CommitFloor(_) => None,
121 }
122 }
123
124 pub fn value(&self) -> Option<&V> {
127 match self {
128 Fixed::Delete(_) => None,
129 Fixed::Update(_, value) => Some(value),
130 Fixed::CommitFloor(_) => None,
131 }
132 }
133
134 pub fn into_value(self) -> Option<V> {
137 match self {
138 Fixed::Delete(_) => None,
139 Fixed::Update(_, value) => Some(value),
140 Fixed::CommitFloor(_) => None,
141 }
142 }
143}
144
145impl<K: Array, V: Codec> Variable<K, V> {
146 pub fn key(&self) -> Option<&K> {
148 match self {
149 Variable::Set(key, _) => Some(key),
150 Variable::Commit(_) => None,
151 Variable::Delete(key) => Some(key),
152 Variable::Update(key, _) => Some(key),
153 Variable::CommitFloor(_, _) => None,
154 }
155 }
156
157 pub fn value(&self) -> Option<&V> {
159 match self {
160 Variable::Set(_, value) => Some(value),
161 Variable::Commit(value) => value.as_ref(),
162 Variable::Delete(_) => None,
163 Variable::Update(_, value) => Some(value),
164 Variable::CommitFloor(value, _) => value.as_ref(),
165 }
166 }
167
168 pub fn into_value(self) -> Option<V> {
170 match self {
171 Variable::Set(_, value) => Some(value),
172 Variable::Commit(value) => value,
173 Variable::Delete(_) => None,
174 Variable::Update(_, value) => Some(value),
175 Variable::CommitFloor(value, _) => value,
176 }
177 }
178}
179
180impl<V: Codec> Write for Keyless<V> {
181 fn write(&self, buf: &mut impl BufMut) {
182 match &self {
183 Keyless::Append(value) => {
184 APPEND_CONTEXT.write(buf);
185 value.write(buf);
186 }
187 Keyless::Commit(metadata) => {
188 COMMIT_CONTEXT.write(buf);
189 metadata.write(buf);
190 }
191 }
192 }
193}
194
195impl<K: Array, V: CodecFixed> Write for Fixed<K, V> {
196 fn write(&self, buf: &mut impl BufMut) {
197 match &self {
198 Fixed::Delete(k) => {
199 DELETE_CONTEXT.write(buf);
200 k.write(buf);
201 buf.put_bytes(0, V::SIZE);
203 }
204 Fixed::Update(k, v) => {
205 UPDATE_CONTEXT.write(buf);
206 k.write(buf);
207 v.write(buf);
208 }
209 Fixed::CommitFloor(floor_loc) => {
210 COMMIT_FLOOR_CONTEXT.write(buf);
211 buf.put_slice(&floor_loc.to_be_bytes());
212 buf.put_bytes(0, Self::SIZE - 1 - u64::SIZE);
214 }
215 }
216 }
217}
218
219impl<K: Array, V: Codec> Write for Variable<K, V> {
220 fn write(&self, buf: &mut impl BufMut) {
221 match &self {
222 Variable::Set(k, v) => {
223 SET_CONTEXT.write(buf);
224 k.write(buf);
225 v.write(buf);
226 }
227 Variable::Commit(v) => {
228 COMMIT_CONTEXT.write(buf);
229 v.write(buf);
230 }
231 Variable::Delete(k) => {
232 DELETE_CONTEXT.write(buf);
233 k.write(buf);
234 }
235 Variable::Update(k, v) => {
236 UPDATE_CONTEXT.write(buf);
237 k.write(buf);
238 v.write(buf);
239 }
240 Variable::CommitFloor(v, floor_loc) => {
241 COMMIT_FLOOR_CONTEXT.write(buf);
242 v.write(buf);
243 UInt(*floor_loc).write(buf);
244 }
245 }
246 }
247}
248
249impl<V: Codec> Read for Keyless<V> {
250 type Cfg = <V as Read>::Cfg;
251
252 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
253 match u8::read(buf)? {
254 APPEND_CONTEXT => Ok(Self::Append(V::read_cfg(buf, cfg)?)),
255 COMMIT_CONTEXT => Ok(Self::Commit(Option::<V>::read_cfg(buf, cfg)?)),
256 e => Err(CodecError::InvalidEnum(e)),
257 }
258 }
259}
260
261impl<K: Array, V: CodecFixed> Read for Fixed<K, V> {
262 type Cfg = <V as Read>::Cfg;
263
264 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
265 at_least(buf, Self::SIZE)?;
266
267 match u8::read(buf)? {
268 UPDATE_CONTEXT => {
269 let key = K::read(buf)?;
270 let value = V::read_cfg(buf, cfg)?;
271 Ok(Self::Update(key, value))
272 }
273 DELETE_CONTEXT => {
274 let key = K::read(buf)?;
275 for _ in 0..V::SIZE {
277 if u8::read(buf)? != 0 {
278 return Err(CodecError::Invalid(
279 "storage::adb::operation::Fixed",
280 "delete value non-zero",
281 ));
282 }
283 }
284 Ok(Self::Delete(key))
285 }
286 COMMIT_FLOOR_CONTEXT => {
287 let floor_loc = u64::read(buf)?;
288 for _ in 0..(Self::SIZE - 1 - u64::SIZE) {
289 if u8::read(buf)? != 0 {
290 return Err(CodecError::Invalid(
291 "storage::adb::operation::Fixed",
292 "commit value non-zero",
293 ));
294 }
295 }
296 Ok(Self::CommitFloor(floor_loc))
297 }
298 e => Err(CodecError::InvalidEnum(e)),
299 }
300 }
301}
302
303impl<K: Array, V: Codec> Read for Variable<K, V> {
304 type Cfg = <V as Read>::Cfg;
305
306 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
307 match u8::read(buf)? {
308 SET_CONTEXT => {
309 let key = K::read(buf)?;
310 let value = V::read_cfg(buf, cfg)?;
311 Ok(Self::Set(key, value))
312 }
313 COMMIT_CONTEXT => Ok(Self::Commit(Option::<V>::read_cfg(buf, cfg)?)),
314 DELETE_CONTEXT => {
315 let key = K::read(buf)?;
316 Ok(Self::Delete(key))
317 }
318 UPDATE_CONTEXT => {
319 let key = K::read(buf)?;
320 let value = V::read_cfg(buf, cfg)?;
321 Ok(Self::Update(key, value))
322 }
323 COMMIT_FLOOR_CONTEXT => {
324 let metadata = Option::<V>::read_cfg(buf, cfg)?;
325 let floor_loc = UInt::read(buf)?;
326 Ok(Self::CommitFloor(metadata, floor_loc.into()))
327 }
328 e => Err(CodecError::InvalidEnum(e)),
329 }
330 }
331}
332
333impl<V: Codec> Display for Keyless<V> {
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 match self {
336 Keyless::Append(value) => write!(f, "[append value:{}]", hex(&value.encode())),
337 Keyless::Commit(value) => {
338 if let Some(value) = value {
339 write!(f, "[commit {}]", hex(&value.encode()))
340 } else {
341 write!(f, "[commit]")
342 }
343 }
344 }
345 }
346}
347
348impl<K: Array, V: CodecFixed> Display for Fixed<K, V> {
349 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350 match self {
351 Fixed::Delete(key) => write!(f, "[key:{key} <deleted>]"),
352 Fixed::Update(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
353 Fixed::CommitFloor(loc) => write!(f, "[commit with inactivity floor: {loc}]"),
354 }
355 }
356}
357
358impl<K: Array, V: Codec> Display for Variable<K, V> {
359 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360 match self {
361 Variable::Set(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
362 Variable::Commit(value) => {
363 if let Some(value) = value {
364 write!(f, "[commit {}]", hex(&value.encode()))
365 } else {
366 write!(f, "[commit]")
367 }
368 }
369 Variable::Delete(key) => write!(f, "[key:{key} <deleted>]"),
370 Variable::Update(key, value) => write!(f, "[key:{key} value:{}]", hex(&value.encode())),
371 Variable::CommitFloor(value, loc) => {
372 if let Some(value) = value {
373 write!(
374 f,
375 "[commit {} with inactivity floor: {loc}]",
376 hex(&value.encode())
377 )
378 } else {
379 write!(f, "[commit with inactivity floor: {loc}]")
380 }
381 }
382 }
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389 use commonware_codec::{DecodeExt, Encode};
390 use commonware_utils::sequence::U64;
391
392 #[test]
393 fn test_to_key() {
394 let key = U64::new(1234);
395 let value = U64::new(56789);
396
397 let update_op = Fixed::Update(key.clone(), value.clone());
398 assert_eq!(&key, update_op.key().unwrap());
399
400 let delete_op = Fixed::<U64, U64>::Delete(key.clone());
401 assert_eq!(&key, delete_op.key().unwrap());
402
403 let commit_op = Fixed::<U64, U64>::CommitFloor(42);
404 assert_eq!(None, commit_op.key());
405 }
406
407 #[test]
408 fn test_to_value() {
409 let key = U64::new(1234);
410 let value = U64::new(56789);
411
412 let update_op = Fixed::Update(key.clone(), value.clone());
413 assert_eq!(&value, update_op.value().unwrap());
414
415 let delete_op = Fixed::<U64, U64>::Delete(key.clone());
416 assert_eq!(None, delete_op.value());
417
418 let commit_op = Fixed::<U64, U64>::CommitFloor(42);
419 assert_eq!(None, commit_op.value());
420 }
421
422 #[test]
423 fn test_operation_array_basic() {
424 let key = U64::new(1234);
425 let value = U64::new(56789);
426
427 let update_op = Fixed::Update(key.clone(), value.clone());
428 assert_eq!(&key, update_op.key().unwrap());
429 assert_eq!(&value, update_op.value().unwrap());
430
431 let from = Fixed::<U64, U64>::decode(update_op.encode()).unwrap();
432 assert_eq!(&key, from.key().unwrap());
433 assert_eq!(&value, from.value().unwrap());
434 assert_eq!(update_op, from);
435
436 let key2 = U64::new(42);
437 let delete_op = Fixed::<U64, U64>::Delete(key2.clone());
438 let from = Fixed::<U64, U64>::decode(delete_op.encode()).unwrap();
439 assert_eq!(&key2, from.key().unwrap());
440 assert_eq!(None, from.value());
441 assert_eq!(delete_op, from);
442
443 let commit_op = Fixed::<U64, U64>::CommitFloor(42);
444 let from = Fixed::<U64, U64>::decode(commit_op.encode()).unwrap();
445 assert_eq!(None, from.value());
446 assert!(matches!(from, Fixed::CommitFloor(42)));
447 assert_eq!(commit_op, from);
448
449 let mut invalid = delete_op.encode();
451 invalid[U64::SIZE + 4] = 0xFF;
452 let decoded = Fixed::<U64, U64>::decode(invalid.as_ref());
453 assert!(matches!(decoded.unwrap_err(), CodecError::Invalid(_, _)));
454
455 let mut invalid = delete_op.encode();
457 invalid[0] = 0xFF;
458 let decoded = Fixed::<U64, U64>::decode(invalid.as_ref());
459 assert!(matches!(
460 decoded.unwrap_err(),
461 CodecError::InvalidEnum(0xFF)
462 ));
463
464 let mut invalid = delete_op.encode().to_vec();
466 invalid.pop();
467 let decoded = Fixed::<U64, U64>::decode(invalid.as_ref());
468 assert!(matches!(decoded.unwrap_err(), CodecError::EndOfBuffer));
469 }
470
471 #[test]
472 fn test_operation_array_display() {
473 let key = U64::new(1234);
474 let value = U64::new(56789);
475 let update_op = Fixed::Update(key.clone(), value.clone());
476 assert_eq!(
477 format!("{update_op}"),
478 format!("[key:{key} value:{}]", hex(&value.encode()))
479 );
480
481 let key2 = U64::new(42);
482 let delete_op = Fixed::<U64, U64>::Delete(key2.clone());
483 assert_eq!(format!("{delete_op}"), format!("[key:{key2} <deleted>]"));
484 }
485
486 #[test]
487 fn test_operation_array_codec() {
488 let key = U64::new(1234);
489 let value = U64::new(5678);
490 let update_op = Fixed::Update(key, value);
491
492 let encoded = update_op.encode();
493 assert_eq!(encoded.len(), Fixed::<U64, U64>::SIZE);
494
495 let decoded = Fixed::<U64, U64>::decode(encoded).unwrap();
496 assert_eq!(update_op, decoded);
497 }
498
499 #[test]
500 fn test_keyless_append() {
501 let append_op = Keyless::Append(U64::new(12345));
502
503 let encoded = append_op.encode();
504 assert_eq!(encoded.len(), 1 + U64::SIZE);
505
506 let decoded = Keyless::<U64>::decode(encoded).unwrap();
507 assert_eq!(append_op, decoded);
508 assert_eq!(
509 format!("{append_op}"),
510 format!("[append value:{}]", hex(&U64::new(12345).encode()))
511 );
512 }
513
514 #[test]
515 fn test_keyless_commit() {
516 let metadata = Some(U64::new(12345));
517 let commit_op = Keyless::<U64>::Commit(metadata.clone());
518
519 let encoded = commit_op.encode();
520 assert_eq!(encoded.len(), 1 + metadata.encode_size());
521
522 let decoded = Keyless::<U64>::decode(encoded).unwrap();
523 let Keyless::Commit(metadata_decoded) = decoded else {
524 panic!("expected commit operation");
525 };
526 assert_eq!(metadata, metadata_decoded);
527 }
528
529 #[test]
530 fn test_keyless_invalid_context() {
531 let invalid = vec![0xFF; 1];
532 let decoded = Keyless::<U64>::decode(invalid.as_ref());
533 assert!(matches!(
534 decoded.unwrap_err(),
535 CodecError::InvalidEnum(0xFF)
536 ));
537 }
538}