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).ok_or_else(|| {
438 IdbError::Parse("Failed to parse log file header (block 0)".to_string())
439 })
440 }
441
442 pub fn read_checkpoint(&mut self, slot: u8) -> Result<LogCheckpoint, IdbError> {
444 let block_no = match slot {
445 0 => 1,
446 1 => 3,
447 _ => {
448 return Err(IdbError::Argument(format!(
449 "Invalid checkpoint slot {} (must be 0 or 1)",
450 slot
451 )))
452 }
453 };
454 let block = self.read_block(block_no)?;
455 LogCheckpoint::parse(&block).ok_or_else(|| {
456 IdbError::Parse(format!("Failed to parse checkpoint at block {}", block_no))
457 })
458 }
459
460 pub fn file_size(&self) -> u64 {
462 self.file_size
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469
470 fn make_block() -> Vec<u8> {
471 vec![0u8; LOG_BLOCK_SIZE]
472 }
473
474 #[test]
475 fn test_log_block_header_parse() {
476 let mut block = make_block();
477 BigEndian::write_u32(&mut block[0..], 42);
479 BigEndian::write_u16(&mut block[4..], 200);
481 BigEndian::write_u16(&mut block[6..], 50);
483 BigEndian::write_u32(&mut block[8..], 7);
485
486 let hdr = LogBlockHeader::parse(&block).unwrap();
487 assert_eq!(hdr.block_no, 42);
488 assert!(!hdr.flush_flag);
489 assert_eq!(hdr.data_len, 200);
490 assert_eq!(hdr.first_rec_group, 50);
491 assert_eq!(hdr.checkpoint_no, 7);
492 assert!(hdr.has_data());
493 }
494
495 #[test]
496 fn test_log_block_flush_bit() {
497 let mut block = make_block();
498 BigEndian::write_u32(&mut block[0..], 0x80000064);
500 BigEndian::write_u16(&mut block[4..], 14); let hdr = LogBlockHeader::parse(&block).unwrap();
503 assert!(hdr.flush_flag);
504 assert_eq!(hdr.block_no, 100);
505 assert!(!hdr.has_data());
506 }
507
508 #[test]
509 fn test_log_block_header_empty() {
510 let block = make_block();
511 let hdr = LogBlockHeader::parse(&block).unwrap();
512 assert_eq!(hdr.block_no, 0);
513 assert!(!hdr.flush_flag);
514 assert_eq!(hdr.data_len, 0);
515 assert_eq!(hdr.first_rec_group, 0);
516 assert_eq!(hdr.checkpoint_no, 0);
517 assert!(!hdr.has_data());
518 }
519
520 #[test]
521 fn test_log_block_header_too_small() {
522 let block = vec![0u8; 10]; assert!(LogBlockHeader::parse(&block).is_none());
524 }
525
526 #[test]
527 fn test_log_block_trailer_parse() {
528 let mut block = make_block();
529 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xCAFEBABE);
530
531 let trailer = LogBlockTrailer::parse(&block).unwrap();
532 assert_eq!(trailer.checksum, 0xCAFEBABE);
533 }
534
535 #[test]
536 fn test_log_file_header_parse() {
537 let mut block = make_block();
538 BigEndian::write_u32(&mut block[LOG_HEADER_GROUP_ID..], 1);
539 BigEndian::write_u64(&mut block[LOG_HEADER_START_LSN..], 0x00000000001A2B3C);
540 BigEndian::write_u32(&mut block[LOG_HEADER_FILE_NO..], 0);
541 let creator = b"MySQL 8.0.32";
543 block[LOG_HEADER_CREATED_BY..LOG_HEADER_CREATED_BY + creator.len()]
544 .copy_from_slice(creator);
545
546 let hdr = LogFileHeader::parse(&block).unwrap();
547 assert_eq!(hdr.group_id, 1);
548 assert_eq!(hdr.start_lsn, 0x1A2B3C);
549 assert_eq!(hdr.file_no, 0);
550 assert_eq!(hdr.created_by, "MySQL 8.0.32");
551 }
552
553 #[test]
554 fn test_log_file_header_empty_created_by() {
555 let block = make_block();
556 let hdr = LogFileHeader::parse(&block).unwrap();
557 assert_eq!(hdr.created_by, "");
558 }
559
560 #[test]
561 fn test_log_checkpoint_parse() {
562 let mut block = make_block();
563 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_NO..], 99);
564 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_LSN..], 0x00000000DEADBEEF);
565 BigEndian::write_u32(&mut block[LOG_CHECKPOINT_OFFSET..], 2048);
566 BigEndian::write_u32(&mut block[LOG_CHECKPOINT_BUF_SIZE..], 65536);
567 BigEndian::write_u64(&mut block[LOG_CHECKPOINT_ARCHIVED_LSN..], 0x00000000CAFEBABE);
568
569 let cp = LogCheckpoint::parse(&block).unwrap();
570 assert_eq!(cp.number, 99);
571 assert_eq!(cp.lsn, 0xDEADBEEF);
572 assert_eq!(cp.offset, 2048);
573 assert_eq!(cp.buf_size, 65536);
574 assert_eq!(cp.archived_lsn, 0xCAFEBABE);
575 }
576
577 #[test]
578 fn test_mlog_record_type_from_u8_known() {
579 assert_eq!(MlogRecordType::from_u8(1), MlogRecordType::Mlog1Byte);
580 assert_eq!(MlogRecordType::from_u8(9), MlogRecordType::MlogRecInsert);
581 assert_eq!(
582 MlogRecordType::from_u8(36),
583 MlogRecordType::MlogCompRecInsert
584 );
585 assert_eq!(
586 MlogRecordType::from_u8(53),
587 MlogRecordType::MlogZipPageCompress
588 );
589 }
590
591 #[test]
592 fn test_mlog_record_type_unknown() {
593 assert_eq!(MlogRecordType::from_u8(0), MlogRecordType::Unknown(0));
594 assert_eq!(MlogRecordType::from_u8(255), MlogRecordType::Unknown(255));
595 assert_eq!(MlogRecordType::from_u8(100), MlogRecordType::Unknown(100));
596 }
597
598 #[test]
599 fn test_mlog_record_type_name() {
600 assert_eq!(MlogRecordType::Mlog1Byte.name(), "MLOG_1BYTE");
601 assert_eq!(MlogRecordType::MlogRecInsert.name(), "MLOG_REC_INSERT");
602 assert_eq!(MlogRecordType::Unknown(99).name(), "UNKNOWN");
603 }
604
605 #[test]
606 fn test_mlog_record_type_display() {
607 assert_eq!(format!("{}", MlogRecordType::Mlog1Byte), "MLOG_1BYTE");
608 assert_eq!(format!("{}", MlogRecordType::Unknown(99)), "UNKNOWN(99)");
609 }
610
611 #[test]
612 fn test_log_block_checksum_validation() {
613 let mut block = make_block();
614 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]);
622 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], crc);
623
624 assert!(validate_log_block_checksum(&block));
625 }
626
627 #[test]
628 fn test_log_block_checksum_invalid() {
629 let mut block = make_block();
630 BigEndian::write_u32(&mut block[0..], 5);
631 BigEndian::write_u32(&mut block[LOG_BLOCK_CHECKSUM_OFFSET..], 0xDEADDEAD);
633
634 assert!(!validate_log_block_checksum(&block));
635 }
636}