<commit_before>use crate::{AssistContext, Assists};
use ide_db::{
    assists::{AssistId, AssistKind},
    syntax_helpers::{
        format_string::is_format_string,
        format_string_exprs::{parse_format_exprs, Arg},
    },
};
use itertools::Itertools;
use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};

// Assist: move_format_string_arg
//
// Move an expression out of a format string.
//
// ```
// macro_rules! format_args {
//     ($lit:literal $(tt:tt)*) => { 0 },
// }
// macro_rules! print {
//     ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
// }
//
// fn main() {
//     print!("{x + 1}$0");
// }
// ```
// ->
// ```
// macro_rules! format_args {
//     ($lit:literal $(tt:tt)*) => { 0 },
// }
// macro_rules! print {
//     ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
// }
//
// fn main() {
//     print!("{}"$0, x + 1);
// }
// ```

pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
    let fmt_string = ctx.find_token_at_offset::<ast::String>()?;
    let tt = fmt_string.syntax().parent_ancestors().find_map(ast::TokenTree::cast)?;

    let expanded_t = ast::String::cast(
        ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()),
    )?;
    if !is_format_string(&expanded_t) {
        return None;
    }

    let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
    if extracted_args.is_empty() {
        return None;
    }

    acc.add(
        AssistId(
            "move_format_string_arg",
            // if there aren't any expressions, then make the assist a RefactorExtract
            if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 {
                AssistKind::RefactorExtract
            } else {
                AssistKind::QuickFix
            },
        ),
        "Extract format args",
        tt.syntax().text_range(),
        |edit| {
            let fmt_range = fmt_string.syntax().text_range();

            // Replace old format string with new format string whose arguments have been extracted
            edit.replace(fmt_range, new_fmt);

            // Insert cursor at end of format string
            edit.insert(fmt_range.end(), "$0");

            // Extract existing arguments in macro
            let tokens =
                tt.token_trees_and_tokens().filter_map(NodeOrToken::into_token).collect_vec();

            let mut existing_args: Vec<String> = vec![];

            let mut current_arg = String::new();
            if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] =
                tokens.as_slice()
            {
                for t in tokens {
                    if t.kind() == COMMA {
                        existing_args.push(current_arg.trim().into());
                        current_arg.clear();
                    } else {
                        current_arg.push_str(t.text());
                    }
                }
                existing_args.push(current_arg.trim().into());

                // delete everything after the format string till end bracket
                // we're going to insert the new arguments later
                edit.delete(TextRange::new(
                    format_string.text_range().end(),
                    end_bracket.text_range().start(),
                ));
            }

            // Start building the new args
            let mut existing_args = existing_args.into_iter();
            let mut args = String::new();

            let mut placeholder_idx = 1;

            for extracted_args in extracted_args {
                // remove expr from format string
                args.push_str(", ");

                match extracted_args {
                    Arg::Ident(s) | Arg::Expr(s) => {
                        // insert arg
                        args.push_str(&s);
                    }
                    Arg::Placeholder => {
                        // try matching with existing argument
                        match existing_args.next() {
                            Some(ea) => {
                                args.push_str(&ea);
                            }
                            None => {
                                // insert placeholder
                                args.push_str(&format!("${placeholder_idx}"));
                                placeholder_idx += 1;
                            }
                        }
                    }
                }
            }

            // Insert new args
            edit.insert(fmt_range.end(), args);
        },
    );

    Some(())
}

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

    const MACRO_DECL: &'static str = r#"
macro_rules! format_args {
    ($lit:literal $(tt:tt)*) => { 0 },
}
macro_rules! print {
    ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
}
"#;

    fn add_macro_decl(s: &'static str) -> String {
        MACRO_DECL.to_string() + s
    }

    #[test]
    fn multiple_middle_arg() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {x + 1:b} {}$0", y + 2, 2);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {:b} {}"$0, y + 2, x + 1, 2);
}
"#,
            ),
        );
    }

    #[test]
    fn single_arg() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{obj.value:b}$0",);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{:b}"$0, obj.value);
}
"#,
            ),
        );
    }

    #[test]
    fn multiple_middle_placeholders_arg() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {x + 1:b} {} {}$0", y + 2, 2);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1);
}
"#,
            ),
        );
    }

    #[test]
    fn multiple_trailing_args() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
}
"#,
            ),
        );
    }

    #[test]
    fn improper_commas() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
}
"#,
            ),
        );
    }
}
<commit_msg>Update crates/ide-assists/src/handlers/move_format_string_arg.rs<commit_after>use crate::{AssistContext, Assists};
use ide_db::{
    assists::{AssistId, AssistKind},
    syntax_helpers::{
        format_string::is_format_string,
        format_string_exprs::{parse_format_exprs, Arg},
    },
};
use itertools::Itertools;
use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};

// Assist: move_format_string_arg
//
// Move an expression out of a format string.
//
// ```
// macro_rules! format_args {
//     ($lit:literal $(tt:tt)*) => { 0 },
// }
// macro_rules! print {
//     ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
// }
//
// fn main() {
//     print!("{x + 1}$0");
// }
// ```
// ->
// ```
// macro_rules! format_args {
//     ($lit:literal $(tt:tt)*) => { 0 },
// }
// macro_rules! print {
//     ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
// }
//
// fn main() {
//     print!("{}"$0, x + 1);
// }
// ```

pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
    let fmt_string = ctx.find_token_at_offset::<ast::String>()?;
    let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;

    let expanded_t = ast::String::cast(
        ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()),
    )?;
    if !is_format_string(&expanded_t) {
        return None;
    }

    let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
    if extracted_args.is_empty() {
        return None;
    }

    acc.add(
        AssistId(
            "move_format_string_arg",
            // if there aren't any expressions, then make the assist a RefactorExtract
            if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 {
                AssistKind::RefactorExtract
            } else {
                AssistKind::QuickFix
            },
        ),
        "Extract format args",
        tt.syntax().text_range(),
        |edit| {
            let fmt_range = fmt_string.syntax().text_range();

            // Replace old format string with new format string whose arguments have been extracted
            edit.replace(fmt_range, new_fmt);

            // Insert cursor at end of format string
            edit.insert(fmt_range.end(), "$0");

            // Extract existing arguments in macro
            let tokens =
                tt.token_trees_and_tokens().filter_map(NodeOrToken::into_token).collect_vec();

            let mut existing_args: Vec<String> = vec![];

            let mut current_arg = String::new();
            if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] =
                tokens.as_slice()
            {
                for t in tokens {
                    if t.kind() == COMMA {
                        existing_args.push(current_arg.trim().into());
                        current_arg.clear();
                    } else {
                        current_arg.push_str(t.text());
                    }
                }
                existing_args.push(current_arg.trim().into());

                // delete everything after the format string till end bracket
                // we're going to insert the new arguments later
                edit.delete(TextRange::new(
                    format_string.text_range().end(),
                    end_bracket.text_range().start(),
                ));
            }

            // Start building the new args
            let mut existing_args = existing_args.into_iter();
            let mut args = String::new();

            let mut placeholder_idx = 1;

            for extracted_args in extracted_args {
                // remove expr from format string
                args.push_str(", ");

                match extracted_args {
                    Arg::Ident(s) | Arg::Expr(s) => {
                        // insert arg
                        args.push_str(&s);
                    }
                    Arg::Placeholder => {
                        // try matching with existing argument
                        match existing_args.next() {
                            Some(ea) => {
                                args.push_str(&ea);
                            }
                            None => {
                                // insert placeholder
                                args.push_str(&format!("${placeholder_idx}"));
                                placeholder_idx += 1;
                            }
                        }
                    }
                }
            }

            // Insert new args
            edit.insert(fmt_range.end(), args);
        },
    );

    Some(())
}

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

    const MACRO_DECL: &'static str = r#"
macro_rules! format_args {
    ($lit:literal $(tt:tt)*) => { 0 },
}
macro_rules! print {
    ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
}
"#;

    fn add_macro_decl(s: &'static str) -> String {
        MACRO_DECL.to_string() + s
    }

    #[test]
    fn multiple_middle_arg() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {x + 1:b} {}$0", y + 2, 2);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {:b} {}"$0, y + 2, x + 1, 2);
}
"#,
            ),
        );
    }

    #[test]
    fn single_arg() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{obj.value:b}$0",);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{:b}"$0, obj.value);
}
"#,
            ),
        );
    }

    #[test]
    fn multiple_middle_placeholders_arg() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {x + 1:b} {} {}$0", y + 2, 2);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1);
}
"#,
            ),
        );
    }

    #[test]
    fn multiple_trailing_args() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
}
"#,
            ),
        );
    }

    #[test]
    fn improper_commas() {
        check_assist(
            move_format_string_arg,
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,);
}
"#,
            ),
            &add_macro_decl(
                r#"
fn main() {
    print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
}
"#,
            ),
        );
    }
}
<|endoftext|>


<commit_before>use crate::utils::{match_def_path, paths, span_lint, trait_ref_of_method};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TypeFoldable;
use rustc_middle::ty::{Adt, Array, RawPtr, Ref, Slice, Tuple, Ty, TypeAndMut};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;

declare_clippy_lint! {
    /// **What it does:** Checks for sets/maps with mutable key types.
    ///
    /// **Why is this bad?** All of `HashMap`, `HashSet`, `BTreeMap` and
    /// `BtreeSet` rely on either the hash or the order of keys be unchanging,
    /// so having types with interior mutability is a bad idea.
    ///
    /// **Known problems:** It's correct to use a struct, that contains interior mutability
    /// as a key, when its `Hash` implementation doesn't access any of the interior mutable types.
    /// However, this lint is unable to recognize this, so it causes a false positive in theses cases.
    /// The `bytes` crate is a great example of this.
    ///
    /// **Example:**
    /// ```rust
    /// use std::cmp::{PartialEq, Eq};
    /// use std::collections::HashSet;
    /// use std::hash::{Hash, Hasher};
    /// use std::sync::atomic::AtomicUsize;
    ///# #[allow(unused)]
    ///
    /// struct Bad(AtomicUsize);
    /// impl PartialEq for Bad {
    ///     fn eq(&self, rhs: &Self) -> bool {
    ///          ..
    /// ; unimplemented!();
    ///     }
    /// }
    ///
    /// impl Eq for Bad {}
    ///
    /// impl Hash for Bad {
    ///     fn hash<H: Hasher>(&self, h: &mut H) {
    ///         ..
    /// ; unimplemented!();
    ///     }
    /// }
    ///
    /// fn main() {
    ///     let _: HashSet<Bad> = HashSet::new();
    /// }
    /// ```
    pub MUTABLE_KEY_TYPE,
    correctness,
    "Check for mutable `Map`/`Set` key type"
}

declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);

impl<'tcx> LateLintPass<'tcx> for MutableKeyType {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
        if let hir::ItemKind::Fn(ref sig, ..) = item.kind {
            check_sig(cx, item.hir_id, &sig.decl);
        }
    }

    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) {
        if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind {
            if trait_ref_of_method(cx, item.hir_id).is_none() {
                check_sig(cx, item.hir_id, &sig.decl);
            }
        }
    }

    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
        if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
            check_sig(cx, item.hir_id, &sig.decl);
        }
    }

    fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
        if let hir::PatKind::Wild = local.pat.kind {
            return;
        }
        check_ty(cx, local.span, cx.typeck_results().pat_ty(&*local.pat));
    }
}

fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) {
    let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
    let fn_sig = cx.tcx.fn_sig(fn_def_id);
    for (hir_ty, ty) in decl.inputs.iter().zip(fn_sig.inputs().skip_binder().iter()) {
        check_ty(cx, hir_ty.span, ty);
    }
    check_ty(
        cx,
        decl.output.span(),
        cx.tcx.erase_late_bound_regions(fn_sig.output()),
    );
}

// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
// generics (because the compiler cannot ensure immutability for unknown types).
fn check_ty<'tcx>(cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) {
    let ty = ty.peel_refs();
    if let Adt(def, substs) = ty.kind() {
        if [&paths::HASHMAP, &paths::BTREEMAP, &paths::HASHSET, &paths::BTREESET]
            .iter()
            .any(|path| match_def_path(cx, def.did, &**path))
            && is_mutable_type(cx, substs.type_at(0), span)
        {
            span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
        }
    }
}

fn is_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span) -> bool {
    match *ty.kind() {
        RawPtr(TypeAndMut { ty: inner_ty, mutbl }) | Ref(_, inner_ty, mutbl) => {
            mutbl == hir::Mutability::Mut || is_mutable_type(cx, inner_ty, span)
        },
        Slice(inner_ty) => is_mutable_type(cx, inner_ty, span),
        Array(inner_ty, size) => {
            size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0) && is_mutable_type(cx, inner_ty, span)
        },
        Tuple(..) => ty.tuple_fields().any(|ty| is_mutable_type(cx, ty, span)),
        Adt(..) => {
            cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
                && !ty.has_escaping_bound_vars()
                && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
        },
        _ => false,
    }
}
<commit_msg>Run cargo dev fmt<commit_after>use crate::utils::{match_def_path, paths, span_lint, trait_ref_of_method};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TypeFoldable;
use rustc_middle::ty::{Adt, Array, RawPtr, Ref, Slice, Tuple, Ty, TypeAndMut};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;

declare_clippy_lint! {
    /// **What it does:** Checks for sets/maps with mutable key types.
    ///
    /// **Why is this bad?** All of `HashMap`, `HashSet`, `BTreeMap` and
    /// `BtreeSet` rely on either the hash or the order of keys be unchanging,
    /// so having types with interior mutability is a bad idea.
    ///
    /// **Known problems:** It's correct to use a struct, that contains interior mutability
    /// as a key, when its `Hash` implementation doesn't access any of the interior mutable types.
    /// However, this lint is unable to recognize this, so it causes a false positive in theses cases.
    /// The `bytes` crate is a great example of this.
    ///
    /// **Example:**
    /// ```rust
    /// use std::cmp::{PartialEq, Eq};
    /// use std::collections::HashSet;
    /// use std::hash::{Hash, Hasher};
    /// use std::sync::atomic::AtomicUsize;
    ///# #[allow(unused)]
    ///
    /// struct Bad(AtomicUsize);
    /// impl PartialEq for Bad {
    ///     fn eq(&self, rhs: &Self) -> bool {
    ///          ..
    /// ; unimplemented!();
    ///     }
    /// }
    ///
    /// impl Eq for Bad {}
    ///
    /// impl Hash for Bad {
    ///     fn hash<H: Hasher>(&self, h: &mut H) {
    ///         ..
    /// ; unimplemented!();
    ///     }
    /// }
    ///
    /// fn main() {
    ///     let _: HashSet<Bad> = HashSet::new();
    /// }
    /// ```
    pub MUTABLE_KEY_TYPE,
    correctness,
    "Check for mutable `Map`/`Set` key type"
}

declare_lint_pass!(MutableKeyType => [ MUTABLE_KEY_TYPE ]);

impl<'tcx> LateLintPass<'tcx> for MutableKeyType {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
        if let hir::ItemKind::Fn(ref sig, ..) = item.kind {
            check_sig(cx, item.hir_id, &sig.decl);
        }
    }

    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) {
        if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind {
            if trait_ref_of_method(cx, item.hir_id).is_none() {
                check_sig(cx, item.hir_id, &sig.decl);
            }
        }
    }

    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) {
        if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
            check_sig(cx, item.hir_id, &sig.decl);
        }
    }

    fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
        if let hir::PatKind::Wild = local.pat.kind {
            return;
        }
        check_ty(cx, local.span, cx.typeck_results().pat_ty(&*local.pat));
    }
}

fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) {
    let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id);
    let fn_sig = cx.tcx.fn_sig(fn_def_id);
    for (hir_ty, ty) in decl.inputs.iter().zip(fn_sig.inputs().skip_binder().iter()) {
        check_ty(cx, hir_ty.span, ty);
    }
    check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output()));
}

// We want to lint 1. sets or maps with 2. not immutable key types and 3. no unerased
// generics (because the compiler cannot ensure immutability for unknown types).
fn check_ty<'tcx>(cx: &LateContext<'tcx>, span: Span, ty: Ty<'tcx>) {
    let ty = ty.peel_refs();
    if let Adt(def, substs) = ty.kind() {
        if [&paths::HASHMAP, &paths::BTREEMAP, &paths::HASHSET, &paths::BTREESET]
            .iter()
            .any(|path| match_def_path(cx, def.did, &**path))
            && is_mutable_type(cx, substs.type_at(0), span)
        {
            span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
        }
    }
}

fn is_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span) -> bool {
    match *ty.kind() {
        RawPtr(TypeAndMut { ty: inner_ty, mutbl }) | Ref(_, inner_ty, mutbl) => {
            mutbl == hir::Mutability::Mut || is_mutable_type(cx, inner_ty, span)
        },
        Slice(inner_ty) => is_mutable_type(cx, inner_ty, span),
        Array(inner_ty, size) => {
            size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0) && is_mutable_type(cx, inner_ty, span)
        },
        Tuple(..) => ty.tuple_fields().any(|ty| is_mutable_type(cx, ty, span)),
        Adt(..) => {
            cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
                && !ty.has_escaping_bound_vars()
                && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
        },
        _ => false,
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)]
/// TODO: Remove the suppressed warnings.
use std::ffi::CString;

use std::mem;
use std::ops::Drop;

use gt::consts::*;
use error::{
    Error,
    AsGnutlsError
};

use gt::gen::{gnutls_x509_crt_fmt_t,
              gnutls_certificate_credentials_t,
              gnutls_certificate_allocate_credentials,
              gnutls_certificate_free_credentials,
              gnutls_certificate_set_x509_trust_file,
              gnutls_certificate_set_x509_trust_dir,
              gnutls_certificate_set_x509_key_file
};

pub use gt::gen::gnutls_credentials_type_t as CredType;

pub struct Cert {
    pub credentials: gnutls_certificate_credentials_t,
    trust_file: bool,
    key_file: bool
}

impl Cert {
    #[allow(unused_must_use)]
    pub fn new() -> Result<Cert, Error> {
        unsafe {
            let mut credentials: gnutls_certificate_credentials_t = mem::zeroed();
            let val = gnutls_certificate_allocate_credentials(&mut credentials);

            if val != GNUTLS_E_SUCCESS {
                return Err(val.as_gnutls_error());
            }

            Ok(Cert{
                credentials: credentials,
                trust_file: false,
                key_file: false
            })
        }
    }

    pub fn x509_set_trust_file(&mut self, file: &'static str,
                           fmt: Option<gnutls_x509_crt_fmt_t>) -> Result<i32, Error> {
        let format: gnutls_x509_crt_fmt_t = match fmt {
            None => gnutls_x509_crt_fmt_t::GNUTLS_X509_FMT_PEM,
            Some(x) => x
        };

        unsafe {
            let processed: i32 = gnutls_certificate_set_x509_trust_file(
                self.credentials,
                CString::new(file).unwrap().as_ptr(),
                format);

            if processed < 1 {
                Err(processed.as_gnutls_error())
            } else {
                self.trust_file = true;
                Ok(processed)
            }
        }
    }

    pub fn x509_set_trust_dir(&mut self, directory: &'static str,
                          fmt: Option<gnutls_x509_crt_fmt_t>)
                          -> Result<i32, Error> {
        let format: gnutls_x509_crt_fmt_t = match fmt {
            None => gnutls_x509_crt_fmt_t::GNUTLS_X509_FMT_PEM,
            Some(x) => x
        };

        unsafe {
            let processed: i32 = gnutls_certificate_set_x509_trust_dir(
                self.credentials,
                CString::new(directory).unwrap().as_ptr(),
                format);

            if processed < 1 {
                Err(processed.as_gnutls_error())
            } else {
                self.trust_file = true;
                Ok(processed)
            }
        }
    }

    pub fn x509_set_key_file(&mut self, cert: &'static str, key: &'static str,
                         fmt: Option<gnutls_x509_crt_fmt_t>)
                         -> Result<Error, Error> {
        let format: gnutls_x509_crt_fmt_t = match fmt {
            None => gnutls_x509_crt_fmt_t::GNUTLS_X509_FMT_PEM,
            Some(x) => x
        };

        unsafe {
            let res: i32 = gnutls_certificate_set_x509_key_file(
                self.credentials,
                CString::new(cert).unwrap().as_ptr(),
                CString::new(key).unwrap().as_ptr(),
                format
            );

            self.key_file = true;
            is_succ!(res)
        }
    }
}

impl Drop for Cert {
    fn drop(&mut self) {
        unsafe {
            // Also free keys, and CRLs?
            gnutls_certificate_free_credentials(self.credentials);
        }
    }
}

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

    #[test]
    fn comp() {
        let mut cert: Cert= match Cert::new() {
            Ok(x) => x,
            Err(_) => {
                panic!("Could not initialize the certificate.")
            }
        };

        assert_eq!(Error::FileError,
                   cert.x509_set_trust_file("tests/does_not_exist.pem", None)
                   .err().unwrap());

        assert_eq!(1, cert.x509_set_trust_file("tests/ca.cert.pem", None)
                   .ok().unwrap());

        match cert.x509_set_trust_dir("does_not_exist/does_not_exist",
                                      None).err().unwrap() {
            Error::None => {},
            _ => {
                panic!("An error occurred when trying to load files.");
            }
        }

        assert_eq!(1, cert.x509_set_trust_dir("tests/", None) .ok().unwrap());

        assert_eq!(Error::DecryptionFailed,
                   cert.x509_set_key_file("tests/ca.cert.pem",
                                          "tests/ca.key.pem",
                                          None)
                   .err().unwrap());

        assert_eq!(Error::None,
                   cert.x509_set_key_file("tests/ca.cert.pem",
                                          "tests/ca.noenckey.pem",
                                          None)
                   .ok().unwrap());
    }
}
<commit_msg>Add function for using system trust<commit_after>#![allow(dead_code)]
/// TODO: Remove the suppressed warnings.
use std::ffi::CString;

use std::mem;
use std::ops::Drop;

use gt::consts::*;
use error::{
    Error,
    AsGnutlsError
};

use gt::gen::{gnutls_x509_crt_fmt_t,
              gnutls_certificate_credentials_t,
              gnutls_certificate_allocate_credentials,
              gnutls_certificate_free_credentials,
              gnutls_certificate_set_x509_system_trust,
              gnutls_certificate_set_x509_trust_file,
              gnutls_certificate_set_x509_trust_dir,
              gnutls_certificate_set_x509_key_file
};

pub use gt::gen::gnutls_credentials_type_t as CredType;

pub struct Cert {
    pub credentials: gnutls_certificate_credentials_t,
    trust_file: bool,
    key_file: bool
}

impl Cert {
    #[allow(unused_must_use)]
    pub fn new() -> Result<Cert, Error> {
        unsafe {
            let mut credentials: gnutls_certificate_credentials_t = mem::zeroed();
            let val = gnutls_certificate_allocate_credentials(&mut credentials);

            if val != GNUTLS_E_SUCCESS {
                return Err(val.as_gnutls_error());
            }

            Ok(Cert{
                credentials: credentials,
                trust_file: false,
                key_file: false
            })
        }
    }

    pub fn x509_set_system_trust(&mut self) -> Result<i32, Error> {
        unsafe {
            let processed: i32 = gnutls_certificate_set_x509_system_trust(
                self.credentials);

            if processed < 0 {
                Err(processed.as_gnutls_error())
            } else {
                Ok(processed)
            }
        }
    }

    pub fn x509_set_trust_file(&mut self, file: &'static str,
                           fmt: Option<gnutls_x509_crt_fmt_t>) -> Result<i32, Error> {
        let format: gnutls_x509_crt_fmt_t = match fmt {
            None => gnutls_x509_crt_fmt_t::GNUTLS_X509_FMT_PEM,
            Some(x) => x
        };

        unsafe {
            let processed: i32 = gnutls_certificate_set_x509_trust_file(
                self.credentials,
                CString::new(file).unwrap().as_ptr(),
                format);

            if processed < 1 {
                Err(processed.as_gnutls_error())
            } else {
                self.trust_file = true;
                Ok(processed)
            }
        }
    }

    pub fn x509_set_trust_dir(&mut self, directory: &'static str,
                          fmt: Option<gnutls_x509_crt_fmt_t>)
                          -> Result<i32, Error> {
        let format: gnutls_x509_crt_fmt_t = match fmt {
            None => gnutls_x509_crt_fmt_t::GNUTLS_X509_FMT_PEM,
            Some(x) => x
        };

        unsafe {
            let processed: i32 = gnutls_certificate_set_x509_trust_dir(
                self.credentials,
                CString::new(directory).unwrap().as_ptr(),
                format);

            if processed < 1 {
                Err(processed.as_gnutls_error())
            } else {
                self.trust_file = true;
                Ok(processed)
            }
        }
    }

    pub fn x509_set_key_file(&mut self, cert: &'static str, key: &'static str,
                         fmt: Option<gnutls_x509_crt_fmt_t>)
                         -> Result<Error, Error> {
        let format: gnutls_x509_crt_fmt_t = match fmt {
            None => gnutls_x509_crt_fmt_t::GNUTLS_X509_FMT_PEM,
            Some(x) => x
        };

        unsafe {
            let res: i32 = gnutls_certificate_set_x509_key_file(
                self.credentials,
                CString::new(cert).unwrap().as_ptr(),
                CString::new(key).unwrap().as_ptr(),
                format
            );

            self.key_file = true;
            is_succ!(res)
        }
    }
}

impl Drop for Cert {
    fn drop(&mut self) {
        unsafe {
            // Also free keys, and CRLs?
            gnutls_certificate_free_credentials(self.credentials);
        }
    }
}

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

    #[test]
    fn comp() {
        let mut cert: Cert= match Cert::new() {
            Ok(x) => x,
            Err(_) => {
                panic!("Could not initialize the certificate.")
            }
        };

        assert!(cert.x509_set_system_trust().ok().unwrap() > 0);

        assert_eq!(Error::FileError,
                   cert.x509_set_trust_file("tests/does_not_exist.pem", None)
                   .err().unwrap());

        assert_eq!(1, cert.x509_set_trust_file("tests/ca.cert.pem", None)
                   .ok().unwrap());

        match cert.x509_set_trust_dir("does_not_exist/does_not_exist",
                                      None).err().unwrap() {
            Error::None => {},
            _ => {
                panic!("An error occurred when trying to load files.");
            }
        }

        assert_eq!(1, cert.x509_set_trust_dir("tests/", None) .ok().unwrap());

        assert_eq!(Error::DecryptionFailed,
                   cert.x509_set_key_file("tests/ca.cert.pem",
                                          "tests/ca.key.pem",
                                          None)
                   .err().unwrap());

        assert_eq!(Error::None,
                   cert.x509_set_key_file("tests/ca.cert.pem",
                                          "tests/ca.noenckey.pem",
                                          None)
                   .ok().unwrap());
    }
}
<|endoftext|>


<commit_before>//! Minimal bindings around libtls. These are not very ergonomic, and may
//! allow you to issue calls that can never succeed (e.g. read/write a server
//! context).

extern crate tls_sys as ffi;
extern crate libc;

use std::ffi::CString;
use libc::{c_void, size_t};
use std::ptr;
use std::error::Error;
use std::fmt;
use std::convert;
use std::io;
#[cfg(unix)]
use std::os::unix::io::RawFd;
#[cfg(windows)]
use std::os::windows::io::RawSocket;
use std::sync::{Once, ONCE_INIT};
use super::util::*;
use chrono::datetime::DateTime;
use chrono::naive::datetime::NaiveDateTime;
use chrono::offset::utc::UTC;

#[derive(Debug)]
pub struct TlsError {
    msg: String,
    code: i64,
}

impl TlsError {
    /// The operation failed because it would block reading
    fn want_pollin(&self) -> bool {
        self.code == ffi::WANT_POLLIN
    }
    /// The operation failed because it would block writing
    fn want_pollout(&self) -> bool {
        self.code == ffi::WANT_POLLIN
    }
    /// The operation failed because it would block, repeating
    /// the same operation should succeed, but will block.
    pub fn wants_more(&self) -> bool {
        self.want_pollin() || self.want_pollout()
    }
    pub fn new<S: Into<String>>(msg: S) -> TlsError {
        TlsError {
            msg: msg.into(),
            code: -1,
        }
    }
}

impl fmt::Display for TlsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.msg)
    }
}
impl Error for TlsError {
    fn description(&self) -> &str {
        &self.msg
    }
}
/// Convert TlsError to io::Error, use WouldBlock if applicable
impl convert::From<TlsError> for io::Error {
    fn from(err: TlsError) -> Self {
        match err.code {
            ffi::WANT_POLLIN | ffi::WANT_POLLOUT => io::Error::new(io::ErrorKind::WouldBlock, err),
            _ => io::Error::new(io::ErrorKind::Other, err.msg),
        }
    }
}

/// result type for TLS operations
pub type TlsResult<T> = Result<T, TlsError>;

/// TLS configuration settings, see `TlsContext::configure` to apply them
pub struct TlsConfig {
    cfg: ffi::Config,
}

impl TlsConfig {
    pub fn new() -> TlsResult<TlsConfig> {
        let p = unsafe { ffi::tls_config_new() };
        if p == ptr::null_mut() {
            Err(TlsError::new("Unable to create TLS config"))
        } else {
            Ok(TlsConfig { cfg: p })
        }
    }

    pub fn set_ca_file(&mut self, path: &str) -> TlsResult<()> {
        let rv = unsafe {
            let path_c = CString::from_vec_unchecked(path.bytes().collect());
            ffi::tls_config_set_ca_file(self.cfg, path_c.as_ptr())
        };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set CA file"))
        }
    }
    pub fn set_ca_path(&mut self, path: &str) -> TlsResult<()> {
        let rv = unsafe {
            let path_c = str_c_ptr(path);
            ffi::tls_config_set_ca_path(self.cfg, path_c)
        };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set CA path"))
        }
    }
    pub fn set_ca_mem(&mut self, ca: &str) -> TlsResult<()> {
        let rv = unsafe { ffi::tls_config_set_ca_mem(self.cfg, ca.as_ptr(), ca.len()) };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set CA from memory"))
        }
    }
    pub fn set_verify_depth(&mut self, depth: i32) {
        unsafe { ffi::tls_config_set_verify_depth(self.cfg, depth) }
    }
    pub fn insecure_noverifyname(&mut self) {
        unsafe { ffi::tls_config_insecure_noverifyname(self.cfg) }
    }
    pub fn insecure_noverifycert(&mut self) {
        unsafe { ffi::tls_config_insecure_noverifycert(self.cfg) }
    }
    pub fn set_key_file(&mut self, path: &str) -> TlsResult<()> {
        let rv = unsafe {
            let path_c = CString::from_vec_unchecked(path.bytes().collect());
            ffi::tls_config_set_key_file(self.cfg, path_c.as_ptr())
        };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set key file"))
        }
    }
    pub fn set_cert_file(&mut self, path: &str) -> TlsResult<()> {
        let rv = unsafe {
            let path_c = CString::from_vec_unchecked(path.bytes().collect());
            ffi::tls_config_set_cert_file(self.cfg, path_c.as_ptr())
        };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set certificate file"))
        }
    }
    pub fn set_protocols(&mut self, protocols: &str) -> TlsResult<()> {
        let mut proto = 0;
        unsafe {
            let proto_c = CString::from_vec_unchecked(protocols.bytes().collect());
            if ffi::tls_config_parse_protocols(&mut proto, proto_c.as_ptr()) == -1 {
                return Err(TlsError::new(format!("Invalid protocols: {}", protocols)));
            }
            ffi::tls_config_set_protocols(self.cfg, proto);
        }
        Ok(())
    }
    pub fn set_ciphers(&mut self, ciphers: &str) -> TlsResult<()> {
        let rv = unsafe {
            let ciphers_c = CString::from_vec_unchecked(ciphers.bytes().collect());
            ffi::tls_config_set_ciphers(self.cfg, ciphers_c.as_ptr())
        };
        if rv == 0 {
            Ok(())
        } else {
            return Err(TlsError::new(format!("Invalid ciphers: {}", ciphers)));
        }
    }
}

impl Drop for TlsConfig {
    fn drop(&mut self) {
        unsafe {
            ffi::tls_config_free(self.cfg);
        }
    }
}

/// A structure that represents all TLS context
///
/// This can be a client connection, a server, or a connection accepted by the server
pub struct TlsContext {
    ptr: ffi::Tls,
    cfg: Option<TlsConfig>,
}

impl TlsContext {
    /// Create a new client context
    pub fn new_client() -> TlsResult<TlsContext> {
        let p = unsafe { ffi::tls_client() };
        if p == ptr::null_mut() {
            Err(TlsError::new("Unable to create TLS client"))
        } else {
            Ok(TlsContext {
                ptr: p,
                cfg: None,
            })
        }
    }

    fn error(&self) -> String {
        unsafe { from_cstr(ffi::tls_error(self.ptr)) }
    }

    fn rv_to_result(&self, rv: i64) -> TlsResult<()> {
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError {
                msg: self.error(),
                code: rv,
            })
        }
    }

    #[cfg(unix)]
    /// Establish a TLS connection over the given socket
    pub fn connect_socket(&mut self, fd: RawFd, servername: &str) -> TlsResult<()> {
        let rv = unsafe {
            let servername_c = str_c_ptr(servername);
            ffi::tls_connect_socket(self.ptr, fd, servername_c)
        };
        self.rv_to_result(rv as i64)
    }

    #[cfg(windows)]
    /// Establish a TLS connection over the given socket
    pub fn connect_socket(&mut self, sock: RawSocket, servername: &str) -> TlsResult<()> {
        let rv = unsafe {
            let servername_c = str_c_ptr(servername);
            // This cast is not exactly safe
            // http://stackoverflow.com/questions/1953639/
            ffi::tls_connect_socket(self.ptr, sock as i32, servername_c)
        };
        self.rv_to_result(rv as i64)
    }

    pub fn conn_version(&self) -> String {
        unsafe { from_cstr(ffi::tls_conn_version(self.ptr)) }
    }
    pub fn conn_cipher(&self) -> String {
        unsafe { from_cstr(ffi::tls_conn_cipher(self.ptr)) }
    }

    /// Apply configuration settings to the context, consuming the config struct
    ///
    /// This should be called BEFORE trying to establish/accept
    /// a connection
    pub fn configure(&mut self, cfg: TlsConfig) -> TlsResult<()> {
        let rv = unsafe { ffi::tls_configure(self.ptr, cfg.cfg) };
        self.cfg = Some(cfg);
        self.rv_to_result(rv as i64)
    }

    pub fn peer_cert_notbefore(&self) -> TlsResult<DateTime<UTC>> {
        let rv = unsafe { ffi::tls_peer_cert_notbefore(self.ptr) };
        if rv == -1 {
            Err(TlsError::new("Unable to get certificate information"))
        } else {
            Ok(DateTime::from_utc(NaiveDateTime::from_timestamp(rv as i64, 0), UTC))
        }
    }

    pub fn peer_cert_notafter(&self) -> TlsResult<DateTime<UTC>> {
        let rv = unsafe { ffi::tls_peer_cert_notafter(self.ptr) };
        if rv == -1 {
            Err(TlsError::new("Unable to get certificate information"))
        } else {
            Ok(DateTime::from_utc(NaiveDateTime::from_timestamp(rv as i64, 0), UTC))
        }
    }

    pub fn peer_cert_hash(&self) -> String {
        unsafe { from_cstr(ffi::tls_peer_cert_hash(self.ptr)) }
    }

    pub fn peer_cert_issuer(&self) -> String {
        unsafe { from_cstr(ffi::tls_peer_cert_issuer(self.ptr)) }
    }

    pub fn peer_cert_subject(&self) -> String {
        unsafe { from_cstr(ffi::tls_peer_cert_subject(self.ptr)) }
    }

    pub fn peer_cert_contains_name(&self, name: &str) -> bool {
        let rv = unsafe {
            let name_c = CString::from_vec_unchecked(name.bytes().collect());
            ffi::tls_peer_cert_contains_name(self.ptr, name_c.as_ptr())
        };
        (rv == 1)
    }

    pub fn peer_cert_provided(&self) -> bool {
        let rv = unsafe { ffi::tls_peer_cert_provided(self.ptr) };
        (rv == 1)
    }

    fn rv_to_result_io(&self, rv: i64) -> TlsResult<usize> {
        match rv {
            ffi::WANT_POLLIN => {
                Err(TlsError {
                    msg: String::new(),
                    code: rv,
                })
            }
            ffi::WANT_POLLOUT => {
                Err(TlsError {
                    msg: String::new(),
                    code: rv,
                })
            }
            rv if rv < 0 => {
                Err(TlsError {
                    msg: self.error(),
                    code: -1,
                })
            }
            rv => Ok(rv as usize),
        }
    }

    /// Complete the TLS handshake
    ///
    /// This function will be called when needed by `read()` or `write()`, but
    /// can be called to complete the handshake.
    pub fn handshake(&mut self) -> TlsResult<()> {
        let rv = unsafe { ffi::tls_handshake(self.ptr) };
        self.rv_to_result_io(rv as i64).map(|_| ())
    }

    pub fn close(&mut self) -> TlsResult<()> {
        let rv = unsafe { ffi::tls_close(self.ptr) };
        self.rv_to_result_io(rv as i64).map(|_| ())
    }

    pub fn read(&mut self, buf: &mut [u8]) -> TlsResult<usize> {
        let buflen = buf.len() as size_t;
        let bptr = buf.as_mut_ptr() as *mut c_void;
        let rv = unsafe { ffi::tls_read(self.ptr, bptr, buflen) };
        self.rv_to_result_io(rv as i64)
    }

    pub fn write(&mut self, buf: &[u8]) -> TlsResult<usize> {
        let buflen = buf.len() as size_t;
        let bptr = buf.as_ptr() as *const c_void;
        let rv = unsafe { ffi::tls_write(self.ptr, bptr, buflen) };
        self.rv_to_result_io(rv as i64)
    }

    /// Create new server context
    pub fn new_server() -> TlsResult<TlsContext> {
        let p = unsafe { ffi::tls_server() };
        if p == ptr::null_mut() {
            Err(TlsError::new("Unable to create TLS server"))
        } else {
            Ok(TlsContext {
                ptr: p,
                cfg: None,
            })
        }
    }

    #[cfg(unix)]
    /// Accept a new TLS connection over an existing socket
    pub fn accept_socket(&mut self, fd: RawFd) -> TlsResult<TlsContext> {
        let mut cctx: ffi::Tls = ptr::null_mut();;
        let rv = unsafe { ffi::tls_accept_socket(self.ptr, &mut cctx, fd) };
        self.rv_to_result(rv as i64)
            .map(|_| {
                TlsContext {
                    ptr: cctx,
                    cfg: None,
                }
            })
    }

    #[cfg(windows)]
    /// Accept a new TLS connection over an existing socket
    pub fn accept_socket(&mut self, sock: RawSocket) -> TlsResult<TlsContext> {
        let mut cctx: ffi::Tls = ptr::null_mut();;
        // This cast is not exactly safe
        // http://stackoverflow.com/questions/1953639/
        let rv = unsafe {
            ffi::tls_accept_socket(self.ptr, &mut cctx, sock as i32)
        };
        self.rv_to_result(rv as i64)
            .map(|_| {
                TlsContext {
                    ptr: cctx,
                    cfg: None,
                }
            })
    }
}

impl Drop for TlsContext {
    fn drop(&mut self) {
        unsafe {
            ffi::tls_free(self.ptr);
        }
    }
}

/// Initialize libtls - make sure to call this before using the API
/// Returns false if libtls failed to initialise.
pub fn init() -> bool {
    static mut RET: i32 = -1;
    static ONCE: Once = ONCE_INIT;
    ONCE.call_once(|| {
        other_init();
        unsafe { RET = ffi::tls_init() };
    });
    unsafe { (RET == 0) }
}
<commit_msg>Add connect_servername to tls::raw<commit_after>//! Minimal bindings around libtls. These are not very ergonomic, and may
//! allow you to issue calls that can never succeed (e.g. read/write a server
//! context).

extern crate tls_sys as ffi;
extern crate libc;

use std::ffi::CString;
use libc::{c_void, size_t};
use std::ptr;
use std::error::Error;
use std::fmt;
use std::convert;
use std::io;
#[cfg(unix)]
use std::os::unix::io::RawFd;
#[cfg(windows)]
use std::os::windows::io::RawSocket;
use std::sync::{Once, ONCE_INIT};
use super::util::*;
use chrono::datetime::DateTime;
use chrono::naive::datetime::NaiveDateTime;
use chrono::offset::utc::UTC;

#[derive(Debug)]
pub struct TlsError {
    msg: String,
    code: i64,
}

impl TlsError {
    /// The operation failed because it would block reading
    fn want_pollin(&self) -> bool {
        self.code == ffi::WANT_POLLIN
    }
    /// The operation failed because it would block writing
    fn want_pollout(&self) -> bool {
        self.code == ffi::WANT_POLLIN
    }
    /// The operation failed because it would block, repeating
    /// the same operation should succeed, but will block.
    pub fn wants_more(&self) -> bool {
        self.want_pollin() || self.want_pollout()
    }
    pub fn new<S: Into<String>>(msg: S) -> TlsError {
        TlsError {
            msg: msg.into(),
            code: -1,
        }
    }
}

impl fmt::Display for TlsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.msg)
    }
}
impl Error for TlsError {
    fn description(&self) -> &str {
        &self.msg
    }
}
/// Convert TlsError to io::Error, use WouldBlock if applicable
impl convert::From<TlsError> for io::Error {
    fn from(err: TlsError) -> Self {
        match err.code {
            ffi::WANT_POLLIN | ffi::WANT_POLLOUT => io::Error::new(io::ErrorKind::WouldBlock, err),
            _ => io::Error::new(io::ErrorKind::Other, err.msg),
        }
    }
}

/// result type for TLS operations
pub type TlsResult<T> = Result<T, TlsError>;

/// TLS configuration settings, see `TlsContext::configure` to apply them
pub struct TlsConfig {
    cfg: ffi::Config,
}

impl TlsConfig {
    pub fn new() -> TlsResult<TlsConfig> {
        let p = unsafe { ffi::tls_config_new() };
        if p == ptr::null_mut() {
            Err(TlsError::new("Unable to create TLS config"))
        } else {
            Ok(TlsConfig { cfg: p })
        }
    }

    pub fn set_ca_file(&mut self, path: &str) -> TlsResult<()> {
        let rv = unsafe {
            let path_c = CString::from_vec_unchecked(path.bytes().collect());
            ffi::tls_config_set_ca_file(self.cfg, path_c.as_ptr())
        };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set CA file"))
        }
    }
    pub fn set_ca_path(&mut self, path: &str) -> TlsResult<()> {
        let rv = unsafe {
            let path_c = str_c_ptr(path);
            ffi::tls_config_set_ca_path(self.cfg, path_c)
        };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set CA path"))
        }
    }
    pub fn set_ca_mem(&mut self, ca: &str) -> TlsResult<()> {
        let rv = unsafe { ffi::tls_config_set_ca_mem(self.cfg, ca.as_ptr(), ca.len()) };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set CA from memory"))
        }
    }
    pub fn set_verify_depth(&mut self, depth: i32) {
        unsafe { ffi::tls_config_set_verify_depth(self.cfg, depth) }
    }
    pub fn insecure_noverifyname(&mut self) {
        unsafe { ffi::tls_config_insecure_noverifyname(self.cfg) }
    }
    pub fn insecure_noverifycert(&mut self) {
        unsafe { ffi::tls_config_insecure_noverifycert(self.cfg) }
    }
    pub fn set_key_file(&mut self, path: &str) -> TlsResult<()> {
        let rv = unsafe {
            let path_c = CString::from_vec_unchecked(path.bytes().collect());
            ffi::tls_config_set_key_file(self.cfg, path_c.as_ptr())
        };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set key file"))
        }
    }
    pub fn set_cert_file(&mut self, path: &str) -> TlsResult<()> {
        let rv = unsafe {
            let path_c = CString::from_vec_unchecked(path.bytes().collect());
            ffi::tls_config_set_cert_file(self.cfg, path_c.as_ptr())
        };
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError::new("Unable to set certificate file"))
        }
    }
    pub fn set_protocols(&mut self, protocols: &str) -> TlsResult<()> {
        let mut proto = 0;
        unsafe {
            let proto_c = CString::from_vec_unchecked(protocols.bytes().collect());
            if ffi::tls_config_parse_protocols(&mut proto, proto_c.as_ptr()) == -1 {
                return Err(TlsError::new(format!("Invalid protocols: {}", protocols)));
            }
            ffi::tls_config_set_protocols(self.cfg, proto);
        }
        Ok(())
    }
    pub fn set_ciphers(&mut self, ciphers: &str) -> TlsResult<()> {
        let rv = unsafe {
            let ciphers_c = CString::from_vec_unchecked(ciphers.bytes().collect());
            ffi::tls_config_set_ciphers(self.cfg, ciphers_c.as_ptr())
        };
        if rv == 0 {
            Ok(())
        } else {
            return Err(TlsError::new(format!("Invalid ciphers: {}", ciphers)));
        }
    }
}

impl Drop for TlsConfig {
    fn drop(&mut self) {
        unsafe {
            ffi::tls_config_free(self.cfg);
        }
    }
}

/// A structure that represents all TLS context
///
/// This can be a client connection, a server, or a connection accepted by the server
pub struct TlsContext {
    ptr: ffi::Tls,
    cfg: Option<TlsConfig>,
}

impl TlsContext {
    /// Create a new client context
    pub fn new_client() -> TlsResult<TlsContext> {
        let p = unsafe { ffi::tls_client() };
        if p == ptr::null_mut() {
            Err(TlsError::new("Unable to create TLS client"))
        } else {
            Ok(TlsContext {
                ptr: p,
                cfg: None,
            })
        }
    }

    fn error(&self) -> String {
        unsafe { from_cstr(ffi::tls_error(self.ptr)) }
    }

    fn rv_to_result(&self, rv: i64) -> TlsResult<()> {
        if rv == 0 {
            Ok(())
        } else {
            Err(TlsError {
                msg: self.error(),
                code: rv,
            })
        }
    }

    /// If port is empty, the port value is assumed to be part of the hostname string as host:port.
    /// If servername is not empty it is used instead of the hostname for verification.
    pub fn connect_servername(&mut self,
                              hostname: &str,
                              port: &str,
                              servername: &str)
                              -> TlsResult<()> {
        let rv = unsafe {
            let hostname_c = CString::from_vec_unchecked(hostname.bytes().collect()).as_ptr();
            // Both port and servername can be NULL
            let port_c = str_c_ptr(port);
            let servername_c = str_c_ptr(servername);
            ffi::tls_connect_servername(self.ptr, hostname_c, port_c, servername_c)
        };
        self.rv_to_result(rv as i64)
    }

    #[cfg(unix)]
    /// Establish a TLS connection over the given socket
    pub fn connect_socket(&mut self, fd: RawFd, servername: &str) -> TlsResult<()> {
        let rv = unsafe {
            let servername_c = str_c_ptr(servername);
            ffi::tls_connect_socket(self.ptr, fd, servername_c)
        };
        self.rv_to_result(rv as i64)
    }

    #[cfg(windows)]
    /// Establish a TLS connection over the given socket
    pub fn connect_socket(&mut self, sock: RawSocket, servername: &str) -> TlsResult<()> {
        let rv = unsafe {
            let servername_c = str_c_ptr(servername);
            // This cast is not exactly safe
            // http://stackoverflow.com/questions/1953639/
            ffi::tls_connect_socket(self.ptr, sock as i32, servername_c)
        };
        self.rv_to_result(rv as i64)
    }

    pub fn conn_version(&self) -> String {
        unsafe { from_cstr(ffi::tls_conn_version(self.ptr)) }
    }
    pub fn conn_cipher(&self) -> String {
        unsafe { from_cstr(ffi::tls_conn_cipher(self.ptr)) }
    }

    /// Apply configuration settings to the context, consuming the config struct
    ///
    /// This should be called BEFORE trying to establish/accept
    /// a connection
    pub fn configure(&mut self, cfg: TlsConfig) -> TlsResult<()> {
        let rv = unsafe { ffi::tls_configure(self.ptr, cfg.cfg) };
        self.cfg = Some(cfg);
        self.rv_to_result(rv as i64)
    }

    pub fn peer_cert_notbefore(&self) -> TlsResult<DateTime<UTC>> {
        let rv = unsafe { ffi::tls_peer_cert_notbefore(self.ptr) };
        if rv == -1 {
            Err(TlsError::new("Unable to get certificate information"))
        } else {
            Ok(DateTime::from_utc(NaiveDateTime::from_timestamp(rv as i64, 0), UTC))
        }
    }

    pub fn peer_cert_notafter(&self) -> TlsResult<DateTime<UTC>> {
        let rv = unsafe { ffi::tls_peer_cert_notafter(self.ptr) };
        if rv == -1 {
            Err(TlsError::new("Unable to get certificate information"))
        } else {
            Ok(DateTime::from_utc(NaiveDateTime::from_timestamp(rv as i64, 0), UTC))
        }
    }

    pub fn peer_cert_hash(&self) -> String {
        unsafe { from_cstr(ffi::tls_peer_cert_hash(self.ptr)) }
    }

    pub fn peer_cert_issuer(&self) -> String {
        unsafe { from_cstr(ffi::tls_peer_cert_issuer(self.ptr)) }
    }

    pub fn peer_cert_subject(&self) -> String {
        unsafe { from_cstr(ffi::tls_peer_cert_subject(self.ptr)) }
    }

    pub fn peer_cert_contains_name(&self, name: &str) -> bool {
        let rv = unsafe {
            let name_c = CString::from_vec_unchecked(name.bytes().collect());
            ffi::tls_peer_cert_contains_name(self.ptr, name_c.as_ptr())
        };
        (rv == 1)
    }

    pub fn peer_cert_provided(&self) -> bool {
        let rv = unsafe { ffi::tls_peer_cert_provided(self.ptr) };
        (rv == 1)
    }

    fn rv_to_result_io(&self, rv: i64) -> TlsResult<usize> {
        match rv {
            ffi::WANT_POLLIN => {
                Err(TlsError {
                    msg: String::new(),
                    code: rv,
                })
            }
            ffi::WANT_POLLOUT => {
                Err(TlsError {
                    msg: String::new(),
                    code: rv,
                })
            }
            rv if rv < 0 => {
                Err(TlsError {
                    msg: self.error(),
                    code: -1,
                })
            }
            rv => Ok(rv as usize),
        }
    }

    /// Complete the TLS handshake
    ///
    /// This function will be called when needed by `read()` or `write()`, but
    /// can be called to complete the handshake.
    pub fn handshake(&mut self) -> TlsResult<()> {
        let rv = unsafe { ffi::tls_handshake(self.ptr) };
        self.rv_to_result_io(rv as i64).map(|_| ())
    }

    pub fn close(&mut self) -> TlsResult<()> {
        let rv = unsafe { ffi::tls_close(self.ptr) };
        self.rv_to_result_io(rv as i64).map(|_| ())
    }

    pub fn read(&mut self, buf: &mut [u8]) -> TlsResult<usize> {
        let buflen = buf.len() as size_t;
        let bptr = buf.as_mut_ptr() as *mut c_void;
        let rv = unsafe { ffi::tls_read(self.ptr, bptr, buflen) };
        self.rv_to_result_io(rv as i64)
    }

    pub fn write(&mut self, buf: &[u8]) -> TlsResult<usize> {
        let buflen = buf.len() as size_t;
        let bptr = buf.as_ptr() as *const c_void;
        let rv = unsafe { ffi::tls_write(self.ptr, bptr, buflen) };
        self.rv_to_result_io(rv as i64)
    }

    /// Create new server context
    pub fn new_server() -> TlsResult<TlsContext> {
        let p = unsafe { ffi::tls_server() };
        if p == ptr::null_mut() {
            Err(TlsError::new("Unable to create TLS server"))
        } else {
            Ok(TlsContext {
                ptr: p,
                cfg: None,
            })
        }
    }

    #[cfg(unix)]
    /// Accept a new TLS connection over an existing socket
    pub fn accept_socket(&mut self, fd: RawFd) -> TlsResult<TlsContext> {
        let mut cctx: ffi::Tls = ptr::null_mut();;
        let rv = unsafe { ffi::tls_accept_socket(self.ptr, &mut cctx, fd) };
        self.rv_to_result(rv as i64)
            .map(|_| {
                TlsContext {
                    ptr: cctx,
                    cfg: None,
                }
            })
    }

    #[cfg(windows)]
    /// Accept a new TLS connection over an existing socket
    pub fn accept_socket(&mut self, sock: RawSocket) -> TlsResult<TlsContext> {
        let mut cctx: ffi::Tls = ptr::null_mut();;
        // This cast is not exactly safe
        // http://stackoverflow.com/questions/1953639/
        let rv = unsafe {
            ffi::tls_accept_socket(self.ptr, &mut cctx, sock as i32)
        };
        self.rv_to_result(rv as i64)
            .map(|_| {
                TlsContext {
                    ptr: cctx,
                    cfg: None,
                }
            })
    }
}

impl Drop for TlsContext {
    fn drop(&mut self) {
        unsafe {
            ffi::tls_free(self.ptr);
        }
    }
}

/// Initialize libtls - make sure to call this before using the API
/// Returns false if libtls failed to initialise.
pub fn init() -> bool {
    static mut RET: i32 = -1;
    static ONCE: Once = ONCE_INIT;
    ONCE.call_once(|| {
        other_init();
        unsafe { RET = ffi::tls_init() };
    });
    unsafe { (RET == 0) }
}

#[test]
fn connect_servername() {
    assert!(init());
    let mut cfg = TlsConfig::new().unwrap();
    cfg.set_ca_file("tests/cert.pem").unwrap();
    let mut cli = TlsContext::new_client().unwrap();
    cli.configure(cfg).unwrap();
    cli.connect_servername("www.google.com", "443", "")
       .unwrap();
    cli.handshake().unwrap();
}

#[test]
fn error_connect_no_host() {
    assert!(init());
    let mut cfg = TlsConfig::new().unwrap();
    cfg.set_ca_file("tests/cert.pem").unwrap();
    let mut cli = TlsContext::new_client().unwrap();
    assert!(cli.connect_servername("", "443", "")
            .is_err());
}

#[test]
fn error_connect_no_port() {
    assert!(init());
    let mut cfg = TlsConfig::new().unwrap();
    cfg.set_ca_file("tests/cert.pem").unwrap();
    let mut cli = TlsContext::new_client().unwrap();
    assert!(cli.connect_servername("www.google.com", "", "")
            .is_err());
}
<|endoftext|>


<commit_before>
<commit_msg>rotational-cipher: Added template to stub file<commit_after>pub fn rotate(input: &str, key: u8) -> String {
    unimplemented!(
        "How would input text '{}' transform when every letter is shifted using key '{}'?",
        input,
        key
    );
}
<|endoftext|>


<commit_before>use crate::graphic::uniform::GlUniform;
use crate::graphic::GlProgram;
use crate::opengl;
use crate::opengl::types::{GLint, GLuint};
use cgmath::Vector2;
use image::{GenericImageView, ImageBuffer, LumaA, Pixel, Rgba, SubImage};
use num_integer::Integer;
use std::convert::{TryFrom, TryInto};
use std::ffi::{c_void, CString};
use std::marker::PhantomData;
use std::rc::Rc;

/// A square texture that functions like a moving window into a larger texture.
/// Incrementality is achieved by using wraparound indexing and overwriting the
/// parts that moved off the texture with the newly loaded parts.
/// See `https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter02.html`,
/// section 2.4 for an illustration, but note that this page does not describe
/// the present implementation, which is simpler.
/// Also publishes an `<texture_name>_texture_offset` uniform whose unit is
/// pixels (not UV coordinates).
///
/// I'm told that a more efficient alternative would be to use a PBO (pixel
/// buffer object), so a future version could use that instead.
pub struct GlMovingWindowTexture
 {
    id: GLuint,
    gl: Rc<opengl::Gl>,
    size: u32,
    texture_unit: u32,
    u_texture_offset: GlUniform<Vector2<i32>>,
    pixel_type: PhantomData
,
}

/// Specifies the offsets in the destination, as well as the image to be pasted
/// there.
struct UpdateRegion<'a, P: Pixel + 'static> {
    x: u32,
    y: u32,
    pixels: SubImage<&'a ImageBuffer>,
}

impl<'a, P: Pixel + 'static> UpdateRegion<'a, P> {
    /// Splits an image with offset into update regions. See
    /// [`incremental_update()`] for a detailed explanation.
    /// This works not only for full-width/full-height strips, but
    /// here are vertical (left) and horizontal (right) strips
    /// visualized:
    ///
    /// ```text
    ///     +---+-+
    ///     | 3 |4|
    ///     +---+-+              +---+---------+
    ///     |   | |              | 3 |    4    |
    ///     | 1 |2|              +---+---------+
    ///     |   | |              | 1 |    2    |
    ///     +---+-+              +---+---------+
    ///        |                        |
    ///        V                        V
    /// +-+-------+---+          +---------+---+
    /// | |       |   |          |    2    | 1 |
    /// |2|       | 1 |          +---------+---+
    /// | |       |   |          |             |
    /// +-+       +---+          +---------+---+
    /// |4|       | 3 |          |    4    | 3 |
    /// +-+-------+---+          +---------+---+
    /// ```
    ///
    /// x is right and y is up, so the origin of the image is the lower left
    /// corner of region 1.

    pub fn new_regions(
        xoff: u32,
        yoff: u32,
        size: u32,
        pixels: &'a ImageBuffer,
    ) -> [UpdateRegion<'a, P>; 4] {
        // Check invariant
        debug_assert!(xoff < size && yoff <= size);
        // These variables are named after the regions they apply to. For
        // instance, regions 1 and 3 share the same width.
        let width_1_3 = pixels.width().min(size - xoff);
        let width_2_4 = pixels.width() - width_1_3;
        let height_1_2 = pixels.height().min(size - yoff);
        let height_3_4 = pixels.height() - height_1_2;
        [
            UpdateRegion {
                x: xoff,
                y: yoff,
                pixels: pixels.view(0, 0, width_1_3, height_1_2),
            },
            UpdateRegion {
                x: 0,
                y: yoff,
                pixels: pixels.view(width_1_3, 0, width_2_4, height_1_2),
            },
            UpdateRegion {
                x: xoff,
                y: 0,
                pixels: pixels.view(0, height_1_2, width_1_3, height_3_4),
            },
            UpdateRegion {
                x: 0,
                y: 0,
                pixels: pixels.view(width_1_3, height_1_2, width_2_4, height_3_4),
            },
        ]
    }
}

impl
 GlMovingWindowTexture

where
    P: TextureFormat + 'static,
    P::Subpixel: 'static + std::fmt::Debug,
{
    /// The texture_unit parameter is just a sequence number for textures
    pub fn new(
        program: &GlProgram,
        gl: Rc<opengl::Gl>,
        name: &str,
        size: u32,
        texture_unit: u32,
        pixels: ImageBuffer,
    ) -> Self {
        let mut id = 0;
        let name_c = CString::new(name).unwrap();
        unsafe {
            gl.UseProgram(program.id);
            let loc = gl.GetUniformLocation(program.id, name_c.as_ptr());
            gl.Uniform1i(loc, i32::try_from(texture_unit).unwrap());

            gl.GenTextures(1, &mut id);
            gl.BindTexture(opengl::TEXTURE_2D, id);

            // Enable wraparound addressing
            gl.TexParameteri(
                opengl::TEXTURE_2D,
                opengl::TEXTURE_WRAP_S,
                opengl::REPEAT as i32,
            );
            gl.TexParameteri(
                opengl::TEXTURE_2D,
                opengl::TEXTURE_WRAP_T,
                opengl::REPEAT as i32,
            );

            // Intended for exact pixel fetching, no interpolation needed
            gl.TexParameteri(
                opengl::TEXTURE_2D,
                opengl::TEXTURE_MIN_FILTER,
                opengl::NEAREST as i32,
            );
            gl.TexParameteri(
                opengl::TEXTURE_2D,
                opengl::TEXTURE_MAG_FILTER,
                opengl::NEAREST as i32,
            );
            // For an explanation of the different parameters, see
            // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml
            gl.TexImage2D(
                opengl::TEXTURE_2D,
                0, // level
                P::INTERNALFORMAT,
                size as i32, // width
                size as i32, // height
                0,           // border
                P::FORMAT,
                P::DTYPE,
                pixels.into_raw().as_ptr() as *const c_void,
            );
        }

        let u_texture_offset = GlUniform::new(
            &program,
            &format!("{}_texture_offset", name),
            Vector2::new(0, 0),
        );

        GlMovingWindowTexture {
            id,
            gl,
            size,
            texture_unit,
            u_texture_offset,
            pixel_type: PhantomData,
        }
    }

    /// Let's say the texture window has moved left and up. The new pixels that
    /// are now inside the window are passed as a vertical and a horizontal
    /// strip. Here they are in the texture window:
    /// ```text
    /// +-----+------------+
    /// |#####|############| <- hori_strip
    /// +-----|------------+
    /// |#####|            |
    /// |#####|            |
    /// |#####|            |
    /// +-----+------------+
    ///    ^
    ///    |
    ///   vert_strip
    /// ```
    ///
    /// You can see that they are redundant where they overlap. This is not
    /// handled to keep the code from becoming more complex. Also, either may
    /// be empty, which is the case for purely horizontal/vertical shifts.
    ///
    /// These strips have to be inserted correctly into the OpenGL texture,
    /// which starts not at 0 but at `texture_offset` because of wrapping indexing.
    /// Updating that texture from such a strip can typically not be done in one
    /// step, but requires breaking it into up to four subregions. This is
    /// because OpenGL textures can be read with wrapping indexing, but not
    /// written with wrapping indexing, so we need to do it ourselves.
    /// Let's look at the vertical strip:
    ///
    /// ```text
    /// <- width ->
    /// +---+-+
    /// | 3 |4|
    /// +---+-+
    /// |   | |  vert_strip
    /// | 1 |2|
    /// |   | |
    /// +---+-+
    /// ```
    ///
    /// Wraparound at the right texture edge causes the split into regions 1/3
    /// vs 2/4, and wraparound at the upper texture edge causes the split into
    /// regions 1/2 vs 3/4.
    /// Here's the texture with the vert_strip placed correctly:
    ///
    /// ```text
    /// <- self.size ->
    /// +-+-------+---+
    /// | |       |   |
    /// |2|       | 1 |
    /// | |       |   |
    /// +-+       +---+ <- texture_offset.y
    /// |4|       | 3 |
    /// +-+-------+---+
    ///           ^
    ///           |
    ///           xoff
    /// ```
    ///
    /// `xoff` is not equal to `texture_offset.x` if the texture moved right.
    ///
    /// The way this is handled is by creating regions as if they always need
    /// splitting, and skipping empty regions that result from that.
    pub fn incremental_update(
        &mut self,
        delta_x: i32,
        delta_y: i32,
        vert_strip: ImageBuffer,
        hori_strip: ImageBuffer,
    ) {
        // Calculate the updated texture offset, which is always in [0; self.size)
        let x_after_update =
            (self.u_texture_offset.value.x + delta_x).mod_floor(&i32::try_from(self.size).unwrap());
        let y_after_update =
            (self.u_texture_offset.value.y + delta_y).mod_floor(&i32::try_from(self.size).unwrap());
        let texture_offset_after_update = Vector2::new(x_after_update, y_after_update);

        // The vertical region's x coordinate needs to be the "lower" (but not
        // really, because of wraparound, so don't use min) of the old and new
        // horizontal texture offset. Why? If delta_x > 0 and we use the new
        // offset, we'll overwrite the wrong data. The y coordinate is just the
        // new vertical texture offset however, which may not seem intuitive.
        // You can convince yourself by staring at the illustration in section
        // 2.4 of the page linked above,
        // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter02.html,
        // remembering that y is up and recalling where the L shape originates.
        // If that doesn't help, draw an image with named pixels and a texture
        // window in it and perform a few updates in different directions,
        // noting where the L-shaped region (aka vert_strip + hori_strip) are
        // placed in the texture.
        let vert_x = if delta_x > 0 {
            self.u_texture_offset.value.x
        } else {
            x_after_update
        };
        let hori_y = if delta_y > 0 {
            self.u_texture_offset.value.y
        } else {
            y_after_update
        };
        let vert_regions = UpdateRegion::new_regions(
            vert_x.try_into().unwrap(),
            y_after_update.try_into().unwrap(),
            self.size,
            &vert_strip,
        );
        let hori_regions = UpdateRegion::new_regions(
            x_after_update.try_into().unwrap(),
            hori_y.try_into().unwrap(),
            self.size,
            &hori_strip,
        );
        let regions = vert_regions.iter().chain(hori_regions.iter());

        // Now we can overwrite the old texture offset
        self.u_texture_offset.value = texture_offset_after_update;

        unsafe {
            self.gl.ActiveTexture(opengl::TEXTURE0 + self.texture_unit);
            self.gl.BindTexture(opengl::TEXTURE_2D, self.id);
            for r in regions {
                let width = i32::try_from(r.pixels.width()).unwrap();
                let height = i32::try_from(r.pixels.height()).unwrap();
                if height * width == 0 {
                    continue;
                }
                let image = r.pixels.to_image();
                // For an explanation of the different parameters, see
                // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexSubImage2D.xhtml
                self.gl.TexSubImage2D(
                    opengl::TEXTURE_2D,
                    0, // level
                    i32::try_from(r.x).unwrap(),
                    i32::try_from(r.y).unwrap(),
                    width,
                    height,
                    P::FORMAT,
                    P::DTYPE,
                    image.into_raw().as_ptr() as *const c_void,
                );
            }
        }
    }

    /// Updates the offset and binds the texture
    pub fn submit(&self) {
        unsafe {
            // The purpose of texture units is to allow us to use more than 1 texture in our shaders.
            self.gl.ActiveTexture(opengl::TEXTURE0 + self.texture_unit);
            // After activating a texture unit, a subsequent BindTexture call will bind that
            // texture to the currently active texture unit.
            self.gl.BindTexture(opengl::TEXTURE_2D, self.id);
        }
        self.u_texture_offset.submit();
    }
}

/// Trait to associate pixel formats with OpenGL constants
pub trait TextureFormat: Pixel {
    const INTERNALFORMAT: GLint;
    const FORMAT: GLuint;
    const DTYPE: GLuint;
}

impl TextureFormat for LumaA<f32> {
    const INTERNALFORMAT: GLint = opengl::RG32F as GLint;
    const FORMAT: GLuint = opengl::RG;
    const DTYPE: GLuint = opengl::FLOAT;
}

impl TextureFormat for Rgba<u8> {
    const INTERNALFORMAT: GLint = opengl::RGBA8 as GLint;
    const FORMAT: GLuint = opengl::RGBA;
    const DTYPE: GLuint = opengl::UNSIGNED_BYTE;
}

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

    #[test]
    fn test_regions() {
        let test_src = ImageBuffer::from_fn(16, 16, |x, y| Rgba::<u8>([x as u8, y as u8, 0, 255]));
        let regions = UpdateRegion::new_regions(4, 7, 16, &test_src);

        let mut dest = ImageBuffer::from_pixel(16, 16, Rgba::<u8>([0, 0, 0, 0]));
        for r in &regions {
            dest.copy_from(&r.pixels, r.x as u32, r.y as u32);
        }
        let reference = ImageBuffer::from_fn(16, 16, |x, y| {
            Rgba::<u8>([(x + 16 - 4) as u8 % 16, (y + 16 - 7) as u8 % 16, 0, 255])
        });
        assert!(dest.pixels().eq(reference.pixels()));
    }
}
<commit_msg>Add missing unwrap() for copy_from() (#428)<commit_after>use crate::graphic::uniform::GlUniform;
use crate::graphic::GlProgram;
use crate::opengl;
use crate::opengl::types::{GLint, GLuint};
use cgmath::Vector2;
use image::{GenericImageView, ImageBuffer, LumaA, Pixel, Rgba, SubImage};
use num_integer::Integer;
use std::convert::{TryFrom, TryInto};
use std::ffi::{c_void, CString};
use std::marker::PhantomData;
use std::rc::Rc;

/// A square texture that functions like a moving window into a larger texture.
/// Incrementality is achieved by using wraparound indexing and overwriting the
/// parts that moved off the texture with the newly loaded parts.
/// See `https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter02.html`,
/// section 2.4 for an illustration, but note that this page does not describe
/// the present implementation, which is simpler.
/// Also publishes an `<texture_name>_texture_offset` uniform whose unit is
/// pixels (not UV coordinates).
///
/// I'm told that a more efficient alternative would be to use a PBO (pixel
/// buffer object), so a future version could use that instead.
pub struct GlMovingWindowTexture
 {
    id: GLuint,
    gl: Rc<opengl::Gl>,
    size: u32,
    texture_unit: u32,
    u_texture_offset: GlUniform<Vector2<i32>>,
    pixel_type: PhantomData
,
}

/// Specifies the offsets in the destination, as well as the image to be pasted
/// there.
struct UpdateRegion<'a, P: Pixel + 'static> {
    x: u32,
    y: u32,
    pixels: SubImage<&'a ImageBuffer>,
}

impl<'a, P: Pixel + 'static> UpdateRegion<'a, P> {
    /// Splits an image with offset into update regions. See
    /// [`incremental_update()`] for a detailed explanation.
    /// This works not only for full-width/full-height strips, but
    /// here are vertical (left) and horizontal (right) strips
    /// visualized:
    ///
    /// ```text
    ///     +---+-+
    ///     | 3 |4|
    ///     +---+-+              +---+---------+
    ///     |   | |              | 3 |    4    |
    ///     | 1 |2|              +---+---------+
    ///     |   | |              | 1 |    2    |
    ///     +---+-+              +---+---------+
    ///        |                        |
    ///        V                        V
    /// +-+-------+---+          +---------+---+
    /// | |       |   |          |    2    | 1 |
    /// |2|       | 1 |          +---------+---+
    /// | |       |   |          |             |
    /// +-+       +---+          +---------+---+
    /// |4|       | 3 |          |    4    | 3 |
    /// +-+-------+---+          +---------+---+
    /// ```
    ///
    /// x is right and y is up, so the origin of the image is the lower left
    /// corner of region 1.

    pub fn new_regions(
        xoff: u32,
        yoff: u32,
        size: u32,
        pixels: &'a ImageBuffer,
    ) -> [UpdateRegion<'a, P>; 4] {
        // Check invariant
        debug_assert!(xoff < size && yoff <= size);
        // These variables are named after the regions they apply to. For
        // instance, regions 1 and 3 share the same width.
        let width_1_3 = pixels.width().min(size - xoff);
        let width_2_4 = pixels.width() - width_1_3;
        let height_1_2 = pixels.height().min(size - yoff);
        let height_3_4 = pixels.height() - height_1_2;
        [
            UpdateRegion {
                x: xoff,
                y: yoff,
                pixels: pixels.view(0, 0, width_1_3, height_1_2),
            },
            UpdateRegion {
                x: 0,
                y: yoff,
                pixels: pixels.view(width_1_3, 0, width_2_4, height_1_2),
            },
            UpdateRegion {
                x: xoff,
                y: 0,
                pixels: pixels.view(0, height_1_2, width_1_3, height_3_4),
            },
            UpdateRegion {
                x: 0,
                y: 0,
                pixels: pixels.view(width_1_3, height_1_2, width_2_4, height_3_4),
            },
        ]
    }
}

impl
 GlMovingWindowTexture

where
    P: TextureFormat + 'static,
    P::Subpixel: 'static + std::fmt::Debug,
{
    /// The texture_unit parameter is just a sequence number for textures
    pub fn new(
        program: &GlProgram,
        gl: Rc<opengl::Gl>,
        name: &str,
        size: u32,
        texture_unit: u32,
        pixels: ImageBuffer,
    ) -> Self {
        let mut id = 0;
        let name_c = CString::new(name).unwrap();
        unsafe {
            gl.UseProgram(program.id);
            let loc = gl.GetUniformLocation(program.id, name_c.as_ptr());
            gl.Uniform1i(loc, i32::try_from(texture_unit).unwrap());

            gl.GenTextures(1, &mut id);
            gl.BindTexture(opengl::TEXTURE_2D, id);

            // Enable wraparound addressing
            gl.TexParameteri(
                opengl::TEXTURE_2D,
                opengl::TEXTURE_WRAP_S,
                opengl::REPEAT as i32,
            );
            gl.TexParameteri(
                opengl::TEXTURE_2D,
                opengl::TEXTURE_WRAP_T,
                opengl::REPEAT as i32,
            );

            // Intended for exact pixel fetching, no interpolation needed
            gl.TexParameteri(
                opengl::TEXTURE_2D,
                opengl::TEXTURE_MIN_FILTER,
                opengl::NEAREST as i32,
            );
            gl.TexParameteri(
                opengl::TEXTURE_2D,
                opengl::TEXTURE_MAG_FILTER,
                opengl::NEAREST as i32,
            );
            // For an explanation of the different parameters, see
            // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml
            gl.TexImage2D(
                opengl::TEXTURE_2D,
                0, // level
                P::INTERNALFORMAT,
                size as i32, // width
                size as i32, // height
                0,           // border
                P::FORMAT,
                P::DTYPE,
                pixels.into_raw().as_ptr() as *const c_void,
            );
        }

        let u_texture_offset = GlUniform::new(
            &program,
            &format!("{}_texture_offset", name),
            Vector2::new(0, 0),
        );

        GlMovingWindowTexture {
            id,
            gl,
            size,
            texture_unit,
            u_texture_offset,
            pixel_type: PhantomData,
        }
    }

    /// Let's say the texture window has moved left and up. The new pixels that
    /// are now inside the window are passed as a vertical and a horizontal
    /// strip. Here they are in the texture window:
    /// ```text
    /// +-----+------------+
    /// |#####|############| <- hori_strip
    /// +-----|------------+
    /// |#####|            |
    /// |#####|            |
    /// |#####|            |
    /// +-----+------------+
    ///    ^
    ///    |
    ///   vert_strip
    /// ```
    ///
    /// You can see that they are redundant where they overlap. This is not
    /// handled to keep the code from becoming more complex. Also, either may
    /// be empty, which is the case for purely horizontal/vertical shifts.
    ///
    /// These strips have to be inserted correctly into the OpenGL texture,
    /// which starts not at 0 but at `texture_offset` because of wrapping indexing.
    /// Updating that texture from such a strip can typically not be done in one
    /// step, but requires breaking it into up to four subregions. This is
    /// because OpenGL textures can be read with wrapping indexing, but not
    /// written with wrapping indexing, so we need to do it ourselves.
    /// Let's look at the vertical strip:
    ///
    /// ```text
    /// <- width ->
    /// +---+-+
    /// | 3 |4|
    /// +---+-+
    /// |   | |  vert_strip
    /// | 1 |2|
    /// |   | |
    /// +---+-+
    /// ```
    ///
    /// Wraparound at the right texture edge causes the split into regions 1/3
    /// vs 2/4, and wraparound at the upper texture edge causes the split into
    /// regions 1/2 vs 3/4.
    /// Here's the texture with the vert_strip placed correctly:
    ///
    /// ```text
    /// <- self.size ->
    /// +-+-------+---+
    /// | |       |   |
    /// |2|       | 1 |
    /// | |       |   |
    /// +-+       +---+ <- texture_offset.y
    /// |4|       | 3 |
    /// +-+-------+---+
    ///           ^
    ///           |
    ///           xoff
    /// ```
    ///
    /// `xoff` is not equal to `texture_offset.x` if the texture moved right.
    ///
    /// The way this is handled is by creating regions as if they always need
    /// splitting, and skipping empty regions that result from that.
    pub fn incremental_update(
        &mut self,
        delta_x: i32,
        delta_y: i32,
        vert_strip: ImageBuffer,
        hori_strip: ImageBuffer,
    ) {
        // Calculate the updated texture offset, which is always in [0; self.size)
        let x_after_update =
            (self.u_texture_offset.value.x + delta_x).mod_floor(&i32::try_from(self.size).unwrap());
        let y_after_update =
            (self.u_texture_offset.value.y + delta_y).mod_floor(&i32::try_from(self.size).unwrap());
        let texture_offset_after_update = Vector2::new(x_after_update, y_after_update);

        // The vertical region's x coordinate needs to be the "lower" (but not
        // really, because of wraparound, so don't use min) of the old and new
        // horizontal texture offset. Why? If delta_x > 0 and we use the new
        // offset, we'll overwrite the wrong data. The y coordinate is just the
        // new vertical texture offset however, which may not seem intuitive.
        // You can convince yourself by staring at the illustration in section
        // 2.4 of the page linked above,
        // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter02.html,
        // remembering that y is up and recalling where the L shape originates.
        // If that doesn't help, draw an image with named pixels and a texture
        // window in it and perform a few updates in different directions,
        // noting where the L-shaped region (aka vert_strip + hori_strip) are
        // placed in the texture.
        let vert_x = if delta_x > 0 {
            self.u_texture_offset.value.x
        } else {
            x_after_update
        };
        let hori_y = if delta_y > 0 {
            self.u_texture_offset.value.y
        } else {
            y_after_update
        };
        let vert_regions = UpdateRegion::new_regions(
            vert_x.try_into().unwrap(),
            y_after_update.try_into().unwrap(),
            self.size,
            &vert_strip,
        );
        let hori_regions = UpdateRegion::new_regions(
            x_after_update.try_into().unwrap(),
            hori_y.try_into().unwrap(),
            self.size,
            &hori_strip,
        );
        let regions = vert_regions.iter().chain(hori_regions.iter());

        // Now we can overwrite the old texture offset
        self.u_texture_offset.value = texture_offset_after_update;

        unsafe {
            self.gl.ActiveTexture(opengl::TEXTURE0 + self.texture_unit);
            self.gl.BindTexture(opengl::TEXTURE_2D, self.id);
            for r in regions {
                let width = i32::try_from(r.pixels.width()).unwrap();
                let height = i32::try_from(r.pixels.height()).unwrap();
                if height * width == 0 {
                    continue;
                }
                let image = r.pixels.to_image();
                // For an explanation of the different parameters, see
                // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexSubImage2D.xhtml
                self.gl.TexSubImage2D(
                    opengl::TEXTURE_2D,
                    0, // level
                    i32::try_from(r.x).unwrap(),
                    i32::try_from(r.y).unwrap(),
                    width,
                    height,
                    P::FORMAT,
                    P::DTYPE,
                    image.into_raw().as_ptr() as *const c_void,
                );
            }
        }
    }

    /// Updates the offset and binds the texture
    pub fn submit(&self) {
        unsafe {
            // The purpose of texture units is to allow us to use more than 1 texture in our shaders.
            self.gl.ActiveTexture(opengl::TEXTURE0 + self.texture_unit);
            // After activating a texture unit, a subsequent BindTexture call will bind that
            // texture to the currently active texture unit.
            self.gl.BindTexture(opengl::TEXTURE_2D, self.id);
        }
        self.u_texture_offset.submit();
    }
}

/// Trait to associate pixel formats with OpenGL constants
pub trait TextureFormat: Pixel {
    const INTERNALFORMAT: GLint;
    const FORMAT: GLuint;
    const DTYPE: GLuint;
}

impl TextureFormat for LumaA<f32> {
    const INTERNALFORMAT: GLint = opengl::RG32F as GLint;
    const FORMAT: GLuint = opengl::RG;
    const DTYPE: GLuint = opengl::FLOAT;
}

impl TextureFormat for Rgba<u8> {
    const INTERNALFORMAT: GLint = opengl::RGBA8 as GLint;
    const FORMAT: GLuint = opengl::RGBA;
    const DTYPE: GLuint = opengl::UNSIGNED_BYTE;
}

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

    #[test]
    fn test_regions() {
        let test_src = ImageBuffer::from_fn(16, 16, |x, y| Rgba::<u8>([x as u8, y as u8, 0, 255]));
        let regions = UpdateRegion::new_regions(4, 7, 16, &test_src);

        let mut dest = ImageBuffer::from_pixel(16, 16, Rgba::<u8>([0, 0, 0, 0]));
        for r in &regions {
            dest.copy_from(&r.pixels, r.x as u32, r.y as u32).unwrap();
        }
        let reference = ImageBuffer::from_fn(16, 16, |x, y| {
            Rgba::<u8>([(x + 16 - 4) as u8 % 16, (y + 16 - 7) as u8 % 16, 0, 255])
        });
        assert!(dest.pixels().eq(reference.pixels()));
    }
}
<|endoftext|>