sqlite_decoder/
wal.rs

1//! https://sqlite.org/fileformat.html#walformat
2
3use crate::IResult;
4use crate::ParserError;
5use nom::bytes::complete::take;
6use sqlite_types::{
7    Wal, WalFrame, WalFrameHeader, WalHeader, MAGIC_NUMBER_1, MAGIC_NUMBER_2, SUPPORTED_FILE_FORMAT,
8};
9
10type BoxError = Box<dyn std::error::Error>;
11
12pub fn decode(input: &[u8]) -> Result<Wal, BoxError> {
13    match decode_wal(input) {
14        Ok((_, wal)) => Ok(wal),
15        Err(err) => Err(format!("failed to decode: {}", err).into()),
16    }
17}
18
19fn decode_wal(input: &[u8]) -> IResult<&[u8], Wal> {
20    let (input, input_header) = take(32usize)(input)?;
21    let (_, header) = decode_header(&input_header)?;
22
23    let mut frames = vec![];
24    let mut input = input;
25    loop {
26        if input.len() < header.page_size as usize {
27            // EOF or not enough bytes to continue
28            break;
29        }
30
31        let ret = decode_frame(&input, &header)?;
32        input = ret.0;
33        frames.push(ret.1);
34    }
35    Ok((input, Wal { header, frames }))
36}
37
38fn read_u32(input: &[u8]) -> IResult<&[u8], u32> {
39    let (input, value) = take(4usize)(input)?;
40    Ok((input, u32::from_be_bytes(value.try_into().unwrap())))
41}
42
43fn decode_header(input: &[u8]) -> IResult<&[u8], WalHeader> {
44    let (input, magic_number) = read_u32(input)?;
45
46    if magic_number != MAGIC_NUMBER_1 && magic_number != MAGIC_NUMBER_2 {
47        return Err(nom::Err::Failure(ParserError(format!(
48            "magic number not found, got: {:?}",
49            magic_number
50        ))));
51    }
52
53    let (input, file_format) = read_u32(input)?;
54
55    if file_format != SUPPORTED_FILE_FORMAT {
56        return Err(nom::Err::Failure(ParserError(format!(
57            "unsupported file format"
58        ))));
59    }
60
61    let (input, page_size) = read_u32(input)?;
62    let (input, checkpoint_seq) = read_u32(input)?;
63    let (input, salt_1) = read_u32(input)?;
64    let (input, salt_2) = read_u32(input)?;
65    let (input, checksum_1) = read_u32(input)?;
66    let (input, checksum_2) = read_u32(input)?;
67
68    Ok((
69        input,
70        WalHeader {
71            magic_number,
72            file_format,
73            page_size,
74            checkpoint_seq,
75            salt_1,
76            salt_2,
77            checksum_1,
78            checksum_2,
79        },
80    ))
81}
82
83fn decode_frame_header(input: &[u8]) -> IResult<&[u8], WalFrameHeader> {
84    let (input, page_number) = read_u32(input)?;
85    let (input, db_size_after_commit) = read_u32(input)?;
86    let (input, salt_1) = read_u32(input)?;
87    let (input, salt_2) = read_u32(input)?;
88    let (input, checksum_1) = read_u32(input)?;
89    let (input, checksum_2) = read_u32(input)?;
90
91    Ok((
92        input,
93        WalFrameHeader {
94            page_number,
95            db_size_after_commit,
96            salt_1,
97            salt_2,
98            checksum_1,
99            checksum_2,
100        },
101    ))
102}
103
104fn decode_frame<'a, 'b>(input: &'a [u8], wal_header: &'b WalHeader) -> IResult<&'a [u8], WalFrame> {
105    let (input, input_frame_header) = take(24usize)(input)?;
106    let (_, frame_header) = decode_frame_header(&input_frame_header)?;
107
108    if wal_header.salt_1 != frame_header.salt_1 || wal_header.salt_2 != frame_header.salt_2 {
109        return Err(nom::Err::Failure(ParserError(format!("Salt don't match"))));
110    }
111
112    // FIXME: check for `The checksum values in the final 8 bytes of the frame-header exactly match the checksum computed consecutively on the first 24 bytes of the WAL header and the first 8 bytes and the content of all frames up to and including the current frame.`
113
114    let (input, data) = take(wal_header.page_size)(input)?;
115
116    Ok((
117        input,
118        WalFrame {
119            header: frame_header,
120            data: data.to_owned(),
121        },
122    ))
123}