File size: 4,400 Bytes
224e773 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | use crate::header::WormHeader;
/// A complete WORM record: fixed-size header followed by variable-length payload.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WormRecord {
pub header: WormHeader,
pub event_type: Vec<u8>,
pub data: Vec<u8>,
pub metadata: Vec<u8>,
}
impl WormRecord {
/// Create a new record from parts.
pub fn new(
header: WormHeader,
event_type: Vec<u8>,
data: Vec<u8>,
metadata: Vec<u8>,
) -> Self {
Self {
header,
event_type,
data,
metadata,
}
}
/// Total size of this record in bytes when serialized.
pub fn total_size(&self) -> usize {
self.header.record_size()
}
/// Serialize this record to bytes (header + payload).
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.header.to_bytes();
bytes.extend_from_slice(&self.event_type);
bytes.extend_from_slice(&self.data);
bytes.extend_from_slice(&self.metadata);
bytes
}
/// Deserialize a record from bytes.
pub fn from_bytes(bytes: &[u8]) -> hyperkitty_core::Result<Self> {
let header_size = crate::header::HEADER_SIZE;
if bytes.len() < header_size {
return Err(hyperkitty_core::Error::StorageError(
format!("record too short: {} < {}", bytes.len(), header_size),
));
}
// Parse header
let header = WormHeader::from_bytes(&bytes[..header_size])?;
// Extract payload
let offset = header_size;
let event_length = header.event_length as usize;
let data_length = header.data_length as usize;
let metadata_length = header.metadata_length as usize;
let expected_len = offset + event_length + data_length + metadata_length;
if bytes.len() < expected_len {
return Err(hyperkitty_core::Error::StorageError(
format!(
"record payload incomplete: {} < {}",
bytes.len(),
expected_len
),
));
}
let event_type = bytes[offset..offset + event_length].to_vec();
let data = bytes[offset + event_length..offset + event_length + data_length].to_vec();
let metadata = bytes[offset + event_length + data_length
..offset + event_length + data_length + metadata_length]
.to_vec();
Ok(Self {
header,
event_type,
data,
metadata,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn record_basic() {
let header = WormHeader::new();
let record = WormRecord::new(header, vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]);
let bytes = record.to_bytes();
assert!(!bytes.is_empty());
assert!(bytes.len() >= crate::header::HEADER_SIZE);
}
#[test]
fn record_round_trip() {
let mut header = WormHeader::new();
header.event_length = 3;
header.data_length = 3;
header.metadata_length = 3;
let event_type = vec![1, 2, 3];
let data = vec![4, 5, 6];
let metadata = vec![7, 8, 9];
let record = WormRecord::new(header, event_type.clone(), data.clone(), metadata.clone());
let bytes = record.to_bytes();
let record2 = WormRecord::from_bytes(&bytes).unwrap();
assert_eq!(record2.event_type, event_type);
assert_eq!(record2.data, data);
assert_eq!(record2.metadata, metadata);
}
#[test]
fn record_empty_payload() {
let header = WormHeader::new();
let record = WormRecord::new(header, vec![], vec![], vec![]);
let bytes = record.to_bytes();
let record2 = WormRecord::from_bytes(&bytes).unwrap();
assert_eq!(record2.event_type.len(), 0);
assert_eq!(record2.data.len(), 0);
assert_eq!(record2.metadata.len(), 0);
}
#[test]
fn record_total_size() {
let mut header = WormHeader::new();
header.event_length = 10;
header.data_length = 20;
header.metadata_length = 30;
let record =
WormRecord::new(header, vec![0; 10], vec![0; 20], vec![0; 30]);
assert_eq!(
record.total_size(),
crate::header::HEADER_SIZE + 10 + 20 + 30
);
}
}
|