<commit_before>// These are taken from http://en.wikipedia.org/wiki/List_of_HTTP_Status_Codes.
// Last updated on 2013-07-04
// Entries from third-party vendors not standardised upon are not included.
// If not specified otherwise, they are defined in RFC 2616.

// Yes, this is ugly code.
// No, I don't mind.
// That was easy. :-)

use std::ascii::StrAsciiExt;
use std::hashmap::HashSet;
use std::either::{Either, Left, Right};
use std::vec;
use super::get_writer;

type HeadingOrStatus = Either<&'static str, Status>;

struct Status {
    code: uint,
    reason: &'static str,
    comment: Option<&'static str>,
}

/// Status with comment
fn StatusC(code: uint, reason: &'static str, comment: &'static str) -> HeadingOrStatus {
    Right(Status { code: code, reason: reason, comment: Some(comment) })
}

/// Status without comment
fn StatusN(code: uint, reason: &'static str) -> HeadingOrStatus {
    Right(Status { code: code, reason: reason, comment: None })
}

impl Status {
    fn ident(&self) -> ~str {
        camel_case(self.reason)
    }

    fn padded_ident(&self) -> ~str {
        self.ident() + " ".repeat(unsafe { longest_ident } - self.ident().len())
    }

    fn reason_padding_spaces(&self) -> ~str {
        " ".repeat(unsafe { longest_reason } - self.reason.len())
    }
}

/// >>> camel_case("I'm a Tea-pot")
/// "ImATeaPot"
fn camel_case(msg: &str) -> ~str {
    let msg = msg.replace("-", " ").replace("'", "");
    let mut result: ~[Ascii] = vec::with_capacity(msg.len());
    let mut capitalise = true;
    for c in msg.iter() {
        let c = match capitalise {
            true => c.to_ascii().to_upper(),
            false => c.to_ascii().to_lower(),
        };
        // For a space, capitalise the next char
        capitalise = c.to_byte() == (' ' as u8);
        if !capitalise {  // And also, for a space, don't keep it
            result.push(c);
        }
    }
    result.to_str_ascii()
}

static mut longest_ident: uint = 0;
static mut longest_reason: uint = 0;

pub fn generate() {
    let out = get_writer("status.rs");
    let entries = [
        Left("1xx Informational"),
        StatusN(100, "Continue"),
        StatusN(101, "Switching Protocols"),
        StatusC(102, "Processing", "WebDAV; RFC 2518"),

        Left("2xx Success"),
        StatusN(200, "OK"),
        StatusN(201, "Created"),
        StatusN(202, "Accepted"),
        StatusC(203, "Non-Authoritative Information", "since HTTP/1.1"),
        StatusN(204, "No Content"),
        StatusN(205, "Reset Content"),
        StatusN(206, "Partial Content"),
        StatusC(207, "Multi-Status", "WebDAV; RFC 4918"),
        StatusC(208, "Already Reported", "WebDAV; RFC 5842"),
        StatusC(226, "IM Used", "RFC 3229"),

        Left("3xx Redirection"),
        StatusN(300, "Multiple Choices"),
        StatusN(301, "Moved Permanently"),
        StatusN(302, "Found"),
        StatusC(303, "See Other", "since HTTP/1.1"),
        StatusN(304, "Not Modified"),
        StatusC(305, "Use Proxy", "since HTTP/1.1"),
        StatusN(306, "Switch Proxy"),
        StatusC(307, "Temporary Redirect", "since HTTP/1.1"),
        StatusC(308, "Permanent Redirect", "approved as experimental RFC: http://tools.ietf.org/html/draft-reschke-http-status-308"),

        Left("4xx Client Error"),
        StatusN(400, "Bad Request"),
        StatusN(401, "Unauthorized"),
        StatusN(402, "Payment Required"),
        StatusN(403, "Forbidden"),
        StatusN(404, "Not Found"),
        StatusN(405, "Method Not Allowed"),
        StatusN(406, "Not Acceptable"),
        StatusN(407, "Proxy Authentication Required"),
        StatusN(408, "Request Timeout"),
        StatusN(409, "Conflict"),
        StatusN(410, "Gone"),
        StatusN(411, "Length Required"),
        StatusN(412, "Precondition Failed"),
        StatusN(413, "Request Entity Too Large"),
        StatusN(414, "Request-URI Too Long"),
        StatusN(415, "Unsupported Media Type"),
        StatusN(416, "Requested Range Not Satisfiable"),
        StatusN(417, "Expectation Failed"),
        StatusC(418, "I'm a teapot", "RFC 2324"),
        StatusN(419, "Authentication Timeout"),
        StatusC(422, "Unprocessable Entity", "WebDAV; RFC 4918"),
        StatusC(423, "Locked", "WebDAV; RFC 4918"),
        StatusC(424, "Failed Dependency", "WebDAV; RFC 4918"),
        StatusC(424, "Method Failure", "WebDAV"),
        StatusC(425, "Unordered Collection", "Internet draft"),
        StatusC(426, "Upgrade Required", "RFC 2817"),
        StatusC(428, "Precondition Required", "RFC 6585"),
        StatusC(429, "Too Many Requests", "RFC 6585"),
        StatusC(431, "Request Header Fields Too Large", "RFC 6585"),
        StatusC(451, "Unavailable For Legal Reasons", "Internet draft"),

        Left("5xx Server Error"),
        StatusN(500, "Internal Server Error"),
        StatusN(501, "Not Implemented"),
        StatusN(502, "Bad Gateway"),
        StatusN(503, "Service Unavailable"),
        StatusN(504, "Gateway Timeout"),
        StatusN(505, "HTTP Version Not Supported"),
        StatusC(506, "Variant Also Negotiates", "RFC 2295"),
        StatusC(507, "Insufficient Storage", "WebDAV; RFC 4918"),
        StatusC(508, "Loop Detected", "WebDAV; RFC 5842"),
        StatusC(510, "Not Extended", "RFC 2774"),
        StatusC(511, "Network Authentication Required", "RFC 6585"),
        ];
    unsafe {
        longest_ident = entries.iter().map(|&e| match e {
            Left(_heading) => 0,
            Right(status) => status.ident().len(),
        }).max_by(|&i| i).unwrap();
        longest_reason = entries.iter().map(|&e| match e {
            Left(_heading) => 0,
            Right(status) => status.reason.len(),
        }).max_by(|&i| i).unwrap();
    }
    out.write_str("// This file is automatically generated file is used as rusthttpserver::status.

use std::num::IntConvertible;
use std::ascii::StrAsciiExt;

/// HTTP status code
#[deriving(Eq)]
pub enum Status {
");
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n    // %s\n", heading)),
            Right(status) => match status.comment {
                None => out.write_str(fmt!("    %s,\n", status.ident())),
                Some(comment) => out.write_str(fmt!("    %s,  // %s\n", status.ident(), comment)),
            },
        }
    }

    out.write_str("
    UnregisteredStatus(u16, ~str),
}

impl Status {

    /// Get the status code
    pub fn code(&self) -> u16 {
        match *self {
");
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n            // %s\n", heading)),
            Right(status) => out.write_str(fmt!("            %s => %u,\n",
                                                status.padded_ident(), status.code)),
        }
    }
    out.write_str("
            UnregisteredStatus(code, _)   => code,
        }
    }

    /// Get the reason phrase
    pub fn reason(&self) -> ~str {
        match *self {
");
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n            // %s\n", heading)),
            Right(status) => out.write_str(fmt!("            %s => ~\"%s\",\n",
                                                status.padded_ident(), status.reason))
        }
    }
    out.write_str("
            UnregisteredStatus(_, ref reason) => (*reason).clone(),
        }
    }

    /// Get a status from the code and reason
    pub fn from_code_and_reason(status: u16, reason: ~str) -> Status {
        let reason_lower = reason.to_ascii_lower();
        match (status, reason_lower.as_slice()) {
");
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n            // %s\n", heading)),
            Right(status) => out.write_str(fmt!("            (%u, \"%s\")%s => %s,\n",
                                                status.code,
                                                status.reason.to_ascii_lower(),
                                                status.reason_padding_spaces(),
                                                status.ident())),
        }
    }
    out.write_str("
            (_, _) => UnregisteredStatus(status, reason),
        }
    }
}

impl ToStr for Status {
    /// Produce the HTTP status message incorporating both code and message,
    /// e.g. `ImATeapot.to_str() == \"418 I'm a teapot\"`
	fn to_str(&self) -> ~str {
		fmt!(\"%? %s\", self.code(), self.reason())
	}
}

impl IntConvertible for Status {

    /// Equivalent to `self.code()`
    fn to_int(&self) -> int {
        self.code() as int
    }

    /// Get a *registered* status code from the number of its status code.
    ///
    /// This will fail if an unknown code is passed.
    ///
    /// For example, `from_int(200)` will return `OK`.
    fn from_int(n: int) -> Status {
        match n {
");
    let mut matched_numbers = HashSet::new();
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n            // %s\n", heading)),
            Right(status) => {
                if !matched_numbers.contains(&status.code) {
                    // Purpose: FailedDependency and MethodFailure both use 424,
                    // but clearly they mustn't both go in here
                    out.write_str(fmt!("            %u => %s,\n", status.code, status.ident()));
                    matched_numbers.insert(status.code);
                }
            },
        }
    }
    out.write_str("
            _   => { fail!(fmt!(\"No registered HTTP status code %d\", n)); }
        }
    }
}");
}
<commit_msg>Fix HTTP status code output.<commit_after>// These are taken from http://en.wikipedia.org/wiki/List_of_HTTP_Status_Codes.
// Last updated on 2013-07-04
// Entries from third-party vendors not standardised upon are not included.
// If not specified otherwise, they are defined in RFC 2616.

// Yes, this is ugly code.
// No, I don't mind.
// That was easy. :-)

use std::ascii::StrAsciiExt;
use std::hashmap::HashSet;
use std::either::{Either, Left, Right};
use std::vec;
use super::get_writer;

type HeadingOrStatus = Either<&'static str, Status>;

struct Status {
    code: uint,
    reason: &'static str,
    comment: Option<&'static str>,
}

/// Status with comment
fn StatusC(code: uint, reason: &'static str, comment: &'static str) -> HeadingOrStatus {
    Right(Status { code: code, reason: reason, comment: Some(comment) })
}

/// Status without comment
fn StatusN(code: uint, reason: &'static str) -> HeadingOrStatus {
    Right(Status { code: code, reason: reason, comment: None })
}

impl Status {
    fn ident(&self) -> ~str {
        camel_case(self.reason)
    }

    fn padded_ident(&self) -> ~str {
        self.ident() + " ".repeat(unsafe { longest_ident } - self.ident().len())
    }

    fn reason_padding_spaces(&self) -> ~str {
        " ".repeat(unsafe { longest_reason } - self.reason.len())
    }
}

/// >>> camel_case("I'm a Tea-pot")
/// "ImATeaPot"
fn camel_case(msg: &str) -> ~str {
    let msg = msg.replace("-", " ").replace("'", "");
    let mut result: ~[Ascii] = vec::with_capacity(msg.len());
    let mut capitalise = true;
    for c in msg.iter() {
        let c = match capitalise {
            true => c.to_ascii().to_upper(),
            false => c.to_ascii().to_lower(),
        };
        // For a space, capitalise the next char
        capitalise = c.to_byte() == (' ' as u8);
        if !capitalise {  // And also, for a space, don't keep it
            result.push(c);
        }
    }
    result.to_str_ascii()
}

static mut longest_ident: uint = 0;
static mut longest_reason: uint = 0;

pub fn generate() {
    let out = get_writer("status.rs");
    let entries = [
        Left("1xx Informational"),
        StatusN(100, "Continue"),
        StatusN(101, "Switching Protocols"),
        StatusC(102, "Processing", "WebDAV; RFC 2518"),

        Left("2xx Success"),
        StatusN(200, "OK"),
        StatusN(201, "Created"),
        StatusN(202, "Accepted"),
        StatusC(203, "Non-Authoritative Information", "since HTTP/1.1"),
        StatusN(204, "No Content"),
        StatusN(205, "Reset Content"),
        StatusN(206, "Partial Content"),
        StatusC(207, "Multi-Status", "WebDAV; RFC 4918"),
        StatusC(208, "Already Reported", "WebDAV; RFC 5842"),
        StatusC(226, "IM Used", "RFC 3229"),

        Left("3xx Redirection"),
        StatusN(300, "Multiple Choices"),
        StatusN(301, "Moved Permanently"),
        StatusN(302, "Found"),
        StatusC(303, "See Other", "since HTTP/1.1"),
        StatusN(304, "Not Modified"),
        StatusC(305, "Use Proxy", "since HTTP/1.1"),
        StatusN(306, "Switch Proxy"),
        StatusC(307, "Temporary Redirect", "since HTTP/1.1"),
        StatusC(308, "Permanent Redirect", "approved as experimental RFC: http://tools.ietf.org/html/draft-reschke-http-status-308"),

        Left("4xx Client Error"),
        StatusN(400, "Bad Request"),
        StatusN(401, "Unauthorized"),
        StatusN(402, "Payment Required"),
        StatusN(403, "Forbidden"),
        StatusN(404, "Not Found"),
        StatusN(405, "Method Not Allowed"),
        StatusN(406, "Not Acceptable"),
        StatusN(407, "Proxy Authentication Required"),
        StatusN(408, "Request Timeout"),
        StatusN(409, "Conflict"),
        StatusN(410, "Gone"),
        StatusN(411, "Length Required"),
        StatusN(412, "Precondition Failed"),
        StatusN(413, "Request Entity Too Large"),
        StatusN(414, "Request-URI Too Long"),
        StatusN(415, "Unsupported Media Type"),
        StatusN(416, "Requested Range Not Satisfiable"),
        StatusN(417, "Expectation Failed"),
        StatusC(418, "I'm a teapot", "RFC 2324"),
        StatusN(419, "Authentication Timeout"),
        StatusC(422, "Unprocessable Entity", "WebDAV; RFC 4918"),
        StatusC(423, "Locked", "WebDAV; RFC 4918"),
        StatusC(424, "Failed Dependency", "WebDAV; RFC 4918"),
        StatusC(424, "Method Failure", "WebDAV"),
        StatusC(425, "Unordered Collection", "Internet draft"),
        StatusC(426, "Upgrade Required", "RFC 2817"),
        StatusC(428, "Precondition Required", "RFC 6585"),
        StatusC(429, "Too Many Requests", "RFC 6585"),
        StatusC(431, "Request Header Fields Too Large", "RFC 6585"),
        StatusC(451, "Unavailable For Legal Reasons", "Internet draft"),

        Left("5xx Server Error"),
        StatusN(500, "Internal Server Error"),
        StatusN(501, "Not Implemented"),
        StatusN(502, "Bad Gateway"),
        StatusN(503, "Service Unavailable"),
        StatusN(504, "Gateway Timeout"),
        StatusN(505, "HTTP Version Not Supported"),
        StatusC(506, "Variant Also Negotiates", "RFC 2295"),
        StatusC(507, "Insufficient Storage", "WebDAV; RFC 4918"),
        StatusC(508, "Loop Detected", "WebDAV; RFC 5842"),
        StatusC(510, "Not Extended", "RFC 2774"),
        StatusC(511, "Network Authentication Required", "RFC 6585"),
        ];
    unsafe {
        longest_ident = entries.iter().map(|&e| match e {
            Left(_heading) => 0,
            Right(status) => status.ident().len(),
        }).max_by(|&i| i).unwrap();
        longest_reason = entries.iter().map(|&e| match e {
            Left(_heading) => 0,
            Right(status) => status.reason.len(),
        }).max_by(|&i| i).unwrap();
    }
    out.write_str("// This file is automatically generated file is used as rusthttpserver::status.

use std::num::IntConvertible;
use std::ascii::StrAsciiExt;

/// HTTP status code
#[deriving(Eq)]
pub enum Status {
");
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n    // %s\n", heading)),
            Right(status) => match status.comment {
                None => out.write_str(fmt!("    %s,\n", status.ident())),
                Some(comment) => out.write_str(fmt!("    %s,  // %s\n", status.ident(), comment)),
            },
        }
    }

    out.write_str("
    UnregisteredStatus(u16, ~str),
}

impl Status {

    /// Get the status code
    pub fn code(&self) -> u16 {
        match *self {
");
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n            // %s\n", heading)),
            Right(status) => out.write_str(fmt!("            %s => %u,\n",
                                                status.padded_ident(), status.code)),
        }
    }
    out.write_str("
            UnregisteredStatus(code, _)   => code,
        }
    }

    /// Get the reason phrase
    pub fn reason(&self) -> ~str {
        match *self {
");
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n            // %s\n", heading)),
            Right(status) => out.write_str(fmt!("            %s => ~\"%s\",\n",
                                                status.padded_ident(), status.reason))
        }
    }
    out.write_str("
            UnregisteredStatus(_, ref reason) => (*reason).clone(),
        }
    }

    /// Get a status from the code and reason
    pub fn from_code_and_reason(status: u16, reason: ~str) -> Status {
        let reason_lower = reason.to_ascii_lower();
        match (status, reason_lower.as_slice()) {
");
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n            // %s\n", heading)),
            Right(status) => out.write_str(fmt!("            (%u, \"%s\")%s => %s,\n",
                                                status.code,
                                                status.reason.to_ascii_lower(),
                                                status.reason_padding_spaces(),
                                                status.ident())),
        }
    }
    out.write_str("
            (_, _) => UnregisteredStatus(status, reason),
        }
    }
}

impl ToStr for Status {
    /// Produce the HTTP status message incorporating both code and message,
    /// e.g. `ImATeapot.to_str() == \"418 I'm a teapot\"`
	fn to_str(&self) -> ~str {
		fmt!(\"%s %s\", self.code().to_str(), self.reason())
	}
}

impl IntConvertible for Status {

    /// Equivalent to `self.code()`
    fn to_int(&self) -> int {
        self.code() as int
    }

    /// Get a *registered* status code from the number of its status code.
    ///
    /// This will fail if an unknown code is passed.
    ///
    /// For example, `from_int(200)` will return `OK`.
    fn from_int(n: int) -> Status {
        match n {
");
    let mut matched_numbers = HashSet::new();
    for &entry in entries.iter() {
        match entry {
            Left(heading) => out.write_str(fmt!("\n            // %s\n", heading)),
            Right(status) => {
                if !matched_numbers.contains(&status.code) {
                    // Purpose: FailedDependency and MethodFailure both use 424,
                    // but clearly they mustn't both go in here
                    out.write_str(fmt!("            %u => %s,\n", status.code, status.ident()));
                    matched_numbers.insert(status.code);
                }
            },
        }
    }
    out.write_str("
            _   => { fail!(fmt!(\"No registered HTTP status code %d\", n)); }
        }
    }
}");
}
<|endoftext|>


<commit_before>use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::{fs, io};
use thiserror::Error;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Measurement {
    pub command: String,
    pub mean: f64,
    pub stddev: f64,
    pub median: f64,
    pub user: f64,
    pub system: f64,
    pub min: f64,
    pub max: f64,
    pub times: Vec<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Measurements {
    pub results: Vec<Measurement>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Data {
    pub threshold: f64,
    pub difference: f64,
    pub baseline: f64,
    pub dev: f64,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Calculation {
    pub metric: String,
    pub regression: bool,
    pub data: Data,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasurementGroup {
    pub version: String,
    pub run: String,
    pub measurement: Measurement,
}

#[derive(Error, Debug)]
pub enum TestError {
    #[error("BadJSONErr: JSON in file cannot be deserialized as expected.\nFilepath: {}\nOriginating Exception:{}", .0.to_string_lossy().into_owned(), .1.as_ref().map_or("None".to_owned(), |e| format!("{}", e)))]
    BadJSONErr(PathBuf, Option<serde_json::Error>),
    #[error("ReadErr: The file cannot be read.\nFilepath: {}\nOriginating Exception:{}", .0.to_string_lossy().into_owned(), .1.as_ref().map_or("None".to_owned(), |e| format!("{}", e)))]
    ReadErr(PathBuf, Option<io::Error>),
    #[error("MissingFilenameErr: The path provided does not specify a file.\nFilepath: {}", .0.to_string_lossy().into_owned())]
    MissingFilenameErr(PathBuf),
    #[error("FilenameNotUnicodeErr: The filename is not expressible in unicode. Consider renaming the file.\nFilepath: {}", .0.to_string_lossy().into_owned())]
    FilenameNotUnicodeErr(PathBuf),
    #[error("BadFileContentsErr: Check that the file exists and is readable.\nFilepath: {}\nOriginating Exception:{}", .0.to_string_lossy().into_owned(), .1.as_ref().map_or("None".to_owned(), |e| format!("{}", e)))]
    BadFileContentsErr(PathBuf, Option<io::Error>),
    #[error("NoResultsErr: The results directory has no json files in it.\nFilepath: {}", .0.to_string_lossy().into_owned())]
    NoResultsErr(PathBuf),
    #[error("OddResultsCountErr: The results directory has an odd number of results in it. Expected an even number.\nFile Count: {}\nFilepath: {}", .0, .1.to_string_lossy().into_owned())]
    OddResultsCountErr(usize, PathBuf),
    #[error("BadGroupSizeErr: Expected two results per group, one for each branch-project pair.\nCount: {}\nGroup: {:?}", .0, .1.into_iter().map(|group| (&group.version[..], &group.run[..])).collect::<Vec<(&str, &str)>>())]
    BadGroupSizeErr(usize, Vec<MeasurementGroup>),
    #[error("BadBranchNameErr: Branch names must be 'baseline' and 'dev'.\nFound: {}, {}", .0, .1)]
    BadBranchNameErr(String, String),
}

fn calculate(metric: &str, dev: &Measurement, baseline: &Measurement) -> Vec<Calculation> {
    let median_threshold = 1.05; // 5% regression threshold
    let median_difference = dev.median / baseline.median;

    let stddev_threshold = 1.05; // 5% regression threshold
    let stddev_difference = dev.stddev / baseline.stddev;

    vec![
        Calculation {
            metric: ["median", metric].join("_"),
            regression: median_difference > median_threshold,
            data: Data {
                threshold: median_threshold,
                difference: median_difference,
                baseline: baseline.median.clone(),
                dev: dev.median.clone(),
            }
        },
        Calculation {
            metric: ["stddev", metric].join("_"),
            regression: stddev_difference > stddev_threshold,
            data: Data {
                threshold: stddev_threshold,
                difference: stddev_difference,
                baseline: baseline.stddev.clone(),
                dev: dev.stddev.clone(),
            }
        },
    ]
}

// given a directory, read all files in the directory and return each
// filename with the deserialized json contents of that file
fn measurements_from_files(
    results_directory: &Path,
) -> Result<Vec<(PathBuf, Measurements)>, TestError> {
    let result_files = fs::read_dir(results_directory)
        .or_else(|e| Err(TestError::ReadErr(results_directory.to_path_buf(), Some(e))))?;

    result_files
        .into_iter()
        .map(|f| f.unwrap().path())
        .filter(|filename| {
            filename
                .extension()
                .and_then(|ext| ext.to_str())
                .map_or(false, |ext| ext.ends_with("json"))
        })
        .map(|filename| {
            fs::read_to_string(&filename)
                .or_else(|e| Err(TestError::BadFileContentsErr(filename.clone(), Some(e))))
                .and_then(|contents| {
                    serde_json::from_str::<Measurements>(&contents)
                        .or_else(|e| Err(TestError::BadJSONErr(filename.clone(), Some(e))))
                })
                .map(|m| (filename, m))
        })
        .collect()
}

// given a list of filename-measurement pairs, detect any regressions by grouping
// measurements together by filename
fn calculate_regressions(
    measurements: &[(PathBuf, Measurement)],
) -> Result<Vec<Calculation>, TestError> {
    let mut measurement_groups: Vec<MeasurementGroup> = measurements
        .into_iter()
        .map(|(p, m)| {
            p.file_name()
                .ok_or_else(|| TestError::MissingFilenameErr(p.to_path_buf()))
                .and_then(|name| {
                    name.to_str()
                        .ok_or_else(|| TestError::FilenameNotUnicodeErr(p.to_path_buf()))
                })
                .map(|name| {
                    let parts: Vec<&str> = name.split("_").collect();
                    MeasurementGroup {
                        version: parts[0].to_owned(),
                        run: parts[1..].join("_"),
                        measurement: m.clone(),
                    }
                })
        })
        .collect::<Result<Vec<MeasurementGroup>, TestError>>()?;

    measurement_groups.sort_by(|x, y| (&x.run, &x.version).cmp(&(&y.run, &y.version)));

    // locking up mutation
    let sorted_measurement_groups = measurement_groups;

    let calculations: Vec<Calculation> = sorted_measurement_groups
        .into_iter()
        .group_by(|x| x.run.clone())
        .into_iter()
        .map(|(_, g)| {
            let mut groups: Vec<MeasurementGroup> = g.collect();
            groups.sort_by(|x, y| x.version.cmp(&y.version));

            match groups.len() {
                2 => {
                    let dev = &groups[0];
                    let baseline = &groups[1];

                    if dev.version == "dev" && baseline.version == "baseline" {
                        calculate(&dev.run, &dev.measurement, &baseline.measurement).into_iter().map(Ok).collect()
                    } else {
                        vec![Err(TestError::BadBranchNameErr(baseline.version.clone(), dev.version.clone()))]
                    }
                },
                i => vec![Err(TestError::BadGroupSizeErr(i, groups))],
            }
        })
        .flatten()
        .collect::<Result<Vec<Calculation>, TestError>>()?;

    Ok(calculations)
}

pub fn regressions(results_directory: &PathBuf) -> Result<Vec<Calculation>, TestError> {
    measurements_from_files(Path::new(&results_directory)).and_then(|v| {
        // exit early with an Err if there are no results to process
        match v.len() {
            0 => Err(TestError::NoResultsErr(results_directory.clone())),
            i if i % 2 == 1 => Err(TestError::OddResultsCountErr(i, results_directory.clone())),
            _ => Ok(()),
        }?;

        let v_next: Vec<(PathBuf, Measurement)> = v
            .into_iter()
            // the way we're running these, the files will each contain exactly one measurement, hence `results[0]`
            .map(|(p, ms)| (p, ms.results[0].clone()))
            .collect();

        calculate_regressions(&v_next)
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_messages() {
        let pairs = vec![
            (
                TestError::BadJSONErr(Path::new("dummy/path/file.json").to_path_buf(), None),
                r#"BadJSONErr: JSON in file cannot be deserialized as expected.
Filepath: dummy/path/file.json
Originating Exception:None"#
            ),
            (
                TestError::ReadErr(Path::new("dummy/path/file.json").to_path_buf(), None),
                r#"ReadErr: The file cannot be read.
Filepath: dummy/path/file.json
Originating Exception:None"#
            ),
            (
                TestError::MissingFilenameErr(Path::new("dummy/path/no_file/").to_path_buf()),
                r#"MissingFilenameErr: The path provided does not specify a file.
Filepath: dummy/path/no_file/"#
            ),
            (
                TestError::FilenameNotUnicodeErr(Path::new("dummy/path/no_file/").to_path_buf()),
                r#"FilenameNotUnicodeErr: The filename is not expressible in unicode. Consider renaming the file.
Filepath: dummy/path/no_file/"#
            ),
            (
                TestError::BadFileContentsErr(Path::new("dummy/path/filenotexist.json").to_path_buf(), None),
                r#"BadFileContentsErr: Check that the file exists and is readable.
Filepath: dummy/path/filenotexist.json
Originating Exception:None"#
            ),
            (
                TestError::NoResultsErr(Path::new("dummy/path/no_file/").to_path_buf()),
                r#"NoResultsErr: The results directory has no json files in it.
Filepath: dummy/path/no_file/"#
            ),
            (
                TestError::OddResultsCountErr(3, Path::new("dummy/path/no_file/").to_path_buf()),
                r#"OddResultsCountErr: The results directory has an odd number of results in it. Expected an even number.
File Count: 3
Filepath: dummy/path/no_file/"#
            ),
            (
                TestError::BadGroupSizeErr(1, vec![MeasurementGroup {
                    version: "dev".to_owned(),
                    run: "some command".to_owned(),
                    measurement: Measurement {
                        command: "some command".to_owned(),
                        mean: 1.0,
                        stddev: 1.0,
                        median: 1.0,
                        user: 1.0,
                        system: 1.0,
                        min: 1.0,
                        max: 1.0,
                        times: vec![1.0, 1.1, 0.9, 1.0, 1.1, 0.9, 1.1],
                    }
                }]),
                r#"BadGroupSizeErr: Expected two results per group, one for each branch-project pair.
Count: 1
Group: [("dev", "some command")]"#
            ),
            (
                TestError::BadBranchNameErr("boop".to_owned(), "noop".to_owned()).to_path_buf(),
                r#"BadBranchNameErr: Branch names must be 'baseline' and 'dev'.
Found: boop, noop"#
            ),

        ];

        for (err, msg) in pairs {
            assert_eq!(format!("{}", err), msg)
        }
    }

}
<commit_msg>type gymnastics<commit_after>use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::{fs, io};
use thiserror::Error;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Measurement {
    pub command: String,
    pub mean: f64,
    pub stddev: f64,
    pub median: f64,
    pub user: f64,
    pub system: f64,
    pub min: f64,
    pub max: f64,
    pub times: Vec<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Measurements {
    pub results: Vec<Measurement>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Data {
    pub threshold: f64,
    pub difference: f64,
    pub baseline: f64,
    pub dev: f64,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Calculation {
    pub metric: String,
    pub regression: bool,
    pub data: Data,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MeasurementGroup {
    pub version: String,
    pub run: String,
    pub measurement: Measurement,
}

#[derive(Error, Debug)]
pub enum TestError {
    #[error("BadJSONErr: JSON in file cannot be deserialized as expected.\nFilepath: {}\nOriginating Exception:{}", .0.to_string_lossy().into_owned(), .1.as_ref().map_or("None".to_owned(), |e| format!("{}", e)))]
    BadJSONErr(PathBuf, Option<serde_json::Error>),
    #[error("ReadErr: The file cannot be read.\nFilepath: {}\nOriginating Exception:{}", .0.to_string_lossy().into_owned(), .1.as_ref().map_or("None".to_owned(), |e| format!("{}", e)))]
    ReadErr(PathBuf, Option<io::Error>),
    #[error("MissingFilenameErr: The path provided does not specify a file.\nFilepath: {}", .0.to_string_lossy().into_owned())]
    MissingFilenameErr(PathBuf),
    #[error("FilenameNotUnicodeErr: The filename is not expressible in unicode. Consider renaming the file.\nFilepath: {}", .0.to_string_lossy().into_owned())]
    FilenameNotUnicodeErr(PathBuf),
    #[error("BadFileContentsErr: Check that the file exists and is readable.\nFilepath: {}\nOriginating Exception:{}", .0.to_string_lossy().into_owned(), .1.as_ref().map_or("None".to_owned(), |e| format!("{}", e)))]
    BadFileContentsErr(PathBuf, Option<io::Error>),
    #[error("NoResultsErr: The results directory has no json files in it.\nFilepath: {}", .0.to_string_lossy().into_owned())]
    NoResultsErr(PathBuf),
    #[error("OddResultsCountErr: The results directory has an odd number of results in it. Expected an even number.\nFile Count: {}\nFilepath: {}", .0, .1.to_string_lossy().into_owned())]
    OddResultsCountErr(usize, PathBuf),
    #[error("BadGroupSizeErr: Expected two results per group, one for each branch-project pair.\nCount: {}\nGroup: {:?}", .0, .1.into_iter().map(|group| (&group.version[..], &group.run[..])).collect::<Vec<(&str, &str)>>())]
    BadGroupSizeErr(usize, Vec<MeasurementGroup>),
    #[error("BadBranchNameErr: Branch names must be 'baseline' and 'dev'.\nFound: {}, {}", .0, .1)]
    BadBranchNameErr(String, String),
}

fn calculate(metric: &str, dev: &Measurement, baseline: &Measurement) -> Vec<Calculation> {
    let median_threshold = 1.05; // 5% regression threshold
    let median_difference = dev.median / baseline.median;

    let stddev_threshold = 1.05; // 5% regression threshold
    let stddev_difference = dev.stddev / baseline.stddev;

    vec![
        Calculation {
            metric: ["median", metric].join("_"),
            regression: median_difference > median_threshold,
            data: Data {
                threshold: median_threshold,
                difference: median_difference,
                baseline: baseline.median.clone(),
                dev: dev.median.clone(),
            }
        },
        Calculation {
            metric: ["stddev", metric].join("_"),
            regression: stddev_difference > stddev_threshold,
            data: Data {
                threshold: stddev_threshold,
                difference: stddev_difference,
                baseline: baseline.stddev.clone(),
                dev: dev.stddev.clone(),
            }
        },
    ]
}

// given a directory, read all files in the directory and return each
// filename with the deserialized json contents of that file
fn measurements_from_files(
    results_directory: &Path,
) -> Result<Vec<(PathBuf, Measurements)>, TestError> {
    let result_files = fs::read_dir(results_directory)
        .or_else(|e| Err(TestError::ReadErr(results_directory.to_path_buf(), Some(e))))?;

    result_files
        .into_iter()
        .map(|f| f.unwrap().path())
        .filter(|filename| {
            filename
                .extension()
                .and_then(|ext| ext.to_str())
                .map_or(false, |ext| ext.ends_with("json"))
        })
        .map(|filename| {
            fs::read_to_string(&filename)
                .or_else(|e| Err(TestError::BadFileContentsErr(filename.clone(), Some(e))))
                .and_then(|contents| {
                    serde_json::from_str::<Measurements>(&contents)
                        .or_else(|e| Err(TestError::BadJSONErr(filename.clone(), Some(e))))
                })
                .map(|m| (filename, m))
        })
        .collect()
}

// given a list of filename-measurement pairs, detect any regressions by grouping
// measurements together by filename
fn calculate_regressions(
    measurements: &[(PathBuf, Measurement)],
) -> Result<Vec<Calculation>, TestError> {
    let mut measurement_groups: Vec<MeasurementGroup> = measurements
        .into_iter()
        .map(|(p, m)| {
            p.file_name()
                .ok_or_else(|| TestError::MissingFilenameErr(p.to_path_buf()))
                .and_then(|name| {
                    name.to_str()
                        .ok_or_else(|| TestError::FilenameNotUnicodeErr(p.to_path_buf()))
                })
                .map(|name| {
                    let parts: Vec<&str> = name.split("_").collect();
                    MeasurementGroup {
                        version: parts[0].to_owned(),
                        run: parts[1..].join("_"),
                        measurement: m.clone(),
                    }
                })
        })
        .collect::<Result<Vec<MeasurementGroup>, TestError>>()?;

    measurement_groups.sort_by(|x, y| (&x.run, &x.version).cmp(&(&y.run, &y.version)));

    // locking up mutation
    let sorted_measurement_groups = measurement_groups;

    let calculations: Vec<Calculation> = sorted_measurement_groups
        .into_iter()
        .group_by(|x| x.run.clone())
        .into_iter()
        .map(|(_, g)| {
            let mut groups: Vec<MeasurementGroup> = g.collect();
            groups.sort_by(|x, y| x.version.cmp(&y.version));

            match groups.len() {
                2 => {
                    let dev = &groups[1];
                    let baseline = &groups[0];

                    if dev.version == "dev" && baseline.version == "baseline" {
                        Ok(calculate(&dev.run, &dev.measurement, &baseline.measurement))
                    } else {
                        Err(TestError::BadBranchNameErr(baseline.version.clone(), dev.version.clone()))
                    }
                },
                i => Err(TestError::BadGroupSizeErr(i, groups)),
            }
        })
        .collect::<Result<Vec<Vec<Calculation>>, TestError>>()?
        .concat();

    Ok(calculations)
}

pub fn regressions(results_directory: &PathBuf) -> Result<Vec<Calculation>, TestError> {
    measurements_from_files(Path::new(&results_directory)).and_then(|v| {
        // exit early with an Err if there are no results to process
        match v.len() {
            0 => Err(TestError::NoResultsErr(results_directory.clone())),
            i if i % 2 == 1 => Err(TestError::OddResultsCountErr(i, results_directory.clone())),
            _ => Ok(()),
        }?;

        let v_next: Vec<(PathBuf, Measurement)> = v
            .into_iter()
            // the way we're running these, the files will each contain exactly one measurement, hence `results[0]`
            .map(|(p, ms)| (p, ms.results[0].clone()))
            .collect();

        calculate_regressions(&v_next)
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_messages() {
        let pairs = vec![
            (
                TestError::BadJSONErr(Path::new("dummy/path/file.json").to_path_buf(), None),
                r#"BadJSONErr: JSON in file cannot be deserialized as expected.
Filepath: dummy/path/file.json
Originating Exception:None"#
            ),
            (
                TestError::ReadErr(Path::new("dummy/path/file.json").to_path_buf(), None),
                r#"ReadErr: The file cannot be read.
Filepath: dummy/path/file.json
Originating Exception:None"#
            ),
            (
                TestError::MissingFilenameErr(Path::new("dummy/path/no_file/").to_path_buf()),
                r#"MissingFilenameErr: The path provided does not specify a file.
Filepath: dummy/path/no_file/"#
            ),
            (
                TestError::FilenameNotUnicodeErr(Path::new("dummy/path/no_file/").to_path_buf()),
                r#"FilenameNotUnicodeErr: The filename is not expressible in unicode. Consider renaming the file.
Filepath: dummy/path/no_file/"#
            ),
            (
                TestError::BadFileContentsErr(Path::new("dummy/path/filenotexist.json").to_path_buf(), None),
                r#"BadFileContentsErr: Check that the file exists and is readable.
Filepath: dummy/path/filenotexist.json
Originating Exception:None"#
            ),
            (
                TestError::NoResultsErr(Path::new("dummy/path/no_file/").to_path_buf()),
                r#"NoResultsErr: The results directory has no json files in it.
Filepath: dummy/path/no_file/"#
            ),
            (
                TestError::OddResultsCountErr(3, Path::new("dummy/path/no_file/").to_path_buf()),
                r#"OddResultsCountErr: The results directory has an odd number of results in it. Expected an even number.
File Count: 3
Filepath: dummy/path/no_file/"#
            ),
            (
                TestError::BadGroupSizeErr(1, vec![MeasurementGroup {
                    version: "dev".to_owned(),
                    run: "some command".to_owned(),
                    measurement: Measurement {
                        command: "some command".to_owned(),
                        mean: 1.0,
                        stddev: 1.0,
                        median: 1.0,
                        user: 1.0,
                        system: 1.0,
                        min: 1.0,
                        max: 1.0,
                        times: vec![1.0, 1.1, 0.9, 1.0, 1.1, 0.9, 1.1],
                    }
                }]),
                r#"BadGroupSizeErr: Expected two results per group, one for each branch-project pair.
Count: 1
Group: [("dev", "some command")]"#
            ),
            (
                TestError::BadBranchNameErr("boop".to_owned(), "noop".to_owned()).to_path_buf(),
                r#"BadBranchNameErr: Branch names must be 'baseline' and 'dev'.
Found: boop, noop"#
            ),

        ];

        for (err, msg) in pairs {
            assert_eq!(format!("{}", err), msg)
        }
    }

}
<|endoftext|>


<commit_before>/*
    Copyright 2017 Andrew Medworth <github@medworth.org.uk>

    This file is part of Dots-and-Boxes Engine.

    Dots-and-Boxes Engine is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Dots-and-Boxes Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Dots-and-Boxes Engine.  If not, see <http://www.gnu.org/licenses/>.
*/

use game::{Move, Position, Side};
use nimstring;
use std::io;
use regex::{Regex, Captures};

#[derive(PartialEq)]
#[derive(Debug)]
enum Command {
    MakeMove(Move),
    UndoMove(Move),
    CalcNimstringValue,
    PrintHelp,
    Quit,
}

impl Command {
    fn execute(self: &Command, pos: &mut Position) {
        match self {
            &Command::MakeMove(m) => { pos.make_move(m.x, m.y, m.side); },
            &Command::UndoMove(m) => { pos.undo_move(m.x, m.y, m.side); },
            &Command::CalcNimstringValue => {
                let (val, per_move) = nimstring::calc_value_with_moves(pos);
                println!("Position value is {}", val);
                let mut moves: Vec<&Move> = per_move.keys().collect();
                moves.sort_by(|a, b| a.x.cmp(&b.x).then(a.y.cmp(&b.y)).then(a.side.cmp(&b.side)));
                for &m in &moves {
                    println!("{} {}", m, per_move.get(m).unwrap());
                }
            },
            &Command::PrintHelp => { print_help(); },
            &Command::Quit => { println!("Bye bye!"); },
        }
    }
}

fn print_help() {
    println!("Available commands:");
    println!("x y t/l/b/r - make move (x,y) top/left/bottom/right");
    println!("u x y t/l/b/r - undo move (x,y) top/left/bottom/right");
    println!("nv - calculate Nimstring value of current position");
    println!("help - print this help message");
    println!("quit/exit - exit program");
}

fn parse_side(side_s: &str) -> Option<Side> {
    if side_s == "l" || side_s == "left" { Some(Side::Left) }
    else if side_s == "r" || side_s == "right" { Some(Side::Right) }
    else if side_s == "t" || side_s == "top" { Some(Side::Top) }
    else if side_s == "b" || side_s == "bottom" { Some(Side::Bottom) }
    else { None }
}

fn parse_move(caps: Captures) -> Result<Move, String> {
    let x = caps[1].parse::<usize>().unwrap();
    let y = caps[2].parse::<usize>().unwrap();
    let side_s = caps[3].to_string();
    match parse_side(&side_s) {
        Some(side) => Ok(Move{x: x, y: y, side: side}),
        None => Err(format!("Unrecognised side: [{}]", side_s))
    }
}

fn parse_command(input: &str) -> Result<Command, String> {
    let input = input.to_lowercase();
    let move_re = Regex::new(r"^(\d+) (\d+) ([a-zA-Z]+)$").unwrap();
    if let Some(caps) = move_re.captures(&input) {
        match parse_move(caps) {
            Ok(m) => return Ok(Command::MakeMove(m)),
            Err(e) => return Err(format!("Cannot extract move from [{}]: {}", input, e))
        }
    }
    let undo_move_re = Regex::new(r"^[uU] (\d+) (\d+) ([a-zA-Z]+)$").unwrap();
    if let Some(caps) = undo_move_re.captures(&input) {
        match parse_move(caps) {
            Ok(m) => return Ok(Command::UndoMove(m)),
            Err(e) => return Err(format!("Cannot extract move from [{}]: {}", input, e))
        }
    }
    if "nv" == input {
        return Ok(Command::CalcNimstringValue);
    }
    if "help" == input {
        return Ok(Command::PrintHelp);
    }
    if "exit" == input || "quit" == input {
        return Ok(Command::Quit);
    }
    Err("Unsupported command".to_string())
}

fn get_next_command() -> Command {
    loop {
        let mut input = String::new();
        if let Err(error) = io::stdin().read_line(&mut input) {
            println!("Error reading from standard input: {}", error);
            continue;
        }
        let input = input.trim();
        match parse_command(&input) {
            Ok(command) => return command,
            Err(error) => {
                println!("Cannot execute [{}]: {}", input, error);
                println!("For help, try 'help'");
            }
        }
    }
}

// Enter the main loop of the CLI
pub fn main_loop(width: usize, height: usize) {
    let mut pos = Position::new_game(width, height);
    loop {
        println!("{}", pos);
        let command = get_next_command();
        command.execute(&mut pos);
        if command == Command::Quit {
            break;
        }
        println!();
    }
}

#[cfg(test)]
mod tests {
    use game::*;
    use cli::*;

    #[test]
    fn parse_make_move_cmd() {
        assert_eq!(Command::MakeMove(Move{x: 3, y: 5, side: Side::Bottom}), parse_command("3 5 b").unwrap());
        assert_eq!(Command::MakeMove(Move{x: 3, y: 5, side: Side::Bottom}), parse_command("3 5 Bottom").unwrap());
    }

    #[test]
    fn parse_undo_move_cmd() {
        assert_eq!(Command::UndoMove(Move{x: 8, y: 6, side: Side::Left}), parse_command("u 8 6 l").unwrap());
        assert_eq!(Command::UndoMove(Move{x: 8, y: 6, side: Side::Left}), parse_command("u 8 6 Left").unwrap());
    }

    #[test]
    fn parse_nimstring_value_cmd() {
        assert_eq!(Command::CalcNimstringValue, parse_command("nv").unwrap());
    }

    #[test]
    fn parse_help_cmd() {
        assert_eq!(Command::PrintHelp, parse_command("help").unwrap());
    }

    #[test]
    fn parse_exit_cmd() {
        assert_eq!(Command::Quit, parse_command("quit").unwrap());
        assert_eq!(Command::Quit, parse_command("exit").unwrap());
    }
}
<commit_msg>Check move legality<commit_after>/*
    Copyright 2017 Andrew Medworth <github@medworth.org.uk>

    This file is part of Dots-and-Boxes Engine.

    Dots-and-Boxes Engine is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Dots-and-Boxes Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Dots-and-Boxes Engine.  If not, see <http://www.gnu.org/licenses/>.
*/

use game::{Move, Position, Side};
use nimstring;
use std::io;
use regex::{Regex, Captures};

#[derive(PartialEq)]
#[derive(Debug)]
enum Command {
    MakeMove(Move),
    UndoMove(Move),
    CalcNimstringValue,
    PrintHelp,
    Quit,
}

impl Command {
    fn execute(self: &Command, pos: &mut Position) {
        match self {
            &Command::MakeMove(m) => {
                if pos.is_legal_move(m.x, m.y, m.side) {
                    pos.make_move(m.x, m.y, m.side);
                }
                else {
                    println!("Not a legal move: {}", m);
                }
            },
            &Command::UndoMove(m) => { pos.undo_move(m.x, m.y, m.side); },
            &Command::CalcNimstringValue => {
                let (val, per_move) = nimstring::calc_value_with_moves(pos);
                println!("Position value is {}", val);
                let mut moves: Vec<&Move> = per_move.keys().collect();
                moves.sort_by(|a, b| a.x.cmp(&b.x).then(a.y.cmp(&b.y)).then(a.side.cmp(&b.side)));
                for &m in &moves {
                    println!("{} {}", m, per_move.get(m).unwrap());
                }
            },
            &Command::PrintHelp => { print_help(); },
            &Command::Quit => { println!("Bye bye!"); },
        }
    }
}

fn print_help() {
    println!("Available commands:");
    println!("x y t/l/b/r - make move (x,y) top/left/bottom/right");
    println!("u x y t/l/b/r - undo move (x,y) top/left/bottom/right");
    println!("nv - calculate Nimstring value of current position");
    println!("help - print this help message");
    println!("quit/exit - exit program");
}

fn parse_side(side_s: &str) -> Option<Side> {
    if side_s == "l" || side_s == "left" { Some(Side::Left) }
    else if side_s == "r" || side_s == "right" { Some(Side::Right) }
    else if side_s == "t" || side_s == "top" { Some(Side::Top) }
    else if side_s == "b" || side_s == "bottom" { Some(Side::Bottom) }
    else { None }
}

fn parse_move(caps: Captures) -> Result<Move, String> {
    let x = caps[1].parse::<usize>().unwrap();
    let y = caps[2].parse::<usize>().unwrap();
    let side_s = caps[3].to_string();
    match parse_side(&side_s) {
        Some(side) => Ok(Move{x: x, y: y, side: side}),
        None => Err(format!("Unrecognised side: [{}]", side_s))
    }
}

fn parse_command(input: &str) -> Result<Command, String> {
    let input = input.to_lowercase();
    let move_re = Regex::new(r"^(\d+) (\d+) ([a-zA-Z]+)$").unwrap();
    if let Some(caps) = move_re.captures(&input) {
        match parse_move(caps) {
            Ok(m) => return Ok(Command::MakeMove(m)),
            Err(e) => return Err(format!("Cannot extract move from [{}]: {}", input, e))
        }
    }
    let undo_move_re = Regex::new(r"^[uU] (\d+) (\d+) ([a-zA-Z]+)$").unwrap();
    if let Some(caps) = undo_move_re.captures(&input) {
        match parse_move(caps) {
            Ok(m) => return Ok(Command::UndoMove(m)),
            Err(e) => return Err(format!("Cannot extract move from [{}]: {}", input, e))
        }
    }
    if "nv" == input {
        return Ok(Command::CalcNimstringValue);
    }
    if "help" == input {
        return Ok(Command::PrintHelp);
    }
    if "exit" == input || "quit" == input {
        return Ok(Command::Quit);
    }
    Err("Unsupported command".to_string())
}

fn get_next_command() -> Command {
    loop {
        let mut input = String::new();
        if let Err(error) = io::stdin().read_line(&mut input) {
            println!("Error reading from standard input: {}", error);
            continue;
        }
        let input = input.trim();
        match parse_command(&input) {
            Ok(command) => return command,
            Err(error) => {
                println!("Cannot execute [{}]: {}", input, error);
                println!("For help, try 'help'");
            }
        }
    }
}

// Enter the main loop of the CLI
pub fn main_loop(width: usize, height: usize) {
    let mut pos = Position::new_game(width, height);
    loop {
        println!("{}", pos);
        let command = get_next_command();
        command.execute(&mut pos);
        if command == Command::Quit {
            break;
        }
        println!();
    }
}

#[cfg(test)]
mod tests {
    use game::*;
    use cli::*;

    #[test]
    fn parse_make_move_cmd() {
        assert_eq!(Command::MakeMove(Move{x: 3, y: 5, side: Side::Bottom}), parse_command("3 5 b").unwrap());
        assert_eq!(Command::MakeMove(Move{x: 3, y: 5, side: Side::Bottom}), parse_command("3 5 Bottom").unwrap());
    }

    #[test]
    fn parse_undo_move_cmd() {
        assert_eq!(Command::UndoMove(Move{x: 8, y: 6, side: Side::Left}), parse_command("u 8 6 l").unwrap());
        assert_eq!(Command::UndoMove(Move{x: 8, y: 6, side: Side::Left}), parse_command("u 8 6 Left").unwrap());
    }

    #[test]
    fn parse_nimstring_value_cmd() {
        assert_eq!(Command::CalcNimstringValue, parse_command("nv").unwrap());
    }

    #[test]
    fn parse_help_cmd() {
        assert_eq!(Command::PrintHelp, parse_command("help").unwrap());
    }

    #[test]
    fn parse_exit_cmd() {
        assert_eq!(Command::Quit, parse_command("quit").unwrap());
        assert_eq!(Command::Quit, parse_command("exit").unwrap());
    }
}
<|endoftext|>


<commit_before>// Bitcoin secp256k1 bindings
// Written in 2014 by
//   Dawid Ciężarkiewicz
//   Andrew Poelstra
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//

//! # Pedersen commitments and related range proofs

use std::mem;

use ContextFlag;
use Error;
use Secp256k1;

use constants;
use ffi;
use key;
use key::{SecretKey, PublicKey};
use rand::{Rng, OsRng};

/// A Pedersen commitment
pub struct Commitment([u8; constants::PEDERSEN_COMMITMENT_SIZE]);
impl_array_newtype!(Commitment, u8, constants::PEDERSEN_COMMITMENT_SIZE);
impl_pretty_debug!(Commitment);

impl Commitment {
    /// Uninitialized commitment, use with caution
    unsafe fn blank() -> Commitment {
        mem::uninitialized()
    }
    /// Builds a commitment from a binary vector. Will panic if the vector is too short.
    pub fn from_vec(c: Vec<u8>) -> Commitment {
        let mut a = [0; constants::PEDERSEN_COMMITMENT_SIZE];
        for i in 0..a.len() {
            a[i] = c[i];
        }
        Commitment(a)
    }
    /// Converts a commitment to a public key
    pub fn to_pubkey(&self, secp: &Secp256k1) -> Result<key::PublicKey, Error> {
        key::PublicKey::from_slice(secp, &self.0)
    }
    /// Underlying data
    pub fn bytes(&self) -> &[u8] {
        &self.0
    }
}

/// A range proof. Typically much larger in memory that the above (~5k).
#[derive(Copy)]
pub struct RangeProof {
    /// The proof itself, at most 5134 bytes long
    pub proof: [u8; constants::MAX_PROOF_SIZE],
    /// The length of the proof
    pub plen: usize,
}

impl Clone for RangeProof {
    #[inline]
    fn clone(&self) -> RangeProof {
        unsafe {
            use std::intrinsics::copy_nonoverlapping;
            use std::mem;
            let mut ret: [u8; constants::MAX_PROOF_SIZE] = mem::uninitialized();
            copy_nonoverlapping(self.proof.as_ptr(),
                                ret.as_mut_ptr(),
                                mem::size_of::<RangeProof>());
            RangeProof {
                proof: ret,
                plen: self.plen,
            }
        }
    }
}

impl RangeProof {
    /// Builds a range proof from a binary vector. Will panic if the vector is longer than the max proof size.
    pub fn from_vec(p: Vec<u8>) -> RangeProof {
        let mut a = [0; constants::MAX_PROOF_SIZE];
        for i in 0..p.len() {
            a[i] = p[i];
        }
        RangeProof {
            proof: a,
            plen: p.len(),
        }
    }
    pub fn bytes(&self) -> &[u8] {
        &self.proof[..self.plen as usize]
    }
    pub fn len(&self) -> usize {
        self.plen
    }
}

/// The range that was proven
pub struct ProofRange {
    /// Min value that was proven
    pub min: u64,
    /// Max value that was proven
    pub max: u64,
}

/// Information about a valid proof after rewinding it.
pub struct ProofInfo {
    /// Whether the proof is valid or not
    pub success: bool,
    /// Value that was used by the commitment
    pub value: u64,
    /// Message embedded in the proof
    pub message: [u8; constants::PROOF_MSG_SIZE],
    /// Length of the embedded message
    pub mlen: i32,
    /// Min value that was proven
    pub min: u64,
    /// Max value that was proven
    pub max: u64,
    /// Exponent used by the proof
    pub exp: i32,
    /// Mantissa used by the proof
    pub mantissa: i32,
}

impl ::std::fmt::Debug for RangeProof {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        try!(write!(f, "{}(", stringify!(RangeProof)));
        for i in self.proof[..self.plen].iter().cloned() {
            try!(write!(f, "{:02x}", i));
        }
        write!(f, ")[{}]", self.plen)
    }
}

impl Secp256k1 {
    /// Creates a pedersen commitment from a value and a blinding factor
    pub fn commit(&self, value: u64, blind: SecretKey) -> Result<Commitment, Error> {

        if self.caps != ContextFlag::Commit {
            return Err(Error::IncapableContext);
        }
        let mut commit = [0; 33];
        unsafe {
            ffi::secp256k1_pedersen_commit(self.ctx, commit.as_mut_ptr(), blind.as_ptr(), value)
        };
        Ok(Commitment(commit))
    }

    /// Convenience method to Create a pedersen commitment only from a value, with a zero blinding factor
    pub fn commit_value(&self, value: u64) -> Result<Commitment, Error> {

        if self.caps != ContextFlag::Commit {
            return Err(Error::IncapableContext);
        }
        let mut commit = [0; 33];
        let zblind = [0; 32];
        unsafe {
            ffi::secp256k1_pedersen_commit(self.ctx, commit.as_mut_ptr(), zblind.as_ptr(), value)
        };
        Ok(Commitment(commit))
    }

    /// Taking vectors of positive and negative commitments as well as an
    /// expected excess, verifies that it all sums to zero.
    pub fn verify_commit_sum(&self,
                             positive: Vec<Commitment>,
                             negative: Vec<Commitment>,
                             excess: i64)
                             -> bool {
        let pos = map_vec!(positive, |p| p.0.as_ptr());
        let neg = map_vec!(negative, |n| n.0.as_ptr());
        unsafe {
            ffi::secp256k1_pedersen_verify_tally(self.ctx,
                                                 pos.as_ptr(),
                                                 pos.len() as i32,
                                                 neg.as_ptr(),
                                                 neg.len() as i32,
                                                 excess) == 1
        }
    }

    /// Computes the sum of multiple positive and negative pedersen commitments.
    pub fn commit_sum(&self,
                      positive: Vec<Commitment>,
                      negative: Vec<Commitment>)
                      -> Result<Commitment, Error> {
        let pos = map_vec!(positive, |p| p.0.as_ptr());
        let neg = map_vec!(negative, |n| n.0.as_ptr());
        let mut ret = unsafe { Commitment::blank() };
        let err = unsafe {
            ffi::secp256k1_pedersen_commit_sum(self.ctx,
                                               ret.as_mut_ptr(),
                                               pos.as_ptr(),
                                               pos.len() as i32,
                                               neg.as_ptr(),
                                               neg.len() as i32)
        };
        println!("-> {}", err);
        if err == 1 {
            Ok(ret)
        } else {
            Err(Error::IncorrectCommitSum)
        }
    }

    /// Computes the sum of multiple positive and negative blinding factors.
    pub fn blind_sum(&self,
                     positive: Vec<SecretKey>,
                     negative: Vec<SecretKey>)
                     -> Result<SecretKey, Error> {
        let mut neg = map_vec!(negative, |n| n.as_ptr());
        let mut all = map_vec!(positive, |p| p.as_ptr());
        all.append(&mut neg);
        let mut ret: [u8; 32] = unsafe { mem::uninitialized() };
        unsafe {
            assert_eq!(ffi::secp256k1_pedersen_blind_sum(self.ctx,
                                                         ret.as_mut_ptr(),
                                                         all.as_ptr(),
                                                         all.len() as i32,
                                                         positive.len() as i32),
                       1)
        }
        // secp256k1 should never return an invalid private
        SecretKey::from_slice(self, &ret)
    }

    /// Produces a range proof for the provided value, using min and max bounds, relying
    /// on the blinding factor and commitment.
    pub fn range_proof(&self,
                       min: u64,
                       value: u64,
                       blind: SecretKey,
                       commit: Commitment)
                       -> RangeProof {

        let mut rng = OsRng::new().unwrap();
        let mut nonce = [0u8; 32];
        rng.fill_bytes(&mut nonce);

        let mut retried = false;
        let mut proof = [0; constants::MAX_PROOF_SIZE];
        let mut plen = constants::MAX_PROOF_SIZE as i32;
        loop {
            let err = unsafe {
                // because: "This can randomly fail with probability around one in 2^100.
                // If this happens, buy a lottery ticket and retry."
                ffi::secp256k1_rangeproof_sign(self.ctx,
                                               proof.as_mut_ptr(),
                                               &mut plen,
                                               min,
                                               commit.as_ptr(),
                                               blind.as_ptr(),
                                               nonce.as_ptr(),
                                               0,
                                               64,
                                               value)
            };
            if retried {
                break;
            }
            if err == 1 {
                retried = true;
            }
        }
        RangeProof {
            proof: proof,
            plen: plen as usize,
        }
    }

    /// Verify a proof that a committed value is within a range.
    pub fn verify_range_proof(&self,
                              commit: Commitment,
                              proof: RangeProof)
                              -> Result<ProofRange, Error> {
        let mut min: u64 = 0;
        let mut max: u64 = 0;

        let success = unsafe {
            ffi::secp256k1_rangeproof_verify(self.ctx,
                                             &mut min,
                                             &mut max,
                                             commit.as_ptr(),
                                             proof.proof.as_ptr(),
                                             proof.plen as i32) == 1
        };
        if success {
            Ok(ProofRange {
                min: min,
                max: max,
            })
        } else {
            Err(Error::InvalidRangeProof)
        }
    }

    /// Verify a range proof proof and rewind the proof to recover information sent by its author.
    pub fn rewind_range_proof(&self,
                              commit: Commitment,
                              proof: RangeProof,
                              nonce: [u8; 32])
                              -> ProofInfo {
        let mut value: u64 = 0;
        let mut blind: [u8; 32] = unsafe { mem::uninitialized() };
        let mut message = [0u8; constants::PROOF_MSG_SIZE];
        let mut mlen: i32 = 0;
        let mut min: u64 = 0;
        let mut max: u64 = 0;
        let success = unsafe {
            ffi::secp256k1_rangeproof_rewind(self.ctx,
                                             blind.as_mut_ptr(),
                                             &mut value,
                                             message.as_mut_ptr(),
                                             &mut mlen,
                                             nonce.as_ptr(),
                                             &mut min,
                                             &mut max,
                                             commit.as_ptr(),
                                             proof.proof.as_ptr(),
                                             proof.plen as i32) == 1
        };
        ProofInfo {
            success: success,
            value: value,
            message: message,
            mlen: mlen,
            min: min,
            max: max,
            exp: 0,
            mantissa: 0,
        }
    }

    pub fn range_proof_info(&self, proof: RangeProof) -> ProofInfo {
        let mut exp: i32 = 0;
        let mut mantissa: i32 = 0;
        let mut min: u64 = 0;
        let mut max: u64 = 0;
        let success = unsafe {
            ffi::secp256k1_rangeproof_info(self.ctx,
                                           &mut exp,
                                           &mut mantissa,
                                           &mut min,
                                           &mut max,
                                           proof.proof.as_ptr(),
                                           proof.plen as i32) == 1
        };
        ProofInfo {
            success: success,
            value: 0,
            message: [0; 4096],
            mlen: 0,
            min: min,
            max: max,
            exp: exp,
            mantissa: mantissa,
        }
    }
}
<commit_msg>Cleaned spurious debug.<commit_after>// Bitcoin secp256k1 bindings
// Written in 2014 by
//   Dawid Ciężarkiewicz
//   Andrew Poelstra
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//

//! # Pedersen commitments and related range proofs

use std::mem;

use ContextFlag;
use Error;
use Secp256k1;

use constants;
use ffi;
use key;
use key::{SecretKey, PublicKey};
use rand::{Rng, OsRng};

/// A Pedersen commitment
pub struct Commitment([u8; constants::PEDERSEN_COMMITMENT_SIZE]);
impl_array_newtype!(Commitment, u8, constants::PEDERSEN_COMMITMENT_SIZE);
impl_pretty_debug!(Commitment);

impl Commitment {
    /// Uninitialized commitment, use with caution
    unsafe fn blank() -> Commitment {
        mem::uninitialized()
    }
    /// Builds a commitment from a binary vector. Will panic if the vector is too short.
    pub fn from_vec(c: Vec<u8>) -> Commitment {
        let mut a = [0; constants::PEDERSEN_COMMITMENT_SIZE];
        for i in 0..a.len() {
            a[i] = c[i];
        }
        Commitment(a)
    }
    /// Converts a commitment to a public key
    pub fn to_pubkey(&self, secp: &Secp256k1) -> Result<key::PublicKey, Error> {
        key::PublicKey::from_slice(secp, &self.0)
    }
    /// Underlying data
    pub fn bytes(&self) -> &[u8] {
        &self.0
    }
}

/// A range proof. Typically much larger in memory that the above (~5k).
#[derive(Copy)]
pub struct RangeProof {
    /// The proof itself, at most 5134 bytes long
    pub proof: [u8; constants::MAX_PROOF_SIZE],
    /// The length of the proof
    pub plen: usize,
}

impl Clone for RangeProof {
    #[inline]
    fn clone(&self) -> RangeProof {
        unsafe {
            use std::intrinsics::copy_nonoverlapping;
            use std::mem;
            let mut ret: [u8; constants::MAX_PROOF_SIZE] = mem::uninitialized();
            copy_nonoverlapping(self.proof.as_ptr(),
                                ret.as_mut_ptr(),
                                mem::size_of::<RangeProof>());
            RangeProof {
                proof: ret,
                plen: self.plen,
            }
        }
    }
}

impl RangeProof {
    /// Builds a range proof from a binary vector. Will panic if the vector is longer than the max proof size.
    pub fn from_vec(p: Vec<u8>) -> RangeProof {
        let mut a = [0; constants::MAX_PROOF_SIZE];
        for i in 0..p.len() {
            a[i] = p[i];
        }
        RangeProof {
            proof: a,
            plen: p.len(),
        }
    }
    pub fn bytes(&self) -> &[u8] {
        &self.proof[..self.plen as usize]
    }
    pub fn len(&self) -> usize {
        self.plen
    }
}

/// The range that was proven
pub struct ProofRange {
    /// Min value that was proven
    pub min: u64,
    /// Max value that was proven
    pub max: u64,
}

/// Information about a valid proof after rewinding it.
pub struct ProofInfo {
    /// Whether the proof is valid or not
    pub success: bool,
    /// Value that was used by the commitment
    pub value: u64,
    /// Message embedded in the proof
    pub message: [u8; constants::PROOF_MSG_SIZE],
    /// Length of the embedded message
    pub mlen: i32,
    /// Min value that was proven
    pub min: u64,
    /// Max value that was proven
    pub max: u64,
    /// Exponent used by the proof
    pub exp: i32,
    /// Mantissa used by the proof
    pub mantissa: i32,
}

impl ::std::fmt::Debug for RangeProof {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        try!(write!(f, "{}(", stringify!(RangeProof)));
        for i in self.proof[..self.plen].iter().cloned() {
            try!(write!(f, "{:02x}", i));
        }
        write!(f, ")[{}]", self.plen)
    }
}

impl Secp256k1 {
    /// Creates a pedersen commitment from a value and a blinding factor
    pub fn commit(&self, value: u64, blind: SecretKey) -> Result<Commitment, Error> {

        if self.caps != ContextFlag::Commit {
            return Err(Error::IncapableContext);
        }
        let mut commit = [0; 33];
        unsafe {
            ffi::secp256k1_pedersen_commit(self.ctx, commit.as_mut_ptr(), blind.as_ptr(), value)
        };
        Ok(Commitment(commit))
    }

    /// Convenience method to Create a pedersen commitment only from a value, with a zero blinding factor
    pub fn commit_value(&self, value: u64) -> Result<Commitment, Error> {

        if self.caps != ContextFlag::Commit {
            return Err(Error::IncapableContext);
        }
        let mut commit = [0; 33];
        let zblind = [0; 32];
        unsafe {
            ffi::secp256k1_pedersen_commit(self.ctx, commit.as_mut_ptr(), zblind.as_ptr(), value)
        };
        Ok(Commitment(commit))
    }

    /// Taking vectors of positive and negative commitments as well as an
    /// expected excess, verifies that it all sums to zero.
    pub fn verify_commit_sum(&self,
                             positive: Vec<Commitment>,
                             negative: Vec<Commitment>,
                             excess: i64)
                             -> bool {
        let pos = map_vec!(positive, |p| p.0.as_ptr());
        let neg = map_vec!(negative, |n| n.0.as_ptr());
        unsafe {
            ffi::secp256k1_pedersen_verify_tally(self.ctx,
                                                 pos.as_ptr(),
                                                 pos.len() as i32,
                                                 neg.as_ptr(),
                                                 neg.len() as i32,
                                                 excess) == 1
        }
    }

    /// Computes the sum of multiple positive and negative pedersen commitments.
    pub fn commit_sum(&self,
                      positive: Vec<Commitment>,
                      negative: Vec<Commitment>)
                      -> Result<Commitment, Error> {
        let pos = map_vec!(positive, |p| p.0.as_ptr());
        let neg = map_vec!(negative, |n| n.0.as_ptr());
        let mut ret = unsafe { Commitment::blank() };
        let err = unsafe {
            ffi::secp256k1_pedersen_commit_sum(self.ctx,
                                               ret.as_mut_ptr(),
                                               pos.as_ptr(),
                                               pos.len() as i32,
                                               neg.as_ptr(),
                                               neg.len() as i32)
        };
        if err == 1 {
            Ok(ret)
        } else {
            Err(Error::IncorrectCommitSum)
        }
    }

    /// Computes the sum of multiple positive and negative blinding factors.
    pub fn blind_sum(&self,
                     positive: Vec<SecretKey>,
                     negative: Vec<SecretKey>)
                     -> Result<SecretKey, Error> {
        let mut neg = map_vec!(negative, |n| n.as_ptr());
        let mut all = map_vec!(positive, |p| p.as_ptr());
        all.append(&mut neg);
        let mut ret: [u8; 32] = unsafe { mem::uninitialized() };
        unsafe {
            assert_eq!(ffi::secp256k1_pedersen_blind_sum(self.ctx,
                                                         ret.as_mut_ptr(),
                                                         all.as_ptr(),
                                                         all.len() as i32,
                                                         positive.len() as i32),
                       1)
        }
        // secp256k1 should never return an invalid private
        SecretKey::from_slice(self, &ret)
    }

    /// Produces a range proof for the provided value, using min and max bounds, relying
    /// on the blinding factor and commitment.
    pub fn range_proof(&self,
                       min: u64,
                       value: u64,
                       blind: SecretKey,
                       commit: Commitment)
                       -> RangeProof {

        let mut rng = OsRng::new().unwrap();
        let mut nonce = [0u8; 32];
        rng.fill_bytes(&mut nonce);

        let mut retried = false;
        let mut proof = [0; constants::MAX_PROOF_SIZE];
        let mut plen = constants::MAX_PROOF_SIZE as i32;
        loop {
            let err = unsafe {
                // because: "This can randomly fail with probability around one in 2^100.
                // If this happens, buy a lottery ticket and retry."
                ffi::secp256k1_rangeproof_sign(self.ctx,
                                               proof.as_mut_ptr(),
                                               &mut plen,
                                               min,
                                               commit.as_ptr(),
                                               blind.as_ptr(),
                                               nonce.as_ptr(),
                                               0,
                                               64,
                                               value)
            };
            if retried {
                break;
            }
            if err == 1 {
                retried = true;
            }
        }
        RangeProof {
            proof: proof,
            plen: plen as usize,
        }
    }

    /// Verify a proof that a committed value is within a range.
    pub fn verify_range_proof(&self,
                              commit: Commitment,
                              proof: RangeProof)
                              -> Result<ProofRange, Error> {
        let mut min: u64 = 0;
        let mut max: u64 = 0;

        let success = unsafe {
            ffi::secp256k1_rangeproof_verify(self.ctx,
                                             &mut min,
                                             &mut max,
                                             commit.as_ptr(),
                                             proof.proof.as_ptr(),
                                             proof.plen as i32) == 1
        };
        if success {
            Ok(ProofRange {
                min: min,
                max: max,
            })
        } else {
            Err(Error::InvalidRangeProof)
        }
    }

    /// Verify a range proof proof and rewind the proof to recover information sent by its author.
    pub fn rewind_range_proof(&self,
                              commit: Commitment,
                              proof: RangeProof,
                              nonce: [u8; 32])
                              -> ProofInfo {
        let mut value: u64 = 0;
        let mut blind: [u8; 32] = unsafe { mem::uninitialized() };
        let mut message = [0u8; constants::PROOF_MSG_SIZE];
        let mut mlen: i32 = 0;
        let mut min: u64 = 0;
        let mut max: u64 = 0;
        let success = unsafe {
            ffi::secp256k1_rangeproof_rewind(self.ctx,
                                             blind.as_mut_ptr(),
                                             &mut value,
                                             message.as_mut_ptr(),
                                             &mut mlen,
                                             nonce.as_ptr(),
                                             &mut min,
                                             &mut max,
                                             commit.as_ptr(),
                                             proof.proof.as_ptr(),
                                             proof.plen as i32) == 1
        };
        ProofInfo {
            success: success,
            value: value,
            message: message,
            mlen: mlen,
            min: min,
            max: max,
            exp: 0,
            mantissa: 0,
        }
    }

    pub fn range_proof_info(&self, proof: RangeProof) -> ProofInfo {
        let mut exp: i32 = 0;
        let mut mantissa: i32 = 0;
        let mut min: u64 = 0;
        let mut max: u64 = 0;
        let success = unsafe {
            ffi::secp256k1_rangeproof_info(self.ctx,
                                           &mut exp,
                                           &mut mantissa,
                                           &mut min,
                                           &mut max,
                                           proof.proof.as_ptr(),
                                           proof.plen as i32) == 1
        };
        ProofInfo {
            success: success,
            value: 0,
            message: [0; 4096],
            mlen: 0,
            min: min,
            max: max,
            exp: exp,
            mantissa: mantissa,
        }
    }
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! Convenience structs and functions for generating configuration for a swarm of libra nodes
use crate::util::gen_genesis_transaction;
use config::{
    config::{KeyPairs, NodeConfig, NodeConfigHelpers},
    seed_peers::{SeedPeersConfig, SeedPeersConfigHelpers},
    trusted_peers::{TrustedPeersConfig, TrustedPeersConfigHelpers},
};
use crypto::signing::KeyPair;
use failure::prelude::*;
use std::path::{Path, PathBuf};
use vm_genesis::default_config;

pub struct SwarmConfig {
    configs: Vec<(PathBuf, NodeConfig)>,
    seed_peers: (PathBuf, SeedPeersConfig),
    trusted_peers: (PathBuf, TrustedPeersConfig),
}

impl SwarmConfig {
    //TODO convert this to use the Builder paradigm
    pub fn new(
        mut template: NodeConfig,
        num_nodes: usize,
        faucet_key: KeyPair,
        prune_seed_peers_for_discovery: bool,
        is_ipv4: bool,
        key_seed: Option<[u8; 32]>,
        output_dir: &Path,
        static_ports: bool,
    ) -> Result<Self> {
        // Generate trusted peer configs + their private keys.
        template.base.data_dir_path = output_dir.into();
        let (peers_private_keys, trusted_peers_config) =
            TrustedPeersConfigHelpers::get_test_config(num_nodes, key_seed);
        trusted_peers_config.save_config(&output_dir.join(&template.base.trusted_peers_file));
        let mut seed_peers_config = SeedPeersConfigHelpers::get_test_config_with_ipver(
            &trusted_peers_config,
            None,
            is_ipv4,
        );

        gen_genesis_transaction(
            &output_dir.join(&template.execution.genesis_file_location),
            &faucet_key,
            &trusted_peers_config,
        )?;

        let mut configs = Vec::new();
        // Generate configs for all nodes.
        for (node_id, addrs) in &seed_peers_config.seed_peers {
            let mut config = template.clone();
            config.base.peer_id = node_id.clone();
            // serialize keypairs on independent {node}.node.keys.toml file
            // this is because the peer_keypairs field is skipped during (de)serialization
            let private_keys = peers_private_keys.get(node_id.as_str()).unwrap();
            let peer_keypairs = KeyPairs::load(private_keys);
            let key_file_name = format!("{}.node.keys.toml", config.base.peer_id);

            config.base.peer_keypairs_file = key_file_name.into();
            peer_keypairs.save_config(&output_dir.join(&config.base.peer_keypairs_file));
            if !static_ports {
                NodeConfigHelpers::randomize_config_ports(&mut config);
            }

            // create subdirectory for storage: <node_id>/db, unless provided directly
            config.storage.dir = config.storage.dir.join(node_id).join("db");

            // If listen address is different from advertised address, we need to set it
            // appropriately below.
            config.network.listen_address = addrs[0].clone();
            config.network.advertised_address = addrs[0].clone();

            config.vm_config = default_config();
            configs.push(config);
        }
        if prune_seed_peers_for_discovery {
            seed_peers_config.seed_peers = seed_peers_config
                .seed_peers
                .clone()
                .into_iter()
                .take(1)
                .collect();
        }
        seed_peers_config.save_config(&output_dir.join(&template.network.seed_peers_file));
        let configs = configs
            .into_iter()
            .map(|config| {
                let file_name = format!("{}.node.config.toml", config.base.peer_id);
                let config_file = output_dir.join(file_name);
                (config_file, config)
            })
            .collect::<Vec<(PathBuf, NodeConfig)>>();

        for (path, node_config) in &configs {
            node_config.save_config(&path);
        }

        Ok(Self {
            configs,
            seed_peers: (
                output_dir.join(template.network.seed_peers_file),
                seed_peers_config,
            ),
            trusted_peers: (
                output_dir.join(template.base.trusted_peers_file),
                trusted_peers_config,
            ),
        })
    }

    pub fn get_configs(&self) -> &[(PathBuf, NodeConfig)] {
        &self.configs
    }

    pub fn get_seed_peers_config(&self) -> &(PathBuf, SeedPeersConfig) {
        &self.seed_peers
    }

    pub fn get_trusted_peers_config(&self) -> &(PathBuf, TrustedPeersConfig) {
        &self.trusted_peers
    }
}

pub struct SwarmConfigBuilder {
    node_count: usize,
    template_path: PathBuf,
    static_ports: bool,
    output_dir: PathBuf,
    force_discovery: bool,
    is_ipv4: bool,
    key_seed: Option<[u8; 32]>,
    faucet_account_keypair_filepath: Option<PathBuf>,
    faucet_account_keypair: Option<KeyPair>,
}
impl Default for SwarmConfigBuilder {
    fn default() -> Self {
        SwarmConfigBuilder {
            node_count: 1,
            template_path: "config/data/configs/node.config.toml".into(),
            static_ports: false,
            output_dir: "configs".into(),
            force_discovery: false,
            is_ipv4: false,
            key_seed: None,
            faucet_account_keypair_filepath: None,
            faucet_account_keypair: None,
        }
    }
}

impl SwarmConfigBuilder {
    pub fn new() -> SwarmConfigBuilder {
        SwarmConfigBuilder::default()
    }

    pub fn randomize_ports(&mut self) -> &mut Self {
        self.static_ports = false;
        self
    }

    pub fn static_ports(&mut self) -> &mut Self {
        self.static_ports = true;
        self
    }

    pub fn with_base<P: AsRef<Path>>(&mut self, base_template_path: P) -> &mut Self {
        self.template_path = base_template_path.as_ref().to_path_buf();
        self
    }

    pub fn with_output_dir<P: AsRef<Path>>(&mut self, output_dir: P) -> &mut Self {
        self.output_dir = output_dir.as_ref().to_path_buf();
        self
    }

    pub fn with_faucet_keypair_filepath<P: AsRef<Path>>(&mut self, keypair_file: P) -> &mut Self {
        self.faucet_account_keypair_filepath = Some(keypair_file.as_ref().to_path_buf());
        self
    }

    pub fn with_faucet_keypair(&mut self, keypair: KeyPair) -> &mut Self {
        self.faucet_account_keypair = Some(keypair);
        self
    }

    pub fn with_nodes(&mut self, n: usize) -> &mut Self {
        self.node_count = n;
        self
    }

    pub fn force_discovery(&mut self) -> &mut Self {
        self.force_discovery = true;
        self
    }

    pub fn with_ipv4(&mut self) -> &mut Self {
        self.is_ipv4 = true;
        self
    }

    pub fn with_ipv6(&mut self) -> &mut Self {
        self.is_ipv4 = false;
        self
    }

    pub fn with_key_seed(&mut self, seed: [u8; 32]) -> &mut Self {
        self.key_seed = Some(seed);
        self
    }

    pub fn build(&self) -> Result<SwarmConfig> {
        // verify required fields
        let faucet_key_path = self.faucet_account_keypair_filepath.clone();
        let faucet_key_option = self.faucet_account_keypair.clone();
        let faucet_key = faucet_key_option.unwrap_or_else(|| {
            generate_keypair::load_key_from_file(
                faucet_key_path.expect("Must provide faucet key file"),
            )
            .expect("Faucet account key is required to generate config")
        });

        // generate all things needed for generation
        if !self.output_dir.is_dir() {
            if !self.output_dir.exists() {
                // generate if doesn't exist
                std::fs::create_dir(&self.output_dir).expect("Failed to create output dir");
            }
            assert!(
                !self.output_dir.is_file(),
                "Output-dir is a file, expecting a directory"
            );
        }

        // read template
        let mut template = NodeConfig::load_template(&self.template_path)?;
        // update everything in the template and then generate swarm config
        let listen_address = if self.is_ipv4 { "0.0.0.0" } else { "::1" };
        let listen_address = listen_address.to_string();
        template.admission_control.address = listen_address.clone();
        template.debug_interface.address = listen_address;

        template.execution.genesis_file_location = "genesis.blob".to_string();

        // Set and generate trusted peers config file
        if template.base.trusted_peers_file.is_empty() {
            template.base.trusted_peers_file = "trusted_peers.config.toml".to_string();
        };
        // Set seed peers file and config. Config is populated in the loop below
        if template.network.seed_peers_file.is_empty() {
            template.network.seed_peers_file = "seed_peers.config.toml".to_string();
        };

        SwarmConfig::new(
            template,
            self.node_count,
            faucet_key,
            self.force_discovery,
            self.is_ipv4,
            self.key_seed,
            &self.output_dir,
            self.static_ports,
        )
    }
}
<commit_msg>[libra swarm] allow custom script execution<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! Convenience structs and functions for generating configuration for a swarm of libra nodes
use crate::util::gen_genesis_transaction;
use config::{
    config::{KeyPairs, NodeConfig, NodeConfigHelpers, VMPublishingOption},
    seed_peers::{SeedPeersConfig, SeedPeersConfigHelpers},
    trusted_peers::{TrustedPeersConfig, TrustedPeersConfigHelpers},
};
use crypto::signing::KeyPair;
use failure::prelude::*;
use std::path::{Path, PathBuf};

pub struct SwarmConfig {
    configs: Vec<(PathBuf, NodeConfig)>,
    seed_peers: (PathBuf, SeedPeersConfig),
    trusted_peers: (PathBuf, TrustedPeersConfig),
}

impl SwarmConfig {
    //TODO convert this to use the Builder paradigm
    pub fn new(
        mut template: NodeConfig,
        num_nodes: usize,
        faucet_key: KeyPair,
        prune_seed_peers_for_discovery: bool,
        is_ipv4: bool,
        key_seed: Option<[u8; 32]>,
        output_dir: &Path,
        static_ports: bool,
    ) -> Result<Self> {
        // Generate trusted peer configs + their private keys.
        template.base.data_dir_path = output_dir.into();
        let (peers_private_keys, trusted_peers_config) =
            TrustedPeersConfigHelpers::get_test_config(num_nodes, key_seed);
        trusted_peers_config.save_config(&output_dir.join(&template.base.trusted_peers_file));
        let mut seed_peers_config = SeedPeersConfigHelpers::get_test_config_with_ipver(
            &trusted_peers_config,
            None,
            is_ipv4,
        );

        gen_genesis_transaction(
            &output_dir.join(&template.execution.genesis_file_location),
            &faucet_key,
            &trusted_peers_config,
        )?;

        let mut configs = Vec::new();
        // Generate configs for all nodes.
        for (node_id, addrs) in &seed_peers_config.seed_peers {
            let mut config = template.clone();
            config.base.peer_id = node_id.clone();
            // serialize keypairs on independent {node}.node.keys.toml file
            // this is because the peer_keypairs field is skipped during (de)serialization
            let private_keys = peers_private_keys.get(node_id.as_str()).unwrap();
            let peer_keypairs = KeyPairs::load(private_keys);
            let key_file_name = format!("{}.node.keys.toml", config.base.peer_id);

            config.base.peer_keypairs_file = key_file_name.into();
            peer_keypairs.save_config(&output_dir.join(&config.base.peer_keypairs_file));
            if !static_ports {
                NodeConfigHelpers::randomize_config_ports(&mut config);
            }

            // create subdirectory for storage: <node_id>/db, unless provided directly
            config.storage.dir = config.storage.dir.join(node_id).join("db");

            // If listen address is different from advertised address, we need to set it
            // appropriately below.
            config.network.listen_address = addrs[0].clone();
            config.network.advertised_address = addrs[0].clone();

            config.vm_config.publishing_options = VMPublishingOption::Open;
            configs.push(config);
        }
        if prune_seed_peers_for_discovery {
            seed_peers_config.seed_peers = seed_peers_config
                .seed_peers
                .clone()
                .into_iter()
                .take(1)
                .collect();
        }
        seed_peers_config.save_config(&output_dir.join(&template.network.seed_peers_file));
        let configs = configs
            .into_iter()
            .map(|config| {
                let file_name = format!("{}.node.config.toml", config.base.peer_id);
                let config_file = output_dir.join(file_name);
                (config_file, config)
            })
            .collect::<Vec<(PathBuf, NodeConfig)>>();

        for (path, node_config) in &configs {
            node_config.save_config(&path);
        }

        Ok(Self {
            configs,
            seed_peers: (
                output_dir.join(template.network.seed_peers_file),
                seed_peers_config,
            ),
            trusted_peers: (
                output_dir.join(template.base.trusted_peers_file),
                trusted_peers_config,
            ),
        })
    }

    pub fn get_configs(&self) -> &[(PathBuf, NodeConfig)] {
        &self.configs
    }

    pub fn get_seed_peers_config(&self) -> &(PathBuf, SeedPeersConfig) {
        &self.seed_peers
    }

    pub fn get_trusted_peers_config(&self) -> &(PathBuf, TrustedPeersConfig) {
        &self.trusted_peers
    }
}

pub struct SwarmConfigBuilder {
    node_count: usize,
    template_path: PathBuf,
    static_ports: bool,
    output_dir: PathBuf,
    force_discovery: bool,
    is_ipv4: bool,
    key_seed: Option<[u8; 32]>,
    faucet_account_keypair_filepath: Option<PathBuf>,
    faucet_account_keypair: Option<KeyPair>,
}
impl Default for SwarmConfigBuilder {
    fn default() -> Self {
        SwarmConfigBuilder {
            node_count: 1,
            template_path: "config/data/configs/node.config.toml".into(),
            static_ports: false,
            output_dir: "configs".into(),
            force_discovery: false,
            is_ipv4: false,
            key_seed: None,
            faucet_account_keypair_filepath: None,
            faucet_account_keypair: None,
        }
    }
}

impl SwarmConfigBuilder {
    pub fn new() -> SwarmConfigBuilder {
        SwarmConfigBuilder::default()
    }

    pub fn randomize_ports(&mut self) -> &mut Self {
        self.static_ports = false;
        self
    }

    pub fn static_ports(&mut self) -> &mut Self {
        self.static_ports = true;
        self
    }

    pub fn with_base<P: AsRef<Path>>(&mut self, base_template_path: P) -> &mut Self {
        self.template_path = base_template_path.as_ref().to_path_buf();
        self
    }

    pub fn with_output_dir<P: AsRef<Path>>(&mut self, output_dir: P) -> &mut Self {
        self.output_dir = output_dir.as_ref().to_path_buf();
        self
    }

    pub fn with_faucet_keypair_filepath<P: AsRef<Path>>(&mut self, keypair_file: P) -> &mut Self {
        self.faucet_account_keypair_filepath = Some(keypair_file.as_ref().to_path_buf());
        self
    }

    pub fn with_faucet_keypair(&mut self, keypair: KeyPair) -> &mut Self {
        self.faucet_account_keypair = Some(keypair);
        self
    }

    pub fn with_nodes(&mut self, n: usize) -> &mut Self {
        self.node_count = n;
        self
    }

    pub fn force_discovery(&mut self) -> &mut Self {
        self.force_discovery = true;
        self
    }

    pub fn with_ipv4(&mut self) -> &mut Self {
        self.is_ipv4 = true;
        self
    }

    pub fn with_ipv6(&mut self) -> &mut Self {
        self.is_ipv4 = false;
        self
    }

    pub fn with_key_seed(&mut self, seed: [u8; 32]) -> &mut Self {
        self.key_seed = Some(seed);
        self
    }

    pub fn build(&self) -> Result<SwarmConfig> {
        // verify required fields
        let faucet_key_path = self.faucet_account_keypair_filepath.clone();
        let faucet_key_option = self.faucet_account_keypair.clone();
        let faucet_key = faucet_key_option.unwrap_or_else(|| {
            generate_keypair::load_key_from_file(
                faucet_key_path.expect("Must provide faucet key file"),
            )
            .expect("Faucet account key is required to generate config")
        });

        // generate all things needed for generation
        if !self.output_dir.is_dir() {
            if !self.output_dir.exists() {
                // generate if doesn't exist
                std::fs::create_dir(&self.output_dir).expect("Failed to create output dir");
            }
            assert!(
                !self.output_dir.is_file(),
                "Output-dir is a file, expecting a directory"
            );
        }

        // read template
        let mut template = NodeConfig::load_template(&self.template_path)?;
        // update everything in the template and then generate swarm config
        let listen_address = if self.is_ipv4 { "0.0.0.0" } else { "::1" };
        let listen_address = listen_address.to_string();
        template.admission_control.address = listen_address.clone();
        template.debug_interface.address = listen_address;

        template.execution.genesis_file_location = "genesis.blob".to_string();

        // Set and generate trusted peers config file
        if template.base.trusted_peers_file.is_empty() {
            template.base.trusted_peers_file = "trusted_peers.config.toml".to_string();
        };
        // Set seed peers file and config. Config is populated in the loop below
        if template.network.seed_peers_file.is_empty() {
            template.network.seed_peers_file = "seed_peers.config.toml".to_string();
        };

        SwarmConfig::new(
            template,
            self.node_count,
            faucet_key,
            self.force_discovery,
            self.is_ipv4,
            self.key_seed,
            &self.output_dir,
            self.static_ports,
        )
    }
}
<|endoftext|>