#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::checksum::jenkins_lookup3;
use crate::message_type::MessageType;
pub struct ObjectHeaderWriter {
messages: Vec<(MessageType, Vec<u8>, u8)>, }
impl ObjectHeaderWriter {
pub fn new() -> Self {
Self {
messages: Vec::new(),
}
}
pub fn add_message(&mut self, msg_type: MessageType, data: Vec<u8>) {
self.messages.push((msg_type, data, 0));
}
pub fn add_message_with_flags(&mut self, msg_type: MessageType, data: Vec<u8>, flags: u8) {
self.messages.push((msg_type, data, flags));
}
pub fn serialize(&self) -> Vec<u8> {
let msg_bytes_total: usize = self.messages.iter()
.map(|(_, data, _)| 4 + data.len())
.sum();
let (flags, chunk_size_width) = if msg_bytes_total <= 255 {
(0x00u8, 1usize)
} else if msg_bytes_total <= 65535 {
(0x01u8, 2)
} else {
(0x02u8, 4)
};
let mut buf = Vec::new();
buf.extend_from_slice(b"OHDR");
buf.push(2);
buf.push(flags);
match chunk_size_width {
1 => buf.push(msg_bytes_total as u8),
2 => buf.extend_from_slice(&(msg_bytes_total as u16).to_le_bytes()),
4 => buf.extend_from_slice(&(msg_bytes_total as u32).to_le_bytes()),
_ => {}
}
for (msg_type, data, msg_flags) in &self.messages {
buf.push(msg_type.to_u16() as u8); buf.extend_from_slice(&(data.len() as u16).to_le_bytes()); buf.push(*msg_flags); buf.extend_from_slice(data);
}
let checksum = jenkins_lookup3(&buf);
buf.extend_from_slice(&checksum.to_le_bytes());
buf
}
}
impl Default for ObjectHeaderWriter {
fn default() -> Self {
Self::new()
}
}
struct DeferredHeader {
writer: ObjectHeaderWriter,
}
pub struct BatchObjectHeaderWriter {
headers: Vec<DeferredHeader>,
}
impl BatchObjectHeaderWriter {
pub fn new() -> Self {
Self {
headers: Vec::new(),
}
}
pub fn add(&mut self, writer: ObjectHeaderWriter) {
self.headers.push(DeferredHeader { writer });
}
pub fn len(&self) -> usize {
self.headers.len()
}
pub fn is_empty(&self) -> bool {
self.headers.is_empty()
}
pub fn compute_sizes(&self) -> Vec<usize> {
self.headers
.iter()
.map(|h| h.writer.serialize().len())
.collect()
}
pub fn serialize_all(&self) -> (Vec<u8>, Vec<usize>) {
let serialized: Vec<Vec<u8>> = self.headers.iter().map(|h| h.writer.serialize()).collect();
let total: usize = serialized.iter().map(|s| s.len()).sum();
let mut buf = Vec::with_capacity(total);
let mut offsets = Vec::with_capacity(serialized.len());
for s in &serialized {
offsets.push(buf.len());
buf.extend_from_slice(s);
}
(buf, offsets)
}
}
impl Default for BatchObjectHeaderWriter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::object_header::ObjectHeader;
#[test]
fn empty_header_roundtrip() {
let writer = ObjectHeaderWriter::new();
let bytes = writer.serialize();
let hdr = ObjectHeader::parse(&bytes, 0, 8, 8).unwrap();
assert_eq!(hdr.version, 2);
assert_eq!(hdr.messages.len(), 0);
}
#[test]
fn two_messages_roundtrip() {
let mut writer = ObjectHeaderWriter::new();
writer.add_message(MessageType::Dataspace, vec![1, 2, 3, 4]);
writer.add_message(MessageType::Datatype, vec![5, 6]);
let bytes = writer.serialize();
let hdr = ObjectHeader::parse(&bytes, 0, 8, 8).unwrap();
assert_eq!(hdr.messages.len(), 2);
assert_eq!(hdr.messages[0].msg_type, MessageType::Dataspace);
assert_eq!(hdr.messages[0].data, vec![1, 2, 3, 4]);
assert_eq!(hdr.messages[1].msg_type, MessageType::Datatype);
assert_eq!(hdr.messages[1].data, vec![5, 6]);
}
#[test]
fn large_header_uses_2byte_chunk_size() {
let mut writer = ObjectHeaderWriter::new();
writer.add_message(MessageType::Datatype, vec![0xAA; 300]);
let bytes = writer.serialize();
let hdr = ObjectHeader::parse(&bytes, 0, 8, 8).unwrap();
assert_eq!(hdr.messages.len(), 1);
assert_eq!(hdr.messages[0].data.len(), 300);
}
#[test]
fn batch_writer_serialize_all() {
let mut batch = BatchObjectHeaderWriter::new();
let mut w1 = ObjectHeaderWriter::new();
w1.add_message(MessageType::Dataspace, vec![1, 2, 3]);
let mut w2 = ObjectHeaderWriter::new();
w2.add_message(MessageType::Datatype, vec![4, 5]);
batch.add(w1);
batch.add(w2);
assert_eq!(batch.len(), 2);
let (buf, offsets) = batch.serialize_all();
assert_eq!(offsets.len(), 2);
assert_eq!(offsets[0], 0);
let h1 = ObjectHeader::parse(&buf, offsets[0], 8, 8).unwrap();
assert_eq!(h1.messages.len(), 1);
assert_eq!(h1.messages[0].msg_type, MessageType::Dataspace);
let h2 = ObjectHeader::parse(&buf, offsets[1], 8, 8).unwrap();
assert_eq!(h2.messages.len(), 1);
assert_eq!(h2.messages[0].msg_type, MessageType::Datatype);
}
#[test]
fn batch_writer_empty() {
let batch = BatchObjectHeaderWriter::new();
assert!(batch.is_empty());
let (buf, offsets) = batch.serialize_all();
assert!(buf.is_empty());
assert!(offsets.is_empty());
}
}