1use crate::format::frame::{
4 FILE_HEADER_LEN, FILE_MAGIC, FileHeader, FormatCheck, FormatError,
5 decode_header as decode_frame_header, encode_header as encode_frame_header, read_u16, read_u64,
6};
7use crate::format::{FormatFamily, FormatRegistry, RegistryError};
8
9use super::LogSeq;
10
11pub const WAL_HEADER_LEN: usize = FILE_HEADER_LEN + 8;
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct WalHeader {
17 pub first_seq: LogSeq,
19}
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum WalHeaderError {
24 Missing,
26 Truncated {
28 needed: usize,
30 available: usize,
32 },
33 WrongMagic {
35 expected: [u8; 8],
37 actual: [u8; 8],
39 },
40 WrongFamily {
42 expected: u16,
44 actual: u16,
46 },
47 UnsupportedVersion {
49 family: u16,
51 version: u16,
53 minimum: u16,
55 maximum: u16,
57 },
58 InvalidHeaderLength {
60 expected: u64,
62 actual: u64,
64 },
65 NonZeroFileLength {
67 actual: u64,
69 },
70 InvalidSharedHeader {
72 check: FormatCheck,
74 },
75}
76
77impl std::fmt::Display for WalHeaderError {
78 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 Self::Missing => formatter.write_str("WAL file header is missing"),
81 Self::Truncated { needed, available } => {
82 write!(
83 formatter,
84 "WAL file header needs {needed} bytes, got {available}"
85 )
86 }
87 Self::WrongMagic { expected, actual } => {
88 write!(formatter, "WAL magic expected {expected:?}, got {actual:?}")
89 }
90 Self::WrongFamily { expected, actual } => {
91 write!(formatter, "WAL family expected {expected}, got {actual}")
92 }
93 Self::UnsupportedVersion {
94 family,
95 version,
96 minimum,
97 maximum,
98 } => write!(
99 formatter,
100 "WAL family {family} version {version} is outside accepted range {minimum}..={maximum}"
101 ),
102 Self::InvalidHeaderLength { expected, actual } => {
103 write!(
104 formatter,
105 "WAL header length expected {expected}, got {actual}"
106 )
107 }
108 Self::NonZeroFileLength { actual } => {
109 write!(formatter, "WAL file length must be zero, got {actual}")
110 }
111 Self::InvalidSharedHeader { check } => {
112 write!(formatter, "WAL shared header failed {check:?}")
113 }
114 }
115 }
116}
117
118impl std::error::Error for WalHeaderError {}
119
120pub fn encode_header(first_seq: LogSeq) -> Result<Vec<u8>, RegistryError> {
122 let family = FormatFamily::Wal;
123 let spec = FormatRegistry::families()
124 .iter()
125 .find(|candidate| candidate.family == family)
126 .ok_or(RegistryError::UnknownFamily(family.id()))?;
127 let mut encoded = encode_frame_header(FileHeader {
128 magic: FILE_MAGIC,
129 family: family.id(),
130 version: spec.current_version,
131 flags: 0,
132 header_length: WAL_HEADER_LEN as u64,
133 file_length: 0,
134 });
135 encoded.extend_from_slice(&first_seq.get().to_le_bytes());
136 Ok(encoded)
137}
138
139pub fn decode_header(bytes: &[u8]) -> Result<WalHeader, WalHeaderError> {
141 if bytes.is_empty() {
142 return Err(WalHeaderError::Missing);
143 }
144 if bytes.len() < WAL_HEADER_LEN {
145 return Err(WalHeaderError::Truncated {
146 needed: WAL_HEADER_LEN,
147 available: bytes.len(),
148 });
149 }
150 let actual_magic = bytes
151 .get(..8)
152 .and_then(|value| <[u8; 8]>::try_from(value).ok())
153 .ok_or(WalHeaderError::Truncated {
154 needed: WAL_HEADER_LEN,
155 available: bytes.len(),
156 })?;
157 let family = read_u16("WAL", bytes, 8).map_err(invalid_shared_header)?;
158 let version = read_u16("WAL", bytes, 10).map_err(invalid_shared_header)?;
159 let header_length = read_u64("WAL", bytes, 16).map_err(invalid_shared_header)?;
160 let common =
161 decode_frame_header("WAL", FormatFamily::Wal, bytes).map_err(|error| {
162 match error.check() {
163 FormatCheck::Magic => WalHeaderError::WrongMagic {
164 expected: FILE_MAGIC,
165 actual: actual_magic,
166 },
167 FormatCheck::Family => WalHeaderError::WrongFamily {
168 expected: FormatFamily::Wal.id(),
169 actual: family,
170 },
171 FormatCheck::Version => match FormatRegistry::require(family, version) {
172 Err(RegistryError::UnsupportedVersion {
173 family,
174 version,
175 minimum,
176 maximum,
177 }) => WalHeaderError::UnsupportedVersion {
178 family,
179 version,
180 minimum,
181 maximum,
182 },
183 Ok(_) | Err(_) => WalHeaderError::InvalidSharedHeader {
184 check: FormatCheck::Version,
185 },
186 },
187 FormatCheck::HeaderLength => WalHeaderError::InvalidHeaderLength {
188 expected: WAL_HEADER_LEN as u64,
189 actual: header_length,
190 },
191 check => WalHeaderError::InvalidSharedHeader { check },
192 }
193 })?;
194 if common.header_length != WAL_HEADER_LEN as u64 {
195 return Err(WalHeaderError::InvalidHeaderLength {
196 expected: WAL_HEADER_LEN as u64,
197 actual: common.header_length,
198 });
199 }
200 if common.file_length != 0 {
201 return Err(WalHeaderError::NonZeroFileLength {
202 actual: common.file_length,
203 });
204 }
205 let first_seq = read_u64("WAL", bytes, FILE_HEADER_LEN).map_err(invalid_shared_header)?;
206 Ok(WalHeader {
207 first_seq: LogSeq::new(first_seq),
208 })
209}
210
211fn invalid_shared_header(error: FormatError) -> WalHeaderError {
212 WalHeaderError::InvalidSharedHeader {
213 check: error.check(),
214 }
215}