use scroll::{Pread, Pwrite, SizeWith};
use crate::error::{Error, Result};
use crate::strtab;
use alloc::collections::btree_map::BTreeMap;
use alloc::vec::Vec;
use core::usize;
pub const SIZEOF_MAGIC: usize = 8;
pub const MAGIC: &[u8; SIZEOF_MAGIC] = b"!<arch>\x0A";
const SIZEOF_FILE_IDENTIFER: usize = 16;
const SIZEOF_FILE_SIZE: usize = 10;
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Pread, Pwrite, SizeWith)]
pub struct MemberHeader {
pub identifier: [u8; 16],
pub timestamp: [u8; 12],
pub owner_id: [u8; 6],
pub group_id: [u8; 6],
pub mode: [u8; 8],
pub file_size: [u8; 10],
pub terminator: [u8; 2],
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Header<'a> {
pub name: &'a str,
pub size: usize,
}
pub const SIZEOF_HEADER: usize = SIZEOF_FILE_IDENTIFER + 12 + 6 + 6 + 8 + SIZEOF_FILE_SIZE + 2;
impl MemberHeader {
pub fn name(&self) -> Result<&str> {
Ok(self
.identifier
.pread_with::<&str>(0, ::scroll::ctx::StrCtx::Length(SIZEOF_FILE_IDENTIFER))?)
}
pub fn size(&self) -> Result<usize> {
match usize::from_str_radix(
self.file_size
.pread_with::<&str>(0, ::scroll::ctx::StrCtx::Length(self.file_size.len()))?
.trim_end(),
10,
) {
Ok(file_size) => Ok(file_size),
Err(err) => Err(Error::Malformed(format!(
"{:?} Bad file_size in header: {:?}",
err, self
))),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Member<'a> {
pub header: Header<'a>,
pub header_offset: u64,
pub offset: u64,
bsd_name: Option<&'a str>,
sysv_name: Option<&'a str>,
}
impl<'a> Member<'a> {
pub fn parse(buffer: &'a [u8], offset: &mut usize) -> Result<Member<'a>> {
let header_offset = *offset;
let name = buffer.pread_with::<&str>(
*offset,
::scroll::ctx::StrCtx::Length(SIZEOF_FILE_IDENTIFER),
)?;
let archive_header = buffer.gread::<MemberHeader>(offset)?;
let mut header = Header {
name,
size: archive_header.size()?,
};
if *offset & 1 == 1 {
*offset += 1;
}
let bsd_name = if let Some(len) = Self::bsd_filename_length(name) {
let name = buffer.pread_with::<&str>(
header_offset + SIZEOF_HEADER,
::scroll::ctx::StrCtx::Length(len),
)?;
*offset = header_offset + SIZEOF_HEADER + len;
header.size -= len;
Some(name.trim_end_matches('\0'))
} else {
None
};
Ok(Member {
header,
header_offset: header_offset as u64,
offset: *offset as u64,
bsd_name,
sysv_name: None,
})
}
pub fn size(&self) -> usize {
self.header.size
}
fn bsd_filename_length(name: &str) -> Option<usize> {
use core::str::FromStr;
if name.len() > 3 && &name[0..3] == "#1/" {
let trimmed_name = &name[3..].trim_end_matches(' ');
if let Ok(len) = usize::from_str(trimmed_name) {
Some(len)
} else {
None
}
} else {
None
}
}
pub fn extended_name(&self) -> &'a str {
if let Some(bsd_name) = self.bsd_name {
bsd_name
} else if let Some(ref sysv_name) = self.sysv_name {
sysv_name
} else {
self.header.name.trim_end_matches(' ').trim_end_matches('/')
}
}
pub fn raw_name(&self) -> &'a str {
self.header.name
}
}
#[derive(Debug, Default)]
pub struct Index<'a> {
pub size: usize,
pub symbol_indexes: Vec<u32>,
pub strtab: Vec<&'a str>,
}
const INDEX_NAME: &str = "/ ";
const NAME_INDEX_NAME: &str = "// ";
const BSD_SYMDEF_NAME: &str = "__.SYMDEF";
const BSD_SYMDEF_SORTED_NAME: &str = "__.SYMDEF SORTED";
impl<'a> Index<'a> {
pub fn parse_sysv_index(buffer: &'a [u8]) -> Result<Self> {
let offset = &mut 0;
let sizeof_table = buffer.gread_with::<u32>(offset, scroll::BE)? as usize;
let mut indexes = Vec::with_capacity(sizeof_table);
for _ in 0..sizeof_table {
indexes.push(buffer.gread_with::<u32>(offset, scroll::BE)?);
}
let sizeof_strtab = buffer.len() - ((sizeof_table * 4) + 4);
let strtab = strtab::Strtab::parse(buffer, *offset, sizeof_strtab, 0x0)?;
Ok(Index {
size: sizeof_table,
symbol_indexes: indexes,
strtab: strtab.to_vec()?,
})
}
pub fn parse_bsd_symdef(buffer: &'a [u8]) -> Result<Self> {
let entries_bytes = buffer.pread_with::<u32>(0, scroll::LE)? as usize;
let entries = entries_bytes / 8;
let strtab_bytes = buffer.pread_with::<u32>(entries_bytes + 4, scroll::LE)? as usize;
let strtab = strtab::Strtab::parse(buffer, entries_bytes + 8, strtab_bytes, 0x0)?;
let mut indexes = Vec::with_capacity(entries);
let mut strings = Vec::with_capacity(entries);
for i in 0..entries {
let string_offset: u32 = buffer.pread_with(i * 8 + 4, scroll::LE)?;
let archive_member: u32 = buffer.pread_with(i * 8 + 8, scroll::LE)?;
let string = match strtab.get(string_offset as usize) {
Some(result) => result,
None => Err(Error::Malformed(format!(
"{} entry {} has string offset {}, which is out of bounds",
BSD_SYMDEF_NAME, i, string_offset
))),
}?;
indexes.push(archive_member);
strings.push(string);
}
Ok(Index {
size: entries,
symbol_indexes: indexes,
strtab: strings,
})
}
pub fn parse_windows_linker_member(buffer: &'a [u8]) -> Result<Self> {
let offset = &mut 0;
let members = buffer.gread_with::<u32>(offset, scroll::LE)? as usize;
let mut member_offsets = Vec::with_capacity(members);
for _ in 0..members {
member_offsets.push(buffer.gread_with::<u32>(offset, scroll::LE)?);
}
let symbols = buffer.gread_with::<u32>(offset, scroll::LE)? as usize;
let mut symbol_offsets = Vec::with_capacity(symbols);
for _ in 0..symbols {
symbol_offsets
.push(member_offsets[buffer.gread_with::<u16>(offset, scroll::LE)? as usize - 1]);
}
let strtab = strtab::Strtab::parse(buffer, *offset, buffer.len() - *offset, 0x0)?;
Ok(Index {
size: symbols,
symbol_indexes: symbol_offsets,
strtab: strtab.to_vec()?,
})
}
}
#[derive(Debug, Default)]
struct NameIndex<'a> {
strtab: strtab::Strtab<'a>,
}
impl<'a> NameIndex<'a> {
pub fn parse(buffer: &'a [u8], offset: &mut usize, size: usize) -> Result<NameIndex<'a>> {
let hacked_size = size + 1;
let strtab = strtab::Strtab::parse(buffer, *offset - 1, hacked_size, b'\n')?;
*offset += hacked_size - 2;
Ok(NameIndex { strtab })
}
pub fn get(&self, name: &str) -> Result<&'a str> {
let idx = name.trim_start_matches('/').trim_end();
match usize::from_str_radix(idx, 10) {
Ok(idx) => {
let name = match self.strtab.get(idx + 1) {
Some(result) => result,
None => Err(Error::Malformed(format!(
"Name {} is out of range in archive NameIndex",
name
))),
}?;
if name != "" {
Ok(name.trim_end_matches('/'))
} else {
Err(Error::Malformed(format!(
"Could not find {:?} in index",
name
)))
}
}
Err(_) => Err(Error::Malformed(format!(
"Bad name index {:?} in index",
name
))),
}
}
}
#[derive(Debug, PartialEq)]
pub enum IndexType {
None,
SysV,
Windows,
BSD,
}
#[derive(Debug)]
pub struct Archive<'a> {
index: Index<'a>,
sysv_name_index: NameIndex<'a>,
member_array: Vec<Member<'a>>,
members: BTreeMap<&'a str, usize>,
symbol_index: BTreeMap<&'a str, usize>,
index_type: IndexType,
}
impl<'a> Archive<'a> {
pub fn parse(buffer: &'a [u8]) -> Result<Archive<'a>> {
let mut magic = [0u8; SIZEOF_MAGIC];
let offset = &mut 0usize;
buffer.gread_inout(offset, &mut magic)?;
if &magic != MAGIC {
return Err(Error::BadMagic(magic.pread(0)?));
}
let mut member_array = Vec::new();
let mut index = Index::default();
let mut index_type = IndexType::None;
let mut sysv_name_index = NameIndex::default();
while *offset + 1 < buffer.len() {
if *offset & 1 == 1 {
*offset += 1;
}
let member = Member::parse(buffer, offset)?;
*offset = member.offset as usize + member.size() as usize;
let name = member.raw_name();
if name == INDEX_NAME {
let data: &[u8] = buffer.pread_with(member.offset as usize, member.size())?;
index = match index_type {
IndexType::None => {
index_type = IndexType::SysV;
Index::parse_sysv_index(data)?
}
IndexType::SysV => {
index_type = IndexType::Windows;
Index::parse_windows_linker_member(data)?
}
IndexType::BSD => {
return Err(Error::Malformed("SysV index occurs after BSD index".into()))
}
IndexType::Windows => {
return Err(Error::Malformed(
"More than two Windows Linker members".into(),
))
}
}
} else if member.bsd_name == Some(BSD_SYMDEF_NAME)
|| member.bsd_name == Some(BSD_SYMDEF_SORTED_NAME)
{
if index_type != IndexType::None {
return Err(Error::Malformed("BSD index occurs after SysV index".into()));
}
index_type = IndexType::BSD;
let data: &[u8] = buffer.pread_with(member.offset as usize, member.size())?;
index = Index::parse_bsd_symdef(data)?;
} else if name == NAME_INDEX_NAME {
let mut name_index_offset: usize = member.offset as usize;
sysv_name_index = NameIndex::parse(buffer, &mut name_index_offset, member.size())?;
} else {
member_array.push(member);
}
}
let mut members = BTreeMap::new();
let mut member_index_by_offset: BTreeMap<u32, usize> = BTreeMap::new();
for (i, member) in member_array.iter_mut().enumerate() {
if let Ok(sysv_name) = sysv_name_index.get(member.raw_name()) {
member.sysv_name = Some(sysv_name);
}
let key = member.extended_name();
members.insert(key, i);
member_index_by_offset.insert(member.header_offset as u32, i);
}
let mut symbol_index: BTreeMap<&str, usize> = BTreeMap::new();
for (member_offset, name) in index.symbol_indexes.iter().zip(index.strtab.iter()) {
let member_index = *member_index_by_offset.get(member_offset).ok_or_else(|| {
Error::Malformed(format!(
"Could not get member {:?} at offset: {}",
name, member_offset
))
})?;
symbol_index.insert(&name, member_index);
}
Ok(Archive {
index,
member_array,
sysv_name_index,
members,
symbol_index,
index_type,
})
}
pub fn get(&self, member: &str) -> Option<&Member> {
if let Some(idx) = self.members.get(member) {
Some(&self.member_array[*idx])
} else {
None
}
}
pub fn get_at(&self, index: usize) -> Option<&Member> {
self.member_array.get(index)
}
pub fn len(&self) -> usize {
self.member_array.len()
}
pub fn extract<'b>(&self, member: &str, buffer: &'b [u8]) -> Result<&'b [u8]> {
if let Some(member) = self.get(member) {
let bytes = buffer.pread_with(member.offset as usize, member.size())?;
Ok(bytes)
} else {
Err(Error::Malformed(format!(
"Cannot extract member {:?}",
member
)))
}
}
pub fn summarize(&self) -> Vec<(&str, &Member, Vec<&'a str>)> {
let mut result = self
.member_array
.iter()
.map(|ref member| (member.extended_name(), *member, Vec::new()))
.collect::<Vec<_>>();
for (symbol_name, member_index) in self.symbol_index.iter() {
result[*member_index].2.push(*symbol_name);
}
result
}
pub fn members(&self) -> Vec<&'a str> {
self.members.keys().cloned().collect()
}
pub fn member_of_symbol(&self, symbol: &str) -> Option<&'a str> {
if let Some(idx) = self.symbol_index.get(symbol) {
Some(self.member_array[*idx].extended_name())
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_member_bsd_filename_length() {
assert_eq!(Member::bsd_filename_length(""), None);
assert_eq!(Member::bsd_filename_length("123"), None);
assert_eq!(Member::bsd_filename_length("#1"), None);
assert_eq!(Member::bsd_filename_length("#1/"), None);
assert_eq!(Member::bsd_filename_length("#2/1"), None);
assert_eq!(Member::bsd_filename_length(INDEX_NAME), None);
assert_eq!(Member::bsd_filename_length(NAME_INDEX_NAME), None);
assert_eq!(Member::bsd_filename_length("#1/1"), Some(1));
assert_eq!(Member::bsd_filename_length("#1/22"), Some(22));
assert_eq!(Member::bsd_filename_length("#1/333"), Some(333));
assert_eq!(Member::bsd_filename_length("#1/1 "), Some(1));
assert_eq!(Member::bsd_filename_length("#1/22 "), Some(22));
assert_eq!(Member::bsd_filename_length("#1/333 "), Some(333));
assert_eq!(Member::bsd_filename_length("#1/1A"), None);
assert_eq!(Member::bsd_filename_length("#1/1 A"), None);
}
}