reth_era/
e2s_types.rs

1//! Types to build e2store files
2//! ie. with `.e2s` extension
3//!
4//! e2store file contains header and entry
5//!
6//! The [`Header`] is an 8-byte structure at the beginning of each record in the file
7//!
8//! An [`Entry`] is a complete record in the file, consisting of both a [`Header`] and its
9//! associated data
10
11use ssz_derive::{Decode, Encode};
12use std::io::{self, Read, Write};
13use thiserror::Error;
14
15/// [`Version`] record: ['e', '2']
16pub const VERSION: [u8; 2] = [0x65, 0x32];
17
18/// Empty record
19pub const EMPTY: [u8; 2] = [0x00, 0x00];
20
21/// `SlotIndex` record: ['i', '2']
22pub const SLOT_INDEX: [u8; 2] = [0x69, 0x32];
23
24/// Error types for e2s file operations
25#[derive(Error, Debug)]
26pub enum E2sError {
27    /// IO error during file operations
28    #[error("IO error: {0}")]
29    Io(#[from] io::Error),
30
31    /// Error during SSZ encoding/decoding
32    #[error("SSZ error: {0}")]
33    Ssz(String),
34
35    /// Reserved field in header not zero
36    #[error("Reserved field in header not zero")]
37    ReservedNotZero,
38
39    /// Error during snappy compression
40    #[error("Snappy compression error: {0}")]
41    SnappyCompression(String),
42
43    /// Error during snappy decompression
44    #[error("Snappy decompression error: {0}")]
45    SnappyDecompression(String),
46
47    /// Error during RLP encoding/decoding
48    #[error("RLP error: {0}")]
49    Rlp(String),
50}
51
52/// Header for TLV records in e2store files
53#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
54pub struct Header {
55    /// Record type identifier
56    pub header_type: [u8; 2],
57
58    /// Length of data following the header
59    pub length: u32,
60
61    /// Reserved field, must be zero
62    pub reserved: u16,
63}
64
65impl Header {
66    /// Create a new header with the specified type and length
67    pub fn new(header_type: [u8; 2], length: u32) -> Self {
68        Self { header_type, length, reserved: 0 }
69    }
70
71    /// Read header from a reader
72    pub fn read<R: Read>(reader: &mut R) -> Result<Option<Self>, E2sError> {
73        let mut header_bytes = [0u8; 8];
74        match reader.read_exact(&mut header_bytes) {
75            Ok(_) => {}
76            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
77            Err(e) => return Err(e.into()),
78        }
79
80        let header: Self = match ssz::Decode::from_ssz_bytes(&header_bytes) {
81            Ok(h) => h,
82            Err(_) => return Err(E2sError::Ssz(String::from("Failed to decode SSZ header"))),
83        };
84
85        if header.reserved != 0 {
86            return Err(E2sError::ReservedNotZero);
87        }
88
89        Ok(Some(header))
90    }
91
92    /// Writes the header to the given writer.
93    pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
94        let encoded = ssz::Encode::as_ssz_bytes(self);
95        writer.write_all(&encoded)
96    }
97}
98
99/// The [`Version`] record must be the first record in an e2store file
100#[derive(Debug, Clone, PartialEq, Eq, Default)]
101pub struct Version;
102
103impl Version {
104    /// Encode this record to the given writer
105    pub fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
106        let header = Header::new(VERSION, 0);
107        header.write(writer)
108    }
109}
110
111/// Complete record in an e2store file, consisting of a type, length, and associated data
112#[derive(Debug, Clone)]
113pub struct Entry {
114    /// Record type identifier
115    pub entry_type: [u8; 2],
116
117    /// Data contained in the entry
118    pub data: Vec<u8>,
119}
120
121impl Entry {
122    /// Create a new entry
123    pub fn new(entry_type: [u8; 2], data: Vec<u8>) -> Self {
124        Self { entry_type, data }
125    }
126
127    /// Read an entry from a reader
128    pub fn read<R: Read>(reader: &mut R) -> Result<Option<Self>, E2sError> {
129        // Read the header first
130        let header = match Header::read(reader)? {
131            Some(h) => h,
132            None => return Ok(None),
133        };
134
135        // Read the data
136        let mut data = vec![0u8; header.length as usize];
137        match reader.read_exact(&mut data) {
138            Ok(_) => {}
139            Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
140                return Err(E2sError::Io(io::Error::new(
141                    io::ErrorKind::UnexpectedEof,
142                    "Unexpected EOF while reading entry data",
143                )));
144            }
145            Err(e) => return Err(e.into()),
146        }
147
148        Ok(Some(Self { entry_type: header.header_type, data }))
149    }
150
151    /// Write the entry to [`Entry`] writer
152    pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
153        let header = Header::new(self.entry_type, self.data.len() as u32);
154        header.write(writer)?;
155        writer.write_all(&self.data)
156    }
157
158    /// Check if this is a [`Version`] entry
159    pub fn is_version(&self) -> bool {
160        self.entry_type == VERSION
161    }
162
163    /// Check if this is a `SlotIndex` entry
164    pub fn is_slot_index(&self) -> bool {
165        self.entry_type == SLOT_INDEX
166    }
167}