<commit_before>use std::io;
use std::path::{PathBuf, Path};
use std::fs::File;
use std::collections::HashMap;
use object::{Object, File as OFile};
use memmap::MmapOptions;
use gimli::*;
use rustc_demangle::demangle;
use cargo::core::Workspace;

use config::Config;
use source_analysis::*;
use traces::*;

/// Describes a function as `low_pc`, `high_pc` and bool representing `is_test`.
type FuncDesc = (u64, u64, FunctionType);

#[derive(Clone, Copy, PartialEq)]
enum FunctionType {
    Generated,
    Test,
    Standard
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum LineType {
    /// Generated test main. Shouldn't be traced.
    TestMain,
    /// Entry of function known to be a test
    TestEntry(u64),
    /// Entry of function. May or may not be test
    FunctionEntry(u64),
    /// Standard statement
    Statement,
    /// Condition
    Condition,
    /// Unknown type
    Unknown,
    /// Unused meta-code
    UnusedGeneric,
}

#[derive(Clone, PartialEq, Eq, Hash)]
struct SourceLocation {
    pub path: PathBuf,
    pub line: u64,
}

#[derive(Debug, Clone)]
pub struct TracerData {
    /// Currently used to find generated __test::main and remove from coverage,
    /// may have uses in future for finding conditions etc
    pub trace_type: LineType,
    /// Start address of the line
    pub address: Option<u64>,
    /// Length of the instruction
    pub length: u64,
}

fn generate_func_desc<R, Offset>(die: &DebuggingInformationEntry<R, Offset>,
                                 debug_str: &DebugStr<R>) -> Result<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut func_type = FunctionType::Standard;
    let low = die.attr_value(DW_AT_low_pc)?;
    let high = die.attr_value(DW_AT_high_pc)?;
    let linkage = die.attr_value(DW_AT_linkage_name)?;

    // Low is a program counter address so stored in an Addr
    let low = match low {
        Some(AttributeValue::Addr(x)) => x,
        _ => 0u64,
    };
    // High is an offset from the base pc, therefore is u64 data.
    let high = match high {
        Some(AttributeValue::Udata(x)) => x,
        _ => 0u64,
    };
    if let Some(AttributeValue::DebugStrRef(offset)) = linkage {
        let name = debug_str.get_str(offset)
            .and_then(|r| r.to_string().map(|s| s.to_string()))
            .unwrap_or_else(|_| "".into());
        let name = demangle(name.as_ref()).to_string();
        // Simplest test is whether it's in tests namespace.
        // Rust guidelines recommend all tests are in a tests module.
        func_type = if name.contains("tests::") {
            FunctionType::Test
        } else if name.contains("__test::main") {
            FunctionType::Generated
        } else {
            FunctionType::Standard
        };
    }
    Ok((low, high, func_type))
}

/// Finds all function entry points and returns a vector
/// This will identify definite tests, but may be prone to false negatives.
fn get_entry_points<R, Offset>(debug_info: &CompilationUnitHeader<R, Offset>,
                               debug_abbrev: &Abbreviations,
                               debug_str: &DebugStr<R>) -> Vec<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut result:Vec<FuncDesc> = Vec::new();
    let mut cursor = debug_info.entries(debug_abbrev);
    // skip compilation unit root.
    let _ = cursor.next_entry();
    while let Ok(Some((_, node))) = cursor.next_dfs() {
        // Function DIE
        if node.tag() == DW_TAG_subprogram {

            if let Ok(fd) = generate_func_desc(node, debug_str) {
                result.push(fd);
            }
        }
    }
    result
}

fn get_addresses_from_program<R, Offset>(prog: IncompleteLineNumberProgram<R>,
                                         entries: &[(u64, LineType)],
                                         project: &Path,
                                         result: &mut HashMap<SourceLocation, Vec<TracerData>>) -> Result<()>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let ( cprog, seq) = prog.sequences()?;
    for s in seq {
        let mut temp_map: HashMap<SourceLocation, TracerData> = HashMap::new();
        let mut sm = cprog.resume_from(&s);
         while let Ok(Some((header, &ln_row))) = sm.next_row() {
             // If this row isn't useful move on
            if !ln_row.is_stmt() || ln_row.line().is_none() {
                continue;
            }
            if let Some(file) = ln_row.file(header) {
                let mut path = PathBuf::new();

                if let Some(dir) = file.directory(header) {
                    if let Ok(temp) = dir.to_string() {
                        path.push(temp.as_ref());
                    }
                }
                if let Ok(p) = path.canonicalize() {
                    path = p;
                }
                // Fix relative paths and determine if in target directory
                // Source in target directory shouldn't be covered as it's either
                // autogenerated or resulting from the projects Cargo.lock
                let is_target = if path.is_relative() {
                    path.starts_with("target")
                } else {
                    path.starts_with(project.join("target"))
                };
                // Source is part of project so we cover it.
                if !is_target && path.starts_with(project) {
                    if let Some(file) = ln_row.file(header) {
                        let line = ln_row.line().unwrap();
                        let file = file.path_name();
                        if let Ok(file) = file.to_string() {
                            path.push(file.as_ref());
                            if !path.is_file() {
                                // Not really a source file!
                                continue;
                            }
                            let address = ln_row.address();
                            let desc = entries.iter()
                                              .filter(|&&(addr, _)| addr == address )
                                              .map(|&(_, t)| t)
                                              .nth(0)
                                              .unwrap_or(LineType::Unknown);
                            let loc = SourceLocation {
                                path,
                                line,
                            };
                            if desc != LineType::TestMain && !temp_map.contains_key(&loc) {
                                temp_map.insert(loc, TracerData {
                                    address: Some(address),
                                    trace_type: desc,
                                    length: 1,
                                });
                            }
                        }
                    }
                }
            }
        }
        for (k, v) in &temp_map {
            if result.contains_key(k) {
                let mut x = result.get_mut(k).unwrap();
                x.push(v.clone());
            } else {
                result.insert(k.clone(), vec![v.clone()]);
            }
        }
    }
    Ok(())
}

fn get_line_addresses(endian: RunTimeEndian,
                      project: &Path,
                      obj: &OFile,
                      analysis: &HashMap<PathBuf, LineAnalysis>,
                      config: &Config) -> Result<TraceMap>  {

    let mut result = TraceMap::new();
    let debug_info = obj.section_data_by_name(".debug_info").unwrap_or_default();
    let debug_info = DebugInfo::new(&debug_info, endian);
    let debug_abbrev = obj.section_data_by_name(".debug_abbrev").unwrap_or_default();
    let debug_abbrev = DebugAbbrev::new(&debug_abbrev, endian);
    let debug_strings = obj.section_data_by_name(".debug_str").unwrap_or_default();
    let debug_strings = DebugStr::new(&debug_strings, endian);
    let debug_line = obj.section_data_by_name(".debug_line").unwrap_or_default();
    let debug_line = DebugLine::new(&debug_line, endian);

    let mut iter = debug_info.units();
    while let Ok(Some(cu)) = iter.next() {
        let addr_size = cu.address_size();
        let abbr = match cu.abbreviations(&debug_abbrev) {
            Ok(a) => a,
            _ => continue,
        };
        let entries = get_entry_points(&cu, &abbr, &debug_strings)
            .iter()
            .map(|&(a, b, c)| {
                match c {
                    FunctionType::Test => (a, LineType::TestEntry(b)),
                    FunctionType::Standard => (a, LineType::FunctionEntry(b)),
                    FunctionType::Generated => (a, LineType::TestMain),
                }
            }).collect::<Vec<_>>();

        if let Ok(Some((_, root))) = cu.entries(&abbr).next_dfs() {
            let offset = match root.attr_value(DW_AT_stmt_list) {
                Ok(Some(AttributeValue::DebugLineRef(o))) => o,
                _ => continue,
            };
            let prog = debug_line.program(offset, addr_size, None, None)?;
            let mut temp_map : HashMap<SourceLocation, Vec<TracerData>> = HashMap::new();
            if let Err(e) = get_addresses_from_program(prog, &entries, project, &mut temp_map) {
                if config.verbose {
                    println!("Potential issue reading test addresses {}", e);
                }
            }
            else {
                // Deduplicate addresses
                for v in temp_map.values_mut() {
                    v.dedup_by_key(|x| x.address);
                }
                let temp_map = temp_map.into_iter()
                                       .filter(|&(ref k, _)| !(config.ignore_tests && k.path.starts_with(project.join("tests"))))
                                       .filter(|&(ref k, _)| !(config.exclude_path(&k.path)))
                                       .filter(|&(ref k, _)| !analysis.should_ignore(k.path.as_ref(), &(k.line as usize)))
                                       .collect::<HashMap<SourceLocation, Vec<TracerData>>>();
                let mut tracemap = TraceMap::new();
                for (k, val) in &temp_map {
                    for v in val.iter() {
                        tracemap.add_trace(&k.path, Trace {
                            line: k.line,
                            address: v.address,
                            length: 1,
                            stats: CoverageStat::Line(0)
                        });
                    }
                }
                result.merge(&tracemap);
            }
        }
    }

    for (file, ref line_analysis) in analysis.iter() {
        if config.exclude_path(file) {
            continue;
        }
        for line in &line_analysis.cover {
            let line = *line as u64;
            if !result.contains_location(file, line) && !line_analysis.should_ignore(line as usize) {
                result.add_trace(file, Trace {
                    line,
                    address: None,
                    length: 0,
                    stats: CoverageStat::Line(0),
                });
            }
        }
    }
    Ok(result)
}

pub fn generate_tracemap(project: &Workspace, test: &Path, config: &Config) -> io::Result<TraceMap> {
    let manifest = project.root();
    let file = File::open(test)?;
    let file = unsafe {
        MmapOptions::new().map(&file)?
    };
    if let Ok(obj) = OFile::parse(&*file) {
        let analysis = get_line_analysis(project, config);
        let endian = if obj.is_little_endian() {
            RunTimeEndian::Little
        } else {
            RunTimeEndian::Big
        };
        if let Ok(result) = get_line_addresses(endian, manifest, &obj, &analysis, config) {
            Ok(result)
        } else {
            Err(io::Error::new(io::ErrorKind::InvalidData, "Error while parsing"))
        }
    } else {
        Err(io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary."))
    }
}

<commit_msg>Fixed test_loader<commit_after>use std::io;
use std::path::{PathBuf, Path};
use std::fs::File;
use std::collections::HashMap;
use object::{Object, File as OFile};
use memmap::MmapOptions;
use gimli::*;
use rustc_demangle::demangle;
use cargo::core::Workspace;

use config::Config;
use source_analysis::*;
use traces::*;

/// Describes a function as `low_pc`, `high_pc` and bool representing `is_test`.
type FuncDesc = (u64, u64, FunctionType);

#[derive(Clone, Copy, PartialEq)]
enum FunctionType {
    Generated,
    Test,
    Standard
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
pub enum LineType {
    /// Generated test main. Shouldn't be traced.
    TestMain,
    /// Entry of function known to be a test
    TestEntry(u64),
    /// Entry of function. May or may not be test
    FunctionEntry(u64),
    /// Standard statement
    Statement,
    /// Condition
    Condition,
    /// Unknown type
    Unknown,
    /// Unused meta-code
    UnusedGeneric,
}

#[derive(Clone, PartialEq, Eq, Hash)]
struct SourceLocation {
    pub path: PathBuf,
    pub line: u64,
}

#[derive(Debug, Clone)]
pub struct TracerData {
    /// Currently used to find generated __test::main and remove from coverage,
    /// may have uses in future for finding conditions etc
    pub trace_type: LineType,
    /// Start address of the line
    pub address: Option<u64>,
    /// Length of the instruction
    pub length: u64,
}

fn generate_func_desc<R, Offset>(die: &DebuggingInformationEntry<R, Offset>,
                                 debug_str: &DebugStr<R>) -> Result<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut func_type = FunctionType::Standard;
    let low = die.attr_value(DW_AT_low_pc)?;
    let high = die.attr_value(DW_AT_high_pc)?;
    let linkage = die.attr_value(DW_AT_linkage_name)?;

    // Low is a program counter address so stored in an Addr
    let low = match low {
        Some(AttributeValue::Addr(x)) => x,
        _ => 0u64,
    };
    // High is an offset from the base pc, therefore is u64 data.
    let high = match high {
        Some(AttributeValue::Udata(x)) => x,
        _ => 0u64,
    };
    if let Some(AttributeValue::DebugStrRef(offset)) = linkage {
        let name = debug_str.get_str(offset)
            .and_then(|r| r.to_string().map(|s| s.to_string()))
            .unwrap_or_else(|_| "".into());
        let name = demangle(name.as_ref()).to_string();
        // Simplest test is whether it's in tests namespace.
        // Rust guidelines recommend all tests are in a tests module.
        func_type = if name.contains("tests::") {
            FunctionType::Test
        } else if name.contains("__test::main") {
            FunctionType::Generated
        } else {
            FunctionType::Standard
        };
    }
    Ok((low, high, func_type))
}

/// Finds all function entry points and returns a vector
/// This will identify definite tests, but may be prone to false negatives.
fn get_entry_points<R, Offset>(debug_info: &CompilationUnitHeader<R, Offset>,
                               debug_abbrev: &Abbreviations,
                               debug_str: &DebugStr<R>) -> Vec<FuncDesc>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let mut result:Vec<FuncDesc> = Vec::new();
    let mut cursor = debug_info.entries(debug_abbrev);
    // skip compilation unit root.
    let _ = cursor.next_entry();
    while let Ok(Some((_, node))) = cursor.next_dfs() {
        // Function DIE
        if node.tag() == DW_TAG_subprogram {

            if let Ok(fd) = generate_func_desc(node, debug_str) {
                result.push(fd);
            }
        }
    }
    result
}

fn get_addresses_from_program<R, Offset>(prog: IncompleteLineNumberProgram<R>,
                                         entries: &[(u64, LineType)],
                                         project: &Path,
                                         result: &mut HashMap<SourceLocation, Vec<TracerData>>) -> Result<()>
    where R: Reader<Offset = Offset>,
          Offset: ReaderOffset
{
    let ( cprog, seq) = prog.sequences()?;
    for s in seq {
        let mut temp_map: HashMap<SourceLocation, TracerData> = HashMap::new();
        let mut sm = cprog.resume_from(&s);
         while let Ok(Some((header, &ln_row))) = sm.next_row() {
             // If this row isn't useful move on
            if !ln_row.is_stmt() || ln_row.line().is_none() {
                continue;
            }
            if let Some(file) = ln_row.file(header) {
                let mut path = project.to_path_buf();
                if let Some(dir) = file.directory(header) {
                    if let Ok(temp) = dir.to_string() {
                        path.push(temp.as_ref());
                    }
                }

                if let Ok(p) = path.canonicalize() {
                    path = p;
                }
                // Fix relative paths and determine if in target directory
                // Source in target directory shouldn't be covered as it's either
                // autogenerated or resulting from the projects Cargo.lock
                let is_target = if path.is_relative() {
                    path.starts_with("target")
                } else {
                    path.starts_with(project.join("target"))
                };

                // Source is part of project so we cover it.
                if !is_target && path.starts_with(project) {
                    if let Some(file) = ln_row.file(header) {
                        let line = ln_row.line().unwrap();
                        let file = file.path_name();
                        if let Ok(file) = file.to_string() {
                            path.push(file.as_ref());
                            if !path.is_file() {
                                // Not really a source file!
                                continue;
                            }
                            let address = ln_row.address();
                            let desc = entries.iter()
                                              .filter(|&&(addr, _)| addr == address )
                                              .map(|&(_, t)| t)
                                              .nth(0)
                                              .unwrap_or(LineType::Unknown);
                            let loc = SourceLocation {
                                path,
                                line,
                            };
                            if desc != LineType::TestMain && !temp_map.contains_key(&loc) {
                                temp_map.insert(loc, TracerData {
                                    address: Some(address),
                                    trace_type: desc,
                                    length: 1,
                                });
                            }
                        }
                    }
                }
            }
        }
        for (k, v) in &temp_map {
            if result.contains_key(k) {
                let mut x = result.get_mut(k).unwrap();
                x.push(v.clone());
            } else {
                result.insert(k.clone(), vec![v.clone()]);
            }
        }
    }
    Ok(())
}

fn get_line_addresses(endian: RunTimeEndian,
                      project: &Path,
                      obj: &OFile,
                      analysis: &HashMap<PathBuf, LineAnalysis>,
                      config: &Config) -> Result<TraceMap>  {

    let mut result = TraceMap::new();
    let debug_info = obj.section_data_by_name(".debug_info").unwrap_or_default();
    let debug_info = DebugInfo::new(&debug_info, endian);
    let debug_abbrev = obj.section_data_by_name(".debug_abbrev").unwrap_or_default();
    let debug_abbrev = DebugAbbrev::new(&debug_abbrev, endian);
    let debug_strings = obj.section_data_by_name(".debug_str").unwrap_or_default();
    let debug_strings = DebugStr::new(&debug_strings, endian);
    let debug_line = obj.section_data_by_name(".debug_line").unwrap_or_default();
    let debug_line = DebugLine::new(&debug_line, endian);

    let mut iter = debug_info.units();
    while let Ok(Some(cu)) = iter.next() {
        let addr_size = cu.address_size();
        let abbr = match cu.abbreviations(&debug_abbrev) {
            Ok(a) => a,
            _ => continue,
        };
        let entries = get_entry_points(&cu, &abbr, &debug_strings)
            .iter()
            .map(|&(a, b, c)| {
                match c {
                    FunctionType::Test => (a, LineType::TestEntry(b)),
                    FunctionType::Standard => (a, LineType::FunctionEntry(b)),
                    FunctionType::Generated => (a, LineType::TestMain),
                }
            }).collect::<Vec<_>>();

        if let Ok(Some((_, root))) = cu.entries(&abbr).next_dfs() {
            let offset = match root.attr_value(DW_AT_stmt_list) {
                Ok(Some(AttributeValue::DebugLineRef(o))) => o,
                _ => continue,
            };
            let prog = debug_line.program(offset, addr_size, None, None)?;
            let mut temp_map : HashMap<SourceLocation, Vec<TracerData>> = HashMap::new();
            if let Err(e) = get_addresses_from_program(prog, &entries, project, &mut temp_map) {
                if config.verbose {
                    println!("Potential issue reading test addresses {}", e);
                }
            }
            else {
                // Deduplicate addresses
                for v in temp_map.values_mut() {
                    v.dedup_by_key(|x| x.address);
                }
                let temp_map = temp_map.into_iter()
                                       .filter(|&(ref k, _)| !(config.ignore_tests && k.path.starts_with(project.join("tests"))))
                                       .filter(|&(ref k, _)| !(config.exclude_path(&k.path)))
                                       .filter(|&(ref k, _)| !analysis.should_ignore(k.path.as_ref(), &(k.line as usize)))
                                       .collect::<HashMap<SourceLocation, Vec<TracerData>>>();
                let mut tracemap = TraceMap::new();
                for (k, val) in &temp_map {
                    for v in val.iter() {
                        tracemap.add_trace(&k.path, Trace {
                            line: k.line,
                            address: v.address,
                            length: 1,
                            stats: CoverageStat::Line(0)
                        });
                    }
                }
                result.merge(&tracemap);
            }
        }
    }

    for (file, ref line_analysis) in analysis.iter() {
        if config.exclude_path(file) {
            continue;
        }
        for line in &line_analysis.cover {
            let line = *line as u64;
            if !result.contains_location(file, line) && !line_analysis.should_ignore(line as usize) {
                result.add_trace(file, Trace {
                    line,
                    address: None,
                    length: 0,
                    stats: CoverageStat::Line(0),
                });
            }
        }
    }
    Ok(result)
}

pub fn generate_tracemap(project: &Workspace, test: &Path, config: &Config) -> io::Result<TraceMap> {
    let manifest = project.root();
    let file = File::open(test)?;
    let file = unsafe {
        MmapOptions::new().map(&file)?
    };
    if let Ok(obj) = OFile::parse(&*file) {
        let analysis = get_line_analysis(project, config);
        let endian = if obj.is_little_endian() {
            RunTimeEndian::Little
        } else {
            RunTimeEndian::Big
        };
        if let Ok(result) = get_line_addresses(endian, manifest, &obj, &analysis, config) {
            Ok(result)
        } else {
            Err(io::Error::new(io::ErrorKind::InvalidData, "Error while parsing"))
        }
    } else {
        Err(io::Error::new(io::ErrorKind::InvalidData, "Unable to parse binary."))
    }
}

<|endoftext|>


<commit_before>//!
//! The [CIPHERS](static.CIPHERS.html) static hash map is built during the
//! compilation of the crate, using `build.rs`. It parses a file derived from
//! the [IANA TLS Cipher Suite
//! Registry](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4)
//! to automatically extract parameters and add all known ciphersuites.

#![allow(non_camel_case_types)]
#![allow(clippy::unreadable_literal)]

use enum_primitive::{enum_from_primitive, enum_from_primitive_impl, enum_from_primitive_impl_ty};

enum_from_primitive! {
/// Key exchange methods
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherKx {
    Null = 0,
    Psk,
    Krb5,
    Srp,
    Rsa,
    Dh,
    Dhe,
    Ecdh,
    Ecdhe,
    Aecdh,
    Eccpwd,
    Tls13,
}
}

enum_from_primitive! {
/// Authentication methods
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherAu {
    Null = 0,
    Psk,
    Krb5,
    Srp,
    Srp_Dss,
    Srp_Rsa,
    Dss,
    Rsa,
    Dhe,
    Ecdsa,
    Eccpwd,
    Tls13,
}
}

enum_from_primitive! {
/// Encryption methods
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherEnc {
    Null,
    Des,
    TripleDes,
    Rc2,
    Rc4,
    Aria,
    Idea,
    Seed,
    Aes,
    Camellia,
    Chacha20_Poly1305,
    Sm4,
}
}

enum_from_primitive! {
/// Encryption modes
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherEncMode {
    Null,
    Cbc,
    Ccm,
    Gcm,
}
}

enum_from_primitive! {
/// Message Authentication Code (MAC) methods
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherMac {
    Null,
    HmacMd5,
    HmacSha1,
    HmacSha256,
    HmacSha384,
    Aead,
}
}

/// TLS Ciphersuite
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TlsCipherSuite {
    pub name: &'static str,
    pub id: u16,
    pub kx: TlsCipherKx,
    pub au: TlsCipherAu,
    pub enc: TlsCipherEnc,
    pub enc_mode: TlsCipherEncMode,
    pub enc_size: u16,
    pub mac: TlsCipherMac,
    pub mac_size: u16,
}

include!(concat!(env!("OUT_DIR"), "/codegen.rs"));

impl TlsCipherSuite {
    pub fn from_id(id: u16) -> Option<&'static TlsCipherSuite> {
        CIPHERS.get(&id)
    }

    pub fn from_name<'a>(name: &'a str) -> Option<&'static TlsCipherSuite> {
        CIPHERS.values().find(|&v| v.name == name)
    }
}

#[cfg(test)]
mod tests {
    use crate::tls_ciphers::{TlsCipherKx, TlsCipherSuite, CIPHERS};

    #[test]
    fn test_cipher_count() {
        println!("loaded: {} cipher suites", CIPHERS.len());
        assert!(!CIPHERS.is_empty());
    }

    #[test]
    fn test_cipher_from_id() {
        let cipher = TlsCipherSuite::from_id(0xc025).expect("could not get cipher");
        println!("Found cipher: {:?}", cipher);
    }

    #[test]
    fn test_cipher_from_name() {
        let cipher = TlsCipherSuite::from_name("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
            .expect("could not get cipher");
        println!("Found cipher: {:?}", cipher);
    }

    #[test]
    fn test_cipher_filter() {
        let ecdhe_ciphers_count = CIPHERS
            .values()
            .filter(|c| c.kx == TlsCipherKx::Ecdhe)
            .count();
        assert!(ecdhe_ciphers_count > 20);
    }
}
<commit_msg>Implement TryFrom u16, TlsCipherSuiteID and &str for TlsCipherSuite<commit_after>//!
//! The [CIPHERS](static.CIPHERS.html) static hash map is built during the
//! compilation of the crate, using `build.rs`. It parses a file derived from
//! the [IANA TLS Cipher Suite
//! Registry](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4)
//! to automatically extract parameters and add all known ciphersuites.

#![allow(non_camel_case_types)]
#![allow(clippy::unreadable_literal)]

use core::convert::TryFrom;
use enum_primitive::{enum_from_primitive, enum_from_primitive_impl, enum_from_primitive_impl_ty};

use crate::TlsCipherSuiteID;

#[derive(Debug)]
pub struct CipherSuiteNotFound(());

enum_from_primitive! {
/// Key exchange methods
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherKx {
    Null = 0,
    Psk,
    Krb5,
    Srp,
    Rsa,
    Dh,
    Dhe,
    Ecdh,
    Ecdhe,
    Aecdh,
    Eccpwd,
    Tls13,
}
}

enum_from_primitive! {
/// Authentication methods
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherAu {
    Null = 0,
    Psk,
    Krb5,
    Srp,
    Srp_Dss,
    Srp_Rsa,
    Dss,
    Rsa,
    Dhe,
    Ecdsa,
    Eccpwd,
    Tls13,
}
}

enum_from_primitive! {
/// Encryption methods
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherEnc {
    Null,
    Des,
    TripleDes,
    Rc2,
    Rc4,
    Aria,
    Idea,
    Seed,
    Aes,
    Camellia,
    Chacha20_Poly1305,
    Sm4,
}
}

enum_from_primitive! {
/// Encryption modes
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherEncMode {
    Null,
    Cbc,
    Ccm,
    Gcm,
}
}

enum_from_primitive! {
/// Message Authentication Code (MAC) methods
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TlsCipherMac {
    Null,
    HmacMd5,
    HmacSha1,
    HmacSha256,
    HmacSha384,
    Aead,
}
}

/// TLS Ciphersuite
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TlsCipherSuite {
    pub name: &'static str,
    pub id: u16,
    pub kx: TlsCipherKx,
    pub au: TlsCipherAu,
    pub enc: TlsCipherEnc,
    pub enc_mode: TlsCipherEncMode,
    pub enc_size: u16,
    pub mac: TlsCipherMac,
    pub mac_size: u16,
}

include!(concat!(env!("OUT_DIR"), "/codegen.rs"));

impl TlsCipherSuite {
    /// Attempt to get reference on `TlsCipherSuite` identified by `id`.
    pub fn from_id(id: u16) -> Option<&'static TlsCipherSuite> {
        CIPHERS.get(&id)
    }

    /// Attempt to get reference on `TlsCipherSuite` identified by `name`.
    pub fn from_name<'a>(name: &'a str) -> Option<&'static TlsCipherSuite> {
        CIPHERS.values().find(|&v| v.name == name)
    }
}

impl TryFrom<u16> for &'static TlsCipherSuite {
    type Error = CipherSuiteNotFound;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        CIPHERS.get(&value).ok_or(CipherSuiteNotFound(()))
    }
}

impl TryFrom<TlsCipherSuiteID> for &'static TlsCipherSuite {
    type Error = CipherSuiteNotFound;

    fn try_from(value: TlsCipherSuiteID) -> Result<Self, Self::Error> {
        CIPHERS.get(&value.0).ok_or(CipherSuiteNotFound(()))
    }
}

impl<'a> TryFrom<&'a str> for &'static TlsCipherSuite {
    type Error = CipherSuiteNotFound;

    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
        CIPHERS
            .values()
            .find(|&v| v.name == value)
            .ok_or(CipherSuiteNotFound(()))
    }
}

#[cfg(test)]
mod tests {
    use crate::tls_ciphers::{TlsCipherKx, TlsCipherSuite, CIPHERS};
    use core::convert::TryFrom;

    #[test]
    fn test_cipher_count() {
        println!("loaded: {} cipher suites", CIPHERS.len());
        assert!(!CIPHERS.is_empty());
    }

    #[test]
    fn test_cipher_from_id() {
        let cipher = <&TlsCipherSuite>::try_from(0xc025).expect("could not get cipher");
        println!("Found cipher: {:?}", cipher);
    }

    #[test]
    fn test_cipher_from_name() {
        let cipher = <&TlsCipherSuite>::try_from("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
            .expect("could not get cipher");
        println!("Found cipher: {:?}", cipher);
    }

    #[test]
    fn test_cipher_filter() {
        let ecdhe_ciphers_count = CIPHERS
            .values()
            .filter(|c| c.kx == TlsCipherKx::Ecdhe)
            .count();
        assert!(ecdhe_ciphers_count > 20);
    }
}
<|endoftext|>


<commit_before>use std::f64;
use std::io::{mod, ByRefWriter, IoError};
use std::num::{Float, FPNaN, FPInfinite};

use ser;
use ser::Serializer;

/// A structure for implementing serialization to JSON.
pub struct Writer<W> {
    writer: W,
}

impl<W: io::Writer> Writer<W> {
    /// Creates a new JSON visitr whose output will be written to the writer
    /// specified.
    #[inline]
    pub fn new(writer: W) -> Writer<W> {
        Writer {
            writer: writer,
        }
    }

    /// Unwrap the Writer from the Serializer.
    #[inline]
    pub fn into_inner(self) -> W {
        self.writer
    }
}

impl<W: io::Writer> ser::Serializer<W, (), IoError> for Writer<W> {
    #[inline]
    fn visit<
        T: ser::Serialize,
    >(&mut self, value: &T) -> Result<(), IoError> {
        value.visit(&mut self.writer, Visitor)
    }
}

struct Visitor;

impl<W: io::Writer> ser::Visitor<W, (), IoError> for Visitor {
    #[inline]
    fn visit_null(&self, writer: &mut W) -> Result<(), IoError> {
        writer.write_str("null")
    }

    #[inline]
    fn visit_bool(&self, writer: &mut W, value: bool) -> Result<(), IoError> {
        if value {
            writer.write_str("true")
        } else {
            writer.write_str("false")
        }
    }

    #[inline]
    fn visit_int(&self, writer: &mut W, value: int) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_i8(&self, writer: &mut W, value: i8) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_i16(&self, writer: &mut W, value: i16) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_i32(&self, writer: &mut W, value: i32) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_i64(&self, writer: &mut W, value: i64) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_uint(&self, writer: &mut W, value: uint) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_u8(&self, writer: &mut W, value: u8) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_u16(&self, writer: &mut W, value: u16) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_u32(&self, writer: &mut W, value: u32) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_u64(&self, writer: &mut W, value: u64) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_f64(&self, writer: &mut W, value: f64) -> Result<(), IoError> {
        fmt_f64_or_null(writer, value)
    }

    #[inline]
    fn visit_char(&self, writer: &mut W, v: char) -> Result<(), IoError> {
        escape_char(writer, v)
    }

    #[inline]
    fn visit_str(&self, writer: &mut W, value: &str) -> Result<(), IoError> {
        escape_str(writer, value)
    }

    #[inline]
    fn visit_seq<
        V: ser::SeqVisitor<W, (), IoError>
    >(&self, writer: &mut W, mut visitor: V) -> Result<(), IoError> {
        try!(writer.write_str("["));

        loop {
            match try!(visitor.visit(writer, Visitor)) {
                Some(()) => { }
                None => { break; }
            }
        }

        writer.write_str("]")
    }

    #[inline]
    fn visit_seq_elt<
        T: ser::Serialize,
    >(&self, writer: &mut W, first: bool, value: T) -> Result<(), IoError> {
        if !first {
            try!(writer.write_str(","));
        }

        value.visit(writer, Visitor)
    }

    #[inline]
    fn visit_map<
        V: ser::MapVisitor<W, (), IoError>
    >(&self, writer: &mut W, mut visitor: V) -> Result<(), IoError> {
        try!(writer.write_str("{{"));

        loop {
            match try!(visitor.visit(writer, Visitor)) {
                Some(()) => { }
                None => { break; }
            }
        }

        writer.write_str("}}")
    }

    #[inline]
    fn visit_map_elt<
        K: ser::Serialize,
        V: ser::Serialize,
    >(&self, writer: &mut W, first: bool, key: K, value: V) -> Result<(), IoError> {
        if !first {
            try!(writer.write_str(","));
        }

        try!(key.visit(writer, Visitor));
        try!(writer.write_str(":"));
        value.visit(writer, Visitor)
    }
}

#[inline]
pub fn escape_bytes<W: io::Writer>(wr: &mut W, bytes: &[u8]) -> Result<(), IoError> {
    try!(wr.write_str("\""));

    let mut start = 0;

    for (i, byte) in bytes.iter().enumerate() {
        let escaped = match *byte {
            b'"' => "\\\"",
            b'\\' => "\\\\",
            b'\x08' => "\\b",
            b'\x0c' => "\\f",
            b'\n' => "\\n",
            b'\r' => "\\r",
            b'\t' => "\\t",
            _ => { continue; }
        };

        if start < i {
            try!(wr.write(bytes.slice(start, i)));
        }

        try!(wr.write_str(escaped));

        start = i + 1;
    }

    if start != bytes.len() {
        try!(wr.write(bytes.slice_from(start)));
    }

    wr.write_str("\"")
}

#[inline]
pub fn escape_str<W: io::Writer>(wr: &mut W, value: &str) -> Result<(), IoError> {
    escape_bytes(wr, value.as_bytes())
}

#[inline]
pub fn escape_char<W: io::Writer>(wr: &mut W, value: char) -> Result<(), IoError> {
    let mut buf = &mut [0, .. 4];
    value.encode_utf8(buf);
    escape_bytes(wr, buf)
}

fn fmt_f64_or_null<W: io::Writer>(wr: &mut W, value: f64) -> Result<(), IoError> {
    match value.classify() {
        FPNaN | FPInfinite => wr.write_str("null"),
        _ => wr.write_str(f64::to_str_digits(value, 6).as_slice()),
    }
}

#[inline]
pub fn to_writer<
    W: io::Writer,
    T: ser::Serialize,
>(wr: &mut W, value: &T) -> Result<(), IoError> {
    let mut wr = Writer::new(wr.by_ref());
    try!(wr.visit(value));
    Ok(())
}

#[inline]
pub fn to_vec<
    T: ser::Serialize,
>(value: &T) -> Result<Vec<u8>, IoError> {
    let mut wr = Vec::with_capacity(128);
    to_writer(&mut wr, value).unwrap();
    Ok(wr)
}

#[inline]
pub fn to_string<
    T: ser::Serialize,
>(value: &T) -> Result<Result<String, Vec<u8>>, IoError> {
    let vec = try!(to_vec(value));
    Ok(String::from_utf8(vec))
}
<commit_msg>Fix: String::from_utf8 changed function signature<commit_after>use std::f64;
use std::io::{mod, ByRefWriter, IoError};
use std::num::{Float, FPNaN, FPInfinite};
use std::str::Utf8Error;

use ser;
use ser::Serializer;

/// A structure for implementing serialization to JSON.
pub struct Writer<W> {
    writer: W,
}

impl<W: io::Writer> Writer<W> {
    /// Creates a new JSON visitr whose output will be written to the writer
    /// specified.
    #[inline]
    pub fn new(writer: W) -> Writer<W> {
        Writer {
            writer: writer,
        }
    }

    /// Unwrap the Writer from the Serializer.
    #[inline]
    pub fn into_inner(self) -> W {
        self.writer
    }
}

impl<W: io::Writer> ser::Serializer<W, (), IoError> for Writer<W> {
    #[inline]
    fn visit<
        T: ser::Serialize,
    >(&mut self, value: &T) -> Result<(), IoError> {
        value.visit(&mut self.writer, Visitor)
    }
}

struct Visitor;

impl<W: io::Writer> ser::Visitor<W, (), IoError> for Visitor {
    #[inline]
    fn visit_null(&self, writer: &mut W) -> Result<(), IoError> {
        writer.write_str("null")
    }

    #[inline]
    fn visit_bool(&self, writer: &mut W, value: bool) -> Result<(), IoError> {
        if value {
            writer.write_str("true")
        } else {
            writer.write_str("false")
        }
    }

    #[inline]
    fn visit_int(&self, writer: &mut W, value: int) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_i8(&self, writer: &mut W, value: i8) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_i16(&self, writer: &mut W, value: i16) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_i32(&self, writer: &mut W, value: i32) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_i64(&self, writer: &mut W, value: i64) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_uint(&self, writer: &mut W, value: uint) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_u8(&self, writer: &mut W, value: u8) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_u16(&self, writer: &mut W, value: u16) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_u32(&self, writer: &mut W, value: u32) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_u64(&self, writer: &mut W, value: u64) -> Result<(), IoError> {
        write!(writer, "{}", value)
    }

    #[inline]
    fn visit_f64(&self, writer: &mut W, value: f64) -> Result<(), IoError> {
        fmt_f64_or_null(writer, value)
    }

    #[inline]
    fn visit_char(&self, writer: &mut W, v: char) -> Result<(), IoError> {
        escape_char(writer, v)
    }

    #[inline]
    fn visit_str(&self, writer: &mut W, value: &str) -> Result<(), IoError> {
        escape_str(writer, value)
    }

    #[inline]
    fn visit_seq<
        V: ser::SeqVisitor<W, (), IoError>
    >(&self, writer: &mut W, mut visitor: V) -> Result<(), IoError> {
        try!(writer.write_str("["));

        loop {
            match try!(visitor.visit(writer, Visitor)) {
                Some(()) => { }
                None => { break; }
            }
        }

        writer.write_str("]")
    }

    #[inline]
    fn visit_seq_elt<
        T: ser::Serialize,
    >(&self, writer: &mut W, first: bool, value: T) -> Result<(), IoError> {
        if !first {
            try!(writer.write_str(","));
        }

        value.visit(writer, Visitor)
    }

    #[inline]
    fn visit_map<
        V: ser::MapVisitor<W, (), IoError>
    >(&self, writer: &mut W, mut visitor: V) -> Result<(), IoError> {
        try!(writer.write_str("{{"));

        loop {
            match try!(visitor.visit(writer, Visitor)) {
                Some(()) => { }
                None => { break; }
            }
        }

        writer.write_str("}}")
    }

    #[inline]
    fn visit_map_elt<
        K: ser::Serialize,
        V: ser::Serialize,
    >(&self, writer: &mut W, first: bool, key: K, value: V) -> Result<(), IoError> {
        if !first {
            try!(writer.write_str(","));
        }

        try!(key.visit(writer, Visitor));
        try!(writer.write_str(":"));
        value.visit(writer, Visitor)
    }
}

#[inline]
pub fn escape_bytes<W: io::Writer>(wr: &mut W, bytes: &[u8]) -> Result<(), IoError> {
    try!(wr.write_str("\""));

    let mut start = 0;

    for (i, byte) in bytes.iter().enumerate() {
        let escaped = match *byte {
            b'"' => "\\\"",
            b'\\' => "\\\\",
            b'\x08' => "\\b",
            b'\x0c' => "\\f",
            b'\n' => "\\n",
            b'\r' => "\\r",
            b'\t' => "\\t",
            _ => { continue; }
        };

        if start < i {
            try!(wr.write(bytes.slice(start, i)));
        }

        try!(wr.write_str(escaped));

        start = i + 1;
    }

    if start != bytes.len() {
        try!(wr.write(bytes.slice_from(start)));
    }

    wr.write_str("\"")
}

#[inline]
pub fn escape_str<W: io::Writer>(wr: &mut W, value: &str) -> Result<(), IoError> {
    escape_bytes(wr, value.as_bytes())
}

#[inline]
pub fn escape_char<W: io::Writer>(wr: &mut W, value: char) -> Result<(), IoError> {
    let mut buf = &mut [0, .. 4];
    value.encode_utf8(buf);
    escape_bytes(wr, buf)
}

fn fmt_f64_or_null<W: io::Writer>(wr: &mut W, value: f64) -> Result<(), IoError> {
    match value.classify() {
        FPNaN | FPInfinite => wr.write_str("null"),
        _ => wr.write_str(f64::to_str_digits(value, 6).as_slice()),
    }
}

#[inline]
pub fn to_writer<
    W: io::Writer,
    T: ser::Serialize,
>(wr: &mut W, value: &T) -> Result<(), IoError> {
    let mut wr = Writer::new(wr.by_ref());
    try!(wr.visit(value));
    Ok(())
}

#[inline]
pub fn to_vec<
    T: ser::Serialize,
>(value: &T) -> Result<Vec<u8>, IoError> {
    let mut wr = Vec::with_capacity(128);
    to_writer(&mut wr, value).unwrap();
    Ok(wr)
}

#[inline]
pub fn to_string<
    T: ser::Serialize,
>(value: &T) -> Result<Result<String, (Vec<u8>, Utf8Error)>, IoError> {
    let vec = try!(to_vec(value));
    Ok(String::from_utf8(vec))
}
<|endoftext|>


<commit_before>
fn add(int x, int y) -> int { ret x + y; }
fn sub(int x, int y) -> int { ret x - y; }
fn mul(int x, int y) -> int { ret x * y; }
fn div(int x, int y) -> int { ret x / y; }
fn rem(int x, int y) -> int { ret x % y; }

fn lt(int x, int y) -> bool { ret x < y; }
fn le(int x, int y) -> bool { ret x <= y; }
fn eq(int x, int y) -> bool { ret x == y; }
fn ne(int x, int y) -> bool { ret x != y; }
fn ge(int x, int y) -> bool { ret x >= y; }
fn gt(int x, int y) -> bool { ret x > y; }

fn positive(int x) -> bool { ret x > 0; }
fn negative(int x) -> bool { ret x < 0; }
fn nonpositive(int x) -> bool { ret x <= 0; }
fn nonnegative(int x) -> bool { ret x >= 0; }

// FIXME: Make sure this works with negative integers.
fn hash(&int x) -> uint { ret x as uint; }
fn eq_alias(&int x, &int y) -> bool { ret x == y; }

iter range(int lo, int hi) -> int {
    let int lo_ = lo;
    while (lo_ < hi) {
        put lo_;
        lo_ += 1;
    }
}

fn to_str(int n, uint radix) -> str
{
    assert (0u < radix && radix <= 16u);
    if (n < 0) {
        ret "-" + uint::to_str((-n) as uint, radix);
    } else {
        ret uint::to_str(n as uint, radix);
    }
}

fn pow(int base, uint exponent) -> int {

    if (exponent == 0u) {
        ret 1;
    } else if (base == 0) {
        ret 0;
    } else {
        auto accum = base;
        auto count = exponent;
        while (count > 1u) {
            accum *= base;
            count -= 1u;
        }
        ret accum;
    }
}

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C .. 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<commit_msg>stdlib: Use if/alt expressions in std::int<commit_after>
fn add(int x, int y) -> int { ret x + y; }
fn sub(int x, int y) -> int { ret x - y; }
fn mul(int x, int y) -> int { ret x * y; }
fn div(int x, int y) -> int { ret x / y; }
fn rem(int x, int y) -> int { ret x % y; }

fn lt(int x, int y) -> bool { ret x < y; }
fn le(int x, int y) -> bool { ret x <= y; }
fn eq(int x, int y) -> bool { ret x == y; }
fn ne(int x, int y) -> bool { ret x != y; }
fn ge(int x, int y) -> bool { ret x >= y; }
fn gt(int x, int y) -> bool { ret x > y; }

fn positive(int x) -> bool { ret x > 0; }
fn negative(int x) -> bool { ret x < 0; }
fn nonpositive(int x) -> bool { ret x <= 0; }
fn nonnegative(int x) -> bool { ret x >= 0; }

// FIXME: Make sure this works with negative integers.
fn hash(&int x) -> uint { ret x as uint; }
fn eq_alias(&int x, &int y) -> bool { ret x == y; }

iter range(int lo, int hi) -> int {
    let int lo_ = lo;
    while (lo_ < hi) {
        put lo_;
        lo_ += 1;
    }
}

fn to_str(int n, uint radix) -> str
{
    assert (0u < radix && radix <= 16u);
    ret if (n < 0) {
        "-" + uint::to_str((-n) as uint, radix)
    } else {
        uint::to_str(n as uint, radix)
    };
}

fn pow(int base, uint exponent) -> int {

    ret if (exponent == 0u) {
        1
    } else if (base == 0) {
        0
    } else {
        auto accum = base;
        auto count = exponent;
        while (count > 1u) {
            accum *= base;
            count -= 1u;
        }
        accum
    };
}

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C .. 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<|endoftext|>


<commit_before>/*
  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

use std::fs;
use std::path::{Path, PathBuf};
use std::collections::{HashMap,HashSet};

use plist::Plist;
use folder::Folder;
use album::Album;
use version::Version;
use master::Master;
use audit::{Reporter,Report,SkipReason,Auditable};
use keyword::{parse_keywords,Keyword};
use store;
use plutils;
use AplibObject;

// This is mostly from db_version = 110

const INFO_PLIST: &'static str = "Info.plist";

const BUNDLE_IDENTIFIER: &'static str = "com.apple.Aperture.library";

const DATABASE_DIR: &'static str = "Database";

// in Database
const DATAMODEL_VERSION_PLIST: &'static str = "DataModelVersion.plist";
const KEYWORDS_PLIST: &'static str = "Keywords.plist";
const ALBUMS_DIR: &'static str = "Albums";
const FOLDERS_DIR: &'static str = "Folders";
const VERSIONS_BASE_DIR: &'static str = "Versions";

/// Info of the library data model
pub struct ModelInfo {
    pub is_iphoto_library: bool,
    pub db_version: i64,
    pub db_minor_version: i64,
    pub db_uuid: String,
    pub create_date: String,
    pub image_io_version: String,
    pub raw_camera_bundle_version: String,
    pub master_count: i64,
    pub version_count: i64,
    pub project_compat_back_to_version: i64,
    pub project_version: i64,
}

impl ModelInfo {
    fn parse(plist : &Plist) -> Option<ModelInfo>
    {
        use plutils::{get_int_value,get_bool_value,get_str_value};

        match *plist {
            Plist::Dictionary(ref dict) => Some(ModelInfo {
                db_uuid: get_str_value(dict, "databaseUuid"),
                db_minor_version: get_int_value(dict,
                                                "DatabaseMinorVersion"),
                db_version: get_int_value(dict, "DatabaseVersion"),
                is_iphoto_library: get_bool_value(dict, "isIPhotoLibrary"),
                create_date: get_str_value(dict, "createDate"),
                image_io_version: get_str_value(dict, "imageIOVersion"),
                raw_camera_bundle_version: get_str_value(
                    dict, "rawCameraBundleVersion"),
                master_count: get_int_value(dict, "masterCount"),
                version_count: get_int_value(dict, "versionCount"),
                project_version: get_int_value(dict, "projectVersion"),
                project_compat_back_to_version: get_int_value(
                    dict, "projectCompatibleBackToVersion"),
            }),
            _ => None
        }
    }
}

pub struct Library {
    path: String,

    version: String,

    folders: HashSet<String>,
    albums: HashSet<String>,
//    keywords: HashSet<String>,
    masters: HashSet<String>,
    versions: HashSet<String>,

    objects: HashMap<String, store::Wrapper>,
    auditor: Option<Reporter>,
}

impl Library {

    pub fn new(p: &str) -> Library
    {
        Library {
            path: p.to_owned(),
            version: "".to_owned(),

            folders: HashSet::new(),
            albums: HashSet::new(),
//            keywords: HashSet::new(),
            masters: HashSet::new(),
            versions: HashSet::new(),

            objects: HashMap::new(),
            auditor: None,
        }
    }

    /// Set an auditor.
    pub fn set_auditor(&mut self, auditor: Option<Reporter>) {
        self.auditor = auditor;
    }

    pub fn get_auditor(&self) -> Option<&Reporter> {
        self.auditor.as_ref()
    }

    /// Store the wrapped object.
    /// Return true if the object was stored
    /// Return false if there already was an object with the same uuid
    /// or if the uuid in invalid.
    pub fn store(&mut self, obj: store::Wrapper) -> bool
    {
        let uuid_str = {
            let uuid = obj.uuid();
            if uuid.is_none() {
                return false;
            }
            uuid.unwrap().to_owned()
        };
        match self.objects.insert(uuid_str, obj) {
            None => true,
            _ => false,
        }
    }

    pub fn get(&self, uuid: &str) -> Option<&store::Wrapper>
    {
        self.objects.get(uuid)
    }

    pub fn library_version(&mut self) -> &String
    {
        if self.version.is_empty()
        {
            let plist_path = self.build_path(INFO_PLIST, false);
            let plist = plutils::parse_plist(&plist_path);
            let mut report = Report::new();
            let audit = self.auditor.is_some();

            match plist {
                Plist::Dictionary(ref dict) => {
                    for (key, value) in dict.iter() {

                        match key.as_ref() {
                            "CFBundleIdentifier" => {
                                if let &Plist::String(ref s) = value {
                                    let bundle_id = s.to_owned();
                                    if bundle_id != BUNDLE_IDENTIFIER {
                                        report.skip(key,
                                                    SkipReason::InvalidData);
                                        println!("FATAL not a library");
                                        return &self.version;
                                    }
                                    if audit {
                                        report.parsed(key);
                                    }
                                } else if audit {
                                    report.skip(key, SkipReason::InvalidType);
                                }
                            },
                            "CFBundleShortVersionString" => {
                                if let &Plist::String(ref s) = value {
                                    self.version = s.to_owned();
                                    if audit {
                                        report.parsed(key);
                                    }
                                } else if audit {
                                    report.skip(key, SkipReason::InvalidType);
                                }
                            },
                            _ => {
                                if audit {
                                    report.ignore(key);
                                }
                            },
                        }
                    }
                    if audit {
                        self.auditor.as_mut().unwrap().parsed(
                            &plist_path.to_string_lossy(), report);
                    }

                },
                _ => {
                    if audit {
                        self.auditor.as_mut().unwrap().skip(
                            &plist_path.to_string_lossy(),
                            SkipReason::InvalidType);
                    }
                }
            }

        }
        &self.version
    }

    fn build_path(&self, dir: &str, database: bool) -> PathBuf
    {
        let mut ppath = PathBuf::from(self.path.to_owned());
        if database {
            ppath.push(DATABASE_DIR);
        }
        ppath.push(dir);

        ppath
    }

    fn list_items(&self, dir: &str, ext: &str) -> Vec<PathBuf>
    {
        let ppath = self.build_path(dir, true);
        let mut list: Vec<PathBuf> = Vec::new();

        if !fs::metadata(&ppath).unwrap().is_dir() {
            // XXX return a Result
            return list;
        }

        for entry in fs::read_dir(&ppath).unwrap() {
            let p = entry.unwrap().path();
            if p.extension().unwrap() == ext {
                list.push(p.to_owned());
            }
        }

        list
    }

    pub fn get_model_info(&self) -> Option<ModelInfo>
    {
        let ppath = self.build_path(DATAMODEL_VERSION_PLIST, true);
        let plist = plutils::parse_plist(ppath.as_ref());

        ModelInfo::parse(&plist)
    }

    fn load_items<T: AplibObject + Auditable, F: FnMut(u64) -> bool>(
        &mut self, dir: &str, ext: &str, set: &mut HashSet<String>, pg: &mut F)
    {
        let file_list = self.list_items(dir, ext);
        let audit = self.auditor.is_some();
        for file in file_list {
            if let Some(obj) = T::from_path(file.as_ref()) {
                set.insert(obj.uuid().to_owned());
                if audit {
                    let report = obj.audit();
                    self.auditor.as_mut().unwrap().parsed(
                        &file.to_string_lossy(), report);
                }
                self.store(T::wrap(obj));
            } else {
                if audit {
                    self.auditor.as_mut().unwrap().skip(
                        &file.to_string_lossy(),
                        SkipReason::ParseFailed);
                }
                println!("Failed to decode object from {:?}", file);
            }
            if !pg(1) {
                println!("Cancelled");
                return;
            }
        }
    }

    pub fn load_albums<F: FnMut(u64) -> bool>(&mut self, pg: &mut F)
    {
        if self.albums.is_empty() {
            let mut albums : HashSet<String> = HashSet::new();
            self.load_items::<Album, F>(ALBUMS_DIR, "apalbum", &mut albums, pg);
            self.albums = albums;
        }
    }

    pub fn get_albums(&self) -> &HashSet<String>
    {
        &self.albums
    }

    pub fn load_folders<F: FnMut(u64) -> bool>(&mut self, pg: &mut F)
    {
        if self.folders.is_empty() {
            let mut folders : HashSet<String> = HashSet::new();
            self.load_items::<Folder, F>(FOLDERS_DIR, "apfolder",
                                         &mut folders, pg);
            self.folders = folders;
        }
    }

    pub fn get_folders(&self) -> &HashSet<String>
    {
        &self.folders
    }

    fn recurse_list_directory(path: &Path, level: i32) -> Vec<PathBuf>
    {
        let mut list: Vec<PathBuf> = Vec::new();
        for entry in fs::read_dir(path).unwrap() {
            let entry = entry.unwrap();
            if entry.metadata().unwrap().is_dir() {
                if level == 0 {
                    list.push(entry.path());
                } else {
                    let mut sublist =
                        Library::recurse_list_directory(&entry.path(),
                                                        level - 1);
                    list.append(&mut sublist)
                }
            }
        }

        list
    }

    fn list_versions_items_dirs(&self) -> Vec<PathBuf>
    {
        let ppath = self.build_path(VERSIONS_BASE_DIR, true);

        if !fs::metadata(&ppath).unwrap().is_dir() {
            // XXX return a Result
            return Vec::new();
        }

        Library::recurse_list_directory(&ppath, 4)
    }

    fn list_versions_items(&self, ext: &str) -> Vec<PathBuf>
    {
        let list = self.list_versions_items_dirs();
        let mut items = Vec::new();

        for dir in list {

            if !fs::metadata(&dir).unwrap().is_dir() {
                continue;
            }

            for entry in fs::read_dir(&dir).unwrap() {
                let entry = entry.unwrap();
                let p = entry.path();
                if p.extension().unwrap() == ext {
                    items.push(entry.path().to_owned());
                }
            }
        }

        items
    }

    fn load_versions_items<T: AplibObject, F: FnMut(u64) -> bool>(
        &mut self, ext: &str,
        set: &mut HashSet<String>, pg: &mut F)
    {
        let file_list = self.list_versions_items(ext);
        for file in file_list {
            if let Some(obj) = T::from_path(file.as_ref()) {
                set.insert(obj.uuid().to_owned());
                self.store(T::wrap(obj));
            } else {
                println!("Error decoding object from {:?}", file);
            }
            if !pg(1) {
                println!("Cancelled!");
                break;
            }
        }
    }

    pub fn load_versions<F: FnMut(u64) -> bool>(&mut self, pg: &mut F)
    {
        if self.versions.is_empty() {
            let mut versions: HashSet<String> = HashSet::new();
            self.load_versions_items::<Version, F>(
                "apversion", &mut versions, pg);
            self.versions = versions;
        }
    }

    pub fn load_masters<F: FnMut(u64) -> bool>(&mut self, pg: &mut F)
    {
        if self.masters.is_empty() {
            let mut masters: HashSet<String> = HashSet::new();
            self.load_versions_items::<Master, F>(
                "apmaster", &mut masters, pg);
            self.masters = masters;
        }
    }

    pub fn get_masters(&self) -> &HashSet<String>
    {
        &self.masters
    }

    pub fn get_versions(&self) -> &HashSet<String>
    {
        &self.versions
    }

    pub fn list_keywords(&self) -> Vec<Keyword>
    {
        parse_keywords(self.build_path(KEYWORDS_PLIST, true).as_ref())
    }
}

<commit_msg>Add some documentation<commit_after>/*
  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

use std::fs;
use std::path::{Path, PathBuf};
use std::collections::{HashMap,HashSet};

use plist::Plist;
use folder::Folder;
use album::Album;
use version::Version;
use master::Master;
use audit::{Reporter,Report,SkipReason,Auditable};
use keyword::{parse_keywords,Keyword};
use store;
use plutils;
use AplibObject;

// This is mostly from db_version = 110

const INFO_PLIST: &'static str = "Info.plist";

const BUNDLE_IDENTIFIER: &'static str = "com.apple.Aperture.library";

const DATABASE_DIR: &'static str = "Database";

// in Database
const DATAMODEL_VERSION_PLIST: &'static str = "DataModelVersion.plist";
const KEYWORDS_PLIST: &'static str = "Keywords.plist";
const ALBUMS_DIR: &'static str = "Albums";
const FOLDERS_DIR: &'static str = "Folders";
const VERSIONS_BASE_DIR: &'static str = "Versions";

/// Info of the library data model
pub struct ModelInfo {
    pub is_iphoto_library: bool,
    pub db_version: i64,
    pub db_minor_version: i64,
    pub db_uuid: String,
    pub create_date: String,
    pub image_io_version: String,
    pub raw_camera_bundle_version: String,
    pub master_count: i64,
    pub version_count: i64,
    pub project_compat_back_to_version: i64,
    pub project_version: i64,
}

impl ModelInfo {
    fn parse(plist : &Plist) -> Option<ModelInfo>
    {
        use plutils::{get_int_value,get_bool_value,get_str_value};

        match *plist {
            Plist::Dictionary(ref dict) => Some(ModelInfo {
                db_uuid: get_str_value(dict, "databaseUuid"),
                db_minor_version: get_int_value(dict,
                                                "DatabaseMinorVersion"),
                db_version: get_int_value(dict, "DatabaseVersion"),
                is_iphoto_library: get_bool_value(dict, "isIPhotoLibrary"),
                create_date: get_str_value(dict, "createDate"),
                image_io_version: get_str_value(dict, "imageIOVersion"),
                raw_camera_bundle_version: get_str_value(
                    dict, "rawCameraBundleVersion"),
                master_count: get_int_value(dict, "masterCount"),
                version_count: get_int_value(dict, "versionCount"),
                project_version: get_int_value(dict, "projectVersion"),
                project_compat_back_to_version: get_int_value(
                    dict, "projectCompatibleBackToVersion"),
            }),
            _ => None
        }
    }
}

/// Library is the Aperture library.
pub struct Library {
    /// The path to the .aplib bundle (the directory)
    path: String,

    /// It's version string (displayed by get info in the Finder)
    version: String,

    /// All the folders UUID
    folders: HashSet<String>,
    /// All the albums UUID
    albums: HashSet<String>,
    //    keywords: HashSet<String>,
    /// All the masters UUID
    masters: HashSet<String>,
    /// All the version UUID
    versions: HashSet<String>,

    /// The object store. The key is the UUID
    objects: HashMap<String, store::Wrapper>,
    /// Auditor for the audit mode.
    auditor: Option<Reporter>,
}

impl Library {

    /// Create a new library object from the exist path to
    /// the bundle directory.
    pub fn new(p: &str) -> Library
    {
        Library {
            path: p.to_owned(),
            version: "".to_owned(),

            folders: HashSet::new(),
            albums: HashSet::new(),
//            keywords: HashSet::new(),
            masters: HashSet::new(),
            versions: HashSet::new(),

            objects: HashMap::new(),
            auditor: None,
        }
    }

    /// Set an auditor.
    pub fn set_auditor(&mut self, auditor: Option<Reporter>) {
        self.auditor = auditor;
    }
    /// Get the auditor
    pub fn get_auditor(&self) -> Option<&Reporter> {
        self.auditor.as_ref()
    }

    /// Store the wrapped object.
    /// Return true if the object was stored
    /// Return false if there already was an object with the same uuid
    /// or if the uuid in invalid.
    pub fn store(&mut self, obj: store::Wrapper) -> bool
    {
        let uuid_str = {
            let uuid = obj.uuid();
            if uuid.is_none() {
                return false;
            }
            uuid.unwrap().to_owned()
        };
        match self.objects.insert(uuid_str, obj) {
            None => true,
            _ => false,
        }
    }

    /// Get an object out of the store by UUID
    pub fn get(&self, uuid: &str) -> Option<&store::Wrapper>
    {
        self.objects.get(uuid)
    }

    /// Get the library version. Will parse the plist for that
    /// if needed.
    pub fn library_version(&mut self) -> &String
    {
        if self.version.is_empty()
        {
            let plist_path = self.build_path(INFO_PLIST, false);
            let plist = plutils::parse_plist(&plist_path);
            let mut report = Report::new();
            let audit = self.auditor.is_some();

            match plist {
                Plist::Dictionary(ref dict) => {
                    for (key, value) in dict.iter() {

                        match key.as_ref() {
                            "CFBundleIdentifier" => {
                                if let &Plist::String(ref s) = value {
                                    let bundle_id = s.to_owned();
                                    if bundle_id != BUNDLE_IDENTIFIER {
                                        report.skip(key,
                                                    SkipReason::InvalidData);
                                        println!("FATAL not a library");
                                        return &self.version;
                                    }
                                    if audit {
                                        report.parsed(key);
                                    }
                                } else if audit {
                                    report.skip(key, SkipReason::InvalidType);
                                }
                            },
                            "CFBundleShortVersionString" => {
                                if let &Plist::String(ref s) = value {
                                    self.version = s.to_owned();
                                    if audit {
                                        report.parsed(key);
                                    }
                                } else if audit {
                                    report.skip(key, SkipReason::InvalidType);
                                }
                            },
                            _ => {
                                if audit {
                                    report.ignore(key);
                                }
                            },
                        }
                    }
                    if audit {
                        self.auditor.as_mut().unwrap().parsed(
                            &plist_path.to_string_lossy(), report);
                    }

                },
                _ => {
                    if audit {
                        self.auditor.as_mut().unwrap().skip(
                            &plist_path.to_string_lossy(),
                            SkipReason::InvalidType);
                    }
                }
            }

        }
        &self.version
    }

    /// Build a path from the bundle root.
    /// If database is true, will be from the Database subdirectory.
    fn build_path(&self, dir: &str, database: bool) -> PathBuf
    {
        let mut ppath = PathBuf::from(self.path.to_owned());
        if database {
            ppath.push(DATABASE_DIR);
        }
        ppath.push(dir);

        ppath
    }

    /// list items in dir with extension ext.
    /// Return a vector with full path for each.
    fn list_items(&self, dir: &str, ext: &str) -> Vec<PathBuf>
    {
        let ppath = self.build_path(dir, true);
        let mut list: Vec<PathBuf> = Vec::new();

        if !fs::metadata(&ppath).unwrap().is_dir() {
            // XXX return a Result
            return list;
        }

        for entry in fs::read_dir(&ppath).unwrap() {
            let p = entry.unwrap().path();
            if p.extension().unwrap() == ext {
                list.push(p.to_owned());
            }
        }

        list
    }

    pub fn get_model_info(&self) -> Option<ModelInfo>
    {
        let ppath = self.build_path(DATAMODEL_VERSION_PLIST, true);
        let plist = plutils::parse_plist(ppath.as_ref());

        ModelInfo::parse(&plist)
    }

    fn load_items<T: AplibObject + Auditable, F: FnMut(u64) -> bool>(
        &mut self, dir: &str, ext: &str, set: &mut HashSet<String>, pg: &mut F)
    {
        let file_list = self.list_items(dir, ext);
        let audit = self.auditor.is_some();
        for file in file_list {
            if let Some(obj) = T::from_path(file.as_ref()) {
                set.insert(obj.uuid().to_owned());
                if audit {
                    let report = obj.audit();
                    self.auditor.as_mut().unwrap().parsed(
                        &file.to_string_lossy(), report);
                }
                self.store(T::wrap(obj));
            } else {
                if audit {
                    self.auditor.as_mut().unwrap().skip(
                        &file.to_string_lossy(),
                        SkipReason::ParseFailed);
                }
                println!("Failed to decode object from {:?}", file);
            }
            if !pg(1) {
                println!("Cancelled");
                return;
            }
        }
    }

    pub fn load_albums<F: FnMut(u64) -> bool>(&mut self, pg: &mut F)
    {
        if self.albums.is_empty() {
            let mut albums : HashSet<String> = HashSet::new();
            self.load_items::<Album, F>(ALBUMS_DIR, "apalbum", &mut albums, pg);
            self.albums = albums;
        }
    }

    pub fn get_albums(&self) -> &HashSet<String>
    {
        &self.albums
    }

    pub fn load_folders<F: FnMut(u64) -> bool>(&mut self, pg: &mut F)
    {
        if self.folders.is_empty() {
            let mut folders : HashSet<String> = HashSet::new();
            self.load_items::<Folder, F>(FOLDERS_DIR, "apfolder",
                                         &mut folders, pg);
            self.folders = folders;
        }
    }

    pub fn get_folders(&self) -> &HashSet<String>
    {
        &self.folders
    }

    fn recurse_list_directory(path: &Path, level: i32) -> Vec<PathBuf>
    {
        let mut list: Vec<PathBuf> = Vec::new();
        for entry in fs::read_dir(path).unwrap() {
            let entry = entry.unwrap();
            if entry.metadata().unwrap().is_dir() {
                if level == 0 {
                    list.push(entry.path());
                } else {
                    let mut sublist =
                        Library::recurse_list_directory(&entry.path(),
                                                        level - 1);
                    list.append(&mut sublist)
                }
            }
        }

        list
    }

    fn list_versions_items_dirs(&self) -> Vec<PathBuf>
    {
        let ppath = self.build_path(VERSIONS_BASE_DIR, true);

        if !fs::metadata(&ppath).unwrap().is_dir() {
            // XXX return a Result
            return Vec::new();
        }

        Library::recurse_list_directory(&ppath, 4)
    }

    fn list_versions_items(&self, ext: &str) -> Vec<PathBuf>
    {
        let list = self.list_versions_items_dirs();
        let mut items = Vec::new();

        for dir in list {

            if !fs::metadata(&dir).unwrap().is_dir() {
                continue;
            }

            for entry in fs::read_dir(&dir).unwrap() {
                let entry = entry.unwrap();
                let p = entry.path();
                if p.extension().unwrap() == ext {
                    items.push(entry.path().to_owned());
                }
            }
        }

        items
    }

    fn load_versions_items<T: AplibObject, F: FnMut(u64) -> bool>(
        &mut self, ext: &str,
        set: &mut HashSet<String>, pg: &mut F)
    {
        let file_list = self.list_versions_items(ext);
        for file in file_list {
            if let Some(obj) = T::from_path(file.as_ref()) {
                set.insert(obj.uuid().to_owned());
                self.store(T::wrap(obj));
            } else {
                println!("Error decoding object from {:?}", file);
            }
            if !pg(1) {
                println!("Cancelled!");
                break;
            }
        }
    }

    pub fn load_versions<F: FnMut(u64) -> bool>(&mut self, pg: &mut F)
    {
        if self.versions.is_empty() {
            let mut versions: HashSet<String> = HashSet::new();
            self.load_versions_items::<Version, F>(
                "apversion", &mut versions, pg);
            self.versions = versions;
        }
    }

    pub fn load_masters<F: FnMut(u64) -> bool>(&mut self, pg: &mut F)
    {
        if self.masters.is_empty() {
            let mut masters: HashSet<String> = HashSet::new();
            self.load_versions_items::<Master, F>(
                "apmaster", &mut masters, pg);
            self.masters = masters;
        }
    }

    pub fn get_masters(&self) -> &HashSet<String>
    {
        &self.masters
    }

    pub fn get_versions(&self) -> &HashSet<String>
    {
        &self.versions
    }

    pub fn list_keywords(&self) -> Vec<Keyword>
    {
        parse_keywords(self.build_path(KEYWORDS_PLIST, true).as_ref())
    }
}

<|endoftext|>