1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use crate::{
    discref::{Page, PageOps},
    io::{read_u16, write_u16, InfallibleWrite, InfallibleWriteFormat},
    PRes,
};
use crc::crc16::checksum_usb;

pub fn double_buffer_check(buffer_0: &[u8], buffer_1: &[u8]) -> (u8, bool) {
    assert!(buffer_0.len() == buffer_1.len());
    let checksum_pos = buffer_0.len() - 2;
    let valid_0 = checksum_usb(&buffer_0[0..checksum_pos]) == read_u16(&buffer_0[checksum_pos..checksum_pos + 2]);
    let valid_1 = checksum_usb(&buffer_1[0..checksum_pos]) == read_u16(&buffer_1[checksum_pos..checksum_pos + 2]);
    let flush_number_0 = buffer_0[buffer_0.len() - 3];
    let flush_number_1 = buffer_1[buffer_1.len() - 3];
    if valid_0 && valid_1 {
        if (flush_number_0 == 2 && flush_number_1 == 1) || (flush_number_0 == 0 && flush_number_1 == 3) {
            (flush_number_0, true)
        } else {
            (flush_number_1, false)
        }
    } else if valid_0 {
        (flush_number_0, true)
    } else if valid_1 {
        (flush_number_1, false)
    } else {
        panic!("cannot open this persy archive seems to have a corrupted journal");
    }
}

pub fn prepare_buffer_flush(buffer: &mut [u8], mut last_flush: u8) -> (u8, u32) {
    last_flush += 1;
    if last_flush == 4 {
        last_flush = 0;
    }
    let checksum_pos = buffer.len() - 2;
    buffer[buffer.len() - 3] = last_flush;
    let crc = checksum_usb(&buffer[0..checksum_pos]);
    write_u16(&mut buffer[checksum_pos..checksum_pos + 2], crc);
    let offset = if last_flush % 2 == 0 { 0 } else { buffer.len() as u32 };
    (last_flush, offset)
}

/// To be safe this write root should be called only once between to fsync
pub fn write_root_page(root: &mut Page, buffer: &mut [u8], version: u8, last_flush: u8) -> PRes<u8> {
    let (last_flush, offset) = prepare_buffer_flush(buffer, last_flush);
    root.write_u8(version);
    root.seek(offset + 1);
    root.write_all(&buffer);
    Ok(last_flush)
}

#[cfg(test)]
mod tests {
    use super::{double_buffer_check, prepare_buffer_flush};
    use crate::io::write_u64;

    #[test]
    fn first_valid() {
        let mut buffer_0 = [0; 11];
        let buffer_1 = [0; 11];
        write_u64(&mut buffer_0, 10);
        prepare_buffer_flush(&mut buffer_0, 0);
        let (flush, first) = double_buffer_check(&buffer_0, &buffer_1);
        assert!(first);
        assert_eq!(flush, 1);

        let mut buffer_0 = [0; 11];
        let mut buffer_1 = [0; 11];
        write_u64(&mut buffer_0, 10);
        prepare_buffer_flush(&mut buffer_0, 0);
        write_u64(&mut buffer_1, 10);
        prepare_buffer_flush(&mut buffer_1, 1);
        buffer_1[10] = 2;
        let (flush, first) = double_buffer_check(&buffer_0, &buffer_1);
        assert!(first);
        assert_eq!(flush, 1);

        let mut buffer_0 = [0; 11];
        let mut buffer_1 = [0; 11];
        write_u64(&mut buffer_0, 10);
        prepare_buffer_flush(&mut buffer_0, 3);
        write_u64(&mut buffer_1, 10);
        prepare_buffer_flush(&mut buffer_1, 2);
        buffer_1[10] = 2;
        let (flush, first) = double_buffer_check(&buffer_0, &buffer_1);
        assert!(first);
        assert_eq!(flush, 0);
    }

    #[test]
    fn second_valid() {
        let mut buffer_0 = [0; 11];
        let mut buffer_1 = [0; 11];
        write_u64(&mut buffer_0, 10);
        prepare_buffer_flush(&mut buffer_0, 0);
        write_u64(&mut buffer_1, 10);
        prepare_buffer_flush(&mut buffer_1, 1);
        let (flush, first) = double_buffer_check(&buffer_0, &buffer_1);
        assert!(!first);
        assert_eq!(flush, 2);

        let mut buffer_0 = [0; 11];
        let mut buffer_1 = [0; 11];
        write_u64(&mut buffer_0, 10);
        prepare_buffer_flush(&mut buffer_0, 2);
        buffer_0[10] = 4;
        write_u64(&mut buffer_1, 10);
        prepare_buffer_flush(&mut buffer_1, 1);
        let (flush, first) = double_buffer_check(&buffer_0, &buffer_1);
        assert!(!first);
        assert_eq!(flush, 2);
    }
}