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