use byteorder::{BigEndian, ReadBytesExt};
use bytes::{BufMut, BytesMut};
use std::io::{self, Cursor, Read};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FileSummaryLnEntryError {
#[error("I/O error: {0}")]
Io(#[from] io::Error),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileSummaryLnEntry {
pub file_number: u64,
pub total_count: i32,
pub total_size: i32,
pub total_in_count: i32,
pub total_in_size: i32,
pub total_ln_count: i32,
pub total_ln_size: i32,
pub max_ln_size: i32,
pub obsolete_in_count: i32,
pub obsolete_ln_count: i32,
pub obsolete_ln_size: i32,
pub obsolete_ln_size_counted: i32,
pub obsolete_offset_count: u32,
pub obsolete_offset_data: Vec<u8>,
pub expiration_histogram: Vec<u8>,
}
impl FileSummaryLnEntry {
#[allow(clippy::too_many_arguments)]
pub fn new(
file_number: u64,
total_count: i32,
total_size: i32,
total_in_count: i32,
total_in_size: i32,
total_ln_count: i32,
total_ln_size: i32,
max_ln_size: i32,
obsolete_in_count: i32,
obsolete_ln_count: i32,
obsolete_ln_size: i32,
obsolete_ln_size_counted: i32,
obsolete_offset_count: u32,
obsolete_offset_data: Vec<u8>,
expiration_histogram: Vec<u8>,
) -> Self {
Self {
file_number,
total_count,
total_size,
total_in_count,
total_in_size,
total_ln_count,
total_ln_size,
max_ln_size,
obsolete_in_count,
obsolete_ln_count,
obsolete_ln_size,
obsolete_ln_size_counted,
obsolete_offset_count,
obsolete_offset_data,
expiration_histogram,
}
}
pub fn log_size(&self) -> usize {
8 + (11 * 4) + 4 + 4 + self.obsolete_offset_data.len() +
4 + self.expiration_histogram.len()
}
pub fn write_to_log(&self, buf: &mut BytesMut) {
buf.put_u64(self.file_number);
buf.put_i32(self.total_count);
buf.put_i32(self.total_size);
buf.put_i32(self.total_in_count);
buf.put_i32(self.total_in_size);
buf.put_i32(self.total_ln_count);
buf.put_i32(self.total_ln_size);
buf.put_i32(self.max_ln_size);
buf.put_i32(self.obsolete_in_count);
buf.put_i32(self.obsolete_ln_count);
buf.put_i32(self.obsolete_ln_size);
buf.put_i32(self.obsolete_ln_size_counted);
buf.put_u32(self.obsolete_offset_count);
buf.put_u32(self.obsolete_offset_data.len() as u32);
buf.put_slice(&self.obsolete_offset_data);
buf.put_u32(self.expiration_histogram.len() as u32);
buf.put_slice(&self.expiration_histogram);
}
pub fn read_from_log(buf: &[u8]) -> Result<Self, FileSummaryLnEntryError> {
let mut cursor = Cursor::new(buf);
let file_number = cursor.read_u64::<BigEndian>()?;
let total_count = cursor.read_i32::<BigEndian>()?;
let total_size = cursor.read_i32::<BigEndian>()?;
let total_in_count = cursor.read_i32::<BigEndian>()?;
let total_in_size = cursor.read_i32::<BigEndian>()?;
let total_ln_count = cursor.read_i32::<BigEndian>()?;
let total_ln_size = cursor.read_i32::<BigEndian>()?;
let max_ln_size = cursor.read_i32::<BigEndian>()?;
let obsolete_in_count = cursor.read_i32::<BigEndian>()?;
let obsolete_ln_count = cursor.read_i32::<BigEndian>()?;
let obsolete_ln_size = cursor.read_i32::<BigEndian>()?;
let obsolete_ln_size_counted = cursor.read_i32::<BigEndian>()?;
let obsolete_offset_count = cursor.read_u32::<BigEndian>()?;
let data_len = cursor.read_u32::<BigEndian>()? as usize;
let mut obsolete_offset_data = vec![0u8; data_len];
cursor.read_exact(&mut obsolete_offset_data)?;
let expiration_histogram = match cursor.read_u32::<BigEndian>() {
Ok(hlen) => {
let mut h = vec![0u8; hlen as usize];
cursor.read_exact(&mut h)?;
h
}
Err(_) => Vec::new(),
};
Ok(Self {
file_number,
total_count,
total_size,
total_in_count,
total_in_size,
total_ln_count,
total_ln_size,
max_ln_size,
obsolete_in_count,
obsolete_ln_count,
obsolete_ln_size,
obsolete_ln_size_counted,
obsolete_offset_count,
obsolete_offset_data,
expiration_histogram,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_summary_ln_roundtrip() {
let entry = FileSummaryLnEntry::new(
5,
1000,
512000,
100,
51200,
900,
460800,
4096,
5,
200,
102400,
200,
3,
vec![0xAC, 0x02, 0x01],
vec![0, 0xAC, 0x02, 0x10], );
let mut buf = BytesMut::new();
entry.write_to_log(&mut buf);
let decoded = FileSummaryLnEntry::read_from_log(&buf).unwrap();
assert_eq!(entry, decoded);
assert_eq!(decoded.file_number, 5);
assert_eq!(decoded.total_count, 1000);
assert_eq!(decoded.total_size, 512000);
assert_eq!(decoded.total_in_count, 100);
assert_eq!(decoded.total_ln_count, 900);
assert_eq!(decoded.max_ln_size, 4096);
assert_eq!(decoded.obsolete_ln_count, 200);
assert_eq!(decoded.obsolete_ln_size, 102400);
assert_eq!(decoded.obsolete_ln_size_counted, 200);
assert_eq!(decoded.obsolete_offset_count, 3);
assert_eq!(decoded.obsolete_offset_data, vec![0xAC, 0x02, 0x01]);
assert_eq!(decoded.expiration_histogram, vec![0, 0xAC, 0x02, 0x10]);
}
#[test]
fn test_file_summary_ln_empty_offsets() {
let entry = FileSummaryLnEntry::new(
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
Vec::new(),
Vec::new(),
);
let mut buf = BytesMut::new();
entry.write_to_log(&mut buf);
let decoded = FileSummaryLnEntry::read_from_log(&buf).unwrap();
assert_eq!(entry, decoded);
assert_eq!(decoded.obsolete_offset_count, 0);
assert!(decoded.obsolete_offset_data.is_empty());
assert!(decoded.expiration_histogram.is_empty());
}
#[test]
fn test_log_size() {
let entry = FileSummaryLnEntry::new(
1,
10,
100,
2,
20,
8,
80,
16,
1,
5,
50,
5,
2,
vec![1, 2, 3, 4],
vec![5, 6],
);
assert_eq!(entry.log_size(), 8 + (11 * 4) + 4 + 4 + 4 + 4 + 2);
let mut buf = BytesMut::new();
entry.write_to_log(&mut buf);
assert_eq!(buf.len(), entry.log_size());
}
#[test]
fn test_file_summary_ln_backward_compat_no_trailer() {
let mut buf = BytesMut::new();
buf.put_u64(9);
for _ in 0..11 {
buf.put_i32(0);
}
buf.put_u32(0); buf.put_u32(0); let decoded = FileSummaryLnEntry::read_from_log(&buf).unwrap();
assert_eq!(decoded.file_number, 9);
assert!(decoded.expiration_histogram.is_empty());
}
}