1use byteorder::{BigEndian, ByteOrder};
2use serde::Serialize;
3use std::fs::File;
4use std::io::{Read, Seek, SeekFrom};
5
6use crate::IdbError;
7
8pub const LOG_BLOCK_SIZE: usize = 512;
10pub const LOG_BLOCK_HDR_SIZE: usize = 14;
11pub const LOG_BLOCK_TRL_SIZE: usize = 4;
12pub const LOG_BLOCK_FLUSH_BIT_MASK: u32 = 0x80000000;
13pub const LOG_BLOCK_CHECKSUM_OFFSET: usize = 508; pub const LOG_FILE_HDR_BLOCKS: u64 = 4; pub const LOG_HEADER_GROUP_ID: usize = 0;
18pub const LOG_HEADER_START_LSN: usize = 4;
19pub const LOG_HEADER_FILE_NO: usize = 12;
20pub const LOG_HEADER_CREATED_BY: usize = 16;
21pub const LOG_HEADER_CREATED_BY_LEN: usize = 32;
22
23pub const LOG_CHECKPOINT_NO: usize = 0;
25pub const LOG_CHECKPOINT_LSN: usize = 8;
26pub const LOG_CHECKPOINT_OFFSET: usize = 16;
27pub const LOG_CHECKPOINT_BUF_SIZE: usize = 20;
28pub const LOG_CHECKPOINT_ARCHIVED_LSN: usize = 24;
29
30#[derive(Debug, Clone, Serialize)]
32pub struct LogFileHeader {
33 pub group_id: u32,
34 pub start_lsn: u64,
35 pub file_no: u32,
36 pub created_by: String,
37}
38
39impl LogFileHeader {
40 pub fn parse(block: &[u8]) -> Option<Self> {
42 if block.len() < LOG_BLOCK_SIZE {
43 return None;
44 }
45
46 let group_id = BigEndian::read_u32(&block[LOG_HEADER_GROUP_ID..]);
47 let start_lsn = BigEndian::read_u64(&block[LOG_HEADER_START_LSN..]);
48 let file_no = BigEndian::read_u32(&block[LOG_HEADER_FILE_NO..]);
49
50 let created_bytes =
51 &block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + LOG_HEADER_CREATED_BY_LEN];
52 let created_by = created_bytes
53 .iter()
54 .take_while(|&&b| b != 0)
55 .map(|&b| b as char)
56 .collect::<String>();
57
58 Some(LogFileHeader {
59 group_id,
60 start_lsn,
61 file_no,
62 created_by,
63 })
64 }
65}
66
67#[derive(Debug, Clone, Serialize)]
69pub struct LogCheckpoint {
70 pub number: u64,
71 pub lsn: u64,
72 pub offset: u32,
73 pub buf_size: u32,
74 pub archived_lsn: u64,
75}
76
77impl LogCheckpoint {
78 pub fn parse(block: &[u8]) -> Option<Self> {
80 if block.len() < LOG_BLOCK_SIZE {
81 return None;
82 }
83
84 let number = BigEndian::read_u64(&block[LOG_CHECKPOINT_NO..]);
85 let lsn = BigEndian::read_u64(&block[LOG_CHECKPOINT_LSN..]);
86 let offset = BigEndian::read_u32(&block[LOG_CHECKPOINT_OFFSET..]);
87 let buf_size = BigEndian::read_u32(&block[LOG_CHECKPOINT_BUF_SIZE..]);
88 let archived_lsn = BigEndian::read_u64(&block[LOG_CHECKPOINT_ARCHIVED_LSN..]);
89
90 Some(LogCheckpoint {
91 number,
92 lsn,
93 offset,
94 buf_size,
95 archived_lsn,
96 })
97 }
98}
99
100#[derive(Debug, Clone, Serialize)]
102pub struct LogBlockHeader {
103 pub block_no: u32,
104 pub flush_flag: bool,
105 pub data_len: u16,
106 pub first_rec_group: u16,
107 pub checkpoint_no: u32,
108}
109
110impl LogBlockHeader {
111 pub fn parse(block: &[u8]) -> Option<Self> {
113 if block.len() < LOG_BLOCK_HDR_SIZE {
114 return None;
115 }
116
117 let raw_block_no = BigEndian::read_u32(&block[0..]);
118 let flush_flag = (raw_block_no & LOG_BLOCK_FLUSH_BIT_MASK) != 0;
119 let block_no = raw_block_no & !LOG_BLOCK_FLUSH_BIT_MASK;
120
121 let data_len = BigEndian::read_u16(&block[4..]);
122 let first_rec_group = BigEndian::read_u16(&block[6..]);
123 let checkpoint_no = BigEndian::read_u32(&block[8..]);
124
125 Some(LogBlockHeader {
126 block_no,
127 flush_flag,
128 data_len,
129 first_rec_group,
130 checkpoint_no,
131 })
132 }
133
134 pub fn has_data(&self) -> bool {
136 self.data_len as usize > LOG_BLOCK_HDR_SIZE
137 }
138}
139
140#[derive(Debug, Clone, Serialize)]
142pub struct LogBlockTrailer {
143 pub checksum: u32,
144}
145
146impl LogBlockTrailer {
147 pub fn parse(block: &[u8]) -> Option<Self> {
149 if block.len() < LOG_BLOCK_SIZE {
150 return None;
151 }
152
153 let checksum = BigEndian::read_u32(&block[LOG_BLOCK_CHECKSUM_OFFSET..]);
154
155 Some(LogBlockTrailer { checksum })
156 }
157}
158
159pub fn validate_log_block_checksum(block: &[u8]) -> bool {
163 if block.len() < LOG_BLOCK_SIZE {
164 return false;
165 }
166 let stored = BigEndian::read_u32(&block[LOG_BLOCK_CHECKSUM_OFFSET..]);
167 let calculated = crc32c::crc32c(&block[..LOG_BLOCK_CHECKSUM_OFFSET]);
168 stored == calculated
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
173pub enum MlogRecordType {
174 Mlog1Byte,
175 Mlog2Bytes,
176 Mlog4Bytes,
177 Mlog8Bytes,
178 MlogRecInsert,
179 MlogRecClustDeleteMark,
180 MlogRecSecDeleteMark,
181 MlogRecUpdate,
182 MlogRecDelete,
183 MlogListEndDelete,
184 MlogListStartDelete,
185 MlogListEndCopyCreated,
186 MlogPageReorganize,
187 MlogPageCreate,
188 MlogUndoInsert,
189 MlogUndoEraseEnd,
190 MlogUndoInit,
191 MlogUndoHdrReuse,
192 MlogRecMinMark,
193 MlogIbufBitmapInit,
194 MlogInitFilePage,
195 MlogWriteString,
196 MlogMultiRecEnd,
197 MlogDummyRecord,
198 MlogFileDelete,
199 MlogCompPageCreate,
200 MlogCompRecInsert,
201 MlogCompRecClustDeleteMark,
202 MlogCompRecSecDeleteMark,
203 MlogCompRecUpdate,
204 MlogCompRecDelete,
205 MlogCompListEndDelete,
206 MlogCompListStartDelete,
207 MlogCompListEndCopyCreated,
208 MlogCompPageReorganize,
209 MlogFileRename,
210 MlogPageCreateRTree,
211 MlogCompPageCreateRTree,
212 MlogTableDynamicMeta,
213 MlogPageCreateSdi,
214 MlogCompPageCreateSdi,
215 MlogFileOpen,
216 MlogFileCreate,
217 MlogZipPageCompress,
218 Unknown(u8),
219}
220
221impl MlogRecordType {
222 pub fn from_u8(val: u8) -> Self {
224 match val {
225 1 => MlogRecordType::Mlog1Byte,
226 2 => MlogRecordType::Mlog2Bytes,
227 4 => MlogRecordType::Mlog4Bytes,
228 8 => MlogRecordType::Mlog8Bytes,
229 9 => MlogRecordType::MlogRecInsert,
230 10 => MlogRecordType::MlogRecClustDeleteMark,
231 11 => MlogRecordType::MlogRecSecDeleteMark,
232 13 => MlogRecordType::MlogRecUpdate,
233 14 => MlogRecordType::MlogRecDelete,
234 15 => MlogRecordType::MlogListEndDelete,
235 16 => MlogRecordType::MlogListStartDelete,
236 17 => MlogRecordType::MlogListEndCopyCreated,
237 18 => MlogRecordType::MlogPageReorganize,
238 19 => MlogRecordType::MlogPageCreate,
239 20 => MlogRecordType::MlogUndoInsert,
240 21 => MlogRecordType::MlogUndoEraseEnd,
241 22 => MlogRecordType::MlogUndoInit,
242 24 => MlogRecordType::MlogUndoHdrReuse,
243 28 => MlogRecordType::MlogRecMinMark,
244 29 => MlogRecordType::MlogIbufBitmapInit,
245 30 => MlogRecordType::MlogInitFilePage,
246 31 => MlogRecordType::MlogWriteString,
247 32 => MlogRecordType::MlogMultiRecEnd,
248 33 => MlogRecordType::MlogDummyRecord,
249 34 => MlogRecordType::MlogFileDelete,
250 35 => MlogRecordType::MlogCompPageCreate,
251 36 => MlogRecordType::MlogCompRecInsert,
252 37 => MlogRecordType::MlogCompRecClustDeleteMark,
253 38 => MlogRecordType::MlogCompRecSecDeleteMark,
254 39 => MlogRecordType::MlogCompRecUpdate,
255 40 => MlogRecordType::MlogCompRecDelete,
256 41 => MlogRecordType::MlogCompListEndDelete,
257 42 => MlogRecordType::MlogCompListStartDelete,
258 43 => MlogRecordType::MlogCompListEndCopyCreated,
259 44 => MlogRecordType::MlogCompPageReorganize,
260 45 => MlogRecordType::MlogFileRename,
261 46 => MlogRecordType::MlogPageCreateRTree,
262 47 => MlogRecordType::MlogCompPageCreateRTree,
263 48 => MlogRecordType::MlogTableDynamicMeta,
264 49 => MlogRecordType::MlogPageCreateSdi,
265 50 => MlogRecordType::MlogCompPageCreateSdi,
266 51 => MlogRecordType::MlogFileOpen,
267 52 => MlogRecordType::MlogFileCreate,
268 53 => MlogRecordType::MlogZipPageCompress,
269 v => MlogRecordType::Unknown(v),
270 }
271 }
272
273 pub fn name(&self) -> &str {
275 match self {
276 MlogRecordType::Mlog1Byte => "MLOG_1BYTE",
277 MlogRecordType::Mlog2Bytes => "MLOG_2BYTES",
278 MlogRecordType::Mlog4Bytes => "MLOG_4BYTES",
279 MlogRecordType::Mlog8Bytes => "MLOG_8BYTES",
280 MlogRecordType::MlogRecInsert => "MLOG_REC_INSERT",
281 MlogRecordType::MlogRecClustDeleteMark => "MLOG_REC_CLUST_DELETE_MARK",
282 MlogRecordType::MlogRecSecDeleteMark => "MLOG_REC_SEC_DELETE_MARK",
283 MlogRecordType::MlogRecUpdate => "MLOG_REC_UPDATE_IN_PLACE",
284 MlogRecordType::MlogRecDelete => "MLOG_REC_DELETE",
285 MlogRecordType::MlogListEndDelete => "MLOG_LIST_END_DELETE",
286 MlogRecordType::MlogListStartDelete => "MLOG_LIST_START_DELETE",
287 MlogRecordType::MlogListEndCopyCreated => "MLOG_LIST_END_COPY_CREATED",
288 MlogRecordType::MlogPageReorganize => "MLOG_PAGE_REORGANIZE",
289 MlogRecordType::MlogPageCreate => "MLOG_PAGE_CREATE",
290 MlogRecordType::MlogUndoInsert => "MLOG_UNDO_INSERT",
291 MlogRecordType::MlogUndoEraseEnd => "MLOG_UNDO_ERASE_END",
292 MlogRecordType::MlogUndoInit => "MLOG_UNDO_INIT",
293 MlogRecordType::MlogUndoHdrReuse => "MLOG_UNDO_HDR_REUSE",
294 MlogRecordType::MlogRecMinMark => "MLOG_REC_MIN_MARK",
295 MlogRecordType::MlogIbufBitmapInit => "MLOG_IBUF_BITMAP_INIT",
296 MlogRecordType::MlogInitFilePage => "MLOG_INIT_FILE_PAGE",
297 MlogRecordType::MlogWriteString => "MLOG_WRITE_STRING",
298 MlogRecordType::MlogMultiRecEnd => "MLOG_MULTI_REC_END",
299 MlogRecordType::MlogDummyRecord => "MLOG_DUMMY_RECORD",
300 MlogRecordType::MlogFileDelete => "MLOG_FILE_DELETE",
301 MlogRecordType::MlogCompPageCreate => "MLOG_COMP_PAGE_CREATE",
302 MlogRecordType::MlogCompRecInsert => "MLOG_COMP_REC_INSERT",
303 MlogRecordType::MlogCompRecClustDeleteMark => "MLOG_COMP_REC_CLUST_DELETE_MARK",
304 MlogRecordType::MlogCompRecSecDeleteMark => "MLOG_COMP_REC_SEC_DELETE_MARK",
305 MlogRecordType::MlogCompRecUpdate => "MLOG_COMP_REC_UPDATE_IN_PLACE",
306 MlogRecordType::MlogCompRecDelete => "MLOG_COMP_REC_DELETE",
307 MlogRecordType::MlogCompListEndDelete => "MLOG_COMP_LIST_END_DELETE",
308 MlogRecordType::MlogCompListStartDelete => "MLOG_COMP_LIST_START_DELETE",
309 MlogRecordType::MlogCompListEndCopyCreated => "MLOG_COMP_LIST_END_COPY_CREATED",
310 MlogRecordType::MlogCompPageReorganize => "MLOG_COMP_PAGE_REORGANIZE",
311 MlogRecordType::MlogFileRename => "MLOG_FILE_RENAME",
312 MlogRecordType::MlogPageCreateRTree => "MLOG_PAGE_CREATE_RTREE",
313 MlogRecordType::MlogCompPageCreateRTree => "MLOG_COMP_PAGE_CREATE_RTREE",
314 MlogRecordType::MlogTableDynamicMeta => "MLOG_TABLE_DYNAMIC_META",
315 MlogRecordType::MlogPageCreateSdi => "MLOG_PAGE_CREATE_SDI",
316 MlogRecordType::MlogCompPageCreateSdi => "MLOG_COMP_PAGE_CREATE_SDI",
317 MlogRecordType::MlogFileOpen => "MLOG_FILE_OPEN",
318 MlogRecordType::MlogFileCreate => "MLOG_FILE_CREATE",
319 MlogRecordType::MlogZipPageCompress => "MLOG_ZIP_PAGE_COMPRESS",
320 MlogRecordType::Unknown(_) => "UNKNOWN",
321 }
322 }
323}
324
325impl std::fmt::Display for MlogRecordType {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 match self {
328 MlogRecordType::Unknown(v) => write!(f, "UNKNOWN({})", v),
329 _ => write!(f, "{}", self.name()),
330 }
331 }
332}
333
334pub struct LogFile {
336 file: File,
337 file_size: u64,
338}
339
340impl LogFile {
341 pub fn open(path: &str) -> Result<Self, IdbError> {
343 let file =
344 File::open(path).map_err(|e| IdbError::Io(format!("Cannot open {}: {}", path, e)))?;
345 let file_size = file
346 .metadata()
347 .map_err(|e| IdbError::Io(format!("Cannot stat {}: {}", path, e)))?
348 .len();
349
350 if file_size < (LOG_FILE_HDR_BLOCKS as usize * LOG_BLOCK_SIZE) as u64 {
351 return Err(IdbError::Parse(format!(
352 "File {} is too small for a redo log ({} bytes, minimum {})",
353 path,
354 file_size,
355 LOG_FILE_HDR_BLOCKS as usize * LOG_BLOCK_SIZE
356 )));
357 }
358
359 Ok(LogFile { file, file_size })
360 }
361
362 pub fn block_count(&self) -> u64 {
364 self.file_size / LOG_BLOCK_SIZE as u64
365 }
366
367 pub fn data_block_count(&self) -> u64 {
369 self.block_count().saturating_sub(LOG_FILE_HDR_BLOCKS)
370 }
371
372 pub fn read_block(&mut self, block_no: u64) -> Result<Vec<u8>, IdbError> {
374 let offset = block_no * LOG_BLOCK_SIZE as u64;
375 if offset + LOG_BLOCK_SIZE as u64 > self.file_size {
376 return Err(IdbError::Io(format!(
377 "Block {} is beyond end of file (offset {}, file size {})",
378 block_no, offset, self.file_size
379 )));
380 }
381
382 self.file
383 .seek(SeekFrom::Start(offset))
384 .map_err(|e| IdbError::Io(format!("Seek error: {}", e)))?;
385
386 let mut buf = vec![0u8; LOG_BLOCK_SIZE];
387 self.file
388 .read_exact(&mut buf)
389 .map_err(|e| IdbError::Io(format!("Read error at block {}: {}", block_no, e)))?;
390
391 Ok(buf)
392 }
393
394 pub fn read_header(&mut self) -> Result<LogFileHeader, IdbError> {
396 let block = self.read_block(0)?;
397 LogFileHeader::parse(&block).ok_or_else(|| {
398 IdbError::Parse("Failed to parse log file header (block 0)".to_string())
399 })
400 }
401
402 pub fn read_checkpoint(&mut self, slot: u8) -> Result<LogCheckpoint, IdbError> {
404 let block_no = match slot {
405 0 => 1,
406 1 => 3,
407 _ => {
408 return Err(IdbError::Argument(format!(
409 "Invalid checkpoint slot {} (must be 0 or 1)",
410 slot
411 )))
412 }
413 };
414 let block = self.read_block(block_no)?;
415 LogCheckpoint::parse(&block).ok_or_else(|| {
416 IdbError::Parse(format!("Failed to parse checkpoint at block {}", block_no))
417 })
418 }
419
420 pub fn file_size(&self) -> u64 {
422 self.file_size
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 fn make_block() -> Vec<u8> {
431 vec![0u8; LOG_BLOCK_SIZE]
432 }
433
434 #[test]
435 fn test_log_block_header_parse() {
436 let mut block = make_block();
437 BigEndian::write_u32(&mut block[0..], 42);
439 BigEndian::write_u16(&mut block[4..], 200);
441 BigEndian::write_u16(&mut block[6..], 50);
443 BigEndian::write_u32(&mut block[8..], 7);
445
446 let hdr = LogBlockHeader::parse(&block).unwrap();
447 assert_eq!(hdr.block_no, 42);
448 assert!(!hdr.flush_flag);
449 assert_eq!(hdr.data_len, 200);
450 assert_eq!(hdr.first_rec_group, 50);
451 assert_eq!(hdr.checkpoint_no, 7);
452 assert!(hdr.has_data());
453 }
454
455 #[test]
456 fn test_log_block_flush_bit() {
457 let mut block = make_block();
458 BigEndian::write_u32(&mut block[0..], 0x80000064);
460 BigEndian::write_u16(&mut block[4..], 14); let hdr = LogBlockHeader::parse(&block).unwrap();
463 assert!(hdr.flush_flag);
464 assert_eq!(hdr.block_no, 100);
465 assert!(!hdr.has_data());
466 }
467
468 #[test]
469 fn test_log_block_header_empty() {
470 let block = make_block();
471 let hdr = LogBlockHeader::parse(&block).unwrap();
472 assert_eq!(hdr.block_no, 0);
473 assert!(!hdr.flush_flag);
474 assert_eq!(hdr.data_len, 0);
475 assert_eq!(hdr.first_rec_group, 0);
476 assert_eq!(hdr.checkpoint_no, 0);
477 assert!(!hdr.has_data());
478 }
479
480 #[test]
481 fn test_log_block_header_too_small() {
482 let block = vec![0u8; 10]; assert!(LogBlockHeader::parse(&block).is_none());
484 }
485
486 #[test]
487 fn test_log_block_trailer_parse() {
488 let mut block = make_block();
489 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xCAFEBABE);
490
491 let trailer = LogBlockTrailer::parse(&block).unwrap();
492 assert_eq!(trailer.checksum, 0xCAFEBABE);
493 }
494
495 #[test]
496 fn test_log_file_header_parse() {
497 let mut block = make_block();
498 BigEndian::write_u32(&mut block[LOG_HEADER_GROUP_ID..], 1);
499 BigEndian::write_u64(&mut block[LOG_HEADER_START_LSN..], 0x00000000001A2B3C);
500 BigEndian::write_u32(&mut block[LOG_HEADER_FILE_NO..], 0);
501 let creator = b"MySQL 8.0.32";
503 block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
504 .copy_from_slice(creator);
505
506 let hdr = LogFileHeader::parse(&block).unwrap();
507 assert_eq!(hdr.group_id, 1);
508 assert_eq!(hdr.start_lsn, 0x1A2B3C);
509 assert_eq!(hdr.file_no, 0);
510 assert_eq!(hdr.created_by, "MySQL 8.0.32");
511 }
512
513 #[test]
514 fn test_log_file_header_empty_created_by() {
515 let block = make_block();
516 let hdr = LogFileHeader::parse(&block).unwrap();
517 assert_eq!(hdr.created_by, "");
518 }
519
520 #[test]
521 fn test_log_checkpoint_parse() {
522 let mut block = make_block();
523 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_NO..], 99);
524 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_LSN..], 0x00000000DEADBEEF);
525 BigEndian::write_u32(&mut block[LOG_CHECKPOINT_OFFSET..], 2048);
526 BigEndian::write_u32(&mut block[LOG_CHECKPOINT_BUF_SIZE..], 65536);
527 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_ARCHIVED_LSN..], 0x00000000CAFEBABE);
528
529 let cp = LogCheckpoint::parse(&block).unwrap();
530 assert_eq!(cp.number, 99);
531 assert_eq!(cp.lsn, 0xDEADBEEF);
532 assert_eq!(cp.offset, 2048);
533 assert_eq!(cp.buf_size, 65536);
534 assert_eq!(cp.archived_lsn, 0xCAFEBABE);
535 }
536
537 #[test]
538 fn test_mlog_record_type_from_u8_known() {
539 assert_eq!(MlogRecordType::from_u8(1), MlogRecordType::Mlog1Byte);
540 assert_eq!(MlogRecordType::from_u8(9), MlogRecordType::MlogRecInsert);
541 assert_eq!(
542 MlogRecordType::from_u8(36),
543 MlogRecordType::MlogCompRecInsert
544 );
545 assert_eq!(
546 MlogRecordType::from_u8(53),
547 MlogRecordType::MlogZipPageCompress
548 );
549 }
550
551 #[test]
552 fn test_mlog_record_type_unknown() {
553 assert_eq!(MlogRecordType::from_u8(0), MlogRecordType::Unknown(0));
554 assert_eq!(MlogRecordType::from_u8(255), MlogRecordType::Unknown(255));
555 assert_eq!(MlogRecordType::from_u8(100), MlogRecordType::Unknown(100));
556 }
557
558 #[test]
559 fn test_mlog_record_type_name() {
560 assert_eq!(MlogRecordType::Mlog1Byte.name(), "MLOG_1BYTE");
561 assert_eq!(MlogRecordType::MlogRecInsert.name(), "MLOG_REC_INSERT");
562 assert_eq!(MlogRecordType::Unknown(99).name(), "UNKNOWN");
563 }
564
565 #[test]
566 fn test_mlog_record_type_display() {
567 assert_eq!(format!("{}", MlogRecordType::Mlog1Byte), "MLOG_1BYTE");
568 assert_eq!(format!("{}", MlogRecordType::Unknown(99)), "UNKNOWN(99)");
569 }
570
571 #[test]
572 fn test_log_block_checksum_validation() {
573 let mut block = make_block();
574 BigEndian::write_u32(&mut block[0..], 5); BigEndian::write_u16(&mut block[4..], 100); BigEndian::write_u16(&mut block[6..], 14); block[14] = 0xAB; let crc = crc32c::crc32c(&block[..LOG_BLOCK_CHECKSUM_OFFSET]);
582 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], crc);
583
584 assert!(validate_log_block_checksum(&block));
585 }
586
587 #[test]
588 fn test_log_block_checksum_invalid() {
589 let mut block = make_block();
590 BigEndian::write_u32(&mut block[0..], 5);
591 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xDEADDEAD);
593
594 assert!(!validate_log_block_checksum(&block));
595 }
596}