<commit_before>use super::{value::Value, Vm};
use crate::{builtin_functions::BuiltinFunction, compiler::lir::Instruction, input::Input};
use itertools::Itertools;
use log::{debug, trace};

impl Vm {
    pub(super) fn run_builtin_function(&mut self, builtin_function: BuiltinFunction) {
        trace!("run_builtin_function: builtin{:?}", builtin_function);

        let return_value = match builtin_function {
            BuiltinFunction::Add => self.add(),
            BuiltinFunction::Equals => Ok(self.equals()),
            BuiltinFunction::GetArgumentCount => self.get_argument_count(),
            BuiltinFunction::IfElse => self.if_else(),
            BuiltinFunction::Panic => self.panic_builtin(),
            BuiltinFunction::Print => self.print(),
            BuiltinFunction::StructGet => self.struct_get(),
            BuiltinFunction::StructGetKeys => self.struct_get_keys(),
            BuiltinFunction::StructHasKey => self.struct_has_key(),
            BuiltinFunction::TypeOf => Ok(self.type_of()),
            BuiltinFunction::Use => self.use_(),
            _ => panic!("Unhandled builtin function: {:?}", builtin_function),
        };
        let return_value = match return_value {
            Ok(value) => value,
            Err(panic_message) => self.panic(panic_message),
        };

        let return_object = self.heap.import(return_value);
        self.data_stack.push(return_object);
    }

    fn add(&mut self) -> Result<Value, String> {
        let b = self
            .pop_value()
            .unwrap()
            .try_into_int()
            .map_err(|it| format!("builtinAdd expects numbers as arguments, got {}.", it))?;
        let a = self
            .pop_value()
            .unwrap()
            .try_into_int()
            .map_err(|it| format!("builtinAdd expects numbers as arguments, got {}.", it))?;

        Ok((a + b).into())
    }

    fn equals(&mut self) -> Value {
        let b = self.pop_value().unwrap();
        let a = self.pop_value().unwrap();

        (a == b).into()
    }

    fn get_argument_count(&mut self) -> Result<Value, String> {
        let (_, chunk) = self
            .pop_value()
            .unwrap()
            .try_into_closure()
            .map_err(|it| format!("builtinAddArgumentCount expects a closure, got {}.", it))?;

        let num_args = self.chunks[chunk].num_args;
        Ok(Value::Int(num_args as u64))
    }

    fn if_else(&mut self) -> Result<Value, String> {
        let else_ = self.pop_value().unwrap();
        let then = self.pop_value().unwrap();
        let condition = self.pop_value().unwrap().try_into_symbol().map_err(|it| {
            format!(
                "builtinIfElse expects a boolean symbol as the condition, got {}.",
                it
            )
        })?;
        if then.clone().try_into_closure().is_err() {
            return Err(format!(
                "builtinIfElse expects a closure as the then, got {}.",
                then
            ));
        }
        if else_.clone().try_into_closure().is_err() {
            return Err(format!(
                "builtinIfElse expects a closure as the else, got {}.",
                else_
            ));
        }
        let condition = match condition.as_str() {
            "True" => true,
            "False" => false,
            _ => {
                return Err(format!(
                    "builtinIfElse expected True or False as a condition, but got {}",
                    condition
                ));
            }
        };

        let closure_object = self.heap.import(if condition { then } else { else_ });
        self.data_stack.push(closure_object);
        self.run_instruction(Instruction::Call);
        Ok(self.pop_value().unwrap())
    }

    fn panic_builtin(&mut self) -> Result<Value, String> {
        let message = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinPanic expects a text as the message, got {}.", it))?;

        Ok(self.panic(message))
    }

    fn print(&mut self) -> Result<Value, String> {
        let message = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinPrint expects a text as the message, got {}.", it))?;

        println!("{:?}", message);
        Ok(Value::nothing())
    }

    fn struct_get(&mut self) -> Result<Value, String> {
        let key = self.pop_value().unwrap();
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructGet expects struct, got {}.", it))?;

        match struct_.get(&key) {
            Some(value) => Ok(value.clone().into()),
            None => Err(format!("Struct does not contain key {:?}.", key)),
        }
    }

    fn struct_get_keys(&mut self) -> Result<Value, String> {
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructGetKeys expects struct, got {}.", it))?;

        Ok(Value::list(struct_.keys().cloned().collect()))
    }

    fn struct_has_key(&mut self) -> Result<Value, String> {
        let key = self.pop_value().unwrap();
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructHasKey expects struct, got {}.", it))?;

        Ok((struct_.contains_key(&key)).into())
    }

    fn type_of(&mut self) -> Value {
        let value = self.pop_value().unwrap();
        match value {
            Value::Int(_) => Value::Symbol("Int".to_owned()).into(),
            Value::Text(_) => Value::Symbol("Text".to_owned()).into(),
            Value::Symbol(_) => Value::Symbol("Symbol".to_owned()).into(),
            Value::Struct(_) => Value::Symbol("Struct".to_owned()).into(),
            Value::Closure { .. } => Value::Symbol("Function".to_owned()).into(),
        }
    }

    fn use_(&mut self) -> Result<Value, String> {
        let target = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinUse expects text as target, got {}.", it))?;
        let current_path_struct = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinUse expects struct as current path, got {}.", it))?;

        let mut current_path = vec![];
        let mut index = 0;
        while let Some(component) = current_path_struct.get(&Value::Int(index)) {
            current_path.push(component.clone().try_into_text().map_err(|it| format!("builtinUse expects a struct as current path with only textual paths as values, but the map contains the value {} for key {}", it, index)));
            index += 1;
        }

        let target = UseTarget::parse(&target)?;

        if target.parent_navigations > current_path.len() {
            return Err("Too many parent navigations.".to_string());
        }

        // let inputs = target.resolve(&current_path[..]);
        // let input = match inputs
        //     .iter()
        //     .filter(|&it| db.get_input(it.to_owned()).is_some())
        //     .next()
        // {
        //     Some(target) => target,
        //     None => {
        //         return self.panic(format!(
        //             "Target doesn't exist. Checked the following path(s): {}",
        //             inputs.iter().map(|it| format!("{}", it)).join(", ")
        //         ));
        //     }
        // };

        Ok(Value::Symbol("Used".to_string()))

        // TODO: Continue implementing use.
        // let (lir, _) = db.lir(input.clone()).unwrap();
        // TODO: Run LIR.
        // let discover_result = db.run_all(input.to_owned(), import_chain.to_owned());

        // TODO: Put public identifiers into map.
        // hir.identifiers
        //     .iter()
        //     .map(|(id, key)| {
        //         let mut key = key.to_owned();
        //         key.get_mut(0..1).unwrap().make_ascii_uppercase();
        //         let key = Value::Symbol(key.to_owned());

        //         let value = match discover_result.get(id) {
        //             Some(value) => value.to_owned()?,
        //             None => return DiscoverResult::ErrorInHir,
        //         };

        //         DiscoverResult::Value((key, value))
        //     })
        //     .collect::<DiscoverResult<HashMap<Value, Value>>>()
        //     .map(|it| Value::Struct(it))
    }
}

struct UseTarget {
    parent_navigations: usize,
    path: Vec<String>,
}
impl UseTarget {
    const PARENT_NAVIGATION_CHAR: char = '.';

    fn parse(target: &str) -> Result<Self, String> {
        let mut parent_navigations = 0;
        let mut target = target;
        while target.chars().next() == Some(UseTarget::PARENT_NAVIGATION_CHAR) {
            parent_navigations += 1;
            target = &target[UseTarget::PARENT_NAVIGATION_CHAR.len_utf8()..];
        }

        let mut path = vec![];
        loop {
            let mut chars = vec![];
            while let Some(c) = target.chars().next() {
                if c == UseTarget::PARENT_NAVIGATION_CHAR {
                    break;
                }
                chars.push(c);
                target = &target[c.len_utf8()..];
            }

            if target.is_empty() {
                path.push(chars.into_iter().join(""));
                break;
            }

            if chars.is_empty() {
                return Err("Target contains consecutive dots (`.`) in the path.".to_owned());
            }

            path.push(chars.into_iter().join(""));
        }
        Ok(UseTarget {
            parent_navigations,
            path,
        })
    }

    fn resolve(&self, current_path: &[String]) -> Vec<Input> {
        let mut path = current_path.to_owned();
        if self.parent_navigations == 0 {
            assert!(!path.is_empty());
            let last = path.last_mut().unwrap();
            if last == ".candy" {
                path.pop();
            } else {
                *last = last
                    .strip_suffix(".candy")
                    .expect("File name must end with `.candy`.")
                    .to_owned();
            }
        } else {
            for _ in 0..self.parent_navigations {
                if path.is_empty() {
                    return vec![];
                }
                path.pop();
            }
        }

        for part in &self.path {
            path.push(part.to_owned());
        }

        let mut result = vec![];

        let mut subdirectory = path.clone();
        subdirectory.push(".candy".to_owned());
        result.push(Input::File(subdirectory));

        if path.len() >= 1 {
            let last = path.last_mut().unwrap();
            *last = format!("{}.candy", last);
            result.push(Input::File(path));
        }
        result
    }
}

impl Vm {
    fn pop_value(&mut self) -> Option<Value> {
        let address = self.data_stack.pop()?;
        Some(self.heap.export(address))
    }
}
<commit_msg>Ensure that the closures given to builtinIfElse take zero parameters<commit_after>use super::{value::Value, Vm};
use crate::{builtin_functions::BuiltinFunction, compiler::lir::Instruction, input::Input};
use itertools::Itertools;
use log::{debug, trace};

impl Vm {
    pub(super) fn run_builtin_function(&mut self, builtin_function: BuiltinFunction) {
        trace!("run_builtin_function: builtin{:?}", builtin_function);

        let return_value = match builtin_function {
            BuiltinFunction::Add => self.add(),
            BuiltinFunction::Equals => Ok(self.equals()),
            BuiltinFunction::GetArgumentCount => self.get_argument_count(),
            BuiltinFunction::IfElse => self.if_else(),
            BuiltinFunction::Panic => self.panic_builtin(),
            BuiltinFunction::Print => self.print(),
            BuiltinFunction::StructGet => self.struct_get(),
            BuiltinFunction::StructGetKeys => self.struct_get_keys(),
            BuiltinFunction::StructHasKey => self.struct_has_key(),
            BuiltinFunction::TypeOf => Ok(self.type_of()),
            BuiltinFunction::Use => self.use_(),
            _ => panic!("Unhandled builtin function: {:?}", builtin_function),
        };
        let return_value = match return_value {
            Ok(value) => value,
            Err(panic_message) => self.panic(panic_message),
        };

        let return_object = self.heap.import(return_value);
        self.data_stack.push(return_object);
    }

    fn add(&mut self) -> Result<Value, String> {
        let b = self
            .pop_value()
            .unwrap()
            .try_into_int()
            .map_err(|it| format!("builtinAdd expects numbers as arguments, got {}.", it))?;
        let a = self
            .pop_value()
            .unwrap()
            .try_into_int()
            .map_err(|it| format!("builtinAdd expects numbers as arguments, got {}.", it))?;

        Ok((a + b).into())
    }

    fn equals(&mut self) -> Value {
        let b = self.pop_value().unwrap();
        let a = self.pop_value().unwrap();

        (a == b).into()
    }

    fn get_argument_count(&mut self) -> Result<Value, String> {
        let (_, chunk) = self
            .pop_value()
            .unwrap()
            .try_into_closure()
            .map_err(|it| format!("builtinAddArgumentCount expects a closure, got {}.", it))?;

        let num_args = self.chunks[chunk].num_args;
        Ok(Value::Int(num_args as u64))
    }

    fn if_else(&mut self) -> Result<Value, String> {
        let else_ = self.pop_value().unwrap();
        let then = self.pop_value().unwrap();
        let condition = self.pop_value().unwrap().try_into_symbol().map_err(|it| {
            format!(
                "builtinIfElse expects a boolean symbol as the condition, got {}.",
                it
            )
        })?;
        match then.clone() {
            Value::Closure { body, .. } => {
                if self.chunks[body].num_args > 0 {
                    return Err(format!("builtinIfElse expects a closure without arguments as the then, got one with {} arguments.", self.chunks[body].num_args));
                }
            }
            _ => {
                return Err(format!(
                    "builtinIfElse expects a closure as the then, got {}.",
                    then
                ))
            }
        };
        match else_.clone() {
            Value::Closure { body, .. } => {
                if self.chunks[body].num_args > 0 {
                    return Err(format!("builtinIfElse expects a closure without arguments as the else, got one with {} arguments.", self.chunks[body].num_args));
                }
            }
            _ => {
                return Err(format!(
                    "builtinIfElse expects a closure as the else, got {}.",
                    else_
                ))
            }
        };
        let condition = match condition.as_str() {
            "True" => true,
            "False" => false,
            _ => {
                return Err(format!(
                    "builtinIfElse expected True or False as a condition, but got {}",
                    condition
                ));
            }
        };

        let closure_object = self.heap.import(if condition { then } else { else_ });
        self.data_stack.push(closure_object);
        self.run_instruction(Instruction::Call);
        Ok(self.pop_value().unwrap())
    }

    fn panic_builtin(&mut self) -> Result<Value, String> {
        let message = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinPanic expects a text as the message, got {}.", it))?;

        Ok(self.panic(message))
    }

    fn print(&mut self) -> Result<Value, String> {
        let message = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinPrint expects a text as the message, got {}.", it))?;

        println!("{:?}", message);
        Ok(Value::nothing())
    }

    fn struct_get(&mut self) -> Result<Value, String> {
        let key = self.pop_value().unwrap();
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructGet expects struct, got {}.", it))?;

        match struct_.get(&key) {
            Some(value) => Ok(value.clone().into()),
            None => Err(format!("Struct does not contain key {:?}.", key)),
        }
    }

    fn struct_get_keys(&mut self) -> Result<Value, String> {
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructGetKeys expects struct, got {}.", it))?;

        Ok(Value::list(struct_.keys().cloned().collect()))
    }

    fn struct_has_key(&mut self) -> Result<Value, String> {
        let key = self.pop_value().unwrap();
        let struct_ = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinStructHasKey expects struct, got {}.", it))?;

        Ok((struct_.contains_key(&key)).into())
    }

    fn type_of(&mut self) -> Value {
        let value = self.pop_value().unwrap();
        match value {
            Value::Int(_) => Value::Symbol("Int".to_owned()).into(),
            Value::Text(_) => Value::Symbol("Text".to_owned()).into(),
            Value::Symbol(_) => Value::Symbol("Symbol".to_owned()).into(),
            Value::Struct(_) => Value::Symbol("Struct".to_owned()).into(),
            Value::Closure { .. } => Value::Symbol("Function".to_owned()).into(),
        }
    }

    fn use_(&mut self) -> Result<Value, String> {
        let target = self
            .pop_value()
            .unwrap()
            .try_into_text()
            .map_err(|it| format!("builtinUse expects text as target, got {}.", it))?;
        let current_path_struct = self
            .pop_value()
            .unwrap()
            .try_into_struct()
            .map_err(|it| format!("builtinUse expects struct as current path, got {}.", it))?;

        let mut current_path = vec![];
        let mut index = 0;
        while let Some(component) = current_path_struct.get(&Value::Int(index)) {
            current_path.push(component.clone().try_into_text().map_err(|it| format!("builtinUse expects a struct as current path with only textual paths as values, but the map contains the value {} for key {}", it, index)));
            index += 1;
        }

        let target = UseTarget::parse(&target)?;

        if target.parent_navigations > current_path.len() {
            return Err("Too many parent navigations.".to_string());
        }

        // let inputs = target.resolve(&current_path[..]);
        // let input = match inputs
        //     .iter()
        //     .filter(|&it| db.get_input(it.to_owned()).is_some())
        //     .next()
        // {
        //     Some(target) => target,
        //     None => {
        //         return self.panic(format!(
        //             "Target doesn't exist. Checked the following path(s): {}",
        //             inputs.iter().map(|it| format!("{}", it)).join(", ")
        //         ));
        //     }
        // };

        Ok(Value::Symbol("Used".to_string()))

        // TODO: Continue implementing use.
        // let (lir, _) = db.lir(input.clone()).unwrap();
        // TODO: Run LIR.
        // let discover_result = db.run_all(input.to_owned(), import_chain.to_owned());

        // TODO: Put public identifiers into map.
        // hir.identifiers
        //     .iter()
        //     .map(|(id, key)| {
        //         let mut key = key.to_owned();
        //         key.get_mut(0..1).unwrap().make_ascii_uppercase();
        //         let key = Value::Symbol(key.to_owned());

        //         let value = match discover_result.get(id) {
        //             Some(value) => value.to_owned()?,
        //             None => return DiscoverResult::ErrorInHir,
        //         };

        //         DiscoverResult::Value((key, value))
        //     })
        //     .collect::<DiscoverResult<HashMap<Value, Value>>>()
        //     .map(|it| Value::Struct(it))
    }
}

struct UseTarget {
    parent_navigations: usize,
    path: Vec<String>,
}
impl UseTarget {
    const PARENT_NAVIGATION_CHAR: char = '.';

    fn parse(target: &str) -> Result<Self, String> {
        let mut parent_navigations = 0;
        let mut target = target;
        while target.chars().next() == Some(UseTarget::PARENT_NAVIGATION_CHAR) {
            parent_navigations += 1;
            target = &target[UseTarget::PARENT_NAVIGATION_CHAR.len_utf8()..];
        }

        let mut path = vec![];
        loop {
            let mut chars = vec![];
            while let Some(c) = target.chars().next() {
                if c == UseTarget::PARENT_NAVIGATION_CHAR {
                    break;
                }
                chars.push(c);
                target = &target[c.len_utf8()..];
            }

            if target.is_empty() {
                path.push(chars.into_iter().join(""));
                break;
            }

            if chars.is_empty() {
                return Err("Target contains consecutive dots (`.`) in the path.".to_owned());
            }

            path.push(chars.into_iter().join(""));
        }
        Ok(UseTarget {
            parent_navigations,
            path,
        })
    }

    fn resolve(&self, current_path: &[String]) -> Vec<Input> {
        let mut path = current_path.to_owned();
        if self.parent_navigations == 0 {
            assert!(!path.is_empty());
            let last = path.last_mut().unwrap();
            if last == ".candy" {
                path.pop();
            } else {
                *last = last
                    .strip_suffix(".candy")
                    .expect("File name must end with `.candy`.")
                    .to_owned();
            }
        } else {
            for _ in 0..self.parent_navigations {
                if path.is_empty() {
                    return vec![];
                }
                path.pop();
            }
        }

        for part in &self.path {
            path.push(part.to_owned());
        }

        let mut result = vec![];

        let mut subdirectory = path.clone();
        subdirectory.push(".candy".to_owned());
        result.push(Input::File(subdirectory));

        if path.len() >= 1 {
            let last = path.last_mut().unwrap();
            *last = format!("{}.candy", last);
            result.push(Input::File(path));
        }
        result
    }
}

impl Vm {
    fn pop_value(&mut self) -> Option<Value> {
        let address = self.data_stack.pop()?;
        Some(self.heap.export(address))
    }
}
<|endoftext|>


<commit_before>// Copyright 2017 TiKV Project Authors. Licensed under Apache-2.0.

use std::error::Error;
use std::fs::File;
use std::io::Read;

use grpcio::{
    CertificateRequestType, Channel, ChannelBuilder, ChannelCredentialsBuilder, ServerBuilder,
    ServerCredentialsBuilder,
};

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(default)]
#[serde(rename_all = "kebab-case")]
pub struct SecurityConfig {
    pub ca_path: String,
    pub cert_path: String,
    pub key_path: String,
    // Test purpose only.
    #[serde(skip)]
    pub override_ssl_target: String,
    pub cipher_file: String,
}

impl Default for SecurityConfig {
    fn default() -> SecurityConfig {
        SecurityConfig {
            ca_path: String::new(),
            cert_path: String::new(),
            key_path: String::new(),
            override_ssl_target: String::new(),
            cipher_file: String::new(),
        }
    }
}

/// Checks and opens key file. Returns `Ok(None)` if the path is empty.
///
///  # Arguments
///
///  - `tag`: only used in the error message, like "ca key", "cert key", "private key", etc.
fn check_key_file(tag: &str, path: &str) -> Result<Option<File>, Box<dyn Error>> {
    if path.is_empty() {
        return Ok(None);
    }
    match File::open(path) {
        Err(e) => Err(format!("failed to open {} to load {}: {:?}", path, tag, e).into()),
        Ok(f) => Ok(Some(f)),
    }
}

/// Loads key file content. Returns `Ok(vec![])` if the path is empty.
fn load_key(tag: &str, path: &str) -> Result<Vec<u8>, Box<dyn Error>> {
    let mut key = vec![];
    let f = check_key_file(tag, path)?;
    match f {
        None => return Ok(vec![]),
        Some(mut f) => {
            if let Err(e) = f.read_to_end(&mut key) {
                return Err(format!("failed to load {} from path {}: {:?}", tag, path, e).into());
            }
        }
    }
    Ok(key)
}

impl SecurityConfig {
    /// Validates ca, cert and private key.
    pub fn validate(&mut self) -> Result<(), Box<dyn Error>> {
        check_key_file("ca key", &self.ca_path)?;
        check_key_file("cert key", &self.cert_path)?;
        check_key_file("private key", &self.key_path)?;
        // TODO: validate whether ca, cert and private key match.
        if (!self.ca_path.is_empty() || !self.cert_path.is_empty() || !self.key_path.is_empty())
            && (self.ca_path.is_empty() || self.cert_path.is_empty() || self.key_path.is_empty())
        {
            return Err("ca, cert and private key should be all configured.".into());
        }

        Ok(())
    }
}

#[derive(Default)]
pub struct SecurityManager {
    ca: Vec<u8>,
    cert: Vec<u8>,
    key: Vec<u8>,
    override_ssl_target: String,
    cipher_file: String,
}

impl Drop for SecurityManager {
    fn drop(&mut self) {
        use zeroize::Zeroize;

        self.key.zeroize();
    }
}

impl SecurityManager {
    pub fn new(cfg: &SecurityConfig) -> Result<SecurityManager, Box<dyn Error>> {
        Ok(SecurityManager {
            ca: load_key("CA", &cfg.ca_path)?,
            cert: load_key("certificate", &cfg.cert_path)?,
            key: load_key("private key", &cfg.key_path)?,
            override_ssl_target: cfg.override_ssl_target.clone(),
            cipher_file: cfg.cipher_file.clone(),
        })
    }

    pub fn connect(&self, mut cb: ChannelBuilder, addr: &str) -> Channel {
        if self.ca.is_empty() {
            cb.connect(addr)
        } else {
            if !self.override_ssl_target.is_empty() {
                cb = cb.override_ssl_target(self.override_ssl_target.clone());
            }
            let cred = ChannelCredentialsBuilder::new()
                .root_cert(self.ca.clone())
                .cert(self.cert.clone(), self.key.clone())
                .build();
            cb.secure_connect(addr, cred)
        }
    }

    pub fn bind(&self, sb: ServerBuilder, addr: &str, port: u16) -> ServerBuilder {
        if self.ca.is_empty() {
            sb.bind(addr, port)
        } else {
            let cred = ServerCredentialsBuilder::new()
                .root_cert(
                    self.ca.clone(),
                    CertificateRequestType::RequestAndRequireClientCertificateAndVerify,
                )
                .add_cert(self.cert.clone(), self.key.clone())
                .build();
            sb.bind_with_cred(addr, port, cred)
        }
    }

    pub fn cipher_file(&self) -> &str {
        &self.cipher_file
    }
}

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

    use std::fs;

    use tempfile::Builder;

    #[test]
    fn test_security() {
        let mut cfg = SecurityConfig::default();
        // default is disable secure connection.
        cfg.validate().unwrap();
        let mut mgr = SecurityManager::new(&cfg).unwrap();
        assert!(mgr.ca.is_empty());
        assert!(mgr.cert.is_empty());
        assert!(mgr.key.is_empty());

        let assert_cfg = |c: fn(&mut SecurityConfig), valid: bool| {
            let mut invalid_cfg = cfg.clone();
            c(&mut invalid_cfg);
            assert_eq!(invalid_cfg.validate().is_ok(), valid);
        };

        // invalid path should be rejected.
        assert_cfg(
            |c| {
                c.ca_path = "invalid ca path".to_owned();
                c.cert_path = "invalid cert path".to_owned();
                c.key_path = "invalid key path".to_owned();
            },
            false,
        );

        let temp = Builder::new().prefix("test_cred").tempdir().unwrap();
        let example_ca = temp.path().join("ca");
        let example_cert = temp.path().join("cert");
        let example_key = temp.path().join("key");
        for (id, f) in (&[&example_ca, &example_cert, &example_key])
            .iter()
            .enumerate()
        {
            fs::write(f, &[id as u8]).unwrap();
        }
        let mut c = cfg.clone();
        c.cert_path = format!("{}", example_cert.display());
        c.key_path = format!("{}", example_key.display());
        // incomplete configuration.
        c.validate().unwrap_err();

        // data should be loaded from file after validating.
        c.ca_path = format!("{}", example_ca.display());
        c.validate().unwrap();
        mgr = SecurityManager::new(&c).unwrap();
        assert_eq!(mgr.ca, vec![0]);
        assert_eq!(mgr.cert, vec![1]);
        assert_eq!(mgr.key, vec![2]);
    }
}
<commit_msg>Security: support certificates reloading (#7150)<commit_after>// Copyright 2017 TiKV Project Authors. Licensed under Apache-2.0.

use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::sync::Arc;

use grpcio::{
    CertificateRequestType, Channel, ChannelBuilder, ChannelCredentialsBuilder, ServerBuilder,
    ServerCredentialsBuilder, ServerCredentialsFetcher,
};

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(default)]
#[serde(rename_all = "kebab-case")]
pub struct SecurityConfig {
    pub ca_path: String,
    pub cert_path: String,
    pub key_path: String,
    // Test purpose only.
    #[serde(skip)]
    pub override_ssl_target: String,
    pub cipher_file: String,
}

impl Default for SecurityConfig {
    fn default() -> SecurityConfig {
        SecurityConfig {
            ca_path: String::new(),
            cert_path: String::new(),
            key_path: String::new(),
            override_ssl_target: String::new(),
            cipher_file: String::new(),
        }
    }
}

/// Checks and opens key file. Returns `Ok(None)` if the path is empty.
///
///  # Arguments
///
///  - `tag`: only used in the error message, like "ca key", "cert key", "private key", etc.
fn check_key_file(tag: &str, path: &str) -> Result<Option<File>, Box<dyn Error>> {
    if path.is_empty() {
        return Ok(None);
    }
    match File::open(path) {
        Err(e) => Err(format!("failed to open {} to load {}: {:?}", path, tag, e).into()),
        Ok(f) => Ok(Some(f)),
    }
}

/// Loads key file content. Returns `Ok(vec![])` if the path is empty.
fn load_key(tag: &str, path: &str) -> Result<Vec<u8>, Box<dyn Error>> {
    let mut key = vec![];
    let f = check_key_file(tag, path)?;
    match f {
        None => return Ok(vec![]),
        Some(mut f) => {
            if let Err(e) = f.read_to_end(&mut key) {
                return Err(format!("failed to load {} from path {}: {:?}", tag, path, e).into());
            }
        }
    }
    Ok(key)
}

type CertResult = Result<(Vec<u8>, Vec<u8>, Vec<u8>), Box<dyn Error>>;

impl SecurityConfig {
    /// Validates ca, cert and private key.
    pub fn validate(&self) -> Result<(), Box<dyn Error>> {
        check_key_file("ca key", &self.ca_path)?;
        check_key_file("cert key", &self.cert_path)?;
        check_key_file("private key", &self.key_path)?;
        // TODO: validate whether ca, cert and private key match.
        if (!self.ca_path.is_empty() || !self.cert_path.is_empty() || !self.key_path.is_empty())
            && (self.ca_path.is_empty() || self.cert_path.is_empty() || self.key_path.is_empty())
        {
            return Err("ca, cert and private key should be all configured.".into());
        }

        Ok(())
    }

    /// Load certificates from the given file path.
    /// Return ca, cert, key after successful read.
    fn load_certs(&self) -> CertResult {
        let ca = load_key("CA", &self.ca_path)?;
        let cert = load_key("certificate", &self.cert_path)?;
        let key = load_key("private key", &self.key_path)?;
        if ca.is_empty() || cert.is_empty() || key.is_empty() {
            return Err("ca, cert and private key should be all configured.".into());
        }
        Ok((ca, cert, key))
    }
}

#[derive(Default)]
pub struct SecurityManager {
    cfg: Arc<SecurityConfig>,
}

impl SecurityManager {
    pub fn new(cfg: &SecurityConfig) -> Result<SecurityManager, Box<dyn Error>> {
        Ok(SecurityManager {
            cfg: Arc::new(cfg.clone()),
        })
    }

    pub fn connect(&self, mut cb: ChannelBuilder, addr: &str) -> Channel {
        if self.cfg.ca_path.is_empty() {
            cb.connect(addr)
        } else {
            if !self.cfg.override_ssl_target.is_empty() {
                cb = cb.override_ssl_target(self.cfg.override_ssl_target.clone());
            }
            // Fill in empty certificate information if read fails.
            // Returning empty certificates delays error processing until
            // actual connection in grpc.
            let (ca, cert, key) = self.cfg.load_certs().unwrap_or_default();

            let cred = ChannelCredentialsBuilder::new()
                .root_cert(ca)
                .cert(cert, key)
                .build();
            cb.secure_connect(addr, cred)
        }
    }

    pub fn bind(&self, sb: ServerBuilder, addr: &str, port: u16) -> ServerBuilder {
        if self.cfg.ca_path.is_empty() {
            sb.bind(addr, port)
        } else {
            let fetcher = Box::new(Fetcher {
                cfg: self.cfg.clone(),
            });
            sb.bind_with_fetcher(
                addr,
                port,
                fetcher,
                CertificateRequestType::RequestAndRequireClientCertificateAndVerify,
            )
        }
    }

    pub fn cipher_file(&self) -> &str {
        &self.cfg.cipher_file
    }
}

struct Fetcher {
    cfg: Arc<SecurityConfig>,
}

impl ServerCredentialsFetcher for Fetcher {
    fn fetch(&self) -> Result<Option<ServerCredentialsBuilder>, Box<dyn Error>> {
        let (ca, cert, key) = self.cfg.load_certs()?;
        let new_cred = ServerCredentialsBuilder::new()
            .add_cert(cert, key)
            .root_cert(
                ca,
                CertificateRequestType::RequestAndRequireClientCertificateAndVerify,
            );
        Ok(Some(new_cred))
    }
}

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

    use std::fs;

    use tempfile::Builder;

    #[test]
    fn test_security() {
        let cfg = SecurityConfig::default();
        // default is disable secure connection.
        cfg.validate().unwrap();
        let mgr = SecurityManager::new(&cfg).unwrap();
        assert!(mgr.cfg.ca_path.is_empty());
        assert!(mgr.cfg.cert_path.is_empty());
        assert!(mgr.cfg.key_path.is_empty());

        let assert_cfg = |c: fn(&mut SecurityConfig), valid: bool| {
            let mut invalid_cfg = cfg.clone();
            c(&mut invalid_cfg);
            assert_eq!(invalid_cfg.validate().is_ok(), valid);
        };

        // invalid path should be rejected.
        assert_cfg(
            |c| {
                c.ca_path = "invalid ca path".to_owned();
                c.cert_path = "invalid cert path".to_owned();
                c.key_path = "invalid key path".to_owned();
            },
            false,
        );

        let temp = Builder::new().prefix("test_cred").tempdir().unwrap();
        let example_ca = temp.path().join("ca");
        let example_cert = temp.path().join("cert");
        let example_key = temp.path().join("key");
        for (id, f) in (&[&example_ca, &example_cert, &example_key])
            .iter()
            .enumerate()
        {
            fs::write(f, &[id as u8]).unwrap();
        }

        let mut c = cfg.clone();
        c.cert_path = format!("{}", example_cert.display());
        c.key_path = format!("{}", example_key.display());
        // incomplete configuration.
        c.validate().unwrap_err();

        // data should be loaded from file after validating.
        c.ca_path = format!("{}", example_ca.display());
        c.validate().unwrap();

        let (ca, cert, key) = c.load_certs().unwrap_or_default();
        assert_eq!(ca, vec![0]);
        assert_eq!(cert, vec![1]);
        assert_eq!(key, vec![2]);
    }
}
<|endoftext|>


<commit_before>// Copyright (C) 2020, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use super::common::alpns;

pub trait Args {
    fn with_docopt(docopt: &docopt::Docopt) -> Self;
}

/// Contains commons arguments for creating a quiche QUIC connection.
pub struct CommonArgs {
    pub alpns: Vec<u8>,
    pub max_data: u64,
    pub max_stream_data: u64,
    pub max_streams_bidi: u64,
    pub max_streams_uni: u64,
    pub idle_timeout: u64,
    pub early_data: bool,
    pub dump_packet_path: Option<String>,
    pub no_grease: bool,
    pub cc_algorithm: String,
    pub disable_hystart: bool,
    pub dgrams_enabled: bool,
    pub dgram_count: u64,
    pub dgram_data: String,
}

/// Creates a new `CommonArgs` structure using the provided [`Docopt`].
///
/// The `Docopt` usage String needs to include the following:
///
/// --http-version VERSION      HTTP version to use.
/// --max-data BYTES            Connection-wide flow control limit.
/// --max-stream-data BYTES     Per-stream flow control limit.
/// --max-streams-bidi STREAMS  Number of allowed concurrent streams.
/// --max-streams-uni STREAMS   Number of allowed concurrent streams.
/// --dump-packets PATH         Dump the incoming packets in PATH.
/// --no-grease                 Don't send GREASE.
/// --cc-algorithm NAME         Set a congestion control algorithm.
/// --disable-hystart           Disable HyStart++.
/// --dgram-proto PROTO         DATAGRAM application protocol.
/// --dgram-count COUNT         Number of DATAGRAMs to send.
///  --dgram-data DATA          DATAGRAM data to send.
///
/// [`Docopt`]: https://docs.rs/docopt/1.1.0/docopt/
impl Args for CommonArgs {
    fn with_docopt(docopt: &docopt::Docopt) -> Self {
        let args = docopt.parse().unwrap_or_else(|e| e.exit());

        let http_version = args.get_str("--http-version");
        let dgram_proto = args.get_str("--dgram-proto");
        let (alpns, dgrams_enabled) = match (http_version, dgram_proto) {
            ("HTTP/0.9", "none") =>
                (alpns::length_prefixed(&alpns::HTTP_09), false),

            ("HTTP/0.9", _) =>
                panic!("Unsupported HTTP version and DATAGRAM protocol."),

            ("HTTP/3", "none") => (alpns::length_prefixed(&alpns::HTTP_3), false),

            ("HTTP/3", "oneway") =>
                (alpns::length_prefixed(&alpns::HTTP_3), true),

            ("all", "none") => (
                [
                    alpns::length_prefixed(&alpns::HTTP_3),
                    alpns::length_prefixed(&alpns::HTTP_09),
                ]
                .concat(),
                false,
            ),

            // SiDuck is it's own application protocol.
            (_, "siduck") => (alpns::length_prefixed(&alpns::SIDUCK), true),

            (..) => panic!("Unsupported HTTP version and DATAGRAM protocol."),
        };

        let dgram_count = args.get_str("--dgram-count");
        let dgram_count = u64::from_str_radix(dgram_count, 10).unwrap();

        let dgram_data = args.get_str("--dgram-data").to_string();

        let max_data = args.get_str("--max-data");
        let max_data = u64::from_str_radix(max_data, 10).unwrap();

        let max_stream_data = args.get_str("--max-stream-data");
        let max_stream_data = u64::from_str_radix(max_stream_data, 10).unwrap();

        let max_streams_bidi = args.get_str("--max-streams-bidi");
        let max_streams_bidi = u64::from_str_radix(max_streams_bidi, 10).unwrap();

        let max_streams_uni = args.get_str("--max-streams-uni");
        let max_streams_uni = u64::from_str_radix(max_streams_uni, 10).unwrap();

        let idle_timeout = args.get_str("--idle-timeout");
        let idle_timeout = u64::from_str_radix(idle_timeout, 10).unwrap();

        let early_data = args.get_bool("--early-data");

        let dump_packet_path = if args.get_str("--dump-packets") != "" {
            Some(args.get_str("--dump-packets").to_string())
        } else {
            None
        };

        let no_grease = args.get_bool("--no-grease");

        let cc_algorithm = args.get_str("--cc-algorithm");

        let disable_hystart = args.get_bool("--disable-hystart");

        CommonArgs {
            alpns,
            max_data,
            max_stream_data,
            max_streams_bidi,
            max_streams_uni,
            idle_timeout,
            early_data,
            dump_packet_path,
            no_grease,
            cc_algorithm: cc_algorithm.to_string(),
            disable_hystart,
            dgrams_enabled,
            dgram_count,
            dgram_data,
        }
    }
}

impl Default for CommonArgs {
    fn default() -> Self {
        CommonArgs {
            alpns: alpns::length_prefixed(&alpns::HTTP_3),
            max_data: 10000000,
            max_stream_data: 1000000,
            max_streams_bidi: 100,
            max_streams_uni: 100,
            idle_timeout: 30000,
            early_data: false,
            dump_packet_path: None,
            no_grease: false,
            cc_algorithm: "cubic".to_string(),
            disable_hystart: false,
            dgrams_enabled: false,
            dgram_count: 0,
            dgram_data: "quack".to_string(),
        }
    }
}

pub const CLIENT_USAGE: &str = "Usage:
  quiche-client [options] URL...
  quiche-client -h | --help

Options:
  --method METHOD          Use the given HTTP request method [default: GET].
  --body FILE              Send the given file as request body.
  --max-data BYTES         Connection-wide flow control limit [default: 10000000].
  --max-stream-data BYTES  Per-stream flow control limit [default: 1000000].
  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].
  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].
  --idle-timeout TIMEOUT   Idle timeout in milliseconds [default: 30000].
  --wire-version VERSION   The version number to send to the server [default: babababa].
  --http-version VERSION   HTTP version to use [default: all].
  --early-data             Enable sending early data.
  --dgram-proto PROTO      DATAGRAM application protocol to use [default: none].
  --dgram-count COUNT      Number of DATAGRAMs to send [default: 0].
  --dgram-data DATA        Data to send for certain types of DATAGRAM application protocol [default: quack].
  --dump-packets PATH      Dump the incoming packets as files in the given directory.
  --dump-responses PATH    Dump response payload as files in the given directory.
  --dump-json              Dump response headers and payload to stdout in JSON format.
  --max-json-payload BYTES  Per-response payload limit when dumping JSON [default: 10000].
  --connect-to ADDRESS     Override ther server's address.
  --no-verify              Don't verify server's certificate.
  --no-grease              Don't send GREASE.
  --cc-algorithm NAME      Specify which congestion control algorithm to use [default: cubic].
  --disable-hystart        Disable HyStart++.
  -H --header HEADER ...   Add a request header.
  -n --requests REQUESTS   Send the given number of identical requests [default: 1].
  --session-file PATH      File used to cache a TLS session for resumption.
  -h --help                Show this screen.
";

/// Application-specific arguments that compliment the `CommonArgs`.
pub struct ClientArgs {
    pub version: u32,
    pub dump_response_path: Option<String>,
    pub dump_json: Option<usize>,
    pub urls: Vec<url::Url>,
    pub reqs_cardinal: u64,
    pub req_headers: Vec<String>,
    pub no_verify: bool,
    pub body: Option<Vec<u8>>,
    pub method: String,
    pub connect_to: Option<String>,
    pub session_file: Option<String>,
}

impl Args for ClientArgs {
    fn with_docopt(docopt: &docopt::Docopt) -> Self {
        let args = docopt.parse().unwrap_or_else(|e| e.exit());

        let version = args.get_str("--wire-version");
        let version = u32::from_str_radix(version, 16).unwrap();

        let dump_response_path = if args.get_str("--dump-responses") != "" {
            Some(args.get_str("--dump-responses").to_string())
        } else {
            None
        };

        let dump_json = args.get_bool("--dump-json");
        let dump_json = if dump_json {
            let max_payload = args.get_str("--max-json-payload");
            let max_payload = usize::from_str_radix(max_payload, 10).unwrap();
            Some(max_payload)
        } else {
            None
        };

        // URLs (can be multiple).
        let urls: Vec<url::Url> = args
            .get_vec("URL")
            .into_iter()
            .map(|x| url::Url::parse(x).unwrap())
            .collect();

        // Request headers (can be multiple).
        let req_headers = args
            .get_vec("--header")
            .into_iter()
            .map(|x| x.to_string())
            .collect();

        let reqs_cardinal = args.get_str("--requests");
        let reqs_cardinal = u64::from_str_radix(reqs_cardinal, 10).unwrap();

        let no_verify = args.get_bool("--no-verify");

        let body = if args.get_bool("--body") {
            std::fs::read(args.get_str("--body")).ok()
        } else {
            None
        };

        let method = args.get_str("--method").to_string();

        let connect_to = if args.get_bool("--connect-to") {
            Some(args.get_str("--connect-to").to_string())
        } else {
            None
        };

        let session_file = if args.get_bool("--session-file") {
            Some(args.get_str("--session-file").to_string())
        } else {
            None
        };

        ClientArgs {
            version,
            dump_response_path,
            dump_json,
            urls,
            req_headers,
            reqs_cardinal,
            no_verify,
            body,
            method,
            connect_to,
            session_file,
        }
    }
}

impl Default for ClientArgs {
    fn default() -> Self {
        ClientArgs {
            version: 0xbabababa,
            dump_response_path: None,
            dump_json: None,
            urls: vec![],
            req_headers: vec![],
            reqs_cardinal: 1,
            no_verify: false,
            body: None,
            method: "GET".to_string(),
            connect_to: None,
            session_file: None,
        }
    }
}

pub const SERVER_USAGE: &str = "Usage:
  quiche-server [options]
  quiche-server -h | --help

Options:
  --listen <addr>             Listen on the given IP:port [default: 127.0.0.1:4433]
  --cert <file>               TLS certificate path [default: src/bin/cert.crt]
  --key <file>                TLS certificate key path [default: src/bin/cert.key]
  --root <dir>                Root directory [default: src/bin/root/]
  --index <name>              The file that will be used as index [default: index.html].
  --name <str>                Name of the server [default: quic.tech]
  --max-data BYTES            Connection-wide flow control limit [default: 10000000].
  --max-stream-data BYTES     Per-stream flow control limit [default: 1000000].
  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].
  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].
  --idle-timeout TIMEOUT   Idle timeout in milliseconds [default: 30000].
  --dump-packets PATH         Dump the incoming packets as files in the given directory.
  --early-data                Enable receiving early data.
  --no-retry                  Disable stateless retry.
  --no-grease                 Don't send GREASE.
  --http-version VERSION      HTTP version to use [default: all].
  --dgram-proto PROTO         DATAGRAM application protocol to use [default: none].
  --dgram-count COUNT         Number of DATAGRAMs to send [default: 0].
  --dgram-data DATA           Data to send for certain types of DATAGRAM application protocol [default: brrr].
  --cc-algorithm NAME         Specify which congestion control algorithm to use [default: cubic].
  --disable-hystart           Disable HyStart++.
  -h --help                   Show this screen.
";

// Application-specific arguments that compliment the `CommonArgs`.
pub struct ServerArgs {
    pub listen: String,
    pub no_retry: bool,
    pub root: String,
    pub index: String,
    pub cert: String,
    pub key: String,
}

impl Args for ServerArgs {
    fn with_docopt(docopt: &docopt::Docopt) -> Self {
        let args = docopt.parse().unwrap_or_else(|e| e.exit());

        let listen = args.get_str("--listen").to_string();
        let no_retry = args.get_bool("--no-retry");
        let root = args.get_str("--root").to_string();
        let index = args.get_str("--index").to_string();
        let cert = args.get_str("--cert").to_string();
        let key = args.get_str("--key").to_string();

        ServerArgs {
            listen,
            no_retry,
            root,
            index,
            cert,
            key,
        }
    }
}
<commit_msg>apps: use parse() to convert string to int<commit_after>// Copyright (C) 2020, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use super::common::alpns;

pub trait Args {
    fn with_docopt(docopt: &docopt::Docopt) -> Self;
}

/// Contains commons arguments for creating a quiche QUIC connection.
pub struct CommonArgs {
    pub alpns: Vec<u8>,
    pub max_data: u64,
    pub max_stream_data: u64,
    pub max_streams_bidi: u64,
    pub max_streams_uni: u64,
    pub idle_timeout: u64,
    pub early_data: bool,
    pub dump_packet_path: Option<String>,
    pub no_grease: bool,
    pub cc_algorithm: String,
    pub disable_hystart: bool,
    pub dgrams_enabled: bool,
    pub dgram_count: u64,
    pub dgram_data: String,
}

/// Creates a new `CommonArgs` structure using the provided [`Docopt`].
///
/// The `Docopt` usage String needs to include the following:
///
/// --http-version VERSION      HTTP version to use.
/// --max-data BYTES            Connection-wide flow control limit.
/// --max-stream-data BYTES     Per-stream flow control limit.
/// --max-streams-bidi STREAMS  Number of allowed concurrent streams.
/// --max-streams-uni STREAMS   Number of allowed concurrent streams.
/// --dump-packets PATH         Dump the incoming packets in PATH.
/// --no-grease                 Don't send GREASE.
/// --cc-algorithm NAME         Set a congestion control algorithm.
/// --disable-hystart           Disable HyStart++.
/// --dgram-proto PROTO         DATAGRAM application protocol.
/// --dgram-count COUNT         Number of DATAGRAMs to send.
///  --dgram-data DATA          DATAGRAM data to send.
///
/// [`Docopt`]: https://docs.rs/docopt/1.1.0/docopt/
impl Args for CommonArgs {
    fn with_docopt(docopt: &docopt::Docopt) -> Self {
        let args = docopt.parse().unwrap_or_else(|e| e.exit());

        let http_version = args.get_str("--http-version");
        let dgram_proto = args.get_str("--dgram-proto");
        let (alpns, dgrams_enabled) = match (http_version, dgram_proto) {
            ("HTTP/0.9", "none") =>
                (alpns::length_prefixed(&alpns::HTTP_09), false),

            ("HTTP/0.9", _) =>
                panic!("Unsupported HTTP version and DATAGRAM protocol."),

            ("HTTP/3", "none") => (alpns::length_prefixed(&alpns::HTTP_3), false),

            ("HTTP/3", "oneway") =>
                (alpns::length_prefixed(&alpns::HTTP_3), true),

            ("all", "none") => (
                [
                    alpns::length_prefixed(&alpns::HTTP_3),
                    alpns::length_prefixed(&alpns::HTTP_09),
                ]
                .concat(),
                false,
            ),

            // SiDuck is it's own application protocol.
            (_, "siduck") => (alpns::length_prefixed(&alpns::SIDUCK), true),

            (..) => panic!("Unsupported HTTP version and DATAGRAM protocol."),
        };

        let dgram_count = args.get_str("--dgram-count");
        let dgram_count = dgram_count.parse::<u64>().unwrap();

        let dgram_data = args.get_str("--dgram-data").to_string();

        let max_data = args.get_str("--max-data");
        let max_data = max_data.parse::<u64>().unwrap();

        let max_stream_data = args.get_str("--max-stream-data");
        let max_stream_data = max_stream_data.parse::<u64>().unwrap();

        let max_streams_bidi = args.get_str("--max-streams-bidi");
        let max_streams_bidi = max_streams_bidi.parse::<u64>().unwrap();

        let max_streams_uni = args.get_str("--max-streams-uni");
        let max_streams_uni = max_streams_uni.parse::<u64>().unwrap();

        let idle_timeout = args.get_str("--idle-timeout");
        let idle_timeout = idle_timeout.parse::<u64>().unwrap();

        let early_data = args.get_bool("--early-data");

        let dump_packet_path = if args.get_str("--dump-packets") != "" {
            Some(args.get_str("--dump-packets").to_string())
        } else {
            None
        };

        let no_grease = args.get_bool("--no-grease");

        let cc_algorithm = args.get_str("--cc-algorithm");

        let disable_hystart = args.get_bool("--disable-hystart");

        CommonArgs {
            alpns,
            max_data,
            max_stream_data,
            max_streams_bidi,
            max_streams_uni,
            idle_timeout,
            early_data,
            dump_packet_path,
            no_grease,
            cc_algorithm: cc_algorithm.to_string(),
            disable_hystart,
            dgrams_enabled,
            dgram_count,
            dgram_data,
        }
    }
}

impl Default for CommonArgs {
    fn default() -> Self {
        CommonArgs {
            alpns: alpns::length_prefixed(&alpns::HTTP_3),
            max_data: 10000000,
            max_stream_data: 1000000,
            max_streams_bidi: 100,
            max_streams_uni: 100,
            idle_timeout: 30000,
            early_data: false,
            dump_packet_path: None,
            no_grease: false,
            cc_algorithm: "cubic".to_string(),
            disable_hystart: false,
            dgrams_enabled: false,
            dgram_count: 0,
            dgram_data: "quack".to_string(),
        }
    }
}

pub const CLIENT_USAGE: &str = "Usage:
  quiche-client [options] URL...
  quiche-client -h | --help

Options:
  --method METHOD          Use the given HTTP request method [default: GET].
  --body FILE              Send the given file as request body.
  --max-data BYTES         Connection-wide flow control limit [default: 10000000].
  --max-stream-data BYTES  Per-stream flow control limit [default: 1000000].
  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].
  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].
  --idle-timeout TIMEOUT   Idle timeout in milliseconds [default: 30000].
  --wire-version VERSION   The version number to send to the server [default: babababa].
  --http-version VERSION   HTTP version to use [default: all].
  --early-data             Enable sending early data.
  --dgram-proto PROTO      DATAGRAM application protocol to use [default: none].
  --dgram-count COUNT      Number of DATAGRAMs to send [default: 0].
  --dgram-data DATA        Data to send for certain types of DATAGRAM application protocol [default: quack].
  --dump-packets PATH      Dump the incoming packets as files in the given directory.
  --dump-responses PATH    Dump response payload as files in the given directory.
  --dump-json              Dump response headers and payload to stdout in JSON format.
  --max-json-payload BYTES  Per-response payload limit when dumping JSON [default: 10000].
  --connect-to ADDRESS     Override ther server's address.
  --no-verify              Don't verify server's certificate.
  --no-grease              Don't send GREASE.
  --cc-algorithm NAME      Specify which congestion control algorithm to use [default: cubic].
  --disable-hystart        Disable HyStart++.
  -H --header HEADER ...   Add a request header.
  -n --requests REQUESTS   Send the given number of identical requests [default: 1].
  --session-file PATH      File used to cache a TLS session for resumption.
  -h --help                Show this screen.
";

/// Application-specific arguments that compliment the `CommonArgs`.
pub struct ClientArgs {
    pub version: u32,
    pub dump_response_path: Option<String>,
    pub dump_json: Option<usize>,
    pub urls: Vec<url::Url>,
    pub reqs_cardinal: u64,
    pub req_headers: Vec<String>,
    pub no_verify: bool,
    pub body: Option<Vec<u8>>,
    pub method: String,
    pub connect_to: Option<String>,
    pub session_file: Option<String>,
}

impl Args for ClientArgs {
    fn with_docopt(docopt: &docopt::Docopt) -> Self {
        let args = docopt.parse().unwrap_or_else(|e| e.exit());

        let version = args.get_str("--wire-version");
        let version = u32::from_str_radix(version, 16).unwrap();

        let dump_response_path = if args.get_str("--dump-responses") != "" {
            Some(args.get_str("--dump-responses").to_string())
        } else {
            None
        };

        let dump_json = args.get_bool("--dump-json");
        let dump_json = if dump_json {
            let max_payload = args.get_str("--max-json-payload");
            let max_payload = max_payload.parse::<usize>().unwrap();
            Some(max_payload)
        } else {
            None
        };

        // URLs (can be multiple).
        let urls: Vec<url::Url> = args
            .get_vec("URL")
            .into_iter()
            .map(|x| url::Url::parse(x).unwrap())
            .collect();

        // Request headers (can be multiple).
        let req_headers = args
            .get_vec("--header")
            .into_iter()
            .map(|x| x.to_string())
            .collect();

        let reqs_cardinal = args.get_str("--requests");
        let reqs_cardinal = reqs_cardinal.parse::<u64>().unwrap();

        let no_verify = args.get_bool("--no-verify");

        let body = if args.get_bool("--body") {
            std::fs::read(args.get_str("--body")).ok()
        } else {
            None
        };

        let method = args.get_str("--method").to_string();

        let connect_to = if args.get_bool("--connect-to") {
            Some(args.get_str("--connect-to").to_string())
        } else {
            None
        };

        let session_file = if args.get_bool("--session-file") {
            Some(args.get_str("--session-file").to_string())
        } else {
            None
        };

        ClientArgs {
            version,
            dump_response_path,
            dump_json,
            urls,
            req_headers,
            reqs_cardinal,
            no_verify,
            body,
            method,
            connect_to,
            session_file,
        }
    }
}

impl Default for ClientArgs {
    fn default() -> Self {
        ClientArgs {
            version: 0xbabababa,
            dump_response_path: None,
            dump_json: None,
            urls: vec![],
            req_headers: vec![],
            reqs_cardinal: 1,
            no_verify: false,
            body: None,
            method: "GET".to_string(),
            connect_to: None,
            session_file: None,
        }
    }
}

pub const SERVER_USAGE: &str = "Usage:
  quiche-server [options]
  quiche-server -h | --help

Options:
  --listen <addr>             Listen on the given IP:port [default: 127.0.0.1:4433]
  --cert <file>               TLS certificate path [default: src/bin/cert.crt]
  --key <file>                TLS certificate key path [default: src/bin/cert.key]
  --root <dir>                Root directory [default: src/bin/root/]
  --index <name>              The file that will be used as index [default: index.html].
  --name <str>                Name of the server [default: quic.tech]
  --max-data BYTES            Connection-wide flow control limit [default: 10000000].
  --max-stream-data BYTES     Per-stream flow control limit [default: 1000000].
  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].
  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].
  --idle-timeout TIMEOUT   Idle timeout in milliseconds [default: 30000].
  --dump-packets PATH         Dump the incoming packets as files in the given directory.
  --early-data                Enable receiving early data.
  --no-retry                  Disable stateless retry.
  --no-grease                 Don't send GREASE.
  --http-version VERSION      HTTP version to use [default: all].
  --dgram-proto PROTO         DATAGRAM application protocol to use [default: none].
  --dgram-count COUNT         Number of DATAGRAMs to send [default: 0].
  --dgram-data DATA           Data to send for certain types of DATAGRAM application protocol [default: brrr].
  --cc-algorithm NAME         Specify which congestion control algorithm to use [default: cubic].
  --disable-hystart           Disable HyStart++.
  -h --help                   Show this screen.
";

// Application-specific arguments that compliment the `CommonArgs`.
pub struct ServerArgs {
    pub listen: String,
    pub no_retry: bool,
    pub root: String,
    pub index: String,
    pub cert: String,
    pub key: String,
}

impl Args for ServerArgs {
    fn with_docopt(docopt: &docopt::Docopt) -> Self {
        let args = docopt.parse().unwrap_or_else(|e| e.exit());

        let listen = args.get_str("--listen").to_string();
        let no_retry = args.get_bool("--no-retry");
        let root = args.get_str("--root").to_string();
        let index = args.get_str("--index").to_string();
        let cert = args.get_str("--cert").to_string();
        let key = args.get_str("--key").to_string();

        ServerArgs {
            listen,
            no_retry,
            root,
            index,
            cert,
            key,
        }
    }
}
<|endoftext|>


<commit_before>mod tree;

pub fn get_start_symbols<'a>() -> Vec<&'a str>
{
	// TODO: Read symbols from rules file
    let mut start_symbols: Vec<&str> = Vec::new();

	start_symbols.push("");

	start_symbols
}

pub fn get_invalid_token_values<'a>() -> Vec<&'a str>
{
    // TODO: Read invalid types from rules file
    let mut invalid_tokens: Vec<&str> = Vec::new();

    invalid_tokens.push("enum");
    invalid_tokens.push("interface");

    invalid_tokens
}

pub fn get_invalid_token_types<'a>() -> Vec<&'a str>
{
    // TODO: Read invalid types from rules file
    let mut invalid_types: Vec<&str> = Vec::new();

    invalid_types.push("literal.null");
    invalid_types.push("block.generics.args");
    invalid_types.push("literal.string");
    invalid_types.push("literal.char");
    invalid_types.push("literal.float");
    invalid_types.push("literal.double");
    invalid_types.push("special.reserved");
    invalid_types.push("construct.conditional");
    invalid_types.push("construct.handles");
    invalid_types.push("construct.switch");
    invalid_types.push("construct.loop");

    invalid_types
}
<commit_msg>Parser structure<commit_after>mod tree;
use std::collections::HashMap;
use parser::tree::ParseTree;

pub struct BodyParser<'a>
{
	pub variables: HashMap<Variable<'a>, &'a str>,
	pub body: ParseTree<'a>,
}

impl<'a> BodyParser<'a>
{
	fn parse() -> Option<ParseResult<'a>>
	{
		None
	}
}

pub struct ParseResult<'a>
{
	end_index: usize,
	tree: ParseTree<'a>,
}

pub struct Variable<'a>
{
	pub name: &'a str,
	pub vtype: &'a str,
}

pub fn get_start_symbols<'a>() -> Vec<&'a str>
{
	// TODO: Read symbols from rules file
    let mut start_symbols: Vec<&str> = Vec::new();

	start_symbols.push("");

	start_symbols
}

pub fn get_invalid_token_values<'a>() -> Vec<&'a str>
{
    // TODO: Read invalid types from rules file
    let mut invalid_tokens: Vec<&str> = Vec::new();

    invalid_tokens.push("enum");
    invalid_tokens.push("interface");

    invalid_tokens
}

pub fn get_invalid_token_types<'a>() -> Vec<&'a str>
{
    // TODO: Read invalid types from rules file
    let mut invalid_types: Vec<&str> = Vec::new();

    invalid_types.push("literal.null");
    invalid_types.push("block.generics.args");
    invalid_types.push("literal.string");
    invalid_types.push("literal.char");
    invalid_types.push("literal.float");
    invalid_types.push("literal.double");
    invalid_types.push("special.reserved");
    invalid_types.push("construct.conditional");
    invalid_types.push("construct.handles");
    invalid_types.push("construct.switch");
    invalid_types.push("construct.loop");

    invalid_types
}
<|endoftext|>


<commit_before>use video::Window;
use get_error;
use SdlResult;

#[allow(non_camel_case_types)]
pub mod ll {
    use libc::{c_int, c_char, uint32_t};
    use video::ll::SDL_Window;

    pub type SDL_MessageBoxFlags = u32;
    pub const SDL_MESSAGEBOX_ERROR : SDL_MessageBoxFlags = 0x00000010;
    pub const SDL_MESSAGEBOX_WARNING : SDL_MessageBoxFlags = 0x00000020;
    pub const SDL_MESSAGEBOX_INFORMATION : SDL_MessageBoxFlags = 0x00000040;

    extern "C" {
        pub fn SDL_ShowSimpleMessageBox(flags: uint32_t, title: *const c_char, message: *const c_char, window: *const SDL_Window) -> c_int;
    }
}

bitflags! {
    flags MessageBoxFlag: u32 {
        const MESSAGEBOX_ERROR = ll::SDL_MESSAGEBOX_ERROR,
        const MESSAGEBOX_WARNING = ll::SDL_MESSAGEBOX_WARNING,
        const MESSAGEBOX_INFORMATION = ll::SDL_MESSAGEBOX_INFORMATION
    }
}

pub fn show_simple_message_box(flags: MessageBoxFlag, title: &str, message: &str, window: &Window) -> SdlResult<()> {
    let result = unsafe {
        title.with_c_str(|title_cstr| {
            message.with_c_str(|message_cstr| {
                ll::SDL_ShowSimpleMessageBox(flags.bits(), title_cstr, message_cstr, window.raw())
            })
        })
    } == 0;

    if result {
        Ok(())
    } else {
        Err(get_error())
    }
}
<commit_msg>show_simple_message_box's window is optional<commit_after>use std::ptr;

use video::Window;
use get_error;
use SdlResult;

#[allow(non_camel_case_types)]
pub mod ll {
    use libc::{c_int, c_char, uint32_t};
    use video::ll::SDL_Window;

    pub type SDL_MessageBoxFlags = u32;
    pub const SDL_MESSAGEBOX_ERROR : SDL_MessageBoxFlags = 0x00000010;
    pub const SDL_MESSAGEBOX_WARNING : SDL_MessageBoxFlags = 0x00000020;
    pub const SDL_MESSAGEBOX_INFORMATION : SDL_MessageBoxFlags = 0x00000040;

    extern "C" {
        pub fn SDL_ShowSimpleMessageBox(flags: uint32_t, title: *const c_char, message: *const c_char, window: *const SDL_Window) -> c_int;
    }
}

bitflags! {
    flags MessageBoxFlag: u32 {
        const MESSAGEBOX_ERROR = ll::SDL_MESSAGEBOX_ERROR,
        const MESSAGEBOX_WARNING = ll::SDL_MESSAGEBOX_WARNING,
        const MESSAGEBOX_INFORMATION = ll::SDL_MESSAGEBOX_INFORMATION
    }
}

pub fn show_simple_message_box(flags: MessageBoxFlag, title: &str, message: &str, window: Option<&Window>) -> SdlResult<()> {
    let result = unsafe {
        title.with_c_str(|title_cstr| {
            message.with_c_str(|message_cstr| {
                ll::SDL_ShowSimpleMessageBox(flags.bits(), title_cstr, message_cstr, window.map_or(ptr::null(), |win| win.raw()))
            })
        })
    } == 0;

    if result {
        Ok(())
    } else {
        Err(get_error())
    }
}
<|endoftext|>


<commit_before>use std::error;
use std::ffi::{CString, NulError};
use std::fmt;
use std::ptr;
use std::os::raw::{c_char,c_int};

use video::WindowRef;
use get_error;

use sys::messagebox as ll;

bitflags! {
    pub flags MessageBoxFlag: u32 {
        const MESSAGEBOX_ERROR =
            ::sys::messagebox::SDL_MessageBoxFlags::SDL_MESSAGEBOX_ERROR as u32,
        const MESSAGEBOX_WARNING =
            ::sys::messagebox::SDL_MessageBoxFlags::SDL_MESSAGEBOX_WARNING as u32,
        const MESSAGEBOX_INFORMATION =
            ::sys::messagebox::SDL_MessageBoxFlags::SDL_MESSAGEBOX_INFORMATION as u32
    }
}

bitflags! {
    pub flags MessageBoxButtonFlag: u32 {
        const MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT =
            ::sys::messagebox::SDL_MessageBoxButtonFlags::SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT as u32,
        const MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT =
            ::sys::messagebox::SDL_MessageBoxButtonFlags::SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT as u32,
        const MESSAGEBOX_BUTTON_NOTHING = 0
    }
}

#[derive(Debug)]
pub struct MessageBoxColorScheme {
    pub background:(u8,u8,u8),
    pub text:(u8,u8,u8),
    pub button_border:(u8,u8,u8),
    pub button_background:(u8,u8,u8),
    pub button_selected:(u8,u8,u8)
}

/// button_id is the integer that will be returned
/// by show_message_box. It is not sed by SDL2,
/// and should only be used to know which button has been triggered
#[derive(Debug)]
pub struct ButtonData<'a> {
    pub flags:MessageBoxButtonFlag,
    pub button_id:i32,
    pub text:&'a str
}

#[derive(Debug)]
pub enum ClickedButton<'a> {
    CloseButton,
    CustomButton(&'a ButtonData<'a>)
}

impl From<MessageBoxColorScheme> for [ll::SDL_MessageBoxColor ; 5] {
    fn from(scheme:MessageBoxColorScheme) -> [ll::SDL_MessageBoxColor ; 5] {
        fn to_message_box_color(t:(u8,u8,u8)) -> ll::SDL_MessageBoxColor {
            ll::SDL_MessageBoxColor {
                r:t.0,
                g:t.1,
                b:t.2
            }
        };
        [to_message_box_color(scheme.background),
        to_message_box_color(scheme.text),
        to_message_box_color(scheme.button_border),
        to_message_box_color(scheme.button_background),
        to_message_box_color(scheme.button_selected)]
    }
}

#[derive(Debug)]
pub enum ShowMessageError {
    InvalidTitle(NulError),
    InvalidMessage(NulError),
    /// Second argument of the tuple (i32) corresponds to the
    /// first button_id having an error
    InvalidButton(NulError,i32),
    SdlError(String),
}

impl fmt::Display for ShowMessageError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::ShowMessageError::*;

        match *self {
            InvalidTitle(ref e) => write!(f, "Invalid title: {}", e),
            InvalidMessage(ref e) => write!(f, "Invalid message: {}", e),
            InvalidButton(ref e, value) => write!(f,
                "Invalid button ({}): {}", value, e),
            SdlError(ref e) => write!(f, "SDL error: {}", e)
        }
    }
}

impl error::Error for ShowMessageError {
    fn description(&self) -> &str {
        use self::ShowMessageError::*;

        match *self {
            InvalidTitle(_) => "invalid title",
            InvalidMessage(_) => "invalid message",
            InvalidButton(..) => "invalid button",
            SdlError(ref e) => e
        }
    }
}

/// Show a simple message box, meant to be informative only.
///
/// There is no way to know if the user clicked "Ok" or closed the message box,
/// If you want to retrieve which button was clicked and customize a bit more
/// your message box, use `show_message_box` instead.
pub fn show_simple_message_box(flags: MessageBoxFlag, title: &str,
        message: &str, window: Option<&WindowRef>)
        -> Result<(), ShowMessageError> {
    use self::ShowMessageError::*;
    let result = unsafe {
        let title = match CString::new(title) {
            Ok(s) => s,
            Err(err) => return Err(InvalidTitle(err)),
        };
        let message = match CString::new(message) {
            Ok(s) => s,
            Err(err) => return Err(InvalidMessage(err)),
        };
        ll::SDL_ShowSimpleMessageBox(
            flags.bits(),
            title.as_ptr() as *const c_char,
            message.as_ptr() as *const c_char,
            window.map_or(ptr::null_mut(), |win| win.raw())
        )
    } == 0;

    if result {
        Ok(())
    } else {
        Err(SdlError(get_error()))
    }
}

/// Show a customizable message box.
///
/// An array of buttons is required for it to work. The array can be empty,
/// but it will have no button beside the close button.
///
/// On success, it will return either the button clicked or the close button.
/// Note that the variant of the `ClickedButton` enum will also be returned if the message box
/// has been forcefully closed (Alt-F4, ...)
///
pub fn show_message_box<'a>(flags:MessageBoxFlag, buttons:&'a [ButtonData], title:&str,
    message:&str, window:Option<&WindowRef>, scheme:Option<MessageBoxColorScheme>)
    -> Result<ClickedButton<'a>,ShowMessageError> {
    use self::ShowMessageError::*;
    let mut button_id : c_int = 0;
    let title = match CString::new(title) {
        Ok(s) => s,
        Err(err) => return Err(InvalidTitle(err)),
    };
    let message = match CString::new(message) {
        Ok(s) => s,
        Err(err) => return Err(InvalidMessage(err)),
    };
    let button_texts : Result<Vec<_>,(_,i32)> = buttons.iter().map(|b|{
        CString::new(b.text).map_err(|e|(e,b.button_id))
    }).collect(); // Create CString for every button; and catch any CString Error
    let button_texts = match button_texts {
        Ok(b) => b,
        Err(e) => return Err(InvalidButton(e.0,e.1))
    };
    let raw_buttons : Vec<ll::SDL_MessageBoxButtonData> =
        buttons.iter().zip(button_texts.iter()).map(|(b,b_text)|{
        ll::SDL_MessageBoxButtonData {
            flags:b.flags.bits(),
            buttonid:b.button_id as c_int,
            text:b_text.as_ptr()
        }
    }).collect();
    let result = unsafe {
        let msg_box_data = ll::SDL_MessageBoxData {
            flags:flags.bits(),
            window:window.map_or(ptr::null_mut(), |win| win.raw()),
            title: title.as_ptr() as *const c_char,
            message: message.as_ptr() as *const c_char,
            numbuttons: raw_buttons.len() as c_int,
            buttons: raw_buttons.as_ptr(),
            color_scheme: if let Some(scheme) = scheme {
                &ll::SDL_MessageBoxColorScheme {
                    colors:From::from(scheme)
                } as *const _
            } else {
                ptr::null()
            }
        };
        ll::SDL_ShowMessageBox(
            &msg_box_data as *const _,
            &mut button_id as &mut _
        )
    } == 0;
    if result {
        match button_id {
            -1 => Ok(ClickedButton::CloseButton),
            id => {
                let button = buttons.iter().find(|b| b.button_id == id);
                Ok(ClickedButton::CustomButton(button.unwrap()))
            }
        }
    } else {
        Err(SdlError(get_error()))
    }
}
<commit_msg>add Into and From between primitive for MessageBoxColorScheme<commit_after>use std::error;
use std::ffi::{CString, NulError};
use std::fmt;
use std::ptr;
use std::os::raw::{c_char,c_int};

use video::WindowRef;
use get_error;

use sys::messagebox as ll;

bitflags! {
    pub flags MessageBoxFlag: u32 {
        const MESSAGEBOX_ERROR =
            ::sys::messagebox::SDL_MessageBoxFlags::SDL_MESSAGEBOX_ERROR as u32,
        const MESSAGEBOX_WARNING =
            ::sys::messagebox::SDL_MessageBoxFlags::SDL_MESSAGEBOX_WARNING as u32,
        const MESSAGEBOX_INFORMATION =
            ::sys::messagebox::SDL_MessageBoxFlags::SDL_MESSAGEBOX_INFORMATION as u32
    }
}

bitflags! {
    pub flags MessageBoxButtonFlag: u32 {
        const MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT =
            ::sys::messagebox::SDL_MessageBoxButtonFlags::SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT as u32,
        const MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT =
            ::sys::messagebox::SDL_MessageBoxButtonFlags::SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT as u32,
        const MESSAGEBOX_BUTTON_NOTHING = 0
    }
}

#[derive(Debug)]
pub struct MessageBoxColorScheme {
    pub background:(u8,u8,u8),
    pub text:(u8,u8,u8),
    pub button_border:(u8,u8,u8),
    pub button_background:(u8,u8,u8),
    pub button_selected:(u8,u8,u8)
}

impl Into<ll::SDL_MessageBoxColorScheme> for MessageBoxColorScheme {
    fn into(self) -> ll::SDL_MessageBoxColorScheme {
        ll::SDL_MessageBoxColorScheme { colors: self.into() }
    }
}

impl From<ll::SDL_MessageBoxColorScheme> for MessageBoxColorScheme {
    fn from(prim: ll::SDL_MessageBoxColorScheme) -> MessageBoxColorScheme {
        prim.colors.into()
    }
}

/// button_id is the integer that will be returned
/// by show_message_box. It is not sed by SDL2,
/// and should only be used to know which button has been triggered
#[derive(Debug)]
pub struct ButtonData<'a> {
    pub flags:MessageBoxButtonFlag,
    pub button_id:i32,
    pub text:&'a str
}

#[derive(Debug)]
pub enum ClickedButton<'a> {
    CloseButton,
    CustomButton(&'a ButtonData<'a>)
}

impl From<MessageBoxColorScheme> for [ll::SDL_MessageBoxColor ; 5] {
    fn from(scheme:MessageBoxColorScheme) -> [ll::SDL_MessageBoxColor ; 5] {
        fn to_message_box_color(t:(u8,u8,u8)) -> ll::SDL_MessageBoxColor {
            ll::SDL_MessageBoxColor {
                r:t.0,
                g:t.1,
                b:t.2
            }
        };
        [to_message_box_color(scheme.background),
        to_message_box_color(scheme.text),
        to_message_box_color(scheme.button_border),
        to_message_box_color(scheme.button_background),
        to_message_box_color(scheme.button_selected)]
    }
}

impl Into<MessageBoxColorScheme> for [ll::SDL_MessageBoxColor ; 5] {
    fn into(self) -> MessageBoxColorScheme {
        fn from_message_box_color(prim_color: ll::SDL_MessageBoxColor) -> (u8, u8, u8) {
            (prim_color.r, prim_color.g, prim_color.b)
        };
        MessageBoxColorScheme{
            background: from_message_box_color(self[0]),
            text: from_message_box_color(self[1]),
            button_border: from_message_box_color(self[2]),
            button_background: from_message_box_color(self[3]),
            button_selected: from_message_box_color(self[4]),
        }
    }
}

#[derive(Debug)]
pub enum ShowMessageError {
    InvalidTitle(NulError),
    InvalidMessage(NulError),
    /// Second argument of the tuple (i32) corresponds to the
    /// first button_id having an error
    InvalidButton(NulError,i32),
    SdlError(String),
}

impl fmt::Display for ShowMessageError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::ShowMessageError::*;

        match *self {
            InvalidTitle(ref e) => write!(f, "Invalid title: {}", e),
            InvalidMessage(ref e) => write!(f, "Invalid message: {}", e),
            InvalidButton(ref e, value) => write!(f,
                "Invalid button ({}): {}", value, e),
            SdlError(ref e) => write!(f, "SDL error: {}", e)
        }
    }
}

impl error::Error for ShowMessageError {
    fn description(&self) -> &str {
        use self::ShowMessageError::*;

        match *self {
            InvalidTitle(_) => "invalid title",
            InvalidMessage(_) => "invalid message",
            InvalidButton(..) => "invalid button",
            SdlError(ref e) => e
        }
    }
}

/// Show a simple message box, meant to be informative only.
///
/// There is no way to know if the user clicked "Ok" or closed the message box,
/// If you want to retrieve which button was clicked and customize a bit more
/// your message box, use `show_message_box` instead.
pub fn show_simple_message_box(flags: MessageBoxFlag, title: &str,
        message: &str, window: Option<&WindowRef>)
        -> Result<(), ShowMessageError> {
    use self::ShowMessageError::*;
    let result = unsafe {
        let title = match CString::new(title) {
            Ok(s) => s,
            Err(err) => return Err(InvalidTitle(err)),
        };
        let message = match CString::new(message) {
            Ok(s) => s,
            Err(err) => return Err(InvalidMessage(err)),
        };
        ll::SDL_ShowSimpleMessageBox(
            flags.bits(),
            title.as_ptr() as *const c_char,
            message.as_ptr() as *const c_char,
            window.map_or(ptr::null_mut(), |win| win.raw())
        )
    } == 0;

    if result {
        Ok(())
    } else {
        Err(SdlError(get_error()))
    }
}

/// Show a customizable message box.
///
/// An array of buttons is required for it to work. The array can be empty,
/// but it will have no button beside the close button.
///
/// On success, it will return either the button clicked or the close button.
/// Note that the variant of the `ClickedButton` enum will also be returned if the message box
/// has been forcefully closed (Alt-F4, ...)
///
pub fn show_message_box<'a>(flags:MessageBoxFlag, buttons:&'a [ButtonData], title:&str,
    message:&str, window:Option<&WindowRef>, scheme:Option<MessageBoxColorScheme>)
    -> Result<ClickedButton<'a>,ShowMessageError> {
    use self::ShowMessageError::*;
    let mut button_id : c_int = 0;
    let title = match CString::new(title) {
        Ok(s) => s,
        Err(err) => return Err(InvalidTitle(err)),
    };
    let message = match CString::new(message) {
        Ok(s) => s,
        Err(err) => return Err(InvalidMessage(err)),
    };
    let button_texts : Result<Vec<_>,(_,i32)> = buttons.iter().map(|b|{
        CString::new(b.text).map_err(|e|(e,b.button_id))
    }).collect(); // Create CString for every button; and catch any CString Error
    let button_texts = match button_texts {
        Ok(b) => b,
        Err(e) => return Err(InvalidButton(e.0,e.1))
    };
    let raw_buttons : Vec<ll::SDL_MessageBoxButtonData> =
        buttons.iter().zip(button_texts.iter()).map(|(b,b_text)|{
        ll::SDL_MessageBoxButtonData {
            flags:b.flags.bits(),
            buttonid:b.button_id as c_int,
            text:b_text.as_ptr()
        }
    }).collect();
    let result = unsafe {
        let msg_box_data = ll::SDL_MessageBoxData {
            flags:flags.bits(),
            window:window.map_or(ptr::null_mut(), |win| win.raw()),
            title: title.as_ptr() as *const c_char,
            message: message.as_ptr() as *const c_char,
            numbuttons: raw_buttons.len() as c_int,
            buttons: raw_buttons.as_ptr(),
            color_scheme: if let Some(scheme) = scheme {
                &ll::SDL_MessageBoxColorScheme {
                    colors:From::from(scheme)
                } as *const _
            } else {
                ptr::null()
            }
        };
        ll::SDL_ShowMessageBox(
            &msg_box_data as *const _,
            &mut button_id as &mut _
        )
    } == 0;
    if result {
        match button_id {
            -1 => Ok(ClickedButton::CloseButton),
            id => {
                let button = buttons.iter().find(|b| b.button_id == id);
                Ok(ClickedButton::CustomButton(button.unwrap()))
            }
        }
    } else {
        Err(SdlError(get_error()))
    }
}
<|endoftext|>