Skip to content

Commit

Permalink
read and validate fanout chunk (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Dec 20, 2021
1 parent 99023bb commit 3ca04e3
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 7 deletions.
47 changes: 44 additions & 3 deletions git-chunk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@
#![deny(unsafe_code)]
#![deny(rust_2018_idioms, missing_docs)]

use std::convert::TryInto;
use std::ops::Range;

/// An identifier to describe the kind of chunk, unique within a chunk file.
pub type Kind = u32;

/// A special value denoting the end of the chunk file table of contents.
pub const SENTINEL: Kind = 0;

/// Turn a u64 Range into a usize range safely, to make chunk ranges useful in memory mapped files.
pub fn into_usize_range(Range { start, end }: Range<file::Offset>) -> Option<Range<usize>> {
let start = start.try_into().ok()?;
let end = end.try_into().ok()?;
Some(Range { start, end })
}

///
pub mod file {
///
Expand All @@ -18,7 +28,7 @@ pub mod file {
use std::ops::Range;

///
pub mod not_found {
pub mod offset_by_kind {
use std::fmt::{Display, Formatter};

/// The error returned by [Index::offset_by_kind()][super::Index::offset_by_kind()].
Expand All @@ -42,6 +52,26 @@ pub mod file {
impl std::error::Error for Error {}
}

///
pub mod data_by_kind {
use quick_error::quick_error;
quick_error! {
/// The error returned by [Index::data_by_kind()][super::Index::data_by_kind()].
#[derive(Debug)]
#[allow(missing_docs)]
pub enum Error {
NotFound(err: super::offset_by_kind::Error) {
display("The chunk wasn't found in the file index")
from()
source(err)
}
FileTooLarge {
display("The offsets into the file couldn't be represented by usize")
}
}
}
}

/// An entry of a chunk file index
pub struct Entry {
/// The kind of the chunk file
Expand All @@ -61,11 +91,22 @@ pub mod file {
&self,
kind: crate::Kind,
name: &'static str,
) -> Result<Range<crate::file::Offset>, not_found::Error> {
) -> Result<Range<crate::file::Offset>, offset_by_kind::Error> {
self.chunks
.iter()
.find_map(|c| (c.kind == kind).then(|| c.offset.clone()))
.ok_or_else(|| not_found::Error { kind, name })
.ok_or_else(|| offset_by_kind::Error { kind, name })
}

/// Find a chunk of `kind` and return its data slice based on its offset.
pub fn data_by_kind<'a>(
&self,
data: &'a [u8],
kind: crate::Kind,
name: &'static str,
) -> Result<&'a [u8], data_by_kind::Error> {
let offset = self.offset_by_kind(kind, name)?;
Ok(&data[crate::into_usize_range(offset).ok_or_else(|| data_by_kind::Error::FileTooLarge)?])
}
}
}
Expand Down
28 changes: 24 additions & 4 deletions git-pack/src/multi_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub struct File {
num_chunks: u8,
/// The amount of pack files contained within
num_packs: u32,
fanout: Range<git_chunk::file::Offset>,
fan: [u32; 256],
}

///
Expand All @@ -49,7 +49,20 @@ mod chunk {
pub const ID: git_chunk::Kind = 0x504e414d; /* "PNAM" */
}
pub mod fanout {
use std::convert::TryInto;

pub const ID: git_chunk::Kind = 0x4f494446; /* "OIDF" */

pub fn from_slice(chunk: &[u8]) -> Option<[u32; 256]> {
if chunk.len() != 4 * 256 {
return None;
}
let mut out = [0; 256];
for (c, f) in chunk.chunks(4).zip(out.iter_mut()) {
*f = u32::from_be_bytes(c.try_into().unwrap());
}
out.into()
}
}
pub mod lookup {
pub const ID: git_chunk::Kind = 0x4f49444c; /* "OIDL" */
Expand Down Expand Up @@ -87,7 +100,11 @@ pub mod init {
#[error(transparent)]
ChunkFileDecode(#[from] git_chunk::file::decode::Error),
#[error(transparent)]
MissingChunk(#[from] git_chunk::file::index::not_found::Error),
MissingChunk(#[from] git_chunk::file::index::offset_by_kind::Error),
#[error(transparent)]
FileTooLarge(#[from] git_chunk::file::index::data_by_kind::Error),
#[error("The multi-pack fan doesn't have the correct size of 256 * 4 bytes")]
MultiPackFanSize,
}
}
pub use error::Error;
Expand Down Expand Up @@ -152,7 +169,10 @@ pub mod init {

let chunks = git_chunk::file::Index::from_bytes(&data, HEADER_LEN, num_chunks as u32)?;
let pack_names = chunks.offset_by_kind(chunk::pack_names::ID, "PNAM")?;
let fanout = chunks.offset_by_kind(chunk::fanout::ID, "OIDF")?;

let fan = chunks.data_by_kind(&data, chunk::fanout::ID, "OIDF")?;
let fan = chunk::fanout::from_slice(fan).ok_or_else(|| Error::MultiPackFanSize)?;

let lookup = chunks.offset_by_kind(chunk::lookup::ID, "OIDL")?;
let offsets = chunks.offset_by_kind(chunk::offsets::ID, "OOFF")?;
let large_offsets = chunks.offset_by_kind(chunk::large_offsets::ID, "LOFF").ok();
Expand All @@ -162,7 +182,7 @@ pub mod init {
path: path.to_owned(),
version,
hash_kind,
fanout,
fan,
num_chunks,
num_packs,
})
Expand Down

0 comments on commit 3ca04e3

Please sign in to comment.