1use byteorder::{BigEndian, ByteOrder};
13use serde::Serialize;
14
15use crate::innodb::constants::*;
16use crate::innodb::page_types::PageType;
17
18#[derive(Debug, Clone, Serialize)]
20pub struct FilHeader {
21 pub checksum: u32,
23 pub page_number: u32,
25 pub prev_page: u32,
28 pub next_page: u32,
31 pub lsn: u64,
33 pub page_type: PageType,
35 pub flush_lsn: u64,
37 pub space_id: u32,
39}
40
41impl FilHeader {
42 pub fn parse(data: &[u8]) -> Option<Self> {
46 if data.len() < SIZE_FIL_HEAD {
47 return None;
48 }
49
50 Some(FilHeader {
51 checksum: BigEndian::read_u32(&data[FIL_PAGE_SPACE_OR_CHKSUM..]),
52 page_number: BigEndian::read_u32(&data[FIL_PAGE_OFFSET..]),
53 prev_page: BigEndian::read_u32(&data[FIL_PAGE_PREV..]),
54 next_page: BigEndian::read_u32(&data[FIL_PAGE_NEXT..]),
55 lsn: BigEndian::read_u64(&data[FIL_PAGE_LSN..]),
56 page_type: PageType::from_u16(BigEndian::read_u16(&data[FIL_PAGE_TYPE..])),
57 flush_lsn: BigEndian::read_u64(&data[FIL_PAGE_FILE_FLUSH_LSN..]),
58 space_id: BigEndian::read_u32(&data[FIL_PAGE_SPACE_ID..]),
59 })
60 }
61
62 pub fn has_prev(&self) -> bool {
64 self.prev_page != FIL_NULL && self.prev_page != 0
65 }
66
67 pub fn has_next(&self) -> bool {
69 self.next_page != FIL_NULL && self.next_page != 0
70 }
71}
72
73#[derive(Debug, Clone, Serialize)]
75pub struct FilTrailer {
76 pub checksum: u32,
78 pub lsn_low32: u32,
80}
81
82impl FilTrailer {
83 pub fn parse(data: &[u8]) -> Option<Self> {
88 if data.len() < SIZE_FIL_TRAILER {
89 return None;
90 }
91
92 Some(FilTrailer {
93 checksum: BigEndian::read_u32(&data[0..]),
94 lsn_low32: BigEndian::read_u32(&data[4..]),
95 })
96 }
97}
98
99#[derive(Debug, Clone, Serialize)]
101pub struct FspHeader {
102 pub space_id: u32,
104 pub size: u32,
106 pub free_limit: u32,
108 pub flags: u32,
110 pub frag_n_used: u32,
112}
113
114impl FspHeader {
115 pub fn parse(page_data: &[u8]) -> Option<Self> {
119 let offset = FIL_PAGE_DATA;
120 if page_data.len() < offset + FSP_HEADER_SIZE {
121 return None;
122 }
123 let data = &page_data[offset..];
124
125 Some(FspHeader {
126 space_id: BigEndian::read_u32(&data[FSP_SPACE_ID..]),
127 size: BigEndian::read_u32(&data[FSP_SIZE..]),
128 free_limit: BigEndian::read_u32(&data[FSP_FREE_LIMIT..]),
129 flags: BigEndian::read_u32(&data[FSP_SPACE_FLAGS..]),
130 frag_n_used: BigEndian::read_u32(&data[FSP_FRAG_N_USED..]),
131 })
132 }
133
134 pub fn page_size_from_flags(&self) -> u32 {
139 use crate::innodb::vendor::detect_vendor_from_flags;
140
141 let vendor_info = detect_vendor_from_flags(self.flags);
142 self.page_size_from_flags_with_vendor(&vendor_info)
143 }
144
145 pub fn page_size_from_flags_with_vendor(
147 &self,
148 vendor_info: &crate::innodb::vendor::VendorInfo,
149 ) -> u32 {
150 let ssize = if vendor_info.is_full_crc32() {
151 self.flags & MARIADB_FSP_FLAGS_FCRC32_PAGE_SSIZE_MASK
153 } else {
154 (self.flags & FSP_FLAGS_MASK_PAGE_SSIZE) >> FSP_FLAGS_POS_PAGE_SSIZE
156 };
157
158 if ssize == 0 {
159 SIZE_PAGE_DEFAULT
161 } else {
162 1u32 << (ssize + 9)
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 fn make_fil_header_bytes(
174 checksum: u32,
175 page_num: u32,
176 prev: u32,
177 next: u32,
178 lsn: u64,
179 page_type: u16,
180 flush_lsn: u64,
181 space_id: u32,
182 ) -> Vec<u8> {
183 let mut buf = vec![0u8; SIZE_FIL_HEAD];
184 BigEndian::write_u32(&mut buf[FIL_PAGE_SPACE_OR_CHKSUM..], checksum);
185 BigEndian::write_u32(&mut buf[FIL_PAGE_OFFSET..], page_num);
186 BigEndian::write_u32(&mut buf[FIL_PAGE_PREV..], prev);
187 BigEndian::write_u32(&mut buf[FIL_PAGE_NEXT..], next);
188 BigEndian::write_u64(&mut buf[FIL_PAGE_LSN..], lsn);
189 BigEndian::write_u16(&mut buf[FIL_PAGE_TYPE..], page_type);
190 BigEndian::write_u64(&mut buf[FIL_PAGE_FILE_FLUSH_LSN..], flush_lsn);
191 BigEndian::write_u32(&mut buf[FIL_PAGE_SPACE_ID..], space_id);
192 buf
193 }
194
195 #[test]
196 fn test_fil_header_parse() {
197 let data = make_fil_header_bytes(
198 0x12345678, 42, 41, 43, 1000, 17855, 2000, 5, );
207 let hdr = FilHeader::parse(&data).unwrap();
208 assert_eq!(hdr.checksum, 0x12345678);
209 assert_eq!(hdr.page_number, 42);
210 assert_eq!(hdr.prev_page, 41);
211 assert_eq!(hdr.next_page, 43);
212 assert_eq!(hdr.lsn, 1000);
213 assert_eq!(hdr.page_type, PageType::Index);
214 assert_eq!(hdr.flush_lsn, 2000);
215 assert_eq!(hdr.space_id, 5);
216 assert!(hdr.has_prev());
217 assert!(hdr.has_next());
218 }
219
220 #[test]
221 fn test_fil_header_null_pages() {
222 let data = make_fil_header_bytes(0, 0, FIL_NULL, FIL_NULL, 0, 0, 0, 0);
223 let hdr = FilHeader::parse(&data).unwrap();
224 assert!(!hdr.has_prev());
225 assert!(!hdr.has_next());
226 }
227
228 #[test]
229 fn test_fil_header_too_short() {
230 let data = vec![0u8; 10];
231 assert!(FilHeader::parse(&data).is_none());
232 }
233
234 #[test]
235 fn test_fil_trailer_parse() {
236 let mut data = vec![0u8; 8];
237 BigEndian::write_u32(&mut data[0..], 0xAABBCCDD);
238 BigEndian::write_u32(&mut data[4..], 0x11223344);
239 let trl = FilTrailer::parse(&data).unwrap();
240 assert_eq!(trl.checksum, 0xAABBCCDD);
241 assert_eq!(trl.lsn_low32, 0x11223344);
242 }
243
244 #[test]
245 fn test_fsp_header_page_size() {
246 let fsp = FspHeader {
247 space_id: 0,
248 size: 100,
249 free_limit: 64,
250 flags: 0, frag_n_used: 0,
252 };
253 assert_eq!(fsp.page_size_from_flags(), SIZE_PAGE_DEFAULT);
254
255 let fsp_16k = FspHeader {
257 flags: 5 << FSP_FLAGS_POS_PAGE_SSIZE,
258 ..fsp
259 };
260 assert_eq!(fsp_16k.page_size_from_flags(), 16384);
261
262 let fsp_4k = FspHeader {
264 flags: 3 << FSP_FLAGS_POS_PAGE_SSIZE,
265 ..fsp
266 };
267 assert_eq!(fsp_4k.page_size_from_flags(), 4096);
268 }
269
270 #[test]
271 fn test_fsp_header_page_size_mariadb_full_crc32() {
272 use crate::innodb::vendor::{MariaDbFormat, VendorInfo};
273
274 let vendor = VendorInfo::mariadb(MariaDbFormat::FullCrc32);
275
276 let fsp = FspHeader {
279 space_id: 0,
280 size: 100,
281 free_limit: 64,
282 flags: 0x10 | 5, frag_n_used: 0,
284 };
285 assert_eq!(fsp.page_size_from_flags_with_vendor(&vendor), 16384);
286
287 let fsp_4k = FspHeader {
289 flags: 0x10 | 3,
290 ..fsp
291 };
292 assert_eq!(fsp_4k.page_size_from_flags_with_vendor(&vendor), 4096);
293
294 let fsp_default = FspHeader {
296 flags: 0x10,
297 ..fsp
298 };
299 assert_eq!(
300 fsp_default.page_size_from_flags_with_vendor(&vendor),
301 SIZE_PAGE_DEFAULT
302 );
303 }
304}