use std::io::Result;
use std::mem::size_of;
use std::slice;
use crate::meta::chunk_info_v2::BlobChunkInfoV2Ondisk;
use crate::meta::BlobMetaChunkInfo;
#[repr(C, packed)]
#[derive(Default)]
pub struct BatchInflateContext {
compressed_size: u32,
uncompressed_batch_size: u32,
__reserved1: u64,
__reserved2: u64,
__reserved3: u64,
__reserved4: u64,
}
impl BatchInflateContext {
pub fn compressed_size(&self) -> u32 {
u32::from_le(self.compressed_size)
}
pub fn set_compressed_size(&mut self, compressed_size: u32) {
self.compressed_size = u32::to_le(compressed_size);
}
pub fn set_uncompressed_batch_size(&mut self, uncompressed_batch_size: u32) {
self.uncompressed_batch_size = u32::to_le(uncompressed_batch_size);
}
pub fn uncompressed_batch_size(&self) -> u32 {
u32::from_le(self.uncompressed_batch_size)
}
pub fn as_slice(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const BatchInflateContext as *const u8,
size_of::<BatchInflateContext>(),
)
}
}
}
pub struct BatchContextGenerator {
chunk_data_buf: Vec<u8>,
contexts: Vec<BatchInflateContext>,
}
impl BatchContextGenerator {
pub fn chunk_data_buf(&self) -> &Vec<u8> {
&self.chunk_data_buf
}
pub fn chunk_data_buf_is_empty(&self) -> bool {
self.chunk_data_buf.is_empty()
}
pub fn chunk_data_buf_len(&self) -> usize {
self.chunk_data_buf.len()
}
pub fn append_chunk_data_buf(&mut self, chunk_data: &[u8]) {
self.chunk_data_buf.extend_from_slice(chunk_data);
}
pub fn clear_chunk_data_buf(&mut self) {
self.chunk_data_buf.clear();
}
pub fn add_context(&mut self, compressed_size: u32) {
let ctx = BatchInflateContext {
compressed_size: u32::to_le(compressed_size),
uncompressed_batch_size: u32::to_le(self.chunk_data_buf_len() as u32),
__reserved1: u64::to_le(0),
__reserved2: u64::to_le(0),
__reserved3: u64::to_le(0),
__reserved4: u64::to_le(0),
};
self.contexts.push(ctx);
}
pub fn new(batch_size: u32) -> Result<Self> {
Ok(Self {
chunk_data_buf: Vec::with_capacity(batch_size as usize),
contexts: Vec::with_capacity(10240),
})
}
pub fn generate_chunk_info(
&mut self,
compressed_offset: u64,
uncompressed_offset: u64,
uncompressed_size: u32,
encrypted: bool,
) -> Result<BlobChunkInfoV2Ondisk> {
let mut chunk = BlobChunkInfoV2Ondisk::default();
chunk.set_compressed_offset(compressed_offset);
chunk.set_compressed_size(0);
chunk.set_uncompressed_offset(uncompressed_offset);
chunk.set_uncompressed_size(uncompressed_size);
chunk.set_batch(true);
chunk.set_batch_index(self.contexts.len() as u32);
chunk.set_uncompressed_offset_in_batch_buf(self.chunk_data_buf_len() as u32);
chunk.set_compressed(true);
chunk.set_encrypted(encrypted);
chunk.set_has_crc32(false);
Ok(chunk)
}
pub fn to_vec(&self) -> Result<(Vec<u8>, u32)> {
let mut data = Vec::new();
for ctx in &self.contexts {
data.extend_from_slice(ctx.as_slice());
}
Ok((data, self.contexts.len() as u32))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::ManuallyDrop;
#[test]
fn test_batch_inflate_context() {
let mut ctx = BatchInflateContext {
compressed_size: 0,
uncompressed_batch_size: 0,
__reserved1: 0,
__reserved2: 0,
__reserved3: 0,
__reserved4: 0,
};
ctx.set_compressed_size(0x20);
assert_eq!(ctx.compressed_size(), 0x20);
ctx.set_uncompressed_batch_size(0x30);
assert_eq!(ctx.uncompressed_batch_size(), 0x30);
let mut v = [0u8; 40];
v[0] = 0x20;
v[4] = 0x30;
assert_eq!(ctx.as_slice(), v);
}
#[test]
fn test_batch_context_generator() {
let mut generator = BatchContextGenerator::new(0x100000).unwrap();
assert!(generator.chunk_data_buf_is_empty());
assert_eq!(generator.chunk_data_buf_len(), 0);
generator.append_chunk_data_buf(&[1, 2, 3, 4]);
assert!(!generator.chunk_data_buf_is_empty());
assert_eq!(generator.chunk_data_buf_len(), 4);
generator.add_context(4);
let (ctx_data, _) = generator.to_vec().unwrap();
let ctx_vec = unsafe {
ManuallyDrop::new(Vec::from_raw_parts(
ctx_data.as_slice().as_ptr() as *mut BatchInflateContext,
1,
1,
))
};
assert_eq!(ctx_vec[0].compressed_size(), 4);
assert_eq!(ctx_vec[0].uncompressed_batch_size(), 4);
generator.clear_chunk_data_buf();
assert!(generator.chunk_data_buf_is_empty());
assert_eq!(generator.chunk_data_buf_len(), 0);
let chunk_info = generator.generate_chunk_info(0, 0, 4, false).unwrap();
assert!(chunk_info.is_batch());
}
}