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
/*
This file is part of Yama.

Yama is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Yama is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Yama.  If not, see <https://www.gnu.org/licenses/>.
*/


use std::hash::Hasher;

use thiserror::Error;

use crate::definitions::XXH64_SEED;
use crate::pile::{Keyspace, RawPile};
use crate::utils::bytes_to_hexstring;

pub struct RawPileIntegrityChecker<RP: RawPile> {
    underlying: RP,
}

impl<RP: RawPile> RawPileIntegrityChecker<RP> {
    pub fn new(underlying: RP) -> Self {
        RawPileIntegrityChecker { underlying }
    }
}

#[derive(Error, Debug)]
#[error("Integrity error for chunk {chunk_id}; expected XXHash {expected_hash} but computed {computed_hash}!")]
pub struct IntegrityError {
    pub chunk_id: String,
    pub expected_hash: String,
    pub computed_hash: String,
}

impl<RP: RawPile> RawPile for RawPileIntegrityChecker<RP> {
    fn exists(&self, kind: Keyspace, key: &[u8]) -> anyhow::Result<bool> {
        self.underlying.exists(kind, key)
    }

    fn read(&self, kind: Keyspace, key: &[u8]) -> anyhow::Result<Option<Vec<u8>>> {
        match self.underlying.read(kind, key)? {
            None => Ok(None),
            Some(mut data_then_hash) => {
                let len = data_then_hash.len();
                let data_only = &data_then_hash[..len - 8];
                let xxhash = &data_then_hash[len - 8..];

                let mut hasher = twox_hash::XxHash64::with_seed(XXH64_SEED);
                hasher.write(&data_only);
                let computed_hash = hasher.finish().to_be_bytes();

                if computed_hash != xxhash {
                    Err(IntegrityError {
                        chunk_id: bytes_to_hexstring(key),
                        expected_hash: bytes_to_hexstring(&xxhash),
                        computed_hash: bytes_to_hexstring(&computed_hash),
                    })?;
                }

                // remove hash from end
                data_then_hash.drain(len - 8..);
                Ok(Some(data_then_hash))
            }
        }
    }

    fn write(&self, kind: Keyspace, key: &[u8], value: &[u8]) -> anyhow::Result<()> {
        // start with the data
        let mut buf = Vec::new();
        buf.extend_from_slice(&value[..]);

        // then append the hash
        let mut hasher = twox_hash::XxHash64::with_seed(XXH64_SEED);
        hasher.write(&value);
        let computed_hash = hasher.finish().to_be_bytes();
        buf.extend_from_slice(&computed_hash);

        self.underlying.write(kind, key, &buf)
    }

    fn delete(&self, kind: Keyspace, key: &[u8]) -> anyhow::Result<()> {
        self.underlying.delete(kind, key)
    }

    fn list_keys(
        &self,
        kind: Keyspace,
    ) -> anyhow::Result<Box<dyn Iterator<Item = anyhow::Result<Vec<u8>>>>> {
        self.underlying.list_keys(kind)
    }

    fn flush(&self) -> anyhow::Result<()> {
        self.underlying.flush()
    }

    fn check_lowlevel(&self) -> anyhow::Result<bool> {
        // TODO integrity check ...?
        self.underlying.check_lowlevel()
    }
}