use byteorder::{ByteOrder, LittleEndian};
use serde::Serialize;
use super::constants::{BINLOG_MAGIC, COMMON_HEADER_SIZE};
pub fn validate_binlog_magic(data: &[u8]) -> bool {
data.len() >= 4 && data[..4] == BINLOG_MAGIC
}
#[derive(Debug, Clone, Serialize)]
pub struct BinlogEventHeader {
pub timestamp: u32,
pub type_code: u8,
pub server_id: u32,
pub event_length: u32,
pub next_position: u32,
pub flags: u16,
}
impl BinlogEventHeader {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < COMMON_HEADER_SIZE {
return None;
}
Some(BinlogEventHeader {
timestamp: LittleEndian::read_u32(&data[0..]),
type_code: data[4],
server_id: LittleEndian::read_u32(&data[5..]),
event_length: LittleEndian::read_u32(&data[9..]),
next_position: LittleEndian::read_u32(&data[13..]),
flags: LittleEndian::read_u16(&data[17..]),
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct FormatDescriptionEvent {
pub binlog_version: u16,
pub server_version: String,
pub create_timestamp: u32,
pub header_length: u8,
pub checksum_alg: u8,
}
impl FormatDescriptionEvent {
pub fn has_checksum(&self) -> bool {
self.checksum_alg == 1
}
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 57 {
return None;
}
let binlog_version = LittleEndian::read_u16(&data[0..]);
let ver_bytes = &data[2..52];
let server_version = std::str::from_utf8(ver_bytes)
.unwrap_or("")
.trim_end_matches('\0')
.to_string();
let create_timestamp = LittleEndian::read_u32(&data[52..]);
let header_length = data[56];
let checksum_alg = if data.len() >= 58 {
if data.len() >= 5 {
data[data.len() - 5]
} else {
0
}
} else {
0
};
Some(FormatDescriptionEvent {
binlog_version,
server_version,
create_timestamp,
header_length,
checksum_alg,
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct RotateEvent {
pub position: u64,
pub next_filename: String,
}
impl RotateEvent {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 8 {
return None;
}
let position = LittleEndian::read_u64(&data[0..]);
let next_filename = std::str::from_utf8(&data[8..])
.unwrap_or("")
.trim_end_matches('\0')
.to_string();
Some(RotateEvent {
position,
next_filename,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binlog_magic_valid() {
assert!(validate_binlog_magic(&BINLOG_MAGIC));
assert!(validate_binlog_magic(&[0xfe, 0x62, 0x69, 0x6e, 0x00]));
}
#[test]
fn test_binlog_magic_invalid() {
assert!(!validate_binlog_magic(&[0x00, 0x00, 0x00, 0x00]));
assert!(!validate_binlog_magic(&[0xfe, 0x62])); assert!(!validate_binlog_magic(&[]));
}
#[test]
fn test_binlog_event_header_parse() {
let mut buf = vec![0u8; 19];
LittleEndian::write_u32(&mut buf[0..], 1700000000);
buf[4] = 15; LittleEndian::write_u32(&mut buf[5..], 1);
LittleEndian::write_u32(&mut buf[9..], 100);
LittleEndian::write_u32(&mut buf[13..], 119);
LittleEndian::write_u16(&mut buf[17..], 0);
let hdr = BinlogEventHeader::parse(&buf).unwrap();
assert_eq!(hdr.timestamp, 1700000000);
assert_eq!(hdr.type_code, 15);
assert_eq!(hdr.server_id, 1);
assert_eq!(hdr.event_length, 100);
assert_eq!(hdr.next_position, 119);
assert_eq!(hdr.flags, 0);
}
#[test]
fn test_binlog_event_header_too_short() {
let buf = vec![0u8; 10];
assert!(BinlogEventHeader::parse(&buf).is_none());
}
#[test]
fn test_format_description_event_parse() {
let mut buf = vec![0u8; 100];
LittleEndian::write_u16(&mut buf[0..], 4);
let ver = b"8.0.35";
buf[2..2 + ver.len()].copy_from_slice(ver);
LittleEndian::write_u32(&mut buf[52..], 1700000000);
buf[56] = 19;
buf[95] = 1;
let fde = FormatDescriptionEvent::parse(&buf).unwrap();
assert_eq!(fde.binlog_version, 4);
assert_eq!(fde.server_version, "8.0.35");
assert_eq!(fde.create_timestamp, 1700000000);
assert_eq!(fde.header_length, 19);
assert_eq!(fde.checksum_alg, 1);
}
#[test]
fn test_format_description_event_too_short() {
let buf = vec![0u8; 30];
assert!(FormatDescriptionEvent::parse(&buf).is_none());
}
}