1use std::io;
13
14pub const WAL_FILE_MAGIC: &[u8; 4] = b"RDBW";
15pub const WAL_FILE_VERSION: u8 = 3;
16pub const WAL_FILE_VERSION_V2: u8 = 2;
17pub const WAL_FILE_HEADER_BYTES: usize = 8;
18pub const MAIN_WAL_SEGMENT_BYTES: u64 = 16 * 1024 * 1024;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct WalFileHeader {
28 pub version: u8,
29}
30
31pub fn encode_wal_file_header() -> [u8; WAL_FILE_HEADER_BYTES] {
32 let mut header = [0u8; WAL_FILE_HEADER_BYTES];
33 header[0..4].copy_from_slice(WAL_FILE_MAGIC);
34 header[4] = WAL_FILE_VERSION;
35 header
36}
37
38pub fn decode_wal_file_header(header: &[u8; WAL_FILE_HEADER_BYTES]) -> io::Result<WalFileHeader> {
39 if &header[0..4] != WAL_FILE_MAGIC {
40 return Err(io::Error::new(
41 io::ErrorKind::InvalidData,
42 "Invalid WAL magic bytes",
43 ));
44 }
45
46 let version = header[4];
47 if version != WAL_FILE_VERSION && version != WAL_FILE_VERSION_V2 {
48 return Err(io::Error::new(
49 io::ErrorKind::InvalidData,
50 format!("Unsupported WAL version: {version}"),
51 ));
52 }
53
54 Ok(WalFileHeader { version })
55}
56
57pub fn next_main_wal_segment_boundary(pos: u64) -> u64 {
63 (pos / MAIN_WAL_SEGMENT_BYTES + 1) * MAIN_WAL_SEGMENT_BYTES
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn wal_file_header_encodes_current_version() {
72 let header = encode_wal_file_header();
73 assert_eq!(&header[0..4], WAL_FILE_MAGIC);
74 assert_eq!(header[4], WAL_FILE_VERSION);
75 assert_eq!(
76 decode_wal_file_header(&header).unwrap().version,
77 WAL_FILE_VERSION
78 );
79 }
80
81 #[test]
82 fn wal_file_header_accepts_legacy_v2() {
83 let mut header = encode_wal_file_header();
84 header[4] = WAL_FILE_VERSION_V2;
85 assert_eq!(
86 decode_wal_file_header(&header).unwrap().version,
87 WAL_FILE_VERSION_V2
88 );
89 }
90
91 #[test]
92 fn wal_file_header_rejects_bad_magic_and_version() {
93 let mut bad_magic = encode_wal_file_header();
94 bad_magic[0] = b'X';
95 assert_eq!(
96 decode_wal_file_header(&bad_magic).unwrap_err().to_string(),
97 "Invalid WAL magic bytes"
98 );
99
100 let mut bad_version = encode_wal_file_header();
101 bad_version[4] = 99;
102 assert_eq!(
103 decode_wal_file_header(&bad_version)
104 .unwrap_err()
105 .to_string(),
106 "Unsupported WAL version: 99"
107 );
108 }
109
110 #[test]
111 fn main_wal_segment_boundary_rounds_strictly_above_position() {
112 assert_eq!(next_main_wal_segment_boundary(0), MAIN_WAL_SEGMENT_BYTES);
113 assert_eq!(next_main_wal_segment_boundary(8), MAIN_WAL_SEGMENT_BYTES);
114 assert_eq!(
115 next_main_wal_segment_boundary(MAIN_WAL_SEGMENT_BYTES - 1),
116 MAIN_WAL_SEGMENT_BYTES
117 );
118 assert_eq!(
119 next_main_wal_segment_boundary(MAIN_WAL_SEGMENT_BYTES),
120 2 * MAIN_WAL_SEGMENT_BYTES
121 );
122 assert_eq!(
123 next_main_wal_segment_boundary(MAIN_WAL_SEGMENT_BYTES + 1),
124 2 * MAIN_WAL_SEGMENT_BYTES
125 );
126 }
127}