use common::*;
use source::*;
mod page_list;
use self::page_list::PageList;
use std::fmt;
type PageNumber = u32;
#[derive(Debug,Copy,Clone)]
struct Header {
page_size: usize,
maximum_valid_page_number: PageNumber,
}
impl Header {
fn pages_needed_to_store(&self, bytes: usize) -> usize {
(bytes + (self.page_size - 1)) / self.page_size
}
fn validate_page_number(&self, page_number: u32) -> Result<PageNumber> {
if page_number == 0 || page_number > self.maximum_valid_page_number {
Err(Error::PageReferenceOutOfRange(page_number))
} else {
Ok(page_number as PageNumber)
}
}
}
#[doc(hidden)]
#[derive(Debug)]
enum StreamTable<'s> {
HeaderOnly { size_in_bytes: usize, stream_table_location_location: PageList },
TableFound { stream_table_location: PageList },
Available { stream_table_view: Box<SourceView<'s>> }
}
fn view<'s>(source: &mut Source<'s>, page_list: &PageList) -> Result<Box<SourceView<'s>>> {
let view = source.view(page_list.source_slices())?;
assert_eq!(view.as_slice().len(), page_list.len());
Ok(view)
}
mod big {
use super::*;
pub const MAGIC: &'static [u8] = b"Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53\x00\x00\x00";
#[derive(Debug, Copy, Clone, Pread)]
#[repr(C, packed)]
struct RawHeader {
magic: [u8; 32],
page_size: u32,
free_page_map: u32,
pages_used: u32,
directory_size: u32,
_reserved: u32,
}
#[derive(Debug)]
pub struct BigMSF<'s, S> {
header: Header,
source: S,
stream_table: StreamTable<'s>,
}
impl<'s, S: Source<'s>> BigMSF<'s, S> {
pub fn new(source: S, header_view: Box<SourceView>) -> Result<BigMSF<'s, S>> {
let mut buf = ParseBuffer::from(header_view.as_slice());
let header: RawHeader = buf.parse()?;
if header.magic != MAGIC {
return Err(Error::UnrecognizedFileFormat);
}
if header.page_size.count_ones() != 1 || header.page_size < 0x100
|| header.page_size > (128 * 0x10000) {
return Err(Error::InvalidPageSize(header.page_size));
}
let header_object = Header{
page_size: header.page_size as usize,
maximum_valid_page_number: header.pages_used,
};
let size_of_stream_table_in_pages = header_object.pages_needed_to_store(header.directory_size as usize);
let size_of_stream_table_page_list_in_pages = header_object.pages_needed_to_store(size_of_stream_table_in_pages * 4);
let mut stream_table_page_list_page_list = PageList::new(header_object.page_size);
for _ in 0..size_of_stream_table_page_list_in_pages {
let n = buf.parse_u32()?;
stream_table_page_list_page_list.push(header_object.validate_page_number(n)?);
}
stream_table_page_list_page_list.truncate(size_of_stream_table_in_pages * 4);
Ok(BigMSF{
header: header_object,
source: source,
stream_table: StreamTable::HeaderOnly {
size_in_bytes: header.directory_size as usize,
stream_table_location_location: stream_table_page_list_page_list,
},
})
}
fn find_stream_table(&mut self) -> Result<()> {
let mut new_stream_table: Option<StreamTable> = None;
if let StreamTable::HeaderOnly { size_in_bytes, ref stream_table_location_location } = self.stream_table {
let location_location = view(&mut self.source, stream_table_location_location)?;
let mut page_list = PageList::new(self.header.page_size);
let mut buf = ParseBuffer::from(location_location.as_slice());
while buf.len() > 0 {
let n = buf.parse_u32()?;
page_list.push(self.header.validate_page_number(n)?);
}
page_list.truncate(size_in_bytes);
new_stream_table = Some(StreamTable::TableFound {
stream_table_location: page_list,
});
}
if let Some(st) = new_stream_table {
self.stream_table = st;
}
Ok(())
}
fn make_stream_table_available(&mut self) -> Result<()> {
if let StreamTable::HeaderOnly { .. } = self.stream_table {
self.find_stream_table()?;
}
let mut new_stream_table: Option<StreamTable> = None;
if let StreamTable::TableFound { ref stream_table_location } = self.stream_table {
let stream_table_view = view(&mut self.source, stream_table_location)?;
new_stream_table = Some(StreamTable::Available {
stream_table_view: stream_table_view,
});
}
if let Some(st) = new_stream_table {
self.stream_table = st;
}
assert!(match &self.stream_table {
&StreamTable::Available { .. } => true,
_ => false
});
Ok(())
}
fn look_up_stream(&mut self, stream_number: u32) -> Result<PageList> {
self.make_stream_table_available()?;
let header = self.header;
let bytes_in_stream: u32;
let page_list: PageList;
if let StreamTable::Available { ref stream_table_view } = self.stream_table {
let stream_table_slice = stream_table_view.as_slice();
let mut stream_table = ParseBuffer::from(stream_table_slice);
let stream_count = stream_table.parse_u32()?;
if stream_number >= stream_count {
return Err(Error::StreamNotFound(stream_number))
}
let mut page_numbers_to_skip: usize = 0;
for _ in 0..stream_number {
let bytes = stream_table.parse_u32()?;
if bytes == 0xffffffff {
} else {
page_numbers_to_skip += header.pages_needed_to_store(bytes as usize);
}
}
bytes_in_stream = stream_table.parse_u32()?;
if bytes_in_stream == 0xffffffff {
return Err(Error::StreamNotFound(stream_number))
}
let pages_in_stream = header.pages_needed_to_store(bytes_in_stream as usize);
let _ = stream_table.take((stream_count - stream_number - 1) as usize * 4)?;
let _ = stream_table.take((page_numbers_to_skip as usize) * 4)?;
let mut list = PageList::new(header.page_size);
for _ in 0..pages_in_stream {
let page_number = stream_table.parse_u32()?;
list.push(self.header.validate_page_number(page_number)?);
}
list.truncate(bytes_in_stream as usize);
page_list = list;
} else {
unreachable!();
}
Ok(page_list)
}
}
impl<'s, S: Source<'s>> MSF<'s, S> for BigMSF<'s, S> {
fn get(&mut self, stream_number: u32, limit: Option<usize>) -> Result<Stream<'s>> {
let mut page_list = self.look_up_stream(stream_number)?;
if let Some(limit) = limit {
page_list.truncate(limit);
}
let view = view(&mut self.source, &page_list)?;
let stream = Stream {
source_view: view,
};
Ok(stream)
}
}
}
mod small {
pub const MAGIC: &'static [u8] = b"Microsoft C/C++ program database 2.00\r\n\x1a\x4a\x47";
}
#[derive(Debug)]
pub struct Stream<'s> {
source_view: Box<SourceView<'s>>,
}
impl<'s> Stream<'s> {
#[inline]
pub fn parse_buffer(&self) -> ParseBuffer {
let slice = self.source_view.as_slice();
ParseBuffer::from(slice)
}
}
pub trait MSF<'s, S> : fmt::Debug {
fn get(&mut self, stream_number: u32, limit: Option<usize>) -> Result<Stream<'s>>;
}
fn header_matches(actual: &[u8], expected: &[u8]) -> bool {
actual.len() >= expected.len() && &actual[0..expected.len()] == expected
}
pub fn open_msf<'s, S: Source<'s> + 's>(mut source: S) -> Result<Box<MSF<'s, S> + 's>> {
let mut header_location = PageList::new(4096);
header_location.push(0);
let header_view = view(&mut source, &header_location)?;
if header_matches(header_view.as_slice(), big::MAGIC) {
let bigmsf = big::BigMSF::new(source, header_view)?;
return Ok(Box::new(bigmsf))
}
if header_matches(header_view.as_slice(), small::MAGIC) {
return Err(Error::UnimplementedFeature("small MSF file format"));
}
Err(Error::UnrecognizedFileFormat)
}
#[cfg(test)]
mod tests {
mod header {
use msf::Header;
use common::Error;
#[test]
fn test_pages_needed_to_store() {
let h = Header{
page_size: 4096,
maximum_valid_page_number: 15,
};
assert_eq!(h.pages_needed_to_store(0), 0);
assert_eq!(h.pages_needed_to_store(1), 1);
assert_eq!(h.pages_needed_to_store(1024), 1);
assert_eq!(h.pages_needed_to_store(2048), 1);
assert_eq!(h.pages_needed_to_store(4095), 1);
assert_eq!(h.pages_needed_to_store(4096), 1);
assert_eq!(h.pages_needed_to_store(4097), 2);
}
#[test]
fn test_validate_page_number() {
let h = Header{
page_size: 4096,
maximum_valid_page_number: 15,
};
assert!(match h.validate_page_number(0) { Err(Error::PageReferenceOutOfRange(0)) => true, _ => false });
assert!(match h.validate_page_number(1) { Ok(1) => true, _ => false });
assert!(match h.validate_page_number(2) { Ok(2) => true, _ => false });
assert!(match h.validate_page_number(14) { Ok(14) => true, _ => false });
assert!(match h.validate_page_number(15) { Ok(15) => true, _ => false });
assert!(match h.validate_page_number(16) { Err(Error::PageReferenceOutOfRange(16)) => true, _ => false });
assert!(match h.validate_page_number(17) { Err(Error::PageReferenceOutOfRange(17)) => true, _ => false });
}
}
}