<commit_before>use cairo;
use glib::translate::*;
use glib_sys;
use libc;
use pango;
use pango_sys;

use color::{Color, ColorSpec};
use error::*;
use length::{RsvgLength, StrokeDasharray};
use node::RsvgNode;
use opacity::{Opacity, OpacitySpec};
use paint_server::PaintServer;
use parsers::Parse;
use util::utf8_cstr;

pub enum RsvgState {}

#[derive(Clone)]
pub struct State {
    join: StrokeLinejoin,
    has_join: bool,
}

impl Default for State {
    fn default() -> State {
        State {
            join: Default::default(),
            has_join: false,
        }
    }
}

// Keep in sync with rsvg-styles.h:UnicodeBidi
#[repr(C)]
pub enum UnicodeBidi {
    Normal,
    Embed,
    Override,
}

// Keep in sync with rsvg-styles.h:TextDecoration
#[repr(C)]
#[derive(Copy, Clone)]
struct TextDecoration {
    overline: glib_sys::gboolean,
    underline: glib_sys::gboolean,
    strike: glib_sys::gboolean,
}

pub struct FontDecor {
    pub overline: bool,
    pub underline: bool,
    pub strike: bool,
}

impl From<TextDecoration> for FontDecor {
    fn from(td: TextDecoration) -> FontDecor {
        FontDecor {
            overline: from_glib(td.overline),
            underline: from_glib(td.underline),
            strike: from_glib(td.strike),
        }
    }
}

extern "C" {
    fn rsvg_state_new() -> *mut RsvgState;
    fn rsvg_state_free(state: *mut RsvgState);
    fn rsvg_state_reinit(state: *mut RsvgState);
    fn rsvg_state_reconstruct(state: *mut RsvgState, node: *const RsvgNode);
    fn rsvg_state_get_affine(state: *const RsvgState) -> cairo::Matrix;
    fn rsvg_state_is_overflow(state: *const RsvgState) -> glib_sys::gboolean;
    fn rsvg_state_has_overflow(state: *const RsvgState) -> glib_sys::gboolean;
    fn rsvg_state_get_cond_true(state: *const RsvgState) -> glib_sys::gboolean;
    fn rsvg_state_set_cond_true(state: *const RsvgState, cond_true: glib_sys::gboolean);
    fn rsvg_state_get_stop_color(state: *const RsvgState) -> *const ColorSpec;
    fn rsvg_state_get_stop_opacity(state: *const RsvgState) -> *const OpacitySpec;
    fn rsvg_state_get_stroke_dasharray(state: *const RsvgState) -> *const StrokeDasharray;
    fn rsvg_state_get_dash_offset(state: *const RsvgState) -> RsvgLength;
    fn rsvg_state_get_current_color(state: *const RsvgState) -> u32;
    fn rsvg_state_get_shape_rendering_type(state: *const RsvgState) -> cairo::Antialias;
    fn rsvg_state_get_text_rendering_type(state: *const RsvgState) -> cairo::Antialias;

    fn rsvg_state_get_stroke(state: *const RsvgState) -> *const PaintServer;
    fn rsvg_state_get_stroke_opacity(state: *const RsvgState) -> u8;
    fn rsvg_state_get_stroke_width(state: *const RsvgState) -> RsvgLength;
    fn rsvg_state_get_miter_limit(state: *const RsvgState) -> f64;
    fn rsvg_state_get_line_cap(state: *const RsvgState) -> cairo::LineCap;
    fn rsvg_state_get_line_join(state: *const RsvgState) -> cairo::LineJoin;
    fn rsvg_state_get_language(state: *const RsvgState) -> *const libc::c_char;
    fn rsvg_state_get_unicode_bidi(state: *const RsvgState) -> UnicodeBidi;
    fn rsvg_state_get_text_dir(state: *const RsvgState) -> pango_sys::PangoDirection;
    fn rsvg_state_get_text_gravity(state: *const RsvgState) -> pango_sys::PangoGravity;
    fn rsvg_state_get_font_family(state: *const RsvgState) -> *const libc::c_char;
    fn rsvg_state_get_font_style(state: *const RsvgState) -> pango_sys::PangoStyle;
    fn rsvg_state_get_font_variant(state: *const RsvgState) -> pango_sys::PangoVariant;
    fn rsvg_state_get_font_weight(state: *const RsvgState) -> pango_sys::PangoWeight;
    fn rsvg_state_get_font_stretch(state: *const RsvgState) -> pango_sys::PangoStretch;
    fn rsvg_state_get_letter_spacing(state: *const RsvgState) -> RsvgLength;
    fn rsvg_state_get_font_decor(state: *const RsvgState) -> *const TextDecoration;
    fn rsvg_state_get_clip_rule(state: *const RsvgState) -> cairo::FillRule;
    fn rsvg_state_get_fill(state: *const RsvgState) -> *const PaintServer;
    fn rsvg_state_get_fill_opacity(state: *const RsvgState) -> u8;
    fn rsvg_state_get_fill_rule(state: *const RsvgState) -> cairo::FillRule;

    fn rsvg_state_get_state_rust(state: *const RsvgState) -> *mut State;
}

pub fn new() -> *mut RsvgState {
    unsafe { rsvg_state_new() }
}

pub fn free(state: *mut RsvgState) {
    unsafe {
        rsvg_state_free(state);
    }
}

pub fn reinit(state: *mut RsvgState) {
    unsafe {
        rsvg_state_reinit(state);
    }
}

pub fn reconstruct(state: *mut RsvgState, node: *const RsvgNode) {
    unsafe {
        rsvg_state_reconstruct(state, node);
    }
}

pub fn get_affine(state: *const RsvgState) -> cairo::Matrix {
    unsafe { rsvg_state_get_affine(state) }
}

pub fn is_overflow(state: *const RsvgState) -> bool {
    unsafe { from_glib(rsvg_state_is_overflow(state)) }
}

pub fn has_overflow(state: *const RsvgState) -> bool {
    unsafe { from_glib(rsvg_state_has_overflow(state)) }
}

pub fn get_cond_true(state: *const RsvgState) -> bool {
    unsafe { from_glib(rsvg_state_get_cond_true(state)) }
}

pub fn set_cond_true(state: *const RsvgState, cond_true: bool) {
    unsafe {
        rsvg_state_set_cond_true(state, cond_true.to_glib());
    }
}

pub fn get_stop_color(state: *const RsvgState) -> Result<Option<Color>, AttributeError> {
    unsafe {
        let spec_ptr = rsvg_state_get_stop_color(state);

        if spec_ptr.is_null() {
            Ok(None)
        } else {
            Color::from_color_spec(&*spec_ptr).map(Some)
        }
    }
}

pub fn get_stop_opacity(state: *const RsvgState) -> Result<Option<Opacity>, AttributeError> {
    unsafe {
        let opacity_ptr = rsvg_state_get_stop_opacity(state);

        if opacity_ptr.is_null() {
            Ok(None)
        } else {
            Opacity::from_opacity_spec(&*opacity_ptr).map(Some)
        }
    }
}

pub fn get_stroke_dasharray<'a>(state: *const RsvgState) -> Option<&'a StrokeDasharray> {
    let dash = unsafe { rsvg_state_get_stroke_dasharray(state) };

    if dash.is_null() {
        None
    } else {
        unsafe { Some(&*dash) }
    }
}

pub fn get_dash_offset(state: *const RsvgState) -> RsvgLength {
    unsafe { rsvg_state_get_dash_offset(state) }
}

pub fn get_current_color(state: *const RsvgState) -> Color {
    let argb = unsafe { rsvg_state_get_current_color(state) };

    Color::from(argb)
}

pub fn get_shape_rendering_type(state: *const RsvgState) -> cairo::Antialias {
    unsafe { rsvg_state_get_shape_rendering_type(state) }
}

pub fn get_text_rendering_type(state: *const RsvgState) -> cairo::Antialias {
    unsafe { rsvg_state_get_text_rendering_type(state) }
}

pub fn get_stroke<'a>(state: *const RsvgState) -> Option<&'a PaintServer> {
    unsafe {
        let ps = rsvg_state_get_stroke(state);

        if ps.is_null() {
            None
        } else {
            Some(&*ps)
        }
    }
}

pub fn get_stroke_opacity(state: *const RsvgState) -> u8 {
    unsafe { rsvg_state_get_stroke_opacity(state) }
}

pub fn get_stroke_width(state: *const RsvgState) -> RsvgLength {
    unsafe { rsvg_state_get_stroke_width(state) }
}

pub fn get_miter_limit(state: *const RsvgState) -> f64 {
    unsafe { rsvg_state_get_miter_limit(state) }
}

pub fn get_line_cap(state: *const RsvgState) -> cairo::LineCap {
    unsafe { rsvg_state_get_line_cap(state) }
}

pub fn get_line_join(state: *const RsvgState) -> cairo::LineJoin {
    unsafe { rsvg_state_get_line_join(state) }
}

pub fn get_language(state: *const RsvgState) -> Option<String> {
    unsafe { from_glib_none(rsvg_state_get_language(state)) }
}

pub fn get_unicode_bidi(state: *const RsvgState) -> UnicodeBidi {
    unsafe { rsvg_state_get_unicode_bidi(state) }
}

pub fn get_text_dir(state: *const RsvgState) -> pango::Direction {
    unsafe { from_glib(rsvg_state_get_text_dir(state)) }
}

pub fn get_text_gravity(state: *const RsvgState) -> pango::Gravity {
    unsafe { from_glib(rsvg_state_get_text_gravity(state)) }
}

pub fn get_font_family(state: *const RsvgState) -> Option<String> {
    unsafe { from_glib_none(rsvg_state_get_font_family(state)) }
}

pub fn get_font_style(state: *const RsvgState) -> pango::Style {
    unsafe { from_glib(rsvg_state_get_font_style(state)) }
}

pub fn get_font_variant(state: *const RsvgState) -> pango::Variant {
    unsafe { from_glib(rsvg_state_get_font_variant(state)) }
}

pub fn get_font_weight(state: *const RsvgState) -> pango::Weight {
    unsafe { from_glib(rsvg_state_get_font_weight(state)) }
}

pub fn get_font_stretch(state: *const RsvgState) -> pango::Stretch {
    unsafe { from_glib(rsvg_state_get_font_stretch(state)) }
}

pub fn get_letter_spacing(state: *const RsvgState) -> RsvgLength {
    unsafe { rsvg_state_get_letter_spacing(state) }
}

pub fn get_font_decor(state: *const RsvgState) -> Option<FontDecor> {
    unsafe {
        let td = rsvg_state_get_font_decor(state);
        if td.is_null() {
            None
        } else {
            Some(FontDecor::from(*td))
        }
    }
}

pub fn get_clip_rule(state: *const RsvgState) -> cairo::FillRule {
    unsafe { rsvg_state_get_clip_rule(state) }
}

pub fn get_fill<'a>(state: *const RsvgState) -> Option<&'a PaintServer> {
    unsafe {
        let ps = rsvg_state_get_fill(state);

        if ps.is_null() {
            None
        } else {
            Some(&*ps)
        }
    }
}

pub fn get_fill_opacity(state: *const RsvgState) -> u8 {
    unsafe { rsvg_state_get_fill_opacity(state) }
}

pub fn get_fill_rule(state: *const RsvgState) -> cairo::FillRule {
    unsafe { rsvg_state_get_fill_rule(state) }
}

pub fn get_state_rust<'a>(state: *const RsvgState) -> &'a mut State {
    unsafe { &mut *rsvg_state_get_state_rust(state) }
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub enum StrokeLinejoin {
    Miter,
    Round,
    Bevel,
    Inherit,
}

#[repr(C)]
pub struct StrokeLinejoinResult {
    valid: glib_sys::gboolean,
    linejoin: StrokeLinejoin,
}

impl Default for StrokeLinejoin {
    fn default() -> StrokeLinejoin {
        StrokeLinejoin::Miter
    }
}

impl Parse for StrokeLinejoin {
    type Data = ();
    type Err = ();

    fn parse(s: &str, _: Self::Data) -> Result<StrokeLinejoin, ()> {
        match s.trim() {
            "miter" => Ok(StrokeLinejoin::Miter),
            "round" => Ok(StrokeLinejoin::Round),
            "bevel" => Ok(StrokeLinejoin::Bevel),
            "inherit" => Ok(StrokeLinejoin::Inherit),
            _ => Err(()),
        }
    }
}

impl From<Result<StrokeLinejoin, ()>> for StrokeLinejoinResult {
    fn from(v: Result<StrokeLinejoin, ()>) -> StrokeLinejoinResult {
        match v {
            Ok(j) => StrokeLinejoinResult {
                valid: true.to_glib(),
                linejoin: j,
            },

            Err(()) => StrokeLinejoinResult {
                valid: false.to_glib(),
                linejoin: StrokeLinejoin::default(),
            },
        }
    }
}

#[no_mangle]
pub extern "C" fn rsvg_stroke_linejoin_get_default() -> StrokeLinejoin {
    StrokeLinejoin::default()
}

#[no_mangle]
pub extern "C" fn rsvg_stroke_linejoin_parse(s: *const libc::c_char) -> StrokeLinejoinResult {
    let s = unsafe { utf8_cstr(s) };

    StrokeLinejoinResult::from(StrokeLinejoin::parse(s, ()))
}

#[no_mangle]
pub extern "C" fn rsvg_state_rust_new() -> *mut State {
    Box::into_raw(Box::new(State::default()))
}

#[no_mangle]
pub extern "C" fn rsvg_state_rust_free(state: *mut State) {
    assert!(!state.is_null());

    unsafe { Box::from_raw(state); }
}

#[no_mangle]
pub extern "C" fn rsvg_state_rust_clone(state: *const State) -> *mut State {
    assert!(!state.is_null());

    unsafe { Box::into_raw(Box::new((*state).clone())) }
}
<commit_msg>StrokeLinejoin::parse: return an AttributeError<commit_after>use cairo;
use glib::translate::*;
use glib_sys;
use libc;
use pango;
use pango_sys;

use color::{Color, ColorSpec};
use error::*;
use length::{RsvgLength, StrokeDasharray};
use node::RsvgNode;
use opacity::{Opacity, OpacitySpec};
use paint_server::PaintServer;
use parsers::Parse;
use util::utf8_cstr;

pub enum RsvgState {}

#[derive(Clone)]
pub struct State {
    join: StrokeLinejoin,
    has_join: bool,
}

impl Default for State {
    fn default() -> State {
        State {
            join: Default::default(),
            has_join: false,
        }
    }
}

// Keep in sync with rsvg-styles.h:UnicodeBidi
#[repr(C)]
pub enum UnicodeBidi {
    Normal,
    Embed,
    Override,
}

// Keep in sync with rsvg-styles.h:TextDecoration
#[repr(C)]
#[derive(Copy, Clone)]
struct TextDecoration {
    overline: glib_sys::gboolean,
    underline: glib_sys::gboolean,
    strike: glib_sys::gboolean,
}

pub struct FontDecor {
    pub overline: bool,
    pub underline: bool,
    pub strike: bool,
}

impl From<TextDecoration> for FontDecor {
    fn from(td: TextDecoration) -> FontDecor {
        FontDecor {
            overline: from_glib(td.overline),
            underline: from_glib(td.underline),
            strike: from_glib(td.strike),
        }
    }
}

extern "C" {
    fn rsvg_state_new() -> *mut RsvgState;
    fn rsvg_state_free(state: *mut RsvgState);
    fn rsvg_state_reinit(state: *mut RsvgState);
    fn rsvg_state_reconstruct(state: *mut RsvgState, node: *const RsvgNode);
    fn rsvg_state_get_affine(state: *const RsvgState) -> cairo::Matrix;
    fn rsvg_state_is_overflow(state: *const RsvgState) -> glib_sys::gboolean;
    fn rsvg_state_has_overflow(state: *const RsvgState) -> glib_sys::gboolean;
    fn rsvg_state_get_cond_true(state: *const RsvgState) -> glib_sys::gboolean;
    fn rsvg_state_set_cond_true(state: *const RsvgState, cond_true: glib_sys::gboolean);
    fn rsvg_state_get_stop_color(state: *const RsvgState) -> *const ColorSpec;
    fn rsvg_state_get_stop_opacity(state: *const RsvgState) -> *const OpacitySpec;
    fn rsvg_state_get_stroke_dasharray(state: *const RsvgState) -> *const StrokeDasharray;
    fn rsvg_state_get_dash_offset(state: *const RsvgState) -> RsvgLength;
    fn rsvg_state_get_current_color(state: *const RsvgState) -> u32;
    fn rsvg_state_get_shape_rendering_type(state: *const RsvgState) -> cairo::Antialias;
    fn rsvg_state_get_text_rendering_type(state: *const RsvgState) -> cairo::Antialias;

    fn rsvg_state_get_stroke(state: *const RsvgState) -> *const PaintServer;
    fn rsvg_state_get_stroke_opacity(state: *const RsvgState) -> u8;
    fn rsvg_state_get_stroke_width(state: *const RsvgState) -> RsvgLength;
    fn rsvg_state_get_miter_limit(state: *const RsvgState) -> f64;
    fn rsvg_state_get_line_cap(state: *const RsvgState) -> cairo::LineCap;
    fn rsvg_state_get_line_join(state: *const RsvgState) -> cairo::LineJoin;
    fn rsvg_state_get_language(state: *const RsvgState) -> *const libc::c_char;
    fn rsvg_state_get_unicode_bidi(state: *const RsvgState) -> UnicodeBidi;
    fn rsvg_state_get_text_dir(state: *const RsvgState) -> pango_sys::PangoDirection;
    fn rsvg_state_get_text_gravity(state: *const RsvgState) -> pango_sys::PangoGravity;
    fn rsvg_state_get_font_family(state: *const RsvgState) -> *const libc::c_char;
    fn rsvg_state_get_font_style(state: *const RsvgState) -> pango_sys::PangoStyle;
    fn rsvg_state_get_font_variant(state: *const RsvgState) -> pango_sys::PangoVariant;
    fn rsvg_state_get_font_weight(state: *const RsvgState) -> pango_sys::PangoWeight;
    fn rsvg_state_get_font_stretch(state: *const RsvgState) -> pango_sys::PangoStretch;
    fn rsvg_state_get_letter_spacing(state: *const RsvgState) -> RsvgLength;
    fn rsvg_state_get_font_decor(state: *const RsvgState) -> *const TextDecoration;
    fn rsvg_state_get_clip_rule(state: *const RsvgState) -> cairo::FillRule;
    fn rsvg_state_get_fill(state: *const RsvgState) -> *const PaintServer;
    fn rsvg_state_get_fill_opacity(state: *const RsvgState) -> u8;
    fn rsvg_state_get_fill_rule(state: *const RsvgState) -> cairo::FillRule;

    fn rsvg_state_get_state_rust(state: *const RsvgState) -> *mut State;
}

pub fn new() -> *mut RsvgState {
    unsafe { rsvg_state_new() }
}

pub fn free(state: *mut RsvgState) {
    unsafe {
        rsvg_state_free(state);
    }
}

pub fn reinit(state: *mut RsvgState) {
    unsafe {
        rsvg_state_reinit(state);
    }
}

pub fn reconstruct(state: *mut RsvgState, node: *const RsvgNode) {
    unsafe {
        rsvg_state_reconstruct(state, node);
    }
}

pub fn get_affine(state: *const RsvgState) -> cairo::Matrix {
    unsafe { rsvg_state_get_affine(state) }
}

pub fn is_overflow(state: *const RsvgState) -> bool {
    unsafe { from_glib(rsvg_state_is_overflow(state)) }
}

pub fn has_overflow(state: *const RsvgState) -> bool {
    unsafe { from_glib(rsvg_state_has_overflow(state)) }
}

pub fn get_cond_true(state: *const RsvgState) -> bool {
    unsafe { from_glib(rsvg_state_get_cond_true(state)) }
}

pub fn set_cond_true(state: *const RsvgState, cond_true: bool) {
    unsafe {
        rsvg_state_set_cond_true(state, cond_true.to_glib());
    }
}

pub fn get_stop_color(state: *const RsvgState) -> Result<Option<Color>, AttributeError> {
    unsafe {
        let spec_ptr = rsvg_state_get_stop_color(state);

        if spec_ptr.is_null() {
            Ok(None)
        } else {
            Color::from_color_spec(&*spec_ptr).map(Some)
        }
    }
}

pub fn get_stop_opacity(state: *const RsvgState) -> Result<Option<Opacity>, AttributeError> {
    unsafe {
        let opacity_ptr = rsvg_state_get_stop_opacity(state);

        if opacity_ptr.is_null() {
            Ok(None)
        } else {
            Opacity::from_opacity_spec(&*opacity_ptr).map(Some)
        }
    }
}

pub fn get_stroke_dasharray<'a>(state: *const RsvgState) -> Option<&'a StrokeDasharray> {
    let dash = unsafe { rsvg_state_get_stroke_dasharray(state) };

    if dash.is_null() {
        None
    } else {
        unsafe { Some(&*dash) }
    }
}

pub fn get_dash_offset(state: *const RsvgState) -> RsvgLength {
    unsafe { rsvg_state_get_dash_offset(state) }
}

pub fn get_current_color(state: *const RsvgState) -> Color {
    let argb = unsafe { rsvg_state_get_current_color(state) };

    Color::from(argb)
}

pub fn get_shape_rendering_type(state: *const RsvgState) -> cairo::Antialias {
    unsafe { rsvg_state_get_shape_rendering_type(state) }
}

pub fn get_text_rendering_type(state: *const RsvgState) -> cairo::Antialias {
    unsafe { rsvg_state_get_text_rendering_type(state) }
}

pub fn get_stroke<'a>(state: *const RsvgState) -> Option<&'a PaintServer> {
    unsafe {
        let ps = rsvg_state_get_stroke(state);

        if ps.is_null() {
            None
        } else {
            Some(&*ps)
        }
    }
}

pub fn get_stroke_opacity(state: *const RsvgState) -> u8 {
    unsafe { rsvg_state_get_stroke_opacity(state) }
}

pub fn get_stroke_width(state: *const RsvgState) -> RsvgLength {
    unsafe { rsvg_state_get_stroke_width(state) }
}

pub fn get_miter_limit(state: *const RsvgState) -> f64 {
    unsafe { rsvg_state_get_miter_limit(state) }
}

pub fn get_line_cap(state: *const RsvgState) -> cairo::LineCap {
    unsafe { rsvg_state_get_line_cap(state) }
}

pub fn get_line_join(state: *const RsvgState) -> cairo::LineJoin {
    unsafe { rsvg_state_get_line_join(state) }
}

pub fn get_language(state: *const RsvgState) -> Option<String> {
    unsafe { from_glib_none(rsvg_state_get_language(state)) }
}

pub fn get_unicode_bidi(state: *const RsvgState) -> UnicodeBidi {
    unsafe { rsvg_state_get_unicode_bidi(state) }
}

pub fn get_text_dir(state: *const RsvgState) -> pango::Direction {
    unsafe { from_glib(rsvg_state_get_text_dir(state)) }
}

pub fn get_text_gravity(state: *const RsvgState) -> pango::Gravity {
    unsafe { from_glib(rsvg_state_get_text_gravity(state)) }
}

pub fn get_font_family(state: *const RsvgState) -> Option<String> {
    unsafe { from_glib_none(rsvg_state_get_font_family(state)) }
}

pub fn get_font_style(state: *const RsvgState) -> pango::Style {
    unsafe { from_glib(rsvg_state_get_font_style(state)) }
}

pub fn get_font_variant(state: *const RsvgState) -> pango::Variant {
    unsafe { from_glib(rsvg_state_get_font_variant(state)) }
}

pub fn get_font_weight(state: *const RsvgState) -> pango::Weight {
    unsafe { from_glib(rsvg_state_get_font_weight(state)) }
}

pub fn get_font_stretch(state: *const RsvgState) -> pango::Stretch {
    unsafe { from_glib(rsvg_state_get_font_stretch(state)) }
}

pub fn get_letter_spacing(state: *const RsvgState) -> RsvgLength {
    unsafe { rsvg_state_get_letter_spacing(state) }
}

pub fn get_font_decor(state: *const RsvgState) -> Option<FontDecor> {
    unsafe {
        let td = rsvg_state_get_font_decor(state);
        if td.is_null() {
            None
        } else {
            Some(FontDecor::from(*td))
        }
    }
}

pub fn get_clip_rule(state: *const RsvgState) -> cairo::FillRule {
    unsafe { rsvg_state_get_clip_rule(state) }
}

pub fn get_fill<'a>(state: *const RsvgState) -> Option<&'a PaintServer> {
    unsafe {
        let ps = rsvg_state_get_fill(state);

        if ps.is_null() {
            None
        } else {
            Some(&*ps)
        }
    }
}

pub fn get_fill_opacity(state: *const RsvgState) -> u8 {
    unsafe { rsvg_state_get_fill_opacity(state) }
}

pub fn get_fill_rule(state: *const RsvgState) -> cairo::FillRule {
    unsafe { rsvg_state_get_fill_rule(state) }
}

pub fn get_state_rust<'a>(state: *const RsvgState) -> &'a mut State {
    unsafe { &mut *rsvg_state_get_state_rust(state) }
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub enum StrokeLinejoin {
    Miter,
    Round,
    Bevel,
    Inherit,
}

#[repr(C)]
pub struct StrokeLinejoinResult {
    valid: glib_sys::gboolean,
    linejoin: StrokeLinejoin,
}

impl Default for StrokeLinejoin {
    fn default() -> StrokeLinejoin {
        StrokeLinejoin::Miter
    }
}

impl Parse for StrokeLinejoin {
    type Data = ();
    type Err = AttributeError;

    fn parse(s: &str, _: Self::Data) -> Result<StrokeLinejoin, AttributeError> {
        match s.trim() {
            "miter" => Ok(StrokeLinejoin::Miter),
            "round" => Ok(StrokeLinejoin::Round),
            "bevel" => Ok(StrokeLinejoin::Bevel),
            "inherit" => Ok(StrokeLinejoin::Inherit),
            _ => Err(AttributeError::from(ParseError::new("expected miter|round|bevel|inherit"))),
        }
    }
}

impl From<Result<StrokeLinejoin, AttributeError>> for StrokeLinejoinResult {
    fn from(v: Result<StrokeLinejoin, AttributeError>) -> StrokeLinejoinResult {
        match v {
            Ok(j) => StrokeLinejoinResult {
                valid: true.to_glib(),
                linejoin: j,
            },

            Err(_) => StrokeLinejoinResult {
                valid: false.to_glib(),
                linejoin: StrokeLinejoin::default(),
            },
        }
    }
}

#[no_mangle]
pub extern "C" fn rsvg_stroke_linejoin_get_default() -> StrokeLinejoin {
    StrokeLinejoin::default()
}

#[no_mangle]
pub extern "C" fn rsvg_stroke_linejoin_parse(s: *const libc::c_char) -> StrokeLinejoinResult {
    let s = unsafe { utf8_cstr(s) };

    StrokeLinejoinResult::from(StrokeLinejoin::parse(s, ()))
}

#[no_mangle]
pub extern "C" fn rsvg_state_rust_new() -> *mut State {
    Box::into_raw(Box::new(State::default()))
}

#[no_mangle]
pub extern "C" fn rsvg_state_rust_free(state: *mut State) {
    assert!(!state.is_null());

    unsafe { Box::from_raw(state); }
}

#[no_mangle]
pub extern "C" fn rsvg_state_rust_clone(state: *const State) -> *mut State {
    assert!(!state.is_null());

    unsafe { Box::into_raw(Box::new((*state).clone())) }
}
<|endoftext|>


<commit_before>use std::sync::Arc;

use std::thread;
use std::sync::atomic::{AtomicUsize, Ordering};

use {Cursive, Printer};
use align::HAlign;
use theme::{ColorStyle, Effect};
use view::View;

pub type CbPromise = Option<Box<Fn(&mut Cursive) + Send>>;

/// Display progress.
pub struct ProgressBar {
    min: usize,
    max: usize,
    value: Arc<AtomicUsize>,
    // TODO: use a Promise instead?
    label_maker: Box<Fn(usize, (usize, usize)) -> String>,
}

pub type Ticker = Box<Fn(usize) + Send>;

fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
    let percent = 101 * (value - min) / (1 + max - min);
    format!("{} %", percent)
}

new_default!(ProgressBar);

impl ProgressBar {
    /// Creates a new progress bar.
    ///
    /// Default values:
    ///
    /// * `min`: 0
    /// * `max`: 100
    /// * `value`: 0
    pub fn new() -> Self {
        ProgressBar {
            min: 0,
            max: 100,
            value: Arc::new(AtomicUsize::new(0)),
            label_maker: Box::new(make_percentage),
        }
    }

    /// Sets the value to follow.
    ///
    /// Use this to manually control the progress to display
    /// by directly modifying the value pointed to by `value`.
    pub fn with_value(mut self, value: Arc<AtomicUsize>) -> Self {
        self.value = value;
        self
    }

    /// Starts a function in a separate thread, and monitor the progress.
    ///
    /// `f` will be given a `Ticker` to increment the bar's progress.
    pub fn start<F: FnOnce(Ticker) + Send + 'static>(&mut self, f: F) {
        let value = self.value.clone();
        let ticker: Ticker = Box::new(move |ticks| {
            value.fetch_add(ticks, Ordering::Relaxed);
        });

        thread::spawn(move || {
            f(ticker);
        });
    }

    /// Starts a function in a separate thread, and monitor the progress.
    ///
    /// Chainable variant.
    pub fn with_task<F: FnOnce(Ticker) + Send + 'static>(mut self, task: F) -> Self {
        self.start(task);
        self
    }

    /// Sets the label generator.
    ///
    /// The given function will be called with `(value, (min, max))`.
    /// Its output will be used as the label to print inside the progress bar.
    ///
    /// The default one shows a percentage progress:
    ///
    /// ```
    /// fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
    ///     let percent = 101 * (value - min) / (1 + max - min);
    ///     format!("{} %", percent)
    /// }
    /// ```
    pub fn with_label<F: Fn(usize, (usize, usize)) -> String + 'static>
        (mut self, label_maker: F)
         -> Self {
        self.label_maker = Box::new(label_maker);
        self
    }

    /// Sets the minimum value.
    ///
    /// When `value` equals `min`, the bar is at the minimum level.
    pub fn min(mut self, min: usize) -> Self {
        self.min = min;
        self
    }

    /// Sets the maximum value.
    ///
    /// When `value` equals `max`, the bar is at the maximum level.
    pub fn max(mut self, max: usize) -> Self {
        self.max = max;
        self
    }

    /// Sets the `min` and `max` range for the value.
    pub fn range(self, min: usize, max: usize) -> Self {
        self.min(min).max(max)
    }

    /// Sets the current value.
    ///
    /// Value is clamped between `min` and `max`.
    pub fn set_value(&mut self, value: usize) {
        self.value.store(value, Ordering::Relaxed);
    }
}

impl View for ProgressBar {
    fn draw(&self, printer: &Printer) {
        // Now, the bar itself...
        let available = printer.size.x;

        let value = self.value.load(Ordering::Relaxed);
        let length = ((1 + available) * (value - self.min)) /
                     (1 + self.max - self.min);

        let label = (self.label_maker)(value, (self.min, self.max));
        let offset = HAlign::Center.get_offset(label.len(), printer.size.x);

        printer.with_color(ColorStyle::Highlight, |printer| {
            printer.with_effect(Effect::Reverse, |printer| {
                printer.print((offset, 0), &label);
            });
            let printer = &printer.sub_printer((0, 0), (length, 1), true);
            printer.print_hline((0, 0), length, " ");
            printer.print((offset, 0), &label);
        });
    }
}
<commit_msg>Add comments to progress bar<commit_after>use std::sync::Arc;

use std::thread;
use std::sync::atomic::{AtomicUsize, Ordering};

use {Cursive, Printer};
use align::HAlign;
use theme::{ColorStyle, Effect};
use view::View;

pub type CbPromise = Option<Box<Fn(&mut Cursive) + Send>>;

/// Animated bar showing a progress value.
///
/// This bar has an internal counter, and adapts the length of the displayed
/// bar to the relative position of the counter between a minimum and maximum
/// values.
///
/// It also prints a customizable text in the center of the bar, which
/// defaults to the progression percentage.
///
/// # Example
///
/// ```
/// # use cursive::prelude::*;
/// let bar = ProgressBar::new()
///                       .with_task(|ticker| {
///                           // This closure is called in parallel.
///                           for _ in 0..100 {
///                               // Here we can communicate some
///                               // advancement back to the bar.
///                               ticker(1);
///                           }
///                       });
/// ```
pub struct ProgressBar {
    min: usize,
    max: usize,
    value: Arc<AtomicUsize>,
    // TODO: use a Promise instead?
    label_maker: Box<Fn(usize, (usize, usize)) -> String>,
}

pub type Ticker = Box<Fn(usize) + Send>;

fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
    let percent = 101 * (value - min) / (1 + max - min);
    format!("{} %", percent)
}

new_default!(ProgressBar);

impl ProgressBar {
    /// Creates a new progress bar.
    ///
    /// Default values:
    ///
    /// * `min`: 0
    /// * `max`: 100
    /// * `value`: 0
    pub fn new() -> Self {
        ProgressBar {
            min: 0,
            max: 100,
            value: Arc::new(AtomicUsize::new(0)),
            label_maker: Box::new(make_percentage),
        }
    }

    /// Sets the value to follow.
    ///
    /// Use this to manually control the progress to display
    /// by directly modifying the value pointed to by `value`.
    pub fn with_value(mut self, value: Arc<AtomicUsize>) -> Self {
        self.value = value;
        self
    }

    /// Starts a function in a separate thread, and monitor the progress.
    ///
    /// `f` will be given a `Ticker` to increment the bar's progress.
    ///
    /// This does not reset the value, so it can be called several times
    /// to advance the progress in multiple sessions.
    pub fn start<F: FnOnce(Ticker) + Send + 'static>(&mut self, f: F) {
        let value = self.value.clone();
        let ticker: Ticker = Box::new(move |ticks| {
            value.fetch_add(ticks, Ordering::Relaxed);
        });

        thread::spawn(move || {
            f(ticker);
        });
    }

    /// Starts a function in a separate thread, and monitor the progress.
    ///
    /// Chainable variant.
    pub fn with_task<F: FnOnce(Ticker) + Send + 'static>(mut self, task: F)
                                                         -> Self {
        self.start(task);
        self
    }

    /// Sets the label generator.
    ///
    /// The given function will be called with `(value, (min, max))`.
    /// Its output will be used as the label to print inside the progress bar.
    ///
    /// The default one shows a percentage progress:
    ///
    /// ```
    /// fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
    ///     let percent = 101 * (value - min) / (1 + max - min);
    ///     format!("{} %", percent)
    /// }
    /// ```
    pub fn with_label<F: Fn(usize, (usize, usize)) -> String + 'static>
        (mut self, label_maker: F)
         -> Self {
        self.label_maker = Box::new(label_maker);
        self
    }

    /// Sets the minimum value.
    ///
    /// When `value` equals `min`, the bar is at the minimum level.
    pub fn min(mut self, min: usize) -> Self {
        self.min = min;
        self
    }

    /// Sets the maximum value.
    ///
    /// When `value` equals `max`, the bar is at the maximum level.
    pub fn max(mut self, max: usize) -> Self {
        self.max = max;
        self
    }

    /// Sets the `min` and `max` range for the value.
    pub fn range(self, min: usize, max: usize) -> Self {
        self.min(min).max(max)
    }

    /// Sets the current value.
    ///
    /// Value is clamped between `min` and `max`.
    pub fn set_value(&mut self, value: usize) {
        self.value.store(value, Ordering::Relaxed);
    }
}

impl View for ProgressBar {
    fn draw(&self, printer: &Printer) {
        // Now, the bar itself...
        let available = printer.size.x;

        let value = self.value.load(Ordering::Relaxed);
        let length = ((1 + available) * (value - self.min)) /
                     (1 + self.max - self.min);

        let label = (self.label_maker)(value, (self.min, self.max));
        let offset = HAlign::Center.get_offset(label.len(), printer.size.x);

        printer.with_color(ColorStyle::Highlight, |printer| {
            printer.with_effect(Effect::Reverse, |printer| {
                printer.print((offset, 0), &label);
            });
            let printer = &printer.sub_printer((0, 0), (length, 1), true);
            printer.print_hline((0, 0), length, " ");
            printer.print((offset, 0), &label);
        });
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2016, Matthew O'Connor
extern crate system_zero_core;

use std::io;
use std::io::Write;
use system_zero_core::*;
use system_zero_core::ast::*;

const PROMPT: &'static str = "> ";

fn repl() -> io::Result<()> {
  loop {
    let mut input = String::new();
    print!("{}", PROMPT);
    try!(io::stdout().flush());
    match io::stdin().read_line(&mut input) {
      Ok(_) => {
        let line: &str = &input[..];
        match parse_one(line) {
          Ok(one) => print!("{:?}", one.normalize()),
          Err(error) => println!("Couldn't parse: {:?}", error),
        }
      }
      Err(error) => println!("Couldn't read: {}", error),
    }
  }
}

fn main() {
  println!("szi - system zero interpreter");
  println!("Copyright (C) 2016 Matthew O'Connor <thegreendragon@gmail.com>");
  std::process::exit(repl().is_ok() as i32)
}
<commit_msg>Update copyright in start message on szi.<commit_after>// Copyright (c) 2016, Matthew O'Connor
extern crate system_zero_core;

use std::io;
use std::io::Write;
use system_zero_core::*;
use system_zero_core::ast::*;

const PROMPT: &'static str = "> ";

fn repl() -> io::Result<()> {
  loop {
    let mut input = String::new();
    print!("{}", PROMPT);
    try!(io::stdout().flush());
    match io::stdin().read_line(&mut input) {
      Ok(_) => {
        let line: &str = &input[..];
        match parse_one(line) {
          Ok(one) => print!("{:?}", one.normalize()),
          Err(error) => println!("Couldn't parse: {:?}", error),
        }
      }
      Err(error) => println!("Couldn't read: {}", error),
    }
  }
}

fn main() {
  println!("szi - system zero interpreter");
  println!("Copyright (c) 2016, Matthew O'Connor <thegreendragon@gmail.com>");
  std::process::exit(repl().is_ok() as i32)
}
<|endoftext|>


<commit_before>use std::hash::Hash;
use std::fmt::Debug;
use std::marker::PhantomData;

use perlin_core::index::posting::{Posting, PostingIterator, PostingDecoder};
use perlin_core::utils::seeking_iterator::{PeekableSeekable, SeekingIterator};
use perlin_core::utils::progress::Progress;

use language::CanApply;
use query::{Weight, ToOperands, Operand};
use field::{Field, Fields};

/// Mimics the functionality of the `try!` macro for `Option`s.
/// Evaluates `Some(x)` to x. Else it returns `None`.
macro_rules! try_option{
    ($operand:expr) => {
        if let Some(x) = $operand {
            x
        } else {
            return None;
        }
    }
}

#[derive(Debug)]
pub enum Combinator {
    All,
    Any,
}

/// This funnel is used at an end of a query pipeline
/// It calls `index.query_atom` and stores the result, which is lazy
/// When `to_operand` is then called, it packs everything into an operator!
pub struct Funnel<'a, T: 'a, TIndex: 'a> {
    index: &'a TIndex,
    result: Vec<PeekableSeekable<Operand<'a>>>,
    _term: PhantomData<T>,
}

impl<'a, T: 'a, TIndex: 'a> Funnel<'a, T, TIndex> {
    pub fn create(index: &'a TIndex) -> Self {
        Funnel {
            index,
            result: Vec::new(),
            _term: PhantomData,
        }
    }

    fn add_posting_list(&mut self,
                        weight: Weight,
                        decoder: PostingDecoder<'a>,
                        term: String,
                        field: String) {
        if weight.0 > 0. {
            self.result.push(PeekableSeekable::new(Operand::Term(weight, decoder, term, field)));
        }
    }
}

impl<'a: 'b, 'b, T: 'a + Hash + Eq + Ord + Debug + ToString> CanApply<&'b T>
    for Funnel<'a, T, Fields<T>> {
    type Output = T;

    fn apply(&mut self, term: &'b T) {
        for (key, index) in self.index.fields.iter() {
            let w = 1./index.term_doc_ratio;
            match index.query_atom(&term) {
                (idf, PostingIterator::Decoder(decoder)) => {
                    self.add_posting_list(Weight(idf.0 * w),
                                          decoder,
                                          term.to_string(),
                                          key.clone());
                }
                _ => {}
            }
        }
    }
}

impl<'a, T: 'a + Hash + Eq + Ord + Debug + ToString> CanApply<T> for Funnel<'a, T, Fields<T>> {
    type Output = T;

    fn apply(&mut self, term: T) {
        for (key, index) in self.index.fields.iter() {
            let w = 1./index.term_doc_ratio;
            match index.query_atom(&term) {
                (idf, PostingIterator::Decoder(decoder)) => {
                    self.add_posting_list(Weight(idf.0 * w),
                                          decoder,
                                          term.to_string(),
                                          key.clone());
                }
                _ => {}
            }
        }
    }
}

impl<'a: 'b, 'b, T: 'a + Hash + Eq + Ord + Debug + ToString> CanApply<&'b T>
    for Funnel<'a, T, Field<T>> {
    type Output = T;

    fn apply(&mut self, term: &'b T) {
        let w = 1./self.index.term_doc_ratio;
        match self.index.query_atom(&term) {
            (idf, PostingIterator::Decoder(decoder)) => {
                self.add_posting_list(Weight(idf.0 * w),
                                      decoder,
                                      term.to_string(),
                                      self.index.name.clone());
            }
            _ => {}
        }
    }
}

impl<'a, T: 'a + Hash + Eq + Ord + Debug + ToString> CanApply<T> for Funnel<'a, T, Field<T>> {
    type Output = T;

    fn apply(&mut self, term: T) {
        let w = 1./self.index.term_doc_ratio;
        match self.index.query_atom(&term) {
            (idf, PostingIterator::Decoder(decoder)) => {
                self.add_posting_list(Weight(idf.0 * w),
                                      decoder,
                                      term.to_string(),
                                      self.index.name.clone());
            }
            _ => {}
        }
    }
}

impl<'a, T: 'a, TIndex> ToOperands<'a> for Funnel<'a, T, TIndex> {
    fn to_operands(self) -> Vec<PeekableSeekable<Operand<'a>>> {
        self.result
    }
}

/// END FUNNEL
#[derive(Debug, Copy, Clone)]
pub struct And;

impl And {
    pub fn next(operands: &mut [PeekableSeekable<Operand>]) -> Option<Posting> {
        let mut focus = try_option!(operands[0].next()); // Acts as temporary to be compared against
        let mut last_iter = 0; // The iterator that last set 'focus'
        'possible_documents: loop {
            // For every term
            for (i, input) in operands.iter_mut().enumerate() {
                // If the focus was set by the current iterator, we have a match
                // We cycled through all the iterators once
                if last_iter == i {
                    continue;
                }

                let v = try_option!(input.next_seek(&focus));
                if v.0 > focus.0 {
                    // If it is larger, we are now looking at a different focus.
                    // Reset focus and last_iter. Then start from the beginning
                    focus = v;
                    last_iter = i;
                    continue 'possible_documents;
                }
            }
            return Some(focus);
        }
    }

    pub fn next_seek(operands: &mut [PeekableSeekable<Operand>],
                     target: &Posting)
                     -> Option<Posting> {
        // Advance operands to `target`
        for op in operands.iter_mut() {
            op.peek_seek(target);
        }
        Self::next(operands)
    }

    pub fn progress(operands: &[PeekableSeekable<Operand>]) -> Progress {
        operands.iter()
            .map(|op| op.inner().progress())
            .max()
            .unwrap_or(Progress::done())
    }
}
<commit_msg>fixed bug that caused empty query to panic<commit_after>use std::hash::Hash;
use std::fmt::Debug;
use std::marker::PhantomData;

use perlin_core::index::posting::{Posting, PostingIterator, PostingDecoder};
use perlin_core::utils::seeking_iterator::{PeekableSeekable, SeekingIterator};
use perlin_core::utils::progress::Progress;

use language::CanApply;
use query::{Weight, ToOperands, Operand};
use field::{Field, Fields};

#[derive(Debug)]
pub enum Combinator {
    All,
    Any,
}

/// This funnel is used at an end of a query pipeline
/// It calls `index.query_atom` and stores the result, which is lazy
/// When `to_operand` is then called, it packs everything into an operator!
pub struct Funnel<'a, T: 'a, TIndex: 'a> {
    index: &'a TIndex,
    result: Vec<PeekableSeekable<Operand<'a>>>,
    _term: PhantomData<T>,
}

impl<'a, T: 'a, TIndex: 'a> Funnel<'a, T, TIndex> {
    pub fn create(index: &'a TIndex) -> Self {
        Funnel {
            index,
            result: Vec::new(),
            _term: PhantomData,
        }
    }

    fn add_posting_list(&mut self,
                        weight: Weight,
                        decoder: PostingDecoder<'a>,
                        term: String,
                        field: String) {
        if weight.0 > 0. {
            self.result.push(PeekableSeekable::new(Operand::Term(weight, decoder, term, field)));
        }
    }
}

impl<'a: 'b, 'b, T: 'a + Hash + Eq + Ord + Debug + ToString> CanApply<&'b T>
    for Funnel<'a, T, Fields<T>> {
    type Output = T;

    fn apply(&mut self, term: &'b T) {
        for (key, index) in self.index.fields.iter() {
            let w = 1./index.term_doc_ratio;
            match index.query_atom(&term) {
                (idf, PostingIterator::Decoder(decoder)) => {
                    self.add_posting_list(Weight(idf.0 * w),
                                          decoder,
                                          term.to_string(),
                                          key.clone());
                }
                _ => {}
            }
        }
    }
}

impl<'a, T: 'a + Hash + Eq + Ord + Debug + ToString> CanApply<T> for Funnel<'a, T, Fields<T>> {
    type Output = T;

    fn apply(&mut self, term: T) {
        for (key, index) in self.index.fields.iter() {
            let w = 1./index.term_doc_ratio;
            match index.query_atom(&term) {
                (idf, PostingIterator::Decoder(decoder)) => {
                    self.add_posting_list(Weight(idf.0 * w),
                                          decoder,
                                          term.to_string(),
                                          key.clone());
                }
                _ => {}
            }
        }
    }
}

impl<'a: 'b, 'b, T: 'a + Hash + Eq + Ord + Debug + ToString> CanApply<&'b T>
    for Funnel<'a, T, Field<T>> {
    type Output = T;

    fn apply(&mut self, term: &'b T) {
        let w = 1./self.index.term_doc_ratio;
        match self.index.query_atom(&term) {
            (idf, PostingIterator::Decoder(decoder)) => {
                self.add_posting_list(Weight(idf.0 * w),
                                      decoder,
                                      term.to_string(),
                                      self.index.name.clone());
            }
            _ => {}
        }
    }
}

impl<'a, T: 'a + Hash + Eq + Ord + Debug + ToString> CanApply<T> for Funnel<'a, T, Field<T>> {
    type Output = T;

    fn apply(&mut self, term: T) {
        let w = 1./self.index.term_doc_ratio;
        match self.index.query_atom(&term) {
            (idf, PostingIterator::Decoder(decoder)) => {
                self.add_posting_list(Weight(idf.0 * w),
                                      decoder,
                                      term.to_string(),
                                      self.index.name.clone());
            }
            _ => {}
        }
    }
}

impl<'a, T: 'a, TIndex> ToOperands<'a> for Funnel<'a, T, TIndex> {
    fn to_operands(self) -> Vec<PeekableSeekable<Operand<'a>>> {
        self.result
    }
}

/// END FUNNEL
#[derive(Debug, Copy, Clone)]
pub struct And;

impl And {
    pub fn next(operands: &mut [PeekableSeekable<Operand>]) -> Option<Posting> {
        if operands.is_empty() {
            return None;
        }
        let mut focus = operands[0].next()?; // Acts as temporary to be compared against
        let mut last_iter = 0; // The iterator that last set 'focus'
        'possible_documents: loop {
            // For every term
            for (i, input) in operands.iter_mut().enumerate() {
                // If the focus was set by the current iterator, we have a match
                // We cycled through all the iterators once
                if last_iter == i {
                    continue;
                }

                let v = input.next_seek(&focus)?;
                if v.0 > focus.0 {
                    // If it is larger, we are now looking at a different focus.
                    // Reset focus and last_iter. Then start from the beginning
                    focus = v;
                    last_iter = i;
                    continue 'possible_documents;
                }
            }
            return Some(focus);
        }
    }

    pub fn next_seek(operands: &mut [PeekableSeekable<Operand>],
                     target: &Posting)
                     -> Option<Posting> {
        // Advance operands to `target`
        for op in operands.iter_mut() {
            op.peek_seek(target);
        }
        Self::next(operands)
    }

    pub fn progress(operands: &[PeekableSeekable<Operand>]) -> Progress {
        operands.iter()
            .map(|op| op.inner().progress())
            .max()
            .unwrap_or(Progress::done())
    }
}
<|endoftext|>


<commit_before>use webcore::value::{Value, Reference, ConversionError};
use webcore::try_from::{TryFrom, TryInto};
use webcore::unsafe_typed_array::UnsafeTypedArray;
use webapi::event_target::{IEventTarget, EventTarget};
use webapi::blob::Blob;
use webapi::array_buffer::ArrayBuffer;

use std::error;
use std::fmt;

/// Wrapper type around a CloseEvent code, indicating why the WebSocket was closed
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SocketCloseCode(pub u16);

// Close codes are defined here:
// https://tools.ietf.org/html/rfc6455#section-7.4
newtype_enum!(SocketCloseCode {
    /// Normal closure; the connection successfully completed whatever purpose for which it was
    /// created.
    NORMAL_CLOSURE = 1000,
    /// The endpoint is going away, either because of a server failure or because the browser is
    /// navigating away from the page that opened the connection.
    GOING_AWAY = 1001,
    /// The endpoint is terminating the connection due to a protocol error.
    PROTOCOL_ERROR = 1002,
    /// The connection is being terminated because the endpoint received data of a type it cannot
    /// accept (for example, a text-only endpoint received binary data).
    UNSUPPORTED_DATA = 1003,
    /// Reserved. Indicates that no status code was provided even though one was expected.
    NO_STATUS_RECEIVED = 1005,
    /// Reserved. Used to indicate that a connection was closed abnormally (that is, with no close
    /// frame being sent) when a status code is expected.
    ABNORMAL_CLOSURE = 1006,
    /// The endpoint is terminating the connection because a message was received that contained
    /// inconsistent data (e.g., non-UTF-8 data within a text message).
    INVALID_FRAME_PAYLOAD_DATA = 1007,
    /// The endpoint is terminating the connection because it received a message that violates its
    /// policy. This is a generic status code, used when codes 1003 and 1009 are not suitable.
    POLICY_VIOLATION = 1008,
    /// The endpoint is terminating the connection because a data frame was received that is too
    /// large.
    MESSAGE_TOO_BIG = 1009,
    /// The client is terminating the connection because it expected the server to negotiate one or
    /// more extensions, but the server didn't.
    MISSING_EXTENSION = 1010,
    /// The server is terminating the connection because it encountered an unexpected condition
    /// that prevented it from fulfilling the request.
    INTERNAL_ERROR = 1011,
    /// The server is terminating the connection because it is restarting.
    SERVICE_RESTART = 1012,
    /// The server is terminating the connection due to a temporary condition, e.g. it is
    /// overloaded and is casting off some of its clients.
    TRY_AGAIN_LATER = 1013,
    /// The server was acting as a gateway or proxy and received an invalid response from the
    /// upstream server. This is similar to 502 HTTP Status Code.
    BAD_GATEWAY = 1014,
    /// Reserved. Indicates that the connection was closed due to a failure to perform a TLS
    /// handshake (e.g., the server certificate can't be verified).
    TLS_HANDSHAKE = 1015,
});

/// The WebSocket object provides the API for creating and managing a WebSocket connection to a
/// server, as well as for sending and receiving data on the connection.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
pub struct WebSocket( Reference );

// WebSocket specification:
// https://html.spec.whatwg.org/multipage/web-sockets.html#the-websocket-interface

impl IEventTarget for WebSocket {}

reference_boilerplate! {
    WebSocket,
    instanceof WebSocket
    convertible to EventTarget
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BinaryType {
    Blob,
    ArrayBuffer
}

impl BinaryType {
    fn to_str(self) -> &'static str {
        match self {
            BinaryType::Blob => "blob",
            BinaryType::ArrayBuffer => "arraybuffer",
        }
    }
    fn from_str(s: &str) -> Self {
        match s {
            "blob" => BinaryType::Blob,
            "arraybuffer" => BinaryType::ArrayBuffer,
            other => panic!("Invalid binary type: {:?}", other)
        }
    }
}

/// A number indicating the state of the `WebSocket`.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants)
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SocketReadyState {
    Connecting = 0,
    Open = 1,
    Closing = 2,
    Closed = 3
}

impl TryFrom<Value> for SocketReadyState {
    type Error = ConversionError;

    /// Performs the conversion.
    fn try_from(v: Value) -> Result<SocketReadyState, ConversionError> {
        match v.try_into()? {
            0 => Ok(SocketReadyState::Connecting),
            1 => Ok(SocketReadyState::Open),
            2 => Ok(SocketReadyState::Closing),
            3 => Ok(SocketReadyState::Closed),
            other => Err(ConversionError::Custom(format!("Unknown ready_state: {}", other)))
        }
    }
}

/// A DOMException
#[derive(Debug)]
pub struct DomException;
impl error::Error for DomException {
    fn description( &self ) -> &str {
        "DomException"
    }
}

impl fmt::Display for DomException {
    fn fmt( &self, formatter: &mut fmt::Formatter ) -> fmt::Result {
        write!( formatter, "DomException" )
    }
}

impl WebSocket {
    /// Returns a newly constructed `WebSocket`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn new(url: &str) -> Result<WebSocket, DomException> {
        js!(
            try {
                return new WebSocket(@{url});
            } catch (error) {
                if (error instanceof DOMException) {
                    return null;
                }
                throw error;
            }
        ).try_into().map_err(|_| DomException)
    }

    /// Returns a newly constructed `WebSocket`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn new_with_protocols(url: &str, protocols: &[&str]) -> Result<WebSocket, DomException> {
        js!(
            try {
                return new WebSocket(@{url}, @{protocols});
            } catch (error) {
                if (error instanceof DOMException) {
                    return null;
                }
                throw error;
            }
        ).try_into().map_err(|_| DomException)
    }

    /// Returns the binary type of the web socket. Only affects received messages.
    /// The default binary type is `Blob`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn binary_type(&self) -> BinaryType {
        let binary_type: String = js!( return @{self}.binaryType; ).try_into().unwrap();
        BinaryType::from_str(&binary_type)
    }

    /// Sets the binary type of the web socket. Only affects received messages.
    /// The default binary type is `Blob`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn set_binary_type(&self, binary_type: BinaryType) {
        js!( @(no_return) @{self}.binaryType = @{binary_type.to_str()}; );
    }

    /// Returns the number of bytes of data that have been queued using calls to send()
    /// but not yet transmitted to the network. This value resets to zero once all queued
    /// data has been sent. This value does not reset to zero when the connection is closed;
    /// if you keep calling send(), this will continue to climb.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn buffered_amount(&self) -> u64 {
        js!( return @{self}.bufferedAmount; ).try_into().unwrap()
    }

    /// Returns the extensions selected by the server. This is currently only the empty
    /// string or a list of extensions as negotiated by the connection.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn extensions(&self) -> String {
        js!( return @{self}.extensions; ).try_into().unwrap()
    }

    /// Returns a string indicating the name of the sub-protocol the server selected;
    /// this will be one of the strings specified in the protocols parameter when
    /// creating the WebSocket object.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn protocol(&self) -> String {
        js!( return @{self}.protocol; ).try_into().unwrap()
    }

    /// Returns the current state of the connection.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn ready_state(&self) -> SocketReadyState {
        js!( return @{self}.protocol; ).try_into().unwrap()
    }

    /// Returns the URL as resolved by the constructor. This is always an absolute URL.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn url(&self) -> String {
        js!( return @{self}.url; ).try_into().unwrap()
    }

    /// Closes the WebSocket connection or connection attempt, if any. If the connection
    /// is already CLOSED, this method does nothing.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
    pub fn close(&self) {
        js!( @(no_return) @{self}.close(); );
    }

    /// Closes the WebSocket connection or connection attempt, if any. If the connection
    /// is already CLOSED, this method does nothing.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
    pub fn close_with_status(&self, code: SocketCloseCode, reason: &str) -> Result<(), DomException> {
        let code = code.0;
        if js!(
            try {
                @{self}.close(@{code}, @{reason});
                return true;
            } catch (error) {
                if (error instanceof DOMException) {
                    return false;
                }
                throw error;
            }
        ).try_into().unwrap() {
            Ok(())
        } else {
            Err(DomException)
        }
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    pub fn send_text(&self, text: &str) {
        js!( @(no_return) @{self}.send(@{text}); );
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    pub fn send_blob(&self, blob: &Blob) {
        js!( @(no_return) @{self}.send(@{blob}); );
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    pub fn send_array_buffer(&self, array_buffer: &ArrayBuffer) {
        js!( @(no_return) @{self}.send(@{array_buffer}); );
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    pub fn send_bytes(&self, bytes: &[u8]) {
        js!( @(no_return) @{self}.send(@{ UnsafeTypedArray(bytes) }); );
    }
}

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

    #[test]
    fn test_close_codes() {
        assert_eq!(&format!("{:?}", SocketCloseCode::NORMAL_CLOSURE), "SocketCloseCode::NORMAL_CLOSURE");
        assert_eq!(&format!("{:?}", SocketCloseCode::GOING_AWAY), "SocketCloseCode::GOING_AWAY");
        assert_eq!(&format!("{:?}", SocketCloseCode(1000)), "SocketCloseCode::NORMAL_CLOSURE");
        assert_eq!(&format!("{:?}", SocketCloseCode(3001)), "SocketCloseCode(3001)");
    }
}
<commit_msg>Add error handling for WebSocket<commit_after>use webcore::value::{Value, Reference, ConversionError};
use webcore::try_from::{TryFrom, TryInto};
use webcore::unsafe_typed_array::UnsafeTypedArray;
use webapi::event_target::{IEventTarget, EventTarget};
use webapi::blob::Blob;
use webapi::array_buffer::ArrayBuffer;
use webapi::dom_exception::{InvalidAccessError, SecurityError, SyntaxError};

use std::error;
use std::fmt;

/// Wrapper type around a CloseEvent code, indicating why the WebSocket was closed
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SocketCloseCode(pub u16);

// Close codes are defined here:
// https://tools.ietf.org/html/rfc6455#section-7.4
newtype_enum!(SocketCloseCode {
    /// Normal closure; the connection successfully completed whatever purpose for which it was
    /// created.
    NORMAL_CLOSURE = 1000,
    /// The endpoint is going away, either because of a server failure or because the browser is
    /// navigating away from the page that opened the connection.
    GOING_AWAY = 1001,
    /// The endpoint is terminating the connection due to a protocol error.
    PROTOCOL_ERROR = 1002,
    /// The connection is being terminated because the endpoint received data of a type it cannot
    /// accept (for example, a text-only endpoint received binary data).
    UNSUPPORTED_DATA = 1003,
    /// Reserved. Indicates that no status code was provided even though one was expected.
    NO_STATUS_RECEIVED = 1005,
    /// Reserved. Used to indicate that a connection was closed abnormally (that is, with no close
    /// frame being sent) when a status code is expected.
    ABNORMAL_CLOSURE = 1006,
    /// The endpoint is terminating the connection because a message was received that contained
    /// inconsistent data (e.g., non-UTF-8 data within a text message).
    INVALID_FRAME_PAYLOAD_DATA = 1007,
    /// The endpoint is terminating the connection because it received a message that violates its
    /// policy. This is a generic status code, used when codes 1003 and 1009 are not suitable.
    POLICY_VIOLATION = 1008,
    /// The endpoint is terminating the connection because a data frame was received that is too
    /// large.
    MESSAGE_TOO_BIG = 1009,
    /// The client is terminating the connection because it expected the server to negotiate one or
    /// more extensions, but the server didn't.
    MISSING_EXTENSION = 1010,
    /// The server is terminating the connection because it encountered an unexpected condition
    /// that prevented it from fulfilling the request.
    INTERNAL_ERROR = 1011,
    /// The server is terminating the connection because it is restarting.
    SERVICE_RESTART = 1012,
    /// The server is terminating the connection due to a temporary condition, e.g. it is
    /// overloaded and is casting off some of its clients.
    TRY_AGAIN_LATER = 1013,
    /// The server was acting as a gateway or proxy and received an invalid response from the
    /// upstream server. This is similar to 502 HTTP Status Code.
    BAD_GATEWAY = 1014,
    /// Reserved. Indicates that the connection was closed due to a failure to perform a TLS
    /// handshake (e.g., the server certificate can't be verified).
    TLS_HANDSHAKE = 1015,
});

/// The WebSocket object provides the API for creating and managing a WebSocket connection to a
/// server, as well as for sending and receiving data on the connection.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
pub struct WebSocket( Reference );

// WebSocket specification:
// https://html.spec.whatwg.org/multipage/web-sockets.html#the-websocket-interface

impl IEventTarget for WebSocket {}

reference_boilerplate! {
    WebSocket,
    instanceof WebSocket
    convertible to EventTarget
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BinaryType {
    Blob,
    ArrayBuffer
}

impl BinaryType {
    fn to_str(self) -> &'static str {
        match self {
            BinaryType::Blob => "blob",
            BinaryType::ArrayBuffer => "arraybuffer",
        }
    }
    fn from_str(s: &str) -> Self {
        match s {
            "blob" => BinaryType::Blob,
            "arraybuffer" => BinaryType::ArrayBuffer,
            other => panic!("Invalid binary type: {:?}", other)
        }
    }
}

/// A number indicating the state of the `WebSocket`.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants)
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SocketReadyState {
    Connecting = 0,
    Open = 1,
    Closing = 2,
    Closed = 3
}

impl TryFrom<Value> for SocketReadyState {
    type Error = ConversionError;

    /// Performs the conversion.
    fn try_from(v: Value) -> Result<SocketReadyState, ConversionError> {
        match v.try_into()? {
            0 => Ok(SocketReadyState::Connecting),
            1 => Ok(SocketReadyState::Open),
            2 => Ok(SocketReadyState::Closing),
            3 => Ok(SocketReadyState::Closed),
            other => Err(ConversionError::Custom(format!("Unknown ready_state: {}", other)))
        }
    }
}

impl WebSocket {
    /// Returns a newly constructed `WebSocket`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn new(url: &str) -> Result<WebSocket, CreationError> {
        js_try!(
            return new WebSocket(@{url});
        ).unwrap()
    }

    /// Returns a newly constructed `WebSocket`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn new_with_protocols(url: &str, protocols: &[&str]) -> Result<WebSocket, CreationError> {
        js_try!(
            return new WebSocket(@{url}, @{protocols});
        ).unwrap()
    }

    /// Returns the binary type of the web socket. Only affects received messages.
    /// The default binary type is `Blob`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn binary_type(&self) -> BinaryType {
        let binary_type: String = js!( return @{self}.binaryType; ).try_into().unwrap();
        BinaryType::from_str(&binary_type)
    }

    /// Sets the binary type of the web socket. Only affects received messages.
    /// The default binary type is `Blob`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn set_binary_type(&self, binary_type: BinaryType) {
        js!( @(no_return) @{self}.binaryType = @{binary_type.to_str()}; );
    }

    /// Returns the number of bytes of data that have been queued using calls to send()
    /// but not yet transmitted to the network. This value resets to zero once all queued
    /// data has been sent. This value does not reset to zero when the connection is closed;
    /// if you keep calling send(), this will continue to climb.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn buffered_amount(&self) -> u64 {
        js!( return @{self}.bufferedAmount; ).try_into().unwrap()
    }

    /// Returns the extensions selected by the server. This is currently only the empty
    /// string or a list of extensions as negotiated by the connection.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn extensions(&self) -> String {
        js!( return @{self}.extensions; ).try_into().unwrap()
    }

    /// Returns a string indicating the name of the sub-protocol the server selected;
    /// this will be one of the strings specified in the protocols parameter when
    /// creating the WebSocket object.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn protocol(&self) -> String {
        js!( return @{self}.protocol; ).try_into().unwrap()
    }

    /// Returns the current state of the connection.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn ready_state(&self) -> SocketReadyState {
        js!( return @{self}.protocol; ).try_into().unwrap()
    }

    /// Returns the URL as resolved by the constructor. This is always an absolute URL.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    pub fn url(&self) -> String {
        js!( return @{self}.url; ).try_into().unwrap()
    }

    /// Closes the WebSocket connection or connection attempt, if any. If the connection
    /// is already CLOSED, this method does nothing.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
    pub fn close(&self) {
        js!( @(no_return) @{self}.close(); );
    }

    /// Closes the WebSocket connection or connection attempt, if any. If the connection
    /// is already CLOSED, this method does nothing.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
    pub fn close_with_status(&self, code: SocketCloseCode, reason: &str) -> Result<(), CloseError> {
        js_try!( @(no_return)
            @{self}.close(@{code.0}, @{reason});
        ).unwrap()
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    pub fn send_text(&self, text: &str) {
        js!( @(no_return) @{self}.send(@{text}); );
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    pub fn send_blob(&self, blob: &Blob) {
        js!( @(no_return) @{self}.send(@{blob}); );
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    pub fn send_array_buffer(&self, array_buffer: &ArrayBuffer) {
        js!( @(no_return) @{self}.send(@{array_buffer}); );
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    pub fn send_bytes(&self, bytes: &[u8]) {
        js!( @(no_return) @{self}.send(@{ UnsafeTypedArray(bytes) }); );
    }
}

/// Errors thrown by `WebSocket::new`.
error_enum_boilerplate! {
    CreationError,
    SecurityError, SyntaxError
}

/// Errors thrown by `WebSocket::close_with_status`.
error_enum_boilerplate! {
    CloseError,
    InvalidAccessError, SyntaxError
}

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

    #[test]
    fn test_close_codes() {
        assert_eq!(&format!("{:?}", SocketCloseCode::NORMAL_CLOSURE), "SocketCloseCode::NORMAL_CLOSURE");
        assert_eq!(&format!("{:?}", SocketCloseCode::GOING_AWAY), "SocketCloseCode::GOING_AWAY");
        assert_eq!(&format!("{:?}", SocketCloseCode(1000)), "SocketCloseCode::NORMAL_CLOSURE");
        assert_eq!(&format!("{:?}", SocketCloseCode(3001)), "SocketCloseCode(3001)");
    }

    #[test]
    fn test_new() {
        assert!(WebSocket::new("ws://localhost").is_ok());

        match WebSocket::new("bad url") {
            Err(CreationError::SyntaxError(_)) => (),
            v => panic!("expected SyntaxError, got {:?}", v),
        }
    }

    #[test]
    fn test_close() {
        let socket = WebSocket::new("ws://localhost").unwrap();

        socket.close();

        assert!(socket.close_with_status( SocketCloseCode::NORMAL_CLOSURE, "closed" ).is_ok());

        // Invalid close code
        match socket.close_with_status( SocketCloseCode(12345), "closed" ) {
            Err(CloseError::InvalidAccessError(_)) => (),
            v => panic!("expected InvalidAccessError, got {:?}", v),
        }

        // Close reason too long (>123 bytes according to spec)
        match socket.close_with_status(
            SocketCloseCode::NORMAL_CLOSURE,
            &(0..200).map(|_| "X").collect::<String>()
        ) {
            Err(CloseError::SyntaxError(_)) => (),
            v => panic!("expected SyntaxError, got {:?}", v),
        }
    }
}
<|endoftext|>


<commit_before>extern crate bytes;
extern crate rand;
extern crate mio;
extern crate uuid;

use std::collections::BTreeMap;
use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr};
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{self, Sender, Receiver, SendError};

use self::rand::{Rng, thread_rng};
use self::bytes::{Buf, ByteBuf};
use self::mio::Token;
use rasputin::server::{rocksdb, Server, Envelope, State, Peer, InMemoryLog,
                       LEADER_DURATION, PEER_BROADCAST};
use rasputin::{Clock, TestClock, Mutation};
use self::uuid::Uuid;

// NetworkSim facilitates testing a cluster against network failures.
// This is accomplished by dropping messages, delaying messages, and randomizing
// which surviving ready messages are chosen in which order (but surviving
// messages between the same two nodes preserve ordering, because we use a
// single tcp connection for now)

enum NetworkCondition {
    Partition(SocketAddr, SocketAddr)
}

struct SimServer {
    server: Server<TestClock, Result<(), SendError<Envelope>>>,
    clock: Arc<TestClock>,
    outbound: Receiver<Envelope>,
    tok: Token,
    addr: SocketAddr,
}

pub struct NetworkSim {
    nodes: BTreeMap<Ipv4Addr, SimServer>,
    filters: Vec<NetworkCondition>,
}

impl NetworkSim {
    pub fn new(num_nodes: u16) -> NetworkSim {
        let mut logs = vec![];
        for i in 0..num_nodes as usize {
            logs.push(InMemoryLog {
                pending: BTreeMap::new(),
                committed: BTreeMap::new(),
                quorum: num_nodes as usize / 2 + 1,
                last_learned_txid: 0,
                last_learned_term: 0,
                last_accepted_txid: 0,
                last_accepted_term: 0,
            });
        }
        NetworkSim::new_from_logs(logs)
    }

    pub fn new_from_logs(logs: Vec<InMemoryLog<Mutation>>) -> NetworkSim {
        let mut peers = vec![];
        let mut peer_strings = vec![];
        for i in 0..logs.len() {
            let ip = Ipv4Addr::new(1, 0, (i / 256) as u8, (i % 256) as u8);
            let port = i as u16;
            peers.push(SocketAddrV4::new(ip, port));
            peer_strings.push(format!("{}:{}", ip, port));
        }

        let mut nodes: BTreeMap<Ipv4Addr, SimServer> = BTreeMap::new();

        let mut toks = 0;
        for (peer, rep_log) in peers.iter().zip(logs) {
            let (tx, rx) = mpsc::channel();

            let clock = Arc::new(TestClock::new());

            let server = Server {
                clock: clock.clone(),
                peer_port: peer.port(),
                cli_port: 65535 - peer.port(),
                id: Uuid::new_v4().to_string(),
                rpc_tx: Box::new(tx),
                max_generated_txid: 0,
                highest_term: 0,
                state: State::Init,
                db: rocksdb::new(
                    format!("_rasputin_test/sim_{}", peer.port())
                ),
                rep_log: Box::new(rep_log),
                peers: peer_strings.clone(),
                rep_peers: BTreeMap::new(),
                pending: BTreeMap::new(),
            };

            nodes.insert(*peer.ip(), SimServer {
                server: server,
                addr: SocketAddr::V4(SocketAddrV4::new(*peer.ip(), peer.port())),
                clock: clock.clone(),
                outbound: rx,
                tok: Token(toks),
            });

            toks += 1;
        }

        NetworkSim{
            nodes: nodes,
            filters: vec![],
        }
    }

    pub fn step(&mut self) {
        let mut rng = thread_rng();
        let mut outbound = vec![];
        for (ip, node) in self.nodes.iter_mut() {
            node.clock.sleep_ms(rng.gen_range(400,500));
            node.server.cron();
            loop {
                match node.outbound.try_recv() {
                    Ok(env) => outbound.push((node.addr, env)),
                    Err(_) => break, // nothing to send
                }
            }
        }
        for (addr, ref env) in outbound {
            if env.address.is_none() {
                // this is a peer broadcast, which will be attempted to be sent
                // to all connected peers.
                // TODO(tyler) apply filters and node selection randomization
                for (_, node) in self.nodes.iter_mut() {
                    node.server.handle_peer(Envelope {
                        address: Some(addr),
                        tok: node.tok,
                        msg: ByteBuf::from_slice(env.msg.bytes()),
                    });
                }
            } else {
                // this is a targeted message
                // TODO(tyler) apply filters and node selection randomization
                for (_, node) in self.nodes.iter_mut() {
                    if node.addr == env.address.unwrap() {
                        println!("matched sender dest {:?}", node.addr);
                        node.server.handle_peer(Envelope {
                            address: Some(addr),
                            tok: node.tok,
                            msg: ByteBuf::from_slice(env.msg.bytes()),
                        });
                    }
                }
            }
            println!("env: {:?}", env.address);
        }
    }
}
<commit_msg>Make random number generator deterministic with a pinned seed.<commit_after>extern crate bytes;
extern crate rand;
extern crate mio;
extern crate uuid;

use std::collections::BTreeMap;
use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr};
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{self, Sender, Receiver, SendError};

use self::rand::{StdRng, SeedableRng, Rng};
use self::bytes::{Buf, ByteBuf};
use self::mio::Token;
use rasputin::server::{rocksdb, Server, Envelope, State, Peer, InMemoryLog,
                       LEADER_DURATION, PEER_BROADCAST};
use rasputin::{Clock, TestClock, Mutation};
use self::uuid::Uuid;

// NetworkSim facilitates testing a cluster against network failures.
// This is accomplished by dropping messages, delaying messages, and randomizing
// which surviving ready messages are chosen in which order (but surviving
// messages between the same two nodes preserve ordering, because we use a
// single tcp connection for now)

enum NetworkCondition {
    Partition(SocketAddr, SocketAddr)
}

struct SimServer {
    server: Server<TestClock, Result<(), SendError<Envelope>>>,
    clock: Arc<TestClock>,
    outbound: Receiver<Envelope>,
    tok: Token,
    addr: SocketAddr,
}

pub struct NetworkSim {
    rng: StdRng,
    nodes: BTreeMap<Ipv4Addr, SimServer>,
    filters: Vec<NetworkCondition>,
}

impl NetworkSim {
    pub fn new(num_nodes: u16) -> NetworkSim {
        let mut logs = vec![];
        for i in 0..num_nodes as usize {
            logs.push(InMemoryLog {
                pending: BTreeMap::new(),
                committed: BTreeMap::new(),
                quorum: num_nodes as usize / 2 + 1,
                last_learned_txid: 0,
                last_learned_term: 0,
                last_accepted_txid: 0,
                last_accepted_term: 0,
            });
        }
        NetworkSim::new_from_logs(logs)
    }

    pub fn new_from_logs(logs: Vec<InMemoryLog<Mutation>>) -> NetworkSim {
        let mut peers = vec![];
        let mut peer_strings = vec![];
        for i in 0..logs.len() {
            let ip = Ipv4Addr::new(1, 0, (i / 256) as u8, (i % 256) as u8);
            let port = i as u16;
            peers.push(SocketAddrV4::new(ip, port));
            peer_strings.push(format!("{}:{}", ip, port));
        }

        let mut nodes: BTreeMap<Ipv4Addr, SimServer> = BTreeMap::new();

        let mut toks = 0;
        for (peer, rep_log) in peers.iter().zip(logs) {
            let (tx, rx) = mpsc::channel();

            let clock = Arc::new(TestClock::new());

            let server = Server {
                clock: clock.clone(),
                peer_port: peer.port(),
                cli_port: 65535 - peer.port(),
                id: Uuid::new_v4().to_string(),
                rpc_tx: Box::new(tx),
                max_generated_txid: 0,
                highest_term: 0,
                state: State::Init,
                db: rocksdb::new(
                    format!("_rasputin_test/sim_{}", peer.port())
                ),
                rep_log: Box::new(rep_log),
                peers: peer_strings.clone(),
                rep_peers: BTreeMap::new(),
                pending: BTreeMap::new(),
            };

            nodes.insert(*peer.ip(), SimServer {
                server: server,
                addr: SocketAddr::V4(SocketAddrV4::new(*peer.ip(), peer.port())),
                clock: clock.clone(),
                outbound: rx,
                tok: Token(toks),
            });

            toks += 1;
        }

        let seed: &[_] = &[0];
        NetworkSim{
            rng: SeedableRng::from_seed(seed),
            nodes: nodes,
            filters: vec![],
        }
    }

    pub fn step(&mut self) {
        let mut outbound = vec![];
        for (ip, node) in self.nodes.iter_mut() {
            node.clock.sleep_ms(self.rng.gen_range(400,500));
            node.server.cron();
            loop {
                match node.outbound.try_recv() {
                    Ok(env) => outbound.push((node.addr, env)),
                    Err(_) => break, // nothing to send
                }
            }
        }
        for (addr, ref env) in outbound {
            if env.address.is_none() {
                // this is a peer broadcast, which will be attempted to be sent
                // to all connected peers.
                // TODO(tyler) apply filters and node selection randomization
                for (_, node) in self.nodes.iter_mut() {
                    node.server.handle_peer(Envelope {
                        address: Some(addr),
                        tok: node.tok,
                        msg: ByteBuf::from_slice(env.msg.bytes()),
                    });
                }
            } else {
                // this is a targeted message
                // TODO(tyler) apply filters and node selection randomization
                for (_, node) in self.nodes.iter_mut() {
                    if node.addr == env.address.unwrap() {
                        println!("matched sender dest {:?}", node.addr);
                        node.server.handle_peer(Envelope {
                            address: Some(addr),
                            tok: node.tok,
                            msg: ByteBuf::from_slice(env.msg.bytes()),
                        });
                    }
                }
            }
            println!("env: {:?}", env.address);
        }
    }
}
<|endoftext|>


<commit_before>use ops::{Deref, DerefMut};
use std::ops;

#[derive(Debug)]
pub enum MustInitialize<T> {
    Initialized(T),
    NotInitialized,
}

pub use self::MustInitialize::Initialized;
pub use self::MustInitialize::NotInitialized;

impl<T> MustInitialize<T> {
    pub fn is_initialized(&self) -> bool {
        match self {
            Initialized(_) => true,
            NotInitialized => false,
        }
    }
    pub fn unwrap(&self) -> &T {
        self.deref()
    }
}

impl<T> Deref for MustInitialize<T> {
    type Target = T;

    fn deref(&self) -> &T {
        match self {
            Initialized(ref r) => r,
            NotInitialized => {
                panic!("Whoops! Something that was supposed to have been initialized at this point, wasn't.")
            }
        }
    }
}

impl<T> DerefMut for MustInitialize<T> {
    fn deref_mut(&mut self) -> &mut T {
        match self {
            Initialized(ref mut r) => r,
            NotInitialized => {
                panic!("Whoops! Something that was supposed to have been initialized at this point, wasn't.")
            }
        }
    }
}
<commit_msg>#[track_caller] on MustInitialize::deref<commit_after>use ops::{Deref, DerefMut};
use std::ops;

#[derive(Debug)]
pub enum MustInitialize<T> {
    Initialized(T),
    NotInitialized,
}

pub use self::MustInitialize::Initialized;
pub use self::MustInitialize::NotInitialized;

impl<T> MustInitialize<T> {
    pub fn is_initialized(&self) -> bool {
        match self {
            Initialized(_) => true,
            NotInitialized => false,
        }
    }
    pub fn unwrap(&self) -> &T {
        self.deref()
    }
}

impl<T> Deref for MustInitialize<T> {
    type Target = T;

    #[track_caller]
    fn deref(&self) -> &T {
        match self {
            Initialized(ref r) => r,
            NotInitialized => {
                panic!("Whoops! Something that was supposed to have been initialized at this point, wasn't.")
            }
        }
    }
}

impl<T> DerefMut for MustInitialize<T> {
    #[track_caller]
    fn deref_mut(&mut self) -> &mut T {
        match self {
            Initialized(ref mut r) => r,
            NotInitialized => {
                panic!("Whoops! Something that was supposed to have been initialized at this point, wasn't.")
            }
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 The Tink-Rust Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

//! This module contains pre-generated [`KeyTemplate`]s for AEAD keys. One can use these templates
//! to generate new Keysets.

use prost::Message;
use tink::proto::{HashType, KeyTemplate, OutputPrefixType};

/// Return a [`KeyTemplate`] that generates an AES-GCM key with the following parameters:
///   - Key size: 16 bytes
///   - Output prefix type: TINK
pub fn aes128_gcm_key_template() -> KeyTemplate {
    create_aes_gcm_key_template(16, OutputPrefixType::Tink)
}

/// Return a [`KeyTemplate`] that generates an AES-GCM key with the following parameters:
///   - Key size: 32 bytes
///   - Output prefix type: TINK
pub fn aes256_gcm_key_template() -> KeyTemplate {
    create_aes_gcm_key_template(32, OutputPrefixType::Tink)
}

/// Return a [`KeyTemplate`] that generates an AES-GCM key with the following parameters:
///   - Key size: 32 bytes
///   - Output prefix type: RAW
pub fn aes256_gcm_no_prefix_key_template() -> KeyTemplate {
    create_aes_gcm_key_template(32, OutputPrefixType::Raw)
}

/// Return a [`KeyTemplate`] that generates an AES-CTR-HMAC-AEAD key with the following parameters:
///  - AES key size: 16 bytes
///  - AES CTR IV size: 16 bytes
///  - HMAC key size: 32 bytes
///  - HMAC tag size: 16 bytes
///  - HMAC hash function: SHA256
pub fn aes128_ctr_hmac_sha256_key_template() -> KeyTemplate {
    create_aes_ctr_hmac_aead_key_template(16, 16, 32, 16, HashType::Sha256)
}

/// Return a [`KeyTemplate`] that generates an AES-CTR-HMAC-AEAD key with the following parameters:
///  - AES key size: 32 bytes
///  - AES CTR IV size: 16 bytes
///  - HMAC key size: 32 bytes
///  - HMAC tag size: 32 bytes
///  - HMAC hash function: SHA256
pub fn aes256_ctr_hmac_sha256_key_template() -> KeyTemplate {
    create_aes_ctr_hmac_aead_key_template(32, 16, 32, 32, HashType::Sha256)
}

/// Return a [`KeyTemplate`] that generates a CHACHA20_POLY1305 key.
pub fn cha_cha20_poly1305_key_template() -> KeyTemplate {
    KeyTemplate {
        /// Don't set value because key_format is not required.
        value: vec![],
        type_url: crate::CHA_CHA20_POLY1305_TYPE_URL.to_string(),
        output_prefix_type: OutputPrefixType::Tink as i32,
    }
}

/// Return a [`KeyTemplate`] that generates a XCHACHA20_POLY1305 key.
pub fn x_cha_cha20_poly1305_key_template() -> KeyTemplate {
    KeyTemplate {
        /// Don't set value because key_format is not required.
        value: vec![],
        type_url: crate::X_CHA_CHA20_POLY1305_TYPE_URL.to_string(),
        output_prefix_type: OutputPrefixType::Tink as i32,
    }
}

/// Return a [`KeyTemplate`] that generates a KmsEnvelopeAead key for a given KEK in remote KMS
pub fn kms_envelope_aead_key_template(uri: &str, dek_t: KeyTemplate) -> KeyTemplate {
    let f = tink::proto::KmsEnvelopeAeadKeyFormat {
        kek_uri: uri.to_string(),
        dek_template: Some(dek_t),
    };
    let mut serialized_format = Vec::new();
    f.encode(&mut serialized_format).unwrap();
    KeyTemplate {
        value: serialized_format,
        type_url: crate::KMS_ENVELOPE_AEAD_TYPE_URL.to_string(),
        output_prefix_type: OutputPrefixType::Tink as i32,
    }
}

/// Return an AES-GCM key template with the given key size in bytes.
fn create_aes_gcm_key_template(key_size: u32, output_prefix_type: OutputPrefixType) -> KeyTemplate {
    let format = tink::proto::AesGcmKeyFormat {
        version: crate::AES_CTR_HMAC_AEAD_KEY_VERSION,
        key_size,
    };
    let mut serialized_format = Vec::new();
    format.encode(&mut serialized_format).unwrap();
    KeyTemplate {
        type_url: crate::AES_GCM_TYPE_URL.to_string(),
        value: serialized_format,
        output_prefix_type: output_prefix_type as i32,
    }
}

fn create_aes_ctr_hmac_aead_key_template(
    aes_key_size: u32,
    iv_size: u32,
    hmac_key_size: u32,
    tag_size: u32,
    hash: HashType,
) -> KeyTemplate {
    let format = tink::proto::AesCtrHmacAeadKeyFormat {
        aes_ctr_key_format: Some(tink::proto::AesCtrKeyFormat {
            params: Some(tink::proto::AesCtrParams { iv_size }),
            key_size: aes_key_size,
        }),
        hmac_key_format: Some(tink::proto::HmacKeyFormat {
            version: crate::AES_CTR_HMAC_AEAD_KEY_VERSION,
            params: Some(tink::proto::HmacParams {
                hash: hash as i32,
                tag_size,
            }),
            key_size: hmac_key_size,
        }),
    };
    let mut serialized_format = Vec::new();
    format.encode(&mut serialized_format).unwrap();
    KeyTemplate {
        value: serialized_format,
        type_url: crate::AES_CTR_HMAC_AEAD_TYPE_URL.to_string(),
        output_prefix_type: OutputPrefixType::Tink as i32,
    }
}
<commit_msg>tink-aead: fix typo in key version<commit_after>// Copyright 2020 The Tink-Rust Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

//! This module contains pre-generated [`KeyTemplate`]s for AEAD keys. One can use these templates
//! to generate new Keysets.

use prost::Message;
use tink::proto::{HashType, KeyTemplate, OutputPrefixType};

/// Return a [`KeyTemplate`] that generates an AES-GCM key with the following parameters:
///   - Key size: 16 bytes
///   - Output prefix type: TINK
pub fn aes128_gcm_key_template() -> KeyTemplate {
    create_aes_gcm_key_template(16, OutputPrefixType::Tink)
}

/// Return a [`KeyTemplate`] that generates an AES-GCM key with the following parameters:
///   - Key size: 32 bytes
///   - Output prefix type: TINK
pub fn aes256_gcm_key_template() -> KeyTemplate {
    create_aes_gcm_key_template(32, OutputPrefixType::Tink)
}

/// Return a [`KeyTemplate`] that generates an AES-GCM key with the following parameters:
///   - Key size: 32 bytes
///   - Output prefix type: RAW
pub fn aes256_gcm_no_prefix_key_template() -> KeyTemplate {
    create_aes_gcm_key_template(32, OutputPrefixType::Raw)
}

/// Return a [`KeyTemplate`] that generates an AES-CTR-HMAC-AEAD key with the following parameters:
///  - AES key size: 16 bytes
///  - AES CTR IV size: 16 bytes
///  - HMAC key size: 32 bytes
///  - HMAC tag size: 16 bytes
///  - HMAC hash function: SHA256
pub fn aes128_ctr_hmac_sha256_key_template() -> KeyTemplate {
    create_aes_ctr_hmac_aead_key_template(16, 16, 32, 16, HashType::Sha256)
}

/// Return a [`KeyTemplate`] that generates an AES-CTR-HMAC-AEAD key with the following parameters:
///  - AES key size: 32 bytes
///  - AES CTR IV size: 16 bytes
///  - HMAC key size: 32 bytes
///  - HMAC tag size: 32 bytes
///  - HMAC hash function: SHA256
pub fn aes256_ctr_hmac_sha256_key_template() -> KeyTemplate {
    create_aes_ctr_hmac_aead_key_template(32, 16, 32, 32, HashType::Sha256)
}

/// Return a [`KeyTemplate`] that generates a CHACHA20_POLY1305 key.
pub fn cha_cha20_poly1305_key_template() -> KeyTemplate {
    KeyTemplate {
        /// Don't set value because key_format is not required.
        value: vec![],
        type_url: crate::CHA_CHA20_POLY1305_TYPE_URL.to_string(),
        output_prefix_type: OutputPrefixType::Tink as i32,
    }
}

/// Return a [`KeyTemplate`] that generates a XCHACHA20_POLY1305 key.
pub fn x_cha_cha20_poly1305_key_template() -> KeyTemplate {
    KeyTemplate {
        /// Don't set value because key_format is not required.
        value: vec![],
        type_url: crate::X_CHA_CHA20_POLY1305_TYPE_URL.to_string(),
        output_prefix_type: OutputPrefixType::Tink as i32,
    }
}

/// Return a [`KeyTemplate`] that generates a KmsEnvelopeAead key for a given KEK in remote KMS
pub fn kms_envelope_aead_key_template(uri: &str, dek_t: KeyTemplate) -> KeyTemplate {
    let f = tink::proto::KmsEnvelopeAeadKeyFormat {
        kek_uri: uri.to_string(),
        dek_template: Some(dek_t),
    };
    let mut serialized_format = Vec::new();
    f.encode(&mut serialized_format).unwrap();
    KeyTemplate {
        value: serialized_format,
        type_url: crate::KMS_ENVELOPE_AEAD_TYPE_URL.to_string(),
        output_prefix_type: OutputPrefixType::Tink as i32,
    }
}

/// Return an AES-GCM key template with the given key size in bytes.
fn create_aes_gcm_key_template(key_size: u32, output_prefix_type: OutputPrefixType) -> KeyTemplate {
    let format = tink::proto::AesGcmKeyFormat {
        version: crate::AES_GCM_KEY_VERSION,
        key_size,
    };
    let mut serialized_format = Vec::new();
    format.encode(&mut serialized_format).unwrap();
    KeyTemplate {
        type_url: crate::AES_GCM_TYPE_URL.to_string(),
        value: serialized_format,
        output_prefix_type: output_prefix_type as i32,
    }
}

/// Return an AES-CTR-HMAC key template with the given parameters.
fn create_aes_ctr_hmac_aead_key_template(
    aes_key_size: u32,
    iv_size: u32,
    hmac_key_size: u32,
    tag_size: u32,
    hash: HashType,
) -> KeyTemplate {
    let format = tink::proto::AesCtrHmacAeadKeyFormat {
        aes_ctr_key_format: Some(tink::proto::AesCtrKeyFormat {
            params: Some(tink::proto::AesCtrParams { iv_size }),
            key_size: aes_key_size,
        }),
        hmac_key_format: Some(tink::proto::HmacKeyFormat {
            version: crate::AES_CTR_HMAC_AEAD_KEY_VERSION,
            params: Some(tink::proto::HmacParams {
                hash: hash as i32,
                tag_size,
            }),
            key_size: hmac_key_size,
        }),
    };
    let mut serialized_format = Vec::new();
    format.encode(&mut serialized_format).unwrap();
    KeyTemplate {
        value: serialized_format,
        type_url: crate::AES_CTR_HMAC_AEAD_TYPE_URL.to_string(),
        output_prefix_type: OutputPrefixType::Tink as i32,
    }
}
<|endoftext|>