imdl 0.1.16

📦 A 40' shipping container for the internet
Documentation
use crate::common::*;

pub(crate) struct Verifier<'a> {
  metainfo: &'a Metainfo,
  base: &'a Path,
  buffer: Vec<u8>,
  piece_length: usize,
  pieces: PieceList,
  sha1: Sha1,
  piece_bytes_hashed: usize,
  progress_bar: Option<ProgressBar>,
}

impl<'a> Verifier<'a> {
  fn new(
    metainfo: &'a Metainfo,
    base: &'a Path,
    progress_bar: Option<ProgressBar>,
  ) -> Result<Verifier<'a>> {
    let piece_length = metainfo.info.piece_length.as_piece_length()?.into_usize();

    Ok(Verifier {
      buffer: vec![0; piece_length],
      piece_bytes_hashed: 0,
      pieces: PieceList::new(),
      sha1: Sha1::new(),
      base,
      metainfo,
      piece_length,
      progress_bar,
    })
  }

  pub(crate) fn verify(
    metainfo: &'a Metainfo,
    base: &'a Path,
    progress_bar: Option<ProgressBar>,
  ) -> Result<Status> {
    Ok(Self::new(metainfo, base, progress_bar)?.verify_metainfo())
  }

  fn verify_metainfo(mut self) -> Status {
    match &self.metainfo.info.mode {
      Mode::Single { length, md5sum } => {
        self.hash(self.base).ok();
        let error = FileError::verify(self.base, *length, *md5sum).err();

        let pieces = self.finish();
        Status::single(pieces, error)
      }
      Mode::Multiple { files } => {
        let mut status = Vec::new();

        for file in files {
          let path = file.path.absolute(self.base);
          self.hash(&path).ok();

          status.push(FileStatus::status(
            &path,
            file.path.clone(),
            file.length,
            file.md5sum,
          ));
        }

        let pieces = self.finish();

        Status::multiple(pieces, status)
      }
    }
  }

  pub(crate) fn hash(&mut self, path: &Path) -> io::Result<()> {
    let mut file = BufReader::new(File::open(path)?);

    loop {
      let remaining = &mut self.buffer[..self.piece_length - self.piece_bytes_hashed];

      let bytes_read = file.read(remaining)?;

      if bytes_read == 0 {
        break;
      }

      let read = &remaining[..bytes_read];

      self.sha1.update(read);

      self.piece_bytes_hashed += bytes_read;

      if self.piece_bytes_hashed == self.piece_length {
        self.pieces.push(self.sha1.digest().into());
        self.sha1.reset();
        self.piece_bytes_hashed = 0;
      }

      if let Some(progress_bar) = &self.progress_bar {
        progress_bar.inc(bytes_read.into_u64());
      }
    }

    Ok(())
  }

  fn finish(&mut self) -> bool {
    if self.piece_bytes_hashed > 0 {
      self.pieces.push(self.sha1.digest().into());
      self.sha1.reset();
      self.piece_bytes_hashed = 0;
    }

    self.pieces == self.metainfo.info.pieces
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn good() -> Result<()> {
    let mut env = test_env! {
      args: [
        "torrent",
        "create",
        "--input",
        "foo",
        "--announce",
        "https://bar",
      ],
      tree: {
        foo: {
          a: "abc",
          d: "efg",
          h: "ijk",
        },
      },
    };

    env.assert_ok();

    let metainfo = env.load_metainfo("foo.torrent");

    assert!(metainfo.verify(&env.resolve("foo")?, None)?.good());

    Ok(())
  }

  #[test]
  fn piece_mismatch() -> Result<()> {
    let mut env = test_env! {
      args: [
        "torrent",
        "create",
        "--input",
        "foo",
        "--announce",
        "https://bar",
      ],
      tree: {
        foo: {
          a: "abc",
          d: "efg",
          h: "ijk",
        },
      },
    };

    env.assert_ok();

    env.write("foo/a", "xyz");

    let metainfo = env.load_metainfo("foo.torrent");

    let status = metainfo.verify(&env.resolve("foo")?, None)?;

    assert_eq!(status.count_bad(), 0);

    assert!(!status.pieces());

    Ok(())
  }
}