nodedb_array/tile/
cell_payload.rs1pub use nodedb_types::OPEN_UPPER;
11use nodedb_types::Surrogate;
12use serde::{Deserialize, Serialize};
13
14use crate::error::{ArrayError, ArrayResult};
15use crate::types::cell_value::value::CellValue;
16
17pub const CELL_TOMBSTONE_SENTINEL: &[u8] = &[0xFF];
19
20pub const CELL_GDPR_ERASURE_SENTINEL: &[u8] = &[0xFE];
22
23#[derive(
27 Debug,
28 Clone,
29 PartialEq,
30 Serialize,
31 Deserialize,
32 zerompk::ToMessagePack,
33 zerompk::FromMessagePack,
34)]
35pub struct CellPayload {
36 pub valid_from_ms: i64,
37 pub valid_until_ms: i64,
38 pub attrs: Vec<CellValue>,
39 pub surrogate: Surrogate,
40}
41
42impl CellPayload {
43 pub fn encode(&self) -> ArrayResult<Vec<u8>> {
44 zerompk::to_msgpack_vec(self).map_err(|e| ArrayError::SegmentCorruption {
45 detail: format!("encode CellPayload: {e}"),
46 })
47 }
48
49 pub fn decode(bytes: &[u8]) -> ArrayResult<Self> {
50 zerompk::from_msgpack(bytes).map_err(|e| ArrayError::SegmentCorruption {
51 detail: format!("decode CellPayload: {e}"),
52 })
53 }
54}
55
56pub fn is_cell_sentinel(bytes: &[u8]) -> bool {
58 is_cell_tombstone(bytes) || is_cell_gdpr_erasure(bytes)
59}
60
61pub fn is_cell_tombstone(bytes: &[u8]) -> bool {
63 bytes == CELL_TOMBSTONE_SENTINEL
64}
65
66pub fn is_cell_gdpr_erasure(bytes: &[u8]) -> bool {
68 bytes == CELL_GDPR_ERASURE_SENTINEL
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use nodedb_types::Surrogate;
75
76 fn sample_payload() -> CellPayload {
77 CellPayload {
78 valid_from_ms: 1_000,
79 valid_until_ms: OPEN_UPPER,
80 attrs: vec![CellValue::Int64(42)],
81 surrogate: Surrogate::ZERO,
82 }
83 }
84
85 #[test]
86 fn payload_msgpack_roundtrip() {
87 let p = sample_payload();
88 let bytes = p.encode().unwrap();
89 let decoded = CellPayload::decode(&bytes).unwrap();
90 assert_eq!(decoded.valid_from_ms, 1_000);
91 assert_eq!(decoded.valid_until_ms, OPEN_UPPER);
92 assert_eq!(decoded.attrs, vec![CellValue::Int64(42)]);
93 assert_eq!(decoded.surrogate, Surrogate::ZERO);
94 }
95
96 #[test]
97 fn encoded_payload_first_byte_is_fixarray() {
98 let bytes = sample_payload().encode().unwrap();
99 assert_eq!(bytes[0], 0x94);
101 }
102
103 #[test]
104 fn sentinels_are_disjoint_from_payload() {
105 let bytes = sample_payload().encode().unwrap();
106 assert!(is_cell_sentinel(&[0xFF]));
107 assert!(is_cell_sentinel(&[0xFE]));
108 assert!(is_cell_tombstone(&[0xFF]));
109 assert!(is_cell_gdpr_erasure(&[0xFE]));
110 assert!(!is_cell_sentinel(&bytes[..1]));
111 }
112
113 #[test]
114 fn tombstone_and_erasure_are_distinct() {
115 assert!(!is_cell_tombstone(CELL_GDPR_ERASURE_SENTINEL));
116 assert!(!is_cell_gdpr_erasure(CELL_TOMBSTONE_SENTINEL));
117 }
118}