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