use crate::{error::E2StoreError, format::types::TYPE_VERSION};
use std::io::{self, Read, Seek};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Header {
pub typ: [u8; 2],
pub length: u32,
pub reserved: u16,
}
impl Header {
pub const SIZE: usize = 8;
pub fn new(typ: [u8; 2], length: u32) -> Self {
Self {
typ,
length,
reserved: 0,
}
}
pub fn decode(src: &[u8]) -> Result<Self, E2StoreError> {
if src.len() < Self::SIZE {
return Err(E2StoreError::TruncatedHeader(src.len()));
}
let typ = [src[0], src[1]];
let length = u32::from_le_bytes([src[2], src[3], src[4], src[5]]);
let reserved = u16::from_le_bytes([src[6], src[7]]);
if reserved != 0 {
return Err(E2StoreError::NonZeroReserved(reserved));
}
Ok(Header {
typ,
length,
reserved,
})
}
pub fn encode(&self) -> [u8; 8] {
let mut buf = [0u8; 8];
buf[0..2].copy_from_slice(&self.typ);
buf[2..6].copy_from_slice(&self.length.to_le_bytes());
buf[6..8].copy_from_slice(&self.reserved.to_le_bytes());
buf
}
pub fn is_version(&self) -> bool {
self.typ == TYPE_VERSION
}
}
#[derive(Debug, Clone)]
pub struct Entry {
pub header: Header,
pub data: Vec<u8>,
}
impl Entry {
pub fn new(typ: [u8; 2], data: Vec<u8>) -> Self {
let length = u32::try_from(data.len()).expect("entry data exceeds u32");
Self {
header: Header::new(typ, length),
data,
}
}
pub fn version() -> Self {
Self::new(TYPE_VERSION, Vec::new())
}
}
pub struct E2StoreReader<R> {
inner: R,
}
impl<R: Read> E2StoreReader<R> {
pub fn new(inner: R) -> Self {
Self { inner }
}
pub fn next_entry(&mut self) -> Result<Option<Entry>, io::Error> {
let mut hdr_buf = [0u8; Header::SIZE];
match self.inner.read_exact(&mut hdr_buf) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
Err(e) => return Err(e),
}
let header = Header::decode(&hdr_buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
let mut data = vec![0u8; header.length as usize];
self.inner.read_exact(&mut data)?;
Ok(Some(Entry { header, data }))
}
pub fn read_all(mut self) -> Result<Vec<Entry>, io::Error> {
let mut entries = Vec::new();
let first = self.next_entry()?;
match first {
Some(e) if e.header.is_version() => entries.push(e),
Some(_) => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"first entry is not a version entry",
));
}
None => {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"empty e2store file",
));
}
}
while let Some(entry) = self.next_entry()? {
entries.push(entry);
}
Ok(entries)
}
pub fn into_inner(self) -> R {
self.inner
}
pub fn next_header_skip(&mut self) -> Result<Option<Header>, io::Error> {
let mut hdr_buf = [0u8; Header::SIZE];
match self.inner.read_exact(&mut hdr_buf) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
Err(e) => return Err(e),
}
let header = Header::decode(&hdr_buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
if header.length > 0 {
let mut discard = vec![0u8; header.length as usize];
self.inner.read_exact(&mut discard)?;
}
Ok(Some(header))
}
pub fn scan_all_headers_skip(&mut self) -> Result<Vec<Header>, io::Error> {
let mut headers = Vec::new();
while let Some(header) = self.next_header_skip()? {
headers.push(header);
}
Ok(headers)
}
}
impl<R: Read + Seek> E2StoreReader<R> {
pub fn next_header_only(&mut self) -> Result<Option<Header>, io::Error> {
let mut hdr_buf = [0u8; Header::SIZE];
match self.inner.read_exact(&mut hdr_buf) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
Err(e) => return Err(e),
}
let header = Header::decode(&hdr_buf)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
if header.length > 0 {
self.inner
.seek(io::SeekFrom::Current(header.length as i64))?;
}
Ok(Some(header))
}
pub fn scan_all_headers(&mut self) -> Result<Vec<Header>, io::Error> {
let mut headers = Vec::new();
while let Some(header) = self.next_header_only()? {
headers.push(header);
}
Ok(headers)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_version_header() {
let bytes = [0x65, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let h = Header::decode(&bytes).unwrap();
assert_eq!(h.typ, TYPE_VERSION);
assert_eq!(h.length, 0);
assert!(h.is_version());
}
#[test]
fn encode_decode_roundtrip() {
let h = Header::new([0x01, 0x00], 42);
let encoded = h.encode();
let decoded = Header::decode(&encoded).unwrap();
assert_eq!(h, decoded);
}
#[test]
fn reject_nonzero_reserved() {
let bytes = [0x65, 0x32, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00];
assert!(Header::decode(&bytes).is_err());
}
#[test]
fn reject_truncated() {
assert!(Header::decode(&[0x65, 0x32, 0x00]).is_err());
}
#[test]
fn read_single_version_entry() {
let data = [0x65, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let mut reader = E2StoreReader::new(&data[..]);
let entry = reader.next_entry().unwrap().unwrap();
assert!(entry.header.is_version());
assert!(entry.data.is_empty());
assert!(reader.next_entry().unwrap().is_none());
}
#[test]
fn read_all_validates_version_first() {
let mut buf = Vec::new();
buf.extend_from_slice(&[0x65, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); buf.extend_from_slice(&[0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00]); buf.extend_from_slice(b"foo");
let reader = E2StoreReader::new(&buf[..]);
let entries = reader.read_all().unwrap();
assert_eq!(entries.len(), 2);
assert_eq!(entries[1].data, b"foo");
}
#[test]
fn read_all_rejects_missing_version() {
let buf = [
0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, b'f', b'o', b'o',
];
let reader = E2StoreReader::new(&buf[..]);
assert!(reader.read_all().is_err());
}
}