<commit_before>extern crate compiletest_rs as compiletest;

use std::path::{PathBuf, Path};
use std::io::Write;

fn compile_fail(sysroot: &str) {
    let flags = format!("--sysroot {} -Dwarnings", sysroot);
    for_all_targets(sysroot, |target| {
        let mut config = compiletest::default_config();
        config.host_rustcflags = Some(flags.clone());
        config.mode = "compile-fail".parse().expect("Invalid mode");
        config.run_lib_path = Path::new(sysroot).join("lib").join("rustlib").join(&target).join("lib");
        config.rustc_path = "target/debug/miri".into();
        config.src_base = PathBuf::from("tests/compile-fail".to_string());
        config.target = target.to_owned();
        config.target_rustcflags = Some(flags.clone());
        compiletest::run_tests(&config);
    });
}

fn run_pass() {
    let mut config = compiletest::default_config();
    config.mode = "run-pass".parse().expect("Invalid mode");
    config.src_base = PathBuf::from("tests/run-pass".to_string());
    config.target_rustcflags = Some("-Dwarnings".to_string());
    config.host_rustcflags = Some("-Dwarnings".to_string());
    compiletest::run_tests(&config);
}

fn for_all_targets<F: FnMut(String)>(sysroot: &str, mut f: F) {
    for target in std::fs::read_dir(format!("{}/lib/rustlib/", sysroot)).unwrap() {
        let target = target.unwrap();
        if !target.metadata().unwrap().is_dir() {
            continue;
        }
        let target = target.file_name().into_string().unwrap();
        if target == "etc" {
            continue;
        }
        let stderr = std::io::stderr();
        writeln!(stderr.lock(), "running tests for target {}", target).unwrap();
        f(target);
    }
}

#[test]
fn compile_test() {
    // Taken from https://github.com/Manishearth/rust-clippy/pull/911.
    let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
    let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
    let sysroot = match (home, toolchain) {
        (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain),
        _ => option_env!("RUST_SYSROOT")
            .expect("need to specify RUST_SYSROOT env var or use rustup or multirust")
            .to_owned(),
    };
    run_pass();
    for_all_targets(&sysroot, |target| {
        let files = std::fs::read_dir("tests/run-pass").unwrap();
        let files: Box<Iterator<Item=_>> = if let Ok(path) = std::env::var("MIRI_RUSTC_TEST") {
            Box::new(files.chain(std::fs::read_dir(path).unwrap()))
        } else {
            // print traces only when not running on the rust run-pass test suite (since tracing is slow)
            std::env::set_var("MIRI_LOG", "trace");
            Box::new(files)
        };
        let mut mir_not_found = 0;
        let mut crate_not_found = 0;
        let mut success = 0;
        let mut failed = 0;
        for file in files {
            let file = file.unwrap();
            let path = file.path();

            if !file.metadata().unwrap().is_file() || !path.to_str().unwrap().ends_with(".rs") {
                continue;
            }

            let stderr = std::io::stderr();
            write!(stderr.lock(), "test [miri-pass] {} ... ", path.display()).unwrap();
            let mut cmd = std::process::Command::new("target/debug/miri");
            cmd.arg(path);
            cmd.arg(format!("--target={}", target));
            let libs = Path::new(&sysroot).join("lib");
            let sysroot = libs.join("rustlib").join(&target).join("lib");
            let paths = std::env::join_paths(&[libs, sysroot]).unwrap();
            cmd.env(compiletest::procsrv::dylib_env_var(), paths);

            match cmd.output() {
                Ok(ref output) if output.status.success() => {
                    success += 1;
                    writeln!(stderr.lock(), "ok").unwrap()
                },
                Ok(output) => {
                    let output_err = std::str::from_utf8(&output.stderr).unwrap();
                    if let Some(text) = output_err.splitn(2, "no mir for `").nth(1) {
                        mir_not_found += 1;
                        let end = text.find('`').unwrap();
                        writeln!(stderr.lock(), "NO MIR FOR `{}`", &text[..end]).unwrap();
                    } else if let Some(text) = output_err.splitn(2, "can't find crate for `").nth(1) {
                        crate_not_found += 1;
                        let end = text.find('`').unwrap();
                        writeln!(stderr.lock(), "CAN'T FIND CRATE FOR `{}`", &text[..end]).unwrap();
                    } else {
                        failed += 1;
                        writeln!(stderr.lock(), "FAILED with exit code {:?}", output.status.code()).unwrap();
                        writeln!(stderr.lock(), "stdout: \n {}", std::str::from_utf8(&output.stdout).unwrap()).unwrap();
                        writeln!(stderr.lock(), "stderr: \n {}", output_err).unwrap();
                    }
                }
                Err(e) => {
                    writeln!(stderr.lock(), "FAILED: {}", e).unwrap();
                    panic!("failed to execute miri");
                },
            }
        }
        let stderr = std::io::stderr();
        writeln!(stderr.lock(), "{} success, {} mir not found, {} crate not found, {} failed", success, mir_not_found, crate_not_found, failed).unwrap();
        assert_eq!(failed, 0, "some tests failed");
    });
    compile_fail(&sysroot);
}
<commit_msg>rustup allows one to install the rust source, that's obviously not a target<commit_after>extern crate compiletest_rs as compiletest;

use std::path::{PathBuf, Path};
use std::io::Write;

fn compile_fail(sysroot: &str) {
    let flags = format!("--sysroot {} -Dwarnings", sysroot);
    for_all_targets(sysroot, |target| {
        let mut config = compiletest::default_config();
        config.host_rustcflags = Some(flags.clone());
        config.mode = "compile-fail".parse().expect("Invalid mode");
        config.run_lib_path = Path::new(sysroot).join("lib").join("rustlib").join(&target).join("lib");
        config.rustc_path = "target/debug/miri".into();
        config.src_base = PathBuf::from("tests/compile-fail".to_string());
        config.target = target.to_owned();
        config.target_rustcflags = Some(flags.clone());
        compiletest::run_tests(&config);
    });
}

fn run_pass() {
    let mut config = compiletest::default_config();
    config.mode = "run-pass".parse().expect("Invalid mode");
    config.src_base = PathBuf::from("tests/run-pass".to_string());
    config.target_rustcflags = Some("-Dwarnings".to_string());
    config.host_rustcflags = Some("-Dwarnings".to_string());
    compiletest::run_tests(&config);
}

fn for_all_targets<F: FnMut(String)>(sysroot: &str, mut f: F) {
    for target in std::fs::read_dir(format!("{}/lib/rustlib/", sysroot)).unwrap() {
        let target = target.unwrap();
        if !target.metadata().unwrap().is_dir() {
            continue;
        }
        let target = target.file_name().into_string().unwrap();
        match &*target {
            "etc" | "src" => continue,
            _ => {},
        }
        let stderr = std::io::stderr();
        writeln!(stderr.lock(), "running tests for target {}", target).unwrap();
        f(target);
    }
}

#[test]
fn compile_test() {
    // Taken from https://github.com/Manishearth/rust-clippy/pull/911.
    let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME"));
    let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN"));
    let sysroot = match (home, toolchain) {
        (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain),
        _ => option_env!("RUST_SYSROOT")
            .expect("need to specify RUST_SYSROOT env var or use rustup or multirust")
            .to_owned(),
    };
    run_pass();
    for_all_targets(&sysroot, |target| {
        let files = std::fs::read_dir("tests/run-pass").unwrap();
        let files: Box<Iterator<Item=_>> = if let Ok(path) = std::env::var("MIRI_RUSTC_TEST") {
            Box::new(files.chain(std::fs::read_dir(path).unwrap()))
        } else {
            // print traces only when not running on the rust run-pass test suite (since tracing is slow)
            std::env::set_var("MIRI_LOG", "trace");
            Box::new(files)
        };
        let mut mir_not_found = 0;
        let mut crate_not_found = 0;
        let mut success = 0;
        let mut failed = 0;
        for file in files {
            let file = file.unwrap();
            let path = file.path();

            if !file.metadata().unwrap().is_file() || !path.to_str().unwrap().ends_with(".rs") {
                continue;
            }

            let stderr = std::io::stderr();
            write!(stderr.lock(), "test [miri-pass] {} ... ", path.display()).unwrap();
            let mut cmd = std::process::Command::new("target/debug/miri");
            cmd.arg(path);
            cmd.arg(format!("--target={}", target));
            let libs = Path::new(&sysroot).join("lib");
            let sysroot = libs.join("rustlib").join(&target).join("lib");
            let paths = std::env::join_paths(&[libs, sysroot]).unwrap();
            cmd.env(compiletest::procsrv::dylib_env_var(), paths);

            match cmd.output() {
                Ok(ref output) if output.status.success() => {
                    success += 1;
                    writeln!(stderr.lock(), "ok").unwrap()
                },
                Ok(output) => {
                    let output_err = std::str::from_utf8(&output.stderr).unwrap();
                    if let Some(text) = output_err.splitn(2, "no mir for `").nth(1) {
                        mir_not_found += 1;
                        let end = text.find('`').unwrap();
                        writeln!(stderr.lock(), "NO MIR FOR `{}`", &text[..end]).unwrap();
                    } else if let Some(text) = output_err.splitn(2, "can't find crate for `").nth(1) {
                        crate_not_found += 1;
                        let end = text.find('`').unwrap();
                        writeln!(stderr.lock(), "CAN'T FIND CRATE FOR `{}`", &text[..end]).unwrap();
                    } else {
                        failed += 1;
                        writeln!(stderr.lock(), "FAILED with exit code {:?}", output.status.code()).unwrap();
                        writeln!(stderr.lock(), "stdout: \n {}", std::str::from_utf8(&output.stdout).unwrap()).unwrap();
                        writeln!(stderr.lock(), "stderr: \n {}", output_err).unwrap();
                    }
                }
                Err(e) => {
                    writeln!(stderr.lock(), "FAILED: {}", e).unwrap();
                    panic!("failed to execute miri");
                },
            }
        }
        let stderr = std::io::stderr();
        writeln!(stderr.lock(), "{} success, {} mir not found, {} crate not found, {} failed", success, mir_not_found, crate_not_found, failed).unwrap();
        assert_eq!(failed, 0, "some tests failed");
    });
    compile_fail(&sysroot);
}
<|endoftext|>


<commit_before>//! The integration suite helpers.

use regex::Regex;
use std::process::Command;
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;

/// List windows will give details about the active sessions in testing.
/// target: A string represented by the {named_session}:{named_window}
/// tmux list-windows -t TARGET -F '#{window_index}: #{window_name}#{?window_active,*, } (#{window_panes} panes) (Dir: #{pane_current_path}) (Session: #{session_name})'
pub fn list_windows(target: &String) -> String {
    let output = Command::new("tmux")
                     .arg("list-windows")
                     .arg("-t")
                     .arg(target)
                     .arg("-F")
                     .arg("'#{window_index}: #{window_name}#{?window_active,*, } (#{window_panes} panes) (Dir: #{pane_current_path}) (Session: #{session_name})'")
                     .output()
                     .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    String::from_utf8_lossy(&output.stdout).into_owned()
}

pub fn open_muxed(project: &String, project_root: &Path) -> () {
    println!("root: {}", project_root.display());
    let output = Command::new("./target/debug/muxed")
        .arg("-d")
        .arg("-p")
        .arg(format!("{}", project_root.display()))
        .arg(project)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    println!("status: {}", output.status);
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}

pub fn kill_session(target: &String) -> () {
    Command::new("tmux")
        .arg("kill-session")
        .arg("-t")
        .arg(target)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

pub fn send_keys(target: &String, exec: &String) -> () {
    Command::new("tmux")
        .arg("send-keys")
        .arg("-t")
        .arg(target)
        .arg(exec)
        .arg("KPEnter")
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

pub fn wait_on(file: &PathBuf) -> () {
    while !file.exists() {
        // Wait increased from 10 to 750 due to the pre_window tests.
        sleep(Duration::from_millis(750));
    }
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SessionValue {
    Usize(usize),
    String(String),
    Empty,
}

impl SessionValue {
    pub fn as_str(&self) -> Option<&str> {
        match self {
            &SessionValue::String(ref s) => Some(s),
            _ => None
        }
    }

    pub fn as_usize(&self) -> Option<usize> {
        match self {
            &SessionValue::Usize(s) => Some(s),
            _ => None
        }
    }
}

#[derive(Debug)]
pub struct TmuxSession {
    pub num_of_windows: usize,
    pub windows: HashMap<String, WindowValues>,
    pub window_active: SessionValue,
    pub name: SessionValue,
}

#[derive(Debug, Clone)]
pub struct WindowValues {
    pub panes: SessionValue,
    pub pane_current_path: SessionValue,
}

static NAME_REGEX:              &'static str = r"\(Session: (.*)\)";
static WINDOW_NAME_REGEX:       &'static str = r":\s(\w*)[$\*-]?\s+\(";
static WINDOW_ACTIVE_REGEX:     &'static str = r"\s(.*)\*";
static PANE_CURRENT_PATH_REGEX: &'static str = r"\(Dir: (.*)\) ";
static PANES_COUNT_REGEX:       &'static str = r"\((\d*) panes\)";

impl TmuxSession {
    pub fn from_string(results: &String) -> TmuxSession {
        let window_name = Regex::new(WINDOW_NAME_REGEX).unwrap();

        let lines: Vec<&str> = results.split("\n").collect();
        let (_, window_lines) = lines.split_last().unwrap();

        let mut windows: HashMap<String, WindowValues> = HashMap::new();

        for line in window_lines {
            let cap = window_name.captures(line).unwrap();
            let name = cap.get(1).unwrap().as_str();

            let win_val = WindowValues{
                panes: TmuxSession::count_panes(line),
                pane_current_path: TmuxSession::retrieve_capture(line, PANE_CURRENT_PATH_REGEX).unwrap_or(SessionValue::Empty)
            };

            windows.insert(name.to_string(), win_val.clone());
        }

        TmuxSession {
          num_of_windows: window_lines.len(),
          windows: windows,
          window_active: TmuxSession::retrieve_capture(window_lines[0], WINDOW_ACTIVE_REGEX).unwrap_or(SessionValue::Empty),
          name: TmuxSession::retrieve_capture(window_lines[0], NAME_REGEX).unwrap_or(SessionValue::Empty)
        }
    }

    pub fn retrieve_capture(line: &str, pattern: &str) -> Result<SessionValue, String> {
        let reg = Regex::new(pattern).unwrap();

        if let Some(caps) = reg.captures(line) {
            return match caps.get(1) {
               Some(x) => Ok(SessionValue::String(x.as_str().to_string())),
               None    => Err("No capture".to_string())
            };
        };

        Err("No capture".to_string())
    }

    pub fn count_panes(line: &str) -> SessionValue {
        let panes = Regex::new(PANES_COUNT_REGEX).unwrap();
        let mut num: &str = "";

        for cap in panes.captures_iter(line) {
            num = match cap.get(1) {
                Some(x) => x.as_str(),
                None    => "0"
            }
        }

        SessionValue::Usize(usize::from_str(num).unwrap())
    }
}

#[test]
fn count_panes_returns_two() {
    let num = TmuxSession::count_panes("1: ssh  (2 panes) (Dir: /Projects/muxed) (session: muxed)");
    assert_eq!(num.as_usize().unwrap(), 2)
}

#[test]
fn count_panes_returns_one() {
    let num = TmuxSession::count_panes("1: ssh  (1 panes) (Dir: /Projects/muxed) (session: muxed)");
    assert_eq!(num.as_usize().unwrap(), 1)
}

#[test]
fn parses_with_trailing_whitespace_after_window_name() {
    let config = "1: ssh  (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_previous_flag() {
    let config = "1: ssh- (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_dollar_sign_flag() {
    let config = "1: ssh$ (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_window_flag() {
    let config = "1: ssh* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn count_three_windows() {
    let config = "1: ssh  (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  2: vim- (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  3: bash* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let num = TmuxSession::from_string(&config.to_string()).num_of_windows;
    assert_eq!(num, 3)
}

#[test]
fn count_four_total_panes() {
    let config = "1: ssh  (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  2: vim- (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  3: bash* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let num = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    let num1 = session.windows.get("vim").unwrap().panes.as_usize().unwrap();
    let num2 = session.windows.get("bash").unwrap().panes.as_usize().unwrap();
    assert_eq!(num, 1);
    assert_eq!(num1, 1);
    assert_eq!(num2, 2)
}

#[test]
fn expect_ok_session_name_capture() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, NAME_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_session_name_capture() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session:)";
    let result = TmuxSession::retrieve_capture(line, NAME_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_muxed_to_be_captured_as_session() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, NAME_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "muxed")
}

#[test]
fn expect_ok_window_name_capture() {
    let line = "2: vim1* (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_NAME_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_window_name_capture() {
    let line = "2: (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_NAME_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_vim1_window_name_capture() {
    let line = "2: vim1* (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_NAME_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "vim1")
}

#[test]
fn expect_ok_active_window_capture() {
    let line = "2: vim* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_ACTIVE_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_active_window_capture() {
    let line = "2: vim (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_ACTIVE_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_vim_active_window_capture() {
    let line = "2: vim* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_ACTIVE_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "vim")
}
<commit_msg>refactor(tests): Use str when String isn't needed<commit_after>//! The integration suite helpers.

use regex::Regex;
use std::process::Command;
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;

/// List windows will give details about the active sessions in testing.
/// target: A string represented by the {named_session}:{named_window}
/// tmux list-windows -t TARGET -F '#{window_index}: #{window_name}#{?window_active,*, } (#{window_panes} panes) (Dir: #{pane_current_path}) (Session: #{session_name})'
pub fn list_windows(target: &String) -> String {
    let output = Command::new("tmux")
                     .arg("list-windows")
                     .arg("-t")
                     .arg(target)
                     .arg("-F")
                     .arg("'#{window_index}: #{window_name}#{?window_active,*, } (#{window_panes} panes) (Dir: #{pane_current_path}) (Session: #{session_name})'")
                     .output()
                     .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    String::from_utf8_lossy(&output.stdout).into_owned()
}

pub fn open_muxed(project: &String, project_root: &Path) -> () {
    println!("root: {}", project_root.display());
    let output = Command::new("./target/debug/muxed")
        .arg("-d")
        .arg("-p")
        .arg(format!("{}", project_root.display()))
        .arg(project)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    println!("status: {}", output.status);
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}

pub fn kill_session(target: &str) -> () {
    Command::new("tmux")
        .arg("kill-session")
        .arg("-t")
        .arg(target)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

pub fn send_keys(target: &String, exec: &String) -> () {
    Command::new("tmux")
        .arg("send-keys")
        .arg("-t")
        .arg(target)
        .arg(exec)
        .arg("KPEnter")
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

pub fn wait_on(file: &PathBuf) -> () {
    while !file.exists() {
        // Wait increased from 10 to 750 due to the pre_window tests.
        sleep(Duration::from_millis(750));
    }
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SessionValue {
    Usize(usize),
    String(String),
    Empty,
}

impl SessionValue {
    pub fn as_str(&self) -> Option<&str> {
        match self {
            &SessionValue::String(ref s) => Some(s),
            _ => None
        }
    }

    pub fn as_usize(&self) -> Option<usize> {
        match self {
            &SessionValue::Usize(s) => Some(s),
            _ => None
        }
    }
}

#[derive(Debug)]
pub struct TmuxSession {
    pub num_of_windows: usize,
    pub windows: HashMap<String, WindowValues>,
    pub window_active: SessionValue,
    pub name: SessionValue,
}

#[derive(Debug, Clone)]
pub struct WindowValues {
    pub panes: SessionValue,
    pub pane_current_path: SessionValue,
}

static NAME_REGEX:              &'static str = r"\(Session: (.*)\)";
static WINDOW_NAME_REGEX:       &'static str = r":\s(\w*)[$\*-]?\s+\(";
static WINDOW_ACTIVE_REGEX:     &'static str = r"\s(.*)\*";
static PANE_CURRENT_PATH_REGEX: &'static str = r"\(Dir: (.*)\) ";
static PANES_COUNT_REGEX:       &'static str = r"\((\d*) panes\)";

impl TmuxSession {
    pub fn from_string(results: &str) -> TmuxSession {
        let window_name = Regex::new(WINDOW_NAME_REGEX).unwrap();

        let lines: Vec<&str> = results.split("\n").collect();
        let (_, window_lines) = lines.split_last().unwrap();

        let mut windows: HashMap<String, WindowValues> = HashMap::new();

        for line in window_lines {
            let cap = window_name.captures(line).unwrap();
            let name = cap.get(1).unwrap().as_str();

            let win_val = WindowValues{
                panes: TmuxSession::count_panes(line),
                pane_current_path: TmuxSession::retrieve_capture(line, PANE_CURRENT_PATH_REGEX).unwrap_or(SessionValue::Empty)
            };

            windows.insert(name.to_string(), win_val.clone());
        }

        TmuxSession {
          num_of_windows: window_lines.len(),
          windows: windows,
          window_active: TmuxSession::retrieve_capture(window_lines[0], WINDOW_ACTIVE_REGEX).unwrap_or(SessionValue::Empty),
          name: TmuxSession::retrieve_capture(window_lines[0], NAME_REGEX).unwrap_or(SessionValue::Empty)
        }
    }

    pub fn retrieve_capture(line: &str, pattern: &str) -> Result<SessionValue, String> {
        let reg = Regex::new(pattern).unwrap();

        if let Some(caps) = reg.captures(line) {
            return match caps.get(1) {
               Some(x) => Ok(SessionValue::String(x.as_str().to_string())),
               None    => Err("No capture".to_string())
            };
        };

        Err("No capture".to_string())
    }

    pub fn count_panes(line: &str) -> SessionValue {
        let panes = Regex::new(PANES_COUNT_REGEX).unwrap();
        let mut num: &str = "";

        for cap in panes.captures_iter(line) {
            num = match cap.get(1) {
                Some(x) => x.as_str(),
                None    => "0"
            }
        }

        SessionValue::Usize(usize::from_str(num).unwrap())
    }
}

#[test]
fn count_panes_returns_two() {
    let num = TmuxSession::count_panes("1: ssh  (2 panes) (Dir: /Projects/muxed) (session: muxed)");
    assert_eq!(num.as_usize().unwrap(), 2)
}

#[test]
fn count_panes_returns_one() {
    let num = TmuxSession::count_panes("1: ssh  (1 panes) (Dir: /Projects/muxed) (session: muxed)");
    assert_eq!(num.as_usize().unwrap(), 1)
}

#[test]
fn parses_with_trailing_whitespace_after_window_name() {
    let config = "1: ssh  (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_previous_flag() {
    let config = "1: ssh- (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_dollar_sign_flag() {
    let config = "1: ssh$ (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_window_flag() {
    let config = "1: ssh* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn count_three_windows() {
    let config = "1: ssh  (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  2: vim- (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  3: bash* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let num = TmuxSession::from_string(&config.to_string()).num_of_windows;
    assert_eq!(num, 3)
}

#[test]
fn count_four_total_panes() {
    let config = "1: ssh  (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  2: vim- (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  3: bash* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let num = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    let num1 = session.windows.get("vim").unwrap().panes.as_usize().unwrap();
    let num2 = session.windows.get("bash").unwrap().panes.as_usize().unwrap();
    assert_eq!(num, 1);
    assert_eq!(num1, 1);
    assert_eq!(num2, 2)
}

#[test]
fn expect_ok_session_name_capture() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, NAME_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_session_name_capture() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session:)";
    let result = TmuxSession::retrieve_capture(line, NAME_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_muxed_to_be_captured_as_session() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, NAME_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "muxed")
}

#[test]
fn expect_ok_window_name_capture() {
    let line = "2: vim1* (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_NAME_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_window_name_capture() {
    let line = "2: (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_NAME_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_vim1_window_name_capture() {
    let line = "2: vim1* (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_NAME_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "vim1")
}

#[test]
fn expect_ok_active_window_capture() {
    let line = "2: vim* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_ACTIVE_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_active_window_capture() {
    let line = "2: vim (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_ACTIVE_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_vim_active_window_capture() {
    let line = "2: vim* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(line, WINDOW_ACTIVE_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "vim")
}
<|endoftext|>


<commit_before>//! Implements the API server.
use std::sync::{Arc, Mutex};
use std::thread;
use std::io::Read;
use std::os::unix::io::{FromRawFd, RawFd};

use libc;
use hyper::server::{Server, Request, Response};
use hyper::header::ContentLength;
use hyper::method::Method;
use hyper::net::HttpListener;
use hyper::uri::RequestUri;
use serde::Deserialize;
use serde_json;

use super::super::config::Config;
use super::super::memdb::stash::{MemDbStash, SyncStatus};
use super::super::Result;
use super::super::utils::{HumanDuration, run_isolated, get_systemd_fd};
use super::handlers;
use super::types::{ApiResponse, ApiError};

/// Result from a healthcheck.
#[derive(Serialize, Clone)]
pub struct HealthCheckResponse {
    pub is_offline: bool,
    pub is_healthy: bool,
    pub sync_lag: u32,
}

/// Shared access to the state of the server.
pub struct ServerContext {
    pub config: Config,
    pub stash: MemDbStash,
    enable_sync: bool,
    cached_memdb_status: Mutex<Option<SyncStatus>>,
}

/// The API server itself.
pub struct ApiServer {
    ctx: Arc<ServerContext>,
}

/// Controls how the server binds to sockets.
pub enum BindOptions<'a> {
    /// Bind according to the config file.
    UseConfig,
    /// Bind to a file descriptor.
    BindToFd(RawFd),
    /// Bind to a specific address (`host:port`).
    BindToAddr(&'a str),
}

impl ServerContext {
    pub fn check_health(&self) -> Result<()> {
        let mut cache_value = self.cached_memdb_status.lock().unwrap();
        *cache_value = Some(self.stash.get_sync_status()?);
        Ok(())
    }

    pub fn get_healthcheck_result(&self) -> Result<HealthCheckResponse> {
        if self.enable_sync {
            let cache_value = self.cached_memdb_status.lock().unwrap();
            if let Some(ref state) = *cache_value {
                Ok(HealthCheckResponse {
                    is_offline: state.is_offline(),
                    is_healthy: state.is_healthy(),
                    sync_lag: state.lag()
                })
            } else {
                Ok(HealthCheckResponse {
                    is_offline: true,
                    is_healthy: false,
                    sync_lag: 0,
                })
            }
        } else {
            Ok(HealthCheckResponse {
                is_offline: true,
                is_healthy: true,
                sync_lag: 0,
            })
        }
    }
}

impl ApiServer {
    /// Create a new server.
    pub fn new(config: &Config, enable_sync: bool) -> Result<ApiServer> {
        Ok(ApiServer {
            ctx: Arc::new(ServerContext {
                config: config.clone(),
                stash: MemDbStash::new(config)?,
                enable_sync: enable_sync,
                cached_memdb_status: Mutex::new(None),
            }),
        })
    }

    /// Spawns a background thread that runs the sync process.
    pub fn spawn_sync_thread(&self) -> Result<()> {
        let interval = self.ctx.config.get_server_sync_interval()?;
        let std_interval = interval.to_std().unwrap();
        info!("Checking for symbols from S3 in background every {}",
              HumanDuration(interval));
        info!("Source Bucket: {}", self.ctx.config.get_aws_bucket_url()?);
        info!("Local SDKs: {}", self.ctx.stash.sdk_count()?);

        let ctx = self.ctx.clone();
        thread::spawn(move || {
            loop {
                let ctx = ctx.clone();
                run_isolated(move || ctx.stash.sync(Default::default()));
                thread::sleep(std_interval);
            }
        });

        Ok(())
    }

    /// Spawns a background check that checks the health of the system.
    pub fn spawn_healthcheck_thread(&self) -> Result<()> {
        let interval = self.ctx.config.get_server_healthcheck_interval()?;
        let std_interval = interval.to_std().unwrap();
        info!("Running healthcheck every {}", HumanDuration(interval));

        // run initial healthcheck right away.
        let ctx = self.ctx.clone();
        ctx.check_health()?;

        thread::spawn(move || {
            loop {
                let ctx = ctx.clone();
                run_isolated(move || ctx.check_health());
                thread::sleep(std_interval);
            }
        });

        Ok(())
    }

    /// Runs the server in a loop.
    pub fn run(&self, threads: usize, opts: BindOptions) -> Result<()> {
        let debug_addr;

        if self.ctx.enable_sync {
            self.spawn_sync_thread()?;
            self.spawn_healthcheck_thread()?;
        } else {
            info!("Background sync is disabled. Health check forced to healthy.");
        }

        let listener = match opts {
            BindOptions::BindToAddr(addr) => {
                debug_addr = format!("http://{}/", addr);
                HttpListener::new(addr)?
            }
            BindOptions::BindToFd(fd) => {
                debug_addr = format!("file descriptor {}", fd);
                // unsafe is okay here because we dup the fd
                unsafe { HttpListener::from_raw_fd(libc::dup(fd)) }
            }
            BindOptions::UseConfig => {
                if let Some(fd) = get_systemd_fd()? {
                    debug_addr = format!("systemd supplied fd");
                    // unsafe is sortof okay here because get_systemd_fd will
                    // not return the fd a second time and we also do not pass
                    // that information to potential children
                    unsafe { HttpListener::from_raw_fd(fd) }
                } else {
                    let addr = self.ctx.config.get_server_socket_addr()?;
                    let (host, port) = addr;
                    debug_addr = format!("http://{}:{}/", host, port);
                    HttpListener::new((host.as_str(), port))?
                }
            }
        };
        info!("Listening on {}", debug_addr);
        info!("Spawning {} listener threads", threads);

        let ctx = self.ctx.clone();
        Server::new(listener)
            .handle_threads(move |req: Request, resp: Response|
        {
            let is_head = req.method == Method::Head;
            let handler = match req.uri {
                RequestUri::AbsolutePath(ref path) => {
                    match path.as_str() {
                        "/health" => handlers::healthcheck_handler,
                        "/lookup" => handlers::lookup_symbol_handler,
                        "/sdks" => handlers::list_sdks_handler,
                        _ => not_found,
                    }
                }
                _ => bad_request,
            };
            match handler(&*ctx.clone(), req) {
                Ok(result) => result,
                Err(err) => ApiResponse::from_error(err).unwrap(),
            }.write_to_response(is_head, resp).unwrap();
        }, threads)?;
        Ok(())
    }
}

/// Helper for the handlers to safely load request data.
pub fn load_request_data<D: Deserialize>(req: &mut Request) -> Result<D> {
    if let Some(&ContentLength(length)) = req.headers.get() {
        if length > 1024 * 1024 * 2 {
            return Err(ApiError::PayloadTooLarge.into());
        }
    } else {
        return Err(ApiError::BadRequest.into());
    }

    let mut body: Vec<u8> = vec![];
    req.read_to_end(&mut body)?;

    Ok(match serde_json::from_slice(&body[..]) {
        Ok(data) => data,
        Err(err) => { return Err(ApiError::BadJson(Box::new(err)).into()); }
    })
}

fn bad_request(_: &ServerContext, _: Request) -> Result<ApiResponse>
{
    Err(ApiError::BadRequest.into())
}

fn not_found(_: &ServerContext, _: Request) -> Result<ApiResponse>
{
    Err(ApiError::NotFound.into())
}
<commit_msg>Improve the healthcheck lock<commit_after>//! Implements the API server.
use std::sync::{Arc, RwLock};
use std::thread;
use std::io::Read;
use std::os::unix::io::{FromRawFd, RawFd};

use libc;
use hyper::server::{Server, Request, Response};
use hyper::header::ContentLength;
use hyper::method::Method;
use hyper::net::HttpListener;
use hyper::uri::RequestUri;
use serde::Deserialize;
use serde_json;

use super::super::config::Config;
use super::super::memdb::stash::{MemDbStash, SyncStatus};
use super::super::Result;
use super::super::utils::{HumanDuration, run_isolated, get_systemd_fd};
use super::handlers;
use super::types::{ApiResponse, ApiError};

/// Result from a healthcheck.
#[derive(Serialize, Clone)]
pub struct HealthCheckResponse {
    pub is_offline: bool,
    pub is_healthy: bool,
    pub sync_lag: u32,
}

/// Shared access to the state of the server.
pub struct ServerContext {
    pub config: Config,
    pub stash: MemDbStash,
    enable_sync: bool,
    cached_memdb_status: RwLock<Option<SyncStatus>>,
}

/// The API server itself.
pub struct ApiServer {
    ctx: Arc<ServerContext>,
}

/// Controls how the server binds to sockets.
pub enum BindOptions<'a> {
    /// Bind according to the config file.
    UseConfig,
    /// Bind to a file descriptor.
    BindToFd(RawFd),
    /// Bind to a specific address (`host:port`).
    BindToAddr(&'a str),
}

impl ServerContext {
    pub fn check_health(&self) -> Result<()> {
        let sync_status = self.stash.get_sync_status()?;
        *self.cached_memdb_status.write().unwrap() = Some(sync_status);
        Ok(())
    }

    pub fn get_healthcheck_result(&self) -> Result<HealthCheckResponse> {
        if self.enable_sync {
            let cache_value = self.cached_memdb_status.read().unwrap();
            if let Some(ref state) = *cache_value {
                Ok(HealthCheckResponse {
                    is_offline: state.is_offline(),
                    is_healthy: state.is_healthy(),
                    sync_lag: state.lag()
                })
            } else {
                Ok(HealthCheckResponse {
                    is_offline: true,
                    is_healthy: false,
                    sync_lag: 0,
                })
            }
        } else {
            Ok(HealthCheckResponse {
                is_offline: true,
                is_healthy: true,
                sync_lag: 0,
            })
        }
    }
}

impl ApiServer {
    /// Create a new server.
    pub fn new(config: &Config, enable_sync: bool) -> Result<ApiServer> {
        Ok(ApiServer {
            ctx: Arc::new(ServerContext {
                config: config.clone(),
                stash: MemDbStash::new(config)?,
                enable_sync: enable_sync,
                cached_memdb_status: RwLock::new(None),
            }),
        })
    }

    /// Spawns a background thread that runs the sync process.
    pub fn spawn_sync_thread(&self) -> Result<()> {
        let interval = self.ctx.config.get_server_sync_interval()?;
        let std_interval = interval.to_std().unwrap();
        info!("Checking for symbols from S3 in background every {}",
              HumanDuration(interval));
        info!("Source Bucket: {}", self.ctx.config.get_aws_bucket_url()?);
        info!("Local SDKs: {}", self.ctx.stash.sdk_count()?);

        let ctx = self.ctx.clone();
        thread::spawn(move || {
            loop {
                let ctx = ctx.clone();
                run_isolated(move || ctx.stash.sync(Default::default()));
                thread::sleep(std_interval);
            }
        });

        Ok(())
    }

    /// Spawns a background check that checks the health of the system.
    pub fn spawn_healthcheck_thread(&self) -> Result<()> {
        let interval = self.ctx.config.get_server_healthcheck_interval()?;
        let std_interval = interval.to_std().unwrap();
        info!("Running healthcheck every {}", HumanDuration(interval));

        // run initial healthcheck right away.
        let ctx = self.ctx.clone();
        ctx.check_health()?;

        thread::spawn(move || {
            loop {
                let ctx = ctx.clone();
                run_isolated(move || ctx.check_health());
                thread::sleep(std_interval);
            }
        });

        Ok(())
    }

    /// Runs the server in a loop.
    pub fn run(&self, threads: usize, opts: BindOptions) -> Result<()> {
        let debug_addr;

        if self.ctx.enable_sync {
            self.spawn_sync_thread()?;
            self.spawn_healthcheck_thread()?;
        } else {
            info!("Background sync is disabled. Health check forced to healthy.");
        }

        let listener = match opts {
            BindOptions::BindToAddr(addr) => {
                debug_addr = format!("http://{}/", addr);
                HttpListener::new(addr)?
            }
            BindOptions::BindToFd(fd) => {
                debug_addr = format!("file descriptor {}", fd);
                // unsafe is okay here because we dup the fd
                unsafe { HttpListener::from_raw_fd(libc::dup(fd)) }
            }
            BindOptions::UseConfig => {
                if let Some(fd) = get_systemd_fd()? {
                    debug_addr = format!("systemd supplied fd");
                    // unsafe is sortof okay here because get_systemd_fd will
                    // not return the fd a second time and we also do not pass
                    // that information to potential children
                    unsafe { HttpListener::from_raw_fd(fd) }
                } else {
                    let addr = self.ctx.config.get_server_socket_addr()?;
                    let (host, port) = addr;
                    debug_addr = format!("http://{}:{}/", host, port);
                    HttpListener::new((host.as_str(), port))?
                }
            }
        };
        info!("Listening on {}", debug_addr);
        info!("Spawning {} listener threads", threads);

        let ctx = self.ctx.clone();
        Server::new(listener)
            .handle_threads(move |req: Request, resp: Response|
        {
            let is_head = req.method == Method::Head;
            let handler = match req.uri {
                RequestUri::AbsolutePath(ref path) => {
                    match path.as_str() {
                        "/health" => handlers::healthcheck_handler,
                        "/lookup" => handlers::lookup_symbol_handler,
                        "/sdks" => handlers::list_sdks_handler,
                        _ => not_found,
                    }
                }
                _ => bad_request,
            };
            match handler(&*ctx.clone(), req) {
                Ok(result) => result,
                Err(err) => ApiResponse::from_error(err).unwrap(),
            }.write_to_response(is_head, resp).unwrap();
        }, threads)?;
        Ok(())
    }
}

/// Helper for the handlers to safely load request data.
pub fn load_request_data<D: Deserialize>(req: &mut Request) -> Result<D> {
    if let Some(&ContentLength(length)) = req.headers.get() {
        if length > 1024 * 1024 * 2 {
            return Err(ApiError::PayloadTooLarge.into());
        }
    } else {
        return Err(ApiError::BadRequest.into());
    }

    let mut body: Vec<u8> = vec![];
    req.read_to_end(&mut body)?;

    Ok(match serde_json::from_slice(&body[..]) {
        Ok(data) => data,
        Err(err) => { return Err(ApiError::BadJson(Box::new(err)).into()); }
    })
}

fn bad_request(_: &ServerContext, _: Request) -> Result<ApiResponse>
{
    Err(ApiError::BadRequest.into())
}

fn not_found(_: &ServerContext, _: Request) -> Result<ApiResponse>
{
    Err(ApiError::NotFound.into())
}
<|endoftext|>


<commit_before>// Copyright 2015 Nathan Sizemore <nathanrsizemore@gmail.com>
//
// 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/.
//
// This Source Code Form is "Incompatible
// With Secondary Licenses", as defined by
// the Mozilla Public License, v. 2.0.

use std::{str, ptr, mem};
use std::ffi::{CStr, CString};
use std::thread;
use std::net::TcpStream;
use std::sync::mpsc::{channel, Sender, Receiver};

use super::libc::{c_int, c_char, c_void};
use super::simple_stream::bstream::Bstream;

extern "C" {
    fn register_writer_tx(tx: *mut c_void);
    fn register_stop_tx(tx: *mut c_void);
}

#[no_mangle]
pub extern "C" fn start(address: *const c_char,
    handler: extern fn(*const c_char, c_int),
    on_connect_handler: extern fn(),
    on_disconnect_handler: extern fn()) -> c_int {

    println!("Rust - start()");

    let mut r_address;
    unsafe {
        r_address = CStr::from_ptr(address);
    }
    let s_address = r_address.to_bytes();
    let host_address = match str::from_utf8(s_address) {
        Ok(safe_str) => safe_str,
        Err(_) => {
            println!("Invalid host address");
            return -1 as c_int;
        }
    };

    println!("Rust - address: {}", host_address);

    // // Create and register a way to kill this client
    // let (k_tx, kill_rx): (Sender<()>, Receiver<()>) = channel();
    // let kill_tx = k_tx.clone();
    // let mut k_tx_ptr = Box::new(k_tx);
    //
    // println!("calling register_stop_tx");
    //
    // let mut k_tx_ptr_clone = k_tx_ptr.clone();
    // unsafe {
    //     let mut k_tx_as_void_ptr: *mut c_void = mem::transmute(k_tx_ptr_clone);
    //     register_stop_tx(&mut *k_tx_as_void_ptr);
    // }
    //
    // // Writer thread's channel
    // let (w_tx, w_rx): (Sender<Vec<u8>>, Receiver<Vec<u8>>) = channel();
    // let mut w_tx_ptr = Box::new(w_tx);
    // unsafe {
    //     register_writer_tx(&mut *w_tx_ptr);
    // }
    //
    // println!("Attempting connect to: {}", host_address);
    //
    // let result = TcpStream::connect(host_address);
    // if result.is_err() {
    //     println!("Error connecting to {} - {}", host_address, result.unwrap_err());
    //     return -1 as c_int;
    // }
    // println!("Connected");
    // on_connect_handler();
    //
    // let stream = result.unwrap();
    // let client = Bstream::new(stream);
    //
    // let r_client = client.clone();
    // let r_kill_tx = kill_tx.clone();
    //
    // let w_client = client.clone();
    // let w_kill_tx = kill_tx.clone();
    //
    // // Start the reader thread
    // thread::Builder::new()
    //     .name("ReaderThread".to_string())
    //     .spawn(move||{
    //         reader_thread(r_client, handler, r_kill_tx)
    //     }).unwrap();
    //
    // // Start the writer thread
    // thread::Builder::new()
    //     .name("WriterThread".to_string())
    //     .spawn(move||{
    //         writer_thread(w_rx, w_client, w_kill_tx)
    //     }).unwrap();
    //
    // // Wait for the kill signal
    // match kill_rx.recv() {
    //     Ok(_) => { }
    //     Err(e) => {
    //         println!("Error on kill channel: {}", e);
    //         return -1 as c_int;
    //     }
    // };
    // on_disconnect_handler();
    //
    // // Exit out in standard C fashion
    0 as c_int
}

/// Writes the complete contents of buffer to the server
/// Returns -1 on error
#[no_mangle]
pub extern "C" fn send_to_writer(w_tx: *mut Sender<Vec<u8>>,
                                 buffer: *const c_char,
                                 count: c_int,
                                 k_tx: *mut Sender<()>) -> c_int {
    if count < 1 {
        println!("Error - count must be greater than zero");
        return -1 as c_int;
    }

    let num_elts = count as usize;
    let mut n_buffer = Vec::<u8>::with_capacity(num_elts);
    for x in 0..num_elts as isize {
        unsafe {
            n_buffer.push(ptr::read(buffer.offset(x)) as u8);
        }
    }

    unsafe {
        match (*w_tx).send(n_buffer) {
            Ok(_) => { }
            Err(e) => {
                println!("Error sending buffer: {}", e);
                let _ = (*k_tx).send(());
                return -1 as c_int;
            }
        };
    }

    0 as c_int
}

/// Forever listens to incoming data and when a complete message is received,
/// the passed callback is hit
fn reader_thread(client: Bstream,
                 event_handler: extern fn(*const c_char, c_int),
                 kill_tx: Sender<()>) {

    let mut reader = client.clone();
    loop {
        match reader.read() {
            Ok(buffer) => {
                // Launch the handler in a new thread
                thread::Builder::new()
                    .name("Reader-Worker".to_string())
                    .spawn(move||{
                        let slice = &buffer[..];
                        let c_buffer = CString::new(slice).unwrap();
                        event_handler(c_buffer.as_ptr(), buffer.len() as c_int);
                    }).unwrap();
            }
            Err(e) => {
                println!("Error: {}", e);
                break;
            }
        };
    }
    println!("Reader thread finished");
    let _ = kill_tx.send(());
}

/// Forever listens to Receiver<Vec<u8>> waiting on messages to come in
/// Once available, blocks until the entire message has been written
fn writer_thread(rx: Receiver<Vec<u8>>, client: Bstream, kill_tx: Sender<()>) {
    let mut writer = client.clone();
    loop {
        match rx.recv() {
            Ok(ref mut buffer) => {
                match writer.write(buffer) {
                    Ok(_) => { }
                    Err(e) => {
                        println!("Error: {}", e);
                        break;
                    }
                };
            }
            Err(e) => {
                println!("Error: {}", e);
                break;
            }
        };
    }
    println!("Writer thread finished");
    let _ = kill_tx.send(());
}
<commit_msg>I think I'm finally close<commit_after>// Copyright 2015 Nathan Sizemore <nathanrsizemore@gmail.com>
//
// 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/.
//
// This Source Code Form is "Incompatible
// With Secondary Licenses", as defined by
// the Mozilla Public License, v. 2.0.

use std::{str, ptr, mem};
use std::ffi::{CStr, CString};
use std::thread;
use std::net::TcpStream;
use std::sync::mpsc::{channel, Sender, Receiver};

use super::libc::{c_int, c_char, c_void};
use super::simple_stream::bstream::Bstream;

extern "C" {
    fn register_writer_tx(tx: *mut c_void);
    fn register_stop_tx(tx: *mut c_void);
}

#[no_mangle]
pub extern "C" fn start(address: *const c_char,
    handler: extern fn(*const c_char, c_int),
    on_connect_handler: extern fn(),
    on_disconnect_handler: extern fn()) -> c_int {

    println!("Rust - start()");

    let mut r_address;
    unsafe {
        r_address = CStr::from_ptr(address);
    }
    let s_address = r_address.to_bytes();
    let host_address = match str::from_utf8(s_address) {
        Ok(safe_str) => safe_str,
        Err(_) => {
            println!("Invalid host address");
            return -1 as c_int;
        }
    };

    println!("Rust - address: {}", host_address);

    // Create and register a way to kill this client
    let (k_tx, kill_rx): (Sender<()>, Receiver<()>) = channel();
    let kill_tx = k_tx.clone();
    let mut k_tx_ptr = Box::new(k_tx);

    println!("calling register_stop_tx");

    let mut k_tx_ptr_clone = k_tx_ptr.clone();
    unsafe {
        let mut k_tx_as_void_ptr: *mut c_void = mem::transmute(k_tx_ptr_clone);
        register_stop_tx(&mut *k_tx_as_void_ptr);
    }
    //
    // // Writer thread's channel
    // let (w_tx, w_rx): (Sender<Vec<u8>>, Receiver<Vec<u8>>) = channel();
    // let mut w_tx_ptr = Box::new(w_tx);
    // unsafe {
    //     register_writer_tx(&mut *w_tx_ptr);
    // }
    //
    // println!("Attempting connect to: {}", host_address);
    //
    // let result = TcpStream::connect(host_address);
    // if result.is_err() {
    //     println!("Error connecting to {} - {}", host_address, result.unwrap_err());
    //     return -1 as c_int;
    // }
    // println!("Connected");
    // on_connect_handler();
    //
    // let stream = result.unwrap();
    // let client = Bstream::new(stream);
    //
    // let r_client = client.clone();
    // let r_kill_tx = kill_tx.clone();
    //
    // let w_client = client.clone();
    // let w_kill_tx = kill_tx.clone();
    //
    // // Start the reader thread
    // thread::Builder::new()
    //     .name("ReaderThread".to_string())
    //     .spawn(move||{
    //         reader_thread(r_client, handler, r_kill_tx)
    //     }).unwrap();
    //
    // // Start the writer thread
    // thread::Builder::new()
    //     .name("WriterThread".to_string())
    //     .spawn(move||{
    //         writer_thread(w_rx, w_client, w_kill_tx)
    //     }).unwrap();
    //
    // // Wait for the kill signal
    // match kill_rx.recv() {
    //     Ok(_) => { }
    //     Err(e) => {
    //         println!("Error on kill channel: {}", e);
    //         return -1 as c_int;
    //     }
    // };
    // on_disconnect_handler();
    //
    // // Exit out in standard C fashion
    0 as c_int
}

/// Writes the complete contents of buffer to the server
/// Returns -1 on error
#[no_mangle]
pub extern "C" fn send_to_writer(w_tx: *mut Sender<Vec<u8>>,
                                 buffer: *const c_char,
                                 count: c_int,
                                 k_tx: *mut Sender<()>) -> c_int {
    if count < 1 {
        println!("Error - count must be greater than zero");
        return -1 as c_int;
    }

    let num_elts = count as usize;
    let mut n_buffer = Vec::<u8>::with_capacity(num_elts);
    for x in 0..num_elts as isize {
        unsafe {
            n_buffer.push(ptr::read(buffer.offset(x)) as u8);
        }
    }

    unsafe {
        match (*w_tx).send(n_buffer) {
            Ok(_) => { }
            Err(e) => {
                println!("Error sending buffer: {}", e);
                let _ = (*k_tx).send(());
                return -1 as c_int;
            }
        };
    }

    0 as c_int
}

/// Forever listens to incoming data and when a complete message is received,
/// the passed callback is hit
fn reader_thread(client: Bstream,
                 event_handler: extern fn(*const c_char, c_int),
                 kill_tx: Sender<()>) {

    let mut reader = client.clone();
    loop {
        match reader.read() {
            Ok(buffer) => {
                // Launch the handler in a new thread
                thread::Builder::new()
                    .name("Reader-Worker".to_string())
                    .spawn(move||{
                        let slice = &buffer[..];
                        let c_buffer = CString::new(slice).unwrap();
                        event_handler(c_buffer.as_ptr(), buffer.len() as c_int);
                    }).unwrap();
            }
            Err(e) => {
                println!("Error: {}", e);
                break;
            }
        };
    }
    println!("Reader thread finished");
    let _ = kill_tx.send(());
}

/// Forever listens to Receiver<Vec<u8>> waiting on messages to come in
/// Once available, blocks until the entire message has been written
fn writer_thread(rx: Receiver<Vec<u8>>, client: Bstream, kill_tx: Sender<()>) {
    let mut writer = client.clone();
    loop {
        match rx.recv() {
            Ok(ref mut buffer) => {
                match writer.write(buffer) {
                    Ok(_) => { }
                    Err(e) => {
                        println!("Error: {}", e);
                        break;
                    }
                };
            }
            Err(e) => {
                println!("Error: {}", e);
                break;
            }
        };
    }
    println!("Writer thread finished");
    let _ = kill_tx.send(());
}
<|endoftext|>


<commit_before>use super::{ParserError, ParserResult};
use super::primitives::{PrimitiveIterator, U1, U2, U4};

use std::ops::Deref;
use std::rc::Rc;

macro_rules! generate_constant_pool_retrieval_method {
    ($variant_name:ident, $struct_name:ident, $method_name:ident) => {
        pub fn $method_name(index: usize,
                            constant_pool: &Vec<ConstantPoolItem>)
            -> ParserResult<Rc<$struct_name>> {
                let actual_index = ConstantPoolItem::shift_index(index);

                if let Some(item) = constant_pool.get(actual_index) {
                    match item {
                        &ConstantPoolItem::$variant_name(ref val) => return Ok(val.clone()),
                        item @ _ => {
                            let name = item.to_friendly_name();
                            return Err(ParserError::UnexpectedConstantPoolItem(name))
                        }
                    }
                }

                Err(ParserError::ConstantPoolIndexOutOfBounds(actual_index))
            }

    }
}

#[derive(Debug)]
pub struct ClassInfo {
    pub tag: U1,
    pub name_index: U2,
}

#[derive(Debug)]
pub struct FieldOrMethodOrInterfaceMethodInfo {
    pub tag: U1,
    pub class_index: U2,
    pub name_and_type_index: U2,
}

#[derive(Debug)]
pub struct StringInfo {
    pub tag: U1,
    pub string_index: U2,
}

#[derive(Debug)]
pub struct NameAndTypeInfo {
    pub tag: U1,
    pub name_index: U2,
    pub descriptor_index: U2,
}

#[derive(Debug)]
pub struct Utf8Info {
    pub tag: U1,
    pub length: U2,
    pub value: String,
}

impl Deref for Utf8Info {
    type Target = str;

    fn deref(&self) -> &str {
        &self.value
    }
}

#[derive(Debug)]
pub enum ConstantPoolItem {
    Class(Rc<ClassInfo>),
    FieldOrMethodOrInterfaceMethod(Rc<FieldOrMethodOrInterfaceMethodInfo>),
    String(Rc<StringInfo>),
    IntegerOrFloat { tag: U1, bytes: U1 },
    LongOrDouble {
        tag: U1,
        high_bytes: U4,
        low_bytes: U4,
    },
    NameAndType(Rc<NameAndTypeInfo>),
    Utf8(Rc<Utf8Info>),
    MethodHandle {
        tag: U1,
        reference_kind: U1,
        reference_index: U2,
    },
    MethodType { tag: U1, descriptor_index: U2 },
    InvokeDynamic {
        tag: U1,
        bootstrap_method_attr_index: U2,
        name_and_type_index: U2,
    },
}

impl ConstantPoolItem {
    pub fn from<T: PrimitiveIterator>(iter: &mut T) -> ParserResult<ConstantPoolItem> {
        let tag = try!(iter.next_u1());

        match tag {
            1 => {
                let length = try!(iter.next_u2());

                let mut byte_vec = vec![];
                for _ in 0..length {
                    byte_vec.push(try!(iter.next_u1()));
                }

                let value = try!(String::from_utf8(byte_vec));

                Ok(ConstantPoolItem::Utf8(Rc::new(Utf8Info {
                    tag: tag,
                    length: length,
                    value: value,
                })))
            }
            7 => {
                Ok(ConstantPoolItem::Class(Rc::new(ClassInfo {
                    tag: tag,
                    name_index: try!(iter.next_u2()),
                })))
            }
            8 => {
                Ok(ConstantPoolItem::String(Rc::new(StringInfo {
                    tag: tag,
                    string_index: try!(iter.next_u2()),
                })))
            }
            9 | 10 | 11 => {
                Ok(ConstantPoolItem::FieldOrMethodOrInterfaceMethod(
                        Rc::new(FieldOrMethodOrInterfaceMethodInfo {
                    tag: tag,
                    class_index: try!(iter.next_u2()),
                    name_and_type_index: try!(iter.next_u2()),
                })))
            }
            12 => {
                Ok(ConstantPoolItem::NameAndType(Rc::new(NameAndTypeInfo {
                    tag: tag,
                    name_index: try!(iter.next_u2()),
                    descriptor_index: try!(iter.next_u2()),
                })))
            }
            _ => Err(ParserError::UnknownConstantPoolTag(tag)),
        }
    }

    #[cfg_attr(rustfmt, rustfmt_skip)]
    pub fn to_friendly_name(&self) -> &'static str {
        match self {
            &ConstantPoolItem::Utf8(..) => "Utf8",
            &ConstantPoolItem::Class(..) => "Class",
            &ConstantPoolItem::String(..) => "String",
            &ConstantPoolItem::FieldOrMethodOrInterfaceMethod(..) =>
                "Field|Method|InterfaceMethod",
                &ConstantPoolItem::NameAndType(..) => "NameAndType",
                _ => "Not yet implemented",
        }
    }

    pub fn retrieve_item(index: usize,
                         constant_pool: &Vec<ConstantPoolItem>)
                         -> ParserResult<&ConstantPoolItem> {
        let actual_index = Self::shift_index(index);
        return constant_pool.get(index)
            .ok_or(ParserError::ConstantPoolIndexOutOfBounds(actual_index));
    }

    generate_constant_pool_retrieval_method!(Class, ClassInfo, retrieve_class_info);
    generate_constant_pool_retrieval_method!(Utf8, Utf8Info, retrieve_utf8_info);

    // TODO: Split the enum/struct into seperate variants
    generate_constant_pool_retrieval_method!(FieldOrMethodOrInterfaceMethod,
                                             FieldOrMethodOrInterfaceMethodInfo,
                                             retrieve_field_info);
    generate_constant_pool_retrieval_method!(FieldOrMethodOrInterfaceMethod,
                                             FieldOrMethodOrInterfaceMethodInfo,
                                             retrieve_method_info);
    generate_constant_pool_retrieval_method!(FieldOrMethodOrInterfaceMethod,
                                             FieldOrMethodOrInterfaceMethodInfo,
                                             retrieve_interface_method_info);

    generate_constant_pool_retrieval_method!(String, StringInfo, retrieve_string_info);
    generate_constant_pool_retrieval_method!(NameAndType,
                                             NameAndTypeInfo,
                                             retrieve_name_and_type_info);

    fn shift_index(unshifted_index: usize) -> usize {
        unshifted_index - 1 // references to the constant pool start from one
    }
}

#[derive(Debug)]
pub struct CodeAttribute {
    pub attribute_name: Rc<Utf8Info>,
    pub max_stack: U2,
    pub max_locals: U2,
    pub code_length: U4,
    pub code: Vec<U1>,
    pub exception_table_length: U2,
    pub exception_table: Vec<ExceptionHandler>,
    pub attributes_count: U2,
    pub attributes: Vec<Attribute>,
}

impl CodeAttribute {
    pub fn from<T: PrimitiveIterator>(attribute_name: Rc<Utf8Info>,
                                      iter: &mut T,
                                      constant_pool: &Vec<ConstantPoolItem>)
                                      -> ParserResult<CodeAttribute> {
        let max_stack = try!(iter.next_u2());
        let max_locals = try!(iter.next_u2());

        let code_length = try!(iter.next_u4());
        let mut code = vec![];
        for _ in 0..code_length {
            code.push(try!(iter.next_u1()));
        }

        let exception_table_length = try!(iter.next_u2());
        let mut exception_table = vec![];
        for _ in 0..exception_table_length {
            exception_table.push(try!(ExceptionHandler::from(iter)));
        }

        let attributes_count = try!(iter.next_u2());
        let mut attributes = vec![];
        for _ in 0..attributes_count {
            attributes.push(try!(Attribute::from(iter, constant_pool)));
        }

        Ok(CodeAttribute {
            attribute_name: attribute_name,
            max_stack: max_stack,
            max_locals: max_locals,
            code_length: code_length,
            code: code,
            exception_table_length: exception_table_length,
            exception_table: exception_table,
            attributes_count: attributes_count,
            attributes: attributes,
        })
    }
}

#[derive(Debug)]
pub struct ExceptionHandler {
    pub start_pc: U2,
    pub end_pc: U2,
    pub handler_pc: U2,
    pub catch_type: U2,
}

impl ExceptionHandler {
    pub fn from<T: PrimitiveIterator>(iter: &mut T) -> ParserResult<ExceptionHandler> {
        let start_pc = try!(iter.next_u2());
        let end_pc = try!(iter.next_u2());
        let handler_pc = try!(iter.next_u2());
        let catch_type = try!(iter.next_u2());

        Ok(ExceptionHandler {
            start_pc: start_pc,
            end_pc: end_pc,
            handler_pc: handler_pc,
            catch_type: catch_type,
        })
    }
}

#[derive(Debug)]
pub enum Attribute {
    Code(Rc<CodeAttribute>),
    Unknown {
        attribute_name: Rc<Utf8Info>,
        info: Vec<U1>,
    },
}

impl Attribute {
    pub fn from<T: PrimitiveIterator>(iter: &mut T,
                                      constant_pool: &Vec<ConstantPoolItem>)
                                      -> ParserResult<Attribute> {
        let attribute_name_index = try!(iter.next_u2());
        let attribute_name =
            try!(ConstantPoolItem::retrieve_utf8_info(attribute_name_index as usize,
                                                      constant_pool));

        let attribute_length = try!(iter.next_u4());

        match &**attribute_name {
            "Code" => {
                Ok(Attribute::Code(Rc::new(try!(CodeAttribute::from(attribute_name,
                                                                    iter,
                                                                    constant_pool)))))
            }
            _ => {
                let mut info = vec![];
                for _ in 0..attribute_length {
                    info.push(try!(iter.next_u1()));
                }

                Ok(Attribute::Unknown {
                    attribute_name: attribute_name,
                    info: info,
                })
            }
        }
    }
}

macro_rules! generate_method_or_field_parser_impl {
    ($impl_name:ident) => {
        impl $impl_name {
            pub fn from<T: PrimitiveIterator>(iter: &mut T,
                                              constant_pool: &Vec<ConstantPoolItem>)
                -> ParserResult<$impl_name> {
                    let access_flags = try!(iter.next_u2());

                    let name_index = try!(iter.next_u2());
                    let name = try!(ConstantPoolItem::retrieve_utf8_info(name_index as usize,
                                                                         constant_pool));

                    let descriptor_index = try!(iter.next_u2());
                    let descriptor = try!(ConstantPoolItem::retrieve_utf8_info(
                            descriptor_index as usize,
                            constant_pool));

                    let attributes_count = try!(iter.next_u2());
                    let mut attributes = vec![];
                    for _ in 0..attributes_count {
                        attributes.push(Rc::new(try!(Attribute::from(iter, constant_pool))));
                    }

                    Ok($impl_name {
                        access_flags: access_flags,
                        name: name,
                        descriptor: descriptor,
                        attributes_count: attributes_count,
                        attributes: attributes,
                    })

                }
        }
    }
}

#[derive(Debug)]
pub struct Field {
    pub access_flags: U2,
    pub name: Rc<Utf8Info>,
    pub descriptor: Rc<Utf8Info>,
    pub attributes_count: U2,
    pub attributes: Vec<Rc<Attribute>>,
}

#[derive(Debug)]
pub struct Method {
    pub access_flags: U2,
    pub name: Rc<Utf8Info>,
    pub descriptor: Rc<Utf8Info>,
    pub attributes_count: U2,
    pub attributes: Vec<Rc<Attribute>>,
}

generate_method_or_field_parser_impl!(Field);
generate_method_or_field_parser_impl!(Method);

pub struct AccessFlags;

impl AccessFlags {
    pub fn is_public(access_flags: U2) -> bool {
        (access_flags & 0x0001) != 0
    }

    pub fn is_private(access_flags: U2) -> bool {
        (access_flags & 0x0002) != 0
    }

    pub fn is_protected(access_flags: U2) -> bool {
        (access_flags & 0x0004) != 0
    }

    pub fn is_static(access_flags: U2) -> bool {
        (access_flags & 0x0008) != 0
    }

    pub fn is_final(access_flags: U2) -> bool {
        (access_flags & 0x0010) != 0
    }

    pub fn is_super(access_flags: U2) -> bool {
        (access_flags & 0x0020) != 0
    }

    pub fn is_volatile(access_flags: U2) -> bool {
        (access_flags & 0x0040) != 0
    }

    pub fn is_bridge(access_flags: U2) -> bool {
        (access_flags & 0x0040) != 0
    }

    pub fn is_transient(access_flags: U2) -> bool {
        (access_flags & 0x0080) != 0
    }

    pub fn is_varargs(access_flags: U2) -> bool {
        (access_flags & 0x0080) != 0
    }

    pub fn is_native(access_flags: U2) -> bool {
        (access_flags & 0x0100) != 0
    }

    pub fn is_interface(access_flags: U2) -> bool {
        (access_flags & 0x0200) != 0
    }

    pub fn is_abstract(access_flags: U2) -> bool {
        (access_flags & 0x0400) != 0
    }

    pub fn is_strict(access_flags: U2) -> bool {
        (access_flags & 0x0800) != 0
    }

    pub fn is_synthetic(access_flags: U2) -> bool {
        (access_flags & 0x1000) != 0
    }

    pub fn is_annotation(access_flags: U2) -> bool {
        (access_flags & 0x2000) != 0
    }

    pub fn is_enum(access_flags: U2) -> bool {
        (access_flags & 0x4000) != 0
    }
}
<commit_msg>Forgot to use the shifted index<commit_after>use super::{ParserError, ParserResult};
use super::primitives::{PrimitiveIterator, U1, U2, U4};

use std::ops::Deref;
use std::rc::Rc;

macro_rules! generate_constant_pool_retrieval_method {
    ($variant_name:ident, $struct_name:ident, $method_name:ident) => {
        pub fn $method_name(index: usize,
                            constant_pool: &Vec<ConstantPoolItem>)
            -> ParserResult<Rc<$struct_name>> {
                let actual_index = ConstantPoolItem::shift_index(index);

                if let Some(item) = constant_pool.get(actual_index) {
                    match item {
                        &ConstantPoolItem::$variant_name(ref val) => return Ok(val.clone()),
                        item @ _ => {
                            let name = item.to_friendly_name();
                            return Err(ParserError::UnexpectedConstantPoolItem(name))
                        }
                    }
                }

                Err(ParserError::ConstantPoolIndexOutOfBounds(actual_index))
            }

    }
}

#[derive(Debug)]
pub struct ClassInfo {
    pub tag: U1,
    pub name_index: U2,
}

#[derive(Debug)]
pub struct FieldOrMethodOrInterfaceMethodInfo {
    pub tag: U1,
    pub class_index: U2,
    pub name_and_type_index: U2,
}

#[derive(Debug)]
pub struct StringInfo {
    pub tag: U1,
    pub string_index: U2,
}

#[derive(Debug)]
pub struct NameAndTypeInfo {
    pub tag: U1,
    pub name_index: U2,
    pub descriptor_index: U2,
}

#[derive(Debug)]
pub struct Utf8Info {
    pub tag: U1,
    pub length: U2,
    pub value: String,
}

impl Deref for Utf8Info {
    type Target = str;

    fn deref(&self) -> &str {
        &self.value
    }
}

#[derive(Debug)]
pub enum ConstantPoolItem {
    Class(Rc<ClassInfo>),
    FieldOrMethodOrInterfaceMethod(Rc<FieldOrMethodOrInterfaceMethodInfo>),
    String(Rc<StringInfo>),
    IntegerOrFloat { tag: U1, bytes: U1 },
    LongOrDouble {
        tag: U1,
        high_bytes: U4,
        low_bytes: U4,
    },
    NameAndType(Rc<NameAndTypeInfo>),
    Utf8(Rc<Utf8Info>),
    MethodHandle {
        tag: U1,
        reference_kind: U1,
        reference_index: U2,
    },
    MethodType { tag: U1, descriptor_index: U2 },
    InvokeDynamic {
        tag: U1,
        bootstrap_method_attr_index: U2,
        name_and_type_index: U2,
    },
}

impl ConstantPoolItem {
    pub fn from<T: PrimitiveIterator>(iter: &mut T) -> ParserResult<ConstantPoolItem> {
        let tag = try!(iter.next_u1());

        match tag {
            1 => {
                let length = try!(iter.next_u2());

                let mut byte_vec = vec![];
                for _ in 0..length {
                    byte_vec.push(try!(iter.next_u1()));
                }

                let value = try!(String::from_utf8(byte_vec));

                Ok(ConstantPoolItem::Utf8(Rc::new(Utf8Info {
                    tag: tag,
                    length: length,
                    value: value,
                })))
            }
            7 => {
                Ok(ConstantPoolItem::Class(Rc::new(ClassInfo {
                    tag: tag,
                    name_index: try!(iter.next_u2()),
                })))
            }
            8 => {
                Ok(ConstantPoolItem::String(Rc::new(StringInfo {
                    tag: tag,
                    string_index: try!(iter.next_u2()),
                })))
            }
            9 | 10 | 11 => {
                Ok(ConstantPoolItem::FieldOrMethodOrInterfaceMethod(
                        Rc::new(FieldOrMethodOrInterfaceMethodInfo {
                    tag: tag,
                    class_index: try!(iter.next_u2()),
                    name_and_type_index: try!(iter.next_u2()),
                })))
            }
            12 => {
                Ok(ConstantPoolItem::NameAndType(Rc::new(NameAndTypeInfo {
                    tag: tag,
                    name_index: try!(iter.next_u2()),
                    descriptor_index: try!(iter.next_u2()),
                })))
            }
            _ => Err(ParserError::UnknownConstantPoolTag(tag)),
        }
    }

    #[cfg_attr(rustfmt, rustfmt_skip)]
    pub fn to_friendly_name(&self) -> &'static str {
        match self {
            &ConstantPoolItem::Utf8(..) => "Utf8",
            &ConstantPoolItem::Class(..) => "Class",
            &ConstantPoolItem::String(..) => "String",
            &ConstantPoolItem::FieldOrMethodOrInterfaceMethod(..) =>
                "Field|Method|InterfaceMethod",
                &ConstantPoolItem::NameAndType(..) => "NameAndType",
                _ => "Not yet implemented",
        }
    }

    pub fn retrieve_item(index: usize,
                         constant_pool: &Vec<ConstantPoolItem>)
                         -> ParserResult<&ConstantPoolItem> {
        let actual_index = Self::shift_index(index);
        return constant_pool.get(actual_index)
            .ok_or(ParserError::ConstantPoolIndexOutOfBounds(actual_index));
    }

    generate_constant_pool_retrieval_method!(Class, ClassInfo, retrieve_class_info);
    generate_constant_pool_retrieval_method!(Utf8, Utf8Info, retrieve_utf8_info);

    // TODO: Split the enum/struct into seperate variants
    generate_constant_pool_retrieval_method!(FieldOrMethodOrInterfaceMethod,
                                             FieldOrMethodOrInterfaceMethodInfo,
                                             retrieve_field_info);
    generate_constant_pool_retrieval_method!(FieldOrMethodOrInterfaceMethod,
                                             FieldOrMethodOrInterfaceMethodInfo,
                                             retrieve_method_info);
    generate_constant_pool_retrieval_method!(FieldOrMethodOrInterfaceMethod,
                                             FieldOrMethodOrInterfaceMethodInfo,
                                             retrieve_interface_method_info);

    generate_constant_pool_retrieval_method!(String, StringInfo, retrieve_string_info);
    generate_constant_pool_retrieval_method!(NameAndType,
                                             NameAndTypeInfo,
                                             retrieve_name_and_type_info);

    fn shift_index(unshifted_index: usize) -> usize {
        unshifted_index - 1 // references to the constant pool start from one
    }
}

#[derive(Debug)]
pub struct CodeAttribute {
    pub attribute_name: Rc<Utf8Info>,
    pub max_stack: U2,
    pub max_locals: U2,
    pub code_length: U4,
    pub code: Vec<U1>,
    pub exception_table_length: U2,
    pub exception_table: Vec<ExceptionHandler>,
    pub attributes_count: U2,
    pub attributes: Vec<Attribute>,
}

impl CodeAttribute {
    pub fn from<T: PrimitiveIterator>(attribute_name: Rc<Utf8Info>,
                                      iter: &mut T,
                                      constant_pool: &Vec<ConstantPoolItem>)
                                      -> ParserResult<CodeAttribute> {
        let max_stack = try!(iter.next_u2());
        let max_locals = try!(iter.next_u2());

        let code_length = try!(iter.next_u4());
        let mut code = vec![];
        for _ in 0..code_length {
            code.push(try!(iter.next_u1()));
        }

        let exception_table_length = try!(iter.next_u2());
        let mut exception_table = vec![];
        for _ in 0..exception_table_length {
            exception_table.push(try!(ExceptionHandler::from(iter)));
        }

        let attributes_count = try!(iter.next_u2());
        let mut attributes = vec![];
        for _ in 0..attributes_count {
            attributes.push(try!(Attribute::from(iter, constant_pool)));
        }

        Ok(CodeAttribute {
            attribute_name: attribute_name,
            max_stack: max_stack,
            max_locals: max_locals,
            code_length: code_length,
            code: code,
            exception_table_length: exception_table_length,
            exception_table: exception_table,
            attributes_count: attributes_count,
            attributes: attributes,
        })
    }
}

#[derive(Debug)]
pub struct ExceptionHandler {
    pub start_pc: U2,
    pub end_pc: U2,
    pub handler_pc: U2,
    pub catch_type: U2,
}

impl ExceptionHandler {
    pub fn from<T: PrimitiveIterator>(iter: &mut T) -> ParserResult<ExceptionHandler> {
        let start_pc = try!(iter.next_u2());
        let end_pc = try!(iter.next_u2());
        let handler_pc = try!(iter.next_u2());
        let catch_type = try!(iter.next_u2());

        Ok(ExceptionHandler {
            start_pc: start_pc,
            end_pc: end_pc,
            handler_pc: handler_pc,
            catch_type: catch_type,
        })
    }
}

#[derive(Debug)]
pub enum Attribute {
    Code(Rc<CodeAttribute>),
    Unknown {
        attribute_name: Rc<Utf8Info>,
        info: Vec<U1>,
    },
}

impl Attribute {
    pub fn from<T: PrimitiveIterator>(iter: &mut T,
                                      constant_pool: &Vec<ConstantPoolItem>)
                                      -> ParserResult<Attribute> {
        let attribute_name_index = try!(iter.next_u2());
        let attribute_name =
            try!(ConstantPoolItem::retrieve_utf8_info(attribute_name_index as usize,
                                                      constant_pool));

        let attribute_length = try!(iter.next_u4());

        match &**attribute_name {
            "Code" => {
                Ok(Attribute::Code(Rc::new(try!(CodeAttribute::from(attribute_name,
                                                                    iter,
                                                                    constant_pool)))))
            }
            _ => {
                let mut info = vec![];
                for _ in 0..attribute_length {
                    info.push(try!(iter.next_u1()));
                }

                Ok(Attribute::Unknown {
                    attribute_name: attribute_name,
                    info: info,
                })
            }
        }
    }
}

macro_rules! generate_method_or_field_parser_impl {
    ($impl_name:ident) => {
        impl $impl_name {
            pub fn from<T: PrimitiveIterator>(iter: &mut T,
                                              constant_pool: &Vec<ConstantPoolItem>)
                -> ParserResult<$impl_name> {
                    let access_flags = try!(iter.next_u2());

                    let name_index = try!(iter.next_u2());
                    let name = try!(ConstantPoolItem::retrieve_utf8_info(name_index as usize,
                                                                         constant_pool));

                    let descriptor_index = try!(iter.next_u2());
                    let descriptor = try!(ConstantPoolItem::retrieve_utf8_info(
                            descriptor_index as usize,
                            constant_pool));

                    let attributes_count = try!(iter.next_u2());
                    let mut attributes = vec![];
                    for _ in 0..attributes_count {
                        attributes.push(Rc::new(try!(Attribute::from(iter, constant_pool))));
                    }

                    Ok($impl_name {
                        access_flags: access_flags,
                        name: name,
                        descriptor: descriptor,
                        attributes_count: attributes_count,
                        attributes: attributes,
                    })

                }
        }
    }
}

#[derive(Debug)]
pub struct Field {
    pub access_flags: U2,
    pub name: Rc<Utf8Info>,
    pub descriptor: Rc<Utf8Info>,
    pub attributes_count: U2,
    pub attributes: Vec<Rc<Attribute>>,
}

#[derive(Debug)]
pub struct Method {
    pub access_flags: U2,
    pub name: Rc<Utf8Info>,
    pub descriptor: Rc<Utf8Info>,
    pub attributes_count: U2,
    pub attributes: Vec<Rc<Attribute>>,
}

generate_method_or_field_parser_impl!(Field);
generate_method_or_field_parser_impl!(Method);

pub struct AccessFlags;

impl AccessFlags {
    pub fn is_public(access_flags: U2) -> bool {
        (access_flags & 0x0001) != 0
    }

    pub fn is_private(access_flags: U2) -> bool {
        (access_flags & 0x0002) != 0
    }

    pub fn is_protected(access_flags: U2) -> bool {
        (access_flags & 0x0004) != 0
    }

    pub fn is_static(access_flags: U2) -> bool {
        (access_flags & 0x0008) != 0
    }

    pub fn is_final(access_flags: U2) -> bool {
        (access_flags & 0x0010) != 0
    }

    pub fn is_super(access_flags: U2) -> bool {
        (access_flags & 0x0020) != 0
    }

    pub fn is_volatile(access_flags: U2) -> bool {
        (access_flags & 0x0040) != 0
    }

    pub fn is_bridge(access_flags: U2) -> bool {
        (access_flags & 0x0040) != 0
    }

    pub fn is_transient(access_flags: U2) -> bool {
        (access_flags & 0x0080) != 0
    }

    pub fn is_varargs(access_flags: U2) -> bool {
        (access_flags & 0x0080) != 0
    }

    pub fn is_native(access_flags: U2) -> bool {
        (access_flags & 0x0100) != 0
    }

    pub fn is_interface(access_flags: U2) -> bool {
        (access_flags & 0x0200) != 0
    }

    pub fn is_abstract(access_flags: U2) -> bool {
        (access_flags & 0x0400) != 0
    }

    pub fn is_strict(access_flags: U2) -> bool {
        (access_flags & 0x0800) != 0
    }

    pub fn is_synthetic(access_flags: U2) -> bool {
        (access_flags & 0x1000) != 0
    }

    pub fn is_annotation(access_flags: U2) -> bool {
        (access_flags & 0x2000) != 0
    }

    pub fn is_enum(access_flags: U2) -> bool {
        (access_flags & 0x4000) != 0
    }
}
<|endoftext|>