<commit_before>// Copyright (c) 2016 Fedor Gogolev <knsd@knsd.net>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::io::{Read, BufRead, Error as IoError, ErrorKind};
use std::string::{FromUtf8Error};

use num::{Zero};
use num::bigint::{BigInt, ToBigInt, Sign};
use byteorder::{ReadBytesExt, LittleEndian, BigEndian, Error as ByteorderError};
use from_ascii::{FromAscii, ParseIntError, ParseFloatError};

use string::{unescape, Error as UnescapeError};
use value::{Value};

use opcodes::*;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        EmptyMarker
        StackTooSmall
        EmptyStack
        InvalidValueOnStack
        InvalidGetValue
        InvalidPutValue
        NotImplemented

        Read(err: ByteorderError) {
            from()
        }
        Io(err: IoError) {
            from()
        }
        UnknownOpcode(opcode: u8) {}

        InvalidInt {
            from(ParseIntError)
        }
        InvalidLong
        InvalidFloat {
            from(ParseFloatError)
        }

        InvalidString {
            from(FromUtf8Error)
        }
        UnescapeError(err: UnescapeError) {
            from()
        }

        InvalidProto(proto: u8)
        NegativeLength
    }
}

#[derive(Debug, PartialEq)]
pub enum BooleanOrInt {
    Boolean(bool),
    Int(i64),
}

fn read_exact<R>(rd: &mut R, mut buf: &mut [u8]) -> Result<(), IoError> where R: Read {
    while !buf.is_empty() {
        match rd.read(buf) {
            Ok(0) => break,
            Ok(n) => { let tmp = buf; buf = &mut tmp[n..]; }
            Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
            Err(e) => return Err(e),
        }
    }
    if !buf.is_empty() {
        Err(IoError::new(ErrorKind::Other,
                       "failed to fill whole buffer"))
    } else {
        Ok(())
    }
}

fn read_until_newline<R>(rd: &mut R) -> Result<Vec<u8>, Error> where R: Read + BufRead {
    let mut buf = Vec::new();
    try!(rd.read_until('\n' as u8, &mut buf));

    // Skip last symbol — \n
    match buf.split_last() {
        Some((&b'\n', init)) => Ok(init.to_vec()),
        _ => return Err(Error::InvalidString),
    }
}

fn read_decimal_int<R>(rd: &mut R) -> Result<BooleanOrInt, Error> where R: Read + BufRead {
    let s = try!(read_until_newline(rd));
    let val = match &s[..] {
        b"00" => BooleanOrInt::Boolean(false),
        b"01" => BooleanOrInt::Boolean(true),
        _ => BooleanOrInt::Int(try!(i64::from_ascii(&s)))
    };
    Ok(val)
}

fn read_decimal_long<R>(rd: &mut R) -> Result<BigInt, Error> where R: Read + BufRead {
    let s = try!(read_until_newline(rd));
    let init = match s.split_last() {
        None => return Err(Error::InvalidString),
        Some((&b'L', init)) => init,
        Some(_) => &s[..],
    };

    match BigInt::parse_bytes(&init, 10) {
        Some(i) => Ok(i),
        None => Err(Error::InvalidLong)
    }
}

fn read_long<R>(rd: &mut R, length: usize) -> Result<BigInt, Error> where R: Read + BufRead {
    let mut buf = vec![0; length];
    try!(read_exact(rd, buf.as_mut()));

    let mut n = BigInt::from_bytes_le(Sign::Plus, &buf);

    let last = match buf.last_mut() {
        None => return Err(Error::InvalidLong),
        Some(last) => last,
    };

    if *last > 127 {
        n = n - (1.to_bigint().unwrap() << (length * 8))
    }

    Ok(n)
}

pub struct Machine {
    stack: Vec<Value>,
    memo: Vec<Value>,
    marker: Option<usize>,
}

impl Machine {
    pub fn new() -> Self {
        Machine {
            stack: Vec::new(),
            memo: vec![Value::None],
            marker: None,
        }
    }

    fn split_off(&mut self) -> Result<Vec<Value>, Error> {
        let at = match self.marker {
            None => return Err(Error::EmptyMarker),
            Some(marker) => marker,
        };

        if at > self.stack.len() {
            return Err(Error::StackTooSmall);
        }

        Ok(self.stack.split_off(at))
    }

    pub fn pop(&mut self) -> Result<Value, Error> {
        match self.stack.pop() {
            None => return Err(Error::EmptyStack),
            Some(value) => Ok(value),
        }
    }

    fn handle_get(&mut self, i: usize) -> Result<(), Error> {
        let value = match self.memo.get(i) {
            None => return Err(Error::InvalidGetValue),
            Some(ref v) => (*v).clone(),
        };
        self.stack.push(value);
        Ok(())
    }

    fn handle_put(&mut self, i: usize) -> Result<(), Error> {
        let value = match self.stack.last() {
            None => return Err(Error::EmptyStack),
            Some(ref v) => (*v).clone(),
        };
        let len = self.memo.len();
        if len != i {
            return Err(Error::InvalidPutValue)
        }
        self.memo.push(value);
        Ok(())
    }

    pub fn execute<R>(&mut self, rd: &mut R) -> Result<bool, Error> where R: Read + BufRead {
        macro_rules! ensure_not_negative {
            ($n: expr) => ({
                if $n < Zero::zero() {
                    return Err(Error::NegativeLength)
                }
            })
        }

        let marker = try!(rd.read_u8());
        match marker {
            PROTO => {
                let version = try!(rd.read_u8());
                if version < 2 {
                    return Err(Error::InvalidProto(version))
                }
            },
            STOP => return Ok(true),

            INT => {
                self.stack.push(match try!(read_decimal_int(rd)) {
                    BooleanOrInt::Boolean(v) => Value::Bool(v),
                    BooleanOrInt::Int(v) => Value::Long(BigInt::from(v)),
                })
            },
            BININT => self.stack.push(Value::Int(try!(rd.read_i32::<LittleEndian>()) as usize)),
            BININT1 => self.stack.push(Value::Int(try!(rd.read_u8()) as usize)),
            BININT2 => self.stack.push(Value::Int(try!(rd.read_u16::<LittleEndian>()) as usize)),
            LONG => self.stack.push(Value::Long(BigInt::from(try!(read_decimal_long(rd))))),
            LONG1 => {
                let length = try!(rd.read_u8());
                self.stack.push(Value::Long(BigInt::from(try!(read_long(rd, length as usize)))))
            }
            LONG4 => {
                let length = try!(rd.read_i32::<LittleEndian>());
                self.stack.push(Value::Long(BigInt::from(try!(read_long(rd, length as usize)))))
            }

            STRING => self.stack.push(Value::String(try!(unescape(&try!(read_until_newline(rd)), false)))),
            BINSTRING => {
                let length = try!(rd.read_i32::<LittleEndian>());
                ensure_not_negative!(length);

                let mut buf = vec![0; length as usize];
                try!(read_exact(rd, &mut buf));
                self.stack.push(Value::String(buf))
            },
            SHORT_BINSTRING => {
                let length = try!(rd.read_u8());
                let mut buf = vec![0; length as usize];
                try!(read_exact(rd, &mut buf));
                self.stack.push(Value::String(buf))
            },

            NONE => self.stack.push(Value::None),
            NEWTRUE => self.stack.push(Value::Bool(true)),
            NEWFALSE => self.stack.push(Value::Bool(false)),

            UNICODE => {
                let buf = try!(unescape(&try!(read_until_newline(rd)), true));
                self.stack.push(Value::Unicode(try!(String::from_utf8(buf))))
            },
            BINUNICODE => {
                let length = try!(rd.read_i32::<LittleEndian>());
                ensure_not_negative!(length);
                let mut buf = vec![0; length as usize];
                try!(read_exact(rd, buf.as_mut()));
                self.stack.push(Value::Unicode(try!(String::from_utf8(buf))))
            },

            FLOAT => {
                let s = try!(read_until_newline(rd));
                self.stack.push(Value::Float(try!(f64::from_ascii(&s))))
            },
            BINFLOAT => {
                self.stack.push(Value::Float(try!(rd.read_f64::<BigEndian>())))
            },

            EMPTY_LIST => {
                self.stack.push(Value::List(Vec::new()))
            },
            APPEND => {
                let v = try!(self.pop());
                match self.stack.last_mut() {
                    None => return Err(Error::EmptyStack),
                    Some(&mut Value::List(ref mut list)) => list.push(v),
                    _ => return Err(Error::InvalidValueOnStack),
                }
            },
            APPENDS => {
                let values = try!(self.split_off());
                match self.stack.last_mut() {
                    None => return Err(Error::EmptyStack),
                    Some(&mut Value::List(ref mut list)) => {
                        list.extend(values);
                    },
                    _ => return Err(Error::InvalidValueOnStack),
                }
            },
            LIST => {
                let values = try!(self.split_off());
                self.stack.push(Value::List(values));
            },

            EMPTY_TUPLE => self.stack.push(Value::Tuple(Vec::new())),
            TUPLE => {
                let values = try!(self.split_off());
                self.stack.push(Value::Tuple(values));
            },
            TUPLE1 => {
                let v1 = try!(self.pop());
                self.stack.push(Value::Tuple(vec![v1]))
            },
            TUPLE2 => {
                let v1 = try!(self.pop());
                let v2 = try!(self.pop());
                self.stack.push(Value::Tuple(vec![v1, v2]))
            },
            TUPLE3 => {
                let v1 = try!(self.pop());
                let v2 = try!(self.pop());
                let v3 = try!(self.pop());
                self.stack.push(Value::Tuple(vec![v1, v2, v3]))
            }

            EMPTY_DICT => self.stack.push(Value::Dict(Vec::new())),
            DICT => {
                let mut values = try!(self.split_off());
                let mut dict = Vec::new();

                for i in 0 .. values.len() / 2 { // TODO: Check panic
                    let key = values.remove(2 * i);
                    let value = values.remove(2 * i + 1);
                    dict.push((key, value));
                }
                self.stack.push(Value::Dict(dict));
            },
            SETITEM => {
                let value = try!(self.pop());
                let key = try!(self.pop());
                match self.stack.last_mut() {
                    None => return Err(Error::EmptyStack),
                    Some(&mut Value::Dict(ref mut dict)) => dict.push((key, value)),
                    _ => return Err(Error::InvalidValueOnStack),
                }
            },
            SETITEMS => {
                let mut values = try!(self.split_off());

                match self.stack.last_mut() {
                    None => return Err(Error::EmptyStack),
                    Some(&mut Value::Dict(ref mut dict)) => {
                        for i in 0 .. values.len() / 2 { // TODO: Check panic
                            let key = values.remove(2 * i);
                            let value = values.remove(2 * i + 1);
                            dict.push((key, value));
                        }
                    },
                    _ => return Err(Error::InvalidValueOnStack),
                }
            },

            POP => {
                try!(self.pop());
            },
            DUP => {
                let value = match self.stack.last() {
                    None => return Err(Error::EmptyStack),
                    Some(ref v) => (*v).clone(),
                };
                self.stack.push(value)
            },
            MARK => {
                self.marker = Some(self.stack.len())
            },
            POP_MARK => {
                try!(self.split_off());
            },

            GET => {
                let n = match try!(read_decimal_int(rd)) {
                    BooleanOrInt::Int(n) => n,
                    BooleanOrInt::Boolean(false) => 0,
                    BooleanOrInt::Boolean(true) => 1,
                };
                ensure_not_negative!(n);
                try!(self.handle_get(n as usize))
            }
            BINGET => {
                try!(self.handle_get(try!(rd.read_u8()) as usize))
            }
            LONG_BINGET => {
                let n = try!(rd.read_i32::<LittleEndian>());
                ensure_not_negative!(n);
                try!(self.handle_get(n as usize))
            }

            PUT => {
                let n = match try!(read_decimal_int(rd)) {
                    BooleanOrInt::Int(n) => n,
                    BooleanOrInt::Boolean(false) => 0,
                    BooleanOrInt::Boolean(true) => 1,
                };
                ensure_not_negative!(n);
                try!(self.handle_put(n as usize))
            }
            BINPUT => {
                try!(self.handle_put(try!(rd.read_u8()) as usize))
            }
            LONG_BINPUT => {
                let n = try!(rd.read_i32::<LittleEndian>());
                ensure_not_negative!(n);
                try!(self.handle_put(n as usize))
            }

            c => return Err(Error::UnknownOpcode(c)),
        }
        Ok(false)
    }
}
<commit_msg>Add __Nonexhaustive error variant<commit_after>// Copyright (c) 2016 Fedor Gogolev <knsd@knsd.net>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::io::{Read, BufRead, Error as IoError, ErrorKind};
use std::string::{FromUtf8Error};

use num::{Zero};
use num::bigint::{BigInt, ToBigInt, Sign};
use byteorder::{ReadBytesExt, LittleEndian, BigEndian, Error as ByteorderError};
use from_ascii::{FromAscii, ParseIntError, ParseFloatError};

use string::{unescape, Error as UnescapeError};
use value::{Value};

use opcodes::*;

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        EmptyMarker
        StackTooSmall
        EmptyStack
        InvalidValueOnStack
        InvalidGetValue
        InvalidPutValue
        NotImplemented

        Read(err: ByteorderError) {
            from()
        }
        Io(err: IoError) {
            from()
        }
        UnknownOpcode(opcode: u8) {}

        InvalidInt {
            from(ParseIntError)
        }
        InvalidLong
        InvalidFloat {
            from(ParseFloatError)
        }

        InvalidString {
            from(FromUtf8Error)
        }
        UnescapeError(err: UnescapeError) {
            from()
        }

        InvalidProto(proto: u8)
        NegativeLength {}

        #[doc(hidden)]
        __Nonexhaustive
    }
}

#[derive(Debug, PartialEq)]
pub enum BooleanOrInt {
    Boolean(bool),
    Int(i64),
}

fn read_exact<R>(rd: &mut R, mut buf: &mut [u8]) -> Result<(), IoError> where R: Read {
    while !buf.is_empty() {
        match rd.read(buf) {
            Ok(0) => break,
            Ok(n) => { let tmp = buf; buf = &mut tmp[n..]; }
            Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
            Err(e) => return Err(e),
        }
    }
    if !buf.is_empty() {
        Err(IoError::new(ErrorKind::Other,
                       "failed to fill whole buffer"))
    } else {
        Ok(())
    }
}

fn read_until_newline<R>(rd: &mut R) -> Result<Vec<u8>, Error> where R: Read + BufRead {
    let mut buf = Vec::new();
    try!(rd.read_until('\n' as u8, &mut buf));

    // Skip last symbol — \n
    match buf.split_last() {
        Some((&b'\n', init)) => Ok(init.to_vec()),
        _ => return Err(Error::InvalidString),
    }
}

fn read_decimal_int<R>(rd: &mut R) -> Result<BooleanOrInt, Error> where R: Read + BufRead {
    let s = try!(read_until_newline(rd));
    let val = match &s[..] {
        b"00" => BooleanOrInt::Boolean(false),
        b"01" => BooleanOrInt::Boolean(true),
        _ => BooleanOrInt::Int(try!(i64::from_ascii(&s)))
    };
    Ok(val)
}

fn read_decimal_long<R>(rd: &mut R) -> Result<BigInt, Error> where R: Read + BufRead {
    let s = try!(read_until_newline(rd));
    let init = match s.split_last() {
        None => return Err(Error::InvalidString),
        Some((&b'L', init)) => init,
        Some(_) => &s[..],
    };

    match BigInt::parse_bytes(&init, 10) {
        Some(i) => Ok(i),
        None => Err(Error::InvalidLong)
    }
}

fn read_long<R>(rd: &mut R, length: usize) -> Result<BigInt, Error> where R: Read + BufRead {
    let mut buf = vec![0; length];
    try!(read_exact(rd, buf.as_mut()));

    let mut n = BigInt::from_bytes_le(Sign::Plus, &buf);

    let last = match buf.last_mut() {
        None => return Err(Error::InvalidLong),
        Some(last) => last,
    };

    if *last > 127 {
        n = n - (1.to_bigint().unwrap() << (length * 8))
    }

    Ok(n)
}

pub struct Machine {
    stack: Vec<Value>,
    memo: Vec<Value>,
    marker: Option<usize>,
}

impl Machine {
    pub fn new() -> Self {
        Machine {
            stack: Vec::new(),
            memo: vec![Value::None],
            marker: None,
        }
    }

    fn split_off(&mut self) -> Result<Vec<Value>, Error> {
        let at = match self.marker {
            None => return Err(Error::EmptyMarker),
            Some(marker) => marker,
        };

        if at > self.stack.len() {
            return Err(Error::StackTooSmall);
        }

        Ok(self.stack.split_off(at))
    }

    pub fn pop(&mut self) -> Result<Value, Error> {
        match self.stack.pop() {
            None => return Err(Error::EmptyStack),
            Some(value) => Ok(value),
        }
    }

    fn handle_get(&mut self, i: usize) -> Result<(), Error> {
        let value = match self.memo.get(i) {
            None => return Err(Error::InvalidGetValue),
            Some(ref v) => (*v).clone(),
        };
        self.stack.push(value);
        Ok(())
    }

    fn handle_put(&mut self, i: usize) -> Result<(), Error> {
        let value = match self.stack.last() {
            None => return Err(Error::EmptyStack),
            Some(ref v) => (*v).clone(),
        };
        let len = self.memo.len();
        if len != i {
            return Err(Error::InvalidPutValue)
        }
        self.memo.push(value);
        Ok(())
    }

    pub fn execute<R>(&mut self, rd: &mut R) -> Result<bool, Error> where R: Read + BufRead {
        macro_rules! ensure_not_negative {
            ($n: expr) => ({
                if $n < Zero::zero() {
                    return Err(Error::NegativeLength)
                }
            })
        }

        let marker = try!(rd.read_u8());
        match marker {
            PROTO => {
                let version = try!(rd.read_u8());
                if version < 2 {
                    return Err(Error::InvalidProto(version))
                }
            },
            STOP => return Ok(true),

            INT => {
                self.stack.push(match try!(read_decimal_int(rd)) {
                    BooleanOrInt::Boolean(v) => Value::Bool(v),
                    BooleanOrInt::Int(v) => Value::Long(BigInt::from(v)),
                })
            },
            BININT => self.stack.push(Value::Int(try!(rd.read_i32::<LittleEndian>()) as usize)),
            BININT1 => self.stack.push(Value::Int(try!(rd.read_u8()) as usize)),
            BININT2 => self.stack.push(Value::Int(try!(rd.read_u16::<LittleEndian>()) as usize)),
            LONG => self.stack.push(Value::Long(BigInt::from(try!(read_decimal_long(rd))))),
            LONG1 => {
                let length = try!(rd.read_u8());
                self.stack.push(Value::Long(BigInt::from(try!(read_long(rd, length as usize)))))
            }
            LONG4 => {
                let length = try!(rd.read_i32::<LittleEndian>());
                self.stack.push(Value::Long(BigInt::from(try!(read_long(rd, length as usize)))))
            }

            STRING => self.stack.push(Value::String(try!(unescape(&try!(read_until_newline(rd)), false)))),
            BINSTRING => {
                let length = try!(rd.read_i32::<LittleEndian>());
                ensure_not_negative!(length);

                let mut buf = vec![0; length as usize];
                try!(read_exact(rd, &mut buf));
                self.stack.push(Value::String(buf))
            },
            SHORT_BINSTRING => {
                let length = try!(rd.read_u8());
                let mut buf = vec![0; length as usize];
                try!(read_exact(rd, &mut buf));
                self.stack.push(Value::String(buf))
            },

            NONE => self.stack.push(Value::None),
            NEWTRUE => self.stack.push(Value::Bool(true)),
            NEWFALSE => self.stack.push(Value::Bool(false)),

            UNICODE => {
                let buf = try!(unescape(&try!(read_until_newline(rd)), true));
                self.stack.push(Value::Unicode(try!(String::from_utf8(buf))))
            },
            BINUNICODE => {
                let length = try!(rd.read_i32::<LittleEndian>());
                ensure_not_negative!(length);
                let mut buf = vec![0; length as usize];
                try!(read_exact(rd, buf.as_mut()));
                self.stack.push(Value::Unicode(try!(String::from_utf8(buf))))
            },

            FLOAT => {
                let s = try!(read_until_newline(rd));
                self.stack.push(Value::Float(try!(f64::from_ascii(&s))))
            },
            BINFLOAT => {
                self.stack.push(Value::Float(try!(rd.read_f64::<BigEndian>())))
            },

            EMPTY_LIST => {
                self.stack.push(Value::List(Vec::new()))
            },
            APPEND => {
                let v = try!(self.pop());
                match self.stack.last_mut() {
                    None => return Err(Error::EmptyStack),
                    Some(&mut Value::List(ref mut list)) => list.push(v),
                    _ => return Err(Error::InvalidValueOnStack),
                }
            },
            APPENDS => {
                let values = try!(self.split_off());
                match self.stack.last_mut() {
                    None => return Err(Error::EmptyStack),
                    Some(&mut Value::List(ref mut list)) => {
                        list.extend(values);
                    },
                    _ => return Err(Error::InvalidValueOnStack),
                }
            },
            LIST => {
                let values = try!(self.split_off());
                self.stack.push(Value::List(values));
            },

            EMPTY_TUPLE => self.stack.push(Value::Tuple(Vec::new())),
            TUPLE => {
                let values = try!(self.split_off());
                self.stack.push(Value::Tuple(values));
            },
            TUPLE1 => {
                let v1 = try!(self.pop());
                self.stack.push(Value::Tuple(vec![v1]))
            },
            TUPLE2 => {
                let v1 = try!(self.pop());
                let v2 = try!(self.pop());
                self.stack.push(Value::Tuple(vec![v1, v2]))
            },
            TUPLE3 => {
                let v1 = try!(self.pop());
                let v2 = try!(self.pop());
                let v3 = try!(self.pop());
                self.stack.push(Value::Tuple(vec![v1, v2, v3]))
            }

            EMPTY_DICT => self.stack.push(Value::Dict(Vec::new())),
            DICT => {
                let mut values = try!(self.split_off());
                let mut dict = Vec::new();

                for i in 0 .. values.len() / 2 { // TODO: Check panic
                    let key = values.remove(2 * i);
                    let value = values.remove(2 * i + 1);
                    dict.push((key, value));
                }
                self.stack.push(Value::Dict(dict));
            },
            SETITEM => {
                let value = try!(self.pop());
                let key = try!(self.pop());
                match self.stack.last_mut() {
                    None => return Err(Error::EmptyStack),
                    Some(&mut Value::Dict(ref mut dict)) => dict.push((key, value)),
                    _ => return Err(Error::InvalidValueOnStack),
                }
            },
            SETITEMS => {
                let mut values = try!(self.split_off());

                match self.stack.last_mut() {
                    None => return Err(Error::EmptyStack),
                    Some(&mut Value::Dict(ref mut dict)) => {
                        for i in 0 .. values.len() / 2 { // TODO: Check panic
                            let key = values.remove(2 * i);
                            let value = values.remove(2 * i + 1);
                            dict.push((key, value));
                        }
                    },
                    _ => return Err(Error::InvalidValueOnStack),
                }
            },

            POP => {
                try!(self.pop());
            },
            DUP => {
                let value = match self.stack.last() {
                    None => return Err(Error::EmptyStack),
                    Some(ref v) => (*v).clone(),
                };
                self.stack.push(value)
            },
            MARK => {
                self.marker = Some(self.stack.len())
            },
            POP_MARK => {
                try!(self.split_off());
            },

            GET => {
                let n = match try!(read_decimal_int(rd)) {
                    BooleanOrInt::Int(n) => n,
                    BooleanOrInt::Boolean(false) => 0,
                    BooleanOrInt::Boolean(true) => 1,
                };
                ensure_not_negative!(n);
                try!(self.handle_get(n as usize))
            }
            BINGET => {
                try!(self.handle_get(try!(rd.read_u8()) as usize))
            }
            LONG_BINGET => {
                let n = try!(rd.read_i32::<LittleEndian>());
                ensure_not_negative!(n);
                try!(self.handle_get(n as usize))
            }

            PUT => {
                let n = match try!(read_decimal_int(rd)) {
                    BooleanOrInt::Int(n) => n,
                    BooleanOrInt::Boolean(false) => 0,
                    BooleanOrInt::Boolean(true) => 1,
                };
                ensure_not_negative!(n);
                try!(self.handle_put(n as usize))
            }
            BINPUT => {
                try!(self.handle_put(try!(rd.read_u8()) as usize))
            }
            LONG_BINPUT => {
                let n = try!(rd.read_i32::<LittleEndian>());
                ensure_not_negative!(n);
                try!(self.handle_put(n as usize))
            }

            c => return Err(Error::UnknownOpcode(c)),
        }
        Ok(false)
    }
}
<|endoftext|>


<commit_before>//! Module for reading and writing SBD messages.
//!
//! Though messages technically come in two flavors, mobile originated and mobile terminated, we
//! only handle mobile originated messages in this library.

use std::cmp::Ordering;
use std::collections::HashMap;
use std::fs::File;
use std::io::{Cursor, Read, Write};
use std::str;
use std::path::Path;

use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use chrono::{DateTime, Duration, TimeZone, UTC};

use super::{Error, Result};
use super::information_element::{InformationElement, InformationElementType};

const INFORMATION_ELEMENT_HEADER_LENGTH: u16 = 3;
const MOBILE_ORIGINATED_HEADER_LENGTH: u16 = 28;
const ASCII_ZERO: u8 = 48;

/// The protocol number of an SBD message.
///
/// At this point, this can *only* be one.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
struct ProtocolRevisionNumber(u8);

impl ProtocolRevisionNumber {
    /// Returns true if this is a valid protocol revision number.
    pub fn valid(&self) -> bool {
        self.0 == 1
    }
}

/// The modem IMEI identifier.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct Imei([u8; 15]);

impl Default for Imei {
    fn default() -> Imei {
        Imei([ASCII_ZERO; 15])
    }
}

impl Imei {
    /// Returns this IMEI number as a `str`.
    ///
    /// # Panics
    ///
    /// Panics if the string is not valid utf8.
    fn as_str(&self) -> &str {
        str::from_utf8(&self.0).unwrap()
    }
}

/// The status of a mobile-originated session.
///
/// The descriptions for these codes are taken directly from the DirectIP documentation.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SessionStatus {
    /// The SBD session completed successfully.
    Ok = 0,
    /// The MO message transfer, if any, was successful. The MT message queued at the GSS is too
    /// large to be transferred within a single SBD session.
    OkMobileTerminatedTooLarge = 1,
    /// The MO message transfer, if any, was successful. The reported location was determined to be
    /// of unacceptable quality. This value is only applicable to IMEIs using SBD protocol revision
    /// 1.
    OkLocationUnacceptableQuality = 2,
    /// The SBD session timed out before session completion.
    Timeout = 10,
    /// The MO message being transferred by the IMEI is too large to be transerred within a single
    /// SBD session.
    MobileOriginatedTooLarge = 12,
    /// An RF link loss ocurred during the SBD session.
    RFLinkLoss = 13,
    /// An IMEI protocol anomaly occurred during SBD session.
    IMEIProtocolAnomaly = 14,
    /// The IMEI is prohibited from accessing the GSS.
    Prohibited = 15,
    /// Unknown session status code.
    Unknown,
}

impl Default for SessionStatus {
    fn default() -> SessionStatus {
        SessionStatus::Unknown
    }
}

impl From<u8> for SessionStatus {
    fn from(n: u8) -> Self {
        match n {
            0 => SessionStatus::Ok,
            1 => SessionStatus::OkMobileTerminatedTooLarge,
            2 => SessionStatus::OkLocationUnacceptableQuality,
            10 => SessionStatus::Timeout,
            12 => SessionStatus::MobileOriginatedTooLarge,
            13 => SessionStatus::RFLinkLoss,
            14 => SessionStatus::IMEIProtocolAnomaly,
            15 => SessionStatus::Prohibited,
            _ => SessionStatus::Unknown,
        }
    }
}

/// A mobile-origined Iridium SBD message.
#[derive(Debug, Eq, PartialEq)]
pub struct Message {
    protocol_revision_number: ProtocolRevisionNumber,
    cdr_reference: u32,
    imei: Imei,
    session_status: SessionStatus,
    momsn: u16,
    mtmsn: u16,
    time_of_session: DateTime<UTC>,
    payload: Vec<u8>,
}

impl Default for Message {
    fn default() -> Message {
        Message {
            protocol_revision_number: ProtocolRevisionNumber(1),
            cdr_reference: Default::default(),
            imei: Default::default(),
            session_status: Default::default(),
            momsn: Default::default(),
            mtmsn: Default::default(),
            time_of_session: UTC.ymd(1970, 1, 1).and_hms(0, 0, 0),
            payload: Vec::new(),
        }
    }
}

impl PartialOrd for Message {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Message {
    fn cmp(&self, other: &Self) -> Ordering {
        self.time_of_session.cmp(&other.time_of_session)
    }
}

impl Message {
    /// Reads in a message from a file path.
    ///
    /// # Examples
    ///
    /// ```
    /// use sbd::message::Message;
    /// let message = Message::from_path("data/0-mo.sbd").unwrap();
    /// ```
    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Message> {
        let file = try!(File::open(path));
        Message::read_from(file)
    }

    /// Reads in a message from an object that implements `Read`.
    ///
    /// Per the specification, oversized and undersized messages will result in an error.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::fs::File;
    /// use sbd::message::Message;
    /// let mut file = File::open("data/0-mo.sbd").unwrap();
    /// let message = Message::read_from(file).unwrap();
    pub fn read_from<R: Read>(mut readable: R) -> Result<Message> {
        let mut message: Message = Default::default();

        message.protocol_revision_number = ProtocolRevisionNumber(try!(readable.read_u8()));
        if !message.protocol_revision_number.valid() {
            return Err(Error::InvalidProtocolRevisionNumber(message.protocol_revision_number.0));
        }
        let overall_message_length = try!(readable.read_u16::<BigEndian>());

        let mut information_elements: HashMap<InformationElementType, InformationElement> = HashMap::new();
        let mut bytes_read = 0u16;
        loop {
            let ie = match InformationElement::read_from(&mut readable) {
                Ok(ie) => ie,
                Err(e) => return Err(e),
            };
            bytes_read += ie.len();
            information_elements.insert(ie.id(), ie);
            if bytes_read >= overall_message_length {
                break
            }
        }

        if try!(readable.take(1).read_to_end(&mut Vec::new())) != 0 {
            return Err(Error::Oversized);
        }

        let header = match information_elements.remove(&InformationElementType::MobileOriginatedHeader) {
            Some(ie) => ie,
            None => return Err(Error::MissingMobileOriginatedHeader),
        };
        let mut cursor = &mut Cursor::new(header.contents_ref());
        message.cdr_reference = try!(cursor.read_u32::<BigEndian>());
        let bytes_read = try!(cursor.take(message.imei.0.len() as u64).read(&mut message.imei.0));
        if bytes_read != message.imei.0.len() {
            return Err(Error::InvalidImei);
        }
        message.session_status = SessionStatus::from(try!(cursor.read_u8()));
        message.momsn = try!(cursor.read_u16::<BigEndian>());
        message.mtmsn = try!(cursor.read_u16::<BigEndian>());
        message.time_of_session = UTC.ymd(1970, 1, 1).and_hms(0, 0, 0) +
            Duration::seconds(try!(cursor.read_u32::<BigEndian>()) as i64);

        let payload = match information_elements.remove(&InformationElementType::MobileOriginatedPayload) {
            Some(ie) => ie,
            None => return Err(Error::MissingMobileOriginatedPayload),
        };
        message.payload = payload.into_contents();

        Ok(message)
    }

    /// Write this message back to a object that can `Write`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::Cursor;
    /// use sbd::message::Message;
    /// let message = Message::from_path("data/0-mo.sbd").unwrap();
    /// let mut cursor = Cursor::new(Vec::new());
    /// message.write_to(&mut cursor);
    /// ```
    pub fn write_to<W: Write>(&self, w: &mut W) -> Result<()> {
        try!(w.write_u8(self.protocol_revision_number.0));
        try!(w.write_u16::<BigEndian>(self.overall_message_length()));
        try!(w.write_u8(InformationElementType::MobileOriginatedHeader as u8));
        try!(w.write_u16::<BigEndian>(MOBILE_ORIGINATED_HEADER_LENGTH));
        try!(w.write_u32::<BigEndian>(self.cdr_reference));
        try!(w.write_all(&self.imei.0));
        try!(w.write_u8(self.session_status as u8));
        try!(w.write_u16::<BigEndian>(self.momsn));
        try!(w.write_u16::<BigEndian>(self.mtmsn));
        try!(w.write_u32::<BigEndian>(self.time_of_session.timestamp() as u32));
        try!(w.write_u8(InformationElementType::MobileOriginatedPayload as u8));
        // TODO can we check to make sure the payload is of appropriate size?
        try!(w.write_u16::<BigEndian>(self.payload.len() as u16));
        try!(w.write_all(&self.payload[..]));
        Ok(())
    }

    /// Returns this message's protocol revision number.
    pub fn protocol_revision_number(&self) -> u8 { self.protocol_revision_number.0 }
    /// Returns this message's IMEI number as a string.
    pub fn imei(&self) -> &str { self.imei.as_str() }
    /// Returns this message's cdr reference number (also called auto id).
    pub fn cdr_reference(&self) -> u32 { self.cdr_reference }
    /// Returns this message's session status.
    pub fn session_status(&self) -> SessionStatus { self.session_status }
    /// Returns this message's mobile originated message number.
    pub fn momsn(&self) -> u16 { self.momsn }
    /// Returns this message's mobile terminated message number.
    pub fn mtmsn(&self) -> u16 { self.mtmsn }
    /// Returns this message's time of session.
    pub fn time_of_session(&self) -> DateTime<UTC> { self.time_of_session }
    /// Returns a reference to this message's payload.
    pub fn payload_ref(&self) -> &Vec<u8> { &self.payload }

    /// Returns the overall message length, as is contained in the message's header.
    ///
    /// The whole message is actually three bytes longer than this value, thanks to the message
    /// header itself.
    fn overall_message_length(&self) -> u16 {
        INFORMATION_ELEMENT_HEADER_LENGTH + MOBILE_ORIGINATED_HEADER_LENGTH +
            INFORMATION_ELEMENT_HEADER_LENGTH + self.payload.len() as u16
    }
}

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

    use std::fs::File;
    use std::io::{Cursor, Read};
    use std::str;

    use chrono::{TimeZone, UTC};

    #[test]
    fn from_path() {
        Message::from_path("data/0-mo.sbd").unwrap();
    }

    #[test]
    fn from_read() {
        let file = File::open("data/0-mo.sbd").unwrap();
        Message::read_from(file).unwrap();
    }

    #[test]
    fn from_path_that_doesnt_exist() {
        assert!(Message::from_path("notafile.sbd").is_err());
    }

    #[test]
    fn from_path_that_is_not_an_sbd_message() {
        assert!(Message::from_path("data/1-invalid.sbd").is_err());
    }

    #[test]
    fn undersized() {
        let file = File::open("data/0-mo.sbd").unwrap();
        let readable = file.take(58);
        assert!(Message::read_from(readable).is_err());
    }

    #[test]
    fn oversized() {
        let file = File::open("data/0-mo.sbd").unwrap();
        let readable = file.chain(Cursor::new(vec![0]));
        assert!(Message::read_from(readable).is_err());
    }

    #[test]
    fn values() {
        let message = Message::from_path("data/0-mo.sbd").unwrap();
        assert_eq!(1, message.protocol_revision_number());
        assert_eq!(1894516585, message.cdr_reference());
        assert_eq!("300234063904190", message.imei());
        assert_eq!(SessionStatus::Ok, message.session_status());
        assert_eq!(75, message.momsn());
        assert_eq!(0, message.mtmsn());
        assert_eq!(UTC.ymd(2015, 7, 9).and_hms(18, 15, 8), message.time_of_session());
        assert_eq!("test message from pete", str::from_utf8(message.payload_ref()).unwrap());
    }

    #[test]
    fn write() {
        let message = Message::from_path("data/0-mo.sbd").unwrap();
        let mut cursor = Cursor::new(Vec::new());
        message.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        let message2 = Message::read_from(cursor).unwrap();
        assert_eq!(message, message2);
    }

    #[test]
    fn write_default() {
        let message: Message = Default::default();
        let mut cursor = Cursor::new(Vec::new());
        message.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        let message2 = Message::read_from(cursor).unwrap();
        assert_eq!(message, message2);
    }

    #[test]
    fn order() {
        let default: Message = Default::default();
        let message = Message::from_path("data/0-mo.sbd").unwrap();
        assert!(message > default);
    }
}
<commit_msg>Add missing close code delimiter<commit_after>//! Module for reading and writing SBD messages.
//!
//! Though messages technically come in two flavors, mobile originated and mobile terminated, we
//! only handle mobile originated messages in this library.

use std::cmp::Ordering;
use std::collections::HashMap;
use std::fs::File;
use std::io::{Cursor, Read, Write};
use std::str;
use std::path::Path;

use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use chrono::{DateTime, Duration, TimeZone, UTC};

use super::{Error, Result};
use super::information_element::{InformationElement, InformationElementType};

const INFORMATION_ELEMENT_HEADER_LENGTH: u16 = 3;
const MOBILE_ORIGINATED_HEADER_LENGTH: u16 = 28;
const ASCII_ZERO: u8 = 48;

/// The protocol number of an SBD message.
///
/// At this point, this can *only* be one.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
struct ProtocolRevisionNumber(u8);

impl ProtocolRevisionNumber {
    /// Returns true if this is a valid protocol revision number.
    pub fn valid(&self) -> bool {
        self.0 == 1
    }
}

/// The modem IMEI identifier.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct Imei([u8; 15]);

impl Default for Imei {
    fn default() -> Imei {
        Imei([ASCII_ZERO; 15])
    }
}

impl Imei {
    /// Returns this IMEI number as a `str`.
    ///
    /// # Panics
    ///
    /// Panics if the string is not valid utf8.
    fn as_str(&self) -> &str {
        str::from_utf8(&self.0).unwrap()
    }
}

/// The status of a mobile-originated session.
///
/// The descriptions for these codes are taken directly from the DirectIP documentation.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SessionStatus {
    /// The SBD session completed successfully.
    Ok = 0,
    /// The MO message transfer, if any, was successful. The MT message queued at the GSS is too
    /// large to be transferred within a single SBD session.
    OkMobileTerminatedTooLarge = 1,
    /// The MO message transfer, if any, was successful. The reported location was determined to be
    /// of unacceptable quality. This value is only applicable to IMEIs using SBD protocol revision
    /// 1.
    OkLocationUnacceptableQuality = 2,
    /// The SBD session timed out before session completion.
    Timeout = 10,
    /// The MO message being transferred by the IMEI is too large to be transerred within a single
    /// SBD session.
    MobileOriginatedTooLarge = 12,
    /// An RF link loss ocurred during the SBD session.
    RFLinkLoss = 13,
    /// An IMEI protocol anomaly occurred during SBD session.
    IMEIProtocolAnomaly = 14,
    /// The IMEI is prohibited from accessing the GSS.
    Prohibited = 15,
    /// Unknown session status code.
    Unknown,
}

impl Default for SessionStatus {
    fn default() -> SessionStatus {
        SessionStatus::Unknown
    }
}

impl From<u8> for SessionStatus {
    fn from(n: u8) -> Self {
        match n {
            0 => SessionStatus::Ok,
            1 => SessionStatus::OkMobileTerminatedTooLarge,
            2 => SessionStatus::OkLocationUnacceptableQuality,
            10 => SessionStatus::Timeout,
            12 => SessionStatus::MobileOriginatedTooLarge,
            13 => SessionStatus::RFLinkLoss,
            14 => SessionStatus::IMEIProtocolAnomaly,
            15 => SessionStatus::Prohibited,
            _ => SessionStatus::Unknown,
        }
    }
}

/// A mobile-origined Iridium SBD message.
#[derive(Debug, Eq, PartialEq)]
pub struct Message {
    protocol_revision_number: ProtocolRevisionNumber,
    cdr_reference: u32,
    imei: Imei,
    session_status: SessionStatus,
    momsn: u16,
    mtmsn: u16,
    time_of_session: DateTime<UTC>,
    payload: Vec<u8>,
}

impl Default for Message {
    fn default() -> Message {
        Message {
            protocol_revision_number: ProtocolRevisionNumber(1),
            cdr_reference: Default::default(),
            imei: Default::default(),
            session_status: Default::default(),
            momsn: Default::default(),
            mtmsn: Default::default(),
            time_of_session: UTC.ymd(1970, 1, 1).and_hms(0, 0, 0),
            payload: Vec::new(),
        }
    }
}

impl PartialOrd for Message {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Message {
    fn cmp(&self, other: &Self) -> Ordering {
        self.time_of_session.cmp(&other.time_of_session)
    }
}

impl Message {
    /// Reads in a message from a file path.
    ///
    /// # Examples
    ///
    /// ```
    /// use sbd::message::Message;
    /// let message = Message::from_path("data/0-mo.sbd").unwrap();
    /// ```
    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Message> {
        let file = try!(File::open(path));
        Message::read_from(file)
    }

    /// Reads in a message from an object that implements `Read`.
    ///
    /// Per the specification, oversized and undersized messages will result in an error.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::fs::File;
    /// use sbd::message::Message;
    /// let mut file = File::open("data/0-mo.sbd").unwrap();
    /// let message = Message::read_from(file).unwrap();
    /// ```
    pub fn read_from<R: Read>(mut readable: R) -> Result<Message> {
        let mut message: Message = Default::default();

        message.protocol_revision_number = ProtocolRevisionNumber(try!(readable.read_u8()));
        if !message.protocol_revision_number.valid() {
            return Err(Error::InvalidProtocolRevisionNumber(message.protocol_revision_number.0));
        }
        let overall_message_length = try!(readable.read_u16::<BigEndian>());

        let mut information_elements: HashMap<InformationElementType, InformationElement> = HashMap::new();
        let mut bytes_read = 0u16;
        loop {
            let ie = match InformationElement::read_from(&mut readable) {
                Ok(ie) => ie,
                Err(e) => return Err(e),
            };
            bytes_read += ie.len();
            information_elements.insert(ie.id(), ie);
            if bytes_read >= overall_message_length {
                break
            }
        }

        if try!(readable.take(1).read_to_end(&mut Vec::new())) != 0 {
            return Err(Error::Oversized);
        }

        let header = match information_elements.remove(&InformationElementType::MobileOriginatedHeader) {
            Some(ie) => ie,
            None => return Err(Error::MissingMobileOriginatedHeader),
        };
        let mut cursor = &mut Cursor::new(header.contents_ref());
        message.cdr_reference = try!(cursor.read_u32::<BigEndian>());
        let bytes_read = try!(cursor.take(message.imei.0.len() as u64).read(&mut message.imei.0));
        if bytes_read != message.imei.0.len() {
            return Err(Error::InvalidImei);
        }
        message.session_status = SessionStatus::from(try!(cursor.read_u8()));
        message.momsn = try!(cursor.read_u16::<BigEndian>());
        message.mtmsn = try!(cursor.read_u16::<BigEndian>());
        message.time_of_session = UTC.ymd(1970, 1, 1).and_hms(0, 0, 0) +
            Duration::seconds(try!(cursor.read_u32::<BigEndian>()) as i64);

        let payload = match information_elements.remove(&InformationElementType::MobileOriginatedPayload) {
            Some(ie) => ie,
            None => return Err(Error::MissingMobileOriginatedPayload),
        };
        message.payload = payload.into_contents();

        Ok(message)
    }

    /// Write this message back to a object that can `Write`.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::io::Cursor;
    /// use sbd::message::Message;
    /// let message = Message::from_path("data/0-mo.sbd").unwrap();
    /// let mut cursor = Cursor::new(Vec::new());
    /// message.write_to(&mut cursor);
    /// ```
    pub fn write_to<W: Write>(&self, w: &mut W) -> Result<()> {
        try!(w.write_u8(self.protocol_revision_number.0));
        try!(w.write_u16::<BigEndian>(self.overall_message_length()));
        try!(w.write_u8(InformationElementType::MobileOriginatedHeader as u8));
        try!(w.write_u16::<BigEndian>(MOBILE_ORIGINATED_HEADER_LENGTH));
        try!(w.write_u32::<BigEndian>(self.cdr_reference));
        try!(w.write_all(&self.imei.0));
        try!(w.write_u8(self.session_status as u8));
        try!(w.write_u16::<BigEndian>(self.momsn));
        try!(w.write_u16::<BigEndian>(self.mtmsn));
        try!(w.write_u32::<BigEndian>(self.time_of_session.timestamp() as u32));
        try!(w.write_u8(InformationElementType::MobileOriginatedPayload as u8));
        // TODO can we check to make sure the payload is of appropriate size?
        try!(w.write_u16::<BigEndian>(self.payload.len() as u16));
        try!(w.write_all(&self.payload[..]));
        Ok(())
    }

    /// Returns this message's protocol revision number.
    pub fn protocol_revision_number(&self) -> u8 { self.protocol_revision_number.0 }
    /// Returns this message's IMEI number as a string.
    pub fn imei(&self) -> &str { self.imei.as_str() }
    /// Returns this message's cdr reference number (also called auto id).
    pub fn cdr_reference(&self) -> u32 { self.cdr_reference }
    /// Returns this message's session status.
    pub fn session_status(&self) -> SessionStatus { self.session_status }
    /// Returns this message's mobile originated message number.
    pub fn momsn(&self) -> u16 { self.momsn }
    /// Returns this message's mobile terminated message number.
    pub fn mtmsn(&self) -> u16 { self.mtmsn }
    /// Returns this message's time of session.
    pub fn time_of_session(&self) -> DateTime<UTC> { self.time_of_session }
    /// Returns a reference to this message's payload.
    pub fn payload_ref(&self) -> &Vec<u8> { &self.payload }

    /// Returns the overall message length, as is contained in the message's header.
    ///
    /// The whole message is actually three bytes longer than this value, thanks to the message
    /// header itself.
    fn overall_message_length(&self) -> u16 {
        INFORMATION_ELEMENT_HEADER_LENGTH + MOBILE_ORIGINATED_HEADER_LENGTH +
            INFORMATION_ELEMENT_HEADER_LENGTH + self.payload.len() as u16
    }
}

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

    use std::fs::File;
    use std::io::{Cursor, Read};
    use std::str;

    use chrono::{TimeZone, UTC};

    #[test]
    fn from_path() {
        Message::from_path("data/0-mo.sbd").unwrap();
    }

    #[test]
    fn from_read() {
        let file = File::open("data/0-mo.sbd").unwrap();
        Message::read_from(file).unwrap();
    }

    #[test]
    fn from_path_that_doesnt_exist() {
        assert!(Message::from_path("notafile.sbd").is_err());
    }

    #[test]
    fn from_path_that_is_not_an_sbd_message() {
        assert!(Message::from_path("data/1-invalid.sbd").is_err());
    }

    #[test]
    fn undersized() {
        let file = File::open("data/0-mo.sbd").unwrap();
        let readable = file.take(58);
        assert!(Message::read_from(readable).is_err());
    }

    #[test]
    fn oversized() {
        let file = File::open("data/0-mo.sbd").unwrap();
        let readable = file.chain(Cursor::new(vec![0]));
        assert!(Message::read_from(readable).is_err());
    }

    #[test]
    fn values() {
        let message = Message::from_path("data/0-mo.sbd").unwrap();
        assert_eq!(1, message.protocol_revision_number());
        assert_eq!(1894516585, message.cdr_reference());
        assert_eq!("300234063904190", message.imei());
        assert_eq!(SessionStatus::Ok, message.session_status());
        assert_eq!(75, message.momsn());
        assert_eq!(0, message.mtmsn());
        assert_eq!(UTC.ymd(2015, 7, 9).and_hms(18, 15, 8), message.time_of_session());
        assert_eq!("test message from pete", str::from_utf8(message.payload_ref()).unwrap());
    }

    #[test]
    fn write() {
        let message = Message::from_path("data/0-mo.sbd").unwrap();
        let mut cursor = Cursor::new(Vec::new());
        message.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        let message2 = Message::read_from(cursor).unwrap();
        assert_eq!(message, message2);
    }

    #[test]
    fn write_default() {
        let message: Message = Default::default();
        let mut cursor = Cursor::new(Vec::new());
        message.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        let message2 = Message::read_from(cursor).unwrap();
        assert_eq!(message, message2);
    }

    #[test]
    fn order() {
        let default: Message = Default::default();
        let message = Message::from_path("data/0-mo.sbd").unwrap();
        assert!(message > default);
    }
}
<|endoftext|>


<commit_before>use error::P25Error;
use nid::NetworkID;
use receiver::DataUnitReceiver;
use status::StreamSymbol;
use trunking::tsbk::{TSBKFields, TSBKReceiver};
use voice::control::LinkControlFields;
use voice::crypto::CryptoControlFields;
use voice::frame::VoiceFrame;
use voice::header::VoiceHeaderFields;

use voice::{
    FrameGroupEvent,
    VoiceCCFrameGroupReceiver,
    VoiceHeaderReceiver,
    VoiceLCFrameGroupReceiver,
    VoiceLCTerminatorReceiver,
};

pub trait MessageHandler {
    fn handle_error(&mut self, err: P25Error);
    fn handle_nid(&mut self, nid: NetworkID);
    fn handle_header(&mut self, header: VoiceHeaderFields);
    fn handle_frame(&mut self, frame: VoiceFrame);
    fn handle_lc(&mut self, lc: LinkControlFields);
    fn handle_cc(&mut self, cc: CryptoControlFields);
    fn handle_data_frag(&mut self, data: u32);
    fn handle_tsbk(&mut self, tsbk: TSBKFields);
    fn handle_term(&mut self);
}

enum State {
    Idle,
    DecodeHeader(VoiceHeaderReceiver),
    DecodeLCFrameGroup(VoiceLCFrameGroupReceiver),
    DecodeCCFrameGroup(VoiceCCFrameGroupReceiver),
    DecodeLCTerminator(VoiceLCTerminatorReceiver),
    DecodeTSBK(TSBKReceiver),
}

pub struct MessageReceiver {
    recv: DataUnitReceiver,
    state: State,
}

impl MessageReceiver {
    pub fn new() -> MessageReceiver {
        MessageReceiver {
            recv: DataUnitReceiver::new(),
            state: State::Idle,
        }
    }

    pub fn feed<H: MessageHandler>(&mut self, s: f32, handler: &mut H) {
        use self::State::*;
        use nid::DataUnit::*;
        use receiver::ReceiverEvent;

        let event = match self.recv.feed(s) {
            Some(Ok(event)) => event,
            Some(Err(err)) => {
                handler.handle_error(err);
                self.recv.resync();

                return;
            },
            None => return,
        };

        let dibit = match event {
            ReceiverEvent::NetworkID(nid) => {
                handler.handle_nid(nid);

                self.state = match nid.data_unit() {
                    VoiceHeader =>
                        DecodeHeader(VoiceHeaderReceiver::new()),
                    VoiceSimpleTerminator => {
                        handler.handle_term();
                        self.recv.flush_pads();
                        Idle
                    },
                    VoiceLCTerminator =>
                        DecodeLCTerminator(VoiceLCTerminatorReceiver::new()),
                    VoiceLCFrameGroup =>
                        DecodeLCFrameGroup(VoiceLCFrameGroupReceiver::new()),
                    VoiceCCFrameGroup =>
                        DecodeCCFrameGroup(VoiceCCFrameGroupReceiver::new()),
                    TrunkingSignaling =>
                        DecodeTSBK(TSBKReceiver::new()),
                    DataPacket => {
                        self.recv.resync();
                        Idle
                    },
                };

                return;
            },
            ReceiverEvent::Symbol(StreamSymbol::Status(_)) => return,
            ReceiverEvent::Symbol(StreamSymbol::Data(dibit)) => dibit,
        };

        match self.state {
            DecodeHeader(ref mut head) => match head.feed(dibit) {
                Some(Ok(h)) => {
                    handler.handle_header(h);
                    self.recv.flush_pads();
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            DecodeLCFrameGroup(ref mut fg) => match fg.feed(dibit) {
                Some(Ok(event)) => match event {
                    FrameGroupEvent::VoiceFrame(vf) => {
                        handler.handle_frame(vf);

                        if fg.done() {
                            self.recv.flush_pads();
                        }
                    },
                    FrameGroupEvent::Extra(lc) => handler.handle_lc(lc),
                    FrameGroupEvent::DataFragment(data) => handler.handle_data_frag(data),
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            DecodeCCFrameGroup(ref mut fg) => match fg.feed(dibit) {
                Some(Ok(event)) => match event {
                    FrameGroupEvent::VoiceFrame(vf) => {
                        handler.handle_frame(vf);

                        if fg.done() {
                            self.recv.flush_pads();
                        }
                    },
                    FrameGroupEvent::Extra(cc) => handler.handle_cc(cc),
                    FrameGroupEvent::DataFragment(data) => handler.handle_data_frag(data),
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            DecodeLCTerminator(ref mut term) => match term.feed(dibit) {
                Some(Ok(lc)) => {
                    handler.handle_lc(lc);
                    handler.handle_term();
                    self.recv.flush_pads();
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            DecodeTSBK(ref mut dec) => match dec.feed(dibit) {
                Some(Ok(tsbk)) => {
                    handler.handle_tsbk(tsbk);

                    if tsbk.is_tail() {
                        self.recv.flush_pads();
                    }
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            Idle => {},
        }
    }
}
<commit_msg>Allow TSBK handler to cause resync<commit_after>use error::P25Error;
use nid::NetworkID;
use receiver::DataUnitReceiver;
use status::StreamSymbol;
use trunking::tsbk::{TSBKFields, TSBKReceiver};
use voice::control::LinkControlFields;
use voice::crypto::CryptoControlFields;
use voice::frame::VoiceFrame;
use voice::header::VoiceHeaderFields;

use voice::{
    FrameGroupEvent,
    VoiceCCFrameGroupReceiver,
    VoiceHeaderReceiver,
    VoiceLCFrameGroupReceiver,
    VoiceLCTerminatorReceiver,
};

pub trait MessageHandler {
    fn handle_error(&mut self, err: P25Error);
    fn handle_nid(&mut self, nid: NetworkID);
    fn handle_header(&mut self, header: VoiceHeaderFields);
    fn handle_frame(&mut self, frame: VoiceFrame);
    fn handle_lc(&mut self, lc: LinkControlFields);
    fn handle_cc(&mut self, cc: CryptoControlFields);
    fn handle_data_frag(&mut self, data: u32);
    fn handle_tsbk(&mut self, tsbk: TSBKFields) -> bool;
    fn handle_term(&mut self);
}

enum State {
    Idle,
    DecodeHeader(VoiceHeaderReceiver),
    DecodeLCFrameGroup(VoiceLCFrameGroupReceiver),
    DecodeCCFrameGroup(VoiceCCFrameGroupReceiver),
    DecodeLCTerminator(VoiceLCTerminatorReceiver),
    DecodeTSBK(TSBKReceiver),
}

pub struct MessageReceiver {
    recv: DataUnitReceiver,
    state: State,
}

impl MessageReceiver {
    pub fn new() -> MessageReceiver {
        MessageReceiver {
            recv: DataUnitReceiver::new(),
            state: State::Idle,
        }
    }

    pub fn feed<H: MessageHandler>(&mut self, s: f32, handler: &mut H) {
        use self::State::*;
        use nid::DataUnit::*;
        use receiver::ReceiverEvent;

        let event = match self.recv.feed(s) {
            Some(Ok(event)) => event,
            Some(Err(err)) => {
                handler.handle_error(err);
                self.recv.resync();

                return;
            },
            None => return,
        };

        let dibit = match event {
            ReceiverEvent::NetworkID(nid) => {
                handler.handle_nid(nid);

                self.state = match nid.data_unit() {
                    VoiceHeader =>
                        DecodeHeader(VoiceHeaderReceiver::new()),
                    VoiceSimpleTerminator => {
                        handler.handle_term();
                        self.recv.flush_pads();
                        Idle
                    },
                    VoiceLCTerminator =>
                        DecodeLCTerminator(VoiceLCTerminatorReceiver::new()),
                    VoiceLCFrameGroup =>
                        DecodeLCFrameGroup(VoiceLCFrameGroupReceiver::new()),
                    VoiceCCFrameGroup =>
                        DecodeCCFrameGroup(VoiceCCFrameGroupReceiver::new()),
                    TrunkingSignaling =>
                        DecodeTSBK(TSBKReceiver::new()),
                    DataPacket => {
                        self.recv.resync();
                        Idle
                    },
                };

                return;
            },
            ReceiverEvent::Symbol(StreamSymbol::Status(_)) => return,
            ReceiverEvent::Symbol(StreamSymbol::Data(dibit)) => dibit,
        };

        match self.state {
            DecodeHeader(ref mut head) => match head.feed(dibit) {
                Some(Ok(h)) => {
                    handler.handle_header(h);
                    self.recv.flush_pads();
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            DecodeLCFrameGroup(ref mut fg) => match fg.feed(dibit) {
                Some(Ok(event)) => match event {
                    FrameGroupEvent::VoiceFrame(vf) => {
                        handler.handle_frame(vf);

                        if fg.done() {
                            self.recv.flush_pads();
                        }
                    },
                    FrameGroupEvent::Extra(lc) => handler.handle_lc(lc),
                    FrameGroupEvent::DataFragment(data) => handler.handle_data_frag(data),
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            DecodeCCFrameGroup(ref mut fg) => match fg.feed(dibit) {
                Some(Ok(event)) => match event {
                    FrameGroupEvent::VoiceFrame(vf) => {
                        handler.handle_frame(vf);

                        if fg.done() {
                            self.recv.flush_pads();
                        }
                    },
                    FrameGroupEvent::Extra(cc) => handler.handle_cc(cc),
                    FrameGroupEvent::DataFragment(data) => handler.handle_data_frag(data),
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            DecodeLCTerminator(ref mut term) => match term.feed(dibit) {
                Some(Ok(lc)) => {
                    handler.handle_lc(lc);
                    handler.handle_term();
                    self.recv.flush_pads();
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            DecodeTSBK(ref mut dec) => match dec.feed(dibit) {
                Some(Ok(tsbk)) => {
                    if handler.handle_tsbk(tsbk) {
                        self.recv.resync();
                    } else if tsbk.is_tail() {
                        self.recv.flush_pads();
                    }
                },
                Some(Err(err)) => {
                    handler.handle_error(err);
                    self.recv.resync();
                },
                None => {},
            },
            Idle => {},
        }
    }
}
<|endoftext|>


<commit_before>/**
 * Various traits intended to be used with the built in numeric types. These
 * allow one to be more specific with trait bounds when using generics.
 *
 * Note: These traits won't be able to be used to their full potential until
 * trait inheritence is implemented.
 */

use core::cmp::{Eq, Ord};

use std::cmp::FuzzyEq;

use num::cast::*;
use num::consts::*;
use num::default_eq::*;

pub trait TypeExt: TypeConsts, DefaultEq {}

pub trait NumExt: Copy, TypeExt, Eq, Num, NumCast, Ord {}

pub trait UnSignedExt: NumExt {}

pub impl u8:   UnSignedExt {}
pub impl u16:  UnSignedExt {}
pub impl u32:  UnSignedExt {}
pub impl u64:  UnSignedExt {}
pub impl uint: UnSignedExt {}

pub trait SignedExt: NumExt {}

pub impl i8:    SignedExt {}
pub impl i16:   SignedExt {}
pub impl i32:   SignedExt {}
pub impl i64:   SignedExt {}
pub impl int:   SignedExt {}

pub impl f32:   SignedExt {}
pub impl f64:   SignedExt {}
pub impl float: SignedExt {}

pub trait IntegerExt: NumExt, IntConsts {}

pub impl u8:   IntegerExt {}
pub impl u16:  IntegerExt {}
pub impl u32:  IntegerExt {}
pub impl u64:  IntegerExt {}
pub impl uint: IntegerExt {}

pub impl i8:   IntegerExt {}
pub impl i16:  IntegerExt {}
pub impl i32:  IntegerExt {}
pub impl i64:  IntegerExt {}
pub impl int:  IntegerExt {}

pub trait FloatExt: NumExt, FloatConsts, FuzzyEq {}

pub impl f32:   FloatExt {}
pub impl f64:   FloatExt {}
pub impl float: FloatExt {}<commit_msg>Create an extension trait for bool<commit_after>/**
 * Various traits intended to be used with the built in numeric types. These
 * allow one to be more specific with trait bounds when using generics.
 *
 * Note: These traits won't be able to be used to their full potential until
 * trait inheritence is implemented.
 */

use core::cmp::{Eq, Ord};

use std::cmp::FuzzyEq;

use num::cast::*;
use num::consts::*;
use num::default_eq::*;

pub trait TypeExt: TypeConsts, DefaultEq {}

pub trait BooleanExt: TypeExt {}

pub impl bool: BooleanExt {}

pub trait NumExt: Copy, TypeExt, Eq, Num, NumCast, Ord {}

pub trait UnSignedExt: NumExt {}

pub impl u8:   UnSignedExt {}
pub impl u16:  UnSignedExt {}
pub impl u32:  UnSignedExt {}
pub impl u64:  UnSignedExt {}
pub impl uint: UnSignedExt {}

pub trait SignedExt: NumExt {}

pub impl i8:    SignedExt {}
pub impl i16:   SignedExt {}
pub impl i32:   SignedExt {}
pub impl i64:   SignedExt {}
pub impl int:   SignedExt {}

pub impl f32:   SignedExt {}
pub impl f64:   SignedExt {}
pub impl float: SignedExt {}

pub trait IntegerExt: NumExt, IntConsts {}

pub impl u8:   IntegerExt {}
pub impl u16:  IntegerExt {}
pub impl u32:  IntegerExt {}
pub impl u64:  IntegerExt {}
pub impl uint: IntegerExt {}

pub impl i8:   IntegerExt {}
pub impl i16:  IntegerExt {}
pub impl i32:  IntegerExt {}
pub impl i64:  IntegerExt {}
pub impl int:  IntegerExt {}

pub trait FloatExt: NumExt, FloatConsts, FuzzyEq {}

pub impl f32:   FloatExt {}
pub impl f64:   FloatExt {}
pub impl float: FloatExt {}<|endoftext|>


<commit_before>//! # condition
//!
//! Evaluates conditions based on task configuration and current env.
//!

#[cfg(test)]
#[path = "./condition_test.rs"]
mod condition_test;

use crate::command;
use crate::environment;
use crate::profile;
use crate::types;
use crate::types::{FlowInfo, RustVersionCondition, Step, TaskCondition};
use crate::version::is_newer;
use envmnt;
use indexmap::IndexMap;
use rust_info;
use rust_info::types::{RustChannel, RustInfo};
use std::path::Path;

fn validate_env_map(env: Option<IndexMap<String, String>>, equal: bool) -> bool {
    match env {
        Some(env_vars) => {
            let mut all_valid = true;

            for (key, current_value) in env_vars.iter() {
                if equal && !envmnt::is_equal(key, current_value) {
                    all_valid = false;
                    break;
                } else if !equal && !envmnt::contains_ignore_case(key, current_value) {
                    all_valid = false;
                    break;
                }
            }

            all_valid
        }
        None => true,
    }
}

fn validate_env(condition: &TaskCondition) -> bool {
    validate_env_map(condition.env.clone(), true)
}

fn validate_env_contains(condition: &TaskCondition) -> bool {
    validate_env_map(condition.env_contains.clone(), false)
}

fn validate_env_set(condition: &TaskCondition) -> bool {
    let env = condition.env_set.clone();

    match env {
        Some(env_vars) => {
            let mut all_valid = true;

            for key in env_vars.iter() {
                if !envmnt::exists(key) {
                    all_valid = false;
                    break;
                }
            }

            all_valid
        }
        None => true,
    }
}

fn validate_env_not_set(condition: &TaskCondition) -> bool {
    let env = condition.env_not_set.clone();

    match env {
        Some(env_vars) => {
            let mut all_valid = true;

            for key in env_vars.iter() {
                if envmnt::exists(key) {
                    all_valid = false;
                    break;
                }
            }

            all_valid
        }
        None => true,
    }
}

fn validate_env_bool(condition: &TaskCondition, truthy: bool) -> bool {
    let env = if truthy {
        condition.env_true.clone()
    } else {
        condition.env_false.clone()
    };

    match env {
        Some(env_vars) => {
            let mut all_valid = true;

            for key in env_vars.iter() {
                let is_true = envmnt::is_or(key, !truthy);

                if is_true != truthy {
                    all_valid = false;
                    break;
                }
            }

            all_valid
        }
        None => true,
    }
}

fn validate_platform(condition: &TaskCondition) -> bool {
    let platforms = condition.platforms.clone();
    match platforms {
        Some(platform_names) => {
            let platform_name = types::get_platform_name();

            let index = platform_names
                .iter()
                .position(|value| *value == platform_name);

            match index {
                None => {
                    debug!(
                        "Failed platform condition, current platform: {}",
                        &platform_name
                    );
                    false
                }
                _ => true,
            }
        }
        None => true,
    }
}

fn validate_profile(condition: &TaskCondition) -> bool {
    let profiles = condition.profiles.clone();
    match profiles {
        Some(profile_names) => {
            let profile_name = profile::get();

            let index = profile_names
                .iter()
                .position(|value| *value == profile_name);

            match index {
                None => {
                    debug!(
                        "Failed profile condition, current profile: {}",
                        &profile_name
                    );
                    false
                }
                _ => true,
            }
        }
        None => true,
    }
}

fn validate_channel(condition: &TaskCondition, flow_info_option: Option<&FlowInfo>) -> bool {
    match flow_info_option {
        Some(flow_info) => {
            let channels = condition.channels.clone();
            match channels {
                Some(channel_names) => match flow_info.env_info.rust_info.channel {
                    Some(value) => {
                        let index = match value {
                            RustChannel::Stable => channel_names
                                .iter()
                                .position(|value| *value == "stable".to_string()),
                            RustChannel::Beta => channel_names
                                .iter()
                                .position(|value| *value == "beta".to_string()),
                            RustChannel::Nightly => channel_names
                                .iter()
                                .position(|value| *value == "nightly".to_string()),
                        };

                        match index {
                            None => {
                                debug!("Failed channel condition");
                                false
                            }
                            _ => true,
                        }
                    }
                    None => false,
                },
                None => true,
            }
        }
        None => true,
    }
}

fn validate_rust_version_condition(rustinfo: RustInfo, condition: RustVersionCondition) -> bool {
    if rustinfo.version.is_some() {
        let current_version = rustinfo.version.unwrap();

        let mut valid = match condition.min {
            Some(version) => {
                version == current_version || is_newer(&version, &current_version, true)
            }
            None => true,
        };

        if valid {
            valid = match condition.max {
                Some(version) => {
                    version == current_version || is_newer(&current_version, &version, true)
                }
                None => true,
            };
        }

        if valid {
            valid = match condition.equal {
                Some(version) => version == current_version,
                None => true,
            };
        }

        valid
    } else {
        true
    }
}

fn validate_rust_version(condition: &TaskCondition) -> bool {
    let rust_version = condition.rust_version.clone();
    match rust_version {
        Some(rust_version_condition) => {
            let rustinfo = rust_info::get();

            validate_rust_version_condition(rustinfo, rust_version_condition)
        }
        None => true,
    }
}

fn validate_files(file_paths: &Vec<String>, exist: bool) -> bool {
    for file_path in file_paths.iter() {
        let expanded_file_path = environment::expand_value(file_path);
        let path = Path::new(&expanded_file_path);

        if path.exists() != exist {
            return false;
        }
    }

    true
}

fn validate_files_exist(condition: &TaskCondition) -> bool {
    let files = condition.files_exist.clone();
    match files {
        Some(ref file_paths) => validate_files(file_paths, true),
        None => true,
    }
}

fn validate_files_not_exist(condition: &TaskCondition) -> bool {
    let files = condition.files_not_exist.clone();
    match files {
        Some(ref file_paths) => validate_files(file_paths, false),
        None => true,
    }
}

fn validate_criteria(flow_info: Option<&FlowInfo>, condition: &Option<TaskCondition>) -> bool {
    match condition {
        Some(ref condition_struct) => {
            debug!("Checking task condition structure.");

            validate_platform(&condition_struct)
                && validate_profile(&condition_struct)
                && validate_channel(&condition_struct, flow_info)
                && validate_env(&condition_struct)
                && validate_env_set(&condition_struct)
                && validate_env_not_set(&condition_struct)
                && validate_env_bool(&condition_struct, true)
                && validate_env_bool(&condition_struct, false)
                && validate_env_contains(&condition_struct)
                && validate_rust_version(&condition_struct)
                && validate_files_exist(&condition_struct)
                && validate_files_not_exist(&condition_struct)
        }
        None => true,
    }
}

fn validate_script(condition_script: &Option<Vec<String>>, script_runner: Option<String>) -> bool {
    match condition_script {
        Some(ref script) => {
            debug!("Checking task condition script.");

            let exit_code =
                command::run_script_get_exit_code(&script, script_runner, &vec![], false);

            if exit_code == 0 {
                true
            } else {
                false
            }
        }
        None => true,
    }
}

pub(crate) fn validate_conditions_without_context(condition: TaskCondition) -> bool {
    validate_criteria(None, &Some(condition))
}

pub(crate) fn validate_conditions(
    flow_info: &FlowInfo,
    condition: &Option<TaskCondition>,
    condition_script: &Option<Vec<String>>,
    script_runner: Option<String>,
) -> bool {
    validate_criteria(Some(&flow_info), &condition)
        && validate_script(&condition_script, script_runner)
}

pub(crate) fn validate_condition_for_step(flow_info: &FlowInfo, step: &Step) -> bool {
    validate_conditions(
        &flow_info,
        &step.config.condition,
        &step.config.condition_script,
        step.config.script_runner.clone(),
    )
}
<commit_msg>clippy fixes<commit_after>//! # condition
//!
//! Evaluates conditions based on task configuration and current env.
//!

#[cfg(test)]
#[path = "./condition_test.rs"]
mod condition_test;

use crate::command;
use crate::environment;
use crate::profile;
use crate::types;
use crate::types::{FlowInfo, RustVersionCondition, Step, TaskCondition};
use crate::version::is_newer;
use envmnt;
use indexmap::IndexMap;
use rust_info;
use rust_info::types::{RustChannel, RustInfo};
use std::path::Path;

fn validate_env_map(env: Option<IndexMap<String, String>>, equal: bool) -> bool {
    match env {
        Some(env_vars) => {
            let mut all_valid = true;

            for (key, current_value) in env_vars.iter() {
                if (equal && !envmnt::is_equal(key, current_value))
                    || (!equal && !envmnt::contains_ignore_case(key, current_value))
                {
                    all_valid = false;
                    break;
                }
            }

            all_valid
        }
        None => true,
    }
}

fn validate_env(condition: &TaskCondition) -> bool {
    validate_env_map(condition.env.clone(), true)
}

fn validate_env_contains(condition: &TaskCondition) -> bool {
    validate_env_map(condition.env_contains.clone(), false)
}

fn validate_env_set(condition: &TaskCondition) -> bool {
    let env = condition.env_set.clone();

    match env {
        Some(env_vars) => {
            let mut all_valid = true;

            for key in env_vars.iter() {
                if !envmnt::exists(key) {
                    all_valid = false;
                    break;
                }
            }

            all_valid
        }
        None => true,
    }
}

fn validate_env_not_set(condition: &TaskCondition) -> bool {
    let env = condition.env_not_set.clone();

    match env {
        Some(env_vars) => {
            let mut all_valid = true;

            for key in env_vars.iter() {
                if envmnt::exists(key) {
                    all_valid = false;
                    break;
                }
            }

            all_valid
        }
        None => true,
    }
}

fn validate_env_bool(condition: &TaskCondition, truthy: bool) -> bool {
    let env = if truthy {
        condition.env_true.clone()
    } else {
        condition.env_false.clone()
    };

    match env {
        Some(env_vars) => {
            let mut all_valid = true;

            for key in env_vars.iter() {
                let is_true = envmnt::is_or(key, !truthy);

                if is_true != truthy {
                    all_valid = false;
                    break;
                }
            }

            all_valid
        }
        None => true,
    }
}

fn validate_platform(condition: &TaskCondition) -> bool {
    let platforms = condition.platforms.clone();
    match platforms {
        Some(platform_names) => {
            let platform_name = types::get_platform_name();

            let index = platform_names
                .iter()
                .position(|value| *value == platform_name);

            match index {
                None => {
                    debug!(
                        "Failed platform condition, current platform: {}",
                        &platform_name
                    );
                    false
                }
                _ => true,
            }
        }
        None => true,
    }
}

fn validate_profile(condition: &TaskCondition) -> bool {
    let profiles = condition.profiles.clone();
    match profiles {
        Some(profile_names) => {
            let profile_name = profile::get();

            let index = profile_names
                .iter()
                .position(|value| *value == profile_name);

            match index {
                None => {
                    debug!(
                        "Failed profile condition, current profile: {}",
                        &profile_name
                    );
                    false
                }
                _ => true,
            }
        }
        None => true,
    }
}

fn validate_channel(condition: &TaskCondition, flow_info_option: Option<&FlowInfo>) -> bool {
    match flow_info_option {
        Some(flow_info) => {
            let channels = condition.channels.clone();
            match channels {
                Some(channel_names) => match flow_info.env_info.rust_info.channel {
                    Some(value) => {
                        let index = match value {
                            RustChannel::Stable => channel_names
                                .iter()
                                .position(|value| *value == "stable".to_string()),
                            RustChannel::Beta => channel_names
                                .iter()
                                .position(|value| *value == "beta".to_string()),
                            RustChannel::Nightly => channel_names
                                .iter()
                                .position(|value| *value == "nightly".to_string()),
                        };

                        match index {
                            None => {
                                debug!("Failed channel condition");
                                false
                            }
                            _ => true,
                        }
                    }
                    None => false,
                },
                None => true,
            }
        }
        None => true,
    }
}

fn validate_rust_version_condition(rustinfo: RustInfo, condition: RustVersionCondition) -> bool {
    if rustinfo.version.is_some() {
        let current_version = rustinfo.version.unwrap();

        let mut valid = match condition.min {
            Some(version) => {
                version == current_version || is_newer(&version, &current_version, true)
            }
            None => true,
        };

        if valid {
            valid = match condition.max {
                Some(version) => {
                    version == current_version || is_newer(&current_version, &version, true)
                }
                None => true,
            };
        }

        if valid {
            valid = match condition.equal {
                Some(version) => version == current_version,
                None => true,
            };
        }

        valid
    } else {
        true
    }
}

fn validate_rust_version(condition: &TaskCondition) -> bool {
    let rust_version = condition.rust_version.clone();
    match rust_version {
        Some(rust_version_condition) => {
            let rustinfo = rust_info::get();

            validate_rust_version_condition(rustinfo, rust_version_condition)
        }
        None => true,
    }
}

fn validate_files(file_paths: &Vec<String>, exist: bool) -> bool {
    for file_path in file_paths.iter() {
        let expanded_file_path = environment::expand_value(file_path);
        let path = Path::new(&expanded_file_path);

        if path.exists() != exist {
            return false;
        }
    }

    true
}

fn validate_files_exist(condition: &TaskCondition) -> bool {
    let files = condition.files_exist.clone();
    match files {
        Some(ref file_paths) => validate_files(file_paths, true),
        None => true,
    }
}

fn validate_files_not_exist(condition: &TaskCondition) -> bool {
    let files = condition.files_not_exist.clone();
    match files {
        Some(ref file_paths) => validate_files(file_paths, false),
        None => true,
    }
}

fn validate_criteria(flow_info: Option<&FlowInfo>, condition: &Option<TaskCondition>) -> bool {
    match condition {
        Some(ref condition_struct) => {
            debug!("Checking task condition structure.");

            validate_platform(&condition_struct)
                && validate_profile(&condition_struct)
                && validate_channel(&condition_struct, flow_info)
                && validate_env(&condition_struct)
                && validate_env_set(&condition_struct)
                && validate_env_not_set(&condition_struct)
                && validate_env_bool(&condition_struct, true)
                && validate_env_bool(&condition_struct, false)
                && validate_env_contains(&condition_struct)
                && validate_rust_version(&condition_struct)
                && validate_files_exist(&condition_struct)
                && validate_files_not_exist(&condition_struct)
        }
        None => true,
    }
}

fn validate_script(condition_script: &Option<Vec<String>>, script_runner: Option<String>) -> bool {
    match condition_script {
        Some(ref script) => {
            debug!("Checking task condition script.");

            let exit_code =
                command::run_script_get_exit_code(&script, script_runner, &vec![], false);

            if exit_code == 0 {
                true
            } else {
                false
            }
        }
        None => true,
    }
}

pub(crate) fn validate_conditions_without_context(condition: TaskCondition) -> bool {
    validate_criteria(None, &Some(condition))
}

pub(crate) fn validate_conditions(
    flow_info: &FlowInfo,
    condition: &Option<TaskCondition>,
    condition_script: &Option<Vec<String>>,
    script_runner: Option<String>,
) -> bool {
    validate_criteria(Some(&flow_info), &condition)
        && validate_script(&condition_script, script_runner)
}

pub(crate) fn validate_condition_for_step(flow_info: &FlowInfo, step: &Step) -> bool {
    validate_conditions(
        &flow_info,
        &step.config.condition,
        &step.config.condition_script,
        step.config.script_runner.clone(),
    )
}
<|endoftext|>