#![no_std]
#![warn(box_pointers, missing_copy_implementations, missing_debug_implementations)]
#![warn(unused_extern_crates, unused_import_braces, unused_qualifications, unused_results)]
#![warn(variant_size_differences)]

// TODO move to a module
macro_rules! check {
    ($e:expr) => {
        if !$e {
            return Err("");
        }
    };
    ($e:expr, $msg: expr) => {
        if !$e {
            return Err($msg);
        }
    };
}

#[cfg(feature = "compression")]
extern crate std;
#[cfg(feature = "compression")]
extern crate flate2;

extern crate zero;

pub mod header;
pub mod sections;
pub mod program;
pub mod symbol_table;
pub mod dynamic;
pub mod hash;

use header::Header;
use sections::{SectionHeader, SectionIter};
use program::{ProgramHeader, ProgramIter};
use zero::{read, read_str};

pub type P32 = u32;
pub type P64 = u64;

#[derive(Debug)]
pub struct ElfFile<'a> {
    pub input: &'a [u8],
    pub header: Header<'a>,
}

impl<'a> ElfFile<'a> {
    pub fn new(input: &'a [u8]) -> Result<ElfFile<'a>, &'static str> {
        let header = try!(header::parse_header(input));
        Ok(ElfFile {
            input: input,
            header: header,
        })
    }

    pub fn section_header(&self, index: u16) -> Result<SectionHeader<'a>, &'static str> {
        sections::parse_section_header(self.input, self.header, index)
    }

    pub fn section_iter<'b>(&'b self) -> SectionIter<'b, 'a> {
        SectionIter {
            file: self,
            next_index: 0,
        }
    }

    pub fn program_header(&self, index: u16) -> Result<ProgramHeader<'a>, &'static str> {
        program::parse_program_header(self.input, self.header, index)
    }

    pub fn program_iter<'b>(&'b self) -> ProgramIter<'b, 'a> {
        ProgramIter {
            file: self,
            next_index: 0,
        }
    }

    pub fn get_shstr(&self, index: u32) -> Result<&'a str, &'static str> {
        self.get_shstr_table().map(|shstr_table| read_str(&shstr_table[(index as usize)..]))
    }

    pub fn get_string(&self, index: u32) -> Result<&'a str, &'static str> {
        let header = try!(self.find_section_by_name(".strtab").ok_or("no .strtab section"));
        if try!(header.get_type()) != sections::ShType::StrTab {
            return Err("expected .strtab to be StrTab");
        }
        Ok(read_str(&header.raw_data(self)[(index as usize)..]))
    }

    pub fn get_dyn_string(&self, index: u32) -> Result<&'a str, &'static str> {
        let header = try!(self.find_section_by_name(".dynstr").ok_or("no .dynstr section"));
        Ok(read_str(&header.raw_data(self)[(index as usize)..]))
    }

    // This is really, stupidly slow. Not sure how to fix that, perhaps keeping
    // a HashTable mapping names to section header indices?
    pub fn find_section_by_name(&self, name: &str) -> Option<SectionHeader<'a>> {
        for sect in self.section_iter() {
            if let Ok(sect_name) = sect.get_name(self) {
                if sect_name == name {
                    return Some(sect);
                }
            }
        }

        None
    }

    fn get_shstr_table(&self) -> Result<&'a [u8], &'static str> {
        // TODO cache this?
        let header = self.section_header(self.header.pt2.sh_str_index());
        header.map(|h| &self.input[(h.offset() as usize)..])
    }
}

/// A trait for things that are common ELF conventions but not part of the ELF
/// specification.
pub trait Extensions<'a> {
    /// Parse and return the value of the .note.gnu.build-id section, if it
    /// exists and is well-formed.
    fn get_gnu_buildid(&self) -> Option<&'a [u8]>;

    /// Parse and return the value of the .gnu_debuglink section, if it
    /// exists and is well-formed.
    fn get_gnu_debuglink(&self) -> Option<(&'a str, u32)>;
}

impl<'a> Extensions<'a> for ElfFile<'a> {
    fn get_gnu_buildid(&self) -> Option<&'a [u8]> {
        self.find_section_by_name(".note.gnu.build-id")
            .and_then(|header| header.get_data(self).ok())
            .and_then(|data| match data {
                // Handle Note32 if it's ever implemented!
                sections::SectionData::Note64(header, data) => Some((header, data)),
                _ => None,
            })
            .and_then(|(header, data)| {
                // Check for NT_GNU_BUILD_ID
                if header.type_() != 0x3 {
                    return None;
                }

                if header.name(data) != "GNU" {
                    return None;
                }

                Some(header.desc(data))
            })
    }

    fn get_gnu_debuglink(&self) -> Option<(&'a str, u32)> {
        self.find_section_by_name(".gnu_debuglink")
            .map(|header| header.raw_data(self))
            .and_then(|data| {
                let file = read_str(data);
                // Round up to the nearest multiple of 4.
                let checksum_pos = ((file.len() + 4) / 4) * 4;
                let checksum: u32 = *read(&data[checksum_pos..]);
                Some((file, checksum))
            })
    }
}

#[cfg(test)]
#[macro_use]
extern crate std;

#[cfg(test)]
mod test {
    use std::prelude::v1::*;

    use std::mem;

    use super::*;
    use header::{HeaderPt1, HeaderPt2_};

    fn mk_elf_header(class: u8) -> Vec<u8> {
        let header_size = mem::size_of::<HeaderPt1>() +
                          match class {
            1 => mem::size_of::<HeaderPt2_<P32>>(),
            2 => mem::size_of::<HeaderPt2_<P64>>(),
            _ => 0,
        };
        let mut header = vec![0x7f, 'E' as u8, 'L' as u8, 'F' as u8];
        let data = 1u8;
        let version = 1u8;
        header.extend_from_slice(&[class, data, version]);
        header.resize(header_size, 0);
        header
    }

    #[test]
    fn interpret_class() {
        assert!(ElfFile::new(&mk_elf_header(0)).is_err());
        assert!(ElfFile::new(&mk_elf_header(1)).is_ok());
        assert!(ElfFile::new(&mk_elf_header(2)).is_ok());
        assert!(ElfFile::new(&mk_elf_header(42u8)).is_err());
    }
}
